();
+ _loadedFields.put(clazz, loaded);
+ } else {
+ loaded = _loadedFields.get(clazz);
+ }
+ if (loaded.containsKey(name)) {
+ // If the field is loaded (or cached as not existing), return the
+ // relevant value, which might be null
+ return loaded.get(name);
+ }
+ try {
+ final Field field = clazz.getDeclaredField(name);
+ field.setAccessible(true);
+ loaded.put(name, field);
+ return field;
+ } catch (final Exception e) {
+ // Cache field as not existing
+ loaded.put(name, null);
+ throw e;
+ }
+ }
+
+ /**
+ * Attempts to get the NMS handle of a CraftBukkit object.
+ *
+ * The only match currently attempted by this method is a retrieval by using
+ * a parameterless {@code getHandle()} method implemented by the runtime
+ * type of the specified object.
+ *
+ *
+ * @param obj
+ * The object for which to retrieve an NMS handle.
+ * @return The NMS handle of the specified object, or {@code null} if it
+ * could not be retrieved using {@code getHandle()}.
+ */
+ public synchronized static Object getHandle(final Object obj) {
+ try {
+ return getMethod(obj.getClass(), "getHandle").invoke(obj);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves a {@link Method} instance declared by the specified class with
+ * the specified name and argument types.
+ * Java access modifiers are ignored during this retrieval. No guarantee is
+ * made as to whether the field
+ * returned will be an instance or static field.
+ *
+ * A global caching mechanism within this class is used to store method.
+ * Combined with synchronization, this guarantees that no method will be
+ * reflectively looked up twice.
+ *
+ *
+ * If a method is deemed suitable for return,
+ * {@link Method#setAccessible(boolean) setAccessible} will be invoked with
+ * an argument of {@code true} before it is returned. This ensures that
+ * callers do not have to check or worry about Java access modifiers when
+ * dealing with the returned instance.
+ *
+ *
+ * This method does not search superclasses of the specified type
+ * for methods with the specified signature. Callers wishing this behavior
+ * should use {@link Class#getDeclaredMethod(String, Class...)}.
+ *
+ * @param clazz
+ * The class which contains the method to retrieve.
+ * @param name
+ * The declared name of the method in the class.
+ * @param args
+ * The formal argument types of the method.
+ * @return A method object with the specified name declared by the specified
+ * class.
+ */
+ public synchronized static Method getMethod(final Class> clazz, final String name, final Class>... args) {
+ if (!_loadedMethods.containsKey(clazz)) {
+ _loadedMethods.put(clazz, new HashMap>, Method>>());
+ }
+
+ final Map>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
+ if (!loadedMethodNames.containsKey(name)) {
+ loadedMethodNames.put(name, new HashMap>, Method>());
+ }
+
+ final Map>, Method> loadedSignatures = loadedMethodNames.get(name);
+ final ArrayWrapper> wrappedArg = new ArrayWrapper>(args);
+ if (loadedSignatures.containsKey(wrappedArg)) {
+ return loadedSignatures.get(wrappedArg);
+ }
+
+ for (final Method m : clazz.getMethods()) {
+ if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
+ m.setAccessible(true);
+ loadedSignatures.put(wrappedArg, m);
+ return m;
+ }
+ }
+ loadedSignatures.put(wrappedArg, null);
+ return null;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static List getMethodByParamsAndType(final Class source, final Class returnType, final Class... args) {
+ final List list = new ArrayList<>();
+ for (final Method method : findMethodByParams(source.getMethods(), args)) {
+ if (method.getReturnType().equals(returnType)) {
+ list.add(method);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Gets a {@link Class} object representing a type contained within the
+ * {@code net.minecraft.server} versioned package.
+ * The class instances returned by this method are cached, such that no
+ * lookup will be done twice (unless multiple threads are accessing this
+ * method simultaneously).
+ *
+ * @param className
+ * The name of the class, excluding the package, within NMS.
+ * @return The class instance representing the specified NMS class, or
+ * {@code null} if it could not be loaded.
+ */
+ public synchronized static Class> getNMSClass(final String className) {
+ if (_loadedNMSClasses.containsKey(className)) {
+ return _loadedNMSClasses.get(className);
+ }
+ final String fullName = "net.minecraft.server." + getVersion() + className;
+ Class> clazz = null;
+ try {
+ clazz = Class.forName(fullName);
+ } catch (final Exception | NoClassDefFoundError | NoSuchMethodError e) {
+ _loadedNMSClasses.put(className, null);
+ return null;
+ }
+ _loadedNMSClasses.put(className, clazz);
+ return clazz;
+ }
+
+ /**
+ * Gets a {@link Class} object representing a type contained within the
+ * {@code org.bukkit.craftbukkit} versioned package.
+ * The class instances returned by this method are cached, such that no
+ * lookup will be done twice (unless multiple threads are accessing this
+ * method simultaneously).
+ *
+ * @param className
+ * The name of the class, excluding the package, within OBC. This
+ * name may contain a subpackage name, such as
+ * {@code inventory.CraftItemStack}.
+ * @return The class instance representing the specified OBC class, or
+ * {@code null} if it could not be loaded.
+ */
+ public synchronized static Class> getOBCClass(final String className) {
+ if (_loadedOBCClasses.containsKey(className)) {
+ return _loadedOBCClasses.get(className);
+ }
+
+ final String fullName = "org.bukkit.craftbukkit." + getVersion() + className;
+ Class> clazz = null;
+ try {
+ clazz = Class.forName(fullName);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ _loadedOBCClasses.put(className, null);
+ return null;
+ }
+ _loadedOBCClasses.put(className, clazz);
+ return clazz;
+ }
+
+ /**
+ * Gets the version string from the package name of the CraftBukkit server
+ * implementation.
+ * This is needed to bypass the JAR package name changing on each update.
+ *
+ * @return The version string of the OBC and NMS packages,
+ * including the trailing dot.
+ */
+ public synchronized static String getVersion() {
+ if (_versionString == null) {
+ if (Bukkit.getServer() == null) {
+ // The server hasn't started, static initializer call?
+ return null;
+ }
+ final String name = Bukkit.getServer().getClass().getPackage().getName();
+ _versionString = name.substring(name.lastIndexOf('.') + 1) + ".";
+ }
+
+ return _versionString;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static List findMethodByParams(final Method[] methods, final Class... args) {
+ final List list = new ArrayList<>();
+ start:
+ for (final Method method : methods) {
+ if (method.getParameterTypes().length == args.length) {
+ final Class[] array = method.getParameterTypes();
+ for (int i = 0; i < args.length; i++) {
+ if (array[i] != args[i]) {
+ continue start;
+ }
+ }
+ method.setAccessible(true);
+ list.add(method);
+ }
+ }
+ return list;
+ }
+
+}
diff --git a/src/main/java/cn/citycraft/TellRaw/common/TextualComponent.java b/src/main/java/cn/citycraft/TellRaw/common/TextualComponent.java
new file mode 100644
index 0000000..314ae67
--- /dev/null
+++ b/src/main/java/cn/citycraft/TellRaw/common/TextualComponent.java
@@ -0,0 +1,352 @@
+package cn.citycraft.TellRaw.common;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+import cn.citycraft.GsonAgent.api.stream.JsonWriter;
+
+/**
+ * Represents a textual component of a message part.
+ * This can be used to not only represent string literals in a JSON message,
+ * but also to represent localized strings and other text values.
+ *
+ * Different instances of this class can be created with static constructor
+ * methods.
+ *
+ */
+public abstract class TextualComponent implements Cloneable {
+ static {
+ ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class);
+ ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class);
+ }
+
+ public static TextualComponent deserialize(final Map map) {
+ if (map.containsKey("key") && map.size() == 2 && map.containsKey("value")) {
+ // Arbitrary text component
+ return ArbitraryTextTypeComponent.deserialize(map);
+ } else if (map.size() >= 2 && map.containsKey("key") && !map.containsKey("value")) {
+ // Complex JSON object
+ return ComplexTextTypeComponent.deserialize(map);
+ }
+
+ return null;
+ }
+
+ public static boolean isTextKey(final String key) {
+ return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector");
+ }
+
+ /**
+ * Create a textual component representing a localized string.
+ * The client will see this text component as their localized version of the
+ * specified string key, which can be overridden by a resource
+ * pack.
+ *
+ * If the specified translation key is not present on the client resource
+ * pack, the translation key will be displayed as a string literal to the
+ * client.
+ *
+ *
+ * @param translateKey
+ * The string key which maps to localized text.
+ * @return The text component representing the specified localized text.
+ */
+ public static TextualComponent localizedText(final String translateKey) {
+ return new ArbitraryTextTypeComponent("translate", translateKey);
+ }
+
+ /**
+ * Create a textual component representing a scoreboard value.
+ * The client will see their own score for the specified objective as the
+ * text represented by this component.
+ *
+ * This method is currently guaranteed to throw an
+ * {@code UnsupportedOperationException} as it is only supported on snapshot
+ * clients.
+ *
+ *
+ * @param scoreboardObjective
+ * The name of the objective for which to display the score.
+ * @return The text component representing the specified scoreboard score
+ * (for the viewing player), or {@code null} if an error occurs
+ * during JSON serialization.
+ */
+ public static TextualComponent objectiveScore(final String scoreboardObjective) {
+ return objectiveScore("*", scoreboardObjective);
+ }
+
+ /**
+ * Create a textual component representing a scoreboard value.
+ * The client will see the score of the specified player for the specified
+ * objective as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an
+ * {@code UnsupportedOperationException} as it is only supported on snapshot
+ * clients.
+ *
+ *
+ * @param playerName
+ * The name of the player whos score will be shown. If this
+ * string represents the single-character sequence "*", the
+ * viewing player's score will be displayed.
+ * Standard minecraft selectors (@a, @p, etc) are not
+ * supported.
+ * @param scoreboardObjective
+ * The name of the objective for which to display the score.
+ * @return The text component representing the specified scoreboard score
+ * for the specified player, or {@code null} if an error occurs
+ * during JSON serialization.
+ */
+ public static TextualComponent objectiveScore(final String playerName, final String scoreboardObjective) {
+ throwUnsupportedSnapshot(); // Remove this line when the feature is
+ // released to non-snapshot versions, in
+ // addition to updating ALL THE OVERLOADS
+ // documentation accordingly
+
+ return new ComplexTextTypeComponent("score", ImmutableMap. builder().put("name", playerName).put("objective", scoreboardObjective).build());
+ }
+
+ /**
+ * Create a textual component representing a string literal.
+ * This is the default type of textual component when a single string
+ * literal is given to a method.
+ *
+ * @param textValue
+ * The text which will be represented.
+ * @return The text component representing the specified literal text.
+ */
+ public static TextualComponent rawText(final String textValue) {
+ return new ArbitraryTextTypeComponent("text", textValue);
+ }
+
+ /**
+ * Create a textual component representing a player name, retrievable by
+ * using a standard minecraft selector.
+ * The client will see the players or entities captured by the specified
+ * selector as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an
+ * {@code UnsupportedOperationException} as it is only supported on snapshot
+ * clients.
+ *
+ *
+ * @param selector
+ * The minecraft player or entity selector which will capture the
+ * entities whose string representations will be displayed in the
+ * place of this text component.
+ * @return The text component representing the name of the entities captured
+ * by the selector.
+ */
+ public static TextualComponent selector(final String selector) {
+ throwUnsupportedSnapshot(); // Remove this line when the feature is
+ // released to non-snapshot versions, in
+ // addition to updating ALL THE OVERLOADS
+ // documentation accordingly
+
+ return new ArbitraryTextTypeComponent("selector", selector);
+ }
+
+ private static void throwUnsupportedSnapshot() {
+ throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
+ }
+
+ static boolean isTranslatableText(final TextualComponent component) {
+ return component instanceof ComplexTextTypeComponent && ((ComplexTextTypeComponent) component).getKey().equals("translate");
+ }
+
+ /**
+ * Clones a textual component instance.
+ * The returned object should not reference this textual component instance,
+ * but should maintain the same key and value.
+ */
+ @Override
+ public abstract TextualComponent clone() throws CloneNotSupportedException;
+
+ /**
+ * @return The JSON key used to represent text components of this type.
+ */
+ public abstract String getKey();
+
+ /**
+ * @return A readable String
+ */
+ public abstract String getReadableString();
+
+ @Override
+ public String toString() {
+ return getReadableString();
+ }
+
+ /**
+ * Writes the text data represented by this textual component to the
+ * specified JSON writer object.
+ * A new object within the writer is not started.
+ *
+ * @param writer
+ * The object to which to write the JSON data.
+ * @throws IOException
+ * If an error occurs while writing to the stream.
+ */
+ public abstract void writeJson(JsonWriter writer) throws IOException;
+
+ /**
+ * Internal class used to represent all types of text components.
+ * Exception validating done is on keys and values.
+ */
+ private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
+
+ private String _key;
+
+ private String _value;
+
+ public ArbitraryTextTypeComponent(final String key, final String value) {
+ setKey(key);
+ setValue(value);
+ }
+
+ public static ArbitraryTextTypeComponent deserialize(final Map map) {
+ return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
+ }
+
+ @Override
+ public TextualComponent clone() throws CloneNotSupportedException {
+ // Since this is a private and final class, we can just
+ // reinstantiate this class instead of casting super.clone
+ return new ArbitraryTextTypeComponent(getKey(), getValue());
+ }
+
+ @Override
+ public String getKey() {
+ return _key;
+ }
+
+ @Override
+ public String getReadableString() {
+ return getValue();
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Map serialize() {
+ return new HashMap() {
+ {
+ put("key", getKey());
+ put("value", getValue());
+ }
+ };
+ }
+
+ public void setKey(final String key) {
+ Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
+ _key = key;
+ }
+
+ public void setValue(final String value) {
+ Preconditions.checkArgument(value != null, "The value must be specified.");
+ _value = value;
+ }
+
+ @Override
+ public void writeJson(final JsonWriter writer) throws IOException {
+ writer.name(getKey()).value(getValue());
+ }
+ }
+
+ /**
+ * Internal class used to represent a text component with a nested JSON
+ * value.
+ * Exception validating done is on keys and values.
+ */
+ private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
+
+ private String _key;
+
+ private Map _value;
+
+ public ComplexTextTypeComponent(final String key, final Map values) {
+ setKey(key);
+ setValue(values);
+ }
+
+ public static ComplexTextTypeComponent deserialize(final Map map) {
+ String key = null;
+ final Map value = new HashMap();
+ for (final Map.Entry valEntry : map.entrySet()) {
+ if (valEntry.getKey().equals("key")) {
+ key = (String) valEntry.getValue();
+ } else if (valEntry.getKey().startsWith("value.")) {
+ value.put(valEntry.getKey().substring(6) /*
+ * Strips out the
+ * value prefix
+ */, valEntry.getValue().toString());
+ }
+ }
+ return new ComplexTextTypeComponent(key, value);
+ }
+
+ @Override
+ public TextualComponent clone() throws CloneNotSupportedException {
+ // Since this is a private and final class, we can just
+ // reinstantiate this class instead of casting super.clone
+ return new ComplexTextTypeComponent(getKey(), getValue());
+ }
+
+ @Override
+ public String getKey() {
+ return _key;
+ }
+
+ @Override
+ public String getReadableString() {
+ return getKey();
+ }
+
+ public Map getValue() {
+ return _value;
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Map serialize() {
+ return new java.util.HashMap() {
+ {
+ put("key", getKey());
+ for (final Map.Entry valEntry : getValue().entrySet()) {
+ put("value." + valEntry.getKey(), valEntry.getValue());
+ }
+ }
+ };
+ }
+
+ public void setKey(final String key) {
+ Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
+ _key = key;
+ }
+
+ public void setValue(final Map value) {
+ Preconditions.checkArgument(value != null, "The value must be specified.");
+ _value = value;
+ }
+
+ @Override
+ public void writeJson(final JsonWriter writer) throws IOException {
+ writer.name(getKey());
+ writer.beginObject();
+ for (final Map.Entry jsonPair : _value.entrySet()) {
+ writer.name(jsonPair.getKey()).value(jsonPair.getValue());
+ }
+ writer.endObject();
+ }
+ }
+}
diff --git a/src/main/java/cn/citycraft/TellRaw/internal/FancyMessageInternal.java b/src/main/java/cn/citycraft/TellRaw/internal/FancyMessageInternal.java
new file mode 100644
index 0000000..3dbff44
--- /dev/null
+++ b/src/main/java/cn/citycraft/TellRaw/internal/FancyMessageInternal.java
@@ -0,0 +1,223 @@
+package cn.citycraft.TellRaw.internal;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.bukkit.Achievement;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.Statistic.Type;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import cn.citycraft.TellRaw.FancyMessage;
+import cn.citycraft.TellRaw.common.Reflection;
+import cn.citycraft.TellRaw.common.TextualComponent;
+
+/**
+ * Represents a formattable message. Such messages can use elements such as
+ * colors, formatting codes, hover and click data, and other features provided
+ * by the vanilla Minecraft JSON message
+ * formatter.
+ * This class allows plugins to emulate the functionality of the vanilla
+ * Minecraft tellraw
+ * command.
+ *
+ * This class follows the builder pattern, allowing for method chaining. It is
+ * set up such that invocations of property-setting methods will affect the
+ * current editing component, and a call to {@link #then()} or
+ * {@link #then(Object)} will append a new editing component to the end of the
+ * message, optionally initializing it with text. Further property-setting
+ * method calls will affect that editing component.
+ *
+ */
+public class FancyMessageInternal extends FancyMessage {
+
+ /**
+ * 新建自动序列化类
+ */
+ public FancyMessageInternal() {
+ this("");
+ }
+
+ /**
+ * 新建自动序列化类
+ *
+ * @param firstPartText
+ * 第一个文本串
+ */
+ public FancyMessageInternal(final String firstPartText) {
+ super(TextualComponent.rawText(firstPartText));
+ }
+
+ @Override
+ public FancyMessage achievementTooltip(final Achievement which) {
+ try {
+ final Object achievement = getNMSAchievementMethod.invoke(null, which);
+ return achievementTooltip((String) nmsAchievement_NameField.get(achievement));
+ } catch (final IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (final IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (final InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occured durring invoking of method.", e);
+ return this;
+ }
+ }
+
+ @Override
+ public FancyMessage itemTooltip(final ItemStack itemStack) {
+ try {
+ final Object nmsItem = getOBCasNMSCopyMethod.invoke(null, itemStack);
+ return itemTooltip(getNMSsaveNBTMethod.invoke(nmsItem, nmsNBTTagCompound.newInstance()).toString());
+ } catch (final Exception e) {
+ e.printStackTrace();
+ return this;
+ }
+ }
+
+ @Override
+ public void send(final CommandSender sender, final String jsonString) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(toOldMessageFormat());
+ return;
+ }
+ final Player player = (Player) sender;
+ try {
+ final Object handle = Reflection.getHandle(player);
+ if (nmsPlayerConnectionField == null) {
+ try {
+ nmsPlayerConnectionField = Reflection.getField(handle.getClass(), "playerConnection");
+ } catch (final Exception e) {
+ for (final Field field : handle.getClass().getDeclaredFields()) {
+ if (field.getType().getSimpleName().contains("NetHandlerPlayServer")) {
+ nmsPlayerConnectionField = field;
+ break;
+ }
+ }
+ }
+ }
+ final Object connection = nmsPlayerConnectionField.get(handle);
+ if (nmsSendPacketMethod == null) {
+ try {
+ nmsSendPacketMethod = Reflection.getMethod(connection.getClass(), "sendPacket", nmsPacketClass);
+ } catch (final Exception e) {
+ nmsSendPacketMethod = Reflection.getMethodByParamsAndType(connection.getClass(), Void.TYPE, nmsPacketClass).get(0);
+ }
+ }
+ if (nmsSendPacketMethod == null) {
+ throw new RuntimeException("could find field: sendPacket in EntityPlayer class");
+ }
+ nmsSendPacketMethod.invoke(connection, createChatPacket(jsonString));
+ } catch (final IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ } catch (final IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ } catch (final InstantiationException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
+ } catch (final InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occured durring invoking of method.", e);
+ } catch (final NoSuchMethodException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
+ } catch (final ClassNotFoundException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
+ } catch (final SecurityException ex) {
+ Logger.getLogger(FancyMessageInternal.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ @Override
+ public FancyMessage statisticTooltip(final Statistic which) {
+ final Type type = which.getType();
+ if (type != Type.UNTYPED) {
+ throw new IllegalArgumentException("That statistic requires an additional " + type + " parameter!");
+ }
+ try {
+ final Object statistic = getNMSStatisticMethod.invoke(null, which);
+ return achievementTooltip((String) nmsStatistic_NameField.get(statistic));
+ } catch (final IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (final IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (final InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occured durring invoking of method.", e);
+ return this;
+ }
+ }
+
+ @Override
+ public FancyMessage statisticTooltip(final Statistic which, final EntityType entity) {
+ final Type type = which.getType();
+ if (type == Type.UNTYPED) {
+ throw new IllegalArgumentException("That statistic needs no additional parameter!");
+ }
+ if (type != Type.ENTITY) {
+ throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
+ }
+ try {
+ final Object statistic = getNMSEntityStatisticMethod.invoke(null, which, entity);
+ return achievementTooltip((String) nmsStatistic_NameField.get(statistic));
+ } catch (final IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (final IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (final InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occured durring invoking of method.", e);
+ return this;
+ }
+ }
+
+ @Override
+ public FancyMessage statisticTooltip(final Statistic which, final Material item) {
+ final Type type = which.getType();
+ if (type == Type.UNTYPED) {
+ throw new IllegalArgumentException("That statistic needs no additional parameter!");
+ }
+ if ((type == Type.BLOCK && item.isBlock()) || type == Type.ENTITY) {
+ throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
+ }
+ try {
+ final Object statistic = getNMSMaterialStatisticMethod.invoke(null, which, item);
+ return achievementTooltip((String) nmsStatistic_NameField.get(statistic));
+ } catch (final IllegalAccessException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
+ return this;
+ } catch (final IllegalArgumentException e) {
+ Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
+ return this;
+ } catch (final InvocationTargetException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A error has occured durring invoking of method.", e);
+ return this;
+ }
+ }
+
+ /**
+ * 创建Chat数据包
+ *
+ * @param json
+ * 需要创建的数据包
+ * @return 创建发包对象
+ * @throws IllegalArgumentException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ * @throws InvocationTargetException
+ * @throws NoSuchMethodException
+ * @throws ClassNotFoundException
+ */
+ private Object createChatPacket(final String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
+ final Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, nmsIChatBaseComponentClass);
+ return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent);
+ }
+}
diff --git a/src/main/java/cn/citycraft/TellRaw/manual/FancyMessageManual.java b/src/main/java/cn/citycraft/TellRaw/manual/FancyMessageManual.java
new file mode 100644
index 0000000..720f480
--- /dev/null
+++ b/src/main/java/cn/citycraft/TellRaw/manual/FancyMessageManual.java
@@ -0,0 +1,85 @@
+package cn.citycraft.TellRaw.manual;
+
+import org.bukkit.Achievement;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Statistic;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import cn.citycraft.TellRaw.FancyMessage;
+import cn.citycraft.TellRaw.common.TextualComponent;
+
+/**
+ * Represents a formattable message. Such messages can use elements such as
+ * colors, formatting codes, hover and click data, and other features provided
+ * by the vanilla Minecraft JSON message
+ * formatter.
+ * This class allows plugins to emulate the functionality of the vanilla
+ * Minecraft tellraw
+ * command.
+ *
+ * This class follows the builder pattern, allowing for method chaining. It is
+ * set up such that invocations of property-setting methods will affect the
+ * current editing component, and a call to {@link #then()} or
+ * {@link #then(Object)} will append a new editing component to the end of the
+ * message, optionally initializing it with text. Further property-setting
+ * method calls will affect that editing component.
+ *
+ */
+public class FancyMessageManual extends FancyMessage {
+ /**
+ * 新建手动序列化类
+ */
+ public FancyMessageManual() {
+ this("");
+ }
+
+ /**
+ * 新建手动序列化类
+ *
+ * @param firstPartText
+ * 第一个文本串
+ */
+ public FancyMessageManual(final String firstPartText) {
+ super(TextualComponent.rawText(firstPartText));
+ }
+
+ @Override
+ public FancyMessage achievementTooltip(final Achievement which) {
+ throw new UnsupportedOperationException("暂时不支持当前操作."); // To change body of generated methods, choose Tools | Templates.
+ }
+
+ @Override
+ public FancyMessage itemTooltip(final ItemStack itemStack) {
+ this.itemTooltip(new GItemStack(itemStack).toString());
+ return this;
+ }
+
+ @Override
+ public void send(final CommandSender sender, final String jsonString) {
+ if (sender instanceof Player) {
+ Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + sender.getName() + " " + jsonString);
+ } else {
+ sender.sendMessage(toOldMessageFormat());
+ }
+ }
+
+ @Override
+ public FancyMessage statisticTooltip(final Statistic which) {
+ throw new UnsupportedOperationException("暂时不支持当前操作.");
+ }
+
+ @Override
+ public FancyMessage statisticTooltip(final Statistic which, final EntityType entity) {
+ throw new UnsupportedOperationException("暂时不支持当前操作.");
+ }
+
+ @Override
+ public FancyMessage statisticTooltip(final Statistic which, final Material item) {
+ throw new UnsupportedOperationException("暂时不支持当前操作.");
+ }
+}
diff --git a/src/main/java/cn/citycraft/TellRaw/manual/GItemStack.java b/src/main/java/cn/citycraft/TellRaw/manual/GItemStack.java
new file mode 100644
index 0000000..8ab1ff5
--- /dev/null
+++ b/src/main/java/cn/citycraft/TellRaw/manual/GItemStack.java
@@ -0,0 +1,85 @@
+package cn.citycraft.TellRaw.manual;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+import java.util.logging.Level;
+
+import org.bukkit.Bukkit;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import cn.citycraft.GsonAgent.GsonAgent;
+import cn.citycraft.GsonAgent.api.stream.JsonWriter;
+import cn.citycraft.TellRaw.common.JsonRepresentedObject;
+
+/**
+ * 序列化Item的类
+ *
+ * @since 2015年12月14日 下午1:30:51
+ * @author 许凯
+ */
+public class GItemStack implements JsonRepresentedObject {
+ private final ItemStack item;
+ private final String jsonString;
+
+ protected GItemStack(final ItemStack item) {
+ this.item = item.clone();
+ final StringWriter string = new StringWriter();
+ final JsonWriter json = GsonAgent.newJsonWriter(string);
+ try {
+ this.writeJson(json);
+ json.close();
+ } catch (final IOException e) {
+ throw new RuntimeException("invalid message:", e);
+ }
+ this.jsonString = string.toString();
+ }
+
+ @Override
+ public String toString() {
+ return this.jsonString;
+ }
+
+ @Override
+ public void writeJson(final JsonWriter json) throws IOException {
+ try {
+ json.beginObject();
+ json.nameWithoutQuotes("id").value(this.item.getTypeId());
+ json.nameWithoutQuotes("Damage").value(this.item.getDurability());
+ if (this.item.getAmount() > 1) {
+ json.nameWithoutQuotes("Count").value(this.item.getAmount());
+ }
+ if (this.item.hasItemMeta()) {
+ json.nameWithoutQuotes("tag").beginObject();
+ final ItemMeta im = item.getItemMeta();
+ if (im.hasEnchants()) {
+ json.nameWithoutQuotes("ench").beginArray();
+ for (final Map.Entry ench : im.getEnchants().entrySet()) {
+ json.beginObject().nameWithoutQuotes("id").value(ench.getKey().getId()).nameWithoutQuotes("lvl").value(ench.getValue()).endObject();
+ }
+ json.endArray();
+ }
+ if (im.hasDisplayName() || im.hasLore()) {
+ json.nameWithoutQuotes("display").beginObject();
+ if (im.hasDisplayName()) {
+ json.nameWithoutQuotes("Name").value(im.getDisplayName());
+ }
+ if (im.hasLore()) {
+ json.nameWithoutQuotes("Lore").beginArray();
+ for (final String line : im.getLore()) {
+ json.value(line);
+ }
+ json.endArray();
+ }
+ json.endObject();
+ }
+ json.endObject();
+ }
+ json.endObject();
+ } catch (final IOException e) {
+ Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e);
+ }
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..b6ab82e
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,13 @@
+Data:
+ #数据保存方式 [sqlite|MySQL]
+ FileSystem: sqlite
+ #MySQL数据库配置 只有当FileSystem配置为MySQL时有效
+ MySQL:
+ #数据库需要自行建立
+ database: minecraft
+ #数据表需要自行建立
+ tablename: prefixs
+ username: root
+ password:
+ ip: localhost
+ port: 3306
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..3f87c06
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,21 @@
+name: ${project.artifactId}
+description: ${project.description}
+main: ${project.groupId}.${project.artifactId}.${project.artifactId}
+version: ${project.version}-Build#${env.GIT_ID}
+author: 喵♂呜
+website: ${jenkins.url}/job/${project.artifactId}/
+commands:
+ ${project.artifactId}:
+ description: ${project.artifactId} - ${project.description}
+ aliases:
+ - xxxx
+ usage: §b使用/${project.artifactId} help 查看帮助!
+ permission: ${project.artifactId}.reload
+ permission-message: §c你没有 的权限来执行此命令!
+permissions:
+ ${project.artifactId}.use:
+ description: ${project.artifactId} 使用!
+ default: true
+ ${project.artifactId}.reload:
+ description: 重新载入插件!
+ default: op
\ No newline at end of file