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