Hi all, today we're going to be creating and sending text messages to players that have an item when you hover the text. I'm creating this tutorial because up until now I've only know how to create basic text messages through the ChatComponent API and item tooltips weren't very intuitive (so I resorted to 3rd party libraries like fanciful). This tutorial will explain and demonstrate (with code examples) what we need to do, and how to do it. The final result will look like this: The tutorial uses a ReflectionUtil class that you can find here: https://gist.github.com/sainttx/34fdd8fa7657024414ba Let's begin. So essentially our checklist to complete the above task is as follows (in no particular order): We required a structured JSON chat message to send to the client This JSON message needs a hoverEvent with show_item action If you're interested about the message structure, take a peak here: http://wiki.vg/Chat We required an item to be used in the hoverEvent of the message We require the item to be in a valid JSON string representation of itself We required an item We'll begin by actually getting the ItemStack that we want to use with as the hover event. You should all be familiar with ItemStack creation and ItemMeta, here's the helper method we're using to get our item: Code (Java): /* * Build our demonstration item */ private ItemStack getExampleItemStack() { ItemStack itemStack = new ItemStack(Material.DIAMOND_SWORD); ItemMeta meta = itemStack.getItemMeta(); List<String> lore = new ArrayList<String>(); // Let's give this diamond sword a cool name and some lore meta.setDisplayName(ChatColor.YELLOW + "Super Sword"); lore.add(ChatColor.RED + "This is a great sword!"); meta.setLore(lore); // ...and some powerful enchantments meta.addEnchant(Enchantment.DAMAGE_ALL, 5, true); meta.addEnchant(Enchantment.FIRE_ASPECT, 2, true); itemStack.setItemMeta(meta); return itemStack; } This is just a regular diamond sword with a display name, a string of lore, and some enchantment. Nothing spectacular or overly complicated. We require the item to be in valid JSON How am I supposed to parse the item into a valid JSON String? The most reliable way to serialize the item into JSON is through the use of NMS. Here is a breakdown of the steps we must take to successfully do this: Convert the org.bukkit.inventory.ItemStack object into a net.minecraft.server.ItemStack object Use the net.minecraft.server.ItemStack#save(NBTTagCompound) method on an empty NBTTagCompound object to save the items data into the object Convert the NBTTagCompound into a String using Object#toString. This String is our valid JSON representation of the ItemStack First I'm going to show you how to accomplish this through the use of raw NMS, and then afterward we can break down the process and do it through the use of Reflection. I highly doubt that the method/class names will change, so the use of Reflection will likely give you long time use where you won't have to update your NMS pathways. Convert the org.bukkit.inventory.ItemStack object into a net.minecraft.server.ItemStack object This is done through the use of static method CraftItemStack#asNMSCopy(ItemStack), essentially: Code (Java): net.minecraft.server.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); and through the use of Reflection: Code (Java): // ItemStack methods to get a net.minecraft.server.ItemStack object for serialization Class<?> craftItemStackClazz = ReflectionUtil.getOBCClass("inventory.CraftItemStack"); Method asNMSCopyMethod = ReflectionUtil.getMethod(craftItemStackClazz, "asNMSCopy", ItemStack.class); Object nmsItemStackObj = asNMSCopyMethod.invoke(null, itemStack); Use the net.minecraft.server.ItemStack#save(NBTTagCompound) method on an empty NBTTagCompound object to save the items data into the object So to do this, all we need to do is create a new NBTTagCompound object and invoke the save method on it. Simple. Code (Java): net.minecraft.server.NBTTagCompound compound = new NBTTagCompound(); compound = nmsItemStack.save(compound); A little less simple with Reflection. Code (Java): // NMS Method to serialize a net.minecraft.server.ItemStack to a valid Json string Class<?> nmsItemStackClazz = ReflectionUtil.getNMSClass("ItemStack"); Class<?> nbtTagCompoundClazz = ReflectionUtil.getNMSClass("NBTTagCompound"); Method saveNmsItemStackMethod = ReflectionUtil.getMethod(nmsItemStackClazz, "save", nbtTagCompoundClazz); Object nmsNbtTagCompoundObj; // This will just be an empty NBTTagCompound instance to invoke the saveNms method Object itemAsJsonObject; // This is the net.minecraft.server.ItemStack after being put through saveNmsItem method nmsNbtTagCompoundObj = nbtTagCompoundClazz.newInstance(); // Create the instance itemAsJsonObject = saveNmsItemStackMethod.invoke(nmsItemStackObj, nmsNbtTagCompoundObj); ... but not really that bad. Convert the NBTTagCompound into a String using Object#toString. This String is our valid JSON representation of the ItemStack This shouldn't really be a section, but all we do is take our net.minecraft.server.NBTTagCompound compound/Object itemAsJsonObject objects from step 2 and run them toString() on them. Code (Java): String json = compound.toString(); // standard object String json = itemAsJsonObject.toString(); // reflection object We'll put both of these into helper methods so that we can reuse the code easily for different items. Here are the results: Code (Java): /** * Converts an {@link org.bukkit.inventory.ItemStack} to a Json string * for sending with {@link net.md_5.bungee.api.chat.BaseComponent}'s. * * @param itemStack the item to convert * @return the Json string representation of the item */ public String convertItemStackToJsonRegular(ItemStack itemStack) { // First we convert the item stack into an NMS itemstack net.minecraft.server.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); net.minecraft.server.NBTTagCompound compound = new NBTTagCompound(); compound = nmsItemStack.save(compound); return compound.toString(); } Code (Java): /** * Converts an {@link org.bukkit.inventory.ItemStack} to a Json string * for sending with {@link net.md_5.bungee.api.chat.BaseComponent}'s. * * @param itemStack the item to convert * @return the Json string representation of the item */ public String convertItemStackToJson(ItemStack itemStack) { // ItemStack methods to get a net.minecraft.server.ItemStack object for serialization Class<?> craftItemStackClazz = ReflectionUtil.getOBCClass("inventory.CraftItemStack"); Method asNMSCopyMethod = ReflectionUtil.getMethod(craftItemStackClazz, "asNMSCopy", ItemStack.class); // NMS Method to serialize a net.minecraft.server.ItemStack to a valid Json string Class<?> nmsItemStackClazz = ReflectionUtil.getNMSClass("ItemStack"); Class<?> nbtTagCompoundClazz = ReflectionUtil.getNMSClass("NBTTagCompound"); Method saveNmsItemStackMethod = ReflectionUtil.getMethod(nmsItemStackClazz, "save", nbtTagCompoundClazz); Object nmsNbtTagCompoundObj; // This will just be an empty NBTTagCompound instance to invoke the saveNms method Object nmsItemStackObj; // This is the net.minecraft.server.ItemStack object received from the asNMSCopy method Object itemAsJsonObject; // This is the net.minecraft.server.ItemStack after being put through saveNmsItem method try { nmsNbtTagCompoundObj = nbtTagCompoundClazz.newInstance(); nmsItemStackObj = asNMSCopyMethod.invoke(null, itemStack); itemAsJsonObject = saveNmsItemStackMethod.invoke(nmsItemStackObj, nmsNbtTagCompoundObj); } catch (Throwable t) { Bukkit.getLogger().log(Level.SEVERE, "failed to serialize itemstack to nms item", t); return null; } // Return a string representation of the serialized object return itemAsJsonObject.toString(); } (the first is our standard, the second reflection) We required a structured JSON chat message Great! We have our valid Json representation of the ItemStack! Now all that's left is creating the chat message. Fortunately, the ChatComponent API takes care of all of this for us and all we have to do is figure out how to create the HoverEvents and TextComponents to send. A breakdown of what we do with the ChatComponent API: Create a TextComponent with the Json representation of the item Put the TextComponent object into a BaseComponent array (because HoverEvent's constructor requires the array for some reason) Create a new TextComponent for our actual text that the player will see Set the hover event for the newly created TextComponent Send the TextComponent to the player And in code, our helper method looks like this: Code (Java): /** * Sends a message to a player with an item as it's tooltip * * @param player the player * @param message the message to send * @param item the item to display in the tooltip */ public void sendItemTooltipMessage(Player player, String message, ItemStack item) { String itemJson = convertItemStackToJson(item); // Prepare a BaseComponent array with the itemJson as a text component BaseComponent[] hoverEventComponents = new BaseComponent[]{ new TextComponent(itemJson) // The only element of the hover events basecomponents is the item json }; // Create the hover event HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_ITEM, hoverEventComponents); /* And now we create the text component (this is the actual text that the player sees) * and set it's hover event to the item event */ TextComponent component = new TextComponent(message); component.setHoverEvent(event); // Finally, send the message to the player player.spigot().sendMessage(component); } So now we're basically done. We can use this in commands, util methods, or whatever you want. Here's a final example of usage in a command so that we actually see what we've done in-game: Code (Java): @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if (!(sender instanceof Player)) { sender.sendMessage("Only players can view item tooltips!"); } else { Player player = (Player) sender; ItemStack tooltipItem = getExampleItemStack(); this.sendItemTooltipMessage(player, ChatColor.GREEN + "Hover over me to see a diamond sword!", tooltipItem); } return true; }
So... Why not just build the JSON ourselves? Without using reflection or nms, we can still get about everything but the amount of nbt tags.
Is there really much benefit in doing so? Sure, if you want you can and at that point you can just replace the second part of the tutorial (serializing the ItemStack) with your own JSON method. However I really don't see the point in going through the trouble to parse the ItemStack yourself when the methods referenced (CraftItemStack#asNMSCopy, net.minecraft.server.ItemStack#save(NBTTagCompound)) have been around for a fairly long time, are unlikely to change, and do everything for you including NBT tags and attributes.
lol I was trying to do this long time ago but the problem is I try doing player.spigot().whatever but it never works .spigot() doesn't work for me, I imported spigot and everything required but still, I don't know whats the problem, can you help me
How can you add raw text(without hover event) right after a hover event text?? The raw text will still have the hover if I use hover_text.addExtra(raw_text) Code (Text): net.minecraft.server.v1_12_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); NBTTagCompound compound = new NBTTagCompound(); nmsItemStack.save(compound); String json = compound.toString(); BaseComponent[] hoverEventComponents = new BaseComponent[]{ new TextComponent(json) // The only element of the hover events basecomponents is the item json }; HoverEvent hover_event = new HoverEvent(HoverEvent.Action.SHOW_ITEM, hoverEventComponents); TextComponent component = new TextComponent("[" + ChatColor.GRAY + name + ChatColor.RESET + " x " + ChatColor.YELLOW + item.getAmount() + ChatColor.RESET + "]"); component.setHoverEvent(hover_event); TextComponent component2 = new TextComponent(); component2.setText("raw text here"); component2.setHoverEvent(null); component.addExtra(component2); for (Player recipients : event.getRecipients()) { recipients.spigot().sendMessage(component); } ps. sorry by not using reflection, will correct it after I did the text connection