diff --git a/pom.xml b/pom.xml
index 88ad922..76e29b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,10 @@
spigot-repo
https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+ dmulloy2-repo
+ https://repo.dmulloy2.net/repository/public/
+
@@ -43,5 +47,11 @@
24.1.0
compile
+
+ com.comphenix.protocol
+ ProtocolLib
+ 5.3.0
+ provided
+
\ No newline at end of file
diff --git a/reflections.txt b/reflections.txt
new file mode 100644
index 0000000..0d65a28
--- /dev/null
+++ b/reflections.txt
@@ -0,0 +1,8 @@
+usage of:
+
+reflections:
+- ping/F3NameListener net.minecraft.network.protocol.common.custom.BrandPayload
+
+protocollib:
+- ping/F3NameListener sending and intercepting CUSTOM_PAYLOADs to modify brand
+- ping/PingChecker sending and intercepting COOKIE_RESPONSEs to measure ping
diff --git a/src/main/java/eu/m724/tweaks/TweaksPlugin.java b/src/main/java/eu/m724/tweaks/TweaksPlugin.java
index 312199f..588924f 100644
--- a/src/main/java/eu/m724/tweaks/TweaksPlugin.java
+++ b/src/main/java/eu/m724/tweaks/TweaksPlugin.java
@@ -3,6 +3,9 @@ package eu.m724.tweaks;
import eu.m724.tweaks.chat.ChatCommands;
import eu.m724.tweaks.chat.ChatManager;
import eu.m724.tweaks.door.DoorListener;
+import eu.m724.tweaks.ping.F3NameListener;
+import eu.m724.tweaks.ping.PingChecker;
+import eu.m724.tweaks.ping.PingCommands;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Objects;
@@ -18,5 +21,9 @@ public class TweaksPlugin extends JavaPlugin {
Objects.requireNonNull(getCommand("chatmanage")).setExecutor(chatCommands);
getServer().getPluginManager().registerEvents(new DoorListener(), this);
+
+ new F3NameListener(this).init();
+ new PingChecker(this).init();
+ Objects.requireNonNull(getCommand("ping")).setExecutor(new PingCommands());
}
}
diff --git a/src/main/java/eu/m724/tweaks/ping/F3NameListener.java b/src/main/java/eu/m724/tweaks/ping/F3NameListener.java
new file mode 100644
index 0000000..ca16aa4
--- /dev/null
+++ b/src/main/java/eu/m724/tweaks/ping/F3NameListener.java
@@ -0,0 +1,90 @@
+package eu.m724.tweaks.ping;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.events.*;
+import com.comphenix.protocol.reflect.StructureModifier;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+public class F3NameListener {
+ private final Plugin plugin;
+ private final Class> brandPayloadClass;
+ private final Constructor> brandPayloadConstructor;
+
+ public F3NameListener(Plugin plugin) {
+ this.plugin = plugin;
+ try {
+ this.brandPayloadClass = Class.forName("net.minecraft.network.protocol.common.custom.BrandPayload");
+ this.brandPayloadConstructor = brandPayloadClass.getConstructor(String.class);
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ throw new BrandPayloadReflectionException(e);
+ }
+ }
+
+ public void init() {
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ double mspt = PlayerPing.getMillisPerTick();
+ boolean showMspt = mspt > 50.05; // mspt is at least like 50.01 because of some measuring overheads (I think)
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ double ping = PlayerPing.getPingMillis(player);
+ String brand;
+ if (ping == 0) {
+ brand = "wait";
+ } else {
+ if (showMspt) {
+ brand = "%.2f mspt | %.2f ms".formatted(mspt, PlayerPing.getPingMillis(player));
+ } else {
+ brand = "%.2f ms".formatted(PlayerPing.getPingMillis(player));
+ }
+ }
+ changeBrand(player, brand);
+ }
+ }
+ }.runTaskTimerAsynchronously(plugin, 0, 200); // 10 sec
+
+ // this is just to make the server not send a brand on login, it doesn't ever run after login
+ ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter(
+ plugin,
+ ListenerPriority.NORMAL,
+ PacketType.Configuration.Server.CUSTOM_PAYLOAD
+ ) {
+ @Override
+ public void onPacketSending(PacketEvent event) {
+ try {
+ PacketContainer packet = event.getPacket();
+ Object brandPayload = brandPayloadConstructor.newInstance("wait");
+ InternalStructure structure = new InternalStructure(brandPayload, new StructureModifier<>(brandPayloadClass));
+ packet.getStructures().write(0, structure);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new BrandPayloadReflectionException(e);
+ }
+ }
+ });
+ }
+
+ private void changeBrand(Player player, String brand) {
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
+ Object brandPayload;
+ try {
+ brandPayload = brandPayloadConstructor.newInstance(brand);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new BrandPayloadReflectionException(e);
+ }
+ InternalStructure structure = new InternalStructure(brandPayload, new StructureModifier<>(brandPayloadClass));
+ packet.getStructures().write(0, structure);
+ ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet);
+ }
+
+ public static class BrandPayloadReflectionException extends RuntimeException {
+ public BrandPayloadReflectionException(Exception e) {
+ super(e);
+ }
+ }
+}
diff --git a/src/main/java/eu/m724/tweaks/ping/MsptChecker.java b/src/main/java/eu/m724/tweaks/ping/MsptChecker.java
new file mode 100644
index 0000000..c9d03f5
--- /dev/null
+++ b/src/main/java/eu/m724/tweaks/ping/MsptChecker.java
@@ -0,0 +1,19 @@
+package eu.m724.tweaks.ping;
+
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class MsptChecker extends BukkitRunnable {
+ private long lastLoop = 0;
+
+ @Override
+ public void run() {
+ long now = System.nanoTime();
+ PlayerPing.nspt = (now - lastLoop) / 100;
+ lastLoop = now;
+ }
+
+ public void init(Plugin plugin) {
+ this.runTaskTimerAsynchronously(plugin, 0, 100); // 5 secs
+ }
+}
diff --git a/src/main/java/eu/m724/tweaks/ping/PingChecker.java b/src/main/java/eu/m724/tweaks/ping/PingChecker.java
new file mode 100644
index 0000000..5f305e3
--- /dev/null
+++ b/src/main/java/eu/m724/tweaks/ping/PingChecker.java
@@ -0,0 +1,63 @@
+package eu.m724.tweaks.ping;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.events.ListenerPriority;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.wrappers.MinecraftKey;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PingChecker extends BukkitRunnable {
+ private final Plugin plugin;
+
+ // TODO concurrnet too?
+ // this is in nanoseconds
+ private final Map pending = new ConcurrentHashMap<>();
+
+ public PingChecker(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public void init() {
+ ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter(
+ plugin,
+ ListenerPriority.NORMAL,
+ PacketType.Play.Client.COOKIE_RESPONSE
+ ) {
+ @Override
+ public void onPacketReceiving(PacketEvent event) {
+ // below line checks, whether the first (and sole) identifier field in the packet is minecraft:chk_ping
+ if (event.getPacket().getMinecraftKeys().read(0).getKey().equals("chk_ping")) {
+ Player player = event.getPlayer();
+
+ long start = pending.remove(player);
+ PlayerPing.pings.put(player, System.nanoTime() - start);
+ // gotta cancel because the server will kick
+ event.setCancelled(true);
+ }
+ }
+ });
+
+ this.runTaskTimerAsynchronously(plugin, 0, 200); // 10 secs
+ new MsptChecker().init(plugin); // TODO should this be here
+ }
+
+ @Override
+ public void run() {
+ plugin.getServer().getOnlinePlayers().forEach(player -> {
+ if (pending.containsKey(player)) return;
+ pending.put(player, System.nanoTime()); // here or at the bottom? probably doesn't matter
+
+ PacketContainer packet = new PacketContainer(PacketType.Play.Server.COOKIE_REQUEST);
+ packet.getMinecraftKeys().write(0, new MinecraftKey("chk_ping"));
+ ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet);
+ });
+ }
+}
diff --git a/src/main/java/eu/m724/tweaks/ping/PingCommands.java b/src/main/java/eu/m724/tweaks/ping/PingCommands.java
new file mode 100644
index 0000000..eda7e00
--- /dev/null
+++ b/src/main/java/eu/m724/tweaks/ping/PingCommands.java
@@ -0,0 +1,24 @@
+package eu.m724.tweaks.ping;
+
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.ComponentBuilder;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class PingCommands implements CommandExecutor {
+ @Override
+ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
+ if (command.getName().equals("ping")) {
+ Player player = (Player) sender;
+ BaseComponent[] component = new ComponentBuilder("Ping: ").color(ChatColor.GOLD)
+ .append("%.2fms".formatted(PlayerPing.getPingMillis(player))).color(ChatColor.AQUA)
+ .create();
+ player.spigot().sendMessage(component);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/eu/m724/tweaks/ping/PlayerPing.java b/src/main/java/eu/m724/tweaks/ping/PlayerPing.java
new file mode 100644
index 0000000..bbf18c2
--- /dev/null
+++ b/src/main/java/eu/m724/tweaks/ping/PlayerPing.java
@@ -0,0 +1,30 @@
+package eu.m724.tweaks.ping;
+
+import org.bukkit.entity.Player;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PlayerPing {
+ // TODO concurrnet?
+ // in nanos
+ static final Map pings = new ConcurrentHashMap<>();
+ // nanos per tick
+ static long nspt = 0L;
+
+ public static long getPingNanos(Player player) {
+ return pings.getOrDefault(player, -1L);
+ }
+
+ public static double getPingMillis(Player player) {
+ return getPingNanos(player) / 1000000.0; // a mil ns in ms
+ }
+
+ public static long getNanosPerTick() {
+ return nspt;
+ }
+
+ public static double getMillisPerTick() {
+ return nspt / 1000000.0; // a mil ns in ms
+ }
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 07486d6..f87310d 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -3,6 +3,7 @@ version: ${project.version}
main: eu.m724.tweaks.TweaksPlugin
api-version: 1.21.1
+depend: [ProtocolLib]
commands:
chat:
@@ -12,4 +13,6 @@ commands:
chatmanage:
description: Chatroom user management commands
aliases: [cm, crm]
+ ping:
+ description: Your ping