From d2ceccd646f33cedca614a9a58bfd84a44b28322 Mon Sep 17 00:00:00 2001 From: Mystiflow Date: Sun, 5 Jan 2020 10:33:44 +1100 Subject: [PATCH] #2725: Various improvements to chat API * More versatile ComponentBuilder system - Allow creating a builder without an initial component - Duplicate the parts when component is created - Add getter for 'parts' * Added cursor API for more fluid component modifying * Don't legacy convert Titles on 1.11 or newer * Simplify plain and legacy text converting code - Shares the addFormat method between all superclasses - Duplicate code in TranslatableComponent moved in separate method --- .../md_5/bungee/api/chat/BaseComponent.java | 25 +++ .../bungee/api/chat/ComponentBuilder.java | 186 ++++++++++++++---- .../bungee/api/chat/KeybindComponent.java | 24 +-- .../md_5/bungee/api/chat/ScoreComponent.java | 8 + .../bungee/api/chat/SelectorComponent.java | 8 + .../md_5/bungee/api/chat/TextComponent.java | 45 +++-- .../api/chat/TranslatableComponent.java | 91 +++------ .../chat/TranslatableComponentSerializer.java | 2 +- .../md_5/bungee/api/chat/ComponentsTest.java | 108 +++++++++- .../module/cmd/server/CommandServer.java | 2 +- .../java/net/md_5/bungee/UserConnection.java | 15 +- 11 files changed, 355 insertions(+), 159 deletions(-) diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java index 8cf218fd..c3ffbf7c 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java @@ -505,4 +505,29 @@ public abstract class BaseComponent } } } + + 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 ); + } + } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java b/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java index 1e1ef711..121a32c4 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java @@ -3,6 +3,8 @@ package net.md_5.bungee.api.chat; import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; import net.md_5.bungee.api.ChatColor; /** @@ -23,11 +25,29 @@ import net.md_5.bungee.api.ChatColor; * part's formatting *

*/ +@NoArgsConstructor public final class ComponentBuilder { - private BaseComponent current; + /** + * The position for the current part to modify. Modified cursors will + * automatically reset to the last part after appending new components. + * Default value at -1 to assert that the builder has no parts. + */ + @Getter + private int cursor = -1; + @Getter private final List parts = new ArrayList(); + private BaseComponent dummy; + + private ComponentBuilder(BaseComponent[] parts) + { + for ( BaseComponent baseComponent : parts ) + { + this.parts.add( baseComponent.duplicate() ); + } + resetCursor(); + } /** * Creates a ComponentBuilder from the other given ComponentBuilder to clone @@ -37,11 +57,7 @@ public final class ComponentBuilder */ public ComponentBuilder(ComponentBuilder original) { - current = original.current.duplicate(); - for ( BaseComponent baseComponent : original.parts ) - { - parts.add( baseComponent.duplicate() ); - } + this( original.parts.toArray( new BaseComponent[ original.parts.size() ] ) ); } /** @@ -51,7 +67,7 @@ public final class ComponentBuilder */ public ComponentBuilder(String text) { - current = new TextComponent( text ); + this( new TextComponent( text ) ); } /** @@ -61,7 +77,58 @@ public final class ComponentBuilder */ public ComponentBuilder(BaseComponent component) { - current = component.duplicate(); + + this( new BaseComponent[] + { + component + } ); + } + + private BaseComponent getDummy() + { + if ( dummy == null ) + { + dummy = new BaseComponent() + { + @Override + public BaseComponent duplicate() + { + return this; + } + }; + } + return dummy; + } + + /** + * Resets the cursor to index of the last element. + * + * @return this ComponentBuilder for chaining + */ + public ComponentBuilder resetCursor() + { + cursor = parts.size() - 1; + return this; + } + + /** + * Sets the position of the current component to be modified + * + * @param pos the cursor position synonymous to an element position for a + * list + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + * @return this ComponentBuilder for chaining + */ + public ComponentBuilder setCursor(int pos) throws IndexOutOfBoundsException + { + if ( ( this.cursor != pos ) && ( pos < 0 || pos >= parts.size() ) ) + { + throw new IndexOutOfBoundsException( "Cursor out of bounds (expected between 0 + " + ( parts.size() - 1 ) + ")" ); + } + + this.cursor = pos; + return this; } /** @@ -88,11 +155,18 @@ public final class ComponentBuilder */ public ComponentBuilder append(BaseComponent component, FormatRetention retention) { - parts.add( current ); - - BaseComponent previous = current; - current = component.duplicate(); - current.copyFormatting( previous, retention, false ); + BaseComponent previous = ( parts.isEmpty() ) ? null : parts.get( parts.size() - 1 ); + if ( previous == null ) + { + previous = dummy; + dummy = null; + } + if ( previous != null ) + { + component.copyFormatting( previous, retention, false ); + } + parts.add( component ); + resetCursor(); return this; } @@ -122,13 +196,9 @@ public final class ComponentBuilder { Preconditions.checkArgument( components.length != 0, "No components to append" ); - BaseComponent previous = current; for ( BaseComponent component : components ) { - parts.add( current ); - - current = component.duplicate(); - current.copyFormatting( previous, retention, false ); + append( component, retention ); } return this; @@ -170,13 +240,7 @@ public final class ComponentBuilder */ public ComponentBuilder append(String text, FormatRetention retention) { - parts.add( current ); - - BaseComponent old = current; - current = new TextComponent( text ); - current.copyFormatting( old, retention, false ); - - return this; + return append( new TextComponent( text ), retention ); } /** @@ -210,6 +274,44 @@ public final class ComponentBuilder return joiner.join( this, retention ); } + /** + * Remove the component part at the position of given index. + * + * @param pos the index to remove at + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + public void removeComponent(int pos) throws IndexOutOfBoundsException + { + if ( parts.remove( pos ) != null ) + { + resetCursor(); + } + } + + /** + * Gets the component part at the position of given index. + * + * @param pos the index to find + * @return the component + * @throws IndexOutOfBoundsException if the index is out of range + * ({@code index < 0 || index >= size()}) + */ + public BaseComponent getComponent(int pos) throws IndexOutOfBoundsException + { + return parts.get( pos ); + } + + /** + * Gets the component at the position of the cursor. + * + * @return the active component or null if builder is empty + */ + public BaseComponent getCurrentComponent() + { + return ( cursor == -1 ) ? getDummy() : parts.get( cursor ); + } + /** * Sets the color of the current part. * @@ -218,7 +320,7 @@ public final class ComponentBuilder */ public ComponentBuilder color(ChatColor color) { - current.setColor( color ); + getCurrentComponent().setColor( color ); return this; } @@ -230,7 +332,7 @@ public final class ComponentBuilder */ public ComponentBuilder bold(boolean bold) { - current.setBold( bold ); + getCurrentComponent().setBold( bold ); return this; } @@ -242,7 +344,7 @@ public final class ComponentBuilder */ public ComponentBuilder italic(boolean italic) { - current.setItalic( italic ); + getCurrentComponent().setItalic( italic ); return this; } @@ -254,7 +356,7 @@ public final class ComponentBuilder */ public ComponentBuilder underlined(boolean underlined) { - current.setUnderlined( underlined ); + getCurrentComponent().setUnderlined( underlined ); return this; } @@ -266,7 +368,7 @@ public final class ComponentBuilder */ public ComponentBuilder strikethrough(boolean strikethrough) { - current.setStrikethrough( strikethrough ); + getCurrentComponent().setStrikethrough( strikethrough ); return this; } @@ -278,7 +380,7 @@ public final class ComponentBuilder */ public ComponentBuilder obfuscated(boolean obfuscated) { - current.setObfuscated( obfuscated ); + getCurrentComponent().setObfuscated( obfuscated ); return this; } @@ -290,7 +392,7 @@ public final class ComponentBuilder */ public ComponentBuilder insertion(String insertion) { - current.setInsertion( insertion ); + getCurrentComponent().setInsertion( insertion ); return this; } @@ -302,7 +404,7 @@ public final class ComponentBuilder */ public ComponentBuilder event(ClickEvent clickEvent) { - current.setClickEvent( clickEvent ); + getCurrentComponent().setClickEvent( clickEvent ); return this; } @@ -314,7 +416,7 @@ public final class ComponentBuilder */ public ComponentBuilder event(HoverEvent hoverEvent) { - current.setHoverEvent( hoverEvent ); + getCurrentComponent().setHoverEvent( hoverEvent ); return this; } @@ -336,24 +438,28 @@ public final class ComponentBuilder */ public ComponentBuilder retain(FormatRetention retention) { - current.retain( retention ); + getCurrentComponent().retain( retention ); return this; } /** * Returns the components needed to display the message created by this - * builder. + * builder.git * * @return the created components */ public BaseComponent[] create() { - BaseComponent[] result = parts.toArray( new BaseComponent[ parts.size() + 1 ] ); - result[parts.size()] = current; - return result; + BaseComponent[] cloned = new BaseComponent[ parts.size() ]; + int i = 0; + for ( BaseComponent part : parts ) + { + cloned[i++] = part.duplicate(); + } + return cloned; } - public static enum FormatRetention + public enum FormatRetention { /** diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java index d0fc1793..039a3489 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java @@ -5,7 +5,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; -import net.md_5.bungee.api.ChatColor; @Getter @Setter @@ -60,29 +59,8 @@ public final class KeybindComponent extends BaseComponent @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 ); - } + addFormat( builder ); builder.append( getKeybind() ); - super.toLegacyText( builder ); } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java index 6a4080a0..ebf7bb31 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java @@ -84,9 +84,17 @@ public final class ScoreComponent extends BaseComponent return new ScoreComponent( this ); } + @Override + protected void toPlainText(StringBuilder builder) + { + builder.append( this.value ); + super.toPlainText( builder ); + } + @Override protected void toLegacyText(StringBuilder builder) { + addFormat( builder ); builder.append( this.value ); super.toLegacyText( builder ); } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java index 4ef2d8cb..132dfabe 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java @@ -50,9 +50,17 @@ public final class SelectorComponent extends BaseComponent return new SelectorComponent( this ); } + @Override + protected void toPlainText(StringBuilder builder) + { + builder.append( this.selector ); + super.toPlainText( builder ); + } + @Override protected void toLegacyText(StringBuilder builder) { + addFormat( builder ); builder.append( this.selector ); super.toLegacyText( builder ); } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java index 3caf9544..dc5d5043 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java @@ -2,6 +2,7 @@ package net.md_5.bungee.api.chat; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.AllArgsConstructor; @@ -172,8 +173,26 @@ public final class TextComponent extends BaseComponent */ public TextComponent(BaseComponent... extras) { - setText( "" ); - setExtra( new ArrayList( Arrays.asList( extras ) ) ); + if ( extras.length == 0 ) + { + return; + } + if ( extras.length == 1 && extras[0] instanceof TextComponent ) + { + setText( ( (TextComponent) extras[0] ).getText() ); + List headExtra = extras[0].getExtra(); + if ( headExtra != null ) + { + for ( BaseComponent extra : headExtra ) + { + addExtra( extra.duplicate() ); + } + } + } else + { + setText( "" ); + setExtra( new ArrayList( Arrays.asList( extras ) ) ); + } } /** @@ -197,27 +216,7 @@ public final class TextComponent extends BaseComponent @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 ); - } + addFormat( builder ); builder.append( text ); super.toLegacyText( builder ); } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index 9f321162..285f1cfe 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -9,7 +9,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; -import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.chat.TranslationRegistry; @Getter @@ -139,43 +138,18 @@ public final class TranslatableComponent extends BaseComponent @Override protected void toPlainText(StringBuilder builder) { - String trans = TranslationRegistry.INSTANCE.translate( translate ); - - 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() ) ); - } - + convert( builder, false ); super.toPlainText( builder ); } @Override protected void toLegacyText(StringBuilder builder) + { + convert( builder, true ); + super.toLegacyText( builder ); + } + + private void convert(StringBuilder builder, boolean applyFormat) { String trans = TranslationRegistry.INSTANCE.translate( translate ); @@ -187,7 +161,10 @@ public final class TranslatableComponent extends BaseComponent int pos = matcher.start(); if ( pos != position ) { - addFormat( builder ); + if ( applyFormat ) + { + addFormat( builder ); + } builder.append( trans.substring( position, pos ) ); } position = matcher.end(); @@ -198,44 +175,32 @@ public final class TranslatableComponent extends BaseComponent case 's': case 'd': String withIndex = matcher.group( 1 ); - with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder ); + + BaseComponent withComponent = with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ); + if ( applyFormat ) + { + withComponent.toLegacyText( builder ); + } else + { + withComponent.toPlainText( builder ); + } break; case '%': - addFormat( builder ); + if ( applyFormat ) + { + addFormat( builder ); + } builder.append( '%' ); break; } } if ( trans.length() != position ) { - addFormat( builder ); + if ( applyFormat ) + { + 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 ); - } } } diff --git a/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java index 3aa22b68..b2e88ffb 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java @@ -24,7 +24,7 @@ public class TranslatableComponentSerializer extends BaseComponentSerializer imp component.setTranslate( object.get( "translate" ).getAsString() ); if ( object.has( "with" ) ) { - component.setWith( Arrays.asList( (BaseComponent[]) context.deserialize( object.get( "with" ), BaseComponent[].class ) ) ); + component.setWith( Arrays.asList( context.deserialize( object.get( "with" ), BaseComponent[].class ) ) ); } return component; } diff --git a/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java b/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java index 64db54af..a88f6a1e 100644 --- a/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java +++ b/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java @@ -11,16 +11,112 @@ import org.junit.Test; public class ComponentsTest { + @Test + public void testEmptyComponentBuilder() + { + ComponentBuilder builder = new ComponentBuilder(); + + BaseComponent[] parts = builder.create(); + Assert.assertEquals( parts.length, 0 ); + + for ( int i = 0; i < 3; i++ ) + { + builder.append( "part:" + i ); + parts = builder.create(); + Assert.assertEquals( parts.length, i + 1 ); + } + } + + @Test + public void testDummyRetaining() + { + ComponentBuilder builder = new ComponentBuilder(); + Assert.assertNotNull( builder.getCurrentComponent() ); + builder.color( ChatColor.GREEN ); + builder.append( "test ", ComponentBuilder.FormatRetention.ALL ); + Assert.assertEquals( builder.getCurrentComponent().getColor(), ChatColor.GREEN ); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testComponentGettingExceptions() + { + ComponentBuilder builder = new ComponentBuilder(); + builder.getComponent( -1 ); + builder.getComponent( 0 ); + builder.getComponent( 1 ); + BaseComponent component = new TextComponent( "Hello" ); + builder.append( component ); + Assert.assertEquals( builder.getComponent( 0 ), component ); + builder.getComponent( 1 ); + } + + @Test + public void testComponentParting() + { + ComponentBuilder builder = new ComponentBuilder(); + TextComponent apple = new TextComponent( "apple" ); + builder.append( apple ); + Assert.assertEquals( builder.getCurrentComponent(), apple ); + Assert.assertEquals( builder.getComponent( 0 ), apple ); + + TextComponent mango = new TextComponent( "mango" ); + TextComponent orange = new TextComponent( "orange" ); + builder.append( mango ); + builder.append( orange ); + builder.removeComponent( 1 ); // Removing mango + Assert.assertEquals( builder.getComponent( 0 ), apple ); + Assert.assertEquals( builder.getComponent( 1 ), orange ); + } + + @Test + public void testToLegacyFromLegacy() + { + String text = "§a§lHello §f§kworld§7!"; + Assert.assertEquals( text, TextComponent.toLegacyText( TextComponent.fromLegacyText( text ) ) ); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testComponentBuilderCursorInvalidPos() + { + ComponentBuilder builder = new ComponentBuilder(); + builder.append( new TextComponent( "Apple, " ) ); + builder.append( new TextComponent( "Orange, " ) ); + builder.setCursor( -1 ); + builder.setCursor( 2 ); + } + + @Test + public void testComponentBuilderCursor() + { + TextComponent t1, t2, t3; + ComponentBuilder builder = new ComponentBuilder(); + Assert.assertEquals( builder.getCursor(), -1 ); + builder.append( t1 = new TextComponent( "Apple, " ) ); + Assert.assertEquals( builder.getCursor(), 0 ); + builder.append( t2 = new TextComponent( "Orange, " ) ); + builder.append( t3 = new TextComponent( "Mango, " ) ); + Assert.assertEquals( builder.getCursor(), 2 ); + + builder.setCursor( 0 ); + Assert.assertEquals( builder.getCurrentComponent(), t1 ); + + // Test that appending new components updates the position to the new list size + // after having previously set it to 0 (first component) + builder.append( new TextComponent( "and Grapefruit" ) ); + Assert.assertEquals( builder.getCursor(), 3 ); + + builder.setCursor( 0 ); + builder.resetCursor(); + Assert.assertEquals( builder.getCursor(), 3 ); + } + @Test public void testLegacyComponentBuilderAppend() { String text = "§a§lHello §r§kworld§7!"; BaseComponent[] components = TextComponent.fromLegacyText( text ); - BaseComponent[] builderComponents = new ComponentBuilder( "" ).append( components ).create(); + BaseComponent[] builderComponents = new ComponentBuilder().append( components ).create(); List list = new ArrayList( Arrays.asList( builderComponents ) ); - // Remove the first element (empty text component). This needs to be done because toLegacyText always - // appends &f regardless if the color is non null or not and would otherwise mess with our unit test. - list.remove( 0 ); Assert.assertEquals( TextComponent.toLegacyText( components ), TextComponent.toLegacyText( list.toArray( new BaseComponent[ list.size() ] ) ) @@ -28,7 +124,7 @@ public class ComponentsTest } @Test - public void testComponentFormatRetention() + public void testFormatRetentionCopyFormatting() { TextComponent first = new TextComponent( "Hello" ); first.setBold( true ); @@ -47,7 +143,7 @@ public class ComponentsTest @Test public void testBuilderClone() { - ComponentBuilder builder = new ComponentBuilder( "Hel" ).color( ChatColor.RED ).append( "lo" ).color( ChatColor.DARK_RED ); + ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.RED ).append( "world" ).color( ChatColor.DARK_RED ); ComponentBuilder cloned = new ComponentBuilder( builder ); Assert.assertEquals( TextComponent.toLegacyText( builder.create() ), TextComponent.toLegacyText( cloned.create() ) ); diff --git a/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java b/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java index 8bd24609..59c104d3 100644 --- a/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java +++ b/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java @@ -40,7 +40,7 @@ public class CommandServer extends Command implements TabExecutor sender.sendMessage( ProxyServer.getInstance().getTranslation( "current_server", ( (ProxiedPlayer) sender ).getServer().getInfo().getName() ) ); } - ComponentBuilder serverList = new ComponentBuilder( "" ).appendLegacy( ProxyServer.getInstance().getTranslation( "server_list" ) ); + ComponentBuilder serverList = new ComponentBuilder().appendLegacy( ProxyServer.getInstance().getTranslation( "server_list" ) ); boolean first = true; for ( ServerInfo server : servers.values() ) { diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index fda93018..5b72efea 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -51,6 +51,7 @@ import net.md_5.bungee.protocol.MinecraftDecoder; import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.Protocol; +import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.packet.Chat; import net.md_5.bungee.protocol.packet.ClientSettings; import net.md_5.bungee.protocol.packet.Kick; @@ -453,10 +454,20 @@ public final class UserConnection implements ProxiedPlayer // transform score components message = ChatComponentTransformer.getInstance().transform( this, message ); - // Action bar doesn't display the new JSON formattings, legacy works - send it using this for now if ( position == ChatMessageType.ACTION_BAR ) { - sendMessage( position, ComponentSerializer.toString( new TextComponent( BaseComponent.toLegacyText( message ) ) ) ); + // Versions older than 1.11 cannot send the Action bar with the new JSON formattings + // Fix by converting to a legacy message, see https://bugs.mojang.com/browse/MC-119145 + if ( ProxyServer.getInstance().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_10 ) + { + sendMessage( position, ComponentSerializer.toString( new TextComponent( BaseComponent.toLegacyText( message ) ) ) ); + } else + { + net.md_5.bungee.protocol.packet.Title title = new net.md_5.bungee.protocol.packet.Title(); + title.setAction( net.md_5.bungee.protocol.packet.Title.Action.ACTIONBAR ); + title.setText( ComponentSerializer.toString( message ) ); + unsafe.sendPacket( title ); + } } else { sendMessage( position, ComponentSerializer.toString( message ) );