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("net.luckperms:api:5.5") // Luckperms
|
||||
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 {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.alttd.chat;
|
||||
import com.alttd.chat.chat_web.ChatMessageSender;
|
||||
import com.alttd.chat.chat_web.handlers.WebChannelChatHandler;
|
||||
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.config.Config;
|
||||
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.web.SseSubscribeClient;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -27,6 +29,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
public class ChatPlugin extends JavaPlugin {
|
||||
|
||||
@Getter
|
||||
@@ -50,7 +53,7 @@ public class ChatPlugin extends JavaPlugin {
|
||||
DatabaseConnection.initialize();
|
||||
serverConfig = new ServerConfig(ServerName.getServerName());
|
||||
ChatMessageSender chatMessageSender = new ChatMessageSender(chatLogHandler, chatAPI.getLuckPerms());
|
||||
registerListener(new PlayerListener(webHandler, serverConfig),
|
||||
registerListener(new PlayerListener(serverConfig, webHandler, this),
|
||||
new ChatListener(chatMessageSender),
|
||||
new BookListener(),
|
||||
new ShutdownListener(chatLogHandler, this)
|
||||
@@ -103,6 +106,8 @@ public class ChatPlugin extends JavaPlugin {
|
||||
|
||||
private void registerWebHandlers(ChatMessageSender chatMessageSender, SseSubscribeClient sseSubscribeClient) {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
import com.alttd.chat.ChatPlugin;
|
||||
import com.alttd.chat.config.Config;
|
||||
import com.alttd.chat.config.ServerConfig;
|
||||
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.util.GalaxyUtility;
|
||||
import com.alttd.chat.util.Utility;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
@@ -37,15 +39,12 @@ import java.util.Stack;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PlayerListener implements Listener {
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
private final WebHandler webHandler;
|
||||
|
||||
public PlayerListener(WebHandler webHandler, ServerConfig serverConfig) {
|
||||
this.webHandler = webHandler;
|
||||
this.serverConfig = serverConfig;
|
||||
}
|
||||
private final ChatPlugin chatPlugin;
|
||||
|
||||
@EventHandler
|
||||
private void onPlayerLogin(PlayerJoinEvent event) {
|
||||
@@ -62,7 +61,7 @@ public class PlayerListener implements Listener {
|
||||
|
||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
if (user != null) {
|
||||
updateServerState(event.getPlayer(), user, true);
|
||||
updateServerState(user);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,7 +69,7 @@ public class PlayerListener implements Listener {
|
||||
ChatUser chatUser = new ChatUser(uuid, -1, null);
|
||||
ChatUserManager.addUser(chatUser);
|
||||
Queries.saveUser(chatUser);
|
||||
updateServerState(event.getPlayer(), chatUser, true);
|
||||
updateServerState(chatUser);
|
||||
//TODO load player on other servers with plugin message?
|
||||
}
|
||||
|
||||
@@ -79,25 +78,18 @@ public class PlayerListener implements Listener {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
ChatUser user = ChatUserManager.getChatUser(uuid);
|
||||
ChatUserManager.removeUser(user);
|
||||
updateServerState(event.getPlayer(), user, false);
|
||||
updateServerState(user);
|
||||
}
|
||||
|
||||
private void updateServerState(Player triggerPlayer, ChatUser chatUser, boolean add) {
|
||||
private void updateServerState(ChatUser chatUser) {
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(chatPlugin, () -> {
|
||||
ArrayList<Player> playerList = new ArrayList<>(Bukkit.getOnlinePlayers());
|
||||
if (add) {
|
||||
if (playerList.stream()
|
||||
.filter(player -> player.getUniqueId().equals(triggerPlayer.getUniqueId()))
|
||||
.findFirst()
|
||||
.isEmpty()) {
|
||||
playerList.add(triggerPlayer);
|
||||
}
|
||||
} 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()
|
||||
);
|
||||
}, 20
|
||||
);//20 ticks aka 1 second delay
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true) // untested
|
||||
|
||||
@@ -3,7 +3,8 @@ version: 2.0.0-SNAPSHOT
|
||||
main: com.alttd.chat.ChatPlugin
|
||||
api-version: 1.19
|
||||
authors: [ Destro, Teriuihi ]
|
||||
depend: [LuckPerms]
|
||||
depend: [ LuckPerms, ProtocolLib ]
|
||||
softdepend: [ TAB ]
|
||||
loadbefore:
|
||||
- mcMMO
|
||||
- GriefPrevention
|
||||
|
||||
Reference in New Issue
Block a user