Adds a copySign command and stuff
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good

Adds a new copySign command that copies all info about a sign, including current lines, placeholders, waxed state, dye for each side, and glow state for each side.
Renames editSign to setSignLine to avoid collision with EssentialsX's editSign
Reduces viewSign to a single command
Makes viewSign display the looked at sign, instead of creating a session
Adds some additional messages
Adds missing documentation to the README
This commit is contained in:
Kristian Knarvik 2024-04-23 17:29:35 +02:00
parent b33f514dca
commit 66c45e00e2
12 changed files with 449 additions and 265 deletions

View File

@ -17,16 +17,21 @@ won't be changed unless the player passes all world protection checks.
## Commands
| Command | Arguments | Description |
|-----------|-----------------------------|-----------------------------------------------------------------------------------------------|
| /editSign | \<line> \<text> \<text> ... | Sets the text of the sign line (1-4) to the given input. Then right-click the sign to update. |
| 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] | Allows the player to view the full contents and details of the looked at sign. If "raw" is true, formatting codes are displayed. If placeholders is true, stored placeholders are displayed. |
| /copySign | | Allows the player to copy the sign they are currently looking at to another sign, including placeholders, formatting codes, dye and waxed state. |
## Permissions
| Permission | Description |
|------------------------------------|---------------------------------------------------------------------------------------------------------------|
| placeholdersigns.* | Gives all permissions. |
| placeholdersigns.edit | Allows unrestricted use of the /editSign command. |
| placeholdersigns.edit.use | Allows use of the /editSign command. |
| placeholdersigns.edit.bypass-waxed | Allows use of the /editSign command on a waxed sign. |
| 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. |
| 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. |

View File

@ -3,8 +3,8 @@ package net.knarcraft.placeholdersigns;
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.EditSignCommand;
import net.knarcraft.placeholdersigns.command.ViewPlaceholderSignCommand;
import net.knarcraft.placeholdersigns.command.ViewSignCommand;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
@ -92,11 +92,9 @@ public final class PlaceholderSigns extends JavaPlugin {
Bukkit.getPluginManager().registerEvents(new SignTextListener(), this);
Bukkit.getPluginManager().registerEvents(new SignClickListener(), this);
registerCommand("editSign", new EditSignCommand());
registerCommand("viewSign", new ViewSignCommand(false));
registerCommand("viewSignRaw", new ViewSignCommand(true));
registerCommand("viewPlaceholderSign", new ViewPlaceholderSignCommand(false));
registerCommand("viewPlaceholderSignRaw", new ViewPlaceholderSignCommand(true));
registerCommand("setSignLine", new EditSignCommand());
registerCommand("viewSign", new ViewSignCommand());
registerCommand("copySign", new CopySignCommand());
}
@Override

View File

@ -0,0 +1,54 @@
package net.knarcraft.placeholdersigns.command;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.container.SignCopyRequest;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
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 signs, including placeholders
*/
public class CopySignCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] strings) {
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;
}
SignCopyRequest signCopyRequest = new SignCopyRequest(player, sign);
placeholderSigns.getRequestHandler().addSignCopyRequest(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[] strings) {
return new ArrayList<>();
}
}

View File

@ -1,5 +1,6 @@
package net.knarcraft.placeholdersigns.command;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.container.SignLineChangeRequest;
@ -30,7 +31,13 @@ public class EditSignCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (args.length < 1 || !(commandSender instanceof Player player)) {
PlaceholderSigns placeholderSigns = PlaceholderSigns.getInstance();
StringFormatter stringFormatter = placeholderSigns.getStringFormatter();
if (!(commandSender instanceof Player player)) {
stringFormatter.displayErrorMessage(commandSender, PlaceholderSignMessage.ERROR_PLAYER_ONLY);
return false;
}
if (args.length < 1) {
return false;
}
@ -61,11 +68,9 @@ public class EditSignCommand implements TabExecutor {
// Register the line change request
SignLineChangeRequest request = new SignLineChangeRequest(player, lineNumber - 1, builder.toString());
PlaceholderSigns placeholderSigns = PlaceholderSigns.getInstance();
placeholderSigns.getRequestHandler().addSignChangeRequest(request);
placeholderSigns.getStringFormatter().displaySuccessMessage(commandSender,
PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT);
stringFormatter.displaySuccessMessage(commandSender, PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT);
return true;
}

View File

@ -1,53 +0,0 @@
package net.knarcraft.placeholdersigns.command;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
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 viewing placeholders on a sign
*/
public class ViewPlaceholderSignCommand implements TabExecutor {
private final boolean raw;
/**
* Instantiates a new view sign command
*
* @param raw <p>Whether this sign displays the raw text or not</p>
*/
public ViewPlaceholderSignCommand(boolean raw) {
this.raw = raw;
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (!(commandSender instanceof Player player)) {
return false;
}
// Register the sign view request
PlaceholderSigns.getInstance().getRequestHandler().addPlaceholderSignViewRequest(player, this.raw);
PlaceholderSigns.getInstance().getStringFormatter().displaySuccessMessage(commandSender,
PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_VIEW);
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -1,7 +1,19 @@
package net.knarcraft.placeholdersigns.command;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.StringReplacer;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.knarlib.util.TabCompletionHelper;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.DyeColor;
import org.bukkit.Location;
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;
@ -11,43 +23,172 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A command for viewing lines on a sign
* A generic sign view command
*/
public class ViewSignCommand implements TabExecutor {
private final boolean raw;
/**
* Instantiates a new view sign command
*
* @param raw <p>Whether this sign displays the raw text or not</p>
*/
public ViewSignCommand(boolean raw) {
this.raw = raw;
}
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
@NotNull String[] arguments) {
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
if (!(commandSender instanceof Player player)) {
stringFormatter.displayErrorMessage(commandSender, PlaceholderSignMessage.ERROR_PLAYER_ONLY);
return false;
}
// Register the sign view request
PlaceholderSigns.getInstance().getRequestHandler().addSignViewRequest(player, this.raw);
boolean showRawText = true;
boolean showPlaceholders = true;
if (arguments.length > 0) {
showRawText = Boolean.parseBoolean(arguments[0]);
}
if (arguments.length > 1) {
showPlaceholders = Boolean.parseBoolean(arguments[1]);
}
PlaceholderSigns.getInstance().getStringFormatter().displaySuccessMessage(commandSender,
PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_VIEW);
Block targetBlock = player.getTargetBlockExact(7);
if (targetBlock == null || !(targetBlock.getState() instanceof Sign sign)) {
stringFormatter.displayErrorMessage(commandSender, PlaceholderSignMessage.ERROR_NOT_LOOKING_AT_SIGN);
return false;
}
printSign(sign, player, showRawText, showPlaceholders);
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
return new ArrayList<>();
@NotNull String[] arguments) {
if (arguments.length == 1) {
return getBooleanTabCompletions(arguments[0]);
} else if (arguments.length == 2) {
return getBooleanTabCompletions(arguments[1]);
} else {
return new ArrayList<>();
}
}
/**
* Gets boolean tab-completions
*
* @param filter <p>The input argument to filter by</p>
* @return <p>The the resulting boolean tab-completions</p>
*/
@NotNull
private List<String> getBooleanTabCompletions(@NotNull String filter) {
return TabCompletionHelper.filterMatchingStartsWith(List.of("true", "false"), filter);
}
/**
* Prints the current contents of a sign to a player
*
* @param sign <p>The sign to print</p>
* @param player <p>The player to display the contents to</p>
*/
private void printSign(@NotNull Sign sign, @NotNull Player player, boolean showRawText, boolean showPlaceholders) {
Location location = sign.getLocation();
SignSide front = sign.getSide(Side.FRONT);
SignSide back = sign.getSide(Side.BACK);
String frontLines = showPlaceholders ? getPlaceholderSignText(location, front, Side.FRONT, showRawText) :
getSignText(front.getLines(), showRawText);
String backLines = showPlaceholders ? getPlaceholderSignText(location, back, Side.BACK, showRawText) :
getSignText(back.getLines(), showRawText);
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
StringReplacer replacer = new StringReplacer(stringFormatter.getUnformattedColoredMessage(
PlaceholderSignMessage.SUCCESS_SIGN_CONTENTS));
replacer.add("{frontLines}", frontLines);
replacer.add("{backLines}", backLines);
replacer.add("{frontDye}", getDye(front));
replacer.add("{frontGlow}", getStatus(front.isGlowingText()));
replacer.add("{backDye}", getDye(back));
replacer.add("{backGlow}", getStatus(back.isGlowingText()));
replacer.add("{waxed}", getStatus(sign.isWaxed()));
player.sendMessage(replacer.replace());
}
/**
* Gets a description of the status of a sign property
*
* @param isTrue <p>Whether the property is true</p>
* @return <p>A description of the property's current status</p>
*/
@NotNull
private String getStatus(boolean isTrue) {
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
if (isTrue) {
return stringFormatter.getUnformattedColoredMessage(PlaceholderSignMessage.SIGN_PROPERTY_CONFIRM);
} else {
return stringFormatter.getUnformattedColoredMessage(PlaceholderSignMessage.SIGN_PROPERTY_DENY);
}
}
/**
* Gets a description of a dye applied to a sign
*
* @param signSide <p>The sign side to get the dye of</p>
* @return <p>The description of the applied dye</p>
*/
@NotNull
private String getDye(@NotNull SignSide signSide) {
DyeColor dyeColor = signSide.getColor();
if (dyeColor == null) {
return "None";
}
ChatColor color = ColorHelper.fromColor(dyeColor.getColor());
return color + signSide.getColor().name();
}
/**
* Gets text from a sign side, and appends it to the given string builder
*
* @param signLocation <p>The location of the sign</p>
* @param signSide <p>The sign side to get text from</p>
* @param side <p>The side of the sign to get placeholders from</p>
* @param raw <p>Whether to get the raw text with used formatting codes</p>
*/
@NotNull
private String getPlaceholderSignText(@NotNull Location signLocation, @NotNull SignSide signSide,
@NotNull Side side, boolean raw) {
String[] lines = signSide.getLines();
PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(signLocation);
if (placeholderSign == null) {
return getSignText(lines, raw);
}
Map<Integer, String> placeholders = placeholderSign.placeholders().get(side);
for (Map.Entry<Integer, String> entry : placeholders.entrySet()) {
lines[entry.getKey()] = entry.getValue();
}
return getSignText(lines, raw);
}
/**
* Gets text from a sign side, and appends it to the given string builder
*
* @param lines <p>The lines on the sign</p>
* @param raw <p>Whether to get the raw text with used formatting codes</p>
*/
@NotNull
private String getSignText(@NotNull String[] lines, boolean raw) {
StringBuilder output = new StringBuilder();
for (int i = 0; i < 4; i++) {
output.append(i + 1).append(". ");
String line = lines[i];
if (raw) {
output.append(line.replace(ChatColor.COLOR_CHAR, '&'));
} else {
output.append(line);
}
output.append(ChatColor.COLOR_CHAR).append("r\n");
}
return output.toString();
}
}

View File

@ -13,11 +13,6 @@ public enum PlaceholderSignMessage implements TranslatableMessage {
*/
SUCCESS_CLICK_SIGN_TO_EDIT,
/**
* The message to display when waiting for a sign selection (view)
*/
SUCCESS_CLICK_SIGN_TO_VIEW,
/**
* The message displayed when a player tries to edit a waxed sign without the necessary permission
*/
@ -42,6 +37,31 @@ public enum PlaceholderSignMessage implements TranslatableMessage {
* The string displayed when a sign property is denied
*/
SIGN_PROPERTY_DENY,
/**
* The message displayed when a player command is used from the console
*/
ERROR_PLAYER_ONLY,
/**
* The message to display when the player is not looking at a sign, which is required
*/
ERROR_NOT_LOOKING_AT_SIGN,
/**
* The message displayed when ready to paste a sign
*/
SUCCESS_CLICK_SIGN_TO_PASTE,
/**
* The message displayed when a sign is successfully pasted
*/
SUCCESS_SIGN_PASTED,
/**
* The message displayed when a protection plugin cancels the sign change event
*/
ERROR_CANCELLED_BY_PROTECTION,
;
@Override

View File

@ -0,0 +1,14 @@
package net.knarcraft.placeholdersigns.container;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* 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>
*/
public record SignCopyRequest(@NotNull Player player, @NotNull Sign sign) {
}

View File

@ -1,5 +1,6 @@
package net.knarcraft.placeholdersigns.handler;
import net.knarcraft.placeholdersigns.container.SignCopyRequest;
import net.knarcraft.placeholdersigns.container.SignLineChangeRequest;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@ -13,17 +14,38 @@ import java.util.Map;
*/
public class PlaceholderSignRequestHandler {
private final Map<Player, SignLineChangeRequest> signChangeRequests;
private final Map<Player, Boolean> signViewRequests;
private final Map<Player, Boolean> placeholderSignViewRequests;
private final @NotNull Map<Player, SignLineChangeRequest> signChangeRequests;
private final Map<Player, SignCopyRequest> signCopyRequests;
/**
* Instantiates a new placeholder sign request handler
*/
public PlaceholderSignRequestHandler() {
this.signChangeRequests = new HashMap<>();
this.signViewRequests = new HashMap<>();
this.placeholderSignViewRequests = new HashMap<>();
this.signCopyRequests = new HashMap<>();
}
/**
* Registers a sign copy request
*
* <p>A sign copy request is basically the result of running the copySign command, which must be stored until the
* player clicks a sign.</p>
*
* @param request <p>The sign copy request to register</p>
*/
public void addSignCopyRequest(@NotNull SignCopyRequest request) {
this.signCopyRequests.put(request.player(), request);
}
/**
* Gets a sign copy request
*
* @param player <p>The player to get the request for</p>
* @return <p>The sign copy request, or null if not found</p>
*/
@Nullable
public SignCopyRequest getSignCopyRequest(@NotNull Player player) {
return this.signCopyRequests.remove(player);
}
/**
@ -49,54 +71,4 @@ public class PlaceholderSignRequestHandler {
return this.signChangeRequests.remove(player);
}
/**
* Registers a sign view request
*
* @param player <p>The player requesting to view a sign</p>
* @param raw <p>Whether to display the raw text (visible formatting codes)</p>
*/
public void addSignViewRequest(@NotNull Player player, boolean raw) {
this.signViewRequests.put(player, raw);
}
/**
* Checks whether the given player has a sign view request
*
* @param player <p>The player to check</p>
* @return <p>Not null if the player has requested to view a sign</p>
*/
@Nullable
public Boolean getSignViewRequest(@NotNull Player player) {
if (this.signViewRequests.containsKey(player)) {
return this.signViewRequests.remove(player);
} else {
return null;
}
}
/**
* Registers a placeholder view request
*
* @param player <p>The player requesting to view a placeholder sign</p>
* @param raw <p>Whether to display the raw text (visible formatting codes)</p>
*/
public void addPlaceholderSignViewRequest(Player player, boolean raw) {
this.placeholderSignViewRequests.put(player, raw);
}
/**
* Checks whether the given player has a placeholder sign view request
*
* @param player <p>The player to check</p>
* @return <p>Not null if the player has requested to view a sign</p>
*/
@Nullable
public Boolean getPlaceholderSignViewRequest(@NotNull Player player) {
if (this.placeholderSignViewRequests.containsKey(player)) {
return this.placeholderSignViewRequests.remove(player);
} else {
return null;
}
}
}

View File

@ -1,19 +1,16 @@
package net.knarcraft.placeholdersigns.listener;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.StringReplacer;
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.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.container.SignCopyRequest;
import net.knarcraft.placeholdersigns.container.SignLineChangeRequest;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
@ -24,6 +21,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Objects;
@ -44,17 +42,13 @@ public class SignClickListener implements Listener {
Player player = event.getPlayer();
PlaceholderSignRequestHandler requestHandler = PlaceholderSigns.getInstance().getRequestHandler();
Boolean hasSignViewRequest = requestHandler.getSignViewRequest(player);
if (hasSignViewRequest != null) {
printSign(sign, player, hasSignViewRequest, false);
// Cancel the event to prevent vanilla behavior
event.setCancelled(true);
return;
}
SignCopyRequest signCopyRequest = requestHandler.getSignCopyRequest(player);
if (signCopyRequest != null) {
if (checkWaxEdit(sign, player, "placeholdersigns.copy.bypass-waxed")) {
return;
}
Boolean hasPlaceholderSignViewRequest = requestHandler.getPlaceholderSignViewRequest(player);
if (hasPlaceholderSignViewRequest != null) {
printSign(sign, player, hasPlaceholderSignViewRequest, true);
pasteSign(signCopyRequest.sign(), sign, player);
event.setCancelled(true);
return;
}
@ -62,9 +56,7 @@ public class SignClickListener implements Listener {
// Check if the player has run the /editSign command
SignLineChangeRequest request = requestHandler.getSignChangeRequest(player);
if (request != null) {
if (sign.isWaxed() && !player.hasPermission("placeholdersigns.edit.bypass-waxed")) {
PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player,
PlaceholderSignMessage.ERROR_WAXED_NO_PERMISSION);
if (checkWaxEdit(sign, player, "placeholdersigns.edit.bypass-waxed")) {
return;
}
@ -75,114 +67,138 @@ public class SignClickListener implements Listener {
}
/**
* Prints the current contents of a sign to a player
* Checks if the player is trying to edit/modify a waxed sign without permission
*
* @param sign <p>The sign to print</p>
* @param player <p>The player to display the contents to</p>
* @param raw <p>Whether to get the raw text with used formatting codes</p>
* @param showPlaceholders <p>Whether to show the actual placeholders stored on the sign</p>
* @param sign <p>The sign the player is trying to edit</p>
* @param player <p>The player trying to edit the sign</p>
* @return <p>True if the player is trying to edit a waxed sign without permission</p>
*/
private void printSign(@NotNull Sign sign, @NotNull Player player, boolean raw, boolean showPlaceholders) {
Location location = sign.getLocation();
SignSide front = sign.getSide(Side.FRONT);
SignSide back = sign.getSide(Side.BACK);
String frontLines = showPlaceholders ? getPlaceholderSignText(location, front, Side.FRONT, raw) :
getSignText(front.getLines(), raw);
String backLines = showPlaceholders ? getPlaceholderSignText(location, back, Side.BACK, raw) :
getSignText(back.getLines(), raw);
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
StringReplacer replacer = new StringReplacer(stringFormatter.getUnformattedColoredMessage(
PlaceholderSignMessage.SUCCESS_SIGN_CONTENTS));
replacer.add("{frontLines}", frontLines);
replacer.add("{backLines}", backLines);
replacer.add("{frontDye}", getDye(front));
replacer.add("{frontGlow}", getStatus(front.isGlowingText()));
replacer.add("{backDye}", getDye(back));
replacer.add("{backGlow}", getStatus(back.isGlowingText()));
replacer.add("{waxed}", getStatus(sign.isWaxed()));
player.sendMessage(replacer.replace());
}
/**
* Gets a description of the status of a sign property
*
* @param isTrue <p>Whether the property is true</p>
* @return <p>A description of the property's current status</p>
*/
@NotNull
private String getStatus(boolean isTrue) {
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
if (isTrue) {
return stringFormatter.getUnformattedColoredMessage(PlaceholderSignMessage.SIGN_PROPERTY_CONFIRM);
private boolean checkWaxEdit(@NotNull Sign sign, @NotNull Player player, @NotNull String permissionNode) {
if (sign.isWaxed() && !player.hasPermission(permissionNode)) {
PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player,
PlaceholderSignMessage.ERROR_WAXED_NO_PERMISSION);
return true;
} else {
return stringFormatter.getUnformattedColoredMessage(PlaceholderSignMessage.SIGN_PROPERTY_DENY);
return false;
}
}
/**
* Gets a description of a dye applied to a sign
* Pastes the data from the source sign to the target sign
*
* @param signSide <p>The sign side to get the dye of</p>
* @return <p>The description of the applied dye</p>
* @param source <p>The source sign to copy data from</p>
* @param target <p>The target sign to paste to</p>
* @param player <p>The player pasting the sign</p>
*/
@NotNull
private String getDye(@NotNull SignSide signSide) {
DyeColor dyeColor = signSide.getColor();
if (dyeColor == null) {
return "None";
private void pasteSign(@NotNull Sign source, @NotNull Sign target, @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);
// 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);
return;
}
String[] backLines = getFinalLines(target, sourceBack, Side.BACK, placeholderSign, player);
if (backLines == null) {
stringFormatter.displayErrorMessage(player, PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);
return;
}
ChatColor color = ColorHelper.fromColor(dyeColor.getColor());
return color + signSide.getColor().name();
// 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);
// Apply changes
target.update();
stringFormatter.displaySuccessMessage(player, PlaceholderSignMessage.SUCCESS_SIGN_PASTED);
}
/**
* Gets text from a sign side, and appends it to the given string builder
* Gets the final lines from a sign side, after inserting placeholders and running a sign change event
*
* @param signLocation <p>The location of the sign</p>
* @param signSide <p>The sign side to get text from</p>
* @param side <p>The side of the sign to get placeholders from</p>
* @param raw <p>Whether to get the raw text with used formatting codes</p>
* @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 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>
*/
@NotNull
private String getPlaceholderSignText(@NotNull Location signLocation, @NotNull SignSide signSide,
@NotNull Side side, boolean raw) {
String[] lines = signSide.getLines();
PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(signLocation);
if (placeholderSign == null) {
return getSignText(lines, raw);
@Nullable
private String[] getFinalLines(@NotNull Sign sign, @NotNull SignSide signSide, @NotNull Side side,
@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);
}
Map<Integer, String> placeholders = placeholderSign.placeholders().get(side);
for (Map.Entry<Integer, String> entry : placeholders.entrySet()) {
lines[entry.getKey()] = entry.getValue();
// 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);
Bukkit.getPluginManager().callEvent(changeEvent);
if (changeEvent.isCancelled()) {
return null;
}
return getSignText(lines, raw);
return changeEvent.getLines();
}
/**
* Gets text from a sign side, and appends it to the given string builder
* Copies all sign option (dye, glowing, waxed) from the source to the target
*
* @param lines <p>The lines on the sign</p>
* @param raw <p>Whether to get the raw text with used formatting codes</p>
* @param source <p>The source sign</p>
* @param target <p>The target sign</p>
*/
@NotNull
private String getSignText(@NotNull String[] lines, boolean raw) {
StringBuilder output = new StringBuilder();
private void copySignOptions(@NotNull Sign source, @NotNull Sign target) {
SignSide sourceFront = source.getSide(Side.FRONT);
SignSide sourceBack = source.getSide(Side.BACK);
SignSide targetFront = target.getSide(Side.FRONT);
SignSide targetBack = target.getSide(Side.BACK);
// Copy glowing state
targetFront.setGlowingText(sourceFront.isGlowingText());
targetBack.setGlowingText(sourceBack.isGlowingText());
// Copy applied dye
targetFront.setColor(sourceFront.getColor());
targetBack.setColor(sourceBack.getColor());
// Apply waxed state
target.setWaxed(source.isWaxed());
}
/**
* Loads placeholders from one side of a sign
*
* @param side <p>The side to load from</p>
* @param lines <p>The array to write placeholders to</p>
* @param placeholders <p>The placeholders to load</p>
*/
private void loadPlaceholders(@NotNull Side side, @NotNull String[] lines,
@NotNull Map<Side, Map<Integer, String>> placeholders) {
if (!placeholders.containsKey(side)) {
return;
}
Map<Integer, String> sidePlaceholders = placeholders.get(side);
for (int i = 0; i < 4; i++) {
output.append(i + 1).append(". ");
String line = lines[i];
if (raw) {
output.append(line.replace(ChatColor.COLOR_CHAR, '&'));
} else {
output.append(line);
if (sidePlaceholders.containsKey(i)) {
lines[i] = sidePlaceholders.get(i);
}
output.append(ChatColor.COLOR_CHAR).append("r\n");
}
return output.toString();
}
/**
@ -220,6 +236,8 @@ public class SignClickListener implements Listener {
// Restore the old placeholder if the action didn't complete
placeholderSign.placeholders().get(side).put(request.line(), oldPlaceholder);
}
PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player,
PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);
return;
}

View File

@ -6,33 +6,27 @@ depend:
- PlaceholderAPI
commands:
editSign:
setSignLine:
usage: /<command> <line> [text] [text] ...
permission: placeholdersigns.edit
permission: placeholdersigns.edit.use
description: Changes the line of a sign, without a text limit, and with color conversion
viewSign:
usage: /<command>
usage: /<command> [output raw formatting codes (true/false)] [show placeholders (true/false)]
permission: placeholdersigns.view
description: Displays the contents of a sign in the chat (useful for lines exceeding the viewable area)
viewSignRaw:
copySign:
usage: /<command>
permission: placeholdersigns.view
description: Displays the raw contents of a sign in the chat (useful for lines exceeding the viewable area, or to see formatting used)
viewPlaceholderSign:
usage: /<command>
permission: placeholdersigns.view
description: Displays the contents of a sign in the chat, including stored placeholders
viewPlaceholderSignRaw:
usage: /<command>
permission: placeholdersigns.view
description: Displays the raw contents of a sign in the chat, including stored placeholders
permission: placeholdersigns.copy.use
description: Copies all sign information from one sign to another
permissions:
placeholdersigns.*:
description: Grants all placeholdersigns permissions
children:
- placeholdersigns.edit
- placeholdersigns.placeholder
- placeholdersigns.view
- placeholdersigns.copy
default: op
placeholdersigns.edit:
description: Allows a player to use the /editSign command without restriction
@ -40,6 +34,12 @@ permissions:
children:
- placeholdersigns.edit.use
- placeholdersigns.edit.bypass-waxed
placeholdersigns.copy:
description: Allows a player to copy a sign to another sign
default: false
children:
- placeholdersigns.copy.use
- placeholdersigns.copy.bypass-waxed
placeholdersigns.edit.use:
description: Allows a player to use the /editSign command
default: false
@ -52,3 +52,9 @@ permissions:
placeholdersigns.view:
description: Allows a player to see the full text of a sign in the chat
default: true
placeholdersigns.copy.use:
description: Allows a player to copy a sign to another sign
default: false
placeholdersigns.copy.bypass-waxed:
description: Allows a player to use the /copySign command and paste onto a waxed sign
default: false

View File

@ -1,8 +1,7 @@
en:
SUCCESS_CLICK_SIGN_TO_EDIT: "Please click the sign you want to change."
SUCCESS_CLICK_SIGN_TO_VIEW: "Please click the sign you want to view."
SUCCESS_CLICK_SIGN_TO_EDIT: "&7Please click the sign you want to change."
ERROR_WAXED_NO_PERMISSION: "You do not have the necessary permissions to edit a waxed sign."
SUCCESS_SIGN_CHANGED: "The sign line was successfully changed."
SUCCESS_SIGN_CHANGED: "&7The sign line was successfully changed."
SUCCESS_SIGN_CONTENTS: |
#78da55&lSign contents:&r
#17A057Front:&r
@ -14,3 +13,8 @@ en:
#ffdf32Waxed {waxed}
SIGN_PROPERTY_CONFIRM: "#FDFFC8Yes"
SIGN_PROPERTY_DENY: "#CAC8FFNo"
ERROR_PLAYER_ONLY: "This command must be used by a player"
ERROR_NOT_LOOKING_AT_SIGN: "You are not currently looking at a sign"
SUCCESS_CLICK_SIGN_TO_PASTE: "&7Click the sign you want to paste onto"
SUCCESS_SIGN_PASTED: "&7Sign pasted!"
ERROR_CANCELLED_BY_PROTECTION: "A protection plugin blocked the sign change"