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