From 96028d2ca18df8b639cea68bfda3b6952edf9fab Mon Sep 17 00:00:00 2001 From: Jitse Boonstra Date: Sun, 20 Oct 2019 10:48:12 +0200 Subject: [PATCH] Started work on #20 --- .../main/java/net/jitse/npclib/api/NPC.java | 20 +++++ .../net/jitse/npclib/api/state/NPCSlot.java | 20 +++++ .../net/jitse/npclib/api/state/NPCState.java | 25 ++++++ .../jitse/npclib/internal/PacketHandler.java | 10 +++ .../net/jitse/npclib/internal/SimpleNPC.java | 88 +++++++++++++++++++ .../jitse/npclib/listeners/ChunkListener.java | 4 +- nms/pom.xml | 20 ++--- .../jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java | 45 +++++++++- .../PacketPlayOutEntityMetadataWrapper.java | 16 ++++ 9 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 api/src/main/java/net/jitse/npclib/api/state/NPCSlot.java create mode 100644 api/src/main/java/net/jitse/npclib/api/state/NPCState.java create mode 100644 nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityMetadataWrapper.java diff --git a/api/src/main/java/net/jitse/npclib/api/NPC.java b/api/src/main/java/net/jitse/npclib/api/NPC.java index 3312cf7..9299468 100644 --- a/api/src/main/java/net/jitse/npclib/api/NPC.java +++ b/api/src/main/java/net/jitse/npclib/api/NPC.java @@ -5,9 +5,12 @@ package net.jitse.npclib.api; import net.jitse.npclib.api.skin.Skin; +import net.jitse.npclib.api.state.NPCSlot; +import net.jitse.npclib.api.state.NPCState; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; public interface NPC { @@ -88,4 +91,21 @@ public interface NPC { * Requires {@link NPC#create} to be used first. */ void destroy(); + + /** + * Toggle a state of the NPC. + * + * @param state The state to be toggled. + * @return Object instance. + */ + NPC toggleState(NPCState state); + + /** + * Change the item in the inventory of the NPC. + * + * @param slot The slot to set the item of. + * @param item The item to set. + * @return Object instance. + */ + NPC setItem(NPCSlot slot, ItemStack item); } diff --git a/api/src/main/java/net/jitse/npclib/api/state/NPCSlot.java b/api/src/main/java/net/jitse/npclib/api/state/NPCSlot.java new file mode 100644 index 0000000..e868eeb --- /dev/null +++ b/api/src/main/java/net/jitse/npclib/api/state/NPCSlot.java @@ -0,0 +1,20 @@ +package net.jitse.npclib.api.state; + +public enum NPCSlot { + + HELMET(4), + CHESTPLATE(3), + LEGGINGS(2), + BOOTS(1), + IN_HAND(0); + + int slot; + + NPCSlot(int slot) { + this.slot = slot; + } + + public int getSlot() { + return slot; + } +} diff --git a/api/src/main/java/net/jitse/npclib/api/state/NPCState.java b/api/src/main/java/net/jitse/npclib/api/state/NPCState.java new file mode 100644 index 0000000..ea17d9e --- /dev/null +++ b/api/src/main/java/net/jitse/npclib/api/state/NPCState.java @@ -0,0 +1,25 @@ +package net.jitse.npclib.api.state; + +public enum NPCState { + + ON_FIRE((byte) 1), + CROUCHED((byte) 2), + INVISIBLE((byte) 32); + + private byte b; + + NPCState(byte b) { + this.b = b; + } + + public byte getByte() { + return b; + } + + public static byte getMasked(final NPCState... status) { + byte b = 0; + for (NPCState s : status) b |= s.getByte(); + return b; + } +} + diff --git a/api/src/main/java/net/jitse/npclib/internal/PacketHandler.java b/api/src/main/java/net/jitse/npclib/internal/PacketHandler.java index 2c4ea29..9ea6cc9 100755 --- a/api/src/main/java/net/jitse/npclib/internal/PacketHandler.java +++ b/api/src/main/java/net/jitse/npclib/internal/PacketHandler.java @@ -4,6 +4,7 @@ package net.jitse.npclib.internal; +import net.jitse.npclib.api.state.NPCSlot; import org.bukkit.entity.Player; /** @@ -16,4 +17,13 @@ interface PacketHandler { void sendShowPackets(Player player); void sendHidePackets(Player player); + + void sendMetadataPacket(Player player); + + void sendEquipmentPacket(Player player, NPCSlot slot); + + default void sendEquipmentPackets(Player player) { + for (NPCSlot slot : NPCSlot.values()) + sendEquipmentPacket(player, slot); + } } diff --git a/api/src/main/java/net/jitse/npclib/internal/SimpleNPC.java b/api/src/main/java/net/jitse/npclib/internal/SimpleNPC.java index 497dc2b..f021cf0 100644 --- a/api/src/main/java/net/jitse/npclib/internal/SimpleNPC.java +++ b/api/src/main/java/net/jitse/npclib/internal/SimpleNPC.java @@ -11,10 +11,13 @@ import net.jitse.npclib.api.NPC; import net.jitse.npclib.api.events.NPCHideEvent; import net.jitse.npclib.api.events.NPCShowEvent; import net.jitse.npclib.api.skin.Skin; +import net.jitse.npclib.api.state.NPCSlot; +import net.jitse.npclib.api.state.NPCState; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import java.util.*; @@ -32,11 +35,14 @@ public abstract class SimpleNPC implements NPC, PacketHandler { private final Set autoHidden = new HashSet<>(); protected double cosFOV = Math.cos(Math.toRadians(60)); + protected NPCState[] activeStates = new NPCState[]{}; protected NPCLib instance; protected Location location; protected Skin skin; + protected ItemStack helmet, chestplate, leggings, boots, inHand; + public SimpleNPC(NPCLib instance, List lines) { this.instance = instance; this.lines = lines == null ? Collections.emptyList() : lines; @@ -153,6 +159,8 @@ public abstract class SimpleNPC implements NPC, PacketHandler { if (auto) { sendShowPackets(player); + sendMetadataPacket(player); + sendEquipmentPackets(player); } else { if (isShown(player)) { throw new RuntimeException("Cannot call show method twice."); @@ -167,6 +175,8 @@ public abstract class SimpleNPC implements NPC, PacketHandler { if (player.getWorld().equals(location.getWorld()) && player.getLocation().distance(location) <= instance.getAutoHideDistance()) { sendShowPackets(player); + sendMetadataPacket(player); + sendEquipmentPackets(player); } else { autoHidden.add(player.getUniqueId()); } @@ -207,4 +217,82 @@ public abstract class SimpleNPC implements NPC, PacketHandler { } } } + + @Override + public NPC toggleState(NPCState state) { + int inActiveStatesIndex = -1; + if (activeStates.length == 0) { // If there're no active states, this is the first to be toggled (on). + activeStates = new NPCState[]{state}; + } else { // Otherwise, there have been states that were toggled, check if we need to toggle something off. + for (int i = 0; i < activeStates.length; i++) { + if (activeStates[i] == state) { // If the state is to be toggled off, save the index so we can remove it. + inActiveStatesIndex = i; + break; + } + } + + if (inActiveStatesIndex > -1) { // If there's a state to be toggled of, create a new array with all items but the one to be toggled off. + NPCState[] newArr = new NPCState[activeStates.length - 1]; + for (int i = 0; i < newArr.length; i++) { + if (inActiveStatesIndex == i) { + continue; + } else if (i < inActiveStatesIndex) { + newArr[i] = activeStates[i]; + } else { + newArr[i] = activeStates[i + 1]; + } + } + activeStates = newArr; + } else { // Else, we need to add a state by appending our state to the array. + NPCState[] newArr = new NPCState[activeStates.length + 1]; + System.arraycopy(activeStates, 0, newArr, 0, activeStates.length); + newArr[activeStates.length] = state; + activeStates = newArr; + } + } + + // Send a new metadata packet to all players that can see the NPC. + for (UUID shownUuid : shown) { + Player player = Bukkit.getPlayer(shownUuid); + if (player != null && isShown(player)) { + sendMetadataPacket(player); + } + } + return this; + } + + @Override + public NPC setItem(NPCSlot slot, ItemStack item) { + if (slot == null) { + throw new NullPointerException("Slot cannot be null"); + } + + switch (slot) { + case HELMET: + this.helmet = item; + break; + case CHESTPLATE: + this.chestplate = item; + break; + case LEGGINGS: + this.leggings = item; + break; + case BOOTS: + this.boots = item; + break; + case IN_HAND: + this.inHand = item; + break; + default: + throw new IllegalArgumentException("Entered an invalid inventory slot"); + } + + for (UUID shownUuid : shown) { + Player player = Bukkit.getPlayer(shownUuid); + if (player != null && isShown(player)) { + sendEquipmentPacket(player, slot); + } + } + return this; + } } diff --git a/api/src/main/java/net/jitse/npclib/listeners/ChunkListener.java b/api/src/main/java/net/jitse/npclib/listeners/ChunkListener.java index ab42b7d..1ac6982 100755 --- a/api/src/main/java/net/jitse/npclib/listeners/ChunkListener.java +++ b/api/src/main/java/net/jitse/npclib/listeners/ChunkListener.java @@ -20,7 +20,7 @@ import java.util.Objects; import java.util.UUID; /** - * @author Jitse Boonstras + * @author Jitse Boonstra */ public class ChunkListener implements Listener { @@ -35,7 +35,7 @@ public class ChunkListener implements Listener { Chunk chunk = event.getChunk(); for (SimpleNPC npc : NPCManager.getAllNPCs()) { - if (!isSameChunk(npc.getLocation(), chunk)) + if (npc.getLocation() == null || !isSameChunk(npc.getLocation(), chunk)) continue; // We aren't unloading the chunk with the NPC in it. // We found an NPC in the chunk being unloaded. Time to hide this NPC from all players. diff --git a/nms/pom.xml b/nms/pom.xml index b37f24d..1c4868b 100644 --- a/nms/pom.xml +++ b/nms/pom.xml @@ -15,16 +15,16 @@ v1_8_R1 - v1_8_R2 - v1_8_R3 - v1_9_R1 - v1_9_R2 - v1_10_R1 - v1_11_R1 - v1_12_R1 - v1_13_R1 - v1_13_R2 - v1_14_R1 + + + + + + + + + + diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java index b60bdd5..b96f09e 100755 --- a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java +++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java @@ -5,17 +5,17 @@ package net.jitse.npclib.nms.v1_8_R1; import net.jitse.npclib.NPCLib; +import net.jitse.npclib.api.state.NPCSlot; import net.jitse.npclib.hologram.Hologram; import net.jitse.npclib.internal.MinecraftVersion; import net.jitse.npclib.internal.SimpleNPC; -import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutEntityHeadRotationWrapper; -import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutNamedEntitySpawnWrapper; -import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutPlayerInfoWrapper; -import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutScoreboardTeamWrapper; +import net.jitse.npclib.nms.v1_8_R1.packets.*; import net.minecraft.server.v1_8_R1.*; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_8_R1.inventory.CraftItemStack; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import java.util.HashSet; import java.util.List; @@ -97,4 +97,41 @@ public class NPC_v1_8_R1 extends SimpleNPC { hologram.destroy(player); } + + @Override + public void sendMetadataPacket(Player player) { + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadataWrapper().create(activeStates, entityId); + + playerConnection.sendPacket(packet); + } + + @Override + public void sendEquipmentPacket(Player player, NPCSlot slot) { + PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection; + + ItemStack item; + switch (slot) { + case HELMET: + item = helmet; + break; + case CHESTPLATE: + item = chestplate; + break; + case LEGGINGS: + item = leggings; + break; + case BOOTS: + item = boots; + break; + case IN_HAND: + item = inHand; + break; + default: + throw new IllegalArgumentException("Slot is not recognized"); + } + + PacketPlayOutEntityEquipment packet = new PacketPlayOutEntityEquipment(entityId, slot.getSlot(), CraftItemStack.asNMSCopy(item)); + playerConnection.sendPacket(packet); + } } diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityMetadataWrapper.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityMetadataWrapper.java new file mode 100644 index 0000000..82dc899 --- /dev/null +++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityMetadataWrapper.java @@ -0,0 +1,16 @@ +package net.jitse.npclib.nms.v1_8_R1.packets; + +import net.jitse.npclib.api.state.NPCState; +import net.minecraft.server.v1_8_R1.DataWatcher; +import net.minecraft.server.v1_8_R1.PacketPlayOutEntityMetadata; + +public class PacketPlayOutEntityMetadataWrapper { + + public PacketPlayOutEntityMetadata create(NPCState[] activateStates, int entityId) { + DataWatcher dataWatcher = new DataWatcher(null); + byte masked = NPCState.getMasked(activateStates); + dataWatcher.a(0, masked); + + return new PacketPlayOutEntityMetadata(entityId, dataWatcher, true); + } +}