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
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. |
| 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
@ -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.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 |

View File

@ -6,11 +6,14 @@ import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.placeholdersigns.command.CopySignCommand;
import net.knarcraft.placeholdersigns.command.CopySignTextCommand;
import net.knarcraft.placeholdersigns.command.EditSignCommand;
import net.knarcraft.placeholdersigns.command.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;
@ -18,6 +21,7 @@ import net.knarcraft.placeholdersigns.runnable.SignUpdate;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
@ -31,7 +35,9 @@ public final class PlaceholderSigns extends JavaPlugin {
private static PlaceholderSigns instance;
private PlaceholderSignHandler signHandler;
private PlaceholderSignRequestHandler requestHandler;
private PlaceholderSignUpdateQueueHandler updateQueueHandler;
private StringFormatter stringFormatter;
private int signUpdateDelay;
/**
* Gets an instance of this plugin
@ -53,21 +59,51 @@ public final class PlaceholderSigns extends JavaPlugin {
return this.signHandler;
}
/**
* Gets this instance's placeholder sign request handler
*
* @return <p>The request handler</p>
*/
@NotNull
public PlaceholderSignRequestHandler getRequestHandler() {
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
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;
getConfig().options().copyDefaults(true);
saveConfig();
Translator translator = new Translator();
translator.registerMessageCategory(PlaceholderSignMessage.SUCCESS_CLICK_SIGN_TO_EDIT);
translator.setColorConversion(ColorConversion.RGB);
@ -77,9 +113,15 @@ public final class PlaceholderSigns extends JavaPlugin {
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.");
@ -87,18 +129,20 @@ public final class PlaceholderSigns extends JavaPlugin {
return;
}
// Update signs' placeholders every second
Bukkit.getScheduler().runTaskTimer(this, new SignUpdate(this.signHandler), 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);
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

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);
}
Map<Integer, String> placeholders = placeholderSign.placeholders().get(side);
Map<Integer, String> placeholders = placeholderSign.getPlaceholders().get(side);
if (placeholders != null) {
for (Map.Entry<Integer, String> entry : placeholders.entrySet()) {
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
*/
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

View File

@ -3,15 +3,78 @@ package net.knarcraft.placeholdersigns.container;
import org.bukkit.Location;
import org.bukkit.block.sign.Side;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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(@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.container.PlaceholderSign;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.sign.Side;
@ -29,15 +30,7 @@ public class PlaceholderSignHandler {
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
@ -57,8 +50,11 @@ public class PlaceholderSignHandler {
*/
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);
}
/**
@ -67,9 +63,24 @@ public class PlaceholderSignHandler {
* @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);
}
/**
* 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() {
this.placeholderSigns = new HashSet<>();
this.locationLookup = new HashMap<>();
this.signsInChunk = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.getConfigurationSection("signs");
@ -104,9 +116,16 @@ public class PlaceholderSignHandler {
allPlaceholders.put(Side.FRONT, frontPlaceholders);
allPlaceholders.put(Side.BACK, backPlaceholders);
PlaceholderSign sign = new PlaceholderSign(signLocation, allPlaceholders);
this.placeholderSigns.add(sign);
this.locationLookup.put(signLocation, sign);
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));
}
}
@ -153,7 +172,7 @@ public class PlaceholderSignHandler {
* @param sign <p>The sign to save</p>
*/
private void saveSign(@NotNull ConfigurationSection section, @NotNull PlaceholderSign sign) {
Location location = sign.location();
Location location = sign.getLocation();
if (location.getWorld() == null) {
return;
}
@ -163,19 +182,21 @@ public class PlaceholderSignHandler {
String frontKey = key + ".placeholders.front";
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) {
for (Map.Entry<Integer, String> entry : frontPlaceholders.entrySet()) {
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) {
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,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());
if (sign != null) {
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);
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)) {
text = placeholders.get(sourceSide).get(sourceLine);
}
@ -162,12 +162,12 @@ public class SignClickListener implements Listener {
// Remove old placeholders from the sign side
Map<Integer, String> oldPlaceholders = null;
if (targetPlaceholderSign != null) {
oldPlaceholders = targetPlaceholderSign.placeholders().remove(clickedSide);
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.placeholders().put(clickedSide, oldPlaceholders);
targetPlaceholderSign.getPlaceholders().put(clickedSide, oldPlaceholders);
}
// Save any placeholder changes
@ -307,7 +307,7 @@ public class SignClickListener implements Listener {
@NotNull Player player) {
String[] sideLines = signSide.getLines();
if (placeholderSign != null) {
Map<Side, Map<Integer, String>> placeholders = placeholderSign.placeholders();
Map<Side, Map<Integer, String>> placeholders = placeholderSign.getPlaceholders();
loadPlaceholders(sourceSide, sideLines, placeholders);
}
@ -385,7 +385,7 @@ public class SignClickListener implements Listener {
String oldPlaceholder = null;
if (targetPlaceholderSign != null) {
// Remove the old placeholder
oldPlaceholder = targetPlaceholderSign.placeholders().get(targetSide).remove(destinationLine);
oldPlaceholder = targetPlaceholderSign.getPlaceholders().get(targetSide).remove(destinationLine);
}
lines[destinationLine] = newText;
@ -396,7 +396,7 @@ public class SignClickListener implements Listener {
if (changeEvent.isCancelled()) {
if (targetPlaceholderSign != null) {
// 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,
PlaceholderSignMessage.ERROR_CANCELLED_BY_PROTECTION);

View File

@ -61,11 +61,12 @@ public class SignTextListener implements Listener {
placeholderSide.put(event.getSide(), placeholders);
// 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.save();
} else if (!placeholders.isEmpty()) {
// 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);
signHandler.save();
}

View File

@ -3,8 +3,11 @@ 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;
@ -31,40 +34,67 @@ public class SignUpdate implements Runnable {
@Override
public void run() {
for (PlaceholderSign placeholderSign : signHandler.getSigns()) {
// Ignore signs away from players
Location location = placeholderSign.location();
if (!location.getChunk().isLoaded()) {
continue;
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;
}
// If no longer a sign, remove
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();
}
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
*

View File

@ -13,6 +13,7 @@ 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<>();
@ -24,12 +25,24 @@ public final class TabCompleteHelper {
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
*

View File

@ -1,2 +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

@ -21,6 +21,7 @@ commands:
unWaxSign:
usage: /<command>
permission: placeholdersigns.unwax
description: Removes the wax from a waxed sign
copySignText:
usage: |
/<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
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.*:
@ -39,6 +44,7 @@ permissions:
- placeholdersigns.view
- placeholdersigns.copy
- placeholdersigns.unwax
- placeholdersigns.setdelay
default: op
placeholdersigns.minimal:
description: Allows minimal access to /setSignLine, /copySign, /copySignText and /viewSign
@ -79,3 +85,6 @@ permissions:
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

@ -19,3 +19,5 @@ en:
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"