mirror of
https://github.com/IntellectualSites/PlotSquared.git
synced 2025-07-10 09:34:43 +02:00
Major code reformatting
This commit is contained in:
@ -1,11 +1,11 @@
|
||||
package com.plotsquared.bukkit.chat;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
||||
/**
|
||||
* Represents a wrapper around an array class of an arbitrary reference type,
|
||||
* which properly implements "value" hash code and equality functions.
|
||||
@ -17,57 +17,17 @@ import org.apache.commons.lang.Validate;
|
||||
* @see Arrays
|
||||
*/
|
||||
public final class ArrayWrapper<E> {
|
||||
|
||||
|
||||
private E[] _array;
|
||||
|
||||
/**
|
||||
* Creates an array wrapper with some elements.
|
||||
* @param elements The elements of the array.
|
||||
*/
|
||||
public ArrayWrapper(final E... elements) {
|
||||
public ArrayWrapper(E... elements) {
|
||||
setArray(elements);
|
||||
}
|
||||
|
||||
private E[] _array;
|
||||
|
||||
/**
|
||||
* Retrieves a reference to the wrapped array instance.
|
||||
* @return The array wrapped by this instance.
|
||||
*/
|
||||
public E[] getArray() {
|
||||
return _array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this wrapper to wrap a new array instance.
|
||||
* @param array The new wrapped array.
|
||||
*/
|
||||
public void setArray(final E[] array) {
|
||||
Validate.notNull(array, "The array must not be null.");
|
||||
_array = array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this object has a value equivalent to another object.
|
||||
* @see Arrays#equals(Object[], Object[])
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (!(other instanceof ArrayWrapper)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.equals(_array, ((ArrayWrapper) other)._array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash code represented by this objects value.
|
||||
* @see Arrays#hashCode(Object[])
|
||||
* @return This object's hash code.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(_array);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an iterable element collection to an array of elements.
|
||||
* The iteration order of the specified object will be used as the array element order.
|
||||
@ -76,28 +36,66 @@ public final class ArrayWrapper<E> {
|
||||
* @return An array of elements in the specified iterable.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] toArray(final Iterable<? extends T> list, final Class<T> c) {
|
||||
public static <T> T[] toArray(Iterable<? extends T> list, Class<T> c) {
|
||||
int size = -1;
|
||||
if (list instanceof Collection<?>) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Collection coll = (Collection) list;
|
||||
@SuppressWarnings("rawtypes") Collection coll = (Collection) list;
|
||||
size = coll.size();
|
||||
}
|
||||
|
||||
|
||||
if (size < 0) {
|
||||
size = 0;
|
||||
// Ugly hack: Count it ourselves
|
||||
for (@SuppressWarnings("unused")
|
||||
final T element : list) {
|
||||
for (@SuppressWarnings("unused") T element : list) {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
final T[] result = (T[]) Array.newInstance(c, size);
|
||||
|
||||
T[] result = (T[]) Array.newInstance(c, size);
|
||||
int i = 0;
|
||||
for (final T element : list) { // Assumes iteration order is consistent
|
||||
for (T element : list) { // Assumes iteration order is consistent
|
||||
result[i++] = element; // Assign array element at index THEN increment counter
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a reference to the wrapped array instance.
|
||||
* @return The array wrapped by this instance.
|
||||
*/
|
||||
public E[] getArray() {
|
||||
return this._array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this wrapper to wrap a new array instance.
|
||||
* @param array The new wrapped array.
|
||||
*/
|
||||
public void setArray(E[] array) {
|
||||
Validate.notNull(array, "The array must not be null.");
|
||||
this._array = array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this object has a value equivalent to another object.
|
||||
* @see Arrays#equals(Object[], Object[])
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof ArrayWrapper)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.equals(this._array, ((ArrayWrapper) other)._array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash code represented by this objects value.
|
||||
* @see Arrays#hashCode(Object[])
|
||||
* @return This object's hash code.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this._array);
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,10 @@ 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 <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>.
|
||||
* 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,
|
||||
@ -53,48 +55,49 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
// The ChatSerializer's instance of Gson
|
||||
private static Object nmsChatSerializerGsonInstance;
|
||||
private static Method fromJsonMethod;
|
||||
|
||||
|
||||
static {
|
||||
ConfigurationSerialization.registerClass(FancyMessage.class);
|
||||
}
|
||||
|
||||
|
||||
private List<MessagePart> messageParts;
|
||||
private String jsonString;
|
||||
private boolean dirty;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a JSON message with text.
|
||||
* @param firstPartText The existing text in the message.
|
||||
*/
|
||||
public FancyMessage(final String firstPartText) {
|
||||
public FancyMessage(String firstPartText) {
|
||||
this(rawText(firstPartText));
|
||||
}
|
||||
|
||||
public FancyMessage(final TextualComponent firstPartText) {
|
||||
messageParts = new ArrayList<>();
|
||||
messageParts.add(new MessagePart(firstPartText));
|
||||
jsonString = null;
|
||||
dirty = false;
|
||||
|
||||
public FancyMessage(TextualComponent firstPartText) {
|
||||
this.messageParts = new ArrayList<>();
|
||||
this.messageParts.add(new MessagePart(firstPartText));
|
||||
this.jsonString = null;
|
||||
this.dirty = false;
|
||||
|
||||
if (nmsPacketPlayOutChatConstructor == null) {
|
||||
try {
|
||||
nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
|
||||
nmsPacketPlayOutChatConstructor =
|
||||
Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
|
||||
nmsPacketPlayOutChatConstructor.setAccessible(true);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
} catch (NoSuchMethodException e) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Could not find Minecraft method or constructor.", e);
|
||||
} catch (final SecurityException e) {
|
||||
} catch (SecurityException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not access constructor.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a JSON message without text.
|
||||
*/
|
||||
public FancyMessage() {
|
||||
this((TextualComponent) null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserialize a JSON-represented message from a mapping of key-value pairs.
|
||||
* This is called by the Bukkit serialization API.
|
||||
@ -102,8 +105,8 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @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();
|
||||
public static FancyMessage deserialize(Map<String, Object> serialized) {
|
||||
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");
|
||||
@ -116,26 +119,26 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @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();
|
||||
public static FancyMessage deserialize(String json) {
|
||||
JsonObject serialized = _stringParser.parse(json).getAsJsonObject();
|
||||
JsonArray extra = serialized.getAsJsonArray("extra"); // Get the extra component
|
||||
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()) {
|
||||
for (JsonElement mPrt : extra) {
|
||||
MessagePart component = new MessagePart();
|
||||
JsonObject messagePart = mPrt.getAsJsonObject();
|
||||
for (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
|
||||
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()) {
|
||||
for (Map.Entry<String, JsonElement> compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) {
|
||||
serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString());
|
||||
}
|
||||
}
|
||||
@ -147,11 +150,11 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
} 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();
|
||||
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();
|
||||
JsonObject object = entry.getValue().getAsJsonObject();
|
||||
component.hoverActionName = object.get("action").getAsString();
|
||||
if (object.get("value").isJsonPrimitive()) {
|
||||
// Assume string
|
||||
@ -166,7 +169,7 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
} else if (entry.getKey().equals("insertion")) {
|
||||
component.insertionData = entry.getValue().getAsString();
|
||||
} else if (entry.getKey().equals("with")) {
|
||||
for (final JsonElement object : entry.getValue().getAsJsonArray()) {
|
||||
for (JsonElement object : entry.getValue().getAsJsonArray()) {
|
||||
if (object.isJsonPrimitive()) {
|
||||
component.translationReplacements.add(new JsonString(object.getAsString()));
|
||||
} else {
|
||||
@ -184,10 +187,10 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
|
||||
@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());
|
||||
FancyMessage instance = (FancyMessage) super.clone();
|
||||
instance.messageParts = new ArrayList<>(this.messageParts.size());
|
||||
for (int i = 0; i < this.messageParts.size(); i++) {
|
||||
instance.messageParts.add(i, this.messageParts.get(i).clone());
|
||||
}
|
||||
instance.dirty = false;
|
||||
instance.jsonString = null;
|
||||
@ -199,141 +202,149 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @param text The new text of the current editing component.
|
||||
* @return This builder instance.
|
||||
*/
|
||||
public FancyMessage text(final String text) {
|
||||
final MessagePart latest = latest();
|
||||
public FancyMessage text(String text) {
|
||||
MessagePart latest = latest();
|
||||
latest.text = rawText(text);
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage text(final TextualComponent text) {
|
||||
final MessagePart latest = latest();
|
||||
public FancyMessage text(TextualComponent text) {
|
||||
MessagePart latest = latest();
|
||||
latest.text = text;
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
public FancyMessage color(final ChatColor color) {
|
||||
public FancyMessage color(ChatColor color) {
|
||||
latest().color = color;
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage style(final ChatColor... styles) {
|
||||
for (final ChatColor style : styles) {
|
||||
public FancyMessage style(ChatColor... styles) {
|
||||
for (ChatColor style : styles) {
|
||||
if (!style.isFormat()) {
|
||||
throw new IllegalArgumentException(style.name() + " is not a style");
|
||||
}
|
||||
}
|
||||
latest().styles.addAll(Arrays.asList(styles));
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage file(final String path) {
|
||||
public FancyMessage file(String path) {
|
||||
onClick("open_file", path);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage link(final String url) {
|
||||
public FancyMessage link(String url) {
|
||||
onClick("open_url", url);
|
||||
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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage suggest(final String command) {
|
||||
public FancyMessage suggest(String command) {
|
||||
onClick("suggest_command", command);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage insert(final String command) {
|
||||
public FancyMessage insert(String command) {
|
||||
latest().insertionData = command;
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage command(final String command) {
|
||||
public FancyMessage command(String command) {
|
||||
onClick("run_command", command);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage achievementTooltip(final String name) {
|
||||
public FancyMessage achievementTooltip(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.
|
||||
* <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.
|
||||
*/
|
||||
public FancyMessage achievementTooltip(final Achievement which) {
|
||||
public FancyMessage achievementTooltip(Achievement which) {
|
||||
try {
|
||||
final Object achievement = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSAchievement", Achievement.class).invoke(null, which);
|
||||
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) {
|
||||
} catch (IllegalAccessException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
||||
return this;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
||||
return this;
|
||||
} catch (final InvocationTargetException e) {
|
||||
} catch (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.
|
||||
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
|
||||
@ -341,36 +352,38 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @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();
|
||||
public FancyMessage statisticTooltip(Statistic which) {
|
||||
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);
|
||||
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) {
|
||||
} catch (IllegalAccessException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
||||
return this;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
||||
return this;
|
||||
} catch (final InvocationTargetException e) {
|
||||
} catch (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.
|
||||
* 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.
|
||||
* @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();
|
||||
public FancyMessage statisticTooltip(Statistic which, Material item) {
|
||||
Type type = which.getType();
|
||||
if (type == Type.UNTYPED) {
|
||||
throw new IllegalArgumentException("That statistic needs no additional parameter!");
|
||||
}
|
||||
@ -378,30 +391,33 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
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);
|
||||
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) {
|
||||
} catch (IllegalAccessException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
||||
return this;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
||||
return this;
|
||||
} catch (final InvocationTargetException e) {
|
||||
} catch (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.
|
||||
* 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.
|
||||
* @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();
|
||||
public FancyMessage statisticTooltip(Statistic which, EntityType entity) {
|
||||
Type type = which.getType();
|
||||
if (type == Type.UNTYPED) {
|
||||
throw new IllegalArgumentException("That statistic needs no additional parameter!");
|
||||
}
|
||||
@ -409,66 +425,69 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
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);
|
||||
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) {
|
||||
} catch (IllegalAccessException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
||||
return this;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
||||
return this;
|
||||
} catch (final InvocationTargetException e) {
|
||||
} catch (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.
|
||||
* <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.
|
||||
*/
|
||||
public FancyMessage itemTooltip(final String itemJSON) {
|
||||
public FancyMessage itemTooltip(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.
|
||||
* <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.
|
||||
*/
|
||||
public FancyMessage itemTooltip(final ItemStack itemStack) {
|
||||
public FancyMessage itemTooltip(ItemStack itemStack) {
|
||||
try {
|
||||
final Object nmsItem = Reflection.getMethod(Reflection.getOBCClass("inventory.CraftItemStack"), "asNMSCopy", ItemStack.class).invoke(null, itemStack);
|
||||
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) {
|
||||
.invoke(nmsItem, Reflection.getNMSClass("NBTTagCompound").newInstance()).toString());
|
||||
} catch (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.
|
||||
* <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.
|
||||
*/
|
||||
public FancyMessage tooltip(final String text) {
|
||||
public FancyMessage tooltip(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.
|
||||
* <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.
|
||||
* @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 Iterable<String> lines) {
|
||||
public FancyMessage tooltip(Iterable<String> lines) {
|
||||
tooltip(ArrayWrapper.toArray(lines, String.class));
|
||||
return this;
|
||||
}
|
||||
@ -497,8 +516,8 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @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();
|
||||
public FancyMessage tooltip(String... lines) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
builder.append(lines[i]);
|
||||
if (i != lines.length - 1) {
|
||||
@ -508,15 +527,15 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
tooltip(builder.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage formattedTooltip(final FancyMessage text) {
|
||||
for (final MessagePart component : text.messageParts) {
|
||||
public FancyMessage formattedTooltip(FancyMessage text) {
|
||||
for (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) {
|
||||
@ -526,25 +545,25 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
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.
|
||||
* <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.
|
||||
*/
|
||||
public FancyMessage formattedTooltip(final FancyMessage... lines) {
|
||||
public FancyMessage formattedTooltip(FancyMessage... lines) {
|
||||
if (lines.length < 1) {
|
||||
onHover(null, null); // Clear tooltip
|
||||
return this;
|
||||
}
|
||||
|
||||
final FancyMessage result = new FancyMessage();
|
||||
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]) {
|
||||
for (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) {
|
||||
@ -557,176 +576,185 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
if (i != lines.length - 1) {
|
||||
result.messageParts.add(new MessagePart(rawText("\n")));
|
||||
}
|
||||
} catch (final CloneNotSupportedException e) {
|
||||
} catch (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.
|
||||
* <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.
|
||||
* @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<FancyMessage> lines) {
|
||||
public FancyMessage formattedTooltip(Iterable<FancyMessage> lines) {
|
||||
return formattedTooltip(ArrayWrapper.toArray(lines, FancyMessage.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 String... replacements) {
|
||||
for (final String str : replacements) {
|
||||
public FancyMessage translationReplacements(String... replacements) {
|
||||
for (String str : replacements) {
|
||||
latest().translationReplacements.add(new JsonString(str));
|
||||
}
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 FancyMessage... replacements) {
|
||||
public FancyMessage translationReplacements(FancyMessage... replacements) {
|
||||
Collections.addAll(latest().translationReplacements, replacements);
|
||||
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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<FancyMessage> replacements) {
|
||||
public FancyMessage translationReplacements(Iterable<FancyMessage> replacements) {
|
||||
return translationReplacements(ArrayWrapper.toArray(replacements, FancyMessage.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage then(final String text) {
|
||||
public FancyMessage then(String text) {
|
||||
return then(rawText(text));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage then(final TextualComponent text) {
|
||||
public FancyMessage then(TextualComponent text) {
|
||||
if (!latest().hasText()) {
|
||||
throw new IllegalStateException("previous message part has no text");
|
||||
}
|
||||
messageParts.add(new MessagePart(text));
|
||||
dirty = true;
|
||||
this.messageParts.add(new MessagePart(text));
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public FancyMessage then() {
|
||||
if (!latest().hasText()) {
|
||||
throw new IllegalStateException("previous message part has no text");
|
||||
}
|
||||
messageParts.add(new MessagePart());
|
||||
dirty = true;
|
||||
this.messageParts.add(new MessagePart());
|
||||
this.dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeJson(final JsonWriter writer) throws IOException {
|
||||
if (messageParts.size() == 1) {
|
||||
public void writeJson(JsonWriter writer) throws IOException {
|
||||
if (this.messageParts.size() == 1) {
|
||||
latest().writeJson(writer);
|
||||
} else {
|
||||
writer.beginObject().name("text").value("").name("extra").beginArray();
|
||||
for (final MessagePart part : this) {
|
||||
for (MessagePart part : this) {
|
||||
part.writeJson(writer);
|
||||
}
|
||||
writer.endArray().endObject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public String toJSONString() {
|
||||
if (!dirty && jsonString != null) {
|
||||
return jsonString;
|
||||
if (!this.dirty && this.jsonString != null) {
|
||||
return this.jsonString;
|
||||
}
|
||||
final StringWriter string = new StringWriter();
|
||||
final JsonWriter json = new JsonWriter(string);
|
||||
StringWriter string = new StringWriter();
|
||||
JsonWriter json = new JsonWriter(string);
|
||||
try {
|
||||
writeJson(json);
|
||||
json.close();
|
||||
} catch (final IOException e) {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("invalid message");
|
||||
}
|
||||
jsonString = string.toString();
|
||||
dirty = false;
|
||||
return jsonString;
|
||||
this.jsonString = string.toString();
|
||||
this.dirty = false;
|
||||
return this.jsonString;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void send(final Player player) {
|
||||
public void send(Player player) {
|
||||
send(player, toJSONString());
|
||||
}
|
||||
|
||||
private void send(final CommandSender sender, final String jsonString) {
|
||||
|
||||
private void send(CommandSender sender, String jsonString) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage(toOldMessageFormat());
|
||||
return;
|
||||
}
|
||||
final Player player = (Player) sender;
|
||||
Player player = (Player) sender;
|
||||
try {
|
||||
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));
|
||||
} catch (final IllegalArgumentException e) {
|
||||
Object handle = Reflection.getHandle(player);
|
||||
Object connection = Reflection.getField(handle.getClass(), "playerConnection").get(handle);
|
||||
Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet"))
|
||||
.invoke(connection, createChatPacket(jsonString));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
|
||||
} catch (final IllegalAccessException e) {
|
||||
} catch (IllegalAccessException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
|
||||
} catch (final InstantiationException e) {
|
||||
} catch (InstantiationException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
|
||||
} catch (final InvocationTargetException e) {
|
||||
} catch (InvocationTargetException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
} catch (NoSuchMethodException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
|
||||
} catch (final ClassNotFoundException e) {
|
||||
} catch (ClassNotFoundException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object createChatPacket(final String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException,
|
||||
ClassNotFoundException {
|
||||
|
||||
private Object createChatPacket(String json)
|
||||
throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException,
|
||||
ClassNotFoundException {
|
||||
if (nmsChatSerializerGsonInstance == null) {
|
||||
// Find the field and its value, completely bypassing obfuscation
|
||||
Class<?> chatSerializerClazz;
|
||||
|
||||
final String version = Reflection.getVersion();
|
||||
final double majorVersion = Double.parseDouble(version.replace('_', '.').substring(1, 4));
|
||||
final int lesserVersion = Integer.parseInt(version.substring(6, 7));
|
||||
String version = Reflection.getVersion();
|
||||
double majorVersion = Double.parseDouble(version.replace('_', '.').substring(1, 4));
|
||||
int lesserVersion = Integer.parseInt(version.substring(6, 7));
|
||||
|
||||
if (majorVersion < 1.8 || majorVersion == 1.8 && lesserVersion == 1) {
|
||||
chatSerializerClazz = Reflection.getNMSClass("ChatSerializer");
|
||||
@ -738,8 +766,9 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
throw new ClassNotFoundException("Can't find the ChatSerializer class");
|
||||
}
|
||||
|
||||
for (final Field declaredField : chatSerializerClazz.getDeclaredFields()) {
|
||||
if (Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().getName().endsWith("Gson")) {
|
||||
for (Field declaredField : chatSerializerClazz.getDeclaredFields()) {
|
||||
if (Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType()
|
||||
.getName().endsWith("Gson")) {
|
||||
// We've found our field
|
||||
declaredField.setAccessible(true);
|
||||
nmsChatSerializerGsonInstance = declaredField.get(null);
|
||||
@ -754,11 +783,11 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
than to reflectively call it
|
||||
Of course, the implementation may change, but fuzzy matches might break with signature changes
|
||||
*/
|
||||
final Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent"));
|
||||
Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent"));
|
||||
|
||||
return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends this message to a command sender.
|
||||
* If the sender is a player, they will receive the fully-fledged formatted display of this message.
|
||||
@ -766,22 +795,22 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @param sender The command sender who will receive the message.
|
||||
* @see #toOldMessageFormat()
|
||||
*/
|
||||
public void send(final CommandSender sender) {
|
||||
public void send(CommandSender sender) {
|
||||
send(sender, toJSONString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends this message to multiple command senders.
|
||||
* @param senders The command senders who will receive the message.
|
||||
* @see #send(CommandSender)
|
||||
*/
|
||||
public void send(final Iterable<? extends CommandSender> senders) {
|
||||
final String string = toJSONString();
|
||||
for (final CommandSender sender : senders) {
|
||||
public void send(Iterable<? extends CommandSender> senders) {
|
||||
String string = toJSONString();
|
||||
for (CommandSender sender : senders) {
|
||||
send(sender, string);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -799,49 +828,49 @@ public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<
|
||||
* @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) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (MessagePart part : this) {
|
||||
result.append(part.color == null ? "" : part.color);
|
||||
for (final ChatColor formatSpecifier : part.styles) {
|
||||
for (ChatColor formatSpecifier : part.styles) {
|
||||
result.append(formatSpecifier);
|
||||
}
|
||||
result.append(part.text);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
private MessagePart latest() {
|
||||
return messageParts.get(messageParts.size() - 1);
|
||||
return this.messageParts.get(this.messageParts.size() - 1);
|
||||
}
|
||||
|
||||
private void onClick(final String name, final String data) {
|
||||
final MessagePart latest = latest();
|
||||
|
||||
private void onClick(String name, String data) {
|
||||
MessagePart latest = latest();
|
||||
latest.clickActionName = name;
|
||||
latest.clickActionData = data;
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void onHover(final String name, final JsonRepresentedObject data) {
|
||||
final MessagePart latest = latest();
|
||||
|
||||
private void onHover(String name, JsonRepresentedObject data) {
|
||||
MessagePart latest = latest();
|
||||
latest.hoverActionName = name;
|
||||
latest.hoverActionData = data;
|
||||
dirty = true;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
|
||||
// Doc copied from interface
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("messageParts", messageParts);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("messageParts", this.messageParts);
|
||||
// map.put("JSON", toJSONString());
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <b>Internally called method. Not for API consumption.</b>
|
||||
* Internally called method. Not for API consumption.
|
||||
*/
|
||||
@Override
|
||||
public Iterator<MessagePart> iterator() {
|
||||
return messageParts.iterator();
|
||||
return this.messageParts.iterator();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
package com.plotsquared.bukkit.chat;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Represents an object that can be serialized to a JSON writer instance.
|
||||
*/
|
||||
interface JsonRepresentedObject {
|
||||
|
||||
|
||||
/**
|
||||
* Writes the JSON representation of this object to the specified writer.
|
||||
* @param writer The JSON writer which will receive the object.
|
||||
* @throws IOException If an error occurs writing to the stream.
|
||||
*/
|
||||
void writeJson(final JsonWriter writer) throws IOException;
|
||||
|
||||
void writeJson(JsonWriter writer) throws IOException;
|
||||
|
||||
}
|
||||
|
@ -1,47 +1,47 @@
|
||||
package com.plotsquared.bukkit.chat;
|
||||
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.intellectualcrafters.configuration.serialization.ConfigurationSerializable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.intellectualcrafters.configuration.serialization.ConfigurationSerializable;
|
||||
|
||||
/**
|
||||
* Represents a JSON string value.
|
||||
* Writes by this object will not write name values nor begin/end objects in the JSON stream.
|
||||
* All writes merely write the represented string value.
|
||||
*/
|
||||
final class JsonString implements JsonRepresentedObject, ConfigurationSerializable {
|
||||
|
||||
|
||||
private final String _value;
|
||||
|
||||
public JsonString(final CharSequence value) {
|
||||
_value = value == null ? null : value.toString();
|
||||
|
||||
public JsonString(CharSequence value) {
|
||||
this._value = value == null ? null : value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeJson(final JsonWriter writer) throws IOException {
|
||||
writer.value(getValue());
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return _value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
final HashMap<String, Object> theSingleValue = new HashMap<String, Object>();
|
||||
theSingleValue.put("stringValue", _value);
|
||||
return theSingleValue;
|
||||
}
|
||||
|
||||
public static JsonString deserialize(final Map<String, Object> map) {
|
||||
|
||||
public static JsonString deserialize(Map<String, Object> map) {
|
||||
return new JsonString(map.get("stringValue").toString());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeJson(JsonWriter writer) throws IOException {
|
||||
writer.value(getValue());
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
HashMap<String, Object> theSingleValue = new HashMap<>();
|
||||
theSingleValue.put("stringValue", this._value);
|
||||
return theSingleValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return _value;
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ final class MessagePart implements JsonRepresentedObject, ConfigurationSerializa
|
||||
static final BiMap<ChatColor, String> stylesToNames;
|
||||
|
||||
static {
|
||||
final ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
|
||||
for (final ChatColor style : ChatColor.values()) {
|
||||
ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
|
||||
for (ChatColor style : ChatColor.values()) {
|
||||
if (!style.isFormat()) {
|
||||
continue;
|
||||
}
|
||||
@ -57,18 +57,18 @@ final class MessagePart implements JsonRepresentedObject, ConfigurationSerializa
|
||||
TextualComponent text = null;
|
||||
String insertionData = null;
|
||||
ArrayList<JsonRepresentedObject> translationReplacements = new ArrayList<>();
|
||||
|
||||
MessagePart(final TextualComponent text) {
|
||||
|
||||
MessagePart(TextualComponent text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
MessagePart() {
|
||||
text = null;
|
||||
this.text = null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static MessagePart deserialize(final Map<String, Object> serialized) {
|
||||
final MessagePart part = new MessagePart((TextualComponent) serialized.get("text"));
|
||||
public static MessagePart deserialize(Map<String, Object> serialized) {
|
||||
MessagePart part = new MessagePart((TextualComponent) serialized.get("text"));
|
||||
part.styles = (ArrayList<ChatColor>) serialized.get("styles");
|
||||
part.color = ChatColor.getByChar(serialized.get("color").toString());
|
||||
part.hoverActionName = (String) serialized.get("hoverActionName");
|
||||
@ -81,70 +81,71 @@ final class MessagePart implements JsonRepresentedObject, ConfigurationSerializa
|
||||
}
|
||||
|
||||
boolean hasText() {
|
||||
return text != null;
|
||||
return this.text != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public MessagePart clone() throws CloneNotSupportedException {
|
||||
final MessagePart obj = (MessagePart) super.clone();
|
||||
obj.styles = (ArrayList<ChatColor>) styles.clone();
|
||||
if (hoverActionData instanceof JsonString) {
|
||||
obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue());
|
||||
} else if (hoverActionData instanceof FancyMessage) {
|
||||
obj.hoverActionData = ((FancyMessage) hoverActionData).clone();
|
||||
MessagePart obj = (MessagePart) super.clone();
|
||||
obj.styles = (ArrayList<ChatColor>) this.styles.clone();
|
||||
if (this.hoverActionData instanceof JsonString) {
|
||||
obj.hoverActionData = new JsonString(((JsonString) this.hoverActionData).getValue());
|
||||
} else if (this.hoverActionData instanceof FancyMessage) {
|
||||
obj.hoverActionData = ((FancyMessage) this.hoverActionData).clone();
|
||||
}
|
||||
obj.translationReplacements = (ArrayList<JsonRepresentedObject>) translationReplacements.clone();
|
||||
obj.translationReplacements = (ArrayList<JsonRepresentedObject>) this.translationReplacements.clone();
|
||||
return obj;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeJson(final JsonWriter json) {
|
||||
public void writeJson(JsonWriter json) {
|
||||
try {
|
||||
json.beginObject();
|
||||
text.writeJson(json);
|
||||
json.name("color").value(color.name().toLowerCase());
|
||||
for (final ChatColor style : styles) {
|
||||
this.text.writeJson(json);
|
||||
json.name("color").value(this.color.name().toLowerCase());
|
||||
for (ChatColor style : this.styles) {
|
||||
json.name(stylesToNames.get(style)).value(true);
|
||||
}
|
||||
if ((clickActionName != null) && (clickActionData != null)) {
|
||||
json.name("clickEvent").beginObject().name("action").value(clickActionName).name("value").value(clickActionData).endObject();
|
||||
if ((this.clickActionName != null) && (this.clickActionData != null)) {
|
||||
json.name("clickEvent").beginObject().name("action").value(this.clickActionName).name("value").value(this.clickActionData)
|
||||
.endObject();
|
||||
}
|
||||
if ((hoverActionName != null) && (hoverActionData != null)) {
|
||||
json.name("hoverEvent").beginObject().name("action").value(hoverActionName).name("value");
|
||||
hoverActionData.writeJson(json);
|
||||
if ((this.hoverActionName != null) && (this.hoverActionData != null)) {
|
||||
json.name("hoverEvent").beginObject().name("action").value(this.hoverActionName).name("value");
|
||||
this.hoverActionData.writeJson(json);
|
||||
json.endObject();
|
||||
}
|
||||
if (insertionData != null) {
|
||||
json.name("insertion").value(insertionData);
|
||||
if (this.insertionData != null) {
|
||||
json.name("insertion").value(this.insertionData);
|
||||
}
|
||||
if ((!translationReplacements.isEmpty()) && (text != null) && TextualComponent.isTranslatableText(text)) {
|
||||
if (!this.translationReplacements.isEmpty() && (this.text != null) && TextualComponent.isTranslatableText(this.text)) {
|
||||
json.name("with").beginArray();
|
||||
for (final JsonRepresentedObject obj : translationReplacements) {
|
||||
for (JsonRepresentedObject obj : this.translationReplacements) {
|
||||
obj.writeJson(json);
|
||||
}
|
||||
json.endArray();
|
||||
}
|
||||
json.endObject();
|
||||
} catch (final IOException e) {
|
||||
} catch (IOException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "A problem occurred during writing of JSON string", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("text", text);
|
||||
map.put("styles", styles);
|
||||
map.put("color", color.getChar());
|
||||
map.put("hoverActionName", hoverActionName);
|
||||
map.put("hoverActionData", hoverActionData);
|
||||
map.put("clickActionName", clickActionName);
|
||||
map.put("clickActionData", clickActionData);
|
||||
map.put("insertion", insertionData);
|
||||
map.put("translationReplacements", translationReplacements);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("text", this.text);
|
||||
map.put("styles", this.styles);
|
||||
map.put("color", this.color.getChar());
|
||||
map.put("hoverActionName", this.hoverActionName);
|
||||
map.put("hoverActionData", this.hoverActionData);
|
||||
map.put("clickActionName", this.clickActionName);
|
||||
map.put("clickActionData", this.clickActionData);
|
||||
map.put("insertion", this.insertionData);
|
||||
map.put("translationReplacements", this.translationReplacements);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public final class Reflection {
|
||||
private Reflection() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the version string from the package name of the CraftBukkit server implementation.
|
||||
* This is needed to bypass the JAR package name changing on each update.
|
||||
@ -42,19 +42,20 @@ public final class Reflection {
|
||||
public synchronized static String getVersion() {
|
||||
return PS.get().IMP.getNMSPackage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package.
|
||||
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
|
||||
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this
|
||||
* method simultaneously).
|
||||
* @param className The name of the class, excluding the package, within NMS.
|
||||
* @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded.
|
||||
*/
|
||||
public synchronized static Class<?> getNMSClass(final String className) {
|
||||
public synchronized static Class<?> getNMSClass(String className) {
|
||||
if (_loadedNMSClasses.containsKey(className)) {
|
||||
return _loadedNMSClasses.get(className);
|
||||
}
|
||||
|
||||
final String fullName = "net.minecraft.server." + getVersion() + "." + className;
|
||||
String fullName = "net.minecraft.server." + getVersion() + "." + className;
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(fullName);
|
||||
@ -66,7 +67,7 @@ public final class Reflection {
|
||||
_loadedNMSClasses.put(className, clazz);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package.
|
||||
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this
|
||||
@ -75,12 +76,12 @@ public final class Reflection {
|
||||
* .CraftItemStack}.
|
||||
* @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded.
|
||||
*/
|
||||
public synchronized static Class<?> getOBCClass(final String className) {
|
||||
public synchronized static Class<?> getOBCClass(String className) {
|
||||
if (_loadedOBCClasses.containsKey(className)) {
|
||||
return _loadedOBCClasses.get(className);
|
||||
}
|
||||
|
||||
final String fullName = "org.bukkit.craftbukkit." + getVersion() + "." + className;
|
||||
String fullName = "org.bukkit.craftbukkit." + getVersion() + "." + className;
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(fullName);
|
||||
@ -92,7 +93,7 @@ public final class Reflection {
|
||||
_loadedOBCClasses.put(className, clazz);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to get the NMS handle of a CraftBukkit object.
|
||||
* <p>
|
||||
@ -102,7 +103,7 @@ public final class Reflection {
|
||||
* @param obj The object for which to retrieve an NMS handle.
|
||||
* @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}.
|
||||
*/
|
||||
public synchronized static Object getHandle(final Object obj) {
|
||||
public synchronized static Object getHandle(Object obj) {
|
||||
try {
|
||||
return getMethod(obj.getClass(), "getHandle").invoke(obj);
|
||||
} catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) {
|
||||
@ -110,7 +111,7 @@ public final class Reflection {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a {@link Field} instance declared by the specified class with the specified name.
|
||||
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
|
||||
@ -129,7 +130,7 @@ public final class Reflection {
|
||||
* @return A field object with the specified name declared by the specified class.
|
||||
* @see Class#getDeclaredField(String)
|
||||
*/
|
||||
public synchronized static Field getField(final Class<?> clazz, final String name) {
|
||||
public synchronized static Field getField(Class<?> clazz, String name) {
|
||||
Map<String, Field> loaded;
|
||||
if (!_loadedFields.containsKey(clazz)) {
|
||||
loaded = new HashMap<>();
|
||||
@ -142,7 +143,7 @@ public final class Reflection {
|
||||
return loaded.get(name);
|
||||
}
|
||||
try {
|
||||
final Field field = clazz.getDeclaredField(name);
|
||||
Field field = clazz.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
loaded.put(name, field);
|
||||
return field;
|
||||
@ -154,7 +155,7 @@ public final class Reflection {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a {@link Method} instance declared by the specified class with the specified name and argument types.
|
||||
* Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
|
||||
@ -164,7 +165,8 @@ public final class Reflection {
|
||||
* no method will be reflectively looked up twice.
|
||||
* </p>
|
||||
* <p>
|
||||
* If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
|
||||
* If a method is deemed suitable for return, {@link Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code
|
||||
* true} before it is returned.
|
||||
* This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
|
||||
* </p>
|
||||
* <p>
|
||||
@ -175,23 +177,23 @@ public final class Reflection {
|
||||
* @param args The formal argument types of the method.
|
||||
* @return A method object with the specified name declared by the specified class.
|
||||
*/
|
||||
public synchronized static Method getMethod(final Class<?> clazz, final String name, final Class<?>... args) {
|
||||
public synchronized static Method getMethod(Class<?> clazz, String name, Class<?>... args) {
|
||||
if (!_loadedMethods.containsKey(clazz)) {
|
||||
_loadedMethods.put(clazz, new HashMap<String, Map<ArrayWrapper<Class<?>>, Method>>());
|
||||
}
|
||||
|
||||
final Map<String, Map<ArrayWrapper<Class<?>>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
|
||||
|
||||
Map<String, Map<ArrayWrapper<Class<?>>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
|
||||
if (!loadedMethodNames.containsKey(name)) {
|
||||
loadedMethodNames.put(name, new HashMap<ArrayWrapper<Class<?>>, Method>());
|
||||
}
|
||||
|
||||
final Map<ArrayWrapper<Class<?>>, Method> loadedSignatures = loadedMethodNames.get(name);
|
||||
final ArrayWrapper<Class<?>> wrappedArg = new ArrayWrapper<>(args);
|
||||
|
||||
Map<ArrayWrapper<Class<?>>, Method> loadedSignatures = loadedMethodNames.get(name);
|
||||
ArrayWrapper<Class<?>> wrappedArg = new ArrayWrapper<>(args);
|
||||
if (loadedSignatures.containsKey(wrappedArg)) {
|
||||
return loadedSignatures.get(wrappedArg);
|
||||
}
|
||||
|
||||
for (final Method m : clazz.getMethods()) {
|
||||
|
||||
for (Method m : clazz.getMethods()) {
|
||||
if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
|
||||
m.setAccessible(true);
|
||||
loadedSignatures.put(wrappedArg, m);
|
||||
@ -201,5 +203,5 @@ public final class Reflection {
|
||||
loadedSignatures.put(wrappedArg, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,107 @@ public abstract class TextualComponent implements Cloneable {
|
||||
ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class);
|
||||
}
|
||||
|
||||
static TextualComponent deserialize(Map<String, Object> map) {
|
||||
if (map.containsKey("key") && (map.size() == 2) && map.containsKey("value")) {
|
||||
// Arbitrary text component
|
||||
return ArbitraryTextTypeComponent.deserialize(map);
|
||||
} else if ((map.size() >= 2) && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) {
|
||||
// Complex JSON object
|
||||
return ComplexTextTypeComponent.deserialize(map);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean isTextKey(String key) {
|
||||
return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector");
|
||||
}
|
||||
|
||||
static boolean isTranslatableText(TextualComponent component) {
|
||||
return (component instanceof ComplexTextTypeComponent) && component.getKey().equals("translate");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a string literal.
|
||||
* This is the default type of textual component when a single string literal is given to a method.
|
||||
* @param textValue The text which will be represented.
|
||||
* @return The text component representing the specified literal text.
|
||||
*/
|
||||
public static TextualComponent rawText(String textValue) {
|
||||
return new ArbitraryTextTypeComponent("text", textValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a localized string.
|
||||
* The client will see this text component as their localized version of the specified string <em>key</em>, which can be overridden by a
|
||||
* resource pack.
|
||||
* <p>
|
||||
* If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to
|
||||
* the client.
|
||||
* </p>
|
||||
* @param translateKey The string key which maps to localized text.
|
||||
* @return The text component representing the specified localized text.
|
||||
*/
|
||||
public static TextualComponent localizedText(String translateKey) {
|
||||
return new ArbitraryTextTypeComponent("translate", translateKey);
|
||||
}
|
||||
|
||||
private static void throwUnsupportedSnapshot() {
|
||||
throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a scoreboard value.
|
||||
* The client will see their own score for the specified objective as the text represented by this component.
|
||||
* <p>
|
||||
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
|
||||
* </p>
|
||||
* @param scoreboardObjective The name of the objective for which to display the score.
|
||||
* @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during
|
||||
* JSON serialization.
|
||||
*/
|
||||
public static TextualComponent objectiveScore(String scoreboardObjective) {
|
||||
return objectiveScore("*", scoreboardObjective);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a scoreboard value.
|
||||
* The client will see the score of the specified player for the specified objective as the text represented by this component.
|
||||
* <p>
|
||||
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
|
||||
* </p>
|
||||
* @param playerName The name of the player whos score will be shown. If this string represents the single-character sequence "*", the viewing
|
||||
* player's score will be displayed.
|
||||
* Standard minecraft selectors (@a, @p, etc) are <em>not</em> supported.
|
||||
* @param scoreboardObjective The name of the objective for which to display the score.
|
||||
* @return The text component representing the specified scoreboard score for the specified player, or {@code null} if an error occurs during
|
||||
* JSON serialization.
|
||||
*/
|
||||
public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) {
|
||||
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
|
||||
// OVERLOADS documentation accordingly
|
||||
|
||||
return new ComplexTextTypeComponent("score",
|
||||
ImmutableMap.<String, String>builder().put("name", playerName).put("objective", scoreboardObjective).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a player name, retrievable by using a standard minecraft selector.
|
||||
* The client will see the players or entities captured by the specified selector as the text represented by this component.
|
||||
* <p>
|
||||
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
|
||||
* </p>
|
||||
* @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in
|
||||
* the place of this text component.
|
||||
* @return The text component representing the name of the entities captured by the selector.
|
||||
*/
|
||||
public static TextualComponent selector(String selector) {
|
||||
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
|
||||
// OVERLOADS documentation accordingly
|
||||
|
||||
return new ArbitraryTextTypeComponent("selector", selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getReadableString();
|
||||
@ -51,27 +152,7 @@ public abstract class TextualComponent implements Cloneable {
|
||||
* @param writer The object to which to write the JSON data.
|
||||
* @throws IOException If an error occurs while writing to the stream.
|
||||
*/
|
||||
public abstract void writeJson(final JsonWriter writer) throws IOException;
|
||||
|
||||
static TextualComponent deserialize(final Map<String, Object> map) {
|
||||
if (map.containsKey("key") && (map.size() == 2) && map.containsKey("value")) {
|
||||
// Arbitrary text component
|
||||
return ArbitraryTextTypeComponent.deserialize(map);
|
||||
} else if ((map.size() >= 2) && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) {
|
||||
// Complex JSON object
|
||||
return ComplexTextTypeComponent.deserialize(map);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean isTextKey(final String key) {
|
||||
return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector");
|
||||
}
|
||||
|
||||
static boolean isTranslatableText(final TextualComponent component) {
|
||||
return (component instanceof ComplexTextTypeComponent) && component.getKey().equals("translate");
|
||||
}
|
||||
public abstract void writeJson(JsonWriter writer) throws IOException;
|
||||
|
||||
/**
|
||||
* Internal class used to represent all types of text components.
|
||||
@ -79,33 +160,37 @@ public abstract class TextualComponent implements Cloneable {
|
||||
*/
|
||||
private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
|
||||
|
||||
public ArbitraryTextTypeComponent(final String key, final String value) {
|
||||
private String _key;
|
||||
private String _value;
|
||||
|
||||
public ArbitraryTextTypeComponent(String key, String value) {
|
||||
setKey(key);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return _key;
|
||||
public static ArbitraryTextTypeComponent deserialize(Map<String, Object> map) {
|
||||
return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
|
||||
}
|
||||
|
||||
public void setKey(final String key) {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this._key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
Preconditions.checkArgument((key != null) && !key.isEmpty(), "The key must be specified.");
|
||||
_key = key;
|
||||
this._key = key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return _value;
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public void setValue(final String value) {
|
||||
public void setValue(String value) {
|
||||
Preconditions.checkArgument(value != null, "The value must be specified.");
|
||||
_value = value;
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
private String _key;
|
||||
private String _value;
|
||||
|
||||
@Override
|
||||
public TextualComponent clone() throws CloneNotSupportedException {
|
||||
// Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
|
||||
@ -113,7 +198,7 @@ public abstract class TextualComponent implements Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeJson(final JsonWriter writer) throws IOException {
|
||||
public void writeJson(JsonWriter writer) throws IOException {
|
||||
writer.name(getKey()).value(getValue());
|
||||
}
|
||||
|
||||
@ -128,10 +213,6 @@ public abstract class TextualComponent implements Cloneable {
|
||||
};
|
||||
}
|
||||
|
||||
public static ArbitraryTextTypeComponent deserialize(final Map<String, Object> map) {
|
||||
return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReadableString() {
|
||||
return getValue();
|
||||
@ -144,33 +225,46 @@ public abstract class TextualComponent implements Cloneable {
|
||||
*/
|
||||
private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
|
||||
|
||||
public ComplexTextTypeComponent(final String key, final Map<String, String> values) {
|
||||
private String _key;
|
||||
private Map<String, String> _value;
|
||||
|
||||
public ComplexTextTypeComponent(String key, Map<String, String> values) {
|
||||
setKey(key);
|
||||
setValue(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return _key;
|
||||
public static ComplexTextTypeComponent deserialize(Map<String, Object> map) {
|
||||
String key = null;
|
||||
Map<String, String> value = new HashMap<String, String>();
|
||||
for (Map.Entry<String, Object> valEntry : map.entrySet()) {
|
||||
if (valEntry.getKey().equals("key")) {
|
||||
key = (String) valEntry.getValue();
|
||||
} else if (valEntry.getKey().startsWith("value.")) {
|
||||
value.put(valEntry.getKey().substring(6) /* Strips out the value prefix */, valEntry.getValue().toString());
|
||||
}
|
||||
}
|
||||
return new ComplexTextTypeComponent(key, value);
|
||||
}
|
||||
|
||||
public void setKey(final String key) {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this._key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
Preconditions.checkArgument((key != null) && !key.isEmpty(), "The key must be specified.");
|
||||
_key = key;
|
||||
this._key = key;
|
||||
}
|
||||
|
||||
public Map<String, String> getValue() {
|
||||
return _value;
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public void setValue(final Map<String, String> value) {
|
||||
public void setValue(Map<String, String> value) {
|
||||
Preconditions.checkArgument(value != null, "The value must be specified.");
|
||||
_value = value;
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
private String _key;
|
||||
private Map<String, String> _value;
|
||||
|
||||
@Override
|
||||
public TextualComponent clone() throws CloneNotSupportedException {
|
||||
// Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
|
||||
@ -178,10 +272,10 @@ public abstract class TextualComponent implements Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeJson(final JsonWriter writer) throws IOException {
|
||||
public void writeJson(JsonWriter writer) throws IOException {
|
||||
writer.name(getKey());
|
||||
writer.beginObject();
|
||||
for (final Map.Entry<String, String> jsonPair : _value.entrySet()) {
|
||||
for (Map.Entry<String, String> jsonPair : this._value.entrySet()) {
|
||||
writer.name(jsonPair.getKey()).value(jsonPair.getValue());
|
||||
}
|
||||
writer.endObject();
|
||||
@ -193,101 +287,16 @@ public abstract class TextualComponent implements Cloneable {
|
||||
return new java.util.HashMap<String, Object>() {
|
||||
{
|
||||
put("key", getKey());
|
||||
for (final Map.Entry<String, String> valEntry : getValue().entrySet()) {
|
||||
for (Map.Entry<String, String> valEntry : getValue().entrySet()) {
|
||||
put("value." + valEntry.getKey(), valEntry.getValue());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ComplexTextTypeComponent deserialize(final Map<String, Object> map) {
|
||||
String key = null;
|
||||
final Map<String, String> value = new HashMap<String, String>();
|
||||
for (final Map.Entry<String, Object> valEntry : map.entrySet()) {
|
||||
if (valEntry.getKey().equals("key")) {
|
||||
key = (String) valEntry.getValue();
|
||||
} else if (valEntry.getKey().startsWith("value.")) {
|
||||
value.put(valEntry.getKey().substring(6) /* Strips out the value prefix */, valEntry.getValue().toString());
|
||||
}
|
||||
}
|
||||
return new ComplexTextTypeComponent(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReadableString() {
|
||||
return getKey();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a string literal.
|
||||
* This is the default type of textual component when a single string literal is given to a method.
|
||||
* @param textValue The text which will be represented.
|
||||
* @return The text component representing the specified literal text.
|
||||
*/
|
||||
public static TextualComponent rawText(final String textValue) {
|
||||
return new ArbitraryTextTypeComponent("text", textValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a localized string.
|
||||
* The client will see this text component as their localized version of the specified string <em>key</em>, which can be overridden by a resource pack.
|
||||
* <p>
|
||||
* If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to the client.
|
||||
* </p>
|
||||
* @param translateKey The string key which maps to localized text.
|
||||
* @return The text component representing the specified localized text.
|
||||
*/
|
||||
public static TextualComponent localizedText(final String translateKey) {
|
||||
return new ArbitraryTextTypeComponent("translate", translateKey);
|
||||
}
|
||||
|
||||
private static void throwUnsupportedSnapshot() {
|
||||
throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a scoreboard value.
|
||||
* The client will see their own score for the specified objective as the text represented by this component.
|
||||
* <p>
|
||||
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
|
||||
* </p>
|
||||
* @param scoreboardObjective The name of the objective for which to display the score.
|
||||
* @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during JSON serialization.
|
||||
*/
|
||||
public static TextualComponent objectiveScore(final String scoreboardObjective) {
|
||||
return objectiveScore("*", scoreboardObjective);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a scoreboard value.
|
||||
* The client will see the score of the specified player for the specified objective as the text represented by this component.
|
||||
* <p>
|
||||
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
|
||||
* </p>
|
||||
* @param playerName The name of the player whos score will be shown. If this string represents the single-character sequence "*", the viewing player's score will be displayed.
|
||||
* Standard minecraft selectors (@a, @p, etc) are <em>not</em> supported.
|
||||
* @param scoreboardObjective The name of the objective for which to display the score.
|
||||
* @return The text component representing the specified scoreboard score for the specified player, or {@code null} if an error occurs during JSON serialization.
|
||||
*/
|
||||
public static TextualComponent objectiveScore(final String playerName, final String scoreboardObjective) {
|
||||
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly
|
||||
|
||||
return new ComplexTextTypeComponent("score", ImmutableMap.<String, String> builder().put("name", playerName).put("objective", scoreboardObjective).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a textual component representing a player name, retrievable by using a standard minecraft selector.
|
||||
* The client will see the players or entities captured by the specified selector as the text represented by this component.
|
||||
* <p>
|
||||
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
|
||||
* </p>
|
||||
* @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in the place of this text component.
|
||||
* @return The text component representing the name of the entities captured by the selector.
|
||||
*/
|
||||
public static TextualComponent selector(final String selector) {
|
||||
throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE OVERLOADS documentation accordingly
|
||||
|
||||
return new ArbitraryTextTypeComponent("selector", selector);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user