diff --git a/core/build.gradle b/core/build.gradle index 0cd8615d..a3b51c3c 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.7.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..79e8279b 100644 --- a/core/src/main/java/com/rexcantor64/triton/Triton.java +++ b/core/src/main/java/com/rexcantor64/triton/Triton.java @@ -9,12 +9,15 @@ 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.player.LanguagePlayer; +import com.rexcantor64.triton.packetinterceptor.PacketEventsManager; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.player.PlayerManager; import com.rexcantor64.triton.plugin.Platform; import com.rexcantor64.triton.plugin.PluginLoader; @@ -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; @@ -35,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; @@ -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,13 +145,25 @@ 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) - .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/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/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/dependencies/Dependency.java b/core/src/main/java/com/rexcantor64/triton/dependencies/Dependency.java index a85f5a40..dcbe9968 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.7.0", + "XoTsr+ybxGU/vsYeRgJz2DHaHTFrNl9fSI+pCtIRIqk=", + 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.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.7.0", + "B6QY2bgD6c4z7YngG5kc9N02/veLKT6NH9wRiflKNXY=", + 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..4f0fdec3 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketEventsListener.java @@ -0,0 +1,58 @@ +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 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; + +/** + * 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 parser = Triton.get().getMessageParser(); + val config = Triton.get().getConfig(); + val updatedHandlers = new HashMap>>(); + + 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); + } + + @Override + public void onPacketSend(PacketSendEvent event) { + val type = event.getPacketType(); + + val handler = receiveHandlers.get(type); + if (handler != null) { + val languagePlayer = Triton.get().getPlayerManager().get(event.getUser().getUUID()); + handler.accept(event, languagePlayer); + } + } +} 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..0f6beed9 --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/packetinterceptor/handlers/ScoreboardPacketHandler.java @@ -0,0 +1,138 @@ +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; +import com.rexcantor64.triton.player.TritonLanguagePlayer; +import lombok.RequiredArgsConstructor; +import lombok.val; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +@RequiredArgsConstructor +public class ScoreboardPacketHandler { + + 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 TritonLanguagePlayer languagePlayer) { + WrapperPlayServerTeams teams = new WrapperPlayServerTeams(event); + + val action = teams.getTeamMode(); + if (action == WrapperPlayServerTeams.TeamMode.REMOVE) { + languagePlayer.getPacketEventsRefresh().discardScoreboardTeam(teams.getTeamName()); + } + + 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(); + + val originalDisplayName = info.getDisplayName(); + val originalPrefix = info.getPrefix(); + val originalSuffix = info.getSuffix(); + + // display name + parser.translateComponent( + originalDisplayName, + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + info.setDisplayName(result); + event.markForReEncode(true); + }); + // prefix + parser.translateComponent( + originalPrefix, + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + info.setPrefix(result); + event.markForReEncode(true); + }); + // suffix + parser.translateComponent( + originalSuffix, + languagePlayer, + syntax + ) + .getResultOrToRemove(Component::empty) + .ifPresent(result -> { + info.setSuffix(result); + event.markForReEncode(true); + }); + + if (event.needsReEncode()) { + val teamInfoCopy = new WrapperPlayServerTeams.ScoreBoardTeamInfo( + originalDisplayName, + originalPrefix, + originalSuffix, + info.getTagVisibility(), + info.getCollisionRule(), + info.getColor(), + info.getOptionData() + ); + + languagePlayer.getPacketEventsRefresh().saveScoreboardTeam(teams.getTeamName(), teamInfoCopy); + } else { + languagePlayer.getPacketEventsRefresh().discardScoreboardTeam(teams.getTeamName()); + } + } + + 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/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..8d3f611b --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/player/PacketEventsRefresh.java @@ -0,0 +1,176 @@ +package com.rexcantor64.triton.player; + +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; + +@RequiredArgsConstructor +public class PacketEventsRefresh { + private final Map teamsMap = new ConcurrentHashMap<>(); + private final Map objectivesMap = 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); + } + + /** + * 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. + * + * @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()) { + val syntax = cfg.getScoreboardSyntax(); + updateScoreboardTeams(user, syntax, parser); + updateScoreboardObjectives(user, syntax, 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); + } + } + + 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; + } +} 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/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/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-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/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-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..83b8fac9 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.7.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/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(); } 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.