Revert to NMS Armor Stands for holograms

Bukkit's armor stands caused too many issues
This commit is contained in:
Eric 2017-06-06 17:39:59 +02:00
parent f69299e20a
commit 4c02a77037
2 changed files with 230 additions and 110 deletions

View File

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

View File

@ -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<Hologram> holograms = new ArrayList<>();
private boolean exists = false;
private List<Object> nmsArmorStands = new ArrayList<>();
private List<ArmorStand> armorStands = new ArrayList<>();
private ArmorStand interactArmorStand;
private List<ArmorStandWrapper> wrappers = new ArrayList<>();
private ArmorStandWrapper interactArmorStandWrapper;
private Location location;
private List<Player> 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<String> 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);
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);
Location loc = getLocation().add(0, y, 0);
interactArmorStandWrapper = new ArmorStandWrapper(plugin, loc, null);
}
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);
for (ArmorStandWrapper wrapper : wrappers) {
wrapper.setVisible(p, true);
}
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);
}
}
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<ArmorStand> getArmorStands() {
return armorStands;
/** Returns the ArmorStandWrappers of this hologram */
public List<ArmorStandWrapper> 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);
}