diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index d20e7b018..41c948410 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -29,8 +29,7 @@ import net.citizensnpcs.trait.Controllable; import net.citizensnpcs.trait.CurrentLocation; import net.citizensnpcs.util.Messages; import net.citizensnpcs.util.NMS; -import net.minecraft.server.v1_8_R1.EnumPlayerInfoAction; -import net.minecraft.server.v1_8_R1.PacketPlayOutPlayerInfo; +import net.minecraft.server.v1_8_R1.*; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -50,10 +49,7 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityTargetEvent; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.*; import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkUnloadEvent; @@ -277,11 +273,95 @@ public class EventListen implements Listener { } @EventHandler(ignoreCancelled = true) - public void onPlayerJoin(PlayerJoinEvent event) { + public void onPlayerJoin(final PlayerJoinEvent event) { for (NPC npc : getAllNPCs()) { if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { - NMS.sendToOnline(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) npc - .getEntity()).getHandle())); + NMS.sendPlayerlistPacket(true, event.getPlayer(), npc); + } + } + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + for (NPC npc : getAllNPCs()) { + if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + NMS.sendPlayerlistPacket(false, event.getPlayer(), npc); + } + } + } + }, 60); + } + + Location roundLocation(Location input) { + return new Location(input.getWorld(), Math.floor(input.getX()), + Math.floor(input.getY()), Math.floor(input.getZ())); + } + + @EventHandler + public void onPlayerWalks(final PlayerMoveEvent event) { + Location from = roundLocation(event.getFrom()); + Location to = roundLocation(event.getTo()); + if (from.equals(to)) { + return; // Don't fire on every movement, just full block+. + } + if (from.getWorld() != to.getWorld()) { + return; // Ignore cross-world movement for now. + } + int maxRad = 50 * 50; // TODO: Adjust me to perfection + for (final NPC npc: getAllNPCs()) { + if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + if (from.getWorld().getName().equalsIgnoreCase(npc.getEntity().getLocation().getWorld().getName()) + && npc.getEntity().getLocation().distanceSquared(to) < maxRad + && npc.getEntity().getLocation().distanceSquared(from) > maxRad) { + showNPCReset(event.getPlayer(), npc); + } + } + } + } + + public void showNPCReset(final Player player, final NPC npc) { + ((CraftPlayer)player).getHandle().playerConnection.sendPacket + (new PacketPlayOutEntityDestroy(npc.getEntity().getEntityId())); + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + if (player.isOnline() && player.isValid() + && npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + NMS.sendPlayerlistPacket(true, player, npc); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutNamedEntitySpawn + (((CraftPlayer) npc.getEntity()).getHandle())); + } + } + }, 1); + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + if (player.isOnline() && player.isValid() + && npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + NMS.sendPlayerlistPacket(false, player, npc); + } + } + }, 61); + } + + @EventHandler + public void onPlayerTeleports(PlayerTeleportEvent event) { + Location from = roundLocation(event.getFrom()); + Location to = roundLocation(event.getTo()); + if (from.equals(to)) { + return; // Don't fire on every movement, just full block+. + } + int maxRad = 50 * 50; // TODO: Adjust me to perfection + for (final NPC npc: getAllNPCs()) { + if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + // if to.world=npc.world and in-range, and if (from.world = npc.world and out-of-range, or from a different world) + Location npcPos = npc.getEntity().getLocation(); + if ((to.getWorld() == npcPos.getWorld() + && npcPos.distanceSquared(to) < maxRad) + && ((from.getWorld() == npcPos.getWorld() + && npcPos.distanceSquared(from) > maxRad) + || from.getWorld() != to.getWorld())) { + showNPCReset(event.getPlayer(), npc); + } } } } diff --git a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java index 91c31cc2c..5b3eed33d 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java +++ b/src/main/java/net/citizensnpcs/npc/entity/EntityHumanNPC.java @@ -35,7 +35,6 @@ import net.minecraft.server.v1_8_R1.NetworkManager; import net.minecraft.server.v1_8_R1.Packet; import net.minecraft.server.v1_8_R1.PacketPlayOutEntityEquipment; import net.minecraft.server.v1_8_R1.PacketPlayOutEntityHeadRotation; -import net.minecraft.server.v1_8_R1.PacketPlayOutPlayerInfo; import net.minecraft.server.v1_8_R1.PlayerInteractManager; import net.minecraft.server.v1_8_R1.WorldServer; @@ -143,11 +142,6 @@ public class EntityHumanNPC extends EntityPlayer implements NPCHolder { return controllerMove; } - private Packet getListPacket(Player player, boolean removeFromPlayerList) { - return new PacketPlayOutPlayerInfo(removeFromPlayerList ? EnumPlayerInfoAction.REMOVE_PLAYER - : EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) player).getHandle()); - } - public NavigationAbstract getNavigation() { return navigation; } diff --git a/src/main/java/net/citizensnpcs/npc/entity/HumanController.java b/src/main/java/net/citizensnpcs/npc/entity/HumanController.java index 6587202be..9086d0c5d 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/HumanController.java +++ b/src/main/java/net/citizensnpcs/npc/entity/HumanController.java @@ -29,6 +29,7 @@ import org.bukkit.craftbukkit.v1_8_R1.CraftServer; import org.bukkit.craftbukkit.v1_8_R1.CraftWorld; import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import com.google.common.collect.Iterables; @@ -66,7 +67,7 @@ public class HumanController extends AbstractEntityController { if (uuid.version() == 4) { // clear version long msb = uuid.getMostSignificantBits(); msb &= ~0x0000000000004000L; - msb |= 0x0000000000002000L; + msb |= 0x0000000000002000L; uuid = new UUID(msb, uuid.getLeastSignificantBits()); } @@ -85,7 +86,16 @@ public class HumanController extends AbstractEntityController { } }, 1); handle.getBukkitEntity().setSleepingIgnored(true); - NMS.sendToOnline(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, handle)); + NMS.sendPlayerlistPacket(true, null, handle.getBukkitEntity()); + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + // Double check that we're still spawned and haven't changed type. + if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + NMS.sendPlayerlistPacket(false, null, npc); + } + } + }, 60); return handle.getBukkitEntity(); } @@ -96,8 +106,7 @@ public class HumanController extends AbstractEntityController { @Override public void remove() { - NMS.sendToOnline(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, - ((CraftPlayer) getBukkitEntity()).getHandle())); + NMS.sendPlayerlistPacket(false, null, (CraftPlayer)getBukkitEntity()); super.remove(); } diff --git a/src/main/java/net/citizensnpcs/util/NMS.java b/src/main/java/net/citizensnpcs/util/NMS.java index 9b02c8638..9f56d0730 100644 --- a/src/main/java/net/citizensnpcs/util/NMS.java +++ b/src/main/java/net/citizensnpcs/util/NMS.java @@ -16,27 +16,7 @@ import net.citizensnpcs.api.util.Messaging; import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.entity.EntityHumanNPC; import net.citizensnpcs.npc.network.EmptyChannel; -import net.minecraft.server.v1_8_R1.AttributeInstance; -import net.minecraft.server.v1_8_R1.Block; -import net.minecraft.server.v1_8_R1.BlockPosition; -import net.minecraft.server.v1_8_R1.ControllerJump; -import net.minecraft.server.v1_8_R1.DamageSource; -import net.minecraft.server.v1_8_R1.EnchantmentManager; -import net.minecraft.server.v1_8_R1.Entity; -import net.minecraft.server.v1_8_R1.EntityHorse; -import net.minecraft.server.v1_8_R1.EntityHuman; -import net.minecraft.server.v1_8_R1.EntityInsentient; -import net.minecraft.server.v1_8_R1.EntityLiving; -import net.minecraft.server.v1_8_R1.EntityMinecartAbstract; -import net.minecraft.server.v1_8_R1.EntityPlayer; -import net.minecraft.server.v1_8_R1.EntityTypes; -import net.minecraft.server.v1_8_R1.GenericAttributes; -import net.minecraft.server.v1_8_R1.MathHelper; -import net.minecraft.server.v1_8_R1.NavigationAbstract; -import net.minecraft.server.v1_8_R1.NetworkManager; -import net.minecraft.server.v1_8_R1.Packet; -import net.minecraft.server.v1_8_R1.PathfinderGoalSelector; -import net.minecraft.server.v1_8_R1.World; +import net.minecraft.server.v1_8_R1.*; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -47,6 +27,7 @@ import org.bukkit.craftbukkit.v1_8_R1.CraftServer; import org.bukkit.craftbukkit.v1_8_R1.CraftSound; import org.bukkit.craftbukkit.v1_8_R1.CraftWorld; import org.bukkit.craftbukkit.v1_8_R1.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer; import org.bukkit.entity.EntityType; import org.bukkit.entity.Horse; import org.bukkit.entity.LivingEntity; @@ -519,6 +500,25 @@ public class NMS { } } + /** + * Send a PlayerInfo packet (adds or removes the NPC to or from the tab list) to the player. + * @param player The player to send the packet to, or null for all players. + */ + public static void sendPlayerlistPacket(boolean showInPlayerlist, Player player, NPC npc) { + sendPlayerlistPacket(showInPlayerlist, player, (CraftPlayer)npc.getEntity()); + } + + public static void sendPlayerlistPacket(boolean showInPlayerlist, Player player, CraftPlayer npc) { + PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(showInPlayerlist ? EnumPlayerInfoAction.ADD_PLAYER: + EnumPlayerInfoAction.REMOVE_PLAYER, npc.getHandle()); + if (player == null) { + sendToOnline(packet); + } + else { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + } + public static void updatePathfindingRange(NPC npc, float pathfindingRange) { if (!npc.isSpawned() || !npc.getEntity().getType().isAlive()) return;