package com.supaham.commons.bukkit.text; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.gson.stream.JsonWriter; import com.supaham.commons.bukkit.utils.ReflectionUtils; import org.bukkit.Achievement; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Part of a message belonging to a {@link FancyMessage}. * * @since 0.2.4 */ public class MessagePart implements Cloneable { ChatColor color = null; ArrayList<ChatColor> styles = new ArrayList<ChatColor>(); String clickEvent = null, clickEventData = null, hoverEvent = null, hoverEventData = null; String text = ""; protected static Class<?> nmsTagCompound = ReflectionUtils.getNMSClass("NBTTagCompound"); protected static Class<?> nmsAchievement = ReflectionUtils.getNMSClass("Achievement"); protected static Class<?> nmsStatistic = ReflectionUtils.getNMSClass("Statistic"); protected static Class<?> nmsItemStack = ReflectionUtils.getNMSClass("ItemStack"); protected static Class<?> obcStatistic = ReflectionUtils.getOBCClass("CraftStatistic"); protected static Class<?> obcItemStack = ReflectionUtils.getOBCClass("inventory.CraftItemStack"); public MessagePart() { } public MessagePart(final String text) { this.text = text; } @Override protected MessagePart clone() { try { super.clone(); MessagePart part = new MessagePart(); part.color = this.color; part.styles = this.styles; part.clickEvent = this.clickEvent; part.clickEventData = this.clickEventData; part.hoverEvent = this.hoverEvent; part.hoverEventData = this.hoverEventData; part.text = this.text; return part; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } /** * Sets the text of this {@link MessagePart}. * * @param text text to set * * @return the previous text, nullable */ public String text(String text) { String prev = this.text; this.text = text; return prev; } /** * Sets the {@link ChatColor} of this MessagePart. * * @param color color to set * * @return the previous color, nullable * * @throws IllegalArgumentException thrown if {@code color} is not a color */ public ChatColor color(final ChatColor color) throws IllegalArgumentException { if (!color.equals(ChatColor.RESET) && !color.isColor()) { throw new IllegalArgumentException(color.name() + " is not a color"); } ChatColor prev = this.color; this.color = color; return prev; } /** * Applies {@link ChatColor} styles to this MessagePart. * * @param styles array of styles to apply * * @return the previous styles * * @throws IllegalArgumentException thrown if {@code styles} contains a color */ public List<ChatColor> style(ChatColor... styles) throws IllegalArgumentException { boolean reset = false; for (final ChatColor style : styles) { if (style.equals(ChatColor.RESET)) { reset = true; break; } if (!style.isFormat()) { throw new IllegalArgumentException(style.name() + " is not a style"); } } List<ChatColor> prev = new ArrayList<ChatColor>(this.styles); if (reset) { this.styles.add(ChatColor.RESET); } else { this.styles.addAll(Arrays.asList(styles)); } return prev; } /** * Opens a file (client-side) on click event. * * @param path the path of the file to open */ public void file(final String path) { onClick("open_file", path); } /** * Opens a URL on click event. * * @param url url to open */ public void link(final String url) { onClick("open_url", url); } /** * Suggests a command on click event. This opens the player's chat box and inserts the given * String. * * @param command command to suggest */ public void suggest(final String command) { onClick("suggest_command", command); } /** * Executes a command on click event. It should be noted that the commands executed are run by the * player, * thus appearing in their recent chat history. * * @param command command to execute */ public void command(final String command) { onClick("run_command", command); } /** * Displays an achievement on hover event. * * @param name name of the achievement to display */ public void achievementTooltip(final String name) { onHover("show_achievement", "achievement." + name); } /** * Displays an {@link Achievement} on hover event. * * @param which achievement to display */ public void achievementTooltip(final Achievement which) { try { Object achievement = ReflectionUtils.getMethod(obcStatistic, "getNMSAchievement").invoke(null, which); achievementTooltip((String) ReflectionUtils.getField(nmsAchievement, "name") .get(achievement)); } catch (Exception e) { e.printStackTrace(); } } /** * Displays a statistic on hover event. * * @param which statistic to display */ public void statisticTooltip(final Statistic which) { Statistic.Type type = which.getType(); if (type != Statistic.Type.UNTYPED) { throw new IllegalArgumentException( "That statistic requires an additional " + type + " parameter!"); } try { Object statistic = ReflectionUtils.getMethod(obcStatistic, "getNMSStatistic").invoke(null, which); achievementTooltip((String) ReflectionUtils.getField(nmsStatistic, "name") .get(statistic)); } catch (Exception e) { e.printStackTrace(); } } /** * Displays a statistic that requires a {@link Material} parameter on hover event. * * @param statistic statistic to display * @param material material to pass to the {@code statistic} */ public void statisticTooltip(final Statistic statistic, Material material) { Statistic.Type type = statistic.getType(); if (type == Statistic.Type.UNTYPED) { throw new IllegalArgumentException("That statistic needs no additional parameter!"); } if ((type == Statistic.Type.BLOCK && material.isBlock()) || type == Statistic.Type.ENTITY) { throw new IllegalArgumentException( "Wrong parameter type for that statistic - needs " + type + "!"); } try { Object obcStatistic = ReflectionUtils.getMethod(MessagePart.obcStatistic, "getMaterialStatistic") .invoke(null, statistic, material); achievementTooltip( (String) ReflectionUtils.getField(nmsStatistic, "name").get(obcStatistic)); } catch (Exception e) { e.printStackTrace(); } } /** * Displays a statistic that requires a {@link EntityType} parameter on hover event. * * @param statistic statistic to display * @param entity entity to pass to the {@code statistic} */ public void statisticTooltip(final Statistic statistic, EntityType entity) { Statistic.Type type = statistic.getType(); if (type == Statistic.Type.UNTYPED) { throw new IllegalArgumentException("That statistic needs no additional parameter!"); } if (type != Statistic.Type.ENTITY) { throw new IllegalArgumentException( "Wrong parameter type for that statistic - needs " + type + "!"); } try { Object obcStatistic = ReflectionUtils.getMethod(MessagePart.obcStatistic, "getEntityStatistic") .invoke(null, statistic, entity); achievementTooltip( (String) ReflectionUtils.getField(nmsStatistic, "name").get(obcStatistic)); } catch (Exception e) { e.printStackTrace(); } } /** * Displays an Item written in json on hover event. * * @param itemJSON item's json to display * * @return this instance of FancyMessage, for chaining. */ public void itemTooltip(final String itemJSON) { onHover("show_item", itemJSON); } /** * Displays an {@link ItemStack} on hover event. * * @param itemStack * * @return this instance of FancyMessage, for chaining. */ public void itemTooltip(final ItemStack itemStack) { try { Object nmsItem = ReflectionUtils.getMethod(obcItemStack, "asNMSCopy", ItemStack.class) .invoke(null, itemStack); itemTooltip( ReflectionUtils.getMethod(nmsItemStack, "save") .invoke(nmsItem, nmsTagCompound.newInstance()) .toString() ); } catch (Exception e) { e.printStackTrace(); } } /** * Displays a tooltip on hover event. * * @param text text to display */ public void tooltip(final String text) { tooltip(text.split("\\n")); } /** * Displays a list of lines on hover event. * * @param lines lines to display */ public void tooltip(final List<String> lines) { tooltip(lines.toArray(new String[lines.size()])); } /** * Displays a list of lines on hover event. * * @param lines array of String to set */ public void tooltip(final String... lines) { onHover("show_text", Joiner.on("\n").skipNulls().join(lines)); } /** * Creates a click event. * * @param name name of the event * @param data data to pass to the event */ public void onClick(final String name, final String data) { Preconditions.checkNotNull(name, "click event name cannot be null."); Preconditions.checkNotNull(data, "click event data cannot be null."); clickEvent = name; clickEventData = data; } /** * Creates a hover event. * * @param name name of the event * @param data data to pass to the event */ public void onHover(final String name, final String data) { Preconditions.checkNotNull(name, "hover event name cannot be null."); Preconditions.checkNotNull(data, "hover event data cannot be null."); hoverEvent = name; hoverEventData = data; } protected String makeMultilineTooltip(final String[] lines) { StringWriter string = new StringWriter(); JsonWriter json = new JsonWriter(string); try { json.beginObject().name("id").value(1); json.name("tag").beginObject().name("display").beginObject(); json.name("Name").value("\\u00A7f" + lines[0].replace("\"", "\\\"")); json.name("Lore").beginArray(); for (int i = 1; i < lines.length; i++) { final String line = lines[i]; json.value(line.isEmpty() ? " " : line.replace("\"", "\\\"")); } json.endArray().endObject().endObject().endObject(); json.close(); } catch (Exception e) { throw new RuntimeException("invalid tooltip", e); } return string.toString(); } JsonWriter writeJson(JsonWriter json) { try { json.beginObject().name("text").value(text); if (color != null) { json.name("color").value(color.name().toLowerCase()); } for (final ChatColor style : styles) { String styleName; switch (style) { case MAGIC: styleName = "obfuscated"; break; case UNDERLINE: styleName = "underlined"; break; default: styleName = style.name().toLowerCase(); break; } json.name(styleName).value(true); } if (clickEvent != null && clickEventData != null) { json.name("clickEvent") .beginObject() .name("action").value(clickEvent) .name("value").value(clickEventData) .endObject(); } if (hoverEvent != null && hoverEventData != null) { json.name("hoverEvent") .beginObject() .name("action").value(hoverEvent) .name("value").value(hoverEventData) .endObject(); } return json.endObject(); } catch (Exception e) { e.printStackTrace(); return json; } } /** * Converts this {@link MessagePart} to a JSON String. * * @return JSON of this MessagePart */ public String toJSONString() { StringWriter json = new StringWriter(); writeJson(new JsonWriter(json)); return json.toString(); } /** * Checks whether this {@link MessagePart} has text. * * @return whether this MessagePart has text */ public boolean hasText() { return !text.isEmpty(); } /** * Sets this {@link MessagePart}'s text. * * @return this MessagePart's text */ public String getText() { return text; } /** * Gets this {@link MessagePart}'s text. * * @param text text to set */ public void setText(String text) { this.text = text; } /** * Gets this {@link MessagePart}'s hover event name. * * @return hover event name, nullable */ public String getHoverEvent() { return hoverEvent; } /** * Sets this {@link MessagePart}'s hover event name. * * @param hoverEvent name of the hover event to set */ public void setHoverEvent(String hoverEvent) { this.hoverEvent = hoverEvent; } /** * Gets this {@link MessagePart}'s hover event data. * * @return hover event data, nullable */ public String getHoverEventData() { return hoverEventData; } /** * Sets this {@link MessagePart}'s hover event data. * * @param hoverEventData hover event data to set */ public void setHoverEventData(String hoverEventData) { this.hoverEventData = hoverEventData; } /** * Gets this {@link MessagePart}'s click event name. * * @return click event name, nullable */ public String getClickEvent() { return clickEvent; } /** * Sets this {@link MessagePart}'s click event name. * * @param clickEvent click event name to set */ public void setClickEvent(String clickEvent) { this.clickEvent = clickEvent; } /** * Gets this {@link MessagePart}'s hover event data. * * @return click event data, nullable */ public String getClickEventData() { return clickEventData; } /** * Sets this {@link MessagePart}'s click event data. * * @param clickEventData click event data to set */ public void setClickEventData(String clickEventData) { this.clickEventData = clickEventData; } /** * Gets this {@link MessagePart}'s chat styles. * * @return */ public ArrayList<ChatColor> getStyles() { return styles; } /** * Sets this {@link MessagePart}'s chat styles. * * @param styles */ public void setStyles(ArrayList<ChatColor> styles) { this.styles = styles; } /** * Gets this {@link MessagePart}'s chat color. * * @return */ public ChatColor getColor() { return color; } /** * Sets this {@link MessagePart}'s chat color. * * @param color */ public void setColor(ChatColor color) { this.color = color; } public void applyClickEvent(FancyMessage fancyMessage) { fancyMessage.onClick(getClickEvent(), getClickEventData()); } public void applyHoverEvent(FancyMessage fancyMessage) { fancyMessage.onHover(getHoverEvent(), getHoverEventData()); } public Object getNMSChatObject() { try { return ReflectionUtils.getMethod(FancyMessage.nmsChatSerializer, "a", String.class) .invoke(null, toJSONString()); } catch (Exception e) { e.printStackTrace(); return null; } } }