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;
}