2015-07-31 03:24:01 +10:00
|
|
|
package com.plotsquared.bukkit.chat;
|
2015-06-24 07:44:44 +10:00
|
|
|
|
2015-07-31 03:24:01 +10:00
|
|
|
import static com.plotsquared.bukkit.chat.TextualComponent.rawText;
|
2015-07-31 00:25:16 +10:00
|
|
|
|
2016-02-19 11:55:00 -05:00
|
|
|
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;
|
|
|
|
|
2015-07-31 00:25:16 +10:00
|
|
|
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;
|
2016-01-11 07:00:56 +11:00
|
|
|
import java.util.Collections;
|
2015-07-31 00:25:16 +10:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.logging.Level;
|
|
|
|
|
2015-06-24 07:44:44 +10:00
|
|
|
/**
|
|
|
|
* 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 <a href="http://minecraft.gamepedia.com/Tellraw#Raw_JSON_Text">JSON message formatter</a>.
|
|
|
|
* This class allows plugins to emulate the functionality of the vanilla Minecraft <a href="http://minecraft.gamepedia.com/Commands#tellraw">tellraw command</a>.
|
|
|
|
* <p>
|
|
|
|
* 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,
|
2015-12-19 12:23:32 -05:00
|
|
|
* and a call to {@link #then(String)} or {@link #text(TextualComponent)} will append a new editing component to the end of the message,
|
2015-06-24 07:44:44 +10:00
|
|
|
* optionally initializing it with text. Further property-setting method calls will affect that editing component.
|
|
|
|
* </p>
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<MessagePart>, ConfigurationSerializable {
|
2016-03-20 22:52:16 -04:00
|
|
|
|
|
|
|
private static final JsonParser _stringParser = new JsonParser();
|
|
|
|
private static Constructor<?> nmsPacketPlayOutChatConstructor;
|
|
|
|
// The ChatSerializer's instance of Gson
|
|
|
|
private static Object nmsChatSerializerGsonInstance;
|
|
|
|
private static Method fromJsonMethod;
|
2015-09-13 14:04:31 +10:00
|
|
|
|
|
|
|
static {
|
2015-09-11 20:09:22 +10:00
|
|
|
ConfigurationSerialization.registerClass(FancyMessage.class);
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
private List<MessagePart> messageParts;
|
|
|
|
private String jsonString;
|
|
|
|
private boolean dirty;
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Creates a JSON message with text.
|
|
|
|
* @param firstPartText The existing text in the message.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage(final String firstPartText) {
|
2015-09-11 20:09:22 +10:00
|
|
|
this(rawText(firstPartText));
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
|
|
|
public FancyMessage(final TextualComponent firstPartText) {
|
2016-01-11 07:00:56 +11:00
|
|
|
messageParts = new ArrayList<>();
|
2015-09-11 20:09:22 +10:00
|
|
|
messageParts.add(new MessagePart(firstPartText));
|
|
|
|
jsonString = null;
|
|
|
|
dirty = false;
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-13 14:04:31 +10:00
|
|
|
if (nmsPacketPlayOutChatConstructor == null) {
|
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
|
|
|
|
nmsPacketPlayOutChatConstructor.setAccessible(true);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final NoSuchMethodException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.SEVERE, "Could not find Minecraft method or constructor.", e);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final SecurityException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not access constructor.", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Creates a JSON message without text.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage() {
|
2015-09-11 20:09:22 +10:00
|
|
|
this((TextualComponent) null);
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2016-03-20 22:52:16 -04:00
|
|
|
/**
|
|
|
|
* Deserialize a JSON-represented message from a mapping of key-value pairs.
|
|
|
|
* This is called by the Bukkit serialization API.
|
|
|
|
* It is not intended for direct public API consumption.
|
|
|
|
* @param serialized The key-value mapping which represents a fancy message.
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public static FancyMessage deserialize(final Map<String, Object> serialized) {
|
|
|
|
final FancyMessage msg = new FancyMessage();
|
|
|
|
msg.messageParts = (List<MessagePart>) serialized.get("messageParts");
|
|
|
|
msg.jsonString = serialized.containsKey("JSON") ? serialized.get("JSON").toString() : null;
|
|
|
|
msg.dirty = !serialized.containsKey("JSON");
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deserialize a fancy message from its JSON representation. This JSON representation is of the format of
|
|
|
|
* that returned by {@link #toJSONString()}, and is compatible with vanilla inputs.
|
|
|
|
* @param json The JSON string which represents a fancy message.
|
|
|
|
* @return A {@code FancyMessage} representing the parametrized JSON message.
|
|
|
|
*/
|
|
|
|
public static FancyMessage deserialize(final String json) {
|
|
|
|
final JsonObject serialized = _stringParser.parse(json).getAsJsonObject();
|
|
|
|
final JsonArray extra = serialized.getAsJsonArray("extra"); // Get the extra component
|
|
|
|
final FancyMessage returnVal = new FancyMessage();
|
|
|
|
returnVal.messageParts.clear();
|
|
|
|
for (final JsonElement mPrt : extra) {
|
|
|
|
final MessagePart component = new MessagePart();
|
|
|
|
final JsonObject messagePart = mPrt.getAsJsonObject();
|
|
|
|
for (final Map.Entry<String, JsonElement> entry : messagePart.entrySet()) {
|
|
|
|
// Deserialize text
|
|
|
|
if (TextualComponent.isTextKey(entry.getKey())) {
|
|
|
|
// The map mimics the YAML serialization, which has a "key" field and one or more "value" fields
|
|
|
|
final Map<String, Object> serializedMapForm = new HashMap<>(); // Must be object due to Bukkit serializer API compliance
|
|
|
|
serializedMapForm.put("key", entry.getKey());
|
|
|
|
if (entry.getValue().isJsonPrimitive()) {
|
|
|
|
// Assume string
|
|
|
|
serializedMapForm.put("value", entry.getValue().getAsString());
|
|
|
|
} else {
|
|
|
|
// Composite object, but we assume each element is a string
|
|
|
|
for (final Map.Entry<String, JsonElement> compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) {
|
|
|
|
serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
component.text = TextualComponent.deserialize(serializedMapForm);
|
|
|
|
} else if (MessagePart.stylesToNames.inverse().containsKey(entry.getKey())) {
|
|
|
|
if (entry.getValue().getAsBoolean()) {
|
|
|
|
component.styles.add(MessagePart.stylesToNames.inverse().get(entry.getKey()));
|
|
|
|
}
|
|
|
|
} else if (entry.getKey().equals("color")) {
|
|
|
|
component.color = ChatColor.valueOf(entry.getValue().getAsString().toUpperCase());
|
|
|
|
} else if (entry.getKey().equals("clickEvent")) {
|
|
|
|
final JsonObject object = entry.getValue().getAsJsonObject();
|
|
|
|
component.clickActionName = object.get("action").getAsString();
|
|
|
|
component.clickActionData = object.get("value").getAsString();
|
|
|
|
} else if (entry.getKey().equals("hoverEvent")) {
|
|
|
|
final JsonObject object = entry.getValue().getAsJsonObject();
|
|
|
|
component.hoverActionName = object.get("action").getAsString();
|
|
|
|
if (object.get("value").isJsonPrimitive()) {
|
|
|
|
// Assume string
|
|
|
|
component.hoverActionData = new JsonString(object.get("value").getAsString());
|
|
|
|
} else {
|
|
|
|
// Assume composite type
|
|
|
|
// The only composite type we currently store is another FancyMessage
|
|
|
|
// Therefore, recursion time!
|
|
|
|
component.hoverActionData =
|
|
|
|
deserialize(object.get("value").toString() /* This should properly serialize the JSON object as a JSON string */);
|
|
|
|
}
|
|
|
|
} else if (entry.getKey().equals("insertion")) {
|
|
|
|
component.insertionData = entry.getValue().getAsString();
|
|
|
|
} else if (entry.getKey().equals("with")) {
|
|
|
|
for (final JsonElement object : entry.getValue().getAsJsonArray()) {
|
|
|
|
if (object.isJsonPrimitive()) {
|
|
|
|
component.translationReplacements.add(new JsonString(object.getAsString()));
|
|
|
|
} else {
|
|
|
|
// Only composite type stored in this array is - again - FancyMessages
|
|
|
|
// Recurse within this function to parse this as a translation replacement
|
|
|
|
component.translationReplacements.add(deserialize(object.toString()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
returnVal.messageParts.add(component);
|
|
|
|
}
|
|
|
|
return returnVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public FancyMessage clone() throws CloneNotSupportedException {
|
|
|
|
final FancyMessage instance = (FancyMessage) super.clone();
|
|
|
|
instance.messageParts = new ArrayList<>(messageParts.size());
|
|
|
|
for (int i = 0; i < messageParts.size(); i++) {
|
|
|
|
instance.messageParts.add(i, messageParts.get(i).clone());
|
|
|
|
}
|
|
|
|
instance.dirty = false;
|
|
|
|
instance.jsonString = null;
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sets the text of the current editing component to a value.
|
|
|
|
* @param text The new text of the current editing component.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage text(final String text) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final MessagePart latest = latest();
|
|
|
|
latest.text = rawText(text);
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sets the text of the current editing component to a value.
|
|
|
|
* @param text The new text of the current editing component.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage text(final TextualComponent text) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final MessagePart latest = latest();
|
|
|
|
latest.text = text;
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sets the color of the current editing component to a value.
|
|
|
|
* @param color The new color of the current editing component.
|
|
|
|
* @return This builder instance.
|
|
|
|
* @exception IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value).
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage color(final ChatColor color) {
|
2015-09-11 20:09:22 +10:00
|
|
|
latest().color = color;
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sets the stylization of the current editing component.
|
|
|
|
* @param styles The array of styles to apply to the editing component.
|
|
|
|
* @return This builder instance.
|
|
|
|
* @exception IllegalArgumentException If any of the enumeration values in the array do not represent formatters.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage style(final ChatColor... styles) {
|
|
|
|
for (final ChatColor style : styles) {
|
|
|
|
if (!style.isFormat()) {
|
|
|
|
throw new IllegalArgumentException(style.name() + " is not a style");
|
|
|
|
}
|
2015-09-11 20:09:22 +10:00
|
|
|
}
|
|
|
|
latest().styles.addAll(Arrays.asList(styles));
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to instruct the client to open a file on the client side filesystem when the currently edited part of the {@code FancyMessage} is clicked.
|
|
|
|
* @param path The path of the file on the client filesystem.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage file(final String path) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onClick("open_file", path);
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to instruct the client to open a webpage in the client's web browser when the currently edited part of the {@code FancyMessage} is clicked.
|
|
|
|
* @param url The URL of the page to open when the link is clicked.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage link(final String url) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onClick("open_url", url);
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* 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 This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage suggest(final String command) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onClick("suggest_command", command);
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to instruct the client to append the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is SHIFT-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 append to the chat bar of the client.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage insert(final String command) {
|
2015-09-11 20:09:22 +10:00
|
|
|
latest().insertionData = command;
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to instruct the client to send the specified string to the server as a chat message when the currently edited part of the {@code FancyMessage} is clicked.
|
|
|
|
* The client <b>will</b> immediately send the command to the server to be executed when the editing component is clicked.
|
|
|
|
* @param command The text to display in the chat bar of the client.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage command(final String command) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onClick("run_command", command);
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display information about an achievement when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param name The name of the achievement to display, excluding the "achievement." prefix.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage achievementTooltip(final String name) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onHover("show_achievement", new JsonString("achievement." + name));
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display information about an achievement when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param which The achievement to display.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage achievementTooltip(final Achievement which) {
|
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
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));
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalAccessException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalArgumentException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final InvocationTargetException e) {
|
2016-02-04 18:45:50 -05:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
|
2015-09-11 20:09:22 +10:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display information about a parameterless statistic when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param which The statistic to display.
|
|
|
|
* @return This builder instance.
|
|
|
|
* @exception IllegalArgumentException If the statistic requires a parameter which was not supplied.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage statisticTooltip(final Statistic which) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final Type type = which.getType();
|
2015-09-13 14:04:31 +10:00
|
|
|
if (type != Type.UNTYPED) {
|
|
|
|
throw new IllegalArgumentException("That statistic requires an additional " + type + " parameter!");
|
|
|
|
}
|
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
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));
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalAccessException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalArgumentException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final InvocationTargetException e) {
|
2016-02-04 18:45:50 -05:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
|
2015-09-11 20:09:22 +10:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @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.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage statisticTooltip(final Statistic which, final Material item) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final Type type = which.getType();
|
2015-09-13 14:04:31 +10:00
|
|
|
if (type == Type.UNTYPED) {
|
|
|
|
throw new IllegalArgumentException("That statistic needs no additional parameter!");
|
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
if (type == Type.BLOCK && item.isBlock() || type == Type.ENTITY) {
|
2015-09-13 14:04:31 +10:00
|
|
|
throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
|
|
|
|
}
|
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
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));
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalAccessException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalArgumentException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final InvocationTargetException e) {
|
2016-02-04 18:45:50 -05:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
|
2015-09-11 20:09:22 +10:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @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.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage statisticTooltip(final Statistic which, final EntityType entity) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final Type type = which.getType();
|
2015-09-13 14:04:31 +10:00
|
|
|
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 {
|
2015-09-11 20:09:22 +10:00
|
|
|
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));
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalAccessException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalArgumentException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
|
|
|
return this;
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final InvocationTargetException e) {
|
2016-02-04 18:45:50 -05:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
|
2015-09-11 20:09:22 +10:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display information about an item when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param itemJSON A string representing the JSON-serialized NBT data tag of an {@link ItemStack}.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage itemTooltip(final String itemJSON) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onHover("show_item", new JsonString(itemJSON)); // Seems a bit hacky, considering we have a JSON object as a parameter
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display information about an item when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param itemStack The stack for which to display information.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage itemTooltip(final ItemStack itemStack) {
|
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
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());
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final Exception e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
e.printStackTrace();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display raw text when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param text The text, which supports newlines, which will be displayed to the client upon hovering.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage tooltip(final String text) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onHover("show_text", new JsonString(text));
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display raw text when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @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.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage tooltip(final Iterable<String> lines) {
|
2015-09-11 20:09:22 +10:00
|
|
|
tooltip(ArrayWrapper.toArray(lines, String.class));
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2016-03-20 22:52:16 -04:00
|
|
|
/*
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the
|
|
|
|
* message.
|
|
|
|
* @param replacements The replacements, in order, that will be used in the language-specific message.
|
|
|
|
* @return This builder instance.
|
|
|
|
*//* ------------
|
|
|
|
public FancyMessage translationReplacements(final Iterable<? extends CharSequence> replacements){
|
|
|
|
for(CharSequence str : replacements){
|
|
|
|
latest().translationReplacements.add(new JsonString(str));
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display raw text when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param lines The lines of text which will be displayed to the client upon hovering.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage tooltip(final String... lines) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final StringBuilder builder = new StringBuilder();
|
2015-09-13 14:04:31 +10:00
|
|
|
for (int i = 0; i < lines.length; i++) {
|
2015-09-11 20:09:22 +10:00
|
|
|
builder.append(lines[i]);
|
2016-03-20 22:52:16 -04:00
|
|
|
if (i != lines.length - 1) {
|
2015-09-11 20:09:22 +10:00
|
|
|
builder.append('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tooltip(builder.toString());
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display formatted text when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param text The formatted text which will be displayed to the client upon hovering.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage formattedTooltip(final FancyMessage text) {
|
|
|
|
for (final MessagePart component : text.messageParts) {
|
2016-03-20 22:52:16 -04:00
|
|
|
if (component.clickActionData != null && component.clickActionName != null) {
|
2015-09-11 20:09:22 +10:00
|
|
|
throw new IllegalArgumentException("The tooltip text cannot have click data.");
|
2016-03-20 22:52:16 -04:00
|
|
|
} else if (component.hoverActionData != null && component.hoverActionName != null) {
|
2015-09-13 14:04:31 +10:00
|
|
|
throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
|
2015-09-11 20:09:22 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
onHover("show_text", text);
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @param lines The lines of formatted text which will be displayed to the client upon hovering.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage formattedTooltip(final FancyMessage... lines) {
|
|
|
|
if (lines.length < 1) {
|
2015-09-11 20:09:22 +10:00
|
|
|
onHover(null, null); // Clear tooltip
|
|
|
|
return this;
|
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
final FancyMessage result = new FancyMessage();
|
|
|
|
result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-13 14:04:31 +10:00
|
|
|
for (int i = 0; i < lines.length; i++) {
|
|
|
|
try {
|
|
|
|
for (final MessagePart component : lines[i]) {
|
2016-03-20 22:52:16 -04:00
|
|
|
if (component.clickActionData != null && component.clickActionName != null) {
|
2015-09-11 20:09:22 +10:00
|
|
|
throw new IllegalArgumentException("The tooltip text cannot have click data.");
|
2016-03-20 22:52:16 -04:00
|
|
|
} else if (component.hoverActionData != null && component.hoverActionName != null) {
|
2015-09-13 14:04:31 +10:00
|
|
|
throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
|
2015-09-11 20:09:22 +10:00
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
if (component.hasText()) {
|
2015-09-11 20:09:22 +10:00
|
|
|
result.messageParts.add(component.clone());
|
|
|
|
}
|
2015-06-24 07:44:44 +10:00
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
if (i != lines.length - 1) {
|
2015-09-11 20:09:22 +10:00
|
|
|
result.messageParts.add(new MessagePart(rawText("\n")));
|
2015-06-24 07:44:44 +10:00
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final CloneNotSupportedException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
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
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text.
|
|
|
|
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
|
|
|
* @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.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage formattedTooltip(final Iterable<FancyMessage> lines) {
|
2015-09-11 20:09:22 +10:00
|
|
|
return formattedTooltip(ArrayWrapper.toArray(lines, FancyMessage.class));
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
|
|
|
|
* @param replacements The replacements, in order, that will be used in the language-specific message.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage translationReplacements(final String... replacements) {
|
|
|
|
for (final String str : replacements) {
|
2015-09-11 20:09:22 +10:00
|
|
|
latest().translationReplacements.add(new JsonString(str));
|
|
|
|
}
|
|
|
|
dirty = true;
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
|
|
|
|
* @param replacements The replacements, in order, that will be used in the language-specific message.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage translationReplacements(final FancyMessage... replacements) {
|
2016-01-11 07:00:56 +11:00
|
|
|
Collections.addAll(latest().translationReplacements, replacements);
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
dirty = true;
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
|
|
|
|
* @param replacements The replacements, in order, that will be used in the language-specific message.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage translationReplacements(final Iterable<FancyMessage> replacements) {
|
2015-09-11 20:09:22 +10:00
|
|
|
return translationReplacements(ArrayWrapper.toArray(replacements, FancyMessage.class));
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Terminate construction of the current editing component, and begin construction of a new message component.
|
|
|
|
* After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
|
|
|
|
* @param text The text which will populate the new message component.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage then(final String text) {
|
2015-09-11 20:09:22 +10:00
|
|
|
return then(rawText(text));
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Terminate construction of the current editing component, and begin construction of a new message component.
|
|
|
|
* After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
|
|
|
|
* @param text The text which will populate the new message component.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage then(final TextualComponent text) {
|
|
|
|
if (!latest().hasText()) {
|
|
|
|
throw new IllegalStateException("previous message part has no text");
|
|
|
|
}
|
2015-09-11 20:09:22 +10:00
|
|
|
messageParts.add(new MessagePart(text));
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Terminate construction of the current editing component, and begin construction of a new message component.
|
|
|
|
* After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
|
|
|
|
* @return This builder instance.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public FancyMessage then() {
|
|
|
|
if (!latest().hasText()) {
|
|
|
|
throw new IllegalStateException("previous message part has no text");
|
|
|
|
}
|
2015-09-11 20:09:22 +10:00
|
|
|
messageParts.add(new MessagePart());
|
|
|
|
dirty = true;
|
|
|
|
return this;
|
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
@Override
|
2015-09-13 14:04:31 +10:00
|
|
|
public void writeJson(final JsonWriter writer) throws IOException {
|
|
|
|
if (messageParts.size() == 1) {
|
2015-09-11 20:09:22 +10:00
|
|
|
latest().writeJson(writer);
|
2015-09-13 14:04:31 +10:00
|
|
|
} else {
|
2015-09-11 20:09:22 +10:00
|
|
|
writer.beginObject().name("text").value("").name("extra").beginArray();
|
2015-09-13 14:04:31 +10:00
|
|
|
for (final MessagePart part : this) {
|
2015-09-11 20:09:22 +10:00
|
|
|
part.writeJson(writer);
|
|
|
|
}
|
|
|
|
writer.endArray().endObject();
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Serialize this fancy message, converting it into syntactically-valid JSON using a {@link JsonWriter}.
|
|
|
|
* This JSON should be compatible with vanilla formatter commands such as {@code /tellraw}.
|
|
|
|
* @return The JSON string representing this object.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public String toJSONString() {
|
2016-03-20 22:52:16 -04:00
|
|
|
if (!dirty && jsonString != null) {
|
2015-09-13 14:04:31 +10:00
|
|
|
return jsonString;
|
|
|
|
}
|
2015-09-11 20:09:22 +10:00
|
|
|
final StringWriter string = new StringWriter();
|
|
|
|
final JsonWriter json = new JsonWriter(string);
|
2015-09-13 14:04:31 +10:00
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
writeJson(json);
|
|
|
|
json.close();
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IOException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
throw new RuntimeException("invalid message");
|
|
|
|
}
|
|
|
|
jsonString = string.toString();
|
|
|
|
dirty = false;
|
|
|
|
return jsonString;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sends this message to a player. The player will receive the fully-fledged formatted display of this message.
|
|
|
|
* @param player The player who will receive the message.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public void send(final Player player) {
|
2015-09-11 20:09:22 +10:00
|
|
|
send(player, toJSONString());
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
|
|
|
private void send(final CommandSender sender, final String jsonString) {
|
|
|
|
if (!(sender instanceof Player)) {
|
2015-09-11 20:09:22 +10:00
|
|
|
sender.sendMessage(toOldMessageFormat());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final Player player = (Player) sender;
|
2015-09-13 14:04:31 +10:00
|
|
|
try {
|
2015-09-11 20:09:22 +10:00
|
|
|
final Object handle = Reflection.getHandle(player);
|
|
|
|
final Object connection = Reflection.getField(handle.getClass(), "playerConnection").get(handle);
|
|
|
|
Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet")).invoke(connection, createChatPacket(jsonString));
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalArgumentException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final IllegalAccessException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final InstantiationException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final InvocationTargetException e) {
|
2016-02-04 18:45:50 -05:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final NoSuchMethodException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
|
2015-09-13 14:04:31 +10:00
|
|
|
} catch (final ClassNotFoundException e) {
|
2015-09-11 20:09:22 +10:00
|
|
|
Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
private Object createChatPacket(final String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException,
|
2015-09-13 14:04:31 +10:00
|
|
|
ClassNotFoundException {
|
|
|
|
if (nmsChatSerializerGsonInstance == null) {
|
2015-09-11 20:09:22 +10:00
|
|
|
// Find the field and its value, completely bypassing obfuscation
|
|
|
|
Class<?> chatSerializerClazz;
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
final String version = Reflection.getVersion();
|
|
|
|
final double majorVersion = Double.parseDouble(version.replace('_', '.').substring(1, 4));
|
|
|
|
final int lesserVersion = Integer.parseInt(version.substring(6, 7));
|
2016-03-20 22:52:16 -04:00
|
|
|
|
|
|
|
if (majorVersion < 1.8 || majorVersion == 1.8 && lesserVersion == 1) {
|
2015-09-11 20:09:22 +10:00
|
|
|
chatSerializerClazz = Reflection.getNMSClass("ChatSerializer");
|
2015-09-13 14:04:31 +10:00
|
|
|
} else {
|
2015-09-11 20:09:22 +10:00
|
|
|
chatSerializerClazz = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
|
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-13 14:04:31 +10:00
|
|
|
if (chatSerializerClazz == null) {
|
|
|
|
throw new ClassNotFoundException("Can't find the ChatSerializer class");
|
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-13 14:04:31 +10:00
|
|
|
for (final Field declaredField : chatSerializerClazz.getDeclaredFields()) {
|
|
|
|
if (Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().getName().endsWith("Gson")) {
|
2015-09-11 20:09:22 +10:00
|
|
|
// We've found our field
|
|
|
|
declaredField.setAccessible(true);
|
|
|
|
nmsChatSerializerGsonInstance = declaredField.get(null);
|
|
|
|
fromJsonMethod = nmsChatSerializerGsonInstance.getClass().getMethod("fromJson", String.class, Class.class);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2016-02-19 11:55:00 -05:00
|
|
|
/*
|
|
|
|
Since the method is so simple, and all the obfuscated methods have the same name, it's easier to reimplement 'IChatBaseComponent a(String)'
|
|
|
|
than to reflectively call it
|
|
|
|
Of course, the implementation may change, but fuzzy matches might break with signature changes
|
|
|
|
*/
|
2015-09-11 20:09:22 +10:00
|
|
|
final Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent"));
|
2016-03-20 22:52:16 -04:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent);
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sends this message to a command sender.
|
|
|
|
* If the sender is a player, they will receive the fully-fledged formatted display of this message.
|
|
|
|
* Otherwise, they will receive a version of this message with less formatting.
|
|
|
|
* @param sender The command sender who will receive the message.
|
|
|
|
* @see #toOldMessageFormat()
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public void send(final CommandSender sender) {
|
2015-09-11 20:09:22 +10:00
|
|
|
send(sender, toJSONString());
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Sends this message to multiple command senders.
|
|
|
|
* @param senders The command senders who will receive the message.
|
|
|
|
* @see #send(CommandSender)
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public void send(final Iterable<? extends CommandSender> senders) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final String string = toJSONString();
|
2015-09-13 14:04:31 +10:00
|
|
|
for (final CommandSender sender : senders) {
|
2015-09-11 20:09:22 +10:00
|
|
|
send(sender, string);
|
|
|
|
}
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* Convert this message to a human-readable string with limited formatting.
|
|
|
|
* This method is used to send this message to clients without JSON formatting support.
|
|
|
|
* <p>
|
|
|
|
* Serialization of this message by using this message will include (in this order for each message part):
|
|
|
|
* <ol>
|
|
|
|
* <li>The color of each message part.</li>
|
2016-02-19 11:55:00 -05:00
|
|
|
* <li>The applicable stylization for each message part.</li>
|
2015-09-11 20:09:22 +10:00
|
|
|
* <li>The core text of the message part.</li>
|
|
|
|
* </ol>
|
|
|
|
* The primary omissions are tooltips and clickable actions. Consequently, this method should be used only as a last resort.
|
|
|
|
* </p>
|
|
|
|
* <p>
|
|
|
|
* Color and formatting can be removed from the returned string by using {@link ChatColor#stripColor(String)}.</p>
|
|
|
|
* @return A human-readable string representing limited formatting in addition to the core text of this message.
|
|
|
|
*/
|
2015-09-13 14:04:31 +10:00
|
|
|
public String toOldMessageFormat() {
|
2015-09-11 20:09:22 +10:00
|
|
|
final StringBuilder result = new StringBuilder();
|
2015-09-13 14:04:31 +10:00
|
|
|
for (final MessagePart part : this) {
|
2015-09-11 20:09:22 +10:00
|
|
|
result.append(part.color == null ? "" : part.color);
|
2015-09-13 14:04:31 +10:00
|
|
|
for (final ChatColor formatSpecifier : part.styles) {
|
2015-09-11 20:09:22 +10:00
|
|
|
result.append(formatSpecifier);
|
|
|
|
}
|
|
|
|
result.append(part.text);
|
|
|
|
}
|
|
|
|
return result.toString();
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
|
|
|
private MessagePart latest() {
|
2015-09-11 20:09:22 +10:00
|
|
|
return messageParts.get(messageParts.size() - 1);
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
|
|
|
private void onClick(final String name, final String data) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final MessagePart latest = latest();
|
|
|
|
latest.clickActionName = name;
|
|
|
|
latest.clickActionData = data;
|
|
|
|
dirty = true;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
|
|
|
private void onHover(final String name, final JsonRepresentedObject data) {
|
2015-09-11 20:09:22 +10:00
|
|
|
final MessagePart latest = latest();
|
|
|
|
latest.hoverActionName = name;
|
|
|
|
latest.hoverActionData = data;
|
|
|
|
dirty = true;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
// Doc copied from interface
|
|
|
|
@Override
|
2015-09-13 14:04:31 +10:00
|
|
|
public Map<String, Object> serialize() {
|
2016-01-11 07:00:56 +11:00
|
|
|
final HashMap<String, Object> map = new HashMap<>();
|
2015-09-11 20:09:22 +10:00
|
|
|
map.put("messageParts", messageParts);
|
|
|
|
// map.put("JSON", toJSONString());
|
|
|
|
return map;
|
|
|
|
}
|
2015-09-13 14:04:31 +10:00
|
|
|
|
2015-09-11 20:09:22 +10:00
|
|
|
/**
|
|
|
|
* <b>Internally called method. Not for API consumption.</b>
|
|
|
|
*/
|
|
|
|
@Override
|
2015-09-13 14:04:31 +10:00
|
|
|
public Iterator<MessagePart> iterator() {
|
2015-09-11 20:09:22 +10:00
|
|
|
return messageParts.iterator();
|
|
|
|
}
|
2015-06-24 07:44:44 +10:00
|
|
|
}
|