Compare commits

..

23 Commits

Author SHA1 Message Date
c9304144fd Fixes some outdated method calls because IntelliJ is kind of shit and refuses to automatically refresh dependencies
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-05-10 15:10:46 +02:00
72beb28a5a Updates dependencies
Some checks failed
KnarCraft/PlaceholderSigns/pipeline/head There was a failure building this commit
2024-05-08 21:48:50 +02:00
5ee861e10a Adds POM relocations
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-05-03 15:24:32 +02:00
295f0c68a4 Fixes a NullPointerException
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-30 15:40:45 +02:00
cb08163d6b Makes sure to un-queue removed signs
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-30 15:26:53 +02:00
59ac1e9107 Makes sure to queue newly created signs for update
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-30 15:24:49 +02:00
0e9985b674 Adds configuration options to the documentation
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-30 03:17:13 +02:00
f8ca5705a5 Implements #1
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-30 03:09:47 +02:00
a34c63b7e2 Implements #4
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-29 19:29:55 +02:00
14f9fa8833 Adds a command for un-waxing a sign
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-28 11:22:18 +02:00
66c45e00e2 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
2024-04-23 17:29:35 +02:00
b33f514dca Prevents a permission error message when not trying to edit a sign
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-23 14:42:53 +02:00
598b6c9cb9 Adds commands for viewing placeholders on a sign
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-22 02:26:08 +02:00
080e6204f4 Fixes #2
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
Makes the edit command able to remove placeholders from signs. Editing the sign normally does not remove the placeholder, as there is no way to differentiate between a player intending to remove a placeholder, and a player not intending to remove a placeholder.
2024-04-21 22:05:09 +02:00
e8c93baac4 Greatly improves display of text
Makes output text configurable
Adds improved formatting and colors when displaying sign contents
Adds information about applied dye and glow status for signs
Properly cancels the default event when using the viewSign and viewSignRaw commands
2024-04-21 21:26:16 +02:00
6129eda989 Fixes an incorrect command usage and missing permission
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-05 15:16:20 +02:00
66bb96631a Fixes some bugs preventing placeholders on the back of signs from working
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-05 03:45:43 +02:00
51564570ad Changes and fixes a lot of things
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
Adds a viewSign command which allows a player to see the entirety of a sign, including any part that overflows the character limit.
Changes the save structure for placeholder signs. This change was necessary to support storage of placeholders on both sides of signs, which is also implemented in this commit.
Updates Spigot
Probably fixes a few bugs
2024-04-04 15:46:12 +02:00
788be0cdcd Prevents sign editing from triggering when changing a sign line
All checks were successful
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
2024-04-04 02:21:52 +02:00
82032dbbbc Makes some 1.20 changes
All checks were successful
EpicKnarvik97/PlaceholderSigns/pipeline/head This commit looks good
KnarCraft/PlaceholderSigns/pipeline/head This commit looks good
Updates spigot version
Updates depreciated sign code
Adds a new permission for editing waxed signs
2023-06-23 20:22:15 +02:00
837eac46b7 Minor improvements
All checks were successful
EpicKnarvik97/PlaceholderSigns/pipeline/head This commit looks good
Makes /editsign convert formatting codes for non-placeholder text
Makes /editsign able to clear a line on a sign
2023-04-07 19:15:40 +02:00
ac8c418991 Adds distribution management to POM
All checks were successful
EpicKnarvik97/PlaceholderSigns/pipeline/head This commit looks good
2023-04-06 17:46:50 +02:00
99d61b239c Adds Jenkinsfile
Some checks failed
EpicKnarvik97/PlaceholderSigns/pipeline/head There was a failure building this commit
2023-04-06 17:44:45 +02:00
30 changed files with 2053 additions and 227 deletions

33
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,33 @@
pipeline {
agent any
tools {
jdk 'JDK17'
}
stages {
stage('Build') {
steps {
echo 'Building...'
sh 'mvn clean & mvn validate & mvn compile'
}
}
stage('Test') {
steps {
echo 'Testing...'
sh 'mvn test'
}
}
stage('Verify') {
steps {
echo 'Verifying...'
sh 'mvn verify -Dmaven.test.skip=true'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
sh 'mvn deploy -Dmaven.install.skip=true -Dmaven.test.skip=true'
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
}
}
}

View File

@ -8,23 +8,48 @@ PlaceholderAPI, the location of the sign, and the lines containing placeholders
set pace by using the saved original lines, and letting PlaceholderAPI replace them. Any color, formatting or RGB color
codes in the original text will be converted each time the sign is updated.
The /editSign command is basically just a command to allow placeholders that won't fit on a sign to be used. As an
The /setSignLine command is basically just a command to allow placeholders that won't fit on a sign to be used. As an
additional benefit, formatting, color and RGB color codes are automatically converted whenever the command is used to
change sign text.
Note that when clicking a sign after using /editSign, a SignChangeEvent is triggered. This means that the sign text
won't be changed unless the player passes all world protection checks.
## Commands
| Command | Arguments | Description |
|-----------|-----------------------------|-----------------------------------------------------------------------------------------------|
| /editSign | \<line> \<text> \<text> ... | Sets the text of the sign line (1-4) to the given input. Then right-click the sign to update. |
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. |
| /setPlaceholderUpdateDelay | \<delay> | Sets the update delay in ticks for the placeholder sign you are currently looking at. Use "null" to un-set the value. 1 second = 20 ticks. |
## Permissions
| Permission | Description |
|------------------------------|---------------------------------------------------------------------------------------------------------------|
| placeholdersigns.* | Gives all permissions. |
| placeholdersigns.edit | Allows the use of the /editSign command. |
| placeholdersigns.placeholder | Allows a player to make signs containing placeholders. Without this, placeholders are treated as normal text. |
| 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 |
| placeholdersigns.setdelay | false | Allows use of the /setPlaceholderUpdateDelay command |
## Configuration options
| Node | Type | Default | Description |
|------------------------|---------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| language | String | en | The language to use. Only "en" is available, but you can add a custom language by copying src/main/resources/strings.yml into the plugin folder, replacing the "en" in the first line and customizing the strings. |
| defaultSignUpdateTicks | Integer | 100 | The number of ticks to wait between each placeholder sign update. This delay is used for any placeholder signs that haven't been overridden using /setPlaceholderUpdateDelay. 1 second = 20 ticks, so the default is 5 seconds. |

53
pom.xml
View File

@ -30,7 +30,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@ -39,6 +39,30 @@
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.knarcraft.knarlib</pattern>
<shadedPattern>net.knarcraft.placeholdersigns.lib.knarlib</shadedPattern>
</relocation>
<relocation>
<pattern>org.jetbrains.annotations</pattern>
<shadedPattern>net.knarcraft.placeholdersigns.lib.annotations</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>net.knarcraft:knarlib</artifact>
<includes>
<include>net/knarcraft/knarlib/**</include>
</includes>
</filter>
<filter>
<artifact>org.jetbrains:annotations</artifact>
<includes>
<include>org/jetbrains/annotations/**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
@ -62,23 +86,38 @@
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>placeholderapi</id>
<id>placeholder-api</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<snapshotRepository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19.4-R0.1-SNAPSHOT</version>
<version>1.20.6-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.1</version>
<scope>provided</scope>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>me.clip</groupId>
@ -86,5 +125,11 @@
<version>2.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.knarcraft</groupId>
<artifactId>knarlib</artifactId>
<version>1.2.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,29 +1,31 @@
package net.knarcraft.placeholdersigns;
import me.clip.placeholderapi.PlaceholderAPI;
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.container.LineChangeRequest;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.command.SetPlaceholderUpdateDelayCommand;
import net.knarcraft.placeholdersigns.command.UnWaxSignCommand;
import net.knarcraft.placeholdersigns.command.ViewSignCommand;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignUpdateQueueHandler;
import net.knarcraft.placeholdersigns.listener.ChunkListener;
import net.knarcraft.placeholdersigns.listener.SignBreakListener;
import net.knarcraft.placeholdersigns.listener.SignClickListener;
import net.knarcraft.placeholdersigns.listener.SignTextListener;
import net.md_5.bungee.api.ChatColor;
import net.knarcraft.placeholdersigns.runnable.SignUpdate;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Sign;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This plugin's main class
@ -32,14 +34,18 @@ public final class PlaceholderSigns extends JavaPlugin {
private static PlaceholderSigns instance;
private PlaceholderSignHandler signHandler;
private Map<Player, LineChangeRequest> changeRequests;
private PlaceholderSignRequestHandler requestHandler;
private PlaceholderSignUpdateQueueHandler updateQueueHandler;
private StringFormatter stringFormatter;
private int signUpdateDelay;
/**
* Gets an instance of this plugin
*
* @return <p>A plugin instance</p>
*/
public static @NotNull PlaceholderSigns getInstance() {
@NotNull
public static PlaceholderSigns getInstance() {
return instance;
}
@ -48,46 +54,74 @@ public final class PlaceholderSigns extends JavaPlugin {
*
* @return <p>The sign handler</p>
*/
public @NotNull PlaceholderSignHandler getSignHandler() {
@NotNull
public PlaceholderSignHandler getSignHandler() {
return this.signHandler;
}
/**
* Registers a sign change request
* Gets this instance's placeholder sign request handler
*
* <p>A sign change request is basically the result of running the editSign command, which must be stored until the
* player clicks a sign.</p>
*
* @param request <p>The sign change request to register</p>
* @return <p>The request handler</p>
*/
public void addChangeRequest(@NotNull LineChangeRequest request) {
changeRequests.put(request.player(), request);
@NotNull
public PlaceholderSignRequestHandler getRequestHandler() {
return this.requestHandler;
}
/**
* Gets a sign change request
* Gets this instance's update queue handler
*
* @param player <p>The player to get the request for</p>
* @return <p>The sign change request, or null if not found</p>
* @return <p>The update queue handler</p>
*/
public @Nullable LineChangeRequest getChangeRequest(@NotNull Player player) {
return changeRequests.remove(player);
@NotNull
public PlaceholderSignUpdateQueueHandler getUpdateQueueHandler() {
return this.updateQueueHandler;
}
@Override
public void onLoad() {
super.onLoad();
// Register serialization classes
ConfigurationSerialization.registerClass(PlaceholderSign.class);
ConfigurationSerialization.registerClass(PlaceholderSignHandler.class);
/**
* Gets the string formatter to use for formatting messages
*
* @return <p>The string formatter</p>
*/
@NotNull
public StringFormatter getStringFormatter() {
return this.stringFormatter;
}
/**
* Gets the default sign update delay
*
* @return <p>The sign update delay</p>
*/
public int getSignUpdateDelay() {
return this.signUpdateDelay;
}
@Override
public void onEnable() {
instance = this;
signHandler = new PlaceholderSignHandler();
changeRequests = new HashMap<>();
signHandler.load();
getConfig().options().copyDefaults(true);
saveConfig();
Translator translator = new Translator();
translator.registerMessageCategory(PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT);
translator.setColorConversion(ColorConversion.RGB);
translator.loadLanguages(this.getDataFolder(), "en", getConfig().getString("language", "en"));
this.stringFormatter = new StringFormatter(this.getDescription().getName(), translator);
this.stringFormatter.setColorConversion(ColorConversion.RGB);
this.stringFormatter.setNamePrefix("#A5682A[&r&l");
this.stringFormatter.setNameSuffix("&r#A5682A]");
signUpdateDelay = getConfig().getInt("defaultSignUpdateTicks", 100);
if (signUpdateDelay < 1) {
signUpdateDelay = 100;
}
this.signHandler = new PlaceholderSignHandler();
this.signHandler.load();
this.requestHandler = new PlaceholderSignRequestHandler();
this.updateQueueHandler = new PlaceholderSignUpdateQueueHandler();
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) {
getLogger().log(Level.WARNING, "Could not find PlaceholderAPI! This plugin is required.");
@ -95,17 +129,20 @@ public final class PlaceholderSigns extends JavaPlugin {
return;
}
// Update signs' placeholders every second
Bukkit.getScheduler().runTaskTimer(this, this::updateSigns, 20 * 10, 20 * 5);
Bukkit.getScheduler().runTaskTimer(this, new SignUpdate(this.signHandler), 20, 1);
Bukkit.getPluginManager().registerEvents(new SignBreakListener(), this);
Bukkit.getPluginManager().registerEvents(new SignTextListener(), this);
Bukkit.getPluginManager().registerEvents(new SignClickListener(), this);
PluginManager pluginManager = Bukkit.getPluginManager();
pluginManager.registerEvents(new SignBreakListener(), this);
pluginManager.registerEvents(new SignTextListener(), this);
pluginManager.registerEvents(new SignClickListener(), this);
pluginManager.registerEvents(new ChunkListener(), this);
PluginCommand editCommand = Bukkit.getPluginCommand("editSign");
if (editCommand != null) {
editCommand.setExecutor(new EditSignCommand());
}
registerCommand("setSignLine", new EditSignCommand());
registerCommand("viewSign", new ViewSignCommand());
registerCommand("copySign", new CopySignCommand());
registerCommand("unWaxSign", new UnWaxSignCommand());
registerCommand("copySignText", new CopySignTextCommand());
registerCommand("setPlaceholderUpdateDelay", new SetPlaceholderUpdateDelayCommand());
}
@Override
@ -114,68 +151,18 @@ public final class PlaceholderSigns extends JavaPlugin {
}
/**
* Updates all loaded and registered placeholder signs
*/
private void updateSigns() {
for (PlaceholderSign placeholderSign : signHandler.getSigns()) {
// Ignore signs away from players
Location location = placeholderSign.location();
if (!location.getChunk().isLoaded()) {
continue;
}
// If no longer a sign, remove
if (!(location.getBlock().getState() instanceof Sign sign)) {
signHandler.unregisterSign(placeholderSign);
continue;
}
// Update placeholders
Map<Integer, String> placeholders = placeholderSign.placeholders();
String[] lines = sign.getLines();
boolean updateRequired = false;
for (int i = 0; i < lines.length; i++) {
String oldText = sign.getLine(i);
// The new text of the sign is either the same, or the original placeholder
String newText;
if (!placeholders.containsKey(i) || placeholders.get(i) == null) {
newText = oldText;
} else {
newText = PlaceholderAPI.setPlaceholders(null, placeholders.get(i));
}
// Convert color codes
newText = translateAllColorCodes(newText);
// Only change the line if the text has changed
if (!newText.equals(oldText)) {
sign.setLine(i, newText);
updateRequired = true;
}
}
// Only update the sign if the text has changed
if (updateRequired) {
sign.update();
}
}
}
/**
* Translates all found color codes to formatting in a string
* Registers a command executor
*
* @param message <p>The string to search for color codes</p>
* @return <p>The message with color codes translated</p>
* @param commandName <p>The name of the command</p>
* @param executor <p>The command's executor</p>
*/
private static String translateAllColorCodes(String message) {
message = ChatColor.translateAlternateColorCodes('&', message);
Pattern pattern = Pattern.compile("&?(#[a-fA-F0-9]{6})");
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group(1)));
private void registerCommand(@NotNull String commandName, @NotNull CommandExecutor executor) {
PluginCommand command = Bukkit.getPluginCommand(commandName);
if (command != null) {
command.setExecutor(executor);
} else {
getLogger().log(Level.SEVERE, "Unable to register command " + commandName);
}
return message;
}
}

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

@ -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,7 +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.container.LineChangeRequest;
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;
@ -17,19 +21,16 @@ 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) {
if (args.length < 2 || !(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;
}
@ -44,26 +45,34 @@ public class EditSignCommand implements TabExecutor {
return false;
}
// Make sure no argument is treated as an empty string
String start;
if (args.length > 1) {
start = args[1];
} else {
start = "";
}
// Get all arguments as a space-separated string
StringBuilder builder = new StringBuilder(args[1]);
StringBuilder builder = new StringBuilder(start);
for (int i = 2; i < args.length; i++) {
builder.append(" ").append(args[i]);
}
// Register the line change request
LineChangeRequest request = new LineChangeRequest(player, lineNumber - 1, builder.toString());
PlaceholderSigns.getInstance().addChangeRequest(request);
SignLineChangeRequest request = new SignLineChangeRequest(player, lineNumber - 1, builder.toString());
placeholderSigns.getRequestHandler().addSignChangeRequest(request);
commandSender.sendMessage("Please click the sign you want to change.");
stringFormatter.displaySuccessMessage(commandSender, PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT);
return true;
}
@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<>();
}

View File

@ -0,0 +1,77 @@
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.PlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.util.TabCompleteHelper;
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;
public class SetPlaceholderUpdateDelayCommand 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;
}
if (arguments.length < 1) {
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;
}
Integer updateDelay = null;
try {
updateDelay = Integer.parseInt(arguments[0]);
} catch (NumberFormatException exception) {
if (!arguments[0].equalsIgnoreCase("null")) {
return false;
}
}
PlaceholderSignHandler signHandler = placeholderSigns.getSignHandler();
PlaceholderSign placeholderSign = signHandler.getFromLocation(sign.getLocation());
if (placeholderSign != null) {
placeholderSign.setUpdateDelay(updateDelay);
signHandler.save();
stringFormatter.displaySuccessMessage(player, PlaceholderSignMessage.SUCCESS_UPDATE_DELAY_CHANGED);
return true;
} else {
stringFormatter.displayErrorMessage(player, PlaceholderSignMessage.ERROR_NOT_LOOKING_AT_PLACEHOLDER_SIGN);
return false;
}
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
return TabCompletionHelper.filterMatchingStartsWith(TabCompleteHelper.getDelays(), arguments[0]);
} else {
return new ArrayList<>();
}
}
}

View File

@ -0,0 +1,52 @@
package net.knarcraft.placeholdersigns.command;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
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 removing the wax on a sign
*/
public class UnWaxSignCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] strings) {
StringFormatter stringFormatter = PlaceholderSigns.getInstance().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;
}
sign.setWaxed(false);
sign.update();
stringFormatter.displaySuccessMessage(commandSender, PlaceholderSignMessage.SUCCESS_SIGN_UN_WAXED);
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

@ -0,0 +1,196 @@
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;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A generic sign view command
*/
public class ViewSignCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
if (!(commandSender instanceof Player player)) {
stringFormatter.displayErrorMessage(commandSender, PlaceholderSignMessage.ERROR_PLAYER_ONLY);
return false;
}
boolean showRawText = true;
boolean showPlaceholders = true;
if (arguments.length > 0) {
showRawText = Boolean.parseBoolean(arguments[0]);
}
if (arguments.length > 1) {
showPlaceholders = Boolean.parseBoolean(arguments[1]);
}
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[] 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.getPlaceholders().get(side);
if (placeholders != null) {
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

@ -0,0 +1,87 @@
package net.knarcraft.placeholdersigns.config;
import net.knarcraft.knarlib.formatting.TranslatableMessage;
import org.jetbrains.annotations.NotNull;
/**
* Translatable plugin messages
*/
public enum PlaceholderSignMessage implements TranslatableMessage {
/**
* The message to display when waiting for a sign selection (edit)
*/
SUCCESS_CLICK_SIGN_TO_EDIT,
/**
* The message displayed when a player tries to edit a waxed sign without the necessary permission
*/
ERROR_WAXED_NO_PERMISSION,
/**
* The message displayed when a sign line has been successfully changed
*/
SUCCESS_SIGN_CHANGED,
/**
* The format used when printing current sign lines
*/
SUCCESS_SIGN_CONTENTS,
/**
* The string displayed when a sign property is confirmed
*/
SIGN_PROPERTY_CONFIRM,
/**
* 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,
/**
* The message displayed when a sign has been successfully un-waxed
*/
SUCCESS_SIGN_UN_WAXED,
/**
* The message displayed when the player isn't looking at a placeholder sign when required
*/
ERROR_NOT_LOOKING_AT_PLACEHOLDER_SIGN,
/**
* The message displayed when a placeholder sign update delay has been changed
*/
SUCCESS_UPDATE_DELAY_CHANGED,
;
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return PlaceholderSignMessage.values();
}
}

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

@ -1,39 +1,80 @@
package net.knarcraft.placeholdersigns.container;
import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.block.sign.Side;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* A sign containing one or more placeholders
*
* @param location <p>The location of the sign</p>
* @param placeholders <p>The original placeholders typed on the sign</p>
*/
public record PlaceholderSign(Location location,
Map<Integer, String> placeholders) implements ConfigurationSerializable {
public final class PlaceholderSign {
@Override
@NotNull
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("location", location);
data.put("placeholders", placeholders);
return data;
private final @NotNull Location location;
private final @NotNull Map<Side, Map<Integer, String>> placeholders;
private @Nullable Integer updateDelay;
/**
* Instantiates a new placeholder sign
*
* @param location <p>The location of the sign</p>
* @param placeholders <p>The original placeholders typed on the sign</p>
* @param updateDelay <p>The delay in ticks between each time this sign's placeholders are updated</p>
*/
public PlaceholderSign(@NotNull Location location, @NotNull Map<Side, Map<Integer, String>> placeholders,
@Nullable Integer updateDelay) {
this.location = location;
this.placeholders = placeholders;
this.updateDelay = updateDelay;
}
/**
* Deserializes the placeholder-sign specified in the given data
* Gets the location of this placeholder sign
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized placeholder sign</p>
* @return <p>The location of this placeholder sign</p>
*/
@SuppressWarnings({"unchecked", "unused"})
public static PlaceholderSign deserialize(Map<String, Object> data) {
return new PlaceholderSign((Location) data.get("location"), (Map<Integer, String>) data.get("placeholders"));
@NotNull
public Location getLocation() {
return location;
}
/**
* Gets the placeholders stored on this sign
*
* @return <p>The stored placeholders</p>
*/
@NotNull
public Map<Side, Map<Integer, String>> getPlaceholders() {
return placeholders;
}
/**
* Gets the update delay for this placeholder sign
*
* @return <p>The update delay</p>
*/
@Nullable
public Integer getUpdateDelay() {
return updateDelay;
}
/**
* Sets the update delay for this placeholder sign
*
* @param updateDelay <p>The new update delay</p>
*/
public void setUpdateDelay(@Nullable Integer updateDelay) {
this.updateDelay = updateDelay;
}
@Override
public boolean equals(@NotNull Object object) {
if (object instanceof PlaceholderSign otherSign) {
return this.location.equals(otherSign.location);
}
return false;
}
}

View File

@ -0,0 +1,19 @@
package net.knarcraft.placeholdersigns.container;
import org.jetbrains.annotations.NotNull;
/**
* A placeholder sign that's queued to be updated
*
* @param placeholderSign <p>The queued placeholder sign</p>
* @param updateTimestamp <p>The timestamp (long milliseconds) that the placeholder sign should be updated at</p>
*/
public record QueuedPlaceholderSign(@NotNull PlaceholderSign placeholderSign,
long updateTimestamp) implements Comparable<QueuedPlaceholderSign> {
@Override
public int compareTo(@NotNull QueuedPlaceholderSign other) {
return Long.compare(this.updateTimestamp, other.updateTimestamp);
}
}

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,6 +1,7 @@
package net.knarcraft.placeholdersigns.container;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* A record of a player's request to change a sign
@ -9,5 +10,5 @@ import org.bukkit.entity.Player;
* @param line <p>The line the player wants to change</p>
* @param text <p>The new text the player provided for the line</p>
*/
public record LineChangeRequest(Player player, int line, String text) {
public record SignLineChangeRequest(@NotNull Player player, int line, @NotNull String text) {
}

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,10 +2,15 @@ package net.knarcraft.placeholdersigns.handler;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.sign.Side;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
@ -13,26 +18,19 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
/**
* A handler for keeping track of placeholder signs
*/
public class PlaceholderSignHandler implements ConfigurationSerializable {
public class PlaceholderSignHandler {
private static final File signsFile = new File(PlaceholderSigns.getInstance().getDataFolder(), "signs.yml");
private Set<PlaceholderSign> placeholderSigns;
private Map<Location, PlaceholderSign> locationLookup;
/**
* Gets all registered signs
*
* @return <p>All registered signs</p>
*/
public @NotNull Set<PlaceholderSign> getSigns() {
return new HashSet<>(placeholderSigns);
}
private Map<Chunk, Set<PlaceholderSign>> signsInChunk;
/**
* Gets a placeholder sign from the given location
@ -40,6 +38,7 @@ public class PlaceholderSignHandler implements ConfigurationSerializable {
* @param location <p>The location of the sign</p>
* @return <p>The sign at the location, or null if no such sign exists</p>
*/
@Nullable
public PlaceholderSign getFromLocation(@NotNull Location location) {
return locationLookup.get(location);
}
@ -51,8 +50,11 @@ public class PlaceholderSignHandler implements ConfigurationSerializable {
*/
public void registerSign(@NotNull PlaceholderSign sign) {
this.placeholderSigns.add(sign);
locationLookup.put(sign.location(), sign);
save();
this.locationLookup.put(sign.getLocation(), sign);
Chunk chunk = sign.getLocation().getChunk();
this.signsInChunk.putIfAbsent(chunk, new HashSet<>());
this.signsInChunk.get(chunk).add(sign);
}
/**
@ -61,19 +63,91 @@ public class PlaceholderSignHandler implements ConfigurationSerializable {
* @param sign <p>The sign to un-register</p>
*/
public void unregisterSign(@NotNull PlaceholderSign sign) {
locationLookup.remove(sign.location());
this.locationLookup.remove(sign.getLocation());
this.placeholderSigns.remove(sign);
save();
this.signsInChunk.get(sign.getLocation().getChunk()).remove(sign);
PlaceholderSigns.getInstance().getUpdateQueueHandler().unQueueSign(sign);
}
/**
* Gets all placeholder signs in the given chunk
*
* @param chunk <p>The chunk to check</p>
* @return <p>All placeholder signs in the chunk</p>
*/
@NotNull
public Set<PlaceholderSign> getFromChunk(@NotNull Chunk chunk) {
if (this.signsInChunk.containsKey(chunk)) {
return this.signsInChunk.get(chunk);
} else {
return new HashSet<>();
}
}
/**
* Loads all placeholder signs from disk
*/
public void load() {
this.placeholderSigns = new HashSet<>();
this.locationLookup = new HashMap<>();
this.signsInChunk = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
PlaceholderSignHandler loadedHandler = (PlaceholderSignHandler) configuration.get("signHandler");
this.placeholderSigns = loadedHandler != null ? loadedHandler.placeholderSigns : new HashSet<>();
this.locationLookup = loadedHandler != null ? loadedHandler.locationLookup : new HashMap<>();
ConfigurationSection signSection = configuration.getConfigurationSection("signs");
if (signSection == null) {
PlaceholderSigns.getInstance().getLogger().log(Level.INFO, "PlaceholderSigns found no signs to load");
return;
}
for (String key : signSection.getKeys(false)) {
String[] locationInfo = key.split(",");
World world = Bukkit.getWorld(UUID.fromString(locationInfo[0]));
double x = Integer.parseInt(locationInfo[1]);
double y = Integer.parseInt(locationInfo[2]);
double z = Integer.parseInt(locationInfo[3]);
Location signLocation = new Location(world, x, y, z);
Map<Side, Map<Integer, String>> allPlaceholders = new HashMap<>();
Map<Integer, String> frontPlaceholders = new HashMap<>();
Map<Integer, String> backPlaceholders = new HashMap<>();
loadPlaceholders(signSection, key + ".placeholders.front.", frontPlaceholders);
loadPlaceholders(signSection, key + ".placeholders.back.", backPlaceholders);
allPlaceholders.put(Side.FRONT, frontPlaceholders);
allPlaceholders.put(Side.BACK, backPlaceholders);
String updateDelayKey = key + ".updateDelay";
int updateDelay = -1;
if (signSection.contains(updateDelayKey)) {
updateDelay = signSection.getInt(updateDelayKey, -1);
}
if (updateDelay < 1) {
updateDelay = PlaceholderSigns.getInstance().getSignUpdateDelay();
}
registerSign(new PlaceholderSign(signLocation, allPlaceholders, updateDelay));
}
}
/**
* Loads placeholders from one side of a sign
*
* @param signSection <p>The configuration section to read</p>
* @param configurationKey <p>The configuration key pointing to the placeholders</p>
* @param placeholderMap <p>The map to add read placeholders to</p>
*/
private void loadPlaceholders(@NotNull ConfigurationSection signSection, @NotNull String configurationKey,
@NotNull Map<Integer, String> placeholderMap) {
for (int i = 0; i < 4; i++) {
String placeholderKey = configurationKey + i;
if (!signSection.contains(placeholderKey)) {
continue;
}
String placeholder = signSection.getString(placeholderKey);
placeholderMap.put(i, placeholder);
}
}
/**
@ -82,37 +156,48 @@ public class PlaceholderSignHandler implements ConfigurationSerializable {
public void save() {
try {
YamlConfiguration configuration = new YamlConfiguration();
configuration.set("signHandler", this);
ConfigurationSection signsSection = configuration.createSection("signs");
for (PlaceholderSign sign : placeholderSigns) {
saveSign(signsSection, sign);
}
configuration.save(signsFile);
} catch (IOException exception) {
PlaceholderSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to save placeholder signs!");
}
}
@NotNull
@Override
public Map<String, Object> serialize() {
Map<String, Object> data = new HashMap<>();
data.put("signs", this.placeholderSigns);
return data;
}
/**
* Deserializes the given placeholder sign handler data
* Saves a sign to the given configuration section
*
* @param data <p>The data to deserialize</p>
* @return <p>The deserialized sign handler</p>
* @param section <p>The configuration section to save to</p>
* @param sign <p>The sign to save</p>
*/
@SuppressWarnings({"unchecked", "unused"})
public static PlaceholderSignHandler deserialize(@NotNull Map<String, Object> data) {
PlaceholderSignHandler placeholderSignHandler = new PlaceholderSignHandler();
placeholderSignHandler.placeholderSigns = (Set<PlaceholderSign>) data.get("signs");
Map<Location, PlaceholderSign> lookup = new HashMap<>();
for (PlaceholderSign sign : placeholderSignHandler.placeholderSigns) {
lookup.put(sign.location(), sign);
private void saveSign(@NotNull ConfigurationSection section, @NotNull PlaceholderSign sign) {
Location location = sign.getLocation();
if (location.getWorld() == null) {
return;
}
placeholderSignHandler.locationLookup = lookup;
return placeholderSignHandler;
String key = location.getWorld().getUID() + "," + location.getBlockX() + "," + location.getBlockY() +
"," + location.getBlockZ();
String frontKey = key + ".placeholders.front";
String backKey = key + ".placeholders.back";
Map<Integer, String> frontPlaceholders = sign.getPlaceholders().get(Side.FRONT);
if (frontPlaceholders != null) {
for (Map.Entry<Integer, String> entry : frontPlaceholders.entrySet()) {
section.set(frontKey + "." + entry.getKey(), entry.getValue());
}
}
Map<Integer, String> backPlaceholders = sign.getPlaceholders().get(Side.BACK);
if (backPlaceholders != null) {
for (Map.Entry<Integer, String> entry : backPlaceholders.entrySet()) {
section.set(backKey + "." + entry.getKey(), entry.getValue());
}
}
section.set(key + ".updateDelay", sign.getUpdateDelay());
}
}

View File

@ -0,0 +1,97 @@
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;
import java.util.HashMap;
import java.util.Map;
/**
* A class for keeping track of placeholder sign requests
*/
public class PlaceholderSignRequestHandler {
private final @NotNull Map<Player, SignLineChangeRequest> signChangeRequests;
private final @NotNull Map<Player, SignTextCopyRequest> signTextCopyRequests;
private final @NotNull Map<Player, SignCopyRequest> signCopyRequests;
/**
* Instantiates a new placeholder sign request handler
*/
public PlaceholderSignRequestHandler() {
this.signChangeRequests = new HashMap<>();
this.signCopyRequests = new HashMap<>();
this.signTextCopyRequests = 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);
}
/**
* Registers a sign change request
*
* <p>A sign change request is basically the result of running the editSign command, which must be stored until the
* player clicks a sign.</p>
*
* @param request <p>The sign change request to register</p>
*/
public void addSignChangeRequest(@NotNull SignLineChangeRequest request) {
this.signChangeRequests.put(request.player(), request);
}
/**
* Gets a sign change request
*
* @param player <p>The player to get the request for</p>
* @return <p>The sign change request, or null if not found</p>
*/
@Nullable
public SignLineChangeRequest getSignChangeRequest(@NotNull Player 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

@ -0,0 +1,77 @@
package net.knarcraft.placeholdersigns.handler;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.container.QueuedPlaceholderSign;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.PriorityQueue;
/**
* A handler for dealing with the placeholder sign update queue
*/
public class PlaceholderSignUpdateQueueHandler {
private final PriorityQueue<QueuedPlaceholderSign> signUpdateQueue;
/**
* Instantiates a new placeholder sign update queue handler
*/
public PlaceholderSignUpdateQueueHandler() {
this.signUpdateQueue = new PriorityQueue<>();
}
/**
* Polls the queue for any placeholder signs due for update
*
* <p>No placeholder sign will be returned unless they are due to be updated.</p>
*
* @return <p>The next queued placeholder sign, or null if no such placeholder sign exists</p>
*/
@Nullable
public PlaceholderSign pollQueue() {
QueuedPlaceholderSign nextSign = signUpdateQueue.peek();
if (nextSign == null) {
return null;
}
long currentTime = System.currentTimeMillis();
if (nextSign.updateTimestamp() < currentTime) {
nextSign = signUpdateQueue.poll();
if (nextSign != null) {
return nextSign.placeholderSign();
}
}
return null;
}
/**
* Peeks at the next item in the queue
*
* @return <p>The next item in the queue, or null if empty</p>
*/
@Nullable
public QueuedPlaceholderSign peekQueue() {
return signUpdateQueue.peek();
}
/**
* Adds the specified queued sign to the queue
*
* @param queuedSign <p>The queued sign to queue</p>
*/
public void queueSign(@NotNull QueuedPlaceholderSign queuedSign) {
this.signUpdateQueue.add(queuedSign);
}
/**
* '
* Removes the specified placeholder sign from the queue
*
* @param placeholderSign <p>The placeholder sign to remove</p>
*/
public void unQueueSign(@NotNull PlaceholderSign placeholderSign) {
this.signUpdateQueue.removeIf((item) -> item.placeholderSign().equals(placeholderSign));
}
}

View File

@ -0,0 +1,38 @@
package net.knarcraft.placeholdersigns.listener;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.container.QueuedPlaceholderSign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* A listener for the loading and unloading of chunks with placeholder signs
*/
public class ChunkListener implements Listener {
@EventHandler
public void onChunkLoad(@NotNull ChunkLoadEvent event) {
PlaceholderSigns instance = PlaceholderSigns.getInstance();
Set<PlaceholderSign> signsAtChunk = instance.getSignHandler().getFromChunk(event.getChunk());
long queueTime = System.currentTimeMillis();
for (PlaceholderSign sign : signsAtChunk) {
instance.getUpdateQueueHandler().queueSign(new QueuedPlaceholderSign(sign, queueTime++));
}
}
@EventHandler
public void onChunkUnload(@NotNull ChunkUnloadEvent event) {
PlaceholderSigns instance = PlaceholderSigns.getInstance();
Set<PlaceholderSign> signsAtChunk = instance.getSignHandler().getFromChunk(event.getChunk());
for (PlaceholderSign sign : signsAtChunk) {
instance.getUpdateQueueHandler().unQueueSign(sign);
}
}
}

View File

@ -9,6 +9,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.jetbrains.annotations.NotNull;
/**
* A listener for placeholder signs being broken
@ -16,7 +17,7 @@ import org.bukkit.event.block.BlockBreakEvent;
public class SignBreakListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onSignBreak(BlockBreakEvent event) {
public void onSignBreak(@NotNull BlockBreakEvent event) {
Block block = event.getBlock();
if (!(block.getState() instanceof Sign)) {
@ -26,6 +27,7 @@ public class SignBreakListener implements Listener {
PlaceholderSign sign = signHandler.getFromLocation(block.getLocation());
if (sign != null) {
signHandler.unregisterSign(sign);
signHandler.save();
}
}

View File

@ -1,14 +1,35 @@
package net.knarcraft.placeholdersigns.listener;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.LineChangeRequest;
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;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
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;
import static net.knarcraft.placeholdersigns.util.SignSideHelper.getOpposite;
import static net.knarcraft.placeholdersigns.util.SignSideHelper.getSide;
/**
* A listener for placeholder signs being clicked
@ -16,38 +37,385 @@ import org.bukkit.event.player.PlayerInteractEvent;
public class SignClickListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onSignClick(PlayerInteractEvent event) {
public void onSignClick(@NotNull PlayerInteractEvent event) {
// Ignore if not a clicked sign
if (!event.hasBlock() || event.getClickedBlock() == null ||
!(event.getClickedBlock().getState() instanceof Sign sign)) {
return;
}
Player player = event.getPlayer();
PlaceholderSignRequestHandler requestHandler = PlaceholderSigns.getInstance().getRequestHandler();
// Check if the player has run the /editSign command
LineChangeRequest request = PlaceholderSigns.getInstance().getChangeRequest(event.getPlayer());
if (request == null) {
SignCopyRequest signCopyRequest = requestHandler.getSignCopyRequest(player);
if (signCopyRequest != null) {
if (checkWaxEdit(sign, player, PlaceholderSignsPermission.BYPASS_WAXED_COPY.getPermissionNode())) {
return;
}
pasteSign(signCopyRequest.sign(), sign, player);
event.setCancelled(true);
return;
}
String[] lines = sign.getLines();
lines[request.line()] = request.text();
// 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;
}
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.getPlaceholders();
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.getPlaceholders().remove(clickedSide);
}
if (pasteSignSide(sourceSide, clickedSide, sourceSign, targetSign,
sourcePlaceholderSign, player) && targetPlaceholderSign != null) {
// Restore the old placeholders if the sign change didn't finish
targetPlaceholderSign.getPlaceholders().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
*
* @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 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 false;
}
}
/**
* Pastes the data from the source sign to the target sign
*
* @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 sourceSign, @NotNull Sign targetSign, @NotNull Player player) {
StringFormatter stringFormatter = PlaceholderSigns.getInstance().getStringFormatter();
PlaceholderSign placeholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(
sourceSign.getLocation());
// Get the lines, with placeholders replaced, and the sign change event processed
if (pasteSignSide(Side.FRONT, Side.FRONT, sourceSign, targetSign, placeholderSign, player) ||
pasteSignSide(Side.BACK, Side.BACK, sourceSign, targetSign, placeholderSign, player)) {
return;
}
// Copy options
copySignOptions(sourceSign, targetSign);
// Apply changes
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 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 sourceSide,
@NotNull Side targetSide, @Nullable PlaceholderSign placeholderSign,
@NotNull Player player) {
String[] sideLines = signSide.getLines();
if (placeholderSign != null) {
Map<Side, Map<Integer, String>> placeholders = placeholderSign.getPlaceholders();
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(event.getClickedBlock(), event.getPlayer(), lines);
SignChangeEvent changeEvent = new SignChangeEvent(Objects.requireNonNull(sign.getBlock()), player, sideLines, targetSide);
Bukkit.getPluginManager().callEvent(changeEvent);
if (changeEvent.isCancelled()) {
return null;
}
return changeEvent.getLines();
}
/**
* Copies all sign option (dye, glowing, waxed) from the source to the target
*
* @param source <p>The source sign</p>
* @param target <p>The target sign</p>
*/
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++) {
if (sidePlaceholders.containsKey(i)) {
lines[i] = sidePlaceholders.get(i);
}
}
}
/**
* Alters the text on a sign, taking care of placeholders and sign change event triggers
*
* @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 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 targetPlaceholderSign = PlaceholderSigns.getInstance().getSignHandler().getFromLocation(targetSign.getLocation());
String oldPlaceholder = null;
if (targetPlaceholderSign != null) {
// Remove the old placeholder
Map<Integer, String> placeholders = targetPlaceholderSign.getPlaceholders().get(targetSide);
if (placeholders != null) {
oldPlaceholder = placeholders.remove(destinationLine);
}
}
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(targetSign.getBlock(), player, lines, targetSide);
Bukkit.getPluginManager().callEvent(changeEvent);
if (changeEvent.isCancelled()) {
if (targetPlaceholderSign != null) {
// Restore the old placeholder if the action didn't complete
targetPlaceholderSign.getPlaceholders().get(targetSide).put(destinationLine, oldPlaceholder);
}
PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player,
PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);
return;
}
// Update the sign with the new text
String[] finalLines = changeEvent.getLines();
for (int i = 0; i < finalLines.length; i++) {
sign.setLine(i, finalLines[i]);
}
sign.update();
targetSignSide.setLine(destinationLine, ColorHelper.translateColorCodes(changeEvent.getLines()[destinationLine],
ColorConversion.RGB));
targetSign.update();
event.getPlayer().sendMessage("The sign line was successfully changed.");
// Save any placeholder changes
signHandler.save();
PlaceholderSigns.getInstance().getStringFormatter().displaySuccessMessage(player,
PlaceholderSignMessage.SUCCESS_SIGN_CHANGED);
}
}

View File

@ -2,13 +2,17 @@ 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.container.QueuedPlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import org.bukkit.Location;
import org.bukkit.block.sign.Side;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
@ -19,9 +23,9 @@ import java.util.Map;
public class SignTextListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSignCreate(SignChangeEvent event) {
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;
}
@ -38,19 +42,34 @@ public class SignTextListener implements Listener {
placeholders.put(i, line);
}
updatePlaceholders(placeholders, event);
}
/**
* Updates placeholders as necessary
*
* @param placeholders <p>The placeholders parsed from the lines</p>
* @param event <p>The triggered sign change event</p>
*/
private void updatePlaceholders(@NotNull Map<Integer, String> placeholders,
@NotNull SignChangeEvent event) {
Location location = event.getBlock().getLocation();
PlaceholderSignHandler signHandler = PlaceholderSigns.getInstance().getSignHandler();
PlaceholderSign existingSign = signHandler.getFromLocation(location);
if (!placeholders.isEmpty() && existingSign == null) {
Map<Side, Map<Integer, String>> placeholderSide = new HashMap<>();
placeholderSide.put(event.getSide(), placeholders);
// Register a new placeholder sign
PlaceholderSign placeholderSign = new PlaceholderSign(event.getBlock().getLocation(), placeholders);
PlaceholderSign placeholderSign = new PlaceholderSign(event.getBlock().getLocation(), placeholderSide, null);
signHandler.registerSign(placeholderSign);
PlaceholderSigns.getInstance().getUpdateQueueHandler().queueSign(new QueuedPlaceholderSign(placeholderSign, 0));
signHandler.save();
} else if (!placeholders.isEmpty()) {
// Overwrite the placeholders of the existing placeholder sign
for (Map.Entry<Integer, String> entry : placeholders.entrySet()) {
existingSign.placeholders().put(entry.getKey(), entry.getValue());
}
Map<Integer, String> existing = existingSign.getPlaceholders().computeIfAbsent(event.getSide(), k -> new HashMap<>());
existing.putAll(placeholders);
signHandler.save();
}
}

View File

@ -0,0 +1,133 @@
package net.knarcraft.placeholdersigns.runnable;
import me.clip.placeholderapi.PlaceholderAPI;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.container.QueuedPlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignUpdateQueueHandler;
import org.bukkit.Location;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* A runnable that updates signs
*/
public class SignUpdate implements Runnable {
private final @NotNull PlaceholderSignHandler signHandler;
/**
* Instantiates a new sign update runnable
*
* @param signHandler <p>The sign handler to get signs from</p>
*/
public SignUpdate(@NotNull PlaceholderSignHandler signHandler) {
this.signHandler = signHandler;
}
@Override
public void run() {
PlaceholderSigns instance = PlaceholderSigns.getInstance();
PlaceholderSignUpdateQueueHandler queueHandler = instance.getUpdateQueueHandler();
long startTime = System.nanoTime();
long currentTime = System.currentTimeMillis();
while (queueHandler.peekQueue() != null && System.nanoTime() - startTime < 25000000) {
PlaceholderSign placeholderSign = instance.getUpdateQueueHandler().pollQueue();
if (placeholderSign == null) {
break;
}
updatePlaceholderSign(placeholderSign, currentTime);
}
}
/**
* Updates the contents of a single placeholder sign
*
* @param placeholderSign <p>The placeholder sign to update</p>
* @param currentTime <p>The current time, used for re-queuing the sign</p>
*/
private void updatePlaceholderSign(@NotNull PlaceholderSign placeholderSign, long currentTime) {
PlaceholderSigns instance = PlaceholderSigns.getInstance();
// Ignore signs away from players
Location location = placeholderSign.getLocation();
// If no longer a sign, remove
if (!(location.getBlock().getState() instanceof Sign sign)) {
this.signHandler.unregisterSign(placeholderSign);
this.signHandler.save();
return;
}
// Update placeholders
SignSide front = sign.getSide(Side.FRONT);
SignSide back = sign.getSide(Side.BACK);
Map<Side, Map<Integer, String>> placeholders = placeholderSign.getPlaceholders();
String[] frontLines = front.getLines();
String[] backLines = back.getLines();
// Only update the sign if the text has changed
boolean updateNecessary = false;
if (placeholders.get(Side.FRONT) != null) {
updateNecessary |= updatePlaceholders(frontLines, placeholders.get(Side.FRONT), front);
}
if (placeholders.get(Side.BACK) != null) {
updateNecessary |= updatePlaceholders(backLines, placeholders.get(Side.BACK), back);
}
if (updateNecessary) {
sign.update();
}
Integer updateDelay = placeholderSign.getUpdateDelay();
if (updateDelay == null) {
updateDelay = instance.getSignUpdateDelay();
}
instance.getUpdateQueueHandler().queueSign(new QueuedPlaceholderSign(placeholderSign, currentTime + (updateDelay * 50)));
}
/**
* Updates the values of placeholders on a sign
*
* @param lines <p>The sign's current lines</p>
* @param placeholders <p>The sign's original placeholder lines</p>
* @param signSide <p>The side of the sign to update placeholders for</p>
* @return <p>True if text has been changed, and the sign needs to be updated</p>
*/
private boolean updatePlaceholders(@NotNull String[] lines, @NotNull Map<Integer, String> placeholders,
@NotNull SignSide signSide) {
boolean changed = false;
for (int i = 0; i < lines.length; i++) {
String oldText = signSide.getLine(i);
// The new text of the sign is either the same, or the original placeholder
String newText;
if (!placeholders.containsKey(i) || placeholders.get(i) == null) {
newText = oldText;
} else {
newText = PlaceholderAPI.setPlaceholders(null, placeholders.get(i));
}
// Convert color codes
newText = ColorHelper.translateColorCodes(newText, ColorConversion.RGB);
// Only change the line if the text has changed
if (!newText.equals(oldText)) {
signSide.setLine(i, newText);
changed = true;
}
}
return changed;
}
}

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,66 @@
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;
private static final List<String> updateDelays;
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());
}
updateDelays = List.of("null", "1", "5", "10", "20", "40", "60", "80", "100");
}
private TabCompleteHelper() {
}
/**
* Gets possible sign update delays
*
* @return <p>Possible sign update delays</p>
*/
@NotNull
public static List<String> getDelays() {
return new ArrayList<>(updateDelays);
}
/**
* 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

@ -0,0 +1,8 @@
# The chosen language for PlaceholderSigns. You can use "en" or any custom language specified in strings.yml
language: en
# How often to update placeholders by default in ticks. 1 second = 20 ticks. The default is 100 ticks = 5 seconds. Note
# that only loaded chunks will have their signs updated to reduce lag and RAM usage from chunk loading. Still, it is
# recommended to set this to a higher value, and only reduce the update delay for individual signs where updated
# information is critical. 1 is the minimal accepted value.
defaultSignUpdateTicks: 100

View File

@ -1,25 +1,90 @@
name: PlaceholderSigns
version: '${project.version}'
main: net.knarcraft.placeholdersigns.PlaceholderSigns
api-version: 1.19
api-version: '1.20'
depend:
- PlaceholderAPI
commands:
editSign:
usage: /<command> <line> <text> [text] ...
permission: placeholdersigns.edit
description: Changes the line of a sign without a text limit
setSignLine:
usage: /<command> <line> [text] [text] ...
permission: placeholdersigns.edit.use
description: Changes the line of a sign, without a text limit, and with color conversion
viewSign:
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)
copySign:
usage: /<command>
permission: placeholdersigns.copy.use
description: Copies all sign information (including dye, glow and waxed) from one sign to another
unWaxSign:
usage: /<command>
permission: placeholdersigns.unwax
description: Removes the wax from a waxed sign
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
setPlaceholderUpdateDelay:
usage: /<command> <delay in ticks>
permission: placeholdersigns.setdelay
description: Sets the update delay for a placeholder sign
permissions:
placeholdersigns.*:
description: Grants all placeholdersigns permissions
children:
- placeholdersigns.edit
- placeholdersigns.placeholder
- placeholdersigns.view
- placeholdersigns.copy
- placeholdersigns.unwax
- placeholdersigns.setdelay
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
description: Allows a player to use the /setSignLine command without restriction
default: false
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 /setSignLine command
default: false
placeholdersigns.edit.bypass-waxed:
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 use the /viewSign command
default: false
placeholdersigns.copy.use:
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 use the /unWax command
default: false
placeholdersigns.setdelay:
description: Allows a player to use the /setPlaceholderUpdateDelay command
default: false

View File

@ -0,0 +1,23 @@
en:
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: "&7The sign line was successfully changed."
SUCCESS_SIGN_CONTENTS: |
#78da55&lSign contents:&r
#17A057Front:&r
{frontLines}#8899A5Dye&r {frontDye}&r, #8899A5Glowing&r {frontGlow}&r
#A01760Back:&r
{backLines}#8899A5Dye&r {backDye}&r, #8899A5Glowing&r {backGlow}&r
#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"
SUCCESS_SIGN_UN_WAXED: "&7The sign was successfully un-waxed"
ERROR_NOT_LOOKING_AT_PLACEHOLDER_SIGN: "You are not currently looking at a placeholder sign"
SUCCESS_UPDATE_DELAY_CHANGED: "&7The placeholder sign's update delay was successfully updated"