diff --git a/src/net/jitse/npclib/api/NPC.java b/src/net/jitse/npclib/api/NPC.java index 8553e31..ca7b90b 100644 --- a/src/net/jitse/npclib/api/NPC.java +++ b/src/net/jitse/npclib/api/NPC.java @@ -150,7 +150,7 @@ public abstract class NPC { shown.remove(player.getUniqueId()); - if (player.getLocation().distance(location) <= autoHideDistance) { + if (player.getWorld().equals(location.getWorld()) && player.getLocation().distance(location) <= autoHideDistance) { sendHidePackets(player); } else { if (autoHidden.contains(player.getUniqueId())) { diff --git a/src/net/jitse/npclib/listeners/ChunkListener.java b/src/net/jitse/npclib/listeners/ChunkListener.java index 8b82b2d..4094128 100644 --- a/src/net/jitse/npclib/listeners/ChunkListener.java +++ b/src/net/jitse/npclib/listeners/ChunkListener.java @@ -28,7 +28,7 @@ public class ChunkListener implements Listener { for (NPC npc : NPCManager.getAllNPCs()) { Chunk npcChunk = npc.getLocation().getChunk(); - if (chunk.getX() == npcChunk.getX() && chunk.getZ() == npcChunk.getZ()) { + if (chunk.equals(npcChunk)) { // Unloaded chunk with NPC in it. Hiding it from all players currently shown to. for (UUID uuid : npc.getShown()) { @@ -51,7 +51,7 @@ public class ChunkListener implements Listener { for (NPC npc : NPCManager.getAllNPCs()) { Chunk npcChunk = npc.getLocation().getChunk(); - if (chunk.getX() == npcChunk.getX() && chunk.getZ() == npcChunk.getZ()) { + if (chunk.equals(npcChunk)) { // Loaded chunk with NPC in it. Showing it to the players again. for (UUID uuid : npc.getShown()) { @@ -62,6 +62,10 @@ public class ChunkListener implements Listener { Player player = Bukkit.getPlayer(uuid); + if (!npcChunk.getWorld().equals(player.getWorld())) { + continue; // Player and NPC are not in the same world. + } + double hideDistance = npc.getAutoHideDistance(); double distanceSquared = player.getLocation().distanceSquared(npc.getLocation()); boolean inRange = distanceSquared <= (hideDistance * hideDistance) || distanceSquared <= (Bukkit.getViewDistance() << 4); diff --git a/src/net/jitse/npclib/listeners/PlayerListener.java b/src/net/jitse/npclib/listeners/PlayerListener.java index 29e9f15..cff5a98 100644 --- a/src/net/jitse/npclib/listeners/PlayerListener.java +++ b/src/net/jitse/npclib/listeners/PlayerListener.java @@ -8,9 +8,11 @@ import net.jitse.npclib.NPCManager; import net.jitse.npclib.api.NPC; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerTeleportEvent; @@ -28,8 +30,25 @@ public class PlayerListener implements Listener { npc.getAutoHidden().remove(player.getUniqueId()); } - if (npc.isActuallyShown(player)) { - npc.hide(player); + // Don't need to use NPC#hide since the entity is not registered in the NMS server. + if (npc.getShown().contains(player.getUniqueId())) { + npc.getShown().remove(player.getUniqueId()); + } + } + } + + @EventHandler + public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + Player player = event.getPlayer(); + World from = event.getFrom(); + + // The PlayerTeleportEvent is call, and will handle visibility in the new world. + for (NPC npc : NPCManager.getAllNPCs()) { + if (npc.getLocation().getWorld().equals(from)) { + if (!npc.getAutoHidden().contains(player.getUniqueId())) { + npc.getAutoHidden().add(player.getUniqueId()); + npc.hide(player, true); + } } } } @@ -52,11 +71,16 @@ public class PlayerListener implements Listener { } private void handleMove(Player player) { + World world = player.getWorld(); for (NPC npc : NPCManager.getAllNPCs()) { if (!npc.getShown().contains(player.getUniqueId())) { continue; // NPC was never supposed to be shown to the player. } + if (!npc.getLocation().getWorld().equals(world)) { + continue; // NPC is not in the same world. + } + // If Bukkit doesn't track the NPC entity anymore, bypass the hiding distance variable. // This will cause issues otherwise (e.g. custom skin disappearing). double hideDistance = npc.getAutoHideDistance(); diff --git a/src/net/jitse/npclib/nms/holograms/Hologram.java b/src/net/jitse/npclib/nms/holograms/Hologram.java index 20c18cb..6512eb5 100644 --- a/src/net/jitse/npclib/nms/holograms/Hologram.java +++ b/src/net/jitse/npclib/nms/holograms/Hologram.java @@ -6,6 +6,7 @@ package net.jitse.npclib.nms.holograms; import com.comphenix.tinyprotocol.Reflection; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import java.util.ArrayList; @@ -25,49 +26,49 @@ public class Hologram { private Set destroyPackets = new HashSet<>(); // Classes: - private final Class entityArmorStandClazz = Reflection.getMinecraftClass("EntityArmorStand"); - private final Class entityLivingClazz = Reflection.getMinecraftClass("EntityLiving"); - private final Class entityClazz = Reflection.getMinecraftClass("Entity"); - private final Class craftWorldClazz = Reflection.getCraftBukkitClass("CraftWorld"); - private final Class craftPlayerClazz = Reflection.getCraftBukkitClass("entity.CraftPlayer"); - private final Class packetPlayOutSpawnEntityLivingClazz = Reflection.getMinecraftClass( + private static final Class ENTITY_ARMOR_STAND_CLAZZ = Reflection.getMinecraftClass("EntityArmorStand"); + private static final Class ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass("EntityLiving"); + private static final Class ENTITY_CLAZZ = Reflection.getMinecraftClass("Entity"); + private static final Class CRAFT_BUKKIT_CLASS = Reflection.getCraftBukkitClass("CraftWorld"); + private static final Class CRAFT_PLAYER_CLAZZ = Reflection.getCraftBukkitClass("entity.CraftPlayer"); + private static final Class PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass( "PacketPlayOutSpawnEntityLiving"); - private final Class packetPlayOutEntityDestroyClazz = Reflection.getMinecraftClass( + private static final Class PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ = Reflection.getMinecraftClass( "PacketPlayOutEntityDestroy"); - private final Class entityPlayerClazz = Reflection.getMinecraftClass("EntityPlayer"); - private final Class playerConnectionClazz = Reflection.getMinecraftClass("PlayerConnection"); - private final Class packetClazz = Reflection.getMinecraftClass("Packet"); + private static final Class ENTITY_PLAYER_CLAZZ = Reflection.getMinecraftClass("EntityPlayer"); + private static final Class PLAYER_CONNECTION_CLAZZ = Reflection.getMinecraftClass("PlayerConnection"); + private static final Class PACKET_CLAZZ = Reflection.getMinecraftClass("Packet"); // Constructors: - private final Reflection.ConstructorInvoker packetPlayOutSpawnEntityLivingConstructor = Reflection - .getConstructor(packetPlayOutSpawnEntityLivingClazz, entityLivingClazz); - private final Reflection.ConstructorInvoker packetPlayOutEntityDestroyConstructor = Reflection - .getConstructor(packetPlayOutEntityDestroyClazz, int[].class); + private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR = Reflection + .getConstructor(PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ, ENTITY_LIVING_CLAZZ); + private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR = Reflection + .getConstructor(PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ, int[].class); // Fields: - private final Reflection.FieldAccessor playerConnectionField = Reflection.getField(entityPlayerClazz, - "playerConnection", playerConnectionClazz); + private static final Reflection.FieldAccessor playerConnectionField = Reflection.getField(ENTITY_PLAYER_CLAZZ, + "playerConnection", PLAYER_CONNECTION_CLAZZ); // Methods: - private final Reflection.MethodInvoker setLocationMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_LOCATION_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setLocation", double.class, double.class, double.class, float.class, float.class); - private final Reflection.MethodInvoker setCustomNameMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_CUSTOM_NAME_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomName", String.class); - private final Reflection.MethodInvoker setCustomNameVisibleMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_CUSTOM_NAME_VISIBLE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setCustomNameVisible", boolean.class); - private final Reflection.MethodInvoker setSmallMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_SMALL_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setSmall", boolean.class); - private final Reflection.MethodInvoker setInvisibleMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_INVISIBLE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setInvisible", boolean.class); - private final Reflection.MethodInvoker setBasePlateMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_BASE_PLATE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setBasePlate", boolean.class); - private final Reflection.MethodInvoker setArmsMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SET_ARMS_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setArms", boolean.class); - private final Reflection.MethodInvoker playerGetHandleMethod = Reflection.getMethod(craftPlayerClazz, + private static final Reflection.MethodInvoker PLAYER_GET_HANDLE_METHOD = Reflection.getMethod(CRAFT_PLAYER_CLAZZ, "getHandle"); - private final Reflection.MethodInvoker sendPacketMethod = Reflection.getMethod(playerConnectionClazz, - "sendPacket", packetClazz); - private final Reflection.MethodInvoker getIdMethod = Reflection.getMethod(entityArmorStandClazz, + private static final Reflection.MethodInvoker SEND_PACKET_METHOD = Reflection.getMethod(PLAYER_CONNECTION_CLAZZ, + "sendPacket", PACKET_CLAZZ); + private static final Reflection.MethodInvoker GET_ID_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "getId"); private final Location start; @@ -78,32 +79,37 @@ public class Hologram { this.start = location; this.lines = lines; - this.worldServer = Reflection.getMethod(craftWorldClazz, "getHandle") - .invoke(craftWorldClazz.cast(location.getWorld())); + this.worldServer = Reflection.getMethod(CRAFT_BUKKIT_CLASS, "getHandle") + .invoke(CRAFT_BUKKIT_CLASS.cast(location.getWorld())); } public void generatePackets(boolean above1_9_r2) { - Reflection.MethodInvoker gravityMethod = (above1_9_r2 ? Reflection.getMethod(entityClazz, - "setNoGravity", boolean.class) : Reflection.getMethod(entityArmorStandClazz, + Reflection.MethodInvoker gravityMethod = (above1_9_r2 ? Reflection.getMethod(ENTITY_CLAZZ, + "setNoGravity", boolean.class) : Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ, "setGravity", boolean.class)); Location location = start.clone().add(0, delta * lines.size(), 0); + Class worldClass = worldServer.getClass().getSuperclass(); + + if (start.getWorld().getEnvironment() != World.Environment.NORMAL) { + worldClass = worldClass.getSuperclass(); + } Reflection.ConstructorInvoker entityArmorStandConstructor = Reflection - .getConstructor(entityArmorStandClazz, worldServer.getClass().getSuperclass()); + .getConstructor(ENTITY_ARMOR_STAND_CLAZZ, worldClass); for (String line : lines) { Object entityArmorStand = entityArmorStandConstructor.invoke(worldServer); - setLocationMethod.invoke(entityArmorStand, location.getX(), location.getY(), location.getZ(), 0, 0); - setCustomNameMethod.invoke(entityArmorStand, line); - setCustomNameVisibleMethod.invoke(entityArmorStand, true); - gravityMethod.invoke(entityArmorStand, (above1_9_r2 ? true : false)); - setSmallMethod.invoke(entityArmorStand, true); - setInvisibleMethod.invoke(entityArmorStand, true); - setBasePlateMethod.invoke(entityArmorStand, false); - setArmsMethod.invoke(entityArmorStand, false); + SET_LOCATION_METHOD.invoke(entityArmorStand, location.getX(), location.getY(), location.getZ(), 0, 0); + SET_CUSTOM_NAME_METHOD.invoke(entityArmorStand, line); + SET_CUSTOM_NAME_VISIBLE_METHOD.invoke(entityArmorStand, true); + gravityMethod.invoke(entityArmorStand, above1_9_r2); + SET_SMALL_METHOD.invoke(entityArmorStand, true); + SET_INVISIBLE_METHOD.invoke(entityArmorStand, true); + SET_BASE_PLATE_METHOD.invoke(entityArmorStand, false); + SET_ARMS_METHOD.invoke(entityArmorStand, false); location.subtract(0, delta, 0); @@ -113,30 +119,30 @@ public class Hologram { armorStands.add(entityArmorStand); - Object spawnPacket = packetPlayOutSpawnEntityLivingConstructor.invoke(entityArmorStand); + Object spawnPacket = PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR.invoke(entityArmorStand); spawnPackets.add(spawnPacket); - Object destroyPacket = packetPlayOutEntityDestroyConstructor - .invoke(new int[]{(int) getIdMethod.invoke(entityArmorStand)}); + Object destroyPacket = PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR + .invoke(new int[]{(int) GET_ID_METHOD.invoke(entityArmorStand)}); destroyPackets.add(destroyPacket); } } public void spawn(Player player) { - Object playerConnection = playerConnectionField.get(playerGetHandleMethod - .invoke(craftPlayerClazz.cast(player))); + Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD + .invoke(CRAFT_PLAYER_CLAZZ.cast(player))); for (Object packet : spawnPackets) { - sendPacketMethod.invoke(playerConnection, packet); + SEND_PACKET_METHOD.invoke(playerConnection, packet); } } public void destroy(Player player) { - Object playerConnection = playerConnectionField.get(playerGetHandleMethod - .invoke(craftPlayerClazz.cast(player))); + Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD + .invoke(CRAFT_PLAYER_CLAZZ.cast(player))); for (Object packet : destroyPackets) { - sendPacketMethod.invoke(playerConnection, packet); + SEND_PACKET_METHOD.invoke(playerConnection, packet); } } }