Nameplates With No (Visible) Mob Underneath (Holograms)

Discussion in 'Resources' started by gyroninja, Feb 20, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    gyroninja

    Spent yesterday working on some code for this. I got a quick implementation going using ProtocolLib and PacketWrapper.

    THIS IS WORKING IN 1.7
    I don't know why people think it broke. I'm not using any nms. The libraries I use work in most versions of minecraft will most likely already work in 1.8. (As long as they don't add any major protocol chages.)

    Here is the code for a Nametag object.
    Code:
    import com.comphenix.packetwrapper.WrapperPlayServerAttachEntity;
    import com.comphenix.packetwrapper.WrapperPlayServerEntityDestroy;
    import com.comphenix.packetwrapper.WrapperPlayServerEntityMetadata;
    import com.comphenix.packetwrapper.WrapperPlayServerEntityTeleport;
    import com.comphenix.packetwrapper.WrapperPlayServerSpawnEntity;
    import com.comphenix.packetwrapper.WrapperPlayServerSpawnEntityLiving;
    import com.comphenix.protocol.ProtocolLibrary;
    import com.comphenix.protocol.wrappers.WrappedDataWatcher;
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    import org.bukkit.Location;
    import org.bukkit.entity.EntityType;
    import org.bukkit.entity.Player;
     
    /**
    *
    * @author gyroninja
    */
    public class Nametag {
     
        int id;
     
        Location loc;
     
        double dy;
     
        String name;
     
        public Nametag(int id, Location loc, double dy, String name) {
     
            this.id = id;
     
            this.loc = loc;
     
            this.dy = dy;
     
            this.name = name;
        }
     
        public void showNametag(Player p) throws InvocationTargetException {
     
            WrapperPlayServerSpawnEntityLiving horse = new WrapperPlayServerSpawnEntityLiving();
     
            horse.setEntityID(id);
     
            horse.setType(EntityType.HORSE);
     
            horse.setX(loc.getX());
            horse.setY(loc.getY() + dy + 55);
            horse.setZ(loc.getZ());
     
            WrappedDataWatcher wdw = new WrappedDataWatcher();
     
            wdw.setObject(10, name);
            wdw.setObject(11, (byte) 1);
     
            wdw.setObject(12, -1700000);
     
            horse.setMetadata(wdw);
     
            ProtocolLibrary.getProtocolManager().sendServerPacket(p, horse.getHandle());
     
            WrapperPlayServerSpawnEntity skull = new WrapperPlayServerSpawnEntity();
     
            skull.setEntityID(id + 1);
     
            skull.setType(66);
     
            skull.setX(loc.getX());
            skull.setY(loc.getY() + dy + 55);
            skull.setZ(loc.getZ());
     
            ProtocolLibrary.getProtocolManager().sendServerPacket(p, skull.getHandle());
     
            WrapperPlayServerAttachEntity attach = new WrapperPlayServerAttachEntity();
     
            attach.setEntityId(id);
            attach.setVehicleId(id + 1);
     
            ProtocolLibrary.getProtocolManager().sendServerPacket(p, attach.getHandle());
        }
     
        public void hideNametag(Player p) throws InvocationTargetException {
     
            WrapperPlayServerEntityDestroy destroy = new WrapperPlayServerEntityDestroy();
     
            ArrayList<Integer> entities = new ArrayList<>();
     
            entities.add(id);
            entities.add(id + 1);
     
            destroy.setEntities(entities);
     
            ProtocolLibrary.getProtocolManager().sendServerPacket(p, destroy.getHandle());
        }
     
        public void setLocation(Player p, Location loc) throws InvocationTargetException {
     
            WrapperPlayServerEntityTeleport teleport = new WrapperPlayServerEntityTeleport();
     
            teleport.setEntityID(id + 1);
     
            teleport.setX(loc.getX());
            teleport.setY(loc.getY() + 55 + dy);
            teleport.setZ(loc.getZ());
     
            ProtocolLibrary.getProtocolManager().sendServerPacket(p, teleport.getHandle());
        }
     
        public void setName(Player p, String name) throws InvocationTargetException {
     
            this.name = name;
     
            WrapperPlayServerEntityMetadata eMeta = new WrapperPlayServerEntityMetadata();
     
            eMeta.setEntityId(id);
     
            WrappedDataWatcher wdw = new WrappedDataWatcher();
     
            wdw.setObject(10, name);
     
            eMeta.setEntityMetadata(wdw.getWatchableObjects());
     
            ProtocolLibrary.getProtocolManager().sendServerPacket(p, eMeta.getHandle());
        }
    }
    
    That is used for having a single nametag entity.

    For a list of multiple Nametags you can use NametagList
    Code:
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    import org.bukkit.Location;
    import org.bukkit.entity.Player;
     
    /**
    *
    * @author gyroninja
    */
    public class NametagList {
     
        ArrayList<Nametag> nametags = new ArrayList<>();
     
        public ArrayList<Nametag> getNametags() {
     
            return nametags;
        }
     
        public void showNametags(Player p) throws InvocationTargetException {
     
            for (Nametag nametag : nametags) {
     
                nametag.showNametag(p);
            }
        }
     
        public void hideNametags(Player p) throws InvocationTargetException {
     
            for (Nametag nametag : nametags) {
     
                nametag.hideNametag(p);
            }
        }
     
        public void setLocation(Player p, Location loc) throws InvocationTargetException {
     
            for (Nametag nametag : nametags) {
     
                nametag.setLocation(p, loc);
            }
        }
    }
    
    To add nametags to a list just call getNametags() and add a Nametag object to them.
    Class for loading pictures to nametags. Note this probably isn't the most optimal code. (GroupNametag extends JavaPlugin it can be any plugin)
    Code:
    import java.awt.AlphaComposite;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.imageio.ImageIO;
    import javax.imageio.ImageReader;
    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.Color;
    import org.bukkit.Location;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.java.JavaPlugin;
     
    /**
    *
    * @author gyroninja
    */
    public class NametagUtils {
     
        private static GroupNametag gnt;
     
        int currentID = 9000;
     
        Color[] colors = {
     
            Color.fromRGB(0, 0, 0),
            Color.fromRGB(0, 0, 170),
            Color.fromRGB(0, 170, 0),
            Color.fromRGB(0, 170, 170),
            Color.fromRGB(170, 0, 0),
            Color.fromRGB(170, 0, 170),
            Color.fromRGB(255, 170, 0),
            Color.fromRGB(170, 170, 170),
            Color.fromRGB(85, 85, 85),
            Color.fromRGB(85, 85, 255),
            Color.fromRGB(85, 255, 85),
            Color.fromRGB(85, 255, 255),
            Color.fromRGB(255, 85, 85),
            Color.fromRGB(255, 85, 255),
            Color.fromRGB(255, 255, 85),
            Color.fromRGB(255, 255, 255)
        };
     
        public void loadImage(String url) {
     
            ArrayList<String> lines = new ArrayList<>();
     
            if (url.endsWith(".gif")) {
     
                try {
     
                    ImageReader reader = (ImageReader)ImageIO.getImageReadersByFormatName("gif").next();
                   
                    reader.setInput(ImageIO.createImageInputStream(new URL(url).openStream()), false);
     
                    int frames = reader.getNumImages(true);
     
                    ArrayList<NametagList> tags = new ArrayList<>();
     
                    for (int i = 0; i < frames; i++) {
     
                        lines = getLinesFromImage(reader.read(i));
     
                        NametagList nametagList = new NametagList();
     
                        for (int i2 = 0; i2 < lines.size(); i2++) {
     
                            String line = lines.get(i2);
     
                            nametagList.nametags.add(new Nametag(currentID, new Location(Bukkit.getWorlds().get(0), 0, 80, 0), 20 - (i2 * 0.25), line));
     
                            currentID += 2;
                        }
     
                        tags.add(nametagList);
     
                        if (i == 0) {
     
                            try {
     
                                for (Player p : Bukkit.getOnlinePlayers()) {
     
                                    nametagList.showNametags(p);
                                }
                            }
     
                            catch (InvocationTargetException ex) {
     
                                Logger.getLogger(GroupNametag.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }
     
                    Bukkit.getScheduler().scheduleSyncDelayedTask(this, new UpdateNametagTask(10, tags));
                }
     
                catch (IOException ex) {
     
                    Logger.getLogger(GroupNametag.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
     
            else {
     
                try {
     
                    lines = getLinesFromImage(ImageIO.read(new URL(url)));
                }
     
                catch (IOException ex) {
     
                    Logger.getLogger(GroupNametag.class.getName()).log(Level.SEVERE, null, ex);
                }
     
                NametagList nametagList = new NametagList();
     
                for (int i = 0; i < lines.size(); i++) {
     
                    String line = lines.get(i);
     
                    nametagList.nametags.add(new Nametag(currentID, new Location(Bukkit.getWorlds().get(0), 0, 80, 0), 20 - (i * 0.25), line));
     
                    currentID += 2;
                }
     
                try {
     
                    for (Player p : Bukkit.getOnlinePlayers()) {
     
                        nametagList.showNametags(p);
                    }
                }
     
                catch (InvocationTargetException ex) {
     
                    Logger.getLogger(GroupNametag.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
     
        public double getDistance(Color c1, Color c2) {
     
            double rmean = (c1.getRed() + c2.getRed()) / 2.0;
     
            double r = c1.getRed() - c2.getRed();
            double g = c1.getGreen() - c2.getGreen();
            int b = c1.getBlue() - c2.getBlue();
     
            double weightR = 2 + rmean / 256.0;
            double weightG = 4.0;
            double weightB = 2 + (255 - rmean) / 256.0;
     
            return weightR * r * r + weightG * g * g + weightB * b * b;
        }
     
        public ArrayList<String> getLinesFromImage(BufferedImage image) {
     
            ArrayList<String> lines = new ArrayList<>();
     
            if (image.getWidth() > 128) {
     
                BufferedImage tmp = new BufferedImage(128, image.getHeight() / (image.getWidth() / 128), BufferedImage.TYPE_INT_ARGB);
     
                Graphics2D resizer = tmp.createGraphics();
     
                resizer.setComposite(AlphaComposite.Src);
     
                resizer.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                resizer.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                resizer.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     
                resizer.drawImage(image, 0, 0, 128, image.getHeight() / (image.getWidth() / 128), null);
     
                resizer.dispose();
     
                image = tmp;
            }
     
            int width = image.getWidth();
            int height = image.getHeight();
     
            for (int y = 0; y < height; y++) {
     
                StringBuilder line = new StringBuilder();
     
                for (int x = 0; x < width; x++) {
     
                    int color = image.getRGB(x, y);
     
                    int red = (color & 0x00ff0000) >> 16;
                    int green = (color & 0x0000ff00) >> 8;
                    int blue = color & 0x000000ff;
     
                    int alpha = (color>>24) & 0xff;
     
                    if (alpha < 128) {
     
                        line.append(ChatColor.WHITE);
                        line.append("⬛");
     
                        continue;
                    }
     
                    int index = 0;
                    int best = -1;
     
                    for (int i = 0; i < colors.length; i++) {
     
                        double distance = getDistance(Color.fromRGB(red, green, blue), colors[i]);
     
                        if (x == 0 && y == 0) {
     
                            Bukkit.broadcastMessage(colors[i].toString());
                        }
     
                        if (distance < best || best == -1) {
     
                            best = (int) distance;
     
                            index = i;
                        }
                    }
     
                    line.append(ChatColor.values()[index]);
                    line.append("⬛");
                }
     
                lines.add(line.toString());
            }
     
            return lines;
        }
    }
    
    And for making the gifs animated
    Code:
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import org.bukkit.Bukkit;
    import org.bukkit.entity.Player;
    import org.bukkit.scheduler.BukkitRunnable;
     
    /**
    *
    * @author gyroninja
    */
    public class UpdateNametagTask extends BukkitRunnable {
     
        int frame = 0;
        int next = 1;
     
        int delay;
     
        ArrayList<NametagList> tags;
     
        public UpdateNametagTask(int delay, ArrayList<NametagList> tags) {
     
            this.delay = delay;
     
            this.tags = tags;
        }
     
        @Override
        public void run() {
     
            try {
     
                for (int i = 0; i < tags.get(frame).nametags.size(); i++) {
     
                    Nametag nametag = tags.get(frame).nametags.get(i);
                    Nametag nametag2 = tags.get(next).nametags.get(i);
     
                    for (Player p : Bukkit.getOnlinePlayers()) {
     
                        //nametag.setName(p, nametag2.name); Meh doesn't work, not exactly sure why.
                        nametag.hideNametag(p);
                        nametag2.showNametag(p);
                    }
                }
     
                frame++;
                next++;
     
                if (frame == tags.size()) {
     
                    frame = 0;
                }
     
                if (next == tags.size()) {
     
                    next = 0;
                }
     
                if (next != 0) {
     
                    Bukkit.getScheduler().scheduleSyncDelayedTask(GroupNametag.getInstance(), this, delay);
                }
            }
     
            catch (InvocationTargetException ex) {
     
                Logger.getLogger(UpdateNametagTask.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    Here's an example of what's possible with these.

    2014-02-20_16.12.54.png

    Edit: Forgot to link to Asdjke's video on this, was in a rush. This version is a bit different and just uses packets to add them.

    Edit2: Upgraded my code to the new PacketWrapper source https://github.com/aadnk/PacketWrapper
    Edit 3: Updated a lot of the code
     
    Willbbz, Garris0n, fromgate and 11 others like this.
  2. Offline

    Garris0n

    You should give credit to the guy who came up with it as well.

    Nice work, though :)
     
    Goblom and DSH105 like this.
  3. Offline

    Goblom

    gyroninja This does not work with current versions of CraftBukkit as Packet names have been changed.

    Packet18SpawnMob is now PacketPlayOutSpawnEntity (I believe, please correct me if im wrong)
     
    spoljo666 likes this.
  4. Offline

    Garris0n

    If nobody else does I'll probably make something to convert images/text nicely in the next week.
     
    mattibijnens and Windy Day like this.
  5. Offline

    gyroninja

    I'm using PacketWrapper (https://github.com/aadnk/PacketWrapper)
    This will work in current versions. I didn't want to dabble with NMS too much with this project
    and this is a cleaner solution.
     
  6. Offline

    ccrama

    Do the nameplates move with the player, or do they remain stationary?
     
  7. Offline

    Quaro

    1. Is it posible to attach it to player?
    2. Could someone update it to 1.7?
    3. Could someone tell my how to make it independence to ProtocolLib?
     
  8. Offline

    Garris0n

    1. Not really, I guess you could teleport it.
    2. I'll make a..."cleaner" version later when I get some certain stuff done.
    3. ^
     
    spoljo666 likes this.
  9. Offline

    Quaro

    Garris0n Would be great if you could find way to attach it to player. I have awesome idea for this.
     
  10. Offline

    Garris0n

    In theory you could.
     
  11. Offline

    Goblom

    Am currently trying to update it to 1.7 NMS.... but im not very good with NMS...

    My current progress is at https://gist.github.com/Goblom/9137084

    Please note, i'm not very good good with NMS and i haven't played with it much. The basis of the Hologram system is there, but there is no way to set the location or the message because i haven't gotten that far into it before pulling some hair out trying to understand the EntityHorse NMS class...

    I Might go back and complete the class in the future but for now, others can use it as a base for which they need to finish (If they wish)
     
  12. Offline

    Garris0n

    https://github.com/Bukkit/CraftBukk...t/minecraft/server/EntityInsentient.java#L745
    https://github.com/Bukkit/CraftBukk...t/minecraft/server/EntityInsentient.java#L757

    Or you could use .getBukkitEntity.
     
  13. Offline

    Goblom

    Garris0n That is using CraftBukkit Classes... Since i know how to use those i didn't want to use them. I want experience with NMS as it interests me and is (not necessarily) a good thing to learn, but its fun to learn and use. Especially when using reflection, because you could could essentially have more control over entity manipulation.

    If you feel that using a CraftBukkit Class is more efficient then by all means go for it.

    Edit: I was wrong, I missed the package of the classes Garris0n posted
     
  14. Offline

    Garris0n

    Wait what? What exactly are you using then?
     
  15. Offline

    Goblom

    Garris0n Its using NMS (Net Minecraft Server) calling classes from net.minecraft.server.[version].[class]

    Using EntityInsentient is using OBC, i think that is what its called, (Org Bukkit CraftBukkit) calling classes from org.bukkit.craftbukkit.[version].[class]

    Edit: EntityInsentiant is not OBC, its NMS, i missed the package for the class.
     
  16. Online

    timtower Administrator Administrator Moderator

    Goblom /me offering to modify the code to the 1.7 system if wanted
     
  17. Offline

    Goblom

    timtower Welcome to it. I might not complete the version i posted as everyone i have talked to recommended using CraftBukkit classes, and i do not want to do that. So, i got stuck, and started pulling hair out...

    If you get stuck trying to read my code, well, you have my skype and are welcome to ask questions :)
     
  18. Offline

    Garris0n

    EntityInsentient is NMS, CraftLivingEntity would be the OBC class.

    Essentially it's structured in three "sets":
    The interfaces from the Bukkit API (Pig) - These are just interfaces to be used as the API dependency, they generally do not contain any actual code.
    The OBC classes from CraftBukkit (CraftPig) - These implement the interfaces and are what the entities "truly" are. They only have one practical use for a developer: getting the...
    NMS classes (EntityPig) - These contain the actual source of the entity, while the OBC class is a kind of "bridge" between the NMS and the API. They also contain plenty of obfuscated methods and other fun stuff.
     
  19. Offline

    Goblom

    Garris0n Woops, i missed the Package name :confused:... But at least i got some of my information right :D I was kind of iffy with my description.

    Edit: Get corrected, and also learned something new. Awesome :D
     
    Garris0n likes this.
  20. Offline

    Garris0n

    Also, just for the record, whenever I do make it my version won't be using reflection. Using raw NMS is more efficient, and the package names were added for good reason and shouldn't be "hacked" around.
     
  21. Offline

    Goblom

    Garris0n (if I understand correctly) By using raw NMS you mean importing nms classes...

    The only reason i opped for reflection is because the names will be the same through mc versions (no need to fix imports for every bukkit update). So, i wont need to import NMS classes for the specific version of MC that i want to use...

    Importing NMS classes that are specific to the version of mc is not that efficient because the imports will have to be changed every update and also not be compatible through bukkit updates.
     
  22. Offline

    Garris0n

    The point is that you have to change them so you actually look through your code and fix any issues. And reflection is just not efficient in general, it's much slower than importing the NMS classes and using them.

    Also, opted.
     
  23. Offline

    Onlineids

    Well I have no Idea wtf anyones talking about here..........
     
    Goblom and Garris0n like this.
  24. Offline

    Goblom

    Onlineids Discussion turned argument turned debate turned discussion, between Garris0n and I
     
    Garris0n likes this.
  25. Offline

    Garris0n

    To?

    Goblom is working on it.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 6, 2016
  26. Offline

    Goblom

    Garris0n Bart

    I have decided to not stop working on a port for 1.7. I will be working with timtower to port this over to 1.7 and above.

    We are going to turn our work into a plugin and post it on bukkit dev. Project has already been created, but main page looks like crap (I need someone to help out with making it look all pretty) (Click me to go to DBO Project Page)
     
  27. Offline

    Garris0n

    Well then, uh, me later Bart.
     
  28. Offline

    TeamJesus

    so no worky in 1.7.x then? and if not, how do i implement it? cheers
     
  29. Offline

    Goblom

  30. Offline

    DevRosemberg

    how can i do this? This looks so awesome. I want to implement this on my network. Is someone updating it?
     
Thread Status:
Not open for further replies.

Share This Page