diff --git a/Core/src/main/java/com/plotsquared/core/command/Move.java b/Core/src/main/java/com/plotsquared/core/command/Move.java index 161d129913..0a4029889a 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Move.java +++ b/Core/src/main/java/com/plotsquared/core/command/Move.java @@ -20,12 +20,16 @@ import com.google.inject.Inject; import com.plotsquared.core.configuration.caption.TranslatableCaption; +import com.plotsquared.core.events.PlotMoveEvent; +import com.plotsquared.core.events.Result; import com.plotsquared.core.location.Location; import com.plotsquared.core.permissions.Permission; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.PlotArea; +import com.plotsquared.core.plot.PlotId; import com.plotsquared.core.plot.world.PlotAreaManager; +import com.plotsquared.core.util.EventDispatcher; import com.plotsquared.core.util.task.RunnableVal2; import com.plotsquared.core.util.task.RunnableVal3; import net.kyori.adventure.text.Component; @@ -43,10 +47,12 @@ public class Move extends SubCommand { private final PlotAreaManager plotAreaManager; + private final EventDispatcher eventDispatcher; @Inject - public Move(final @NonNull PlotAreaManager plotAreaManager) { + public Move(final @NonNull PlotAreaManager plotAreaManager, final @NonNull EventDispatcher eventDispatcher) { this.plotAreaManager = plotAreaManager; + this.eventDispatcher = eventDispatcher; } @Override @@ -68,40 +74,54 @@ public CompletableFuture execute( boolean override = false; if (args.length == 2 && args[1].equalsIgnoreCase("-f")) { args = new String[]{args[0]}; - override = true; + override = player.hasPermission(Permission.PERMISSION_ADMIN); // Only allow force with admin permission } if (args.length != 1) { sendUsage(player); return CompletableFuture.completedFuture(false); } PlotArea area = this.plotAreaManager.getPlotAreaByString(args[0]); - Plot plot2; + Plot tmpTargetPlot; if (area == null) { - plot2 = Plot.getPlotFromString(player, args[0], true); - if (plot2 == null) { + tmpTargetPlot = Plot.getPlotFromString(player, args[0], true); + if (tmpTargetPlot == null) { return CompletableFuture.completedFuture(false); } } else { - plot2 = area.getPlotAbs(plot1.getId()); + tmpTargetPlot = area.getPlotAbs(plot1.getId()); } - if (plot1.equals(plot2)) { + final PlotMoveEvent moveEvent = this.eventDispatcher.callMove(player, plot1, tmpTargetPlot); + final Plot targetPlot = moveEvent.destination(); + if (!override) { + override = moveEvent.getEventResult() == Result.FORCE; + } + + if (moveEvent.getEventResult() == Result.DENY) { + if (moveEvent.sendErrorMessage()) { + player.sendMessage(TranslatableCaption.of("move.event_cancelled")); + } + return CompletableFuture.completedFuture(false); + } + + if (plot1.equals(targetPlot)) { player.sendMessage(TranslatableCaption.of("invalid.origin_cant_be_target")); return CompletableFuture.completedFuture(false); } - if (!plot1.getArea().isCompatible(plot2.getArea()) && (!override || !player.hasPermission(Permission.PERMISSION_ADMIN))) { + if (!plot1.getArea().isCompatible(targetPlot.getArea()) && !override) { player.sendMessage(TranslatableCaption.of("errors.plotworld_incompatible")); return CompletableFuture.completedFuture(false); } - if (plot1.isMerged() || plot2.isMerged()) { + if (plot1.isMerged() || targetPlot.isMerged()) { player.sendMessage(TranslatableCaption.of("move.move_merged")); return CompletableFuture.completedFuture(false); } // Set strings here as the plot objects are mutable (the PlotID changes after being moved). + PlotId oldPlotId = PlotId.of(plot1.getId().getX(), plot1.getId().getY()); String p1 = plot1.toString(); - String p2 = plot2.toString(); + String p2 = targetPlot.toString(); - return plot1.getPlotModificationManager().move(plot2, player, () -> { + return plot1.getPlotModificationManager().move(targetPlot, player, () -> { }, false).thenApply(result -> { if (result) { player.sendMessage( @@ -111,6 +131,7 @@ public CompletableFuture execute( .tag("target", Tag.inserting(Component.text(p2))) .build() ); + this.eventDispatcher.callPostMove(player, oldPlotId, targetPlot); return true; } else { player.sendMessage(TranslatableCaption.of("move.requires_unowned")); diff --git a/Core/src/main/java/com/plotsquared/core/command/Swap.java b/Core/src/main/java/com/plotsquared/core/command/Swap.java index e56def26f3..332a6975a4 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Swap.java +++ b/Core/src/main/java/com/plotsquared/core/command/Swap.java @@ -18,11 +18,15 @@ */ package com.plotsquared.core.command; +import com.google.inject.Inject; import com.plotsquared.core.configuration.caption.TranslatableCaption; +import com.plotsquared.core.events.PlotSwapEvent; +import com.plotsquared.core.events.Result; import com.plotsquared.core.location.Location; import com.plotsquared.core.permissions.Permission; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.util.EventDispatcher; import com.plotsquared.core.util.task.RunnableVal2; import com.plotsquared.core.util.task.RunnableVal3; import net.kyori.adventure.text.Component; @@ -38,6 +42,9 @@ requiredType = RequiredType.PLAYER) public class Swap extends SubCommand { + @Inject + private EventDispatcher eventDispatcher; + @Override public CompletableFuture execute( PlotPlayer player, String[] args, @@ -45,41 +52,49 @@ public CompletableFuture execute( RunnableVal2 whenDone ) { Location location = player.getLocation(); - Plot plot1 = location.getPlotAbs(); - if (plot1 == null) { + Plot plot = location.getPlotAbs(); + if (plot == null) { player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); return CompletableFuture.completedFuture(false); } - if (!plot1.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN)) { - player.sendMessage(TranslatableCaption.of("permission.no_plot_perms")); - return CompletableFuture.completedFuture(false); - } if (args.length != 1) { sendUsage(player); return CompletableFuture.completedFuture(false); } - Plot plot2 = Plot.getPlotFromString(player, args[0], true); - if (plot2 == null) { + final Plot plotArg = Plot.getPlotFromString(player, args[0], true); + if (plotArg == null) { + return CompletableFuture.completedFuture(false); + } + final PlotSwapEvent event = this.eventDispatcher.callSwap(player, plot, plotArg); + if (event.getEventResult() == Result.DENY) { + if (event.sendErrorMessage()) { + player.sendMessage(TranslatableCaption.of("swap.event_cancelled")); + } + return CompletableFuture.completedFuture(false); + } + if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN) && event.getEventResult() != Result.FORCE) { + player.sendMessage(TranslatableCaption.of("permission.no_plot_perms")); return CompletableFuture.completedFuture(false); } - if (plot1.equals(plot2)) { + final Plot target = event.target(); + if (plot.equals(target)) { player.sendMessage(TranslatableCaption.of("invalid.origin_cant_be_target")); return CompletableFuture.completedFuture(false); } - if (!plot1.getArea().isCompatible(plot2.getArea())) { + if (!plot.getArea().isCompatible(target.getArea())) { player.sendMessage(TranslatableCaption.of("errors.plotworld_incompatible")); return CompletableFuture.completedFuture(false); } - if (plot1.isMerged() || plot2.isMerged()) { + if (plot.isMerged() || target.isMerged()) { player.sendMessage(TranslatableCaption.of("swap.swap_merged")); return CompletableFuture.completedFuture(false); } // Set strings here as the plot objects are mutable (the PlotID changes after being moved). - String p1 = plot1.toString(); - String p2 = plot2.toString(); + String p1 = plot.toString(); + String p2 = target.toString(); - return plot1.getPlotModificationManager().move(plot2, player, () -> { + return plot.getPlotModificationManager().move(target, player, () -> { }, true).thenApply(result -> { if (result) { player.sendMessage( @@ -89,6 +104,7 @@ public CompletableFuture execute( .tag("target", Tag.inserting(Component.text(p2))) .build() ); + this.eventDispatcher.callPostSwap(player, plot, target); return true; } else { player.sendMessage(TranslatableCaption.of("swap.swap_overlap")); diff --git a/Core/src/main/java/com/plotsquared/core/events/PlotMoveEvent.java b/Core/src/main/java/com/plotsquared/core/events/PlotMoveEvent.java new file mode 100644 index 0000000000..40a52860c6 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/events/PlotMoveEvent.java @@ -0,0 +1,119 @@ +/* + * 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.events; + +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.plot.PlotId; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * Called when a {@link PlotPlayer} attempts to move a {@link Plot} to another {@link Plot}. + * + *
+ *
    + *
  • {@link #getPlotPlayer()} is the initiator of the move action (most likely the command executor)
  • + *
  • {@link #getPlot()} is the plot to be moved
  • + *
  • {@link #destination()} is the plot, where the plot will be moved to.
  • + *
+ * + * @see com.plotsquared.core.command.Move + * @since TODO + */ +public class PlotMoveEvent extends PlotPlayerEvent implements CancellablePlotEvent { + + private Plot destination; + private boolean sendErrorMessage = true; + private Result result = Result.ACCEPT; + + public PlotMoveEvent(final PlotPlayer initiator, final Plot plot, final Plot destination) { + super(initiator, plot); + this.destination = destination; + } + + /** + * Set the new {@link Plot} to where the plot should be moved to. + * + * @param destination The plot representing the new location. + * @since TODO + */ + public void setDestination(@NonNull final Plot destination) { + this.destination = Objects.requireNonNull(destination); + } + + /** + * Set the new destination based off their X and Y coordinates. Calls {@link #setDestination(Plot)} while using the + * {@link com.plotsquared.core.plot.PlotArea} provided by the current {@link #destination()}. + *

+ * Note: the coordinates are not minecraft world coordinates, but the underlying {@link PlotId}s coordinates. + * + * @param x The X coordinate of the {@link PlotId} + * @param y The Y coordinate of the {@link PlotId} + * @since TODO + */ + public void setDestination(final int x, final int y) { + this.destination = Objects.requireNonNull(this.destination.getArea()).getPlot(PlotId.of(x, y)); + } + + /** + * Set whether to send a generic message to the user ({@code Move was cancelled by an external plugin}). If set to {@code + * false}, make sure to send a message to the player yourself to avoid confusion. + * + * @param sendErrorMessage {@code true} if PlotSquared should send a generic error message to the player. + * @since TODO + */ + public void setSendErrorMessage(final boolean sendErrorMessage) { + this.sendErrorMessage = sendErrorMessage; + } + + /** + * @return The destination for the plot to be moved to. + * @since TODO + */ + public Plot destination() { + return destination; + } + + /** + * @return {@code true} if PlotSquared should send a generic error message to the player. + * @since TODO + */ + public boolean sendErrorMessage() { + return sendErrorMessage; + } + + /** + * {@inheritDoc} + */ + @Override + public Result getEventResult() { + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void setEventResult(Result eventResult) { + this.result = Objects.requireNonNull(eventResult); + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/events/PlotSwapEvent.java b/Core/src/main/java/com/plotsquared/core/events/PlotSwapEvent.java new file mode 100644 index 0000000000..17d284c4b0 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/events/PlotSwapEvent.java @@ -0,0 +1,125 @@ +/* + * 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.events; + +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.plot.PlotId; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Objects; + +/** + * Called when a player swaps {@link #getPlot() their Plot} with {@link #target() another Plot}. + * + * @see com.plotsquared.core.command.Swap + * @since TODO + */ +public class PlotSwapEvent extends PlotPlayerEvent implements CancellablePlotEvent { + + private Plot target; + private boolean sendErrorMessage = true; + private Result result = Result.ACCEPT; + + public PlotSwapEvent(final PlotPlayer plotPlayer, final Plot plot, final Plot target) { + super(plotPlayer, plot); + this.target = target; + } + + /** + * Set the plot which should be swapped with {@link #getPlot()}. + * + * @param target The target swapping plot. + * @since TODO + */ + public void setTarget(@NonNull final Plot target) { + this.target = Objects.requireNonNull(target); + } + + /** + * Set the new destination based off their X and Y coordinates. Calls {@link #setTarget(Plot)} while using the + * {@link com.plotsquared.core.plot.PlotArea} provided by the current {@link #target()}. + *

+ * Note: the coordinates are not minecraft world coordinates, but the underlying {@link PlotId}s coordinates. + * + * @param x The X coordinate of the {@link PlotId} + * @param y The Y coordinate of the {@link PlotId} + * @since TODO + */ + public void setTarget(final int x, final int y) { + this.target = Objects.requireNonNull(this.target.getArea()).getPlot(PlotId.of(x, y)); + } + + /** + * Set whether to send a generic message to the user ({@code Swap was cancelled by an external plugin}). If set to {@code + * false}, make sure to send a message to the player yourself to avoid confusion. + * + * @param sendErrorMessage {@code true} if PlotSquared should send a generic error message to the player. + * @since TODO + */ + public void setSendErrorMessage(final boolean sendErrorMessage) { + this.sendErrorMessage = sendErrorMessage; + } + + /** + * The target plot to swap with. + * + * @return The plot. + * @since TODO + */ + public Plot target() { + return target; + } + + /** + * @return {@code true} if PlotSquared should send a generic error message to the player. + * @since TODO + */ + public boolean sendErrorMessage() { + return sendErrorMessage; + } + + /** + * The plot issuing the swap with {@link #target()}. + * + * @return The plot. + */ + @Override + public Plot getPlot() { + return super.getPlot(); // delegate for overriding the documentation + } + + /** + * {@inheritDoc} + */ + @Override + public @Nullable Result getEventResult() { + return this.result; + } + + /** + * {@inheritDoc} + */ + @Override + public void setEventResult(@Nullable final Result eventResult) { + this.result = eventResult; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/events/post/PostPlotMoveEvent.java b/Core/src/main/java/com/plotsquared/core/events/post/PostPlotMoveEvent.java new file mode 100644 index 0000000000..bc525ce612 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/events/post/PostPlotMoveEvent.java @@ -0,0 +1,49 @@ +/* + * 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.events.post; + +import com.plotsquared.core.events.PlotPlayerEvent; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.plot.PlotId; + +/** + * Called after a plot move was performed and succeeded. + * + * @see com.plotsquared.core.events.PlotMoveEvent + * @since TODO + */ +public class PostPlotMoveEvent extends PlotPlayerEvent { + + private final PlotId oldPlot; + + public PostPlotMoveEvent(final PlotPlayer initiator, final PlotId oldPlot, final Plot plot) { + super(initiator, plot); + this.oldPlot = oldPlot; + } + + /** + * @return The id of the old plot location. + * @since TODO + */ + public PlotId oldPlot() { + return oldPlot; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/events/post/PostPlotSwapEvent.java b/Core/src/main/java/com/plotsquared/core/events/post/PostPlotSwapEvent.java new file mode 100644 index 0000000000..9783bbf0d2 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/events/post/PostPlotSwapEvent.java @@ -0,0 +1,65 @@ +/* + * 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.events.post; + +import com.plotsquared.core.events.PlotPlayerEvent; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; + +/** + * Called after a plot swap was performed and succeeded. + *

+ * Note: {@link Plot#getId()} of the {@link #target() target Plot} will be the {@link com.plotsquared.core.plot.PlotId} + * of the {@link #getPlot() issuing plot} at this event stage (as the swap already happened). If you need the PlotId of the + * origin plot (where the player was standing on when issuing the command, e.g.) before the swap, use the {@link #target()} + * Plot, as the IDs in the plot objects were mutated. (The target plot is now the present origin plot) + * + * @see com.plotsquared.core.events.PlotSwapEvent + * @since TODO + */ +public class PostPlotSwapEvent extends PlotPlayerEvent { + + private final Plot target; + + public PostPlotSwapEvent(final PlotPlayer initiator, final Plot plot, final Plot target) { + super(initiator, plot); + this.target = target; + } + + /** + * The plot that initiated the plot swap. + * + * @return The plot. + */ + @Override + public Plot getPlot() { + return super.getPlot(); // delegate for overriding the documentation + } + + /** + * The plot that was swapped with {@link #getPlot()}. (The command argument) + * + * @return The plot. + * @since TODO + */ + public Plot target() { + return target; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java b/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java index e20957b28b..e5a8d4b106 100644 --- a/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java +++ b/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java @@ -45,7 +45,9 @@ import com.plotsquared.core.events.PlotFlagAddEvent; import com.plotsquared.core.events.PlotFlagRemoveEvent; import com.plotsquared.core.events.PlotMergeEvent; +import com.plotsquared.core.events.PlotMoveEvent; import com.plotsquared.core.events.PlotRateEvent; +import com.plotsquared.core.events.PlotSwapEvent; import com.plotsquared.core.events.PlotUnlinkEvent; import com.plotsquared.core.events.RemoveRoadEntityEvent; import com.plotsquared.core.events.TeleportCause; @@ -55,6 +57,8 @@ import com.plotsquared.core.events.post.PostPlotClearEvent; import com.plotsquared.core.events.post.PostPlotDeleteEvent; import com.plotsquared.core.events.post.PostPlotMergeEvent; +import com.plotsquared.core.events.post.PostPlotMoveEvent; +import com.plotsquared.core.events.post.PostPlotSwapEvent; import com.plotsquared.core.events.post.PostPlotUnlinkEvent; import com.plotsquared.core.listener.PlayerBlockEventType; import com.plotsquared.core.location.Direction; @@ -336,6 +340,26 @@ public void callPostPlayerBuyPlot(PlotPlayer player, OfflinePlotPlayer previo eventBus.post(new PostPlayerBuyPlotEvent(player, previousOwner, plot, price)); } + public PlotMoveEvent callMove(PlotPlayer initiator, Plot from, Plot destination) { + PlotMoveEvent event = new PlotMoveEvent(initiator, from, destination); + callEvent(event); + return event; + } + + public void callPostMove(PlotPlayer initiator, PlotId old, Plot destination) { + callEvent(new PostPlotMoveEvent(initiator, old, destination)); + } + + public PlotSwapEvent callSwap(PlotPlayer initiator, Plot plot, Plot target) { + PlotSwapEvent event = new PlotSwapEvent(initiator, plot, target); + callEvent(event); + return event; + } + + public void callPostSwap(PlotPlayer initiator, Plot plot, Plot target) { + callEvent(new PostPlotSwapEvent(initiator, plot, target)); + } + public void doJoinTask(final PlotPlayer player) { if (player == null) { return; //possible future warning message to figure out where we are retrieving null diff --git a/Core/src/main/resources/lang/messages_en.json b/Core/src/main/resources/lang/messages_en.json index 5ea33d6a6f..73682c91eb 100644 --- a/Core/src/main/resources/lang/messages_en.json +++ b/Core/src/main/resources/lang/messages_en.json @@ -6,6 +6,7 @@ "move.move_merged": "Merged plots may not be moved. Please unmerge the plot before performing the move.", "move.copy_success": "Successfully copied plots -> ", "move.requires_unowned": "The location specified is already occupied.", + "move.event_cancelled": "Move was cancelled by an external plugin", "debug.requires_unmerged": "The plot cannot be merged.", "debug.debug_header": " Debug Information\n", "debug.debug_section": ">> ", @@ -65,6 +66,7 @@ "swap.swap_overlap": "The proposed areas are not allowed to overlap.", "swap.swap_success": "Successfully swapped plots -> ", "swap.swap_merged": "Merged plots may not be swapped. Please unmerge the plots before performing the swap.", + "swap.event_cancelled": "Swap was cancelled by an external plugin", "swap.progress_region1_copy": "Current region 1 copy progress: %", "swap.progress_region2_copy": "Current region 2 copy progress: %", "swap.progress_region1_paste": "Current region 1 paste progress: %",