001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.generator; 020 021import com.google.inject.Inject; 022import com.google.inject.assistedinject.Assisted; 023import com.intellectualsites.annotations.DoNotUse; 024import com.plotsquared.core.PlotSquared; 025import com.plotsquared.core.configuration.ConfigurationSection; 026import com.plotsquared.core.configuration.Settings; 027import com.plotsquared.core.configuration.file.YamlConfiguration; 028import com.plotsquared.core.inject.annotations.WorldConfig; 029import com.plotsquared.core.inject.factory.ProgressSubscriberFactory; 030import com.plotsquared.core.location.Location; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.PlotArea; 033import com.plotsquared.core.plot.PlotId; 034import com.plotsquared.core.plot.PlotManager; 035import com.plotsquared.core.plot.schematic.Schematic; 036import com.plotsquared.core.queue.GlobalBlockQueue; 037import com.plotsquared.core.util.FileUtils; 038import com.plotsquared.core.util.MathMan; 039import com.plotsquared.core.util.SchematicHandler; 040import com.sk89q.jnbt.CompoundTag; 041import com.sk89q.jnbt.CompoundTagBuilder; 042import com.sk89q.worldedit.entity.Entity; 043import com.sk89q.worldedit.extent.clipboard.Clipboard; 044import com.sk89q.worldedit.extent.transform.BlockTransformExtent; 045import com.sk89q.worldedit.internal.helper.MCDirections; 046import com.sk89q.worldedit.math.BlockVector2; 047import com.sk89q.worldedit.math.BlockVector3; 048import com.sk89q.worldedit.math.Vector3; 049import com.sk89q.worldedit.math.transform.AffineTransform; 050import com.sk89q.worldedit.util.Direction; 051import com.sk89q.worldedit.world.biome.BiomeType; 052import com.sk89q.worldedit.world.block.BaseBlock; 053import org.apache.logging.log4j.LogManager; 054import org.apache.logging.log4j.Logger; 055import org.checkerframework.checker.nullness.qual.NonNull; 056import org.checkerframework.checker.nullness.qual.Nullable; 057 058import java.io.File; 059import java.lang.reflect.Field; 060import java.util.ArrayList; 061import java.util.HashMap; 062import java.util.List; 063import java.util.Locale; 064 065public class HybridPlotWorld extends ClassicPlotWorld { 066 067 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + HybridPlotWorld.class.getSimpleName()); 068 private static final AffineTransform transform = new AffineTransform().rotateY(90); 069 public boolean ROAD_SCHEMATIC_ENABLED; 070 public boolean PLOT_SCHEMATIC = false; 071 @Deprecated(forRemoval = true, since = "6.9.0") 072 public int PLOT_SCHEMATIC_HEIGHT = -1; 073 public short PATH_WIDTH_LOWER; 074 public short PATH_WIDTH_UPPER; 075 public HashMap<Integer, BaseBlock[]> G_SCH; 076 public HashMap<Integer, BiomeType> G_SCH_B; 077 /** 078 * The Y level at which schematic generation will start, lowest of either road or plot schematic generation. 079 */ 080 public int SCHEM_Y; 081 private Location SIGN_LOCATION; 082 private File root = null; 083 private int lastOverlayHeightError = Integer.MIN_VALUE; 084 private List<Entity> schem3Entities = null; 085 private BlockVector3 schem3MinPoint = null; 086 private boolean schem1PopulationNeeded = false; 087 private boolean schem2PopulationNeeded = false; 088 private boolean schem3PopulationNeeded = false; 089 090 @Inject 091 private SchematicHandler schematicHandler; 092 093 @Inject 094 public HybridPlotWorld( 095 @Assisted("world") final String worldName, 096 @javax.annotation.Nullable @Assisted("id") final String id, 097 @Assisted final @NonNull IndependentPlotGenerator generator, 098 @javax.annotation.Nullable @Assisted("min") final PlotId min, 099 @javax.annotation.Nullable @Assisted("max") final PlotId max, 100 @WorldConfig final @NonNull YamlConfiguration worldConfiguration, 101 final @NonNull GlobalBlockQueue blockQueue 102 ) { 103 super(worldName, id, generator, min, max, worldConfiguration, blockQueue); 104 PlotSquared.platform().injector().injectMembers(this); 105 } 106 107 @Deprecated(forRemoval = true, since = "6.9.0") 108 public static byte wrap(byte data, int start) { 109 if ((data >= start) && (data < (start + 4))) { 110 data = (byte) ((((data - start) + 2) & 3) + start); 111 } 112 return data; 113 } 114 115 @Deprecated(forRemoval = true, since = "6.9.0") 116 public static byte wrap2(byte data, int start) { 117 if ((data >= start) && (data < (start + 2))) { 118 data = (byte) ((((data - start) + 1) & 1) + start); 119 } 120 return data; 121 } 122 123 public static BaseBlock rotate(BaseBlock id) { 124 125 CompoundTag tag = id.getNbtData(); 126 127 if (tag != null) { 128 // Handle blocks which store their rotation in NBT 129 if (tag.containsKey("Rot")) { 130 int rot = tag.asInt("Rot"); 131 132 Direction direction = MCDirections.fromRotation(rot); 133 134 if (direction != null) { 135 Vector3 vector = transform.apply(direction.toVector()).subtract(transform.apply(Vector3.ZERO)).normalize(); 136 Direction newDirection = 137 Direction.findClosest( 138 vector, 139 Direction.Flag.CARDINAL | Direction.Flag.ORDINAL | Direction.Flag.SECONDARY_ORDINAL 140 ); 141 142 if (newDirection != null) { 143 CompoundTagBuilder builder = tag.createBuilder(); 144 145 builder.putByte("Rot", (byte) MCDirections.toRotation(newDirection)); 146 147 id.setNbtData(builder.build()); 148 } 149 } 150 } 151 } 152 return BlockTransformExtent.transform(id, transform); 153 } 154 155 @NonNull 156 @Override 157 protected PlotManager createManager() { 158 return new HybridPlotManager(this, PlotSquared.platform().regionManager(), 159 PlotSquared.platform().injector().getInstance(ProgressSubscriberFactory.class) 160 ); 161 } 162 163 public Location getSignLocation(@NonNull Plot plot) { 164 plot = plot.getBasePlot(false); 165 final Location bot = plot.getBottomAbs(); 166 if (SIGN_LOCATION == null) { 167 return bot.withY(ROAD_HEIGHT + 1).add(-1, 0, -2); 168 } else { 169 return bot.withY(0).add(SIGN_LOCATION.getX(), SIGN_LOCATION.getY(), SIGN_LOCATION.getZ()); 170 } 171 } 172 173 /** 174 * <p>This method is called when a world loads. Make sure you set all your constants here. You are provided with the 175 * configuration section for that specific world.</p> 176 */ 177 @Override 178 public void loadConfiguration(ConfigurationSection config) { 179 super.loadConfiguration(config); 180 if ((this.ROAD_WIDTH & 1) == 0) { 181 this.PATH_WIDTH_LOWER = (short) (Math.floor(this.ROAD_WIDTH / 2f) - 1); 182 } else { 183 this.PATH_WIDTH_LOWER = (short) Math.floor(this.ROAD_WIDTH / 2f); 184 } 185 if (this.ROAD_WIDTH == 0) { 186 this.PATH_WIDTH_UPPER = (short) (this.SIZE + 1); 187 } else { 188 this.PATH_WIDTH_UPPER = (short) (this.PATH_WIDTH_LOWER + this.PLOT_WIDTH + 1); 189 } 190 try { 191 setupSchematics(); 192 } catch (Exception event) { 193 event.printStackTrace(); 194 } 195 196 // Dump world settings 197 if (Settings.DEBUG) { 198 LOGGER.info("- Dumping settings for ClassicPlotWorld with name {}", this.getWorldName()); 199 final Field[] fields = this.getClass().getFields(); 200 for (final Field field : fields) { 201 final String name = field.getName().toLowerCase(Locale.ENGLISH); 202 if (name.contains("g_sch")) { 203 continue; 204 } 205 Object value; 206 try { 207 final boolean accessible = field.isAccessible(); 208 field.setAccessible(true); 209 value = field.get(this); 210 field.setAccessible(accessible); 211 } catch (final IllegalAccessException e) { 212 value = String.format("Failed to parse: %s", e.getMessage()); 213 } 214 LOGGER.info("-- {} = {}", name, value); 215 } 216 } 217 } 218 219 @Override 220 public boolean isCompatible(final @NonNull PlotArea plotArea) { 221 if (!(plotArea instanceof SquarePlotWorld)) { 222 return false; 223 } 224 return ((SquarePlotWorld) plotArea).PLOT_WIDTH == this.PLOT_WIDTH; 225 } 226 227 public void setupSchematics() throws SchematicHandler.UnsupportedFormatException { 228 this.G_SCH = new HashMap<>(); 229 this.G_SCH_B = new HashMap<>(); 230 231 // Try to determine root. This means that plot areas can have separate schematic 232 // directories 233 if (!(root = 234 FileUtils.getFile( 235 PlotSquared.platform().getDirectory(), 236 "schematics/GEN_ROAD_SCHEMATIC/" + this.getWorldName() + "/" + this.getId() 237 )) 238 .exists()) { 239 root = FileUtils.getFile( 240 PlotSquared.platform().getDirectory(), 241 "schematics/GEN_ROAD_SCHEMATIC/" + this.getWorldName() 242 ); 243 } 244 245 File schematic1File = new File(root, "sideroad.schem"); 246 if (!schematic1File.exists()) { 247 schematic1File = new File(root, "sideroad.schematic"); 248 } 249 File schematic2File = new File(root, "intersection.schem"); 250 if (!schematic2File.exists()) { 251 schematic2File = new File(root, "intersection.schematic"); 252 } 253 File schematic3File = new File(root, "plot.schem"); 254 if (!schematic3File.exists()) { 255 schematic3File = new File(root, "plot.schematic"); 256 } 257 Schematic schematic1 = this.schematicHandler.getSchematic(schematic1File); 258 Schematic schematic2 = this.schematicHandler.getSchematic(schematic2File); 259 Schematic schematic3 = this.schematicHandler.getSchematic(schematic3File); 260 261 // If the plot schematic contains entities, then they need to be populated upon generation. 262 if (schematic3 != null && !schematic3.getClipboard().getEntities().isEmpty()) { 263 this.schem3Entities = new ArrayList<>(schematic3.getClipboard().getEntities()); 264 this.schem3MinPoint = schematic3.getClipboard().getMinimumPoint(); 265 this.schem3PopulationNeeded = true; 266 } 267 268 int shift = this.ROAD_WIDTH / 2; 269 int oddshift = (this.ROAD_WIDTH & 1); 270 271 SCHEM_Y = schematicStartHeight(); 272 273 // plotY and roadY are important to allow plot and/or road schematic "overflow" into each other without causing AIOOB 274 // exceptions when attempting either to set blocks to, or get block from G_SCH 275 // Default plot schematic start height, normalized to the minimum height schematics are pasted from. 276 int plotY = PLOT_HEIGHT - SCHEM_Y; 277 int minRoadWall = Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT ? Math.min(ROAD_HEIGHT, WALL_HEIGHT) : ROAD_HEIGHT; 278 // Default road schematic start height, normalized to the minimum height schematics are pasted from. 279 int roadY = minRoadWall - SCHEM_Y; 280 281 int worldGenHeight = getMaxGenHeight() - getMinGenHeight() + 1; 282 283 int maxSchematicHeight = 0; 284 int plotSchemHeight = 0; 285 286 // SCHEM_Y should be normalised to the plot "start" height 287 if (schematic3 != null) { 288 plotSchemHeight = maxSchematicHeight = schematic3.getClipboard().getDimensions().getY(); 289 if (maxSchematicHeight == worldGenHeight) { 290 SCHEM_Y = getMinGenHeight(); 291 plotY = 0; 292 } else if (!Settings.Schematics.PASTE_ON_TOP) { 293 SCHEM_Y = getMinBuildHeight(); 294 plotY = 0; 295 } 296 } 297 298 int roadSchemHeight; 299 300 if (schematic1 != null) { 301 roadSchemHeight = Math.max( 302 schematic1.getClipboard().getDimensions().getY(), 303 schematic2.getClipboard().getDimensions().getY() 304 ); 305 maxSchematicHeight = Math.max(roadSchemHeight, maxSchematicHeight); 306 if (maxSchematicHeight == worldGenHeight) { 307 SCHEM_Y = getMinGenHeight(); 308 roadY = 0; // Road is the lowest schematic 309 if (schematic3 != null && schematic3.getClipboard().getDimensions().getY() != worldGenHeight) { 310 // Road is the lowest schematic. Normalize plotY to it. 311 if (Settings.Schematics.PASTE_ON_TOP) { 312 plotY = PLOT_HEIGHT - getMinGenHeight(); 313 } else { 314 plotY = getMinBuildHeight() - getMinGenHeight(); 315 } 316 } 317 } else if (!Settings.Schematics.PASTE_ROAD_ON_TOP) { 318 if (SCHEM_Y == getMinGenHeight()) { // Only possible if plot schematic is enabled 319 // Plot is still the lowest schematic, normalize roadY to it 320 roadY = getMinBuildHeight() - getMinGenHeight(); 321 } else if (schematic3 != null) { 322 SCHEM_Y = getMinBuildHeight(); 323 roadY = 0;// Road is the lowest schematic 324 if (Settings.Schematics.PASTE_ON_TOP) { 325 // Road is the lowest schematic. Normalize plotY to it. 326 plotY = PLOT_HEIGHT - getMinBuildHeight(); 327 } 328 maxSchematicHeight = Math.max(maxSchematicHeight, plotY + plotSchemHeight); 329 } 330 } else { 331 roadY = minRoadWall - SCHEM_Y; 332 maxSchematicHeight = Math.max(maxSchematicHeight, roadY + roadSchemHeight); 333 } 334 } 335 336 if (schematic3 != null) { 337 this.PLOT_SCHEMATIC = true; 338 Clipboard blockArrayClipboard3 = schematic3.getClipboard(); 339 340 BlockVector3 d3 = blockArrayClipboard3.getDimensions(); 341 short w3 = (short) d3.getX(); 342 short l3 = (short) d3.getZ(); 343 short h3 = (short) d3.getY(); 344 if (w3 > PLOT_WIDTH || h3 > PLOT_WIDTH) { 345 this.ROAD_SCHEMATIC_ENABLED = true; 346 } 347 int centerShiftZ; 348 if (l3 < this.PLOT_WIDTH) { 349 centerShiftZ = (this.PLOT_WIDTH - l3) / 2; 350 } else { 351 centerShiftZ = (PLOT_WIDTH - l3) / 2; 352 } 353 int centerShiftX; 354 if (w3 < this.PLOT_WIDTH) { 355 centerShiftX = (this.PLOT_WIDTH - w3) / 2; 356 } else { 357 centerShiftX = (PLOT_WIDTH - w3) / 2; 358 } 359 360 BlockVector3 min = blockArrayClipboard3.getMinimumPoint(); 361 for (short x = 0; x < w3; x++) { 362 for (short z = 0; z < l3; z++) { 363 for (short y = 0; y < h3; y++) { 364 BaseBlock id = blockArrayClipboard3.getFullBlock(BlockVector3.at( 365 x + min.getBlockX(), 366 y + min.getBlockY(), 367 z + min.getBlockZ() 368 )); 369 schem3PopulationNeeded |= id.hasNbtData(); 370 addOverlayBlock( 371 (short) (x + shift + oddshift + centerShiftX), 372 (short) (y + plotY), 373 (short) (z + shift + oddshift + centerShiftZ), 374 id, 375 false, 376 maxSchematicHeight 377 ); 378 } 379 if (blockArrayClipboard3.hasBiomes()) { 380 BiomeType biome = blockArrayClipboard3.getBiome(BlockVector2.at( 381 x + min.getBlockX(), 382 z + min.getBlockZ() 383 )); 384 addOverlayBiome( 385 (short) (x + shift + oddshift + centerShiftX), 386 (short) (z + shift + oddshift + centerShiftZ), 387 biome 388 ); 389 } 390 } 391 } 392 393 if (Settings.DEBUG) { 394 LOGGER.info("- plot schematic: {}", schematic3File.getPath()); 395 } 396 } 397 if ((schematic1 == null && schematic2 == null) || this.ROAD_WIDTH == 0) { 398 if (Settings.DEBUG) { 399 LOGGER.info("- schematic: false"); 400 } 401 return; 402 } 403 this.ROAD_SCHEMATIC_ENABLED = true; 404 // Do not populate road if using schematic population 405 // TODO: What? this.ROAD_BLOCK = BlockBucket.empty(); // BlockState.getEmptyData(this.ROAD_BLOCK); // BlockUtil.get(this.ROAD_BLOCK.id, (byte) 0); 406 407 Clipboard blockArrayClipboard1 = schematic1.getClipboard(); 408 409 BlockVector3 d1 = blockArrayClipboard1.getDimensions(); 410 short w1 = (short) d1.getX(); 411 short l1 = (short) d1.getZ(); 412 short h1 = (short) d1.getY(); 413 // Workaround for schematic height issue if proper calculation of road schematic height is disabled 414 if (!Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT) { 415 h1 += Math.max(ROAD_HEIGHT - WALL_HEIGHT, 0); 416 } 417 418 BlockVector3 min = blockArrayClipboard1.getMinimumPoint(); 419 for (short x = 0; x < w1; x++) { 420 for (short z = 0; z < l1; z++) { 421 for (short y = 0; y < h1; y++) { 422 BaseBlock id = blockArrayClipboard1.getFullBlock(BlockVector3.at( 423 x + min.getBlockX(), 424 y + min.getBlockY(), 425 z + min.getBlockZ() 426 )); 427 schem1PopulationNeeded |= id.hasNbtData(); 428 addOverlayBlock( 429 (short) (x - shift), 430 (short) (y + roadY), 431 (short) (z + shift + oddshift), 432 id, 433 false, 434 maxSchematicHeight 435 ); 436 addOverlayBlock( 437 (short) (z + shift + oddshift), 438 (short) (y + roadY), 439 (short) (shift - x + (oddshift - 1)), 440 id, 441 true, 442 maxSchematicHeight 443 ); 444 } 445 if (blockArrayClipboard1.hasBiomes()) { 446 BiomeType biome = blockArrayClipboard1.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); 447 addOverlayBiome((short) (x - shift), (short) (z + shift + oddshift), biome); 448 addOverlayBiome((short) (z + shift + oddshift), (short) (shift - x + (oddshift - 1)), biome); 449 } 450 } 451 } 452 453 Clipboard blockArrayClipboard2 = schematic2.getClipboard(); 454 BlockVector3 d2 = blockArrayClipboard2.getDimensions(); 455 short w2 = (short) d2.getX(); 456 short l2 = (short) d2.getZ(); 457 short h2 = (short) d2.getY(); 458 // Workaround for schematic height issue if proper calculation of road schematic height is disabled 459 if (!Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT) { 460 h2 += Math.max(ROAD_HEIGHT - WALL_HEIGHT, 0); 461 } 462 min = blockArrayClipboard2.getMinimumPoint(); 463 for (short x = 0; x < w2; x++) { 464 for (short z = 0; z < l2; z++) { 465 for (short y = 0; y < h2; y++) { 466 BaseBlock id = blockArrayClipboard2.getFullBlock(BlockVector3.at( 467 x + min.getBlockX(), 468 y + min.getBlockY(), 469 z + min.getBlockZ() 470 )); 471 schem2PopulationNeeded |= id.hasNbtData(); 472 addOverlayBlock( 473 (short) (x - shift), 474 (short) (y + roadY), 475 (short) (z - shift), 476 id, 477 false, 478 maxSchematicHeight 479 ); 480 } 481 if (blockArrayClipboard2.hasBiomes()) { 482 BiomeType biome = blockArrayClipboard2.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); 483 addOverlayBiome((short) (x - shift), (short) (z - shift), biome); 484 } 485 } 486 } 487 } 488 489 /** 490 * @deprecated This method should not be available for public API usage and will be made private. 491 */ 492 @Deprecated(forRemoval = true, since = "6.10.2") 493 public void addOverlayBlock(short x, short y, short z, BaseBlock id, boolean rotate, int height) { 494 if (z < 0) { 495 z += this.SIZE; 496 } else if (z >= this.SIZE) { 497 z -= this.SIZE; 498 } 499 if (x < 0) { 500 x += this.SIZE; 501 } else if (x >= this.SIZE) { 502 x -= this.SIZE; 503 } 504 if (rotate) { 505 id = rotate(id); 506 } 507 int pair = MathMan.pair(x, z); 508 BaseBlock[] existing = this.G_SCH.computeIfAbsent(pair, k -> new BaseBlock[height]); 509 if (y >= height) { 510 if (y > lastOverlayHeightError) { 511 lastOverlayHeightError = y; 512 LOGGER.error( 513 "Error adding overlay block in world {}. `y > height`. y={}, height={}", 514 getWorldName(), 515 y, 516 height 517 ); 518 } 519 return; 520 } 521 existing[y] = id; 522 } 523 524 /** 525 * @deprecated This method should not be available for public API usage and will be made private. 526 */ 527 @Deprecated(forRemoval = true, since = "6.10.2") 528 public void addOverlayBiome(short x, short z, BiomeType id) { 529 if (z < 0) { 530 z += this.SIZE; 531 } else if (z >= this.SIZE) { 532 z -= this.SIZE; 533 } 534 if (x < 0) { 535 x += this.SIZE; 536 } else if (x >= this.SIZE) { 537 x -= this.SIZE; 538 } 539 int pair = MathMan.pair(x, z); 540 this.G_SCH_B.put(pair, id); 541 } 542 543 /** 544 * Get the entities contained within the plot schematic for generation. Intended for internal use only. 545 * 546 * @since 6.9.0 547 */ 548 @DoNotUse 549 public @Nullable List<Entity> getPlotSchematicEntities() { 550 return schem3Entities; 551 } 552 553 /** 554 * Get the minimum point of the plot schematic for generation. Intended for internal use only. 555 * 556 * @since 6.9.0 557 */ 558 @DoNotUse 559 public @Nullable BlockVector3 getPlotSchematicMinPoint() { 560 return schem3MinPoint; 561 } 562 563 /** 564 * Get if post-generation population of chunks with tiles/entities is needed for this world. Not for public API use. 565 * 566 * @since 6.9.0 567 */ 568 @DoNotUse 569 public boolean populationNeeded() { 570 return schem1PopulationNeeded || schem2PopulationNeeded || schem3PopulationNeeded; 571 } 572 573 /** 574 * @deprecated in favour of {@link HybridPlotWorld#getSchematicRoot()} 575 */ 576 @Deprecated(forRemoval = true, since = "6.9.0") 577 public File getRoot() { 578 return this.root; 579 } 580 581 /** 582 * Get the root folder for this world's generation schematics. May be null if schematics not initialised via 583 * {@link HybridPlotWorld#setupSchematics()} 584 * 585 * @since 6.9.0 586 */ 587 public @Nullable File getSchematicRoot() { 588 return this.root; 589 } 590 591}