package net.citizensnpcs; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Function; import org.bukkit.Bukkit; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDeathEvent; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Play.Server; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.wrappers.PlayerInfoData; import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedDataValue; import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedSignedProperty; import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import net.citizensnpcs.api.event.NPCAddTraitEvent; import net.citizensnpcs.api.event.NPCDespawnEvent; import net.citizensnpcs.api.event.NPCEvent; import net.citizensnpcs.api.event.NPCSpawnEvent; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.trait.trait.MobType; import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.trait.ClickRedirectTrait; import net.citizensnpcs.trait.HologramTrait; import net.citizensnpcs.trait.MirrorTrait; import net.citizensnpcs.trait.RotationTrait; import net.citizensnpcs.trait.RotationTrait.PacketRotationSession; import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.SkinProperty; public class ProtocolLibListener implements Listener { private ProtocolManager manager; private final Map mirrorTraits = Maps.newConcurrentMap(); private Citizens plugin; private final Map rotationTraits = Maps.newConcurrentMap(); public ProtocolLibListener(Citizens plugin) { this.plugin = plugin; manager = ProtocolLibrary.getProtocolManager(); Bukkit.getPluginManager().registerEvents(this, plugin); manager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, Server.ENTITY_METADATA) { @Override public void onPacketSending(PacketEvent event) { NPC npc = getNPCFromPacket(event); if (npc == null) return; PacketContainer packet = event.getPacket(); int version = manager.getProtocolVersion(event.getPlayer()); if (npc.data().has(NPC.Metadata.HOLOGRAM_FOR) || npc.data().has(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER)) { Function hvs = npc.data().get(NPC.Metadata.HOLOGRAM_LINE_SUPPLIER); Object fakeName = null; if (hvs != null) { String suppliedName = hvs.apply(event.getPlayer()); fakeName = version <= 340 ? suppliedName : Optional.of(Messaging.minecraftComponentFromRawMessage(suppliedName)); } boolean sneaking = npc.getOrAddTrait(ClickRedirectTrait.class).getRedirectNPC() .getOrAddTrait(HologramTrait.class).isHologramSneaking(npc, event.getPlayer()); boolean delta = false; if (version < 761) { List wwo = packet.getWatchableCollectionModifier().readSafely(0); if (wwo == null) return; for (WrappedWatchableObject wo : wwo) { if (fakeName != null && wo.getIndex() == 2) { wo.setValue(fakeName); delta = true; } else if (sneaking && wo.getIndex() == 0) { byte b = (byte) (((Number) wo.getValue()).byteValue() | 0x02); wo.setValue(b); delta = true; } } if (delta) { packet.getWatchableCollectionModifier().write(0, wwo); } } else { List wdvs = packet.getDataValueCollectionModifier().readSafely(0); if (wdvs == null) return; for (WrappedDataValue wdv : wdvs) { if (fakeName != null && wdv.getIndex() == 2) { wdv.setRawValue(fakeName); delta = true; } else if (sneaking && wdv.getIndex() == 0) { byte b = (byte) (((Number) wdv.getValue()).byteValue() | 0x02); wdv.setValue(b); delta = true; } } if (delta) { packet.getDataValueCollectionModifier().write(0, wdvs); } } } } }); manager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.HIGHEST, Arrays.asList(Server.PLAYER_INFO), ListenerOptions.ASYNC) { @Override public void onPacketSending(PacketEvent event) { int version = manager.getProtocolVersion(event.getPlayer()); if (version >= 761) { NMS.onPlayerInfoAdd(event.getPlayer(), event.getPacket().getHandle(), uuid -> mirrorTraits.get(uuid)); return; } List list = event.getPacket().getPlayerInfoDataLists().readSafely(0); if (list == null) return; boolean changed = false; GameProfile playerProfile = null; WrappedGameProfile wgp = null; WrappedChatComponent playerName = null; for (int i = 0; i < list.size(); i++) { PlayerInfoData npcInfo = list.get(i); if (npcInfo == null) continue; MirrorTrait trait = mirrorTraits.get(npcInfo.getProfile().getUUID()); if (trait == null || !trait.isMirroring(event.getPlayer())) continue; if (playerProfile == null) { playerProfile = NMS.getProfile(event.getPlayer()); wgp = WrappedGameProfile.fromPlayer(event.getPlayer()); playerName = WrappedChatComponent.fromText(event.getPlayer().getDisplayName()); } if (trait.mirrorName()) { list.set(i, new PlayerInfoData(wgp.withId(npcInfo.getProfile().getId()), npcInfo.getLatency(), npcInfo.getGameMode(), playerName)); continue; } Collection textures = playerProfile.getProperties().get("textures"); if (textures == null || textures.size() == 0) continue; npcInfo.getProfile().getProperties().clear(); for (String key : playerProfile.getProperties().keySet()) { npcInfo.getProfile().getProperties().putAll(key, Iterables.transform(playerProfile.getProperties().get(key), skin -> { SkinProperty sp = SkinProperty.fromMojang(skin); return new WrappedSignedProperty(sp.name, sp.value, sp.signature); })); } changed = true; } if (changed) { event.getPacket().getPlayerInfoDataLists().write(0, list); } } }); manager.addPacketListener(new PacketAdapter( plugin, ListenerPriority.HIGHEST, Arrays.asList(Server.ENTITY_HEAD_ROTATION, Server.ENTITY_LOOK, Server.REL_ENTITY_MOVE_LOOK, Server.ENTITY_MOVE_LOOK, Server.ENTITY_TELEPORT), ListenerOptions.ASYNC) { @Override public void onPacketSending(PacketEvent event) { Integer eid = null; try { eid = event.getPacket().getIntegers().readSafely(0); if (eid == null) return; } catch (FieldAccessException | IllegalArgumentException ex) { if (!LOGGED_ERROR) { Messaging.severe( "Error retrieving entity from ID: ProtocolLib error? Suppressing further exceptions unless debugging."); ex.printStackTrace(); LOGGED_ERROR = true; } else if (Messaging.isDebugging()) { ex.printStackTrace(); } return; } RotationTrait trait = rotationTraits.get(eid); if (trait == null) return; PacketRotationSession session = trait.getPacketSession(event.getPlayer()); if (session == null || !session.isActive()) return; PacketContainer packet = event.getPacket(); PacketType type = event.getPacketType(); Messaging.debug(session.getBodyYaw(), session.getHeadYaw(), "OVERWRITTEN " + type + " " + packet.getHandle()); if (type == Server.ENTITY_HEAD_ROTATION) { packet.getBytes().write(0, degToByte(session.getHeadYaw())); } else if (type == Server.ENTITY_LOOK || type == Server.ENTITY_MOVE_LOOK || type == Server.REL_ENTITY_MOVE_LOOK || type == Server.ENTITY_TELEPORT) { packet.getBytes().write(0, degToByte(session.getBodyYaw())); packet.getBytes().write(1, degToByte(session.getPitch())); } session.onPacketOverwritten(); } }); } private NPC getNPCFromPacket(PacketEvent event) { PacketContainer packet = event.getPacket(); try { Object entityModifier = packet.getEntityModifier(event).getTarget(); return entityModifier instanceof NPCHolder ? ((NPCHolder) entityModifier).getNPC() : null; } catch (FieldAccessException | IllegalArgumentException ex) { if (!LOGGED_ERROR) { Messaging.severe( "Error retrieving entity from ID: ProtocolLib error? Suppressing further exceptions unless debugging."); ex.printStackTrace(); LOGGED_ERROR = true; } else if (Messaging.isDebugging()) { ex.printStackTrace(); } return null; } } @EventHandler(ignoreCancelled = true) public void onEntityDeath(EntityDeathEvent event) { rotationTraits.remove(event.getEntity().getEntityId()); } @EventHandler(ignoreCancelled = true) public void onNPCDespawn(NPCDespawnEvent event) { if (event.getNPC().getEntity() == null) return; rotationTraits.remove(event.getNPC().getEntity().getEntityId()); mirrorTraits.remove(event.getNPC().getEntity().getUniqueId()); } @EventHandler(ignoreCancelled = true) public void onNPCSpawn(NPCSpawnEvent event) { onSpawn(event); } private void onSpawn(NPCEvent event) { if (event.getNPC().hasTrait(RotationTrait.class)) { rotationTraits.put(event.getNPC().getEntity().getEntityId(), event.getNPC().getTraitNullable(RotationTrait.class)); } if (event.getNPC().hasTrait(MirrorTrait.class) && event.getNPC().getOrAddTrait(MobType.class).getType() == EntityType.PLAYER) { mirrorTraits.put(event.getNPC().getEntity().getUniqueId(), event.getNPC().getTraitNullable(MirrorTrait.class)); } } @EventHandler(ignoreCancelled = true) public void onTraitAdd(NPCAddTraitEvent event) { if (!event.getNPC().isSpawned()) return; onSpawn(event); } private static byte degToByte(float in) { return (byte) (in * 256.0F / 360.0F); } private static boolean LOGGED_ERROR = false; }