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