package cn.citycraft.TellRaw; import static cn.citycraft.TellRaw.common.TextualComponent.rawText; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.bukkit.Achievement; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.command.CommandSender; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.utility.MinecraftReflection; import cn.citycraft.GsonAgent.GsonAgent; import cn.citycraft.GsonAgent.api.JsonArray; import cn.citycraft.GsonAgent.api.JsonElement; import cn.citycraft.GsonAgent.api.JsonObject; import cn.citycraft.GsonAgent.api.JsonParser; import cn.citycraft.GsonAgent.api.stream.JsonWriter; import cn.citycraft.TellRaw.common.ArrayWrapper; import cn.citycraft.TellRaw.common.JsonRepresentedObject; import cn.citycraft.TellRaw.common.JsonString; import cn.citycraft.TellRaw.common.MessagePart; import cn.citycraft.TellRaw.common.Reflection; import cn.citycraft.TellRaw.common.TextualComponent; import cn.citycraft.TellRaw.internal.FancyMessageInternal; import cn.citycraft.TellRaw.manual.FancyMessageManual; /** * 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()} or * {@link #then(Object)} 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 abstract class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable* 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 {@link FancyMessage} */ 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 {@link FancyMessage} */ public FancyMessage formattedTooltip(final FancyMessage... lines) { if (lines.length < 1) { onHover(null, null); // Clear tooltip return this; } final FancyMessage result = newFM(); 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); } /** * 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 {@link FancyMessage} */ public FancyMessage formattedTooltip(final Iterable* 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 {@link FancyMessage} */ public abstract FancyMessage itemTooltip(final ItemStack itemStack); /** * 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 {@link FancyMessage} */ public FancyMessage itemTooltip(final String itemJSON) { onHover("show_item", new JsonString(itemJSON)); return this; } /** * Internally called method. Not for API consumption. */ @Override public Iterator* 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 {@link FancyMessage} * @exception IllegalArgumentException * If the statistic requires a parameter which was not * supplied. */ public abstract FancyMessage statisticTooltip(final Statistic which); /** * 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 {@link FancyMessage} * @exception IllegalArgumentException * If the statistic requires a parameter which was not * supplied, or was supplied a parameter that was not * required. */ public abstract FancyMessage statisticTooltip(final Statistic which, final EntityType entity); /** * 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 {@link FancyMessage} * @exception IllegalArgumentException * If the statistic requires a parameter which was not * supplied, or was supplied a parameter that was not * required. */ public abstract FancyMessage statisticTooltip(final Statistic which, final Material item); /** * Sets the stylization of the current editing component. * * @param styles * The array of styles to apply to the editing component. * @return {@link FancyMessage} * @exception IllegalArgumentException * If any of the enumeration values in the array do not * represent formatters. */ public FancyMessage style(final ChatColor... styles) { for (final ChatColor style : styles) { if (!style.isFormat()) { throw new IllegalArgumentException(style.name() + " is not a style"); } } latest().styles.addAll(Arrays.asList(styles)); dirty = true; return this; } /** * Set the behavior of the current editing component to instruct the client * to replace the chat input box content with the specified string when the * currently edited part of the {@code FancyMessage} is clicked. * The client will not immediately send the command to the server to be * executed unless the client player submits the command/chat message, * usually with the enter key. * * @param command * The text to display in the chat bar of the client. * @return {@link FancyMessage} */ public FancyMessage suggest(final String command) { onClick("suggest_command", command); return this; } /** * 设置当前消息串的文本 * * @param text * 需要设置的文本 * @return {@link FancyMessage} */ public FancyMessage text(final String text) { final MessagePart latest = latest(); latest.text = rawText(text); dirty = true; return this; } /** * 设置当前消息串的文本 * * @param text * 需要设置的文本 * @return {@link FancyMessage} */ public FancyMessage text(final TextualComponent text) { final MessagePart latest = latest(); latest.text = text; dirty = true; return this; } /** * 停止编辑当前的消息串 并且开始一个新的消息串 * * 之后的操作将会应用于新的消息串上 * * @return {@link FancyMessage} */ public FancyMessage then() { if (!latest().hasText()) { throw new IllegalStateException("previous message part has no text"); } messageParts.add(new MessagePart()); dirty = true; return this; } /** * 停止编辑当前的消息串 并且开始一个新的消息串 * * 之后的操作将会应用于新的消息串上 * * @param text * 一个新的操作串需要的文本. * @return {@link FancyMessage} */ public FancyMessage then(final String text) { return then(rawText(text)); } /** * 停止编辑当前的消息串 并且开始一个新的消息串 * * 之后的操作将会应用于新的消息串上 * * @param text * 一个新的操作串. * @return {@link FancyMessage} */ public FancyMessage then(final TextualComponent text) { if (!latest().hasText()) { throw new IllegalStateException("previous message part has no text"); } messageParts.add(new MessagePart(text)); dirty = true; return this; } /** * 序列化当前对象, 使用 {@link JsonWriter} 转换为一个有效的Json串 * 当前Json串可用于类似 {@code /tellraw} 的命令上 * * @return 返回这个对象的Json序列化字符串. */ public String toJSONString() { if (!dirty && jsonString != null) { return jsonString; } final StringWriter string = new StringWriter(); final JsonWriter json = GsonAgent.newJsonWriter(string); try { writeJson(json); json.close(); } catch (final IOException e) { throw new RuntimeException("invalid message"); } jsonString = string.toString(); dirty = false; return jsonString; } /** * 将此消息转换为具有有限格式的人可读字符串。 * 此方法用于发送此消息给没有JSON格式支持客户端。 ** 序列化每个消息部分(每个部分都需要分别序列化): *
* 颜色和格式可以从返回的字符串中删除 通过{@link ChatColor#stripColor(String)}. *
* * @return 发送给老版本客户端以及控制台。 */ 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(); } /** * 设置当前的操作串 当鼠标悬浮时将会显示文本 * * 当前方法将不会继承本类的颜色样式等参数. * * * @param lines * 当鼠标悬浮时他将会显示 支持换行符 * @return {@link FancyMessage} */ public FancyMessage tooltip(final Iterable