[Lib] NametagSpawner (Spawn Nametags without Mobs)

Discussion in 'Resources' started by DevRosemberg, Feb 21, 2014.

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

    DevRosemberg

    Hey Everyone, DevRo here and today with Comphenix we wanted to create a method to spawn those Nametags easily, so here are the classes you will need, this version supports images:

    Thanks to Asjdke for the idea:



    Code:java
    1. /**
    2. * User: bobacadodl
    3. * Date: 1/25/14
    4. * Time: 11:03 PM
    5. */
    6. public enum ImageChar {
    7. BLOCK('\u2588'),
    8. DARK_SHADE('\u2593'),
    9. MEDIUM_SHADE('\u2592'),
    10. LIGHT_SHADE('\u2591');
    11. private char c;
    12.  
    13. ImageChar(char c) {
    14. this.c = c;
    15. }
    16.  
    17. public char getChar() {
    18. return c;
    19. }
    20. }


    Code:java
    1. /**
    2. * User: bobacadodl
    3. * Date: 1/25/14
    4. * Time: 10:28 PM
    5. */
    6. public class ImageMessage {
    7. private final Color[] colors = {
    8. new Color(0, 0, 0),
    9. new Color(0, 0, 170),
    10. new Color(0, 170, 0),
    11. new Color(0, 170, 170),
    12. new Color(170, 0, 0),
    13. new Color(170, 0, 170),
    14. new Color(255, 170, 0),
    15. new Color(170, 170, 170),
    16. new Color(85, 85, 85),
    17. new Color(85, 85, 255),
    18. new Color(85, 255, 85),
    19. new Color(85, 255, 255),
    20. new Color(255, 85, 85),
    21. new Color(255, 85, 255),
    22. new Color(255, 255, 85),
    23. new Color(255, 255, 255),
    24. };
    25.  
    26. protected String[] lines;
    27.  
    28. public ImageMessage(BufferedImage image, int height, char imgChar) {
    29. ChatColor[][] chatColors = toChatColorArray(image, height);
    30. lines = toImgMessage(chatColors, imgChar);
    31. }
    32.  
    33. public ImageMessage(ChatColor[][] chatColors, char imgChar) {
    34. lines = toImgMessage(chatColors, imgChar);
    35. }
    36.  
    37. public ImageMessage(String... imgLines) {
    38. lines = imgLines;
    39. }
    40.  
    41. public ImageMessage appendText(String... text) {
    42. for (int y = 0; y < lines.length; y++) {
    43. if (text.length > y) {
    44. lines[y] += " " + text[y];
    45. }
    46. }
    47. return this;
    48. }
    49.  
    50. public ImageMessage appendCenteredText(String... text) {
    51. for (int y = 0; y < lines.length; y++) {
    52. if (text.length > y) {
    53. int len = ChatPaginator.AVERAGE_CHAT_PAGE_WIDTH - lines[y].length();
    54. lines[y] = lines[y] + center(text[y], len);
    55. } else {
    56. return this;
    57. }
    58. }
    59. return this;
    60. }
    61.  
    62. private ChatColor[][] toChatColorArray(BufferedImage image, int height) {
    63. double ratio = (double) image.getHeight() / image.getWidth();
    64. int width = (int) (height / ratio);
    65. if (width > 10) width = 10;
    66. BufferedImage resized = resizeImage(image, (int) (height / ratio), height);
    67.  
    68. ChatColor[][] chatImg = new ChatColor[resized.getWidth()][resized.getHeight()];
    69. for (int x = 0; x < resized.getWidth(); x++) {
    70. for (int y = 0; y < resized.getHeight(); y++) {
    71. int rgb = resized.getRGB(x, y);
    72. ChatColor closest = getClosestChatColor(new Color(rgb));
    73. chatImg[x][y] = closest;
    74. }
    75. }
    76. return chatImg;
    77. }
    78.  
    79. private String[] toImgMessage(ChatColor[][] colors, char imgchar) {
    80. String[] lines = new String[colors[0].length];
    81. for (int y = 0; y < colors[0].length; y++) {
    82. String line = "";
    83. for (int x = 0; x < colors.length; x++) {
    84. line += colors[x][y].toString() + imgchar;
    85. }
    86. lines[y] = line + ChatColor.RESET;
    87. }
    88. return lines;
    89. }
    90.  
    91. private BufferedImage resizeImage(BufferedImage originalImage, int width, int height) {
    92. BufferedImage resizedImage = new BufferedImage(width, height, 6);
    93. Graphics2D g = resizedImage.createGraphics();
    94. g.drawImage(originalImage, 0, 0, width, height, null);
    95. g.dispose();
    96. g.setComposite(AlphaComposite.Src);
    97. g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    98. g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    99. g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    100. return resizedImage;
    101. }
    102.  
    103. private double getDistance(Color c1, Color c2) {
    104. double rmean = (c1.getRed() + c2.getRed()) / 2.0;
    105. double r = c1.getRed() - c2.getRed();
    106. double g = c1.getGreen() - c2.getGreen();
    107. int b = c1.getBlue() - c2.getBlue();
    108. double weightR = 2 + rmean / 256.0;
    109. double weightG = 4.0;
    110. double weightB = 2 + (255 - rmean) / 256.0;
    111. return weightR * r * r + weightG * g * g + weightB * b * b;
    112. }
    113.  
    114. private boolean areIdentical(Color c1, Color c2) {
    115. return Math.abs(c1.getRed() - c2.getRed()) <= 5 &&
    116. Math.abs(c1.getGreen() - c2.getGreen()) <= 5 &&
    117. Math.abs(c1.getBlue() - c2.getBlue()) <= 5;
    118.  
    119. }
    120.  
    121. private ChatColor getClosestChatColor(Color color) {
    122. if (color.getAlpha() < 128) return ChatColor.BLACK;
    123.  
    124. int index = 0;
    125. double best = -1;
    126.  
    127. for (int i = 0; i < colors.length; i++) {
    128. if (areIdentical(colors, color)) {
    129. return ChatColor.values();
    130. }
    131. }
    132.  
    133. for (int i = 0; i < colors.length; i++) {
    134. double distance = getDistance(color, colors);
    135. if (distance < best || best == -1) {
    136. best = distance;
    137. index = i;
    138. }
    139. }
    140.  
    141. // Minecraft has 15 colors
    142. return ChatColor.values()[index];
    143. }
    144.  
    145. private String center(String s, int length) {
    146. if (s.length() > length) {
    147. return s.substring(0, length);
    148. } else if (s.length() == length) {
    149. return s;
    150. } else {
    151. int leftPadding = (length - s.length()) / 2;
    152. StringBuilder leftBuilder = new StringBuilder();
    153. for (int i = 0; i < leftPadding; i++) {
    154. leftBuilder.append(" ");
    155. }
    156. return leftBuilder.toString() + s;
    157. }
    158. }
    159.  
    160. public String[] getLines() {
    161. return lines;
    162. }
    163.  
    164. public void sendToPlayer(Player player) {
    165. for (String line : lines) {
    166. player.sendMessage(line);
    167. }
    168. }
    169. }


    Code:java
    1. public class NameTagMessage extends ImageMessage {
    2. private NameTagSpawner spawner;
    3. private Location location;
    4.  
    5. private double lineSpacing = 0.25d;
    6.  
    7. public NameTagMessage(BufferedImage image, int height, char imgChar) {
    8. super(image, height, imgChar);
    9. initialize(height);
    10. }
    11.  
    12. public NameTagMessage(ChatColor[][] chatColors, char imgChar) {
    13. super(chatColors, imgChar);
    14. this.location = Preconditions.checkNotNull(location, "location cannot be NULL");
    15. initialize(chatColors.length);
    16. }
    17.  
    18. public NameTagMessage(String... imgLines) {
    19. super(imgLines);
    20. initialize(imgLines.length);
    21. }
    22.  
    23. private void initialize(int height) {
    24. this.spawner = new NameTagSpawner(height);
    25. }
    26.  
    27. @Override
    28. public NameTagMessage appendCenteredText(String... text) {
    29. super.appendCenteredText(text);
    30. return this;
    31. }
    32.  
    33. @Override
    34. public NameTagMessage appendText(String... text) {
    35. super.appendText(text);
    36. return this;
    37. }
    38.  
    39. public void setLocation(Location location) {
    40. this.location = location;
    41. }
    42.  
    43. public Location getLocation() {
    44. return location;
    45. }
    46.  
    47. /**
    48. * Retrieve the default amount of meters in the y-axis between each name tag.
    49. * @return The line spacing.
    50. */
    51. public double getLineSpacing() {
    52. return lineSpacing;
    53. }
    54.  
    55. /**
    56. * Set the default amount of meters in the y-axis between each name tag.
    57. * @param lineSpacing - the name spacing.
    58. */
    59. public void setLineSpacing(double lineSpacing) {
    60. this.lineSpacing = lineSpacing;
    61. }
    62.  
    63. @Override
    64. public void sendToPlayer(Player player) {
    65. sendToPlayer(player, location != null ? location : player.getLocation());
    66. }
    67.  
    68. /**
    69. * Send a floating image message to the given player at the specified starting location.
    70. * @param player - the player.
    71. * @param location - the starting location.
    72. */
    73. public void sendToPlayer(Player player, Location location) {
    74. for (int i = 0; i < lines.length; i++) {
    75. spawner.setNameTag(i, player, location, -i * lineSpacing, lines);
    76. }
    77. }
    78. }


    Code:java
    1. // These can be found in the following project:
    2. // [url]https://github.com/aadnk/PacketWrapper[/url]
    3. import com.comphenix.example.wrapper.WrapperPlayServerAttachEntity;
    4. import com.comphenix.example.wrapper.WrapperPlayServerSpawnEntity;
    5. import com.comphenix.example.wrapper.WrapperPlayServerSpawnEntityLiving;
    6. import com.comphenix.protocol.wrappers.WrappedDataWatcher;
    7.  
    8. /**
    9. * Represents a spawner of name tags.
    10. * @author Kristian
    11. */
    12. public class NameTagSpawner {
    13. private static final int WITHER_SKULL = 66;
    14.  
    15. // Shared entity ID allocator
    16. private static int SHARED_ENTITY_ID = Short.MAX_VALUE;
    17.  
    18. // The starting entity ID
    19. private int startEntityId;
    20. private int nameTagCount;
    21.  
    22. /**
    23. * Construct a new name tag spawner.
    24. * <p>
    25. * Specify a number of name tags to spawn.
    26. * @param nameTags - the maximum number of name tags we will spawn at any given time.
    27. */
    28. public NameTagSpawner(int nameTagCount) {
    29. this.startEntityId = SHARED_ENTITY_ID;
    30. this.nameTagCount = nameTagCount;
    31.  
    32. // We need to reserve two entity IDs per name tag
    33. SHARED_ENTITY_ID += nameTagCount * 2;
    34. }
    35.  
    36. /**
    37. * Retrieve the maximum number of name tags we can spawn.
    38. * @return The maximum number.
    39. */
    40. public int getNameTagCount() {
    41. return nameTagCount;
    42. }
    43.  
    44. /**
    45. * Set the location and message of a name tag.
    46. * @param index - index of the name tag. Cannot exceeed {@link #getNameTagCount()}.
    47. * @param observer - the observing player.
    48. * @param location - the location in the same world as the player.
    49. * @param dY - Y value to add to the final location.
    50. * @param message - the message to display.
    51. */
    52. public void setNameTag(int index, Player observer, Location location, double dY, String message) {
    53. WrapperPlayServerAttachEntity attach = new WrapperPlayServerAttachEntity();
    54. WrapperPlayServerSpawnEntityLiving horse = createHorsePacket(index, location, dY, message);
    55. WrapperPlayServerSpawnEntity skull = createSkullPacket(index, location, dY);
    56.  
    57. // The horse is riding on the skull
    58. attach.setEntityId(horse.getEntityID());
    59. attach.setVehicleId(skull.getEntityID());
    60.  
    61. horse.sendPacket(observer);
    62. skull.sendPacket(observer);
    63. attach.sendPacket(observer);
    64. }
    65.  
    66. // Construct the invisible horse packet
    67. private WrapperPlayServerSpawnEntityLiving createHorsePacket(int index, Location location, double dY, String message) {
    68. WrapperPlayServerSpawnEntityLiving horse = new WrapperPlayServerSpawnEntityLiving();
    69. horse.setEntityID(startEntityId + index * 2);
    70. horse.setType(EntityType.HORSE);
    71. horse.setX(location.getX());
    72. horse.setY(location.getY() + dY + 55);
    73. horse.setZ(location.getZ());
    74.  
    75. WrappedDataWatcher wdw = new WrappedDataWatcher();
    76. wdw.setObject(10, message);
    77. wdw.setObject(11, (byte) 1);
    78. wdw.setObject(12, -1700000);
    79. horse.setMetadata(wdw);
    80. return horse;
    81. }
    82.  
    83. // Construct the wither skull packet
    84. private WrapperPlayServerSpawnEntity createSkullPacket(int index, Location location, double dY) {
    85. WrapperPlayServerSpawnEntity skull = new WrapperPlayServerSpawnEntity();
    86. skull.setEntityID(startEntityId + index * 2 + 1);
    87. skull.setType(WITHER_SKULL);
    88. skull.setX(location.getX());
    89. skull.setY(location.getY() + dY + 55);
    90. skull.setZ(location.getZ());
    91. return skull;
    92. }
    93. }


    So, now after having these classes you can do something like the following:

    Code:java
    1. spawner.setNameTag(0, player, NametagHandler.getOne(), 0, ChatColor.YELLOW + "Welcome to");
    2. spawner.setNameTag(1, player, NametagHandler.getOne(), -0.25, ChatColor.GOLD + "The Cosmos");


    Which will display the following:

    [​IMG]

    Or you can even do images like the following:

    Code:java
    1. TheCosmosHub.getInstance().message.sendToPlayer(player, NametagHandler.getLogo());


    (Remember, always use a try & catch to load the file), Images will look like the following:
    [​IMG]

    TODO List:
    - Make Images Be Able to Move around
    - Support Animations (GIFs).

    Best of Luck and we both Comphenix and me expect to be seeing this in lots of Projects. Regards.

    PD: Ask bobacadodl to add Alpha Values support so you dont get Black squares in alpha area.
     
  2. Offline

    Adriani6

    Nice !
     
  3. Offline

    Goblom

    Grrr, u cheater you are using ProtocolLib!?!?!?

    On the other hand, great work.

    I'm sure there will be at least 20 plugins that will now be able to do this sort of stuff within the next 3 days :/
     
    Skyost and filoghost like this.
  4. Offline

    DevRosemberg

    Goblom Totally true, and well, it was the most safe version to do it so why not.
     
  5. Offline

    Goblom

    DevRosemberg

    filoghost and I were doing a small competition between each other to see who can release the first hologram type plugin, we were going to use nms instead of protocol lib.

    Now, since this has been released the competition can now be ruined by anyone with a tiny bit of java experience. This kinda ruined a fun game :(
     
  6. Offline

    DevRosemberg

  7. Offline

    Garris0n

    Just add "no ProtocolLib allowed" to the set of rules.
     
    DevRosemberg likes this.
  8. Offline

    Comphenix

    I've now updated the example with support for moving floating images. You can get the latest version on GitHub.

    There's also an automated build of this example on my Jenkins server, in case you don't want to go to the trouble of building it just to test the feature.
     
    ZeusAllMighty11 and spoljo666 like this.
  9. Offline

    gyroninja

    Nice!

    Though when did protocollib get banned form this contest. My original code used protocl lib and packetwrapper.
    I never used NMS for this. :p

    I've been working on getting images to text working too. I guess we've been looking at similar sources since our code looks very a like at the moment. Though my code atm supports GIF's.
     
  10. Offline

    bobacadodl

    LOL. :) I literally just finished coding this exact same thing myself, and I come on bukkit to see that someone already released it.
    Anyways, nice job!
     
  11. Offline

    DevRosemberg

  12. Offline

    WizardCM

    This is incredibly cool. Too bad it rotates, otherwise it'd be awesome for signposts and directional things. Would it be possible at all to make them only render when you're looking from a certain direction?
     
  13. Offline

    viper_monster

    DevRosemberg how would I clear a NameTagMessage that I previously spawned?

    nvm, figured it out... :p haha
     
  14. Offline

    DevRosemberg

    spoljo666 Would have replied but i was not here :)

    Next step: Add support for Alpha Values to add transparency to images.

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

    Hoolean

    WizardCM

    Unfortunately, I believe that nametags always face you when rendering.
     
  16. Offline

    DevRosemberg

    Hoolean Yep, they do, maybe there is a way with ProtocolLib or something, ill ask Comp.

    Edit: No, its not possible, its how they are rendered. Can't be changed.
     
  17. Offline

    WizardCM

    Meaning you also can't hide them at certain angles, at least for now? That's a bummer. Maybe in 1.8?
     
  18. Offline

    DevRosemberg

    WizardCM Try to convince mojang to do it, maybe they add it but i dont know when.
     
  19. Offline

    Comphenix

    DevRosemberg likes this.
  20. Offline

    codename_B

    You could always detect the "look angle" of the player and only show them when they're looking in a certain min/max yaw from a certain location.

    JUST SAYIN.

    Yes it's not as simple as it sounds, but it's not impossibru either ;)
     
  21. Offline

    Garris0n

    You could check the angle and remove it under certain conditions.

    Oh wait codename_B said that. Oh well, he didn't quote you, so now you know.
     
  22. Offline

    ccrama

    DevRosemberg the sendToPlayer method

    Code:java
    1. public void sendToPlayer(Player player, Location location) {
    2. for (int i = 0; i < lines.length; i++) {
    3. spawner.setNameTag(i, player, location, -i * lineSpacing, lines);
    4. }
    5. }


    returns an error. sendToPlayer's "lines" is a string array, not a single string, but in NameTagSpawner class it doesn't use an array, and uses a single string. I don't want to mess with it too much to fix it as I don't know what my changes would do to the overall code, because that method seems to be used by both the image methods and the plain text methods.
     
  23. Offline

    DevRosemberg

    ccrama What are you trying to do.
     
  24. Offline

    ccrama

    DevRosemberg Just copying your classes into my plugin. The methods used in the NameTagMessage class don't match the variable types used in NameTagSpawner

    More specifically
    PHP:
    The method setNameTag(intPlayerLocationdoubleStringin the type NameTagSpawner is not applicable for the arguments (intPlayerLocationdoubleString[])
     
  25. Offline

    felixferr0w

    Haha! Good job guys!
    Look's like I wasn't the only who was working on a project like this... :p
    Well, might as well finish it ;)
     
  26. Offline

    Comphenix

    Ah, I think I know what's happening here. The original line contained the following array reference :
    Code:
    lines[i]
    Which must have been treated as BBCode and begun italicizing the rest of the code. Then, since any formatting is forcefully removed in code blocks after you edit the post, the italicization got lost as well.

    The Bukkit forum software is just too unreliable for large code posts, especially if you don't re-insert it when you edit posts. I recommend downloading the code from GitHub instead.
     
  27. Offline

    ccrama



    Ah, yes. That's exactly what happened. Github worked! Testing out now ( DevRosemberg needs to link the github on the OP :p)

    Hmm, the code works, but only every once in a while. Every couple joins works great, and sometimes it just doesn't do anything at all. I edited @Comphenix's example code, and can't quite figure out if it doesn't work because of the glitchy nature of these holograms, or if I did something wrong. Here's my code

    Code:java
    1. public Map<Player, BukkitTask> playerTask = Maps.newHashMap();
    2. public boolean stopTask(Player player) {
    3. BukkitTask task = playerTask.remove(player);
    4.  
    5. if (task != null) {
    6. task.cancel();
    7. return true;
    8. }
    9. return false;
    10. }
    11. @EventHandler
    12. public void createHologram(PlayerJoinEvent e) {
    13. final Player player = e.getPlayer();
    14. final NameTagMessage message = Main.message; //this code is below
    15. if (!stopTask(player)) {
    16. final Location loc = Main.hub.add(0, 5, 0); //this location works, not the issue.
    17. message.sendToPlayer(player, loc);
    18.  
    19. playerTask.put(player,
    20. Bukkit.getServer().getScheduler().runTaskTimer(Bukkit.getPluginManager().getPlugin("MF2"), new Runnable() {
    21. int numb = 70;
    22. @Override
    23. public void run() {
    24. if(numb > 0){
    25. loc.add(0, 0.02, 0);
    26. numb = numb - 1;
    27. message.move(player, loc);
    28. } else {
    29. stopTask(player);
    30. }
    31. }
    32. }, 1, 1));
    33.  
    34. } else {
    35. message.clear(player);
    36. stopTask(player);
    37. }
    38. }


    Main class code:
    Code:java
    1. public static NameTagMessage message;
    2. public BufferedImage image;
    3.  
    4. //in my onEnable()
    5. File icon =new File("plugins" + File.separator + "MineFortress2"
    6. + File.separator + "pictures" + File.separator + "ctf.png"); //you may say, oh you were just looking for the MF2 plugin in the above code! I am using a universal folder for my plugins called MineFortress2, so that isn't the issue
    7.  
    8. try {
    9. image = ImageIO.read(icon);
    10. message = new NameTagMessage(image, 30, ImageChar.BLOCK.getChar());
    11. } catch (IOException e) {
    12. throw new RuntimeException("Cannot read image " + icon, e);
    13. }


    If anyone could help me out, that would be great!

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

    DevRosemberg

    ccrama Umm, why are you doing "MineFortress......." instead of just doing getDataFolder(); I'm not home right now so i can't help you much.
     
  29. Offline

    ccrama

    DevRosemberg MineFortress2 isn't my data folder, its a different folder in the root of my server. It has all my database files/images for easy access
     
  30. Offline

    DevRosemberg

Thread Status:
Not open for further replies.

Share This Page