diff --git a/gradle.properties b/gradle.properties index 2fe978a..b2649ee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Project metadata group=xyz.earthcow.networkjoinmessages -version=3.1.0 +version=3.2.0 description=A plugin handling join, leave and swap messages for proxy servers. # Plugin.yml metadata diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/bungee/BungeeMain.java b/src/main/java/xyz/earthcow/networkjoinmessages/bungee/BungeeMain.java index e9975ea..af4d9db 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/bungee/BungeeMain.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/bungee/BungeeMain.java @@ -180,8 +180,13 @@ public PremiumVanish getVanishAPI() { } @Override - public void runTaskLater(Runnable task, int timeInSecondsLater) { - getProxy().getScheduler().schedule(this, task, timeInSecondsLater, TimeUnit.SECONDS); + public void cancelTask(int taskId) { + getProxy().getScheduler().cancel(taskId); + } + + @Override + public int runTaskRepeatedly(Runnable task, int timeInSecondsLater) { + return getProxy().getScheduler().schedule(this, task, timeInSecondsLater, timeInSecondsLater, TimeUnit.SECONDS).getId(); } @Override diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/bungee/abstraction/BungeePlayer.java b/src/main/java/xyz/earthcow/networkjoinmessages/bungee/abstraction/BungeePlayer.java index 64b1cb8..59a0687 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/bungee/abstraction/BungeePlayer.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/bungee/abstraction/BungeePlayer.java @@ -16,6 +16,8 @@ public class BungeePlayer implements CorePlayer { private final ProxiedPlayer bungeePlayer; private CoreBackendServer lastKnownConnectedServer; private final Audience audience; + private String cachedLeaveMessage; + private boolean disconnecting = false; public BungeePlayer(ProxiedPlayer bungeePlayer) { this.bungeePlayer = bungeePlayer; @@ -77,4 +79,22 @@ public void setLastKnownConnectedServer(CoreBackendServer server) { public boolean isInLimbo() { return false; } + + @Override + public String getCachedLeaveMessage() { + return cachedLeaveMessage; + } + @Override + public void setCachedLeaveMessage(String cachedLeaveMessage) { + this.cachedLeaveMessage = cachedLeaveMessage; + } + + @Override + public boolean isDisconnecting() { + return disconnecting; + } + @Override + public void setDisconnecting() { + this.disconnecting = true; + } } diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/Core.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/Core.java index b62b71d..f8df566 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/Core.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/Core.java @@ -50,7 +50,7 @@ public Core(CorePlugin plugin) { this.coreImportCommand = new CoreImportCommand(corePlayerListener.getPlayerJoinTracker()); this.coreSpoofCommand = new CoreSpoofCommand(plugin, storage, messageHandler); - this.coreReloadCommand = new CoreReloadCommand(configManager, storage, discordIntegration, messageHandler); + this.coreReloadCommand = new CoreReloadCommand(plugin, configManager, storage, discordIntegration, messageHandler); this.coreToggleJoinCommand = new CoreToggleJoinCommand(storage, messageHandler); } diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/MessageHandler.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/MessageHandler.java index eb42ddf..ebf71a2 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/MessageHandler.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/MessageHandler.java @@ -18,6 +18,8 @@ public final class MessageHandler { private final Storage storage; private final Formatter formatter; + private final Map taskIds = new HashMap<>(); + @Nullable private final SayanVanishHook sayanVanishHook; @@ -26,6 +28,36 @@ public MessageHandler(CorePlugin plugin, Storage storage, Formatter formatter, @ this.storage = storage; this.formatter = formatter; this.sayanVanishHook = sayanVanishHook; + initCacheTasks(); + } + + public void initCacheTasks() { + taskIds.values().forEach(plugin::cancelTask); + taskIds.clear(); + if (storage.getLeaveCacheDuration() == 0) return; + for (CorePlayer player : plugin.getAllPlayers()) { + startLeaveCacheTaskForPlayer(player); + } + } + + public void startLeaveCacheTaskForPlayer(CorePlayer player) { + if (storage.getLeaveCacheDuration() == 0) return; + taskIds.put( + player.getUniqueId(), + plugin.runTaskRepeatedly(() -> updateCachedLeaveMessage(player), storage.getLeaveCacheDuration()) + ); + } + + public void stopLeaveCacheTaskForPlayer(CorePlayer player) { + if (taskIds.isEmpty()) return; + Integer taskId = taskIds.remove(player.getUniqueId()); + if (taskId == null) return; + plugin.cancelTask(taskId); + } + + public void updateCachedLeaveMessage(CorePlayer player) { + plugin.getCoreLogger().debug("Updating cached leave message for player " + player.getName()); + formatter.parsePlaceholdersAndThen(formatLeaveMessage(player), player, player::setCachedLeaveMessage); } /** diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/Storage.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/Storage.java index 157ba41..74b89c3 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/Storage.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/Storage.java @@ -14,7 +14,7 @@ import java.util.*; /** - * Singleton class for holding config values and user data that should persist after the user leaves the proxy + * Class for holding config values and user data that should persist after the user leaves the proxy */ public final class Storage { @@ -85,6 +85,9 @@ public final class Storage { @Getter private String consoleSilentLeave; + @Getter + private int leaveCacheDuration; + /** * The default silent state of a player joining with the networkjoinmessages.silent permission * Default: true - Someone joining with the permission will be silent (not send a join message) @@ -206,6 +209,8 @@ public void setUpDefaultValuesFromConfig() { /// Settings + this.leaveCacheDuration = config.getInt("Settings.LeaveNetworkMessageCacheDuration"); + this.silentJoinDefaultState = config.getBoolean("Settings.SilentJoinDefaultState"); this.swapServerMessageEnabled = config.getBoolean("Settings.SwapServerMessageEnabled"); @@ -266,6 +271,16 @@ public void setUpDefaultValuesFromConfig() { ); this.swapServerMessageRequires = "ANY"; } + + // Verify leave cache duration + if (leaveCacheDuration < 0) { + plugin.getCoreLogger() + .info( + "Setting error: Settings.LeaveNetworkMessageCacheDuration " + + "requires a non-negative value. Defaulting to 0 behavior." + ); + this.leaveCacheDuration = 0; + } } public boolean getSilentMessageState(CorePlayer player) { @@ -594,6 +609,8 @@ public String getLeaveNetworkMessage() { public Collection getCustomCharts() { List customCharts = new ArrayList<>(); + customCharts.add(new SimplePie("leave_cache_duration", () -> String.valueOf(swapServerMessageEnabled))); + customCharts.add(new SimplePie("swap_enabled", () -> String.valueOf(swapServerMessageEnabled))); customCharts.add(new SimplePie("first_join_enabled", () -> String.valueOf(firstJoinNetworkMessageEnabled))); customCharts.add(new SimplePie("join_enabled", () -> String.valueOf(joinNetworkMessageEnabled))); diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlayer.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlayer.java index c52c062..e334b71 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlayer.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlayer.java @@ -24,4 +24,10 @@ public interface CorePlayer extends CoreCommandSender { Audience getAudience(); boolean isInLimbo(); + + String getCachedLeaveMessage(); + void setCachedLeaveMessage(String cachedLeaveMessage); + + boolean isDisconnecting(); + void setDisconnecting(); } diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlugin.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlugin.java index 7cdbc78..4808070 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlugin.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/abstraction/CorePlugin.java @@ -26,7 +26,8 @@ public interface CorePlugin { PremiumVanish getVanishAPI(); - void runTaskLater(Runnable task, int timeInSecondsLater); + void cancelTask(int taskId); + int runTaskRepeatedly(Runnable task, int timeInSecondsLater); void runTaskAsync(Runnable task); boolean isPluginLoaded(String pluginName); diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/commands/CoreReloadCommand.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/commands/CoreReloadCommand.java index b5b63c5..7651ed8 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/commands/CoreReloadCommand.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/commands/CoreReloadCommand.java @@ -4,18 +4,21 @@ import xyz.earthcow.networkjoinmessages.common.MessageHandler; import xyz.earthcow.networkjoinmessages.common.Storage; import xyz.earthcow.networkjoinmessages.common.abstraction.CoreCommandSender; +import xyz.earthcow.networkjoinmessages.common.abstraction.CorePlugin; import xyz.earthcow.networkjoinmessages.common.modules.DiscordIntegration; import java.util.List; public class CoreReloadCommand implements Command { + private final CorePlugin plugin; private final ConfigManager configManager; private final Storage storage; private final DiscordIntegration discordIntegration; private final MessageHandler messageHandler; - public CoreReloadCommand(ConfigManager configManager, Storage storage, DiscordIntegration discordIntegration, MessageHandler messageHandler) { + public CoreReloadCommand(CorePlugin plugin, ConfigManager configManager, Storage storage, DiscordIntegration discordIntegration, MessageHandler messageHandler) { + this.plugin = plugin; this.configManager = configManager; this.storage = storage; this.discordIntegration = discordIntegration; @@ -28,10 +31,15 @@ public void execute(CoreCommandSender coreCommandSender, String[] args) { configManager.reload(); storage.setUpDefaultValuesFromConfig(); discordIntegration.loadVariables(); - - messageHandler.sendMessage(coreCommandSender, - storage.getReloadConfirmation() - ); + messageHandler.initCacheTasks(); + + // Update all player's cached leave message + plugin.runTaskAsync(() -> { + plugin.getAllPlayers().forEach(messageHandler::updateCachedLeaveMessage); + messageHandler.sendMessage(coreCommandSender, + storage.getReloadConfirmation() + ); + }); } } diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/common/listeners/CorePlayerListener.java b/src/main/java/xyz/earthcow/networkjoinmessages/common/listeners/CorePlayerListener.java index 4fd37e3..cf46597 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/common/listeners/CorePlayerListener.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/common/listeners/CorePlayerListener.java @@ -52,7 +52,7 @@ public CorePlayerListener(CorePlugin plugin, Storage storage, MessageHandler mes private boolean isSilentEvent(@NotNull CorePlayer player) { // Event is silent if, the player has a silent message state OR // premiumVanish is present, the treat vanished players as silent option is true, and the player is vanished - plugin.getCoreLogger().debug("Checking if the event for player " + player + " should been silent:"); + plugin.getCoreLogger().debug("Checking if the event for player " + player.getName() + " should been silent:"); plugin.getCoreLogger().debug(String.format( "silent message state: %s, SayanVanish hook is NOT null: %s, SVTreatVanishedPlayersAsSilent: %s, " + "SayanVanish player is vanished: %s, PremiumVanish hook is NOT null: %s, " + @@ -161,6 +161,9 @@ private void handlePlayerJoin(@NotNull CorePlayer player, @NotNull CoreBackendSe storage.setConnected(player, true); player.setLastKnownConnectedServer(server); + messageHandler.updateCachedLeaveMessage(player); + messageHandler.startLeaveCacheTaskForPlayer(player); + boolean firstJoin = !firstJoinTracker.hasJoined(player.getUniqueId()); MessageType msgType = firstJoin ? MessageType.FIRST_JOIN : MessageType.JOIN; @@ -204,6 +207,8 @@ private void handlePlayerJoin(@NotNull CorePlayer player, @NotNull CoreBackendSe private void handlePlayerSwap(@NotNull CorePlayer player, @NotNull CoreBackendServer server, boolean fromLimbo) { player.setLastKnownConnectedServer(server); + messageHandler.updateCachedLeaveMessage(player); + String to = server.getName(); String from = storage.getFrom(player); @@ -275,20 +280,27 @@ public void onServerConnected(@NotNull CorePlayer player, @NotNull CoreBackendSe * @param player Trigger player */ public void onDisconnect(@NotNull CorePlayer player) { + if (player.isDisconnecting()) { + plugin.getCoreLogger().debug("Disconnect event ignored for player " + player.getName() + + ", due to another disconnect event already processing them"); + return; + } + player.setDisconnecting(); if (shouldNotBroadcast(player, MessageType.LEAVE)) { plugin.getPlayerManager().removePlayer(player.getUniqueId()); storage.setConnected(player, false); + messageHandler.stopLeaveCacheTaskForPlayer(player); return; } - String message = messageHandler.formatLeaveMessage(player); + String message = player.getCachedLeaveMessage(); // Silent boolean isSilent = isSilentEvent(player); - // Broadcast message - messageHandler.broadcastMessage(message, MessageType.LEAVE, player, isSilent); + // Broadcast message - NULL parseTarget skips parsing placeholders in sendMessage + messageHandler.broadcastMessage(message, MessageType.LEAVE, player.getCurrentServer().getName(), "", null, isSilent); Component formattedMessage = Formatter.deserialize(message); // Call the custom NetworkLeaveEvent @@ -304,6 +316,7 @@ public void onDisconnect(@NotNull CorePlayer player) { plugin.getPlayerManager().removePlayer(player.getUniqueId()); storage.setConnected(player, false); + messageHandler.stopLeaveCacheTaskForPlayer(player); } public H2PlayerJoinTracker getPlayerJoinTracker() { diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/velocity/VelocityMain.java b/src/main/java/xyz/earthcow/networkjoinmessages/velocity/VelocityMain.java index 37022dc..afb3030 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/velocity/VelocityMain.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/velocity/VelocityMain.java @@ -10,6 +10,7 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.scheduler.ScheduledTask; import lombok.Getter; import org.bstats.charts.CustomChart; import org.bstats.velocity.Metrics; @@ -28,10 +29,12 @@ import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.stream.Collectors; @Plugin( @@ -69,6 +72,8 @@ public class VelocityMain implements CorePlugin { private VelocityDiscordListener velocityDiscordListener = null; + private final List tasks = new ArrayList<>(); + @Inject public VelocityMain(ProxyServer proxy, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { this.proxy = proxy; @@ -169,8 +174,20 @@ public void unregisterDiscordListener() { } @Override - public void runTaskLater(Runnable task, int timeInSecondsLater) { - proxy.getScheduler().buildTask(this, task).delay(timeInSecondsLater, TimeUnit.SECONDS).schedule(); + public void cancelTask(int taskId) { + try { + tasks.get(taskId).cancel(); + } catch (IndexOutOfBoundsException ignored) { + } + } + + @Override + public int runTaskRepeatedly(Runnable task, int timeInSecondsLater) { + tasks.add( + proxy.getScheduler().buildTask(this, task) + .delay(timeInSecondsLater, TimeUnit.SECONDS).repeat(timeInSecondsLater, TimeUnit.SECONDS).schedule() + ); + return tasks.size() - 1; } @Override diff --git a/src/main/java/xyz/earthcow/networkjoinmessages/velocity/abstraction/VelocityPlayer.java b/src/main/java/xyz/earthcow/networkjoinmessages/velocity/abstraction/VelocityPlayer.java index ea13ac2..37c3aa5 100644 --- a/src/main/java/xyz/earthcow/networkjoinmessages/velocity/abstraction/VelocityPlayer.java +++ b/src/main/java/xyz/earthcow/networkjoinmessages/velocity/abstraction/VelocityPlayer.java @@ -17,6 +17,8 @@ public class VelocityPlayer implements CorePlayer { private final Player velocityPlayer; private CoreBackendServer lastKnownConnectedServer; private final Audience audience; + private String cachedLeaveMessage; + private boolean disconnecting = false; public VelocityPlayer(Player velocityPlayer) { this.velocityPlayer = velocityPlayer; @@ -84,4 +86,23 @@ public boolean isInLimbo() { //noinspection ConstantValue return ((ConnectedPlayer) velocityPlayer).getConnection().getState().name() == null; } + + @Override + public String getCachedLeaveMessage() { + return cachedLeaveMessage; + } + @Override + public void setCachedLeaveMessage(String cachedLeaveMessage) { + this.cachedLeaveMessage = cachedLeaveMessage; + } + + @Override + public boolean isDisconnecting() { + return disconnecting; + } + + @Override + public void setDisconnecting() { + this.disconnecting = true; + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a59a76f..0a5dd87 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -111,6 +111,13 @@ Messages: ConsoleSilentJoin: "Join message was silenced. %player% joined the network" ConsoleSilentLeave: "Leave message was silenced. %player% left the network" Settings: + # As PAPIProxyBridge is unable to parse placeholders for offline players, a cache for the leave network message must + # be implemented. For placeholders that frequently change such as experience level, armor rating, etc. you may wish + # for this cache to update more often. + # Designate how long in seconds the leave network message will be cached or use 0 to only update cache on join and + # server swap. + LeaveNetworkMessageCacheDuration: 45 + # Should players with the networkjoinmessages.silent permission be silenced by default? # It will be set to this for all players after a reboot. They can toggle it for themselves with the /njoinspoof toggle command SilentJoinDefaultState: true @@ -206,4 +213,4 @@ OtherPlugins: debug: false # Do not touch this -config-version: 8 \ No newline at end of file +config-version: 9 \ No newline at end of file