From 7f454866002503a843c6125dff2ac36945bd8c9c Mon Sep 17 00:00:00 2001 From: ulumulu1510 Date: Wed, 8 Apr 2015 06:38:01 +0200 Subject: [PATCH] Implement Message Json and sendRaw methods --- .../adapter/LowercaseEnumAdapter.java | 98 ++ .../massivecore/cmd/MassiveCommand.java | 40 + .../cmd/MassiveCoreBukkitCommand.java | 2 +- .../massivecore/mixin/MessageMixin.java | 24 +- .../mixin/MessageMixinAbstract.java | 56 +- .../mixin/MessageMixinDefault.java | 55 ++ .../massivecraft/massivecore/mixin/Mixin.java | 43 + .../massivecore/mixin/TitleMixinDefault.java | 6 +- .../massivecraft/massivecore/mson/ATest.java | 108 +++ .../massivecraft/massivecore/mson/Mson.java | 897 ++++++++++++++++++ .../massivecore/mson/MsonEvent.java | 129 +++ .../massivecore/mson/MsonEventAction.java | 44 + .../massivecore/store/SenderEntity.java | 18 + .../massivecore/util/PacketUtil.java | 272 ++++++ .../massivecore/util/TitleUtil.java | 206 ---- 15 files changed, 1780 insertions(+), 218 deletions(-) create mode 100644 src/com/massivecraft/massivecore/adapter/LowercaseEnumAdapter.java create mode 100644 src/com/massivecraft/massivecore/mson/ATest.java create mode 100644 src/com/massivecraft/massivecore/mson/Mson.java create mode 100644 src/com/massivecraft/massivecore/mson/MsonEvent.java create mode 100644 src/com/massivecraft/massivecore/mson/MsonEventAction.java create mode 100644 src/com/massivecraft/massivecore/util/PacketUtil.java delete mode 100644 src/com/massivecraft/massivecore/util/TitleUtil.java diff --git a/src/com/massivecraft/massivecore/adapter/LowercaseEnumAdapter.java b/src/com/massivecraft/massivecore/adapter/LowercaseEnumAdapter.java new file mode 100644 index 00000000..89d7fc94 --- /dev/null +++ b/src/com/massivecraft/massivecore/adapter/LowercaseEnumAdapter.java @@ -0,0 +1,98 @@ +package com.massivecraft.massivecore.adapter; + +import java.lang.reflect.Type; + +import com.massivecraft.massivecore.xlib.gson.JsonDeserializationContext; +import com.massivecraft.massivecore.xlib.gson.JsonDeserializer; +import com.massivecraft.massivecore.xlib.gson.JsonElement; +import com.massivecraft.massivecore.xlib.gson.JsonNull; +import com.massivecraft.massivecore.xlib.gson.JsonParseException; +import com.massivecraft.massivecore.xlib.gson.JsonPrimitive; +import com.massivecraft.massivecore.xlib.gson.JsonSerializationContext; +import com.massivecraft.massivecore.xlib.gson.JsonSerializer; + +public class LowercaseEnumAdapter> implements JsonDeserializer, JsonSerializer +{ + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + protected final Class clazz; + public Class getClazz() { return this.clazz; } + + // -------------------------------------------- // + // INSTANCE & CONSTRUCT + // -------------------------------------------- // + + public static > LowercaseEnumAdapter get(Class clazz) + { + return new LowercaseEnumAdapter(clazz); + } + + public LowercaseEnumAdapter(Class clazz) + { + if (clazz == null) throw new IllegalArgumentException("passed clazz param is null"); + if ( ! clazz.isEnum()) throw new IllegalArgumentException("passed clazz param must be an enum"); + this.clazz = clazz; + } + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) + { + if (src == null) return JsonNull.INSTANCE; + return new JsonPrimitive(src.name().toLowerCase()); + } + + @Override + public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException + { + if (json == null) return null; + T value = getEnumValueFrom(json); + return value; + } + + // -------------------------------------------- // + // UTIL + // -------------------------------------------- // + + public static T[] getEnumValues(Class clazz) + { + if (clazz == null) throw new IllegalArgumentException("passed clazz param is null"); + if ( ! clazz.isEnum()) throw new IllegalArgumentException("passed clazz param must be an enum"); + + T[] ret = clazz.getEnumConstants(); + if (ret == null) throw new RuntimeException("failed to retrieve enum constants at runtime"); + + return ret; + } + + public static String getComparable(Enum value) + { + if (value == null) return null; + return getComparable(value.name()); + } + + public static String getComparable(String string) + { + if (string == null) return null; + return string.toLowerCase(); + } + + public T getEnumValueFrom(JsonElement json) + { + String jsonString = json.getAsString(); + jsonString = getComparable(jsonString); + + for (T value : getEnumValues(clazz)) + { + if (getComparable(value).equals(jsonString)) return value; + } + + return null; + } + +} diff --git a/src/com/massivecraft/massivecore/cmd/MassiveCommand.java b/src/com/massivecraft/massivecore/cmd/MassiveCommand.java index 775ad075..a696dde9 100644 --- a/src/com/massivecraft/massivecore/cmd/MassiveCommand.java +++ b/src/com/massivecraft/massivecore/cmd/MassiveCommand.java @@ -24,6 +24,7 @@ import com.massivecraft.massivecore.cmd.req.Req; import com.massivecraft.massivecore.cmd.req.ReqHasPerm; import com.massivecraft.massivecore.collections.MassiveList; import com.massivecraft.massivecore.mixin.Mixin; +import com.massivecraft.massivecore.mson.Mson; import com.massivecraft.massivecore.util.PermUtil; import com.massivecraft.massivecore.util.Txt; @@ -1093,6 +1094,45 @@ public class MassiveCommand return Mixin.msgOne(this.sender, msgs); } + // CONVENIENCE RAW + + public boolean sendRaw(Mson mson) + { + return Mixin.messageRawOne(this.sender, mson); + } + + public boolean sendRaw(Mson... mson) + { + return Mixin.messageRawOne(this.sender, mson); + } + + public boolean sendRaw(Collection mson) + { + return Mixin.messageRawOne(this.sender, mson); + } + + // CONVENIENCE MSON + + public Mson mson() + { + return Mson.mson(); + } + + public Mson mson(Object... parts) + { + return Mson.mson(parts); + } + + public List msons(Object... parts) + { + return Mson.msons(parts); + } + + public List msons(Collection parts) + { + return Mson.msons(parts); + } + // -------------------------------------------- // // ARGUMENT READERS // -------------------------------------------- // diff --git a/src/com/massivecraft/massivecore/cmd/MassiveCoreBukkitCommand.java b/src/com/massivecraft/massivecore/cmd/MassiveCoreBukkitCommand.java index 14a5db24..f0f01221 100644 --- a/src/com/massivecraft/massivecore/cmd/MassiveCoreBukkitCommand.java +++ b/src/com/massivecraft/massivecore/cmd/MassiveCoreBukkitCommand.java @@ -35,7 +35,7 @@ public class MassiveCoreBukkitCommand extends Command implements PluginIdentifia name, massiveCommand.getDesc(), massiveCommand.getUseageTemplate(), - new ArrayList() // We don't use aliases + Collections.emptyList() // We don't use aliases ); this.massiveCommand = massiveCommand; } diff --git a/src/com/massivecraft/massivecore/mixin/MessageMixin.java b/src/com/massivecraft/massivecore/mixin/MessageMixin.java index 2a04f108..b2a5a998 100644 --- a/src/com/massivecraft/massivecore/mixin/MessageMixin.java +++ b/src/com/massivecraft/massivecore/mixin/MessageMixin.java @@ -5,11 +5,12 @@ import java.util.Collection; import org.bukkit.command.CommandSender; import com.massivecraft.massivecore.Predictate; +import com.massivecraft.massivecore.mson.Mson; public interface MessageMixin { // -------------------------------------------- // - // RAW MESSAGE + // MESSAGE // -------------------------------------------- // // All @@ -28,7 +29,7 @@ public interface MessageMixin public boolean messageOne(Object sendeeObject, Collection messages); // -------------------------------------------- // - // PARSE MESSAGE + // PARSE MSG // -------------------------------------------- // // All @@ -46,4 +47,23 @@ public interface MessageMixin public boolean msgOne(Object sendeeObject, String msg, Object... args); public boolean msgOne(Object sendeeObject, Collection msgs); + // -------------------------------------------- // + // RAW MESSAGE + // -------------------------------------------- // + + // All + public boolean messageRawAll(Mson mson); + public boolean messageRawAll(Mson... msons); + public boolean messageRawAll(Collection msons); + + // Predictate + public boolean messageRawPredictate(Predictate predictate, Mson mson); + public boolean messageRawPredictate(Predictate predictate, Mson... msons); + public boolean messageRawPredictate(Predictate predictate, Collection msons); + + // One + public boolean messageRawOne(Object sendeeObject, Mson mson); + public boolean messageRawOne(Object sendeeObject, Mson... msons); + public boolean messageRawOne(Object sendeeObject, Collection msons); + } diff --git a/src/com/massivecraft/massivecore/mixin/MessageMixinAbstract.java b/src/com/massivecraft/massivecore/mixin/MessageMixinAbstract.java index 3014ed56..1d3af601 100644 --- a/src/com/massivecraft/massivecore/mixin/MessageMixinAbstract.java +++ b/src/com/massivecraft/massivecore/mixin/MessageMixinAbstract.java @@ -2,24 +2,25 @@ package com.massivecraft.massivecore.mixin; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import org.bukkit.command.CommandSender; import com.massivecraft.massivecore.Predictate; -import com.massivecraft.massivecore.util.MUtil; +import com.massivecraft.massivecore.mson.Mson; import com.massivecraft.massivecore.util.Txt; public abstract class MessageMixinAbstract implements MessageMixin { // -------------------------------------------- // - // RAW MESSAGE + // MESSAGE // -------------------------------------------- // // All @Override public boolean messageAll(String message) { - return this.messageAll(Arrays.asList(message)); + return this.messageAll(Collections.singleton(message)); } @Override @@ -32,7 +33,7 @@ public abstract class MessageMixinAbstract implements MessageMixin @Override public boolean messagePredictate(Predictate predictate, String message) { - return this.messagePredictate(predictate, MUtil.list(message)); + return this.messagePredictate(predictate, Collections.singleton(message)); } @Override @@ -45,7 +46,7 @@ public abstract class MessageMixinAbstract implements MessageMixin @Override public boolean messageOne(Object sendeeObject, String message) { - return this.messageOne(sendeeObject, MUtil.list(message)); + return this.messageOne(sendeeObject, Collections.singleton(message)); } @Override @@ -55,7 +56,7 @@ public abstract class MessageMixinAbstract implements MessageMixin } // -------------------------------------------- // - // PARSE MESSAGE + // PARSE MSG // -------------------------------------------- // // They are all in abstract! @@ -116,4 +117,47 @@ public abstract class MessageMixinAbstract implements MessageMixin return this.messageOne(sendeeObject, Txt.parse(msgs)); } + // -------------------------------------------- // + // RAW MESSAGE + // -------------------------------------------- // + + // All + @Override + public boolean messageRawAll(Mson mson) + { + return this.messageRawAll(Arrays.asList(mson)); + } + + @Override + public boolean messageRawAll(Mson... msons) + { + return this.messageRawAll(Arrays.asList(msons)); + } + + // Predictate + @Override + public boolean messageRawPredictate(Predictate predictate, Mson mson) + { + return this.messageRawPredictate(predictate, Arrays.asList(mson)); + } + + @Override + public boolean messageRawPredictate(Predictate predictate, Mson... msons) + { + return this.messageRawPredictate(predictate, Arrays.asList(msons)); + } + + // One + @Override + public boolean messageRawOne(Object sendeeObject, Mson mson) + { + return this.messageRawOne(sendeeObject, Arrays.asList(mson)); + } + + @Override + public boolean messageRawOne(Object sendeeObject, Mson... msons) + { + return this.messageRawOne(sendeeObject, Arrays.asList(msons)); + } + } diff --git a/src/com/massivecraft/massivecore/mixin/MessageMixinDefault.java b/src/com/massivecraft/massivecore/mixin/MessageMixinDefault.java index bce165fc..89f6f05c 100644 --- a/src/com/massivecraft/massivecore/mixin/MessageMixinDefault.java +++ b/src/com/massivecraft/massivecore/mixin/MessageMixinDefault.java @@ -3,9 +3,12 @@ package com.massivecraft.massivecore.mixin; import java.util.Collection; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import com.massivecraft.massivecore.Predictate; +import com.massivecraft.massivecore.mson.Mson; import com.massivecraft.massivecore.util.IdUtil; +import com.massivecraft.massivecore.util.PacketUtil; public class MessageMixinDefault extends MessageMixinAbstract { @@ -53,5 +56,57 @@ public class MessageMixinDefault extends MessageMixinAbstract sendee.sendMessage(messages.toArray(new String[0])); return true; } + + // Raw message aka. JsonString + @Override + public boolean messageRawAll(Collection msons) + { + if (msons == null) return false; + for (CommandSender sender : IdUtil.getOnlineSenders()) + { + this.messageRawOne(sender, msons); + } + return true; + } + + @Override + public boolean messageRawPredictate(Predictate predictate, Collection msons) + { + if (predictate == null) return false; + if (msons == null) return false; + for (CommandSender sender : IdUtil.getOnlineSenders()) + { + if ( ! predictate.apply(sender)) continue; + this.messageRawOne(sender, msons); + } + return true; + } + + @Override + public boolean messageRawOne(Object sendeeObject, Collection msons) + { + if (msons == null) return false; + + CommandSender sender = IdUtil.getSender(sendeeObject); + if (sender == null) return false; + + if (sender instanceof Player && PacketUtil.isRawAvailable()) + { + Player player = (Player) sender; + for (Mson mson : msons) + { + PacketUtil.sendRaw(player, mson.toRaw()); + } + } + else + { + for (Mson mson : msons) + { + sender.sendMessage(mson.toPlain()); + } + } + + return true; + } } diff --git a/src/com/massivecraft/massivecore/mixin/Mixin.java b/src/com/massivecraft/massivecore/mixin/Mixin.java index b015afe9..9c4f127f 100644 --- a/src/com/massivecraft/massivecore/mixin/Mixin.java +++ b/src/com/massivecraft/massivecore/mixin/Mixin.java @@ -14,6 +14,7 @@ import org.bukkit.permissions.Permissible; import com.massivecraft.massivecore.Predictate; import com.massivecraft.massivecore.event.EventMassiveCorePlayerLeave; +import com.massivecraft.massivecore.mson.Mson; import com.massivecraft.massivecore.ps.PS; import com.massivecraft.massivecore.teleport.Destination; @@ -312,6 +313,48 @@ public class Mixin return getMessageMixin().msgOne(senderObject, msgs); } + // RAW All + public static boolean messageRawAll(Mson mson) + { + return getMessageMixin().messageRawAll(mson); + } + public static boolean messageRawAll(Mson... msons) + { + return getMessageMixin().messageRawAll(msons); + } + public static boolean messageRawAll(Collection msons) + { + return getMessageMixin().messageRawAll(msons); + } + + // RAW Predictate + public static boolean messageRawPredictate(Predictate predictate, Mson mson) + { + return getMessageMixin().messageRawPredictate(predictate, mson); + } + public static boolean messageRawPredictate(Predictate predictate, Mson... msons) + { + return getMessageMixin().messageRawPredictate(predictate, msons); + } + public static boolean messageRawPredictate(Predictate predictate, Collection msons) + { + return getMessageMixin().messageRawPredictate(predictate, msons); + } + + // RAW One + public static boolean messageRawOne(Object senderObject, Mson mson) + { + return getMessageMixin().messageRawOne(senderObject, mson); + } + public static boolean messageRawOne(Object senderObject, Mson... msons) + { + return getMessageMixin().messageRawOne(senderObject, msons); + } + public static boolean messageRawOne(Object senderObject, Collection msons) + { + return getMessageMixin().messageRawOne(senderObject, msons); + } + // -------------------------------------------- // // STATIC EXPOSE: TITLE // -------------------------------------------- // diff --git a/src/com/massivecraft/massivecore/mixin/TitleMixinDefault.java b/src/com/massivecraft/massivecore/mixin/TitleMixinDefault.java index 2585496c..ba8c395c 100644 --- a/src/com/massivecraft/massivecore/mixin/TitleMixinDefault.java +++ b/src/com/massivecraft/massivecore/mixin/TitleMixinDefault.java @@ -3,7 +3,7 @@ package com.massivecraft.massivecore.mixin; import org.bukkit.entity.Player; import com.massivecraft.massivecore.util.IdUtil; -import com.massivecraft.massivecore.util.TitleUtil; +import com.massivecraft.massivecore.util.PacketUtil; public class TitleMixinDefault extends TitleMixinAbstract { @@ -29,13 +29,13 @@ public class TitleMixinDefault extends TitleMixinAbstract if (titleSub == null) titleSub = ""; if (titleMain == null) titleMain = ""; - return TitleUtil.sendTitle(player, ticksIn, ticksStay, ticksOut, titleMain, titleSub); + return PacketUtil.sendTitle(player, ticksIn, ticksStay, ticksOut, titleMain, titleSub); } @Override public boolean isTitlesAvailable() { - return TitleUtil.isAvailable(); + return PacketUtil.isTitleAvailable(); } } diff --git a/src/com/massivecraft/massivecore/mson/ATest.java b/src/com/massivecraft/massivecore/mson/ATest.java new file mode 100644 index 00000000..6307a3a8 --- /dev/null +++ b/src/com/massivecraft/massivecore/mson/ATest.java @@ -0,0 +1,108 @@ +package com.massivecraft.massivecore.mson; + +import static com.massivecraft.massivecore.mson.Mson.mson; + +import java.util.List; + +import org.bukkit.ChatColor; + +import com.massivecraft.massivecore.collections.MassiveList; +import com.massivecraft.massivecore.util.Txt; + +public class ATest +{ + public static void main(String[] args) + { + try + { + // Test getMsonFrom + Mson ofMson = mson(new Mson().text("hello")); + System.out.println("ofMson:" + ofMson); // Success + + Mson ofString = mson("hello"); + System.out.println("ofString:" + ofString); // Success + + Mson ofCollection = mson(new MassiveList("hello ", "you!")); + System.out.println("ofCollection:" + ofCollection); // Success + + // Test children + Mson child = mson("test").color(ChatColor.BLUE) + .addChild(" test2").link("www.massivecraft.com") + .addChild(" test3 ") + .addChildren("this ", "is only ", "one way to do it!") + .root() + .tooltip("Holy moly!"); + System.out.println("child:" + child.root()); // Success + + // Test siblings + Mson sibling = mson( + "test", + " test2", + " test3", + new Mson().text(" Test4, children: ").addChild("Child1 ") + .addSiblings("Sibling 1 ", "Sibling 2 ", "Sibling 3 ") + .addSibling("Sibling 4").root() + ).tooltip("Holy moly!"); + System.out.println("sibling:" + sibling.root()); // Success + + // Test fromParsedMessage + Mson parsed = Mson.fromParsedMessage(Txt.parse("white info bad green")); + System.out.println("parsed:" + parsed); // Success + + Mson parsed2 = Mson.parse("white info bad green"); + System.out.println("parsed2:" + parsed2.toRaw()); // Success + + Mson parseFormat = Mson.parse("insert here %s whatever you %s could wish for!", "!1337!", "herpi derp"); + System.out.println("parseFormat:" + parseFormat); // Success + + // Test format + Mson format = Mson.format("Just a %s simple string! :)", "very"); + System.out.println("format:" + format); // Success + + // Test split + List split = format.split(Txt.REGEX_WHITESPACE.toString()); + System.out.println("split:" + mson(split)); // Success + + /* + * Test replace + */ + + Mson charr = mson("1 2 3 4 5 6 1 7 tests").addChild(" 01010101").root().replace('1', '0'); + System.out.println("char:" + charr); // Success + + Mson sequence = mson("1 2 3 4 5 6 1 7 tests").addChild(" 01010101").root().replace("1", "0"); + System.out.println("sequence:" + sequence); // Success + + Mson regex = mson("1 2 3 4 5 6 1 7 tests").addChild(" 01010101").root().replaceAll("1", "0"); + System.out.println("regex:" + regex); // Success + + //Mson replaceFirst = Mson.of("1 2 3 4 5 6 1 7 tests").addChild(" 01010101").getRoot().replaceFirst("1", "0"); + //System.out.println("replaceFirst:" + replaceFirst.toRaw()); // Success + + /* + * Test special replaceAll + */ + + // replace string mson + Mson replaceAll1 = mson("1 2 3 4 5 6 1 7 tests").color(ChatColor.BLUE).addChild(" 1+1+1").addChild("herpiderp").root().replaceAll("1", mson("0")); + System.out.println("replaceAll1:" + replaceAll1.root()); // Success + + Mson overload = mson("hello").addChild("hello").addChild("hello").root().replaceAll("hello", mson("lol")); + System.out.println("overload:" + overload.root()); // Success + + Mson toReplace = new Mson().text("hallo"); + + // replace mson mson + Mson replaceAll2 = mson("1 2 3 4 5 6 7 tests").addChild(toReplace).addSibling(" miep").root().replaceAll(toReplace, mson("tests")); + System.out.println("replaceAll2:" + replaceAll2.root()); // Success + + Mson overload2 = mson("1 2 3 4 5 6 7 tests").addChild("hallo").addChild("hallo").addChild("hallo").addSibling(" miep").root().replaceAll(mson("hallo"), mson("tests")); + System.out.println("overload2:" + overload2.root()); // Success + } + catch(Throwable t) + { + t.printStackTrace(); + } + } + +} diff --git a/src/com/massivecraft/massivecore/mson/Mson.java b/src/com/massivecraft/massivecore/mson/Mson.java new file mode 100644 index 00000000..c0c6ba74 --- /dev/null +++ b/src/com/massivecraft/massivecore/mson/Mson.java @@ -0,0 +1,897 @@ +package com.massivecraft.massivecore.mson; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.regex.Pattern; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import com.massivecraft.massivecore.Predictate; +import com.massivecraft.massivecore.adapter.LowercaseEnumAdapter; +import com.massivecraft.massivecore.collections.MassiveList; +import com.massivecraft.massivecore.mixin.Mixin; +import com.massivecraft.massivecore.util.MUtil; +import com.massivecraft.massivecore.util.Txt; +import com.massivecraft.massivecore.xlib.gson.Gson; +import com.massivecraft.massivecore.xlib.gson.GsonBuilder; +import com.massivecraft.massivecore.xlib.gson.JsonElement; + +public class Mson implements Serializable +{ + // -------------------------------------------- // + // CONSTANTS + // -------------------------------------------- // + + private static final long serialVersionUID = 1L; + + public static final Pattern PARSE_PREFIX = Pattern.compile("\u00A7"); + + // -------------------------------------------- // + // GSON + // -------------------------------------------- // + + public static final Gson GSON = new GsonBuilder() + .disableHtmlEscaping() + .registerTypeAdapter(ChatColor.class, LowercaseEnumAdapter.get(ChatColor.class)) + .registerTypeAdapter(MsonEventAction.class, LowercaseEnumAdapter.get(MsonEventAction.class)) + .create(); + + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + // FIELD: The Msons text + // A parents text can't be null, then Mojang throws an exception. + // It does not make sense for something which doesn't have extras + // to not have text, because then it doesn't show up at all. + protected String text = ""; + public String text() { return this.text; } + public Mson text(String text) { this.text = Objects.requireNonNull(text); return this; } + public boolean textHas() { return ! this.text().isEmpty(); } + + // FIELD: Color of the mson + protected ChatColor color = null; + public ChatColor color() { return this.color; } + public ChatColor colorEffective() { return color() != null ? color() : colorInherited(); } + public ChatColor colorInherited() { return parentHas() ? parent().colorEffective() : null; } + public Mson color(ChatColor color) + { + if (color != null && ! color.isColor()) throw new IllegalArgumentException("color must be a color"); + this.color = color; + return this; + } + + // FIELD: bold + protected Boolean bold = null; + public Boolean bold() { return bold; } + public Mson bold(Boolean bold) { this.bold = bold; return this; } + public boolean boldEffective() { return bold() != null ? bold() : boldInherited(); } + public boolean boldInherited() { return parentHas() && parent().boldEffective(); } + + // FIELD: italic + protected Boolean italic = null; + public Boolean italic() { return this.italic; } + public Mson italic(Boolean italic) { this.italic = italic; return this; } + public boolean italicEffective() { return italic() != null ? italic() : italicInherited(); } + protected boolean italicInherited() { return parentHas() && parent().italicEffective(); } + + // FIELD: underlined + protected Boolean underlined = null; + public Boolean underlined() { return this.underlined; } + public Mson underlined(Boolean underlined) { this.underlined = underlined; return this; } + public boolean underlinedEffective() { return italic() != null ? italic() : italicInherited(); } + protected boolean underlinedInherited() { return parentHas() && parent().underlinedEffective(); } + + // FIELD: strikethrough + protected Boolean strikethrough = null; + public Boolean strikethrough() { return this.strikethrough; } + public Mson striketrhough(Boolean strikethrough) { this.strikethrough = strikethrough; return this; } + public boolean strikethroughEffective() { return strikethrough() != null ? strikethrough() : strikethroughInherited(); } + protected boolean strikethroughInherited() { return parentHas() && parent().strikethroughEffective(); } + + // FIELD: obfuscated + protected Boolean obfuscated = null; + public Boolean obfuscated() { return this.obfuscated; } + public Mson obfuscated(Boolean obfuscated) { this.obfuscated = obfuscated; return this; } + public boolean obfuscatedEffective() { return obfuscated() != null ? obfuscated() : obfuscatedInherited(); } + protected boolean obfuscatedInherited() { return parentHas() && parent().obfuscatedEffective(); } + + // FIELD: The Events which happen when you click, hover over or shift-click the message + protected MsonEvent clickEvent = null; + public MsonEvent clickEvent() { return this.clickEvent; } + public MsonEvent clickEventEffective() { return clickEvent() != null ? clickEvent() : clickEventInherited(); } + protected MsonEvent clickEventInherited() { return this.parentHas() ? this.parent().clickEventEffective() : null; } + public Mson clickEvent(MsonEvent clickEvent) + { + if (clickEvent != null && ! clickEvent.isClickEvent()) throw new IllegalArgumentException("clickEvent may not be hoverEvent"); + this.clickEvent = clickEvent; + return this; + } + + protected MsonEvent hoverEvent = null; + public MsonEvent hoverEvent() { return this.hoverEvent; } + public MsonEvent hoverEventEffective() { return hoverEvent() != null ? hoverEvent() : hoverEventInherited(); } + protected MsonEvent hoverEventInherited() { return this.parentHas() ? this.parent().hoverEventEffective() : null; } + public Mson hoverEvent(MsonEvent hoverEvent) + { + if (hoverEvent != null && ! hoverEvent.isHoverEvent()) throw new IllegalArgumentException("hoverEvent may not be clickEvent"); + this.hoverEvent = hoverEvent; + return this; + } + + protected String insertionString = null; + public String insertionString() { return this.insertionString; } + public String insertionStringEffective() { return insertionString() != null ? insertionString() : insertionStringInherited(); } + public Mson insertionString(String insertionString) { this.insertionString = insertionString; return this; } + protected String insertionStringInherited() { return this.parentHas() ? this.parent().insertionStringEffective() : null; } + + // The other parts of the message + protected List extra = null; + public List extra() { return this.extra; } + public Mson extra(List extra) { this.extra = extra; return this; } + public boolean extraHas() { return this.extra() != null; } + + public List extraCreative() + { + if ( ! this.extraHas()) this.extra(new MassiveList()); + return this.extra(); + } + + // Parent & Root + protected transient Mson parent = null; + public Mson parent() { return this.parent; } + public Mson parent(Mson parent) { this.parent = parent; return this; } + public boolean parentHas() { return this.parent() != null; } + + public Mson parentCreative() + { + if ( ! this.parentHas()) this.parent(new Mson()); + return this.parent(); + } + + public boolean isRoot() { return this.parent() == null; } + public Mson root() + { + Mson root = this; + while (true) + { + Mson parent = root.parent(); + if (parent == null) break; + root = parent; + } + return root; + } + + // -------------------------------------------- // + // CONVENIENCE MSON EVENT + // -------------------------------------------- // + + public Mson link(String link) { this.clickEvent(MsonEvent.openUrl(link)); return this; } + + public Mson suggest(String replace) { this.clickEvent(MsonEvent.replace(replace)); return this; } + + public Mson command(String command) { this.clickEvent(MsonEvent.performCmd(command)); return this; } + + public Mson tooltip(String tooltip) { this.hoverEvent(MsonEvent.hoverText(text)); return this; } + public Mson tooltip(String... tooltip) { this.hoverEvent(MsonEvent.hoverText(text)); return this; } + public Mson tooltip(Collection tooltip) { this.hoverEvent(MsonEvent.hoverText(text)); return this; } + + public Mson tooltipParse(String tooltip) { this.hoverEvent(MsonEvent.hoverTextParse(text)); return this; } + public Mson tooltipParse(String... tooltip) { this.hoverEvent(MsonEvent.hoverTextParse(text)); return this; } + public Mson tooltipParse(Collection tooltip) { this.hoverEvent(MsonEvent.hoverTextParse(text)); return this; } + + // -------------------------------------------- // + // CONVENIENCE STYLE + // -------------------------------------------- // + + public Mson style(ChatColor... styles) + { + for (ChatColor style : styles) + { + this.style(style); + } + return this; + } + + public Mson style(ChatColor style) + { + if (style == ChatColor.RESET) return this.removeStyles(); + if (style == ChatColor.BOLD) return this.bold(true); + if (style == ChatColor.ITALIC) return this.italic(true); + if (style == ChatColor.UNDERLINE) return this.underlined(true); + if (style == ChatColor.STRIKETHROUGH) return this.striketrhough(true); + if (style == ChatColor.MAGIC) return this.obfuscated(true); + if (style.isColor()) return this.color(color); + + throw new UnsupportedOperationException(style.name()); + } + + public Mson removeStyles() + { + // NOTE: We can't use null. + // Since we want to override color and format in parents. + this.color = ChatColor.WHITE; + this.bold = false; + this.italic = false; + this.underlined = false; + this.strikethrough = false; + this.obfuscated = false; + return this; + } + + // -------------------------------------------- // + // BUILD TREE + // -------------------------------------------- // + + // Child, called on parent or root + public Mson addChild(Object child) + { + if (child == null) throw new NullPointerException("child"); + + Mson mson = Mson.mson(child); + List extra = this.extraCreative(); + + mson.parent(this); + extra.add(mson); + + return mson; // Return child + } + + public Mson addChildren(Object... children) + { + if (children == null) throw new NullPointerException("children"); + + for (Object part : children) + { + this.addChild(part); + } + + return this; // Return this + } + + // Sibling, normally called on child + public Mson addSibling(Object sibling) + { + if (sibling == null) throw new NullPointerException("sibling"); + Mson parent = this.parentCreative(); + return parent.addChild(sibling); // Return sibling + } + + public Mson addSiblings(Object... siblings) + { + if (siblings == null) throw new NullPointerException("siblings"); + Mson parent = this.parentCreative(); + + if ( ! parent.equals(this.parent())) this.parent(parent); + parent.addChildren(siblings); + return this; // Return this + } + + // -------------------------------------------- // + // SIMPLE CLEAN + // -------------------------------------------- // + // These methods, exist to clean up after mistakes. + // So they are very forgiving. + + public boolean isEmpty() + { + // It has text, not empty. + if (this.textHas()) return false; + + if (this.extraHas()) + { + for (Mson extra : this.extra()) + { + // It is null. So kinda empty. + if (extra == null) continue; + + // It is empty + if (extra.isEmpty()) continue; + + // It was not empty. + return false; + } + } + + // We are empty. + return true; + } + + // Detaches uneccessary extras. + public Mson simpleClean() + { + if ( ! this.extraHas()) return this; + if (this.extra().isEmpty()) + { + this.extra(null); + return this; + } + + for (ListIterator it = this.extra().listIterator(); it.hasNext();) + { + Mson extra = it.next(); + if (extra == null) + { + it.remove(); + continue; + } + + if (extra.isEmpty()) + { + extra.parent(null); + it.remove(); + continue; + } + + extra.simpleClean(); + } + return this; + } + + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + public Mson() + { + + } + + public static Mson mson() + { + return new Mson(); + } + + public static Mson mson(Object... parts) + { + return Mson.getMson(parts); + } + + private static Mson getMson(Object part) + { + if (part == null) throw new NullPointerException("part"); + + if (part instanceof Mson) + { + return (Mson) part; + } + else if (part instanceof String) + { + String text = (String) part; + return mson().text(text); + } + else if (part instanceof Collection) + { + Collection parts = (Collection) part; + List msons = Mson.msons(parts); + + if (msons.isEmpty()) return mson(); + if (msons.size() == 1) return msons.get(0); + + return mson().extra(msons); + } + else if (part instanceof Object[]) + { + Object[] parts = (Object[]) part; + return getMson(Arrays.asList(parts)); + } + else + { + throw new IllegalArgumentException("We only accept Strings, Msons, Collections and Arrays"); + } + } + + public static List msons(Object... parts) + { + return msons(Arrays.asList(parts)); + } + + public static List msons(Collection parts) + { + if (parts == null) throw new NullPointerException("parts"); + + List msons = new MassiveList(); + + for (Object part : parts) + { + msons.add(getMson(part)); + } + + return msons; + } + + public static Mson fromParsedMessage(String message) + { + if (message == null) throw new NullPointerException("message"); + + // Everything must have a color. + // Because when we split, we assume that each part starts with a color code. + // Here we assure it starts with one. + message = ensureStartsWithColorCode(message); + + // We split at color/format change. + String[] parts = PARSE_PREFIX.split(message); + + // Since we start with a color, the first element will be empty. + // We don't want that empty element. + parts = Arrays.copyOfRange(parts, 1, parts.length); + + List msons = new ArrayList(); + + ChatColor latestColor = null; + Boolean bold = null; + Boolean italic = null; + Boolean underlined = null; + Boolean strikethrough = null; + Boolean obfuscated = null; + + for (String part : parts) + { + ChatColor color = ChatColor.getByChar(part.charAt(0)); + String text = part.substring(1); + + if ((color != null && color.isColor()) || color == ChatColor.RESET) + { + latestColor = color; + bold = null; + italic = null; + underlined = null; + strikethrough = null; + obfuscated = null; + } + if (color == ChatColor.RESET) latestColor = null; + else if (color == ChatColor.BOLD) bold = true; + else if (color == ChatColor.ITALIC) italic = true; + else if (color == ChatColor.UNDERLINE) underlined = true; + else if (color == ChatColor.STRIKETHROUGH) strikethrough = true; + else if (color == ChatColor.MAGIC) obfuscated = true; + + // Don't add empty msons. + if (text.isEmpty()) continue; + + Mson mson = new Mson() + .text(text) + .color(latestColor) + .bold(bold) + .italic(italic) + .underlined(underlined) + .striketrhough(strikethrough) + .obfuscated(obfuscated); + + msons.add(mson); + } + + return Mson.mson(msons); + } + + private static String ensureStartsWithColorCode(String message) + { + if ( ! message.startsWith("\u00A7")) + { + message = ChatColor.RESET + message; + } + return message; + } + + // Parse redirects, convert to Mson directly + public static Mson parse(String string) { return Mson.fromParsedMessage(Txt.parse(string)); } + public static Mson parse(String format, Object... args) { return Mson.fromParsedMessage(Txt.parse(format, args)); } + + public static Mson format(String format, Object... args) + { + return Mson.mson(String.format(format, args)); + } + + public Mson copy() + { + Mson copy = new Mson() + .text(this.text) + .color(this.color) + .bold(this.bold) + .italic(this.italic) + .underlined(this.underlined) + .striketrhough(this.strikethrough) + .obfuscated(this.obfuscated) + .insertionString(this.insertionString) + .clickEvent(this.clickEvent) + .hoverEvent(this.hoverEvent); + + if (this.extraHas()) + { + List extras = new MassiveList(this.extra.size()); + for (Mson extra : this.extra) + { + Mson extraCopy = extra.copy(); + extraCopy.parent(copy); + extras.add(extraCopy); + } + copy.extra(extras); + } + + return copy; + } + + private Mson copyFormatAndBehaviour(Mson ancestor) + { + if (ancestor == null) throw new NullPointerException("ancestor"); + + this.color(ancestor.color()); + this.bold(ancestor.bold()); + this.italic(ancestor.italic()); + this.underlined(ancestor.underlined()); + this.striketrhough(ancestor.strikethrough()); + this.obfuscated(ancestor.obfuscated()); + this.hoverEvent(ancestor.hoverEvent()); + this.clickEvent(ancestor.clickEvent()); + this.insertionString(ancestor.insertionString()); + + return this; + } + + // -------------------------------------------- // + // STRING LIKE METHODS + // -------------------------------------------- // + + // Split + public List split(String regex) + { + return this.split(regex, 0); + } + + public List split(String regex, int limit) + { + return split(Pattern.compile(regex), limit); + } + + public List split(Pattern pattern) + { + return this.split(pattern, 0); + } + + public List split(Pattern pattern, int limit) + { + if (pattern == null) throw new NullPointerException("pattern"); + + boolean limited = (limit == 0 ? false : true); + List msons = new MassiveList(); + + String[] splited = pattern.split(this.text(), limit); + if (splited.length == 1) return new MassiveList(this); + + for (String string : splited) + { + if (string.isEmpty()) continue; + Mson part = new Mson().text(string); + part.copyFormatAndBehaviour(this); + msons.add(part); + } + + int size = msons.size(); + + for (Mson part : this.extraCreative()) + { + if (limited) + { + limit -= size; + if (limit <= 0) break; + } + + List innerMsons = part.split(pattern, limit); + msons.addAll(innerMsons); + size = innerMsons.size(); + } + + return msons; + } + + // Replace + public Mson replace(char oldChar, char newChar) + { + this.text(this.text().replace(oldChar, newChar)); + + if (this.extraHas()) + { + for (Mson part : this.extra()) + { + part.replace(oldChar, newChar); + } + } + + return this; + } + + public Mson replace(CharSequence replace, CharSequence replacement) + { + if (replace == null) throw new NullPointerException("replace"); + if (replacement == null) throw new NullPointerException("replacement"); + + this.text(this.text().replace(replace, replacement)); + + if (this.extraHas()) + { + for (Mson part : this.extra()) + { + part.replace(replace, replacement); + } + } + + return this; + } + + public Mson replaceAll(String regex, String replacement) + { + if (regex == null) throw new NullPointerException("regex"); + if (replacement == null) throw new NullPointerException("replacement"); + + this.text(this.text().replaceAll(regex, replacement)); + + if (this.extraHas()) + { + for (Mson part : this.extra()) + { + part.replaceAll(regex, replacement); + } + } + + return this; + } + + // Special replace all + public Mson replaceAll(String regex, Mson replacement) + { + return replaceAll(Pattern.compile(regex), replacement); + } + + public Mson replaceAll(Pattern pattern, Mson replacement) + { + if (pattern == null) throw new NullPointerException("pattern"); + if (replacement == null) throw new NullPointerException("replacement"); + + // We don't want the same object to eventually be part of itself + Mson repCopy = replacement.copy(); + String text = (this.text() + " "); // Prepare text + + // Split the string of this msons text and create an iterator + // and create the list of mson with the replacements in between ... + List msons = new MassiveList(); + + for (ListIterator it = Arrays.asList(pattern.split(text)).listIterator(); it.hasNext();) + { + String string = it.next(); + + // string might be empty, we don't want these empty msons + if ( ! string.isEmpty()) + { + Mson part = Mson.mson(string); + msons.add(part); + + // Delete security spacing at the last string + if ( ! it.hasNext()) part.text(string.substring(0, string.length() - 1)); + } + + // In between every part, add in replacement + if (it.hasNext()) msons.add(repCopy); + } + + // ... and forge back together, unless the whole text is "replaced", then set it to be the replacement itself. + Mson mson; + if ( ! msons.isEmpty()) + { + mson = msons.get(0).copy(); + msons.remove(0); + mson.copyFormatAndBehaviour(this); + if ( ! msons.isEmpty()) mson.extra(msons); + } + else + { + mson = repCopy; + } + + mson.parent(this.parent()); + + // If there is no extra, return here... + List extra = this.extra(); + if (extra == null) return mson; + + // ... otherwise iterate over the extra and modify it. + for (ListIterator it = extra.listIterator(); it.hasNext();) + { + Mson part = it.next(); + int index = extra.indexOf(part); + + part = part.replaceAll(pattern, replacement); + part.parent(mson); + + extra.set(index, part); + } + + // If the current mson has any extra ... + List extras = mson.extra(); + if( extras != null) + { + // ... apply tree structure again ... + extras.get(extras.size() - 1).extra(extra); + } + else + { + // ... set the extra directly ... + mson.extra(extra); + } + + mson.simpleClean(); + // ... and return recreated mson + return mson; + } + + public Mson replaceAll(Mson replace, Mson replacement) + { + if (replace == null) throw new NullPointerException("replace"); + if (replacement == null) throw new NullPointerException("replacement"); + + // We don't want the same object to eventually be part of itself + Mson repCopy = replacement.copy(); + + Mson mson = this; + List extra = this.extra(); + + if (mson.equals(replace)) + { + mson = repCopy; + mson.parent(this.parent()); + if (extra != null) mson.extraCreative().addAll(extra); + } + + if (extra == null) return mson; + + for (ListIterator it = extra.listIterator(); it.hasNext();) + { + int index = it.nextIndex(); + Mson part = it.next(); + + part = part.replaceAll(replace, replacement); + part.parent(mson); + + extra.set(index, part); + } + + mson.extra(extra); + return mson; + } + + // -------------------------------------------- // + // SEND + // -------------------------------------------- // + + // All + public boolean sendAll() + { + return Mixin.messageRawAll(this); + } + + // Predictate + public boolean sendPredictate(Predictate predictate) + { + return Mixin.messageRawPredictate(predictate, this); + } + + // One + public boolean sendOne(Object senderObject) + { + return Mixin.messageRawOne(senderObject, this); + } + + // -------------------------------------------- // + // TO JSON, RAW, PLAIN & STRING + // -------------------------------------------- // + + public JsonElement rootToJson() { return this.root().toJson(); } + public JsonElement toJson() + { + return GSON.toJsonTree(this); + } + + public String rootToRaw() { return this.root().toRaw(); } + public String toRaw() + { + return this.toJson().toString(); + } + + public String toPlain() + { + StringBuilder ret = new StringBuilder(); + + ret.append(toPlainSimple(this)); + + if (this.extraHas()) + { + for (Mson part : this.extra()) + { + ret.append(ChatColor.RESET); + ret.append(part.toPlain()); + } + } + + return ret.toString(); + } + + // Turns a single mson (without it's children) into plain text. + protected static String toPlainSimple(Mson mson) + { + if (mson.textHas()) + { + // Color must be put in BEFORE formatting. + // http://minecraft.gamepedia.com/Formatting_codes#Formatting_codes + + StringBuilder ret = new StringBuilder(mson.text().length()); + if (mson.colorEffective() != null) ret.append(mson.colorEffective()); + if (mson.boldEffective()) ret.append(ChatColor.BOLD); + if (mson.italicEffective()) ret.append(ChatColor.ITALIC); + if (mson.underlinedEffective()) ret.append(ChatColor.UNDERLINE); + if (mson.strikethroughEffective()) ret.append(ChatColor.STRIKETHROUGH); + if (mson.obfuscatedEffective()) ret.append(ChatColor.MAGIC); + ret.append(mson.text()); + + return ret.toString(); + } + + return ""; + } + + @Override + public String toString() + { + return this.toRaw(); + } + + // -------------------------------------------- // + // EQUALS AND HASHCODE + // -------------------------------------------- // + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Objects.hashCode(this.text); + result = prime * result + Objects.hashCode(this.color); + result = prime * result + Objects.hashCode(this.bold); + result = prime * result + Objects.hashCode(this.italic); + result = prime * result + Objects.hashCode(this.obfuscated); + result = prime * result + Objects.hashCode(this.strikethrough); + result = prime * result + Objects.hashCode(this.underlined); + result = prime * result + Objects.hashCode(this.clickEvent); + result = prime * result + Objects.hashCode(this.hoverEvent); + result = prime * result + Objects.hashCode(this.insertionString); + result = prime * result + Objects.hashCode(this.extra); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if ( ! (obj instanceof Mson)) return false; + Mson that = (Mson) obj; + + if ( ! MUtil.equals(this.text, that.text)) return false; + if ( ! MUtil.equals(this.color, that.color)) return false; + if ( ! MUtil.equals(this.bold, that.bold)) return false; + if ( ! MUtil.equals(this.italic, that.italic)) return false; + if ( ! MUtil.equals(this.obfuscated, that.obfuscated)) return false; + if ( ! MUtil.equals(this.strikethrough, that.strikethrough)) return false; + if ( ! MUtil.equals(this.underlined, that.underlined)) return false; + if ( ! MUtil.equals(this.clickEvent, that.clickEvent)) return false; + if ( ! MUtil.equals(this.hoverEvent, that.hoverEvent)) return false; + if ( ! MUtil.equals(this.insertionString, that.insertionString)) return false; + if ( ! MUtil.equals(this.extra, that.extra)) return false; + + return true; + } + +} diff --git a/src/com/massivecraft/massivecore/mson/MsonEvent.java b/src/com/massivecraft/massivecore/mson/MsonEvent.java new file mode 100644 index 00000000..e8486a58 --- /dev/null +++ b/src/com/massivecraft/massivecore/mson/MsonEvent.java @@ -0,0 +1,129 @@ +package com.massivecraft.massivecore.mson; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Objects; + +import com.massivecraft.massivecore.util.MUtil; +import com.massivecraft.massivecore.util.Txt; + +public class MsonEvent implements Serializable +{ + // -------------------------------------------- // + // CONSTANTS + // -------------------------------------------- // + + private static final long serialVersionUID = 1L; + + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + protected final MsonEventAction action; + public MsonEventAction getEventActionType() { return this.action; } + + protected final String value; + public String getActionText() { return this.value; } + + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + protected MsonEvent(MsonEventAction action, String value) + { + this.action = action; + this.value = value; + } + + // -------------------------------------------- // + // FACTORY + // -------------------------------------------- // + + public static MsonEvent valueOf(MsonEventAction type, String action) + { + return new MsonEvent(type, action); + } + + // clickEvents + public static MsonEvent openUrl(String url) + { + return MsonEvent.valueOf(MsonEventAction.OPEN_URL, url); + } + + public static MsonEvent replace(String replace) + { + return MsonEvent.valueOf(MsonEventAction.SUGGEST_COMMAND , replace); + } + + public static MsonEvent performCmd(String cmd) + { + return MsonEvent.valueOf(MsonEventAction.RUN_COMMAND, cmd); + } + + // hoverEvents + public static MsonEvent hoverText(String hoverText) + { + return MsonEvent.valueOf(MsonEventAction.SHOW_TEXT, hoverText); + } + + public static MsonEvent hoverText(String... hoverTexts) + { + return MsonEvent.valueOf(MsonEventAction.SHOW_TEXT, Txt.implode(hoverTexts, "\n")); + } + + public static MsonEvent hoverText(Collection hoverTexts) + { + return MsonEvent.valueOf(MsonEventAction.SHOW_TEXT, Txt.implode(hoverTexts, "\n")); + } + + // hoverEventsParsed + public static MsonEvent hoverTextParse(String hoverText) + { + return MsonEvent.valueOf(MsonEventAction.SHOW_TEXT, Txt.parse(hoverText)); + } + + public static MsonEvent hoverTextParse(String... hoverTexts) + { + return MsonEvent.valueOf(MsonEventAction.SHOW_TEXT, Txt.parse(Txt.implode(hoverTexts, "\n"))); + } + + public static MsonEvent hoverTextParse(Collection hoverTexts) + { + return MsonEvent.valueOf(MsonEventAction.SHOW_TEXT, Txt.parse(Txt.implode(hoverTexts, "\n"))); + } + + // -------------------------------------------- // + // CONVENIENCE + // -------------------------------------------- // + + public boolean isHoverEvent() { return this.getEventActionType().isHoverAction(); } + public boolean isClickEvent() { return this.getEventActionType().isClickAction(); } + + // -------------------------------------------- // + // EQUALS AND HASHCODE + // -------------------------------------------- // + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Objects.hashCode(action); + result = prime * result + Objects.hashCode(value); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if ( ! (obj instanceof MsonEvent)) return false; + MsonEvent that = (MsonEvent) obj; + + if ( ! MUtil.equals(this.action, that.action)) return false; + if ( ! MUtil.equals(this.value, that.value)) return false; + + return true; + } + +} diff --git a/src/com/massivecraft/massivecore/mson/MsonEventAction.java b/src/com/massivecraft/massivecore/mson/MsonEventAction.java new file mode 100644 index 00000000..c0bfcf39 --- /dev/null +++ b/src/com/massivecraft/massivecore/mson/MsonEventAction.java @@ -0,0 +1,44 @@ +package com.massivecraft.massivecore.mson; + +public enum MsonEventAction +{ + // -------------------------------------------- // + // ENUM + // -------------------------------------------- // + + SUGGEST_COMMAND(), + RUN_COMMAND(), + OPEN_URL(), + SHOW_TEXT(true) + + // End of list + ; + + // -------------------------------------------- // + // FIELD + // -------------------------------------------- // + + private boolean isHoverAction; + public boolean isHoverAction() { return this.isHoverAction; } + + // NOTE: This behaviour might change. + // So to check if something is a click action this method should be called. + // Doing !action.isHoverAction(); + // Shouldn't be done outside of this class. + public boolean isClickAction() { return ! this.isHoverAction(); } + + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + private MsonEventAction(boolean isHoveraction) + { + this.isHoverAction = isHoveraction; + } + + private MsonEventAction() + { + this(false); + } + +} diff --git a/src/com/massivecraft/massivecore/store/SenderEntity.java b/src/com/massivecraft/massivecore/store/SenderEntity.java index 401a8ffb..43dea6f2 100644 --- a/src/com/massivecraft/massivecore/store/SenderEntity.java +++ b/src/com/massivecraft/massivecore/store/SenderEntity.java @@ -9,6 +9,7 @@ import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import com.massivecraft.massivecore.mixin.Mixin; +import com.massivecraft.massivecore.mson.Mson; import com.massivecraft.massivecore.util.IdUtil; public abstract class SenderEntity> extends Entity @@ -159,6 +160,23 @@ public abstract class SenderEntity> extends Entity return Mixin.msgOne(this.getId(), msgs); } + // CONVENIENCE SEND RAW + + public boolean sendRaw(Mson mson) + { + return Mixin.messageRawOne(this.getId(), mson); + } + + public boolean sendRaw(Mson... msons) + { + return Mixin.messageRawOne(this.getId(), msons); + } + + public boolean sendRaw(Collection msons) + { + return Mixin.messageRawOne(this.getId(), msons); + } + // CONVENIENCE GAME-MODE public GameMode getGameMode(GameMode def) diff --git a/src/com/massivecraft/massivecore/util/PacketUtil.java b/src/com/massivecraft/massivecore/util/PacketUtil.java new file mode 100644 index 00000000..488ff7fe --- /dev/null +++ b/src/com/massivecraft/massivecore/util/PacketUtil.java @@ -0,0 +1,272 @@ +package com.massivecraft.massivecore.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; + +import org.bukkit.entity.Player; +import org.json.simple.JSONObject; + +import com.massivecraft.massivecore.MassiveCore; +import com.massivecraft.massivecore.particleeffect.ReflectionUtils; +import com.massivecraft.massivecore.particleeffect.ReflectionUtils.PackageType; + +public final class PacketUtil +{ + // -------------------------------------------- // + // FIELDS + // -------------------------------------------- // + + // Is using + private static boolean useTitles = false; + private static boolean useRaw = false; + + // The enums used to tell which packet it is. + // They correspond to the commands with the same name. + private static Class titleEnumClass; + private static Enum titleMainEnum; + private static Enum titleSubEnum; + private static Enum titleTimesEnum; + + // Method used to prepare text so it can be sent + private static Method chatSerializer; + // The object we send instead of a string + private static Class iChatBaseComponent; + + // Handling the players conenction + private static Method getHandle; + private static Field playerConnection; + private static Method sendPacket; + + // The title packet and its constructor + private static Constructor titlePacketConstructor; + private static Constructor titlePacketConstructorTimes; + + // The chat packet and its constructor + private static Constructor chatPacketConstructor; + + // -------------------------------------------- // + // SETUP + // -------------------------------------------- // + + static + { + try + { + // The enum used for titles + titleEnumClass = getTitleEnumClass(); + + // Get the title enum values. + for (Object o : titleEnumClass.getEnumConstants()) + { + Enum e = (Enum) o; + if (e.name().equalsIgnoreCase("TITLE")) titleMainEnum = e; + else if (e.name().equalsIgnoreCase("SUBTITLE")) titleSubEnum = e; + else if (e.name().equalsIgnoreCase("TIMES")) titleTimesEnum = e; + } + + // Get chatserializer and chat component. + iChatBaseComponent = PackageType.MINECRAFT_SERVER.getClass("IChatBaseComponent"); + + chatSerializer = getChatSerializer(); + + // Get title packet and it's constructor + Class titlePacketClass = PackageType.MINECRAFT_SERVER.getClass("PacketPlayOutTitle"); + titlePacketConstructor = ReflectionUtils.getConstructor(titlePacketClass, titleEnumClass, iChatBaseComponent); + titlePacketConstructorTimes = ReflectionUtils.getConstructor(titlePacketClass, titleEnumClass, iChatBaseComponent, Integer.class, Integer.class, Integer.class); + + // Get Chat packet and it's constructor + Class chatPacketClass = PackageType.MINECRAFT_SERVER.getClass("PacketPlayOutChat"); + chatPacketConstructor = ReflectionUtils.getConstructor(chatPacketClass, iChatBaseComponent); + + // Player connection + getHandle = ReflectionUtils.getMethod("CraftPlayer", PackageType.CRAFTBUKKIT_ENTITY, "getHandle"); + playerConnection = ReflectionUtils.getField("EntityPlayer", PackageType.MINECRAFT_SERVER, false, "playerConnection"); + sendPacket = ReflectionUtils.getMethod(playerConnection.getType(), "sendPacket", PackageType.MINECRAFT_SERVER.getClass("Packet")); + + // Set accessible + setAllAccessible(); + + // This succeeded, we use titles and the chat. + useTitles = true; + useRaw = true; + } + catch (Exception ex) + { + MassiveCore.get().log(Level.INFO, "If you use 1.7.X or below, disregard this error"); + MassiveCore.get().log(Level.INFO, "If you use 1.8.X or above, please report at https://github.com/MassiveCraft/MassiveCore/issues"); + ex.printStackTrace(); + MassiveCore.get().log(Level.INFO, "If you use 1.7.X or below, disregard this error"); + MassiveCore.get().log(Level.INFO, "If you use 1.8.X or above, please report at https://github.com/MassiveCraft/MassiveCore/issues"); + + // It didn't succeed, we will not use titles. + useTitles = false; + useRaw = false; + } + + } + + public static Class getTitleEnumClass() throws ClassNotFoundException + { + Class ret; + try + { + ret = titleEnumClass = PackageType.MINECRAFT_SERVER.getClass("EnumTitleAction"); + } + catch (ClassNotFoundException e) + { + // Since 1.8.3 + ret = titleEnumClass = PackageType.MINECRAFT_SERVER.getClass("PacketPlayOutTitle$EnumTitleAction"); + } + + return ret; + } + + public static Method getChatSerializer() throws Exception + { + Method ret; + try + { + ret = chatSerializer = PackageType.MINECRAFT_SERVER.getClass("ChatSerializer").getDeclaredMethod("a", String.class); + } + catch (ClassNotFoundException e) + { + // Since 1.8.3 + ret = chatSerializer = PackageType.MINECRAFT_SERVER.getClass("IChatBaseComponent$ChatSerializer").getDeclaredMethod("a", String.class); + } + + return ret; + } + + public static void setAllAccessible() + { + chatSerializer.setAccessible(true); + titlePacketConstructor.setAccessible(true); + titlePacketConstructorTimes.setAccessible(true); + chatPacketConstructor.setAccessible(true); + getHandle.setAccessible(true); + playerConnection.setAccessible(true); + sendPacket.setAccessible(true); + } + + // -------------------------------------------- // + // AVAILABLE + // -------------------------------------------- // + + public static boolean isTitleAvailable() + { + return useTitles; + } + + public static boolean isRawAvailable() + { + return useRaw; + } + + // -------------------------------------------- // + // SEND TITLES + // -------------------------------------------- // + + public static boolean sendTitle(Player player, int ticksIn, int ticksStay, int ticksOut, String titleMain, String titleSub) + { + if ( ! useTitles) return false; + + try + { + // Fadein, stay, fadeout + Object timesPacket = titlePacketConstructorTimes.newInstance(titleTimesEnum, null, ticksIn, ticksStay, ticksOut); + sendPacket(player, timesPacket); + + if (titleMain != null) + { + titleMain = toJson(titleMain); + + // Title + Object titleMainChat = toChatBaseComponent(titleMain); + Object titleMainPacket = titlePacketConstructor.newInstance(titleMainEnum, titleMainChat); + sendPacket(player, titleMainPacket); + + } + + if (titleSub != null) + { + titleSub = toJson(titleSub); + // SubTitle + Object titleSubChat = toChatBaseComponent(titleSub); + Object titleSubPacket = titlePacketConstructor.newInstance(titleSubEnum, titleSubChat); + sendPacket(player, titleSubPacket); + } + + } + catch (Exception ex) + { + MassiveCore.get().log("Sending title failed!"); + ex.printStackTrace(); + // So we failed, didn't work. + return false; + } + + //It worked. + return true; + } + + // -------------------------------------------- // + // SEND RAW + // -------------------------------------------- // + + public static boolean sendRaw(Player player, String string) + { + if ( ! useRaw) return false; + + try + { + Object rawChat = toChatBaseComponent(string); + Object chatPacket = chatPacketConstructor.newInstance(rawChat); + + sendPacket(player, chatPacket); + } + catch (Exception ex) + { + MassiveCore.get().log("Sending raw chat failed!"); + ex.printStackTrace(); + // So we failed and it didn't work. + return false; + } + + return true; + } + + // -------------------------------------------- // + // UTIL + // -------------------------------------------- // + + public static void sendPacket(Player player, Object packet) throws Exception + { + sendPacket.invoke(getPlayerConnection(player), packet); + } + + public static Object getPlayerConnection(Player player) throws Exception + { + return playerConnection.get(getHandle.invoke(player)); + } + + public static Object toChatBaseComponent(String str) throws Exception + { + return chatSerializer.invoke(null, str); + } + + // -------------------------------------------- // + // JSON + // -------------------------------------------- // + + public static String toJson(String str) + { + str = JSONObject.escape(str); + + str = "{\"text\": \"" + str + "\"}"; + + return str; + } + +} diff --git a/src/com/massivecraft/massivecore/util/TitleUtil.java b/src/com/massivecraft/massivecore/util/TitleUtil.java deleted file mode 100644 index 6fb58c19..00000000 --- a/src/com/massivecraft/massivecore/util/TitleUtil.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.massivecraft.massivecore.util; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.logging.Level; - -import org.bukkit.entity.Player; -import org.json.simple.JSONObject; - -import com.massivecraft.massivecore.MassiveCore; -import com.massivecraft.massivecore.particleeffect.ReflectionUtils; -import com.massivecraft.massivecore.particleeffect.ReflectionUtils.PackageType; - -public final class TitleUtil -{ - // -------------------------------------------- // - // FIELDS - // -------------------------------------------- // - - private static boolean useTitles = false; - - // The enums used to tell which packet it is. - // They correspond to the commands with the same name. - private static Class titleEnumClass; - private static Enum titleEnum; - private static Enum subtitleEnum; - private static Enum timesEnum; - - // Method used to prepare text so it can be send - private static Method chatSerializer; - // The object we send instead of a string - private static Class iChatBaseComponent; - - // Handling the players conenction - private static Method getHandle; - private static Field playerConnection; - private static Method sendPacket; - - // The packet and its constructor - private static Constructor packetConstructor; - private static Constructor packetConstructorTimes; - - // -------------------------------------------- // - // SETUP - // -------------------------------------------- // - - static - { - try - { - // The enum used - try - { - titleEnumClass = PackageType.MINECRAFT_SERVER.getClass("EnumTitleAction"); - } - catch (ClassNotFoundException e) - { - // Since 1.8.3 - titleEnumClass = PackageType.MINECRAFT_SERVER.getClass("PacketPlayOutTitle$EnumTitleAction"); - } - - // Get the enum values. - for (Object o : titleEnumClass.getEnumConstants()) - { - Enum e = (Enum) o; - if (e.name().equalsIgnoreCase("TITLE")) titleEnum = e; - else if (e.name().equalsIgnoreCase("SUBTITLE")) subtitleEnum = e; - else if (e.name().equalsIgnoreCase("TIMES")) timesEnum = e; - } - - // Get chatserializer and chat component. - iChatBaseComponent = PackageType.MINECRAFT_SERVER.getClass("IChatBaseComponent"); - - try - { - chatSerializer = PackageType.MINECRAFT_SERVER.getClass("ChatSerializer").getDeclaredMethod("a", String.class); - } - catch (ClassNotFoundException e) - { - // Since 1.8.3 - chatSerializer = PackageType.MINECRAFT_SERVER.getClass("IChatBaseComponent$ChatSerializer").getDeclaredMethod("a", String.class); - } - - // Get packet and it's constructor - Class packetClass = PackageType.MINECRAFT_SERVER.getClass("PacketPlayOutTitle"); - packetConstructor = ReflectionUtils.getConstructor(packetClass, titleEnumClass, iChatBaseComponent); - packetConstructorTimes = ReflectionUtils.getConstructor(packetClass, titleEnumClass, iChatBaseComponent, Integer.class, Integer.class, Integer.class); - - // Player connection - getHandle = ReflectionUtils.getMethod("CraftPlayer", PackageType.CRAFTBUKKIT_ENTITY, "getHandle"); - playerConnection = ReflectionUtils.getField("EntityPlayer", PackageType.MINECRAFT_SERVER, false, "playerConnection"); - sendPacket = ReflectionUtils.getMethod(playerConnection.getType(), "sendPacket", PackageType.MINECRAFT_SERVER.getClass("Packet")); - - // Set accesible - chatSerializer.setAccessible(true); - packetConstructor.setAccessible(true); - packetConstructorTimes.setAccessible(true); - getHandle.setAccessible(true); - playerConnection.setAccessible(true); - sendPacket.setAccessible(true); - - // This succeeded, we use titles. - useTitles = true; - } - catch (Exception e) - { - MassiveCore.get().log(Level.INFO, "If you use 1.7.X or below, disregard this error"); - MassiveCore.get().log(Level.INFO, "If you use 1.8.X or above, please report at https://github.com/MassiveCraft/MassiveCore/issues"); - e.printStackTrace(); - MassiveCore.get().log(Level.INFO, "If you use 1.7.X or below, disregard this error"); - MassiveCore.get().log(Level.INFO, "If you use 1.8.X or above, please report at https://github.com/MassiveCraft/MassiveCore/issues"); - - // It didn't suceed, we will not use titles. - useTitles = false; - } - - } - - // -------------------------------------------- // - // AVAILABLE - // -------------------------------------------- // - - public static boolean isAvailable() - { - return useTitles; - } - - // -------------------------------------------- // - // SEND TITLES - // -------------------------------------------- // - - public static boolean sendTitle(Player player, int ticksIn, int ticksStay, int ticksOut, String titleMain, String titleSub) - { - if ( ! useTitles) - { - return false; - } - - try - { - // Fadein, stay, fadeout - Object timesPacket = packetConstructorTimes.newInstance(timesEnum, null, ticksIn, ticksStay, ticksOut); - sendPacket.invoke(playerConnection.get( getHandle.invoke(player) ), timesPacket); - } - catch (Exception e) - { - e.printStackTrace(); - // So we failed, didn't work. - return false; - } - - if (titleMain != null) - { - titleMain = toJson(titleMain); - try - { - // Title - Object titleMainChat = chatSerializer.invoke(null, titleMain); - Object titleMainPacket = packetConstructor.newInstance(titleEnum, titleMainChat); - sendPacket.invoke(playerConnection.get(getHandle.invoke(player)), titleMainPacket); - } - catch (Exception e) - { - e.printStackTrace(); - // So we failed, didn't work. - return false; - } - } - - if (titleSub != null) - { - titleSub = toJson(titleSub); - try - { - // SubTitle - Object titleSubChat = chatSerializer.invoke(null, titleSub); - Object titleSubPacket = packetConstructor.newInstance(subtitleEnum, titleSubChat); - sendPacket.invoke(playerConnection.get(getHandle.invoke(player)), titleSubPacket); - } - catch (Exception e) - { - e.printStackTrace(); - // So we failed, didn't work. - return false; - } - } - - //It worked. - return true; - } - - // -------------------------------------------- // - // JSON - // -------------------------------------------- // - - public static String toJson(String str) - { - str = JSONObject.escape(str); - - str = "{\"text\": \"" + str + "\"}"; - - return str; - } - -}