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