Implements #4
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good

This commit is contained in:
Kristian Knarvik 2024-04-29 19:29:55 +02:00
parent 14f9fa8833
commit a34c63b7e2
12 changed files with 567 additions and 109 deletions

View File

@ -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 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. 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 additional benefit, formatting, color and RGB color codes are automatically converted whenever the command is used to
change sign text. 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 ## Commands
| Command | Arguments | Description | Note that commands have some restrictions in place, so giving the weakest permissions to any player should be safe:
|--------------|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| /setSignLine | \<line> \<text> \<text> ... | Sets the text of the sign line (1-4) to the given input. Then right-click the sign to update. | - Players cannot create placeholder signs with any commands without the `placeholdersigns.placeholder` permission.
| /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. | - Players cannot cause a destructive action on a waxed sign without the correct bypass permission.
| /copySign | | Copies the sign you are currently looking at to another sign, including placeholders, formatting codes, dye and waxed state. | - All sign changes are run through the SignChangeEvent, so the player cannot edit a sign if any protection plugin would
| /unWaxSign | | Removes the wax from the sign you are currently looking at. | prevent them from editing the sign normally.
| Command | Arguments | Description |
|---------------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| /setSignLine | \<line> \<text> \<text> ... | 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 ## Permissions
| Permission | Description | | Permission | Default | Description |
|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| placeholdersigns.* | Gives all permissions. | | placeholdersigns.* | op | Gives all permissions. |
| placeholdersigns.edit | Allows unrestricted use of the /setSignLine command. | | placeholdersigns.minimal | true | Allows minimal access to /setSignLine, /copySign, /copySignText and /viewSign on un-waxed signs. |
| placeholdersigns.edit.use | Allows use of the /setSignLine command. | | placeholdersigns.edit | false | Allows unrestricted use of the /setSignLine command. |
| placeholdersigns.edit.bypass-waxed | Allows use of the /setSignLine command on a waxed sign. | | placeholdersigns.edit.use | false | Allows use of the /setSignLine command. |
| 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.edit.bypass-waxed | false | Allows use of the /setSignLine command on a waxed sign. |
| placeholdersigns.copy | Allows unrestricted use of the /copySign command. | | 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.copy.use | Allows use of the /copySign command. | | placeholdersigns.view | false | Allows a player to use the /viewSign command. |
| placeholdersigns.copy.bypass-waxed | Allows pasting a sign copied with /copySign onto a waxed sign. | | placeholdersigns.copy | false | Allows unrestricted use of the /copySign and /copySignText commands. |
| placeholdersigns.unwax | Allows use of the /unWaxSign command | | 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 |

View File

@ -4,6 +4,7 @@ import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.Translator; import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.placeholdersigns.command.CopySignCommand; import net.knarcraft.placeholdersigns.command.CopySignCommand;
import net.knarcraft.placeholdersigns.command.CopySignTextCommand;
import net.knarcraft.placeholdersigns.command.EditSignCommand; import net.knarcraft.placeholdersigns.command.EditSignCommand;
import net.knarcraft.placeholdersigns.command.UnWaxSignCommand; import net.knarcraft.placeholdersigns.command.UnWaxSignCommand;
import net.knarcraft.placeholdersigns.command.ViewSignCommand; import net.knarcraft.placeholdersigns.command.ViewSignCommand;
@ -97,6 +98,7 @@ public final class PlaceholderSigns extends JavaPlugin {
registerCommand("viewSign", new ViewSignCommand()); registerCommand("viewSign", new ViewSignCommand());
registerCommand("copySign", new CopySignCommand()); registerCommand("copySign", new CopySignCommand());
registerCommand("unWaxSign", new UnWaxSignCommand()); registerCommand("unWaxSign", new UnWaxSignCommand());
registerCommand("copySignText", new CopySignTextCommand());
} }
@Override @Override

View File

@ -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<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
List<String> 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<>();
}
}
}

View File

@ -1,9 +1,11 @@
package net.knarcraft.placeholdersigns.command; package net.knarcraft.placeholdersigns.command;
import net.knarcraft.knarlib.formatting.StringFormatter; import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.util.TabCompletionHelper;
import net.knarcraft.placeholdersigns.PlaceholderSigns; import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage; import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.container.SignLineChangeRequest; import net.knarcraft.placeholdersigns.container.SignLineChangeRequest;
import net.knarcraft.placeholdersigns.util.TabCompleteHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@ -19,15 +21,6 @@ import java.util.List;
*/ */
public class EditSignCommand implements TabExecutor { public class EditSignCommand implements TabExecutor {
private static final List<String> lineNumbers;
static {
lineNumbers = new ArrayList<>();
for (int i = 1; i < 5; i++) {
lineNumbers.add(String.valueOf(i));
}
}
@Override @Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) { @NotNull String[] args) {
@ -77,9 +70,9 @@ public class EditSignCommand implements TabExecutor {
@Nullable @Nullable
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) { @NotNull String[] arguments) {
if (args.length == 1) { if (arguments.length == 1) {
return lineNumbers; return TabCompletionHelper.filterMatchingStartsWith(TabCompleteHelper.getLineNumbers(), arguments[0]);
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@ -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 <p>The permission node belonging to this permission</p>
*/
PlaceholderSignsPermission(@NotNull String permissionNode) {
this.permissionNode = permissionNode;
}
/**
* Gets this permission's permission node
*
* @return <p>This permission's permission node</p>
*/
@NotNull
public String getPermissionNode() {
return this.permissionNode;
}
}

View File

@ -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 <p>The player requesting the sign copy</p>
* @param sign <p>The sign the player wants to copy</p>
* @param side <p>The side of the sign top copy from (null = both)</p>
* @param sourceLine <p>The line on the source sign to copy (null = all)</p>
* @param destinationLine <p>The line on the destination sign to overwrite</p>
*/
public record SignTextCopyRequest(@NotNull Player player, @NotNull Sign sign, @Nullable Side side,
@Nullable Integer sourceLine, @Nullable Integer destinationLine) {
}

View File

@ -2,6 +2,7 @@ package net.knarcraft.placeholdersigns.handler;
import net.knarcraft.placeholdersigns.container.SignCopyRequest; import net.knarcraft.placeholdersigns.container.SignCopyRequest;
import net.knarcraft.placeholdersigns.container.SignLineChangeRequest; import net.knarcraft.placeholdersigns.container.SignLineChangeRequest;
import net.knarcraft.placeholdersigns.container.SignTextCopyRequest;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -15,7 +16,8 @@ import java.util.Map;
public class PlaceholderSignRequestHandler { public class PlaceholderSignRequestHandler {
private final @NotNull Map<Player, SignLineChangeRequest> signChangeRequests; private final @NotNull Map<Player, SignLineChangeRequest> signChangeRequests;
private final Map<Player, SignCopyRequest> signCopyRequests; private final @NotNull Map<Player, SignTextCopyRequest> signTextCopyRequests;
private final @NotNull Map<Player, SignCopyRequest> signCopyRequests;
/** /**
* Instantiates a new placeholder sign request handler * Instantiates a new placeholder sign request handler
@ -23,6 +25,7 @@ public class PlaceholderSignRequestHandler {
public PlaceholderSignRequestHandler() { public PlaceholderSignRequestHandler() {
this.signChangeRequests = new HashMap<>(); this.signChangeRequests = new HashMap<>();
this.signCopyRequests = new HashMap<>(); this.signCopyRequests = new HashMap<>();
this.signTextCopyRequests = new HashMap<>();
} }
/** /**
@ -71,4 +74,24 @@ public class PlaceholderSignRequestHandler {
return this.signChangeRequests.remove(player); return this.signChangeRequests.remove(player);
} }
/**
* Registers a sign text copy request
*
* @param signCopyRequest <p>The request to register</p>
*/
public void addSignTextCopyRequest(@NotNull SignTextCopyRequest signCopyRequest) {
this.signTextCopyRequests.put(signCopyRequest.player(), signCopyRequest);
}
/**
* Gets a sign text copy request
*
* @param player <p>The player to get the request for</p>
* @return <p>The sign text copy request, or null if not found</p>
*/
@Nullable
public SignTextCopyRequest getSignTextCopyRequest(@NotNull Player player) {
return this.signTextCopyRequests.remove(player);
}
} }

View File

@ -5,9 +5,11 @@ import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper; import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.placeholdersigns.PlaceholderSigns; import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage; import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.config.PlaceholderSignsPermission;
import net.knarcraft.placeholdersigns.container.PlaceholderSign; import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.container.SignCopyRequest; import net.knarcraft.placeholdersigns.container.SignCopyRequest;
import net.knarcraft.placeholdersigns.container.SignLineChangeRequest; import net.knarcraft.placeholdersigns.container.SignLineChangeRequest;
import net.knarcraft.placeholdersigns.container.SignTextCopyRequest;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -26,6 +28,9 @@ import org.jetbrains.annotations.Nullable;
import java.util.Map; import java.util.Map;
import java.util.Objects; 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 * A listener for placeholder signs being clicked
*/ */
@ -44,7 +49,7 @@ public class SignClickListener implements Listener {
SignCopyRequest signCopyRequest = requestHandler.getSignCopyRequest(player); SignCopyRequest signCopyRequest = requestHandler.getSignCopyRequest(player);
if (signCopyRequest != null) { if (signCopyRequest != null) {
if (checkWaxEdit(sign, player, "placeholdersigns.copy.bypass-waxed")) { if (checkWaxEdit(sign, player, PlaceholderSignsPermission.BYPASS_WAXED_COPY.getPermissionNode())) {
return; return;
} }
@ -53,19 +58,167 @@ public class SignClickListener implements Listener {
return; return;
} }
// Check if the player has run the /editSign command // Check if the player has run the /setSignLine command
SignLineChangeRequest request = requestHandler.getSignChangeRequest(player); SignLineChangeRequest signChangeRequest = requestHandler.getSignChangeRequest(player);
if (request != null) { if (signChangeRequest != null) {
if (checkWaxEdit(sign, player, "placeholdersigns.edit.bypass-waxed")) { if (checkWaxEdit(sign, player, PlaceholderSignsPermission.BYPASS_WAXED_EDIT.getPermissionNode())) {
return; return;
} }
SignSide standingOn = sign.getTargetSide(player); doSignChange(sign, signChangeRequest.line(), player, getSide(sign, player), signChangeRequest.text());
Side side = sign.getSide(Side.FRONT).equals(standingOn) ? Side.FRONT : Side.BACK; event.setCancelled(true);
doSignChange(sign, request, event, player, side); 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 <p>The sign to paste onto</p>
* @param signTextCopyRequest <p>The requested sign text copy request to fulfill</p>
* @param player <p>The player wanting to paste</p>
* @param clickedSide <p>The sign side the player wants to paste onto</p>
*/
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 <p>The sign text copy request to handle</p>
* @param sourcePlaceholderSign <p>The source sign's placeholder sign, or null</p>
* @param sourceSide <p>The side of the source sign to copy text from</p>
* @param clickedSide <p>The side the player clicked to paste</p>
* @param targetSign <p>The sign to paste text onto</p>
* @param player <p>The player performing the copy paste</p>
*/
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<Side, Map<Integer, String>> 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 <p>The sign handler to use for dealing with placeholder signs</p>
* @param sourcePlaceholderSign <p>The source sign's placeholder sign, or null</p>
* @param targetPlaceholderSign <p>The destination's placeholder sign, or null</p>
* @param sourceSide <p>The side of the source sign to copy text from</p>
* @param clickedSide <p>The side the player clicked to paste</p>
* @param sourceSign <p>The sign to copy text from</p>
* @param targetSign <p>The sign to paste text onto</p>
* @param player <p>The player performing the copy paste</p>
*/
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<Integer, String> 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 <p>The sign handler to use for dealing with placeholder signs</p>
* @param sourcePlaceholderSign <p>The source sign's placeholder sign, or null</p>
* @param targetPlaceholderSign <p>The destination's placeholder sign, or null</p>
* @param clickedSide <p>The side the player clicked to paste</p>
* @param sourceSign <p>The sign to copy text from</p>
* @param targetSign <p>The sign to paste text onto</p>
* @param player <p>The player performing the copy paste</p>
*/
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 * 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 * Pastes the data from the source sign to the target sign
* *
* @param source <p>The source sign to copy data from</p> * @param sourceSign <p>The source sign to copy data from</p>
* @param target <p>The target sign to paste to</p> * @param targetSign <p>The target sign to paste to</p>
* @param player <p>The player pasting the sign</p> * @param player <p>The player pasting the sign</p>
*/ */
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(); StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(source.getLocation()); PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(
SignSide sourceFront = source.getSide(Side.FRONT); sourceSign.getLocation());
SignSide sourceBack = source.getSide(Side.BACK);
SignSide targetFront = target.getSide(Side.FRONT);
SignSide targetBack = target.getSide(Side.BACK);
// Get the lines, with placeholders replaced, and the sign change event processed // Get the lines, with placeholders replaced, and the sign change event processed
String[] frontLines = getFinalLines(target, sourceFront, Side.FRONT, placeholderSign, player); if (pasteSignSide(Side.FRONT, Side.FRONT, sourceSign, targetSign, placeholderSign, player) ||
if (frontLines == null) { pasteSignSide(Side.BACK, Side.BACK, sourceSign, targetSign, placeholderSign, player)) {
stringFormatter.displayErrorMessage(player, PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);
return; 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 // Copy options
copySignOptions(source, target); copySignOptions(sourceSign, targetSign);
// Apply changes // Apply changes
target.update(); targetSign.update();
stringFormatter.displaySuccessMessage(player, PlaceholderSignMessage.SUCCESS_SIGN_PASTED); stringFormatter.displaySuccessMessage(player, PlaceholderSignMessage.SUCCESS_SIGN_PASTED);
} }
/**
* Pastes one side of a sign onto another
*
* @param sourceSide <p>The sign side to copy</p>
* @param destinationSide <p>The sign side to paste to</p>
* @param source <p>The source sign</p>
* @param target <p>The destination sign</p>
* @param placeholderSign <p>The placeholder sign belonging to the source sign, or null</p>
* @param player <p>The player performing the paste</p>
* @return <p>False if the pasting completed successfully. True if blocked by another plugin.</p>
*/
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 * Gets the final lines from a sign side, after inserting placeholders and running a sign change event
* *
* @param sign <p>The sign that's changed</p> * @param sign <p>The sign that's changed</p>
* @param signSide <p>The side of the sign to get lines from</p> * @param signSide <p>The side of the sign to get lines from</p>
* @param side <p>The side that's processed</p> * @param sourceSide <p>The side that text is taken from</p>
* @param targetSide <p>The side that text is changed for</p>
* @param placeholderSign <p>The placeholder sign corresponding to the sign, if any</p> * @param placeholderSign <p>The placeholder sign corresponding to the sign, if any</p>
* @param player <p>The player attempting to paste the sign</p> * @param player <p>The player attempting to paste the sign</p>
* @return <p>The final lines, or null if the event was cancelled</p> * @return <p>The final lines, or null if the event was cancelled</p>
*/ */
@Nullable @Nullable
private String[] getFinalLines(@NotNull Sign sign, @NotNull SignSide signSide, @NotNull Side side, private String[] getFinalLines(@NotNull Sign sign, @NotNull SignSide signSide, @NotNull Side sourceSide,
@Nullable PlaceholderSign placeholderSign, @NotNull Player player) { @NotNull Side targetSide, @Nullable PlaceholderSign placeholderSign,
@NotNull Player player) {
String[] sideLines = signSide.getLines(); String[] sideLines = signSide.getLines();
if (placeholderSign != null) { if (placeholderSign != null) {
Map<Side, Map<Integer, String>> placeholders = placeholderSign.placeholders(); Map<Side, Map<Integer, String>> 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 // 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); Bukkit.getPluginManager().callEvent(changeEvent);
if (changeEvent.isCancelled()) { if (changeEvent.isCancelled()) {
return null; 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 <p>The sign to be changed</p> * @param targetSign <p>The sign to be changed</p>
* @param request <p>The sign line change request to perform</p> * @param destinationLine <p>The line to be changed on the sign</p>
* @param event <p>The interaction event that triggered this</p> * @param player <p>The player trying to change the sign</p>
* @param player <p>The player triggering the sign change</p> * @param targetSide <p>The side of the sign to change the line for</p>
* @param newText <p>The new text to apply to the sign</p>
*/ */
private void doSignChange(@NotNull Sign sign, @NotNull SignLineChangeRequest request, private void doSignChange(@NotNull Sign targetSign, int destinationLine, @NotNull Player player,
@NotNull PlayerInteractEvent event, @NotNull Player player, @NotNull Side side) { @NotNull Side targetSide, @NotNull String newText) {
// Cancel the event to prevent vanilla behavior SignSide targetSignSide = targetSign.getSide(targetSide);
event.setCancelled(true); String[] lines = targetSignSide.getLines();
SignSide signSide = sign.getSide(side);
String[] lines = signSide.getLines();
PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler(); PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler();
PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(sign.getLocation()); PlaceholderSign targetPlaceholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(targetSign.getLocation());
String oldPlaceholder = null; String oldPlaceholder = null;
if (placeholderSign != null) { if (targetPlaceholderSign != null) {
// Remove the old placeholder // 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 // 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()), SignChangeEvent changeEvent = new SignChangeEvent(targetSign.getBlock(), player, lines, targetSide);
player, lines, side);
Bukkit.getPluginManager().callEvent(changeEvent); Bukkit.getPluginManager().callEvent(changeEvent);
if (changeEvent.isCancelled()) { if (changeEvent.isCancelled()) {
if (placeholderSign != null) { if (targetPlaceholderSign != null) {
// Restore the old placeholder if the action didn't complete // 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, PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player,
PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION); PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);
@ -242,11 +404,9 @@ public class SignClickListener implements Listener {
} }
// Update the sign with the new text // Update the sign with the new text
String[] finalLines = changeEvent.getLines(); targetSignSide.setLine(destinationLine, ColorHelper.translateColorCodes(changeEvent.getLines()[destinationLine],
for (int i = 0; i < finalLines.length; i++) { ColorConversion.RGB));
signSide.setLine(i, ColorHelper.translateColorCodes(finalLines[i], ColorConversion.RGB)); targetSign.update();
}
sign.update();
// Save any placeholder changes // Save any placeholder changes
signHandler.save(); signHandler.save();

View File

@ -2,6 +2,7 @@ package net.knarcraft.placeholdersigns.listener;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import net.knarcraft.placeholdersigns.PlaceholderSigns; import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignsPermission;
import net.knarcraft.placeholdersigns.container.PlaceholderSign; import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import org.bukkit.Location; import org.bukkit.Location;
@ -23,7 +24,7 @@ public class SignTextListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSignCreateOrEdit(@NotNull SignChangeEvent event) { public void onSignCreateOrEdit(@NotNull SignChangeEvent event) {
// Only check for placeholders if the player is allowed to // 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; return;
} }

View File

@ -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 <p>The sign to check</p>
* @param player <p>The player to check</p>
* @return <p>The side of the sign the player is standing on</p>
*/
@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 <p>The side to get the opposite of</p>
* @return <p>The opposite side</p>
*/
@NotNull
public static Side getOpposite(@NotNull Side side) {
return side == Side.FRONT ? Side.BACK : Side.FRONT;
}
}

View File

@ -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<String> lineNumbers;
private static final List<String> 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 <p>Possible sign line numbers</p>
*/
@NotNull
public static List<String> getLineNumbers() {
return new ArrayList<>(lineNumbers);
}
/**
* Gets possible sign sides
*
* @return <p>Possible sign sides</p>
*/
@NotNull
public static List<String> getSignSides() {
return new ArrayList<>(signSides);
}
}

View File

@ -17,10 +17,18 @@ commands:
copySign: copySign:
usage: /<command> usage: /<command>
permission: placeholdersigns.copy.use 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: unWaxSign:
usage: /<command> usage: /<command>
permission: placeholdersigns.unwax permission: placeholdersigns.unwax
copySignText:
usage: |
/<command> [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: permissions:
placeholdersigns.*: placeholdersigns.*:
@ -32,8 +40,14 @@ permissions:
- placeholdersigns.copy - placeholdersigns.copy
- placeholdersigns.unwax - placeholdersigns.unwax
default: op 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: 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 default: false
children: children:
- placeholdersigns.edit.use - placeholdersigns.edit.use
@ -45,23 +59,23 @@ permissions:
- placeholdersigns.copy.use - placeholdersigns.copy.use
- placeholdersigns.copy.bypass-waxed - placeholdersigns.copy.bypass-waxed
placeholdersigns.edit.use: placeholdersigns.edit.use:
description: Allows a player to use the /editSign command description: Allows a player to use the /setSignLine command
default: false default: false
placeholdersigns.edit.bypass-waxed: 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 default: false
placeholdersigns.placeholder: 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 default: false
placeholdersigns.view: placeholdersigns.view:
description: Allows a player to see the full text of a sign in the chat description: Allows a player to use the /viewSign command
default: true default: false
placeholdersigns.copy.use: 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 default: false
placeholdersigns.copy.bypass-waxed: placeholdersigns.copy.bypass-waxed:
description: Allows a player to use the /copySign command and paste onto a waxed sign description: Allows a player to use the /copySign command and paste onto a waxed sign
default: false default: false
placeholdersigns.unwax: placeholdersigns.unwax:
description: Allows a player to remove the wax from a sign description: Allows a player to use the /unWax command
default: false default: false