[Class] Holograms in 1.8+

Discussion in 'Spigot Plugin Development' started by Janhektor, Dec 23, 2014.

Thread Status:
Not open for further replies.
  1. Hey guys,

    I have written a class to create holograms in version 1.8 and higher (for later).
    I allow you to use this code for commercial use or private use.
    You can simply create a new class and paste this code.
    Change the package-declaration and you are finish.
    The Code:
    Code (Text):
    package de.janhektor.infosigns.lib;

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.List;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.entity.Player;


    public class HoloAPI {
       
        private List<String> lines;
        private Location loc;
        private static final double ABS = 0.23D;
        private static String path;
        private static String version;
       
       
        static {
            path = Bukkit.getServer().getClass().getPackage().getName();
            version = path.substring(path.lastIndexOf(".")+1, path.length());
        }

        public HoloAPI(Location loc, List<String> lines) {
            this.lines = lines;
            this.loc = loc;
        }
       
        // Boolean successfully
        public boolean display(Player p) {
            Location displayLoc = loc.clone().add(0, (ABS * lines.size()) - 1.97D, 0);
            for (int i = 0; i < lines.size(); i++) {
                Object packet = this.getPacket(this.loc.getWorld(), displayLoc.getX(), displayLoc.getY(), displayLoc.getZ(), this.lines.get(i));
                if (packet == null) return false;
                this.sendPacket(p, packet);
                displayLoc.add(0, -ABS, 0);
            }
           
            return true;
        }
       
        public Object getPacket(World w, double x, double y, double z, String text) {
            try {
                Class<?> armorStand = Class.forName("net.minecraft.server." + version + ".EntityArmorStand");
                Class<?> worldClass = Class.forName("net.minecraft.server." + version + ".World");
                Class<?> nmsEntity = Class.forName("net.minecraft.server." + version + ".Entity");
                Class<?> craftWorld = Class.forName("org.bukkit.craftbukkit." + version + ".CraftWorld");
                Class<?> packetClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutSpawnEntityLiving");
                Class<?> entityLivingClass = Class.forName("net.minecraft.server." + version + ".EntityLiving");
                Constructor<?> cww = armorStand.getConstructor(new Class<?>[] { worldClass });
                Object craftWorldObj = craftWorld.cast(w);
                Method getHandleMethod = craftWorldObj.getClass().getMethod("getHandle", new Class<?>[0]);
                Object entityObject = cww.newInstance(new Object[] { getHandleMethod.invoke(craftWorldObj, new Object[0]) });
                Method setCustomName = entityObject.getClass().getMethod("setCustomName", new Class<?>[] { String.class });
                setCustomName.invoke(entityObject, new Object[] { text });
                Method setCustomNameVisible = nmsEntity.getMethod("setCustomNameVisible", new Class[] { boolean.class });
                setCustomNameVisible.invoke(entityObject, new Object[] { true });
                Method setGravity = entityObject.getClass().getMethod("setGravity", new Class<?>[] { boolean.class });
                setGravity.invoke(entityObject, new Object[] { false });
                Method setLocation = entityObject.getClass().getMethod("setLocation", new Class<?>[] { double.class, double.class, double.class, float.class, float.class });
                setLocation.invoke(entityObject, new Object[] { x, y, z, 0.0F, 0.0F });
                Method setInvisible = entityObject.getClass().getMethod("setInvisible", new Class<?>[] { boolean.class });
                setInvisible.invoke(entityObject, new Object[] { true });
                Constructor<?> cw = packetClass.getConstructor(new Class<?>[] { entityLivingClass });
                Object packetObject = cw.newInstance(new Object[] { entityObject });
                return packetObject;
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
       
        private void sendPacket(Player p, Object packet) {
            String path = Bukkit.getServer().getClass().getPackage().getName();
            String version = path.substring(path.lastIndexOf(".") + 1, path.length());
            try {
               Method getHandle = p.getClass().getMethod("getHandle");
               Object entityPlayer = getHandle.invoke(p);
               Object pConnection = entityPlayer.getClass().getField("playerConnection").get(entityPlayer);
               Class<?> packetClass = Class.forName("net.minecraft.server." + version + ".Packet");
               Method sendMethod = pConnection.getClass().getMethod("sendPacket", new Class[] { packetClass });
               sendMethod.invoke(pConnection, new Object[] { packet });
            } catch (Exception e) {
               e.printStackTrace();
            }
         }

    }
     
    If you find a bug or have an idea to optimize this code, contact me.

    Have fun!

    Janhektor
     
    • Like Like x 4
    • Informative Informative x 1
  2. @Janhektor optimalization? You could save a lot of reflection by using the API. That aside, I see about:
    • 7 class lookups
    • 2 constructor lookups
    • 8 method lookups
    That could be static final constants, one redundant cast (craftWorld.cast(...)), a path and version you create while you have these nice statics already (in sendPacket) and an Exception caught.
     
    • Agree Agree x 6
  3. You should lookup the classes, methods, and constructors in the constructor and then create the actual instances and invoke methods in your method to display the hologram. It will be more efficient for multiple uses.
     
    • Agree Agree x 1
  4. Thank you for your answers!

    I'm going to build a cache for re-used things.
    Tomorrow (or maybe today) I'll post the better solution here.
     
    • Like Like x 2
  5. So, I have your tips included.
    The new code is a little bit faster:
    Code (Text):
    package de.janhektor.holo;

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.List;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.entity.Player;


    public class HoloAPI {
       
        private List<String> lines;
        private Location loc;
       
        private static final double ABS = 0.23D;
        private static String path;
        private static String version;
       
        /*
         * Cache for getPacket()-Method
         */
        private static Class<?> armorStand;
        private static Class<?> worldClass;
        private static Class<?> nmsEntity;
        private static Class<?> craftWorld;
        private static Class<?> packetClass;
        private static Class<?> entityLivingClass;
        private static Constructor<?> armorStandConstructor;
       
        /*
         * Cache for sendPacket()-Method
         */
        private static Class<?> nmsPacket;
       
       
        static {
            path = Bukkit.getServer().getClass().getPackage().getName();
            version = path.substring(path.lastIndexOf(".")+1, path.length());
           
            try {
                armorStand = Class.forName("net.minecraft.server." + version + ".EntityArmorStand");
                worldClass = Class.forName("net.minecraft.server." + version + ".World");
                nmsEntity = Class.forName("net.minecraft.server." + version + ".Entity");
                craftWorld = Class.forName("org.bukkit.craftbukkit." + version + ".CraftWorld");
                packetClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutSpawnEntityLiving");
                entityLivingClass = Class.forName("net.minecraft.server." + version + ".EntityLiving");
                armorStandConstructor = armorStand.getConstructor(new Class[] { worldClass });
               
                nmsPacket = Class.forName("net.minecraft.server." + version + ".Packet");
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) {
                System.err.println("Error - Classes not initialized!");
                ex.printStackTrace();
            }
        }

        public HoloAPI(Location loc, List<String> lines) {
            this.lines = lines;
            this.loc = loc;
        }
       
        // Boolean successfully
        public boolean display(Player p) {
            Location displayLoc = loc.clone().add(0, (ABS * lines.size()) - 1.97D, 0);
            for (int i = 0; i < lines.size(); i++) {
                Object packet = this.getPacket(this.loc.getWorld(), displayLoc.getX(), displayLoc.getY(), displayLoc.getZ(), this.lines.get(i));
                if (packet == null) return false;
                this.sendPacket(p, packet);
                displayLoc.add(0, -ABS, 0);
            }
           
            return true;
        }
       
        private Object getPacket(World w, double x, double y, double z, String text) {
            try {
                Object craftWorldObj = craftWorld.cast(w);
                Method getHandleMethod = craftWorldObj.getClass().getMethod("getHandle", new Class<?>[0]);
                Object entityObject = armorStandConstructor.newInstance(new Object[] { getHandleMethod.invoke(craftWorldObj, new Object[0]) });
                Method setCustomName = entityObject.getClass().getMethod("setCustomName", new Class<?>[] { String.class });
                setCustomName.invoke(entityObject, new Object[] { text });
                Method setCustomNameVisible = nmsEntity.getMethod("setCustomNameVisible", new Class[] { boolean.class });
                setCustomNameVisible.invoke(entityObject, new Object[] { true });
                Method setGravity = entityObject.getClass().getMethod("setGravity", new Class<?>[] { boolean.class });
                setGravity.invoke(entityObject, new Object[] { false });
                Method setLocation = entityObject.getClass().getMethod("setLocation", new Class<?>[] { double.class, double.class, double.class, float.class, float.class });
                setLocation.invoke(entityObject, new Object[] { x, y, z, 0.0F, 0.0F });
                Method setInvisible = entityObject.getClass().getMethod("setInvisible", new Class<?>[] { boolean.class });
                setInvisible.invoke(entityObject, new Object[] { true });
                Constructor<?> cw = packetClass.getConstructor(new Class<?>[] { entityLivingClass });
                Object packetObject = cw.newInstance(new Object[] { entityObject });
                return packetObject;
            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
       
        private void sendPacket(Player p, Object packet) {
            try {
               Method getHandle = p.getClass().getMethod("getHandle");
               Object entityPlayer = getHandle.invoke(p);
               Object pConnection = entityPlayer.getClass().getField("playerConnection").get(entityPlayer);
               Method sendMethod = pConnection.getClass().getMethod("sendPacket", new Class[] { nmsPacket });
               sendMethod.invoke(pConnection, new Object[] { packet });
            } catch (Exception e) {
               e.printStackTrace();
            }
         }

    }
     
     
    • Like Like x 5
  6. Do you have a method to destroy the hologram?
     
  7. Sorry for my late answer.
    You can simply cache the entity id (or the whole EntityLiving object which is created by HoloAPI#getPacket) and send an EntityDestroyPacket to all players seeing your hologram. Get further information here: http://wiki.vg/Protocol#Destroy_Entities. I'm going to publish an update of this class today, if I've enough time to implement these feature.
     
  8. Done!

    Now, the class provides an additional method for removing the hologram for a specified player. Thanks @BukkitPVP for your idea, it's implemented now and it works fine. I also make some improvements like a better cache or JavaDocs.

    If you have got another idea or some problems with this class, feel free to contact me.

    Code (Text):
    package de.janhektor.test.api;

    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.UUID;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.entity.Player;

    public class HoloAPI {
     
        private List<Object> destroyCache;
        private List<Object> spawnCache;
        private List<UUID> players;
        private List<String> lines;
        private Location loc;
     
        private static final double ABS = 0.23D;
        private static String path;
        private static String version;
     
        /*
         * Cache for getPacket()-Method
         */
        private static Class<?> armorStand;
        private static Class<?> worldClass;
        private static Class<?> nmsEntity;
        private static Class<?> craftWorld;
        private static Class<?> packetClass;
        private static Class<?> entityLivingClass;
        private static Constructor<?> armorStandConstructor;
       
        /*
         * Cache for getDestroyPacket()-Method
         */
        private static Class<?> destroyPacketClass;
        private static Constructor<?> destroyPacketConstructor;
     
        /*
         * Cache for sendPacket()-Method
         */
        private static Class<?> nmsPacket;
     
     
        static {
            path = Bukkit.getServer().getClass().getPackage().getName();
            version = path.substring(path.lastIndexOf(".")+1, path.length());
         
            try {
                armorStand = Class.forName("net.minecraft.server." + version + ".EntityArmorStand");
                worldClass = Class.forName("net.minecraft.server." + version + ".World");
                nmsEntity = Class.forName("net.minecraft.server." + version + ".Entity");
                craftWorld = Class.forName("org.bukkit.craftbukkit." + version + ".CraftWorld");
                packetClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutSpawnEntityLiving");
                entityLivingClass = Class.forName("net.minecraft.server." + version + ".EntityLiving");
                armorStandConstructor = armorStand.getConstructor(new Class[] { worldClass });
             
                destroyPacketClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutEntityDestroy");
                destroyPacketConstructor = destroyPacketClass.getConstructor(int[].class);
               
                nmsPacket = Class.forName("net.minecraft.server." + version + ".Packet");
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) {
                System.err.println("Error - Classes not initialized!");
                ex.printStackTrace();
            }
        }

        /**
         * Create a new hologram
         * Note: The constructor will automatically initialize the internal cache; it may take some millis
         * @param loc The location where this hologram is shown
         * @param lines The text-lines, from top to bottom, farbcodes are possible
         */
        public HoloAPI(Location loc, String... lines) {
            this(loc, Arrays.asList(lines));
        }
       
        /**
         * Create a new hologram
         * Note: The constructor will automatically initialize the internal cache; it may take some millis
         * @param loc The location where this hologram is shown
         * @param lines The text-lines, from top to bottom, farbcodes are possible
         */
        public HoloAPI(Location loc, List<String> lines) {
            this.lines = lines;
            this.loc = loc;
            this.players = new ArrayList<>();
            this.spawnCache = new ArrayList<>();
            this.destroyCache = new ArrayList<>();
           
            // Init
            Location displayLoc = loc.clone().add(0, (ABS * lines.size()) - 1.97D, 0);
            for (int i = 0; i < lines.size(); i++) {
                Object packet = this.getPacket(this.loc.getWorld(), displayLoc.getX(), displayLoc.getY(), displayLoc.getZ(), this.lines.get(i));
                this.spawnCache.add(packet);
                try {
                    Field field = packetClass.getDeclaredField("a");
                    field.setAccessible(true);
                    this.destroyCache.add(this.getDestroyPacket(new int[] { (int) field.get(packet) }));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                displayLoc.add(0, ABS * (-1), 0);
            }
        }
     
        /**
         * Shows this hologram to the given player
         * @param p The player who will see this hologram at the location specified by calling the constructor
         * @return true, if the action was successful, else false
         */
        public boolean display(Player p) {
            for (int i = 0; i < spawnCache.size(); i++) {
                this.sendPacket(p, spawnCache.get(i));
            }
         
            this.players.add(p.getUniqueId());
            return true;
        }
       
        /**
         * Removes this hologram from the players view
         * @param p The target player
         * @return true, if the action was successful, else false (including the try to remove a non-existing hologram)
         */
        public boolean destroy(Player p) {
            if (this.players.contains(p.getUniqueId())) {
                for (int i = 0; i < this.destroyCache.size(); i++) {
                    this.sendPacket(p, this.destroyCache.get(i));
                }
                this.players.remove(p.getUniqueId());
                return true;
            }
            return false;
        }
     
        private Object getPacket(World w, double x, double y, double z, String text) {
            try {
                Object craftWorldObj = craftWorld.cast(w);
                Method getHandleMethod = craftWorldObj.getClass().getMethod("getHandle", new Class<?>[0]);
                Object entityObject = armorStandConstructor.newInstance(new Object[] { getHandleMethod.invoke(craftWorldObj, new Object[0]) });
                Method setCustomName = entityObject.getClass().getMethod("setCustomName", new Class<?>[] { String.class });
                setCustomName.invoke(entityObject, new Object[] { text });
                Method setCustomNameVisible = nmsEntity.getMethod("setCustomNameVisible", new Class[] { boolean.class });
                setCustomNameVisible.invoke(entityObject, new Object[] { true });
                Method setGravity = entityObject.getClass().getMethod("setGravity", new Class<?>[] { boolean.class });
                setGravity.invoke(entityObject, new Object[] { false });
                Method setLocation = entityObject.getClass().getMethod("setLocation", new Class<?>[] { double.class, double.class, double.class, float.class, float.class });
                setLocation.invoke(entityObject, new Object[] { x, y, z, 0.0F, 0.0F });
                Method setInvisible = entityObject.getClass().getMethod("setInvisible", new Class<?>[] { boolean.class });
                setInvisible.invoke(entityObject, new Object[] { true });
                Constructor<?> cw = packetClass.getConstructor(new Class<?>[] { entityLivingClass });
                Object packetObject = cw.newInstance(new Object[] { entityObject });
                return packetObject;
            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
       
        private Object getDestroyPacket(int... id) {
            try {
                return destroyPacketConstructor.newInstance(id);
            } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
     
        private void sendPacket(Player p, Object packet) {
            try {
               Method getHandle = p.getClass().getMethod("getHandle");
               Object entityPlayer = getHandle.invoke(p);
               Object pConnection = entityPlayer.getClass().getField("playerConnection").get(entityPlayer);
               Method sendMethod = pConnection.getClass().getMethod("sendPacket", new Class[] { nmsPacket });
               sendMethod.invoke(pConnection, new Object[] { packet });
            } catch (Exception e) {
               e.printStackTrace();
            }
         }

    }
     
    • Useful Useful x 2
  9. Is there a option or can you tell me how to create induvidual holograms? I need this for Showing stats :/ I want to show stats for the player but other players can't see it. So they only see their own Stats.
     
  10. Yes, it's possible to create individual holograms using my class.
    You need to create one separate instance of these class per player. I give you a simple example:
    Code (Text):

            for (Player p : Bukkit.getOnlinePlayers()) {
                HoloAPI hologram = new HoloAPI(p.getLocation(), "Your Name: " + p.getName());
                hologram.display(p);
            }
     
    Please keep in mind that you need the created instance of HoloAPI to destroy the hologram. I recommend you to cache the HoloAPI-objects in a map, if you want to hide the holograms without relogging.
     
  11. So I can use that method on a playerJoinEvent? Sorry im new to Holograms.
     
    • Funny Funny x 1
  12. Yes, of course you can.
    Please keep in mind that the location which is expected by the constructor, represents the bottom (last line) of your hologram.
     
  13. Can you show me a example? English is not my best language :/
     
  14. ArmorStands despawn after a given distance so you need to resend the packets to the player if he walks away and then comes back. A method that simply allows to change the name of the spawned armor stand would be good for people that want animated holograms without respawning it each time they change something. And a method to teleport the hologram.
     
  15. Thanks a lot!
     
    • Like Like x 1
  16. Thank you for your help! Now I can create better plugins!
     
    • Agree Agree x 1
  17. Hey there,

    thanks for your positive feedback.
    After ~ 2 months I updated the Hologram-class (now called HoloAPI) again.
    There are some little improvements to observe the Google Code Style Guidelines.
    Additionally, I added an interface which contains the core methods (HoloAPI#display / HoloAPI#destroy). It's now possible to create a new instance by simply calling HoloAPI#newInstance (static) instead of the class constructor.

    Note about Java 8
    In order to use the new changes it's required to have Java 8 or greater installed on the machine. If you're using an older version of Java, there are two possible ways:
    1. Use an older version (e.g. 1.3, see above)
    2. Modify the sourcecode by removing the interface and making some changes in the implementation
    Migrate from 1.3
    To update the HoloAPI, just replace your current class with the following sourcecode. If you're going to create a new instance, you can alternatively write this:
    Code (Text):
    HoloAPI api = HoloAPI.newInstance(location, "Hello World!")
    Sourcecode
    File: HoloAPI.java
    Code (Text):
    import java.util.Arrays;
    import java.util.List;

    import org.bukkit.Location;
    import org.bukkit.entity.Player;

    /**
    * @author Janhektor
    * @version 1.4 (December 30, 2015)
    */
    public interface HoloAPI {

        /**
         * Shows this hologram to the given player
         * @param p The player who will see this hologram at the location specified by calling the constructor
         * @return true if the action was successful, else false
         */
        boolean display(Player player);
       
        /**
         * Removes this hologram from the players view
         * @param p The target player
         * @return true if the action was successful, else false (including the try to remove a non-existing hologram)
         */
        boolean destroy(Player player);
       
        /**
         * Create a new hologram
         * Note: The internal cache will be automatically initialized, it may take some millis
         * @param loc The location where this hologram is shown
         * @param lines The text-lines, from top to bottom, farbcodes are possible
         */
        public static HoloAPI newInstance(Location location, String... lines) {
            return newInstance(location, Arrays.asList(lines));
        }
       
        /**
         * Create a new hologram
         * Note: The internal cache will be automatically initialized, it may take some millis
         * @param loc The location where this hologram is shown
         * @param lines The text-lines, from top to bottom, farbcodes are possible
         */
        public static HoloAPI newInstance(Location location, List<String> lines) {
            return new DefaultHoloAPI(location, lines);
        }
    }
     

    File: DefaultHoloAPI.java

    Code (Text):
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.UUID;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.World;
    import org.bukkit.entity.Player;

    /**
    * @author Janhektor
    * @version 1.4 (December 30, 2015)
    */
    public class DefaultHoloAPI implements HoloAPI {
        private final List<Object> destroyCache;
        private final List<Object> spawnCache;
        private final List<UUID> players;
        private final List<String> lines;
        private final Location loc;
        private static final double ABS = 0.23D;
        private static String path;
        private static String version;
        /*
         * Cache for getPacket()-Method
         */
        private static Class<?> armorStand;
        private static Class<?> worldClass;
        private static Class<?> nmsEntity;
        private static Class<?> craftWorld;
        private static Class<?> packetClass;
        private static Class<?> entityLivingClass;
        private static Constructor<?> armorStandConstructor;
       
        /*
         * Cache for getDestroyPacket()-Method
         */
        private static Class<?> destroyPacketClass;
        private static Constructor<?> destroyPacketConstructor;
        /*
         * Cache for sendPacket()-Method
         */
        private static Class<?> nmsPacket;
        static {
            path = Bukkit.getServer().getClass().getPackage().getName();
            version = path.substring(path.lastIndexOf(".")+1, path.length());
         
            try {
                armorStand = Class.forName("net.minecraft.server." + version + ".EntityArmorStand");
                worldClass = Class.forName("net.minecraft.server." + version + ".World");
                nmsEntity = Class.forName("net.minecraft.server." + version + ".Entity");
                craftWorld = Class.forName("org.bukkit.craftbukkit." + version + ".CraftWorld");
                packetClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutSpawnEntityLiving");
                entityLivingClass = Class.forName("net.minecraft.server." + version + ".EntityLiving");
                armorStandConstructor = armorStand.getConstructor(new Class[] { worldClass });
             
                destroyPacketClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutEntityDestroy");
                destroyPacketConstructor = destroyPacketClass.getConstructor(int[].class);
               
                nmsPacket = Class.forName("net.minecraft.server." + version + ".Packet");
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) {
                System.err.println("Error - Classes not initialized!");
                ex.printStackTrace();
            }
        }

        public DefaultHoloAPI(Location loc, String... lines) {
            this(loc, Arrays.asList(lines));
        }
       
        public DefaultHoloAPI(Location loc, List<String> lines) {
            this.lines = lines;
            this.loc = loc;
            this.players = new ArrayList<>();
            this.spawnCache = new ArrayList<>();
            this.destroyCache = new ArrayList<>();
           
            // Init
            Location displayLoc = loc.clone().add(0, (ABS * lines.size()) - 1.97D, 0);
            for (int i = 0; i < lines.size(); i++) {
                Object packet = this.getPacket(this.loc.getWorld(), displayLoc.getX(), displayLoc.getY(), displayLoc.getZ(), this.lines.get(i));
                this.spawnCache.add(packet);
                try {
                    Field field = packetClass.getDeclaredField("a");
                    field.setAccessible(true);
                    this.destroyCache.add(this.getDestroyPacket(new int[] { (int) field.get(packet) }));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                displayLoc.add(0, ABS * (-1), 0);
            }
        }
        @Override
        public boolean display(Player p) {
            for (int i = 0; i < this.spawnCache.size(); i++) {
                this.sendPacket(p, this.spawnCache.get(i));
            }
         
            this.players.add(p.getUniqueId());
            return true;
        }
       
        @Override
        public boolean destroy(Player p) {
            if (this.players.contains(p.getUniqueId())) {
                for (int i = 0; i < this.destroyCache.size(); i++) {
                    this.sendPacket(p, this.destroyCache.get(i));
                }
                this.players.remove(p.getUniqueId());
                return true;
            }
            return false;
        }
        private Object getPacket(World w, double x, double y, double z, String text) {
            try {
                Object craftWorldObj = craftWorld.cast(w);
                Method getHandleMethod = craftWorldObj.getClass().getMethod("getHandle", new Class<?>[0]);
                Object entityObject = armorStandConstructor.newInstance(new Object[] { getHandleMethod.invoke(craftWorldObj, new Object[0]) });
                Method setCustomName = entityObject.getClass().getMethod("setCustomName", new Class<?>[] { String.class });
                setCustomName.invoke(entityObject, new Object[] { text });
                Method setCustomNameVisible = nmsEntity.getMethod("setCustomNameVisible", new Class[] { boolean.class });
                setCustomNameVisible.invoke(entityObject, new Object[] { true });
                Method setGravity = entityObject.getClass().getMethod("setGravity", new Class<?>[] { boolean.class });
                setGravity.invoke(entityObject, new Object[] { false });
                Method setLocation = entityObject.getClass().getMethod("setLocation", new Class<?>[] { double.class, double.class, double.class, float.class, float.class });
                setLocation.invoke(entityObject, new Object[] { x, y, z, 0.0F, 0.0F });
                Method setInvisible = entityObject.getClass().getMethod("setInvisible", new Class<?>[] { boolean.class });
                setInvisible.invoke(entityObject, new Object[] { true });
                Constructor<?> cw = packetClass.getConstructor(new Class<?>[] { entityLivingClass });
                Object packetObject = cw.newInstance(new Object[] { entityObject });
                return packetObject;
            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
       
        private Object getDestroyPacket(int... id) {
            try {
                return destroyPacketConstructor.newInstance(id);
            } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
        private void sendPacket(Player p, Object packet) {
            try {
               Method getHandle = p.getClass().getMethod("getHandle");
               Object entityPlayer = getHandle.invoke(p);
               Object pConnection = entityPlayer.getClass().getField("playerConnection").get(entityPlayer);
               Method sendMethod = pConnection.getClass().getMethod("sendPacket", new Class[] { nmsPacket });
               sendMethod.invoke(pConnection, new Object[] { packet });
            } catch (Exception e) {
               e.printStackTrace();
            }
         }

    }
    Have fun with these updates!

    ~ Janhektor
     
    • Useful Useful x 1
  18. Thanks for that
    I love this resource!!
     
    • Agree Agree x 1
  19. As I said, armor stands despawn client side so you need to resend them as the player approaches them again. And a change name and teleport method maybe? I don't need them as I have my own API but it might be useful for other users.
     
  20. sothatsit

    Patron

    You still don't cache your fields/methods. Also, why do you now use an interface? I see no need for it.
     
Thread Status:
Not open for further replies.