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

This commit is contained in:
Kristian Knarvik 2024-04-30 03:09:47 +02:00
parent a34c63b7e2
commit f8ca5705a5
18 changed files with 489 additions and 76 deletions

View File

@ -21,13 +21,14 @@ Note that commands have some restrictions in place, so giving the weakest permis
- All sign changes are run through the SignChangeEvent, so the player cannot edit a sign if any protection plugin would - 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. prevent them from editing the sign normally.
| Command | Arguments | Description | | 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. | | /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. | | /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. | | /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. | | /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. | | /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 ## Permissions
@ -44,3 +45,4 @@ Note that commands have some restrictions in place, so giving the weakest permis
| placeholdersigns.copy.use | false | Allows 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.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.unwax | false | Allows use of the /unWaxSign command |
| placeholdersigns.setdelay | false | Allows use of the /setPlaceholderUpdateDelay command |

View File

@ -6,11 +6,14 @@ import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.placeholdersigns.command.CopySignCommand; import net.knarcraft.placeholdersigns.command.CopySignCommand;
import net.knarcraft.placeholdersigns.command.CopySignTextCommand; import net.knarcraft.placeholdersigns.command.CopySignTextCommand;
import net.knarcraft.placeholdersigns.command.EditSignCommand; import net.knarcraft.placeholdersigns.command.EditSignCommand;
import net.knarcraft.placeholdersigns.command.SetPlaceholderUpdateDelayCommand;
import net.knarcraft.placeholdersigns.command.UnWaxSignCommand; import net.knarcraft.placeholdersigns.command.UnWaxSignCommand;
import net.knarcraft.placeholdersigns.command.ViewSignCommand; import net.knarcraft.placeholdersigns.command.ViewSignCommand;
import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage; import net.knarcraft.placeholdersigns.config.PlaceholderSignMessage;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignRequestHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignUpdateQueueHandler;
import net.knarcraft.placeholdersigns.listener.ChunkListener;
import net.knarcraft.placeholdersigns.listener.SignBreakListener; import net.knarcraft.placeholdersigns.listener.SignBreakListener;
import net.knarcraft.placeholdersigns.listener.SignClickListener; import net.knarcraft.placeholdersigns.listener.SignClickListener;
import net.knarcraft.placeholdersigns.listener.SignTextListener; import net.knarcraft.placeholdersigns.listener.SignTextListener;
@ -18,6 +21,7 @@ import net.knarcraft.placeholdersigns.runnable.SignUpdate;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -31,7 +35,9 @@ public final class PlaceholderSigns extends JavaPlugin {
private static PlaceholderSigns instance; private static PlaceholderSigns instance;
private PlaceholderSignHandler signHandler; private PlaceholderSignHandler signHandler;
private PlaceholderSignRequestHandler requestHandler; private PlaceholderSignRequestHandler requestHandler;
private PlaceholderSignUpdateQueueHandler updateQueueHandler;
private StringFormatter stringFormatter; private StringFormatter stringFormatter;
private int signUpdateDelay;
/** /**
* Gets an instance of this plugin * Gets an instance of this plugin
@ -53,21 +59,51 @@ public final class PlaceholderSigns extends JavaPlugin {
return this.signHandler; return this.signHandler;
} }
/**
* Gets this instance's placeholder sign request handler
*
* @return <p>The request handler</p>
*/
@NotNull @NotNull
public PlaceholderSignRequestHandler getRequestHandler() { public PlaceholderSignRequestHandler getRequestHandler() {
return this.requestHandler; return this.requestHandler;
} }
/**
* Gets this instance's update queue handler
*
* @return <p>The update queue handler</p>
*/
@NotNull
public PlaceholderSignUpdateQueueHandler getUpdateQueueHandler() {
return this.updateQueueHandler;
}
/**
* Gets the string formatter to use for formatting messages
*
* @return <p>The string formatter</p>
*/
@NotNull @NotNull
public StringFormatter getStringFormatter() { public StringFormatter getStringFormatter() {
return this.stringFormatter; return this.stringFormatter;
} }
/**
* Gets the default sign update delay
*
* @return <p>The sign update delay</p>
*/
public int getSignUpdateDelay() {
return this.signUpdateDelay;
}
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
getConfig().options().copyDefaults(true); getConfig().options().copyDefaults(true);
saveConfig(); saveConfig();
Translator translator = new Translator(); Translator translator = new Translator();
translator.registerMessageCategory(PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT); translator.registerMessageCategory(PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT);
translator.setColorConversion(ColorConversion.RGB); translator.setColorConversion(ColorConversion.RGB);
@ -77,9 +113,15 @@ public final class PlaceholderSigns extends JavaPlugin {
this.stringFormatter.setNamePrefix("#A5682A[&r&l"); this.stringFormatter.setNamePrefix("#A5682A[&r&l");
this.stringFormatter.setNameSuffix("&r#A5682A]"); this.stringFormatter.setNameSuffix("&r#A5682A]");
signUpdateDelay = getConfig().getInt("defaultSignUpdateTicks", 100);
if (signUpdateDelay < 1) {
signUpdateDelay = 100;
}
this.signHandler = new PlaceholderSignHandler(); this.signHandler = new PlaceholderSignHandler();
this.signHandler.load(); this.signHandler.load();
this.requestHandler = new PlaceholderSignRequestHandler(); this.requestHandler = new PlaceholderSignRequestHandler();
this.updateQueueHandler = new PlaceholderSignUpdateQueueHandler();
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) { if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") == null) {
getLogger().log(Level.WARNING, "Could not find PlaceholderAPI! This plugin is required."); getLogger().log(Level.WARNING, "Could not find PlaceholderAPI! This plugin is required.");
@ -87,18 +129,20 @@ public final class PlaceholderSigns extends JavaPlugin {
return; return;
} }
// Update signs' placeholders every second Bukkit.getScheduler().runTaskTimer(this, new SignUpdate(this.signHandler), 20, 1);
Bukkit.getScheduler().runTaskTimer(this, new SignUpdate(this.signHandler), 20 * 10, 20 * 5);
Bukkit.getPluginManager().registerEvents(new SignBreakListener(), this); PluginManager pluginManager = Bukkit.getPluginManager();
Bukkit.getPluginManager().registerEvents(new SignTextListener(), this); pluginManager.registerEvents(new SignBreakListener(), this);
Bukkit.getPluginManager().registerEvents(new SignClickListener(), this); pluginManager.registerEvents(new SignTextListener(), this);
pluginManager.registerEvents(new SignClickListener(), this);
pluginManager.registerEvents(new ChunkListener(), this);
registerCommand("setSignLine", new EditSignCommand()); registerCommand("setSignLine", new EditSignCommand());
registerCommand("viewSign", new ViewSignCommand()); registerCommand("viewSign", new ViewSignCommand());
registerCommand("copySign", new CopySignCommand()); registerCommand("copySign", new CopySignCommand());
registerCommand("unWaxSign", new UnWaxSignCommand()); registerCommand("unWaxSign", new UnWaxSignCommand());
registerCommand("copySignText", new CopySignTextCommand()); registerCommand("copySignText", new CopySignTextCommand());
registerCommand("setPlaceholderUpdateDelay", new SetPlaceholderUpdateDelayCommand());
} }
@Override @Override

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

@ -160,7 +160,7 @@ public class ViewSignCommand implements TabExecutor {
return getSignText(lines, raw); return getSignText(lines, raw);
} }
Map<Integer, String> placeholders = placeholderSign.placeholders().get(side); Map<Integer, String> placeholders = placeholderSign.getPlaceholders().get(side);
if (placeholders != null) { if (placeholders != null) {
for (Map.Entry<Integer, String> entry : placeholders.entrySet()) { for (Map.Entry<Integer, String> entry : placeholders.entrySet()) {
lines[entry.getKey()] = entry.getValue(); lines[entry.getKey()] = entry.getValue();

View File

@ -67,6 +67,16 @@ public enum PlaceholderSignMessage implements TranslatableMessage {
* The message displayed when a sign has been successfully un-waxed * The message displayed when a sign has been successfully un-waxed
*/ */
SUCCESS_SIGN_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 @Override

View File

@ -3,15 +3,78 @@ package net.knarcraft.placeholdersigns.container;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.sign.Side; import org.bukkit.block.sign.Side;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map; import java.util.Map;
/** /**
* A sign containing one or more placeholders * 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(@NotNull Location location, @NotNull Map<Side, Map<Integer, String>> placeholders) { public final class PlaceholderSign {
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;
}
/**
* Gets the location of this placeholder sign
*
* @return <p>The location of this placeholder sign</p>
*/
@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

@ -3,6 +3,7 @@ package net.knarcraft.placeholdersigns.handler;
import net.knarcraft.placeholdersigns.PlaceholderSigns; import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign; import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.sign.Side; import org.bukkit.block.sign.Side;
@ -29,15 +30,7 @@ public class PlaceholderSignHandler {
private Set<PlaceholderSign> placeholderSigns; private Set<PlaceholderSign> placeholderSigns;
private Map<Location, PlaceholderSign> locationLookup; private Map<Location, PlaceholderSign> locationLookup;
private Map<Chunk, Set<PlaceholderSign>> signsInChunk;
/**
* Gets all registered signs
*
* @return <p>All registered signs</p>
*/
public @NotNull Set<PlaceholderSign> getSigns() {
return new HashSet<>(placeholderSigns);
}
/** /**
* Gets a placeholder sign from the given location * Gets a placeholder sign from the given location
@ -57,8 +50,11 @@ public class PlaceholderSignHandler {
*/ */
public void registerSign(@NotNull PlaceholderSign sign) { public void registerSign(@NotNull PlaceholderSign sign) {
this.placeholderSigns.add(sign); this.placeholderSigns.add(sign);
locationLookup.put(sign.location(), sign); this.locationLookup.put(sign.getLocation(), sign);
save();
Chunk chunk = sign.getLocation().getChunk();
this.signsInChunk.putIfAbsent(chunk, new HashSet<>());
this.signsInChunk.get(chunk).add(sign);
} }
/** /**
@ -67,9 +63,24 @@ public class PlaceholderSignHandler {
* @param sign <p>The sign to un-register</p> * @param sign <p>The sign to un-register</p>
*/ */
public void unregisterSign(@NotNull PlaceholderSign sign) { public void unregisterSign(@NotNull PlaceholderSign sign) {
locationLookup.remove(sign.location()); this.locationLookup.remove(sign.getLocation());
this.placeholderSigns.remove(sign); this.placeholderSigns.remove(sign);
save(); this.signsInChunk.get(sign.getLocation().getChunk()).remove(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<>();
}
} }
/** /**
@ -78,6 +89,7 @@ public class PlaceholderSignHandler {
public void load() { public void load() {
this.placeholderSigns = new HashSet<>(); this.placeholderSigns = new HashSet<>();
this.locationLookup = new HashMap<>(); this.locationLookup = new HashMap<>();
this.signsInChunk = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile); YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.getConfigurationSection("signs"); ConfigurationSection signSection = configuration.getConfigurationSection("signs");
@ -104,9 +116,16 @@ public class PlaceholderSignHandler {
allPlaceholders.put(Side.FRONT, frontPlaceholders); allPlaceholders.put(Side.FRONT, frontPlaceholders);
allPlaceholders.put(Side.BACK, backPlaceholders); allPlaceholders.put(Side.BACK, backPlaceholders);
PlaceholderSign sign = new PlaceholderSign(signLocation, allPlaceholders); String updateDelayKey = key + ".updateDelay";
this.placeholderSigns.add(sign); int updateDelay = -1;
this.locationLookup.put(signLocation, sign); if (signSection.contains(updateDelayKey)) {
updateDelay = signSection.getInt(updateDelayKey, -1);
}
if (updateDelay < 1) {
updateDelay = PlaceholderSigns.getInstance().getSignUpdateDelay();
}
registerSign(new PlaceholderSign(signLocation, allPlaceholders, updateDelay));
} }
} }
@ -153,7 +172,7 @@ public class PlaceholderSignHandler {
* @param sign <p>The sign to save</p> * @param sign <p>The sign to save</p>
*/ */
private void saveSign(@NotNull ConfigurationSection section, @NotNull PlaceholderSign sign) { private void saveSign(@NotNull ConfigurationSection section, @NotNull PlaceholderSign sign) {
Location location = sign.location(); Location location = sign.getLocation();
if (location.getWorld() == null) { if (location.getWorld() == null) {
return; return;
} }
@ -163,19 +182,21 @@ public class PlaceholderSignHandler {
String frontKey = key + ".placeholders.front"; String frontKey = key + ".placeholders.front";
String backKey = key + ".placeholders.back"; String backKey = key + ".placeholders.back";
Map<Integer, String> frontPlaceholders = sign.placeholders().get(Side.FRONT); Map<Integer, String> frontPlaceholders = sign.getPlaceholders().get(Side.FRONT);
if (frontPlaceholders != null) { if (frontPlaceholders != null) {
for (Map.Entry<Integer, String> entry : frontPlaceholders.entrySet()) { for (Map.Entry<Integer, String> entry : frontPlaceholders.entrySet()) {
section.set(frontKey + "." + entry.getKey(), entry.getValue()); section.set(frontKey + "." + entry.getKey(), entry.getValue());
} }
} }
Map<Integer, String> backPlaceholders = sign.placeholders().get(Side.BACK); Map<Integer, String> backPlaceholders = sign.getPlaceholders().get(Side.BACK);
if (backPlaceholders != null) { if (backPlaceholders != null) {
for (Map.Entry<Integer, String> entry : backPlaceholders.entrySet()) { for (Map.Entry<Integer, String> entry : backPlaceholders.entrySet()) {
section.set(backKey + "." + entry.getKey(), entry.getValue()); section.set(backKey + "." + entry.getKey(), entry.getValue());
} }
} }
section.set(key + ".updateDelay", sign.getUpdateDelay());
} }
} }

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

@ -27,6 +27,7 @@ public class SignBreakListener implements Listener {
PlaceholderSign sign = signHandler.getFromLocation(block.getLocation()); PlaceholderSign sign = signHandler.getFromLocation(block.getLocation());
if (sign != null) { if (sign != null) {
signHandler.unregisterSign(sign); signHandler.unregisterSign(sign);
signHandler.save();
} }
} }

View File

@ -133,7 +133,7 @@ public class SignClickListener implements Listener {
String text = signTextCopyRequest.sign().getSide(signTextCopyRequest.side()).getLine(sourceLine); String text = signTextCopyRequest.sign().getSide(signTextCopyRequest.side()).getLine(sourceLine);
if (sourcePlaceholderSign != null) { if (sourcePlaceholderSign != null) {
Map<Side, Map<Integer, String>> placeholders = sourcePlaceholderSign.placeholders(); Map<Side, Map<Integer, String>> placeholders = sourcePlaceholderSign.getPlaceholders();
if (placeholders.containsKey(sourceSide) && placeholders.get(sourceSide).containsKey(sourceLine)) { if (placeholders.containsKey(sourceSide) && placeholders.get(sourceSide).containsKey(sourceLine)) {
text = placeholders.get(sourceSide).get(sourceLine); text = placeholders.get(sourceSide).get(sourceLine);
} }
@ -162,12 +162,12 @@ public class SignClickListener implements Listener {
// Remove old placeholders from the sign side // Remove old placeholders from the sign side
Map<Integer, String> oldPlaceholders = null; Map<Integer, String> oldPlaceholders = null;
if (targetPlaceholderSign != null) { if (targetPlaceholderSign != null) {
oldPlaceholders = targetPlaceholderSign.placeholders().remove(clickedSide); oldPlaceholders = targetPlaceholderSign.getPlaceholders().remove(clickedSide);
} }
if (pasteSignSide(sourceSide, clickedSide, sourceSign, targetSign, if (pasteSignSide(sourceSide, clickedSide, sourceSign, targetSign,
sourcePlaceholderSign, player) && targetPlaceholderSign != null) { sourcePlaceholderSign, player) && targetPlaceholderSign != null) {
// Restore the old placeholders if the sign change didn't finish // Restore the old placeholders if the sign change didn't finish
targetPlaceholderSign.placeholders().put(clickedSide, oldPlaceholders); targetPlaceholderSign.getPlaceholders().put(clickedSide, oldPlaceholders);
} }
// Save any placeholder changes // Save any placeholder changes
@ -307,7 +307,7 @@ public class SignClickListener implements Listener {
@NotNull Player player) { @NotNull Player player) {
String[] sideLines = signSide.getLines(); String[] sideLines = signSide.getLines();
if (placeholderSign != null) { if (placeholderSign != null) {
Map<Side, Map<Integer, String>> placeholders = placeholderSign.placeholders(); Map<Side, Map<Integer, String>> placeholders = placeholderSign.getPlaceholders();
loadPlaceholders(sourceSide, sideLines, placeholders); loadPlaceholders(sourceSide, sideLines, placeholders);
} }
@ -385,7 +385,7 @@ public class SignClickListener implements Listener {
String oldPlaceholder = null; String oldPlaceholder = null;
if (targetPlaceholderSign != null) { if (targetPlaceholderSign != null) {
// Remove the old placeholder // Remove the old placeholder
oldPlaceholder = targetPlaceholderSign.placeholders().get(targetSide).remove(destinationLine); oldPlaceholder = targetPlaceholderSign.getPlaceholders().get(targetSide).remove(destinationLine);
} }
lines[destinationLine] = newText; lines[destinationLine] = newText;
@ -396,7 +396,7 @@ public class SignClickListener implements Listener {
if (changeEvent.isCancelled()) { if (changeEvent.isCancelled()) {
if (targetPlaceholderSign != null) { if (targetPlaceholderSign != null) {
// Restore the old placeholder if the action didn't complete // Restore the old placeholder if the action didn't complete
targetPlaceholderSign.placeholders().get(targetSide).put(destinationLine, oldPlaceholder); targetPlaceholderSign.getPlaceholders().get(targetSide).put(destinationLine, oldPlaceholder);
} }
PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player, PlaceholderSigns.getInstance().getStringFormatter().displayErrorMessage(player,
PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION); PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);

View File

@ -61,11 +61,12 @@ public class SignTextListener implements Listener {
placeholderSide.put(event.getSide(), placeholders); placeholderSide.put(event.getSide(), placeholders);
// Register a new placeholder sign // Register a new placeholder sign
PlaceholderSign placeholderSign = new PlaceholderSign(event.getBlock().getLocation(), placeholderSide); PlaceholderSign placeholderSign = new PlaceholderSign(event.getBlock().getLocation(), placeholderSide, null);
signHandler.registerSign(placeholderSign); signHandler.registerSign(placeholderSign);
signHandler.save();
} else if (!placeholders.isEmpty()) { } else if (!placeholders.isEmpty()) {
// Overwrite the placeholders of the existing placeholder sign // Overwrite the placeholders of the existing placeholder sign
Map<Integer, String> existing = existingSign.placeholders().computeIfAbsent(event.getSide(), k -> new HashMap<>()); Map<Integer, String> existing = existingSign.getPlaceholders().computeIfAbsent(event.getSide(), k -> new HashMap<>());
existing.putAll(placeholders); existing.putAll(placeholders);
signHandler.save(); signHandler.save();
} }

View File

@ -3,8 +3,11 @@ package net.knarcraft.placeholdersigns.runnable;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper; import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.placeholdersigns.PlaceholderSigns;
import net.knarcraft.placeholdersigns.container.PlaceholderSign; import net.knarcraft.placeholdersigns.container.PlaceholderSign;
import net.knarcraft.placeholdersigns.container.QueuedPlaceholderSign;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler; import net.knarcraft.placeholdersigns.handler.PlaceholderSignHandler;
import net.knarcraft.placeholdersigns.handler.PlaceholderSignUpdateQueueHandler;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.block.Sign; import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side; import org.bukkit.block.sign.Side;
@ -31,40 +34,67 @@ public class SignUpdate implements Runnable {
@Override @Override
public void run() { public void run() {
for (PlaceholderSign placeholderSign : signHandler.getSigns()) { PlaceholderSigns instance = PlaceholderSigns.getInstance();
// Ignore signs away from players PlaceholderSignUpdateQueueHandler queueHandler = instance.getUpdateQueueHandler();
Location location = placeholderSign.location();
if (!location.getChunk().isLoaded()) { long startTime = System.nanoTime();
continue; long currentTime = System.currentTimeMillis();
while (queueHandler.peekQueue() != null && System.nanoTime() - startTime < 25000000) {
PlaceholderSign placeholderSign = instance.getUpdateQueueHandler().pollQueue();
if (placeholderSign == null) {
break;
} }
// If no longer a sign, remove updatePlaceholderSign(placeholderSign, currentTime);
if (!(location.getBlock().getState() instanceof Sign sign)) {
signHandler.unregisterSign(placeholderSign);
continue;
}
// Update placeholders
SignSide front = sign.getSide(Side.FRONT);
SignSide back = sign.getSide(Side.BACK);
Map<Side, Map<Integer, String>> placeholders = placeholderSign.placeholders();
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();
}
} }
} }
/**
* 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 * Updates the values of placeholders on a sign
* *

View File

@ -13,6 +13,7 @@ public final class TabCompleteHelper {
private static final List<String> lineNumbers; private static final List<String> lineNumbers;
private static final List<String> signSides; private static final List<String> signSides;
private static final List<String> updateDelays;
static { static {
lineNumbers = new ArrayList<>(); lineNumbers = new ArrayList<>();
@ -24,12 +25,24 @@ public final class TabCompleteHelper {
for (Side side : Side.values()) { for (Side side : Side.values()) {
signSides.add(side.name()); signSides.add(side.name());
} }
updateDelays = List.of("null", "1", "5", "10", "20", "40", "60", "80", "100");
} }
private TabCompleteHelper() { 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 * Gets possible sign line numbers
* *

View File

@ -1,2 +1,8 @@
# The chosen language for PlaceholderSigns. You can use "en" or any custom language specified in strings.yml # The chosen language for PlaceholderSigns. You can use "en" or any custom language specified in strings.yml
language: en 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

@ -21,6 +21,7 @@ commands:
unWaxSign: unWaxSign:
usage: /<command> usage: /<command>
permission: placeholdersigns.unwax permission: placeholdersigns.unwax
description: Removes the wax from a waxed sign
copySignText: copySignText:
usage: | usage: |
/<command> [side] [sourceLine] [destinationLine] /<command> [side] [sourceLine] [destinationLine]
@ -29,6 +30,10 @@ commands:
destinationLine = The destination sign line to overwrite. If not set, it's the same as sourceLine destinationLine = The destination sign line to overwrite. If not set, it's the same as sourceLine
permission: placeholdersigns.copy.use permission: placeholdersigns.copy.use
description: Copies all text, or a single line, from one sign to another, either the specified side or both 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: permissions:
placeholdersigns.*: placeholdersigns.*:
@ -39,6 +44,7 @@ permissions:
- placeholdersigns.view - placeholdersigns.view
- placeholdersigns.copy - placeholdersigns.copy
- placeholdersigns.unwax - placeholdersigns.unwax
- placeholdersigns.setdelay
default: op default: op
placeholdersigns.minimal: placeholdersigns.minimal:
description: Allows minimal access to /setSignLine, /copySign, /copySignText and /viewSign description: Allows minimal access to /setSignLine, /copySign, /copySignText and /viewSign
@ -79,3 +85,6 @@ permissions:
placeholdersigns.unwax: placeholdersigns.unwax:
description: Allows a player to use the /unWax command description: Allows a player to use the /unWax command
default: false default: false
placeholdersigns.setdelay:
description: Allows a player to use the /setPlaceholderUpdateDelay command
default: false

View File

@ -19,3 +19,5 @@ en:
SUCCESS_SIGN_PASTED: "&7Sign pasted!" SUCCESS_SIGN_PASTED: "&7Sign pasted!"
ERROR_CANCELLED_BY_PROTECTION: "A protection plugin blocked the sign change" ERROR_CANCELLED_BY_PROTECTION: "A protection plugin blocked the sign change"
SUCCESS_SIGN_UN_WAXED: "&7The sign was successfully un-waxed" 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"