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 17af2508ac5..0127b7f6f00 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 @@ -215,11 +215,29 @@ public BaseComponent duplicateWithoutFormatting() public static String toLegacyText(BaseComponent... components) { StringBuilder builder = new StringBuilder(); + ComponentStyle currentLegacy = new ComponentStyle(); + currentLegacy.setColor( ChatColor.RESET ); + + toLegacyText( builder, currentLegacy, components ); + return builder.toString(); + } + + /** + * Converts the components to a string that uses the old formatting codes + * ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} + * + * @param builder the StringBuilder to append to + * @param currentLegacy the style at the end of {@code builder} + * @param components the components to convert + * @return the style at the end of the legacy string + */ + public static ComponentStyle toLegacyText(StringBuilder builder, ComponentStyle currentLegacy, BaseComponent... components) + { for ( BaseComponent msg : components ) { - builder.append( msg.toLegacyText() ); + currentLegacy = msg.toLegacyText( builder, currentLegacy ); } - return builder.toString(); + return currentLegacy; } /** @@ -273,15 +291,20 @@ public void setColor(ChatColor color) */ public ChatColor getColor() { - if ( !style.hasColor() ) + return getColor( ChatColor.WHITE ); + } + + ChatColor getColor(ChatColor def) + { + if ( style.hasColor() ) { - if ( parent == null ) - { - return ChatColor.WHITE; - } - return parent.getColor(); + return style.getColor(); } - return style.getColor(); + if ( parent == null ) + { + return def; + } + return parent.getColor( def ); } /** @@ -651,46 +674,148 @@ void toPlainText(StringBuilder builder) public String toLegacyText() { StringBuilder builder = new StringBuilder(); - toLegacyText( builder ); + ComponentStyle currentLegacy = new ComponentStyle(); + currentLegacy.setColor( ChatColor.RESET ); + toLegacyText( builder, currentLegacy ); return builder.toString(); } - void toLegacyText(StringBuilder builder) + /** + * Converts the component to a string that uses the old formatting codes + * ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} + * + * @param currentLegacy the style at the end of the string the result of this method will be appended to + * @return the string in the old format + */ + public String toLegacyText(ComponentStyle currentLegacy) { - if ( extra != null ) + StringBuilder builder = new StringBuilder(); + toLegacyText( builder, currentLegacy ); + return builder.toString(); + } + + /** + * Converts the component to a string that uses the old formatting codes + * ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} + * + * @param builder the StringBuilder to append to + * @param currentLegacy the style at the end of {@code builder} + * @return the style at the end of the legacy string + */ + public ComponentStyle toLegacyText(StringBuilder builder, ComponentStyle currentLegacy) + { + currentLegacy = currentLegacy.clone(); + if ( !currentLegacy.hasColor() ) { - for ( BaseComponent e : extra ) - { - e.toLegacyText( builder ); - } + currentLegacy.setColor( ChatColor.RESET ); + } + return toLegacyText( builder, currentLegacy.hasColor() ? currentLegacy.getColor() : ChatColor.RESET, currentLegacy ); + } + + /** + * Converts the component to a string that uses the old formatting codes + * ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} + * + * @param builder the StringBuilder to append to + * @param baseColor the color to use if no color is set, but a format downgrade is needed + * @param currentLegacy the style at the end of {@code builder} + * @return the new current style at the end of the {@code builder} + */ + ComponentStyle toLegacyText(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) + { + if ( extra == null ) + { + return currentLegacy; } + for ( BaseComponent e : extra ) + { + currentLegacy = e.toLegacyText( builder, baseColor, currentLegacy ); + } + return currentLegacy; } - void addFormat(StringBuilder builder) + private static boolean colorEquals(ChatColor a, ChatColor b) { - if ( style.hasColor() || parent != null ) + if ( a == b ) { - builder.append( getColor() ); + return true; } - if ( isBold() ) + if ( a == null || b == null ) { - builder.append( ChatColor.BOLD ); + return false; } - if ( isItalic() ) + if ( ChatColor.RESET.equals( a ) ) { - builder.append( ChatColor.ITALIC ); + return ChatColor.WHITE.equals( b ) || ChatColor.RESET.equals( b ); } - if ( isUnderlined() ) + if ( ChatColor.RESET.equals( b ) ) { - builder.append( ChatColor.UNDERLINE ); + return ChatColor.WHITE.equals( a ) || ChatColor.RESET.equals( a ); } - if ( isStrikethrough() ) + return a.equals( b ); + } + + /** + * @param builder the StringBuilder to append to + * @param baseColor the color to use if no color is set, but a format downgrade is needed + * @param currentLegacy the style at the end of {@code builder} + * @return the new current style at the end of {@code builder} + */ + ComponentStyle addFormat(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) + { + // Check if we can skip adding color code + if ( colorEquals( getColor(), currentLegacy.getColor() ) && currentLegacy.isLegacyFormattingUpgrade( style ) ) + { + if ( isBold() && !currentLegacy.isBold() ) + { + builder.append( ChatColor.BOLD ); + } + if ( isItalic() && !currentLegacy.isItalic() ) + { + builder.append( ChatColor.ITALIC ); + } + if ( isUnderlined() && !currentLegacy.isUnderlined() ) + { + builder.append( ChatColor.UNDERLINE ); + } + if ( isStrikethrough() && !currentLegacy.isStrikethrough() ) + { + builder.append( ChatColor.STRIKETHROUGH ); + } + if ( isObfuscated() && !currentLegacy.isObfuscated() ) + { + builder.append( ChatColor.MAGIC ); + } + } else { - builder.append( ChatColor.STRIKETHROUGH ); + builder.append( getColor( baseColor ) == null ? baseColor : getColor( baseColor ) ); + 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 ); + } } - if ( isObfuscated() ) + currentLegacy = style; + if ( currentLegacy.getColor() == null ) { - builder.append( ChatColor.MAGIC ); + currentLegacy = style.clone(); + currentLegacy.setColor( getColor( baseColor ) == null ? baseColor : getColor( baseColor ) ); } + return currentLegacy; } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ComponentStyle.java b/chat/src/main/java/net/md_5/bungee/api/chat/ComponentStyle.java index 15f44ea730c..c8d830c06dd 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ComponentStyle.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ComponentStyle.java @@ -200,6 +200,15 @@ public boolean isEmpty() && strikethrough == null && obfuscated == null; } + boolean isLegacyFormattingUpgrade(ComponentStyle newStyle) + { + return ( newStyle.isBold() || !isBold() ) + && ( newStyle.isItalic() || !isItalic() ) + && ( newStyle.isUnderlined() || !isUnderlined() ) + && ( newStyle.isStrikethrough() || !isStrikethrough() ) + && ( newStyle.isObfuscated() || !isObfuscated() ); + } + @Override public ComponentStyle clone() { 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 4cefe259bd7..6b056df78f4 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,6 +5,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import net.md_5.bungee.api.ChatColor; @Getter @Setter @@ -57,10 +58,10 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + ComponentStyle toLegacyText(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) { - addFormat( builder ); + currentLegacy = addFormat( builder, baseColor, currentLegacy ); builder.append( getKeybind() ); - super.toLegacyText( builder ); + return super.toLegacyText( builder, baseColor, currentLegacy ); } } 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 ebf7bb31421..2de0ce1286c 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 @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import net.md_5.bungee.api.ChatColor; /** * This component displays the score based on a player score on the scoreboard. @@ -92,10 +93,10 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + ComponentStyle toLegacyText(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) { - addFormat( builder ); + currentLegacy = addFormat( builder, baseColor, currentLegacy ); builder.append( this.value ); - super.toLegacyText( builder ); + return super.toLegacyText( builder, baseColor, currentLegacy ); } } 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 ff1e260bcb6..6a3c7bc7f9f 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 @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import net.md_5.bungee.api.ChatColor; /** * This component processes a target selector into a pre-formatted set of @@ -76,10 +77,10 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + ComponentStyle toLegacyText(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) { - addFormat( builder ); + currentLegacy = addFormat( builder, baseColor, currentLegacy ); builder.append( this.selector ); - super.toLegacyText( builder ); + return super.toLegacyText( builder, baseColor, currentLegacy ); } } 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 0971a384c70..27727e7cdd4 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 @@ -265,7 +265,7 @@ public TextComponent(BaseComponent... extras) { return; } - setExtra( new ArrayList( Arrays.asList( extras ) ) ); + setExtra( new ArrayList<>( Arrays.asList( extras ) ) ); } /** @@ -287,11 +287,14 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + ComponentStyle toLegacyText(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) { - addFormat( builder ); + // cannot eliminate formatting codes if text is empty to keep test case testFormattingOnlyTextConversion happy + // (this could be solved to always add formatting if (parent == null) and to not ignore formatting at the end in + // general in case plugins are doing weird combinations with conversions + currentLegacy = addFormat( builder, baseColor, currentLegacy ); builder.append( text ); - super.toLegacyText( builder ); + return super.toLegacyText( builder, baseColor, currentLegacy ); } @Override 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 13c095646ac..946ab24d3aa 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,6 +9,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.chat.TranslationRegistry; @Getter @@ -157,18 +158,18 @@ public void addWith(BaseComponent component) @Override protected void toPlainText(StringBuilder builder) { - convert( builder, false ); + convert( builder, null, null ); super.toPlainText( builder ); } @Override - protected void toLegacyText(StringBuilder builder) + ComponentStyle toLegacyText(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) { - convert( builder, true ); - super.toLegacyText( builder ); + currentLegacy = convert( builder, baseColor, currentLegacy ); + return super.toLegacyText( builder, baseColor, currentLegacy ); } - private void convert(StringBuilder builder, boolean applyFormat) + private ComponentStyle convert(StringBuilder builder, ChatColor baseColor, ComponentStyle currentLegacy) { String trans = TranslationRegistry.INSTANCE.translate( translate ); @@ -185,11 +186,11 @@ private void convert(StringBuilder builder, boolean applyFormat) int pos = matcher.start(); if ( pos != position ) { - if ( applyFormat ) + if ( baseColor != null ) { - addFormat( builder ); + currentLegacy = addFormat( builder, baseColor, currentLegacy ); } - builder.append( trans.substring( position, pos ) ); + builder.append( trans, position, pos ); } position = matcher.end(); @@ -201,18 +202,18 @@ private void convert(StringBuilder builder, boolean applyFormat) String withIndex = matcher.group( 1 ); BaseComponent withComponent = with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ); - if ( applyFormat ) + if ( baseColor != null ) { - withComponent.toLegacyText( builder ); + currentLegacy = withComponent.toLegacyText( builder, baseColor, currentLegacy ); } else { withComponent.toPlainText( builder ); } break; case '%': - if ( applyFormat ) + if ( baseColor != null ) { - addFormat( builder ); + currentLegacy = addFormat( builder, baseColor, currentLegacy ); } builder.append( '%' ); break; @@ -220,11 +221,15 @@ private void convert(StringBuilder builder, boolean applyFormat) } if ( trans.length() != position ) { - if ( applyFormat ) + if ( baseColor != null ) { - addFormat( builder ); + currentLegacy = addFormat( builder, baseColor, currentLegacy ); } - builder.append( trans.substring( position, trans.length() ) ); + builder.append( trans, position, trans.length() ); + } else if ( baseColor != null ) + { + currentLegacy = addFormat( builder, baseColor, currentLegacy ); } + return currentLegacy; } } 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 c5eb1990876..35226d6ff50 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 @@ -563,8 +563,7 @@ public void testLegacyConverter() BaseComponent[] test2 = TextComponent.fromLegacyText( "Text http://spigotmc.org " + ChatColor.GREEN + "google.com/test" ); assertEquals( "Text http://spigotmc.org google.com/test", BaseComponent.toPlainText( test2 ) ); - //The extra ChatColor instances are sometimes inserted when not needed but it doesn't change the result - assertEquals( "Text http://spigotmc.org " + ChatColor.GREEN + "google.com/test" + ChatColor.GREEN, BaseComponent.toLegacyText( test2 ) ); + assertEquals( "Text http://spigotmc.org " + ChatColor.GREEN + "google.com/test", BaseComponent.toLegacyText( test2 ) ); ClickEvent url1 = test2[1].getClickEvent(); assertNotNull( url1 ); @@ -585,35 +584,32 @@ public void testTranslateComponent() assertEquals( "Given Golden Sword * 5 to thinkofdeath", component.toPlainText() ); component.setColor( ChatColor.RED ); - assertEquals( ChatColor.RED + "Given " + ChatColor.RED + "Golden Sword" + ChatColor.RED + " * " + ChatColor.RED - + "5" + ChatColor.RED + " to " + ChatColor.RED + "thinkofdeath", component.toLegacyText() ); + assertEquals( ChatColor.RED + "Given Golden Sword * 5 to thinkofdeath", component.toLegacyText() ); item.setColor( ChatColor.AQUA ); - assertEquals( ChatColor.RED + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.RED + " * " + ChatColor.RED - + "5" + ChatColor.RED + " to " + ChatColor.RED + "thinkofdeath", component.toLegacyText() ); + assertEquals( ChatColor.RED + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.RED + " * 5 to thinkofdeath", + component.toLegacyText() ); component.setColor( null ); // fails, actual value is "Given §bGolden Sword * §f5 to §fthinkofdeath" // assertEquals( "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.RESET + " * 5 to thinkofdeath", component.toLegacyText() ); BaseComponent legacyColorTest = new ComponentBuilder( "Test " ).color( ChatColor.RED ).append( component ).build(); - assertEquals( ChatColor.RED + "Test " + ChatColor.RED + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.RED - + " * " + ChatColor.RED + "5" + ChatColor.RED + " to " + ChatColor.RED + "thinkofdeath", legacyColorTest.toLegacyText() ); + assertEquals( ChatColor.RED + "Test Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.RED + + " * 5 to thinkofdeath", legacyColorTest.toLegacyText() ); BaseComponent legacyColorTest2 = new TextComponent( "Test " ); legacyColorTest2.addExtra( new ComponentBuilder( "abc " ).color( ChatColor.GRAY ).build() ); legacyColorTest2.addExtra( component ); legacyColorTest2.addExtra( new ComponentBuilder( " def" ).build() ); assertEquals( "Test " + ChatColor.GRAY + "abc " + ChatColor.RED + "Given " + ChatColor.AQUA + "Golden Sword" - + ChatColor.RED + " * " + ChatColor.RED + "5" + ChatColor.RED + " to " + ChatColor.RED + "thinkofdeath" - + ChatColor.WHITE + " def", legacyColorTest2.toLegacyText() ); + + ChatColor.RED + " * 5 to thinkofdeath" + ChatColor.RESET + " def", legacyColorTest2.toLegacyText() ); legacyColorTest2.setColor( ChatColor.RED ); assertEquals( ChatColor.RED + "Test " + ChatColor.GRAY + "abc " + ChatColor.RED + "Given " + ChatColor.AQUA - + "Golden Sword" + ChatColor.RED + " * " + ChatColor.RED + "5" + ChatColor.RED + " to " + ChatColor.RED - + "thinkofdeath" + ChatColor.RED + " def", legacyColorTest2.toLegacyText() ); + + "Golden Sword" + ChatColor.RED + " * 5 to thinkofdeath def", legacyColorTest2.toLegacyText() ); TranslatableComponent positional = new TranslatableComponent( "book.pageIndicator", "5", "50" ); assertEquals( "Page 5 of 50", positional.toPlainText() ); - assertEquals( "Page " + ChatColor.WHITE + "5 of " + ChatColor.WHITE + "50", positional.toLegacyText() ); + assertEquals( "Page 5 of 50", positional.toLegacyText() ); TranslatableComponent one_four_two = new TranslatableComponent( "filled_map.buried_treasure" ); assertEquals( "Buried Treasure Map", one_four_two.toPlainText() ); @@ -932,7 +928,7 @@ public void testExtraFormatting() component.addExtra( new ComponentBuilder( " xd" ).build() ); assertEquals( "Hello World! xd", component.toPlainText() ); - assertEquals( ChatColor.GOLD + "Hello " + ChatColor.GOLD + ChatColor.BOLD + "World" + ChatColor.RED + "!" + assertEquals( ChatColor.GOLD + "Hello " + ChatColor.BOLD + "World" + ChatColor.RED + "!" + ChatColor.GOLD + " xd", component.toLegacyText() ); } @@ -947,8 +943,8 @@ public void testExtraFormattingNested() assertEquals( "Hello World! xd", component.toPlainText() ); // Empty extra text component 2 (holding extra "!") adds the redudant gold formatting, // see TextComponent#toLegacyText comment as to why we keep it - assertEquals( ChatColor.GOLD + "Hello " + ChatColor.GOLD + ChatColor.GOLD + ChatColor.BOLD + "World" - + ChatColor.GOLD + ChatColor.RED + "!" + ChatColor.GOLD + ChatColor.GOLD + " xd", component.toLegacyText() ); + assertEquals( ChatColor.GOLD + "Hello " + ChatColor.BOLD + "World" + ChatColor.GOLD + ChatColor.RED + "!" + + ChatColor.GOLD + " xd", component.toLegacyText() ); } @Test @@ -964,7 +960,6 @@ public void testArrayToLegacyConversionContext() }; assertEquals( "Hello World!", BaseComponent.toPlainText( components ) ); - // fails, actual result doesn't reset or whiten the "!": "Hello §lWorld!" assertEquals( "Hello " + ChatColor.BOLD + "World" + ChatColor.RESET + "!", BaseComponent.toLegacyText( components ) ); } } diff --git a/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java b/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java index 47c06baafcf..6a05cc3e52e 100644 --- a/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java +++ b/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java @@ -12,7 +12,7 @@ public void testMissingPlaceholdersAdded() { TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholders: %s", 2, "aoeu" ); assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toPlainText() ); - assertEquals( "§fTest string with §f2§f placeholders: §faoeu", testComponent.toLegacyText() ); + assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toLegacyText() ); } @Test @@ -23,6 +23,6 @@ public void testJsonSerialisation() BaseComponent[] baseComponents = ComponentSerializer.parse( jsonString ); assertEquals( "Test string with a placeholder", BaseComponent.toPlainText( baseComponents ) ); - assertEquals( "§fTest string with §fa§f placeholder", BaseComponent.toLegacyText( baseComponents ) ); + assertEquals( "Test string with a placeholder", BaseComponent.toLegacyText( baseComponents ) ); } }