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 ) );