It started on work with commands then I got carried away.

This commit is contained in:
MattBDev
2019-07-25 14:44:10 -04:00
parent 01c371df9c
commit ff5860113d
184 changed files with 1694 additions and 2580 deletions

View File

@ -5,7 +5,6 @@ import com.boydti.fawe.IFawe;
import com.boydti.fawe.beta.implementation.QueueHandler;
import com.boydti.fawe.bukkit.beta.BukkitQueue;
import com.boydti.fawe.bukkit.beta.BukkitQueueHandler;
import com.boydti.fawe.bukkit.chat.BukkitChatManager;
import com.boydti.fawe.bukkit.listener.BrushListener;
import com.boydti.fawe.bukkit.listener.BukkitImageListener;
import com.boydti.fawe.bukkit.listener.CFIPacketListener;
@ -97,11 +96,6 @@ public class FaweBukkit implements IFawe, Listener {
if (Bukkit.getVersion().contains("git-Paper") && Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING > 1) {
new RenderListener(plugin);
}
try {
Fawe.get().setChatManager(new BukkitChatManager());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} catch (final Throwable e) {
e.printStackTrace();
Bukkit.getServer().shutdown();

View File

@ -1,64 +0,0 @@
package com.boydti.fawe.bukkit.chat;
import com.boydti.fawe.bukkit.BukkitPlayer;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.util.chat.ChatManager;
import com.boydti.fawe.util.chat.Message;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.ChatColor;
public class BukkitChatManager implements ChatManager<FancyMessage> {
@Override
public FancyMessage builder() {
return new FancyMessage("");
}
@Override
public void color(Message message, String color) {
message.$(this).color(ChatColor.getByChar(BBC.color(color).substring(1)));
}
@Override
public void tooltip(Message message, Message... tooltips) {
List<FancyMessage> lines = new ArrayList<>();
for (Message tooltip : tooltips) {
lines.add(tooltip.$(this));
}
message.$(this).formattedTooltip(lines);
}
@Override
public void command(Message message, String command) {
message.$(this).command(command);
}
@Override
public void text(Message message, String text) {
message.$(this).color(BBC.color(text));
}
@Override
public void send(Message Message, FawePlayer player) {
if (!(player instanceof BukkitPlayer)) {
player.sendMessage(Message.$(this).toOldMessageFormat());
} else {
Message.$(this).send(((BukkitPlayer) player).parent);
}
}
@Override
public void suggest(Message Message, String command) {
Message.$(this).suggest(command);
}
@Override
public void link(Message Message, String url) {
Message.$(this).link(url);
}
}

View File

@ -1,874 +0,0 @@
package com.boydti.fawe.bukkit.chat;
import static com.boydti.fawe.bukkit.chat.TextualComponent.rawText;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonWriter;
import 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.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* 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, and a call to
* {@link #then()} or {@link #then(String)} will append a new editing component to the end of the
* message, optionally initializing it with text. Further property-setting method calls will affect
* that editing component.
* </p>
*/
public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<MessagePart>,
ConfigurationSerializable {
static {
ConfigurationSerialization.registerClass(FancyMessage.class);
}
private List<MessagePart> messageParts;
private int index;
private String jsonString;
private boolean dirty;
private static Constructor<?> nmsPacketPlayOutChatConstructor;
@Override
public FancyMessage clone() throws CloneNotSupportedException {
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.index = index;
instance.dirty = false;
instance.jsonString = null;
return instance;
}
/**
* Creates a JSON message with text.
*
* @param firstPartText The existing text in the message.
*/
public FancyMessage(String firstPartText) {
this(rawText(firstPartText));
}
private FancyMessage(TextualComponent firstPartText) {
messageParts = new ArrayList<>();
messageParts.add(new MessagePart(firstPartText));
index = messageParts.size();
jsonString = null;
dirty = false;
if (nmsPacketPlayOutChatConstructor == null) {
try {
nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat")
.getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
nmsPacketPlayOutChatConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
Bukkit.getLogger()
.log(Level.SEVERE, "Could not find Minecraft method or constructor.", 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);
}
/**
* 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(String text) {
MessagePart latest = latest();
latest.text = rawText(text);
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(TextualComponent text) {
MessagePart latest = latest();
latest.text = text;
dirty = true;
return this;
}
/**
* @param text Text with coloring
* @return This builder instance.
* @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not
* a color (but a format value).
*/
public FancyMessage color(String text) {
index = messageParts.size();
boolean color = false;
ArrayDeque<ChatColor> colors = new ArrayDeque<>();
int last = 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (color != (color = false)) {
ChatColor chatColor = ChatColor.getByChar(c);
if (chatColor != null) {
if (i - last > 1) {
append(text.substring(last, i - 1));
colors.forEach(this::color);
colors.clear();
}
colors.add(chatColor);
last = i + 1;
}
}
if (c == '\u00A7') {
color = true;
}
}
if (text.length() - last > 0) {
append(text.substring(last));
colors.forEach(this::color);
}
index++;
return this;
}
/**
* Sets the color of the current editing component to a value.<br /> Setting the color will
* clear current styles
*
* @param color The new color of the current editing component.
* @return This builder instance.
* @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not
* a color (but a format value).
*/
public FancyMessage color(ChatColor color) {
if (!color.isColor()) {
if (color.isFormat()) {
return style(color);
}
if (color == ChatColor.RESET) {
color = ChatColor.WHITE;
}
} else {
latest().styles.clear();
}
latest().color = color;
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.
* @throws IllegalArgumentException If any of the enumeration values in the array do not
* represent formatters.
*/
public FancyMessage style(ChatColor... styles) {
for (ChatColor style : styles) {
if (!style.isFormat()) {
color(style);
}
}
latest().styles.addAll(Arrays.asList(styles));
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.
*
* @param path The path of the file on the client filesystem.
* @return This builder instance.
*/
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.
*
* @param url The URL of the page to open when the link is clicked.
* @return This builder instance.
*/
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.
*
* @param command The text to display in the chat bar of the client.
* @return This builder instance.
*/
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.
*
* @param command The text to append to the chat bar of the client.
* @return This builder instance.
*/
public FancyMessage insert(String command) {
onCurrent(m -> m.insertionData = command);
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. 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(String command) {
onClick("run_command", command);
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(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.
* @return This builder instance.
*/
public FancyMessage tooltip(Iterable<String> lines) {
tooltip(ArrayWrapper.toArray(lines, String.class));
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.
* @return This builder instance.
*/
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) {
builder.append('\n');
}
}
tooltip(builder.toString());
return this;
}
/**
* Set the behavior of the current editing component to display formatted text when the client
* hovers over the text.
* <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(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) {
throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
}
}
onHover("show_text", text);
return this;
}
/**
* Set the behavior of the current editing component to display the specified lines of formatted
* text when the client hovers over the text.
* <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(FancyMessage... lines) {
if (lines.length < 1) {
onHover(null, null); // Clear tooltip
return this;
}
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 (MessagePart component : lines[i]) {
if (component.clickActionData != null && component.clickActionName != null) {
throw new IllegalArgumentException(
"The tooltip text cannot have click data.");
} else if (component.hoverActionData != null
&& component.hoverActionName != null) {
throw new IllegalArgumentException(
"The tooltip text cannot have a tooltip.");
}
if (component.hasText()) {
result.messageParts.add(component.clone());
result.index = result.messageParts.size();
}
}
if (i != lines.length - 1) {
result.messageParts.add(new MessagePart(rawText("\n")));
result.index = result.messageParts.size();
}
} 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.
* @return This builder instance.
*/
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.
*
* @param replacements The replacements, in order, that will be used in the language-specific
* message.
* @return This builder instance.
*/
public FancyMessage translationReplacements(String... replacements) {
for (String str : replacements) {
latest().translationReplacements.add(new JsonString(str));
}
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.
* @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;
}
*/
/**
* 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(FancyMessage... replacements) {
Collections.addAll(latest().translationReplacements, replacements);
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.
*
* @param replacements The replacements, in order, that will be used in the language-specific
* message.
* @return This builder instance.
*/
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.
*
* @param text The text which will populate the new message component.
* @return This builder instance.
*/
public FancyMessage then(String text) {
return then(rawText(text));
}
private FancyMessage append(String text) {
if (!latest().hasText()) {
throw new IllegalStateException("previous message part has no text");
}
MessagePart latest = latest();
messageParts.add(new MessagePart(rawText(text)));
latest().color = latest.color;
latest().styles.addAll(latest.styles);
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.
*
* @param text The text which will populate the new message component.
* @return This builder instance.
*/
public FancyMessage then(TextualComponent text) {
if (!latest().hasText()) {
throw new IllegalStateException("previous message part has no text");
}
messageParts.add(new MessagePart(text));
index = messageParts.size();
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.
*
* @return This builder instance.
*/
public FancyMessage then() {
if (!latest().hasText()) {
throw new IllegalStateException("previous message part has no text");
}
messageParts.add(new MessagePart());
index = messageParts.size();
dirty = true;
return this;
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
if (messageParts.size() == 1) {
latest().writeJson(writer);
} else {
writer.beginObject().name("text").value("").name("extra").beginArray();
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;
}
StringWriter string = new StringWriter();
JsonWriter json = new JsonWriter(string);
try {
writeJson(json);
json.close();
} catch (IOException e) {
throw new RuntimeException("invalid message");
}
jsonString = string.toString();
dirty = false;
return 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(Player player) {
send(player, toJSONString());
}
private void send(CommandSender sender, String jsonString) {
if (!(sender instanceof Player)) {
sender.sendMessage(toOldMessageFormat());
return;
}
Player player = (Player) sender;
try {
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 (IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
} catch (InstantiationException e) {
Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
} catch (InvocationTargetException e) {
Bukkit.getLogger()
.log(Level.WARNING, "A error has occurred during invoking of method.", e);
} catch (NoSuchMethodException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
} catch (ClassNotFoundException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
}
}
// The ChatSerializer's instance of Gson
private static Object nmsChatSerializerGsonInstance;
private static Method fromJsonMethod;
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;
// Get the three parts of the version string (major version is currently unused)
// vX_Y_RZ
// X = major
// Y = minor
// Z = revision
final String version = Reflection.getVersion();
String[] split = version.substring(1, version.length() - 1)
.split("_"); // Remove trailing dot
//int majorVersion = Integer.parseInt(split[0]);
int minorVersion = Integer.parseInt(split[1]);
int revisionVersion = Integer.parseInt(split[2].substring(1)); // Substring to ignore R
if (minorVersion < 8 || minorVersion == 8 && revisionVersion == 1) {
chatSerializerClazz = Reflection.getNMSClass("ChatSerializer");
} else {
chatSerializerClazz = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
}
if (chatSerializerClazz == null) {
throw new ClassNotFoundException("Can't find the ChatSerializer class");
}
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);
fromJsonMethod = nmsChatSerializerGsonInstance.getClass()
.getMethod("fromJson", String.class, Class.class);
break;
}
}
}
// 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
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. Otherwise, they will receive a version of
* this message with less formatting.
*
* @param sender The command sender who will receive the message.
* @see #toOldMessageFormat()
*/
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(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.
* <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>
* <li>The applicable stylizations for each message part.</li>
* <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.
*/
public String toOldMessageFormat() {
StringBuilder result = new StringBuilder();
for (MessagePart part : this) {
result.append(part.color == null ? "" : part.color);
for (ChatColor formatSpecifier : part.styles) {
result.append(formatSpecifier);
}
result.append(part.text);
}
return result.toString();
}
private void onCurrent(Consumer<MessagePart> call) {
for (int i = index - 1; i < messageParts.size(); i++) {
call.accept(messageParts.get(i));
}
}
private MessagePart latest() {
return messageParts.get(messageParts.size() - 1);
}
private void onClick(String name, String data) {
onCurrent(m -> {
m.clickActionName = name;
m.clickActionData = data;
});
dirty = true;
}
private void onHover(String name, JsonRepresentedObject data) {
onCurrent(m -> {
m.hoverActionName = name;
m.hoverActionData = data;
});
dirty = true;
}
// Doc copied from interface
@Override
public Map<String, Object> serialize() {
HashMap<String, Object> map = new HashMap<>();
map.put("messageParts", messageParts);
// map.put("JSON", toJSONString());
return map;
}
/**
* Deserializes 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(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");
return msg;
}
/**
* <b>Internally called method. Not for API consumption.</b>
*/
@Override
@NotNull
public Iterator<MessagePart> iterator() {
return messageParts.iterator();
}
private static JsonParser _stringParser = new JsonParser();
/**
* Deserializes 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 parameterized JSON message.
*/
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 (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
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 (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")) {
JsonObject object = entry.getValue().getAsJsonObject();
component.clickActionName = object.get("action").getAsString();
component.clickActionData = object.get("value").getAsString();
} else if (entry.getKey().equals("hoverEvent")) {
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 (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);
returnVal.index = returnVal.messageParts.size();
}
return returnVal;
}
}

View File

@ -1,156 +0,0 @@
package com.boydti.fawe.bukkit.chat;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
/**
* Internal class: Represents a component of a JSON-serializable {@link FancyMessage}.
*/
final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable {
ChatColor color = ChatColor.WHITE;
ArrayList<ChatColor> styles = new ArrayList<>();
String clickActionName = null;
String clickActionData = null;
String hoverActionName = null;
JsonRepresentedObject hoverActionData = null;
TextualComponent text = null;
String insertionData = null;
ArrayList<JsonRepresentedObject> translationReplacements = new ArrayList<>();
MessagePart(final TextualComponent text) {
this.text = text;
}
MessagePart() {
this.text = null;
}
boolean hasText() {
return text != null;
}
@Override
@SuppressWarnings("unchecked")
public MessagePart clone() throws CloneNotSupportedException {
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();
}
obj.translationReplacements = (ArrayList<JsonRepresentedObject>) translationReplacements.clone();
return obj;
}
static final BiMap<ChatColor, String> stylesToNames;
static {
ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
for (final ChatColor style : ChatColor.values()) {
if (!style.isFormat()) {
continue;
}
String styleName;
switch (style) {
case MAGIC:
styleName = "obfuscated";
break;
case UNDERLINE:
styleName = "underlined";
break;
default:
styleName = style.name().toLowerCase();
break;
}
builder.put(style, styleName);
}
stylesToNames = builder.build();
}
public void writeJson(JsonWriter json) {
try {
json.beginObject();
text.writeJson(json);
json.name("color").value(color.name().toLowerCase());
for (final ChatColor style : 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 (hoverActionName != null && hoverActionData != null) {
json.name("hoverEvent")
.beginObject()
.name("action").value(hoverActionName)
.name("value");
hoverActionData.writeJson(json);
json.endObject();
}
if (insertionData != null) {
json.name("insertion").value(insertionData);
}
if (translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) {
json.name("with").beginArray();
for (JsonRepresentedObject obj : translationReplacements) {
obj.writeJson(json);
}
json.endArray();
}
json.endObject();
} catch (IOException e) {
Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e);
}
}
public Map<String, Object> serialize() {
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);
return map;
}
@SuppressWarnings("unchecked")
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");
part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData");
part.clickActionName = (String) serialized.get("clickActionName");
part.clickActionData = (String) serialized.get("clickActionData");
part.insertionData = (String) serialized.get("insertion");
part.translationReplacements = (ArrayList<JsonRepresentedObject>) serialized.get("translationReplacements");
return part;
}
static {
ConfigurationSerialization.registerClass(MessagePart.class);
}
}

View File

@ -26,7 +26,9 @@ import com.sk89q.worldedit.event.platform.Interaction;
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.formatting.text.TextComponent.Builder;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.List;
import com.sk89q.worldedit.world.block.BlockStateHolder;
@ -59,7 +61,7 @@ public class CFIPacketListener implements Listener {
// Direct digging to the virtual world
registerBlockEvent(PacketType.Play.Client.BLOCK_DIG, false, new RunnableVal3<PacketEvent, VirtualWorld, BlockVector3>() {
@Override
public void run(PacketEvent event, VirtualWorld gen, BlockVector3 pt) {
public void run(Builder event, URI gen, String pt) {
try {
Player plr = event.getPlayer();
BlockVector3 realPos = pt.add(gen.getOrigin().toBlockPoint());
@ -75,7 +77,7 @@ public class CFIPacketListener implements Listener {
// Direct placing to the virtual world
RunnableVal3<PacketEvent, VirtualWorld, BlockVector3> placeTask = new RunnableVal3<PacketEvent, VirtualWorld, BlockVector3>() {
@Override
public void run(PacketEvent event, VirtualWorld gen, BlockVector3 pt) {
public void run(Builder event, URI gen, String pt) {
try {
Player plr = event.getPlayer();
List<EnumWrappers.Hand> hands = event.getPacket().getHands().getValues();
@ -112,7 +114,7 @@ public class CFIPacketListener implements Listener {
// Cancel block change packets where the real world overlaps with the virtual one
registerBlockEvent(PacketType.Play.Server.BLOCK_CHANGE, false, new RunnableVal3<PacketEvent, VirtualWorld, BlockVector3>() {
@Override
public void run(PacketEvent event, VirtualWorld gen, BlockVector3 pt) {
public void run(Builder event, URI gen, String pt) {
// Do nothing
}
});

View File

@ -3,7 +3,7 @@ package com.boydti.fawe.bukkit.regions;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.regions.FaweMask;
import com.boydti.fawe.util.Perm;
import com.boydti.fawe.util.Permission;
import com.massivecraft.factions.FLocation;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import org.bukkit.Chunk;
@ -30,7 +30,8 @@ public class FactionsOneFeature extends BukkitMaskManager implements Listener {
public FaweMask getMask(final FawePlayer<Player> fp, MaskType type) {
final Player player = fp.parent;
final Chunk chunk = player.getLocation().getChunk();
final boolean perm = Perm.hasPermission(FawePlayer.wrap(player), "fawe.factions.wilderness");
final boolean perm = Permission
.hasPermission(fp.toWorldEditPlayer(), "fawe.factions.wilderness");
final World world = player.getWorld();
RegionWrapper locs = new RegionWrapper(chunk.getX(), chunk.getX(), chunk.getZ(), chunk.getZ());
@ -40,7 +41,7 @@ public class FactionsOneFeature extends BukkitMaskManager implements Listener {
if (this.isAdded(locs, world, player, perm, type)) {
boolean hasPerm = true;
while (hasPerm && (count > 0)) {
while (hasPerm && count > 0) {
count--;
hasPerm = false;

View File

@ -4,7 +4,7 @@ import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.regions.FaweMask;
import com.boydti.fawe.util.Perm;
import com.boydti.fawe.util.Permission;
import com.massivecraft.factions.Board;
import com.massivecraft.factions.FLocation;
import com.massivecraft.factions.Faction;
@ -28,7 +28,8 @@ public class FactionsUUIDFeature extends BukkitMaskManager implements Listener {
public FaweMask getMask(final FawePlayer<Player> fp, MaskType type) {
final Player player = fp.parent;
final Chunk chunk = player.getLocation().getChunk();
final boolean perm = Perm.hasPermission(FawePlayer.wrap(player), "fawe.factions.wilderness");
final boolean perm = Permission
.hasPermission(fp.toWorldEditPlayer(), "fawe.factions.wilderness");
final World world = player.getWorld();
RegionWrapper locs = new RegionWrapper(chunk.getX(), chunk.getX(), chunk.getZ(), chunk.getZ());
@ -38,7 +39,7 @@ public class FactionsUUIDFeature extends BukkitMaskManager implements Listener {
if (this.isAdded(locs, world, player, perm, type)) {
boolean hasPerm = true;
while (hasPerm && (count > 0)) {
while (hasPerm && count > 0) {
count--;
hasPerm = false;

View File

@ -1,15 +1,15 @@
package com.boydti.fawe.bukkit.wrapper.state;
import com.boydti.fawe.bukkit.chat.FancyMessage;
import com.boydti.fawe.bukkit.wrapper.AsyncBlock;
import com.boydti.fawe.bukkit.wrapper.AsyncBlockState;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer;
import com.sk89q.worldedit.util.formatting.text.serializer.legacy.LegacyComponentSerializer;
import java.util.Map;
import net.minecraft.server.v1_14_R1.TileEntitySign;
import org.bukkit.DyeColor;
import org.bukkit.block.Sign;
import org.bukkit.persistence.PersistentDataContainer;
@ -37,18 +37,18 @@ public class AsyncSign extends AsyncBlockState implements Sign {
private String fromJson(String jsonInput) {
if (jsonInput == null || jsonInput.isEmpty()) return "";
return FancyMessage.deserialize(jsonInput).toOldMessageFormat();
return GsonComponentSerializer.INSTANCE.deserialize(jsonInput).toString();
}
private String toJson(String oldInput) {
if (oldInput == null || oldInput.isEmpty()) return "";
return new FancyMessage("").color(oldInput).toJSONString();
return LegacyComponentSerializer.INSTANCE.serialize(TextComponent.of(oldInput));
}
@Override
public String getLine(int index) throws IndexOutOfBoundsException {
CompoundTag nbt = getNbtData();
return nbt == null ? null : fromJson(nbt.getString("Text" + (index + 1)));
return nbt == null ? "" : fromJson(nbt.getString("Text" + (index + 1)));
}
@Override
@ -80,7 +80,7 @@ public class AsyncSign extends AsyncBlockState implements Sign {
CompoundTag nbt = getNbtData();
if (nbt != null) {
String color = nbt.getString("Color").toUpperCase();
if (color != null) return DyeColor.valueOf(color);
return DyeColor.valueOf(color);
}
return DyeColor.BLACK;
}