package com.plotsquared.bukkit.chat; import static com.plotsquared.bukkit.chat.TextualComponent.rawText; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonWriter; import com.intellectualcrafters.configuration.serialization.ConfigurationSerializable; import com.intellectualcrafters.configuration.serialization.ConfigurationSerialization; import org.bukkit.Achievement; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.Statistic.Type; import org.bukkit.command.CommandSender; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; /** * Represents a formattable message. Such messages can use elements such as colors, formatting codes, hover and click data, and other features provided by the vanilla Minecraft JSON message formatter. * This class allows plugins to emulate the functionality of the vanilla Minecraft tellraw command. *
* This class follows the builder pattern, allowing for method chaining. * It is set up such that invocations of property-setting methods will affect the current editing component, * and a call to {@link #then(String)} or {@link #text(TextualComponent)} will append a new editing component to the end of the message, * optionally initializing it with text. Further property-setting method calls will affect that editing component. *
*/ public class FancyMessage implements JsonRepresentedObject, Cloneable, IterableTooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param name The name of the achievement to display, excluding the "achievement." prefix. * @return This builder instance. */ public FancyMessage achievementTooltip(final String name) { onHover("show_achievement", new JsonString("achievement." + name)); return this; } /** * Set the behavior of the current editing component to display information about an achievement when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param which The achievement to display. * @return This builder instance. */ public FancyMessage achievementTooltip(final Achievement which) { try { final Object achievement = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSAchievement", Achievement.class).invoke(null, which); return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Achievement"), "name").get(achievement)); } catch (final IllegalAccessException e) { Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); return this; } catch (final IllegalArgumentException e) { Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); return this; } catch (final InvocationTargetException e) { Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); return this; } } /** * Set the behavior of the current editing component to display information about a parameterless statistic when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param which The statistic to display. * @return This builder instance. * @exception IllegalArgumentException If the statistic requires a parameter which was not supplied. */ public FancyMessage statisticTooltip(final Statistic which) { final Type type = which.getType(); if (type != Type.UNTYPED) { throw new IllegalArgumentException("That statistic requires an additional " + type + " parameter!"); } try { final Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSStatistic", Statistic.class).invoke(null, which); return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic)); } catch (final IllegalAccessException e) { Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); return this; } catch (final IllegalArgumentException e) { Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); return this; } catch (final InvocationTargetException e) { Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); return this; } } /** * Set the behavior of the current editing component to display information about a statistic parameter with a material when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param which The statistic to display. * @param item The sole material parameter to the statistic. * @return This builder instance. * @exception IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required. */ public FancyMessage statisticTooltip(final Statistic which, final Material item) { final Type type = which.getType(); if (type == Type.UNTYPED) { throw new IllegalArgumentException("That statistic needs no additional parameter!"); } if (type == Type.BLOCK && item.isBlock() || type == Type.ENTITY) { throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!"); } try { final Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getMaterialStatistic", Statistic.class, Material.class).invoke(null, which, item); return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic)); } catch (final IllegalAccessException e) { Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); return this; } catch (final IllegalArgumentException e) { Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); return this; } catch (final InvocationTargetException e) { Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); return this; } } /** * Set the behavior of the current editing component to display information about a statistic parameter with an entity type when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param which The statistic to display. * @param entity The sole entity type parameter to the statistic. * @return This builder instance. * @exception IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required. */ public FancyMessage statisticTooltip(final Statistic which, final EntityType entity) { final Type type = which.getType(); if (type == Type.UNTYPED) { throw new IllegalArgumentException("That statistic needs no additional parameter!"); } if (type != Type.ENTITY) { throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!"); } try { final Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getEntityStatistic", Statistic.class, EntityType.class).invoke(null, which, entity); return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic)); } catch (final IllegalAccessException e) { Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e); return this; } catch (final IllegalArgumentException e) { Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e); return this; } catch (final InvocationTargetException e) { Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e); return this; } } /** * Set the behavior of the current editing component to display information about an item when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param itemJSON A string representing the JSON-serialized NBT data tag of an {@link ItemStack}. * @return This builder instance. */ public FancyMessage itemTooltip(final String itemJSON) { onHover("show_item", new JsonString(itemJSON)); // Seems a bit hacky, considering we have a JSON object as a parameter return this; } /** * Set the behavior of the current editing component to display information about an item when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param itemStack The stack for which to display information. * @return This builder instance. */ public FancyMessage itemTooltip(final ItemStack itemStack) { try { final Object nmsItem = Reflection.getMethod(Reflection.getOBCClass("inventory.CraftItemStack"), "asNMSCopy", ItemStack.class).invoke(null, itemStack); return itemTooltip(Reflection.getMethod(Reflection.getNMSClass("ItemStack"), "save", Reflection.getNMSClass("NBTTagCompound")) .invoke(nmsItem, Reflection.getNMSClass("NBTTagCompound").newInstance()).toString()); } catch (final Exception e) { e.printStackTrace(); return this; } } /** * Set the behavior of the current editing component to display raw text when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param text The text, which supports newlines, which will be displayed to the client upon hovering. * @return This builder instance. */ public FancyMessage tooltip(final String text) { onHover("show_text", new JsonString(text)); return this; } /** * Set the behavior of the current editing component to display raw text when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. * @return This builder instance. */ public FancyMessage tooltip(final IterableTooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param lines The lines of text which will be displayed to the client upon hovering. * @return This builder instance. */ public FancyMessage tooltip(final String... lines) { final StringBuilder builder = new StringBuilder(); for (int i = 0; i < lines.length; i++) { builder.append(lines[i]); if (i != lines.length - 1) { builder.append('\n'); } } tooltip(builder.toString()); return this; } /** * Set the behavior of the current editing component to display formatted text when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param text The formatted text which will be displayed to the client upon hovering. * @return This builder instance. */ public FancyMessage formattedTooltip(final FancyMessage text) { for (final MessagePart component : text.messageParts) { if (component.clickActionData != null && component.clickActionName != null) { throw new IllegalArgumentException("The tooltip text cannot have click data."); } else if (component.hoverActionData != null && component.hoverActionName != null) { throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); } } onHover("show_text", text); return this; } /** * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param lines The lines of formatted text which will be displayed to the client upon hovering. * @return This builder instance. */ public FancyMessage formattedTooltip(final FancyMessage... lines) { if (lines.length < 1) { onHover(null, null); // Clear tooltip return this; } final FancyMessage result = new FancyMessage(); result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object for (int i = 0; i < lines.length; i++) { try { for (final MessagePart component : lines[i]) { if (component.clickActionData != null && component.clickActionName != null) { throw new IllegalArgumentException("The tooltip text cannot have click data."); } else if (component.hoverActionData != null && component.hoverActionName != null) { throw new IllegalArgumentException("The tooltip text cannot have a tooltip."); } if (component.hasText()) { result.messageParts.add(component.clone()); } } if (i != lines.length - 1) { result.messageParts.add(new MessagePart(rawText("\n"))); } } catch (final CloneNotSupportedException e) { Bukkit.getLogger().log(Level.WARNING, "Failed to clone object", e); return this; } } return formattedTooltip(result.messageParts.isEmpty() ? null : result); // Throws NPE if size is 0, intended } /** * Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text. *Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.
* @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created. * @return This builder instance. */ public FancyMessage formattedTooltip(final Iterable* Serialization of this message by using this message will include (in this order for each message part): *
* Color and formatting can be removed from the returned string by using {@link ChatColor#stripColor(String)}.
* @return A human-readable string representing limited formatting in addition to the core text of this message. */ public String toOldMessageFormat() { final StringBuilder result = new StringBuilder(); for (final MessagePart part : this) { result.append(part.color == null ? "" : part.color); for (final ChatColor formatSpecifier : part.styles) { result.append(formatSpecifier); } result.append(part.text); } return result.toString(); } private MessagePart latest() { return messageParts.get(messageParts.size() - 1); } private void onClick(final String name, final String data) { final MessagePart latest = latest(); latest.clickActionName = name; latest.clickActionData = data; dirty = true; } private void onHover(final String name, final JsonRepresentedObject data) { final MessagePart latest = latest(); latest.hoverActionName = name; latest.hoverActionData = data; dirty = true; } // Doc copied from interface @Override public Map