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