2016-01-13 05:31:39 +00:00
package cn.citycraft.TellRaw ;
import static cn.citycraft.TellRaw.common.TextualComponent.rawText ;
import java.io.IOException ;
import java.io.StringWriter ;
import java.lang.reflect.Constructor ;
import java.lang.reflect.Field ;
import java.lang.reflect.Method ;
import java.lang.reflect.Modifier ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.HashMap ;
import java.util.Iterator ;
import java.util.List ;
import java.util.Map ;
import java.util.logging.Level ;
import org.bukkit.Achievement ;
import org.bukkit.Bukkit ;
import org.bukkit.ChatColor ;
import org.bukkit.Material ;
import org.bukkit.Statistic ;
import org.bukkit.command.CommandSender ;
import org.bukkit.configuration.serialization.ConfigurationSerializable ;
import org.bukkit.configuration.serialization.ConfigurationSerialization ;
import org.bukkit.entity.EntityType ;
import org.bukkit.entity.Player ;
import org.bukkit.inventory.ItemStack ;
import org.bukkit.plugin.Plugin ;
import com.comphenix.protocol.utility.MinecraftReflection ;
import cn.citycraft.GsonAgent.GsonAgent ;
import cn.citycraft.GsonAgent.api.JsonArray ;
import cn.citycraft.GsonAgent.api.JsonElement ;
import cn.citycraft.GsonAgent.api.JsonObject ;
import cn.citycraft.GsonAgent.api.JsonParser ;
import cn.citycraft.GsonAgent.api.stream.JsonWriter ;
import cn.citycraft.TellRaw.common.ArrayWrapper ;
import cn.citycraft.TellRaw.common.JsonRepresentedObject ;
import cn.citycraft.TellRaw.common.JsonString ;
import cn.citycraft.TellRaw.common.MessagePart ;
import cn.citycraft.TellRaw.common.Reflection ;
import cn.citycraft.TellRaw.common.TextualComponent ;
import cn.citycraft.TellRaw.internal.FancyMessageInternal ;
import cn.citycraft.TellRaw.manual.FancyMessageManual ;
/ * *
* Represents a formattable message . Such messages can use elements such as
* colors , formatting codes , hover and click data , and other features provided
* by the vanilla Minecraft < a
* href = "http://minecraft.gamepedia.com/Tellraw#Raw_JSON_Text" > JSON message
* formatter < / a > .
* This class allows plugins to emulate the functionality of the vanilla
* Minecraft < a href = "http://minecraft.gamepedia.com/Commands#tellraw" > tellraw
* command < / a > .
* < p >
* This class follows the builder pattern , allowing for method chaining . It is
* set up such that invocations of property - setting methods will affect the
* current editing component , and a call to { @link # then ( ) } or
* { @link # then ( 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 .
* < / p >
* /
public abstract class FancyMessage implements JsonRepresentedObject , Cloneable , Iterable < MessagePart > , ConfigurationSerializable {
private static JsonParser _stringParser ;
private static Class < ? extends FancyMessage > internalClass ;
protected static Class < ? > chatSerializerClazz ;
protected static Method fromJsonMethod ;
protected static Method getNMSAchievementMethod = null ;
protected static Method getNMSEntityStatisticMethod = null ;
protected static Method getNMSMaterialStatisticMethod = null ;
protected static Method getNMSsaveNBTMethod = null ;
protected static Method getNMSStatisticMethod = null ;
protected static Method getOBCasNMSCopyMethod = null ;
protected static Field nmsAchievement_NameField = null ;
protected static Class < ? > nmsAchievementClass = null ;
protected static Object nmsChatSerializerGsonInstance ;
protected static Class < ? > nmsIChatBaseComponentClass ;
protected static Class < ? > nmsItemStack = null ;
protected static Class < ? > nmsNBTTagCompound = null ;
protected static Class < ? > nmsPacketClass ;
protected static Constructor < ? > nmsPacketPlayOutChatConstructor ;
protected static Field nmsPlayerConnectionField = null ;
protected static Method nmsSendPacketMethod = null ;
protected static Field nmsStatistic_NameField = null ;
protected static Class < ? > nmsStatisticClass = null ;
protected static Class < ? > obcCraftItemStack = null ;
protected static Class < ? > obcCraftStatisticClass = null ;
static {
ConfigurationSerialization . registerClass ( FancyMessage . class ) ;
if ( PluginHelperConfig . getConfig ( ) . getBoolean ( "TellrawManualHandle" , false ) ) {
internalClass = FancyMessageManual . class ;
Bukkit . getLogger ( ) . info ( "[TellRawLib] 指定使用手动序列化类处理..." ) ;
} else {
boolean useProtocolLib = false ;
final Plugin plugin = Bukkit . getPluginManager ( ) . getPlugin ( "ProtocolLib" ) ;
if ( plugin ! = null ) {
if ( ! plugin . isEnabled ( ) ) {
Bukkit . getPluginManager ( ) . enablePlugin ( plugin ) ;
}
if ( plugin . isEnabled ( ) ) {
useProtocolLib = true ;
}
}
try {
_stringParser = GsonAgent . newJsonParser ( ) ;
if ( ! useProtocolLib ) {
try {
nmsPacketPlayOutChatConstructor = Reflection . getNMSClass ( "PacketPlayOutChat" ) . getDeclaredConstructor ( Reflection . getNMSClass ( "IChatBaseComponent" ) ) ;
nmsPacketPlayOutChatConstructor . setAccessible ( true ) ;
} catch ( final NoSuchMethodException e ) {
Bukkit . getLogger ( ) . log ( Level . SEVERE , "Could not find Minecraft method or constructor." , e ) ;
throw e ;
} catch ( final SecurityException e ) {
Bukkit . getLogger ( ) . log ( Level . WARNING , "Could not access constructor." , e ) ;
throw e ;
}
} else {
try {
nmsPacketPlayOutChatConstructor = com . comphenix . protocol . PacketType . Play . Server . CHAT . getPacketClass ( ) . getDeclaredConstructor ( MinecraftReflection . getIChatBaseComponentClass ( ) ) ;
nmsPacketPlayOutChatConstructor . setAccessible ( true ) ;
} catch ( final NoSuchMethodException ex ) {
Bukkit . getLogger ( ) . log ( Level . SEVERE , "Could not find Minecraft method or constructor." , ex ) ;
throw ex ;
} catch ( final SecurityException e ) {
Bukkit . getLogger ( ) . log ( Level . WARNING , "Could not access constructor." , e ) ;
throw e ;
}
}
internalClass = FancyMessageInternal . class ;
if ( ! useProtocolLib ) {
try {
obcCraftStatisticClass = Reflection . getOBCClass ( "CraftStatistic" ) ;
getNMSStatisticMethod = Reflection . getMethod ( obcCraftStatisticClass , "getNMSStatistic" , Statistic . class ) ;
nmsStatisticClass = Reflection . getNMSClass ( "Statistic" ) ;
try {
nmsStatistic_NameField = Reflection . getField ( nmsStatisticClass , "name" ) ;
} catch ( final Exception e ) {
nmsStatistic_NameField = Reflection . getDeclaredFieldByType ( nmsStatisticClass , String . class ) . get ( 0 ) ;
}
getNMSMaterialStatisticMethod = Reflection . getMethod ( obcCraftStatisticClass , "getMaterialStatistic" , Statistic . class , Material . class ) ;
getNMSEntityStatisticMethod = Reflection . getMethod ( obcCraftStatisticClass , "getEntityStatistic" , Statistic . class , EntityType . class ) ;
nmsAchievementClass = Reflection . getNMSClass ( "Achievement" ) ;
getNMSAchievementMethod = Reflection . getMethod ( obcCraftStatisticClass , "getNMSAchievement" , Achievement . class ) ;
try {
nmsAchievement_NameField = Reflection . getField ( nmsAchievementClass , "name" ) ;
} catch ( final Exception e ) {
nmsAchievement_NameField = Reflection . getDeclaredFieldByType ( nmsAchievementClass , String . class ) . get ( 0 ) ;
}
obcCraftItemStack = Reflection . getOBCClass ( "inventory.CraftItemStack" ) ;
getOBCasNMSCopyMethod = Reflection . getMethod ( obcCraftItemStack , "asNMSCopy" , ItemStack . class ) ;
nmsItemStack = Reflection . getNMSClass ( "ItemStack" ) ;
nmsNBTTagCompound = Reflection . getNMSClass ( "NBTTagCompound" ) ;
try {
getNMSsaveNBTMethod = Reflection . getMethod ( nmsItemStack , "save" , nmsNBTTagCompound ) ;
} catch ( final Exception e ) {
getNMSsaveNBTMethod = Reflection . getMethodByParamsAndType ( nmsItemStack , nmsNBTTagCompound , nmsNBTTagCompound ) . get ( 0 ) ;
}
nmsPacketClass = Reflection . getNMSClass ( "Packet" ) ;
try {
chatSerializerClazz = Reflection . getNMSClass ( "IChatBaseComponent$ChatSerializer" ) ;
} catch ( final Exception e ) {
chatSerializerClazz = Reflection . getNMSClass ( "ChatSerializer" ) ;
}
nmsIChatBaseComponentClass = Reflection . getNMSClass ( "IChatBaseComponent" ) ;
} catch ( final Exception e ) {
throw e ;
}
} else {
try {
obcCraftStatisticClass = MinecraftReflection . getCraftBukkitClass ( "CraftStatistic" ) ;
getNMSStatisticMethod = Reflection . getMethod ( obcCraftStatisticClass , "getNMSStatistic" , Statistic . class ) ;
nmsStatisticClass = MinecraftReflection . getMinecraftClass ( "Statistic" ) ;
try {
nmsStatistic_NameField = Reflection . getField ( nmsStatisticClass , "name" ) ;
} catch ( final Exception e ) {
nmsStatistic_NameField = Reflection . getDeclaredFieldByType ( nmsStatisticClass , String . class ) . get ( 0 ) ;
}
getNMSMaterialStatisticMethod = Reflection . getMethod ( obcCraftStatisticClass , "getMaterialStatistic" , Statistic . class , Material . class ) ;
getNMSEntityStatisticMethod = Reflection . getMethod ( obcCraftStatisticClass , "getEntityStatistic" , Statistic . class , EntityType . class ) ;
nmsAchievementClass = MinecraftReflection . getMinecraftClass ( "Achievement" ) ;
getNMSAchievementMethod = Reflection . getMethod ( obcCraftStatisticClass , "getNMSAchievement" , Achievement . class ) ;
try {
nmsAchievement_NameField = Reflection . getField ( nmsAchievementClass , "name" ) ;
} catch ( final Exception e ) {
nmsAchievement_NameField = Reflection . getDeclaredFieldByType ( nmsAchievementClass , String . class ) . get ( 0 ) ;
}
obcCraftItemStack = MinecraftReflection . getCraftItemStackClass ( ) ;
getOBCasNMSCopyMethod = Reflection . getMethod ( obcCraftItemStack , "asNMSCopy" , ItemStack . class ) ;
nmsItemStack = MinecraftReflection . getItemStackClass ( ) ;
nmsNBTTagCompound = MinecraftReflection . getNBTCompoundClass ( ) ;
try {
getNMSsaveNBTMethod = Reflection . getMethod ( nmsItemStack , "save" , nmsNBTTagCompound ) ;
} catch ( final Exception e ) {
getNMSsaveNBTMethod = Reflection . getMethodByParamsAndType ( nmsItemStack , nmsNBTTagCompound , nmsNBTTagCompound ) . get ( 0 ) ;
}
nmsPacketClass = MinecraftReflection . getPacketClass ( ) ;
chatSerializerClazz = MinecraftReflection . getChatSerializerClass ( ) ;
// Since the method is so simple, and all the obfuscated methods have the same name, it's easier to reimplement 'IChatBaseComponent a(String)' than to reflectively call it
// Of course, the implementation may change, but fuzzy matches might break with signature changes
nmsIChatBaseComponentClass = MinecraftReflection . getIChatBaseComponentClass ( ) ;
} catch ( final Exception e ) {
throw e ;
}
}
if ( nmsChatSerializerGsonInstance = = null ) {
// Find the field and its value, completely bypassing obfuscation
if ( chatSerializerClazz = = null ) {
throw new ClassNotFoundException ( "Can't find the ChatSerializer class" ) ;
}
for ( final Field declaredField : chatSerializerClazz . getDeclaredFields ( ) ) {
if ( Modifier . isFinal ( declaredField . getModifiers ( ) ) & & Modifier . isStatic ( declaredField . getModifiers ( ) ) & & declaredField . getType ( ) . getName ( ) . endsWith ( "Gson" ) ) {
// We've found our field
declaredField . setAccessible ( true ) ;
nmsChatSerializerGsonInstance = declaredField . get ( null ) ;
fromJsonMethod = nmsChatSerializerGsonInstance . getClass ( ) . getMethod ( "fromJson" , String . class , Class . class ) ;
break ;
}
}
}
2016-01-13 08:15:17 +00:00
if ( getNMSsaveNBTMethod = = null | | nmsNBTTagCompound = = null ) {
throw new ClassNotFoundException ( "Can't find the NBTMethod or NBTTagCompound class" ) ;
}
2016-01-13 05:31:39 +00:00
if ( ! useProtocolLib ) {
Bukkit . getLogger ( ) . info ( "[TellRawLib] 使用ChatSerializer序列化类处理..." ) ;
} else {
Bukkit . getLogger ( ) . info ( "[TellRawLib] 使用ProtocolLib序列化类处理..." ) ;
}
} catch ( final Exception | Error e ) {
internalClass = FancyMessageManual . class ;
Bukkit . getLogger ( ) . info ( "[TellRawLib] 使用GItemStack序列化类处理..." ) ;
}
}
}
private boolean dirty ;
private String jsonString ;
protected List < MessagePart > messageParts ;
public FancyMessage ( final TextualComponent firstPartText ) {
messageParts = new ArrayList < MessagePart > ( ) ;
messageParts . add ( new MessagePart ( firstPartText ) ) ;
}
/ * *
* Deserializes a JSON - represented message from a mapping of key - value
* pairs .
* This is called by the Bukkit serialization API .
* It is not intended for direct public API consumption .
*
* @param serialized
* The key - value mapping which represents a fancy message .
* /
@SuppressWarnings ( "unchecked" )
public static FancyMessage deserialize ( final Map < String , Object > serialized ) {
final FancyMessage msg = newFM ( ) ;
msg . messageParts = ( List < MessagePart > ) serialized . get ( "messageParts" ) ;
msg . jsonString = serialized . containsKey ( "JSON" ) ? serialized . get ( "JSON" ) . toString ( ) : null ;
msg . dirty = ! serialized . containsKey ( "JSON" ) ;
return msg ;
}
/ * *
* Deserializes a fancy message from its JSON representation . This JSON
* representation is of the format of
* that returned by { @link # toJSONString ( ) } , and is compatible with vanilla
* inputs .
*
* @param json
* The JSON string which represents a fancy message .
* @return A { @code FancyMessage } representing the parameterized JSON
* message .
* /
public static FancyMessage deserialize ( final String json ) {
final JsonObject serialized = _stringParser . parse ( json ) . getAsJsonObject ( ) ;
final JsonArray extra = serialized . getAsJsonArray ( "extra" ) ; // Get the extra
// component
final FancyMessage returnVal = newFM ( ) ;
returnVal . messageParts . clear ( ) ;
for ( final JsonElement mPrt : extra ) {
final MessagePart component = new MessagePart ( ) ;
final JsonObject messagePart = mPrt . getAsJsonObject ( ) ;
for ( final Map . Entry < String , JsonElement > entry : messagePart . entrySet ( ) ) {
// Deserialize text
if ( TextualComponent . isTextKey ( entry . getKey ( ) ) ) {
// The map mimics the YAML serialization, which has a "key" field and one or more "value" fields
final Map < String , Object > serializedMapForm = new HashMap < String , Object > ( ) ; // Must be object due to Bukkit serializer API compliance
serializedMapForm . put ( "key" , entry . getKey ( ) ) ;
if ( entry . getValue ( ) . isJsonPrimitive ( ) ) {
// Assume string
serializedMapForm . put ( "value" , entry . getValue ( ) . getAsString ( ) ) ;
} else {
// Composite object, but we assume each element is a string
for ( final Map . Entry < String , JsonElement > compositeNestedElement : entry . getValue ( ) . getAsJsonObject ( ) . entrySet ( ) ) {
serializedMapForm . put ( "value." + compositeNestedElement . getKey ( ) , compositeNestedElement . getValue ( ) . getAsString ( ) ) ;
}
}
component . text = TextualComponent . deserialize ( serializedMapForm ) ;
} else if ( MessagePart . stylesToNames . inverse ( ) . containsKey ( entry . getKey ( ) ) ) {
if ( entry . getValue ( ) . getAsBoolean ( ) ) {
component . styles . add ( MessagePart . stylesToNames . inverse ( ) . get ( entry . getKey ( ) ) ) ;
}
} else if ( entry . getKey ( ) . equals ( "color" ) ) {
component . color = ChatColor . valueOf ( entry . getValue ( ) . getAsString ( ) . toUpperCase ( ) ) ;
} else if ( entry . getKey ( ) . equals ( "clickEvent" ) ) {
final JsonObject object = entry . getValue ( ) . getAsJsonObject ( ) ;
component . clickActionName = object . get ( "action" ) . getAsString ( ) ;
component . clickActionData = object . get ( "value" ) . getAsString ( ) ;
} else if ( entry . getKey ( ) . equals ( "hoverEvent" ) ) {
final JsonObject object = entry . getValue ( ) . getAsJsonObject ( ) ;
component . hoverActionName = object . get ( "action" ) . getAsString ( ) ;
if ( object . get ( "value" ) . isJsonPrimitive ( ) ) {
// Assume string
component . hoverActionData = new JsonString ( object . get ( "value" ) . getAsString ( ) ) ;
} else {
// Assume composite type
component . hoverActionData = deserialize ( object . get ( "value" ) . toString ( ) ) ;
}
} else if ( entry . getKey ( ) . equals ( "insertion" ) ) {
component . insertionData = entry . getValue ( ) . getAsString ( ) ;
} else if ( entry . getKey ( ) . equals ( "with" ) ) {
for ( final JsonElement object : entry . getValue ( ) . getAsJsonArray ( ) ) {
if ( object . isJsonPrimitive ( ) ) {
component . translationReplacements . add ( new JsonString ( object . getAsString ( ) ) ) ;
} else {
component . translationReplacements . add ( deserialize ( object . toString ( ) ) ) ;
}
}
}
}
returnVal . messageParts . add ( component ) ;
}
return returnVal ;
}
/ * *
* 获 得 消 息 工 厂 类
*
* @return 消 息 工 厂
* /
public static FancyMessage newFM ( ) {
try {
return FancyMessage . internalClass . newInstance ( ) ;
} catch ( final Exception e ) {
return new FancyMessageManual ( ) ;
}
}
/ * *
* 获 得 消 息 工 厂 类
*
* @param firstPartText
* 首 字 符 串
* @return 消 息 工 厂
* /
public static FancyMessage newFM ( final String firstPartText ) {
return newFM ( ) . text ( firstPartText ) ;
}
/ * *
* 在 客 户 端 显 示 一 个 成 就 .
* < / p >
* 当 前 方 法 将 不 会 继 承 本 类 的 颜 色 样 式 等 参 数 .
* < / p >
*
* @param which
* 需 要 显 示 的 成 就
*
* @return { @link FancyMessage }
* /
public abstract FancyMessage achievementTooltip ( final Achievement which ) ;
/ * *
* 在 客 户 端 显 示 一 个 成 就 .
* < / p >
* 当 前 方 法 将 不 会 继 承 本 类 的 颜 色 样 式 等 参 数 .
* < / p >
*
* @param name
* 需 要 显 示 的 成 就 名 称
*
* @return { @link FancyMessage }
* /
public FancyMessage achievementTooltip ( final String name ) {
onHover ( "show_achievement" , new JsonString ( "achievement." + name ) ) ;
return this ;
}
@Override
public FancyMessage clone ( ) throws CloneNotSupportedException {
final FancyMessage instance = ( FancyMessage ) super . clone ( ) ;
instance . messageParts = new ArrayList < MessagePart > ( messageParts . size ( ) ) ;
for ( int i = 0 ; i < messageParts . size ( ) ; i + + ) {
instance . messageParts . add ( i , messageParts . get ( i ) . clone ( ) ) ;
}
instance . dirty = false ;
instance . jsonString = null ;
return instance ;
}
/ * *
* 设 置 当 前 被 编 辑 串 的
*
* @param color
* 当 前 串 的 颜 色
* @return { @link FancyMessage }
* @exception IllegalArgumentException
* 如 果 参 数 不 是 一 个 { @link ChatColor } 的 颜 色 值 ( 是 一 个 样 式 字 符 ) .
* /
public FancyMessage color ( final ChatColor color ) {
if ( ! color . isColor ( ) ) {
throw new IllegalArgumentException ( color . name ( ) + "不是一个颜色" ) ;
}
latest ( ) . color = color ;
dirty = true ;
return this ;
}
/ * *
* 服 务 器 将 特 殊 的 字 符 串 发 送 给 客 户 端 .
* < / p >
* 当 玩 家 点 击 时 客 户 端 < b > 将 会 < / b > 立 即 将 命 令 发 送 给 服 务 器 .
* < / p >
* 命 令 将 绑 定 在 当 前 编 辑 的 文 本 上
* < / p >
*
* @param command
* 点 击 执 行 命 令
* @return { @link FancyMessage }
* /
public FancyMessage command ( final String command ) {
onClick ( "run_command" , command ) ;
return this ;
}
/ * *
* Set the behavior of the current editing component to instruct the client
* to open a file on the client side filesystem when the currently edited
* part of the { @code FancyMessage } is clicked .
*
* @param path
* The path of the file on the client filesystem .
* @return { @link FancyMessage }
* /
public FancyMessage file ( final String path ) {
onClick ( "open_file" , path ) ;
return this ;
}
/ * *
* Set the behavior of the current editing component to display formatted
* text when the client hovers over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param text
* The formatted text which will be displayed to the client upon
* hovering .
* @return { @link FancyMessage }
* /
public FancyMessage formattedTooltip ( final FancyMessage text ) {
for ( final MessagePart component : text . messageParts ) {
if ( component . clickActionData ! = null & & component . clickActionName ! = null ) {
throw new IllegalArgumentException ( "The tooltip text cannot have click data." ) ;
} else if ( component . hoverActionData ! = null & & component . hoverActionName ! = null ) {
throw new IllegalArgumentException ( "The tooltip text cannot have a tooltip." ) ;
}
}
onHover ( "show_text" , text ) ;
return this ;
}
/ * *
* Set the behavior of the current editing component to display the
* specified lines of formatted text when the client hovers over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param lines
* The lines of formatted text which will be displayed to the
* client upon hovering .
* @return { @link FancyMessage }
* /
public FancyMessage formattedTooltip ( final FancyMessage . . . lines ) {
if ( lines . length < 1 ) {
onHover ( null , null ) ; // Clear tooltip
return this ;
}
final FancyMessage result = newFM ( ) ;
result . messageParts . clear ( ) ; // Remove the one existing text component
// that exists by default, which
// destabilizes the object
for ( int i = 0 ; i < lines . length ; i + + ) {
try {
for ( final MessagePart component : lines [ i ] ) {
if ( component . clickActionData ! = null & & component . clickActionName ! = null ) {
throw new IllegalArgumentException ( "The tooltip text cannot have click data." ) ;
} else if ( component . hoverActionData ! = null & & component . hoverActionName ! = null ) {
throw new IllegalArgumentException ( "The tooltip text cannot have a tooltip." ) ;
}
if ( component . hasText ( ) ) {
result . messageParts . add ( component . clone ( ) ) ;
}
}
if ( i ! = lines . length - 1 ) {
result . messageParts . add ( new MessagePart ( rawText ( "\n" ) ) ) ;
}
} catch ( final CloneNotSupportedException e ) {
Bukkit . getLogger ( ) . log ( Level . WARNING , "Failed to clone object" , e ) ;
return this ;
}
}
return formattedTooltip ( result . messageParts . isEmpty ( ) ? null : result ) ;
}
/ * *
* Set the behavior of the current editing component to display the
* specified lines of formatted text when the client hovers over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param lines
* The lines of text which will be displayed to the client upon
* hovering . The iteration order of this object will be the order
* in which the lines of the tooltip are created .
* @return { @link FancyMessage }
* /
public FancyMessage formattedTooltip ( final Iterable < FancyMessage > lines ) {
return formattedTooltip ( ArrayWrapper . toArray ( lines , FancyMessage . class ) ) ;
}
/ * *
* Set the behavior of the current editing component to instruct the client
* to append the chat input box content with the specified string when the
* currently edited part of the { @code FancyMessage } is SHIFT - CLICKED .
* The client will not immediately send the command to the server to be
* executed unless the client player submits the command / chat message ,
* usually with the enter key .
*
* @param command
* The text to append to the chat bar of the client .
* @return { @link FancyMessage }
* /
public FancyMessage insert ( final String command ) {
latest ( ) . insertionData = command ;
dirty = true ;
return this ;
}
/ * *
* Set the behavior of the current editing component to display information
* about an item when the client hovers over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param itemStack
* The stack for which to display information .
* @return { @link FancyMessage }
* /
public abstract FancyMessage itemTooltip ( final ItemStack itemStack ) ;
/ * *
* Set the behavior of the current editing component to display information
* about an item when the client hovers over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param itemJSON
* A string representing the JSON - serialized NBT data tag of an
* { @link ItemStack } .
* @return { @link FancyMessage }
* /
public FancyMessage itemTooltip ( final String itemJSON ) {
onHover ( "show_item" , new JsonString ( itemJSON ) ) ;
return this ;
}
/ * *
* < b > Internally called method . Not for API consumption . < / b >
* /
@Override
public Iterator < MessagePart > iterator ( ) {
return messageParts . iterator ( ) ;
}
/ * *
* Set the behavior of the current editing component to instruct the client
* to open a webpage in the client ' s web browser when the currently edited
* part of the { @code FancyMessage } is clicked .
*
* @param url
* The URL of the page to open when the link is clicked .
* @return { @link FancyMessage }
* /
public FancyMessage link ( final String url ) {
onClick ( "open_url" , url ) ;
return this ;
}
/ * *
* Sends this message to a command sender .
* If the sender is a player , they will receive the fully - fledged formatted
* display of this message .
* Otherwise , they will receive a version of this message with less
* formatting .
*
* @param sender
* The command sender who will receive the message .
* @see # toOldMessageFormat ( )
* /
public void send ( final CommandSender sender ) {
send ( sender , toJSONString ( ) ) ;
}
public abstract void send ( final CommandSender sender , final String jsonString ) ;
/ * *
* Sends this message to multiple command senders .
*
* @param senders
* The command senders who will receive the message .
* @see # send ( CommandSender )
* /
public void send ( final Iterable < ? extends CommandSender > senders ) {
final String string = toJSONString ( ) ;
for ( final CommandSender sender : senders ) {
send ( sender , string ) ;
}
}
/ * *
* Sends this message to a player . The player will receive the fully - fledged
* formatted display of this message .
*
* @param player
* The player who will receive the message .
* /
public void send ( final Player player ) {
send ( player , toJSONString ( ) ) ;
}
@Override
public Map < String , Object > serialize ( ) {
final HashMap < String , Object > map = new HashMap < String , Object > ( ) ;
map . put ( "messageParts" , messageParts ) ;
// map.put("JSON", toJSONString());
return map ;
}
/ * *
* Set the behavior of the current editing component to display information
* about a parameterless statistic when the client hovers over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param which
* The statistic to display .
* @return { @link FancyMessage }
* @exception IllegalArgumentException
* If the statistic requires a parameter which was not
* supplied .
* /
public abstract FancyMessage statisticTooltip ( final Statistic which ) ;
/ * *
* Set the behavior of the current editing component to display information
* about a statistic parameter with an entity type when the client hovers
* over the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param which
* The statistic to display .
* @param entity
* The sole entity type parameter to the statistic .
* @return { @link FancyMessage }
* @exception IllegalArgumentException
* If the statistic requires a parameter which was not
* supplied , or was supplied a parameter that was not
* required .
* /
public abstract FancyMessage statisticTooltip ( final Statistic which , final EntityType entity ) ;
/ * *
* Set the behavior of the current editing component to display information
* about a statistic parameter with a material when the client hovers over
* the text .
* < p >
* Tooltips do not inherit display characteristics , such as color and
* styles , from the message component on which they are applied .
* < / p >
*
* @param which
* The statistic to display .
* @param item
* The sole material parameter to the statistic .
* @return { @link FancyMessage }
* @exception IllegalArgumentException
* If the statistic requires a parameter which was not
* supplied , or was supplied a parameter that was not
* required .
* /
public abstract FancyMessage statisticTooltip ( final Statistic which , final Material item ) ;
/ * *
* Sets the stylization of the current editing component .
*
* @param styles
* The array of styles to apply to the editing component .
* @return { @link FancyMessage }
* @exception IllegalArgumentException
* If any of the enumeration values in the array do not
* represent formatters .
* /
public FancyMessage style ( final ChatColor . . . styles ) {
for ( final ChatColor style : styles ) {
if ( ! style . isFormat ( ) ) {
throw new IllegalArgumentException ( style . name ( ) + " is not a style" ) ;
}
}
latest ( ) . styles . addAll ( Arrays . asList ( styles ) ) ;
dirty = true ;
return this ;
}
/ * *
* Set the behavior of the current editing component to instruct the client
* to replace the chat input box content with the specified string when the
* currently edited part of the { @code FancyMessage } is clicked .
* The client will not immediately send the command to the server to be
* executed unless the client player submits the command / chat message ,
* usually with the enter key .
*
* @param command
* The text to display in the chat bar of the client .
* @return { @link FancyMessage }
* /
public FancyMessage suggest ( final String command ) {
onClick ( "suggest_command" , command ) ;
return this ;
}
/ * *
* 设 置 当 前 消 息 串 的 文 本
*
* @param text
* 需 要 设 置 的 文 本
* @return { @link FancyMessage }
* /
public FancyMessage text ( final String text ) {
final MessagePart latest = latest ( ) ;
latest . text = rawText ( text ) ;
dirty = true ;
return this ;
}
/ * *
* 设 置 当 前 消 息 串 的 文 本
*
* @param text
* 需 要 设 置 的 文 本
* @return { @link FancyMessage }
* /
public FancyMessage text ( final TextualComponent text ) {
final MessagePart latest = latest ( ) ;
latest . text = text ;
dirty = true ;
return this ;
}
/ * *
* 停 止 编 辑 当 前 的 消 息 串 并 且 开 始 一 个 新 的 消 息 串
* < / p >
* 之 后 的 操 作 将 会 应 用 于 新 的 消 息 串 上
*
* @return { @link FancyMessage }
* /
public FancyMessage then ( ) {
if ( ! latest ( ) . hasText ( ) ) {
throw new IllegalStateException ( "previous message part has no text" ) ;
}
messageParts . add ( new MessagePart ( ) ) ;
dirty = true ;
return this ;
}
/ * *
* 停 止 编 辑 当 前 的 消 息 串 并 且 开 始 一 个 新 的 消 息 串
* < / p >
* 之 后 的 操 作 将 会 应 用 于 新 的 消 息 串 上
*
* @param text
* 一 个 新 的 操 作 串 需 要 的 文 本 .
* @return { @link FancyMessage }
* /
public FancyMessage then ( final String text ) {
return then ( rawText ( text ) ) ;
}
/ * *
* 停 止 编 辑 当 前 的 消 息 串 并 且 开 始 一 个 新 的 消 息 串
* < / p >
* 之 后 的 操 作 将 会 应 用 于 新 的 消 息 串 上
*
* @param text
* 一 个 新 的 操 作 串 .
* @return { @link FancyMessage }
* /
public FancyMessage then ( final TextualComponent text ) {
if ( ! latest ( ) . hasText ( ) ) {
throw new IllegalStateException ( "previous message part has no text" ) ;
}
messageParts . add ( new MessagePart ( text ) ) ;
dirty = true ;
return this ;
}
/ * *
* 序 列 化 当 前 对 象 , 使 用 { @link JsonWriter } 转 换 为 一 个 有 效 的 Json 串
* 当 前 Json 串 可 用 于 类 似 { @code / tellraw } 的 命 令 上
*
* @return 返 回 这 个 对 象 的 Json 序 列 化 字 符 串 .
* /
public String toJSONString ( ) {
if ( ! dirty & & jsonString ! = null ) {
return jsonString ;
}
final StringWriter string = new StringWriter ( ) ;
final JsonWriter json = GsonAgent . newJsonWriter ( string ) ;
try {
writeJson ( json ) ;
json . close ( ) ;
} catch ( final IOException e ) {
throw new RuntimeException ( "invalid message" ) ;
}
jsonString = string . toString ( ) ;
dirty = false ;
return jsonString ;
}
/ * *
* 将 此 消 息 转 换 为 具 有 有 限 格 式 的 人 可 读 字 符 串 。
* 此 方 法 用 于 发 送 此 消 息 给 没 有 JSON 格 式 支 持 客 户 端 。
* < p >
* 序 列 化 每 个 消 息 部 分 ( 每 个 部 分 都 需 要 分 别 序 列 化 ) :
* < ol >
* < li > 消 息 串 的 颜 色 . < / li >
* < li > 消 息 串 的 样 式 . < / li >
* < li > 消 息 串 的 文 本 . < / li >
* < / ol >
* 这 个 方 法 会 丢 失 点 击 操 作 和 悬 浮 操 作 所 以 仅 用 于 最 后 的 手 段
* < / p >
* < p >
* 颜 色 和 格 式 可 以 从 返 回 的 字 符 串 中 删 除 通 过 { @link ChatColor # stripColor ( String ) } .
* < / p >
*
* @return 发 送 给 老 版 本 客 户 端 以 及 控 制 台 。
* /
public String toOldMessageFormat ( ) {
final StringBuilder result = new StringBuilder ( ) ;
for ( final MessagePart part : this ) {
result . append ( part . color = = null ? "" : part . color ) ;
for ( final ChatColor formatSpecifier : part . styles ) {
result . append ( formatSpecifier ) ;
}
result . append ( part . text ) ;
}
return result . toString ( ) ;
}
/ * *
* 设 置 当 前 的 操 作 串 当 鼠 标 悬 浮 时 将 会 显 示 文 本
* < / p >
* 当 前 方 法 将 不 会 继 承 本 类 的 颜 色 样 式 等 参 数 .
* < / p >
*
* @param lines
* 当 鼠 标 悬 浮 时 他 将 会 显 示 支 持 换 行 符
* @return { @link FancyMessage }
* /
public FancyMessage tooltip ( final Iterable < String > lines ) {
tooltip ( ArrayWrapper . toArray ( lines , String . class ) ) ;
return this ;
}
/ * *
* 设 置 当 前 的 操 作 串 当 鼠 标 悬 浮 时 将 会 显 示 文 本
* < / p >
* 当 前 方 法 将 不 会 继 承 本 类 的 颜 色 样 式 等 参 数 .
* < / p >
*
* @param lines
* 当 鼠 标 悬 浮 时 他 将 会 显 示 支 持 换 行 符
* @return { @link FancyMessage }
* /
public FancyMessage tooltip ( final String text ) {
onHover ( "show_text" , new JsonString ( text ) ) ;
return this ;
}
/ * *
* 设 置 当 前 的 操 作 串 当 鼠 标 悬 浮 时 将 会 显 示 文 本
* < / p >
* 当 前 方 法 将 不 会 继 承 本 类 的 颜 色 样 式 等 参 数 .
* < / p >
*
* @param lines
* 当 鼠 标 悬 浮 时 他 将 会 显 示 支 持 换 行 符
* @return { @link FancyMessage }
* /
public FancyMessage tooltip ( final String . . . lines ) {
final StringBuilder builder = new StringBuilder ( ) ;
for ( int i = 0 ; i < lines . length ; i + + ) {
builder . append ( lines [ i ] ) ;
if ( i ! = lines . length - 1 ) {
builder . append ( '\n' ) ;
}
}
tooltip ( builder . toString ( ) ) ;
return this ;
}
/ * *
* If the text is a translatable key , and it has replaceable values , this
* function can be used to set the replacements that will be used in the
* message .
*
* @param replacements
* The replacements , in order , that will be used in the
* language - specific message .
* @return { @link FancyMessage }
* /
public FancyMessage translationReplacements ( final FancyMessage . . . replacements ) {
for ( final FancyMessage str : replacements ) {
latest ( ) . translationReplacements . add ( str ) ;
}
dirty = true ;
return this ;
}
/ * *
* If the text is a translatable key , and it has replaceable values , this
* function can be used to set the replacements that will be used in the
* message .
*
* @param replacements
* The replacements , in order , that will be used in the
* language - specific message .
* @return { @link FancyMessage }
* /
public FancyMessage translationReplacements ( final Iterable < FancyMessage > replacements ) {
return translationReplacements ( ArrayWrapper . toArray ( replacements , FancyMessage . class ) ) ;
}
/ * *
* If the text is a translatable key , and it has replaceable values , this
* function can be used to set the replacements that will be used in the
* message .
*
* @param replacements
* The replacements , in order , that will be used in the
* language - specific message .
* @return { @link FancyMessage }
* /
public FancyMessage translationReplacements ( final String . . . replacements ) {
for ( final String str : replacements ) {
latest ( ) . translationReplacements . add ( new JsonString ( str ) ) ;
}
dirty = true ;
return this ;
}
@Override
public void writeJson ( final JsonWriter writer ) throws IOException {
if ( messageParts . size ( ) = = 1 ) {
latest ( ) . writeJson ( writer ) ;
} else {
writer . beginObject ( ) . name ( "text" ) . value ( "" ) . name ( "extra" ) . beginArray ( ) ;
for ( final MessagePart part : this ) {
part . writeJson ( writer ) ;
}
writer . endArray ( ) . endObject ( ) ;
}
}
/ * *
* 获 得 最 后 一 个 操 作 串
*
* @return 最 后 一 个 操 作 的 消 息 串
* /
private MessagePart latest ( ) {
return messageParts . get ( messageParts . size ( ) - 1 ) ;
}
/ * *
* 添 加 点 击 操 作
*
* @param name
* 点 击 名 称
* @param data
* 点 击 操 作
* /
private void onClick ( final String name , final String data ) {
final MessagePart latest = latest ( ) ;
latest . clickActionName = name ;
latest . clickActionData = data ;
dirty = true ;
}
/ * *
* 添 加 显 示 操 作
*
* @param name
* 悬 浮 显 示
* @param data
* 显 示 内 容
* /
private void onHover ( final String name , final JsonRepresentedObject data ) {
final MessagePart latest = latest ( ) ;
latest . hoverActionName = name ;
latest . hoverActionData = data ;
dirty = true ;
}
}