From f2ed575f8ce34a504a4ecf5dc1e17bc421db01ab Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 18 Jun 2022 13:17:23 +0200 Subject: [PATCH] Untested full tablist Signed-off-by: Lixfel --- .../bungeecore/listeners/TablistManager.java | 335 ------------------ .../network/handlers/FightInfoHandler.java | 3 +- .../steamwar/bungeecore/tablist/Tablist.java | 240 +++++++++++++ .../bungeecore/tablist/TablistGroup.java | 45 +++ .../bungeecore/tablist/TablistListener.java | 102 ------ .../bungeecore/tablist/TablistManager.java | 163 ++------- .../bungeecore/tablist/TablistPart.java | 75 ++++ .../bungeecore/tablist/TablistServer.java | 91 +++++ 8 files changed, 487 insertions(+), 567 deletions(-) delete mode 100644 src/de/steamwar/bungeecore/listeners/TablistManager.java create mode 100644 src/de/steamwar/bungeecore/tablist/Tablist.java create mode 100644 src/de/steamwar/bungeecore/tablist/TablistGroup.java delete mode 100644 src/de/steamwar/bungeecore/tablist/TablistListener.java create mode 100644 src/de/steamwar/bungeecore/tablist/TablistPart.java create mode 100644 src/de/steamwar/bungeecore/tablist/TablistServer.java diff --git a/src/de/steamwar/bungeecore/listeners/TablistManager.java b/src/de/steamwar/bungeecore/listeners/TablistManager.java deleted file mode 100644 index e1c3325..0000000 --- a/src/de/steamwar/bungeecore/listeners/TablistManager.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - This file is a part of the SteamWar software. - - Copyright (C) 2020 SteamWar.de-Serverteam - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package de.steamwar.bungeecore.listeners; - -import codecrafter47.bungeetablistplus.api.bungee.BungeeTabListPlusAPI; -import codecrafter47.bungeetablistplus.api.bungee.Icon; -import codecrafter47.bungeetablistplus.tablist.DefaultCustomTablist; -import de.steamwar.bungeecore.BungeeCore; -import de.steamwar.bungeecore.Message; -import de.steamwar.bungeecore.Servertype; -import de.steamwar.bungeecore.Subserver; -import de.steamwar.bungeecore.sql.SteamwarUser; -import de.steamwar.bungeecore.sql.UserGroup; -import net.md_5.bungee.BungeeCord; -import de.steamwar.network.packets.common.FightInfoPacket; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.config.ServerInfo; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.PlayerDisconnectEvent; -import net.md_5.bungee.api.event.PostLoginEvent; -import net.md_5.bungee.event.EventHandler; - -import javax.imageio.ImageIO; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.TimeUnit; - -public class TablistManager extends BasicListener { - - private static final Map tablists = new HashMap<>(); - - @EventHandler - public synchronized void onJoin(PostLoginEvent e){ - BungeeCord.getInstance().getScheduler().schedule(BungeeCore.get(), () -> { - if (e.getPlayer().isConnected()) { - tablists.put(e.getPlayer(), new Tablist(e.getPlayer())); - } - }, 1, TimeUnit.SECONDS); - } - - @EventHandler - public synchronized void onLeave(PlayerDisconnectEvent e){ - tablists.remove(e.getPlayer()); - } - - - private static final Map fightInfos = new HashMap<>(); - - public static synchronized void newFightInfo(ServerInfo info, FightInfoPacket packet) { - fightInfos.put(info, packet); - fightInfos.keySet().removeIf(serverInfo -> serverInfo.getPlayers().isEmpty()); - } - - - private static Icon darkGray; - private static Icon gray; - - private int seconds = 0; - private int size; - private TablistGroup tablist; - - public TablistManager(){ - ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), this::updateTablist, 1, 1, TimeUnit.SECONDS); - try{ - BungeeTabListPlusAPI.createIcon(ImageIO.read(new File("/configs/BungeeTabListPlus/heads/colors/dark_gray.png")), (icon) -> darkGray = icon); - BungeeTabListPlusAPI.createIcon(ImageIO.read(new File("/configs/BungeeTabListPlus/heads/colors/gray.png")), (icon) -> gray = icon); - }catch(IOException e){ - throw new SecurityException("Could not load icons", e); - } - - ProxyServer.getInstance().getPlayers().forEach(p -> tablists.put(p, new Tablist(p))); - } - - private synchronized void updateTablist(){ - //Calculate server-player-map - tablist = new TablistGroup(true, ""); - TablistGroup bau = new TablistGroup(false, "Bau"); - for (ServerInfo server : new ArrayList<>(ProxyServer.getInstance().getServers().values())){ - if(server.getPlayers().isEmpty()) - continue; - - Subserver subserver = Subserver.getSubserver(server); - if(subserver != null && subserver.getType() == Servertype.BAUSERVER) - bau.addSubTablist(new TablistServer(server)); - else if(fightInfos.containsKey(server)) - tablist.addSubTablist(new TablistServer(server, fightInfos.get(server))); - else - tablist.addSubTablist(new TablistServer(server)); - } - if(bau.size() > 0) - tablist.addSubTablist(bau); - - size = (int) Math.ceil((tablist.size() - 1) / 20.0); - tablists.values().forEach(Tablist::refresh); - seconds++; - } - - private class Tablist extends DefaultCustomTablist { - private final ProxiedPlayer player; - private int pos = 0; - - private Tablist(ProxiedPlayer player){ - this.player = player; - BungeeTabListPlusAPI.setCustomTabList(player, this); - } - - private String header(){ - int phase = (seconds % 16) / 3; - switch(phase){ - case 0: - return Message.parse("TABLIST_PHASE_1", player); - case 1: - return Message.parse("TABLIST_PHASE_2", player); - case 2: - default: - return Message.parse("TABLIST_PHASE_DEFAULT", player); - } - } - - private String ping(){ - int ping = player.getPing(); - if(ping < 50){ - return "§a" + ping; - }else if(ping < 150){ - return "§e" + ping; - }else{ - return "§c" + ping; - } - } - - private void refresh(){ - if (player.getServer() == null) { - return; - } - pos = 0; - setHeader(header()); - setFooter(Message.parse("TABLIST_FOOTER", player, player.getServer().getInfo().getName(), ping(), ProxyServer.getInstance().getPlayers().size())); - int currentSize = size > 4 ? tablist.slimSize(player) : size; - setSize(currentSize, 20); - - tablist.print(this, size > 4); - - while (pos < currentSize*20){ - setSlot(darkGray, "", 1000); - } - } - - private void setSlot(Icon icon, String name, int ping){ - if(pos / 20 >= getColumns()) - return; - - setSlot(pos % 20, pos / 20, icon, name, ping); - pos++; - } - } - - private interface TablistPart { - int size(); - int slimSize(ProxiedPlayer viewer); - String name(); - void print(Tablist viewer, boolean slim); - } - - private static class TablistGroup implements TablistPart { - - private final boolean withHeaders; - private final String orderName; - private final List subTablists = new ArrayList<>(); - - private TablistGroup(boolean withHeaders, String orderName) { - this.withHeaders = withHeaders; - this.orderName = orderName; - } - - private void addSubTablist(TablistPart tablist){ - subTablists.add(tablist); - subTablists.sort((t1, t2) -> t1.name().compareToIgnoreCase(t2.name())); - } - - @Override - public int size() { - return slimSize(null); - } - - @Override - public int slimSize(ProxiedPlayer viewer) { - return subTablists.stream().mapToInt(tPart -> viewer == null ? tPart.size() : tPart.slimSize(viewer)).map(size -> { - size += withHeaders ? 1 : 0; // Space for header - size += withHeaders && size > 1 ? 1 : 0; // Space for footer - return size; - }).sum(); - } - - @Override - public String name() { - return orderName; - } - - @Override - public void print(Tablist viewer, boolean slim) { - for (int i = 0; i < subTablists.size(); i++) { - TablistPart tPart = subTablists.get(i); - String name = tPart.name(); - if (name.equals("Bau")) { - name = Message.parse("TABLIST_BAU", viewer.player); - } - boolean withoutFooter = i == subTablists.size() - 1; - if (withHeaders) { - if (slim) { - int slimSize = tPart.slimSize(viewer.player); - int size = tPart.size(); - if (size == slimSize) { - viewer.setSlot(gray, "§7§l" + name, 1000); - } else if (slimSize == 0) { - viewer.setSlot(gray, "§7§l" + name + " §8(§7" + size + "§8)", 1000); - withoutFooter = true; - } else { - viewer.setSlot(gray, "§7§l" + name + " §8(§7+" + (size - slimSize) + "§8)", 1000); - } - } else { - viewer.setSlot(gray, "§7§l" + name, 1000); - } - } - tPart.print(viewer, slim); - if (withHeaders && !withoutFooter) { - viewer.setSlot(darkGray, "", 1000); - } - } - } - } - - private static class TablistServer implements TablistPart { - private static class TablistPlayer { - private final ProxiedPlayer player; - private final String defaultName; - - private TablistPlayer(ProxiedPlayer player, String defaultName) { - this.player = player; - this.defaultName = defaultName; - } - } - private final List players = new ArrayList<>(); - private final ServerInfo info; - private final Subserver subserver; - - private TablistServer(ServerInfo info, FightInfoPacket packet){ - this.info = info; - subserver = Subserver.getSubserver(info); - Collection onlinePlayers = info.getPlayers(); - addPlayers(packet.getBlueName().substring(0, 2), packet.getBluePlayers(), onlinePlayers); - addPlayers(packet.getRedName().substring(0, 2), packet.getRedPlayers(), onlinePlayers); - addPlayers("§7", packet.getSpectators(), onlinePlayers); - } - - private void addPlayers(String prefix, List teamPlayers, Collection onlinePlayers){ - teamPlayers.stream().map(SteamwarUser::get).map( - user -> onlinePlayers.stream().filter(player -> player.getUniqueId().equals(user.getUuid())).findAny() - ).filter(Optional::isPresent).map(Optional::get).sorted( - (p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName()) - ).forEachOrdered(player -> players.add(new TablistPlayer(player, prefix + player.getName()))); - } - - private TablistServer(ServerInfo info) { - this.info = info; - subserver = Subserver.getSubserver(info); - info.getPlayers().forEach(player -> players.add(new TablistPlayer(player, SteamwarUser.get(player.getUniqueId()).getUserGroup().getColorCode() + player.getName()))); - players.sort((tp1, tp2) -> tp1.player.getName().compareToIgnoreCase(tp2.player.getName())); - } - - private boolean displaySlim(ProxiedPlayer viewer, ProxiedPlayer player){ - if(viewer.getServer().getInfo() == info) - return true; - - if(subserver != null && subserver.getType() == Servertype.ARENA && info.getPlayers().size() == 1) - return true; - - SteamwarUser user = SteamwarUser.get(player); - if(user.getUserGroup() != UserGroup.Member) - return true; - - return user.getTeam() != 0 && SteamwarUser.get(viewer).getTeam() == user.getTeam(); - } - - @Override - public int size() { - return players.size(); - } - - @Override - public int slimSize(ProxiedPlayer viewer) { - if(viewer.getServer().getInfo() == info) - return size(); - - return players.stream().mapToInt(player -> displaySlim(viewer, player.player) ? 1 : 0).sum(); - } - - @Override - public String name() { - return info.getName(); - } - - @Override - public void print(Tablist viewer, boolean slim) { - boolean sameServer = viewer.player.getServer().getInfo() == info; - - SteamwarUser user = SteamwarUser.get(viewer.player.getUniqueId()); - for(TablistPlayer player : players){ - if(slim && !displaySlim(viewer.player, player.player)) - continue; - - int ping = sameServer ? 1 : 1000; - String name = player.defaultName.startsWith("§7") && user.getTeam() != 0 && user.getTeam() == SteamwarUser.get(player.player.getUniqueId()).getTeam() ? "§f" + player.player.getName() : player.defaultName; - viewer.setSlot(BungeeTabListPlusAPI.getIconFromPlayer(player.player), name, ping); - } - } - } -} diff --git a/src/de/steamwar/bungeecore/network/handlers/FightInfoHandler.java b/src/de/steamwar/bungeecore/network/handlers/FightInfoHandler.java index 2c98de0..3f4bff2 100644 --- a/src/de/steamwar/bungeecore/network/handlers/FightInfoHandler.java +++ b/src/de/steamwar/bungeecore/network/handlers/FightInfoHandler.java @@ -19,10 +19,9 @@ package de.steamwar.bungeecore.network.handlers; -import de.steamwar.bungeecore.listeners.TablistManager; -import de.steamwar.bungeecore.network.NetworkReceiver; import de.steamwar.bungeecore.network.NetworkSender; import de.steamwar.bungeecore.network.ServerMetaInfo; +import de.steamwar.bungeecore.tablist.TablistManager; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.common.FightInfoPacket; import net.md_5.bungee.api.config.ServerInfo; diff --git a/src/de/steamwar/bungeecore/tablist/Tablist.java b/src/de/steamwar/bungeecore/tablist/Tablist.java new file mode 100644 index 0000000..426cb23 --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/Tablist.java @@ -0,0 +1,240 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bungeecore.tablist; + +import de.steamwar.messages.ChatSender; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import net.md_5.bungee.ServerConnection; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.chat.ComponentSerializer; +import net.md_5.bungee.netty.PipelineUtils; +import net.md_5.bungee.protocol.DefinedPacket; +import net.md_5.bungee.protocol.PacketWrapper; +import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; +import net.md_5.bungee.protocol.packet.PlayerListItem; +import net.md_5.bungee.protocol.packet.Team; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Tablist extends MessageToMessageDecoder { + + private static final UUID[] uuids = IntStream.range(0, 80).mapToObj(i -> UUID.randomUUID()).toArray(UUID[]::new); + private static final String[] names = IntStream.range(0, 80).mapToObj(i -> " »SW« " + i).toArray(String[]::new); + private static final String TAB_TEAM = "»SW-Tab"; + private static final Team teamPacket = new Team(TAB_TEAM, (byte) 0, ComponentSerializer.toString(TextComponent.fromLegacyText("")), ComponentSerializer.toString(TextComponent.fromLegacyText("")), ComponentSerializer.toString(TextComponent.fromLegacyText("")), "never", "never", 21, (byte)0x00, names); + + private final Map directlySent = new HashMap<>(); //TODO Persist over softreload + private final Set npcs = new HashSet<>(); + private final List current = new ArrayList<>(); + + private final ProxiedPlayer player; + private final ChatSender viewer; + private ServerConnection connection; + + public Tablist(ProxiedPlayer player) { + this.player = player; + this.viewer = ChatSender.of(player); + onServerSwitch(); + } + + public void update(TablistPart global, int seconds) { + if (player.getServer() == null) + return; + + player.unsafe().sendPacket(new PlayerListHeaderFooter( + ComponentSerializer.toString(header(viewer, seconds)), + ComponentSerializer.toString(viewer.parse(false, "TABLIST_FOOTER", connection.getInfo().getName(), ping(), ProxyServer.getInstance().getPlayers().size())) + )); + + List tablist = new ArrayList<>(); + List direct = new ArrayList<>(); + global.print(viewer, player, tablist, direct); + + // NPC handling + List addNpc = new ArrayList<>(); + List removeNpc = new ArrayList<>(); + for (TablistPart.Item item : direct) { + if(npcs.remove(item.getUuid())) + removeNpc.add(directlySent.get(item.getUuid()).getUsername()); + + if(!directlySent.containsKey(item.getUuid())) + tablist.add(0, item); + } + Set nonNPCs = direct.stream().map(TablistPart.Item::getUuid).collect(Collectors.toSet()); + for(PlayerListItem.Item item : directlySent.values()) { + if(!nonNPCs.contains(item.getUuid()) && !npcs.contains(item.getUuid())) + addNpc.add(item.getUsername()); + } + sendNpcPacket(addNpc, false); + sendNpcPacket(removeNpc, true); + + // Main list handling + int i = 0; + List add = new ArrayList<>(); + List update = new ArrayList<>(); + for (; i < tablist.size() && i < 80; i++) { + PlayerListItem.Item tabItem; + if(current.size() > i) { + tabItem = current.get(i); + } else { + tabItem = new PlayerListItem.Item(); + tabItem.setUuid(uuids[i]); + tabItem.setUsername(names[i]); + tabItem.setGamemode(1); + tabItem.setPing(1000); + current.add(tabItem); + } + + TablistPart.Item item = tablist.get(i); + if(!Arrays.equals(tabItem.getProperties(), item.getProperties())) { + tabItem.setProperties(item.getProperties()); + tabItem.setDisplayName(item.getDisplayName()); + add.add(tabItem); + } else if(!item.getDisplayName().equals(tabItem.getDisplayName())) { + tabItem.setDisplayName(item.getDisplayName()); + update.add(tabItem); + } + } + sendTabPacket(add, PlayerListItem.Action.ADD_PLAYER); + sendTabPacket(update, PlayerListItem.Action.UPDATE_DISPLAY_NAME); + + // Excess removal + List remove = new ArrayList<>(); + while(i < current.size()) { + remove.add(current.remove(i)); + } + sendTabPacket(remove, PlayerListItem.Action.REMOVE_PLAYER); + } + + public void onServerSwitch() { + connection = (ServerConnection) player.getServer(); + directlySent.clear(); + sendNpcPacket(npcs.stream().map(npc -> directlySent.get(npc).getUsername()).collect(Collectors.toList()), true); + npcs.clear(); + + if(connection != null) { + connection.getCh().getHandle().pipeline().addBefore(PipelineUtils.BOSS_HANDLER, "steamwar-tablist", this); + player.unsafe().sendPacket(teamPacket); + } + } + + public void remove() { + sendTabPacket(current, PlayerListItem.Action.REMOVE_PLAYER); + current.clear(); + sendNpcPacket(npcs.stream().map(npc -> directlySent.get(npc).getUsername()).collect(Collectors.toList()), true); + npcs.clear(); + } + + @Override + protected void decode(ChannelHandlerContext ctx, PacketWrapper packetWrapper, List out) { + if(!connection.isObsolete()) { + DefinedPacket packet = packetWrapper.packet; + + if(packet instanceof PlayerListHeaderFooter) { + packetWrapper.trySingleRelease(); + return; + } + + if(packet instanceof PlayerListItem) { + PlayerListItem list = (PlayerListItem) packet; + PlayerListItem.Action action = list.getAction(); + + switch(action) { + case UPDATE_LATENCY: + case UPDATE_DISPLAY_NAME: + packetWrapper.trySingleRelease(); + return; + case UPDATE_GAMEMODE: + for (PlayerListItem.Item item : list.getItems()) { + ProxiedPlayer p = ProxyServer.getInstance().getPlayer(item.getUuid()); + if(p != null && p != player && item.getGamemode() == 3) { + item.setGamemode(1); + } + } + break; + case ADD_PLAYER: + for (PlayerListItem.Item item : list.getItems()) { + item.setPing(1); + item.setDisplayName(ComponentSerializer.toString(TextComponent.fromLegacyText(""))); + if(!player.getUniqueId().equals(item.getUuid()) && item.getGamemode() == 3) + item.setGamemode(1); + + directlySent.put(item.getUuid(), item); + } + break; + case REMOVE_PLAYER: + for(PlayerListItem.Item item : list.getItems()) { + directlySent.remove(item.getUuid()); + npcs.remove(item.getUuid()); + } + break; + } + } + } + out.add(packetWrapper); + } + + private void sendTabPacket(List items, PlayerListItem.Action action) { + if(!items.isEmpty()) { + PlayerListItem packet = new PlayerListItem(); + packet.setAction(action); + packet.setItems(items.toArray(new PlayerListItem.Item[0])); + player.unsafe().sendPacket(packet); + } + } + + private void sendNpcPacket(List names, boolean remove) { + if(!names.isEmpty()) { + Team packet = new Team(TAB_TEAM); + packet.setMode((byte) (remove ? 4 : 3)); + packet.setPlayers(names.toArray(new String[0])); + player.unsafe().sendPacket(packet); + } + } + + private BaseComponent[] header(ChatSender p, int seconds) { + int phase = (seconds % 16) / 3; + switch (phase) { + case 0: + return p.parse(false, "TABLIST_PHASE_1"); + case 1: + return p.parse(false, "TABLIST_PHASE_2"); + default: + return p.parse(false, "TABLIST_PHASE_DEFAULT"); + } + } + + private String ping() { + int ping = player.getPing(); + if (ping < 50) { + return "§a" + ping; + } else if (ping < 150) { + return "§e" + ping; + } else { + return "§c" + ping; + } + } +} diff --git a/src/de/steamwar/bungeecore/tablist/TablistGroup.java b/src/de/steamwar/bungeecore/tablist/TablistGroup.java new file mode 100644 index 0000000..fdb227a --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistGroup.java @@ -0,0 +1,45 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bungeecore.tablist; + +import de.steamwar.messages.ChatSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.List; + +public class TablistGroup implements TablistPart { + private final List sublists; + + public TablistGroup(List sublists) { + this.sublists = sublists; + } + + @Override + public String sortKey() { + return ""; + } + + @Override + public void print(ChatSender viewer, ProxiedPlayer player, List tablist, List direct) { + for (TablistPart sublist : sublists) { + sublist.print(viewer, player, tablist, direct); + } + } +} diff --git a/src/de/steamwar/bungeecore/tablist/TablistListener.java b/src/de/steamwar/bungeecore/tablist/TablistListener.java deleted file mode 100644 index 19c3607..0000000 --- a/src/de/steamwar/bungeecore/tablist/TablistListener.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2022 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.bungeecore.tablist; - -import de.steamwar.bungeecore.sql.SteamwarUser; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; -import net.md_5.bungee.ServerConnection; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.chat.ComponentSerializer; -import net.md_5.bungee.protocol.DefinedPacket; -import net.md_5.bungee.protocol.PacketWrapper; -import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; -import net.md_5.bungee.protocol.packet.PlayerListItem; -import net.md_5.bungee.protocol.packet.Team; - -import java.util.ArrayList; -import java.util.List; - -public class TablistListener extends MessageToMessageDecoder { - - //private boolean teamSent = false; - private final ProxiedPlayer player; - private final ServerConnection connection; - - public TablistListener(ProxiedPlayer player, ServerConnection connection) { - this.player = player; - this.connection = connection; - player.unsafe().sendPacket(TablistManager.teamPacket); - } - - @Override - protected void decode(ChannelHandlerContext ctx, PacketWrapper packetWrapper, List out) { - if(!connection.isObsolete()) { - DefinedPacket packet = packetWrapper.packet; - - if(packet instanceof PlayerListHeaderFooter) { - packetWrapper.trySingleRelease(); - return; - } else if(packet instanceof PlayerListItem) { - PlayerListItem list = (PlayerListItem) packet; - PlayerListItem.Action action = list.getAction(); - if(action == PlayerListItem.Action.UPDATE_DISPLAY_NAME || action == PlayerListItem.Action.UPDATE_LATENCY) { - packetWrapper.trySingleRelease(); - return; - } - - if(action == PlayerListItem.Action.UPDATE_GAMEMODE) { - for (PlayerListItem.Item item : list.getItems()) { - ProxiedPlayer p = ProxyServer.getInstance().getPlayer(item.getUuid()); - if(p != null && p != player && item.getGamemode() == 3) { - item.setGamemode(1); - } - } - }else if(action != PlayerListItem.Action.REMOVE_PLAYER) { - List npcs = new ArrayList<>(); - for (PlayerListItem.Item item : list.getItems()) { - ProxiedPlayer p = ProxyServer.getInstance().getPlayer(item.getUuid()); - if(p == null) { - item.setPing(1000); - item.setDisplayName(ComponentSerializer.toString(TextComponent.fromLegacyText(""))); - npcs.add(item.getUsername()); - } else { - item.setPing(1); - item.setDisplayName(ComponentSerializer.toString(TextComponent.fromLegacyText(SteamwarUser.get(p.getUniqueId()).getUserGroup().getColorCode() + p.getName()))); - if(p != player && item.getGamemode() == 3) { - item.setGamemode(1); - } - } - } - - if(!npcs.isEmpty()) { - Team teamPacket = new Team(TablistManager.TAB_TEAM); - teamPacket.setMode((byte) 3); - teamPacket.setPlayers(npcs.toArray(new String[0])); - player.unsafe().sendPacket(teamPacket); - } - } - } - } - out.add(packetWrapper); - } -} diff --git a/src/de/steamwar/bungeecore/tablist/TablistManager.java b/src/de/steamwar/bungeecore/tablist/TablistManager.java index bb844b1..541e176 100644 --- a/src/de/steamwar/bungeecore/tablist/TablistManager.java +++ b/src/de/steamwar/bungeecore/tablist/TablistManager.java @@ -20,43 +20,29 @@ package de.steamwar.bungeecore.tablist; import de.steamwar.bungeecore.BungeeCore; +import de.steamwar.bungeecore.Servertype; +import de.steamwar.bungeecore.Subserver; import de.steamwar.bungeecore.listeners.BasicListener; -import de.steamwar.bungeecore.sql.SteamwarUser; -import de.steamwar.messages.ChatSender; -import net.md_5.bungee.ServerConnection; +import de.steamwar.network.packets.common.FightInfoPacket; import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.ServerSwitchEvent; -import net.md_5.bungee.chat.ComponentSerializer; -import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.netty.PipelineUtils; -import net.md_5.bungee.protocol.PlayerPublicKey; -import net.md_5.bungee.protocol.Property; -import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; -import net.md_5.bungee.protocol.packet.PlayerListItem; -import net.md_5.bungee.protocol.packet.Team; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; -import java.util.stream.Stream; public class TablistManager extends BasicListener { - public static final String TAB_TEAM = "»SW-Tab"; + private static final Map fightInfos = new HashMap<>(); - private static final Property[] GRAY = new Property[]{new Property("textures", Base64.getEncoder().encodeToString(("{\"textures\":{\"SKIN\":{\"url\":\"https://steamwar.de/gray.png\"}}}").getBytes(StandardCharsets.UTF_8)))}; - private static final Property[] LIGHT_GRAY = new Property[]{new Property("textures", Base64.getEncoder().encodeToString(("{\"textures\":{\"SKIN\":{\"url\":\"https://steamwar.de/lightgray.png\"}}}").getBytes(StandardCharsets.UTF_8)))}; - - private static final UUID[] uuids = IntStream.range(0, 80).mapToObj(i -> UUID.randomUUID()).toArray(UUID[]::new); - private static final String[] names = IntStream.range(0, 80).mapToObj(i -> " »SW« " + i).toArray(String[]::new); - public static final Team teamPacket = new Team(TAB_TEAM, (byte) 0, ComponentSerializer.toString(TextComponent.fromLegacyText("")), ComponentSerializer.toString(TextComponent.fromLegacyText("")), ComponentSerializer.toString(TextComponent.fromLegacyText("")), "never", "never", 21, (byte)0x00, names); // new String[]{} + public static synchronized void newFightInfo(ServerInfo info, FightInfoPacket packet) { + fightInfos.put(info, packet); + fightInfos.keySet().removeIf(serverInfo -> serverInfo.getPlayers().isEmpty()); + } private final Map tablists = new HashMap<>(); @@ -76,8 +62,9 @@ public class TablistManager extends BasicListener { @EventHandler public void onServerConnection(ServerSwitchEvent event) { - ServerConnection server = (ServerConnection) event.getPlayer().getServer(); - server.getCh().getHandle().pipeline().addBefore(PipelineUtils.BOSS_HANDLER, "steamwar-tablist", new TablistListener(event.getPlayer(), server)); + synchronized (tablists) { + tablists.get(event.getPlayer()).onServerSwitch(); + } } @EventHandler @@ -95,110 +82,30 @@ public class TablistManager extends BasicListener { } private void updateTablist() { + List buildPlayers = new ArrayList<>(); + List subservers = new ArrayList<>(); + for (ServerInfo server : ProxyServer.getInstance().getServersCopy().values()){ + if(server.getPlayers().isEmpty()) + continue; + + Subserver subserver = Subserver.getSubserver(server); + if(subserver != null && subserver.getType() == Servertype.BAUSERVER) + server.getPlayers().forEach(player -> buildPlayers.add(new TablistPart.Item(player))); + else if(fightInfos.containsKey(server)) + subservers.add(new TablistServer(server, fightInfos.get(server))); + else + subservers.add(new TablistServer(server)); + } + subservers.sort((s1, s2) -> s1.sortKey().compareToIgnoreCase(s2.sortKey())); + if(!buildPlayers.isEmpty()) { + buildPlayers.sort((p1, p2) -> p1.getDisplayName().compareToIgnoreCase(p2.getDisplayName())); + subservers.add(new TablistServer(null, "Build", viewer -> viewer.parseToLegacy("TABLIST_BAU"), buildPlayers)); + } + TablistPart global = new TablistGroup(subservers); + synchronized (tablists) { - tablists.forEach((player, tablist) -> tablist.update()); - seconds++; - } - } - - private class Tablist { - private final ProxiedPlayer player; - private int shown = 0; - - private Tablist(ProxiedPlayer player) { - this.player = player; - } - - private void update() { - if(player.getServer() == null) - return; - - player.unsafe().sendPacket(new PlayerListHeaderFooter( - ComponentSerializer.toString(header(ChatSender.of(player))), - ComponentSerializer.toString(ChatSender.of(player).parse(false, "TABLIST_FOOTER", player.getServer().getInfo().getName(), ping(), ProxyServer.getInstance().getPlayers().size())) - )); - - PlayerListItem addPacket = new PlayerListItem(); - addPacket.setAction(PlayerListItem.Action.ADD_PLAYER); - - PlayerListItem.Item[] items = ProxyServer.getInstance().getServers().entrySet().stream().filter(entry -> entry.getValue() != player.getServer().getInfo()).sorted((e1, e2) -> e1.getKey().compareToIgnoreCase(e2.getKey())).flatMap(entry -> Stream.concat(Stream.of(construct("", false), construct("§7§l" + entry.getKey(), true)), entry.getValue().getPlayers().stream().map(this::construct))).toArray(PlayerListItem.Item[]::new); - if(items.length > 80) { - items = Arrays.copyOf(items, 80); - } - for(int i = 0; i < items.length; i++) { - items[i].setUuid(uuids[i]); - items[i].setUsername(names[i]); - } - addPacket.setItems(items); - - if(shown > items.length) - player.unsafe().sendPacket(removePacket(items.length)); - - shown = items.length; - player.unsafe().sendPacket(addPacket); - //TODO Set direct displaynames for onserver personel (and update correctly) - //TODO Add direct onserver not shown - } - - private BaseComponent[] header(ChatSender p){ - int phase = (seconds % 16) / 3; - switch(phase){ - case 0: - return p.parse(false, "TABLIST_PHASE_1"); - case 1: - return p.parse(false, "TABLIST_PHASE_2"); - default: - return p.parse(false, "TABLIST_PHASE_DEFAULT"); - } - } - - private String ping() { - int ping = player.getPing(); - if(ping < 50){ - return "§a" + ping; - }else if(ping < 150){ - return "§e" + ping; - }else{ - return "§c" + ping; - } - } - - private PlayerListItem.Item construct(String displayName, boolean lightgray) { - // TODO lightgray ? LIGHT_GRAY : GRAY - return construct(null, null, new Property[]{}, null, 1, 1000, displayName); - } - - private PlayerListItem.Item construct(ProxiedPlayer player) { - return construct(player.getUniqueId(), null, ((InitialHandler)player.getPendingConnection()).getLoginProfile().getProperties(), ((InitialHandler)player.getPendingConnection()).getLoginRequest().getPublicKey(), 1, 1000, SteamwarUser.get(player.getUniqueId()).getUserGroup().getColorCode() + player.getName()); - } - - private PlayerListItem.Item construct(UUID uuid, String username, Property[] properties, PlayerPublicKey publicKey, int gamemode, int ping, String displayName) { - PlayerListItem.Item item = new PlayerListItem.Item(); - item.setUuid(uuid); - item.setUsername(username); - item.setProperties(properties); - item.setPublicKey(publicKey); - item.setGamemode(gamemode); - item.setPing(ping); - item.setDisplayName(ComponentSerializer.toString(TextComponent.fromLegacyText(displayName))); - return item; - } - - private PlayerListItem.Item construct(UUID uuid) { - PlayerListItem.Item item = new PlayerListItem.Item(); - item.setUuid(uuid); - return item; - } - - private PlayerListItem removePacket(int newLength) { - PlayerListItem packet = new PlayerListItem(); - packet.setAction(PlayerListItem.Action.REMOVE_PLAYER); - packet.setItems(IntStream.range(newLength, shown).mapToObj(i -> construct(uuids[i])).toArray(PlayerListItem.Item[]::new)); - return packet; - } - - private void remove() { - player.unsafe().sendPacket(removePacket(0)); + tablists.forEach((player, tablist) -> tablist.update(global, seconds)); } + seconds++; } } diff --git a/src/de/steamwar/bungeecore/tablist/TablistPart.java b/src/de/steamwar/bungeecore/tablist/TablistPart.java new file mode 100644 index 0000000..2388116 --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistPart.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bungeecore.tablist; + +import de.steamwar.bungeecore.sql.SteamwarUser; +import de.steamwar.messages.ChatSender; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.chat.ComponentSerializer; +import net.md_5.bungee.connection.InitialHandler; +import net.md_5.bungee.protocol.Property; + +import java.util.List; +import java.util.UUID; + +interface TablistPart { + String sortKey(); + void print(ChatSender viewer, ProxiedPlayer player, List tablist, List direct); + + class Item { + + public static Property[] playerProperties(ProxiedPlayer player) { + return ((InitialHandler) player.getPendingConnection()).getLoginProfile().getProperties(); + } + + private final UUID uuid; + private final String displayName; + private final Property[] properties; + + public Item(UUID uuid, String displayName, Property[] properties) { + this.uuid = uuid; + this.displayName = reformat(displayName); + this.properties = properties; + } + + public Item(ProxiedPlayer player) { + this.uuid = player.getUniqueId(); + this.displayName = reformat(SteamwarUser.get(player.getUniqueId()).getUserGroup().getColorCode() + player.getName()); + this.properties = playerProperties(player); + } + + public UUID getUuid() { + return uuid; + } + + public String getDisplayName() { + return displayName; + } + + public Property[] getProperties() { + return properties; + } + + private String reformat(String string) { + return ComponentSerializer.toString(TextComponent.fromLegacyText(string)); + } + } +} diff --git a/src/de/steamwar/bungeecore/tablist/TablistServer.java b/src/de/steamwar/bungeecore/tablist/TablistServer.java new file mode 100644 index 0000000..d78bced --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistServer.java @@ -0,0 +1,91 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bungeecore.tablist; + +import de.steamwar.bungeecore.sql.SteamwarUser; +import de.steamwar.messages.ChatSender; +import de.steamwar.network.packets.common.FightInfoPacket; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.protocol.Property; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class TablistServer implements TablistPart { + + public static final Property[] GRAY = new Property[]{new Property("textures", "eyJ0aW1lc3RhbXAiOjE0NTU1NzQxMTk0MzMsInByb2ZpbGVJZCI6ImIzYjE4MzQ1MzViZjRiNzU4ZTBjZGJmMGY4MjA2NTZlIiwicHJvZmlsZU5hbWUiOiIxMDExMTEiLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzZlNzJkMzE0NzczMmQ5NzFkZWZhZTIzMWIzOGQ5NDI0MTRiMDU3YTcxNTFjNTNjNWZkNjI5NmEzYjllZGEwYWIifX19", "ro/ZKHt7278yhCr+CFTcPp/q6wAUlef//85k2DzkfRaZqy0CtGgwisDs2U4pVKvQ2pfXvitzWgbJvD0bLeQ12xWi4c1Fc29LCArosVJoFmrJDHz7N2MlstHT+ynQROb9d2aiFA6uOXfLjPKb1noUZ/YQoZjqcPIvD5oFZtD5DHV5O4hYz0IvgHbIjDqjz6ITsTcKiBlbxNg2loTFxSlW1ZfnNCO+kcAmeyB5NFY3j0e+/AqVANiNoiC3OKsECM/yEx/acf+vKWcT8mQn4wRoIGtxfEU7ZjNtgdh73NvXXBygW+K9AiJ242g8Y06Xxuk8kaNEGmT6H/mM7nbwjZmQQXpi/Pao2gYqyeIofeCPfr8RsGXoDX3nXDAw8/LyhTCHgx+sp6IQYSfGcSMJtoNeTJ0liIFxqn1V9/zKmzOZAPzR6qrQPOjoRFljLAlv7rfzotaEqh/1ldd40GdS8tstczn7f29OQerNDaqvbDb00Gy0STdUr1bVyCDptA54XKjT9WFv7QpBikEculxqSppAXPxD2Fb/ZmphbZx8WEGfG6bVFhf6fQdDAUXlcv8BxjElNPwlolF86M2KJd5VquLluhrCjwID7OK/pffNultAVH+Lxw4QOAXmJqjUrA1KHgyG1S0Cwj/f4E2hdxZJBvkfVtq9qPkd9nignhEoTCTOHf0=")}; + public static final Property[] LIGHT_GRAY = new Property[]{new Property("textures", "eyJ0aW1lc3RhbXAiOjE0NTU2MjU1OTM5NjIsInByb2ZpbGVJZCI6ImIzYjE4MzQ1MzViZjRiNzU4ZTBjZGJmMGY4MjA2NTZlIiwicHJvZmlsZU5hbWUiOiIxMDExMTEiLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzc4Y2I3ZmMyMDhiMzM4NTUwNGE4MTQ0MjA0NDI4ZmRjZDYzMjRiZWIzMWNhMmNlODZjYzQyNGI5NjNkODVjIn19fQ==", "R/wZUZRC1dishRdM9a2SSxxW3oYa0XSb/MxHbQpEUA791HxyqjaKLDu0wFX2r2a8ZTeVjzXpNzkg3+PkrA11o8h7lt86MTD1pi/rQqj/WRuoqf2LP+ypbssKV+LU15cYez2cj3QQVcJDXgWEnfSLNuBv6NG8BDUpUAjTWldvu99NCJHUoD0jNMHxY/fu4k5vCgOjaBaKgkjVk2bmUhegusmtMwco+3pYx+y8+gUW8ptx5SnePG+dOwTqLyBFiOt2AQ+gSvbU/jP9aAXgxOwz/b1pMaBWtzVhFU865NHlIdSpIHg/sh3uNah3a7gTgtTvxPQv1OzM/KtqYKiamsrRzAQMzRcs4A7Tp0GakLuxEaz401IwvQ7UGVYLFzGUVLB2MyqtPgifiqQSQxZpiqj9sM5QadhsUw00nfX7mTdW46U0MtNIbby1rLrvgQKoj08zt6LJlhI3yjyawy4iZkgF4oc+PCNwZc93GIbVL9LJaGkXk3RVA+JpGwfMJrGVbL7hl8ibbAcUv7uCEWdkAgZCd6w75jEE4tlhDSPDD4rXbn+FeTZRg2n/PGKtnoTZRzbniiFaNoSAHDZSVRG39xvBDFvtmL3SPaKhzKaifiYrgNn453WtR3kymqdAtPf1GN9d1VltGZ/+vMPwqPJb6thcrlcU64UGHbg1olRkiyZHvY8=")}; + + private final ServerInfo server; + private final String sortKey; + private final Function title; + private final List players; + + public TablistServer(ServerInfo server) { + this(server, server.getName(), viewer -> server.getName(), server.getPlayers().stream().sorted((p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName())).map(TablistPart.Item::new).collect(Collectors.toList())); + } + + public TablistServer(ServerInfo server, FightInfoPacket info) { + this(server, server.getName(), viewer -> server.getName(), new ArrayList<>()); + + Collection onlinePlayers = server.getPlayers(); + addPlayers(info.getBlueName().substring(0, 2), info.getBluePlayers(), onlinePlayers); + addPlayers(info.getRedName().substring(0, 2), info.getRedPlayers(), onlinePlayers); + addPlayers("§7", info.getSpectators(), onlinePlayers); + } + + public TablistServer(ServerInfo server, String sortKey, Function title, List players) { + this.server = server; + this.sortKey = sortKey; + this.title = title; + this.players = players; + } + + @Override + public String sortKey() { + return sortKey; + } + + @Override + public void print(ChatSender viewer, ProxiedPlayer player, List tablist, List direct) { + boolean onServer = player.getServer().getInfo() == server; + List items = onServer ? direct : tablist; + + if(!onServer && title != null) { + items.add(new Item(null, "", GRAY)); + items.add(new Item(null, "§7" + title.apply(viewer), LIGHT_GRAY)); + } + + items.addAll(players); + } + + private void addPlayers(String prefix, List teamPlayers, Collection onlinePlayers){ + teamPlayers.stream().map(SteamwarUser::get).map( + user -> onlinePlayers.stream().filter(player -> player.getUniqueId().equals(user.getUuid())).findAny() + ).filter(Optional::isPresent).map(Optional::get).sorted( + (p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName()) + ).forEachOrdered(player -> players.add(new Item(player.getUniqueId(), prefix + player.getName(), Item.playerProperties(player)))); + } +}