Merge pull request #748 from thinkofdeath/master

Chat Component API
This commit is contained in:
thinkofdeath 2013-12-14 02:31:42 -08:00
commit 0dcba749dc
25 changed files with 3251 additions and 220 deletions

View File

@ -1,5 +1,7 @@
package net.md_5.bungee.api; package net.md_5.bungee.api;
import net.md_5.bungee.api.chat.BaseComponent;
import java.util.Collection; import java.util.Collection;
public interface CommandSender public interface CommandSender
@ -17,6 +19,7 @@ public interface CommandSender
* *
* @param message the message to send * @param message the message to send
*/ */
@Deprecated
public void sendMessage(String message); public void sendMessage(String message);
/** /**
@ -25,8 +28,23 @@ public interface CommandSender
* *
* @param messages the messages to send * @param messages the messages to send
*/ */
@Deprecated
public void sendMessages(String... messages); public void sendMessages(String... messages);
/**
* Send a message to this sender.
*
* @param message the message to send
*/
public void sendMessage(BaseComponent... message);
/**
* Send a message to this sender.
*
* @param message the message to send
*/
public void sendMessage(BaseComponent message);
/** /**
* Get all groups this user is part of. This returns an unmodifiable * Get all groups this user is part of. This returns an unmodifiable
* collection. * collection.

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.api; package net.md_5.bungee.api;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.api.plugin.PluginManager;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.io.File; import java.io.File;
@ -231,8 +232,23 @@ public abstract class ProxyServer
* *
* @param message the message to broadcast * @param message the message to broadcast
*/ */
@Deprecated
public abstract void broadcast(String message); public abstract void broadcast(String message);
/**
* Send the specified message to the console and all connected players.
*
* @param message the message to broadcast
*/
public abstract void broadcast(BaseComponent... message);
/**
* Send the specified message to the console and all connected players.
*
* @param message the message to broadcast
*/
public abstract void broadcast(BaseComponent message);
/** /**
* Gets a new instance of this proxies custom tab list. * Gets a new instance of this proxies custom tab list.
* *

View File

@ -0,0 +1,392 @@
package net.md_5.bungee.api.chat;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.md_5.bungee.api.ChatColor;
import java.util.ArrayList;
import java.util.List;
@Setter
@NoArgsConstructor
public abstract class BaseComponent
{
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
BaseComponent parent;
/**
* The color of this component and any child
* components (unless overridden)
*/
@Getter(AccessLevel.NONE)
private ChatColor color;
/**
* Whether this component and any child
* components (unless overridden) is bold
*/
@Getter(AccessLevel.NONE)
private Boolean bold;
/**
* Whether this component and any child
* components (unless overridden) is italic
*/
@Getter(AccessLevel.NONE)
private Boolean italic;
/**
* Whether this component and any child
* components (unless overridden) is underlined
*/
@Getter(AccessLevel.NONE)
private Boolean underlined;
/**
* Whether this component and any child
* components (unless overridden) is strikethrough
*/
@Getter(AccessLevel.NONE)
private Boolean strikethrough;
/**
* Whether this component and any child
* components (unless overridden) is obfuscated
*/
@Getter(AccessLevel.NONE)
private Boolean obfuscated;
/**
* Appended components that inherit this component's
* formatting and events
*/
@Getter
private List<BaseComponent> extra;
/**
* The action to preform when this component (and
* child components) are clicked
*/
@Getter
private ClickEvent clickEvent;
/**
* The action to preform when this component (and
* child components) are hovered over
*/
@Getter
private HoverEvent hoverEvent;
protected BaseComponent(BaseComponent old)
{
setColor( old.getColorRaw() );
setBold( old.isBoldRaw() );
setItalic( old.isItalicRaw() );
setUnderlined( old.isUnderlined() );
setStrikethrough( old.isStrikethroughRaw() );
setObfuscated( old.isObfuscatedRaw() );
setClickEvent( old.getClickEvent() );
setHoverEvent( old.getHoverEvent() );
}
/**
* Converts the components to a string that uses the
* old formatting codes ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
* @param components the components to convert
* @return the string in the old format
*/
public static String toLegacyText(BaseComponent... components)
{
StringBuilder builder = new StringBuilder();
for ( BaseComponent msg : components )
{
builder.append( msg.toLegacyText() );
}
return builder.toString();
}
/**
* Converts the components into a string without
* any formatting
* @param components the components to convert
* @return the string as plain text
*/
public static String toPlainText(BaseComponent... components)
{
StringBuilder builder = new StringBuilder();
for ( BaseComponent msg : components )
{
builder.append( msg.toPlainText() );
}
return builder.toString();
}
/**
* Returns the color of this component. This uses the parent's color
* if this component doesn't have one. {@link net.md_5.bungee.api.ChatColor#WHITE}
* is returned if no color is found.
*
* @return the color of this component
*/
public ChatColor getColor()
{
if ( color == null )
{
if ( parent == null )
{
return ChatColor.WHITE;
}
return parent.getColor();
}
return color;
}
/**
* Returns the color of this component without checking the parents
* color. May return null
*
* @return the color of this component
*/
public ChatColor getColorRaw()
{
return color;
}
/**
* Returns whether this component is bold. This uses the parent's
* setting if this component hasn't been set. false is returned
* if none of the parent chain has been set.
*
* @return whether the component is bold
*/
public boolean isBold()
{
if ( bold == null )
{
return parent != null && parent.isBold();
}
return bold;
}
/**
* Returns whether this component is bold without checking
* the parents setting. May return null
*
* @return whether the component is bold
*/
public Boolean isBoldRaw()
{
return bold;
}
/**
* Returns whether this component is italic. This uses the parent's
* setting if this component hasn't been set. false is returned
* if none of the parent chain has been set.
*
* @return whether the component is italic
*/
public boolean isItalic()
{
if ( italic == null )
{
return parent != null && parent.isItalic();
}
return italic;
}
/**
* Returns whether this component is italic without checking
* the parents setting. May return null
*
* @return whether the component is italic
*/
public Boolean isItalicRaw()
{
return italic;
}
/**
* Returns whether this component is underlined. This uses the parent's
* setting if this component hasn't been set. false is returned
* if none of the parent chain has been set.
*
* @return whether the component is underlined
*/
public boolean isUnderlined()
{
if ( underlined == null )
{
return parent != null && parent.isUnderlined();
}
return underlined;
}
/**
* Returns whether this component is underlined without checking
* the parents setting. May return null
*
* @return whether the component is underlined
*/
public Boolean isUnderlinedRaw()
{
return underlined;
}
/**
* Returns whether this component is strikethrough. This uses the parent's
* setting if this component hasn't been set. false is returned
* if none of the parent chain has been set.
*
* @return whether the component is strikethrough
*/
public boolean isStrikethrough()
{
if ( strikethrough == null )
{
return parent != null && parent.isStrikethrough();
}
return strikethrough;
}
/**
* Returns whether this component is strikethrough without checking
* the parents setting. May return null
*
* @return whether the component is strikethrough
*/
public Boolean isStrikethroughRaw()
{
return strikethrough;
}
/**
* Returns whether this component is obfuscated. This uses the parent's
* setting if this component hasn't been set. false is returned
* if none of the parent chain has been set.
*
* @return whether the component is obfuscated
*/
public boolean isObfuscated()
{
if ( obfuscated == null )
{
return parent != null && parent.isObfuscated();
}
return obfuscated;
}
/**
* Returns whether this component is obfuscated without checking
* the parents setting. May return null
*
* @return whether the component is obfuscated
*/
public Boolean isObfuscatedRaw()
{
return obfuscated;
}
public void setExtra(List<BaseComponent> components)
{
for ( BaseComponent component : components )
{
component.parent = this;
}
extra = components;
}
/**
* Appends a text element to the component. The text will
* inherit this component's formatting
*
* @param text the text to append
*/
public void addExtra(String text)
{
addExtra( new TextComponent( text ) );
}
/**
* Appends a component to the component. The text will
* inherit this component's formatting
*
* @param component the component to append
*/
public void addExtra(BaseComponent component)
{
if ( extra == null )
{
extra = new ArrayList<>();
}
component.parent = this;
extra.add( component );
}
/**
* Returns whether the component has any formatting
* or events applied to it
* @return
*/
public boolean hasFormatting()
{
return color != null || bold != null ||
italic != null || underlined != null ||
strikethrough != null || obfuscated != null ||
hoverEvent != null || clickEvent != null;
}
/**
* Converts the component into a string without
* any formatting
* @return the string as plain text
*/
public String toPlainText()
{
StringBuilder builder = new StringBuilder();
toPlainText( builder );
return builder.toString();
}
protected void toPlainText(StringBuilder builder)
{
if ( extra != null )
{
for ( BaseComponent e : extra )
{
e.toPlainText( builder );
}
}
}
/**
* Converts the component to a string that uses the
* old formatting codes ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
* @return the string in the old format
*/
public String toLegacyText()
{
StringBuilder builder = new StringBuilder();
toLegacyText( builder );
return builder.toString();
}
protected void toLegacyText(StringBuilder builder)
{
if ( extra != null )
{
for ( BaseComponent e : extra )
{
e.toLegacyText( builder );
}
}
}
@Override
public String toString()
{
return String.format( "BaseComponent{color=%s, bold=%b, italic=%b, underlined=%b, strikethrough=%b, obfuscated=%b, clickEvent=%s, hoverEvent=%s}", getColor().getName(), isBold(), isItalic(), isUnderlined(), isStrikethrough(), isObfuscated(), getClickEvent(), getHoverEvent() );
}
}

View File

@ -0,0 +1,53 @@
package net.md_5.bungee.api.chat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@AllArgsConstructor
final public class ClickEvent
{
/**
* The type of action to preform on click
*/
private final Action action;
/**
* Depends on action
* @see net.md_5.bungee.api.chat.ClickEvent.Action
*/
private final String value;
public enum Action
{
/**
* Open a url at the path given by
* {@link net.md_5.bungee.api.chat.ClickEvent#getValue()}
*/
OPEN_URL,
/**
* Open a file at the path given by
* {@link net.md_5.bungee.api.chat.ClickEvent#getValue()}
*/
OPEN_FILE,
/**
* Run the command given by
* {@link net.md_5.bungee.api.chat.ClickEvent#getValue()}
*/
RUN_COMMAND,
/**
* Inserts the string given by
* {@link net.md_5.bungee.api.chat.ClickEvent#getValue()}
* into the players text box
*/
SUGGEST_COMMAND
}
@Override
public String toString()
{
return String.format( "ClickEvent{action=%s, value=%s}", action, value );
}
}

View File

@ -0,0 +1,157 @@
package net.md_5.bungee.api.chat;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.ChatColor;
import java.util.ArrayList;
import java.util.List;
/**
* ComponentBuilder simplifies creating basic messages by allowing
* the use of a chainable builder.
* <p/>
* <pre>
* new ComponentBuilder("Hello ").color(ChatColor.RED).
* append("World").color(ChatColor.BLUE).
* append("!").bold(true).create();
* </pre>
* <p/>
* All methods (excluding {@link #append(String)} and {@link #create()}
* work on the last part appended to the builder, so in the example
* above "Hello " would be {@link net.md_5.bungee.api.ChatColor#RED}
* and "World" would be {@link net.md_5.bungee.api.ChatColor#BLUE} but
* "!" would be bold and {@link net.md_5.bungee.api.ChatColor#BLUE}
* because append copies the previous part's formatting
*/
public class ComponentBuilder
{
private TextComponent current;
private List<BaseComponent> parts = new ArrayList<>();
/**
* Creates a componentBuilder with the given text as the
* first part.
*
* @param text the first text element
*/
public ComponentBuilder(String text)
{
current = new TextComponent( text );
}
/**
* Appends the text to the builder and makes it the current
* target for formatting. The text will have all the
* formatting from the previous part.
*
* @param text the text to append
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder append(String text)
{
parts.add( current );
current = new TextComponent( current );
current.setText( text );
return this;
}
/**
* Sets the color of the current part.
*
* @param color the new color
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder color(ChatColor color)
{
current.setColor( color );
return this;
}
/**
* Sets whether the current part is bold.
*
* @param bold whether this part is bold
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder bold(boolean bold)
{
current.setBold( bold );
return this;
}
/**
* Sets whether the current part is italic
*
* @param italic whether this part is italic
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder italic(boolean italic)
{
current.setItalic( italic );
return this;
}
/**
* Sets whether the current part is underlined
*
* @param underlined whether this part is underlined
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder underlined(boolean underlined)
{
current.setUnderlined( underlined );
return this;
}
/**
* Sets whether the current part is strikethrough
*
* @param strikethrough whether this part is strikethrough
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder strikethrough(boolean strikethrough)
{
current.setStrikethrough( strikethrough );
return this;
}
/**
* Sets whether the current pat is obfuscated
*
* @param obfuscated whether this part is obfuscated
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder obfuscated(boolean obfuscated)
{
current.setObfuscated( obfuscated );
return this;
}
/**
* Sets the click event for the current part.
* @param clickEvent
* @return
*/
public ComponentBuilder event(ClickEvent clickEvent)
{
current.setClickEvent( clickEvent );
return this;
}
public ComponentBuilder event(HoverEvent hoverEvent)
{
current.setHoverEvent( hoverEvent );
return this;
}
/**
* Returns the components needed to display the message
* created by this builder
* @return the created components
*/
public BaseComponent[] create()
{
parts.add( current );
return parts.toArray( new BaseComponent[parts.size()] );
}
}

View File

@ -0,0 +1,27 @@
package net.md_5.bungee.api.chat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@AllArgsConstructor
final public class HoverEvent
{
private final Action action;
private final BaseComponent[] value;
public enum Action
{
SHOW_TEXT,
SHOW_ACHIEVEMENT,
SHOW_ITEM
}
@Override
public String toString()
{
return String.format( "HoverEvent{action=%s, value=%s}", action, value );
}
}

View File

@ -0,0 +1,158 @@
package net.md_5.bungee.api.chat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.md_5.bungee.api.ChatColor;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class TextComponent extends BaseComponent
{
private static final Pattern url = Pattern.compile( "^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$" );
/**
* Converts the old formatting system that used {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
* into the new json based system.
* @param message the text to convert
* @return the components needed to print the message to the client
*/
public static BaseComponent[] fromLegacyText(String message)
{
ArrayList<BaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
TextComponent component = new TextComponent();
Matcher matcher = url.matcher( message );
for ( int i = 0; i < message.length(); i++ )
{
char c = message.charAt( i );
if ( c == ChatColor.COLOR_CHAR )
{
i++;
c = message.charAt( i );
if ( c >= 'A' && c <= 'Z' )
{
c += 32;
}
if ( builder.length() > 0 )
{
TextComponent old = component;
component = new TextComponent( old );
old.setText( builder.toString() );
builder = new StringBuilder();
components.add( old );
}
ChatColor format = ChatColor.getByChar( c );
switch ( format )
{
case BOLD:
component.setBold( true );
break;
case ITALIC:
component.setItalic( true );
break;
case UNDERLINE:
component.setUnderlined( true );
break;
case STRIKETHROUGH:
component.setStrikethrough( true );
break;
case MAGIC:
component.setObfuscated( true );
break;
case RESET:
format = ChatColor.WHITE;
default:
component = new TextComponent();
component.setColor( format );
break;
}
continue;
}
int pos = message.indexOf( ' ', i );
if ( pos == -1 ) pos = message.length();
if ( matcher.region( i, pos ).find() )
{ //Web link handling
if ( builder.length() > 0 )
{
TextComponent old = component;
component = new TextComponent( old );
old.setText( builder.toString() );
builder = new StringBuilder();
components.add( old );
}
TextComponent old = component;
component = new TextComponent( old );
String urlString = message.substring( i, pos );
component.setText( urlString );
component.setClickEvent( new ClickEvent( ClickEvent.Action.OPEN_URL,
urlString.startsWith( "http" ) ? urlString : "http://" + urlString ) );
components.add( component );
i += pos - i - 1;
component = old;
continue;
}
builder.append( c );
}
if ( builder.length() > 0 )
{
component.setText( builder.toString() );
components.add( component );
}
return components.toArray( new BaseComponent[components.size()] );
}
/**
* The text of the component that will be
* displayed to the client
*/
private String text;
/**
* Creates a TextComponent with formatting and text
* from the passed component
* @param textComponent the component to copy from
*/
public TextComponent(TextComponent textComponent)
{
super( textComponent );
setText( textComponent.getText() );
}
@Override
protected void toPlainText(StringBuilder builder)
{
builder.append( text );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
{
builder.append( getColor() );
if ( isBold() ) builder.append( ChatColor.BOLD );
if ( isItalic() ) builder.append( ChatColor.ITALIC );
if ( isUnderlined() ) builder.append( ChatColor.UNDERLINE );
if ( isStrikethrough() ) builder.append( ChatColor.STRIKETHROUGH );
if ( isObfuscated() ) builder.append( ChatColor.MAGIC );
builder.append( text );
super.toLegacyText( builder );
}
@Override
public String toString()
{
return String.format( "TextComponent{text=%s, %s}", text, super.toString() );
}
}

View File

@ -0,0 +1,204 @@
package net.md_5.bungee.api.chat;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.md_5.bungee.api.ChatColor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Getter
@Setter
@NoArgsConstructor
public class TranslatableComponent extends BaseComponent
{
private final ResourceBundle locales = ResourceBundle.getBundle( "mojang-translations/en_US" );
private final Pattern format = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" );
/**
* The key into the Minecraft locale files to use for the
* translation. The text depends on the client's locale setting.
* The console is always en_US
*/
private String translate;
/**
* The components to substitute into the translation
*/
private List<BaseComponent> with;
/**
* Creates a translatable component with the passed substitutions
* @see #setTranslate(String)
* @see #setWith(java.util.List)
* @param translate the translation key
* @param with the {@link java.lang.String}s and {@link net.md_5.bungee.api.chat.BaseComponent}s
* to use into the translation
*/
public TranslatableComponent(String translate, Object... with)
{
setTranslate( translate );
List<BaseComponent> temp = new ArrayList<>();
for ( Object w : with )
{
if ( w instanceof String )
{
temp.add( new TextComponent( (String) w ) );
} else
{
temp.add( (BaseComponent) w );
}
}
setWith( temp );
}
/**
* Sets the translation substitutions to be used in
* this component. Removes any previously set
* substitutions
* @param components the components to substitute
*/
public void setWith(List<BaseComponent> components)
{
for ( BaseComponent component : components )
{
component.parent = this;
}
with = components;
}
/**
* Adds a text substitution to the component. The text will
* inherit this component's formatting
*
* @param text the text to substitute
*/
public void addWith(String text)
{
addWith( new TextComponent( text ) );
}
/**
* Adds a component substitution to the component. The text will
* inherit this component's formatting
*
* @param component the component to substitute
*/
public void addWith(BaseComponent component)
{
if ( with == null )
{
with = new ArrayList<>();
}
component.parent = this;
with.add( component );
}
@Override
protected void toPlainText(StringBuilder builder)
{
String trans = locales.getString( translate );
if ( trans == null )
{
builder.append( translate );
} else
{
Matcher matcher = format.matcher( trans );
int position = 0;
int i = 0;
while ( matcher.find( position ) )
{
int pos = matcher.start();
if ( pos != position )
{
builder.append( trans.substring( position, pos ) );
}
position = matcher.end();
String formatCode = matcher.group( 2 );
switch ( formatCode.charAt( 0 ) )
{
case 's':
case 'd':
String withIndex = matcher.group( 1 );
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toPlainText( builder );
break;
case '%':
builder.append( '%' );
break;
}
}
if ( trans.length() != position )
{
builder.append( trans.substring( position, trans.length() ) );
}
}
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
{
String trans = locales.getString( translate );
if ( trans == null )
{
addFormat( builder );
builder.append( translate );
} else
{
Matcher matcher = format.matcher( trans );
int position = 0;
int i = 0;
while ( matcher.find( position ) )
{
int pos = matcher.start();
if ( pos != position )
{
addFormat( builder );
builder.append( trans.substring( position, pos ) );
}
position = matcher.end();
String formatCode = matcher.group( 2 );
switch ( formatCode.charAt( 0 ) )
{
case 's':
case 'd':
String withIndex = matcher.group( 1 );
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder );
break;
case '%':
addFormat( builder );
builder.append( '%' );
break;
}
}
if ( trans.length() != position )
{
addFormat( builder );
builder.append( trans.substring( position, trans.length() ) );
}
}
super.toLegacyText( builder );
}
private void addFormat(StringBuilder builder)
{
builder.append( getColor() );
if ( isBold() ) builder.append( ChatColor.BOLD );
if ( isItalic() ) builder.append( ChatColor.ITALIC );
if ( isUnderlined() ) builder.append( ChatColor.UNDERLINE );
if ( isStrikethrough() ) builder.append( ChatColor.STRIKETHROUGH );
if ( isObfuscated() ) builder.append( ChatColor.MAGIC );
}
@Override
public String toString()
{
return String.format( "TranslatableComponent{translate=%s, with=%s, %s}", translate, with, super.toString() );
}
}

View File

@ -1,6 +1,8 @@
package net.md_5.bungee.api.connection; package net.md_5.bungee.api.connection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
/** /**
@ -26,8 +28,29 @@ public interface Connection
* @param reason the reason shown to the player / sent to the server on * @param reason the reason shown to the player / sent to the server on
* disconnect * disconnect
*/ */
@Deprecated
void disconnect(String reason); void disconnect(String reason);
/**
* Disconnects this end of the connection for the specified reason. If this
* is an {@link ProxiedPlayer} the respective server connection will be
* closed too.
*
* @param reason the reason shown to the player / sent to the server on
* disconnect
*/
void disconnect(BaseComponent... reason);
/**
* Disconnects this end of the connection for the specified reason. If this
* is an {@link ProxiedPlayer} the respective server connection will be
* closed too.
*
* @param reason the reason shown to the player / sent to the server on
* disconnect
*/
void disconnect(BaseComponent reason);
/** /**
* Get the unsafe methods of this class. * Get the unsafe methods of this class.
* *

View File

@ -3,6 +3,8 @@ package net.md_5.bungee.api.event;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Cancellable; import net.md_5.bungee.api.plugin.Cancellable;
@ -28,7 +30,7 @@ public class ServerKickEvent extends Event implements Cancellable
/** /**
* Kick reason. * Kick reason.
*/ */
private String kickReason; private BaseComponent[] kickReasonComponent;
/** /**
* Server to send player to if this event is cancelled. * Server to send player to if this event is cancelled.
*/ */
@ -44,16 +46,26 @@ public class ServerKickEvent extends Event implements Cancellable
CONNECTING, CONNECTED, UNKNOWN; CONNECTING, CONNECTED, UNKNOWN;
} }
public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer) public ServerKickEvent(ProxiedPlayer player, BaseComponent[] kickReasonComponent, ServerInfo cancelServer)
{ {
this( player, kickReason, cancelServer, State.UNKNOWN ); this( player, kickReasonComponent, cancelServer, State.UNKNOWN );
} }
public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer, State state) public ServerKickEvent(ProxiedPlayer player, BaseComponent[] kickReasonComponent, ServerInfo cancelServer, State state)
{ {
this.player = player; this.player = player;
this.kickReason = kickReason; this.kickReasonComponent = kickReasonComponent;
this.cancelServer = cancelServer; this.cancelServer = cancelServer;
this.state = state; this.state = state;
} }
@Deprecated
public String getKickReason() {
return BaseComponent.toLegacyText( kickReasonComponent );
}
@Deprecated
public void setKickReason(String reason) {
kickReasonComponent = TextComponent.fromLegacyText( reason );
}
} }

View File

@ -1,6 +1,9 @@
package net.md_5.bungee; package net.md_5.bungee;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.log.BungeeLogger; import net.md_5.bungee.log.BungeeLogger;
import net.md_5.bungee.reconnect.YamlReconnectHandler; import net.md_5.bungee.reconnect.YamlReconnectHandler;
import net.md_5.bungee.scheduler.BungeeScheduler; import net.md_5.bungee.scheduler.BungeeScheduler;
@ -134,6 +137,7 @@ public class BungeeCord extends ProxyServer
getPluginManager().registerCommand( null, new CommandPerms() ); getPluginManager().registerCommand( null, new CommandPerms() );
getPluginManager().registerCommand( null, new CommandSend() ); getPluginManager().registerCommand( null, new CommandSend() );
getPluginManager().registerCommand( null, new CommandFind() ); getPluginManager().registerCommand( null, new CommandFind() );
getPluginManager().registerCommand( null, new CommandAlertRaw() );
registerChannel( "BungeeCord" ); registerChannel( "BungeeCord" );
} }
@ -486,11 +490,21 @@ public class BungeeCord extends ProxyServer
@Override @Override
public void broadcast(String message) public void broadcast(String message)
{ {
getConsole().sendMessage( message ); broadcast( TextComponent.fromLegacyText( message ) );
// TODO: Here too
for (String msg : ChatConverter.toJSONChat( message )) {
broadcast( new Chat( msg ) );
} }
@Override
public void broadcast(BaseComponent... message)
{
getConsole().sendMessage( BaseComponent.toLegacyText( message ) );
broadcast( new Chat( ComponentSerializer.toString( message ) ) );
}
@Override
public void broadcast(BaseComponent message)
{
getConsole().sendMessage( message.toLegacyText() );
broadcast( new Chat( ComponentSerializer.toString( message ) ) );
} }
public void addConnection(UserConnection con) public void addConnection(UserConnection con)

View File

@ -1,197 +0,0 @@
package net.md_5.bungee;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ChatConverter {
private static final Gson gson = new Gson();
private static final char COLOR_CHAR = '\u00A7';
private static final Pattern url = Pattern.compile("^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$");
public static String[] toJSONChat(String txt) {
Message msg = new Message();
ArrayList<String> parts = new ArrayList<String>();
StringBuilder outBuffer = new StringBuilder("[");
StringBuilder buf = new StringBuilder();
Matcher matcher = url.matcher(txt);
for (int i = 0; i < txt.length(); i++) {
char c = txt.charAt(i);
if (c != COLOR_CHAR) {
int pos = txt.indexOf(' ', i);
if (pos == -1) pos = txt.length();
if (matcher.region(i, pos).find()) { //Web link handling
msg.text = buf.toString();
buf = new StringBuilder();
outBuffer = append(parts, outBuffer, msg);
Message old = msg;
msg = new Message(old);
msg.clickEvent = new ClickEvent();
msg.clickEvent.action = "open_url";
String urlString = txt.substring(i, pos);
if (urlString.startsWith("http")) {
msg.text = msg.clickEvent.value = urlString;
} else {
msg.text = urlString;
msg.clickEvent.value = "http://" + urlString;
}
outBuffer = append(parts, outBuffer, msg);
i += pos - i - 1;
msg = new Message(old);
continue;
}
buf.append(c);
continue;
}
i++;
c = txt.charAt(i);
if (c >= 'A' && c <= 'Z') {
c += 32;
}
msg.text = buf.toString();
buf = new StringBuilder();
outBuffer = append(parts, outBuffer, msg);
msg = new Message(msg);
switch(c) {
case 'k':
msg.obfuscated = Boolean.TRUE;
break;
case 'l':
msg.bold = Boolean.TRUE;
break;
case 'm':
msg.strikethrough = Boolean.TRUE;
break;
case 'n':
msg.underlined = Boolean.TRUE;
break;
case 'o':
msg.italic = Boolean.TRUE;
break;
default:
msg.obfuscated = null;
msg.bold = null;
msg.strikethrough = null;
msg.underlined = null;
msg.italic = null;
if (c != 'r') {
msg.color = Color.fromCode(Character.toString(c));
} else {
msg.color = Color.WHITE;
}
break;
}
}
msg.text = buf.toString();
append(parts, outBuffer, msg);
parts.add(outBuffer.append("]").toString());
String[] pArray = new String[parts.size()];
parts.toArray(pArray);
return pArray;
}
private static StringBuilder append(ArrayList<String> parts, StringBuilder outBuffer, Message part) {
String p = gson.toJson(part);
if (p.length() + outBuffer.length() + 1 >= Short.MAX_VALUE - 20) {
outBuffer.append("]");
parts.add(outBuffer.toString());
outBuffer = new StringBuilder("[");
}
if (outBuffer.length() != 1) {
outBuffer.append(",");
}
outBuffer.append(p);
return outBuffer;
}
}
class Message {
public String text;
public Boolean bold;
public Boolean italic;
public Boolean underlined;
public Boolean strikethrough;
public Boolean obfuscated;
public Color color;
public ClickEvent clickEvent;
public Message() {
}
public Message(Message old) {
this.bold = old.bold;
this.italic = old.italic;
this.underlined = old.underlined;
this.strikethrough = old.strikethrough;
this.color = old.color;
}
}
class ClickEvent {
public String action;
public String value;
}
enum Color {
@SerializedName("black")
BLACK("0"),
@SerializedName("dark_blue")
DARK_BLUE("1"),
@SerializedName("dark_green")
DARK_GREEN("2"),
@SerializedName("dark_aqua")
DARK_AQUA("3"),
@SerializedName("dark_red")
DARK_RED("4"),
@SerializedName("dark_purple")
DARK_PURPLE("5"),
@SerializedName("gold")
GOLD("6"),
@SerializedName("gray")
GRAY("7"),
@SerializedName("dark_gray")
DARK_GRAY("8"),
@SerializedName("blue")
BLUE("9"),
@SerializedName("green")
GREEN("a"),
@SerializedName("aqua")
AQUA("b"),
@SerializedName("red")
RED("c"),
@SerializedName("light_purple")
LIGHT_PURPLE("d"),
@SerializedName("yellow")
YELLOW("e"),
@SerializedName("white")
WHITE("f");
public String code;
Color(String code) {
this.code = code;
}
private static HashMap<String, Color> codeMap = new HashMap<String, Color>();
public static Color fromCode(String code) {
return codeMap.get(code);
}
static {
for (Color color : values()) {
codeMap.put(color.code, color);
}
}
}

View File

@ -5,7 +5,10 @@ import java.util.concurrent.TimeUnit;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.PluginMessage;
@ -39,11 +42,17 @@ public class ServerConnection implements Server
@Override @Override
public synchronized void disconnect(String reason) public synchronized void disconnect(String reason)
{
disconnect( TextComponent.fromLegacyText( reason ) );
}
@Override
public void disconnect(BaseComponent... reason)
{ {
if ( !ch.isClosed() ) if ( !ch.isClosed() )
{ {
// TODO: Can we just use a future here? // TODO: Can we just use a future here?
unsafe().sendPacket( new Kick( reason ) ); unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) );
ch.getHandle().eventLoop().schedule( new Runnable() ch.getHandle().eventLoop().schedule( new Runnable()
{ {
@Override @Override
@ -53,6 +62,13 @@ public class ServerConnection implements Server
} }
}, 100, TimeUnit.MILLISECONDS ); }, 100, TimeUnit.MILLISECONDS );
} }
}
@Override
public void disconnect(BaseComponent reason)
{
disconnect( new BaseComponent[]{reason} );
} }
@Override @Override

View File

@ -15,6 +15,7 @@ import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.score.Objective; import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.api.score.Scoreboard; import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team; import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.CancelSendSignal; import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.DownstreamBridge; import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.HandlerBoss;
@ -211,7 +212,7 @@ public class ServerConnector extends PacketHandler
{ {
def = null; def = null;
} }
ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( user, kick.getMessage(), def, ServerKickEvent.State.CONNECTING ) ); ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( user, ComponentSerializer.parse(kick.getMessage()), def, ServerKickEvent.State.CONNECTING ) );
if ( event.isCancelled() && event.getCancelServer() != null ) if ( event.isCancelled() && event.getCancelServer() != null )
{ {
user.connect( event.getCancelServer() ); user.connect( event.getCancelServer() );

View File

@ -20,12 +20,15 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PermissionCheckEvent; import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard; import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.tab.TabListHandler; import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.HandlerBoss;
@ -256,15 +259,27 @@ public final class UserConnection implements ProxiedPlayer
@Override @Override
public synchronized void disconnect(String reason) public synchronized void disconnect(String reason)
{ {
disconnect0( ChatConverter.toJSONChat( reason )[0] ); disconnect0( TextComponent.fromLegacyText( reason ) );
} }
public synchronized void disconnect0(String reason) @Override
public void disconnect(BaseComponent... reason)
{
disconnect0( reason );
}
@Override
public void disconnect(BaseComponent reason)
{
disconnect0( reason );
}
public synchronized void disconnect0(BaseComponent ...reason)
{ {
if ( ch.getHandle().isActive() ) if ( ch.getHandle().isActive() )
{ {
bungee.getLogger().log( Level.INFO, "[" + getName() + "] disconnected with: " + reason ); bungee.getLogger().log( Level.INFO, "[" + getName() + "] disconnected with: " + BaseComponent.toLegacyText( reason ) );
unsafe().sendPacket( new Kick( reason ) ); unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) );
ch.close(); ch.close();
if ( server != null ) if ( server != null )
{ {
@ -283,9 +298,7 @@ public final class UserConnection implements ProxiedPlayer
@Override @Override
public void sendMessage(String message) public void sendMessage(String message)
{ {
for(String msg : ChatConverter.toJSONChat( message )) { sendMessage( TextComponent.fromLegacyText( message ) );
unsafe().sendPacket( new Chat( msg ) );
}
} }
@Override @Override
@ -297,6 +310,18 @@ public final class UserConnection implements ProxiedPlayer
} }
} }
@Override
public void sendMessage(BaseComponent... message)
{
unsafe().sendPacket( new Chat( ComponentSerializer.toString( message ) ) );
}
@Override
public void sendMessage(BaseComponent message)
{
unsafe().sendPacket( new Chat( ComponentSerializer.toString( message ) ) );
}
@Override @Override
public void sendData(String channel, byte[] data) public void sendData(String channel, byte[] data)
{ {

View File

@ -0,0 +1,116 @@
package net.md_5.bungee.chat;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import java.util.Arrays;
public class BaseComponentSerializer
{
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
{
if ( object.has( "color" ) )
{
component.setColor( ChatColor.valueOf( object.get( "color" ).getAsString().toUpperCase() ) );
}
if ( object.has( "bold" ) )
{
component.setBold( object.get( "bold" ).getAsBoolean() );
}
if ( object.has( "italic" ) )
{
component.setItalic( object.get( "italic" ).getAsBoolean() );
}
if ( object.has( "underlined" ) )
{
component.setUnderlined( object.get( "underlined" ).getAsBoolean() );
}
if ( object.has( "strikethrough" ) )
{
component.setUnderlined( object.get( "strikethrough" ).getAsBoolean() );
}
if ( object.has( "obfuscated" ) )
{
component.setUnderlined( object.get( "obfuscated" ).getAsBoolean() );
}
if ( object.has( "extra" ) )
{
component.setExtra( Arrays.<BaseComponent>asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
}
//Events
if ( object.has( "clickEvent" ) )
{
JsonObject event = object.getAsJsonObject( "clickEvent" );
component.setClickEvent( new ClickEvent(
ClickEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase() ),
event.get( "value" ).getAsString() ) );
}
if ( object.has( "hoverEvent" ) )
{
JsonObject event = object.getAsJsonObject( "hoverEvent" );
BaseComponent[] res;
if (event.get("value").isJsonArray()) {
res = context.deserialize( event.get( "value" ), BaseComponent[].class );
} else {
res = new BaseComponent[]{context.<BaseComponent>deserialize( event.get( "value" ), BaseComponent.class )};
}
component.setHoverEvent( new HoverEvent( HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase() ), res ) );
}
}
protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context)
{
if ( component.getColorRaw() != null )
{
object.addProperty( "color", component.getColorRaw().getName() );
}
if ( component.isBoldRaw() != null )
{
object.addProperty( "bold", component.isBoldRaw() );
}
if ( component.isItalicRaw() != null )
{
object.addProperty( "italic", component.isItalicRaw() );
}
if ( component.isUnderlinedRaw() != null )
{
object.addProperty( "underlined", component.isUnderlinedRaw() );
}
if ( component.isStrikethroughRaw() != null )
{
object.addProperty( "strikethrough", component.isStrikethroughRaw() );
}
if ( component.isObfuscatedRaw() != null )
{
object.addProperty( "obfuscated", component.isObfuscatedRaw() );
}
if ( component.getExtra() != null )
{
object.add( "extra", context.serialize( component.getExtra() ) );
}
//Events
if ( component.getClickEvent() != null )
{
JsonObject clickEvent = new JsonObject();
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase() );
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
}
if ( component.getHoverEvent() != null )
{
JsonObject hoverEvent = new JsonObject();
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase() );
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getValue() ) );
object.add( "hoverEvent", hoverEvent );
}
}
}

View File

@ -0,0 +1,66 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import java.lang.reflect.Type;
public class ComponentSerializer implements JsonSerializer<BaseComponent>, JsonDeserializer<BaseComponent>
{
private final static Gson gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ).
create();
public static BaseComponent[] parse(String json)
{
if ( json.startsWith( "[" ) )
{ //Array
return gson.fromJson( json, BaseComponent[].class );
}
return new BaseComponent[]{gson.fromJson( json, BaseComponent.class )};
}
public static String toString(BaseComponent component)
{
return gson.toJson( component );
}
public static String toString(BaseComponent... components)
{
return gson.toJson( components );
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
return context.deserialize( json, TextComponent.class );
}
@Override
public JsonElement serialize(BaseComponent src, Type typeOfSrc, JsonSerializationContext context)
{
return context.serialize( src, src.getClass() );
}
}

View File

@ -0,0 +1,39 @@
package net.md_5.bungee.chat;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.md_5.bungee.api.chat.TextComponent;
import java.lang.reflect.Type;
public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent>
{
@Override
public TextComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
TextComponent component = new TextComponent();
JsonObject object = json.getAsJsonObject();
deserialize( object, component, context );
component.setText( object.get( "text" ).getAsString() );
return component;
}
@Override
public JsonElement serialize(TextComponent src, Type typeOfSrc, JsonSerializationContext context)
{
if ( !src.hasFormatting() )
{
return new JsonPrimitive( src.getText() );
}
JsonObject object = new JsonObject();
serialize( object, src, context );
object.addProperty( "text", src.getText() );
return object;
}
}

View File

@ -0,0 +1,44 @@
package net.md_5.bungee.chat;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import java.lang.reflect.Type;
import java.util.Arrays;
public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent>
{
@Override
public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
TranslatableComponent component = new TranslatableComponent();
JsonObject object = json.getAsJsonObject();
deserialize( object, component, context );
component.setTranslate( object.get( "translate" ).getAsString() );
if ( object.has( "with" ) )
{
component.setWith( Arrays.asList( (BaseComponent[]) context.deserialize( object.get( "with" ), BaseComponent[].class ) ) );
}
return component;
}
@Override
public JsonElement serialize(TranslatableComponent src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject object = new JsonObject();
serialize( object, src, context );
object.addProperty( "translate", src.getTranslate() );
if ( src.getWith() != null )
{
object.add( "with", context.serialize( src.getWith() ) );
}
return object;
}
}

View File

@ -0,0 +1,47 @@
package net.md_5.bungee.command;
import com.google.common.base.Joiner;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.chat.ComponentSerializer;
import java.util.Arrays;
public class CommandAlertRaw extends Command
{
public CommandAlertRaw()
{
super( "alertraw", "bungeecord.command.alert" );
}
@Override
public void execute(CommandSender sender, String[] args)
{
if ( args.length == 0 )
{
sender.sendMessage( ChatColor.RED + "You must supply a message." );
} else
{
String message = Joiner.on(' ').join( args );
try
{
ProxyServer.getInstance().broadcast( ComponentSerializer.parse( message ) );
} catch ( Exception e )
{
sender.sendMessage(
new ComponentBuilder( "An error occured while parsing your message. (Hover for details)" ).
color( ChatColor.RED ).underlined( true ).
event( new HoverEvent( HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder( e.getMessage() ).color( ChatColor.RED ).create() ) ).
create() );
}
}
}
}

View File

@ -5,6 +5,7 @@ import java.util.Collections;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
/** /**
* Command sender representing the proxy console. * Command sender representing the proxy console.
@ -34,6 +35,18 @@ public class ConsoleCommandSender implements CommandSender
} }
} }
@Override
public void sendMessage(BaseComponent... message)
{
sendMessage( BaseComponent.toLegacyText( message ) );
}
@Override
public void sendMessage(BaseComponent message)
{
sendMessage( message.toLegacyText() );
}
@Override @Override
public String getName() public String getName()
{ {

View File

@ -20,6 +20,7 @@ import net.md_5.bungee.api.score.Position;
import net.md_5.bungee.api.score.Score; import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.api.score.Scoreboard; import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team; import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler; import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.PacketWrapper;
@ -355,13 +356,13 @@ public class DownstreamBridge extends PacketHandler
{ {
def = null; def = null;
} }
ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( con, kick.getMessage(), def, ServerKickEvent.State.CONNECTED ) ); ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( con, ComponentSerializer.parse(kick.getMessage()), def, ServerKickEvent.State.CONNECTED ) );
if ( event.isCancelled() && event.getCancelServer() != null ) if ( event.isCancelled() && event.getCancelServer() != null )
{ {
con.connectNow( event.getCancelServer() ); con.connectNow( event.getCancelServer() );
} else } else
{ {
con.disconnect0( event.getKickReason() ); // TODO: Json concat util method // TODO: Prefix our own stuff. con.disconnect0( event.getKickReasonComponent() ); // TODO: Prefix our own stuff.
} }
server.setObsolete( true ); server.setObsolete( true );
throw new CancelSendSignal(); throw new CancelSendSignal();

View File

@ -16,6 +16,8 @@ import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.PendingConnection;
@ -23,6 +25,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.http.HttpClient; import net.md_5.bungee.http.HttpClient;
import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
@ -395,13 +398,29 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override @Override
public synchronized void disconnect(String reason) public synchronized void disconnect(String reason)
{ {
if ( !ch.isClosed() ) if (!ch.isClosed())
{ {
unsafe().sendPacket( new Kick( ChatConverter.toJSONChat( reason )[0] ) ); unsafe().sendPacket( new Kick( ComponentSerializer.toString( TextComponent.fromLegacyText( reason ) ) ) );
ch.close(); ch.close();
} }
} }
@Override
public void disconnect(BaseComponent... reason)
{
if ( !ch.isClosed() )
{
unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) );
ch.close();
}
}
@Override
public void disconnect(BaseComponent reason)
{
disconnect( new BaseComponent[]{reason} );
}
@Override @Override
public String getName() public String getName()
{ {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
package net.md_5.bungee.chat;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.junit.Assert;
import org.junit.Test;
public class ComponentsTest
{
@Test
public void testBasicComponent()
{
TextComponent textComponent = new TextComponent( "Hello world" );
textComponent.setColor( ChatColor.RED );
Assert.assertEquals( "Hello world", textComponent.toPlainText() );
Assert.assertEquals( ChatColor.RED + "Hello world", textComponent.toLegacyText() );
}
@Test
public void testLegacyConverter()
{
BaseComponent[] test1 = TextComponent.fromLegacyText( ChatColor.AQUA + "Aqua " + ChatColor.RED + ChatColor.BOLD + "RedBold" );
Assert.assertEquals( "Aqua RedBold", BaseComponent.toPlainText( test1 ) );
Assert.assertEquals( ChatColor.AQUA + "Aqua " + ChatColor.RED + ChatColor.BOLD + "RedBold", BaseComponent.toLegacyText( test1 ) );
BaseComponent[] test2 = TextComponent.fromLegacyText( "Text http://spigotmc.org " + ChatColor.GREEN + "google.com/test" );
Assert.assertEquals( "Text http://spigotmc.org google.com/test", BaseComponent.toPlainText( test2 ) );
//The extra ChatColor.WHITEs are sometimes inserted when not needed but it doesn't change the result
Assert.assertEquals( ChatColor.WHITE + "Text " + ChatColor.WHITE + "http://spigotmc.org" + ChatColor.WHITE
+ " " + ChatColor.GREEN + "google.com/test", BaseComponent.toLegacyText( test2 ) );
ClickEvent url1 = test2[1].getClickEvent();
Assert.assertNotNull( url1 );
Assert.assertTrue( url1.getAction() == ClickEvent.Action.OPEN_URL );
Assert.assertEquals( "http://spigotmc.org", url1.getValue() );
ClickEvent url2 = test2[3].getClickEvent();
Assert.assertNotNull( url2 );
Assert.assertTrue( url2.getAction() == ClickEvent.Action.OPEN_URL );
Assert.assertEquals( "http://google.com/test", url2.getValue() );
}
@Test
public void testTranslateComponent()
{
TranslatableComponent item = new TranslatableComponent( "item.swordGold.name" );
item.setColor( ChatColor.AQUA );
TranslatableComponent translatableComponent = new TranslatableComponent( "commands.give.success",
item, "5",
"thinkofdeath" );
Assert.assertEquals( "Given Golden Sword * 5 to thinkofdeath", translatableComponent.toPlainText() );
Assert.assertEquals( ChatColor.WHITE + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.WHITE
+ " * " + ChatColor.WHITE + "5" + ChatColor.WHITE + " to " + ChatColor.WHITE + "thinkofdeath",
translatableComponent.toLegacyText() );
TranslatableComponent positional = new TranslatableComponent( "book.pageIndicator", "5", "50" );
Assert.assertEquals( "Page 5 of 50", positional.toPlainText() );
Assert.assertEquals( ChatColor.WHITE + "Page " + ChatColor.WHITE + "5" + ChatColor.WHITE + " of " + ChatColor.WHITE + "50", positional.toLegacyText() );
}
@Test
public void testBuilder()
{
BaseComponent[] components = new ComponentBuilder( "Hello " ).color( ChatColor.RED ).
append( "World" ).bold( true ).color( ChatColor.BLUE ).
append( "!" ).color( ChatColor.YELLOW ).create();
Assert.assertEquals( "Hello World!", BaseComponent.toPlainText( components ) );
Assert.assertEquals( ChatColor.RED + "Hello " + ChatColor.BLUE + ChatColor.BOLD +
"World" + ChatColor.YELLOW + ChatColor.BOLD + "!", BaseComponent.toLegacyText( components ) );
}
}