diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index 8336ea7e..66385c88 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/tablist/TablistListener.java b/src/de/steamwar/bungeecore/tablist/TablistListener.java new file mode 100644 index 00000000..48404050 --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistListener.java @@ -0,0 +1,64 @@ +/* + * 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 io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import net.md_5.bungee.ServerConnection; +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 java.util.List; + +public class TablistListener extends MessageToMessageDecoder { + + private final ServerConnection connection; + + public TablistListener(ServerConnection connection) { + this.connection = connection; + } + + @Override + protected void decode(ChannelHandlerContext ctx, PacketWrapper packetWrapper, List out) { + //Team + if(!connection.isObsolete()) { + DefinedPacket packet = packetWrapper.packet; + + if(packet instanceof PlayerListHeaderFooter) { + packetWrapper.trySingleRelease(); + return; + } else if(packet instanceof PlayerListItem) { + PlayerListItem list = (PlayerListItem) packet; + if(list.getAction() == PlayerListItem.Action.UPDATE_LATENCY || list.getAction() == PlayerListItem.Action.ADD_PLAYER) { + for (PlayerListItem.Item item : list.getItems()) { + item.setPing(1); + //item.setUsername("AAA"); + //item.setDisplayName(ComponentSerializer.toString(TextComponent.fromLegacyText(""))); + } + } + } + } + out.add(packetWrapper); + + + } +} diff --git a/src/de/steamwar/bungeecore/tablist/TablistManager.java b/src/de/steamwar/bungeecore/tablist/TablistManager.java new file mode 100644 index 00000000..69543d0f --- /dev/null +++ b/src/de/steamwar/bungeecore/tablist/TablistManager.java @@ -0,0 +1,154 @@ +/* + * 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.listeners.BasicListener; +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.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.PlayerListItem; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +public class TablistManager extends BasicListener { + + private final Map tablists = new HashMap<>(); + + 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) { + ServerConnection server = (ServerConnection) event.getPlayer().getServer(); + server.getCh().getHandle().pipeline().addBefore(PipelineUtils.BOSS_HANDLER, "steamwar-tablist", new TablistListener(server)); + } + + @EventHandler + public void onLeave(PlayerDisconnectEvent event) { + synchronized (tablists) { + tablists.remove(event.getPlayer()); + } + } + + public void disable() { + synchronized (tablists) { + tablists.forEach((player, tablist) -> tablist.remove()); + tablists.clear(); + } + } + + private void updateTablist() { + synchronized (tablists) { + tablists.forEach((player, tablist) -> tablist.update()); + } + } + + private static class Tablist { + private final ProxiedPlayer player; + private PlayerListItem.Item[] shown = new PlayerListItem.Item[0]; + + private Tablist(ProxiedPlayer player) { + this.player = player; + } + + private void update() { + if(player.getServer() == null) + return; + + //TODO header & footer + PlayerListItem removePacket = removePacket(); + + PlayerListItem addPacket = new PlayerListItem(); + addPacket.setAction(PlayerListItem.Action.ADD_PLAYER); + + shown = 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("", "gray"), construct("§7§l" + entry.getKey(), "lightgray")), entry.getValue().getPlayers().stream().map(this::construct))).toArray(PlayerListItem.Item[]::new); + for(int i = 0; i < shown.length; i++) { + shown[i].setUsername("»SW« " + i); + } + addPacket.setItems(shown); + + player.unsafe().sendPacket(removePacket); + player.unsafe().sendPacket(addPacket); + } + + private PlayerListItem.Item construct(String displayName, String color) { + UUID uuid = UUID.randomUUID(); + return construct(uuid, null, new Property[]{createSkin(uuid, "»SW«", color)}, null, 1, 1000, displayName); + } + + private Property createSkin(UUID uuid, String username, String color) { + // \"timestamp\":0,\"profileId\":\"" + uuid.toString().replace("-", "") + "\",\"profileName\":\"" + username + "\", + return new Property("textures", Base64.getEncoder().encodeToString(("{\"textures\":{\"SKIN\":{\"url\":\"https://steamwar.de/" + color + ".png\"}}}").getBytes(StandardCharsets.UTF_8))); + } + + private PlayerListItem.Item construct(ProxiedPlayer player) { + return construct(player.getUniqueId(), null, ((InitialHandler)player.getPendingConnection()).getLoginProfile().getProperties(), ((InitialHandler)player.getPendingConnection()).getLoginRequest().getPublicKey(), 1, 1000, player.getDisplayName()); + } + + 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 removePacket() { + PlayerListItem packet = new PlayerListItem(); + packet.setAction(PlayerListItem.Action.REMOVE_PLAYER); + packet.setItems(shown); + return packet; + } + + private void remove() { + player.unsafe().sendPacket(removePacket()); + } + } +} diff --git a/src/plugin.yml b/src/plugin.yml index 85fafe1f..5bb14e05 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