Implements #4
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
This commit is contained in:
parent
14f9fa8833
commit
a34c63b7e2
39
README.md
39
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
|
||||
|
||||
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 | \<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
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
@ -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
|
||||
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<String> 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<String> 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<>();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
}
|
@ -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<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
|
||||
@ -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 <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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 <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
|
||||
*
|
||||
@ -86,68 +239,80 @@ public class SignClickListener implements Listener {
|
||||
/**
|
||||
* Pastes the data from the source sign to the target sign
|
||||
*
|
||||
* @param source <p>The source sign to copy data from</p>
|
||||
* @param target <p>The target sign to paste to</p>
|
||||
* @param sourceSign <p>The source sign to copy data from</p>
|
||||
* @param targetSign <p>The target sign to paste to</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();
|
||||
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 <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
|
||||
*
|
||||
* @param sign <p>The sign that's changed</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 player <p>The player attempting to paste the sign</p>
|
||||
* @return <p>The final lines, or null if the event was cancelled</p>
|
||||
*/
|
||||
@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<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
|
||||
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 <p>The sign to be changed</p>
|
||||
* @param request <p>The sign line change request to perform</p>
|
||||
* @param event <p>The interaction event that triggered this</p>
|
||||
* @param player <p>The player triggering the sign change</p>
|
||||
* @param targetSign <p>The sign to be changed</p>
|
||||
* @param destinationLine <p>The line to be changed on the sign</p>
|
||||
* @param player <p>The player trying to change the sign</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,
|
||||
@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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -17,10 +17,18 @@ commands:
|
||||
copySign:
|
||||
usage: /<command>
|
||||
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: /<command>
|
||||
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:
|
||||
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
|
Loading…
Reference in New Issue
Block a user