package fr.pandacube.lib.chat; import java.awt.Color; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.function.UnaryOperator; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.BlockNBTComponent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentBuilder; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.EntityNBTComponent; import net.kyori.adventure.text.KeybindComponent; import net.kyori.adventure.text.ScoreComponent; import net.kyori.adventure.text.SelectorComponent; import net.kyori.adventure.text.StorageNBTComponent; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration.State; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; public abstract sealed class Chat extends ChatStatic implements HoverEventSource, ComponentLike { protected final ComponentBuilder builder; protected boolean console = false; /* package */ Chat(ComponentBuilder b) { builder = Objects.requireNonNull(b, "Provided component builder must not be null"); } public Component getAdv() { return builder.build(); } public BaseComponent get() { return toBungee(getAdv()); } public BaseComponent[] getAsArray() { return toBungeeArray(getAdv()); } private static final LegacyComponentSerializer LEGACY_SERIALIZER_BUNGEE_FIENDLY = LegacyComponentSerializer.builder() .hexColors() .useUnusualXRepeatedCharacterHexFormat() .build(); public String getLegacyText() { return LEGACY_SERIALIZER_BUNGEE_FIENDLY.serialize(getAdv()); } public String getPlainText() { return PlainTextComponentSerializer.plainText().serializeOr(getAdv(), ""); } public Chat then(Component comp) { if (comp instanceof TextComponent txtComp) { if (!txtComp.hasStyling() && (txtComp.content().isEmpty())) { // no need to add the provided component to the current component. // but eventual child component must be added if (!txtComp.children().isEmpty()) { for (Component child : txtComp.children()) then(child); } return this; } } builder.append(comp); return this; } public Chat then(BaseComponent subComponent) { return then(toAdventure(subComponent)); } public Chat then(ComponentLike comp) { return then(comp.asComponent()); } public Chat then(BaseComponent[] components) { return then(toAdventure(components)); } public Chat thenText(Object plainText) { return then(text(plainText)); } public Chat thenInfo(Object plainText) { return then(infoText(plainText)); } public Chat thenWarning(Object plainText) { return then(warningText(plainText)); } public Chat thenSuccess(Object plainText) { return then(successText(plainText)); } public Chat thenFailure(Object plainText) { return then(failureText(plainText)); } public Chat thenData(Object plainText) { return then(dataText(plainText)); } public Chat thenDecoration(Object plainText) { return then(decorationText(plainText)); } public Chat thenPlayerName(String legacyText) { return then(playerNameText(legacyText)); } public Chat thenPlayerName(Component comp) { return then(playerNameComponent(comp)); } public Chat thenNewLine() { return then(Component.newline()); } public Chat thenLegacyText(Object legacyText) { return then(legacyText(legacyText)); } public Chat thenTranslation(String key, Object... with) { return then(translation(key, with)); } public Chat thenKeyBind(String key) { return then(keybind(key)); } public Chat thenScore(String name, String objective) { return then(score(name, objective)); } public Chat thenURLLink(Chat inner, String url, Chat hover) { return then(clickableURL(inner, url, hover)); } public Chat thenURLLink(Chat inner, String url) { return then(clickableURL(inner, url)); } public Chat thenURLLink(String url, Chat hover) { return then(clickableURL(url, hover)); } public Chat thenURLLink(String url) { return then(clickableURL(url)); } public Chat thenCommandLink(Chat inner, String cmdWithSlash, Chat hover) { return then(clickableCommand(inner, cmdWithSlash, hover)); } public Chat thenCommandLink(Chat inner, String cmdWithSlash) { return then(clickableCommand(inner, cmdWithSlash)); } public Chat thenCommandLink(String cmdWithSlash, Chat hover) { return then(clickableCommand(cmdWithSlash, hover)); } public Chat thenCommandLink(String cmdWithSlash) { return then(clickableCommand(cmdWithSlash)); } public Chat thenCommandSuggest(Chat inner, String cmdWithSlash, Chat hover) { return then(clickableSuggest(inner, cmdWithSlash, hover)); } public Chat thenCommandSuggest(Chat inner, String cmdWithSlash) { return then(clickableSuggest(inner, cmdWithSlash)); } public Chat thenCommandSuggest(String cmdWithSlash, Chat hover) { return then(clickableSuggest(cmdWithSlash, hover)); } public Chat thenCommandSuggest(String cmdWithSlash) { return then(clickableSuggest(cmdWithSlash)); } /** * Draws a full line with the default decoration char, colored with the default decoration color. * @return this, for method chaining */ public Chat thenEmptyCharLine() { return then(ChatUtil.emptyLine(config.decorationChar, config.decorationColor, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, * and with the provided Chat left aligned on the line, default to the decoration color, and surrounded with 1 space on each side. * @return this, for method chaining */ public Chat thenLeftTextCharLine(Chat leftText) { return then(ChatUtil.leftText(chat().decorationColor().thenText(" ").then(leftText).thenText(" "), config.decorationChar, config.decorationColor, config.nbCharMargin, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, * and with the provided component left aligned on the line, default to the decoration color, and surrounded with 1 space on each side. * @return this, for method chaining */ public Chat thenLeftTextCharLine(BaseComponent leftText) { return thenLeftTextCharLine(chatComponent(leftText)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, * and with the provided Chat right aligned on the line, default to the decoration color, and surrounded with 1 space on each side. * @return this, for method chaining */ public Chat thenRightTextCharLine(Chat rightText) { return then(ChatUtil.rightText(chat().decorationColor().thenText(" ").then(rightText).thenText(" "), config.decorationChar, config.decorationColor, config.nbCharMargin, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, * and with the provided component right aligned on the line, default to the decoration color, and surrounded with 1 space on each side. * @return this, for method chaining */ public Chat thenRightTextCharLine(BaseComponent leftText) { return thenRightTextCharLine(chatComponent(leftText)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, * and with the provided Chat centered on the line, default to the decoration color, and surrounded with 1 space on each side. * @return this, for method chaining */ public Chat thenCenterTextCharLine(Chat centerText) { return then(ChatUtil.centerText(chat().decorationColor().thenText(" ").then(centerText).thenText(" "), config.decorationChar, config.decorationColor, console)); } /** * Draws a full line with the default decoration char, colored with the default decoration color, * and with the provided component centered on the line, default to the decoration color, and surrounded with 1 space on each side. * @return this, for method chaining */ public Chat thenCenterTextCharLine(BaseComponent leftText) { return thenCenterTextCharLine(chatComponent(leftText)); } public static final class FormatableChat extends Chat { /* package */ FormatableChat(ComponentBuilder c) { super(c); } public FormatableChat console(boolean c) { console = c; return this; } public FormatableChat color(TextColor c) { builder.color(c); return this; } public FormatableChat color(ChatColor c) { return color(c == null ? null : TextColor.color(c.getColor().getRGB())); } public FormatableChat color(Color c) { return color(c == null ? null : TextColor.color(c.getRGB())); } public FormatableChat color(String c) { return color(c == null ? null : ChatColor.of(c)); } public FormatableChat black() { return color(NamedTextColor.BLACK); } public FormatableChat darkBlue() { return color(NamedTextColor.DARK_BLUE); } public FormatableChat darkGreen() { return color(NamedTextColor.DARK_GREEN); } public FormatableChat darkAqua() { return color(NamedTextColor.DARK_AQUA); } public FormatableChat darkRed() { return color(NamedTextColor.DARK_RED); } public FormatableChat darkPurple() { return color(NamedTextColor.DARK_PURPLE); } public FormatableChat gold() { return color(NamedTextColor.GOLD); } public FormatableChat gray() { return color(NamedTextColor.GRAY); } public FormatableChat darkGray() { return color(NamedTextColor.DARK_GRAY); } public FormatableChat blue() { return color(NamedTextColor.BLUE); } public FormatableChat green() { return color(NamedTextColor.GREEN); } public FormatableChat aqua() { return color(NamedTextColor.AQUA); } public FormatableChat red() { return color(NamedTextColor.RED); } public FormatableChat lightPurple() { return color(NamedTextColor.LIGHT_PURPLE); } public FormatableChat yellow() { return color(NamedTextColor.YELLOW); } public FormatableChat white() { return color(NamedTextColor.WHITE); } public FormatableChat successColor() { return color(config.successColor); } public FormatableChat failureColor() { return color(config.failureColor); } public FormatableChat infoColor() { return color(config.infoColor); } public FormatableChat warningColor() { return color(config.warningColor); } public FormatableChat dataColor() { return color(config.dataColor); } public FormatableChat decorationColor() { return color(config.decorationColor); } public FormatableChat urlColor() { return color(config.urlColor); } public FormatableChat commandColor() { return color(config.commandColor); } public FormatableChat highlightedCommandColor() { return color(config.highlightedCommandColor); } public FormatableChat broadcastColor() { return color(config.broadcastColor); } private FormatableChat setStyle(Consumer styleOp) { builder.style(styleOp); return this; } private FormatableChat setDecoration(TextDecoration deco, Boolean state) { return setStyle(b -> b.decoration(deco, State.byBoolean(state))); } public FormatableChat bold(Boolean b) { return setDecoration(TextDecoration.BOLD, b); } public FormatableChat bold() { return bold(true); } public FormatableChat italic(Boolean i) { return setDecoration(TextDecoration.ITALIC, i); } public FormatableChat italic() { return italic(true); } public FormatableChat underlined(Boolean u) { return setDecoration(TextDecoration.UNDERLINED, u); } public FormatableChat underlined() { return underlined(true); } public FormatableChat strikethrough(Boolean s) { return setDecoration(TextDecoration.STRIKETHROUGH, s); } public FormatableChat strikethrough() { return strikethrough(true); } public FormatableChat obfuscated(Boolean o) { return setDecoration(TextDecoration.OBFUSCATED, o); } public FormatableChat obfuscated() { return obfuscated(true); } public FormatableChat font(Key f) { return setStyle(s -> s.font(f)); } public FormatableChat shiftClickInsertion(String i) { builder.insertion(i); return this; } private FormatableChat click(ClickEvent e) { builder.clickEvent(e); return this; } public FormatableChat clickCommand(String cmdWithSlash) { return click(ClickEvent.runCommand(cmdWithSlash)); } public FormatableChat clickSuggest(String cmdWithSlash) { return click(ClickEvent.suggestCommand(cmdWithSlash)); } public FormatableChat clickClipboard(String value) { return click(ClickEvent.copyToClipboard(value)); } public FormatableChat clickURL(String url) { return click(ClickEvent.openUrl(url)); } public FormatableChat clickBookPage(int page) { return click(ClickEvent.changePage(page)); } public FormatableChat hover(HoverEventSource e) { builder.hoverEvent(e); return this; } public FormatableChat hover(Chat v) { return hover(v.getAdv()); } public FormatableChat hover(BaseComponent v) { return hover(toAdventure(v)); } public FormatableChat hover(BaseComponent[] v) { return hover(toAdventure(v)); } public FormatableChat hover(String legacyText) { return hover(legacyText(legacyText)); } } @Override public HoverEvent asHoverEvent(UnaryOperator op) { return HoverEvent.showText(op.apply(getAdv())); } @Override public Component asComponent() { return getAdv(); } @Override public boolean equals(Object obj) { return obj instanceof Chat c && getAdv().equals(c.getAdv()); } @Override public int hashCode() { return getAdv().hashCode(); } @Override public String toString() { return getPlainText(); } /* package */ static ComponentLike[] filterObjToComponentLike(Object[] values) { if (values == null) return null; ComponentLike[] ret = new ComponentLike[values.length]; for (int i = 0; i < values.length; i++) { Object v = values[i]; if (v instanceof BaseComponent[]) ret[i] = toAdventure((BaseComponent[]) v); else if (v instanceof BaseComponent) ret[i] = toAdventure((BaseComponent) v); else if (v instanceof ComponentLike) ret[i] = (ComponentLike) v; else ret[i] = Component.text(Objects.toString(v)); } return ret; } public static Component toAdventure(BaseComponent[] components) { return BungeeComponentSerializer.get().deserialize(components); } public static Component toAdventure(BaseComponent component) { return toAdventure(new BaseComponent[] { component }); } public static BaseComponent[] toBungeeArray(Component component) { return BungeeComponentSerializer.get().serialize(component); } public static BaseComponent toBungee(Component component) { BaseComponent[] arr = toBungeeArray(component); return arr.length == 1 ? arr[0] : new net.md_5.bungee.api.chat.TextComponent(arr); } public static ComponentBuilder componentToBuilder(Component c) { ComponentBuilder builder; if (c instanceof TextComponent) { builder = Component.text() .content(((TextComponent) c).content()); } else if (c instanceof TranslatableComponent) { builder = Component.translatable() .key(((TranslatableComponent) c).key()) .args(((TranslatableComponent) c).args()); } else if (c instanceof SelectorComponent) { builder = Component.selector() .pattern(((SelectorComponent) c).pattern()); } else if (c instanceof ScoreComponent) { builder = Component.score() .name(((ScoreComponent) c).name()) .objective(((ScoreComponent) c).objective()); } else if (c instanceof KeybindComponent) { builder = Component.keybind() .keybind(((KeybindComponent) c).keybind()); } else if (c instanceof BlockNBTComponent) { builder = Component.blockNBT() .interpret(((BlockNBTComponent) c).interpret()) .nbtPath(((BlockNBTComponent) c).nbtPath()) .pos(((BlockNBTComponent) c).pos()); } else if (c instanceof EntityNBTComponent) { builder = Component.entityNBT() .interpret(((EntityNBTComponent) c).interpret()) .nbtPath(((EntityNBTComponent) c).nbtPath()) .selector(((EntityNBTComponent) c).selector()); } else if (c instanceof StorageNBTComponent) { builder = Component.storageNBT() .interpret(((StorageNBTComponent) c).interpret()) .nbtPath(((StorageNBTComponent) c).nbtPath()) .storage(((StorageNBTComponent) c).storage()); } else { throw new IllegalArgumentException("Unknows component type " + c.getClass()); } return builder.style(c.style()).append(c.children()); } public static Chat italicFalseIfNotSet(Chat c) { c.builder.style(b -> { if (b.build().decoration(TextDecoration.ITALIC) == State.NOT_SET) { ((FormatableChat) c).italic(false); } }); return c; } protected static final Config config = new Config(); public static Config getConfig() { return config; } public static class Config { public TextColor decorationColor = NamedTextColor.YELLOW; public char decorationChar = '-'; public int nbCharMargin = 1; public TextColor successColor = NamedTextColor.GREEN; public TextColor failureColor = NamedTextColor.RED; public TextColor infoColor = NamedTextColor.GOLD; public TextColor warningColor = NamedTextColor.GOLD; public TextColor dataColor = NamedTextColor.GRAY; public TextColor urlColor = NamedTextColor.GREEN; public TextColor commandColor = NamedTextColor.GRAY; public TextColor highlightedCommandColor = NamedTextColor.WHITE; public TextColor broadcastColor = NamedTextColor.YELLOW; public Supplier prefix; public int getPrefixWidth(boolean console) { Chat c = prefix == null ? null : prefix.get(); return c == null ? 0 : ChatUtil.componentWidth(c.getAdv(), console); } } }