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.plot; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.PlotSquared; 023import com.plotsquared.core.configuration.ConfigurationUtil; 024import com.plotsquared.core.configuration.Settings; 025import com.plotsquared.core.configuration.caption.Caption; 026import com.plotsquared.core.configuration.caption.LocaleHolder; 027import com.plotsquared.core.configuration.caption.TranslatableCaption; 028import com.plotsquared.core.database.DBFunc; 029import com.plotsquared.core.events.PlotComponentSetEvent; 030import com.plotsquared.core.events.PlotMergeEvent; 031import com.plotsquared.core.events.PlotUnlinkEvent; 032import com.plotsquared.core.events.Result; 033import com.plotsquared.core.generator.ClassicPlotWorld; 034import com.plotsquared.core.generator.SquarePlotWorld; 035import com.plotsquared.core.inject.factory.ProgressSubscriberFactory; 036import com.plotsquared.core.location.Direction; 037import com.plotsquared.core.location.Location; 038import com.plotsquared.core.player.PlotPlayer; 039import com.plotsquared.core.plot.flag.PlotFlag; 040import com.plotsquared.core.queue.QueueCoordinator; 041import com.plotsquared.core.util.PlayerManager; 042import com.plotsquared.core.util.task.TaskManager; 043import com.plotsquared.core.util.task.TaskTime; 044import com.sk89q.worldedit.function.pattern.Pattern; 045import com.sk89q.worldedit.math.BlockVector2; 046import com.sk89q.worldedit.regions.CuboidRegion; 047import com.sk89q.worldedit.world.biome.BiomeType; 048import com.sk89q.worldedit.world.block.BlockTypes; 049import net.kyori.adventure.text.minimessage.Template; 050import org.apache.logging.log4j.LogManager; 051import org.apache.logging.log4j.Logger; 052import org.checkerframework.checker.nullness.qual.NonNull; 053import org.checkerframework.checker.nullness.qual.Nullable; 054 055import java.util.ArrayDeque; 056import java.util.ArrayList; 057import java.util.Collection; 058import java.util.HashSet; 059import java.util.Iterator; 060import java.util.Set; 061import java.util.UUID; 062import java.util.concurrent.CompletableFuture; 063import java.util.concurrent.atomic.AtomicBoolean; 064import java.util.stream.Collectors; 065 066/** 067 * Manager that handles {@link Plot} modifications 068 */ 069public final class PlotModificationManager { 070 071 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlotModificationManager.class.getSimpleName()); 072 073 private final Plot plot; 074 private final ProgressSubscriberFactory subscriberFactory; 075 076 @Inject 077 PlotModificationManager(final @NonNull Plot plot) { 078 this.plot = plot; 079 this.subscriberFactory = PlotSquared.platform().injector().getInstance(ProgressSubscriberFactory.class); 080 } 081 082 /** 083 * Copy a plot to a location, both physically and the settings 084 * 085 * @param destination destination plot 086 * @param actor the actor associated with the copy 087 * @return Future that completes with {@code true} if the copy was successful, else {@code false} 088 */ 089 public CompletableFuture<Boolean> copy(final @NonNull Plot destination, @Nullable PlotPlayer<?> actor) { 090 final CompletableFuture<Boolean> future = new CompletableFuture<>(); 091 final PlotId offset = PlotId.of( 092 destination.getId().getX() - this.plot.getId().getX(), 093 destination.getId().getY() - this.plot.getId().getY() 094 ); 095 final Location db = destination.getBottomAbs(); 096 final Location ob = this.plot.getBottomAbs(); 097 final int offsetX = db.getX() - ob.getX(); 098 final int offsetZ = db.getZ() - ob.getZ(); 099 if (!this.plot.hasOwner()) { 100 TaskManager.runTaskLater(() -> future.complete(false), TaskTime.ticks(1L)); 101 return future; 102 } 103 final Set<Plot> plots = this.plot.getConnectedPlots(); 104 for (final Plot plot : plots) { 105 final Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); 106 if (other.hasOwner()) { 107 TaskManager.runTaskLater(() -> future.complete(false), TaskTime.ticks(1L)); 108 return future; 109 } 110 } 111 // world border 112 destination.updateWorldBorder(); 113 // copy data 114 for (final Plot plot : plots) { 115 final Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); 116 other.getPlotModificationManager().create(plot.getOwner(), false); 117 if (!plot.getFlagContainer().getFlagMap().isEmpty()) { 118 final Collection<PlotFlag<?, ?>> existingFlags = other.getFlags(); 119 other.getFlagContainer().clearLocal(); 120 other.getFlagContainer().addAll(plot.getFlagContainer().getFlagMap().values()); 121 // Update the database 122 for (final PlotFlag<?, ?> flag : existingFlags) { 123 final PlotFlag<?, ?> newFlag = other.getFlagContainer().queryLocal(flag.getClass()); 124 if (other.getFlagContainer().queryLocal(flag.getClass()) == null) { 125 DBFunc.removeFlag(other, flag); 126 } else { 127 DBFunc.setFlag(other, newFlag); 128 } 129 } 130 } 131 if (plot.isMerged()) { 132 other.setMerged(plot.getMerged()); 133 } 134 if (plot.members != null && !plot.members.isEmpty()) { 135 other.members = plot.members; 136 for (UUID member : plot.members) { 137 DBFunc.setMember(other, member); 138 } 139 } 140 if (plot.trusted != null && !plot.trusted.isEmpty()) { 141 other.trusted = plot.trusted; 142 for (UUID trusted : plot.trusted) { 143 DBFunc.setTrusted(other, trusted); 144 } 145 } 146 if (plot.denied != null && !plot.denied.isEmpty()) { 147 other.denied = plot.denied; 148 for (UUID denied : plot.denied) { 149 DBFunc.setDenied(other, denied); 150 } 151 } 152 } 153 // copy terrain 154 final ArrayDeque<CuboidRegion> regions = new ArrayDeque<>(this.plot.getRegions()); 155 final Runnable run = new Runnable() { 156 @Override 157 public void run() { 158 if (regions.isEmpty()) { 159 final QueueCoordinator queue = plot.getArea().getQueue(); 160 for (final Plot current : plot.getConnectedPlots()) { 161 destination.getManager().claimPlot(current, queue); 162 } 163 if (queue.size() > 0) { 164 queue.enqueue(); 165 } 166 destination.getPlotModificationManager().setSign(); 167 future.complete(true); 168 return; 169 } 170 CuboidRegion region = regions.poll(); 171 Location[] corners = Plot.getCorners(plot.getWorldName(), region); 172 Location pos1 = corners[0]; 173 Location pos2 = corners[1]; 174 Location newPos = pos1.add(offsetX, 0, offsetZ).withWorld(destination.getWorldName()); 175 PlotSquared.platform().regionManager().copyRegion(pos1, pos2, newPos, actor, this); 176 } 177 }; 178 run.run(); 179 return future; 180 } 181 182 /** 183 * Clear the plot 184 * 185 * <p> 186 * Use {@link #deletePlot(PlotPlayer, Runnable)} to clear and delete a plot 187 * </p> 188 * 189 * @param whenDone A runnable to execute when clearing finishes, or null 190 * @see #clear(boolean, boolean, PlotPlayer, Runnable) 191 */ 192 public void clear(final @Nullable Runnable whenDone) { 193 this.clear(false, false, null, whenDone); 194 } 195 196 /** 197 * Clear the plot 198 * 199 * <p> 200 * Use {@link #deletePlot(PlotPlayer, Runnable)} to clear and delete a plot 201 * </p> 202 * 203 * @param checkRunning Whether or not already executing tasks should be checked 204 * @param isDelete Whether or not the plot is being deleted 205 * @param actor The actor clearing the plot 206 * @param whenDone A runnable to execute when clearing finishes, or null 207 */ 208 public boolean clear( 209 final boolean checkRunning, 210 final boolean isDelete, 211 final @Nullable PlotPlayer<?> actor, 212 final @Nullable Runnable whenDone 213 ) { 214 if (checkRunning && this.plot.getRunning() != 0) { 215 return false; 216 } 217 final Set<CuboidRegion> regions = this.plot.getRegions(); 218 final Set<Plot> plots = this.plot.getConnectedPlots(); 219 final ArrayDeque<Plot> queue = new ArrayDeque<>(plots); 220 if (isDelete) { 221 this.removeSign(); 222 } 223 final PlotManager manager = this.plot.getArea().getPlotManager(); 224 Runnable run = new Runnable() { 225 @Override 226 public void run() { 227 if (queue.isEmpty()) { 228 Runnable run = () -> { 229 for (CuboidRegion region : regions) { 230 Location[] corners = Plot.getCorners(plot.getWorldName(), region); 231 PlotSquared.platform().regionManager().clearAllEntities(corners[0], corners[1]); 232 } 233 TaskManager.runTask(whenDone); 234 }; 235 QueueCoordinator queue = plot.getArea().getQueue(); 236 for (Plot current : plots) { 237 if (isDelete || !current.hasOwner()) { 238 manager.unClaimPlot(current, null, queue); 239 } else { 240 manager.claimPlot(current, queue); 241 if (plot.getArea() instanceof ClassicPlotWorld cpw) { 242 manager.setComponent(current.getId(), "wall", cpw.WALL_FILLING.toPattern(), actor, queue); 243 } 244 } 245 } 246 if (queue.size() > 0) { 247 queue.setCompleteTask(run); 248 queue.enqueue(); 249 return; 250 } 251 run.run(); 252 return; 253 } 254 Plot current = queue.poll(); 255 current.clearCache(); 256 if (plot.getArea().getTerrain() != PlotAreaTerrainType.NONE) { 257 try { 258 PlotSquared.platform().regionManager().regenerateRegion( 259 current.getBottomAbs(), 260 current.getTopAbs(), 261 false, 262 this 263 ); 264 } catch (UnsupportedOperationException exception) { 265 exception.printStackTrace(); 266 return; 267 } 268 return; 269 } 270 manager.clearPlot(current, this, actor, null); 271 } 272 }; 273 PlotUnlinkEvent event = PlotSquared.get().getEventDispatcher() 274 .callUnlink( 275 this.plot.getArea(), 276 this.plot, 277 true, 278 !isDelete, 279 isDelete ? PlotUnlinkEvent.REASON.DELETE : PlotUnlinkEvent.REASON.CLEAR 280 ); 281 if (event.getEventResult() != Result.DENY) { 282 if (this.unlinkPlot(event.isCreateRoad(), event.isCreateSign(), run)) { 283 PlotSquared.get().getEventDispatcher().callPostUnlink(plot, event.getReason()); 284 } 285 } else { 286 run.run(); 287 } 288 return true; 289 } 290 291 /** 292 * Sets the biome for a plot asynchronously. 293 * 294 * @param biome The biome e.g. "forest" 295 * @param whenDone The task to run when finished, or null 296 */ 297 public void setBiome(final @Nullable BiomeType biome, final @NonNull Runnable whenDone) { 298 final ArrayDeque<CuboidRegion> regions = new ArrayDeque<>(this.plot.getRegions()); 299 final int extendBiome; 300 if (this.plot.getArea() instanceof SquarePlotWorld) { 301 extendBiome = (((SquarePlotWorld) this.plot.getArea()).ROAD_WIDTH > 0) ? 1 : 0; 302 } else { 303 extendBiome = 0; 304 } 305 Runnable run = new Runnable() { 306 @Override 307 public void run() { 308 if (regions.isEmpty()) { 309 TaskManager.runTask(whenDone); 310 return; 311 } 312 CuboidRegion region = regions.poll(); 313 PlotSquared.platform().regionManager().setBiome(region, extendBiome, biome, plot.getArea(), this); 314 } 315 }; 316 run.run(); 317 } 318 319 /** 320 * Unlink the plot and all connected plots. 321 * 322 * @param createRoad whether to recreate road 323 * @param createSign whether to recreate signs 324 * @return success/!cancelled 325 */ 326 public boolean unlinkPlot(final boolean createRoad, final boolean createSign) { 327 return unlinkPlot(createRoad, createSign, null); 328 } 329 330 /** 331 * Unlink the plot and all connected plots. 332 * 333 * @param createRoad whether to recreate road 334 * @param createSign whether to recreate signs 335 * @param whenDone Task to run when unlink is complete 336 * @return success/!cancelled 337 * @since 6.10.9 338 */ 339 public boolean unlinkPlot(final boolean createRoad, final boolean createSign, final Runnable whenDone) { 340 if (!this.plot.isMerged()) { 341 if (whenDone != null) { 342 whenDone.run(); 343 } 344 return false; 345 } 346 final Set<Plot> plots = this.plot.getConnectedPlots(); 347 ArrayList<PlotId> ids = new ArrayList<>(plots.size()); 348 for (Plot current : plots) { 349 current.setHome(null); 350 current.clearCache(); 351 ids.add(current.getId()); 352 } 353 this.plot.clearRatings(); 354 QueueCoordinator queue = this.plot.getArea().getQueue(); 355 if (createSign) { 356 this.removeSign(); 357 } 358 PlotManager manager = this.plot.getArea().getPlotManager(); 359 if (createRoad) { 360 manager.startPlotUnlink(ids, queue); 361 } 362 if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL && createRoad) { 363 for (Plot current : plots) { 364 if (current.isMerged(Direction.EAST)) { 365 manager.createRoadEast(current, queue); 366 if (current.isMerged(Direction.SOUTH)) { 367 manager.createRoadSouth(current, queue); 368 if (current.isMerged(Direction.SOUTHEAST)) { 369 manager.createRoadSouthEast(current, queue); 370 } 371 } 372 } 373 if (current.isMerged(Direction.SOUTH)) { 374 manager.createRoadSouth(current, queue); 375 } 376 } 377 } 378 for (Plot current : plots) { 379 boolean[] merged = new boolean[]{false, false, false, false}; 380 current.setMerged(merged); 381 } 382 if (createSign) { 383 queue.setCompleteTask(() -> TaskManager.runTaskAsync(() -> { 384 for (Plot current : plots) { 385 current.getPlotModificationManager().setSign(PlayerManager.resolveName(current.getOwnerAbs()).getComponent( 386 LocaleHolder.console())); 387 } 388 if (whenDone != null) { 389 TaskManager.runTask(whenDone); 390 } 391 })); 392 } else if (whenDone != null) { 393 queue.setCompleteTask(whenDone); 394 } 395 if (createRoad) { 396 manager.finishPlotUnlink(ids, queue); 397 } 398 queue.enqueue(); 399 return true; 400 } 401 402 /** 403 * Sets the sign for a plot to a specific name 404 * 405 * @param name name 406 */ 407 public void setSign(final @NonNull String name) { 408 if (!this.plot.isLoaded()) { 409 return; 410 } 411 PlotManager manager = this.plot.getArea().getPlotManager(); 412 if (this.plot.getArea().allowSigns()) { 413 Location location = manager.getSignLoc(this.plot); 414 String id = this.plot.getId().toString(); 415 Caption[] lines = new Caption[]{TranslatableCaption.of("signs.owner_sign_line_1"), TranslatableCaption.of( 416 "signs.owner_sign_line_2"), 417 TranslatableCaption.of("signs.owner_sign_line_3"), TranslatableCaption.of("signs.owner_sign_line_4")}; 418 PlotSquared.platform().worldUtil().setSign(location, lines, Template.of("id", id), Template.of("owner", name)); 419 } 420 } 421 422 /** 423 * Resend all chunks inside the plot to nearby players<br> 424 * This should not need to be called 425 */ 426 public void refreshChunks() { 427 final HashSet<BlockVector2> chunks = new HashSet<>(); 428 for (final CuboidRegion region : this.plot.getRegions()) { 429 for (int x = region.getMinimumPoint().getX() >> 4; x <= region.getMaximumPoint().getX() >> 4; x++) { 430 for (int z = region.getMinimumPoint().getZ() >> 4; z <= region.getMaximumPoint().getZ() >> 4; z++) { 431 if (chunks.add(BlockVector2.at(x, z))) { 432 PlotSquared.platform().worldUtil().refreshChunk(x, z, this.plot.getWorldName()); 433 } 434 } 435 } 436 } 437 } 438 439 /** 440 * Remove the plot sign if it is set. 441 */ 442 public void removeSign() { 443 PlotManager manager = this.plot.getArea().getPlotManager(); 444 if (!this.plot.getArea().allowSigns()) { 445 return; 446 } 447 Location location = manager.getSignLoc(this.plot); 448 QueueCoordinator queue = 449 PlotSquared.platform().globalBlockQueue().getNewQueue(PlotSquared 450 .platform() 451 .worldUtil() 452 .getWeWorld(this.plot.getWorldName())); 453 queue.setBlock(location.getX(), location.getY(), location.getZ(), BlockTypes.AIR.getDefaultState()); 454 queue.enqueue(); 455 } 456 457 /** 458 * Sets the plot sign if plot signs are enabled. 459 */ 460 public void setSign() { 461 if (!this.plot.hasOwner()) { 462 this.setSign("unknown"); 463 return; 464 } 465 PlotSquared.get().getImpromptuUUIDPipeline().getSingle( 466 this.plot.getOwnerAbs(), 467 (username, sign) -> this.setSign(username) 468 ); 469 } 470 471 /** 472 * Register a plot and create it in the database<br> 473 * - The plot will not be created if the owner is null<br> 474 * - Any setting from before plot creation will not be saved until the server is stopped properly. i.e. Set any values/options after plot 475 * creation. 476 * 477 * @return {@code true} if plot was created successfully 478 */ 479 public boolean create() { 480 return this.create(this.plot.getOwnerAbs(), true); 481 } 482 483 /** 484 * Register a plot and create it in the database<br> 485 * - The plot will not be created if the owner is null<br> 486 * - Any setting from before plot creation will not be saved until the server is stopped properly. i.e. Set any values/options after plot 487 * creation. 488 * 489 * @param uuid the uuid of the plot owner 490 * @param notify notify 491 * @return {@code true} if plot was created successfully, else {@code false} 492 */ 493 public boolean create(final @NonNull UUID uuid, final boolean notify) { 494 this.plot.setOwnerAbs(uuid); 495 Plot existing = this.plot.getArea().getOwnedPlotAbs(this.plot.getId()); 496 if (existing != null) { 497 throw new IllegalStateException("Plot already exists!"); 498 } 499 if (notify) { 500 Integer meta = (Integer) this.plot.getArea().getMeta("worldBorder"); 501 if (meta != null) { 502 this.plot.updateWorldBorder(); 503 } 504 } 505 this.plot.clearCache(); 506 this.plot.getTrusted().clear(); 507 this.plot.getMembers().clear(); 508 this.plot.getDenied().clear(); 509 this.plot.settings = new PlotSettings(); 510 if (this.plot.getArea().addPlot(this.plot)) { 511 DBFunc.createPlotAndSettings(this.plot, () -> { 512 PlotArea plotworld = plot.getArea(); 513 if (notify && plotworld.isAutoMerge()) { 514 final PlotPlayer<?> player = PlotSquared.platform().playerManager().getPlayerIfExists(uuid); 515 516 PlotMergeEvent event = PlotSquared.get().getEventDispatcher().callMerge( 517 this.plot, 518 Direction.ALL, 519 Integer.MAX_VALUE, 520 player 521 ); 522 523 if (event.getEventResult() == Result.DENY) { 524 if (player != null) { 525 player.sendMessage( 526 TranslatableCaption.of("events.event_denied"), 527 Template.of("value", "Auto merge on claim") 528 ); 529 } 530 return; 531 } 532 if (plot.getPlotModificationManager().autoMerge(event.getDir(), event.getMax(), uuid, player, true)) { 533 PlotSquared.get().getEventDispatcher().callPostMerge(player, plot); 534 } 535 } 536 }); 537 return true; 538 } 539 LOGGER.info( 540 "Failed to add plot {} to plot area {}", 541 this.plot.getId().toCommaSeparatedString(), 542 this.plot.getArea().toString() 543 ); 544 return false; 545 } 546 547 /** 548 * Auto merge a plot in a specific direction. 549 * 550 * @param dir the direction to merge 551 * @param max the max number of merges to do 552 * @param uuid the UUID it is allowed to merge with 553 * @param actor The actor executing the task 554 * @param removeRoads whether to remove roads 555 * @return {@code true} if a merge takes place, else {@code false} 556 */ 557 public boolean autoMerge( 558 final @NonNull Direction dir, 559 int max, 560 final @NonNull UUID uuid, 561 @Nullable PlotPlayer<?> actor, 562 final boolean removeRoads 563 ) { 564 //Ignore merging if there is no owner for the plot 565 if (!this.plot.hasOwner()) { 566 return false; 567 } 568 Set<Plot> connected = this.plot.getConnectedPlots(); 569 HashSet<PlotId> merged = connected.stream().map(Plot::getId).collect(Collectors.toCollection(HashSet::new)); 570 ArrayDeque<Plot> frontier = new ArrayDeque<>(connected); 571 Plot current; 572 boolean toReturn = false; 573 HashSet<Plot> visited = new HashSet<>(); 574 QueueCoordinator queue = this.plot.getArea().getQueue(); 575 while ((current = frontier.poll()) != null && max >= 0) { 576 if (visited.contains(current)) { 577 continue; 578 } 579 visited.add(current); 580 Set<Plot> plots; 581 if ((dir == Direction.ALL || dir == Direction.NORTH) && !current.isMerged(Direction.NORTH)) { 582 Plot other = current.getRelative(Direction.NORTH); 583 if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) 584 || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { 585 current.mergePlot(other, removeRoads, queue); 586 merged.add(current.getId()); 587 merged.add(other.getId()); 588 toReturn = true; 589 590 if (removeRoads) { 591 ArrayList<PlotId> ids = new ArrayList<>(); 592 ids.add(current.getId()); 593 ids.add(other.getId()); 594 this.plot.getManager().finishPlotMerge(ids, queue); 595 } 596 } 597 } 598 if (max >= 0 && (dir == Direction.ALL || dir == Direction.EAST) && !current.isMerged(Direction.EAST)) { 599 Plot other = current.getRelative(Direction.EAST); 600 if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) 601 || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { 602 current.mergePlot(other, removeRoads, queue); 603 merged.add(current.getId()); 604 merged.add(other.getId()); 605 toReturn = true; 606 607 if (removeRoads) { 608 ArrayList<PlotId> ids = new ArrayList<>(); 609 ids.add(current.getId()); 610 ids.add(other.getId()); 611 this.plot.getManager().finishPlotMerge(ids, queue); 612 } 613 } 614 } 615 if (max >= 0 && (dir == Direction.ALL || dir == Direction.SOUTH) && !current.isMerged(Direction.SOUTH)) { 616 Plot other = current.getRelative(Direction.SOUTH); 617 if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) 618 || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { 619 current.mergePlot(other, removeRoads, queue); 620 merged.add(current.getId()); 621 merged.add(other.getId()); 622 toReturn = true; 623 624 if (removeRoads) { 625 ArrayList<PlotId> ids = new ArrayList<>(); 626 ids.add(current.getId()); 627 ids.add(other.getId()); 628 this.plot.getManager().finishPlotMerge(ids, queue); 629 } 630 } 631 } 632 if (max >= 0 && (dir == Direction.ALL || dir == Direction.WEST) && !current.isMerged(Direction.WEST)) { 633 Plot other = current.getRelative(Direction.WEST); 634 if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) 635 || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { 636 current.mergePlot(other, removeRoads, queue); 637 merged.add(current.getId()); 638 merged.add(other.getId()); 639 toReturn = true; 640 641 if (removeRoads) { 642 ArrayList<PlotId> ids = new ArrayList<>(); 643 ids.add(current.getId()); 644 ids.add(other.getId()); 645 this.plot.getManager().finishPlotMerge(ids, queue); 646 } 647 } 648 } 649 } 650 if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { 651 queue.addProgressSubscriber(subscriberFactory.createWithActor(actor)); 652 } 653 if (queue.size() > 0) { 654 queue.enqueue(); 655 } 656 visited.forEach(Plot::clearCache); 657 return toReturn; 658 } 659 660 /** 661 * Moves a plot physically, as well as the corresponding settings. 662 * 663 * @param destination Plot moved to 664 * @param actor The actor executing the task 665 * @param whenDone task when done 666 * @param allowSwap whether to swap plots 667 * @return {@code true} if the move was successful, else {@code false} 668 */ 669 public @NonNull CompletableFuture<Boolean> move( 670 final @NonNull Plot destination, 671 final @Nullable PlotPlayer<?> actor, 672 final @NonNull Runnable whenDone, 673 final boolean allowSwap 674 ) { 675 final PlotId offset = PlotId.of( 676 destination.getId().getX() - this.plot.getId().getX(), 677 destination.getId().getY() - this.plot.getId().getY() 678 ); 679 Location db = destination.getBottomAbs(); 680 Location ob = this.plot.getBottomAbs(); 681 final int offsetX = db.getX() - ob.getX(); 682 final int offsetZ = db.getZ() - ob.getZ(); 683 if (!this.plot.hasOwner()) { 684 TaskManager.runTaskLater(whenDone, TaskTime.ticks(1L)); 685 return CompletableFuture.completedFuture(false); 686 } 687 AtomicBoolean occupied = new AtomicBoolean(false); 688 Set<Plot> plots = this.plot.getConnectedPlots(); 689 for (Plot plot : plots) { 690 Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); 691 if (other.hasOwner()) { 692 if (!allowSwap) { 693 TaskManager.runTaskLater(whenDone, TaskTime.ticks(1L)); 694 return CompletableFuture.completedFuture(false); 695 } 696 occupied.set(true); 697 } else { 698 plot.getPlotModificationManager().removeSign(); 699 } 700 } 701 // world border 702 destination.updateWorldBorder(); 703 final ArrayDeque<CuboidRegion> regions = new ArrayDeque<>(this.plot.getRegions()); 704 // move / swap data 705 final PlotArea originArea = this.plot.getArea(); 706 707 final Iterator<Plot> plotIterator = plots.iterator(); 708 709 CompletableFuture<Boolean> future = null; 710 if (plotIterator.hasNext()) { 711 while (plotIterator.hasNext()) { 712 final Plot plot = plotIterator.next(); 713 final Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); 714 final CompletableFuture<Boolean> swapResult = plot.swapData(other); 715 if (future == null) { 716 future = swapResult; 717 } else { 718 future = future.thenCombine(swapResult, (fn, th) -> fn); 719 } 720 } 721 } else { 722 future = CompletableFuture.completedFuture(true); 723 } 724 725 return future.thenApply(result -> { 726 if (!result) { 727 return false; 728 } 729 // copy terrain 730 if (occupied.get()) { 731 new Runnable() { 732 @Override 733 public void run() { 734 if (regions.isEmpty()) { 735 // Update signs 736 destination.getPlotModificationManager().setSign(); 737 setSign(); 738 // Run final tasks 739 TaskManager.runTask(whenDone); 740 } else { 741 CuboidRegion region = regions.poll(); 742 Location[] corners = Plot.getCorners(plot.getWorldName(), region); 743 Location pos1 = corners[0]; 744 Location pos2 = corners[1]; 745 Location pos3 = pos1.add(offsetX, 0, offsetZ).withWorld(destination.getWorldName()); 746 PlotSquared.platform().regionManager().swap(pos1, pos2, pos3, actor, this); 747 } 748 } 749 }.run(); 750 } else { 751 new Runnable() { 752 @Override 753 public void run() { 754 if (regions.isEmpty()) { 755 Plot plot = destination.getRelative(0, 0); 756 Plot originPlot = 757 originArea.getPlotAbs(PlotId.of( 758 plot.getId().getX() - offset.getX(), 759 plot.getId().getY() - offset.getY() 760 )); 761 final Runnable clearDone = () -> { 762 QueueCoordinator queue = PlotModificationManager.this.plot.getArea().getQueue(); 763 for (final Plot current : plot.getConnectedPlots()) { 764 PlotModificationManager.this.plot.getManager().claimPlot(current, queue); 765 } 766 if (queue.size() > 0) { 767 queue.enqueue(); 768 } 769 plot.getPlotModificationManager().setSign(); 770 TaskManager.runTask(whenDone); 771 }; 772 if (originPlot != null) { 773 originPlot.getPlotModificationManager().clear(false, true, actor, clearDone); 774 } else { 775 clearDone.run(); 776 } 777 return; 778 } 779 final Runnable task = this; 780 CuboidRegion region = regions.poll(); 781 Location[] corners = Plot.getCorners( 782 PlotModificationManager.this.plot.getWorldName(), 783 region 784 ); 785 final Location pos1 = corners[0]; 786 final Location pos2 = corners[1]; 787 Location newPos = pos1.add(offsetX, 0, offsetZ).withWorld(destination.getWorldName()); 788 PlotSquared.platform().regionManager().copyRegion(pos1, pos2, newPos, actor, task); 789 } 790 }.run(); 791 } 792 return true; 793 }); 794 } 795 796 /** 797 * Unlink a plot and remove the roads 798 * 799 * @return {@code true} if plot was linked 800 * @see #unlinkPlot(boolean, boolean) 801 */ 802 public boolean unlink() { 803 return this.unlinkPlot(true, true); 804 } 805 806 /** 807 * Swap the plot contents and settings with another location<br> 808 * - The destination must correspond to a valid plot of equal dimensions 809 * 810 * @param destination The other plot to swap with 811 * @param actor The actor executing the task 812 * @param whenDone A task to run when finished, or null 813 * @return Future that completes with {@code true} if the swap was successful, else {@code false} 814 */ 815 public @NonNull CompletableFuture<Boolean> swap( 816 final @NonNull Plot destination, 817 @Nullable PlotPlayer<?> actor, 818 final @NonNull Runnable whenDone 819 ) { 820 return this.move(destination, actor, whenDone, true); 821 } 822 823 /** 824 * Moves the plot to an empty location<br> 825 * - The location must be empty 826 * 827 * @param destination Where to move the plot 828 * @param actor The actor executing the task 829 * @param whenDone A task to run when done, or null 830 * @return Future that completes with {@code true} if the move was successful, else {@code false} 831 */ 832 public @NonNull CompletableFuture<Boolean> move( 833 final @NonNull Plot destination, 834 @Nullable PlotPlayer<?> actor, 835 final @NonNull Runnable whenDone 836 ) { 837 return this.move(destination, actor, whenDone, false); 838 } 839 840 /** 841 * Sets a component for a plot to the provided blocks<br> 842 * - E.g. floor, wall, border etc.<br> 843 * - The available components depend on the generator being used<br> 844 * 845 * @param component Component to set 846 * @param blocks Pattern to use the generation 847 * @param actor The actor executing the task 848 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 849 * otherwise writes to the queue but does not enqueue. 850 * @return {@code true} if the component was set successfully, else {@code false} 851 */ 852 public boolean setComponent( 853 final @NonNull String component, 854 final @NonNull Pattern blocks, 855 @Nullable PlotPlayer<?> actor, 856 final @Nullable QueueCoordinator queue 857 ) { 858 final PlotComponentSetEvent event = PlotSquared.get().getEventDispatcher().callComponentSet(this.plot, component, blocks); 859 return this.plot.getManager().setComponent(this.plot.getId(), event.getComponent(), event.getPattern(), actor, queue); 860 } 861 862 /** 863 * Delete a plot (use null for the runnable if you don't need to be notified on completion) 864 * 865 * <p> 866 * Use {@link PlotModificationManager#clear(boolean, boolean, PlotPlayer, Runnable)} to simply clear a plot 867 * </p> 868 * 869 * @param actor The actor executing the task 870 * @param whenDone task to run when plot has been deleted. Nullable 871 * @return {@code true} if the deletion was successful, {@code false} if not 872 * @see PlotSquared#removePlot(Plot, boolean) 873 */ 874 public boolean deletePlot(@Nullable PlotPlayer<?> actor, final Runnable whenDone) { 875 if (!this.plot.hasOwner()) { 876 return false; 877 } 878 final Set<Plot> plots = this.plot.getConnectedPlots(); 879 this.clear(false, true, actor, () -> { 880 for (Plot current : plots) { 881 current.unclaim(); 882 } 883 TaskManager.runTask(whenDone); 884 }); 885 return true; 886 } 887 888 /** 889 * /** 890 * Sets components such as border, wall, floor. 891 * (components are generator specific) 892 * 893 * @param component component to set 894 * @param blocks string of block(s) to set component to 895 * @param actor The player executing the task 896 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 897 * otherwise writes to the queue but does not enqueue. 898 * @return {@code true} if the update was successful, {@code false} if not 899 */ 900 @Deprecated 901 public boolean setComponent( 902 String component, 903 String blocks, 904 @Nullable PlotPlayer<?> actor, 905 @Nullable QueueCoordinator queue 906 ) { 907 final BlockBucket parsed = ConfigurationUtil.BLOCK_BUCKET.parseString(blocks); 908 if (parsed != null && parsed.isEmpty()) { 909 return false; 910 } 911 return this.setComponent(component, parsed.toPattern(), actor, queue); 912 } 913 914 /** 915 * Remove the south road section of a plot<br> 916 * - Used when a plot is merged<br> 917 * 918 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 919 * otherwise writes to the queue but does not enqueue. 920 */ 921 public void removeRoadSouth(final @Nullable QueueCoordinator queue) { 922 if (this.plot.getArea().getType() != PlotAreaType.NORMAL && this.plot 923 .getArea() 924 .getTerrain() == PlotAreaTerrainType.ROAD) { 925 Plot other = this.plot.getRelative(Direction.SOUTH); 926 Location bot = other.getBottomAbs(); 927 Location top = this.plot.getTopAbs(); 928 Location pos1 = Location.at(this.plot.getWorldName(), bot.getX(), plot.getArea().getMinGenHeight(), top.getZ()); 929 Location pos2 = Location.at(this.plot.getWorldName(), top.getX(), plot.getArea().getMaxGenHeight(), bot.getZ()); 930 PlotSquared.platform().regionManager().regenerateRegion(pos1, pos2, true, null); 931 } else if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL) { // no road generated => no road to remove 932 this.plot.getManager().removeRoadSouth(this.plot, queue); 933 } 934 } 935 936 /** 937 * Remove the east road section of a plot<br> 938 * - Used when a plot is merged<br> 939 * 940 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 941 * otherwise writes to the queue but does not enqueue. 942 */ 943 public void removeRoadEast(@Nullable QueueCoordinator queue) { 944 if (this.plot.getArea().getType() != PlotAreaType.NORMAL && this.plot 945 .getArea() 946 .getTerrain() == PlotAreaTerrainType.ROAD) { 947 Plot other = this.plot.getRelative(Direction.EAST); 948 Location bot = other.getBottomAbs(); 949 Location top = this.plot.getTopAbs(); 950 Location pos1 = Location.at(this.plot.getWorldName(), top.getX(), plot.getArea().getMinGenHeight(), bot.getZ()); 951 Location pos2 = Location.at(this.plot.getWorldName(), bot.getX(), plot.getArea().getMaxGenHeight(), top.getZ()); 952 PlotSquared.platform().regionManager().regenerateRegion(pos1, pos2, true, null); 953 } else if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL) { // no road generated => no road to remove 954 this.plot.getArea().getPlotManager().removeRoadEast(this.plot, queue); 955 } 956 } 957 958 /** 959 * Remove the SE road (only effects terrain) 960 * 961 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 962 * otherwise writes to the queue but does not enqueue. 963 */ 964 public void removeRoadSouthEast(@Nullable QueueCoordinator queue) { 965 if (this.plot.getArea().getType() != PlotAreaType.NORMAL && this.plot 966 .getArea() 967 .getTerrain() == PlotAreaTerrainType.ROAD) { 968 Plot other = this.plot.getRelative(1, 1); 969 Location pos1 = this.plot.getTopAbs().add(1, 0, 1); 970 Location pos2 = other.getBottomAbs().subtract(1, 0, 1); 971 PlotSquared.platform().regionManager().regenerateRegion(pos1, pos2, true, null); 972 } else if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL) { // no road generated => no road to remove 973 this.plot.getArea().getPlotManager().removeRoadSouthEast(this.plot, queue); 974 } 975 } 976 977}