From 64ac8bed9e405803fed1ea5b8dae34f66ac08ef4 Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Wed, 3 Dec 2014 23:48:37 -0800 Subject: [PATCH 1/6] First pass at removing NPCs from the playerlist imperfect (causes pointless respawns) but a functional proof-of-concept. --- .../java/net/citizensnpcs/EventListen.java | 54 ++++++++++++++++--- .../npc/entity/EntityHumanNPC.java | 6 --- .../npc/entity/HumanController.java | 13 ++++- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index d20e7b018..340a861f7 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,13 +273,55 @@ 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 + ((CraftPlayer)event.getPlayer()).getHandle().playerConnection.sendPacket( + new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) npc .getEntity()).getHandle())); } } + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + for (NPC npc : getAllNPCs()) { + if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + ((CraftPlayer)event.getPlayer()).getHandle().playerConnection.sendPacket( + new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) npc + .getEntity()).getHandle())); + } + } + } + }, 60); + } + + @EventHandler + public void onPlayerWalks(final PlayerMoveEvent event) { + if (event.getFrom().getY() > 255 || event.getFrom().getY() < 0 + || event.getTo().getY() > 255 || event.getTo().getY() < 0) { + return; // Don't fire if players go outside the world, as that would be more difficult to handle. + } + Location from = event.getFrom().getBlock().getLocation(); + Location to = event.getTo().getBlock().getLocation(); + 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 (npc.getEntity().getLocation().distanceSquared(to) < maxRad && + npc.getEntity().getLocation().distanceSquared(from) > maxRad) { + // TODO: Replicate this with packets! + npc.despawn(); + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + npc.spawn(npc.getStoredLocation()); + } + }, 1); + } + } + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 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..5d3ccb54c 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()); } @@ -86,6 +87,16 @@ public class HumanController extends AbstractEntityController { }, 1); handle.getBukkitEntity().setSleepingIgnored(true); NMS.sendToOnline(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, handle)); + 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.sendToOnline(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, + ((CraftPlayer) getBukkitEntity()).getHandle())); + } + } + }, 60); return handle.getBukkitEntity(); } From 306138165f6dcdb383b7765f802d2bd3494c3fe1 Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Thu, 4 Dec 2014 16:12:45 -0800 Subject: [PATCH 2/6] Second pass: error fixing, more invisibility fixes Handle teleporting, avoid cross-world errors. --- .../java/net/citizensnpcs/EventListen.java | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index 340a861f7..f50939b71 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -306,19 +306,53 @@ public class EventListen implements Listener { if (from.equals(to)) { return; // Don't fire on every movement, just full block+. } + if (!from.getWorld().getName().equalsIgnoreCase(to.getWorld().getName())) { + 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 (npc.getEntity().getLocation().distanceSquared(to) < maxRad && - npc.getEntity().getLocation().distanceSquared(from) > maxRad) { - // TODO: Replicate this with packets! - npc.despawn(); - Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { - @Override - public void run() { - npc.spawn(npc.getStoredLocation()); - } - }, 1); + 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) { + // TODO: Replicate this with packets! + npc.despawn(); + Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { + @Override + public void run() { + npc.spawn(npc.getStoredLocation()); + } + }, 1); + } + + @EventHandler + public void onPlayerTeleports(PlayerTeleportEvent event) { + if (event.getFrom().getY() > 255 || event.getFrom().getY() < 0 + || event.getTo().getY() > 255 || event.getTo().getY() < 0) { + return; // Don't fire if players go outside the world, as that would be more difficult to handle. + } + Location from = event.getFrom().getBlock().getLocation(); + Location to = event.getTo().getBlock().getLocation(); + 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) + if ((to.getWorld().getName().equalsIgnoreCase(npc.getEntity().getLocation().getWorld().getName()) + && npc.getEntity().getLocation().distanceSquared(to) < maxRad) + && ((from.getWorld().getName().equalsIgnoreCase(npc.getEntity().getLocation().getWorld().getName()) + && npc.getEntity().getLocation().distanceSquared(from) > maxRad) + || !from.getWorld().getName().equalsIgnoreCase(to.getWorld().getName()))) { + showNPCReset(event.getPlayer(), npc); } } } From 1c6a1cc719293777d578ab4c0a98c47404f19f1b Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Fri, 5 Dec 2014 22:06:03 -0800 Subject: [PATCH 3/6] Third pass: packet magic to pseudo-respawn NPCs also cache the NPC position to slightly improve efficiency. Micro-effic matters a lot here. --- .../java/net/citizensnpcs/EventListen.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index f50939b71..ba97c257f 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -322,14 +322,30 @@ public class EventListen implements Listener { } public void showNPCReset(final Player player, final NPC npc) { - // TODO: Replicate this with packets! - npc.despawn(); + ((CraftPlayer)player).getHandle().playerConnection.sendPacket + (new PacketPlayOutEntityDestroy(npc.getEntity().getEntityId())); Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { @Override public void run() { - npc.spawn(npc.getStoredLocation()); + if (player.isOnline() && player.isValid() + && npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo + (EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) npc.getEntity()).getHandle())); + ((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) { + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo + (EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) npc.getEntity()).getHandle())); + } + } + }, 61); } @EventHandler @@ -347,11 +363,12 @@ public class EventListen implements Listener { 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) - if ((to.getWorld().getName().equalsIgnoreCase(npc.getEntity().getLocation().getWorld().getName()) - && npc.getEntity().getLocation().distanceSquared(to) < maxRad) - && ((from.getWorld().getName().equalsIgnoreCase(npc.getEntity().getLocation().getWorld().getName()) - && npc.getEntity().getLocation().distanceSquared(from) > maxRad) - || !from.getWorld().getName().equalsIgnoreCase(to.getWorld().getName()))) { + 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); } } From 33ef7d2a55a0ba3d53ece7747b445f110b114ee2 Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Fri, 5 Dec 2014 22:11:03 -0800 Subject: [PATCH 4/6] micro-efficiency, improvements Don't call .getBlock which is both slow and doesn't work in all cases. Also don't compare worlds by name, compare by instance. --- src/main/java/net/citizensnpcs/EventListen.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index ba97c257f..6a6c5e046 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -295,18 +295,19 @@ public class EventListen implements Listener { }, 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) { - if (event.getFrom().getY() > 255 || event.getFrom().getY() < 0 - || event.getTo().getY() > 255 || event.getTo().getY() < 0) { - return; // Don't fire if players go outside the world, as that would be more difficult to handle. - } - Location from = event.getFrom().getBlock().getLocation(); - Location to = event.getTo().getBlock().getLocation(); + 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().getName().equalsIgnoreCase(to.getWorld().getName())) { + if (from.getWorld() != to.getWorld()) { return; // Ignore cross-world movement for now. } int maxRad = 50 * 50; // TODO: Adjust me to perfection From ab67c208d1cb9fe305dfebcf582727da8df605e4 Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Sat, 6 Dec 2014 19:07:06 -0800 Subject: [PATCH 5/6] Refactor Move the playerlsit packet handling to the NMS class --- .../java/net/citizensnpcs/EventListen.java | 8 ++--- src/main/java/net/citizensnpcs/util/NMS.java | 29 +++++-------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index 6a6c5e046..06ef7a630 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -276,9 +276,7 @@ public class EventListen implements Listener { public void onPlayerJoin(final PlayerJoinEvent event) { for (NPC npc : getAllNPCs()) { if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { - ((CraftPlayer)event.getPlayer()).getHandle().playerConnection.sendPacket( - new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) npc - .getEntity()).getHandle())); + NMS.sendPlayerlistPacket(true, event.getPlayer(), npc); } } Bukkit.getScheduler().scheduleSyncDelayedTask(CitizensAPI.getPlugin(), new Runnable() { @@ -286,9 +284,7 @@ public class EventListen implements Listener { public void run() { for (NPC npc : getAllNPCs()) { if (npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { - ((CraftPlayer)event.getPlayer()).getHandle().playerConnection.sendPacket( - new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) npc - .getEntity()).getHandle())); + NMS.sendPlayerlistPacket(false, event.getPlayer(), npc); } } } diff --git a/src/main/java/net/citizensnpcs/util/NMS.java b/src/main/java/net/citizensnpcs/util/NMS.java index 9b02c8638..4c8db24c7 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,12 @@ public class NMS { } } + public static void sendPlayerlistPacket(boolean showInPlayerlist, Player player, NPC npc) { + ((CraftPlayer)player).getHandle().playerConnection.sendPacket( + new PacketPlayOutPlayerInfo(showInPlayerlist ? EnumPlayerInfoAction.ADD_PLAYER: + EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) npc.getEntity()).getHandle())); + } + public static void updatePathfindingRange(NPC npc, float pathfindingRange) { if (!npc.isSpawned() || !npc.getEntity().getType().isAlive()) return; From 50fa13cef0e1cbbd3bc4ea77182eebf97a0c7075 Mon Sep 17 00:00:00 2001 From: mcmonkey4eva Date: Sun, 7 Dec 2014 18:04:13 -0800 Subject: [PATCH 6/6] Handle all cases of the packet in NMS.java --- .../java/net/citizensnpcs/EventListen.java | 14 ++++---------- .../npc/entity/HumanController.java | 8 +++----- src/main/java/net/citizensnpcs/util/NMS.java | 19 ++++++++++++++++--- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/citizensnpcs/EventListen.java b/src/main/java/net/citizensnpcs/EventListen.java index 06ef7a630..41c948410 100644 --- a/src/main/java/net/citizensnpcs/EventListen.java +++ b/src/main/java/net/citizensnpcs/EventListen.java @@ -326,8 +326,7 @@ public class EventListen implements Listener { public void run() { if (player.isOnline() && player.isValid() && npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { - ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo - (EnumPlayerInfoAction.ADD_PLAYER, ((CraftPlayer) npc.getEntity()).getHandle())); + NMS.sendPlayerlistPacket(true, player, npc); ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutNamedEntitySpawn (((CraftPlayer) npc.getEntity()).getHandle())); } @@ -338,8 +337,7 @@ public class EventListen implements Listener { public void run() { if (player.isOnline() && player.isValid() && npc.isSpawned() && npc.getEntity().getType() == EntityType.PLAYER) { - ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo - (EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) npc.getEntity()).getHandle())); + NMS.sendPlayerlistPacket(false, player, npc); } } }, 61); @@ -347,12 +345,8 @@ public class EventListen implements Listener { @EventHandler public void onPlayerTeleports(PlayerTeleportEvent event) { - if (event.getFrom().getY() > 255 || event.getFrom().getY() < 0 - || event.getTo().getY() > 255 || event.getTo().getY() < 0) { - return; // Don't fire if players go outside the world, as that would be more difficult to handle. - } - Location from = event.getFrom().getBlock().getLocation(); - Location to = event.getTo().getBlock().getLocation(); + Location from = roundLocation(event.getFrom()); + Location to = roundLocation(event.getTo()); if (from.equals(to)) { return; // Don't fire on every movement, just full block+. } diff --git a/src/main/java/net/citizensnpcs/npc/entity/HumanController.java b/src/main/java/net/citizensnpcs/npc/entity/HumanController.java index 5d3ccb54c..9086d0c5d 100644 --- a/src/main/java/net/citizensnpcs/npc/entity/HumanController.java +++ b/src/main/java/net/citizensnpcs/npc/entity/HumanController.java @@ -86,14 +86,13 @@ 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.sendToOnline(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, - ((CraftPlayer) getBukkitEntity()).getHandle())); + NMS.sendPlayerlistPacket(false, null, npc); } } }, 60); @@ -107,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 4c8db24c7..9f56d0730 100644 --- a/src/main/java/net/citizensnpcs/util/NMS.java +++ b/src/main/java/net/citizensnpcs/util/NMS.java @@ -500,10 +500,23 @@ 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) { - ((CraftPlayer)player).getHandle().playerConnection.sendPacket( - new PacketPlayOutPlayerInfo(showInPlayerlist ? EnumPlayerInfoAction.ADD_PLAYER: - EnumPlayerInfoAction.REMOVE_PLAYER, ((CraftPlayer) npc.getEntity()).getHandle())); + 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) {