Rework NMS

Fixes hologram duplicates
Fixes support for 1.14
Breaks hologram interaction (Events cannot be sent to the server)

Armor stand and item entities are now totally client side,they are not
even created, but instead are just put in a packet to send to the client

Performance has not been tested!
This commit is contained in:
Eric 2019-04-28 20:56:49 +02:00
parent bac5a24b37
commit 77a837fc05
4 changed files with 248 additions and 181 deletions

View File

@ -2,99 +2,44 @@ package de.epiceric.shopchest.nms;
import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.utils.Utils; import de.epiceric.shopchest.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.lang.reflect.Method; import java.lang.reflect.Field;
import java.util.UUID; import java.util.UUID;
public class ArmorStandWrapper { public class ArmorStandWrapper {
private Class<?> worldClass = Utils.getNMSClass("World"); private final Class<?> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
private Class<?> worldServerClass = Utils.getNMSClass("WorldServer"); private final Class<?> packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata");
private Class<?> dataWatcherClass = Utils.getNMSClass("DataWatcher"); private final Class<?> packetPlayOutEntityTeleportClass = Utils.getNMSClass("PacketPlayOutEntityTeleport");
private Class<?> iChatBaseComponentClass = Utils.getNMSClass("IChatBaseComponent"); private final Class<?> dataWatcherClass = Utils.getNMSClass("DataWatcher");
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 ShopChest plugin; private ShopChest plugin;
private Object nmsWorld;
private Object entity; private Object entity;
private Location location; private Location location;
private String customName; private String customName;
private UUID uuid; private UUID uuid;
private int entityId; private int entityId = -1;
public ArmorStandWrapper(ShopChest plugin, Location location, String customName, boolean interactable) { public ArmorStandWrapper(ShopChest plugin, Location location, String customName, boolean interactable) {
this.plugin = plugin; this.plugin = plugin;
this.location = location; this.location = location;
this.customName = customName; 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) { public void setVisible(Player player, boolean visible) {
try { try {
Object entityLiving = entityLivingClass.cast(entity);
Object packet;
if (visible) { if (visible) {
packet = packetPlayOutSpawnEntityLivingClass.getConstructor(entityLivingClass).newInstance(entityLiving); Object dataWatcher = Utils.createDataWatcher(customName, null);
} else { entityId = Utils.getFreeEntityId();
packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId}); 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) { } catch (ReflectiveOperationException e) {
plugin.getLogger().severe("Could not change hologram visibility"); plugin.getLogger().severe("Could not change hologram visibility");
plugin.debug("Could not change armor stand visibility"); plugin.debug("Could not change armor stand visibility");
@ -104,12 +49,21 @@ public class ArmorStandWrapper {
public void setLocation(Location location) { public void setLocation(Location location) {
this.location = location; this.location = location;
try { try {
entityClass.getMethod("setPosition", double.class, double.class, double.class).invoke( Object packet = packetPlayOutEntityTeleportClass.getConstructor().newInstance();
entity, location.getX(), location.getY(), location.getZ()); 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()) { for (Player player : location.getWorld().getPlayers()) {
Utils.sendPacket(plugin, packet, player); Utils.sendPacket(plugin, packet, player);
@ -123,39 +77,14 @@ public class ArmorStandWrapper {
public void setCustomName(String customName) { public void setCustomName(String customName) {
this.customName = customName; this.customName = customName;
Object dataWatcher = Utils.createDataWatcher(customName, null);
try { 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) Object packet = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class)
.newInstance(entityId, dataWatcher, true); .newInstance(entityId, dataWatcher, true);
for (Player player : location.getWorld().getPlayers()) { for (Player player : location.getWorld().getPlayers()) {
Utils.sendPacket(plugin, packet, player); Utils.sendPacket(plugin, packet, player);
} }
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
plugin.getLogger().severe("Could not set hologram text"); plugin.getLogger().severe("Could not set hologram text");
plugin.debug("Could not set armor stand custom name"); plugin.debug("Could not set armor stand custom name");
@ -164,22 +93,9 @@ public class ArmorStandWrapper {
} }
public void remove() { public void remove() {
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : location.getWorld().getPlayers()) {
setVisible(player, false); 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() { public int getEntityId() {

View File

@ -86,11 +86,11 @@ public class Hologram {
*/ */
public boolean contains(ArmorStand armorStand) { public boolean contains(ArmorStand armorStand) {
for (ArmorStandWrapper wrapper : wrappers) { for (ArmorStandWrapper wrapper : wrappers) {
if (wrapper.getUuid().equals(armorStand.getUniqueId())) { if (armorStand.getUniqueId().equals(wrapper.getUuid())) {
return true; return true;
} }
} }
return interactArmorStandWrapper != null && interactArmorStandWrapper.getUuid().equals(armorStand.getUniqueId()); return interactArmorStandWrapper != null && armorStand.getUniqueId().equals(interactArmorStandWrapper.getUuid());
} }
/** /**

View File

@ -4,11 +4,10 @@ import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.utils.Utils; import de.epiceric.shopchest.utils.Utils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -23,94 +22,43 @@ public class ShopItem {
private final ItemStack itemStack; private final ItemStack itemStack;
private final Location location; private final Location location;
private int entityId = -1;
private final Class<?> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); private final Class<?> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
private final Object[] creationPackets = new Object[3]; private final Class<?> packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity");
private final Object entityItem; private final Class<?> packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata");
private final int entityId; 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) { public ShopItem(ShopChest plugin, ItemStack itemStack, Location location) {
this.plugin = plugin; this.plugin = plugin;
this.itemStack = itemStack; this.itemStack = itemStack;
this.location = location; 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<?> 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<?>[] { Class<?>[] requiredClasses = new Class<?>[] {
nmsWorldClass, craftWorldClass, nmsItemStackClass, craftItemStackClass, entityItemClass, nmsItemStackClass, craftItemStackClass, packetPlayOutEntityMetadataClass, dataWatcherClass,
packetPlayOutSpawnEntityClass, packetPlayOutEntityMetadataClass, dataWatcherClass,
packetPlayOutEntityDestroyClass, entityClass, packetPlayOutEntityVelocityClass, packetPlayOutEntityDestroyClass, entityClass, packetPlayOutEntityVelocityClass,
}; };
for (Class<?> c : requiredClasses) { for (Class<?> c : requiredClasses) {
if (c == null) { if (c == null) {
plugin.debug("Failed to create shop item: Could not find all required classes"); plugin.debug("Failed to create shop item: Could not find all required classes");
entityItem = null;
entityId = -1;
return; 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) * @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() { public Location getLocation() {
return location.clone(); return location.clone();
} }
/**
* @return {@code net.minecraft.server.[VERSION].EntityItem}
*/
public Object getEntityItem() {
return entityItem;
}
/** /**
* @return A clone of this Item's {@link ItemStack} * @return A clone of this Item's {@link ItemStack}
*/ */
@ -139,8 +87,22 @@ public class ShopItem {
*/ */
public void showPlayer(Player p, boolean force) { public void showPlayer(Player p, boolean force) {
if (viewers.add(p.getUniqueId()) || force) { if (viewers.add(p.getUniqueId()) || force) {
for (Object packet : creationPackets) { try {
Utils.sendPacket(plugin, packet, p); 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);
} }
} }
} }

View File

@ -9,16 +9,21 @@ import de.epiceric.shopchest.nms.CustomBookMeta;
import de.epiceric.shopchest.nms.JsonBuilder; import de.epiceric.shopchest.nms.JsonBuilder;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.*; import org.bukkit.inventory.meta.*;
import org.bukkit.util.Vector;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -26,6 +31,9 @@ import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class Utils { 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 * Send a packet to a player
* @param plugin An instance of the {@link ShopChest} plugin * @param plugin An instance of the {@link ShopChest} plugin