diff --git a/bukkit/paper/src/main/java/me/neznamy/tab/platforms/paper/PaperComponentConverter.java b/bukkit/paper/src/main/java/me/neznamy/tab/platforms/paper/PaperComponentConverter.java index 08c0d9a47..a67523b2d 100644 --- a/bukkit/paper/src/main/java/me/neznamy/tab/platforms/paper/PaperComponentConverter.java +++ b/bukkit/paper/src/main/java/me/neznamy/tab/platforms/paper/PaperComponentConverter.java @@ -1,20 +1,18 @@ package me.neznamy.tab.platforms.paper; import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter; -import me.neznamy.tab.shared.chat.ChatModifier; -import me.neznamy.tab.shared.chat.SimpleComponent; -import me.neznamy.tab.shared.chat.StructuredComponent; -import me.neznamy.tab.shared.chat.TabComponent; -import net.minecraft.ChatFormatting; +import me.neznamy.tab.shared.chat.*; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.format.TextDecoration; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.Style; import net.minecraft.network.chat.TextColor; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; +import org.jetbrains.annotations.Nullable; /** * Component converter using direct mojang-mapped code for versions 1.20.5+. @@ -25,38 +23,71 @@ public class PaperComponentConverter extends ComponentConverter { @Override @NotNull public Component convert(@NotNull TabComponent component, boolean modern) { - if (component instanceof SimpleComponent) return Component.literal(((SimpleComponent) component).getText()); - - StructuredComponent component1 = (StructuredComponent) component; - MutableComponent nmsComponent = Component.literal(component1.getText()); - nmsComponent.setStyle(createModifierModern(component1.getModifier(), modern)); - for (StructuredComponent extra : component1.getExtra()) { - nmsComponent.append(convert(extra, modern)); + switch (component) { + case SimpleComponent simpleComponent -> { + return Component.literal(simpleComponent.getText()); + } + case StructuredComponent component1 -> { + MutableComponent nmsComponent = Component.literal(component1.getText()); + ChatModifier modifier = component1.getModifier(); + TextColor color = null; + if (modifier.getColor() != null) { + if (modern) { + color = TextColor.fromRgb(modifier.getColor().getRgb()); + } else { + color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb()); + } + } + nmsComponent.setStyle(newStyle(color, modifier.isBold(), modifier.isItalic(), modifier.isUnderlined(), + modifier.isUnderlined(), modifier.isObfuscated(), modifier.getFont())); + for (StructuredComponent extra : component1.getExtra()) { + nmsComponent.append(convert(extra, modern)); + } + return nmsComponent; + } + case AdventureComponent component1 -> { + return fromAdventure(component1.getComponent()); + } + default -> throw new IllegalStateException("Unexpected component type: " + component.getClass().getName()); } - return nmsComponent; } @NotNull - private Style createModifierModern(@NotNull ChatModifier modifier, boolean modern) { - TextColor color = null; - if (modifier.getColor() != null) { - if (modern) { - color = TextColor.fromRgb(modifier.getColor().getRgb()); - } else { - color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb()); - } + private Component fromAdventure(@NotNull net.kyori.adventure.text.Component component) { + MutableComponent nmsComponent; + if (component instanceof TextComponent component1) { + nmsComponent = Component.literal(component1.content()); + } else if (component instanceof TranslatableComponent component1) { + nmsComponent = Component.translatable(component1.key()); + } else throw new IllegalStateException("Cannot convert " + component.getClass().getName()); + + net.kyori.adventure.text.format.TextColor color = component.color(); + Key font = component.style().font(); + nmsComponent.setStyle(newStyle( + color == null ? null : TextColor.fromRgb(color.value()), + component.style().hasDecoration(TextDecoration.BOLD), + component.style().hasDecoration(TextDecoration.ITALIC), + component.style().hasDecoration(TextDecoration.UNDERLINED), + component.style().hasDecoration(TextDecoration.STRIKETHROUGH), + component.style().hasDecoration(TextDecoration.OBFUSCATED), + font == null ? null : font.asString() + )); + for (net.kyori.adventure.text.Component extra : component.children()) { + nmsComponent.append(fromAdventure(extra)); } - List formats = new ArrayList<>(); - if (modifier.isBold()) formats.add(ChatFormatting.BOLD); - if (modifier.isItalic()) formats.add(ChatFormatting.ITALIC); - if (modifier.isUnderlined()) formats.add(ChatFormatting.UNDERLINE); - if (modifier.isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH); - if (modifier.isObfuscated()) formats.add(ChatFormatting.OBFUSCATED); + return nmsComponent; + } - Style style = Style.EMPTY; - if (color != null) style = style.withColor(color); - if (!formats.isEmpty()) style = style.applyFormats(formats.toArray(new ChatFormatting[0])); - if (modifier.getFont() != null) style = style.withFont(ResourceLocation.tryParse(modifier.getFont())); - return style; + @NotNull + private Style newStyle(@Nullable TextColor color, boolean bold, boolean italic, boolean underlined, + boolean strikethrough, boolean obfuscated, @Nullable String font) { + return Style.EMPTY + .withColor(color) + .withBold(bold) + .withItalic(italic) + .withUnderlined(underlined) + .withStrikethrough(strikethrough) + .withObfuscated(obfuscated) + .withFont(font == null ? null : ResourceLocation.tryParse(font)); } } diff --git a/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/nms/converter/ReflectionComponentConverter.java b/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/nms/converter/ReflectionComponentConverter.java index 93bd47e80..048c7a520 100644 --- a/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/nms/converter/ReflectionComponentConverter.java +++ b/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/nms/converter/ReflectionComponentConverter.java @@ -2,13 +2,15 @@ import lombok.SneakyThrows; import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection; -import me.neznamy.tab.shared.chat.ChatModifier; -import me.neznamy.tab.shared.chat.SimpleComponent; -import me.neznamy.tab.shared.chat.StructuredComponent; -import me.neznamy.tab.shared.chat.TabComponent; +import me.neznamy.tab.shared.chat.*; import me.neznamy.tab.shared.util.FunctionWithException; import me.neznamy.tab.shared.util.ReflectionUtils; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.format.TextDecoration; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -23,6 +25,7 @@ public class ReflectionComponentConverter extends ComponentConverter { private final FunctionWithException newTextComponent; + private final FunctionWithException newTranslatableComponent; private final BiFunction convertModifier; private final Class ChatModifier = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier"); @@ -50,6 +53,8 @@ public ReflectionComponentConverter() throws ReflectiveOperationException { if (BukkitReflection.getMinorVersion() >= 19) { Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class); newTextComponent = text -> IChatBaseComponent_b.invoke(null, text); + Method IChatBaseComponent_c = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"c", "translatable"}, String.class); + newTranslatableComponent = text -> IChatBaseComponent_c.invoke(null, text); Class IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent", "IChatMutableComponent"); Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier); ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent); @@ -57,9 +62,14 @@ public ReflectionComponentConverter() throws ReflectiveOperationException { Class ChatComponentText = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText"); Constructor newChatComponentText = ChatComponentText.getConstructor(String.class); newTextComponent = newChatComponentText::newInstance; + + Class ChatMessage = BukkitReflection.getClass("network.chat.TranslatableComponent", "network.chat.ChatMessage", "ChatMessage"); + Constructor newChatMessage = ChatMessage.getConstructor(String.class, Object[].class); + newTranslatableComponent = text -> newChatMessage.newInstance(text, new Object[0]); + Class ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent"); Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier); - ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent); + ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent); } if (BukkitReflection.getMinorVersion() >= 16) { Class chatHexColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor"); @@ -89,13 +99,44 @@ public ReflectionComponentConverter() throws ReflectiveOperationException { @SneakyThrows @NotNull public Object convert(@NotNull TabComponent component, boolean modern) { - if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText()); + if (component instanceof SimpleComponent) { + return newTextComponent.apply(((SimpleComponent) component).getText()); + } else if (component instanceof StructuredComponent) { + StructuredComponent component1 = (StructuredComponent) component; + Object nmsComponent = newTextComponent.apply(component1.getText()); + Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern)); + for (StructuredComponent extra : component1.getExtra()) { + ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern)); + } + return nmsComponent; + } else { + return fromAdventure(((AdventureComponent)component).getComponent()); + } + } - StructuredComponent component1 = (StructuredComponent) component; - Object nmsComponent = newTextComponent.apply(component1.getText()); - Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern)); - for (StructuredComponent extra : component1.getExtra()) { - ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern)); + @SneakyThrows + @NotNull + private Object fromAdventure(@NotNull net.kyori.adventure.text.Component component) { + Object nmsComponent; + if (component instanceof TextComponent) { + nmsComponent = newTextComponent.apply(((TextComponent) component).content()); + } else if (component instanceof TranslatableComponent) { + nmsComponent = newTranslatableComponent.apply(((TranslatableComponent)component).key()); + } else throw new IllegalStateException("Cannot convert " + component.getClass().getName()); + + net.kyori.adventure.text.format.TextColor color = component.color(); + Key font = component.style().font(); + Component_modifier.set(nmsComponent, newStyleModern( + color == null ? null : ChatHexColor_fromRGB.invoke(null, color.value()), + component.style().hasDecoration(TextDecoration.BOLD), + component.style().hasDecoration(TextDecoration.ITALIC), + component.style().hasDecoration(TextDecoration.UNDERLINED), + component.style().hasDecoration(TextDecoration.STRIKETHROUGH), + component.style().hasDecoration(TextDecoration.OBFUSCATED), + font == null ? null : font.asString() + )); + for (net.kyori.adventure.text.Component extra : component.children()) { + ChatBaseComponent_addSibling.invoke(nmsComponent, fromAdventure(extra)); } return nmsComponent; } @@ -110,47 +151,61 @@ private Object createModifierModern(@NotNull ChatModifier modifier, boolean mode color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb()); } } + return newStyleModern( + color, + modifier.isBold(), + modifier.isItalic(), + modifier.isUnderlined(), + modifier.isStrikethrough(), + modifier.isObfuscated(), + modifier.getFont()); + } + + @SneakyThrows + private Object createModifierLegacy(@NotNull ChatModifier modifier) { + Object nmsModifier = newChatModifier.newInstance(); + if (modifier.getColor() != null) { + ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name())); + } + if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true); + if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true); + if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true); + if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true); + if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true); + return nmsModifier; + } + + @SneakyThrows + @NotNull + private Object newStyleModern(@Nullable Object color, boolean bold, boolean italic, boolean underlined, + boolean strikethrough, boolean obfuscated, @Nullable String font) { if (BukkitReflection.is1_21_4Plus()) { return newChatModifier.newInstance( color, 0, - modifier.isBold(), - modifier.isItalic(), - modifier.isUnderlined(), - modifier.isStrikethrough(), - modifier.isObfuscated(), + bold, + italic, + underlined, + strikethrough, + obfuscated, null, null, null, - modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont()) + font == null ? null : ResourceLocation_tryParse.invoke(null, font) ); } else { return newChatModifier.newInstance( color, - modifier.isBold(), - modifier.isItalic(), - modifier.isUnderlined(), - modifier.isStrikethrough(), - modifier.isObfuscated(), + bold, + italic, + underlined, + strikethrough, + obfuscated, null, null, null, - modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont()) + font == null ? null : ResourceLocation_tryParse.invoke(null, font) ); } } - - @SneakyThrows - private Object createModifierLegacy(@NotNull ChatModifier modifier) { - Object nmsModifier = newChatModifier.newInstance(); - if (modifier.getColor() != null) { - ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name())); - } - if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true); - if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true); - if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true); - if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true); - if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true); - return nmsModifier; - } } diff --git a/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/platform/BukkitPlatform.java b/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/platform/BukkitPlatform.java index db668561e..0fad971a2 100644 --- a/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/platform/BukkitPlatform.java +++ b/bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/platform/BukkitPlatform.java @@ -20,10 +20,7 @@ import me.neznamy.tab.shared.TAB; import me.neznamy.tab.shared.TabConstants; import me.neznamy.tab.shared.backend.BackendPlatform; -import me.neznamy.tab.shared.chat.EnumChatFormat; -import me.neznamy.tab.shared.chat.SimpleComponent; -import me.neznamy.tab.shared.chat.StructuredComponent; -import me.neznamy.tab.shared.chat.TabComponent; +import me.neznamy.tab.shared.chat.*; import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration; import me.neznamy.tab.shared.features.PlaceholderManagerImpl; import me.neznamy.tab.shared.features.injection.PipelineInjector; @@ -41,6 +38,7 @@ import me.neznamy.tab.shared.util.PerformanceUtil; import me.neznamy.tab.shared.util.ReflectionUtils; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.milkbowl.vault.chat.Chat; import net.milkbowl.vault.permission.Permission; import org.bstats.bukkit.Metrics; @@ -360,25 +358,30 @@ public void runSync(@NotNull Entity entity, @NotNull Runnable task) { */ @NotNull public String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) { - if (component instanceof SimpleComponent) return component.toLegacyText(); - StructuredComponent iComponent = (StructuredComponent) component; - StringBuilder sb = new StringBuilder(); - if (iComponent.getModifier().getColor() != null) { - if (serverVersion.supportsRGB() && rgbClient) { - String hexCode = iComponent.getModifier().getColor().getHexCode(); - char c = EnumChatFormat.COLOR_CHAR; - sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1)) - .append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3)) - .append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5)); - } else { - sb.append(iComponent.getModifier().getColor().getLegacyColor()); - } + if (component instanceof SimpleComponent) { + return ((SimpleComponent) component).getText(); } - sb.append(iComponent.getModifier().getMagicCodes()); - sb.append(iComponent.getText()); - for (StructuredComponent extra : iComponent.getExtra()) { - sb.append(toBukkitFormat(extra, rgbClient)); + if (component instanceof StructuredComponent) { + StructuredComponent iComponent = (StructuredComponent) component; + StringBuilder sb = new StringBuilder(); + if (iComponent.getModifier().getColor() != null) { + if (serverVersion.supportsRGB() && rgbClient) { + String hexCode = iComponent.getModifier().getColor().getHexCode(); + char c = EnumChatFormat.COLOR_CHAR; + sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1)) + .append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3)) + .append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5)); + } else { + sb.append(iComponent.getModifier().getColor().getLegacyColor()); + } + } + sb.append(iComponent.getModifier().getMagicCodes()); + sb.append(iComponent.getText()); + for (StructuredComponent extra : iComponent.getExtra()) { + sb.append(toBukkitFormat(extra, rgbClient)); + } + return sb.toString(); } - return sb.toString(); + return LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build().serialize(((AdventureComponent)component).getComponent()); } } \ No newline at end of file diff --git a/bungeecord/src/main/java/me/neznamy/tab/platforms/bungeecord/BungeePlatform.java b/bungeecord/src/main/java/me/neznamy/tab/platforms/bungeecord/BungeePlatform.java index 9f3dc7e47..d85ecf137 100644 --- a/bungeecord/src/main/java/me/neznamy/tab/platforms/bungeecord/BungeePlatform.java +++ b/bungeecord/src/main/java/me/neznamy/tab/platforms/bungeecord/BungeePlatform.java @@ -13,7 +13,6 @@ import me.neznamy.tab.shared.chat.*; import me.neznamy.tab.shared.features.injection.PipelineInjector; import me.neznamy.tab.shared.features.redis.RedisSupport; -import me.neznamy.tab.shared.hook.PremiumVanishHook; import me.neznamy.tab.shared.platform.BossBar; import me.neznamy.tab.shared.platform.Scoreboard; import me.neznamy.tab.shared.platform.TabList; @@ -136,35 +135,41 @@ public void registerChannel() { @Override @NotNull public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) { - if (component instanceof SimpleComponent) return new TextComponent(component.toLegacyText()); - StructuredComponent iComponent = (StructuredComponent) component; - TextComponent textComponent = new TextComponent(iComponent.getText()); - ChatModifier modifier = iComponent.getModifier(); - if (modifier.getColor() != null) { - if (modern) { - textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode())); - } else { - textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name())); - } + if (component instanceof SimpleComponent) { + return new TextComponent(((SimpleComponent) component).getText()); } + if (component instanceof StructuredComponent) { + StructuredComponent iComponent = (StructuredComponent) component; + TextComponent textComponent = new TextComponent(iComponent.getText()); + ChatModifier modifier = iComponent.getModifier(); + if (modifier.getColor() != null) { + if (modern) { + textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode())); + } else { + textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name())); + } + } - if (modifier.isBold()) textComponent.setBold(true); - if (modifier.isItalic()) textComponent.setItalic(true); - if (modifier.isObfuscated()) textComponent.setObfuscated(true); - if (modifier.isStrikethrough()) textComponent.setStrikethrough(true); - if (modifier.isUnderlined()) textComponent.setUnderlined(true); + if (modifier.isBold()) textComponent.setBold(true); + if (modifier.isItalic()) textComponent.setItalic(true); + if (modifier.isObfuscated()) textComponent.setObfuscated(true); + if (modifier.isStrikethrough()) textComponent.setStrikethrough(true); + if (modifier.isUnderlined()) textComponent.setUnderlined(true); - textComponent.setFont(modifier.getFont()); + textComponent.setFont(modifier.getFont()); - if (!iComponent.getExtra().isEmpty()) { - List list = new ArrayList<>(); - for (StructuredComponent extra : iComponent.getExtra()) { - list.add(convertComponent(extra, modern)); + if (!iComponent.getExtra().isEmpty()) { + List list = new ArrayList<>(); + for (StructuredComponent extra : iComponent.getExtra()) { + list.add(convertComponent(extra, modern)); + } + textComponent.setExtra(list); } - textComponent.setExtra(list); - } - return textComponent; + return textComponent; + } + throw new UnsupportedOperationException("Adventure components created using MiniMessage syntax are not supported on BungeeCord. " + + "You can request the implementation if you ran into this error."); } @Override diff --git a/fabric/src/main/java/me/neznamy/tab/platforms/fabric/FabricPlatform.java b/fabric/src/main/java/me/neznamy/tab/platforms/fabric/FabricPlatform.java index a1d704b24..07f866e73 100644 --- a/fabric/src/main/java/me/neznamy/tab/platforms/fabric/FabricPlatform.java +++ b/fabric/src/main/java/me/neznamy/tab/platforms/fabric/FabricPlatform.java @@ -135,16 +135,20 @@ public File getDataFolder() { @Override @NotNull public Component convertComponent(@NotNull TabComponent component, boolean modern) { - if (component instanceof SimpleComponent) return FabricMultiVersion.newTextComponent(((SimpleComponent) component).getText()); - - StructuredComponent component1 = (StructuredComponent) component; - Component nmsComponent = FabricMultiVersion.newTextComponent(component1.getText()); - - FabricMultiVersion.setStyle(nmsComponent, FabricMultiVersion.convertModifier(component1.getModifier(), modern)); - for (StructuredComponent extra : component1.getExtra()) { - FabricMultiVersion.addSibling(nmsComponent, convertComponent(extra, modern)); + if (component instanceof SimpleComponent component1) { + return FabricMultiVersion.newTextComponent(component1.getText()); + } + if (component instanceof StructuredComponent component1) { + Component nmsComponent = FabricMultiVersion.newTextComponent(component1.getText()); + + FabricMultiVersion.setStyle(nmsComponent, FabricMultiVersion.convertModifier(component1.getModifier(), modern)); + for (StructuredComponent extra : component1.getExtra()) { + FabricMultiVersion.addSibling(nmsComponent, convertComponent(extra, modern)); + } + return nmsComponent; } - return nmsComponent; + throw new UnsupportedOperationException("Adventure components created using MiniMessage syntax are not supported on Fabric. " + + "You can request the implementation if you ran into this error."); } @Override diff --git a/shared/src/main/java/me/neznamy/tab/shared/chat/AdventureComponent.java b/shared/src/main/java/me/neznamy/tab/shared/chat/AdventureComponent.java new file mode 100644 index 000000000..9b5cb8ca6 --- /dev/null +++ b/shared/src/main/java/me/neznamy/tab/shared/chat/AdventureComponent.java @@ -0,0 +1,51 @@ +package me.neznamy.tab.shared.chat; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.jetbrains.annotations.NotNull; + +/** + * This class is a wrapper for Adventure component created by MiniMessage. This not only + * speeds up the performance by not having to do unnecessary conversions, but also supports + * translatable components, which TAB components do not support. + */ +@RequiredArgsConstructor +@Getter +public class AdventureComponent extends TabComponent { + + @NotNull + private final Component component; + + @Override + @NotNull + public String toLegacyText() { + return LegacyComponentSerializer.legacySection().serialize(component); + } + + @Override + @NotNull + public String toRawText() { + StringBuilder builder = new StringBuilder(); + if (component instanceof TextComponent) builder.append(((TextComponent) component).content()); + for (Component extra : component.children()) { + if (extra instanceof TextComponent) builder.append(((TextComponent) extra).content()); + } + return builder.toString(); + } + + @Override + @NotNull + protected TextColor fetchLastColor() { + net.kyori.adventure.text.format.TextColor lastColor = component.color(); + for (Component extra : component.children()) { + if (extra.color() != null) { + lastColor = extra.color(); + } + } + if (lastColor == null) return TextColor.legacy(EnumChatFormat.WHITE); + return new TextColor(lastColor.red(), lastColor.green(), lastColor.blue()); + } +} diff --git a/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/RGBUtils.java b/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/RGBUtils.java index 7e9f95a6a..be683ba19 100644 --- a/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/RGBUtils.java +++ b/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/RGBUtils.java @@ -1,23 +1,14 @@ package me.neznamy.tab.shared.chat.rgb; import lombok.Getter; -import me.neznamy.tab.shared.chat.rgb.format.BukkitFormat; -import me.neznamy.tab.shared.chat.rgb.format.HtmlFormat; -import me.neznamy.tab.shared.chat.rgb.format.KyoriFormat; -import me.neznamy.tab.shared.chat.rgb.format.MiniMessageFormat; -import me.neznamy.tab.shared.chat.rgb.format.UnnamedFormat1; +import me.neznamy.tab.shared.chat.rgb.format.*; import me.neznamy.tab.shared.chat.rgb.gradient.CMIGradient; import me.neznamy.tab.shared.chat.rgb.gradient.CommonGradient; import me.neznamy.tab.shared.chat.rgb.gradient.GradientPattern; import me.neznamy.tab.shared.chat.rgb.gradient.NexEngineGradient; -import me.neznamy.tab.shared.util.ReflectionUtils; +import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; import java.util.regex.Pattern; -import me.neznamy.tab.shared.chat.rgb.format.CMIFormat; -import me.neznamy.tab.shared.chat.rgb.format.RGBFormatter; -import org.jetbrains.annotations.NotNull; /** * A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients @@ -37,18 +28,13 @@ public class RGBUtils { * Constructs new instance and loads all RGB patterns and gradients */ public RGBUtils() { - List list = new ArrayList<>(); - if (ReflectionUtils.classExists("net.kyori.adventure.text.minimessage.MiniMessage") && - ReflectionUtils.classExists("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer")) { - list.add(new MiniMessageFormat()); - } - list.add(new BukkitFormat()); - list.add(new CMIFormat()); - list.add(new UnnamedFormat1()); - list.add(new HtmlFormat()); - list.add(new KyoriFormat()); - formats = list.toArray(new RGBFormatter[0]); - + formats = new RGBFormatter[] { + new BukkitFormat(), + new CMIFormat(), + new UnnamedFormat1(), + new HtmlFormat(), + new KyoriFormat() + }; gradients = new GradientPattern[] { //{#RRGGBB>}text{#RRGGBB<} new CMIGradient(), diff --git a/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/format/MiniMessageFormat.java b/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/format/MiniMessageFormat.java deleted file mode 100644 index 7c46c0d01..000000000 --- a/shared/src/main/java/me/neznamy/tab/shared/chat/rgb/format/MiniMessageFormat.java +++ /dev/null @@ -1,32 +0,0 @@ -package me.neznamy.tab.shared.chat.rgb.format; - -import me.neznamy.tab.shared.chat.EnumChatFormat; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.jetbrains.annotations.NotNull; - -/** - * Call to MiniMessage API to reformat text to &x&R&R&G&G&B&B - */ -public class MiniMessageFormat implements RGBFormatter { - - /** Serializer that uses &x format on all platforms, even those that do not have it enabled by default */ - private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder() - .hexColors().useUnusualXRepeatedCharacterHexFormat().build(); - - /** Dummy character to append to disable MiniMessage's color compacting that prevents team color from being detected as last color of prefix */ - private static final char dummyChar = Character.MAX_VALUE; - - @Override - @NotNull - public String reformat(@NotNull String text) { - if (!text.contains("<")) return text; // User did not even attempt to use MiniMessage - if (text.contains(EnumChatFormat.COLOR_STRING)) return text; - try { - String serialized = SERIALIZER.serialize(MiniMessage.miniMessage().deserialize(text + dummyChar)); - return serialized.substring(0, serialized.length()-1); // Remove the dummy char back - } catch (Throwable ignored) { - return text; - } - } -} \ No newline at end of file diff --git a/shared/src/main/java/me/neznamy/tab/shared/hook/AdventureHook.java b/shared/src/main/java/me/neznamy/tab/shared/hook/AdventureHook.java index a182b5569..79a8beef5 100644 --- a/shared/src/main/java/me/neznamy/tab/shared/hook/AdventureHook.java +++ b/shared/src/main/java/me/neznamy/tab/shared/hook/AdventureHook.java @@ -1,9 +1,6 @@ package me.neznamy.tab.shared.hook; -import me.neznamy.tab.shared.chat.ChatModifier; -import me.neznamy.tab.shared.chat.StructuredComponent; -import me.neznamy.tab.shared.chat.SimpleComponent; -import me.neznamy.tab.shared.chat.TabComponent; +import me.neznamy.tab.shared.chat.*; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; @@ -52,6 +49,7 @@ private static EnumSet[] loadDecorations() { */ @NotNull public static Component toAdventureComponent(@NotNull TabComponent component, boolean modern) { + if (component instanceof AdventureComponent) return ((AdventureComponent) component).getComponent(); if (component instanceof SimpleComponent) return Component.text(((SimpleComponent) component).getText()); StructuredComponent iComponent = (StructuredComponent) component; ChatModifier modifier = iComponent.getModifier(); diff --git a/shared/src/main/java/me/neznamy/tab/shared/hook/MiniMessageHook.java b/shared/src/main/java/me/neznamy/tab/shared/hook/MiniMessageHook.java new file mode 100644 index 000000000..92edcb60f --- /dev/null +++ b/shared/src/main/java/me/neznamy/tab/shared/hook/MiniMessageHook.java @@ -0,0 +1,41 @@ +package me.neznamy.tab.shared.hook; + +import me.neznamy.tab.shared.chat.AdventureComponent; +import me.neznamy.tab.shared.chat.EnumChatFormat; +import me.neznamy.tab.shared.chat.TabComponent; +import me.neznamy.tab.shared.util.ReflectionUtils; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Class for hooking into MiniMessage to support its syntax. + */ +public class MiniMessageHook { + + /** Minimessage deserializer with disabled component post-processing */ + @Nullable + private static final MiniMessage mm = ReflectionUtils.classExists("net.kyori.adventure.text.minimessage.MiniMessage") && + ReflectionUtils.classExists("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer") ? + MiniMessage.builder().postProcessor(c->c).build() : null; + + /** + * Attempts to parse the text into an adventure component using MiniMessage syntax. If MiniMessage is + * not available or the text failed to parse for any reason, {@code null} is returned. + * + * @param text + * Text to attempt to parse + * @return Parsed component or {@code null} if unable to parse + */ + @Nullable + public static TabComponent parseText(@NotNull String text) { + if (mm == null) return null; + if (!text.contains("<")) return null; // User did not even attempt to use MiniMessage + if (text.contains(EnumChatFormat.COLOR_STRING)) return null; + try { + return new AdventureComponent(mm.deserialize(text)); + } catch (Throwable ignored) { + return null; + } + } +} diff --git a/shared/src/main/java/me/neznamy/tab/shared/util/cache/StringToComponentCache.java b/shared/src/main/java/me/neznamy/tab/shared/util/cache/StringToComponentCache.java index 563dfb08a..50ecef573 100644 --- a/shared/src/main/java/me/neznamy/tab/shared/util/cache/StringToComponentCache.java +++ b/shared/src/main/java/me/neznamy/tab/shared/util/cache/StringToComponentCache.java @@ -3,6 +3,7 @@ import me.neznamy.tab.shared.chat.EnumChatFormat; import me.neznamy.tab.shared.chat.SimpleComponent; import me.neznamy.tab.shared.chat.TabComponent; +import me.neznamy.tab.shared.hook.MiniMessageHook; /** * Cache for String -> TabComponent conversion. @@ -19,6 +20,8 @@ public class StringToComponentCache extends Cache { */ public StringToComponentCache(String name, int cacheSize) { super(name, cacheSize, text -> { + TabComponent component = MiniMessageHook.parseText(text); + if (component != null) return component; return text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x") || text.contains("<") ? TabComponent.fromColoredText(text) : //contains RGB colors or font new SimpleComponent(text); //no RGB diff --git a/sponge7/src/main/java/me/neznamy/tab/platforms/sponge7/SpongeTabPlayer.java b/sponge7/src/main/java/me/neznamy/tab/platforms/sponge7/SpongeTabPlayer.java index 581fb7951..1ae7fb9ec 100644 --- a/sponge7/src/main/java/me/neznamy/tab/platforms/sponge7/SpongeTabPlayer.java +++ b/sponge7/src/main/java/me/neznamy/tab/platforms/sponge7/SpongeTabPlayer.java @@ -45,7 +45,7 @@ public int getPing() { @Override public void sendMessage(@NotNull TabComponent message) { - getPlayer().sendMessage(Text.of(message.toLegacyText())); + getPlayer().sendMessage((Text) platform.convertComponent(message, false)); } @Override