diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index 8336ea7..66385c8 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -27,6 +27,7 @@ import de.steamwar.bungeecore.listeners.mods.*; import de.steamwar.bungeecore.network.BungeeNetworkHandler; import de.steamwar.bungeecore.network.NetworkReceiver; import de.steamwar.bungeecore.sql.*; +import de.steamwar.bungeecore.tablist.TablistManager; import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWTypeMapperCreator; import de.steamwar.command.TypeMapper; @@ -68,6 +69,7 @@ public class BungeeCore extends Plugin { public static final Map commands = new HashMap<>(); private ErrorLogger errorLogger; + private TablistManager tablistManager; @Override public void onEnable(){ @@ -175,7 +177,7 @@ public class BungeeCore extends Plugin { new SessionManager(); new NetworkReceiver(); BungeeNetworkHandler.register(); - new TablistManager(); + tablistManager = new TablistManager(); new SettingsChangedListener(); getProxy().getScheduler().schedule(this, () -> { @@ -203,6 +205,7 @@ public class BungeeCore extends Plugin { } } + tablistManager.disable(); errorLogger.unregister(); Statement.closeAll(); } 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..53c2bbe --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/Tablist.java @@ -0,0 +1,252 @@ +/* + * 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.Storage; +import de.steamwar.messages.ChatSender; +import io.netty.channel.ChannelHandler; +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; + +@ChannelHandler.Sharable +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("")), "always", "always", 21, (byte)0x00, names); + + private final Map directTabItems; + 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); + this.directTabItems = Storage.directTabItems.computeIfAbsent(player, p -> new HashMap<>()); + 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<>(); + List update = new ArrayList<>(); + for (TablistPart.Item item : direct) { + if(npcs.remove(item.getUuid())) + removeNpc.add(directTabItems.get(item.getUuid()).getUsername()); + + PlayerListItem.Item tabItem = directTabItems.get(item.getUuid()); + if(tabItem == null) { + tablist.add(0, item); + } else if(!tabItem.getDisplayName().equals(item.getDisplayName())) { + tabItem.setDisplayName(item.getDisplayName()); + update.add(tabItem); + } + } + Set nonNPCs = direct.stream().map(TablistPart.Item::getUuid).collect(Collectors.toSet()); + for(PlayerListItem.Item item : directTabItems.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<>(); + 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(); + directTabItems.clear(); + sendNpcPacket(npcs.stream().map(npc -> directTabItems.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 disable() { + sendTabPacket(current, PlayerListItem.Action.REMOVE_PLAYER); + current.clear(); + sendNpcPacket(npcs.stream().map(npc -> directTabItems.get(npc).getUsername()).collect(Collectors.toList()), true); + npcs.clear(); + + if(connection != null) + connection.getCh().getHandle().pipeline().remove(this); + } + + @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); + + directTabItems.put(item.getUuid(), item); + } + break; + case REMOVE_PLAYER: + for(PlayerListItem.Item item : list.getItems()) { + directTabItems.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/TablistBuild.java b/src/de/steamwar/bungeecore/tablist/TablistBuild.java new file mode 100644 index 0000000..dcb8f67 --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistBuild.java @@ -0,0 +1,69 @@ +/* + * 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.Servertype; +import de.steamwar.bungeecore.Subserver; +import de.steamwar.messages.ChatSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class TablistBuild implements TablistPart { + + private final List servers = new ArrayList<>(); + private final Map> players = new HashMap<>(); + + public TablistBuild() { + for (ServerInfo server : ProxyServer.getInstance().getServersCopy().values()){ + Subserver subserver = Subserver.getSubserver(server); + if(server.getPlayers().isEmpty() || subserver == null || subserver.getType() != Servertype.BAUSERVER) + continue; + + servers.add(server); + players.put(server, server.getPlayers().stream().sorted(((p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName()))).map(Item::new).collect(Collectors.toList())); + } + servers.sort((s1, s2) -> s1.getName().compareToIgnoreCase(s2.getName())); + } + + @Override + public String sortKey() { + return "Build"; + } + + @Override + public void print(ChatSender viewer, ProxiedPlayer player, List tablist, List direct) { + ServerInfo server = player.getServer().getInfo(); + if(players.keySet().stream().anyMatch(info -> server != info)) { + tablist.add(new Item(null, "", TablistServer.GRAY)); + tablist.add(new Item(null, viewer.parseToLegacy("TABLIST_BAU"), TablistServer.LIGHT_GRAY)); + } + + for(ServerInfo info : servers) { + (server == info ? direct : tablist).addAll(players.get(info)); + } + } +} 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/TablistManager.java b/src/de/steamwar/bungeecore/tablist/TablistManager.java new file mode 100644 index 0000000..0f02f55 --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistManager.java @@ -0,0 +1,107 @@ +/* + * 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.BungeeCore; +import de.steamwar.bungeecore.Servertype; +import de.steamwar.bungeecore.Storage; +import de.steamwar.bungeecore.Subserver; +import de.steamwar.bungeecore.listeners.BasicListener; +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.api.event.ServerSwitchEvent; +import net.md_5.bungee.event.EventHandler; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class TablistManager extends BasicListener { + + 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 final Map tablists = new HashMap<>(); + + private int seconds = 0; + + public TablistManager() { + ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), this::updateTablist, 1, 1, TimeUnit.SECONDS); + ProxyServer.getInstance().getPlayers().forEach(player -> tablists.put(player, new Tablist(player))); + } + + @EventHandler + public void onJoin(PostLoginEvent event) { + synchronized (tablists) { + tablists.put(event.getPlayer(), new Tablist(event.getPlayer())); + } + } + + @EventHandler + public void onServerConnection(ServerSwitchEvent event) { + synchronized (tablists) { + tablists.get(event.getPlayer()).onServerSwitch(); + } + } + + @EventHandler + public void onLeave(PlayerDisconnectEvent event) { + synchronized (tablists) { + tablists.remove(event.getPlayer()); + Storage.directTabItems.remove(event.getPlayer()); + } + } + + public void disable() { + synchronized (tablists) { + tablists.forEach((player, tablist) -> tablist.disable()); + tablists.clear(); + } + } + + private void updateTablist() { + List subservers = new ArrayList<>(); + for (ServerInfo server : ProxyServer.getInstance().getServersCopy().values()){ + if(server.getPlayers().isEmpty()) + continue; + + Subserver subserver = Subserver.getSubserver(server); + if(fightInfos.containsKey(server)) + subservers.add(new TablistServer(server, fightInfos.get(server))); + else if(subserver == null || subserver.getType() != Servertype.BAUSERVER) + subservers.add(new TablistServer(server)); + } + subservers.add(new TablistBuild()); + subservers.sort((s1, s2) -> s1.sortKey().compareToIgnoreCase(s2.sortKey())); + TablistPart global = new TablistGroup(subservers); + + synchronized (tablists) { + 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..0a95a48 --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistServer.java @@ -0,0 +1,86 @@ +/* + * 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.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 List players; + + public TablistServer(ServerInfo server) { + this(server, 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, 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, List players) { + this.server = server; + this.players = players; + } + + @Override + public String sortKey() { + return server.getName(); + } + + @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) { + items.add(new Item(null, "", GRAY)); + items.add(new Item(null, "§7§l" + server.getName(), 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)))); + } +} diff --git a/src/de/steamwar/messages/BungeeCore.properties b/src/de/steamwar/messages/BungeeCore.properties index 45a4f05..e18f608 100644 --- a/src/de/steamwar/messages/BungeeCore.properties +++ b/src/de/steamwar/messages/BungeeCore.properties @@ -552,7 +552,7 @@ TABLIST_PHASE_1=§8Teamspeak: §eSteam§8War.de TABLIST_PHASE_2=§8Discord: §8https://§eSteam§8War.de/discord TABLIST_PHASE_DEFAULT=§8Website: https://§eSteam§8War.de TABLIST_FOOTER=§e{0} {1}§8ms §ePlayers§8: §7{2} -TABLIST_BAU=Build +TABLIST_BAU=§7§lBuild LIST_COMMAND=§e{0}§8: §7{1} #EventStarter diff --git a/src/de/steamwar/messages/BungeeCore_de.properties b/src/de/steamwar/messages/BungeeCore_de.properties index 21a7a55..d3f7d22 100644 --- a/src/de/steamwar/messages/BungeeCore_de.properties +++ b/src/de/steamwar/messages/BungeeCore_de.properties @@ -531,7 +531,7 @@ TABLIST_PHASE_1=§8Teamspeak: §eSteam§8War.de TABLIST_PHASE_2=§8Discord: §8https://§eSteam§8War.de/discord TABLIST_PHASE_DEFAULT=§8Website: https://§eSteam§8War.de TABLIST_FOOTER=§e{0} {1}§8ms §eSpieler§8: §7{2} -TABLIST_BAU=Bau +TABLIST_BAU=§7§lBau LIST_COMMAND=§e{0}§8: §7{1} #EventStarter diff --git a/src/de/steamwar/messages/ChatSender.java b/src/de/steamwar/messages/ChatSender.java index 582fd6f..6879505 100644 --- a/src/de/steamwar/messages/ChatSender.java +++ b/src/de/steamwar/messages/ChatSender.java @@ -115,6 +115,10 @@ public interface ChatSender { return parseToComponent(false, message).toLegacyText(); } + default BaseComponent[] parse(boolean prefixed, String format, Object... params) { + return parse(prefixed, new Message(format, params)); + } + default BaseComponent[] parse(boolean prefixed, Message message) { Locale locale = getLocale(); ResourceBundle resourceBundle = SteamwarResourceBundle.getResourceBundle(locale); diff --git a/src/de/steamwar/messages/SteamwarResourceBundle.java b/src/de/steamwar/messages/SteamwarResourceBundle.java index c2e71ab..2252609 100644 --- a/src/de/steamwar/messages/SteamwarResourceBundle.java +++ b/src/de/steamwar/messages/SteamwarResourceBundle.java @@ -35,7 +35,7 @@ public class SteamwarResourceBundle extends PropertyResourceBundle { return getResourceBundle(locale.toString(), getResourceBundle(locale.getLanguage(), getResourceBundle( "", null))); } - private static ResourceBundle getResourceBundle(String locale, ResourceBundle parent) { + private static synchronized ResourceBundle getResourceBundle(String locale, ResourceBundle parent) { return bundles.computeIfAbsent(locale, locale1 -> { InputStream inputStream = Message.class.getResourceAsStream(BASE_PATH + ("".equals(locale) ? "" : "_" + locale) + ".properties"); if(inputStream == null) diff --git a/src/plugin.yml b/src/plugin.yml index 85fafe1..5bb14e0 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -2,4 +2,5 @@ name: BungeeCore main: de.steamwar.bungeecore.BungeeCore version: 1.0 author: Lixfel -depends: [PersistentBungeeCore, BungeeTabListPlus] \ No newline at end of file +depends: + - PersistentBungeeCore \ No newline at end of file