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.plotsquared.core.PlotSquared; 023import com.plotsquared.core.configuration.Settings; 024import com.plotsquared.core.events.PlotFlagAddEvent; 025import com.plotsquared.core.events.Result; 026import com.plotsquared.core.listener.WEExtent; 027import com.plotsquared.core.location.Location; 028import com.plotsquared.core.plot.Plot; 029import com.plotsquared.core.plot.PlotArea; 030import com.plotsquared.core.plot.PlotAreaType; 031import com.plotsquared.core.plot.PlotId; 032import com.plotsquared.core.plot.PlotManager; 033import com.plotsquared.core.plot.expiration.PlotAnalysis; 034import com.plotsquared.core.plot.flag.GlobalFlagContainer; 035import com.plotsquared.core.plot.flag.PlotFlag; 036import com.plotsquared.core.plot.flag.implementations.AnalysisFlag; 037import com.plotsquared.core.plot.world.PlotAreaManager; 038import com.plotsquared.core.queue.BlockArrayCacheScopedQueueCoordinator; 039import com.plotsquared.core.queue.GlobalBlockQueue; 040import com.plotsquared.core.queue.QueueCoordinator; 041import com.plotsquared.core.util.ChunkManager; 042import com.plotsquared.core.util.EventDispatcher; 043import com.plotsquared.core.util.MathMan; 044import com.plotsquared.core.util.RegionManager; 045import com.plotsquared.core.util.RegionUtil; 046import com.plotsquared.core.util.SchematicHandler; 047import com.plotsquared.core.util.WorldUtil; 048import com.plotsquared.core.util.task.RunnableVal; 049import com.plotsquared.core.util.task.TaskManager; 050import com.plotsquared.core.util.task.TaskTime; 051import com.sk89q.worldedit.math.BlockVector2; 052import com.sk89q.worldedit.math.BlockVector3; 053import com.sk89q.worldedit.regions.CuboidRegion; 054import com.sk89q.worldedit.world.biome.BiomeType; 055import com.sk89q.worldedit.world.block.BaseBlock; 056import com.sk89q.worldedit.world.block.BlockState; 057import com.sk89q.worldedit.world.block.BlockType; 058import com.sk89q.worldedit.world.block.BlockTypes; 059import org.apache.logging.log4j.LogManager; 060import org.apache.logging.log4j.Logger; 061import org.checkerframework.checker.nullness.qual.NonNull; 062import org.checkerframework.checker.nullness.qual.Nullable; 063 064import java.io.File; 065import java.util.ArrayDeque; 066import java.util.ArrayList; 067import java.util.Collections; 068import java.util.HashSet; 069import java.util.Iterator; 070import java.util.LinkedHashSet; 071import java.util.List; 072import java.util.Objects; 073import java.util.Set; 074import java.util.concurrent.atomic.AtomicBoolean; 075import java.util.concurrent.atomic.AtomicInteger; 076 077public class HybridUtils { 078 079 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + HybridUtils.class.getSimpleName()); 080 private static final BlockState AIR = BlockTypes.AIR.getDefaultState(); 081 082 /** 083 * Deprecated and likely to be removed in a future release. 084 */ 085 @Deprecated(forRemoval = true, since = "7.0.0") 086 public static HybridUtils manager; 087 public static Set<BlockVector2> regions; 088 public static int height; 089 // Use ordered for reasonable chunk loading order to reduce paper unloading neighbour chunks and then us attempting to load 090 // them again, causing errors 091 public static Set<BlockVector2> chunks = new LinkedHashSet<>(); 092 public static PlotArea area; 093 public static boolean UPDATE = false; 094 095 private final PlotAreaManager plotAreaManager; 096 private final ChunkManager chunkManager; 097 private final GlobalBlockQueue blockQueue; 098 private final WorldUtil worldUtil; 099 private final SchematicHandler schematicHandler; 100 private final EventDispatcher eventDispatcher; 101 102 @Inject 103 public HybridUtils( 104 final @NonNull PlotAreaManager plotAreaManager, 105 final @NonNull ChunkManager chunkManager, 106 final @NonNull GlobalBlockQueue blockQueue, 107 final @NonNull WorldUtil worldUtil, 108 final @NonNull SchematicHandler schematicHandler, 109 final @NonNull EventDispatcher eventDispatcher 110 ) { 111 this.plotAreaManager = plotAreaManager; 112 this.chunkManager = chunkManager; 113 this.blockQueue = blockQueue; 114 this.worldUtil = worldUtil; 115 this.schematicHandler = schematicHandler; 116 this.eventDispatcher = eventDispatcher; 117 } 118 119 public void regeneratePlotWalls(final PlotArea area) { 120 PlotManager plotManager = area.getPlotManager(); 121 plotManager.regenerateAllPlotWalls(null); 122 } 123 124 public void analyzeRegion(final String world, final CuboidRegion region, final RunnableVal<PlotAnalysis> whenDone) { 125 // int diff, int variety, int vertices, int rotation, int height_sd 126 /* 127 * diff: compare to base by looping through all blocks 128 * variety: add to HashSet for each BlockState 129 * height_sd: loop over all blocks and get top block 130 * 131 * vertices: store air map and compare with neighbours 132 * for each block check the adjacent 133 * - Store all blocks then go through in second loop 134 * - recheck each block 135 * 136 */ 137 TaskManager.runTaskAsync(() -> { 138 final PlotArea area = this.plotAreaManager.getPlotArea(world, null); 139 if (!(area instanceof HybridPlotWorld hpw)) { 140 return; 141 } 142 143 final BlockVector3 bot = region.getMinimumPoint(); 144 final BlockVector3 top = region.getMaximumPoint(); 145 146 final int bx = bot.getX(); 147 final int bz = bot.getZ(); 148 final int tx = top.getX(); 149 final int tz = top.getZ(); 150 final int cbx = bx >> 4; 151 final int cbz = bz >> 4; 152 final int ctx = tx >> 4; 153 final int ctz = tz >> 4; 154 final int width = tx - bx + 1; 155 final int length = tz - bz + 1; 156 final int height = area.getMaxGenHeight() - area.getMinGenHeight() + 1; 157 final int minHeight = area.getMinGenHeight(); 158 159 final BlockState[][][] newBlocks = new BlockState[height][width][length]; 160 161 BlockArrayCacheScopedQueueCoordinator oldBlockQueue = new BlockArrayCacheScopedQueueCoordinator( 162 Location.at("", region.getMinimumPoint().withY(hpw.getMinGenHeight())), 163 Location.at("", region.getMaximumPoint().withY(hpw.getMaxGenHeight())) 164 ); 165 166 region.getChunks().forEach(chunkPos -> { 167 int relChunkX = chunkPos.getX() - cbx; 168 int relChunkZ = chunkPos.getZ() - cbz; 169 oldBlockQueue.setOffsetX(relChunkX << 4); 170 oldBlockQueue.setOffsetZ(relChunkZ << 4); 171 hpw.getGenerator().generateChunk(oldBlockQueue, hpw, false); 172 }); 173 174 final BlockState[][][] oldBlocks = oldBlockQueue.getBlockStates(); 175 176 QueueCoordinator queue = area.getQueue(); 177 queue.addReadChunks(region.getChunks()); 178 queue.setChunkConsumer(chunkPos -> { 179 int X = chunkPos.getX(); 180 int Z = chunkPos.getZ(); 181 int minX; 182 if (X == cbx) { 183 minX = bx & 15; 184 } else { 185 minX = 0; 186 } 187 int minZ; 188 if (Z == cbz) { 189 minZ = bz & 15; 190 } else { 191 minZ = 0; 192 } 193 int maxX; 194 if (X == ctx) { 195 maxX = tx & 15; 196 } else { 197 maxX = 15; 198 } 199 int maxZ; 200 if (Z == ctz) { 201 maxZ = tz & 15; 202 } else { 203 maxZ = 15; 204 } 205 206 int chunkBlockX = X << 4; 207 int chunkBlockZ = Z << 4; 208 209 int xb = chunkBlockX - bx; 210 int zb = chunkBlockZ - bz; 211 for (int x = minX; x <= maxX; x++) { 212 int xx = chunkBlockX + x; 213 for (int z = minZ; z <= maxZ; z++) { 214 int zz = chunkBlockZ + z; 215 for (int yIndex = 0; yIndex < height; yIndex++) { 216 int y = yIndex + minHeight; 217 BlockState block = queue.getBlock(xx, y, zz); 218 if (block == null) { 219 block = AIR; 220 } 221 int xr = xb + x; 222 int zr = zb + z; 223 newBlocks[yIndex][xr][zr] = block; 224 } 225 } 226 } 227 }); 228 229 final Runnable run = () -> { 230 int size = width * length; 231 int[] changes = new int[size]; 232 int[] faces = new int[size]; 233 int[] data = new int[size]; 234 int[] air = new int[size]; 235 int[] variety = new int[size]; 236 int i = 0; 237 for (int x = 0; x < width; x++) { 238 for (int z = 0; z < length; z++) { 239 Set<BlockType> types = new HashSet<>(); 240 for (int yIndex = 0; yIndex < height; yIndex++) { 241 BlockState old = oldBlocks[yIndex][x][z]; // Nullable 242 BlockState now = newBlocks[yIndex][x][z]; // Not null 243 if (now == null) { 244 throw new NullPointerException(String.format( 245 "\"now\" block null attempting to perform plot analysis. Indexes: x=%d of %d, yIndex=%d" + 246 " of %d, z=%d of %d", 247 x, 248 width, 249 yIndex, 250 height, 251 z, 252 length 253 )); 254 } 255 if (!now.equals(old) && !(old == null && now.getBlockType().equals(BlockTypes.AIR))) { 256 changes[i]++; 257 } 258 if (now.getBlockType().getMaterial().isAir()) { 259 air[i]++; 260 } else { 261 // check vertices 262 // modifications_adjacent 263 if (x > 0 && z > 0 && yIndex > 0 && x < width - 1 && z < length - 1 && yIndex < (height - 1)) { 264 if (newBlocks[yIndex - 1][x][z].getBlockType().getMaterial().isAir()) { 265 faces[i]++; 266 } 267 if (newBlocks[yIndex][x - 1][z].getBlockType().getMaterial().isAir()) { 268 faces[i]++; 269 } 270 if (newBlocks[yIndex][x][z - 1].getBlockType().getMaterial().isAir()) { 271 faces[i]++; 272 } 273 if (newBlocks[yIndex + 1][x][z].getBlockType().getMaterial().isAir()) { 274 faces[i]++; 275 } 276 if (newBlocks[yIndex][x + 1][z].getBlockType().getMaterial().isAir()) { 277 faces[i]++; 278 } 279 if (newBlocks[yIndex][x][z + 1].getBlockType().getMaterial().isAir()) { 280 faces[i]++; 281 } 282 } 283 284 if (!now.equals(now.getBlockType().getDefaultState())) { 285 data[i]++; 286 } 287 types.add(now.getBlockType()); 288 } 289 } 290 variety[i] = types.size(); 291 i++; 292 } 293 } 294 // analyze plot 295 // put in analysis obj 296 297 // run whenDone 298 PlotAnalysis analysis = new PlotAnalysis(); 299 analysis.changes = (int) (MathMan.getMean(changes) * 100); 300 analysis.faces = (int) (MathMan.getMean(faces) * 100); 301 analysis.data = (int) (MathMan.getMean(data) * 100); 302 analysis.air = (int) (MathMan.getMean(air) * 100); 303 analysis.variety = (int) (MathMan.getMean(variety) * 100); 304 305 analysis.changes_sd = (int) (MathMan.getSD(changes, analysis.changes) * 100); 306 analysis.faces_sd = (int) (MathMan.getSD(faces, analysis.faces) * 100); 307 analysis.data_sd = (int) (MathMan.getSD(data, analysis.data) * 100); 308 analysis.air_sd = (int) (MathMan.getSD(air, analysis.air) * 100); 309 analysis.variety_sd = (int) (MathMan.getSD(variety, analysis.variety) * 100); 310 whenDone.value = analysis; 311 whenDone.run(); 312 }; 313 queue.setCompleteTask(run); 314 queue.enqueue(); 315 }); 316 } 317 318 public void analyzePlot(final Plot origin, final RunnableVal<PlotAnalysis> whenDone) { 319 final ArrayDeque<CuboidRegion> zones = new ArrayDeque<>(origin.getRegions()); 320 final ArrayList<PlotAnalysis> analysis = new ArrayList<>(); 321 Runnable run = new Runnable() { 322 @Override 323 public void run() { 324 if (zones.isEmpty()) { 325 if (!analysis.isEmpty()) { 326 whenDone.value = new PlotAnalysis(); 327 for (PlotAnalysis data : analysis) { 328 whenDone.value.air += data.air; 329 whenDone.value.air_sd += data.air_sd; 330 whenDone.value.changes += data.changes; 331 whenDone.value.changes_sd += data.changes_sd; 332 whenDone.value.data += data.data; 333 whenDone.value.data_sd += data.data_sd; 334 whenDone.value.faces += data.faces; 335 whenDone.value.faces_sd += data.faces_sd; 336 whenDone.value.variety += data.variety; 337 whenDone.value.variety_sd += data.variety_sd; 338 } 339 whenDone.value.air /= analysis.size(); 340 whenDone.value.air_sd /= analysis.size(); 341 whenDone.value.changes /= analysis.size(); 342 whenDone.value.changes_sd /= analysis.size(); 343 whenDone.value.data /= analysis.size(); 344 whenDone.value.data_sd /= analysis.size(); 345 whenDone.value.faces /= analysis.size(); 346 whenDone.value.faces_sd /= analysis.size(); 347 whenDone.value.variety /= analysis.size(); 348 whenDone.value.variety_sd /= analysis.size(); 349 } else { 350 whenDone.value = analysis.get(0); 351 } 352 List<Integer> result = new ArrayList<>(); 353 result.add(whenDone.value.changes); 354 result.add(whenDone.value.faces); 355 result.add(whenDone.value.data); 356 result.add(whenDone.value.air); 357 result.add(whenDone.value.variety); 358 359 result.add(whenDone.value.changes_sd); 360 result.add(whenDone.value.faces_sd); 361 result.add(whenDone.value.data_sd); 362 result.add(whenDone.value.air_sd); 363 result.add(whenDone.value.variety_sd); 364 PlotFlag<?, ?> plotFlag = GlobalFlagContainer.getInstance().getFlag(AnalysisFlag.class).createFlagInstance( 365 result); 366 PlotFlagAddEvent event = eventDispatcher.callFlagAdd(plotFlag, origin); 367 if (event.getEventResult() == Result.DENY) { 368 return; 369 } 370 origin.setFlag(event.getFlag()); 371 TaskManager.runTask(whenDone); 372 return; 373 } 374 CuboidRegion region = zones.poll(); 375 final Runnable task = this; 376 analyzeRegion(origin.getWorldName(), region, new RunnableVal<>() { 377 @Override 378 public void run(PlotAnalysis value) { 379 analysis.add(value); 380 TaskManager.runTaskLater(task, TaskTime.ticks(1L)); 381 } 382 }); 383 } 384 }; 385 run.run(); 386 } 387 388 public final ArrayList<BlockVector2> getChunks(BlockVector2 region) { 389 ArrayList<BlockVector2> chunks = new ArrayList<>(); 390 int sx = region.getX() << 5; 391 int sz = region.getZ() << 5; 392 for (int x = sx; x < sx + 32; x++) { 393 for (int z = sz; z < sz + 32; z++) { 394 chunks.add(BlockVector2.at(x, z)); 395 } 396 } 397 return chunks; 398 } 399 400 public boolean scheduleRoadUpdate(PlotArea area, int extend) { 401 if (HybridUtils.UPDATE) { 402 return false; 403 } 404 HybridUtils.UPDATE = true; 405 Set<BlockVector2> regions = this.worldUtil.getChunkChunks(PlotSquared.platform().getPlatformWorld(area.getWorldName())); 406 return scheduleRoadUpdate(area, regions, extend, new LinkedHashSet<>()); 407 } 408 409 public boolean scheduleSingleRegionRoadUpdate(Plot plot, int extend) { 410 if (HybridUtils.UPDATE) { 411 return false; 412 } 413 HybridUtils.UPDATE = true; 414 Set<BlockVector2> regions = new HashSet<>(); 415 regions.add(RegionManager.getRegion(plot.getCenterSynchronous())); 416 return scheduleRoadUpdate(plot.getArea(), regions, extend, new LinkedHashSet<>()); 417 } 418 419 public boolean scheduleRoadUpdate( 420 final PlotArea area, 421 Set<BlockVector2> regions, 422 final int extend, 423 Set<BlockVector2> chunks 424 ) { 425 HybridUtils.regions = regions; 426 HybridUtils.area = area; 427 HybridUtils.height = extend; 428 HybridUtils.chunks = chunks; 429 final int initial = 1024 * regions.size() + chunks.size(); 430 final AtomicInteger count = new AtomicInteger(0); 431 TaskManager.runTask(new Runnable() { 432 @Override 433 public void run() { 434 if (!UPDATE) { 435 Iterator<BlockVector2> iter = chunks.iterator(); 436 QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); 437 queue.setShouldGen(false); 438 while (iter.hasNext()) { 439 BlockVector2 chunk = iter.next(); 440 iter.remove(); 441 RoadRegenerateResult result = regenerateRoad(area, chunk, extend, queue); 442 if (result != RoadRegenerateResult.SUCCESS) { 443 LOGGER.info( 444 "Failed to regenerate roads in chunk {}: {}", chunk, 445 result.getMessage() 446 ); 447 if (result.irrecoverable()) { 448 LOGGER.error("Cancelling road regen"); 449 chunks.clear(); 450 queue.cancel(); 451 HybridUtils.regions.clear(); 452 return; 453 } 454 } 455 } 456 queue.enqueue(); 457 LOGGER.info("Cancelled road task"); 458 return; 459 } 460 count.incrementAndGet(); 461 if (count.intValue() % 10 == 0) { 462 LOGGER.info("Progress: {}%", 100 * (initial - (chunks.size() + 1024 * regions.size())) / initial); 463 } 464 if (HybridUtils.regions.isEmpty() && chunks.isEmpty()) { 465 regeneratePlotWalls(area); 466 467 HybridUtils.UPDATE = false; 468 LOGGER.info("Finished road conversion"); 469 // CANCEL TASK 470 } else { 471 final Runnable task = this; 472 TaskManager.runTaskAsync(() -> { 473 try { 474 if (chunks.size() < 64) { 475 if (!HybridUtils.regions.isEmpty()) { 476 Iterator<BlockVector2> iterator = HybridUtils.regions.iterator(); 477 BlockVector2 loc = iterator.next(); 478 iterator.remove(); 479 LOGGER.info("Updating .mcr: {}, {} (approx 1024 chunks)", loc.getX(), loc.getZ()); 480 LOGGER.info("- Remaining: {}", HybridUtils.regions.size()); 481 chunks.addAll(getChunks(loc)); 482 System.gc(); 483 } 484 } 485 if (!chunks.isEmpty()) { 486 TaskManager.getPlatformImplementation().sync(() -> { 487 Iterator<BlockVector2> iterator = chunks.iterator(); 488 if (chunks.size() >= 32) { 489 QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); 490 queue.setShouldGen(false); 491 for (int i = 0; i < 32; i++) { 492 final BlockVector2 chunk = iterator.next(); 493 iterator.remove(); 494 RoadRegenerateResult result = regenerateRoad(area, chunk, extend, queue); 495 if (result != RoadRegenerateResult.SUCCESS) { 496 LOGGER.info( 497 "Failed to regenerate the road in chunk {}: {}", chunk, 498 result.getMessage() 499 ); 500 if (result.irrecoverable()) { 501 LOGGER.error("Cancelling road regen"); 502 chunks.clear(); 503 queue.cancel(); 504 HybridUtils.regions.clear(); 505 TaskManager.runTaskLater(task, TaskTime.seconds(1L)); 506 return null; 507 } 508 } 509 } 510 queue.setCompleteTask(task); 511 queue.enqueue(); 512 return null; 513 } 514 QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(area.getWorldName())); 515 queue.setShouldGen(false); 516 while (!chunks.isEmpty()) { 517 final BlockVector2 chunk = iterator.next(); 518 iterator.remove(); 519 RoadRegenerateResult result = regenerateRoad(area, chunk, extend, queue); 520 if (result != RoadRegenerateResult.SUCCESS) { 521 LOGGER.info( 522 "Failed to regenerate road in chunk {}: {}", chunk, 523 result.getMessage() 524 ); 525 if (result.irrecoverable()) { 526 LOGGER.error("Cancelling road regen"); 527 chunks.clear(); 528 queue.cancel(); 529 HybridUtils.regions.clear(); 530 TaskManager.runTaskLater(task, TaskTime.seconds(1L)); 531 return null; 532 } 533 } 534 } 535 queue.setCompleteTask(task); 536 queue.enqueue(); 537 return null; 538 }); 539 return; 540 } 541 } catch (Exception e) { 542 Iterator<BlockVector2> iterator = HybridUtils.regions.iterator(); 543 BlockVector2 loc = iterator.next(); 544 iterator.remove(); 545 LOGGER.error( 546 "Error! Could not update '{}/region/r.{}.{}.mca' (Corrupt chunk?)", 547 area.getWorldHash(), 548 loc.getX(), 549 loc.getZ(), 550 e 551 ); 552 } 553 TaskManager.runTaskLater(task, TaskTime.seconds(1L)); 554 }); 555 } 556 } 557 }); 558 return true; 559 } 560 561 public boolean setupRoadSchematic(Plot plot) { 562 final String world = plot.getWorldName(); 563 final QueueCoordinator queue = blockQueue.getNewQueue(worldUtil.getWeWorld(world)); 564 Location bot = plot.getBottomAbs().subtract(1, 0, 1); 565 Location top = plot.getTopAbs(); 566 final HybridPlotWorld plotworld = Objects.requireNonNull((HybridPlotWorld) plot.getArea()); 567 // Do not use plotworld#schematicStartHeight() here as we want to restore the pre 6.1.4 way of doing it if 568 // USE_WALL_IN_ROAD_SCHEM_HEIGHT is false 569 int schemY = Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT ? 570 Math.min(plotworld.PLOT_HEIGHT, Math.min(plotworld.WALL_HEIGHT, plotworld.ROAD_HEIGHT)) : plotworld.ROAD_HEIGHT; 571 int sx = bot.getX() - plotworld.ROAD_WIDTH + 1; 572 int sz = bot.getZ() + 1; 573 int sy = Settings.Schematics.PASTE_ROAD_ON_TOP ? schemY : plot.getArea().getMinGenHeight(); 574 int ex = bot.getX(); 575 int ez = top.getZ(); 576 int ey = get_ey(plotworld, queue, sx, ex, sz, ez, sy); 577 int bz = sz - plotworld.ROAD_WIDTH; 578 int tz = sz - 1; 579 int ty = get_ey(plotworld, queue, sx, ex, bz, tz, sy); 580 581 final Set<CuboidRegion> sideRoad = Collections.singleton(RegionUtil.createRegion(sx, ex, sy, ey, sz, ez)); 582 final Set<CuboidRegion> intersection = Collections.singleton(RegionUtil.createRegion(sx, ex, sy, ty, bz, tz)); 583 584 final String dir = Settings.Paths.SCHEMATICS + File.separator + "GEN_ROAD_SCHEMATIC" + File.separator + plot 585 .getArea() 586 .toString() + File.separator; 587 588 this.schematicHandler.getCompoundTag(world, sideRoad) 589 .whenComplete((compoundTag, throwable) -> { 590 schematicHandler.save(compoundTag, dir + "sideroad.schem"); 591 schematicHandler.getCompoundTag(world, intersection) 592 .whenComplete((c, t) -> { 593 schematicHandler.save(c, dir + "intersection.schem"); 594 plotworld.ROAD_SCHEMATIC_ENABLED = true; 595 try { 596 plotworld.setupSchematics(); 597 } catch (SchematicHandler.UnsupportedFormatException e) { 598 LOGGER.error(e); 599 } 600 }); 601 }); 602 return true; 603 } 604 605 private int get_ey(final HybridPlotWorld hpw, QueueCoordinator queue, int sx, int ex, int sz, int ez, int sy) { 606 int ey = sy; 607 for (int x = sx; x <= ex; x++) { 608 for (int z = sz; z <= ez; z++) { 609 for (int y = sy; y <= hpw.getMaxGenHeight(); y++) { 610 if (y > ey) { 611 BlockState block = queue.getBlock(x, y, z); 612 if (!block.getBlockType().getMaterial().isAir()) { 613 ey = y; 614 } 615 } 616 } 617 } 618 } 619 return ey; 620 } 621 622 /** 623 * Regenerate the road in a chunk in a plot area. 624 * 625 * @param area Plot area to regenerate road for 626 * @param chunk Chunk location to regenerate 627 * @param extend How far to extend setting air above the road 628 * @param queueCoordinator {@link QueueCoordinator} to use to set the blocks. Null if one should be created and enqueued 629 * @return {@link RoadRegenerateResult} result 630 * @since 6.6.0 631 */ 632 public RoadRegenerateResult regenerateRoad( 633 final PlotArea area, 634 final BlockVector2 chunk, 635 int extend, 636 @Nullable QueueCoordinator queueCoordinator 637 ) { 638 int x = chunk.getX() << 4; 639 int z = chunk.getZ() << 4; 640 int ex = x + 15; 641 int ez = z + 15; 642 HybridPlotWorld plotWorld = (HybridPlotWorld) area; 643 if (!plotWorld.ROAD_SCHEMATIC_ENABLED) { 644 return RoadRegenerateResult.FAIL_ROAD_SCHEMATICS_NOT_ENABLED; 645 } 646 AtomicBoolean toCheck = new AtomicBoolean(false); 647 if (plotWorld.getType() == PlotAreaType.PARTIAL) { 648 boolean chunk1 = area.contains(x, z); 649 boolean chunk2 = area.contains(ex, ez); 650 if (!chunk1 && !chunk2) { 651 return RoadRegenerateResult.SUCCESS; 652 } else { 653 toCheck.set(chunk1 ^ chunk2); 654 } 655 } 656 PlotManager manager = area.getPlotManager(); 657 PlotId id1 = manager.getPlotId(x, 0, z); 658 PlotId id2 = manager.getPlotId(ex, 0, ez); 659 x = x - plotWorld.ROAD_OFFSET_X; 660 z -= plotWorld.ROAD_OFFSET_Z; 661 int finalX = x; 662 int finalZ = z; 663 boolean enqueue; 664 QueueCoordinator queue; 665 if (queueCoordinator == null) { 666 queue = this.blockQueue.getNewQueue(worldUtil.getWeWorld(plotWorld.getWorldName())); 667 enqueue = true; 668 } else { 669 queue = queueCoordinator; 670 enqueue = false; 671 } 672 if (id1 == null || !id1.equals(id2)) { 673 if (id1 != null) { 674 Plot p1 = area.getPlotAbs(id1); 675 if (p1 != null && p1.hasOwner() && p1.isMerged()) { 676 toCheck.set(true); 677 } 678 } 679 if (id2 != null && !toCheck.get()) { 680 Plot p2 = area.getPlotAbs(id2); 681 if (p2 != null && p2.hasOwner() && p2.isMerged()) { 682 toCheck.set(true); 683 } 684 } 685 short size = plotWorld.SIZE; 686 for (int X = 0; X < 16; X++) { 687 short absX = (short) ((finalX + X) % size); 688 for (int Z = 0; Z < 16; Z++) { 689 short absZ = (short) ((finalZ + Z) % size); 690 if (absX < 0) { 691 absX += size; 692 } 693 if (absZ < 0) { 694 absZ += size; 695 } 696 boolean condition; 697 if (toCheck.get()) { 698 condition = manager.getPlotId( 699 finalX + X + plotWorld.ROAD_OFFSET_X, 700 1, 701 finalZ + Z + plotWorld.ROAD_OFFSET_Z 702 ) == null; 703 } else { 704 boolean gx = absX > plotWorld.PATH_WIDTH_LOWER; 705 boolean gz = absZ > plotWorld.PATH_WIDTH_LOWER; 706 boolean lx = absX < plotWorld.PATH_WIDTH_UPPER; 707 boolean lz = absZ < plotWorld.PATH_WIDTH_UPPER; 708 condition = !gx || !gz || !lx || !lz; 709 } 710 if (condition) { 711 BaseBlock[] blocks = plotWorld.G_SCH.get(MathMan.pair(absX, absZ)); 712 int minY = plotWorld.getRoadYStart(); 713 int maxDy = Math.max(extend, blocks.length); 714 for (int dy = 0; dy < maxDy; dy++) { 715 if (dy > blocks.length - 1) { 716 queue.setBlock( 717 finalX + X + plotWorld.ROAD_OFFSET_X, 718 minY + dy, 719 finalZ + Z + plotWorld.ROAD_OFFSET_Z, 720 WEExtent.AIRBASE 721 ); 722 } else { 723 BaseBlock block = blocks[dy]; 724 if (block != null) { 725 queue.setBlock( 726 finalX + X + plotWorld.ROAD_OFFSET_X, 727 minY + dy, 728 finalZ + Z + plotWorld.ROAD_OFFSET_Z, 729 block 730 ); 731 } else { 732 queue.setBlock( 733 finalX + X + plotWorld.ROAD_OFFSET_X, 734 minY + dy, 735 finalZ + Z + plotWorld.ROAD_OFFSET_Z, 736 WEExtent.AIRBASE 737 ); 738 } 739 } 740 } 741 BiomeType biome = plotWorld.G_SCH_B.get(MathMan.pair(absX, absZ)); 742 if (biome != null) { 743 queue.setBiome(finalX + X + plotWorld.ROAD_OFFSET_X, finalZ + Z + plotWorld.ROAD_OFFSET_Z, biome); 744 } else { 745 queue.setBiome( 746 finalX + X + plotWorld.ROAD_OFFSET_X, 747 finalZ + Z + plotWorld.ROAD_OFFSET_Z, 748 plotWorld.getPlotBiome() 749 ); 750 } 751 } 752 } 753 } 754 if (enqueue) { 755 queue.enqueue(); 756 } 757 } 758 return RoadRegenerateResult.SUCCESS; 759 } 760 761 public enum RoadRegenerateResult { 762 763 SUCCESS(true, null), 764 FAIL_ROAD_SCHEMATICS_NOT_ENABLED(false, "Plot world does not have road schematics enabled - are they correctly set up?"); 765 766 private final boolean recoverable; 767 private final String message; 768 769 RoadRegenerateResult(boolean recoverable, String message) { 770 this.recoverable = recoverable; 771 this.message = message; 772 } 773 774 public boolean irrecoverable() { 775 return !recoverable; 776 } 777 778 public String getMessage() { 779 return message; 780 } 781 782 } 783 784}