diff --git a/README.md b/README.md index 6c8ee7c..4733814 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # NPC-Lib -Minecraft NPC library for 1.8-1.15 servers. +Asynchronous, high-performance Minecraft NPC library for 1.8-1.16 servers. + This Library does only support the latest patch release of a supported version (for example 1.13.2). Issues with older patch versions (for example 1.13.1) won't be fixed. @@ -20,7 +21,7 @@ Maven com.github.juliarn npc-lib - 2.3-RELEASE + 2.4-RELEASE ``` @@ -31,7 +32,7 @@ maven { url 'https://jitpack.io' } -compile group: 'com.github.juliarn', name: 'npc-lib', version: '2.3-RELEASE' +compile group: 'com.github.juliarn', name: 'npc-lib', version: '2.4-RELEASE' ``` Add ProtocolLib as dependency to your plugin.yml. It could look like this: diff --git a/pom.xml b/pom.xml index 74a98d4..26fdbfe 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.juliarn npc-lib - 2.3-RELEASE + 2.4-RELEASE jar @@ -46,7 +46,7 @@ com.comphenix.protocol ProtocolLib - 4.5.0 + 4.6.0-SNAPSHOT provided diff --git a/src/main/java/com/github/juliarn/npc/NPC.java b/src/main/java/com/github/juliarn/npc/NPC.java index dd13844..ec3f110 100644 --- a/src/main/java/com/github/juliarn/npc/NPC.java +++ b/src/main/java/com/github/juliarn/npc/NPC.java @@ -21,6 +21,8 @@ public class NPC { private final Collection seeingPlayers = new CopyOnWriteArraySet<>(); + private final Collection excludedPlayers = new CopyOnWriteArraySet<>(); + private final int entityId = RANDOM.nextInt(Short.MAX_VALUE); private final WrappedGameProfile gameProfile; @@ -81,6 +83,35 @@ public boolean isShownFor(Player player) { return this.seeingPlayers.contains(player); } + /** + * Adds a player which should be explicitly excluded from seeing this NPC + * + * @param player the player to be excluded + */ + public void addExcludedPlayer(Player player) { + this.excludedPlayers.add(player); + } + + /** + * Removes a player from being explicitly excluded from seeing this NPC + * + * @param player the player to be included again + */ + public void removeExcludedPlayer(Player player) { + this.excludedPlayers.remove(player); + } + + /** + * @return a modifiable collection of all players which are explicitly excluded from seeing this NPC + */ + public Collection getExcludedPlayers() { + return this.excludedPlayers; + } + + public boolean isExcluded(Player player) { + return this.excludedPlayers.contains(player); + } + /** * Creates a new animation modifier which serves methods to play animations on an NPC * diff --git a/src/main/java/com/github/juliarn/npc/NPCPool.java b/src/main/java/com/github/juliarn/npc/NPCPool.java index 3ecffc7..5449a07 100644 --- a/src/main/java/com/github/juliarn/npc/NPCPool.java +++ b/src/main/java/com/github/juliarn/npc/NPCPool.java @@ -19,6 +19,7 @@ import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -102,18 +103,21 @@ private void npcTick() { for (Player player : ImmutableList.copyOf(Bukkit.getOnlinePlayers())) { for (NPC npc : this.npcMap.values()) { if (!npc.getLocation().getWorld().equals(player.getLocation().getWorld())) { + if (npc.isShownFor(player)) { + npc.hide(player); + } continue; } double distance = npc.getLocation().distanceSquared(player.getLocation()); - if (distance >= this.spawnDistance && npc.isShownFor(player)) { + if ((npc.isExcluded(player) || distance >= this.spawnDistance) && npc.isShownFor(player)) { npc.hide(player); - } else if (distance <= this.spawnDistance && !npc.isShownFor(player)) { + } else if ((!npc.isExcluded(player) && distance <= this.spawnDistance) && !npc.isShownFor(player)) { npc.show(player, this.javaPlugin, this.tabListRemoveTicks); } - if (npc.isLookAtPlayer() && distance <= this.actionDistance) { + if (npc.isShownFor(player) && npc.isLookAtPlayer() && distance <= this.actionDistance) { npc.rotation().queueLookAt(player.getLocation()).send(player); } } @@ -140,12 +144,24 @@ public void removeNPC(int entityId) { } @EventHandler - public void handleQuit(PlayerQuitEvent event) { + public void handleRespawn(PlayerRespawnEvent event) { Player player = event.getPlayer(); this.npcMap.values().stream() .filter(npc -> npc.isShownFor(player)) - .forEach(npc -> npc.removeSeeingPlayer(player)); + .forEach(npc -> npc.hide(player)); + } + + @EventHandler + public void handleQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + this.npcMap.values().stream() + .filter(npc -> npc.isShownFor(player) || npc.isExcluded(player)) + .forEach(npc -> { + npc.removeSeeingPlayer(player); + npc.removeExcludedPlayer(player); + }); } @EventHandler @@ -153,7 +169,8 @@ public void handleSneak(PlayerToggleSneakEvent event) { Player player = event.getPlayer(); this.npcMap.values().stream() - .filter(npc -> npc.isImitatePlayer() && npc.isShownFor(player) && npc.getLocation().distanceSquared(player.getLocation()) <= this.actionDistance) + .filter(npc -> npc.isImitatePlayer() && npc.isShownFor(player)) + .filter(npc -> npc.getLocation().getWorld().equals(player.getWorld()) && npc.getLocation().distanceSquared(player.getLocation()) <= this.actionDistance) .forEach(npc -> npc.metadata().queue(MetadataModifier.EntityMetadata.SNEAKING, event.isSneaking()).send(player)); } @@ -163,7 +180,8 @@ public void handleClick(PlayerInteractEvent event) { if (event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) { this.npcMap.values().stream() - .filter(npc -> npc.isImitatePlayer() && npc.isShownFor(player) && npc.getLocation().distanceSquared(player.getLocation()) <= this.actionDistance) + .filter(npc -> npc.isImitatePlayer() && npc.isShownFor(player)) + .filter(npc -> npc.getLocation().getWorld().equals(player.getWorld()) && npc.getLocation().distanceSquared(player.getLocation()) <= this.actionDistance) .forEach(npc -> npc.animation().queue(AnimationModifier.EntityAnimation.SWING_MAIN_ARM).send(player)); } } diff --git a/src/main/java/com/github/juliarn/npc/modifier/EquipmentModifier.java b/src/main/java/com/github/juliarn/npc/modifier/EquipmentModifier.java index b28fb8d..36f9c5c 100644 --- a/src/main/java/com/github/juliarn/npc/modifier/EquipmentModifier.java +++ b/src/main/java/com/github/juliarn/npc/modifier/EquipmentModifier.java @@ -4,10 +4,13 @@ import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.Pair; import com.github.juliarn.npc.NPC; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Collections; + public class EquipmentModifier extends NPCModifier { public EquipmentModifier(@NotNull NPC npc) { @@ -17,8 +20,12 @@ public EquipmentModifier(@NotNull NPC npc) { public EquipmentModifier queue(@NotNull EnumWrappers.ItemSlot itemSlot, @NotNull ItemStack equipment) { PacketContainer packetContainer = super.newContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); - packetContainer.getItemSlots().write(MINECRAFT_VERSION < 9 ? 1 : 0, itemSlot); - packetContainer.getItemModifier().write(0, equipment); + if (MINECRAFT_VERSION < 16) { + packetContainer.getItemSlots().write(MINECRAFT_VERSION < 9 ? 1 : 0, itemSlot); + packetContainer.getItemModifier().write(0, equipment); + } else { + packetContainer.getSlotStackPairLists().write(0, Collections.singletonList(new Pair<>(itemSlot, equipment))); + } return this; } @@ -26,8 +33,17 @@ public EquipmentModifier queue(@NotNull EnumWrappers.ItemSlot itemSlot, @NotNull public EquipmentModifier queue(int itemSlot, @NotNull ItemStack equipment) { PacketContainer packetContainer = super.newContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); - packetContainer.getIntegers().write(MINECRAFT_VERSION < 9 ? 1 : 0, itemSlot); - packetContainer.getItemModifier().write(0, equipment); + if (MINECRAFT_VERSION < 16) { + packetContainer.getIntegers().write(MINECRAFT_VERSION < 9 ? 1 : 0, itemSlot); + packetContainer.getItemModifier().write(0, equipment); + } else { + for (EnumWrappers.ItemSlot slot : EnumWrappers.ItemSlot.values()) { + if (slot.ordinal() == itemSlot) { + packetContainer.getSlotStackPairLists().write(0, Collections.singletonList(new Pair<>(slot, equipment))); + break; + } + } + } return this; }