From 750e74360ac0fad286099f49f0ef50d0afe4969a Mon Sep 17 00:00:00 2001 From: filoghost Date: Sat, 24 Apr 2021 23:37:54 +0200 Subject: [PATCH] Partial placeholder refactoring --- .../holographicdisplays/api/HologramsAPI.java | 26 +- .../api/internal/BackendAPI.java | 4 +- .../api/placeholder/Placeholder.java | 12 + .../api/placeholder/PlaceholderFactory.java | 14 + .../api/placeholder/PlaceholderReplacer.java | 19 +- .../holographicdisplays/core/Utils.java | 20 - .../core/hologram/StandardTextLine.java | 2 + .../core/placeholder/RelativePlaceholder.java | 35 +- .../RelativePlaceholderReplacer.java | 14 + .../DefaultBackendAPI.java | 27 +- .../HolographicDisplays.java | 22 +- .../bungeecord/BungeeServerTracker.java | 3 +- .../bridge/protocollib/PacketListener.java | 5 +- .../object/api/APIHologram.java | 4 +- .../object/api/APIHologramManager.java | 6 +- .../object/api/APITextLine.java | 2 +- .../object/base/BaseHologram.java | 8 +- .../object/base/BaseHologramLine.java | 4 +- .../object/base/BaseTextLine.java | 26 +- .../object/internal/InternalHologram.java | 4 +- .../internal/InternalHologramManager.java | 6 +- .../object/internal/InternalTextLine.java | 2 +- .../placeholder/DynamicLineData.java | 79 ---- .../placeholder/Placeholder.java | 80 ---- .../placeholder/PlaceholderException.java | 23 ++ .../placeholder/PlaceholderManager.java | 38 ++ .../placeholder/PlaceholdersManager.java | 377 ------------------ .../placeholder/PlaceholdersRegistry.java | 103 ----- .../PlaceholdersReplacementTracker.java | 113 ++++++ .../placeholder/PlaceholdersUpdateTask.java | 141 +++++++ .../AnimationPlaceholder.java} | 22 +- .../AnimationRegistry.java} | 31 +- .../internal/DefaultPlaceholders.java | 103 +++++ .../OnlinePlayersPlaceholderFactory.java | 77 ++++ .../internal/StaticPlaceholder.java | 28 ++ .../WorldPlayersPlaceholderFactory.java | 64 +++ .../parsing/PlaceholderIdentifier.java | 41 ++ .../parsing/PlaceholderOccurrence.java | 97 +++++ .../placeholder/parsing/PluginName.java | 46 +++ .../parsing/StringWithPlaceholders.java | 134 +++++++ .../registry/PlaceholderExpansion.java | 53 +++ .../registry/PlaceholderRegistry.java | 91 +++++ .../placeholder/util/SimplePlaceholder.java | 34 ++ .../util/SingletonPlaceholderFactory.java | 26 ++ .../task/WorldPlayerCounterTask.java | 55 --- .../parsing/StringWithPlaceholdersTest.java | 68 ++++ 46 files changed, 1340 insertions(+), 849 deletions(-) create mode 100644 api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/Placeholder.java create mode 100644 api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderFactory.java create mode 100644 core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholderReplacer.java delete mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/DynamicLineData.java delete mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/Placeholder.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderException.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderManager.java delete mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersManager.java delete mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersRegistry.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersReplacementTracker.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersUpdateTask.java rename plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/{CyclicPlaceholderReplacer.java => internal/AnimationPlaceholder.java} (51%) rename plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/{AnimationsRegistry.java => internal/AnimationRegistry.java} (79%) create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/DefaultPlaceholders.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/OnlinePlayersPlaceholderFactory.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/StaticPlaceholder.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/WorldPlayersPlaceholderFactory.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderIdentifier.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderOccurrence.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PluginName.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholders.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderExpansion.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderRegistry.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SimplePlaceholder.java create mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SingletonPlaceholderFactory.java delete mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/task/WorldPlayerCounterTask.java create mode 100644 plugin/src/test/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholdersTest.java diff --git a/api/src/main/java/me/filoghost/holographicdisplays/api/HologramsAPI.java b/api/src/main/java/me/filoghost/holographicdisplays/api/HologramsAPI.java index 09b8f882..6417f87d 100644 --- a/api/src/main/java/me/filoghost/holographicdisplays/api/HologramsAPI.java +++ b/api/src/main/java/me/filoghost/holographicdisplays/api/HologramsAPI.java @@ -48,27 +48,16 @@ public class HologramsAPI { return BackendAPI.getImplementation().getHolograms(plugin); } - - /** - * Registers a new placeholder that can be used in holograms created with commands. - * With this method, you can basically expand the core of HolographicDisplays. - * - * @param plugin the owner plugin of the placeholder - * @param textPlaceholder the text that the placeholder will be associated to (e.g.: "{onlinePlayers}") - * @param refreshRate the refresh rate of the placeholder, in seconds. Keep in mind that the minimum is 0.1 seconds, and that will be rounded to tenths of seconds - * @param replacer the implementation that will return the text to replace the placeholder, where the update() method is called every refreshRate seconds - * @return true if the registration was successfull, false if it was already registered - */ - public static boolean registerPlaceholder(Plugin plugin, String textPlaceholder, double refreshRate, PlaceholderReplacer replacer) { - return BackendAPI.getImplementation().registerPlaceholder(plugin, textPlaceholder, refreshRate, replacer); + public static void registerPlaceholder(Plugin plugin, String identifier, int refreshIntervalTicks, PlaceholderReplacer replacer) { + BackendAPI.getImplementation().registerPlaceholder(plugin, identifier, refreshIntervalTicks, replacer); } /** - * Finds all the placeholders registered by a given plugin. + * Returns all the placeholder identifiers registered by a given plugin. * * @param plugin the plugin to search for - * @return a collection of placeholders registered by the plugin + * @return a collection of placeholder identifiers registered by the plugin */ public static Collection getRegisteredPlaceholders(Plugin plugin) { return BackendAPI.getImplementation().getRegisteredPlaceholders(plugin); @@ -79,11 +68,10 @@ public class HologramsAPI { * Unregister a placeholder created by a plugin. * * @param plugin the plugin that owns the placeholder - * @param textPlaceholder the placeholder to remove - * @return true if found and removed, false otherwise + * @param identifier the identifier of the placeholder to remove */ - public static boolean unregisterPlaceholder(Plugin plugin, String textPlaceholder) { - return BackendAPI.getImplementation().unregisterPlaceholder(plugin, textPlaceholder); + public static void unregisterPlaceholder(Plugin plugin, String identifier) { + BackendAPI.getImplementation().unregisterPlaceholder(plugin, identifier); } diff --git a/api/src/main/java/me/filoghost/holographicdisplays/api/internal/BackendAPI.java b/api/src/main/java/me/filoghost/holographicdisplays/api/internal/BackendAPI.java index 555f7146..29c25a7b 100644 --- a/api/src/main/java/me/filoghost/holographicdisplays/api/internal/BackendAPI.java +++ b/api/src/main/java/me/filoghost/holographicdisplays/api/internal/BackendAPI.java @@ -33,11 +33,11 @@ public abstract class BackendAPI { public abstract Collection getHolograms(Plugin plugin); - public abstract boolean registerPlaceholder(Plugin plugin, String textPlaceholder, double refreshRate, PlaceholderReplacer replacer); + public abstract void registerPlaceholder(Plugin plugin, String identifier, int refreshIntervalTicks, PlaceholderReplacer replacer); public abstract Collection getRegisteredPlaceholders(Plugin plugin); - public abstract boolean unregisterPlaceholder(Plugin plugin, String textPlaceholder); + public abstract void unregisterPlaceholder(Plugin plugin, String identifier); public abstract void unregisterPlaceholders(Plugin plugin); diff --git a/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/Placeholder.java b/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/Placeholder.java new file mode 100644 index 00000000..f01452e6 --- /dev/null +++ b/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/Placeholder.java @@ -0,0 +1,12 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.api.placeholder; + +public interface Placeholder extends PlaceholderReplacer { + + int getRefreshIntervalTicks(); + +} diff --git a/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderFactory.java b/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderFactory.java new file mode 100644 index 00000000..e0e2bc07 --- /dev/null +++ b/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderFactory.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.api.placeholder; + +import org.jetbrains.annotations.Nullable; + +public interface PlaceholderFactory { + + Placeholder getPlaceholder(@Nullable String argument); + +} diff --git a/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderReplacer.java b/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderReplacer.java index 86f403eb..5aefa233 100644 --- a/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderReplacer.java +++ b/api/src/main/java/me/filoghost/holographicdisplays/api/placeholder/PlaceholderReplacer.java @@ -5,12 +5,23 @@ */ package me.filoghost.holographicdisplays.api.placeholder; +import org.jetbrains.annotations.Nullable; + +/** + * Simple callback to provide a placeholder replacement. + */ +@FunctionalInterface public interface PlaceholderReplacer { /** - * Called to update a placeholder's replacement. - * @return the replacement + * Callback for providing a placeholder replacement, given the argument of the placeholder (if present). + *

+ * For example, the argument of {test} is null, the argument of {test: hello world} is the string "hello world". + *

+ * Warning: this method should be performance efficient, as it may be invoked often. + * + * @return the placeholder replacement */ - String update(); - + String getReplacement(@Nullable String argument); + } diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/Utils.java b/core/src/main/java/me/filoghost/holographicdisplays/core/Utils.java index 15c0b00d..343f5274 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/Utils.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/Utils.java @@ -65,26 +65,6 @@ public class Utils { return result.toString(); } - - public static String sanitize(String s) { - return s != null ? s : "null"; - } - - - public static boolean isThereNonNull(Object... objects) { - if (objects == null) { - return false; - } - - for (Object object : objects) { - if (object != null) { - return true; - } - } - - return false; - } - public static String formatExceptionMessage(Throwable t) { return formatExceptionMessage(t.getMessage()); diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/hologram/StandardTextLine.java b/core/src/main/java/me/filoghost/holographicdisplays/core/hologram/StandardTextLine.java index 4d0bff28..6f84cbd4 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/hologram/StandardTextLine.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/hologram/StandardTextLine.java @@ -14,6 +14,8 @@ public interface StandardTextLine extends StandardTouchableLine { String getText(); + boolean isAllowPlaceholders(); + Collection getRelativePlaceholders(); NMSArmorStand getNMSArmorStand(); diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholder.java b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholder.java index c220a45a..76c67656 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholder.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholder.java @@ -10,31 +10,37 @@ import org.bukkit.entity.Player; import java.util.Collection; import java.util.HashSet; -public abstract class RelativePlaceholder { +public class RelativePlaceholder implements RelativePlaceholderReplacer { private static final Collection registry = new HashSet<>(); // The placeholder itself, something like {player}. private final String textPlaceholder; - public RelativePlaceholder(String textPlaceholder) { + private final RelativePlaceholderReplacer replacer; + + public RelativePlaceholder(String textPlaceholder, RelativePlaceholderReplacer replacer) { this.textPlaceholder = textPlaceholder; + this.replacer = replacer; } public String getTextPlaceholder() { return textPlaceholder; } - public abstract String getReplacement(Player player); + @Override + public String getReplacement(Player player) { + return replacer.getReplacement(player); + } - public static void register(RelativePlaceholder relativePlaceholder) { + public static void register(String textPlaceholder, RelativePlaceholderReplacer replacer) { for (RelativePlaceholder existingPlaceholder : registry) { - if (existingPlaceholder.getTextPlaceholder().equals(relativePlaceholder.getTextPlaceholder())) { + if (existingPlaceholder.getTextPlaceholder().equals(textPlaceholder)) { throw new IllegalArgumentException("Relative placeholder already registered"); } } - registry.add(relativePlaceholder); + registry.add(new RelativePlaceholder(textPlaceholder, replacer)); } public static Collection getRegistry() { @@ -42,21 +48,8 @@ public abstract class RelativePlaceholder { } static { - register(new RelativePlaceholder("{player}") { - - @Override - public String getReplacement(Player player) { - return player.getName(); - } - }); - - register(new RelativePlaceholder("{displayname}") { - - @Override - public String getReplacement(Player player) { - return player.getDisplayName(); - } - }); + register("{player}", Player::getName); + register("{displayname}", Player::getDisplayName); } } diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholderReplacer.java b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholderReplacer.java new file mode 100644 index 00000000..4a14617b --- /dev/null +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/placeholder/RelativePlaceholderReplacer.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.core.placeholder; + +import org.bukkit.entity.Player; + +public interface RelativePlaceholderReplacer { + + String getReplacement(Player player); + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/DefaultBackendAPI.java b/plugin/src/main/java/me/filoghost/holographicdisplays/DefaultBackendAPI.java index 0f21e4d1..2d4f700c 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/DefaultBackendAPI.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/DefaultBackendAPI.java @@ -11,8 +11,7 @@ import me.filoghost.holographicdisplays.api.internal.BackendAPI; import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.object.api.APIHologramManager; -import me.filoghost.holographicdisplays.placeholder.Placeholder; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersRegistry; +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderRegistry; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; @@ -24,9 +23,9 @@ public class DefaultBackendAPI extends BackendAPI { private final APIHologramManager apiHologramManager; private final NMSManager nmsManager; - private final PlaceholdersRegistry placeholderRegistry; + private final PlaceholderRegistry placeholderRegistry; - public DefaultBackendAPI(APIHologramManager apiHologramManager, NMSManager nmsManager, PlaceholdersRegistry placeholderRegistry) { + public DefaultBackendAPI(APIHologramManager apiHologramManager, NMSManager nmsManager, PlaceholderRegistry placeholderRegistry) { this.apiHologramManager = apiHologramManager; this.nmsManager = nmsManager; this.placeholderRegistry = placeholderRegistry; @@ -43,12 +42,12 @@ public class DefaultBackendAPI extends BackendAPI { } @Override - public boolean registerPlaceholder(Plugin plugin, String textPlaceholder, double refreshRate, PlaceholderReplacer replacer) { - Preconditions.notNull(textPlaceholder, "textPlaceholder"); - Preconditions.checkArgument(refreshRate >= 0, "refreshRate should be positive"); + public void registerPlaceholder(Plugin plugin, String identifier, int refreshIntervalTicks, PlaceholderReplacer replacer) { + Preconditions.notNull(identifier, "identifier"); + Preconditions.checkArgument(refreshIntervalTicks >= 0, "refreshIntervalTicks should be positive"); Preconditions.notNull(replacer, "replacer"); - return placeholderRegistry.register(new Placeholder(plugin, textPlaceholder, refreshRate, replacer)); + placeholderRegistry.registerReplacer(plugin, identifier, refreshIntervalTicks, replacer); } @Override @@ -66,22 +65,20 @@ public class DefaultBackendAPI extends BackendAPI { @Override public Collection getRegisteredPlaceholders(Plugin plugin) { Preconditions.notNull(plugin, "plugin"); - return placeholderRegistry.getTextPlaceholdersByPlugin(plugin); + return placeholderRegistry.getRegisteredIdentifiers(plugin); } @Override - public boolean unregisterPlaceholder(Plugin plugin, String textPlaceholder) { + public void unregisterPlaceholder(Plugin plugin, String identifier) { Preconditions.notNull(plugin, "plugin"); - Preconditions.notNull(textPlaceholder, "textPlaceholder"); - return placeholderRegistry.unregister(plugin, textPlaceholder); + Preconditions.notNull(identifier, "identifier"); + placeholderRegistry.unregister(plugin, identifier); } @Override public void unregisterPlaceholders(Plugin plugin) { Preconditions.notNull(plugin, "plugin"); - for (String placeholder : getRegisteredPlaceholders(plugin)) { - unregisterPlaceholder(plugin, placeholder); - } + placeholderRegistry.unregisterAll(plugin); } } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/HolographicDisplays.java b/plugin/src/main/java/me/filoghost/holographicdisplays/HolographicDisplays.java index 1c489de8..6d3040e1 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/HolographicDisplays.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/HolographicDisplays.java @@ -28,9 +28,9 @@ import me.filoghost.holographicdisplays.object.api.APIHologram; import me.filoghost.holographicdisplays.object.api.APIHologramManager; import me.filoghost.holographicdisplays.object.internal.InternalHologram; import me.filoghost.holographicdisplays.object.internal.InternalHologramManager; -import me.filoghost.holographicdisplays.placeholder.AnimationsRegistry; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; -import me.filoghost.holographicdisplays.task.WorldPlayerCounterTask; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; +import me.filoghost.holographicdisplays.placeholder.internal.AnimationRegistry; +import me.filoghost.holographicdisplays.placeholder.internal.DefaultPlaceholders; import me.filoghost.holographicdisplays.util.NMSVersion; import org.bstats.bukkit.MetricsLite; import org.bukkit.Bukkit; @@ -47,8 +47,8 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke private InternalHologramManager internalHologramManager; private APIHologramManager apiHologramManager; private BungeeServerTracker bungeeServerTracker; - private AnimationsRegistry animationRegistry; - private PlaceholdersManager placeholderManager; + private AnimationRegistry animationRegistry; + private PlaceholderManager placeholderManager; @Override public void onCheckedEnable() throws PluginEnableException { @@ -91,8 +91,8 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke } bungeeServerTracker = new BungeeServerTracker(this); - animationRegistry = new AnimationsRegistry(); - placeholderManager = new PlaceholdersManager(bungeeServerTracker, animationRegistry); + animationRegistry = new AnimationRegistry(); + placeholderManager = new PlaceholderManager(); configManager = new ConfigManager(getDataFolder().toPath()); internalHologramManager = new InternalHologramManager(nmsManager, placeholderManager); apiHologramManager = new APIHologramManager(nmsManager, placeholderManager); @@ -111,8 +111,7 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke ProtocolLibHook.setup(this, nmsManager, this, errorCollector); // Start repeating tasks. - placeholderManager.startRefreshTask(this); - Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new WorldPlayerCounterTask(), 0L, 3 * 20); + placeholderManager.startUpdaterTask(this); HologramCommandManager commandManager = new HologramCommandManager(configManager, internalHologramManager, nmsManager); commandManager.register(this); @@ -124,7 +123,7 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke registerListener(updateNotificationListener); // Enable the API. - BackendAPI.setImplementation(new DefaultBackendAPI(apiHologramManager, nmsManager, placeholderManager.getRegistry())); + BackendAPI.setImplementation(new DefaultBackendAPI(apiHologramManager, nmsManager, placeholderManager.getPlaceholderRegistry())); // Register bStats metrics int pluginID = 3123; @@ -139,7 +138,8 @@ public class HolographicDisplays extends FCommonsPlugin implements ProtocolPacke } public void load(boolean deferHologramsCreation, ErrorCollector errorCollector) { - placeholderManager.untrackAll(); + DefaultPlaceholders.resetAndRegister(placeholderManager.getPlaceholderRegistry(), animationRegistry, bungeeServerTracker); + internalHologramManager.clearAll(); configManager.reloadCustomPlaceholders(errorCollector); diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/bungeecord/BungeeServerTracker.java b/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/bungeecord/BungeeServerTracker.java index 0c41a981..284993b5 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/bungeecord/BungeeServerTracker.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/bungeecord/BungeeServerTracker.java @@ -14,6 +14,7 @@ import me.filoghost.holographicdisplays.disk.Configuration; import me.filoghost.holographicdisplays.disk.ServerAddress; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.net.SocketTimeoutException; @@ -47,7 +48,7 @@ public class BungeeServerTracker { this::runPeriodicUpdateTask, 1, timeUnit.toSeconds(updateInterval) * 20L); } - public ServerInfo getCurrentServerInfo(String serverName) { + public ServerInfo getCurrentServerInfo(@NotNull String serverName) { // If it wasn't already tracked, send an update request instantly if (!Configuration.pingerEnabled && !trackedServers.containsKey(serverName)) { bungeeMessenger.sendPlayerCountRequest(serverName); diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/protocollib/PacketListener.java b/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/protocollib/PacketListener.java index 58ad40a7..70459da3 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/protocollib/PacketListener.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/bridge/protocollib/PacketListener.java @@ -101,8 +101,11 @@ class PacketListener extends PacketAdapter { } StandardTextLine textLine = (StandardTextLine) hologramLine; + if (!textLine.isAllowPlaceholders()) { + return; + } + Collection relativePlaceholders = textLine.getRelativePlaceholders(); - if (relativePlaceholders == null || relativePlaceholders.isEmpty()) { return; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologram.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologram.java index 276c2540..f2bbd7d2 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologram.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologram.java @@ -14,7 +14,7 @@ import me.filoghost.holographicdisplays.api.line.TextLine; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.disk.Configuration; import me.filoghost.holographicdisplays.object.base.BaseHologram; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -31,7 +31,7 @@ public class APIHologram extends BaseHologram implements Hologr private boolean allowPlaceholders; - protected APIHologram(Location source, Plugin plugin, NMSManager nmsManager, APIHologramManager apiHologramManager, PlaceholdersManager placeholderManager) { + protected APIHologram(Location source, Plugin plugin, NMSManager nmsManager, APIHologramManager apiHologramManager, PlaceholderManager placeholderManager) { super(source, nmsManager, placeholderManager); Preconditions.notNull(plugin, "plugin"); this.plugin = plugin; diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologramManager.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologramManager.java index 5442f0ae..235f3a06 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologramManager.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APIHologramManager.java @@ -8,7 +8,7 @@ package me.filoghost.holographicdisplays.object.api; import me.filoghost.holographicdisplays.api.Hologram; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.object.base.BaseHologramManager; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; import org.bukkit.Location; import org.bukkit.plugin.Plugin; @@ -20,9 +20,9 @@ import java.util.List; public class APIHologramManager extends BaseHologramManager { private final NMSManager nmsManager; - private final PlaceholdersManager placeholderManager; + private final PlaceholderManager placeholderManager; - public APIHologramManager(NMSManager nmsManager, PlaceholdersManager placeholderManager) { + public APIHologramManager(NMSManager nmsManager, PlaceholderManager placeholderManager) { this.nmsManager = nmsManager; this.placeholderManager = placeholderManager; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APITextLine.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APITextLine.java index ed5dc940..805b193b 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APITextLine.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/api/APITextLine.java @@ -23,7 +23,7 @@ public class APITextLine extends BaseTextLine implements TextLine, APIHologramLi } @Override - protected boolean isAllowPlaceholders() { + public boolean isAllowPlaceholders() { return parent.isAllowPlaceholders(); } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologram.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologram.java index 6b3a9637..b03368ff 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologram.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologram.java @@ -10,7 +10,7 @@ import me.filoghost.holographicdisplays.core.hologram.StandardHologram; import me.filoghost.holographicdisplays.core.hologram.StandardHologramLine; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.disk.Configuration; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; import org.bukkit.Location; import org.bukkit.World; @@ -22,13 +22,13 @@ import java.util.List; public abstract class BaseHologram extends BaseHologramComponent implements StandardHologram { private final NMSManager nmsManager; - private final PlaceholdersManager placeholderManager; + private final PlaceholderManager placeholderManager; private final List lines; private final List unmodifiableLinesView; private boolean deleted; - public BaseHologram(Location location, NMSManager nmsManager, PlaceholdersManager placeholderManager) { + public BaseHologram(Location location, NMSManager nmsManager, PlaceholderManager placeholderManager) { this.placeholderManager = placeholderManager; Preconditions.notNull(location, "location"); this.setLocation(location); @@ -41,7 +41,7 @@ public abstract class BaseHologram extends BaseH return nmsManager; } - protected final PlaceholdersManager getPlaceholderManager() { + protected final PlaceholderManager getPlaceholderManager() { return placeholderManager; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologramLine.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologramLine.java index f1dde101..febe53ab 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologramLine.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseHologramLine.java @@ -11,7 +11,7 @@ import me.filoghost.holographicdisplays.core.hologram.StandardHologram; import me.filoghost.holographicdisplays.core.hologram.StandardHologramLine; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.core.nms.SpawnFailedException; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; import org.bukkit.World; public abstract class BaseHologramLine extends BaseHologramComponent implements StandardHologramLine { @@ -34,7 +34,7 @@ public abstract class BaseHologramLine extends BaseHologramComponent implements return hologram.getNMSManager(); } - protected final PlaceholdersManager getPlaceholderManager() { + protected final PlaceholderManager getPlaceholderManager() { return hologram.getPlaceholderManager(); } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseTextLine.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseTextLine.java index c7483b26..3118c804 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseTextLine.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/base/BaseTextLine.java @@ -13,10 +13,11 @@ import org.bukkit.World; import java.util.ArrayList; import java.util.Collection; +import java.util.List; public abstract class BaseTextLine extends BaseTouchableLine implements StandardTextLine { - private final ArrayList relativePlaceholders; + private final List relativePlaceholders; private String text; private NMSArmorStand textEntity; @@ -26,8 +27,6 @@ public abstract class BaseTextLine extends BaseTouchableLine implements Standard setText(text); } - protected abstract boolean isAllowPlaceholders(); - @Override public String getText() { return text; @@ -37,17 +36,8 @@ public abstract class BaseTextLine extends BaseTouchableLine implements Standard this.text = text; if (textEntity != null) { - if (text != null && !text.isEmpty()) { - textEntity.setCustomNameNMS(text); - if (isAllowPlaceholders()) { - getPlaceholderManager().trackIfNecessary(this); - } - } else { - textEntity.setCustomNameNMS(""); // It will not appear - if (isAllowPlaceholders()) { - getPlaceholderManager().untrack(this); - } - } + textEntity.setCustomNameNMS(text != null ? text : ""); + getPlaceholderManager().updateTracking(this); } relativePlaceholders.clear(); @@ -70,9 +60,7 @@ public abstract class BaseTextLine extends BaseTouchableLine implements Standard textEntity.setCustomNameNMS(text); } - if (isAllowPlaceholders()) { - getPlaceholderManager().trackIfNecessary(this); - } + getPlaceholderManager().updateTracking(this); } @Override @@ -96,10 +84,6 @@ public abstract class BaseTextLine extends BaseTouchableLine implements Standard @Override public Collection getRelativePlaceholders() { - if (!isAllowPlaceholders()) { - return null; - } - return relativePlaceholders; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologram.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologram.java index 2d52ab0f..200fbcc5 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologram.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologram.java @@ -8,7 +8,7 @@ package me.filoghost.holographicdisplays.object.internal; import me.filoghost.holographicdisplays.HolographicDisplays; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.object.base.BaseHologram; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -18,7 +18,7 @@ public class InternalHologram extends BaseHologram { private final String name; - protected InternalHologram(Location source, String name, NMSManager nmsManager, PlaceholdersManager placeholderManager) { + protected InternalHologram(Location source, String name, NMSManager nmsManager, PlaceholderManager placeholderManager) { super(source, nmsManager, placeholderManager); this.name = name; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologramManager.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologramManager.java index 9cf8f4d7..1d8baf63 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologramManager.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalHologramManager.java @@ -7,15 +7,15 @@ package me.filoghost.holographicdisplays.object.internal; import me.filoghost.holographicdisplays.core.nms.NMSManager; import me.filoghost.holographicdisplays.object.base.BaseHologramManager; -import me.filoghost.holographicdisplays.placeholder.PlaceholdersManager; +import me.filoghost.holographicdisplays.placeholder.PlaceholderManager; import org.bukkit.Location; public class InternalHologramManager extends BaseHologramManager { private final NMSManager nmsManager; - private final PlaceholdersManager placeholderManager; + private final PlaceholderManager placeholderManager; - public InternalHologramManager(NMSManager nmsManager, PlaceholdersManager placeholderManager) { + public InternalHologramManager(NMSManager nmsManager, PlaceholderManager placeholderManager) { this.nmsManager = nmsManager; this.placeholderManager = placeholderManager; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalTextLine.java b/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalTextLine.java index 3b63c98d..9ad2c02c 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalTextLine.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/object/internal/InternalTextLine.java @@ -17,7 +17,7 @@ public class InternalTextLine extends BaseTextLine implements InternalHologramLi } @Override - protected boolean isAllowPlaceholders() { + public boolean isAllowPlaceholders() { return true; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/DynamicLineData.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/DynamicLineData.java deleted file mode 100644 index 236f6b60..00000000 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/DynamicLineData.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) filoghost and contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package me.filoghost.holographicdisplays.placeholder; - -import me.filoghost.fcommons.Preconditions; -import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; -import me.filoghost.holographicdisplays.core.nms.entity.NMSArmorStand; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class DynamicLineData { - - private final NMSArmorStand entity; - private final String originalName; - - private Set placeholders; - private final Map animations; - private final Map replacers; - - public DynamicLineData(NMSArmorStand entity, String originalName) { - Preconditions.notNull(entity, "entity"); - - this.entity = entity; - this.originalName = originalName; - placeholders = new HashSet<>(); - animations = new HashMap<>(); - replacers = new HashMap<>(); - } - - public NMSArmorStand getEntity() { - return entity; - } - - public String getOriginalName() { - return originalName; - } - - public void setPlaceholders(Set placeholders) { - this.placeholders = placeholders; - } - - public Set getPlaceholders() { - return placeholders; - } - - public Map getReplacers() { - return replacers; - } - - public Map getAnimations() { - return animations; - } - - @Override - public int hashCode() { - return entity.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - DynamicLineData other = (DynamicLineData) obj; - return this.entity == other.entity; - } - - - -} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/Placeholder.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/Placeholder.java deleted file mode 100644 index c8717684..00000000 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/Placeholder.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) filoghost and contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package me.filoghost.holographicdisplays.placeholder; - -import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; -import org.bukkit.plugin.Plugin; - -public class Placeholder { - - // The plugin that owns this placeholder. - private final Plugin owner; - - // The placeholder itself, something like {onlinePlayers}. Case sensitive! - private final String textPlaceholder; - - // How many tenths of second between each refresh. - private final int tenthsToRefresh; - - // This is the current replacement for this placeholder. - private String currentReplacement; - - private final PlaceholderReplacer replacer; - - public Placeholder(Plugin owner, String textPlaceholder, double refreshRate, PlaceholderReplacer replacer) { - this.owner = owner; - this.textPlaceholder = textPlaceholder; - this.tenthsToRefresh = refreshRate <= 0.1 ? 1 : (int) (refreshRate * 10.0); - this.replacer = replacer; - this.currentReplacement = ""; - } - - public Plugin getOwner() { - return owner; - } - - public int getTenthsToRefresh() { - return tenthsToRefresh; - } - - public String getTextPlaceholder() { - return textPlaceholder; - } - - public String getCurrentReplacement() { - return currentReplacement; - } - - public void setCurrentReplacement(String replacement) { - this.currentReplacement = replacement != null ? replacement : "null"; - } - - public void update() { - setCurrentReplacement(replacer.update()); - } - - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - - if (obj instanceof Placeholder) { - return ((Placeholder) obj).textPlaceholder.equals(this.textPlaceholder); - } - - return false; - } - - - @Override - public int hashCode() { - return textPlaceholder.hashCode(); - } - - -} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderException.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderException.java new file mode 100644 index 00000000..e96896a9 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderException.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder; + +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderExpansion; + +public class PlaceholderException extends Exception { + + private final PlaceholderExpansion placeholderExpansion; + + public PlaceholderException(Throwable cause, PlaceholderExpansion placeholderExpansion) { + super(cause); + this.placeholderExpansion = placeholderExpansion; + } + + public PlaceholderExpansion getPlaceholderExpansion() { + return placeholderExpansion; + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderManager.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderManager.java new file mode 100644 index 00000000..24988af3 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholderManager.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder; + +import me.filoghost.holographicdisplays.core.hologram.StandardTextLine; +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderRegistry; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +public class PlaceholderManager { + + private final PlaceholderRegistry placeholderRegistry; + private final PlaceholdersUpdateTask placeholdersUpdateTask; + + public PlaceholderManager() { + this.placeholderRegistry = new PlaceholderRegistry(); + PlaceholdersReplacementTracker placeholdersReplacementTracker = new PlaceholdersReplacementTracker(placeholderRegistry); + this.placeholdersUpdateTask = new PlaceholdersUpdateTask(placeholdersReplacementTracker); + + placeholderRegistry.setChangeListener(placeholdersReplacementTracker::clearOutdatedSources); + } + + public PlaceholderRegistry getPlaceholderRegistry() { + return placeholderRegistry; + } + + public void startUpdaterTask(Plugin plugin) { + Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, placeholdersUpdateTask, 0, 1); + } + + public void updateTracking(StandardTextLine line) { + placeholdersUpdateTask.updateTracking(line); + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersManager.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersManager.java deleted file mode 100644 index 4d999ee4..00000000 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersManager.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright (C) filoghost and contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package me.filoghost.holographicdisplays.placeholder; - -import me.filoghost.fcommons.Strings; -import me.filoghost.fcommons.logging.Log; -import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; -import me.filoghost.holographicdisplays.bridge.bungeecord.ServerInfo; -import me.filoghost.holographicdisplays.bridge.bungeecord.BungeeServerTracker; -import me.filoghost.holographicdisplays.core.Utils; -import me.filoghost.holographicdisplays.core.hologram.StandardTextLine; -import me.filoghost.holographicdisplays.core.nms.entity.NMSArmorStand; -import me.filoghost.holographicdisplays.disk.Configuration; -import me.filoghost.holographicdisplays.task.WorldPlayerCounterTask; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class PlaceholdersManager { - - private static final String PINGER_NOT_ENABLED_ERROR = "[Please enable pinger]"; - - private static final Pattern BUNGEE_ONLINE_PATTERN = makePlaceholderWithArgsPattern("online"); - private static final Pattern BUNGEE_MAX_PATTERN = makePlaceholderWithArgsPattern("max_players"); - private static final Pattern BUNGEE_MOTD_PATTERN = makePlaceholderWithArgsPattern("motd"); - private static final Pattern BUNGEE_MOTD_2_PATTERN = makePlaceholderWithArgsPattern("motd2"); - private static final Pattern BUNGEE_STATUS_PATTERN = makePlaceholderWithArgsPattern("status"); - private static final Pattern ANIMATION_PATTERN = makePlaceholderWithArgsPattern("animation"); - private static final Pattern WORLD_PATTERN = makePlaceholderWithArgsPattern("world"); - - private final PlaceholdersRegistry placeholderRegistry; - private final AnimationsRegistry animationRegistry; - private final BungeeServerTracker bungeeServerTracker; - protected final Set linesToUpdate; - private long elapsedTenthsOfSecond; - - public PlaceholdersManager(BungeeServerTracker bungeeServerTracker, AnimationsRegistry animationRegistry) { - this.placeholderRegistry = new PlaceholdersRegistry(this); - this.animationRegistry = animationRegistry; - this.bungeeServerTracker = bungeeServerTracker; - this.linesToUpdate = new HashSet<>(); - - placeholderRegistry.addDefaultPlaceholders(); - } - - public void startRefreshTask(Plugin plugin) { - Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, () -> { - - for (Placeholder placeholder : placeholderRegistry.getPlaceholders()) { - if (elapsedTenthsOfSecond % placeholder.getTenthsToRefresh() == 0) { - try { - placeholder.update(); - } catch (Throwable t) { - Log.warning("The placeholder " + placeholder.getTextPlaceholder() + " registered by the plugin " + placeholder.getOwner().getName() + " generated an exception while updating. Please contact the author of " + placeholder.getOwner().getName(), t); - } - } - } - - for (Placeholder placeholder : animationRegistry.getAnimationsByFilename().values()) { - if (elapsedTenthsOfSecond % placeholder.getTenthsToRefresh() == 0) { - placeholder.update(); - } - } - - Iterator iter = linesToUpdate.iterator(); - DynamicLineData currentLineData; - - while (iter.hasNext()) { - currentLineData = iter.next(); - - if (currentLineData.getEntity().isDeadNMS()) { - iter.remove(); - } else { - updatePlaceholders(currentLineData); - } - } - - elapsedTenthsOfSecond++; - - }, 2L, 2L); - } - - - public void untrackAll() { - linesToUpdate.clear(); - } - - public void untrack(StandardTextLine line) { - Iterator iter = linesToUpdate.iterator(); - while (iter.hasNext()) { - DynamicLineData data = iter.next(); - if (data.getEntity() == line.getNMSArmorStand()) { - iter.remove(); - data.getEntity().setCustomNameNMS(data.getOriginalName()); - } - } - } - - public void trackIfNecessary(StandardTextLine line) { - String text = line.getText(); - if (text == null || text.isEmpty()) { - return; - } - - NMSArmorStand entity = line.getNMSArmorStand(); - if (entity == null) { - return; - } - - boolean updateText = false; - - // Lazy initialization. - Set normalPlaceholders = null; - Map bungeeReplacers = null; - Map worldsOnlinePlayersReplacers = null; - Map animationsPlaceholders = null; - - Matcher matcher; - - for (Placeholder placeholder : placeholderRegistry.getPlaceholders()) { - if (text.contains(placeholder.getTextPlaceholder())) { - if (normalPlaceholders == null) { - normalPlaceholders = new HashSet<>(); - } - normalPlaceholders.add(placeholder); - } - } - - - // Players in a world count pattern. - matcher = WORLD_PATTERN.matcher(text); - while (matcher.find()) { - if (worldsOnlinePlayersReplacers == null) { - worldsOnlinePlayersReplacers = new HashMap<>(); - } - - final String worldsNames = extractArgumentFromPlaceholder(matcher); - - if (worldsNames.contains(",")) { - String[] split = worldsNames.split(","); - for (int i = 0; i < split.length; i++) { - split[i] = split[i].trim(); - } - - final String[] worldsToTrack = split; - - // Add it to tracked worlds. - worldsOnlinePlayersReplacers.put(matcher.group(), () -> { - return WorldPlayerCounterTask.getCount(worldsToTrack); - }); - } else { - // Normal, single tracked world. - worldsOnlinePlayersReplacers.put(matcher.group(), () -> { - return WorldPlayerCounterTask.getCount(worldsNames); - }); - } - } - - // BungeeCord online pattern. - matcher = BUNGEE_ONLINE_PATTERN.matcher(text); - while (matcher.find()) { - if (bungeeReplacers == null) { - bungeeReplacers = new HashMap<>(); - } - - final String serverName = extractArgumentFromPlaceholder(matcher); - - if (serverName.contains(",")) { - String[] serversToTrack = Strings.splitAndTrim(serverName, ","); - - // Add it to tracked servers. - bungeeReplacers.put(matcher.group(), () -> { - int count = 0; - for (String serverToTrack : serversToTrack) { - count += bungeeServerTracker.getCurrentServerInfo(serverToTrack).getOnlinePlayers(); - } - return String.valueOf(count); - }); - } else { - // Normal, single tracked server. - bungeeReplacers.put(matcher.group(), () -> { - return String.valueOf(bungeeServerTracker.getCurrentServerInfo(serverName).getOnlinePlayers()); - }); - } - } - - // BungeeCord max players pattern. - matcher = BUNGEE_MAX_PATTERN.matcher(text); - while (matcher.find()) { - if (bungeeReplacers == null) { - bungeeReplacers = new HashMap<>(); - } - - final String serverName = extractArgumentFromPlaceholder(matcher); - - // Add it to tracked servers. - bungeeReplacers.put(matcher.group(), () -> { - if (!Configuration.pingerEnabled) { - return PINGER_NOT_ENABLED_ERROR; - } - - return String.valueOf(bungeeServerTracker.getCurrentServerInfo(serverName).getMaxPlayers()); - }); - } - - // BungeeCord motd pattern. - matcher = BUNGEE_MOTD_PATTERN.matcher(text); - while (matcher.find()) { - if (bungeeReplacers == null) { - bungeeReplacers = new HashMap<>(); - } - - final String serverName = extractArgumentFromPlaceholder(matcher); - - // Add it to tracked servers. - bungeeReplacers.put(matcher.group(), () -> { - if (!Configuration.pingerEnabled) { - return PINGER_NOT_ENABLED_ERROR; - } - - return bungeeServerTracker.getCurrentServerInfo(serverName).getMotdLine1(); - }); - } - - // BungeeCord motd (line 2) pattern. - matcher = BUNGEE_MOTD_2_PATTERN.matcher(text); - while (matcher.find()) { - if (bungeeReplacers == null) { - bungeeReplacers = new HashMap<>(); - } - - final String serverName = extractArgumentFromPlaceholder(matcher); - - // Add it to tracked servers. - bungeeReplacers.put(matcher.group(), () -> { - if (!Configuration.pingerEnabled) { - return PINGER_NOT_ENABLED_ERROR; - } - - return bungeeServerTracker.getCurrentServerInfo(serverName).getMotdLine2(); - }); - } - - // BungeeCord status pattern. - matcher = BUNGEE_STATUS_PATTERN.matcher(text); - while (matcher.find()) { - if (bungeeReplacers == null) { - bungeeReplacers = new HashMap<>(); - } - - final String serverName = extractArgumentFromPlaceholder(matcher); - - // Add it to tracked servers. - bungeeReplacers.put(matcher.group(), () -> { - if (!Configuration.pingerEnabled) { - return PINGER_NOT_ENABLED_ERROR; - } - - ServerInfo serverInfo = bungeeServerTracker.getCurrentServerInfo(serverName); - if (serverInfo.isOnline()) { - return Configuration.pingerStatusOnline; - } else { - return Configuration.pingerStatusOffline; - } - }); - } - - - // Animation pattern. - matcher = ANIMATION_PATTERN.matcher(text); - while (matcher.find()) { - String fileName = extractArgumentFromPlaceholder(matcher); - Placeholder animation = animationRegistry.getAnimation(fileName); - - // If exists... - if (animation != null) { - if (animationsPlaceholders == null) { - animationsPlaceholders = new HashMap<>(); - } - - animationsPlaceholders.put(matcher.group(), animation); - - } else { - text = text.replace(matcher.group(), "[Animation not found: " + fileName + "]"); - updateText = true; - } - } - - if (Utils.isThereNonNull(normalPlaceholders, bungeeReplacers, worldsOnlinePlayersReplacers, animationsPlaceholders)) { - DynamicLineData lineData = new DynamicLineData(entity, text); - - if (normalPlaceholders != null) { - lineData.setPlaceholders(normalPlaceholders); - } - - if (bungeeReplacers != null) { - lineData.getReplacers().putAll(bungeeReplacers); - } - - if (worldsOnlinePlayersReplacers != null) { - lineData.getReplacers().putAll(worldsOnlinePlayersReplacers); - } - - if (animationsPlaceholders != null) { - lineData.getAnimations().putAll(animationsPlaceholders); - } - - // It could be already tracked! - if (!linesToUpdate.add(lineData)) { - linesToUpdate.remove(lineData); - linesToUpdate.add(lineData); - } - - updatePlaceholders(lineData); - - } else { - - // The name needs to be updated anyways. - if (updateText) { - entity.setCustomNameNMS(text); - } - } - } - - - private void updatePlaceholders(DynamicLineData lineData) { - String oldCustomName = lineData.getEntity().getCustomNameStringNMS(); - String newCustomName = lineData.getOriginalName(); - - if (!lineData.getPlaceholders().isEmpty()) { - for (Placeholder placeholder : lineData.getPlaceholders()) { - newCustomName = newCustomName.replace(placeholder.getTextPlaceholder(), Utils.sanitize(placeholder.getCurrentReplacement())); - } - } - - if (!lineData.getReplacers().isEmpty()) { - for (Entry entry : lineData.getReplacers().entrySet()) { - newCustomName = newCustomName.replace(entry.getKey(), Utils.sanitize(entry.getValue().update())); - } - } - - if (!lineData.getAnimations().isEmpty()) { - for (Entry entry : lineData.getAnimations().entrySet()) { - newCustomName = newCustomName.replace(entry.getKey(), Utils.sanitize(entry.getValue().getCurrentReplacement())); - } - } - - // Update only if needed, don't send useless packets. - if (!oldCustomName.equals(newCustomName)) { - lineData.getEntity().setCustomNameNMS(newCustomName); - } - } - - public PlaceholdersRegistry getRegistry() { - return placeholderRegistry; - } - - private static Pattern makePlaceholderWithArgsPattern(String prefix) { - return Pattern.compile("(\\{" + Pattern.quote(prefix) + ":)(.+?)(\\})"); - } - - private static String extractArgumentFromPlaceholder(Matcher matcher) { - return matcher.group(2).trim(); - } - -} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersRegistry.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersRegistry.java deleted file mode 100644 index f688ca12..00000000 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersRegistry.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) filoghost and contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package me.filoghost.holographicdisplays.placeholder; - -import me.filoghost.holographicdisplays.HolographicDisplays; -import me.filoghost.holographicdisplays.core.Utils; -import me.filoghost.holographicdisplays.disk.Configuration; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.plugin.Plugin; - -import java.time.Instant; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -public class PlaceholdersRegistry { - - private final PlaceholdersManager placeholderManager; - private final Set placeholders; - - public PlaceholdersRegistry(PlaceholdersManager placeholderManager) { - this.placeholderManager = placeholderManager; - this.placeholders = new HashSet<>(); - } - - - public boolean register(Placeholder placeholder) { - if (placeholders.contains(placeholder)) { - return false; - } - - placeholders.add(placeholder); - return true; - } - - public Set getTextPlaceholdersByPlugin(Plugin plugin) { - Set found = new HashSet<>(); - - for (Placeholder placeholder : placeholders) { - if (placeholder.getOwner().equals(plugin)) { - found.add(placeholder.getTextPlaceholder()); - } - } - - return found; - } - - public boolean unregister(Plugin plugin, String textPlaceholder) { - Iterator iter = placeholders.iterator(); - - while (iter.hasNext()) { - Placeholder placeholder = iter.next(); - - if (placeholder.getOwner().equals(plugin) && placeholder.getTextPlaceholder().equals(textPlaceholder)) { - iter.remove(); - - for (DynamicLineData data : placeholderManager.linesToUpdate) { - data.getPlaceholders().remove(placeholder); - } - - return true; - } - } - - return false; - } - - protected Set getPlaceholders() { - return placeholders; - } - - public void addDefaultPlaceholders() { - register(new Placeholder(HolographicDisplays.getInstance(), "{online}", 1.0, () -> { - return String.valueOf(Bukkit.getOnlinePlayers().size()); - })); - - register(new Placeholder(HolographicDisplays.getInstance(), "{max_players}", 10.0, () -> { - return String.valueOf(Bukkit.getMaxPlayers()); - })); - - register(new Placeholder(HolographicDisplays.getInstance(), "{motd}", 60.0, () -> { - return Bukkit.getMotd(); - })); - - register(new Placeholder(HolographicDisplays.getInstance(), "{time}", 0.9, () -> { - return Configuration.timeFormat.format(Instant.now()); - })); - - register(new Placeholder(HolographicDisplays.getInstance(), "&u", 0.2, new CyclicPlaceholderReplacer(Utils.toStringList( - ChatColor.RED, - ChatColor.GOLD, - ChatColor.YELLOW, - ChatColor.GREEN, - ChatColor.AQUA, - ChatColor.LIGHT_PURPLE - )))); - } - -} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersReplacementTracker.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersReplacementTracker.java new file mode 100644 index 00000000..161af5e7 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersReplacementTracker.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder; + +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; +import me.filoghost.holographicdisplays.placeholder.parsing.PlaceholderOccurrence; +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderExpansion; +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderRegistry; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; + +public class PlaceholdersReplacementTracker { + + private final PlaceholderRegistry registry; + private final Map currentReplacements; + + public PlaceholdersReplacementTracker(PlaceholderRegistry registry) { + this.registry = registry; + // Use WeakHashMap to ensure that when a PlaceholderOccurrence is no longer referenced in other objects + // the corresponding entry is removed from the map automatically. + this.currentReplacements = new WeakHashMap<>(); + } + + public void clearOutdatedSources() { + // Remove entries whose placeholder expansion sources are outdated + currentReplacements.entrySet().removeIf(entry -> { + PlaceholderOccurrence placeholderOccurrence = entry.getKey(); + PlaceholderExpansion currentSource = entry.getValue().source; + PlaceholderExpansion newSource = registry.findBestMatch(placeholderOccurrence); + + return !Objects.equals(currentSource, newSource); + }); + } + + @Nullable + public String getOrUpdateReplacement(PlaceholderOccurrence placeholderOccurrence, long currentTick) throws PlaceholderException { + ReplacementHolder replacementHolder = currentReplacements.get(placeholderOccurrence); + + if (replacementHolder == null) { + replacementHolder = createReplacementHolder(placeholderOccurrence); + currentReplacements.put(placeholderOccurrence, replacementHolder); + } + + try { + replacementHolder.refreshIfNeeded(placeholderOccurrence, currentTick); + } catch (Throwable t) { + throw new PlaceholderException(t, replacementHolder.source); + } + + return replacementHolder.currentReplacement; + } + + private ReplacementHolder createReplacementHolder(PlaceholderOccurrence placeholderOccurrence) throws PlaceholderException { + PlaceholderExpansion placeholderExpansion = registry.findBestMatch(placeholderOccurrence); + Placeholder placeholder; + + if (placeholderExpansion != null) { + PlaceholderFactory placeholderFactory = placeholderExpansion.getPlaceholderFactory(); + try { + placeholder = placeholderFactory.getPlaceholder(placeholderOccurrence.getArgument()); + } catch (Throwable t) { + throw new PlaceholderException(t, placeholderExpansion); + } + } else { + placeholder = null; + } + + return new ReplacementHolder(placeholderExpansion, placeholder); + } + + + private static class ReplacementHolder { + + final PlaceholderExpansion source; + final Placeholder placeholder; + + String currentReplacement; + long lastUpdateTick = -1; + + public ReplacementHolder(PlaceholderExpansion source, Placeholder placeholder) { + this.source = source; + this.placeholder = placeholder; + } + + public void refreshIfNeeded(PlaceholderOccurrence placeholderOccurrence, long currentTick) { + if (needsRefresh(currentTick)) { + currentReplacement = placeholder.getReplacement(placeholderOccurrence.getArgument()); + lastUpdateTick = currentTick; + } + } + + private boolean needsRefresh(long currentTick) { + if (placeholder == null || lastUpdateTick == currentTick) { + return false; // No need to refresh + } + + if (lastUpdateTick == -1) { + return true; // Force at least the initial refresh + } + + return currentTick - lastUpdateTick >= placeholder.getRefreshIntervalTicks(); + } + + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersUpdateTask.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersUpdateTask.java new file mode 100644 index 00000000..2e16f47a --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/PlaceholdersUpdateTask.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder; + +import me.filoghost.fcommons.logging.Log; +import me.filoghost.holographicdisplays.core.hologram.StandardTextLine; +import me.filoghost.holographicdisplays.core.nms.entity.NMSArmorStand; +import me.filoghost.holographicdisplays.placeholder.parsing.PlaceholderOccurrence; +import me.filoghost.holographicdisplays.placeholder.parsing.StringWithPlaceholders; +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderExpansion; +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.WeakHashMap; + +public class PlaceholdersUpdateTask implements Runnable { + + private final PlaceholdersReplacementTracker placeholdersReplacementTracker; + private final Map trackedLines; + private final Map lastErrorLogByPlaceholderExpansion; + + private long currentTick; + + public PlaceholdersUpdateTask(PlaceholdersReplacementTracker placeholdersReplacementTracker) { + this.placeholdersReplacementTracker = placeholdersReplacementTracker; + this.trackedLines = new LinkedHashMap<>(); + this.lastErrorLogByPlaceholderExpansion = new WeakHashMap<>(); + } + + @Override + public void run() { + Iterator iterator = trackedLines.values().iterator(); + while (iterator.hasNext()) { + TrackedLine trackedLine = iterator.next(); + + if (trackedLine.entity.isDeadNMS()) { + iterator.remove(); + continue; + } + + trackedLine.updateNameWithPlaceholders(); + } + + currentTick++; + } + + public void updateTracking(StandardTextLine line) { + TrackedLine trackedLine = createTrackedLineIfNeeded(line); + + if (trackedLine != null) { + trackedLines.put(line, trackedLine); + trackedLine.updateNameWithPlaceholders(); + } else { + TrackedLine untrackedLine = trackedLines.remove(line); + if (untrackedLine != null) { + untrackedLine.restoreOriginalName(); + } + } + } + + @Nullable + private TrackedLine createTrackedLineIfNeeded(StandardTextLine textLine) { + if (!textLine.isAllowPlaceholders()) { + return null; + } + + String text = textLine.getText(); + if (text == null || text.isEmpty()) { + return null; + } + + NMSArmorStand entity = textLine.getNMSArmorStand(); + if (entity == null) { + return null; + } + + StringWithPlaceholders textWithPlaceholders = new StringWithPlaceholders(text); + if (!textWithPlaceholders.containsPlaceholders()) { + return null; + } + + return new TrackedLine(textLine, entity, textWithPlaceholders); + } + + private String getCurrentReplacement(PlaceholderOccurrence placeholderOccurrence) { + try { + return placeholdersReplacementTracker.getOrUpdateReplacement(placeholderOccurrence, currentTick); + } catch (PlaceholderException e) { + handleException(e); + return "[Error]"; + } + } + + private void handleException(PlaceholderException exception) { + PlaceholderExpansion placeholderExpansion = exception.getPlaceholderExpansion(); + + Long lastErrorLog = lastErrorLogByPlaceholderExpansion.get(placeholderExpansion); + if (lastErrorLog != null && currentTick - lastErrorLog < 20) { + return; // Avoid spamming the console too frequently + } + lastErrorLogByPlaceholderExpansion.put(placeholderExpansion, currentTick); + + Log.warning("The placeholder \"" + placeholderExpansion.getIdentifier() + "\"" + + " registered by the plugin " + placeholderExpansion.getPluginName() + + " generated an exception." + + " Please contact the author of " + placeholderExpansion.getPluginName(), + exception.getCause()); + } + + + private class TrackedLine { + + final StandardTextLine textLine; + final NMSArmorStand entity; + final StringWithPlaceholders nameWithPlaceholders; + + TrackedLine(StandardTextLine textLine, NMSArmorStand entity, StringWithPlaceholders nameWithPlaceholders) { + this.textLine = textLine; + this.entity = entity; + this.nameWithPlaceholders = nameWithPlaceholders; + } + + void updateNameWithPlaceholders() { + String newName = nameWithPlaceholders.replacePlaceholders(PlaceholdersUpdateTask.this::getCurrentReplacement); + entity.setCustomNameNMS(newName); + } + + void restoreOriginalName() { + if (!entity.isDeadNMS()) { + entity.setCustomNameNMS(textLine.getText() != null ? textLine.getText() : ""); + } + } + + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/CyclicPlaceholderReplacer.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/AnimationPlaceholder.java similarity index 51% rename from plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/CyclicPlaceholderReplacer.java rename to plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/AnimationPlaceholder.java index 98f79bcb..ad3764cf 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/CyclicPlaceholderReplacer.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/AnimationPlaceholder.java @@ -3,31 +3,37 @@ * * SPDX-License-Identifier: GPL-3.0-or-later */ -package me.filoghost.holographicdisplays.placeholder; +package me.filoghost.holographicdisplays.placeholder.internal; -import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; import java.util.List; -public class CyclicPlaceholderReplacer implements PlaceholderReplacer { +public class AnimationPlaceholder implements Placeholder { + private final int refreshIntervalTicks; private final List frames; private int currentIndex; - public CyclicPlaceholderReplacer(List frames) { + public AnimationPlaceholder(int refreshIntervalTicks, List frames) { this.frames = frames; - currentIndex = 0; + this.refreshIntervalTicks = refreshIntervalTicks; } @Override - public String update() { + public int getRefreshIntervalTicks() { + return refreshIntervalTicks; + } + + @Override + public String getReplacement(String argument) { String result = frames.get(currentIndex); - + currentIndex++; if (currentIndex >= frames.size()) { currentIndex = 0; } - + return result; } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/AnimationsRegistry.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/AnimationRegistry.java similarity index 79% rename from plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/AnimationsRegistry.java rename to plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/AnimationRegistry.java index b1f2b148..0267f772 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/AnimationsRegistry.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/AnimationRegistry.java @@ -3,14 +3,15 @@ * * SPDX-License-Identifier: GPL-3.0-or-later */ -package me.filoghost.holographicdisplays.placeholder; +package me.filoghost.holographicdisplays.placeholder.internal; import me.filoghost.fcommons.config.exception.ConfigSaveException; import me.filoghost.fcommons.logging.ErrorCollector; -import me.filoghost.holographicdisplays.HolographicDisplays; import me.filoghost.holographicdisplays.core.DebugLogger; import me.filoghost.holographicdisplays.disk.ConfigManager; import me.filoghost.holographicdisplays.disk.StringConverter; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; import java.io.IOException; import java.nio.file.Files; @@ -20,8 +21,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; -public class AnimationsRegistry { - +public class AnimationRegistry implements PlaceholderFactory { + private static final String SPEED_PREFIX = "speed:"; private final Map animationsByFilename = new HashMap<>(); @@ -80,21 +81,23 @@ public class AnimationsRegistry { lines.set(i, StringConverter.toReadableFormat(lines.get(i))); } - animationsByFilename.put(fileName, new Placeholder(HolographicDisplays.getInstance(), fileName, speed, new CyclicPlaceholderReplacer(lines))); + int refreshIntervalTicks = Math.min((int) (speed * 20.0), 1); + animationsByFilename.put(fileName, new AnimationPlaceholder(refreshIntervalTicks, lines)); DebugLogger.info("Successfully loaded animation \"" + fileName + "\", speed = " + speed + "."); } catch (Exception e) { errorCollector.add(e, "couldn't load the animation file \"" + fileName + "\""); } } - - - public Map getAnimationsByFilename() { - return animationsByFilename; - } - - public Placeholder getAnimation(String name) { - return animationsByFilename.get(name); - } + @Override + public Placeholder getPlaceholder(String fileNameArgument) { + Placeholder placeholder = animationsByFilename.get(fileNameArgument); + if (placeholder != null) { + return placeholder; + } else { + return new StaticPlaceholder("[Animation not found: " + fileNameArgument + "]"); + } + } + } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/DefaultPlaceholders.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/DefaultPlaceholders.java new file mode 100644 index 00000000..2a98d5de --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/DefaultPlaceholders.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.internal; + +import me.filoghost.holographicdisplays.HolographicDisplays; +import me.filoghost.holographicdisplays.bridge.bungeecord.BungeeServerTracker; +import me.filoghost.holographicdisplays.bridge.bungeecord.ServerInfo; +import me.filoghost.holographicdisplays.core.Utils; +import me.filoghost.holographicdisplays.disk.Configuration; +import me.filoghost.holographicdisplays.placeholder.registry.PlaceholderRegistry; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; + +import java.time.Instant; + +public class DefaultPlaceholders { + + private static final String PINGER_NOT_ENABLED_ERROR = "[Please enable pinger]"; + public static final String NO_SERVER_SPECIFIED_ERROR = "[No server specified]"; + + public static void resetAndRegister(PlaceholderRegistry placeholderRegistry, AnimationRegistry animationRegistry, BungeeServerTracker bungeeServerTracker) { + HolographicDisplays plugin = HolographicDisplays.getInstance(); + placeholderRegistry.unregisterAll(plugin); + + // TODO restore "&u" + placeholderRegistry.register(plugin, "rainbow", new AnimationPlaceholder(4, Utils.toStringList( + ChatColor.RED, + ChatColor.GOLD, + ChatColor.YELLOW, + ChatColor.GREEN, + ChatColor.AQUA, + ChatColor.LIGHT_PURPLE + ))); + + placeholderRegistry.registerReplacer(plugin, "time", 10, (argument) -> { + return Configuration.timeFormat.format(Instant.now()); + }); + + placeholderRegistry.registerFactory(plugin, "animation", animationRegistry); + + placeholderRegistry.registerFactory(plugin, "world", new WorldPlayersPlaceholderFactory()); + + placeholderRegistry.registerFactory(plugin, "online", new OnlinePlayersPlaceholderFactory(bungeeServerTracker)); + + placeholderRegistry.registerReplacer(plugin, "max_players", 20, (serverName) -> { + if (serverName == null) { + // No argument specified, return max players of this server + return String.valueOf(Bukkit.getMaxPlayers()); + } + + if (!Configuration.pingerEnabled) { + return PINGER_NOT_ENABLED_ERROR; + } + + return String.valueOf(bungeeServerTracker.getCurrentServerInfo(serverName).getMaxPlayers()); + }); + + placeholderRegistry.registerReplacer(plugin, "status", 20, (serverName) -> { + if (serverName == null) { + return NO_SERVER_SPECIFIED_ERROR; + } + + if (!Configuration.pingerEnabled) { + return PINGER_NOT_ENABLED_ERROR; + } + + ServerInfo serverInfo = bungeeServerTracker.getCurrentServerInfo(serverName); + if (serverInfo.isOnline()) { + return Configuration.pingerStatusOnline; + } else { + return Configuration.pingerStatusOffline; + } + }); + + placeholderRegistry.registerReplacer(plugin, "motd", 20, (serverName) -> { + if (serverName == null) { + return NO_SERVER_SPECIFIED_ERROR; + } + + if (!Configuration.pingerEnabled) { + return PINGER_NOT_ENABLED_ERROR; + } + + return bungeeServerTracker.getCurrentServerInfo(serverName).getMotdLine1(); + }); + + placeholderRegistry.registerReplacer(plugin, "motd2", 20, (serverName) -> { + if (serverName == null) { + return NO_SERVER_SPECIFIED_ERROR; + } + + if (!Configuration.pingerEnabled) { + return PINGER_NOT_ENABLED_ERROR; + } + + return bungeeServerTracker.getCurrentServerInfo(serverName).getMotdLine2(); + }); + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/OnlinePlayersPlaceholderFactory.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/OnlinePlayersPlaceholderFactory.java new file mode 100644 index 00000000..79c3c76b --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/OnlinePlayersPlaceholderFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.internal; + +import me.filoghost.fcommons.Strings; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; +import me.filoghost.holographicdisplays.bridge.bungeecord.BungeeServerTracker; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.Nullable; + +public class OnlinePlayersPlaceholderFactory implements PlaceholderFactory { + + private final BungeeServerTracker bungeeServerTracker; + + public OnlinePlayersPlaceholderFactory(BungeeServerTracker bungeeServerTracker) { + this.bungeeServerTracker = bungeeServerTracker; + } + + @Override + public Placeholder getPlaceholder(@Nullable String argument) { + if (argument == null) { + // No argument specified, return online players in this server + return new LocalOnlinePlayersPlaceholder(); + } + + String[] serverNames = Strings.splitAndTrim(argument, ","); + return new BungeeOnlinePlayersPlaceholder(serverNames, bungeeServerTracker); + } + + + private static class LocalOnlinePlayersPlaceholder implements Placeholder { + + @Override + public int getRefreshIntervalTicks() { + return 20; + } + + @Override + public String getReplacement(@Nullable String argument) { + return String.valueOf(Bukkit.getOnlinePlayers().size()); + } + + } + + + private static class BungeeOnlinePlayersPlaceholder implements Placeholder { + + private final String[] serverNames; + private final BungeeServerTracker bungeeServerTracker; + + public BungeeOnlinePlayersPlaceholder(String[] serverNames, BungeeServerTracker bungeeServerTracker) { + this.serverNames = serverNames; + this.bungeeServerTracker = bungeeServerTracker; + } + + @Override + public int getRefreshIntervalTicks() { + return 20; + } + + @Override + public String getReplacement(@Nullable String argument) { + int count = 0; + for (String serverName : serverNames) { + count += bungeeServerTracker.getCurrentServerInfo(serverName).getOnlinePlayers(); + } + + return String.valueOf(count); + } + + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/StaticPlaceholder.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/StaticPlaceholder.java new file mode 100644 index 00000000..b219d3f1 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/StaticPlaceholder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.internal; + +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; + +public class StaticPlaceholder implements Placeholder { + + private final String text; + + public StaticPlaceholder(String text) { + this.text = text; + } + + @Override + public int getRefreshIntervalTicks() { + return Integer.MAX_VALUE; + } + + @Override + public String getReplacement(String argument) { + return text; + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/WorldPlayersPlaceholderFactory.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/WorldPlayersPlaceholderFactory.java new file mode 100644 index 00000000..25131940 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/internal/WorldPlayersPlaceholderFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.internal; + +import me.filoghost.fcommons.Strings; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +public class WorldPlayersPlaceholderFactory implements PlaceholderFactory { + + @Override + public Placeholder getPlaceholder(@Nullable String argument) { + if (argument == null) { + return new StaticPlaceholder("[No world specified]"); + } + + String[] worldNames = Strings.splitAndTrim(argument, ","); + return new WorldPlayersPlaceholder(worldNames); + } + + + private static class WorldPlayersPlaceholder implements Placeholder { + + private final String[] worldNames; + + public WorldPlayersPlaceholder(String[] worldNames) { + this.worldNames = worldNames; + } + + @Override + public int getRefreshIntervalTicks() { + return 20; + } + + @Override + public String getReplacement(@Nullable String argument) { + int count = 0; + + for (String worldName : worldNames) { + World world = Bukkit.getWorld(worldName); + if (world == null) { + return "[World \"" + worldName + "\" not found]"; + } + + for (Player player : world.getPlayers()) { + if (!player.hasMetadata("NPC")) { + count++; + } + } + } + + return String.valueOf(count); + } + + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderIdentifier.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderIdentifier.java new file mode 100644 index 00000000..58985d3d --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderIdentifier.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.parsing; + +import me.filoghost.fcommons.collection.CaseInsensitiveString; + +public class PlaceholderIdentifier { + + private final CaseInsensitiveString identifier; + + public PlaceholderIdentifier(String identifier) { + this.identifier = new CaseInsensitiveString(identifier); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + PlaceholderIdentifier other = (PlaceholderIdentifier) obj; + return this.identifier.equals(other.identifier); + } + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + @Override + public String toString() { + return identifier.toString(); + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderOccurrence.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderOccurrence.java new file mode 100644 index 00000000..157fe422 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PlaceholderOccurrence.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.parsing; + +import me.filoghost.fcommons.Strings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public class PlaceholderOccurrence { + + private final PluginName pluginName; + private final PlaceholderIdentifier identifier; + private final String argument; + + private final int hashCode; // Cached for performance reasons + + private PlaceholderOccurrence(PluginName pluginName, PlaceholderIdentifier identifier, String argument) { + this.pluginName = pluginName; + this.identifier = identifier; + this.argument = argument; + this.hashCode = Objects.hash(pluginName, identifier, argument); + } + + @Nullable + public PluginName getPluginName() { + return pluginName; + } + + @NotNull + public PlaceholderIdentifier getIdentifier() { + return identifier; + } + + @Nullable + public String getArgument() { + return argument; + } + + /* + * Valid placeholder formats: + * {identifier} + * {identifier: argument} + * {pluginName/identifier} + * {pluginName/identifier: argument} + * + * identifier is required, pluginName and argument are optional + */ + public static PlaceholderOccurrence parse(String placeholderContent) { + PluginName pluginName = null; + String argument = null; + String identifierString; + + if (placeholderContent.contains(":")) { + String[] parts = Strings.splitAndTrim(placeholderContent, ":", 2); + identifierString = parts[0]; + argument = parts[1]; + } else { + identifierString = placeholderContent; + } + + if (identifierString.contains("/")) { + String[] parts = Strings.splitAndTrim(identifierString, "/", 2); + pluginName = new PluginName(parts[0]); + identifierString = parts[1]; + } + + PlaceholderIdentifier identifier = new PlaceholderIdentifier(identifierString); + return new PlaceholderOccurrence(pluginName, identifier, argument); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + + PlaceholderOccurrence other = (PlaceholderOccurrence) obj; + return this.hashCode == other.hashCode + && Objects.equals(this.pluginName, other.pluginName) + && Objects.equals(this.identifier, other.identifier) + && Objects.equals(this.argument, other.argument); + } + + @Override + public int hashCode() { + return hashCode; + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PluginName.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PluginName.java new file mode 100644 index 00000000..890e04a3 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/PluginName.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.parsing; + +import me.filoghost.fcommons.collection.CaseInsensitiveString; +import org.bukkit.plugin.Plugin; + +public class PluginName { + + private final CaseInsensitiveString pluginName; + + public PluginName(Plugin plugin) { + this(plugin.getName()); + } + + public PluginName(String pluginName) { + this.pluginName = new CaseInsensitiveString(pluginName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + PluginName other = (PluginName) obj; + return this.pluginName.equals(other.pluginName); + } + + @Override + public int hashCode() { + return pluginName.hashCode(); + } + + @Override + public String toString() { + return pluginName.toString(); + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholders.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholders.java new file mode 100644 index 00000000..37c1570b --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholders.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.parsing; + +import me.filoghost.fcommons.collection.CollectionUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class StringWithPlaceholders { + + private static final char PLACEHOLDER_END_CHAR = '}'; + private static final char PLACEHOLDER_START_CHAR = '{'; + + private final String string; + private final List placeholderMatches; + + public StringWithPlaceholders(String string) { + this.string = string; + this.placeholderMatches = findPlaceholders(string); + } + + protected List getPlaceholders() { + return CollectionUtils.transform(placeholderMatches, match -> match.content); + } + + public boolean containsPlaceholders() { + return placeholderMatches != null && !placeholderMatches.isEmpty(); + } + + public String replacePlaceholders(Function replaceFunction) { + if (!containsPlaceholders()) { + return string; + } + + StringBuilder output = new StringBuilder(); + int lastAppendIndex = 0; + + for (PlaceholderMatch match : placeholderMatches) { + // Append leading text (if any) + if (lastAppendIndex != match.startIndex) { + output.append(string, lastAppendIndex, match.startIndex); + } + + String replacement = replaceFunction.apply(match.content); + if (replacement != null) { + // Append placeholder replacement + output.append(replacement); + lastAppendIndex = match.endIndex; + } else { + // If no replacement is provided, do not replace the occurrence + output.append(match.unparsedString); + } + + } + + // Append trailing text (if any) + if (lastAppendIndex < string.length()) { + output.append(string, lastAppendIndex, string.length()); + } + + return output.toString(); + } + + @Nullable + private List findPlaceholders(String input) { + int currentIndex = 0; + int placeholderStartIndex = -1; + List matches = null; + + while (currentIndex < input.length()) { + char currentChar = input.charAt(currentIndex); + + if (placeholderStartIndex >= 0) { + if (currentChar == PLACEHOLDER_END_CHAR) { + int endIndex = currentIndex + 1; + + // The unparsed string includes the opening and closing tags (e.g.: "{online: lobby}") + String unparsedString = input.substring(placeholderStartIndex, endIndex); + + // The content string does NOT include the opening and closing tags (e.g.: "online: lobby") + String contentString = unparsedString.substring(1, unparsedString.length() - 1); + PlaceholderOccurrence content = PlaceholderOccurrence.parse(contentString); + + if (matches == null) { + matches = new ArrayList<>(); + } + matches.add(new PlaceholderMatch( + content, + unparsedString, + placeholderStartIndex, + endIndex)); + + placeholderStartIndex = -1; + + } else if (currentChar == PLACEHOLDER_START_CHAR) { + // Nested placeholder, ignore outer placeholder and update start index + placeholderStartIndex = currentIndex; + } + } else { + if (currentChar == PLACEHOLDER_START_CHAR) { + placeholderStartIndex = currentIndex; + } + } + + currentIndex++; + } + + return matches; + } + + + private static class PlaceholderMatch { + + private final PlaceholderOccurrence content; + private final String unparsedString; + private final int startIndex; + private final int endIndex; + + public PlaceholderMatch(PlaceholderOccurrence parsedContent, String unparsedString, int startIndex, int endIndex) { + this.content = parsedContent; + this.unparsedString = unparsedString; + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderExpansion.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderExpansion.java new file mode 100644 index 00000000..3f38df68 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderExpansion.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.registry; + +import me.filoghost.fcommons.Preconditions; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; +import me.filoghost.holographicdisplays.placeholder.parsing.PlaceholderIdentifier; +import me.filoghost.holographicdisplays.placeholder.parsing.PluginName; +import org.bukkit.plugin.Plugin; + +public class PlaceholderExpansion { + + private final PluginName pluginName; + private final PlaceholderIdentifier identifier; + private final PlaceholderFactory placeholderFactory; + + public PlaceholderExpansion(Plugin plugin, String identifier, PlaceholderFactory placeholderFactory) { + Preconditions.notNull(plugin, "plugin"); + Preconditions.notEmpty(identifier, "identifier"); + for (char c : identifier.toCharArray()) { + Preconditions.checkArgument(isValidIdentifierCharacter(c), "identifier contains invalid character '" + c + "'"); + } + Preconditions.notNull(placeholderFactory, "placeholderFactory"); + + this.pluginName = new PluginName(plugin); + this.identifier = new PlaceholderIdentifier(identifier); + this.placeholderFactory = placeholderFactory; + } + + private boolean isValidIdentifierCharacter(char c) { + return ('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || ('0' <= c && c <= '9') + || c == '-' + || c == '_'; + } + + public PluginName getPluginName() { + return pluginName; + } + + public PlaceholderIdentifier getIdentifier() { + return identifier; + } + + public PlaceholderFactory getPlaceholderFactory() { + return placeholderFactory; + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderRegistry.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderRegistry.java new file mode 100644 index 00000000..dab2e178 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/registry/PlaceholderRegistry.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.registry; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Table; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; +import me.filoghost.holographicdisplays.placeholder.parsing.PlaceholderIdentifier; +import me.filoghost.holographicdisplays.placeholder.parsing.PlaceholderOccurrence; +import me.filoghost.holographicdisplays.placeholder.parsing.PluginName; +import me.filoghost.holographicdisplays.placeholder.util.SimplePlaceholder; +import me.filoghost.holographicdisplays.placeholder.util.SingletonPlaceholderFactory; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class PlaceholderRegistry { + + private final Table placeholderExpansions; + private Runnable changeListener; + + public PlaceholderRegistry() { + this.placeholderExpansions = HashBasedTable.create(); + } + + public void setChangeListener(Runnable changeListener) { + this.changeListener = changeListener; + } + + public void registerReplacer(Plugin plugin, String identifier, int refreshIntervalTicks, PlaceholderReplacer placeholderReplacer) { + register(plugin, identifier, new SimplePlaceholder(refreshIntervalTicks, placeholderReplacer)); + } + + public void register(Plugin plugin, String identifier, Placeholder placeholder) { + registerFactory(plugin, identifier, new SingletonPlaceholderFactory(placeholder)); + } + + public void registerFactory(Plugin plugin, String identifier, PlaceholderFactory placeholderFactory) { + PlaceholderExpansion expansion = new PlaceholderExpansion(plugin, identifier, placeholderFactory); + placeholderExpansions.put(expansion.getIdentifier(), expansion.getPluginName(), expansion); + + changeListener.run(); + } + + public void unregisterAll(Plugin plugin) { + placeholderExpansions.column(new PluginName(plugin)).clear(); + + changeListener.run(); + } + + public void unregister(Plugin plugin, String identifier) { + placeholderExpansions.remove(new PlaceholderIdentifier(identifier), new PluginName(plugin)); + + changeListener.run(); + } + + @Nullable + public PlaceholderExpansion findBestMatch(PlaceholderOccurrence textOccurrence) { + PluginName pluginName = textOccurrence.getPluginName(); + PlaceholderIdentifier identifier = textOccurrence.getIdentifier(); + + if (pluginName != null) { + // Find exact entry if plugin name is specified + return placeholderExpansions.get(identifier, pluginName); + + } else { + // Otherwise find any match with the given identifier + return Iterables.getFirst(placeholderExpansions.row(identifier).values(), null); + } + } + + public List getRegisteredIdentifiers(Plugin plugin) { + PluginName pluginName = new PluginName(plugin); + List identifiers = new ArrayList<>(); + + for (PlaceholderExpansion expansion : placeholderExpansions.column(pluginName).values()) { + identifiers.add(expansion.getIdentifier().toString()); + } + + return identifiers; + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SimplePlaceholder.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SimplePlaceholder.java new file mode 100644 index 00000000..1a9fabc8 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SimplePlaceholder.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.util; + +import me.filoghost.fcommons.Preconditions; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderReplacer; + +public class SimplePlaceholder implements Placeholder { + + private final int refreshIntervalTicks; + private final PlaceholderReplacer placeholderReplacer; + + public SimplePlaceholder(int refreshIntervalTicks, PlaceholderReplacer placeholderReplacer) { + Preconditions.checkArgument(refreshIntervalTicks >= 0, "refreshIntervalTicks cannot be negative"); + Preconditions.notNull(placeholderReplacer, "placeholderReplacer"); + this.refreshIntervalTicks = refreshIntervalTicks; + this.placeholderReplacer = placeholderReplacer; + } + + @Override + public int getRefreshIntervalTicks() { + return refreshIntervalTicks; + } + + @Override + public String getReplacement(String argument) { + return placeholderReplacer.getReplacement(argument); + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SingletonPlaceholderFactory.java b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SingletonPlaceholderFactory.java new file mode 100644 index 00000000..12212223 --- /dev/null +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/placeholder/util/SingletonPlaceholderFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.util; + +import me.filoghost.fcommons.Preconditions; +import me.filoghost.holographicdisplays.api.placeholder.Placeholder; +import me.filoghost.holographicdisplays.api.placeholder.PlaceholderFactory; + +public class SingletonPlaceholderFactory implements PlaceholderFactory { + + private final Placeholder placeholder; + + public SingletonPlaceholderFactory(Placeholder placeholder) { + Preconditions.notNull(placeholder, "placeholder"); + this.placeholder = placeholder; + } + + @Override + public Placeholder getPlaceholder(String argument) { + return placeholder; + } + +} diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/task/WorldPlayerCounterTask.java b/plugin/src/main/java/me/filoghost/holographicdisplays/task/WorldPlayerCounterTask.java deleted file mode 100644 index 9d0f99cd..00000000 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/task/WorldPlayerCounterTask.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) filoghost and contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package me.filoghost.holographicdisplays.task; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class WorldPlayerCounterTask implements Runnable { - - private static final Map worlds = new HashMap<>(); - - @Override - public void run() { - worlds.clear(); - - for (World world : Bukkit.getWorlds()) { - List players = world.getPlayers(); - int count = 0; - - for (Player player : players) { - if (!player.hasMetadata("NPC")) { - count++; - } - } - worlds.put(world.getName(), count); - } - } - - public static String getCount(String[] worldsNames) { - int total = 0; - for (String worldName : worldsNames) { - Integer count = worlds.get(worldName); - if (count == null) { - return "[World \"" + worldName + "\" not found]"; - } - - total += count; - } - - return String.valueOf(total); - } - - public static String getCount(String worldName) { - Integer count = worlds.get(worldName); - return count != null ? count.toString() : "[World \"" + worldName + "\" not found]"; - } -} diff --git a/plugin/src/test/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholdersTest.java b/plugin/src/test/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholdersTest.java new file mode 100644 index 00000000..d4029f51 --- /dev/null +++ b/plugin/src/test/java/me/filoghost/holographicdisplays/placeholder/parsing/StringWithPlaceholdersTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.placeholder.parsing; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.*; + +class StringWithPlaceholdersTest { + + @ParameterizedTest(name = "[{index}] {0} -> {1}") + @MethodSource("replacementsTestArguments") + void replacements(String input, String expectedOutput) { + StringWithPlaceholders s = new StringWithPlaceholders(input); + assertThat(s.replacePlaceholders(occurrence -> "#")).isEqualTo(expectedOutput); + } + + static Stream replacementsTestArguments() { + return Stream.of( + Arguments.of("{}", "#"), // Empty placeholder should still be detected + Arguments.of("{p}{p}", "##"), + Arguments.of("{p} {p} {p}", "# # #"), + Arguments.of("{{p}}", "{#}"), // Only the innermost placeholder should be replaced + Arguments.of("{p abc", "{p abc"), // Placeholder without closing tag + Arguments.of("abc p}", "abc p}") // Placeholder without opening tag + ); + } + + @ParameterizedTest(name = "[{index}] {0} -> {1}, {2}, {3}") + @MethodSource("parsingTestArguments") + void parsing(String input, String expectedPluginName, String expectedIdentifier, String expectedArgument) { + StringWithPlaceholders s = new StringWithPlaceholders(input); + + List placeholders = s.getPlaceholders(); + assertThat(placeholders).hasSize(1); + + PlaceholderOccurrence placeholder = placeholders.get(0); + + if (expectedPluginName != null) { + assertThat(placeholder.getPluginName()).hasToString(expectedPluginName); + } else { + assertThat(placeholder.getPluginName()).isNull(); + } + assertThat(placeholder.getIdentifier()).hasToString(expectedIdentifier); + assertThat(placeholder.getArgument()).isEqualTo(expectedArgument); + } + + static Stream parsingTestArguments() { + return Stream.of( + Arguments.of("{}", null, "", null), + Arguments.of("{identifier}", null, "identifier", null), + Arguments.of("{plugin/identifier}", "plugin", "identifier", null), + Arguments.of("{plugin/identifier: argument}", "plugin", "identifier", "argument"), + Arguments.of("{identifier: argument}", null, "identifier", "argument"), + Arguments.of("{plugin/identifier/nestedIdentifier}", "plugin", "identifier/nestedIdentifier", null), + Arguments.of("{identifier: argument: nestedArgument}", null, "identifier", "argument: nestedArgument") + ); + } + +}