package com.bringholm.nametagchanger; import com.bringholm.reflectutil.v1_1_1.ReflectUtil; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.wrappers.*; import com.google.common.collect.Lists; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.logging.Level; /** * The packet handler implementation using ProtocolLib * @author AlvinB */ public class ProtocolLibPacketHandler extends PacketAdapter implements IPacketHandler { private static final Class<?> ENTITY_PLAYER = ReflectUtil.getNMSClass("EntityPlayer").getOrThrow(); private static final Method GET_HANDLE = ReflectUtil.getMethod(ReflectUtil.getCBClass("entity.CraftPlayer").getOrThrow(), "getHandle").getOrThrow(); private static final Field PING = ReflectUtil.getField(ENTITY_PLAYER, "ping").getOrThrow(); private static final int CREATE_SCOREBOARD_TEAM_MODE = 0; private static final int JOIN_SCOREBOARD_TEAM_MODE = 3; private static final int LEAVE_SCOREBOARD_TEAM_MODE = 4; ProtocolLibPacketHandler(Plugin plugin) { super(plugin, PacketType.Play.Server.PLAYER_INFO, PacketType.Play.Server.SCOREBOARD_TEAM); ProtocolLibrary.getProtocolManager().addPacketListener(this); } @Override public void onPacketSending(PacketEvent e) { if (NameTagChanger.INSTANCE.sendingPackets) { return; } if (e.getPacketType() == PacketType.Play.Server.PLAYER_INFO) { List<PlayerInfoData> list = Lists.newArrayList(); boolean modified = false; for (PlayerInfoData infoData : e.getPacket().getPlayerInfoDataLists().read(0)) { if (NameTagChanger.INSTANCE.gameProfiles.containsKey(infoData.getProfile().getUUID())) { UUID uuid = infoData.getProfile().getUUID(); Player player = Bukkit.getPlayer(uuid); WrappedChatComponent displayName = infoData.getDisplayName() == null ? WrappedChatComponent.fromText(player == null ? infoData.getProfile().getName() : player.getPlayerListName()) : infoData.getDisplayName(); WrappedGameProfile gameProfile = getProtocolLibProfileWrapper(NameTagChanger.INSTANCE.gameProfiles.get(uuid)); PlayerInfoData newInfoData = new PlayerInfoData(gameProfile, infoData.getLatency(), infoData.getGameMode(), displayName); list.add(newInfoData); modified = true; } else { list.add(infoData); } } if (modified) { e.getPacket().getPlayerInfoDataLists().write(0, list); } } else { int mode = e.getPacket().getIntegers().read(1); if (mode == CREATE_SCOREBOARD_TEAM_MODE || mode == LEAVE_SCOREBOARD_TEAM_MODE || mode == JOIN_SCOREBOARD_TEAM_MODE) { @SuppressWarnings("unchecked") Collection<String> entriesToAdd = (Collection<String>) e.getPacket().getSpecificModifier(Collection.class).read(0); Map<UUID, String> changedPlayerNames = NameTagChanger.INSTANCE.getChangedPlayers(); //noinspection Duplicates for (String entry : entriesToAdd) { for (UUID uuid : changedPlayerNames.keySet()) { Player changedPlayer = Bukkit.getPlayer(uuid); if (changedPlayer != null && changedPlayer.getName().equals(entry)) { entriesToAdd.remove(entry); entriesToAdd.add(changedPlayerNames.get(uuid)); break; } } } } } } @Override public void sendTabListRemovePacket(Player playerToRemove, Player seer) { PacketContainer packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.PLAYER_INFO); packet.getPlayerInfoAction().write(0, EnumWrappers.PlayerInfoAction.REMOVE_PLAYER); PlayerInfoData playerInfoData = new PlayerInfoData(WrappedGameProfile.fromPlayer(playerToRemove), 0, EnumWrappers.NativeGameMode.NOT_SET, null); packet.getPlayerInfoDataLists().write(0, Collections.singletonList(playerInfoData)); try { ProtocolLibrary.getProtocolManager().sendServerPacket(seer, packet); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send tab list remove packet!", e); } } @Override public void sendTabListAddPacket(Player playerToAdd, GameProfileWrapper newProfile, Player seer) { PacketContainer packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.PLAYER_INFO); int ping = (int) ReflectUtil.getFieldValue(ReflectUtil.invokeMethod(playerToAdd, GET_HANDLE).getOrThrow(), PING).getOrThrow(); packet.getPlayerInfoAction().write(0, EnumWrappers.PlayerInfoAction.ADD_PLAYER); PlayerInfoData playerInfoData = new PlayerInfoData(getProtocolLibProfileWrapper(newProfile), ping, EnumWrappers.NativeGameMode.fromBukkit(playerToAdd.getGameMode()), WrappedChatComponent.fromText(playerToAdd.getPlayerListName())); packet.getPlayerInfoDataLists().write(0, Collections.singletonList(playerInfoData)); try { ProtocolLibrary.getProtocolManager().sendServerPacket(seer, packet); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send tab list add packet!", e); } } @Override public void sendEntityDestroyPacket(Player playerToDestroy, Player seer) { PacketContainer packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.ENTITY_DESTROY); packet.getIntegerArrays().write(0, new int[] {playerToDestroy.getEntityId()}); try { ProtocolLibrary.getProtocolManager().sendServerPacket(seer, packet); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send entity destroy packet!", e); } } @Override public void sendNamedEntitySpawnPacket(Player playerToSpawn, Player seer) { PacketContainer packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.NAMED_ENTITY_SPAWN); packet.getIntegers().write(0, playerToSpawn.getEntityId()); packet.getUUIDs().write(0, playerToSpawn.getUniqueId()); if (ReflectUtil.isVersionHigherThan(1, 8, 8)) { packet.getDoubles().write(0, playerToSpawn.getLocation().getX()); packet.getDoubles().write(1, playerToSpawn.getLocation().getY()); packet.getDoubles().write(2, playerToSpawn.getLocation().getZ()); } else { packet.getIntegers().write(0, (int) Math.floor(playerToSpawn.getLocation().getX() * 32D)); packet.getIntegers().write(1, (int) Math.floor(playerToSpawn.getLocation().getY() * 32D)); packet.getIntegers().write(2, (int) Math.floor(playerToSpawn.getLocation().getZ() * 32D)); } packet.getBytes().write(0, (byte) (playerToSpawn.getLocation().getYaw() * 256F / 360F)); packet.getBytes().write(1, (byte) (playerToSpawn.getLocation().getPitch() * 256F / 360F)); packet.getDataWatcherModifier().write(0, WrappedDataWatcher.getEntityWatcher(playerToSpawn)); try { ProtocolLibrary.getProtocolManager().sendServerPacket(seer, packet); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send named entity spawn packet!", e); } } @Override public void sendEntityEquipmentPacket(Player playerToSpawn, Player seer) { int entityID = playerToSpawn.getEntityId(); // ProtocolLib converts some ItemStacks with Material.AIR to null, causing exceptions if (playerToSpawn.getInventory().getItemInMainHand() != null && playerToSpawn.getInventory().getItemInMainHand().getType() != Material.AIR) { doEquipmentPacketSend(entityID, EnumWrappers.ItemSlot.MAINHAND, playerToSpawn.getInventory().getItemInMainHand(), seer); } if (playerToSpawn.getInventory().getItemInOffHand() != null && playerToSpawn.getInventory().getItemInOffHand().getType() != Material.AIR) { doEquipmentPacketSend(entityID, EnumWrappers.ItemSlot.OFFHAND, playerToSpawn.getInventory().getItemInOffHand(), seer); } if (playerToSpawn.getInventory().getBoots() != null && playerToSpawn.getInventory().getBoots().getType() != Material.AIR) { doEquipmentPacketSend(entityID, EnumWrappers.ItemSlot.FEET, playerToSpawn.getInventory().getBoots(), seer); } if (playerToSpawn.getInventory().getLeggings() != null && playerToSpawn.getInventory().getLeggings().getType() != Material.AIR) { doEquipmentPacketSend(entityID, EnumWrappers.ItemSlot.LEGS, playerToSpawn.getInventory().getLeggings(), seer); } if (playerToSpawn.getInventory().getChestplate() != null && playerToSpawn.getInventory().getChestplate().getType() != Material.AIR) { doEquipmentPacketSend(entityID, EnumWrappers.ItemSlot.CHEST, playerToSpawn.getInventory().getChestplate(), seer); } if (playerToSpawn.getInventory().getHelmet() != null && playerToSpawn.getInventory().getHelmet().getType() != Material.AIR) { doEquipmentPacketSend(entityID, EnumWrappers.ItemSlot.HEAD, playerToSpawn.getInventory().getHelmet(), seer); } } private void doEquipmentPacketSend(int entityID, EnumWrappers.ItemSlot slot, ItemStack itemStack, Player recipient) { PacketContainer packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.ENTITY_EQUIPMENT); packet.getIntegers().write(0, entityID); packet.getItemSlots().write(0, slot); packet.getItemModifier().write(0, itemStack); try { ProtocolLibrary.getProtocolManager().sendServerPacket(recipient, packet); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send equipment packet!", e); } } @Override public void sendScoreboardRemovePacket(String playerToRemove, Player seer, String team) { try { ProtocolLibrary.getProtocolManager().sendServerPacket(seer, getScoreboardPacket(team, playerToRemove, LEAVE_SCOREBOARD_TEAM_MODE)); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send scoreboard remove packet!", e); } } @Override public void sendScoreboardAddPacket(String playerToAdd, Player seer, String team) { try { ProtocolLibrary.getProtocolManager().sendServerPacket(seer, getScoreboardPacket(team, playerToAdd, JOIN_SCOREBOARD_TEAM_MODE)); } catch (InvocationTargetException e) { logMessage(Level.SEVERE, "Failed to send scoreboard add packet!", e); } } @SuppressWarnings("unchecked") private PacketContainer getScoreboardPacket(String team, String entryToAdd, int mode) { PacketContainer packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.SCOREBOARD_TEAM); packet.getStrings().write(0, team); packet.getIntegers().write(1, mode); ((Collection<String>) packet.getSpecificModifier(Collection.class).read(0)).add(entryToAdd); return packet; } @Override public GameProfileWrapper getDefaultPlayerProfile(Player player) { WrappedGameProfile wrappedGameProfile = WrappedGameProfile.fromPlayer(player); GameProfileWrapper wrapper = new GameProfileWrapper(wrappedGameProfile.getUUID(), wrappedGameProfile.getName()); for (Map.Entry<String, Collection<WrappedSignedProperty>> entry : wrappedGameProfile.getProperties().asMap().entrySet()) { for (WrappedSignedProperty wrappedSignedProperty : entry.getValue()) { wrapper.getProperties().put(entry.getKey(), new GameProfileWrapper.PropertyWrapper(wrappedSignedProperty.getName(), wrappedSignedProperty.getValue(), wrappedSignedProperty.getSignature())); } } return wrapper; } private static WrappedGameProfile getProtocolLibProfileWrapper(GameProfileWrapper wrapper) { WrappedGameProfile wrappedGameProfile = new WrappedGameProfile(wrapper.getUUID(), wrapper.getName()); for (Map.Entry<String, Collection<GameProfileWrapper.PropertyWrapper>> entry : wrapper.getProperties().asMap().entrySet()) { for (GameProfileWrapper.PropertyWrapper propertyWrapper : entry.getValue()) { wrappedGameProfile.getProperties().put(entry.getKey(), new WrappedSignedProperty(propertyWrapper.getName(), propertyWrapper.getValue(), propertyWrapper.getSignature())); } } return wrappedGameProfile; } @Override public void shutdown() { ProtocolLibrary.getProtocolManager().removePacketListener(this); } private void logMessage(Level level, String message, Exception e) { if (level == Level.SEVERE) { System.err.println("[NameTagChanger] " + message); } else { NameTagChanger.INSTANCE.printMessage(message); } if (e != null) { e.printStackTrace(); } } }