From a8a17222aea3361e0eca584a0a2e4fce8a71d6b1 Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Wed, 13 Nov 2024 23:49:11 +0100 Subject: [PATCH 1/5] feat: add experimental packetevents support Only Velocity is supported by this commit, Bungee and Spigot will come later. This experimental feature can be enabled by setting `experimental-use-packetevents` to true on config. No actual packets are handled by this commit, it just lays the groundwork for future packet handling. --- core/build.gradle | 2 + .../triton/loader/utils/CommonLoader.java | 5 ++ .../triton/loader/utils/LoaderFlag.java | 1 + .../java/com/rexcantor64/triton/Triton.java | 40 +++++++++++ .../rexcantor64/triton/config/MainConfig.java | 2 + .../triton/dependencies/Dependency.java | 27 ++++++++ .../PacketEventsListener.java | 55 +++++++++++++++ .../PacketEventsManager.java | 67 +++++++++++++++++++ .../handlers/ScoreboardPacketHandler.java | 13 ++++ .../triton/bungeecord/BungeeTriton.java | 5 ++ .../src/main/resources/config_bungeecord.yml | 7 ++ .../triton/spigot/SpigotTriton.java | 5 ++ .../src/main/resources/config_spigot.yml | 7 ++ triton-velocity/build.gradle | 2 + .../triton/loader/VelocityLoader.java | 4 ++ .../triton/velocity/VelocityTriton.java | 6 ++ .../VelocityPacketEventsManager.java | 27 ++++++++ .../velocity/plugin/VelocityPlugin.java | 3 + .../src/main/resources/config_velocity.yml | 7 ++ 19 files changed, 285 insertions(+) create mode 100644 core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java create mode 100644 core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsManager.java create mode 100644 core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java create mode 100644 triton-velocity/src/main/java/com/rexcantor64/triton/velocity/packetinterceptor/VelocityPacketEventsManager.java diff --git a/core/build.gradle b/core/build.gradle index 0cd8615d..011394b8 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -31,6 +31,8 @@ dependencies { compileOnly 'org.apache.logging.log4j:log4j-core:2.17.1' compileOnly 'org.yaml:snakeyaml:2.0' + compileOnly 'com.github.retrooper:packetevents-api:2.6.0' + implementation 'org.slf4j:slf4j-nop:1.7.36' implementation 'com.zaxxer:HikariCP:4.0.3' implementation('com.tananaev:json-patch:1.1') { diff --git a/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/CommonLoader.java b/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/CommonLoader.java index cabf4e1b..5a11e102 100644 --- a/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/CommonLoader.java +++ b/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/CommonLoader.java @@ -37,6 +37,11 @@ public LoaderBootstrap loadPlugin() { } } + if (flags.contains(LoaderFlag.VENDOR_PACKET_EVENTS)) { + relocations.add(new Relocation("com/github/retrooper/packetevents", "com/rexcantor64/triton/lib/packetevents/api")); + relocations.add(new Relocation("io/github/retrooper/packetevents", "com/rexcantor64/triton/lib/packetevents/impl")); + } + @SuppressWarnings("resource") JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), relocations, CORE_JAR_NAME, jarInJarName); diff --git a/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/LoaderFlag.java b/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/LoaderFlag.java index 87c6e8b6..c601f67e 100644 --- a/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/LoaderFlag.java +++ b/core/loader/src/main/java/com/rexcantor64/triton/loader/utils/LoaderFlag.java @@ -3,4 +3,5 @@ public enum LoaderFlag { SHADE_ADVENTURE, RELOCATE_ADVENTURE, + VENDOR_PACKET_EVENTS, } diff --git a/core/src/main/java/com/rexcantor64/triton/Triton.java b/core/src/main/java/com/rexcantor64/triton/Triton.java index 9f376d2e..1663ea22 100644 --- a/core/src/main/java/com/rexcantor64/triton/Triton.java +++ b/core/src/main/java/com/rexcantor64/triton/Triton.java @@ -9,11 +9,14 @@ import com.rexcantor64.triton.config.interfaces.ConfigurationProvider; import com.rexcantor64.triton.config.interfaces.YamlConfiguration; import com.rexcantor64.triton.debug.DumpManager; +import com.rexcantor64.triton.dependencies.Dependency; import com.rexcantor64.triton.language.LanguageManager; import com.rexcantor64.triton.language.TranslationManager; import com.rexcantor64.triton.language.parser.AdventureParser; +import com.rexcantor64.triton.loader.utils.LoaderFlag; import com.rexcantor64.triton.logger.TritonLogger; import com.rexcantor64.triton.migration.LanguageMigration; +import com.rexcantor64.triton.packetinterceptor.PacketEventsManager; import com.rexcantor64.triton.player.LanguagePlayer; import com.rexcantor64.triton.player.PlayerManager; import com.rexcantor64.triton.plugin.Platform; @@ -26,6 +29,7 @@ import com.rexcantor64.triton.web.TwinManager; import lombok.Getter; import lombok.val; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.InputStreamReader; @@ -58,6 +62,7 @@ public abstract class Triton

private Storage storage; private TritonLogger logger; private DumpManager dumpManager; + protected @Nullable PacketEventsManager packetEventsManager; protected Triton(PlayerManager

playerManager, B bridgeManager) { this.playerManager = playerManager; @@ -106,6 +111,29 @@ protected void onEnable() { reload(); twinManager = new TwinManager(this); + + if (config.isUsePacketEvents()) { + val dependencyManager = Triton.get().getLoader().getDependencyManager(); + val isPacketEventsVendored = dependencyManager.hasLoaderFlag(LoaderFlag.VENDOR_PACKET_EVENTS); + if (isPacketEventsVendored) { + // load packet events dependency (netty and platform related modules are loaded later) + dependencyManager.loadDependency(Dependency.PACKET_EVENTS_API); + } + this.initPacketEventsManager(); + } + + if (this.packetEventsManager != null) { + // TODO: when spigot is supported, onLoad needs to be called earlier + this.packetEventsManager.onLoad(); + this.packetEventsManager.onEnable(); + } + } + + protected void onDisable() { + // TODO: this should be called at some point + if (this.packetEventsManager != null) { + this.packetEventsManager.onDisable(); + } } public void reload() { @@ -117,9 +145,21 @@ public void reload() { setupStorage(); languageManager.setup(); translationManager.setup(); + if (this.packetEventsManager != null) { + this.packetEventsManager.onReload(); + } startConfigRefreshTask(); } + /** + * Initialize the platform's {@link PacketEventsManager} by setting the + * {@link Triton#packetEventsManager} variable. + * A platform is allowed to not do anything in case PacketEvents is not supported (yet). + * + * @since 4.0.0 + */ + protected abstract void initPacketEventsManager(); + public void refreshPlayers() { playerManager.getAll().stream() .filter(Objects::nonNull) diff --git a/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java b/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java index de474d1b..19efb662 100644 --- a/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java +++ b/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java @@ -97,6 +97,7 @@ public class MainConfig implements TritonConfig { private boolean preventPlaceholdersInChat; private int maxPlaceholdersInMessage; private boolean asyncProtocolLib; + private boolean usePacketEvents; private String storageType = "local"; private String serverName; @@ -182,6 +183,7 @@ private void setup(Configuration section) { this.logLevel = section.getInt("log-level", 0); this.configAutoRefresh = section.getInt("config-auto-refresh-interval", -1); this.asyncProtocolLib = section.getBoolean("experimental-async-protocol-lib", false); + this.usePacketEvents = section.getBoolean("experimental-use-packetevents", false); Configuration languageCreation = section.getSection("language-creation"); setupLanguageCreation(languageCreation); diff --git a/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java b/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java index a85f5a40..b9948c9a 100644 --- a/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java +++ b/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java @@ -116,6 +116,33 @@ public enum Dependency { "1.0.0", "K95aei1z+hFmoPFmOiLDr30naP/E/qMxd0w8D2IiXRE=", relocate("net{}kyori{}option", "kyori{}option") + ), + + // PacketEvents + PACKET_EVENTS_API( + "com{}github{}retrooper", + "packetevents-api", + "2.6.0", + "CdIZKtdDgdpw0uQR/tJOsAfZF66/oRRT0JMqSXCLAWs=", + relocate("com{}github{}retrooper{}packetevents", "packetevents{}api"), + relocate("io{}github{}retrooper{}packetevents", "packetevents{}impl") + // TODO relocate adventure (?) + ), + PACKET_EVENTS_NETTY_COMMON( + "com{}github{}retrooper", + "packetevents-netty-common", + "2.6.0", + "IzZVaD5pI267Q2baymTiFtB9E8119YkhkPZeLXCJLTc=", + relocate("com{}github{}retrooper{}packetevents", "packetevents{}api"), + relocate("io{}github{}retrooper{}packetevents", "packetevents{}impl") + ), + PACKET_EVENTS_VELOCITY( + "com{}github{}retrooper", + "packetevents-velocity", + "2.6.0", + "93wEevRi6xSrz+mGVMaHO7sLfsH4AAISsmxNigSU4RA=", + relocate("com{}github{}retrooper{}packetevents", "packetevents{}api"), + relocate("io{}github{}retrooper{}packetevents", "packetevents{}impl") ); @Getter diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java new file mode 100644 index 00000000..7a019f01 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java @@ -0,0 +1,55 @@ +package com.rexcantor64.triton.packetinterceptor; + +import com.github.retrooper.packetevents.event.PacketListener; +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; +import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.packetinterceptor.handlers.ScoreboardPacketHandler; +import lombok.val; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Main entrypoint for intercepting packets with PacketEvents. + *

+ * The {@link PacketEventsListener#setupHandlers()} function is NOT + * called on constructor and thus needs to be called separately. + * + * @since 4.0.0 + */ +public class PacketEventsListener implements PacketListener { + + private Map> receiveHandlers = Collections.emptyMap(); + + /** + * Setup handlers according to what is enabled on config. + * + * @since 4.0.0 + */ + public void setupHandlers() { + val config = Triton.get().getConfig(); + val updatedHandlers = new HashMap>(); + + if (config.isScoreboards()) { + val scoreboardHandler = new ScoreboardPacketHandler(); + updatedHandlers.put(PacketType.Play.Server.TEAMS, scoreboardHandler::onTeamsPacket); + } + + receiveHandlers = Collections.unmodifiableMap(updatedHandlers); + } + + @Override + public void onPacketSend(PacketSendEvent event) { + val type = event.getPacketType(); + System.out.println("type = " + type); + + val handler = receiveHandlers.get(type); + if (handler != null) { + handler.accept(event); + } + } +} diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsManager.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsManager.java new file mode 100644 index 00000000..d6d4de39 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsManager.java @@ -0,0 +1,67 @@ +package com.rexcantor64.triton.packetinterceptor; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.PacketEventsAPI; +import com.github.retrooper.packetevents.event.PacketListenerPriority; +import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.dependencies.Dependency; +import com.rexcantor64.triton.loader.utils.LoaderFlag; +import lombok.NonNull; +import lombok.val; + +/** + * This class exists to avoid {@link NoClassDefFoundError} when not using PacketEvents. + * It acts as a "buffer" so that the PacketEvents classes are not loaded by Java unless + * PacketEvents support is enabled for Triton. + * + * @since 4.0.0 + */ +public abstract class PacketEventsManager { + + private final @NonNull PacketEventsListener packetEventsListener = new PacketEventsListener(); + + public void onLoad() { + val dependencyManager = Triton.get().getLoader().getDependencyManager(); + val isPacketEventsVendored = dependencyManager.hasLoaderFlag(LoaderFlag.VENDOR_PACKET_EVENTS); + if (isPacketEventsVendored) { + // load packet events dependency (api is loaded in the Triton class to allow this class to load successfully) + dependencyManager.loadDependency(Dependency.PACKET_EVENTS_NETTY_COMMON); + dependencyManager.loadDependency(this.getPlatformPacketEventsDependency()); + + initPacketEventsPlatform(); + } + this.packetEventsListener.setupHandlers(); + PacketEvents.getAPI().load(); + if (isPacketEventsVendored) { + // Don't check for PacketEvents updates if it's vendored (the user can't do anything) + // noinspection UnstableApiUsage + PacketEvents.getAPI().getSettings().checkForUpdates(false); + } + PacketEvents.getAPI().getEventManager().registerListener(this.packetEventsListener, PacketListenerPriority.HIGHEST); + } + + public void onEnable() { + PacketEvents.getAPI().init(); + } + + public void onDisable() { + PacketEvents.getAPI().terminate(); + } + + public void onReload() { + this.packetEventsListener.setupHandlers(); + } + + /** + * Call {@link PacketEvents#setAPI(PacketEventsAPI)} with the appropriate platform. + * + * @since 4.0.0 + */ + protected abstract void initPacketEventsPlatform(); + + /** + * @return Get the PacketEvents dependency to download for the current platform + * @since 4.0.0 + */ + protected abstract @NonNull Dependency getPlatformPacketEventsDependency(); +} diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java new file mode 100644 index 00000000..6ff26700 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java @@ -0,0 +1,13 @@ +package com.rexcantor64.triton.packetinterceptor.handlers; + +import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; + +public class ScoreboardPacketHandler { + + public void onTeamsPacket(PacketSendEvent event) { + WrapperPlayServerTeams teams = new WrapperPlayServerTeams(event); + // TODO + } + +} diff --git a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java index a53bc55d..86c01bb8 100644 --- a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java +++ b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java @@ -107,6 +107,11 @@ public void reload() { bridgeManager.sendConfigToEveryone(); } + @Override + protected void initPacketEventsManager() { + // TODO: triton does not support packetevents on this platform yet + } + @Override protected void startConfigRefreshTask() { if (configRefreshTask != null) configRefreshTask.cancel(); diff --git a/triton-bungeecord/src/main/resources/config_bungeecord.yml b/triton-bungeecord/src/main/resources/config_bungeecord.yml index 1f557716..2f5a5a8f 100644 --- a/triton-bungeecord/src/main/resources/config_bungeecord.yml +++ b/triton-bungeecord/src/main/resources/config_bungeecord.yml @@ -112,6 +112,13 @@ storage: # useSSL: false # verifyServerCertificate: false +# EXPERIMENTAL: Use PacketEvents for intercepting packets. +# Some packets that have not been migrated might still use the legacy +# method of interception. +# When stable, this will be enabled by default. +# Changing this REQUIRES a server restart. +experimental-use-packetevents: false + # Here you can enable and disable certain features of the plugin. # Every section contains a "syntax-lang", "syntax-args" and "syntax-arg" field. These are used in the placeholders. # If you change them, make sure to adjust accordingly in your plugins. diff --git a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java index 56a48361..278ef670 100644 --- a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java +++ b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java @@ -167,6 +167,11 @@ public void reload() { this.bannerBuilder.flushCache(); } + @Override + protected void initPacketEventsManager() { + // TODO: triton does not support packetevents on this platform yet + } + @Override protected void startConfigRefreshTask() { if (refreshTaskId != -1) Bukkit.getScheduler().cancelTask(refreshTaskId); diff --git a/triton-spigot/src/main/resources/config_spigot.yml b/triton-spigot/src/main/resources/config_spigot.yml index e5316024..d04ea6c9 100644 --- a/triton-spigot/src/main/resources/config_spigot.yml +++ b/triton-spigot/src/main/resources/config_spigot.yml @@ -131,6 +131,13 @@ storage: # Changing this REQUIRES a server restart. experimental-async-protocol-lib: false +# EXPERIMENTAL: Use PacketEvents for intercepting packets. +# Some packets that have not been migrated might still use the legacy +# method of interception. +# When stable, this will be enabled by default. +# Changing this REQUIRES a server restart. +experimental-use-packetevents: false + # Here you can enable and disable certain features of the plugin. # Every section contains a "syntax-lang", "syntax-args" and "syntax-arg" field. These are used in the placeholders. # If you change them, make sure to adjust accordingly in your plugins. diff --git a/triton-velocity/build.gradle b/triton-velocity/build.gradle index ae09676c..bd9a7f93 100644 --- a/triton-velocity/build.gradle +++ b/triton-velocity/build.gradle @@ -22,6 +22,8 @@ dependencies { implementation 'net.byteflux:libby-velocity:1.3.0' implementation 'org.bstats:bstats-velocity:3.1.0' + + compileOnly 'com.github.retrooper:packetevents-velocity:2.6.0' } shadowJar { diff --git a/triton-velocity/loader/src/main/java/com/rexcantor64/triton/loader/VelocityLoader.java b/triton-velocity/loader/src/main/java/com/rexcantor64/triton/loader/VelocityLoader.java index be537257..5cb1dace 100644 --- a/triton-velocity/loader/src/main/java/com/rexcantor64/triton/loader/VelocityLoader.java +++ b/triton-velocity/loader/src/main/java/com/rexcantor64/triton/loader/VelocityLoader.java @@ -4,6 +4,7 @@ import com.google.inject.name.Named; import com.rexcantor64.triton.loader.utils.CommonLoader; import com.rexcantor64.triton.loader.utils.LoaderBootstrap; +import com.rexcantor64.triton.loader.utils.LoaderFlag; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Dependency; @@ -23,6 +24,8 @@ version = "@version@", authors = {"Rexcantor64"}, dependencies = { + // Soft-depend on packet events: use bundled version, but allow using one available in the server + @Dependency(id = "packetevents", optional = true), // https://github.com/retrooper/packetevents // List of plugins that interfere with Triton in event listeners. // If Triton loads after, its listeners are fired after in events with the same priority, // which is what we want since Triton benefits from having the final saying in the event. @@ -50,6 +53,7 @@ public VelocityLoader(ProxyServer server, Logger logger, @Named("triton") Plugin .constructorValue(logger) .constructorValue(container) .constructorValue(dataDirectory) + .flag(LoaderFlag.VENDOR_PACKET_EVENTS) // TODO: allow using server's packet events .build() .loadPlugin(); } diff --git a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java index d07b4e37..5cb17f91 100644 --- a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java +++ b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java @@ -7,6 +7,7 @@ import com.rexcantor64.triton.velocity.bridge.VelocityBridgeManager; import com.rexcantor64.triton.velocity.commands.handler.VelocityCommandHandler; import com.rexcantor64.triton.velocity.listeners.VelocityListener; +import com.rexcantor64.triton.velocity.packetinterceptor.VelocityPacketEventsManager; import com.rexcantor64.triton.velocity.player.VelocityLanguagePlayer; import com.rexcantor64.triton.velocity.plugin.VelocityPlugin; import com.velocitypowered.api.proxy.ProxyServer; @@ -80,6 +81,11 @@ public void reload() { } } + @Override + protected void initPacketEventsManager() { + this.packetEventsManager = new VelocityPacketEventsManager(); + } + @Override protected void startConfigRefreshTask() { if (configRefreshTask != null) configRefreshTask.cancel(); diff --git a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/packetinterceptor/VelocityPacketEventsManager.java b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/packetinterceptor/VelocityPacketEventsManager.java new file mode 100644 index 00000000..ffbee150 --- /dev/null +++ b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/packetinterceptor/VelocityPacketEventsManager.java @@ -0,0 +1,27 @@ +package com.rexcantor64.triton.velocity.packetinterceptor; + +import com.github.retrooper.packetevents.PacketEvents; +import com.rexcantor64.triton.dependencies.Dependency; +import com.rexcantor64.triton.packetinterceptor.PacketEventsManager; +import com.rexcantor64.triton.velocity.VelocityTriton; +import io.github.retrooper.packetevents.velocity.factory.VelocityPacketEventsBuilder; +import lombok.NonNull; +import lombok.val; + +public class VelocityPacketEventsManager extends PacketEventsManager { + @Override + protected void initPacketEventsPlatform() { + val loader = VelocityTriton.asVelocity().getLoader(); + PacketEvents.setAPI(VelocityPacketEventsBuilder.build( + loader.getServer(), + loader.getPluginContainer(), + loader.getVelocityLogger(), + loader.getDataDirectory() + )); + } + + @Override + protected @NonNull Dependency getPlatformPacketEventsDependency() { + return Dependency.PACKET_EVENTS_VELOCITY; + } +} diff --git a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/plugin/VelocityPlugin.java b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/plugin/VelocityPlugin.java index 3d2ec39c..8ccdc7c5 100644 --- a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/plugin/VelocityPlugin.java +++ b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/plugin/VelocityPlugin.java @@ -28,6 +28,8 @@ public class VelocityPlugin implements PluginLoader, LoaderBootstrap { @Getter private final Object plugin; private final ProxyServer server; + @Getter + private final Logger velocityLogger; private final TritonLogger tritonLogger; @Getter private final PluginContainer pluginContainer; @@ -41,6 +43,7 @@ public class VelocityPlugin implements PluginLoader, LoaderBootstrap { public VelocityPlugin(Object loader, ProxyServer server, Logger logger, @Named("triton") PluginContainer container, @DataDirectory Path dataDirectory, Set loaderFlags) { this.plugin = loader; this.server = server; + this.velocityLogger = logger; this.tritonLogger = new SLF4JLogger(logger); this.pluginContainer = container; this.dataDirectory = dataDirectory; diff --git a/triton-velocity/src/main/resources/config_velocity.yml b/triton-velocity/src/main/resources/config_velocity.yml index e23ed2a9..5269e0ab 100644 --- a/triton-velocity/src/main/resources/config_velocity.yml +++ b/triton-velocity/src/main/resources/config_velocity.yml @@ -111,6 +111,13 @@ storage: # useSSL: false # verifyServerCertificate: false +# EXPERIMENTAL: Use PacketEvents for intercepting packets. +# Some packets that have not been migrated might still use the legacy +# method of interception. +# When stable, this will be enabled by default. +# Changing this REQUIRES a server restart. +experimental-use-packetevents: false + # Here you can enable and disable certain features of the plugin. # Every section contains a "syntax-lang", "syntax-args" and "syntax-arg" field. These are used in the placeholders. # If you change them, make sure to adjust accordingly in your plugins. From 99f4833d26b64050f40cc3dca6ff489938a1e922 Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Sun, 17 Nov 2024 19:36:54 +0100 Subject: [PATCH 2/5] feat(core): handle scoreboard teams packet with packetevents Refreshing functionality is still not implemented. --- .../PacketEventsListener.java | 13 ++-- .../handlers/ScoreboardPacketHandler.java | 76 ++++++++++++++++++- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java index 7a019f01..0a59d8d4 100644 --- a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java @@ -6,11 +6,13 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.packetinterceptor.handlers.ScoreboardPacketHandler; +import com.rexcantor64.triton.player.LanguagePlayer; import lombok.val; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -23,7 +25,7 @@ */ public class PacketEventsListener implements PacketListener { - private Map> receiveHandlers = Collections.emptyMap(); + private Map> receiveHandlers = Collections.emptyMap(); /** * Setup handlers according to what is enabled on config. @@ -31,11 +33,12 @@ public class PacketEventsListener implements PacketListener { * @since 4.0.0 */ public void setupHandlers() { + val parser = Triton.get().getMessageParser(); val config = Triton.get().getConfig(); - val updatedHandlers = new HashMap>(); + val updatedHandlers = new HashMap>(); if (config.isScoreboards()) { - val scoreboardHandler = new ScoreboardPacketHandler(); + val scoreboardHandler = new ScoreboardPacketHandler(parser, config); updatedHandlers.put(PacketType.Play.Server.TEAMS, scoreboardHandler::onTeamsPacket); } @@ -45,11 +48,11 @@ public void setupHandlers() { @Override public void onPacketSend(PacketSendEvent event) { val type = event.getPacketType(); - System.out.println("type = " + type); val handler = receiveHandlers.get(type); if (handler != null) { - handler.accept(event); + val languagePlayer = Triton.get().getPlayerManager().get(event.getUser().getUUID()); + handler.accept(event, languagePlayer); } } } diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java index 6ff26700..2f114c7f 100644 --- a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java @@ -2,12 +2,84 @@ import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; +import com.rexcantor64.triton.config.MainConfig; +import com.rexcantor64.triton.language.parser.AdventureParser; +import com.rexcantor64.triton.player.LanguagePlayer; +import lombok.RequiredArgsConstructor; +import lombok.val; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +@RequiredArgsConstructor public class ScoreboardPacketHandler { - public void onTeamsPacket(PacketSendEvent event) { + private final @NotNull AdventureParser parser; + private final @NotNull MainConfig.FeatureSyntax syntax; + + public ScoreboardPacketHandler(@NotNull AdventureParser parser, @NotNull MainConfig config) { + this.parser = parser; + this.syntax = config.getScoreboardSyntax(); + } + + public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull LanguagePlayer languagePlayer) { WrapperPlayServerTeams teams = new WrapperPlayServerTeams(event); - // TODO + + val action = teams.getTeamMode(); + if (action == WrapperPlayServerTeams.TeamMode.REMOVE) { + // TODO remove from cache + } + + if (action != WrapperPlayServerTeams.TeamMode.CREATE && action != WrapperPlayServerTeams.TeamMode.UPDATE) { + // we are only interested in new/update actions + return; + } + + val infoOpt = teams.getTeamInfo(); + if (!infoOpt.isPresent()) { + // this should never happen since we have filtered by the actions before + return; + } + val info = infoOpt.get(); + + // display name + parser.translateComponent( + info.getDisplayName(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + info.setDisplayName(result); + event.markForReEncode(true); + }); + // prefix + parser.translateComponent( + info.getPrefix(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + info.setPrefix(result); + event.markForReEncode(true); + }); + // suffix + parser.translateComponent( + info.getSuffix(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + info.setSuffix(result); + event.markForReEncode(true); + }); + + if (event.needsReEncode()) { + // TODO save data to cache + } else { + // TODO remove team from cache if exists + } } } From 40725111de19d6ef2bc7501ce2b5e47b5c193c9c Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Wed, 18 Dec 2024 22:32:32 +0000 Subject: [PATCH 3/5] feat(core): add scoreboard teams refresh with packetevents --- .../java/com/rexcantor64/triton/Triton.java | 6 +- .../triton/bridge/BridgeSerializer.java | 4 +- .../rexcantor64/triton/debug/DumpManager.java | 8 +- .../PacketEventsListener.java | 7 +- .../handlers/ScoreboardPacketHandler.java | 30 +++-- .../triton/player/LanguagePlayer.java | 24 ---- .../triton/player/PacketEventsRefresh.java | 109 ++++++++++++++++++ .../triton/player/PlayerManager.java | 2 +- .../triton/player/TritonLanguagePlayer.java | 48 ++++++++ .../triton/storage/LocalStorage.java | 4 +- .../triton/storage/MysqlStorage.java | 4 +- .../rexcantor64/triton/storage/Storage.java | 4 +- .../bridge/BungeeBridgeManager.java | 4 +- .../player/BungeeLanguagePlayer.java | 13 ++- .../EntitiesPacketHandler.java | 4 +- .../spigot/player/SpigotLanguagePlayer.java | 12 +- .../player/VelocityLanguagePlayer.java | 11 +- 17 files changed, 232 insertions(+), 62 deletions(-) delete mode 100644 core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java create mode 100644 core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java create mode 100644 core/src/main/java/com/rexcantor64/triton/player/TritonLanguagePlayer.java diff --git a/core/src/main/java/com/rexcantor64/triton/Triton.java b/core/src/main/java/com/rexcantor64/triton/Triton.java index 1663ea22..79e8279b 100644 --- a/core/src/main/java/com/rexcantor64/triton/Triton.java +++ b/core/src/main/java/com/rexcantor64/triton/Triton.java @@ -17,7 +17,7 @@ import com.rexcantor64.triton.logger.TritonLogger; import com.rexcantor64.triton.migration.LanguageMigration; import com.rexcantor64.triton.packetinterceptor.PacketEventsManager; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.player.PlayerManager; import com.rexcantor64.triton.plugin.Platform; import com.rexcantor64.triton.plugin.PluginLoader; @@ -39,7 +39,7 @@ import java.util.UUID; @Getter -public abstract class Triton

implements com.rexcantor64.triton.api.Triton { +public abstract class Triton

, B extends BridgeManager> implements com.rexcantor64.triton.api.Triton { // Main instances protected static Triton instance; @@ -163,7 +163,7 @@ public void reload() { public void refreshPlayers() { playerManager.getAll().stream() .filter(Objects::nonNull) - .forEach(LanguagePlayer::refreshAll); + .forEach(TritonLanguagePlayer::refreshAll); } public Configuration loadYAML(String fileName, String internalFileName) { diff --git a/core/src/main/java/com/rexcantor64/triton/bridge/BridgeSerializer.java b/core/src/main/java/com/rexcantor64/triton/bridge/BridgeSerializer.java index 7f01e7c9..06c8ddb5 100644 --- a/core/src/main/java/com/rexcantor64/triton/bridge/BridgeSerializer.java +++ b/core/src/main/java/com/rexcantor64/triton/bridge/BridgeSerializer.java @@ -6,7 +6,7 @@ import com.rexcantor64.triton.commands.handler.CommandEvent; import com.rexcantor64.triton.language.item.LanguageSign; import com.rexcantor64.triton.language.item.LanguageText; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.storage.LocalStorage; import lombok.Getter; import lombok.NonNull; @@ -202,7 +202,7 @@ public static List buildTranslationData(String serverName, byte @NonNull return outList; } - public static byte[] buildPlayerLanguageData(LanguagePlayer lp) { + public static byte[] buildPlayerLanguageData(TritonLanguagePlayer lp) { val out = ByteStreams.newDataOutput(); out.writeByte(ActionP2S.SEND_PLAYER_LANGUAGE.getKey()); val uuid = lp.getUUID(); diff --git a/core/src/main/java/com/rexcantor64/triton/debug/DumpManager.java b/core/src/main/java/com/rexcantor64/triton/debug/DumpManager.java index cc18f0de..4f443e79 100644 --- a/core/src/main/java/com/rexcantor64/triton/debug/DumpManager.java +++ b/core/src/main/java/com/rexcantor64/triton/debug/DumpManager.java @@ -3,7 +3,7 @@ import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.api.config.FeatureSyntax; import com.rexcantor64.triton.api.language.Localized; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import lombok.Cleanup; import lombok.Getter; import lombok.val; @@ -70,7 +70,7 @@ public void enableForEveryone(Collection enabledTypes) { /** * Enable message dumping for messages of certain types (chat, action bars, etc.) * sent to a given player. - * Some messages that are not translated using the {@link com.rexcantor64.triton.player.LanguagePlayer} + * Some messages that are not translated using the {@link TritonLanguagePlayer} * instance might not be correctly identified. *

* If the message dumping is already enabled for the player, current @@ -149,8 +149,8 @@ private boolean shouldDump(Localized localized, FeatureSyntax type) { // Quickly determine that dumping is disabled without querying the Map return false; } - if (localized instanceof LanguagePlayer) { - val uuid = ((LanguagePlayer) localized).getUUID(); + if (localized instanceof TritonLanguagePlayer) { + val uuid = ((TritonLanguagePlayer) localized).getUUID(); val playerSettings = filter.get(uuid); if (playerSettings != null) { // Use referential equality instead of object equality, since we want to compare if the diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java index 0a59d8d4..c88e4fa1 100644 --- a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java @@ -6,14 +6,13 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.packetinterceptor.handlers.ScoreboardPacketHandler; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import lombok.val; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; -import java.util.function.Consumer; /** * Main entrypoint for intercepting packets with PacketEvents. @@ -25,7 +24,7 @@ */ public class PacketEventsListener implements PacketListener { - private Map> receiveHandlers = Collections.emptyMap(); + private Map>> receiveHandlers = Collections.emptyMap(); /** * Setup handlers according to what is enabled on config. @@ -35,7 +34,7 @@ public class PacketEventsListener implements PacketListener { public void setupHandlers() { val parser = Triton.get().getMessageParser(); val config = Triton.get().getConfig(); - val updatedHandlers = new HashMap>(); + val updatedHandlers = new HashMap>>(); if (config.isScoreboards()) { val scoreboardHandler = new ScoreboardPacketHandler(parser, config); diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java index 2f114c7f..41ebda03 100644 --- a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java @@ -4,7 +4,7 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; import com.rexcantor64.triton.config.MainConfig; import com.rexcantor64.triton.language.parser.AdventureParser; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import lombok.RequiredArgsConstructor; import lombok.val; import net.kyori.adventure.text.Component; @@ -21,12 +21,12 @@ public ScoreboardPacketHandler(@NotNull AdventureParser parser, @NotNull MainCon this.syntax = config.getScoreboardSyntax(); } - public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull LanguagePlayer languagePlayer) { + public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull TritonLanguagePlayer languagePlayer) { WrapperPlayServerTeams teams = new WrapperPlayServerTeams(event); val action = teams.getTeamMode(); if (action == WrapperPlayServerTeams.TeamMode.REMOVE) { - // TODO remove from cache + languagePlayer.getPacketEventsRefresh().discardScoreboardTeam(teams.getTeamName()); } if (action != WrapperPlayServerTeams.TeamMode.CREATE && action != WrapperPlayServerTeams.TeamMode.UPDATE) { @@ -41,9 +41,13 @@ public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull LanguagePlaye } val info = infoOpt.get(); + val originalDisplayName = info.getDisplayName(); + val originalPrefix = info.getPrefix(); + val originalSuffix = info.getSuffix(); + // display name parser.translateComponent( - info.getDisplayName(), + originalDisplayName, languagePlayer, syntax ) @@ -54,7 +58,7 @@ public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull LanguagePlaye }); // prefix parser.translateComponent( - info.getPrefix(), + originalPrefix, languagePlayer, syntax ) @@ -65,7 +69,7 @@ public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull LanguagePlaye }); // suffix parser.translateComponent( - info.getSuffix(), + originalSuffix, languagePlayer, syntax ) @@ -76,9 +80,19 @@ public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull LanguagePlaye }); if (event.needsReEncode()) { - // TODO save data to cache + val teamInfoCopy = new WrapperPlayServerTeams.ScoreBoardTeamInfo( + originalDisplayName, + originalPrefix, + originalSuffix, + info.getTagVisibility(), + info.getCollisionRule(), + info.getColor(), + info.getOptionData() + ); + + languagePlayer.getPacketEventsRefresh().saveScoreboardTeam(teams.getTeamName(), teamInfoCopy); } else { - // TODO remove team from cache if exists + languagePlayer.getPacketEventsRefresh().discardScoreboardTeam(teams.getTeamName()); } } diff --git a/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java b/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java deleted file mode 100644 index f9c8f156..00000000 --- a/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.rexcantor64.triton.player; - -import com.rexcantor64.triton.api.language.Language; - -import java.util.UUID; - -public interface LanguagePlayer extends com.rexcantor64.triton.api.players.LanguagePlayer { - - boolean isWaitingForClientLocale(); - - void waitForClientLocale(); - - @Override - default Language getLanguage() { - return this.getLang(); - } - - /** - * @return the UUID that is to be used for storage-related operations - */ - default UUID getStorageUniqueId() { - return this.getUUID(); - } -} diff --git a/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java b/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java new file mode 100644 index 00000000..6034ba93 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java @@ -0,0 +1,109 @@ +package com.rexcantor64.triton.player; + +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams.ScoreBoardTeamInfo; +import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.config.MainConfig; +import com.rexcantor64.triton.language.parser.AdventureParser; +import lombok.RequiredArgsConstructor; +import lombok.val; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor +public class PacketEventsRefresh { + private final Map teamsMap = new ConcurrentHashMap<>(); + + private final TritonLanguagePlayer languagePlayer; + + /** + * Stores info of a team for later (i.e., to refresh text when language is changed). + * The team will NOT be copied, so the caller is responsible for making sure the data + * is not mutated afterwards. + * + * @param teamName The name of the team the info belongs to. + * @param teamInfo The info of the team. + * @since 4.0.0 + */ + public void saveScoreboardTeam(String teamName, ScoreBoardTeamInfo teamInfo) { + teamsMap.put(teamName, teamInfo); + } + + /** + * Forget info about the given team. + * + * @param teamName The name of the team to forget. + * @since 4.0.0 + */ + public void discardScoreboardTeam(String teamName) { + teamsMap.remove(teamName); + } + + /** + * Refresh all active features of a player. + * + * @since 4.0.0 + */ + public void refreshAll() { + val playerOpt = this.languagePlayer.getPlatformPlayer(); + if (!playerOpt.isPresent()) { + return; + } + val player = playerOpt.get(); + val user = PacketEvents.getAPI().getPlayerManager().getUser(player); + val parser = Triton.get().getMessageParser(); + val cfg = Triton.get().getConfig(); + + if (cfg.isScoreboards()) { + updateScoreboardTeams(user, cfg.getScoreboardSyntax(), parser); + } + } + + private void updateScoreboardTeams(@NotNull User user, @NotNull MainConfig.FeatureSyntax syntax, @NotNull AdventureParser parser) { + for (val entry : teamsMap.entrySet()) { + val info = entry.getValue(); + val infoCopy = new ScoreBoardTeamInfo( + info.getDisplayName(), + info.getPrefix(), + info.getSuffix(), + info.getTagVisibility(), + info.getCollisionRule(), + info.getColor(), + info.getOptionData() + ); + + // displayName + parser.translateComponent( + infoCopy.getDisplayName(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(infoCopy::setDisplayName); + // prefix + parser.translateComponent( + infoCopy.getPrefix(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(infoCopy::setPrefix); + // suffix + parser.translateComponent( + infoCopy.getSuffix(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(infoCopy::setSuffix); + + val packet = new WrapperPlayServerTeams(entry.getKey(), WrapperPlayServerTeams.TeamMode.UPDATE, infoCopy); + user.sendPacketSilently(packet); + } + } +} diff --git a/core/src/main/java/com/rexcantor64/triton/player/PlayerManager.java b/core/src/main/java/com/rexcantor64/triton/player/PlayerManager.java index 7cdb0f9b..f5254b55 100644 --- a/core/src/main/java/com/rexcantor64/triton/player/PlayerManager.java +++ b/core/src/main/java/com/rexcantor64/triton/player/PlayerManager.java @@ -10,7 +10,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -public class PlayerManager implements com.rexcantor64.triton.api.players.PlayerManager { +public class PlayerManager> implements com.rexcantor64.triton.api.players.PlayerManager { private final Map players = new ConcurrentHashMap<>(); diff --git a/core/src/main/java/com/rexcantor64/triton/player/TritonLanguagePlayer.java b/core/src/main/java/com/rexcantor64/triton/player/TritonLanguagePlayer.java new file mode 100644 index 00000000..3d5a2382 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/player/TritonLanguagePlayer.java @@ -0,0 +1,48 @@ +package com.rexcantor64.triton.player; + +import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.api.language.Language; +import com.rexcantor64.triton.api.players.LanguagePlayer; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; + +public abstract class TritonLanguagePlayer

implements LanguagePlayer { + + @Nullable + @Getter + private PacketEventsRefresh packetEventsRefresh; + + protected TritonLanguagePlayer() { + if (Triton.get().getConfig().isUsePacketEvents()) { + this.packetEventsRefresh = new PacketEventsRefresh(this); + } + } + + public abstract boolean isWaitingForClientLocale(); + + public abstract void waitForClientLocale(); + + public abstract @NotNull Optional

getPlatformPlayer(); + + public void refreshAll() { + if (packetEventsRefresh != null) { + Triton.get().runAsync(() -> packetEventsRefresh.refreshAll()); + } + } + + @Override + public Language getLanguage() { + return this.getLang(); + } + + /** + * @return the UUID that is to be used for storage-related operations + */ + public UUID getStorageUniqueId() { + return this.getUUID(); + } +} diff --git a/core/src/main/java/com/rexcantor64/triton/storage/LocalStorage.java b/core/src/main/java/com/rexcantor64/triton/storage/LocalStorage.java index eae8264c..ccdd1033 100644 --- a/core/src/main/java/com/rexcantor64/triton/storage/LocalStorage.java +++ b/core/src/main/java/com/rexcantor64/triton/storage/LocalStorage.java @@ -10,7 +10,7 @@ import com.rexcantor64.triton.language.item.LanguageItem; import com.rexcantor64.triton.language.item.LanguageText; import com.rexcantor64.triton.language.item.serializers.CollectionSerializer; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.utils.FileUtils; import lombok.Cleanup; import lombok.val; @@ -58,7 +58,7 @@ public Language getLanguageFromIp(String ip) { } @Override - public Language getLanguage(LanguagePlayer lp) { + public Language getLanguage(TritonLanguagePlayer lp) { Triton.get().getLogger().logTrace("[Local Storage] Getting language for player %1", lp); String lang = languageMap.get(lp.getStorageUniqueId().toString()); if ((Triton.isProxy() || !Triton.get().getConfig().isBungeecord()) && diff --git a/core/src/main/java/com/rexcantor64/triton/storage/MysqlStorage.java b/core/src/main/java/com/rexcantor64/triton/storage/MysqlStorage.java index 59796ec5..de8136f1 100644 --- a/core/src/main/java/com/rexcantor64/triton/storage/MysqlStorage.java +++ b/core/src/main/java/com/rexcantor64/triton/storage/MysqlStorage.java @@ -11,7 +11,7 @@ import com.rexcantor64.triton.language.item.LanguageText; import com.rexcantor64.triton.language.item.SignLocation; import com.rexcantor64.triton.language.item.TWINData; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import lombok.Cleanup; @@ -126,7 +126,7 @@ public Language getLanguageFromIp(String ip) { } @Override - public Language getLanguage(LanguagePlayer lp) { + public Language getLanguage(TritonLanguagePlayer lp) { Triton.get().getLogger().logTrace("[MySQL Storage] Getting language for player %1", lp); String lang = getValueFromStorage(lp.getStorageUniqueId().toString()); if ((Triton.isProxy() || !Triton.get().getConfig().isBungeecord()) && diff --git a/core/src/main/java/com/rexcantor64/triton/storage/Storage.java b/core/src/main/java/com/rexcantor64/triton/storage/Storage.java index 0230f044..436fe203 100644 --- a/core/src/main/java/com/rexcantor64/triton/storage/Storage.java +++ b/core/src/main/java/com/rexcantor64/triton/storage/Storage.java @@ -5,7 +5,7 @@ import com.rexcantor64.triton.language.item.LanguageItem; import com.rexcantor64.triton.language.item.LanguageSign; import com.rexcantor64.triton.language.item.SignLocation; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import lombok.Getter; import lombok.Setter; import lombok.val; @@ -26,7 +26,7 @@ public abstract class Storage { public abstract Language getLanguageFromIp(String ip); - public abstract Language getLanguage(LanguagePlayer lp); + public abstract Language getLanguage(TritonLanguagePlayer lp); public abstract void setLanguage(@Nullable UUID uuid, @Nullable String ip, @NotNull Language newLanguage); diff --git a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/bridge/BungeeBridgeManager.java b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/bridge/BungeeBridgeManager.java index dd18765a..512c78ef 100644 --- a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/bridge/BungeeBridgeManager.java +++ b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/bridge/BungeeBridgeManager.java @@ -7,7 +7,7 @@ import com.rexcantor64.triton.bungeecord.player.BungeeLanguagePlayer; import com.rexcantor64.triton.commands.handler.CommandEvent; import com.rexcantor64.triton.language.item.SignLocation; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import lombok.NonNull; import lombok.val; import net.md_5.bungee.api.config.ServerInfo; @@ -106,7 +106,7 @@ public void sendPlayerLanguage(BungeeLanguagePlayer lp) { sendPlayerLanguage(lp, lp.getParent().getServer()); } - public void sendPlayerLanguage(@NonNull LanguagePlayer lp, @NonNull Server server) { + public void sendPlayerLanguage(@NonNull TritonLanguagePlayer lp, @NonNull Server server) { Triton.get().getLogger().logTrace("Sending player %1 language to server %2", lp, server.getInfo().getName()); val out = BridgeSerializer.buildPlayerLanguageData(lp); server.sendData("triton:main", out); diff --git a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/player/BungeeLanguagePlayer.java b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/player/BungeeLanguagePlayer.java index dd1d2590..6bac549b 100644 --- a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/player/BungeeLanguagePlayer.java +++ b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/player/BungeeLanguagePlayer.java @@ -6,7 +6,7 @@ import com.rexcantor64.triton.bungeecord.BungeeTriton; import com.rexcantor64.triton.bungeecord.packetinterceptor.BungeeListener; import com.rexcantor64.triton.language.ExecutableCommand; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.utils.SocketUtils; import lombok.val; import net.md_5.bungee.BungeeCord; @@ -17,12 +17,14 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.protocol.packet.Chat; +import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; -public class BungeeLanguagePlayer implements LanguagePlayer { +public class BungeeLanguagePlayer extends TritonLanguagePlayer { private final UUID uuid; private Connection currentConnection; @@ -37,6 +39,7 @@ public class BungeeLanguagePlayer implements LanguagePlayer { private boolean waitingForClientLocale = false; public BungeeLanguagePlayer(UUID parent) { + super(); this.uuid = parent; this.parent = BungeeCord.getInstance().getPlayer(parent); this.currentConnection = this.parent; @@ -49,6 +52,11 @@ public BungeeLanguagePlayer(UUID uuid, Connection connection) { load(); } + @Override + public @NotNull Optional getPlatformPlayer() { + return Optional.of(this.parent); + } + public void setBossbar(UUID uuid, BaseComponent lastBossBar) { bossBars.put(uuid, lastBossBar.duplicate()); } @@ -104,6 +112,7 @@ public void setLang(Language language, boolean sendToSpigot) { } public void refreshAll() { + super.refreshAll(); if (listener == null) return; listener.refreshTab(); if (Triton.get().getConfig().isTab() && lastTabHeader != null && lastTabFooter != null) diff --git a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/EntitiesPacketHandler.java b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/EntitiesPacketHandler.java index 2922b5f1..8b3d8e5e 100644 --- a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/EntitiesPacketHandler.java +++ b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/packetinterceptor/EntitiesPacketHandler.java @@ -14,7 +14,7 @@ import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.rexcantor64.triton.api.wrappers.EntityType; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.spigot.SpigotTriton; import com.rexcantor64.triton.spigot.player.SpigotLanguagePlayer; import com.rexcantor64.triton.spigot.utils.EntityTypeUtils; @@ -951,7 +951,7 @@ private final void removeEntities(Stream ids, Map... maps) * @return The translated legacy text, truncated to 16 characters. */ @Contract("_, null -> null") - private @Nullable String translateAndTruncate(LanguagePlayer languagePlayer, @Nullable String string) { + private @Nullable String translateAndTruncate(TritonLanguagePlayer languagePlayer, @Nullable String string) { if (string == null) { return null; } diff --git a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/player/SpigotLanguagePlayer.java b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/player/SpigotLanguagePlayer.java index 829fea54..7d15415d 100644 --- a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/player/SpigotLanguagePlayer.java +++ b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/player/SpigotLanguagePlayer.java @@ -9,7 +9,7 @@ import com.rexcantor64.triton.api.language.Language; import com.rexcantor64.triton.language.ExecutableCommand; import com.rexcantor64.triton.language.item.SignLocation; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.spigot.SpigotTriton; import com.rexcantor64.triton.spigot.packetinterceptor.ProtocolLibListener; import com.rexcantor64.triton.storage.LocalStorage; @@ -25,6 +25,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashSet; @@ -35,7 +36,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -public class SpigotLanguagePlayer implements LanguagePlayer { +public class SpigotLanguagePlayer extends TritonLanguagePlayer { private final UUID uuid; /** @@ -76,6 +77,7 @@ public class SpigotLanguagePlayer implements LanguagePlayer { private final Map legacySigns = new ConcurrentHashMap<>(); // until 1.19_R1 only public SpigotLanguagePlayer(UUID p) { + super(); uuid = p; proxyUniqueId = this.uuid; load(); @@ -172,6 +174,7 @@ private Optional getInterceptor() { * for the given player, that is, packets are sent to ensure they're updated with the player's language. */ public void refreshAll() { + super.refreshAll(); Triton.get().runAsync(() -> toBukkit().ifPresent(player -> { refreshEntities(); refreshSigns(); @@ -260,6 +263,11 @@ public Optional toBukkit() { return Optional.ofNullable(bukkit); } + @Override + public @NotNull Optional getPlatformPlayer() { + return toBukkit(); + } + @Override public UUID getUUID() { return uuid; diff --git a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/player/VelocityLanguagePlayer.java b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/player/VelocityLanguagePlayer.java index 287b47e9..00686f5c 100644 --- a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/player/VelocityLanguagePlayer.java +++ b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/player/VelocityLanguagePlayer.java @@ -3,7 +3,7 @@ import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.api.language.Language; import com.rexcantor64.triton.language.ExecutableCommand; -import com.rexcantor64.triton.player.LanguagePlayer; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.utils.SocketUtils; import com.rexcantor64.triton.velocity.VelocityTriton; import com.rexcantor64.triton.velocity.packetinterceptor.VelocityNettyEncoder; @@ -27,7 +27,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -public class VelocityLanguagePlayer implements LanguagePlayer { +public class VelocityLanguagePlayer extends TritonLanguagePlayer { @Getter private final Player parent; @@ -46,6 +46,7 @@ public class VelocityLanguagePlayer implements LanguagePlayer { private final RefreshFeatures refresher; public VelocityLanguagePlayer(@NotNull Player parent) { + super(); this.parent = parent; this.refresher = new RefreshFeatures(this); Triton.get().runAsync(this::load); @@ -56,6 +57,11 @@ public static VelocityLanguagePlayer fromUUID(UUID uuid) { return player.map(VelocityLanguagePlayer::new).orElse(null); } + @Override + public @NotNull Optional getPlatformPlayer() { + return Optional.of(this.parent); + } + public void setBossbar(UUID uuid, Component lastBossBar) { bossBars.put(uuid, lastBossBar); } @@ -129,6 +135,7 @@ public void setLang(Language language, boolean sendToSpigot) { } public void refreshAll() { + super.refreshAll(); this.refresher.refreshAll(); } From bb5ab448a7741d9356cfa95c13e24a97d8b7a991 Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Wed, 18 Dec 2024 22:40:36 +0000 Subject: [PATCH 4/5] chore: upgrade packetevents to 2.7.0 See https://github.com/retrooper/packetevents/releases/tag/v2.7.0 --- core/build.gradle | 2 +- .../rexcantor64/triton/dependencies/Dependency.java | 12 ++++++------ triton-velocity/build.gradle | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 011394b8..a3b51c3c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -31,7 +31,7 @@ dependencies { compileOnly 'org.apache.logging.log4j:log4j-core:2.17.1' compileOnly 'org.yaml:snakeyaml:2.0' - compileOnly 'com.github.retrooper:packetevents-api:2.6.0' + compileOnly 'com.github.retrooper:packetevents-api:2.7.0' implementation 'org.slf4j:slf4j-nop:1.7.36' implementation 'com.zaxxer:HikariCP:4.0.3' diff --git a/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java b/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java index b9948c9a..dcbe9968 100644 --- a/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java +++ b/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java @@ -122,8 +122,8 @@ public enum Dependency { PACKET_EVENTS_API( "com{}github{}retrooper", "packetevents-api", - "2.6.0", - "CdIZKtdDgdpw0uQR/tJOsAfZF66/oRRT0JMqSXCLAWs=", + "2.7.0", + "XoTsr+ybxGU/vsYeRgJz2DHaHTFrNl9fSI+pCtIRIqk=", relocate("com{}github{}retrooper{}packetevents", "packetevents{}api"), relocate("io{}github{}retrooper{}packetevents", "packetevents{}impl") // TODO relocate adventure (?) @@ -131,16 +131,16 @@ public enum Dependency { PACKET_EVENTS_NETTY_COMMON( "com{}github{}retrooper", "packetevents-netty-common", - "2.6.0", - "IzZVaD5pI267Q2baymTiFtB9E8119YkhkPZeLXCJLTc=", + "2.7.0", + "cpJZ4KLNP9JRnRma3fi4mg7YrMqVTLzYgo43Sq8REEQ=", relocate("com{}github{}retrooper{}packetevents", "packetevents{}api"), relocate("io{}github{}retrooper{}packetevents", "packetevents{}impl") ), PACKET_EVENTS_VELOCITY( "com{}github{}retrooper", "packetevents-velocity", - "2.6.0", - "93wEevRi6xSrz+mGVMaHO7sLfsH4AAISsmxNigSU4RA=", + "2.7.0", + "B6QY2bgD6c4z7YngG5kc9N02/veLKT6NH9wRiflKNXY=", relocate("com{}github{}retrooper{}packetevents", "packetevents{}api"), relocate("io{}github{}retrooper{}packetevents", "packetevents{}impl") ); diff --git a/triton-velocity/build.gradle b/triton-velocity/build.gradle index bd9a7f93..83b8fac9 100644 --- a/triton-velocity/build.gradle +++ b/triton-velocity/build.gradle @@ -23,7 +23,7 @@ dependencies { implementation 'org.bstats:bstats-velocity:3.1.0' - compileOnly 'com.github.retrooper:packetevents-velocity:2.6.0' + compileOnly 'com.github.retrooper:packetevents-velocity:2.7.0' } shadowJar { From d8a5ff5f978009b4f9ac1b56df1f2ea699f7c3df Mon Sep 17 00:00:00 2001 From: Diogo Correia Date: Wed, 18 Dec 2024 23:04:44 +0000 Subject: [PATCH 5/5] feat(core): handle scoreboard objective packet with packetevents --- .../PacketEventsListener.java | 1 + .../handlers/ScoreboardPacketHandler.java | 39 +++++++++++ .../triton/player/PacketEventsRefresh.java | 69 ++++++++++++++++++- 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java index c88e4fa1..4f0fdec3 100644 --- a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java @@ -39,6 +39,7 @@ public void setupHandlers() { if (config.isScoreboards()) { val scoreboardHandler = new ScoreboardPacketHandler(parser, config); updatedHandlers.put(PacketType.Play.Server.TEAMS, scoreboardHandler::onTeamsPacket); + updatedHandlers.put(PacketType.Play.Server.SCOREBOARD_OBJECTIVE, scoreboardHandler::onObjectivePacket); } receiveHandlers = Collections.unmodifiableMap(updatedHandlers); diff --git a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java index 41ebda03..0f6beed9 100644 --- a/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java @@ -1,6 +1,7 @@ package com.rexcantor64.triton.packetinterceptor.handlers; import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerScoreboardObjective; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; import com.rexcantor64.triton.config.MainConfig; import com.rexcantor64.triton.language.parser.AdventureParser; @@ -96,4 +97,42 @@ public void onTeamsPacket(@NotNull PacketSendEvent event, @NotNull TritonLanguag } } + public void onObjectivePacket(@NotNull PacketSendEvent event, @NotNull TritonLanguagePlayer languagePlayer) { + val packet = new WrapperPlayServerScoreboardObjective(event); + + val action = packet.getMode(); + if (action == WrapperPlayServerScoreboardObjective.ObjectiveMode.REMOVE) { + languagePlayer.getPacketEventsRefresh().discardScoreboardObjective(packet.getName()); + } + + if (action != WrapperPlayServerScoreboardObjective.ObjectiveMode.CREATE && action != WrapperPlayServerScoreboardObjective.ObjectiveMode.UPDATE) { + // we are only interested in new/update actions + return; + } + + val originalDisplayName = packet.getDisplayName(); + + parser.translateComponent( + originalDisplayName, + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + packet.setDisplayName(result); + event.markForReEncode(true); + }); + + if (event.needsReEncode()) { + languagePlayer.getPacketEventsRefresh().saveScoreboardObjective( + packet.getName(), + originalDisplayName, + packet.getRenderType(), + packet.getScoreFormat() + ); + } else { + languagePlayer.getPacketEventsRefresh().discardScoreboardObjective(packet.getName()); + } + } + } diff --git a/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java b/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java index 6034ba93..8d3f611b 100644 --- a/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java +++ b/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java @@ -2,15 +2,20 @@ import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.protocol.score.ScoreFormat; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerScoreboardObjective; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerScoreboardObjective.RenderType; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerTeams.ScoreBoardTeamInfo; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.config.MainConfig; import com.rexcantor64.triton.language.parser.AdventureParser; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.val; import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -18,6 +23,7 @@ @RequiredArgsConstructor public class PacketEventsRefresh { private final Map teamsMap = new ConcurrentHashMap<>(); + private final Map objectivesMap = new ConcurrentHashMap<>(); private final TritonLanguagePlayer languagePlayer; @@ -44,6 +50,33 @@ public void discardScoreboardTeam(String teamName) { teamsMap.remove(teamName); } + /** + * Stores info of an objective for later (i.e., to refresh text when language is changed). + * + * @param objectiveName The name of the objective the info belongs to. + * @param displayName The untranslated display name of the team. + * @param renderType The last render type sent with the packet. + * @param scoreFormat The last score format sent with the packet. + * @since 4.0.0 + */ + public void saveScoreboardObjective(@NotNull String objectiveName, + @NotNull Component displayName, + @Nullable RenderType renderType, + @Nullable ScoreFormat scoreFormat) { + val info = new ScoreboardObjective(displayName, renderType, scoreFormat); + objectivesMap.put(objectiveName, info); + } + + /** + * Forget info about the given objective. + * + * @param objectiveName The name of the objective to forget. + * @since 4.0.0 + */ + public void discardScoreboardObjective(String objectiveName) { + objectivesMap.remove(objectiveName); + } + /** * Refresh all active features of a player. * @@ -60,7 +93,9 @@ public void refreshAll() { val cfg = Triton.get().getConfig(); if (cfg.isScoreboards()) { - updateScoreboardTeams(user, cfg.getScoreboardSyntax(), parser); + val syntax = cfg.getScoreboardSyntax(); + updateScoreboardTeams(user, syntax, parser); + updateScoreboardObjectives(user, syntax, parser); } } @@ -106,4 +141,36 @@ private void updateScoreboardTeams(@NotNull User user, @NotNull MainConfig.Featu user.sendPacketSilently(packet); } } + + private void updateScoreboardObjectives(@NotNull User user, @NotNull MainConfig.FeatureSyntax syntax, @NotNull AdventureParser parser) { + for (val entry : objectivesMap.entrySet()) { + val info = entry.getValue(); + + val packet = new WrapperPlayServerScoreboardObjective( + entry.getKey(), + WrapperPlayServerScoreboardObjective.ObjectiveMode.UPDATE, + info.getDisplayName(), + info.getRenderType(), + info.getScoreFormat() + ); + + parser.translateComponent( + packet.getDisplayName(), + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(packet::setDisplayName); + + user.sendPacketSilently(packet); + } + } + + @RequiredArgsConstructor + @Getter + private static class ScoreboardObjective { + private final Component displayName; + private final RenderType renderType; + private final ScoreFormat scoreFormat; + } }