/* * PlotSquared, a land and world management plugin for Minecraft. * Copyright (C) IntellectualSites * Copyright (C) IntellectualSites team and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.plotsquared.core.plot; import com.google.inject.Inject; import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.ConfigurationUtil; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.configuration.caption.Caption; import com.plotsquared.core.configuration.caption.LocaleHolder; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.database.DBFunc; import com.plotsquared.core.events.PlotComponentSetEvent; import com.plotsquared.core.events.PlotMergeEvent; import com.plotsquared.core.events.PlotUnlinkEvent; import com.plotsquared.core.events.Result; import com.plotsquared.core.generator.ClassicPlotWorld; import com.plotsquared.core.generator.SquarePlotWorld; import com.plotsquared.core.inject.factory.ProgressSubscriberFactory; import com.plotsquared.core.location.Direction; import com.plotsquared.core.location.Location; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.flag.PlotFlag; import com.plotsquared.core.queue.QueueCoordinator; import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskTime; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockTypes; import net.kyori.adventure.text.minimessage.Template; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; /** * Manager that handles {@link Plot} modifications */ public final class PlotModificationManager { private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlotModificationManager.class.getSimpleName()); private final Plot plot; private final ProgressSubscriberFactory subscriberFactory; @Inject PlotModificationManager(final @NonNull Plot plot) { this.plot = plot; this.subscriberFactory = PlotSquared.platform().injector().getInstance(ProgressSubscriberFactory.class); } /** * Copy a plot to a location, both physically and the settings * * @param destination destination plot * @param actor the actor associated with the copy * @return Future that completes with {@code true} if the copy was successful, else {@code false} */ public CompletableFuture copy(final @NonNull Plot destination, @Nullable PlotPlayer actor) { final CompletableFuture future = new CompletableFuture<>(); final PlotId offset = PlotId.of( destination.getId().getX() - this.plot.getId().getX(), destination.getId().getY() - this.plot.getId().getY() ); final Location db = destination.getBottomAbs(); final Location ob = this.plot.getBottomAbs(); final int offsetX = db.getX() - ob.getX(); final int offsetZ = db.getZ() - ob.getZ(); if (!this.plot.hasOwner()) { TaskManager.runTaskLater(() -> future.complete(false), TaskTime.ticks(1L)); return future; } final Set plots = this.plot.getConnectedPlots(); for (final Plot plot : plots) { final Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); if (other.hasOwner()) { TaskManager.runTaskLater(() -> future.complete(false), TaskTime.ticks(1L)); return future; } } // world border destination.updateWorldBorder(); // copy data for (final Plot plot : plots) { final Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); other.getPlotModificationManager().create(plot.getOwner(), false); if (!plot.getFlagContainer().getFlagMap().isEmpty()) { final Collection> existingFlags = other.getFlags(); other.getFlagContainer().clearLocal(); other.getFlagContainer().addAll(plot.getFlagContainer().getFlagMap().values()); // Update the database for (final PlotFlag flag : existingFlags) { final PlotFlag newFlag = other.getFlagContainer().queryLocal(flag.getClass()); if (other.getFlagContainer().queryLocal(flag.getClass()) == null) { DBFunc.removeFlag(other, flag); } else { DBFunc.setFlag(other, newFlag); } } } if (plot.isMerged()) { other.setMerged(plot.getMerged()); } if (plot.members != null && !plot.members.isEmpty()) { other.members = plot.members; for (UUID member : plot.members) { DBFunc.setMember(other, member); } } if (plot.trusted != null && !plot.trusted.isEmpty()) { other.trusted = plot.trusted; for (UUID trusted : plot.trusted) { DBFunc.setTrusted(other, trusted); } } if (plot.denied != null && !plot.denied.isEmpty()) { other.denied = plot.denied; for (UUID denied : plot.denied) { DBFunc.setDenied(other, denied); } } } // copy terrain final ArrayDeque regions = new ArrayDeque<>(this.plot.getRegions()); final Runnable run = new Runnable() { @Override public void run() { if (regions.isEmpty()) { final QueueCoordinator queue = plot.getArea().getQueue(); for (final Plot current : plot.getConnectedPlots()) { destination.getManager().claimPlot(current, queue); } if (queue.size() > 0) { queue.enqueue(); } destination.getPlotModificationManager().setSign(); future.complete(true); return; } CuboidRegion region = regions.poll(); Location[] corners = Plot.getCorners(plot.getWorldName(), region); Location pos1 = corners[0]; Location pos2 = corners[1]; Location newPos = pos1.add(offsetX, 0, offsetZ).withWorld(destination.getWorldName()); PlotSquared.platform().regionManager().copyRegion(pos1, pos2, newPos, actor, this); } }; run.run(); return future; } /** * Clear the plot * *

* Use {@link #deletePlot(PlotPlayer, Runnable)} to clear and delete a plot *

* * @param whenDone A runnable to execute when clearing finishes, or null * @see #clear(boolean, boolean, PlotPlayer, Runnable) */ public void clear(final @Nullable Runnable whenDone) { this.clear(false, false, null, whenDone); } /** * Clear the plot * *

* Use {@link #deletePlot(PlotPlayer, Runnable)} to clear and delete a plot *

* * @param checkRunning Whether or not already executing tasks should be checked * @param isDelete Whether or not the plot is being deleted * @param actor The actor clearing the plot * @param whenDone A runnable to execute when clearing finishes, or null */ public boolean clear( final boolean checkRunning, final boolean isDelete, final @Nullable PlotPlayer actor, final @Nullable Runnable whenDone ) { if (checkRunning && this.plot.getRunning() != 0) { return false; } final Set regions = this.plot.getRegions(); final Set plots = this.plot.getConnectedPlots(); final ArrayDeque queue = new ArrayDeque<>(plots); if (isDelete) { this.removeSign(); } PlotUnlinkEvent event = PlotSquared.get().getEventDispatcher() .callUnlink( this.plot.getArea(), this.plot, true, !isDelete, isDelete ? PlotUnlinkEvent.REASON.DELETE : PlotUnlinkEvent.REASON.CLEAR ); if (event.getEventResult() != Result.DENY && this.unlinkPlot(event.isCreateRoad(), event.isCreateSign())) { PlotSquared.get().getEventDispatcher().callPostUnlink(plot, event.getReason()); } final PlotManager manager = this.plot.getArea().getPlotManager(); Runnable run = new Runnable() { @Override public void run() { if (queue.isEmpty()) { Runnable run = () -> { for (CuboidRegion region : regions) { Location[] corners = Plot.getCorners(plot.getWorldName(), region); PlotSquared.platform().regionManager().clearAllEntities(corners[0], corners[1]); } TaskManager.runTask(whenDone); }; QueueCoordinator queue = plot.getArea().getQueue(); for (Plot current : plots) { if (isDelete || !current.hasOwner()) { manager.unClaimPlot(current, null, queue); } else { manager.claimPlot(current, queue); if (plot.getArea() instanceof ClassicPlotWorld cpw) { manager.setComponent(current.getId(), "wall", cpw.WALL_FILLING.toPattern(), actor, queue); } } } if (queue.size() > 0) { queue.setCompleteTask(run); queue.enqueue(); return; } run.run(); return; } Plot current = queue.poll(); current.clearCache(); if (plot.getArea().getTerrain() != PlotAreaTerrainType.NONE) { try { PlotSquared.platform().regionManager().regenerateRegion( current.getBottomAbs(), current.getTopAbs(), false, this ); } catch (UnsupportedOperationException exception) { exception.printStackTrace(); return; } return; } manager.clearPlot(current, this, actor, null); } }; run.run(); return true; } /** * Sets the biome for a plot asynchronously. * * @param biome The biome e.g. "forest" * @param whenDone The task to run when finished, or null */ public void setBiome(final @Nullable BiomeType biome, final @NonNull Runnable whenDone) { final ArrayDeque regions = new ArrayDeque<>(this.plot.getRegions()); final int extendBiome; if (this.plot.getArea() instanceof SquarePlotWorld) { extendBiome = (((SquarePlotWorld) this.plot.getArea()).ROAD_WIDTH > 0) ? 1 : 0; } else { extendBiome = 0; } Runnable run = new Runnable() { @Override public void run() { if (regions.isEmpty()) { TaskManager.runTask(whenDone); return; } CuboidRegion region = regions.poll(); PlotSquared.platform().regionManager().setBiome(region, extendBiome, biome, plot.getArea(), this); } }; run.run(); } /** * Unlink the plot and all connected plots. * * @param createRoad whether to recreate road * @param createSign whether to recreate signs * @return success/!cancelled */ public boolean unlinkPlot(final boolean createRoad, final boolean createSign) { if (!this.plot.isMerged()) { return false; } final Set plots = this.plot.getConnectedPlots(); ArrayList ids = new ArrayList<>(plots.size()); for (Plot current : plots) { current.setHome(null); current.clearCache(); ids.add(current.getId()); } this.plot.clearRatings(); QueueCoordinator queue = this.plot.getArea().getQueue(); if (createSign) { this.removeSign(); } PlotManager manager = this.plot.getArea().getPlotManager(); if (createRoad) { manager.startPlotUnlink(ids, queue); } if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL && createRoad) { for (Plot current : plots) { if (current.isMerged(Direction.EAST)) { manager.createRoadEast(current, queue); if (current.isMerged(Direction.SOUTH)) { manager.createRoadSouth(current, queue); if (current.isMerged(Direction.SOUTHEAST)) { manager.createRoadSouthEast(current, queue); } } } if (current.isMerged(Direction.SOUTH)) { manager.createRoadSouth(current, queue); } } } for (Plot current : plots) { boolean[] merged = new boolean[]{false, false, false, false}; current.setMerged(merged); } if (createSign) { queue.setCompleteTask(() -> TaskManager.runTaskAsync(() -> { for (Plot current : plots) { current.getPlotModificationManager().setSign(PlayerManager.resolveName(current.getOwnerAbs()).getComponent( LocaleHolder.console())); } })); } if (createRoad) { manager.finishPlotUnlink(ids, queue); } if (queue != null) { queue.enqueue(); } return true; } /** * Sets the sign for a plot to a specific name * * @param name name */ public void setSign(final @NonNull String name) { if (!this.plot.isLoaded()) { return; } PlotManager manager = this.plot.getArea().getPlotManager(); if (this.plot.getArea().allowSigns()) { Location location = manager.getSignLoc(this.plot); String id = this.plot.getId().toString(); Caption[] lines = new Caption[]{TranslatableCaption.of("signs.owner_sign_line_1"), TranslatableCaption.of( "signs.owner_sign_line_2"), TranslatableCaption.of("signs.owner_sign_line_3"), TranslatableCaption.of("signs.owner_sign_line_4")}; PlotSquared.platform().worldUtil().setSign(location, lines, Template.of("id", id), Template.of("owner", name)); } } /** * Resend all chunks inside the plot to nearby players
* This should not need to be called */ public void refreshChunks() { final HashSet chunks = new HashSet<>(); for (final CuboidRegion region : this.plot.getRegions()) { for (int x = region.getMinimumPoint().getX() >> 4; x <= region.getMaximumPoint().getX() >> 4; x++) { for (int z = region.getMinimumPoint().getZ() >> 4; z <= region.getMaximumPoint().getZ() >> 4; z++) { if (chunks.add(BlockVector2.at(x, z))) { PlotSquared.platform().worldUtil().refreshChunk(x, z, this.plot.getWorldName()); } } } } } /** * Remove the plot sign if it is set. */ public void removeSign() { PlotManager manager = this.plot.getArea().getPlotManager(); if (!this.plot.getArea().allowSigns()) { return; } Location location = manager.getSignLoc(this.plot); QueueCoordinator queue = PlotSquared.platform().globalBlockQueue().getNewQueue(PlotSquared .platform() .worldUtil() .getWeWorld(this.plot.getWorldName())); queue.setBlock(location.getX(), location.getY(), location.getZ(), BlockTypes.AIR.getDefaultState()); queue.enqueue(); } /** * Sets the plot sign if plot signs are enabled. */ public void setSign() { if (!this.plot.hasOwner()) { this.setSign("unknown"); return; } PlotSquared.get().getImpromptuUUIDPipeline().getSingle( this.plot.getOwnerAbs(), (username, sign) -> this.setSign(username) ); } /** * Register a plot and create it in the database
* - The plot will not be created if the owner is null
* - Any setting from before plot creation will not be saved until the server is stopped properly. i.e. Set any values/options after plot * creation. * * @return {@code true} if plot was created successfully */ public boolean create() { return this.create(this.plot.getOwnerAbs(), true); } /** * Register a plot and create it in the database
* - The plot will not be created if the owner is null
* - Any setting from before plot creation will not be saved until the server is stopped properly. i.e. Set any values/options after plot * creation. * * @param uuid the uuid of the plot owner * @param notify notify * @return {@code true} if plot was created successfully, else {@code false} */ public boolean create(final @NonNull UUID uuid, final boolean notify) { this.plot.setOwnerAbs(uuid); Plot existing = this.plot.getArea().getOwnedPlotAbs(this.plot.getId()); if (existing != null) { throw new IllegalStateException("Plot already exists!"); } if (notify) { Integer meta = (Integer) this.plot.getArea().getMeta("worldBorder"); if (meta != null) { this.plot.updateWorldBorder(); } } this.plot.clearCache(); this.plot.getTrusted().clear(); this.plot.getMembers().clear(); this.plot.getDenied().clear(); this.plot.settings = new PlotSettings(); if (this.plot.getArea().addPlot(this.plot)) { DBFunc.createPlotAndSettings(this.plot, () -> { PlotArea plotworld = plot.getArea(); if (notify && plotworld.isAutoMerge()) { final PlotPlayer player = PlotSquared.platform().playerManager().getPlayerIfExists(uuid); PlotMergeEvent event = PlotSquared.get().getEventDispatcher().callMerge( this.plot, Direction.ALL, Integer.MAX_VALUE, player ); if (event.getEventResult() == Result.DENY) { if (player != null) { player.sendMessage( TranslatableCaption.of("events.event_denied"), Template.of("value", "Auto merge on claim") ); } return; } if (plot.getPlotModificationManager().autoMerge(event.getDir(), event.getMax(), uuid, player, true)) { PlotSquared.get().getEventDispatcher().callPostMerge(player, plot); } } }); return true; } LOGGER.info( "Failed to add plot {} to plot area {}", this.plot.getId().toCommaSeparatedString(), this.plot.getArea().toString() ); return false; } /** * Auto merge a plot in a specific direction. * * @param dir the direction to merge * @param max the max number of merges to do * @param uuid the UUID it is allowed to merge with * @param actor The actor executing the task * @param removeRoads whether to remove roads * @return {@code true} if a merge takes place, else {@code false} */ public boolean autoMerge( final @NonNull Direction dir, int max, final @NonNull UUID uuid, @Nullable PlotPlayer actor, final boolean removeRoads ) { //Ignore merging if there is no owner for the plot if (!this.plot.hasOwner()) { return false; } Set connected = this.plot.getConnectedPlots(); HashSet merged = connected.stream().map(Plot::getId).collect(Collectors.toCollection(HashSet::new)); ArrayDeque frontier = new ArrayDeque<>(connected); Plot current; boolean toReturn = false; HashSet visited = new HashSet<>(); QueueCoordinator queue = this.plot.getArea().getQueue(); while ((current = frontier.poll()) != null && max >= 0) { if (visited.contains(current)) { continue; } visited.add(current); Set plots; if ((dir == Direction.ALL || dir == Direction.NORTH) && !current.isMerged(Direction.NORTH)) { Plot other = current.getRelative(Direction.NORTH); if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { current.mergePlot(other, removeRoads, queue); merged.add(current.getId()); merged.add(other.getId()); toReturn = true; if (removeRoads) { ArrayList ids = new ArrayList<>(); ids.add(current.getId()); ids.add(other.getId()); this.plot.getManager().finishPlotMerge(ids, queue); } } } if (max >= 0 && (dir == Direction.ALL || dir == Direction.EAST) && !current.isMerged(Direction.EAST)) { Plot other = current.getRelative(Direction.EAST); if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { current.mergePlot(other, removeRoads, queue); merged.add(current.getId()); merged.add(other.getId()); toReturn = true; if (removeRoads) { ArrayList ids = new ArrayList<>(); ids.add(current.getId()); ids.add(other.getId()); this.plot.getManager().finishPlotMerge(ids, queue); } } } if (max >= 0 && (dir == Direction.ALL || dir == Direction.SOUTH) && !current.isMerged(Direction.SOUTH)) { Plot other = current.getRelative(Direction.SOUTH); if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { current.mergePlot(other, removeRoads, queue); merged.add(current.getId()); merged.add(other.getId()); toReturn = true; if (removeRoads) { ArrayList ids = new ArrayList<>(); ids.add(current.getId()); ids.add(other.getId()); this.plot.getManager().finishPlotMerge(ids, queue); } } } if (max >= 0 && (dir == Direction.ALL || dir == Direction.WEST) && !current.isMerged(Direction.WEST)) { Plot other = current.getRelative(Direction.WEST); if (other != null && other.isOwner(uuid) && (other.getBasePlot(false).equals(current.getBasePlot(false)) || (plots = other.getConnectedPlots()).size() <= max && frontier.addAll(plots) && (max -= plots.size()) != -1)) { current.mergePlot(other, removeRoads, queue); merged.add(current.getId()); merged.add(other.getId()); toReturn = true; if (removeRoads) { ArrayList ids = new ArrayList<>(); ids.add(current.getId()); ids.add(other.getId()); this.plot.getManager().finishPlotMerge(ids, queue); } } } } if (actor != null && Settings.QUEUE.NOTIFY_PROGRESS) { queue.addProgressSubscriber(subscriberFactory.createWithActor(actor)); } if (queue.size() > 0) { queue.enqueue(); } visited.forEach(Plot::clearCache); return toReturn; } /** * Moves a plot physically, as well as the corresponding settings. * * @param destination Plot moved to * @param actor The actor executing the task * @param whenDone task when done * @param allowSwap whether to swap plots * @return {@code true} if the move was successful, else {@code false} */ public @NonNull CompletableFuture move( final @NonNull Plot destination, final @Nullable PlotPlayer actor, final @NonNull Runnable whenDone, final boolean allowSwap ) { final PlotId offset = PlotId.of( destination.getId().getX() - this.plot.getId().getX(), destination.getId().getY() - this.plot.getId().getY() ); Location db = destination.getBottomAbs(); Location ob = this.plot.getBottomAbs(); final int offsetX = db.getX() - ob.getX(); final int offsetZ = db.getZ() - ob.getZ(); if (!this.plot.hasOwner()) { TaskManager.runTaskLater(whenDone, TaskTime.ticks(1L)); return CompletableFuture.completedFuture(false); } AtomicBoolean occupied = new AtomicBoolean(false); Set plots = this.plot.getConnectedPlots(); for (Plot plot : plots) { Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); if (other.hasOwner()) { if (!allowSwap) { TaskManager.runTaskLater(whenDone, TaskTime.ticks(1L)); return CompletableFuture.completedFuture(false); } occupied.set(true); } else { plot.getPlotModificationManager().removeSign(); } } // world border destination.updateWorldBorder(); final ArrayDeque regions = new ArrayDeque<>(this.plot.getRegions()); // move / swap data final PlotArea originArea = this.plot.getArea(); final Iterator plotIterator = plots.iterator(); CompletableFuture future = null; if (plotIterator.hasNext()) { while (plotIterator.hasNext()) { final Plot plot = plotIterator.next(); final Plot other = plot.getRelative(destination.getArea(), offset.getX(), offset.getY()); final CompletableFuture swapResult = plot.swapData(other); if (future == null) { future = swapResult; } else { future = future.thenCombine(swapResult, (fn, th) -> fn); } } } else { future = CompletableFuture.completedFuture(true); } return future.thenApply(result -> { if (!result) { return false; } // copy terrain if (occupied.get()) { new Runnable() { @Override public void run() { if (regions.isEmpty()) { // Update signs destination.getPlotModificationManager().setSign(); setSign(); // Run final tasks TaskManager.runTask(whenDone); } else { CuboidRegion region = regions.poll(); Location[] corners = Plot.getCorners(plot.getWorldName(), region); Location pos1 = corners[0]; Location pos2 = corners[1]; Location pos3 = pos1.add(offsetX, 0, offsetZ).withWorld(destination.getWorldName()); PlotSquared.platform().regionManager().swap(pos1, pos2, pos3, actor, this); } } }.run(); } else { new Runnable() { @Override public void run() { if (regions.isEmpty()) { Plot plot = destination.getRelative(0, 0); Plot originPlot = originArea.getPlotAbs(PlotId.of( plot.getId().getX() - offset.getX(), plot.getId().getY() - offset.getY() )); final Runnable clearDone = () -> { QueueCoordinator queue = PlotModificationManager.this.plot.getArea().getQueue(); for (final Plot current : plot.getConnectedPlots()) { PlotModificationManager.this.plot.getManager().claimPlot(current, queue); } if (queue.size() > 0) { queue.enqueue(); } plot.getPlotModificationManager().setSign(); TaskManager.runTask(whenDone); }; if (originPlot != null) { originPlot.getPlotModificationManager().clear(false, true, actor, clearDone); } else { clearDone.run(); } return; } final Runnable task = this; CuboidRegion region = regions.poll(); Location[] corners = Plot.getCorners( PlotModificationManager.this.plot.getWorldName(), region ); final Location pos1 = corners[0]; final Location pos2 = corners[1]; Location newPos = pos1.add(offsetX, 0, offsetZ).withWorld(destination.getWorldName()); PlotSquared.platform().regionManager().copyRegion(pos1, pos2, newPos, actor, task); } }.run(); } return true; }); } /** * Unlink a plot and remove the roads * * @return {@code true} if plot was linked * @see #unlinkPlot(boolean, boolean) */ public boolean unlink() { return this.unlinkPlot(true, true); } /** * Swap the plot contents and settings with another location
* - The destination must correspond to a valid plot of equal dimensions * * @param destination The other plot to swap with * @param actor The actor executing the task * @param whenDone A task to run when finished, or null * @return Future that completes with {@code true} if the swap was successful, else {@code false} */ public @NonNull CompletableFuture swap( final @NonNull Plot destination, @Nullable PlotPlayer actor, final @NonNull Runnable whenDone ) { return this.move(destination, actor, whenDone, true); } /** * Moves the plot to an empty location
* - The location must be empty * * @param destination Where to move the plot * @param actor The actor executing the task * @param whenDone A task to run when done, or null * @return Future that completes with {@code true} if the move was successful, else {@code false} */ public @NonNull CompletableFuture move( final @NonNull Plot destination, @Nullable PlotPlayer actor, final @NonNull Runnable whenDone ) { return this.move(destination, actor, whenDone, false); } /** * Sets a component for a plot to the provided blocks
* - E.g. floor, wall, border etc.
* - The available components depend on the generator being used
* * @param component Component to set * @param blocks Pattern to use the generation * @param actor The actor executing the task * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, * otherwise writes to the queue but does not enqueue. * @return {@code true} if the component was set successfully, else {@code false} */ public boolean setComponent( final @NonNull String component, final @NonNull Pattern blocks, @Nullable PlotPlayer actor, final @Nullable QueueCoordinator queue ) { final PlotComponentSetEvent event = PlotSquared.get().getEventDispatcher().callComponentSet(this.plot, component, blocks); return this.plot.getManager().setComponent(this.plot.getId(), event.getComponent(), event.getPattern(), actor, queue); } /** * Delete a plot (use null for the runnable if you don't need to be notified on completion) * *

* Use {@link PlotModificationManager#clear(boolean, boolean, PlotPlayer, Runnable)} to simply clear a plot *

* * @param actor The actor executing the task * @param whenDone task to run when plot has been deleted. Nullable * @return {@code true} if the deletion was successful, {@code false} if not * @see PlotSquared#removePlot(Plot, boolean) */ public boolean deletePlot(@Nullable PlotPlayer actor, final Runnable whenDone) { if (!this.plot.hasOwner()) { return false; } final Set plots = this.plot.getConnectedPlots(); this.clear(false, true, actor, () -> { for (Plot current : plots) { current.unclaim(); } TaskManager.runTask(whenDone); }); return true; } /** * /** * Sets components such as border, wall, floor. * (components are generator specific) * * @param component component to set * @param blocks string of block(s) to set component to * @param actor The player executing the task * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, * otherwise writes to the queue but does not enqueue. * @return {@code true} if the update was successful, {@code false} if not */ @Deprecated public boolean setComponent( String component, String blocks, @Nullable PlotPlayer actor, @Nullable QueueCoordinator queue ) { final BlockBucket parsed = ConfigurationUtil.BLOCK_BUCKET.parseString(blocks); if (parsed != null && parsed.isEmpty()) { return false; } return this.setComponent(component, parsed.toPattern(), actor, queue); } /** * Remove the south road section of a plot
* - Used when a plot is merged
* * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, * otherwise writes to the queue but does not enqueue. */ public void removeRoadSouth(final @Nullable QueueCoordinator queue) { if (this.plot.getArea().getType() != PlotAreaType.NORMAL && this.plot .getArea() .getTerrain() == PlotAreaTerrainType.ROAD) { Plot other = this.plot.getRelative(Direction.SOUTH); Location bot = other.getBottomAbs(); Location top = this.plot.getTopAbs(); Location pos1 = Location.at(this.plot.getWorldName(), bot.getX(), plot.getArea().getMinGenHeight(), top.getZ()); Location pos2 = Location.at(this.plot.getWorldName(), top.getX(), plot.getArea().getMaxGenHeight(), bot.getZ()); PlotSquared.platform().regionManager().regenerateRegion(pos1, pos2, true, null); } else if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL) { // no road generated => no road to remove this.plot.getManager().removeRoadSouth(this.plot, queue); } } /** * Remove the east road section of a plot
* - Used when a plot is merged
* * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, * otherwise writes to the queue but does not enqueue. */ public void removeRoadEast(@Nullable QueueCoordinator queue) { if (this.plot.getArea().getType() != PlotAreaType.NORMAL && this.plot .getArea() .getTerrain() == PlotAreaTerrainType.ROAD) { Plot other = this.plot.getRelative(Direction.EAST); Location bot = other.getBottomAbs(); Location top = this.plot.getTopAbs(); Location pos1 = Location.at(this.plot.getWorldName(), top.getX(), plot.getArea().getMinGenHeight(), bot.getZ()); Location pos2 = Location.at(this.plot.getWorldName(), bot.getX(), plot.getArea().getMaxGenHeight(), top.getZ()); PlotSquared.platform().regionManager().regenerateRegion(pos1, pos2, true, null); } else if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL) { // no road generated => no road to remove this.plot.getArea().getPlotManager().removeRoadEast(this.plot, queue); } } /** * Remove the SE road (only effects terrain) * * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, * otherwise writes to the queue but does not enqueue. */ public void removeRoadSouthEast(@Nullable QueueCoordinator queue) { if (this.plot.getArea().getType() != PlotAreaType.NORMAL && this.plot .getArea() .getTerrain() == PlotAreaTerrainType.ROAD) { Plot other = this.plot.getRelative(1, 1); Location pos1 = this.plot.getTopAbs().add(1, 0, 1); Location pos2 = other.getBottomAbs().subtract(1, 0, 1); PlotSquared.platform().regionManager().regenerateRegion(pos1, pos2, true, null); } else if (this.plot.getArea().getTerrain() != PlotAreaTerrainType.ALL) { // no road generated => no road to remove this.plot.getArea().getPlotManager().removeRoadSouthEast(this.plot, queue); } } }