From 4c02a77037cc90dc5bd5713e45c80cdd9de32743 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 6 Jun 2017 17:39:59 +0200 Subject: [PATCH] Revert to NMS Armor Stands for holograms Bukkit's armor stands caused too many issues --- .../shopchest/nms/ArmorStandWrapper.java | 163 ++++++++++++++++ .../de/epiceric/shopchest/nms/Hologram.java | 177 +++++++----------- 2 files changed, 230 insertions(+), 110 deletions(-) create mode 100644 src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java diff --git a/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java b/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java new file mode 100644 index 0000000..b5a2be7 --- /dev/null +++ b/src/main/java/de/epiceric/shopchest/nms/ArmorStandWrapper.java @@ -0,0 +1,163 @@ +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.Player; + +import java.lang.reflect.Method; +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 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 Object entity; + private Location location; + private String customName; + private UUID uuid; + private int entityId; + + public ArmorStandWrapper(ShopChest plugin, Location location, String customName) { + this.plugin = plugin; + this.location = location; + this.customName = customName; + + try { + Object craftWorld = location.getWorld().getClass().cast(location.getWorld()); + Object worldServer = craftWorld.getClass().getMethod("getHandle").invoke(craftWorld); + + entity = entityArmorStandClass.getConstructor(worldClass, double.class, double.class,double.class) + .newInstance(worldServer, location.getX(), location.getY(), location.getZ()); + + if (customName != null && !customName.trim().isEmpty()) { + entityArmorStandClass.getMethod("setCustomName", String.class).invoke(entity, customName); + 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 + Method addEntityMethod = worldServerClass.getDeclaredMethod((Utils.getMajorVersion() == 8 ? "a" : "b"), entityClass); + addEntityMethod.setAccessible(true); + addEntityMethod.invoke(worldServerClass.cast(worldServer), 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}); + } + + Utils.sendPacket(plugin, packet, player); + } catch (ReflectiveOperationException e) { + plugin.getLogger().severe("Could not change hologram visibility"); + plugin.debug("Could not change armor stand visibility"); + plugin.debug(e); + } + } + + 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(entityClass).newInstance(entity); + + for (Player player : location.getWorld().getPlayers()) { + Utils.sendPacket(plugin, packet, player); + } + } catch (ReflectiveOperationException e) { + plugin.getLogger().severe("Could not set hologram location"); + plugin.debug("Could not set armor stand location"); + plugin.debug(e); + } + } + + public void setCustomName(String customName) { + this.customName = customName; + + try { + if (customName != null && !customName.isEmpty()) { + entityClass.getMethod("setCustomName", String.class).invoke(entity, customName); + entityClass.getMethod("setCustomNameVisible", boolean.class).invoke(entity, true); + } else { + entityClass.getMethod("setCustomName", String.class).invoke(entity, ""); + 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"); + plugin.debug(e); + } + } + + public void remove() { + for (Player player : Bukkit.getOnlinePlayers()) { + setVisible(player, false); + } + } + + public int getEntityId() { + return entityId; + } + + public UUID getUuid() { + return uuid; + } + + public Location getLocation() { + return location.clone(); + } + + public String getCustomName() { + return customName; + } + + public Object getEntity() { + return entity; + } +} diff --git a/src/main/java/de/epiceric/shopchest/nms/Hologram.java b/src/main/java/de/epiceric/shopchest/nms/Hologram.java index 9aa48a9..d288422 100644 --- a/src/main/java/de/epiceric/shopchest/nms/Hologram.java +++ b/src/main/java/de/epiceric/shopchest/nms/Hologram.java @@ -2,15 +2,12 @@ package de.epiceric.shopchest.nms; import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.config.Config; -import de.epiceric.shopchest.utils.Utils; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; @@ -19,82 +16,59 @@ public class Hologram { private static List holograms = new ArrayList<>(); private boolean exists = false; - private List nmsArmorStands = new ArrayList<>(); - private List armorStands = new ArrayList<>(); - private ArmorStand interactArmorStand; + private List wrappers = new ArrayList<>(); + private ArmorStandWrapper interactArmorStandWrapper; private Location location; private List visible = new ArrayList<>(); private ShopChest plugin; private Config config; - private Class entityArmorStandClass = Utils.getNMSClass("EntityArmorStand"); - private Class packetPlayOutSpawnEntityLivingClass = Utils.getNMSClass("PacketPlayOutSpawnEntityLiving"); - private Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); - private Class entityLivingClass = Utils.getNMSClass("EntityLiving"); - public Hologram(ShopChest plugin, String[] lines, Location location) { this.plugin = plugin; this.config = plugin.getShopChestConfig(); this.location = location; - Class[] requiredClasses = new Class[] { - entityArmorStandClass, entityLivingClass, packetPlayOutSpawnEntityLivingClass, - packetPlayOutEntityDestroyClass, - }; - - for (Class c : requiredClasses) { - if (c == null) { - plugin.debug("Failed to create hologram: Could not find all required classes"); - return; - } - } - create(lines); } public void addLine(int line, String text) { + addLine(line, text, false); + } + + private void addLine(int line, String text, boolean forceUpdateLine) { if (text == null || text.isEmpty()) return; - if (line >= armorStands.size()) { - line = armorStands.size(); + if (line >= wrappers.size()) { + line = wrappers.size(); } text = ChatColor.translateAlternateColorCodes('&', text); if (config.hologram_fixed_bottom) { for (int i = 0; i < line; i++) { - ArmorStand stand = armorStands.get(i); - stand.teleport(stand.getLocation().add(0, 0.25, 0)); + ArmorStandWrapper wrapper = wrappers.get(i); + wrapper.setLocation(wrapper.getLocation().add(0, 0.25, 0)); } } else { - for (int i = line; i < armorStands.size(); i++) { - ArmorStand stand = armorStands.get(i); - stand.teleport(stand.getLocation().subtract(0, 0.25, 0)); + for (int i = line; i < wrappers.size(); i++) { + ArmorStandWrapper wrapper = wrappers.get(i); + wrapper.setLocation(wrapper.getLocation().subtract(0, 0.25, 0)); } } - Location location = this.location.clone(); + Location loc = getLocation(); if (!config.hologram_fixed_bottom) { - location.subtract(0, line * 0.25, 0); + loc.subtract(0, line * 0.25, 0); } - try { - ArmorStand armorStand = (ArmorStand) location.getWorld().spawnEntity(location, EntityType.ARMOR_STAND); - armorStand.setGravity(false); - armorStand.setVisible(false); - armorStand.setCustomName(text); - armorStand.setCustomNameVisible(true); + ArmorStandWrapper wrapper = new ArmorStandWrapper(plugin, loc, text); + wrappers.add(line, wrapper); - Object craftArmorStand = armorStand.getClass().cast(armorStand); - Object nmsArmorStand = craftArmorStand.getClass().getMethod("getHandle").invoke(craftArmorStand); - - nmsArmorStands.add(line, nmsArmorStand); - armorStands.add(line, armorStand); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - plugin.getLogger().severe("Could not create Hologram with reflection"); - plugin.debug("Could not create Hologram with reflection"); - plugin.debug(e); + if (forceUpdateLine) { + for (Player player : visible) { + wrapper.setVisible(player, true); + } } } @@ -106,38 +80,37 @@ public class Hologram { text = ChatColor.translateAlternateColorCodes('&', text); - if (line >= armorStands.size()) { - addLine(line, text); + if (line >= wrappers.size()) { + addLine(line, text, true); return; } - armorStands.get(line).setCustomName(text); + wrappers.get(line).setCustomName(text); } public void removeLine(int line) { - if (line < armorStands.size()) { + if (line < wrappers.size()) { if (config.hologram_fixed_bottom) { for (int i = 0; i < line; i++) { - ArmorStand stand = armorStands.get(i); - stand.teleport(stand.getLocation().subtract(0, 0.25, 0)); + ArmorStandWrapper wrapper = wrappers.get(i); + wrapper.setLocation(wrapper.getLocation().subtract(0, 0.25, 0)); } } else { - for (int i = line + 1; i < armorStands.size(); i++) { - ArmorStand stand = armorStands.get(i); - stand.teleport(stand.getLocation().add(0, 0.25, 0)); + for (int i = line + 1; i < wrappers.size(); i++) { + ArmorStandWrapper wrapper = wrappers.get(i); + wrapper.setLocation(wrapper.getLocation().add(0, 0.25, 0)); } } - armorStands.get(line).remove(); - armorStands.remove(line); - nmsArmorStands.remove(line); + wrappers.get(line).remove(); + wrappers.remove(line); } } public String[] getLines() { List lines = new ArrayList<>(); - for (ArmorStand armorStand : armorStands) { - lines.add(armorStand.getCustomName()); + for (ArmorStandWrapper wrapper : wrappers) { + lines.add(wrapper.getCustomName()); } return lines.toArray(new String[lines.size()]); @@ -149,23 +122,15 @@ public class Hologram { } if (plugin.getShopChestConfig().enable_hologram_interaction) { - Location loc = location.clone().add(0, 0.4, 0); + double y = 0.6; + if (config.hologram_fixed_bottom) y = 0.85; - try { - ArmorStand armorStand = (ArmorStand) loc.getWorld().spawnEntity(loc, EntityType.ARMOR_STAND); - armorStand.setGravity(false); - armorStand.setVisible(false); + Location loc = getLocation().add(0, y, 0); + interactArmorStandWrapper = new ArmorStandWrapper(plugin, loc, null); + } - Object craftArmorStand = armorStand.getClass().cast(armorStand); - Object nmsArmorStand = craftArmorStand.getClass().getMethod("getHandle").invoke(craftArmorStand); - - nmsArmorStands.add(nmsArmorStand); - interactArmorStand = armorStand; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - plugin.getLogger().severe("Could not create Hologram with reflection"); - plugin.debug("Could not create Hologram with reflection"); - plugin.debug(e); - } + for (Player player : location.getWorld().getPlayers()) { + plugin.getShopUtils().updateShops(player, true); } holograms.add(this); @@ -186,18 +151,11 @@ public class Hologram { new BukkitRunnable() { @Override public void run() { - for (Object o : nmsArmorStands) { - try { - Object entityLiving = entityLivingClass.cast(o); - Object packet = packetPlayOutSpawnEntityLivingClass.getConstructor(entityLivingClass).newInstance(entityLiving); - - Utils.sendPacket(plugin, packet, p); - } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { - plugin.getLogger().severe("Could not show Hologram to player with reflection"); - plugin.debug("Could not show Hologram to player with reflection"); - plugin.debug(e); - } + for (ArmorStandWrapper wrapper : wrappers) { + wrapper.setVisible(p, true); } + + interactArmorStandWrapper.setVisible(p, true); } }.runTaskAsynchronously(plugin); @@ -211,19 +169,11 @@ public class Hologram { new BukkitRunnable() { @Override public void run() { - for (Object o : nmsArmorStands) { - try { - int id = (int) entityArmorStandClass.getMethod("getId").invoke(o); - - Object packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[] {id}); - - Utils.sendPacket(plugin, packet, p); - } catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { - plugin.getLogger().severe("Could not hide Hologram from player with reflection"); - plugin.debug("Could not hide Hologram from player with reflection"); - plugin.debug(e); - } + for (ArmorStandWrapper wrapper : wrappers) { + wrapper.setVisible(p, false); } + + interactArmorStandWrapper.setVisible(p, false); } }.runTaskAsynchronously(plugin); @@ -250,17 +200,22 @@ public class Hologram { * @return Whether the given armor stand is part of the hologram */ public boolean contains(ArmorStand armorStand) { - return armorStands.contains(armorStand); + for (ArmorStandWrapper wrapper : wrappers) { + if (wrapper.getUuid().equals(armorStand.getUniqueId())) { + return true; + } + } + return interactArmorStandWrapper != null && interactArmorStandWrapper.getUuid().equals(armorStand.getUniqueId()); } - /** Returns the ArmorStands of this hologram */ - public List getArmorStands() { - return armorStands; + /** Returns the ArmorStandWrappers of this hologram */ + public List getArmorStandWrappers() { + return wrappers; } - /** Returns the ArmorStand of this hologram that is responsible for interaction */ - public ArmorStand getInteractArmorStand() { - return interactArmorStand; + /** Returns the ArmorStandWrapper of this hologram that is positioned higher to be used for interaction */ + public ArmorStandWrapper getInteractArmorStandWrapper() { + return interactArmorStandWrapper; } /** @@ -268,14 +223,16 @@ public class Hologram { * Hologram will be hidden from all players and will be killed */ public void remove() { - for (ArmorStand armorStand : armorStands) { - armorStand.remove(); + for (ArmorStandWrapper wrapper : wrappers) { + wrapper.remove(); } - if (interactArmorStand != null) { - interactArmorStand.remove(); + if (interactArmorStandWrapper != null) { + interactArmorStandWrapper.remove(); } + wrappers.clear(); + exists = false; holograms.remove(this); }