Partial placeholder refactoring

This commit is contained in:
filoghost 2021-04-24 23:37:54 +02:00
parent 19f3a87ba3
commit 750e74360a
46 changed files with 1340 additions and 849 deletions

View File

@ -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 <b>refreshRate</b> 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<String> 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);
}

View File

@ -33,11 +33,11 @@ public abstract class BackendAPI {
public abstract Collection<Hologram> 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<String> 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);

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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).
* <p>
* For example, the argument of {test} is null, the argument of {test: hello world} is the string "hello world".
* <p>
* <b>Warning</b>: this method should be performance efficient, as it may be invoked often.
*
* @return the placeholder replacement
*/
String update();
String getReplacement(@Nullable String argument);
}

View File

@ -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());

View File

@ -14,6 +14,8 @@ public interface StandardTextLine extends StandardTouchableLine {
String getText();
boolean isAllowPlaceholders();
Collection<RelativePlaceholder> getRelativePlaceholders();
NMSArmorStand getNMSArmorStand();

View File

@ -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<RelativePlaceholder> 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<RelativePlaceholder> 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);
}
}

View File

@ -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);
}

View File

@ -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<String> 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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -101,8 +101,11 @@ class PacketListener extends PacketAdapter {
}
StandardTextLine textLine = (StandardTextLine) hologramLine;
if (!textLine.isAllowPlaceholders()) {
return;
}
Collection<RelativePlaceholder> relativePlaceholders = textLine.getRelativePlaceholders();
if (relativePlaceholders == null || relativePlaceholders.isEmpty()) {
return;
}

View File

@ -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<APIHologramLine> 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;

View File

@ -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<APIHologram> {
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;
}

View File

@ -23,7 +23,7 @@ public class APITextLine extends BaseTextLine implements TextLine, APIHologramLi
}
@Override
protected boolean isAllowPlaceholders() {
public boolean isAllowPlaceholders() {
return parent.isAllowPlaceholders();
}

View File

@ -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<T extends StandardHologramLine> extends BaseHologramComponent implements StandardHologram {
private final NMSManager nmsManager;
private final PlaceholdersManager placeholderManager;
private final PlaceholderManager placeholderManager;
private final List<T> lines;
private final List<T> 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<T extends StandardHologramLine> extends BaseH
return nmsManager;
}
protected final PlaceholdersManager getPlaceholderManager() {
protected final PlaceholderManager getPlaceholderManager() {
return placeholderManager;
}

View File

@ -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();
}

View File

@ -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<RelativePlaceholder> relativePlaceholders;
private final List<RelativePlaceholder> 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<RelativePlaceholder> getRelativePlaceholders() {
if (!isAllowPlaceholders()) {
return null;
}
return relativePlaceholders;
}

View File

@ -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<InternalHologramLine> {
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;
}

View File

@ -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<InternalHologram> {
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;
}

View File

@ -17,7 +17,7 @@ public class InternalTextLine extends BaseTextLine implements InternalHologramLi
}
@Override
protected boolean isAllowPlaceholders() {
public boolean isAllowPlaceholders() {
return true;
}

View File

@ -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<Placeholder> placeholders;
private final Map<String, Placeholder> animations;
private final Map<String, PlaceholderReplacer> 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<Placeholder> placeholders) {
this.placeholders = placeholders;
}
public Set<Placeholder> getPlaceholders() {
return placeholders;
}
public Map<String, PlaceholderReplacer> getReplacers() {
return replacers;
}
public Map<String, Placeholder> 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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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<DynamicLineData> 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<DynamicLineData> 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<DynamicLineData> 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<Placeholder> normalPlaceholders = null;
Map<String, PlaceholderReplacer> bungeeReplacers = null;
Map<String, PlaceholderReplacer> worldsOnlinePlayersReplacers = null;
Map<String, Placeholder> 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<String, PlaceholderReplacer> entry : lineData.getReplacers().entrySet()) {
newCustomName = newCustomName.replace(entry.getKey(), Utils.sanitize(entry.getValue().update()));
}
}
if (!lineData.getAnimations().isEmpty()) {
for (Entry<String, Placeholder> 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();
}
}

View File

@ -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<Placeholder> 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<String> getTextPlaceholdersByPlugin(Plugin plugin) {
Set<String> 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<Placeholder> 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<Placeholder> 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
))));
}
}

View File

@ -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<PlaceholderOccurrence, ReplacementHolder> 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();
}
}
}

View File

@ -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<StandardTextLine, TrackedLine> trackedLines;
private final Map<PlaceholderExpansion, Long> lastErrorLogByPlaceholderExpansion;
private long currentTick;
public PlaceholdersUpdateTask(PlaceholdersReplacementTracker placeholdersReplacementTracker) {
this.placeholdersReplacementTracker = placeholdersReplacementTracker;
this.trackedLines = new LinkedHashMap<>();
this.lastErrorLogByPlaceholderExpansion = new WeakHashMap<>();
}
@Override
public void run() {
Iterator<TrackedLine> 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() : "");
}
}
}
}

View File

@ -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<String> frames;
private int currentIndex;
public CyclicPlaceholderReplacer(List<String> frames) {
public AnimationPlaceholder(int refreshIntervalTicks, List<String> 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;
}

View File

@ -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<String, Placeholder> 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<String, Placeholder> 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 + "]");
}
}
}

View File

@ -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();
});
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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<PlaceholderMatch> placeholderMatches;
public StringWithPlaceholders(String string) {
this.string = string;
this.placeholderMatches = findPlaceholders(string);
}
protected List<PlaceholderOccurrence> getPlaceholders() {
return CollectionUtils.transform(placeholderMatches, match -> match.content);
}
public boolean containsPlaceholders() {
return placeholderMatches != null && !placeholderMatches.isEmpty();
}
public String replacePlaceholders(Function<PlaceholderOccurrence, String> 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<PlaceholderMatch> findPlaceholders(String input) {
int currentIndex = 0;
int placeholderStartIndex = -1;
List<PlaceholderMatch> 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;
}
}
}

View File

@ -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;
}
}

View File

@ -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<PlaceholderIdentifier, PluginName, PlaceholderExpansion> 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<String> getRegisteredIdentifiers(Plugin plugin) {
PluginName pluginName = new PluginName(plugin);
List<String> identifiers = new ArrayList<>();
for (PlaceholderExpansion expansion : placeholderExpansions.column(pluginName).values()) {
identifiers.add(expansion.getIdentifier().toString());
}
return identifiers;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<String, Integer> worlds = new HashMap<>();
@Override
public void run() {
worlds.clear();
for (World world : Bukkit.getWorlds()) {
List<Player> 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]";
}
}

View File

@ -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<Arguments> 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<PlaceholderOccurrence> 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<Arguments> 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")
);
}
}