diff --git a/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java b/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java index 02c67c4..78395a7 100644 --- a/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java +++ b/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java @@ -2,99 +2,44 @@ package de.epiceric.shopchest.nms; import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.utils.Utils; -import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; -import java.lang.reflect.Method; +import java.lang.reflect.Field; import java.util.UUID; public class ArmorStandWrapper { - - private Class worldClass = Utils.getNMSClass("World"); - private Class worldServerClass = Utils.getNMSClass("WorldServer"); - private Class dataWatcherClass = Utils.getNMSClass("DataWatcher"); - private Class iChatBaseComponentClass = Utils.getNMSClass("IChatBaseComponent"); - private Class chatMessageClass = Utils.getNMSClass("ChatMessage"); - private Class entityClass = Utils.getNMSClass("Entity"); - private Class entityArmorStandClass = Utils.getNMSClass("EntityArmorStand"); - private Class entityLivingClass = Utils.getNMSClass("EntityLiving"); - private Class packetPlayOutSpawnEntityLivingClass = Utils.getNMSClass("PacketPlayOutSpawnEntityLiving"); - private Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); - private Class packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata"); - private Class packetPlayOutEntityTeleportClass = Utils.getNMSClass("PacketPlayOutEntityTeleport"); + + private final Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); + private final Class packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata"); + private final Class packetPlayOutEntityTeleportClass = Utils.getNMSClass("PacketPlayOutEntityTeleport"); + private final Class dataWatcherClass = Utils.getNMSClass("DataWatcher"); private ShopChest plugin; - - private Object nmsWorld; - private Object entity; private Location location; private String customName; private UUID uuid; - private int entityId; + private int entityId = -1; public ArmorStandWrapper(ShopChest plugin, Location location, String customName, boolean interactable) { this.plugin = plugin; this.location = location; this.customName = customName; - - try { - Object craftWorld = location.getWorld().getClass().cast(location.getWorld()); - nmsWorld = craftWorld.getClass().getMethod("getHandle").invoke(craftWorld); - - entity = entityArmorStandClass.getConstructor(worldClass, double.class, double.class,double.class) - .newInstance(nmsWorld, location.getX(), location.getY(), location.getZ()); - - if (customName != null && !customName.trim().isEmpty()) { - if (Utils.getMajorVersion() < 13) { - entityArmorStandClass.getMethod("setCustomName", String.class).invoke(entity, customName); - } else { - Object chatMessage = chatMessageClass.getConstructor(String.class, Object[].class) - .newInstance(customName, new Object[0]); - - entityArmorStandClass.getMethod("setCustomName", iChatBaseComponentClass).invoke(entity, chatMessage); - } - entityArmorStandClass.getMethod("setCustomNameVisible", boolean.class).invoke(entity, true); - } - - if (Utils.getMajorVersion() < 10) { - entityArmorStandClass.getMethod("setGravity", boolean.class).invoke(entity, false); - } else { - entityArmorStandClass.getMethod("setNoGravity", boolean.class).invoke(entity, true); - } - - entityArmorStandClass.getMethod("setInvisible", boolean.class).invoke(entity, true); - - // Adds the entity to some lists so it can call interact events - // It will also automatically load/unload it when far away - if (interactable) { - Method addEntityMethod = worldServerClass.getDeclaredMethod(Utils.getMajorVersion() == 8 ? "a" : "b", entityClass); - addEntityMethod.setAccessible(true); - addEntityMethod.invoke(worldServerClass.cast(nmsWorld), entity); - } - - uuid = (UUID) entityClass.getMethod("getUniqueID").invoke(entity); - entityId = (int) entityArmorStandClass.getMethod("getId").invoke(entity); - } catch (ReflectiveOperationException e) { - plugin.getLogger().severe("Failed to create line for hologram"); - plugin.debug("Failed to create armor stand"); - plugin.debug(e); - } } public void setVisible(Player player, boolean visible) { try { - Object entityLiving = entityLivingClass.cast(entity); - Object packet; - if (visible) { - packet = packetPlayOutSpawnEntityLivingClass.getConstructor(entityLivingClass).newInstance(entityLiving); - } else { - packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId}); + Object dataWatcher = Utils.createDataWatcher(customName, null); + entityId = Utils.getFreeEntityId(); + Utils.sendPacket(plugin, Utils.createPacketSpawnEntity(plugin, entityId, UUID.randomUUID(), location, EntityType.ARMOR_STAND), player); + Utils.sendPacket(plugin, packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class) + .newInstance(entityId, dataWatcher, true), player); + } else if (entityId != -1) { + Utils.sendPacket(plugin, packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId}), player); } - - Utils.sendPacket(plugin, packet, player); } catch (ReflectiveOperationException e) { plugin.getLogger().severe("Could not change hologram visibility"); plugin.debug("Could not change armor stand visibility"); @@ -104,12 +49,21 @@ public class ArmorStandWrapper { public void setLocation(Location location) { this.location = location; - try { - entityClass.getMethod("setPosition", double.class, double.class, double.class).invoke( - entity, location.getX(), location.getY(), location.getZ()); + Object packet = packetPlayOutEntityTeleportClass.getConstructor().newInstance(); + Field[] fields = packetPlayOutEntityTeleportClass.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + } - Object packet = packetPlayOutEntityTeleportClass.getConstructor(entityClass).newInstance(entity); + boolean isPre9 = Utils.getMajorVersion() < 9; + fields[0].set(packet, entityId); + fields[1].set(packet, isPre9 ? location.getBlockX() : location.getX()); + fields[2].set(packet, isPre9 ? location.getBlockY() : location.getY()); + fields[3].set(packet, isPre9 ? location.getBlockZ() : location.getZ()); + fields[4].set(packet, (byte) 0); + fields[5].set(packet, (byte) 0); + fields[6].set(packet, true); for (Player player : location.getWorld().getPlayers()) { Utils.sendPacket(plugin, packet, player); @@ -123,39 +77,14 @@ public class ArmorStandWrapper { public void setCustomName(String customName) { this.customName = customName; - + Object dataWatcher = Utils.createDataWatcher(customName, null); try { - if (customName != null && !customName.isEmpty()) { - if (Utils.getMajorVersion() < 13) { - entityClass.getMethod("setCustomName", String.class).invoke(entity, customName); - } else { - Object chatMessage = chatMessageClass.getConstructor(String.class, Object[].class) - .newInstance(customName, new Object[0]); - - entityClass.getMethod("setCustomName", iChatBaseComponentClass).invoke(entity, chatMessage); - } - entityClass.getMethod("setCustomNameVisible", boolean.class).invoke(entity, true); - } else { - if (Utils.getMajorVersion() < 13) { - entityClass.getMethod("setCustomName", String.class).invoke(entity, ""); - } else { - Object chatMessage = chatMessageClass.getConstructor(String.class, Object[].class) - .newInstance("", new Object[0]); - - entityClass.getMethod("setCustomName", iChatBaseComponentClass).invoke(entity, chatMessage); - } - entityClass.getMethod("setCustomNameVisible", boolean.class).invoke(entity, false); - } - - Object dataWatcher = entityClass.getMethod("getDataWatcher").invoke(entity); - Object packet = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class) .newInstance(entityId, dataWatcher, true); for (Player player : location.getWorld().getPlayers()) { Utils.sendPacket(plugin, packet, player); } - } catch (ReflectiveOperationException e) { plugin.getLogger().severe("Could not set hologram text"); plugin.debug("Could not set armor stand custom name"); @@ -164,22 +93,9 @@ public class ArmorStandWrapper { } public void remove() { - for (Player player : Bukkit.getOnlinePlayers()) { + for (Player player : location.getWorld().getPlayers()) { setVisible(player, false); } - - try { - // Removes the entity from the lists it was added to for interaction - Method addEntityMethod = worldServerClass.getDeclaredMethod(Utils.getMajorVersion() == 8 ? "b" : "c", entityClass); - addEntityMethod.setAccessible(true); - addEntityMethod.invoke(worldServerClass.cast(nmsWorld), entity); - - entityClass.getMethod("die").invoke(entity); - } catch (ReflectiveOperationException e) { - plugin.getLogger().severe("Could not remove hologram"); - plugin.debug("Could not remove armor stand from entity lists"); - plugin.debug(e); - } } public int getEntityId() { diff --git a/src/main/java/de/epiceric/shopchest/nms/Hologram.java b/src/main/java/de/epiceric/shopchest/nms/Hologram.java index aac1c09..f3343d2 100644 --- a/src/main/java/de/epiceric/shopchest/nms/Hologram.java +++ b/src/main/java/de/epiceric/shopchest/nms/Hologram.java @@ -86,11 +86,11 @@ public class Hologram { */ public boolean contains(ArmorStand armorStand) { for (ArmorStandWrapper wrapper : wrappers) { - if (wrapper.getUuid().equals(armorStand.getUniqueId())) { + if (armorStand.getUniqueId().equals(wrapper.getUuid())) { return true; } } - return interactArmorStandWrapper != null && interactArmorStandWrapper.getUuid().equals(armorStand.getUniqueId()); + return interactArmorStandWrapper != null && armorStand.getUniqueId().equals(interactArmorStandWrapper.getUuid()); } /** diff --git a/src/main/java/de/epiceric/shopchest/shop/ShopItem.java b/src/main/java/de/epiceric/shopchest/shop/ShopItem.java index d1ffb8a..1358cf4 100644 --- a/src/main/java/de/epiceric/shopchest/shop/ShopItem.java +++ b/src/main/java/de/epiceric/shopchest/shop/ShopItem.java @@ -4,11 +4,10 @@ import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.utils.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -23,94 +22,43 @@ public class ShopItem { private final ItemStack itemStack; private final Location location; + private int entityId = -1; + private final Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); - private final Object[] creationPackets = new Object[3]; - private final Object entityItem; - private final int entityId; + private final Class packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity"); + private final Class packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata"); + private final Class dataWatcherClass = Utils.getNMSClass("DataWatcher"); + private final Class vec3dClass = Utils.getNMSClass("Vec3D"); + private final Class craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack"); + private final Class nmsItemStackClass = Utils.getNMSClass("ItemStack"); public ShopItem(ShopChest plugin, ItemStack itemStack, Location location) { this.plugin = plugin; this.itemStack = itemStack; this.location = location; - Class packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity"); - Class dataWatcherClass = Utils.getNMSClass("DataWatcher"); - Class packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata"); - Class packetPlayOutSpawnEntityClass = Utils.getNMSClass("PacketPlayOutSpawnEntity"); Class entityClass = Utils.getNMSClass("Entity"); - Class entityItemClass = Utils.getNMSClass("EntityItem"); - Class craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack"); - Class nmsItemStackClass = Utils.getNMSClass("ItemStack"); - Class craftWorldClass = Utils.getCraftClass("CraftWorld"); - Class nmsWorldClass = Utils.getNMSClass("World"); Class[] requiredClasses = new Class[] { - nmsWorldClass, craftWorldClass, nmsItemStackClass, craftItemStackClass, entityItemClass, - packetPlayOutSpawnEntityClass, packetPlayOutEntityMetadataClass, dataWatcherClass, + nmsItemStackClass, craftItemStackClass, packetPlayOutEntityMetadataClass, dataWatcherClass, packetPlayOutEntityDestroyClass, entityClass, packetPlayOutEntityVelocityClass, }; for (Class c : requiredClasses) { if (c == null) { plugin.debug("Failed to create shop item: Could not find all required classes"); - entityItem = null; - entityId = -1; return; } } - - Object tmpEntityItem = null; - int tmpEntityId = -1; - - try { - Object craftWorld = craftWorldClass.cast(location.getWorld()); - Object nmsWorld = craftWorldClass.getMethod("getHandle").invoke(craftWorld); - - Object nmsItemStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, itemStack); - - Constructor entityItemConstructor = entityItemClass.getConstructor(nmsWorldClass); - tmpEntityItem = entityItemConstructor.newInstance(nmsWorld); - - entityItemClass.getMethod("setPosition", double.class, double.class, double.class).invoke(tmpEntityItem, location.getX(), location.getY(), location.getZ()); - entityItemClass.getMethod("setItemStack", nmsItemStackClass).invoke(tmpEntityItem, nmsItemStack); - if (Utils.getMajorVersion() >= 10) entityItemClass.getMethod("setNoGravity", boolean.class).invoke(tmpEntityItem, true); - - Field ageField = entityItemClass.getDeclaredField("age"); - ageField.setAccessible(true); - ageField.setInt(tmpEntityItem, -32768); - - tmpEntityId = (int) entityItemClass.getMethod("getId").invoke(tmpEntityItem); - Object dataWatcher = entityItemClass.getMethod("getDataWatcher").invoke(tmpEntityItem); - - creationPackets[0] = packetPlayOutSpawnEntityClass.getConstructor(entityClass, int.class).newInstance(tmpEntityItem, 2); - creationPackets[1] = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class).newInstance(tmpEntityId, dataWatcher, true); - creationPackets[2] = packetPlayOutEntityVelocityClass.getConstructor(int.class, double.class, double.class, double.class).newInstance(tmpEntityId, 0D, 0D, 0D); - } catch (NoSuchMethodException | NoSuchFieldException | InstantiationException | IllegalAccessException | InvocationTargetException e) { - plugin.getLogger().severe("Failed to create shop item"); - plugin.debug("Failed to create shop item with reflection"); - plugin.debug(e); - } - - entityItem = tmpEntityItem; - entityId = tmpEntityId; } /** * @return Clone of the location, where the shop item should be (it could have been moved by something, even though it shouldn't) - * To get the exact location, use reflection and extract the location of the {@code EntityItem} - * which you can get in {@link #getEntityItem()}. */ public Location getLocation() { return location.clone(); } - /** - * @return {@code net.minecraft.server.[VERSION].EntityItem} - */ - public Object getEntityItem() { - return entityItem; - } - /** * @return A clone of this Item's {@link ItemStack} */ @@ -139,9 +87,23 @@ public class ShopItem { */ public void showPlayer(Player p, boolean force) { if (viewers.add(p.getUniqueId()) || force) { - for (Object packet : creationPackets) { - Utils.sendPacket(plugin, packet, p); - } + try { + Object nmsItemStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, itemStack); + Object dataWatcher = Utils.createDataWatcher(null, nmsItemStack); + entityId = Utils.getFreeEntityId(); + Utils.sendPacket(plugin, Utils.createPacketSpawnEntity(plugin, entityId, UUID.randomUUID(), location, EntityType.DROPPED_ITEM), p); + Utils.sendPacket(plugin, packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class).newInstance(entityId, dataWatcher, true), p); + if (Utils.getMajorVersion() < 14) { + Utils.sendPacket(plugin, packetPlayOutEntityVelocityClass.getConstructor(int.class, double.class, double.class, double.class).newInstance(entityId, 0D, 0D, 0D), p); + } else { + Object vec3d = vec3dClass.getConstructor(double.class, double.class, double.class).newInstance(0D, 0D, 0D); + Utils.sendPacket(plugin, packetPlayOutEntityVelocityClass.getConstructor(int.class, vec3dClass).newInstance(entityId, vec3d), p); + } + } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException | InstantiationException e) { + plugin.getLogger().severe("Failed to create item!"); + plugin.debug("Failed to create item!"); + plugin.debug(e); + } } } diff --git a/src/main/java/de/epiceric/shopchest/utils/Utils.java b/src/main/java/de/epiceric/shopchest/utils/Utils.java index 7ae73a5..553a437 100644 --- a/src/main/java/de/epiceric/shopchest/utils/Utils.java +++ b/src/main/java/de/epiceric/shopchest/utils/Utils.java @@ -9,16 +9,21 @@ import de.epiceric.shopchest.nms.CustomBookMeta; import de.epiceric.shopchest.nms.JsonBuilder; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.*; +import org.bukkit.util.Vector; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -26,6 +31,9 @@ import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; public class Utils { @@ -326,6 +334,187 @@ public class Utils { } } + /** + * Create a NMS data watcher object to send via a {@code PacketPlayOutEntityMetadata} packet. + * Gravity will be disabled and the custom name will be displayed if available. + * @param customName Custom Name of the entity or {@code null} + * @param nmsItemStack NMS ItemStack or {@code null} if armor stand + */ + public static Object createDataWatcher(String customName, Object nmsItemStack) { + String version = getServerVersion(); + int majorVersion = getMajorVersion(); + + try { + Class entityClass = getNMSClass("Entity"); + Class entityArmorStandClass = getNMSClass("EntityArmorStand"); + Class entityItemClass = getNMSClass("EntityItem"); + Class dataWatcherClass = getNMSClass("DataWatcher"); + Class dataWatcherObjectClass = getNMSClass("DataWatcherObject"); + + byte flags = nmsItemStack == null ? (byte) (1 << 5) : 0; // invisible if armor stand + + Object dataWatcher = dataWatcherClass.getConstructor(entityClass).newInstance((Object) null); + if (majorVersion < 9) { + Method a = dataWatcherClass.getMethod("a", int.class, Object.class); + a.invoke(dataWatcher, 0, flags); // flags + a.invoke(dataWatcher, 1, (short) 300); // air ticks (?) + a.invoke(dataWatcher, 2, (byte) (customName != null ? 1 : 0)); // custom name visible + a.invoke(dataWatcher, 3, customName); // custom name + a.invoke(dataWatcher, 4, (byte) 1); // silent + a.invoke(dataWatcher, 10, nmsItemStack == null ? (byte) 1 : nmsItemStack); // item / no gravity + } else { + Method register = dataWatcherClass.getMethod("register", dataWatcherObjectClass, Object.class); + String[] dataWatcherObjectFieldNames; + + if ("v1_9_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"ax", "ay", "aA", "az", "aB", "a", "c"}; + } else if ("v1_9_R2".equals(version)){ + dataWatcherObjectFieldNames = new String[] {"ay", "az", "aB", "aA", "aC", "a", "c"}; + } else if ("v1_10_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"aa", "az", "aB", "aA", "aC", "aD", "c"}; + } else if ("v1_11_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"Z", "az", "aB", "aA", "aC", "aD", "c"}; + } else if ("v1_12_R1".equals(version) || "v1_12_R2".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"Z", "aA", "aC", "aB", "aD", "aE", "c"}; + } else if ("v1_13_R1".equals(version) || "v1_13_R2".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"ac", "aD", "aF", "aE", "aG", "aH", "b"}; + } else if ("v1_14_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"W", "AIR_TICKS", "aA", "az", "aB", "aC", "ITEM"}; + } else { + return null; + } + + Class f6Class = majorVersion < 9 ? entityArmorStandClass : entityClass; + + Field f1 = entityClass.getDeclaredField(dataWatcherObjectFieldNames[0]); + Field f2 = entityClass.getDeclaredField(dataWatcherObjectFieldNames[1]); + Field f3 = entityClass.getDeclaredField(dataWatcherObjectFieldNames[2]); + Field f4 = entityClass.getDeclaredField(dataWatcherObjectFieldNames[3]); + Field f5 = entityClass.getDeclaredField(dataWatcherObjectFieldNames[4]); + Field f6 = f6Class.getDeclaredField(dataWatcherObjectFieldNames[5]); + Field f7 = entityItemClass.getDeclaredField(dataWatcherObjectFieldNames[6]); + + f1.setAccessible(true); + f2.setAccessible(true); + f3.setAccessible(true); + f4.setAccessible(true); + f5.setAccessible(true); + f6.setAccessible(true); + f7.setAccessible(true); + + register.invoke(dataWatcher, f1.get(null), flags); // flags + register.invoke(dataWatcher, f2.get(null), 300); // air ticks (?) + register.invoke(dataWatcher, f3.get(null), customName != null); // custom name visible + if (majorVersion < 13) register.invoke(dataWatcher, f4.get(null), customName); // custom name + register.invoke(dataWatcher, f5.get(null), true); // silent + + if (nmsItemStack != null) { + register.invoke(dataWatcher, f7.get(null), majorVersion < 11 ? Optional.of(nmsItemStack) : nmsItemStack); // item + } + + if (majorVersion >= 10 || nmsItemStack == null) { + register.invoke(dataWatcher, f6.get(null), true); // no gravity + if (majorVersion >= 13) { + if (customName != null) { + Class chatSerializerClass = Utils.getNMSClass("IChatBaseComponent$ChatSerializer"); + Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, JsonBuilder.parse(customName).toString()); + register.invoke(dataWatcher, f4.get(null), Optional.of(iChatBaseComponent)); // custom name + } else { + register.invoke(dataWatcher, f4.get(null), Optional.empty()); // custom name + } + } + } + } + return dataWatcher; + } catch (InstantiationException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + ShopChest.getInstance().getLogger().severe("Failed to create data watcher!"); + ShopChest.getInstance().debug("Failed to create data watcher"); + ShopChest.getInstance().debug(e); + } + return null; + } + + /** + * Get a free entity ID for use in {@link #createPacketSpawnEntity(ShopChest, int, UUID, Location, Vector, EntityType)} + * + * @return The id or {@code -1} if a free entity ID could not be retrieved. + */ + public static int getFreeEntityId() { + try { + Class entityClass = getNMSClass("Entity"); + Field entityCountField = entityClass.getDeclaredField("entityCount"); + entityCountField.setAccessible(true); + if (entityCountField.getType() == int.class) { + int id = entityCountField.getInt(null); + entityCountField.setInt(null, id+1); + return id; + } else if (entityCountField.getType() == AtomicInteger.class) { + return ((AtomicInteger) entityCountField.get(null)).incrementAndGet(); + } + + return -1; + } catch (Exception e) { + return -1; + } + } + + /** + * Create a {@code PacketPlayOutSpawnEntity} object. + * Only {@link EntityType#ARMOR_STAND} and {@link EntityType#DROPPED_ITEM} are supported! + */ + public static Object createPacketSpawnEntity(ShopChest plugin, int id, UUID uuid, Location loc, EntityType type) { + try { + Class packetClass = getNMSClass("PacketPlayOutSpawnEntity"); + Object packet = packetClass.getConstructor().newInstance(); + boolean isPre9 = getMajorVersion() < 9; + boolean isPre14 = getMajorVersion() < 14; + + Field[] fields = new Field[12]; + fields[0] = packetClass.getDeclaredField("a"); // ID + fields[1] = packetClass.getDeclaredField("b"); // UUID (Only 1.9+) + fields[2] = packetClass.getDeclaredField(isPre9 ? "b" : "c"); // Loc X + fields[3] = packetClass.getDeclaredField(isPre9 ? "c" : "d"); // Loc Y + fields[4] = packetClass.getDeclaredField(isPre9 ? "d" : "e"); // Loc Z + fields[5] = packetClass.getDeclaredField(isPre9 ? "e" : "f"); // Mot X + fields[6] = packetClass.getDeclaredField(isPre9 ? "f" : "g"); // Mot Y + fields[7] = packetClass.getDeclaredField(isPre9 ? "g" : "h"); // Mot Z + fields[8] = packetClass.getDeclaredField(isPre9 ? "h" : "i"); // Pitch + fields[9] = packetClass.getDeclaredField(isPre9 ? "i" : "j"); // Yaw + fields[10] = packetClass.getDeclaredField(isPre9 ? "j" : "k"); // Type + fields[11] = packetClass.getDeclaredField(isPre9 ? "k" : "l"); // Data + + for (Field field : fields) { + field.setAccessible(true); + } + + Object entityType = null; + if (!isPre14) { + Class entityTypesClass = getNMSClass("EntityTypes"); + entityType = entityTypesClass.getField(type == EntityType.ARMOR_STAND ? "ARMOR_STAND" : "ITEM").get(null); + } + + fields[0].set(packet, id); + if (!isPre9) fields[1].set(packet, uuid); + fields[2].set(packet, isPre9 ? loc.getBlockX() : loc.getX()); + fields[3].set(packet, isPre9 ? loc.getBlockY() : loc.getY()); + fields[4].set(packet, isPre9 ? loc.getBlockZ() : loc.getZ()); + fields[5].set(packet, 0); + fields[6].set(packet, 0); + fields[7].set(packet, 0); + fields[8].set(packet, 0); + fields[9].set(packet, 0); + fields[10].set(packet, isPre14 ? (type == EntityType.ARMOR_STAND ? 78 : 2) : entityType); + fields[11].set(packet, 0); + + return packet; + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException | InstantiationException e) { + plugin.getLogger().severe("Failed to create packet to spawn entity!"); + plugin.debug("Failed to create packet to spawn entity!"); + plugin.debug(e); + return null; + } + } + /** * Send a packet to a player * @param plugin An instance of the {@link ShopChest} plugin