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.