From a34c63b7e2501f143147a26e2e8f8e43b131e13e Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Mon, 29 Apr 2024 19:29:55 +0200 Subject: [PATCH] Implements #4 --- README.md | 49 +-- .../placeholdersigns/PlaceholderSigns.java | 2 + .../command/CopySignTextCommand.java | 99 ++++++ .../command/EditSignCommand.java | 17 +- .../config/PlaceholderSignsPermission.java | 47 +++ .../container/SignTextCopyRequest.java | 20 ++ .../PlaceholderSignRequestHandler.java | 25 +- .../listener/SignClickListener.java | 290 ++++++++++++++---- .../listener/SignTextListener.java | 3 +- .../placeholdersigns/util/SignSideHelper.java | 39 +++ .../util/TabCompleteHelper.java | 53 ++++ src/main/resources/plugin.yml | 32 +- 12 files changed, 567 insertions(+), 109 deletions(-) create mode 100644 src/main/java/net/knarcraft/placeholdersigns/command/CopySignTextCommand.java create mode 100644 src/main/java/net/knarcraft/placeholdersigns/config/PlaceholderSignsPermission.java create mode 100644 src/main/java/net/knarcraft/placeholdersigns/container/SignTextCopyRequest.java create mode 100644 src/main/java/net/knarcraft/placeholdersigns/util/SignSideHelper.java create mode 100644 src/main/java/net/knarcraft/placeholdersigns/util/TabCompleteHelper.java diff --git a/README.md b/README.md index 610483e..e3f757b 100644 --- a/README.md +++ b/README.md @@ -8,32 +8,39 @@ PlaceholderAPI, the location of the sign, and the lines containing placeholders set pace by using the saved original lines, and letting PlaceholderAPI replace them. Any color, formatting or RGB color codes in the original text will be converted each time the sign is updated. -The /editSign command is basically just a command to allow placeholders that won't fit on a sign to be used. As an +The /setSignLine command is basically just a command to allow placeholders that won't fit on a sign to be used. As an additional benefit, formatting, color and RGB color codes are automatically converted whenever the command is used to change sign text. -Note that when clicking a sign after using /editSign, a SignChangeEvent is triggered. This means that the sign text -won't be changed unless the player passes all world protection checks. - ## Commands -| Command | Arguments | Description | -|--------------|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| /setSignLine | \ \ \ ... | Sets the text of the sign line (1-4) to the given input. Then right-click the sign to update. | -| /viewSign | \[raw true/false] \[placeholders true/false] | Shows the full contents and details of the sign you are currently looking at. If "raw" is true, formatting codes are displayed. If placeholders is true, stored placeholders are displayed. | -| /copySign | | Copies the sign you are currently looking at to another sign, including placeholders, formatting codes, dye and waxed state. | -| /unWaxSign | | Removes the wax from the sign you are currently looking at. | +Note that commands have some restrictions in place, so giving the weakest permissions to any player should be safe: + +- Players cannot create placeholder signs with any commands without the `placeholdersigns.placeholder` permission. +- Players cannot cause a destructive action on a waxed sign without the correct bypass permission. +- All sign changes are run through the SignChangeEvent, so the player cannot edit a sign if any protection plugin would + prevent them from editing the sign normally. + +| Command | Arguments | Description | +|---------------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| /setSignLine | \ \ \ ... | Sets the text of the sign line (1-4) to the given input. Then right-click the sign to update. | +| /viewSign | \[raw true/false] \[placeholders true/false] | Shows the full contents and details of the sign you are currently looking at. If "raw" is true, formatting codes are displayed. If placeholders is true, stored placeholders are displayed. | +| /copySign | | Copies the sign you are currently looking at to another sign, including placeholders, formatting codes, dye and waxed state. | +| /copySignText | \[side] \[source line] \[target line] | Copies the text of the sign you are currently looking at to another sign, including placeholders and formatting codes. If no argument is given, both sides are copied, and the front text is copies to the side of the target sign you click. If a side is specified, the text of that side is copied, and pasted to the sign side you click (use `this` as the sign side to select the side you are looking at when executing the command). The source line argument is used to only copy a single line from the selected sign side. The target line argument is used to specify the line to paste to (defaults to same as the source line) on the clicked side. | +| /unWaxSign | | Removes the wax from the sign you are currently looking at. | ## Permissions -| Permission | Description | -|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| placeholdersigns.* | Gives all permissions. | -| placeholdersigns.edit | Allows unrestricted use of the /setSignLine command. | -| placeholdersigns.edit.use | Allows use of the /setSignLine command. | -| placeholdersigns.edit.bypass-waxed | Allows use of the /setSignLine command on a waxed sign. | -| placeholdersigns.placeholder | Allows a player to make signs containing placeholders. Without this, placeholders are treated as normal text for all commands, and when editing sign text. | -| placeholdersigns.copy | Allows unrestricted use of the /copySign command. | -| placeholdersigns.copy.use | Allows use of the /copySign command. | -| placeholdersigns.copy.bypass-waxed | Allows pasting a sign copied with /copySign onto a waxed sign. | -| placeholdersigns.unwax | Allows use of the /unWaxSign command | \ No newline at end of file +| Permission | Default | Description | +|------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| placeholdersigns.* | op | Gives all permissions. | +| placeholdersigns.minimal | true | Allows minimal access to /setSignLine, /copySign, /copySignText and /viewSign on un-waxed signs. | +| placeholdersigns.edit | false | Allows unrestricted use of the /setSignLine command. | +| placeholdersigns.edit.use | false | Allows use of the /setSignLine command. | +| placeholdersigns.edit.bypass-waxed | false | Allows use of the /setSignLine command on a waxed sign. | +| placeholdersigns.placeholder | false | Allows a player to make signs containing placeholders. Without this, placeholders are treated as normal text for all commands, and when editing sign text. | +| placeholdersigns.view | false | Allows a player to use the /viewSign command. | +| placeholdersigns.copy | false | Allows unrestricted use of the /copySign and /copySignText commands. | +| placeholdersigns.copy.use | false | Allows use of the /copySign and /copySignText commands. | +| placeholdersigns.copy.bypass-waxed | false | Allows pasting a sign copied with /copySign and /copySignText onto a waxed sign. | +| placeholdersigns.unwax | false | Allows use of the /unWaxSign command | \ No newline at end of file diff --git a/src/main/java/net/knarcraft/placeholdersigns/PlaceholderSigns.java b/src/main/java/net/knarcraft/placeholdersigns/PlaceholderSigns.java index ced1a05..f1e84d3 100644 --- a/src/main/java/net/knarcraft/placeholdersigns/PlaceholderSigns.java +++ b/src/main/java/net/knarcraft/placeholdersigns/PlaceholderSigns.java @@ -4,6 +4,7 @@ import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.Translator; import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.placeholdersigns.command.CopySignCommand; +import net.knarcraft.placeholdersigns.command.CopySignTextCommand; import net.knarcraft.placeholdersigns.command.EditSignCommand; import net.knarcraft.placeholdersigns.command.UnWaxSignCommand; import net.knarcraft.placeholdersigns.command.ViewSignCommand; @@ -97,6 +98,7 @@ public final class PlaceholderSigns extends JavaPlugin { registerCommand("viewSign", new ViewSignCommand()); registerCommand("copySign", new CopySignCommand()); registerCommand("unWaxSign", new UnWaxSignCommand()); + registerCommand("copySignText", new CopySignTextCommand()); } @Override diff --git a/src/main/java/net/knarcraft/placeholdersigns/command/CopySignTextCommand.java b/src/main/java/net/knarcraft/placeholdersigns/command/CopySignTextCommand.java new file mode 100644 index 0000000..b892323 --- /dev/null +++ b/src/main/java/net/knarcraft/placeholdersigns/command/CopySignTextCommand.java @@ -0,0 +1,99 @@ +package net.knarcraft.placeholdersigns.command; + +import net.knarcraft.knarlib.formatting.StringFormatter; +import net.knarcraft.knarlib.util.TabCompletionHelper; +import net.knarcraft.placeholdersigns.PlaceholderSigns; +import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage; +import net.knarcraft.placeholdersigns.container.SignTextCopyRequest; +import net.knarcraft.placeholdersigns.util.TabCompleteHelper; +import org.bukkit.block.Block; +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A command for copying a sign's text + */ +public class CopySignTextCommand implements TabExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + PlaceholderSigns placeholderSigns = PlaceholderSigns.getInstance(); + StringFormatter stringFormatter = placeholderSigns.getStringFormatter(); + if (!(commandSender instanceof Player player)) { + stringFormatter.displayErrorMessage(commandSender, PlaceholderSignMessage.ERROR_PLAYER_ONLY); + return false; + } + + Block targetBlock = player.getTargetBlockExact(7); + if (targetBlock == null || !(targetBlock.getState() instanceof Sign sign)) { + stringFormatter.displayErrorMessage(commandSender, PlaceholderSignMessage.ERROR_NOT_LOOKING_AT_SIGN); + return false; + } + + Side side = null; + Integer sourceLine = null; + Integer destinationLine = null; + + if (arguments.length > 0) { + try { + side = Side.valueOf(arguments[0]); + } catch (IllegalArgumentException exception) { + // Choose the side the player is currently looking at + if (arguments[0].equalsIgnoreCase("this")) { + SignSide standingOn = sign.getTargetSide(player); + side = sign.getSide(Side.FRONT).equals(standingOn) ? Side.FRONT : Side.BACK; + } else { + return false; + } + } + } + if (arguments.length > 1) { + try { + sourceLine = Integer.parseInt(arguments[1]) - 1; + } catch (NumberFormatException exception) { + return false; + } + } + if (arguments.length > 2) { + try { + destinationLine = Integer.parseInt(arguments[2]) - 1; + } catch (NumberFormatException exception) { + return false; + } + } + + SignTextCopyRequest signCopyRequest = new SignTextCopyRequest(player, sign, side, sourceLine, destinationLine); + placeholderSigns.getRequestHandler().addSignTextCopyRequest(signCopyRequest); + + stringFormatter.displaySuccessMessage(commandSender, PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_PASTE); + return true; + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (arguments.length == 1) { + List signSides = TabCompleteHelper.getSignSides(); + signSides.add("this"); + return TabCompletionHelper.filterMatchingStartsWith(signSides, arguments[0]); + } else if (arguments.length < 4) { + return TabCompletionHelper.filterMatchingStartsWith(TabCompleteHelper.getLineNumbers(), + arguments[arguments.length - 1]); + } else { + return new ArrayList<>(); + } + } + +} diff --git a/src/main/java/net/knarcraft/placeholdersigns/command/EditSignCommand.java b/src/main/java/net/knarcraft/placeholdersigns/command/EditSignCommand.java index 7631c8c..d6c6c6b 100644 --- a/src/main/java/net/knarcraft/placeholdersigns/command/EditSignCommand.java +++ b/src/main/java/net/knarcraft/placeholdersigns/command/EditSignCommand.java @@ -1,9 +1,11 @@ package net.knarcraft.placeholdersigns.command; import net.knarcraft.knarlib.formatting.StringFormatter; +import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.placeholdersigns.PlaceholderSigns; import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage; import net.knarcraft.placeholdersigns.container.SignLineChangeRequest; +import net.knarcraft.placeholdersigns.util.TabCompleteHelper; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; @@ -19,15 +21,6 @@ import java.util.List; */ public class EditSignCommand implements TabExecutor { - private static final List lineNumbers; - - static { - lineNumbers = new ArrayList<>(); - for (int i = 1; i < 5; i++) { - lineNumbers.add(String.valueOf(i)); - } - } - @Override public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) { @@ -77,9 +70,9 @@ public class EditSignCommand implements TabExecutor { @Nullable @Override public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, - @NotNull String[] args) { - if (args.length == 1) { - return lineNumbers; + @NotNull String[] arguments) { + if (arguments.length == 1) { + return TabCompletionHelper.filterMatchingStartsWith(TabCompleteHelper.getLineNumbers(), arguments[0]); } else { return new ArrayList<>(); } diff --git a/src/main/java/net/knarcraft/placeholdersigns/config/PlaceholderSignsPermission.java b/src/main/java/net/knarcraft/placeholdersigns/config/PlaceholderSignsPermission.java new file mode 100644 index 0000000..2d70b98 --- /dev/null +++ b/src/main/java/net/knarcraft/placeholdersigns/config/PlaceholderSignsPermission.java @@ -0,0 +1,47 @@ +package net.knarcraft.placeholdersigns.config; + +import org.jetbrains.annotations.NotNull; + +/** + * Permissions used by placeholdersigns + */ +public enum PlaceholderSignsPermission { + + /** + * The permission for bypassing the restriction on pasting onto waxed signs + */ + BYPASS_WAXED_COPY("placeholdersigns.copy.bypass-waxed"), + + /** + * The permission for bypassing the restriction on editing waxed signs + */ + BYPASS_WAXED_EDIT("placeholdersigns.edit.bypass-waxed"), + + /** + * The permission for creating new placeholder signs + */ + USE_PLACEHOLDERS("placeholdersigns.placeholder"), + ; + + private final @NotNull String permissionNode; + + /** + * Instantiates a new placeholder signs permission + * + * @param permissionNode

The permission node belonging to this permission

+ */ + PlaceholderSignsPermission(@NotNull String permissionNode) { + this.permissionNode = permissionNode; + } + + /** + * Gets this permission's permission node + * + * @return

This permission's permission node

+ */ + @NotNull + public String getPermissionNode() { + return this.permissionNode; + } + +} diff --git a/src/main/java/net/knarcraft/placeholdersigns/container/SignTextCopyRequest.java b/src/main/java/net/knarcraft/placeholdersigns/container/SignTextCopyRequest.java new file mode 100644 index 0000000..1aeb91e --- /dev/null +++ b/src/main/java/net/knarcraft/placeholdersigns/container/SignTextCopyRequest.java @@ -0,0 +1,20 @@ +package net.knarcraft.placeholdersigns.container; + +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A record of a player's request to copy a sign + * + * @param player

The player requesting the sign copy

+ * @param sign

The sign the player wants to copy

+ * @param side

The side of the sign top copy from (null = both)

+ * @param sourceLine

The line on the source sign to copy (null = all)

+ * @param destinationLine

The line on the destination sign to overwrite

+ */ +public record SignTextCopyRequest(@NotNull Player player, @NotNull Sign sign, @Nullable Side side, + @Nullable Integer sourceLine, @Nullable Integer destinationLine) { +} diff --git a/src/main/java/net/knarcraft/placeholdersigns/handler/PlaceholderSignRequestHandler.java b/src/main/java/net/knarcraft/placeholdersigns/handler/PlaceholderSignRequestHandler.java index cb4caae..c65f8a4 100644 --- a/src/main/java/net/knarcraft/placeholdersigns/handler/PlaceholderSignRequestHandler.java +++ b/src/main/java/net/knarcraft/placeholdersigns/handler/PlaceholderSignRequestHandler.java @@ -2,6 +2,7 @@ package net.knarcraft.placeholdersigns.handler; import net.knarcraft.placeholdersigns.container.SignCopyRequest; import net.knarcraft.placeholdersigns.container.SignLineChangeRequest; +import net.knarcraft.placeholdersigns.container.SignTextCopyRequest; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,7 +16,8 @@ import java.util.Map; public class PlaceholderSignRequestHandler { private final @NotNull Map signChangeRequests; - private final Map signCopyRequests; + private final @NotNull Map signTextCopyRequests; + private final @NotNull Map signCopyRequests; /** * Instantiates a new placeholder sign request handler @@ -23,6 +25,7 @@ public class PlaceholderSignRequestHandler { public PlaceholderSignRequestHandler() { this.signChangeRequests = new HashMap<>(); this.signCopyRequests = new HashMap<>(); + this.signTextCopyRequests = new HashMap<>(); } /** @@ -71,4 +74,24 @@ public class PlaceholderSignRequestHandler { return this.signChangeRequests.remove(player); } + /** + * Registers a sign text copy request + * + * @param signCopyRequest

The request to register

+ */ + public void addSignTextCopyRequest(@NotNull SignTextCopyRequest signCopyRequest) { + this.signTextCopyRequests.put(signCopyRequest.player(), signCopyRequest); + } + + /** + * Gets a sign text copy request + * + * @param player

The player to get the request for

+ * @return

The sign text copy request, or null if not found

+ */ + @Nullable + public SignTextCopyRequest getSignTextCopyRequest(@NotNull Player player) { + return this.signTextCopyRequests.remove(player); + } + } diff --git a/src/main/java/net/knarcraft/placeholdersigns/listener/SignClickListener.java b/src/main/java/net/knarcraft/placeholdersigns/listener/SignClickListener.java index c3018dc..31c9a86 100644 --- a/src/main/java/net/knarcraft/placeholdersigns/listener/SignClickListener.java +++ b/src/main/java/net/knarcraft/placeholdersigns/listener/SignClickListener.java @@ -5,9 +5,11 @@ import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.knarlib.util.ColorHelper; import net.knarcraft.placeholdersigns.PlaceholderSigns; import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage; +import net.knarcraft.placeholdersigns.config.PlaceholderSignsPermission; import net.knarcraft.placeholdersigns.container.PlaceholderSign; import net.knarcraft.placeholdersigns.container.SignCopyRequest; import net.knarcraft.placeholdersigns.container.SignLineChangeRequest; +import net.knarcraft.placeholdersigns.container.SignTextCopyRequest; import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler; import org.bukkit.Bukkit; @@ -26,6 +28,9 @@ import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.Objects; +import static net.knarcraft.placeholdersigns.util.SignSideHelper.getOpposite; +import static net.knarcraft.placeholdersigns.util.SignSideHelper.getSide; + /** * A listener for placeholder signs being clicked */ @@ -44,7 +49,7 @@ public class SignClickListener implements Listener { SignCopyRequest signCopyRequest = requestHandler.getSignCopyRequest(player); if (signCopyRequest != null) { - if (checkWaxEdit(sign, player, "placeholdersigns.copy.bypass-waxed")) { + if (checkWaxEdit(sign, player, PlaceholderSignsPermission.BYPASS_WAXED_COPY.getPermissionNode())) { return; } @@ -53,19 +58,167 @@ public class SignClickListener implements Listener { return; } - // Check if the player has run the /editSign command - SignLineChangeRequest request = requestHandler.getSignChangeRequest(player); - if (request != null) { - if (checkWaxEdit(sign, player, "placeholdersigns.edit.bypass-waxed")) { + // Check if the player has run the /setSignLine command + SignLineChangeRequest signChangeRequest = requestHandler.getSignChangeRequest(player); + if (signChangeRequest != null) { + if (checkWaxEdit(sign, player, PlaceholderSignsPermission.BYPASS_WAXED_EDIT.getPermissionNode())) { return; } - SignSide standingOn = sign.getTargetSide(player); - Side side = sign.getSide(Side.FRONT).equals(standingOn) ? Side.FRONT : Side.BACK; - doSignChange(sign, request, event, player, side); + doSignChange(sign, signChangeRequest.line(), player, getSide(sign, player), signChangeRequest.text()); + event.setCancelled(true); + return; + } + + SignTextCopyRequest signTextCopyRequest = requestHandler.getSignTextCopyRequest(player); + if (signTextCopyRequest != null) { + if (checkWaxEdit(sign, player, PlaceholderSignsPermission.BYPASS_WAXED_COPY.getPermissionNode())) { + return; + } + + pasteSignText(sign, signTextCopyRequest, player, getSide(sign, player)); + event.setCancelled(true); } } + /** + * Pastes sign text according to the given signTextCopyRequest + * + * @param targetSign

The sign to paste onto

+ * @param signTextCopyRequest

The requested sign text copy request to fulfill

+ * @param player

The player wanting to paste

+ * @param clickedSide

The sign side the player wants to paste onto

+ */ + private void pasteSignText(@NotNull Sign targetSign, @NotNull SignTextCopyRequest signTextCopyRequest, + @NotNull Player player, @NotNull Side clickedSide) { + Side sourceSide = signTextCopyRequest.side(); + Sign sourceSign = signTextCopyRequest.sign(); + PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler(); + PlaceholderSign sourcePlaceholderSign = signHandler.getFromLocation(sourceSign.getLocation()); + PlaceholderSign targetPlaceholderSign = signHandler.getFromLocation(targetSign.getLocation()); + + if (sourceSide == null) { + // Copy both sides + copySignText(signHandler, sourcePlaceholderSign, targetPlaceholderSign, clickedSide, sourceSign, targetSign, + player); + } else if (signTextCopyRequest.sourceLine() == null) { + // Copy the side the player looked at while executing the command + copySignSideText(signHandler, sourcePlaceholderSign, targetPlaceholderSign, sourceSide, clickedSide, + sourceSign, targetSign, player); + } else { + copySignLine(signTextCopyRequest, sourcePlaceholderSign, sourceSide, clickedSide, targetSign, player); + } + } + + /** + * Copies sign line from one sign side to another + * + * @param signTextCopyRequest

The sign text copy request to handle

+ * @param sourcePlaceholderSign

The source sign's placeholder sign, or null

+ * @param sourceSide

The side of the source sign to copy text from

+ * @param clickedSide

The side the player clicked to paste

+ * @param targetSign

The sign to paste text onto

+ * @param player

The player performing the copy paste

+ */ + private void copySignLine(@NotNull SignTextCopyRequest signTextCopyRequest, + @Nullable PlaceholderSign sourcePlaceholderSign, @NotNull Side sourceSide, + @NotNull Side clickedSide, @NotNull Sign targetSign, @NotNull Player player) { + if (signTextCopyRequest.side() == null || signTextCopyRequest.sourceLine() == null) { + throw new RuntimeException("Non-null variable is null"); + } + + int sourceLine = signTextCopyRequest.sourceLine(); + int destinationLine = signTextCopyRequest.destinationLine() != null ? + signTextCopyRequest.destinationLine() : sourceLine; + + String text = signTextCopyRequest.sign().getSide(signTextCopyRequest.side()).getLine(sourceLine); + if (sourcePlaceholderSign != null) { + Map> placeholders = sourcePlaceholderSign.placeholders(); + if (placeholders.containsKey(sourceSide) && placeholders.get(sourceSide).containsKey(sourceLine)) { + text = placeholders.get(sourceSide).get(sourceLine); + } + } + + doSignChange(targetSign, destinationLine, player, clickedSide, text); + } + + /** + * Copies sign text from one sign side to another + * + * @param signHandler

The sign handler to use for dealing with placeholder signs

+ * @param sourcePlaceholderSign

The source sign's placeholder sign, or null

+ * @param targetPlaceholderSign

The destination's placeholder sign, or null

+ * @param sourceSide

The side of the source sign to copy text from

+ * @param clickedSide

The side the player clicked to paste

+ * @param sourceSign

The sign to copy text from

+ * @param targetSign

The sign to paste text onto

+ * @param player

The player performing the copy paste

+ */ + private void copySignSideText(@NotNull PlaceholderSignHandler signHandler, + @Nullable PlaceholderSign sourcePlaceholderSign, + @Nullable PlaceholderSign targetPlaceholderSign, @NotNull Side sourceSide, + @NotNull Side clickedSide, @NotNull Sign sourceSign, @NotNull Sign targetSign, + @NotNull Player player) { + // Remove old placeholders from the sign side + Map oldPlaceholders = null; + if (targetPlaceholderSign != null) { + oldPlaceholders = targetPlaceholderSign.placeholders().remove(clickedSide); + } + if (pasteSignSide(sourceSide, clickedSide, sourceSign, targetSign, + sourcePlaceholderSign, player) && targetPlaceholderSign != null) { + // Restore the old placeholders if the sign change didn't finish + targetPlaceholderSign.placeholders().put(clickedSide, oldPlaceholders); + } + + // Save any placeholder changes + signHandler.save(); + + // Apply changes + targetSign.update(); + + PlaceholderSigns.getInstance().getStringFormatter().displaySuccessMessage(player, + PlaceholderSignMessage.SUCCESS_SIGN_PASTED); + } + + /** + * Copies sign text from one sign to another + * + * @param signHandler

The sign handler to use for dealing with placeholder signs

+ * @param sourcePlaceholderSign

The source sign's placeholder sign, or null

+ * @param targetPlaceholderSign

The destination's placeholder sign, or null

+ * @param clickedSide

The side the player clicked to paste

+ * @param sourceSign

The sign to copy text from

+ * @param targetSign

The sign to paste text onto

+ * @param player

The player performing the copy paste

+ */ + private void copySignText(@NotNull PlaceholderSignHandler signHandler, + @Nullable PlaceholderSign sourcePlaceholderSign, + @Nullable PlaceholderSign targetPlaceholderSign, @NotNull Side clickedSide, + @NotNull Sign sourceSign, @NotNull Sign targetSign, @NotNull Player player) { + // Un-register the old placeholder sign to remove its data + if (targetPlaceholderSign != null) { + signHandler.unregisterSign(targetPlaceholderSign); + } + + // Copy both sides of the sign. Paste the front to whichever side the player is currently looking at + if ((pasteSignSide(Side.FRONT, clickedSide, sourceSign, targetSign, sourcePlaceholderSign, player) || + pasteSignSide(Side.BACK, getOpposite(clickedSide), sourceSign, targetSign, sourcePlaceholderSign, player)) && + targetPlaceholderSign != null) { + // Re-register the old placeholder sign if the new text couldn't be applied + signHandler.registerSign(targetPlaceholderSign); + } + + // Save any placeholder changes + signHandler.save(); + + // Apply changes + targetSign.update(); + + PlaceholderSigns.getInstance().getStringFormatter().displaySuccessMessage(player, + PlaceholderSignMessage.SUCCESS_SIGN_PASTED); + } + + /** * Checks if the player is trying to edit/modify a waxed sign without permission * @@ -86,68 +239,80 @@ public class SignClickListener implements Listener { /** * Pastes the data from the source sign to the target sign * - * @param source

The source sign to copy data from

- * @param target

The target sign to paste to

- * @param player

The player pasting the sign

+ * @param sourceSign

The source sign to copy data from

+ * @param targetSign

The target sign to paste to

+ * @param player

The player pasting the sign

*/ - private void pasteSign(@NotNull Sign source, @NotNull Sign target, @NotNull Player player) { + private void pasteSign(@NotNull Sign sourceSign, @NotNull Sign targetSign, @NotNull Player player) { StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter(); - PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(source.getLocation()); - SignSide sourceFront = source.getSide(Side.FRONT); - SignSide sourceBack = source.getSide(Side.BACK); - SignSide targetFront = target.getSide(Side.FRONT); - SignSide targetBack = target.getSide(Side.BACK); + PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation( + sourceSign.getLocation()); // Get the lines, with placeholders replaced, and the sign change event processed - String[] frontLines = getFinalLines(target, sourceFront, Side.FRONT, placeholderSign, player); - if (frontLines == null) { - stringFormatter.displayErrorMessage(player, PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION); + if (pasteSignSide(Side.FRONT, Side.FRONT, sourceSign, targetSign, placeholderSign, player) || + pasteSignSide(Side.BACK, Side.BACK, sourceSign, targetSign, placeholderSign, player)) { return; } - String[] backLines = getFinalLines(target, sourceBack, Side.BACK, placeholderSign, player); - if (backLines == null) { - stringFormatter.displayErrorMessage(player, PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION); - return; - } - - // Update the lines on both sides of the target sign - for (int i = 0; i < frontLines.length; i++) { - targetFront.setLine(i, ColorHelper.translateColorCodes(frontLines[i], ColorConversion.RGB)); - } - for (int i = 0; i < backLines.length; i++) { - targetBack.setLine(i, ColorHelper.translateColorCodes(backLines[i], ColorConversion.RGB)); - } // Copy options - copySignOptions(source, target); + copySignOptions(sourceSign, targetSign); // Apply changes - target.update(); + targetSign.update(); stringFormatter.displaySuccessMessage(player, PlaceholderSignMessage.SUCCESS_SIGN_PASTED); } + /** + * Pastes one side of a sign onto another + * + * @param sourceSide

The sign side to copy

+ * @param destinationSide

The sign side to paste to

+ * @param source

The source sign

+ * @param target

The destination sign

+ * @param placeholderSign

The placeholder sign belonging to the source sign, or null

+ * @param player

The player performing the paste

+ * @return

False if the pasting completed successfully. True if blocked by another plugin.

+ */ + private boolean pasteSignSide(@NotNull Side sourceSide, @NotNull Side destinationSide, @NotNull Sign source, + @NotNull Sign target, @Nullable PlaceholderSign placeholderSign, @NotNull Player player) { + SignSide sourceSignSide = source.getSide(sourceSide); + SignSide targetSignSide = target.getSide(destinationSide); + String[] frontLines = getFinalLines(target, sourceSignSide, sourceSide, destinationSide, placeholderSign, player); + if (frontLines == null) { + PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player, + PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION); + return true; + } + for (int i = 0; i < frontLines.length; i++) { + targetSignSide.setLine(i, ColorHelper.translateColorCodes(frontLines[i], ColorConversion.RGB)); + } + return false; + } + /** * Gets the final lines from a sign side, after inserting placeholders and running a sign change event * * @param sign

The sign that's changed

* @param signSide

The side of the sign to get lines from

- * @param side

The side that's processed

+ * @param sourceSide

The side that text is taken from

+ * @param targetSide

The side that text is changed for

* @param placeholderSign

The placeholder sign corresponding to the sign, if any

* @param player

The player attempting to paste the sign

* @return

The final lines, or null if the event was cancelled

*/ @Nullable - private String[] getFinalLines(@NotNull Sign sign, @NotNull SignSide signSide, @NotNull Side side, - @Nullable PlaceholderSign placeholderSign, @NotNull Player player) { + private String[] getFinalLines(@NotNull Sign sign, @NotNull SignSide signSide, @NotNull Side sourceSide, + @NotNull Side targetSide, @Nullable PlaceholderSign placeholderSign, + @NotNull Player player) { String[] sideLines = signSide.getLines(); if (placeholderSign != null) { Map> placeholders = placeholderSign.placeholders(); - loadPlaceholders(side, sideLines, placeholders); + loadPlaceholders(sourceSide, sideLines, placeholders); } // Run the sign change event to allow protection plugins to cancel, and allow the sign text listener to trigger - SignChangeEvent changeEvent = new SignChangeEvent(Objects.requireNonNull(sign.getBlock()), player, sideLines, side); + SignChangeEvent changeEvent = new SignChangeEvent(Objects.requireNonNull(sign.getBlock()), player, sideLines, targetSide); Bukkit.getPluginManager().callEvent(changeEvent); if (changeEvent.isCancelled()) { return null; @@ -202,39 +367,36 @@ public class SignClickListener implements Listener { } /** - * Perform the changing of a sign according to the given sign change request + * Alters the text on a sign, taking care of placeholders and sign change event triggers * - * @param sign

The sign to be changed

- * @param request

The sign line change request to perform

- * @param event

The interaction event that triggered this

- * @param player

The player triggering the sign change

+ * @param targetSign

The sign to be changed

+ * @param destinationLine

The line to be changed on the sign

+ * @param player

The player trying to change the sign

+ * @param targetSide

The side of the sign to change the line for

+ * @param newText

The new text to apply to the sign

*/ - private void doSignChange(@NotNull Sign sign, @NotNull SignLineChangeRequest request, - @NotNull PlayerInteractEvent event, @NotNull Player player, @NotNull Side side) { - // Cancel the event to prevent vanilla behavior - event.setCancelled(true); - - SignSide signSide = sign.getSide(side); - String[] lines = signSide.getLines(); + private void doSignChange(@NotNull Sign targetSign, int destinationLine, @NotNull Player player, + @NotNull Side targetSide, @NotNull String newText) { + SignSide targetSignSide = targetSign.getSide(targetSide); + String[] lines = targetSignSide.getLines(); PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler(); - PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(sign.getLocation()); + PlaceholderSign targetPlaceholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(targetSign.getLocation()); String oldPlaceholder = null; - if (placeholderSign != null) { + if (targetPlaceholderSign != null) { // Remove the old placeholder - oldPlaceholder = placeholderSign.placeholders().get(side).remove(request.line()); + oldPlaceholder = targetPlaceholderSign.placeholders().get(targetSide).remove(destinationLine); } - lines[request.line()] = request.text(); + lines[destinationLine] = newText; // Run the sign change event to allow protection plugins to cancel, and allow the sign text listener to trigger - SignChangeEvent changeEvent = new SignChangeEvent(Objects.requireNonNull(event.getClickedBlock()), - player, lines, side); + SignChangeEvent changeEvent = new SignChangeEvent(targetSign.getBlock(), player, lines, targetSide); Bukkit.getPluginManager().callEvent(changeEvent); if (changeEvent.isCancelled()) { - if (placeholderSign != null) { + if (targetPlaceholderSign != null) { // Restore the old placeholder if the action didn't complete - placeholderSign.placeholders().get(side).put(request.line(), oldPlaceholder); + targetPlaceholderSign.placeholders().get(targetSide).put(destinationLine, oldPlaceholder); } PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player, PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION); @@ -242,11 +404,9 @@ public class SignClickListener implements Listener { } // Update the sign with the new text - String[] finalLines = changeEvent.getLines(); - for (int i = 0; i < finalLines.length; i++) { - signSide.setLine(i, ColorHelper.translateColorCodes(finalLines[i], ColorConversion.RGB)); - } - sign.update(); + targetSignSide.setLine(destinationLine, ColorHelper.translateColorCodes(changeEvent.getLines()[destinationLine], + ColorConversion.RGB)); + targetSign.update(); // Save any placeholder changes signHandler.save(); diff --git a/src/main/java/net/knarcraft/placeholdersigns/listener/SignTextListener.java b/src/main/java/net/knarcraft/placeholdersigns/listener/SignTextListener.java index d5101fc..502be89 100644 --- a/src/main/java/net/knarcraft/placeholdersigns/listener/SignTextListener.java +++ b/src/main/java/net/knarcraft/placeholdersigns/listener/SignTextListener.java @@ -2,6 +2,7 @@ package net.knarcraft.placeholdersigns.listener; import me.clip.placeholderapi.PlaceholderAPI; import net.knarcraft.placeholdersigns.PlaceholderSigns; +import net.knarcraft.placeholdersigns.config.PlaceholderSignsPermission; import net.knarcraft.placeholdersigns.container.PlaceholderSign; import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler; import org.bukkit.Location; @@ -23,7 +24,7 @@ public class SignTextListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onSignCreateOrEdit(@NotNull SignChangeEvent event) { // Only check for placeholders if the player is allowed to - if (!event.getPlayer().hasPermission("placeholdersigns.placeholder")) { + if (!event.getPlayer().hasPermission(PlaceholderSignsPermission.USE_PLACEHOLDERS.getPermissionNode())) { return; } diff --git a/src/main/java/net/knarcraft/placeholdersigns/util/SignSideHelper.java b/src/main/java/net/knarcraft/placeholdersigns/util/SignSideHelper.java new file mode 100644 index 0000000..bfba3ad --- /dev/null +++ b/src/main/java/net/knarcraft/placeholdersigns/util/SignSideHelper.java @@ -0,0 +1,39 @@ +package net.knarcraft.placeholdersigns.util; + +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public final class SignSideHelper { + + private SignSideHelper() { + + } + + /** + * Gets the side of the given sign the given player is standing on (which side the player clicked) + * + * @param sign

The sign to check

+ * @param player

The player to check

+ * @return

The side of the sign the player is standing on

+ */ + @NotNull + public static Side getSide(@NotNull Sign sign, @NotNull Player player) { + SignSide standingOn = sign.getTargetSide(player); + return sign.getSide(Side.FRONT).equals(standingOn) ? Side.FRONT : Side.BACK; + } + + /** + * Gets the opposite sign side from the given sign side + * + * @param side

The side to get the opposite of

+ * @return

The opposite side

+ */ + @NotNull + public static Side getOpposite(@NotNull Side side) { + return side == Side.FRONT ? Side.BACK : Side.FRONT; + } + +} diff --git a/src/main/java/net/knarcraft/placeholdersigns/util/TabCompleteHelper.java b/src/main/java/net/knarcraft/placeholdersigns/util/TabCompleteHelper.java new file mode 100644 index 0000000..9dd9a64 --- /dev/null +++ b/src/main/java/net/knarcraft/placeholdersigns/util/TabCompleteHelper.java @@ -0,0 +1,53 @@ +package net.knarcraft.placeholdersigns.util; + +import org.bukkit.block.sign.Side; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * A helper class for generating tab-completions + */ +public final class TabCompleteHelper { + + private static final List lineNumbers; + private static final List signSides; + + static { + lineNumbers = new ArrayList<>(); + for (int i = 1; i < 5; i++) { + lineNumbers.add(String.valueOf(i)); + } + + signSides = new ArrayList<>(); + for (Side side : Side.values()) { + signSides.add(side.name()); + } + } + + private TabCompleteHelper() { + + } + + /** + * Gets possible sign line numbers + * + * @return

Possible sign line numbers

+ */ + @NotNull + public static List getLineNumbers() { + return new ArrayList<>(lineNumbers); + } + + /** + * Gets possible sign sides + * + * @return

Possible sign sides

+ */ + @NotNull + public static List getSignSides() { + return new ArrayList<>(signSides); + } + +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6d49f74..49e65fc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,10 +17,18 @@ commands: copySign: usage: / permission: placeholdersigns.copy.use - description: Copies all sign information from one sign to another + description: Copies all sign information (including dye, glow and waxed) from one sign to another unWaxSign: usage: / permission: placeholdersigns.unwax + copySignText: + usage: | + / [side] [sourceLine] [destinationLine] + side = The side of the sign to copy text from. If not specified, both sides are copied. + sourceLine = The line to copy from the source sign + destinationLine = The destination sign line to overwrite. If not set, it's the same as sourceLine + permission: placeholdersigns.copy.use + description: Copies all text, or a single line, from one sign to another, either the specified side or both permissions: placeholdersigns.*: @@ -32,8 +40,14 @@ permissions: - placeholdersigns.copy - placeholdersigns.unwax default: op + placeholdersigns.minimal: + description: Allows minimal access to /setSignLine, /copySign, /copySignText and /viewSign + children: + - placeholdersigns.edit.use + - placeholdersigns.copy.use + - placeholdersigns.view placeholdersigns.edit: - description: Allows a player to use the /editSign command without restriction + description: Allows a player to use the /setSignLine command without restriction default: false children: - placeholdersigns.edit.use @@ -45,23 +59,23 @@ permissions: - placeholdersigns.copy.use - placeholdersigns.copy.bypass-waxed placeholdersigns.edit.use: - description: Allows a player to use the /editSign command + description: Allows a player to use the /setSignLine command default: false placeholdersigns.edit.bypass-waxed: - description: Allows a player to use the /editSign command on a waxed sign + description: Allows a player to use the /setSignLine command on a waxed sign default: false placeholdersigns.placeholder: - description: Allows a player to make signs containing placeholders + description: Allows a player to make signs containing placeholders (both through this plugin's commands, and through simply editing a sign) default: false placeholdersigns.view: - description: Allows a player to see the full text of a sign in the chat - default: true + description: Allows a player to use the /viewSign command + default: false placeholdersigns.copy.use: - description: Allows a player to copy a sign to another sign + description: Allows a player to use the /copySign and /copySignText commands default: false placeholdersigns.copy.bypass-waxed: description: Allows a player to use the /copySign command and paste onto a waxed sign default: false placeholdersigns.unwax: - description: Allows a player to remove the wax from a sign + description: Allows a player to use the /unWax command default: false \ No newline at end of file