Refactor PlayerListener and add delayed server state updates for web integration.
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
package com.alttd.chat.web.handler_class;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PlayerListState {
|
||||||
|
|
||||||
|
private List<UUID> activePlayers;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ dependencies {
|
|||||||
compileOnly("org.apache.commons:commons-lang3:3.17.0") // needs an alternative, already removed from upstream api and will be removed in server
|
compileOnly("org.apache.commons:commons-lang3:3.17.0") // needs an alternative, already removed from upstream api and will be removed in server
|
||||||
compileOnly("net.luckperms:api:5.5") // Luckperms
|
compileOnly("net.luckperms:api:5.5") // Luckperms
|
||||||
implementation("com.alttd.inventory_gui:InventoryGUI:1.1.5-SNAPSHOT")
|
implementation("com.alttd.inventory_gui:InventoryGUI:1.1.5-SNAPSHOT")
|
||||||
|
compileOnly("com.github.NEZNAMY", "TAB-API", "6.1.2") // TAB
|
||||||
|
compileOnly("net.dmulloy2:ProtocolLib:5.4.0") // ProtocolLib
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.alttd.chat;
|
|||||||
import com.alttd.chat.chat_web.ChatMessageSender;
|
import com.alttd.chat.chat_web.ChatMessageSender;
|
||||||
import com.alttd.chat.chat_web.handlers.WebChannelChatHandler;
|
import com.alttd.chat.chat_web.handlers.WebChannelChatHandler;
|
||||||
import com.alttd.chat.chat_web.handlers.WebChatHandler;
|
import com.alttd.chat.chat_web.handlers.WebChatHandler;
|
||||||
|
import com.alttd.chat.chat_web.handlers.WebUpdatePlayerList;
|
||||||
import com.alttd.chat.commands.*;
|
import com.alttd.chat.commands.*;
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
import com.alttd.chat.config.ServerConfig;
|
import com.alttd.chat.config.ServerConfig;
|
||||||
@@ -20,6 +21,7 @@ import com.alttd.chat.util.ServerName;
|
|||||||
import com.alttd.chat.util.Utility;
|
import com.alttd.chat.util.Utility;
|
||||||
import com.alttd.chat.web.SseSubscribeClient;
|
import com.alttd.chat.web.SseSubscribeClient;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@@ -27,6 +29,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ChatPlugin extends JavaPlugin {
|
public class ChatPlugin extends JavaPlugin {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -50,7 +53,7 @@ public class ChatPlugin extends JavaPlugin {
|
|||||||
DatabaseConnection.initialize();
|
DatabaseConnection.initialize();
|
||||||
serverConfig = new ServerConfig(ServerName.getServerName());
|
serverConfig = new ServerConfig(ServerName.getServerName());
|
||||||
ChatMessageSender chatMessageSender = new ChatMessageSender(chatLogHandler, chatAPI.getLuckPerms());
|
ChatMessageSender chatMessageSender = new ChatMessageSender(chatLogHandler, chatAPI.getLuckPerms());
|
||||||
registerListener(new PlayerListener(webHandler, serverConfig),
|
registerListener(new PlayerListener(serverConfig, webHandler, this),
|
||||||
new ChatListener(chatMessageSender),
|
new ChatListener(chatMessageSender),
|
||||||
new BookListener(),
|
new BookListener(),
|
||||||
new ShutdownListener(chatLogHandler, this)
|
new ShutdownListener(chatLogHandler, this)
|
||||||
@@ -103,6 +106,8 @@ public class ChatPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
private void registerWebHandlers(ChatMessageSender chatMessageSender, SseSubscribeClient sseSubscribeClient) {
|
private void registerWebHandlers(ChatMessageSender chatMessageSender, SseSubscribeClient sseSubscribeClient) {
|
||||||
sseSubscribeClient.register("web_chat", new WebChatHandler(chatMessageSender));
|
sseSubscribeClient.register("web_chat", new WebChatHandler(chatMessageSender));
|
||||||
|
//TODO [Stijn] [2026-08-22]: send to velocity as well so it can add the players to the tab command
|
||||||
|
sseSubscribeClient.register("player_state", new WebUpdatePlayerList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package com.alttd.chat.chat_web.handlers;
|
||||||
|
|
||||||
|
import com.alttd.chat.managers.ChatUserManager;
|
||||||
|
import com.alttd.chat.objects.ChatUser;
|
||||||
|
import com.alttd.chat.web.WebHandler;
|
||||||
|
import com.alttd.chat.web.handler_class.PlayerListState;
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.wrappers.EnumWrappers;
|
||||||
|
import com.comphenix.protocol.wrappers.PlayerInfoData;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.neznamy.tab.api.TabAPI;
|
||||||
|
import me.neznamy.tab.api.TabPlayer;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class WebUpdatePlayerList implements WebHandler<PlayerListState> {
|
||||||
|
private final ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||||
|
private final Map<UUID, FakeTabPlayer> registeredPlayers = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<PlayerListState> type() {
|
||||||
|
return PlayerListState.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void handle(PlayerListState playerListState) {
|
||||||
|
Set<UUID> bukkitOnline = new HashSet<>();
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
bukkitOnline.add(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<UUID, FakeTabPlayer> desiredPlayers = createDesiredPlayers(playerListState, bukkitOnline);
|
||||||
|
|
||||||
|
removeInactiveOrOnlinePlayers(desiredPlayers);
|
||||||
|
|
||||||
|
createNewPlayers(desiredPlayers);
|
||||||
|
|
||||||
|
updateTab(desiredPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateTab(Map<UUID, FakeTabPlayer> desiredPlayers) {
|
||||||
|
TabAPI tabAPI;
|
||||||
|
try {
|
||||||
|
tabAPI = TabAPI.getInstance();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
log.error("Failed to get TabAPI instance", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (TabPlayer onlinePlayer : tabAPI.getOnlinePlayers()) {
|
||||||
|
if (!desiredPlayers.containsKey(onlinePlayer.getUniqueId())) {
|
||||||
|
if (onlinePlayer.getGroup().equals("web")) {
|
||||||
|
onlinePlayer.setTemporaryGroup(null);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (onlinePlayer.getGroup().equals("web")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
onlinePlayer.setTemporaryGroup("web");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull Map<UUID, FakeTabPlayer> createDesiredPlayers(PlayerListState playerListState, Set<UUID> bukkitOnline) {
|
||||||
|
Map<UUID, FakeTabPlayer> desiredPlayers = new LinkedHashMap<>();
|
||||||
|
for (ChatUser chatUser : playerListState.getActivePlayers()
|
||||||
|
.stream()
|
||||||
|
.map(ChatUserManager::getChatUser)
|
||||||
|
.toList()) {
|
||||||
|
if (chatUser == null || chatUser.getUuid() == null) {
|
||||||
|
log.warn("PlayerListState contained a null ChatUser or ChatUser with null UUID");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID uuid = chatUser.getUuid();
|
||||||
|
|
||||||
|
if (bukkitOnline.contains(uuid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
desiredPlayers.putIfAbsent(
|
||||||
|
uuid,
|
||||||
|
new FakeTabPlayer(uuid, getOfflinePlayerName(uuid))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return desiredPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewPlayers(Map<UUID, FakeTabPlayer> desiredPlayers) {
|
||||||
|
for (FakeTabPlayer desired : desiredPlayers.values()) {
|
||||||
|
FakeTabPlayer existing = registeredPlayers.get(desired.uuid());
|
||||||
|
|
||||||
|
if (existing == null) {
|
||||||
|
addFakePlayer(desired, Bukkit.getOnlinePlayers());
|
||||||
|
registeredPlayers.put(desired.uuid(), desired);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFakePlayers(Set.of(desired.uuid()), Bukkit.getOnlinePlayers());
|
||||||
|
addFakePlayer(desired, Bukkit.getOnlinePlayers());
|
||||||
|
registeredPlayers.put(desired.uuid(), desired);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeInactiveOrOnlinePlayers(Map<UUID, FakeTabPlayer> desiredPlayers) {
|
||||||
|
Set<UUID> toRemove = new HashSet<>(registeredPlayers.keySet());
|
||||||
|
toRemove.removeAll(desiredPlayers.keySet());
|
||||||
|
|
||||||
|
if (!toRemove.isEmpty()) {
|
||||||
|
removeFakePlayers(toRemove, Bukkit.getOnlinePlayers());
|
||||||
|
toRemove.forEach(registeredPlayers::remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFakePlayer(FakeTabPlayer fakePlayer, Collection<? extends Player> viewers) {
|
||||||
|
PacketContainer packet = protocolManager.createPacket(PacketType.Play.Server.PLAYER_INFO);
|
||||||
|
|
||||||
|
packet.getPlayerInfoActions().write(
|
||||||
|
0,
|
||||||
|
EnumSet.of(
|
||||||
|
EnumWrappers.PlayerInfoAction.ADD_PLAYER,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_LISTED,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_DISPLAY_NAME,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_GAME_MODE,
|
||||||
|
EnumWrappers.PlayerInfoAction.UPDATE_LATENCY
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
WrappedGameProfile profile = new WrappedGameProfile(
|
||||||
|
fakePlayer.uuid(),
|
||||||
|
fakePlayer.name()
|
||||||
|
);
|
||||||
|
|
||||||
|
PlayerInfoData data = new PlayerInfoData(
|
||||||
|
fakePlayer.uuid(),
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
EnumWrappers.NativeGameMode.SURVIVAL,
|
||||||
|
profile,
|
||||||
|
WrappedChatComponent.fromText(fakePlayer.name())
|
||||||
|
);
|
||||||
|
|
||||||
|
writePlayerInfoData(packet, List.of(data));
|
||||||
|
send(packet, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFakePlayers(Set<UUID> uuids, Collection<? extends Player> viewers) {
|
||||||
|
if (uuids.isEmpty() || viewers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketContainer packet = protocolManager.createPacket(PacketType.Play.Server.PLAYER_INFO_REMOVE);
|
||||||
|
packet.getUUIDLists().write(0, new ArrayList<>(uuids));
|
||||||
|
send(packet, viewers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writePlayerInfoData(PacketContainer packet, List<PlayerInfoData> data) {
|
||||||
|
StructureModifier<List<PlayerInfoData>> modifier = packet.getPlayerInfoDataLists();
|
||||||
|
if (modifier.size() == 0) {
|
||||||
|
throw new IllegalStateException("PLAYER_INFO packet has no PlayerInfoData list");
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier.write(modifier.size() - 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(PacketContainer packet, Collection<? extends Player> viewers) {
|
||||||
|
for (Player viewer : viewers) {
|
||||||
|
protocolManager.sendServerPacket(viewer, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getOfflinePlayerName(UUID uuid) {
|
||||||
|
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||||
|
return offlinePlayer.getName() == null ? offlinePlayer.getUniqueId().toString() : offlinePlayer.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FakeTabPlayer(UUID uuid, String name) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.alttd.chat.listeners;
|
package com.alttd.chat.listeners;
|
||||||
|
|
||||||
|
import com.alttd.chat.ChatPlugin;
|
||||||
import com.alttd.chat.config.Config;
|
import com.alttd.chat.config.Config;
|
||||||
import com.alttd.chat.config.ServerConfig;
|
import com.alttd.chat.config.ServerConfig;
|
||||||
import com.alttd.chat.database.Queries;
|
import com.alttd.chat.database.Queries;
|
||||||
@@ -11,6 +12,7 @@ import com.alttd.chat.objects.Toggleable;
|
|||||||
import com.alttd.chat.objects.chat_log.WebHandler;
|
import com.alttd.chat.objects.chat_log.WebHandler;
|
||||||
import com.alttd.chat.util.GalaxyUtility;
|
import com.alttd.chat.util.GalaxyUtility;
|
||||||
import com.alttd.chat.util.Utility;
|
import com.alttd.chat.util.Utility;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextReplacementConfig;
|
import net.kyori.adventure.text.TextReplacementConfig;
|
||||||
import net.kyori.adventure.text.format.Style;
|
import net.kyori.adventure.text.format.Style;
|
||||||
@@ -37,15 +39,12 @@ import java.util.Stack;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class PlayerListener implements Listener {
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
private final ServerConfig serverConfig;
|
private final ServerConfig serverConfig;
|
||||||
private final WebHandler webHandler;
|
private final WebHandler webHandler;
|
||||||
|
private final ChatPlugin chatPlugin;
|
||||||
public PlayerListener(WebHandler webHandler, ServerConfig serverConfig) {
|
|
||||||
this.webHandler = webHandler;
|
|
||||||
this.serverConfig = serverConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
private void onPlayerLogin(PlayerJoinEvent event) {
|
private void onPlayerLogin(PlayerJoinEvent event) {
|
||||||
@@ -62,7 +61,7 @@ public class PlayerListener implements Listener {
|
|||||||
|
|
||||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
updateServerState(event.getPlayer(), user, true);
|
updateServerState(user);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ public class PlayerListener implements Listener {
|
|||||||
ChatUser chatUser = new ChatUser(uuid, -1, null);
|
ChatUser chatUser = new ChatUser(uuid, -1, null);
|
||||||
ChatUserManager.addUser(chatUser);
|
ChatUserManager.addUser(chatUser);
|
||||||
Queries.saveUser(chatUser);
|
Queries.saveUser(chatUser);
|
||||||
updateServerState(event.getPlayer(), chatUser, true);
|
updateServerState(chatUser);
|
||||||
//TODO load player on other servers with plugin message?
|
//TODO load player on other servers with plugin message?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,25 +78,18 @@ public class PlayerListener implements Listener {
|
|||||||
UUID uuid = event.getPlayer().getUniqueId();
|
UUID uuid = event.getPlayer().getUniqueId();
|
||||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||||
ChatUserManager.removeUser(user);
|
ChatUserManager.removeUser(user);
|
||||||
updateServerState(event.getPlayer(), user, false);
|
updateServerState(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateServerState(Player triggerPlayer, ChatUser chatUser, boolean add) {
|
private void updateServerState(ChatUser chatUser) {
|
||||||
ArrayList<Player> playerList = new ArrayList<>(Bukkit.getOnlinePlayers());
|
Bukkit.getScheduler().runTaskLaterAsynchronously(chatPlugin, () -> {
|
||||||
if (add) {
|
ArrayList<Player> playerList = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||||
if (playerList.stream()
|
String name = Bukkit.getServer().getName();
|
||||||
.filter(player -> player.getUniqueId().equals(triggerPlayer.getUniqueId()))
|
webHandler.updateServerState(name,
|
||||||
.findFirst()
|
playerList.stream().map(player -> WebPlayerMapper.fromPlayer(player, chatUser)).toList()
|
||||||
.isEmpty()) {
|
);
|
||||||
playerList.add(triggerPlayer);
|
}, 20
|
||||||
}
|
);//20 ticks aka 1 second delay
|
||||||
} else {
|
|
||||||
playerList.removeIf(player -> player.getUniqueId().equals(triggerPlayer.getUniqueId()));
|
|
||||||
}
|
|
||||||
String name = Bukkit.getServer().getName();
|
|
||||||
webHandler.updateServerState(name,
|
|
||||||
playerList.stream().map(player -> WebPlayerMapper.fromPlayer(player, chatUser)).toList()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true) // untested
|
@EventHandler(ignoreCancelled = true) // untested
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ name: ChatPlugin
|
|||||||
version: 2.0.0-SNAPSHOT
|
version: 2.0.0-SNAPSHOT
|
||||||
main: com.alttd.chat.ChatPlugin
|
main: com.alttd.chat.ChatPlugin
|
||||||
api-version: 1.19
|
api-version: 1.19
|
||||||
authors: [Destro, Teriuihi]
|
authors: [ Destro, Teriuihi ]
|
||||||
depend: [LuckPerms]
|
depend: [ LuckPerms, ProtocolLib ]
|
||||||
|
softdepend: [ TAB ]
|
||||||
loadbefore:
|
loadbefore:
|
||||||
- mcMMO
|
- mcMMO
|
||||||
- GriefPrevention
|
- GriefPrevention
|
||||||
@@ -18,7 +19,7 @@ commands:
|
|||||||
aliases: togglegc
|
aliases: togglegc
|
||||||
message:
|
message:
|
||||||
permission: command.chat.message
|
permission: command.chat.message
|
||||||
aliases: [msg, tell]
|
aliases: [ msg, tell ]
|
||||||
reply:
|
reply:
|
||||||
permission: command.chat.message
|
permission: command.chat.message
|
||||||
aliases: r
|
aliases: r
|
||||||
@@ -39,6 +40,6 @@ commands:
|
|||||||
permission: chat.command.clear-chat
|
permission: chat.command.clear-chat
|
||||||
emoteslist:
|
emoteslist:
|
||||||
permission: chat.command.emoteslist
|
permission: chat.command.emoteslist
|
||||||
aliases: [emotes]
|
aliases: [ emotes ]
|
||||||
nick:
|
nick:
|
||||||
permission: chat.command.nick
|
permission: chat.command.nick
|
||||||
Reference in New Issue
Block a user