diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java
index 568db4c..623a266 100644
--- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/Chat.java
@@ -3,21 +3,13 @@ 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;
@@ -32,424 +24,981 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
+/**
+ * A builder for chat components.
+ *
+ * Use one of the provided static methods to create a new instance.
+ *
+ * This class implements {@link ComponentLike} and {@link HoverEventSource} so they can be used directly in
+ * Adventure API and its implentation without using the final methods of this builder.
+ *
+ * The unique possible concrete subclass of this class, {@link FormatableChat}, takes care of the formating of the
+ * builded component. The rationale for this design is explained in the documentation of {@link FormatableChat}.
+ */
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; }
+ /* package */ final ComponentBuilder, ?> builder;
+ /* package */ boolean console = false;
+ /* package */ Integer maxWidth = null;
- 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 */ Chat(ComponentBuilder, ?> b) {
+ builder = Objects.requireNonNull(b, "Provided component builder must not be null");
+ }
- /* 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);
- }
- }
-
-
-
+
+
+
+
+
+
+ /*
+ * Builder terminal operation and serialization
+ */
+
+
+ /**
+ * Builds the component into Adventure Component instance.
+ * @return the {@link Component} builded from this {@link Chat} component.
+ */
+ public Component getAdv() {
+ return builder.build();
+ }
+
+ /**
+ * Builds the component into BungeeCord {@link BaseComponent} instance.
+ * @return the {@link BaseComponent} builded from this {@link Chat} component.
+ */
+ public BaseComponent get() {
+ return toBungee(getAdv());
+ }
+
+ /**
+ * Builds the component into BungeeCord {@link BaseComponent} array.
+ * @return the {@link BaseComponent} array builded from this {@link Chat} component.
+ */
+ public BaseComponent[] getAsArray() {
+ return toBungeeArray(getAdv());
+ }
+
+ private static final LegacyComponentSerializer LEGACY_SERIALIZER_BUNGEE_FIENDLY = LegacyComponentSerializer.builder()
+ .hexColors()
+ .useUnusualXRepeatedCharacterHexFormat()
+ .build();
+
+ /**
+ * Converts the builded component into legacy text.
+ * @return the legacy text. RGB colors are in BungeeCord format.
+ */
+ public String getLegacyText() {
+ return LEGACY_SERIALIZER_BUNGEE_FIENDLY.serialize(getAdv());
+ }
+
+ /**
+ * Converts the builded component into plain text.
+ * @return the plain text of this component.
+ */
+ public String getPlainText() {
+ return PlainTextComponentSerializer.plainText().serializeOr(getAdv(), "");
+ }
+
+ @Override
+ public HoverEvent asHoverEvent(UnaryOperator op) {
+ return HoverEvent.showText(op.apply(getAdv()));
+ }
+
+ /**
+ * Builds the component into Adventure Component instance.
+ * @return the {@link Component} builded from this {@link Chat} component.
+ */
+ @Override
+ public Component asComponent() {
+ return getAdv();
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ /*
+ * Sub-component appending
+ */
+
+
+ /**
+ * Appends a component to this component.
+ * @param comp the component to append.
+ * @return this.
+ */
+ 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;
+ }
+
+ /**
+ * Appends a BungeeCord {@link BaseComponent} to this component.
+ * @param comp the component to append.
+ * @return this.
+ */
+ public Chat then(BaseComponent comp) {
+ return then(toAdventure(comp));
+ }
+
+ /**
+ * Appends a component to this component.
+ * @param comp the component to append.
+ * @return this.
+ */
+ public Chat then(ComponentLike comp) {
+ if (comp instanceof ChatFilledLine ac) {
+ ac.console(console);
+ if (maxWidth != null)
+ ac.maxWidth(maxWidth);
+ }
+ return then(comp.asComponent());
+ }
+
+ /**
+ * Appends a BungeeCord {@link BaseComponent} array to this component.
+ * @param comp the components to append.
+ * @return this.
+ */
+ public Chat then(BaseComponent[] comp) {
+ return then(toAdventure(comp));
+ }
+
+
+
+
+
+
+
+
+ /*
+ * Special sub-components appending
+ */
+
+ /**
+ * Appends a plain text to this component.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenText(Object plainText) { return then(text(plainText)); }
+
+ /**
+ * Appends a plain text to this component, colored using {@link ChatConfig#infoColor}.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenInfo(Object plainText) { return then(infoText(plainText)); }
+
+ /**
+ * Appends a plain text to this component, colored using {@link ChatConfig#warningColor}.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenWarning(Object plainText) { return then(warningText(plainText)); }
+
+ /**
+ * Appends a plain text to this component, colored using {@link ChatConfig#successColor}.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenSuccess(Object plainText) { return then(successText(plainText)); }
+
+ /**
+ * Appends a plain text to this component, colored using {@link ChatConfig#failureColor}.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenFailure(Object plainText) { return then(failureText(plainText)); }
+
+ /**
+ * Appends a plain text to this component, colored using {@link ChatConfig#dataColor}.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenData(Object plainText) { return then(dataText(plainText)); }
+
+ /**
+ * Appends a plain text to this component, colored using {@link ChatConfig#decorationColor}.
+ * @param plainText the plain text.
+ * @return this.
+ */
+ public Chat thenDecoration(Object plainText) { return then(decorationText(plainText)); }
+
+ /**
+ * Appends a component with the provided legacy text as its main text content, and colored in white in case there is
+ * no color on the generated parent component.
+ * @param legacyText the legacy text.
+ * @return this.
+ */
+ public Chat thenPlayerName(String legacyText) { return then(playerNameText(legacyText)); }
+
+ /**
+ * Appends the provided Component, coloring it in white in case there is no color defined. If the provided component
+ * is an instance of Chat, its content will be duplicated, and the provided one will be untouched.
+ * @param comp the component.
+ * @return this.
+ */
+ public Chat thenPlayerName(Component comp) { return then(playerNameComponent(comp)); }
+
+ /**
+ * Appends a component consisting of a new line.
+ * @return this.
+ */
+ public Chat thenNewLine() { return then(Component.newline()); }
+
+ /**
+ * Appends a component with the provided legacy text as its content.
+ * @param legacyText the legacy text.
+ * @return this.
+ */
+ public Chat thenLegacyText(Object legacyText) { return then(legacyText(legacyText)); }
+
+ /**
+ * Appends a component with the provided translation key and parameters.
+ * @param key the translation key.
+ * @param with the translation parameters.
+ * @return this.
+ */
+ public Chat thenTranslation(String key, Object... with) { return then(translation(key, with)); }
+
+ /**
+ * Appends a component with the provided keybind.
+ * @param key the keybind to display.
+ * @return this.
+ */
+ public Chat thenKeyBind(String key) { return then(keybind(key)); }
+
+ /**
+ * Appends a component with the provided score name and objective.
+ * @param name the score name.
+ * @param objective the score objective.
+ * @return this.
+ */
+ public Chat thenScore(String name, String objective) { return then(score(name, objective)); }
+
+
+ /**
+ * Appends a component that leads to a URL when clicked.
+ * @param inner the component to make clickable.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @param hover the content to display when hovering the component.
+ * @return this.
+ */
+ public Chat thenClickableURL(ComponentLike inner, String url, HoverEventSource> hover) { return then(clickableURL(inner, url, hover)); }
+
+ /**
+ * Appends a component that leads to a URL when clicked.
+ *
+ * When hovered, the component will display the url. To customize the hover content, use
+ * {@link #thenClickableURL(ComponentLike, String, HoverEventSource)}.
+ * @param inner the component to make clickable.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @return this.
+ */
+ public Chat thenClickableURL(ComponentLike inner, String url) { return then(clickableURL(inner, url)); }
+
+ /**
+ * Appends a component that leads to a URL when clicked.
+ *
+ * The text on which to click will be the URL itself. To configure the clicked text, use
+ * {@link #thenClickableURL(ComponentLike, String, HoverEventSource)}.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @param hover the content to display when hovering the component.
+ * @return this.
+ */
+ public Chat thenClickableURL(String url, HoverEventSource> hover) { return then(clickableURL(url, hover)); }
+
+ /**
+ * Appends a component that leads to a URL when clicked.
+ *
+ * The text on which to click will be the URL itself. To configure the clicked text, use
+ * {@link #thenClickableURL(ComponentLike, String)}.
+ *
+ * When hovered, the component will display the url. To customize the hover content, use
+ * {@link #thenClickableURL(String, HoverEventSource)}.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @return this.
+ */
+ public Chat thenClickableURL(String url) { return then(clickableURL(url)); }
+
+
+ /**
+ * Appends a component that runs a command when clicked.
+ * @param inner the component to make clickable.
+ * @param cmdWithSlash the command to run. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenClickableCommand(ComponentLike inner, String cmdWithSlash, HoverEventSource> hover) { return then(clickableCommand(inner, cmdWithSlash, hover)); }
+
+ /**
+ * Appends a component that runs a command when clicked.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #thenClickableCommand(ComponentLike, String, HoverEventSource)}.
+ * @param inner the component to make clickable.
+ * @param cmdWithSlash the command to run. Must start with {@code "/"}.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenClickableCommand(ComponentLike inner, String cmdWithSlash) { return then(clickableCommand(inner, cmdWithSlash)); }
+
+ /**
+ * Appends a component that runs a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #thenClickableCommand(ComponentLike, String, HoverEventSource)}.
+ * @param cmdWithSlash the command to run. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenClickableCommand(String cmdWithSlash, HoverEventSource> hover) { return then(clickableCommand(cmdWithSlash, hover)); }
+
+ /**
+ * Appends a component that runs a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #thenClickableCommand(ComponentLike, String)}.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #thenClickableCommand(String, HoverEventSource)}.
+ * @param cmdWithSlash the command to run. Must start with {@code "/"}.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenClickableCommand(String cmdWithSlash) { return then(clickableCommand(cmdWithSlash)); }
+
+
+ /**
+ * Appends a component that pre-fill the chat box with a command when clicked.
+ * @param inner the component to make clickable.
+ * @param cmdWithSlash the command to suggest. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenCommandSuggest(ComponentLike inner, String cmdWithSlash, HoverEventSource> hover) { return then(clickableSuggest(inner, cmdWithSlash, hover)); }
+
+ /**
+ * Appends a component that pre-fill the chat box with a command when clicked.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #thenCommandSuggest(ComponentLike, String, HoverEventSource)}.
+ * @param inner the component to make clickable.
+ * @param cmdWithSlash the command to suggest. Must start with {@code "/"}.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenCommandSuggest(ComponentLike inner, String cmdWithSlash) { return then(clickableSuggest(inner, cmdWithSlash)); }
+
+ /**
+ * Appends a component that pre-fill the chat box with a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #thenCommandSuggest(ComponentLike, String, HoverEventSource)}.
+ * @param cmdWithSlash the command to suggest. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenCommandSuggest(String cmdWithSlash, HoverEventSource> hover) { return then(clickableSuggest(cmdWithSlash, hover)); }
+
+ /**
+ * Appends a component that pre-fill the chat box with a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #thenCommandSuggest(ComponentLike, String)}.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #thenCommandSuggest(String, HoverEventSource)}.
+ * @param cmdWithSlash the command to suggest. Must start with {@code "/"}.
+ * @return this.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public Chat thenCommandSuggest(String cmdWithSlash) { return then(clickableSuggest(cmdWithSlash)); }
+
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and
+ * color and a left-aligned text.
+ * @param leftText the text aligned to the left.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a left-aligned text.
+ */
+ public Chat thenLeftText(ComponentLike leftText) { return then(leftText(leftText, console)); }
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and
+ * color and a left-aligned text.
+ * @param leftText the text aligned to the left.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a left-aligned text.
+ * @deprecated uses Bungeecord chat API.
+ */
+ @Deprecated
+ public Chat thenLeftText(BaseComponent leftText) { return thenLeftText(chatComponent(leftText)); }
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and
+ * color and a right-aligned text.
+ * @param rightText the text aligned to the right.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a right-aligned text.
+ */
+ public Chat thenRightText(ComponentLike rightText) { return then(rightText(rightText, console)); }
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and
+ * color and a right-aligned text.
+ * @param rightText the text aligned to the right.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a right-aligned text.
+ * @deprecated uses Bungeecord chat API.
+ */
+ @Deprecated
+ public Chat thenRightText(BaseComponent rightText) { return thenRightText(chatComponent(rightText)); }
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and
+ * color and a centered text.
+ * @param centerText the text aligned to the center.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a centered text.
+ */
+ public Chat thenCenterText(ComponentLike centerText) {
+ return then(centerText(centerText, console));
+ }
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and
+ * color and a centered text.
+ * @param centerText the text aligned to the center.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a centered text.
+ * @deprecated uses Bungeecord chat API.
+ */
+ @Deprecated
+ public Chat thenCenterText(BaseComponent centerText) {
+ return thenCenterText(chatComponent(centerText));
+ }
+
+ /**
+ * Appends a component filling a line of chat (or console) with the configured decoration character and color.
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with a decoration character and color.
+ */
+ public Chat thenFilledLine() { return then(filledLine(console)); }
+
+
+
+
+
+
+
+
+
+
+
+ /**
+ * A {@link Chat} that can be formatted.
+ *
+ * The purpose of subclassing {@link Chat} is to avoid ambiguity with the way the Bungee chat component builder works.
+ * Here is an example of to use their builder (from
+ * the Spigot wiki):
+ *
{@code
+ * BaseComponent[] component = new ComponentBuilder("Hello ").color(ChatColor.RED)
+ * .append("world").color(ChatColor.DARK_RED).bold(true)
+ * .append("!").color(ChatColor.RED)
+ * .create();
+ * }
+ * Here, when you call a formating method (like {@code bold(boolean)} or {@code color(ChatColor)}) after the
+ * {@code append(String)} method, the formating apply to the last sub-component appended.
+ *
+ * In our design, we want the formating to apply to the currently builded component, not the last appended one.
+ * The purpose is to make the component structure clearer and have better control of the formating over the
+ * component hierarchy.
+ * Here is the equivalent of the above code, with the {@link Chat} API:
+ *
{@code
+ * Chat component = Chat.text("Hello ").red()
+ * .then(Chat.text("world").darkRed().bold())
+ * .thenText("!"); // short for .then(Chat.text("!"))
+ * // the red color for "!" is not needed because the parent component is already red.
+ * }
+ * When calling {@link #then(Component) #then(...)} on a {@link FormatableChat}, the method returns itself, casted
+ * to {@link Chat}, to prevent future formating (that the programmer would think it formats the previously appended
+ * sub-component). If the formatting of the currently builded component is needed, since {@link Chat} is a sealed
+ * class which only subclass is {@link FormatableChat}, you can cast the builder, and use the format methods again.
+ * {@code
+ * Chat component = Chat.text("Hello ").red()
+ * .then(Chat.text("world").darkRed().bold())
+ * .thenText("!");
+ * // ok now I want to underline everything:
+ * ((FormatableChat)component).underlined(); // this will not format only the last appended text.
+ * }
+ */
+ public static final class FormatableChat extends Chat {
+ /* package */ FormatableChat(ComponentBuilder, ?> c) {
+ super(c);
+ }
+
+
+ /**
+ * Configure if this component will be rendered on console or not.
+ * @param c true for console, false for game UI.
+ * @return this.
+ */
+ public FormatableChat console(boolean c) { console = c; return this; }
+ /**
+ * Configure the width of the line.
+ * @param w the width to consider when rendering the line. In pixel for game UI rendering, n character for
+ * console rendering.
+ * @return this.
+ */
+ public FormatableChat maxWidth(int w) { maxWidth = w; return this; }
+
+ /**
+ * Sets the color of this component.
+ * @param c the color.
+ * @return this.
+ */
+ public FormatableChat color(TextColor c) { builder.color(c); return this; }
+ /**
+ * Sets the color of this component.
+ * @param c the color.
+ * @return this.
+ */
+ public FormatableChat color(ChatColor c) { return color(c == null ? null : TextColor.color(c.getColor().getRGB())); }
+ /**
+ * Sets the color of this component.
+ * @param c the color.
+ * @return this.
+ */
+ public FormatableChat color(Color c) { return color(c == null ? null : TextColor.color(c.getRGB())); }
+ /**
+ * Sets the color of this component.
+ * @param c the color.
+ * @return this.
+ */
+ public FormatableChat color(String c) { return color(c == null ? null : ChatColor.of(c)); }
+
+
+ /**
+ * Sets the color of this component to {@link NamedTextColor#BLACK}.
+ * @return this.
+ */
+ public FormatableChat black() { return color(NamedTextColor.BLACK); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#DARK_BLUE}.
+ * @return this.
+ */
+ public FormatableChat darkBlue() { return color(NamedTextColor.DARK_BLUE); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#DARK_GREEN}.
+ * @return this.
+ */
+ public FormatableChat darkGreen() { return color(NamedTextColor.DARK_GREEN); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#DARK_AQUA}.
+ * @return this.
+ */
+ public FormatableChat darkAqua() { return color(NamedTextColor.DARK_AQUA); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#DARK_RED}.
+ * @return this.
+ */
+ public FormatableChat darkRed() { return color(NamedTextColor.DARK_RED); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#DARK_PURPLE}.
+ * @return this.
+ */
+ public FormatableChat darkPurple() { return color(NamedTextColor.DARK_PURPLE); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#GOLD}.
+ * @return this.
+ */
+ public FormatableChat gold() { return color(NamedTextColor.GOLD); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#GRAY}.
+ * @return this.
+ */
+ public FormatableChat gray() { return color(NamedTextColor.GRAY); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#DARK_GRAY}.
+ * @return this.
+ */
+ public FormatableChat darkGray() { return color(NamedTextColor.DARK_GRAY); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#BLUE}.
+ * @return this.
+ */
+ public FormatableChat blue() { return color(NamedTextColor.BLUE); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#GREEN}.
+ * @return this.
+ */
+ public FormatableChat green() { return color(NamedTextColor.GREEN); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#AQUA}.
+ * @return this.
+ */
+ public FormatableChat aqua() { return color(NamedTextColor.AQUA); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#RED}.
+ * @return this.
+ */
+ public FormatableChat red() { return color(NamedTextColor.RED); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#LIGHT_PURPLE}.
+ * @return this.
+ */
+ public FormatableChat lightPurple() { return color(NamedTextColor.LIGHT_PURPLE); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#YELLOW}.
+ * @return this.
+ */
+ public FormatableChat yellow() { return color(NamedTextColor.YELLOW); }
+ /**
+ * Sets the color of this component to {@link NamedTextColor#WHITE}.
+ * @return this.
+ */
+ public FormatableChat white() { return color(NamedTextColor.WHITE); }
+
+
+ /**
+ * Sets the color of this component to {@link ChatConfig#successColor}.
+ * @return this.
+ */
+ public FormatableChat successColor() { return color(ChatConfig.successColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#failureColor}.
+ * @return this.
+ */
+ public FormatableChat failureColor() { return color(ChatConfig.failureColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#infoColor}.
+ * @return this.
+ */
+ public FormatableChat infoColor() { return color(ChatConfig.infoColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#warningColor}.
+ * @return this.
+ */
+ public FormatableChat warningColor() { return color(ChatConfig.warningColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#dataColor}.
+ * @return this.
+ */
+ public FormatableChat dataColor() { return color(ChatConfig.dataColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#decorationColor}.
+ * @return this.
+ */
+ public FormatableChat decorationColor() { return color(ChatConfig.decorationColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#urlColor}.
+ * @return this.
+ */
+ public FormatableChat urlColor() { return color(ChatConfig.urlColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#commandColor}.
+ * @return this.
+ */
+ public FormatableChat commandColor() { return color(ChatConfig.commandColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#highlightedCommandColor}.
+ * @return this.
+ */
+ public FormatableChat highlightedCommandColor() { return color(ChatConfig.highlightedCommandColor); }
+ /**
+ * Sets the color of this component to {@link ChatConfig#broadcastColor}.
+ * @return this.
+ */
+ public FormatableChat broadcastColor() { return color(ChatConfig.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)));
+ }
+
+
+ /**
+ * Sets the bold status of this component.
+ * @param b true to enable, false to disable, or null to inherit from parent.
+ * @return this.
+ */
+ public FormatableChat bold(Boolean b) { return setDecoration(TextDecoration.BOLD, b); }
+ /**
+ * Enables the bold status of this component.
+ * @return this.
+ */
+ public FormatableChat bold() { return bold(true); }
+ /**
+ * Sets the italic status of this component.
+ * @param i true to enable, false to disable, or null to inherit from parent.
+ * @return this.
+ */
+ public FormatableChat italic(Boolean i) { return setDecoration(TextDecoration.ITALIC, i); }
+ /**
+ * Enables the italic status of this component.
+ * @return this.
+ */
+ public FormatableChat italic() { return italic(true); }
+ /**
+ * Sets the underlined status of this component.
+ * @param u true to enable, false to disable, or null to inherit from parent.
+ * @return this.
+ */
+ public FormatableChat underlined(Boolean u) { return setDecoration(TextDecoration.UNDERLINED, u); }
+ /**
+ * Enables the underlined status of this component.
+ * @return this.
+ */
+ public FormatableChat underlined() { return underlined(true); }
+ /**
+ * Sets the strikethrough status of this component.
+ * @param s true to enable, false to disable, or null to inherit from parent.
+ * @return this.
+ */
+ public FormatableChat strikethrough(Boolean s) { return setDecoration(TextDecoration.STRIKETHROUGH, s); }
+ /**
+ * Enables the strikethrough status of this component.
+ * @return this.
+ */
+ public FormatableChat strikethrough() { return strikethrough(true); }
+ /**
+ * Sets the obfuscated status of this component.
+ * @param o true to enable, false to disable, or null to inherit from parent.
+ * @return this.
+ */
+ public FormatableChat obfuscated(Boolean o) { return setDecoration(TextDecoration.OBFUSCATED, o); }
+ /**
+ * Enables the obfuscated status of this component.
+ * @return this.
+ */
+ public FormatableChat obfuscated() { return obfuscated(true); }
+
+
+ /**
+ * Sets the font of this component.
+ * @param f the font namespaced key.
+ * @return this.
+ */
+ public FormatableChat font(Key f) { return setStyle(s -> s.font(f)); }
+
+
+ /**
+ * Configure this component to insert the specified text at the cursor position when clicked.
+ * @param i the text to insert.
+ * @return this.
+ */
+ public FormatableChat shiftClickInsertion(String i) { builder.insertion(i); return this; }
+
+
+ /**
+ * Configure this component’s click event.
+ * @param e the {@link ClickEvent}.
+ * @return this.
+ */
+ private FormatableChat click(ClickEvent e) { builder.clickEvent(e); return this; }
+ /**
+ * Configure this component to execute the specified command when clicked.
+ * @param cmdWithSlash the command to execute.
+ * @return this.
+ */
+ public FormatableChat clickCommand(String cmdWithSlash) { return click(ClickEvent.runCommand(cmdWithSlash)); }
+ /**
+ * Configure this component to insert in the chat-box the specified command when clicked.
+ * @param cmdWithSlash the command to suggest.
+ * @return this.
+ */
+ public FormatableChat clickSuggest(String cmdWithSlash) { return click(ClickEvent.suggestCommand(cmdWithSlash)); }
+ /**
+ * Configure this component to copy into clipboard the specified text when clicked.
+ * @param value the text to copy.
+ * @return this.
+ */
+ public FormatableChat clickClipboard(String value) { return click(ClickEvent.copyToClipboard(value)); }
+ /**
+ * Configure this component to open the specified URL when clicked.
+ * @param url the URL to open.
+ * @return this.
+ */
+ public FormatableChat clickURL(String url) { return click(ClickEvent.openUrl(url)); }
+ /**
+ * Configure this component to change the page of the opened book when clicked.
+ * @param page the page to go to.
+ * @return this.
+ */
+ public FormatableChat clickBookPage(int page) { return click(ClickEvent.changePage(page)); }
+
+
+ /**
+ * Configure this component’s hover event.
+ * @param e the {@link HoverEventSource}.
+ * @return this.
+ */
+ public FormatableChat hover(HoverEventSource> e) { builder.hoverEvent(e); return this; }
+ /**
+ * Configure this component to show the provided component when hovered.
+ * @param v the component to show.
+ * @return this.
+ */
+ public FormatableChat hover(Component v) { return hover((HoverEventSource) v); }
+ /**
+ * Configure this component to show the provided component when hovered.
+ * @param v the component to show.
+ * @return this.
+ */
+ public FormatableChat hover(Chat v) { return hover((HoverEventSource) v); }
+ /**
+ * Configure this component to show the provided component when hovered.
+ * @param v the component to show.
+ * @return this.
+ */
+ public FormatableChat hover(ComponentLike v) { return hover(v.asComponent()); }
+ /**
+ * Configure this component to show the provided component when hovered.
+ * @param v the component to show.
+ * @return this.
+ */
+ public FormatableChat hover(BaseComponent v) { return hover(toAdventure(v)); }
+ /**
+ * Configure this component to show the provided component when hovered.
+ * @param v the component to show.
+ * @return this.
+ */
+ public FormatableChat hover(BaseComponent[] v) { return hover(toAdventure(v)); }
+ /**
+ * Configure this component to show the provided legacy text when hovered.
+ * @param legacyText the legacy text to show.
+ * @return this.
+ */
+ public FormatableChat hover(String legacyText) { return hover(legacyText(legacyText)); }
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Chat c
+ && builder.equals(c.builder);
+ }
+
+ @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;
+ }
+
+
+ /**
+ * Converts the Bungee {@link BaseComponent} array into Adventure {@link Component}.
+ * @param components the Bungee {@link BaseComponent} array.
+ * @return a {@link Component}.
+ */
+ public static Component toAdventure(BaseComponent[] components) {
+ return BungeeComponentSerializer.get().deserialize(components);
+ }
+ /**
+ * Converts the Bungee {@link BaseComponent} into Adventure {@link Component}.
+ * @param component the Bungee {@link BaseComponent}.
+ * @return a {@link Component}.
+ */
+ public static Component toAdventure(BaseComponent component) {
+ return toAdventure(new BaseComponent[] { component });
+ }
+
+ /**
+ * Converts the Adventure {@link Component} into Bungee {@link BaseComponent} array.
+ * @param component the Adventure {@link Component}.
+ * @return a {@link BaseComponent} array.
+ */
+ public static BaseComponent[] toBungeeArray(Component component) {
+ return BungeeComponentSerializer.get().serialize(component);
+ }
+ /**
+ * Converts the Adventure {@link Component} into Bungee {@link BaseComponent}.
+ * @param component the Adventure {@link Component}.
+ * @return a {@link BaseComponent}.
+ */
+ 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);
+ }
+
+ /**
+ * Force the italic formating to be set to false if it is not explicitely set in the component.
+ * This is useful for item lores that defaults to italic in the game UI.
+ * @param c the {@link Chat} in which to set the italic property if needed.
+ * @return the provided {@link Chat} instance.
+ */
+ 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;
+ }
+
+
}
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorGradient.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorGradient.java
new file mode 100644
index 0000000..933f74c
--- /dev/null
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorGradient.java
@@ -0,0 +1,55 @@
+package fr.pandacube.lib.chat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.kyori.adventure.text.format.TextColor;
+
+/**
+ * A custom gradient with a least 2 colors in it.
+ */
+public class ChatColorGradient {
+ private record GradientColor(float location, TextColor color) { }
+
+ private final List colors = new ArrayList<>();
+
+ /**
+ * Put a specific color at a specific location in the gradient.
+ * @param gradientLocation the location in the gradient.
+ * @param gradientColor the color to put at this location.
+ * @return this.
+ */
+ public synchronized ChatColorGradient add(float gradientLocation, TextColor gradientColor) {
+ colors.add(new GradientColor(gradientLocation, gradientColor));
+ return this;
+ }
+
+ /**
+ * Compute a color by interpolating between the 2 colors surrounding the provided location.
+ * @param gradientLocation the location at which to pick the gradient color.
+ * @return the computed color.
+ */
+ public synchronized TextColor pickColorAt(float gradientLocation) {
+ if (colors.isEmpty())
+ throw new IllegalStateException("Must define at least one color in this ChatValueGradient instance.");
+ if (colors.size() == 1)
+ return colors.get(0).color();
+
+ colors.sort((p1, p2) -> Float.compare(p1.location(), p2.location()));
+
+ if (gradientLocation <= colors.get(0).location())
+ return colors.get(0).color();
+ if (gradientLocation >= colors.get(colors.size() - 1).location())
+ return colors.get(colors.size() - 1).color();
+
+ int p1 = 1;
+ for (; p1 < colors.size(); p1++) {
+ if (colors.get(p1).location() >= gradientLocation)
+ break;
+ }
+ int p0 = p1 - 1;
+ float v0 = colors.get(p0).location(), v1 = colors.get(p1).location();
+ TextColor cc0 = colors.get(p0).color(), cc1 = colors.get(p1).color();
+ return ChatColorUtil.interpolateColor(v0, v1, gradientLocation, cc0, cc1);
+ }
+}
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorUtil.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorUtil.java
index ab75e2e..3d00a9f 100644
--- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorUtil.java
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatColorUtil.java
@@ -1,61 +1,68 @@
package fr.pandacube.lib.chat;
-import java.util.ArrayList;
-import java.util.List;
import java.util.regex.Pattern;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.util.RGBLike;
import net.md_5.bungee.api.ChatColor;
+/**
+ * Provides methods to manipulate legacy colors and {@link ChatColor} class.
+ */
public class ChatColorUtil {
-
-
-
- public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoPpRr";
+ /**
+ * All characters that represent a colorcode.
+ */
public static final String ALL_COLORS = "0123456789AaBbCcDdEeFf";
+ /**
+ * All characters that represent a color or format code.
+ */
+ public static final String ALL_CODES = ALL_COLORS + "KkLlMmNnOoPpRr";
private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("§x(?>§[\\da-f]){6}", Pattern.CASE_INSENSITIVE);
private static final Pattern ESS_COLOR_PATTERN = Pattern.compile("§#[\\da-f]{6}", Pattern.CASE_INSENSITIVE);
-
+
/**
- * Return the legacy format needed to reproduce the format at the end of the provided legacy text.
+ * Returns the legacy format needed to reproduce the format at the end of the provided legacy text.
* Supports standard chat colors and formats, BungeeCord Chat rgb format and EssentialsX rgb format.
* The RGB value from EssentialsX format is converted to BungeeCord Chat when included in the returned value.
+ * @param legacyText the legacy formated text.
+ * @return the active format at the end of the provided text.
*/
- public static String getLastColors(String legacyText) {
+ public static String getLastColors(String legacyText) {
StringBuilder result = new StringBuilder();
int length = legacyText.length();
for (int index = length - 2; index >= 0; index--) {
if (legacyText.charAt(index) == ChatColor.COLOR_CHAR) {
-
- // detection of rgb color §x§0§1§2§3§4§5
+
+ // detection of rgb color §x§0§1§2§3§4§5
String rgb;
- if (index > 11
- && legacyText.charAt(index - 12) == ChatColor.COLOR_CHAR
- && (legacyText.charAt(index - 11) == 'x'
- || legacyText.charAt(index - 11) == 'X')
- && HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
- result.insert(0, rgb);
- break;
- }
-
- // detection of rgb color §#012345 (and converting it to bungee chat format)
- if (index < length - 7
- && legacyText.charAt(index + 1) == '#'
- && ESS_COLOR_PATTERN.matcher(rgb = legacyText.substring(index, index + 8)).matches()) {
- rgb = "§x§" + rgb.charAt(2) + "§" + rgb.charAt(3)
- + "§" + rgb.charAt(4) + "§" + rgb.charAt(5)
- + "§" + rgb.charAt(6) + "§" + rgb.charAt(7);
- result.insert(0, rgb);
- break;
- }
-
- // try detect non-rgb format
+ if (index > 11
+ && legacyText.charAt(index - 12) == ChatColor.COLOR_CHAR
+ && (legacyText.charAt(index - 11) == 'x'
+ || legacyText.charAt(index - 11) == 'X')
+ && HEX_COLOR_PATTERN.matcher(rgb = legacyText.substring(index - 12, index + 2)).matches()) {
+ result.insert(0, rgb);
+ break;
+ }
+
+ // detection of rgb color §#012345 (and converting it to bungee chat format)
+ if (index < length - 7
+ && legacyText.charAt(index + 1) == '#'
+ && ESS_COLOR_PATTERN.matcher(rgb = legacyText.substring(index, index + 8)).matches()) {
+ rgb = "§x§" + rgb.charAt(2) + "§" + rgb.charAt(3)
+ + "§" + rgb.charAt(4) + "§" + rgb.charAt(5)
+ + "§" + rgb.charAt(6) + "§" + rgb.charAt(7);
+ result.insert(0, rgb);
+ break;
+ }
+
+ // try detect non-rgb format
char colorChar = legacyText.charAt(index + 1);
ChatColor legacyColor = getChatColorByChar(colorChar);
@@ -65,8 +72,8 @@ public class ChatColorUtil {
// Once we find a color or reset we can stop searching
char col = legacyColor.toString().charAt(1);
if ((col >= '0' && col <= '9')
- || (col >= 'a' && col <= 'f')
- || col == 'r') {
+ || (col >= 'a' && col <= 'f')
+ || col == 'r') {
break;
}
}
@@ -75,229 +82,215 @@ public class ChatColorUtil {
return result.toString();
}
-
- public static ChatColor getChatColorByChar(char code) {
+
+ /**
+ * Returns the {@link ChatColor} associated with the provided char, case insensitive.
+ * @param code the case insensitive char code.
+ * @return the corresponding {@link ChatColor}.
+ */
+ public static ChatColor getChatColorByChar(char code) {
return ChatColor.getByChar(Character.toLowerCase(code));
}
-
-
-
- /**
- * Translate the color code of the provided string, that uses the the color char, to
- * the {@code §} color code format.
- *
- * This method is the improved version of {@link ChatColor#translateAlternateColorCodes(char, String)},
- * because it takes into account essentials RGB color code, and {@code altColorChar} escaping (by doubling it).
- * Essentials RGB color code are converted to Bungee chat RGB format, so the returned string can be converted
- * to component (see {@link Chat#legacyText(Object)}).
- *
- * This method should be used for user input (no permission check) or string configuration, but not string
- * from another API or containing URLs.
- */
+
+
+
+ /**
+ * Translate the color code of the provided string, that uses the alt color char, to the {@code §} color code
+ * format.
+ *
+ * This method is the improved version of {@link ChatColor#translateAlternateColorCodes(char, String)},
+ * because it takes into account essentials RGB color code, and {@code altColorChar} escaping (by doubling it).
+ * Essentials RGB color code are converted to Bungee chat RGB format, so the returned string can be converted
+ * to component (see {@link Chat#legacyText(Object)}).
+ *
+ * This method should be used for user input (no permission check) or string configuration, but not string
+ * from another API or containing URLs.
+ * @param altColorChar the alternative character to prefix color codes (usually {@code '&'}).
+ * @param textToTranslate the text to translate.
+ * @return the string translated to proper legacy text.
+ */
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
{
- char colorChar = ChatColor.COLOR_CHAR;
- StringBuilder acc = new StringBuilder();
+ char colorChar = ChatColor.COLOR_CHAR;
+ StringBuilder acc = new StringBuilder();
char[] b = textToTranslate.toCharArray();
for ( int i = 0; i < b.length; i++ )
{
- if (i < b.length - 1 // legacy chat format
- && b[i] == altColorChar && ALL_CODES.indexOf(b[i + 1]) > -1)
+ if (i < b.length - 1 // legacy chat format
+ && b[i] == altColorChar && ALL_CODES.indexOf(b[i + 1]) > -1)
{
- acc.append(colorChar);
- acc.append(lowerCase(b[i + 1]));
- i++;
- }
- else if (i < b.length - 13 // bungee chat RGB format
- && b[i] == altColorChar
- && lowerCase(b[i + 1]) == 'x'
- && b[i + 2] == altColorChar && ALL_COLORS.indexOf(b[i + 3]) > -1
- && b[i + 4] == altColorChar && ALL_COLORS.indexOf(b[i + 5]) > -1
- && b[i + 6] == altColorChar && ALL_COLORS.indexOf(b[i + 7]) > -1
- && b[i + 8] == altColorChar && ALL_COLORS.indexOf(b[i + 9]) > -1
- && b[i + 10] == altColorChar && ALL_COLORS.indexOf(b[i + 11]) > -1
- && b[i + 12] == altColorChar && ALL_COLORS.indexOf(b[i + 13]) > -1) {
- acc.append(colorChar).append(lowerCase(b[i + 1]));
- acc.append(colorChar).append(lowerCase(b[i + 3]));
- acc.append(colorChar).append(lowerCase(b[i + 5]));
- acc.append(colorChar).append(lowerCase(b[i + 7]));
- acc.append(colorChar).append(lowerCase(b[i + 9]));
- acc.append(colorChar).append(lowerCase(b[i + 11]));
- acc.append(colorChar).append(lowerCase(b[i + 13]));
- i+=13;
- }
- else if (i < b.length - 7 // Essentials chat RGB format
- && b[i] == altColorChar
- && b[i + 1] == '#'
- && ALL_COLORS.indexOf(b[i + 2]) > -1 && ALL_COLORS.indexOf(b[i + 3]) > -1
- && ALL_COLORS.indexOf(b[i + 4]) > -1 && ALL_COLORS.indexOf(b[i + 5]) > -1
- && ALL_COLORS.indexOf(b[i + 6]) > -1 && ALL_COLORS.indexOf(b[i + 7]) > -1) {
- acc.append(colorChar).append('x');
- acc.append(colorChar).append(lowerCase(b[i + 2]));
- acc.append(colorChar).append(lowerCase(b[i + 3]));
- acc.append(colorChar).append(lowerCase(b[i + 4]));
- acc.append(colorChar).append(lowerCase(b[i + 5]));
- acc.append(colorChar).append(lowerCase(b[i + 6]));
- acc.append(colorChar).append(lowerCase(b[i + 7]));
- i+=7;
- }
- else if (i < b.length - 1 && b[i] == altColorChar && b[i + 1] == altColorChar) {
- acc.append(altColorChar);
- i++;
+ acc.append(colorChar);
+ acc.append(lowerCase(b[i + 1]));
+ i++;
+ }
+ else if (i < b.length - 13 // bungee chat RGB format
+ && b[i] == altColorChar
+ && lowerCase(b[i + 1]) == 'x'
+ && b[i + 2] == altColorChar && ALL_COLORS.indexOf(b[i + 3]) > -1
+ && b[i + 4] == altColorChar && ALL_COLORS.indexOf(b[i + 5]) > -1
+ && b[i + 6] == altColorChar && ALL_COLORS.indexOf(b[i + 7]) > -1
+ && b[i + 8] == altColorChar && ALL_COLORS.indexOf(b[i + 9]) > -1
+ && b[i + 10] == altColorChar && ALL_COLORS.indexOf(b[i + 11]) > -1
+ && b[i + 12] == altColorChar && ALL_COLORS.indexOf(b[i + 13]) > -1) {
+ acc.append(colorChar).append(lowerCase(b[i + 1]));
+ acc.append(colorChar).append(lowerCase(b[i + 3]));
+ acc.append(colorChar).append(lowerCase(b[i + 5]));
+ acc.append(colorChar).append(lowerCase(b[i + 7]));
+ acc.append(colorChar).append(lowerCase(b[i + 9]));
+ acc.append(colorChar).append(lowerCase(b[i + 11]));
+ acc.append(colorChar).append(lowerCase(b[i + 13]));
+ i+=13;
+ }
+ else if (i < b.length - 7 // Essentials chat RGB format
+ && b[i] == altColorChar
+ && b[i + 1] == '#'
+ && ALL_COLORS.indexOf(b[i + 2]) > -1 && ALL_COLORS.indexOf(b[i + 3]) > -1
+ && ALL_COLORS.indexOf(b[i + 4]) > -1 && ALL_COLORS.indexOf(b[i + 5]) > -1
+ && ALL_COLORS.indexOf(b[i + 6]) > -1 && ALL_COLORS.indexOf(b[i + 7]) > -1) {
+ acc.append(colorChar).append('x');
+ acc.append(colorChar).append(lowerCase(b[i + 2]));
+ acc.append(colorChar).append(lowerCase(b[i + 3]));
+ acc.append(colorChar).append(lowerCase(b[i + 4]));
+ acc.append(colorChar).append(lowerCase(b[i + 5]));
+ acc.append(colorChar).append(lowerCase(b[i + 6]));
+ acc.append(colorChar).append(lowerCase(b[i + 7]));
+ i+=7;
+ }
+ else if (i < b.length - 1 && b[i] == altColorChar && b[i + 1] == altColorChar) {
+ acc.append(altColorChar);
+ i++;
}
else {
- acc.append(b[i]);
+ acc.append(b[i]);
}
}
return acc.toString();
}
-
- private static char lowerCase(char c) { return Character.toLowerCase(c); }
-
-
-
- /**
- * Force a text to be italic, while keeping other formatting and colors.
- * The text is prefixed with the ITALIC tag, but is not reset at the end.
- * @param legacyText the original text
- * @return the text fully italic
- */
- public static String forceItalic(String legacyText) {
- return forceFormat(legacyText, ChatColor.ITALIC);
- }
-
- /**
- * Force a text to be bold, while keeping other formatting and colors.
- * The text is prefixed with the BOLD tag, but is not reset at the end.
- * @param legacyText the original text
- * @return the text fully bold
- */
- public static String forceBold(String legacyText) {
- return forceFormat(legacyText, ChatColor.BOLD);
- }
-
- /**
- * Force a text to be underlined, while keeping other formatting and colors.
- * The text is prefixed with the UNDERLINE tag, but is not reset at the end.
- * @param legacyText the original text
- * @return the text fully underlined
- */
- public static String forceUnderline(String legacyText) {
- return forceFormat(legacyText, ChatColor.UNDERLINE);
- }
-
- /**
- * Force a text to be stroked through, while keeping other formatting and colors.
- * The text is prefixed with the STRIKETHROUGH tag, but is not reset at the end.
- * @param legacyText the original text
- * @return the text fully stroked through
- */
- public static String forceStrikethrough(String legacyText) {
- return forceFormat(legacyText, ChatColor.STRIKETHROUGH);
- }
-
- /**
- * Force a text to be obfuscated, while keeping other formatting and colors.
- * The text is prefixed with the MAGIC tag, but is not reset at the end.
- * @param legacyText the original text
- * @return the text fully obfuscated
- */
- public static String forceObfuscated(String legacyText) {
- return forceFormat(legacyText, ChatColor.MAGIC);
- }
-
-
-
- private static String forceFormat(String legacyText, ChatColor format) {
- return format + legacyText
- .replace(format.toString(), "") // remove previous tag to make the result cleaner
- .replaceAll("§([a-frA-FR\\d])", "§$1" + format);
- }
-
-
-
-
-
- /**
- * Replace the RESET tag of the input string to the specified color tag.
- * @param legacyText the original text
- * @param color the color to used to replace the RESET tag
- * (can be a combination of a color tag followed by multiple format tag)
- * @return the resulting text
- */
- public static String resetToColor(String legacyText, String color) {
- return legacyText.replace(ChatColor.RESET.toString(), color);
- }
-
-
-
-
- public static TextColor toAdventure(ChatColor bungee) {
- if (bungee == null)
- return null;
- if (bungee.getColor() == null)
- throw new IllegalArgumentException("The provided Bungee ChatColor must be an actual color (not format nor reset).");
- return TextColor.color(bungee.getColor().getRGB());
- }
-
- public static ChatColor toBungee(TextColor col) {
- if (col == null)
- return null;
- if (col instanceof NamedTextColor) {
- return ChatColor.of(((NamedTextColor) col).toString());
- }
- return ChatColor.of(col.asHexString());
- }
-
-
-
-
- public static TextColor interpolateColor(float v0, float v1, float v, TextColor cc0, TextColor cc1) {
- float normV = (v - v0) / (v1 - v0);
- return TextColor.lerp(normV, cc0, cc1);
- }
-
-
-
-
-
-
-
- public static class ChatValueGradient {
- private record GradientValueColor(float value, TextColor color) { } // Java 16
-
- final List colors = new ArrayList<>();
-
- public synchronized ChatValueGradient add(float v, TextColor col) {
- colors.add(new GradientValueColor(v, col));
- return this;
- }
-
- public synchronized TextColor pickColorAt(float v) {
- if (colors.isEmpty())
- throw new IllegalStateException("Must define at least one color in this ChatValueGradient instance.");
- if (colors.size() == 1)
- return colors.get(0).color();
-
- colors.sort((p1, p2) -> Float.compare(p1.value(), p2.value()));
-
- if (v <= colors.get(0).value())
- return colors.get(0).color();
- if (v >= colors.get(colors.size() - 1).value())
- return colors.get(colors.size() - 1).color();
-
- int p1 = 1;
- for (; p1 < colors.size(); p1++) {
- if (colors.get(p1).value() >= v)
- break;
- }
- int p0 = p1 - 1;
- float v0 = colors.get(p0).value(), v1 = colors.get(p1).value();
- TextColor cc0 = colors.get(p0).color(), cc1 = colors.get(p1).color();
- return interpolateColor(v0, v1, v, cc0, cc1);
- }
- }
-
+ private static char lowerCase(char c) { return Character.toLowerCase(c); }
+
+
+
+
+ /**
+ * Force a text to be italic, while keeping other formatting and colors.
+ * The text is prefixed with the ITALIC tag, but is not reset at the end.
+ * @param legacyText the original text.
+ * @return the text fully italic.
+ */
+ public static String forceItalic(String legacyText) {
+ return forceFormat(legacyText, ChatColor.ITALIC);
+ }
+
+ /**
+ * Force a text to be bold, while keeping other formatting and colors.
+ * The text is prefixed with the BOLD tag, but is not reset at the end.
+ * @param legacyText the original text.
+ * @return the text fully bold.
+ */
+ public static String forceBold(String legacyText) {
+ return forceFormat(legacyText, ChatColor.BOLD);
+ }
+
+ /**
+ * Force a text to be underlined, while keeping other formatting and colors.
+ * The text is prefixed with the UNDERLINE tag, but is not reset at the end.
+ * @param legacyText the original text.
+ * @return the text fully underlined.
+ */
+ public static String forceUnderline(String legacyText) {
+ return forceFormat(legacyText, ChatColor.UNDERLINE);
+ }
+
+ /**
+ * Force a text to be stroked through, while keeping other formatting and colors.
+ * The text is prefixed with the STRIKETHROUGH tag, but is not reset at the end.
+ * @param legacyText the original text.
+ * @return the text fully stroked through.
+ */
+ public static String forceStrikethrough(String legacyText) {
+ return forceFormat(legacyText, ChatColor.STRIKETHROUGH);
+ }
+
+ /**
+ * Force a text to be obfuscated, while keeping other formatting and colors.
+ * The text is prefixed with the MAGIC tag, but is not reset at the end.
+ * @param legacyText the original text.
+ * @return the text fully obfuscated.
+ */
+ public static String forceObfuscated(String legacyText) {
+ return forceFormat(legacyText, ChatColor.MAGIC);
+ }
+
+
+
+ private static String forceFormat(String legacyText, ChatColor format) {
+ return format + legacyText
+ .replace(format.toString(), "") // remove previous tag to make the result cleaner
+ .replaceAll("§([a-frA-FR\\d])", "§$1" + format);
+ }
+
+
+
+
+
+ /**
+ * Replace the RESET tag of the input string to the specified color tag.
+ * @param legacyText the original text
+ * @param color the color to used to replace the RESET tag
+ * (can be a combination of a color tag followed by multiple format tag).
+ * @return the resulting text.
+ */
+ public static String resetToColor(String legacyText, String color) {
+ return legacyText.replace(ChatColor.RESET.toString(), color);
+ }
+
+
+
+
+ /**
+ * Converts the provided {@link ChatColor} to its Adventure counterpart.
+ * @param bungee a BungeeCord {@link ChatColor} instance.
+ * @return the {@link TextColor} equivalent to the provided {@link ChatColor}.
+ */
+ public static TextColor toAdventure(ChatColor bungee) {
+ if (bungee == null)
+ return null;
+ if (bungee.getColor() == null)
+ throw new IllegalArgumentException("The provided Bungee ChatColor must be an actual color (not format nor reset).");
+ return TextColor.color(bungee.getColor().getRGB());
+ }
+
+ /**
+ * Converts the provided {@link TextColor} to its BungeeCord counterpart.
+ * @param col a Adventure {@link TextColor} instance.
+ * @return the {@link ChatColor} equivalent to the provided {@link TextColor}.
+ */
+ public static ChatColor toBungee(TextColor col) {
+ if (col == null)
+ return null;
+ if (col instanceof NamedTextColor) {
+ return ChatColor.of(((NamedTextColor) col).toString());
+ }
+ return ChatColor.of(col.asHexString());
+ }
+
+
+ /**
+ * Create a color, interpolating between 2 colors.
+ * @param v0 the value corresponding to color {@code cc0}.
+ * @param v1 the value corresponding to color {@code cc1}.
+ * @param v the value between {@code v0} and {@code v1} to interpolate.
+ * @param cc0 the first color.
+ * @param cc1 the second color.
+ * @return the interpolated color.
+ * @see TextColor#lerp(float, RGBLike, RGBLike)
+ */
+ public static TextColor interpolateColor(float v0, float v1, float v, TextColor cc0, TextColor cc1) {
+ float normV = (v - v0) / (v1 - v0);
+ return TextColor.lerp(normV, cc0, cc1);
+ }
+
+
}
\ No newline at end of file
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatConfig.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatConfig.java
new file mode 100644
index 0000000..75e3668
--- /dev/null
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatConfig.java
@@ -0,0 +1,90 @@
+package fr.pandacube.lib.chat;
+
+import java.util.function.Supplier;
+
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
+
+/**
+ * Class holding static configuration values for chat component rendering.
+ */
+public class ChatConfig {
+
+ /**
+ * The color used for decoration.
+ */
+ public static TextColor decorationColor = NamedTextColor.YELLOW;
+
+ /**
+ * The character used as a pattern for decoration.
+ */
+ public static char decorationChar = '-';
+
+ /**
+ * The default margin for left and right aligned text.
+ */
+ public static int nbCharMargin = 1;
+
+ /**
+ * The color used for successful messages.
+ */
+ public static TextColor successColor = NamedTextColor.GREEN;
+
+ /**
+ * The color used for error/failure messages.
+ */
+ public static TextColor failureColor = NamedTextColor.RED;
+
+ /**
+ * the color used for informational messages.
+ */
+ public static TextColor infoColor = NamedTextColor.GOLD;
+
+ /**
+ * The color used for warning messages.
+ */
+ public static TextColor warningColor = NamedTextColor.GOLD;
+
+ /**
+ * The color used to display data in a message.
+ */
+ public static TextColor dataColor = NamedTextColor.GRAY;
+
+ /**
+ * The color used for displayed URLs and clickable URLs.
+ */
+ public static TextColor urlColor = NamedTextColor.GREEN;
+
+ /**
+ * The color used for displayed commands and clickable commands.
+ */
+ public static TextColor commandColor = NamedTextColor.GRAY;
+
+ /**
+ * The color sued to display a command that is highlighted. For example, the current page in a pagination.
+ */
+ public static TextColor highlightedCommandColor = NamedTextColor.WHITE;
+
+ /**
+ * The color used for broadcasted messages.
+ * It is often used in combination with {@link #prefix}.
+ */
+ public static TextColor broadcastColor = NamedTextColor.YELLOW;
+
+ /**
+ * The prefix used for prefixed messages.
+ * It can be a sylized name of the server, like {@code "[Pandacube] "}.
+ * It is often used in combination with {@link #broadcastColor}.
+ */
+ public static Supplier prefix;
+
+ /**
+ * Gets the width of the configured {@link #prefix}.
+ * @param console if the width has to be calculated for the console or not.
+ * @return the width of the configured {@link #prefix}.
+ */
+ public static int getPrefixWidth(boolean console) {
+ Chat c;
+ return prefix == null ? 0 : (c = prefix.get()) == null ? 0 : ChatUtil.componentWidth(c.getAdv(), console);
+ }
+}
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatFilledLine.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatFilledLine.java
new file mode 100644
index 0000000..39527fc
--- /dev/null
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatFilledLine.java
@@ -0,0 +1,208 @@
+package fr.pandacube.lib.chat;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.ComponentLike;
+import net.kyori.adventure.text.format.TextColor;
+import org.jetbrains.annotations.NotNull;
+
+import fr.pandacube.lib.chat.Chat.FormatableChat;
+
+/**
+ * Builder for a {@link Chat} component for filling a chat line, with decoration and eventual aligned text.
+ */
+public class ChatFilledLine implements ComponentLike {
+
+ /**
+ * Builder for a filled line with the provided left-aligned text.
+ * @param text the text to align ont the left.
+ * @return a new {@link ChatFilledLine} builder.
+ */
+ public static ChatFilledLine leftText(ComponentLike text) {
+ return new ChatFilledLine(text, Alignment.LEFT);
+ }
+
+ /**
+ * Builder for a filled line with the provided right-aligned text.
+ * @param text the text to align ont the right.
+ * @return a new {@link ChatFilledLine} builder.
+ */
+ public static ChatFilledLine rightText(ComponentLike text) {
+ return new ChatFilledLine(text, Alignment.RIGHT);
+ }
+
+ /**
+ * Builder for a filled line with the provided centered text.
+ * @param text the text to center.
+ * @return a new {@link ChatFilledLine} builder.
+ */
+ public static ChatFilledLine centerText(ComponentLike text) {
+ return new ChatFilledLine(text, Alignment.CENTER);
+ }
+
+ /**
+ * Builder for a filled line with no text.
+ * @return a new {@link ChatFilledLine} builder.
+ */
+ public static ChatFilledLine filled() {
+ return new ChatFilledLine(null, Alignment.NONE);
+ }
+
+
+
+
+
+
+ private final ComponentLike text;
+ private final Alignment alignment;
+ private char decorationChar = ChatConfig.decorationChar;
+ private TextColor decorationColor = ChatConfig.decorationColor;
+ private boolean decorationBold = false;
+ private int nbSide = ChatConfig.nbCharMargin;
+ private boolean spacesAroundText = false;
+ private boolean console = false;
+ private Integer maxWidth = null;
+
+ private ChatFilledLine(ComponentLike text, Alignment alignment) {
+ this.text = text;
+ this.alignment = alignment;
+ }
+
+
+ /**
+ * Sets the decoration char.
+ * @param decoChar the character that will fill the line.
+ * @return this.
+ */
+ public ChatFilledLine decoChar(char decoChar) {
+ decorationChar = decoChar;
+ return this;
+ }
+
+ /**
+ * Sets the decoration color.
+ * @param decoColor the color of the characters filling the line.
+ * @return this.
+ */
+ public ChatFilledLine decoColor(TextColor decoColor) {
+ decorationColor = decoColor;
+ return this;
+ }
+
+ /**
+ * Sets the decoration in bold.
+ * @return this.
+ */
+ public ChatFilledLine decoBold() {
+ decorationBold = true;
+ return this;
+ }
+
+ /**
+ * Sets the number of side character when the text is aligned left or right.
+ * @param nbSide the number of character that will separate the border from the side of the text.
+ * @return this.
+ */
+ public ChatFilledLine nbSide(int nbSide) {
+ this.nbSide = nbSide;
+ return this;
+ }
+
+ /**
+ * Adds spaces around the text.
+ * @return this.
+ */
+ public ChatFilledLine spacesAroundText() {
+ spacesAroundText = true;
+ return this;
+ }
+
+ /**
+ * Configure if the line will be rendered on console or not.
+ * @param console true for console, false for game UI.
+ * @return this.
+ */
+ public ChatFilledLine console(boolean console) {
+ this.console = console;
+ return this;
+ }
+
+ /**
+ * Configure the width of the line.
+ * @param maxWidth the width to consider when rendering the line. In pixel for game UI rendering, n character for
+ * console rendering.
+ * @return this.
+ */
+ public ChatFilledLine maxWidth(int maxWidth) {
+ this.maxWidth = maxWidth;
+ return this;
+ }
+
+
+ /**
+ * Renders this line to a {@link FormatableChat}.
+ * @return a new {@link FormatableChat} builded by this {@link ChatFilledLine}.
+ */
+ public FormatableChat toChat() {
+ int maxWidth = (this.maxWidth != null)
+ ? this.maxWidth
+ : console ? ChatUtil.CONSOLE_NB_CHAR_DEFAULT : ChatUtil.DEFAULT_CHAT_WIDTH;
+
+ if (alignment == Alignment.NONE) {
+ int count = maxWidth / ChatUtil.charW(decorationChar, console, decorationBold);
+ return Chat.text(ChatUtil.repeatedChar(decorationChar, count)).color(decorationColor).bold(decorationBold);
+ }
+
+ ComponentLike text = spacesAroundText
+ ? Chat.text(" ").then(this.text).thenText(" ")
+ : this.text;
+
+ int textWidth = ChatUtil.componentWidth(text.asComponent(), console);
+
+ if (textWidth > maxWidth)
+ return (FormatableChat) text;
+
+ int repeatedCharWidth = ChatUtil.charW(decorationChar, console, decorationBold);
+ int nbCharLeft = 0, nbCharRight = 0;
+
+ switch (alignment) {
+ case CENTER -> {
+ nbCharLeft = nbCharRight = (maxWidth - textWidth) / 2 / repeatedCharWidth;
+ if (nbCharLeft == 0)
+ return (FormatableChat) text;
+ }
+ case LEFT, RIGHT -> {
+ int remWidth = textWidth + nbSide * repeatedCharWidth;
+ if (remWidth > maxWidth)
+ return (FormatableChat) text;
+ boolean left = alignment == Alignment.LEFT;
+ int nbOtherSide = (maxWidth - remWidth) / repeatedCharWidth;
+ nbCharLeft = left ? nbSide : nbOtherSide;
+ nbCharRight = left ? nbOtherSide : nbSide;
+ }
+ }
+
+ Chat d = Chat.chat()
+ .then(Chat.text(ChatUtil.repeatedChar(decorationChar, nbCharLeft)).color(decorationColor).bold(decorationBold))
+ .then(text);
+ if (decorationChar != ' ')
+ d.then(Chat.text(ChatUtil.repeatedChar(decorationChar, nbCharRight)).color(decorationColor).bold(decorationBold));
+ return (FormatableChat) d;
+ }
+
+
+ @Override
+ public @NotNull Component asComponent() {
+ return toChat().asComponent();
+ }
+
+
+
+
+
+ private enum Alignment {
+ LEFT,
+ CENTER,
+ RIGHT,
+ NONE
+ }
+}
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java
index f74883e..92b0107 100644
--- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatStatic.java
@@ -2,199 +2,641 @@ package fr.pandacube.lib.chat;
import java.util.Objects;
+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.HoverEventSource;
import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import fr.pandacube.lib.chat.Chat.FormatableChat;
+/**
+ * Abstract class holding the publicly accessible methods to create an instance of {@link Chat} component.
+ */
public abstract class ChatStatic {
- private static FormatableChat chatComponent(Component c) {
- return new FormatableChat(Chat.componentToBuilder(c));
- }
+ private static FormatableChat chatComponent(Component c) {
+ return new FormatableChat(componentToBuilder(c));
+ }
- public static FormatableChat chatComponent(BaseComponent c) {
- return new FormatableChat(Chat.componentToBuilder(Chat.toAdventure(c)));
- }
+ /**
+ * Creates a {@link FormatableChat} from the provided Bungee {@link BaseComponent}.
+ * @param c the {@link BaseComponent}.
+ * @return a new {@link FormatableChat}.
+ */
+ public static FormatableChat chatComponent(BaseComponent c) {
+ return new FormatableChat(componentToBuilder(Chat.toAdventure(c)));
+ }
- public static FormatableChat chatComponent(ComponentLike c) {
- return chatComponent(c.asComponent());
- }
-
- public static FormatableChat chat() {
- return new FormatableChat(Component.text());
- }
-
- public static FormatableChat chatComponent(BaseComponent[] c) {
- return chatComponent(Chat.toAdventure(c));
- }
+ /**
+ * Creates a {@link FormatableChat} from the provided {@link ComponentLike}.
+ * If the provided component is an instance of {@link Chat}, its content will be duplicated, and the provided one
+ * will be untouched.
+ * @param c the {@link ComponentLike}.
+ * @return a new {@link FormatableChat}.
+ */
+ public static FormatableChat chatComponent(ComponentLike c) {
+ return chatComponent(c.asComponent());
+ }
+ /**
+ * Creates a {@link FormatableChat} with an empty main text content.
+ * @return a new empty {@link FormatableChat}.
+ */
+ public static FormatableChat chat() {
+ return new FormatableChat(Component.text());
+ }
- /**
- * Create a Chat instance with the provided plain text as its main text content.
- *
- * @param plainText the text to use as he content of the new Chat instance.
- * @return a Chat instance with the provided text as its main text content.
- * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
- * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
- * instead.
- */
- public static FormatableChat text(Object plainText) {
- if (plainText instanceof ComponentLike) {
- throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + plainText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
- }
- return new FormatableChat(Component.text().content(Objects.toString(plainText)));
- }
-
-
- /**
- * Create a Chat instance with the provided legacy text as its main text content.
- *
- * @param legacyText the text to use as he content of the new Chat instance.
- * @return a Chat instance with the provided text as its main text content.
- * @throws IllegalArgumentException If the {@code plainText} parameter is instance of {@link Chat} or
- * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
- * instead.
- */
- public static FormatableChat legacyText(Object legacyText) {
- if (legacyText instanceof ComponentLike) {
- throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + legacyText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
- }
- return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
- }
-
- public static FormatableChat infoText(Object plainText) {
- return text(plainText).infoColor();
- }
-
- public static FormatableChat warningText(Object plainText) {
- return text(plainText).warningColor();
- }
-
- public static FormatableChat dataText(Object plainText) {
- return text(plainText).dataColor();
- }
-
- public static FormatableChat decorationText(Object plainText) {
- return text(plainText).decorationColor();
- }
-
- public static FormatableChat successText(Object plainText) {
- return text(plainText).successColor();
- }
-
- public static FormatableChat failureText(Object plainText) {
- return text(plainText).failureColor();
- }
-
- public static FormatableChat playerNameText(String legacyText) {
- FormatableChat fc = legacyText(legacyText);
- fc.builder.colorIfAbsent(NamedTextColor.WHITE);
- return fc;
- }
-
- public static FormatableChat playerNameComponent(Component c) {
- FormatableChat fc = chatComponent(c);
- fc.builder.colorIfAbsent(NamedTextColor.WHITE);
- return fc;
- }
-
- public static FormatableChat translation(String key, Object... with) {
- return new FormatableChat(Component.translatable().key(key).args(Chat.filterObjToComponentLike(with)));
- }
-
- public static FormatableChat keybind(String key) {
- return new FormatableChat(Component.keybind().keybind(key));
- }
-
- public static FormatableChat score(String name, String objective) {
- return new FormatableChat(Component.score().name(name).objective(objective));
- }
-
- public static FormatableChat clickableURL(Chat inner, String url, Chat hover) {
- Objects.requireNonNull(url, "url");
- if (inner == null)
- inner = text(url);
- if (hover == null)
- hover = text(ChatUtil.wrapInLimitedPixels(url, 240));
- return (FormatableChat) chat().clickURL(url).urlColor().hover(hover).then(inner);
- }
-
- public static FormatableChat clickableURL(Chat inner, String url) {
- return clickableURL(inner, url, null);
- }
-
- public static FormatableChat clickableURL(String url, Chat hover) {
- return clickableURL(null, url, hover);
- }
-
- public static FormatableChat clickableURL(String url) {
- return clickableURL(null, url, null);
- }
-
- public static FormatableChat clickableCommand(Chat inner, String commandWithSlash, Chat hover) {
- Objects.requireNonNull(commandWithSlash, "commandWithSlash");
- if (!commandWithSlash.startsWith("/"))
- throw new IllegalArgumentException("commandWithSlash must start with a '/' character.");
- if (inner == null)
- inner = text(commandWithSlash);
- if (hover == null)
- hover = text(ChatUtil.wrapInLimitedPixels(commandWithSlash, 240));
- return (FormatableChat) chat().clickCommand(commandWithSlash).commandColor().hover(hover).then(inner);
- }
-
- public static FormatableChat clickableCommand(Chat inner, String commandWithSlash) {
- return clickableCommand(inner, commandWithSlash, null);
- }
-
- public static FormatableChat clickableCommand(String commandWithSlash, Chat hover) {
- return clickableCommand(null, commandWithSlash, hover);
- }
-
- public static FormatableChat clickableCommand(String commandWithSlash) {
- return clickableCommand(null, commandWithSlash, null);
- }
-
- public static FormatableChat clickableSuggest(Chat inner, String commandWithSlash, Chat hover) {
- Objects.requireNonNull(commandWithSlash, "commandWithSlash");
- if (!commandWithSlash.startsWith("/"))
- throw new IllegalArgumentException("commandWithSlash must start with a '/' character.");
- if (inner == null)
- inner = text(commandWithSlash);
- if (hover == null)
- hover = text(ChatUtil.wrapInLimitedPixels(commandWithSlash, 240));
- return (FormatableChat) chat().clickSuggest(commandWithSlash).commandColor().hover(hover).then(inner);
- }
-
- public static FormatableChat clickableSuggest(Chat inner, String commandWithSlash) {
- return clickableSuggest(inner, commandWithSlash, null);
- }
-
- public static FormatableChat clickableSuggest(String commandWithSlash, Chat hover) {
- return clickableSuggest(null, commandWithSlash, hover);
- }
-
- public static FormatableChat clickableSuggest(String commandWithSlash) {
- return clickableSuggest(null, commandWithSlash, null);
- }
+ /**
+ * Creates a {@link FormatableChat} from the provided Bungee {@link BaseComponent BaseComponent[]}.
+ * @param c the array of {@link BaseComponent}.
+ * @return a new {@link FormatableChat}.
+ */
+ public static FormatableChat chatComponent(BaseComponent[] c) {
+ return chatComponent(Chat.toAdventure(c));
+ }
- public static Chat prefixedAndColored(ComponentLike message) {
- return Chat.chat()
- .broadcastColor()
- .then(Chat.getConfig().prefix.get())
- .then(message);
- }
-
-
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
+ * instead.
+ */
+ public static FormatableChat text(Object plainText) {
+ if (plainText instanceof ComponentLike) {
+ throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + plainText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
+ }
+ return new FormatableChat(Component.text().content(Objects.toString(plainText)));
+ }
+
+
+ /**
+ * Creates a {@link FormatableChat} with the provided legacy text as its content.
+ * @param legacyText the legacy text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its content.
+ * @throws IllegalArgumentException If the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)}
+ * instead.
+ */
+ public static FormatableChat legacyText(Object legacyText) {
+ if (legacyText instanceof ComponentLike) {
+ throw new IllegalArgumentException("Expected any object except instance of " + ComponentLike.class + ". Received " + legacyText + ". Please use ChatStatic.chatComponent(ComponentLike) instead.");
+ }
+ return chatComponent(LegacyComponentSerializer.legacySection().deserialize(Objects.toString(legacyText)));
+ }
+
+
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
+ * {@link ChatConfig#infoColor configured info color}.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#infoColor()} instead.
+ */
+ public static FormatableChat infoText(Object plainText) {
+ return text(plainText).infoColor();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
+ * {@link ChatConfig#warningColor configured warning color}.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#warningColor()} instead.
+ */
+ public static FormatableChat warningText(Object plainText) {
+ return text(plainText).warningColor();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
+ * {@link ChatConfig#dataColor configured data color}.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#dataColor()} instead.
+ */
+ public static FormatableChat dataText(Object plainText) {
+ return text(plainText).dataColor();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
+ * {@link ChatConfig#decorationColor configured decorationColor color}.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#decorationColor()} instead.
+ */
+ public static FormatableChat decorationText(Object plainText) {
+ return text(plainText).decorationColor();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
+ * {@link ChatConfig#successColor configured success color}.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#successColor()} instead.
+ */
+ public static FormatableChat successText(Object plainText) {
+ return text(plainText).successColor();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided plain text as its main text content, and colored using the
+ * {@link ChatConfig#failureColor configured failure color}.
+ * @param plainText the text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#failureColor()} instead.
+ */
+ public static FormatableChat failureText(Object plainText) {
+ return text(plainText).failureColor();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided legacy text as its main text content, and colored in white in
+ * case there is no color on the generated parent component.
+ * @param legacyText the legacy text to use as the content.
+ * @return a new {@link FormatableChat} with the provided text as its main text content, and the configured color.
+ * @throws IllegalArgumentException if the {@code plainText} parameter is instance of {@link Chat} or
+ * {@link Component}. The caller should use {@link #chatComponent(ComponentLike)} and
+ * {@link FormatableChat#failureColor()} instead.
+ */
+ public static FormatableChat playerNameText(String legacyText) {
+ FormatableChat fc = legacyText(legacyText);
+ fc.builder.colorIfAbsent(NamedTextColor.WHITE);
+ return fc;
+ }
+
+ /**
+ * Creates a {@link FormatableChat} from the provided {@link Component}, coloring in white the generated parent
+ * component in case there is no color defined.
+ * If the provided component is an instance of {@link Chat}, its content will be duplicated, and the provided one
+ * will be untouched.
+ * @param c the {@link Component}.
+ * @return a new {@link FormatableChat}.
+ */
+ public static FormatableChat playerNameComponent(Component c) {
+ FormatableChat fc = chatComponent(c);
+ fc.builder.colorIfAbsent(NamedTextColor.WHITE);
+ return fc;
+ }
+
+
+
+
+ /**
+ * Creates a {@link FormatableChat} with the provided translation key and parameters.
+ * @param key the translation key.
+ * @param with the translation parameters.
+ * @return a new {@link FormatableChat} with the provided translation key and parameters.
+ */
+ public static FormatableChat translation(String key, Object... with) {
+ return new FormatableChat(Component.translatable().key(key).args(Chat.filterObjToComponentLike(with)));
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided keybind.
+ * @param key the keybind to display.
+ * @return a new {@link FormatableChat} with the provided keybind.
+ */
+ public static FormatableChat keybind(String key) {
+ return new FormatableChat(Component.keybind().keybind(key));
+ }
+
+ /**
+ * Creates a {@link FormatableChat} with the provided score name and objective.
+ * @param name the score name.
+ * @param objective the score objective.
+ * @return a new {@link FormatableChat} with the provided score name and objective.
+ */
+ public static FormatableChat score(String name, String objective) {
+ return new FormatableChat(Component.score().name(name).objective(objective));
+ }
+
+
+
+
+
+
+ /**
+ * Creates a {@link FormatableChat} that leads to a URL when clicked.
+ * @param inner the component to make clickable.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @param hover the content to display when hovering the component.
+ * @return a new {@link FormatableChat} that leads to a URL when clicked.
+ */
+ public static FormatableChat clickableURL(ComponentLike inner, String url, HoverEventSource> hover) {
+ Objects.requireNonNull(url, "url");
+ if (inner == null)
+ inner = text(url);
+ if (hover == null)
+ hover = text(ChatUtil.wrapInLimitedPixels(url, 240));
+ return (FormatableChat) chat().clickURL(url).urlColor().hover(hover).then(inner);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that leads to a URL when clicked.
+ *
+ * When hovered, the component will display the url. To customize the hover content, use
+ * {@link #clickableURL(ComponentLike, String, HoverEventSource)}.
+ * @param inner the component to make clickable.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @return a new {@link FormatableChat} that leads to a URL when clicked.
+ */
+ public static FormatableChat clickableURL(ComponentLike inner, String url) {
+ return clickableURL(inner, url, null);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that leads to a URL when clicked.
+ *
+ * The text on which to click will be the URL itself. To configure the clicked text, use
+ * {@link #clickableURL(ComponentLike, String, HoverEventSource)}.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @param hover the content to display when hovering the component.
+ * @return a new {@link FormatableChat} that leads to a URL when clicked.
+ */
+ public static FormatableChat clickableURL(String url, HoverEventSource> hover) {
+ return clickableURL(null, url, hover);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that leads to a URL when clicked.
+ *
+ * The text on which to click will be the URL itself. To configure the clicked text, use
+ * {@link #clickableURL(ComponentLike, String)}.
+ *
+ * When hovered, the component will display the url. To customize the hover content, use
+ * {@link #clickableURL(String, HoverEventSource)}.
+ * @param url the target url. Must start with {@code "http://"} or {@code "https://"}.
+ * @return a new {@link FormatableChat} that leads to a URL when clicked.
+ */
+ public static FormatableChat clickableURL(String url) {
+ return clickableURL(null, url, null);
+ }
+
+
+
+
+
+
+ /**
+ * Creates a {@link FormatableChat} that runs a command when clicked.
+ * @param inner the component to make clickable.
+ * @param commandWithSlash the command to run. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return a new {@link FormatableChat} that runs a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableCommand(ComponentLike inner, String commandWithSlash, HoverEventSource> hover) {
+ Objects.requireNonNull(commandWithSlash, "commandWithSlash");
+ if (!commandWithSlash.startsWith("/"))
+ throw new IllegalArgumentException("commandWithSlash must start with a '/' character.");
+ if (inner == null)
+ inner = text(commandWithSlash);
+ if (hover == null)
+ hover = text(ChatUtil.wrapInLimitedPixels(commandWithSlash, 240));
+ return (FormatableChat) chat().clickCommand(commandWithSlash).commandColor().hover(hover).then(inner);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that runs a command when clicked.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #clickableCommand(ComponentLike, String, HoverEventSource)}.
+ * @param inner the component to make clickable.
+ * @param commandWithSlash the command to run. Must start with {@code "/"}.
+ * @return a new {@link FormatableChat} that runs a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableCommand(ComponentLike inner, String commandWithSlash) {
+ return clickableCommand(inner, commandWithSlash, null);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that runs a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #clickableCommand(ComponentLike, String, HoverEventSource)}.
+ * @param commandWithSlash the command to run. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return a new {@link FormatableChat} that runs a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableCommand(String commandWithSlash, HoverEventSource> hover) {
+ return clickableCommand(null, commandWithSlash, hover);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that runs a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #clickableCommand(ComponentLike, String)}.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #clickableCommand(String, HoverEventSource)}.
+ * @param commandWithSlash the command to run. Must start with {@code "/"}.
+ * @return a new {@link FormatableChat} that runs a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableCommand(String commandWithSlash) {
+ return clickableCommand(null, commandWithSlash, null);
+ }
+
+
+
+
+
+
+ /**
+ * Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ * @param inner the component to make clickable.
+ * @param commandWithSlash the command to suggest. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableSuggest(ComponentLike inner, String commandWithSlash, HoverEventSource> hover) {
+ Objects.requireNonNull(commandWithSlash, "commandWithSlash");
+ if (!commandWithSlash.startsWith("/"))
+ throw new IllegalArgumentException("commandWithSlash must start with a '/' character.");
+ if (inner == null)
+ inner = text(commandWithSlash);
+ if (hover == null)
+ hover = text(ChatUtil.wrapInLimitedPixels(commandWithSlash, 240));
+ return (FormatableChat) chat().clickSuggest(commandWithSlash).commandColor().hover(hover).then(inner);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #clickableSuggest(ComponentLike, String, HoverEventSource)}.
+ * @param inner the component to make clickable.
+ * @param commandWithSlash the command to suggest. Must start with {@code "/"}.
+ * @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableSuggest(ComponentLike inner, String commandWithSlash) {
+ return clickableSuggest(inner, commandWithSlash, null);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #clickableSuggest(ComponentLike, String, HoverEventSource)}.
+ * @param commandWithSlash the command to suggest. Must start with {@code "/"}.
+ * @param hover the content to display when hovering the component.
+ * @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableSuggest(String commandWithSlash, HoverEventSource> hover) {
+ return clickableSuggest(null, commandWithSlash, hover);
+ }
+
+ /**
+ * Creates a {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ *
+ * The text on which to click will be the command itself. To configure the clicked text, use
+ * {@link #clickableSuggest(ComponentLike, String)}.
+ *
+ * When hovered, the component will display the command itself. To customize the hover content, use
+ * {@link #clickableSuggest(String, HoverEventSource)}.
+ * @param commandWithSlash the command to suggest. Must start with {@code "/"}.
+ * @return a new {@link FormatableChat} that pre-fill the chat box with a command when clicked.
+ * @throws IllegalArgumentException if {@code commandWithSlash} does not start with a {@code "/"}.
+ */
+ public static FormatableChat clickableSuggest(String commandWithSlash) {
+ return clickableSuggest(null, commandWithSlash, null);
+ }
+
+
+
+
+
+
+
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with decoration and a left-aligned text.
+ * @param text the text aligned to the left.
+ * @param decorationChar the character used for decoration around the text.
+ * @param decorationColor the color used for the decoration characters.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with decoration and a left-aligned text.
+ * @see ChatFilledLine#leftText(ComponentLike)
+ */
+ public static FormatableChat leftText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
+ return ChatFilledLine.leftText(text).decoChar(decorationChar).decoColor(decorationColor).spacesAroundText().console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with the configured decoration character and
+ * color and a left-aligned text.
+ * @param text the text aligned to the left.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a left-aligned text.
+ * @see ChatFilledLine#leftText(ComponentLike)
+ * @see ChatConfig#decorationChar
+ * @see ChatConfig#decorationColor
+ */
+ public static FormatableChat leftText(ComponentLike text, boolean console) {
+ return ChatFilledLine.leftText(text).spacesAroundText().console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with decoration and a right-aligned text.
+ * @param text the text aligned to the right.
+ * @param decorationChar the character used for decoration around the text.
+ * @param decorationColor the color used for the decoration characters.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with decoration and a right-aligned
+ * text.
+ * @see ChatFilledLine#rightText(ComponentLike)
+ */
+ public static FormatableChat rightText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
+ return ChatFilledLine.rightText(text).decoChar(decorationChar).decoColor(decorationColor).spacesAroundText().console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with the configured decoration character and
+ * color and a right-aligned text.
+ * @param text the text aligned to the right.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a right-aligned text.
+ * @see ChatFilledLine#rightText(ComponentLike)
+ * @see ChatConfig#decorationChar
+ * @see ChatConfig#decorationColor
+ */
+ public static FormatableChat rightText(ComponentLike text, boolean console) {
+ return ChatFilledLine.rightText(text).spacesAroundText().console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with decoration and a centered text.
+ * @param text the text aligned to the center.
+ * @param decorationChar the character used for decoration around the text.
+ * @param decorationColor the color used for the decoration characters.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with decoration and a centered text.
+ * @see ChatFilledLine#centerText(ComponentLike)
+ */
+ public static FormatableChat centerText(ComponentLike text, char decorationChar, TextColor decorationColor, boolean console) {
+ return ChatFilledLine.centerText(text).decoChar(decorationChar).decoColor(decorationColor).spacesAroundText().console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with the configured decoration character and
+ * color and a centered text.
+ * @param text the text aligned to the center.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with the configured decoration character
+ * and color and a centered text.
+ * @see ChatFilledLine#centerText(ComponentLike)
+ * @see ChatConfig#decorationChar
+ * @see ChatConfig#decorationColor
+ */
+ public static FormatableChat centerText(ComponentLike text, boolean console) {
+ return ChatFilledLine.centerText(text).spacesAroundText().console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with a decoration character and color.
+ * @param decorationChar the character used for decoration.
+ * @param decorationColor the color used for the decoration characters.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with a decoration character and color.
+ * @see ChatFilledLine#filled()
+ */
+ public static FormatableChat filledLine(char decorationChar, TextColor decorationColor, boolean console) {
+ return ChatFilledLine.filled().decoChar(decorationChar).decoColor(decorationColor).console(console).toChat();
+ }
+
+ /**
+ * Creates a {@link FormatableChat} filling a line of chat (or console) with the configured decoration character and
+ * color.
+ * @param console if the line is rendered on console (true) or IG (false).
+ * @return a new {@link FormatableChat} filling a line of chat (or console) with a decoration character and color.
+ * @see ChatFilledLine#filled()
+ * @see ChatConfig#decorationChar
+ * @see ChatConfig#decorationColor
+ */
+ public static FormatableChat filledLine(boolean console) {
+ return ChatFilledLine.filled().console(console).toChat();
+ }
+
+
+
+
+
+
+
+
+
+
+ /**
+ * Adds the configured prefix and broadcast color to the provided message.
+ * @param message the message to decorate.
+ * @return the decorated message.
+ */
+ public static Chat prefixedAndColored(ComponentLike message) {
+ return Chat.chat()
+ .broadcastColor()
+ .then(ChatConfig.prefix.get())
+ .then(message);
+ }
+
+
+
+
+
+
+
+
+
+
+ private 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());
+ }
+
}
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java
new file mode 100644
index 0000000..54f49cc
--- /dev/null
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java
@@ -0,0 +1,77 @@
+package fr.pandacube.lib.chat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A tree structure of {@link Chat} component intended to be rendered using {@link #render(boolean)}.
+ */
+public class ChatTreeNode {
+
+ private static final String TREE_MIDDLE_CONNECTED = "├";
+ private static final String TREE_END_CONNECTED = "└";
+ private static final String TREE_MIDDLE_OPEN = "│§0`§r";
+ private static final String TREE_END_OPEN = "§0```§r";
+ private static final String TREE_MIDDLE_OPEN_CONSOLE = "│";
+ private static final String TREE_END_OPEN_CONSOLE = " "; // nbsp
+
+
+ /**
+ * The component for the current node.
+ */
+ public final Chat component;
+
+ /**
+ * Children nodes.
+ */
+ public final List children = new ArrayList<>();
+
+ /**
+ * Construct an new {@link ChatTreeNode}.
+ * @param cmp the component for the current node.
+ */
+ public ChatTreeNode(Chat cmp) {
+ component = cmp;
+ }
+
+ /**
+ * Adds a child to the current node.
+ * @param child the child to add.
+ * @return this.
+ */
+ public ChatTreeNode addChild(ChatTreeNode child) {
+ children.add(child);
+ return this;
+ }
+
+ /**
+ * Generate a tree view based on this tree structure.
+ *
+ * Each element in the returned list represent 1 line of this tree view.
+ * Thus, the caller may send each line separately or at once depending of the quantity of data.
+ * @param console true to render for console, false otherwise.
+ * @return an array of component, each element being a single line.
+ */
+ public List render(boolean console) {
+ List ret = new ArrayList<>();
+
+ ret.add(ChatStatic.chat()
+ .then(component));
+
+ for (int i = 0; i < children.size(); i++) {
+ List childComponents = children.get(i).render(console);
+ boolean last = i == children.size() - 1;
+ for (int j = 0; j < childComponents.size(); j++) {
+
+ String prefix = last ? (j == 0 ? TREE_END_CONNECTED : (console ? TREE_END_OPEN_CONSOLE : TREE_END_OPEN))
+ : (j == 0 ? TREE_MIDDLE_CONNECTED : (console ? TREE_MIDDLE_OPEN_CONSOLE : TREE_MIDDLE_OPEN));
+
+ ret.add(ChatStatic.text(prefix)
+ .then(childComponents.get(j)));
+ }
+ }
+
+
+ return ret;
+ }
+}
diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatUtil.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatUtil.java
index feb0be8..2a25c19 100644
--- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatUtil.java
+++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatUtil.java
@@ -26,757 +26,581 @@ import fr.pandacube.lib.chat.Chat.FormatableChat;
*/
public class ChatUtil {
- /*
- * Note : this field is for easy listing of all characters with special sizes. It will all be reported to
- * #CHAR_SIZES on class initialization for optimization.
- */
- private static final Map SIZE_CHARS_MAPPING = Map.ofEntries(
- Map.entry(-6, "§"),
- Map.entry(2, "!.,:;i|¡'"),
- Map.entry(3, "`lìí’‘"),
- Map.entry(4, " I[]tï×"),
- Map.entry(5, "\"()*<>fk{}"),
- Map.entry(7, "@~®©«»"),
- Map.entry(9, "├└")
- );
+ /*
+ * Note : this field is for easy listing of all characters with special sizes. It will all be reported to
+ * #CHAR_SIZES on class initialization for optimization.
+ */
+ private static final Map SIZE_CHARS_MAPPING = Map.ofEntries(
+ Map.entry(-6, "§"),
+ Map.entry(2, "!.,:;i|¡'"),
+ Map.entry(3, "`lìí’‘"),
+ Map.entry(4, " I[]tï×"),
+ Map.entry(5, "\"()*<>fk{}"),
+ Map.entry(7, "@~®©«»"),
+ Map.entry(9, "├└")
+ );
- /**
- * The default text pixel width for a character in the default Minecraft font.
- * If a character has another width, it should be found in {@link #CHAR_SIZES}.
- */
- public static final int DEFAULT_CHAR_SIZE = 6;
+ /**
+ * The default text pixel width for a character in the default Minecraft font.
+ * If a character has another width, it should be found in {@link #CHAR_SIZES}.
+ */
+ public static final int DEFAULT_CHAR_SIZE = 6;
- /**
- * Mapping indicating the text pixel with for specific characters in the default Minecraft font.
- * If a character doesn’t have a mapping in this map, then its width is {@link #DEFAULT_CHAR_SIZE}.
- */
- public static final Map CHAR_SIZES;
- static {
- Map charSizes = new HashMap<>();
- for (var e : SIZE_CHARS_MAPPING.entrySet()) {
- int size = e.getKey();
- for (char c : e.getValue().toCharArray()) {
- charSizes.put(c, size);
- }
- }
- CHAR_SIZES = Collections.unmodifiableMap(charSizes);
- }
+ /**
+ * Mapping indicating the text pixel with for specific characters in the default Minecraft font.
+ * If a character doesn’t have a mapping in this map, then its width is {@link #DEFAULT_CHAR_SIZE}.
+ */
+ public static final Map CHAR_SIZES;
+ static {
+ Map charSizes = new HashMap<>();
+ for (var e : SIZE_CHARS_MAPPING.entrySet()) {
+ int size = e.getKey();
+ for (char c : e.getValue().toCharArray()) {
+ charSizes.put(c, size);
+ }
+ }
+ CHAR_SIZES = Collections.unmodifiableMap(charSizes);
+ }
- /**
- * The default width of the Minecraft Java Edition chat window, in text pixels.
- */
- public static final int DEFAULT_CHAT_WIDTH = 320;
+ /**
+ * The default width of the Minecraft Java Edition chat window, in text pixels.
+ */
+ public static final int DEFAULT_CHAT_WIDTH = 320;
- /**
- * The width of a Minecraft sign, in text pixels.
- */
- public static final int SIGN_WIDTH = 90;
+ /**
+ * The width of a Minecraft sign, in text pixels.
+ */
+ public static final int SIGN_WIDTH = 90;
- /**
- * The width of a Minecraft book content, in text pixels.
- */
- public static final int BOOK_WIDTH = 116;
+ /**
+ * The width of a Minecraft book content, in text pixels.
+ */
+ public static final int BOOK_WIDTH = 116;
- /**
- * The width of a Minecraft server MOTD message, in text pixels.
- */
- public static final int MOTD_WIDTH = 270;
+ /**
+ * The width of a Minecraft server MOTD message, in text pixels.
+ */
+ public static final int MOTD_WIDTH = 270;
- /**
- * The width of a Minecraft Bedrock Edition form button, in text pixels.
- */
- public static final int BEDROCK_FORM_WIDE_BUTTON = 178;
+ /**
+ * The width of a Minecraft Bedrock Edition form button, in text pixels.
+ */
+ public static final int BEDROCK_FORM_WIDE_BUTTON = 178;
- /**
- * The default number of character per lines for the console.
- */
- public static final int CONSOLE_NB_CHAR_DEFAULT = 50;
+ /**
+ * The default number of character per lines for the console.
+ */
+ public static final int CONSOLE_NB_CHAR_DEFAULT = 50;
- /**
- * Create a {@link Chat} that is a cliquable URL link.
- * It is equivalent to the HTML {@code } tag pointing to another page.
- * @param text the link text.
- * @param url the destination url. must starts with {@code http} or {@code https}.
- * @return a {@link Chat} that is a cliquable URL link.
- * @deprecated it uses String for displayed text. Use {@link Chat#clickableURL(Chat, String)} instead.
- */
- @Deprecated(forRemoval = true, since = "2022-07-27")
- public static FormatableChat createURLLink(String text, String url) {
- return Chat.clickableURL(text == null ? null : Chat.legacyText(text), url);
- }
-
- /**
- * Create a {@link Chat} that is a cliquable URL link.
- * It is equivalent to the HTML {@code } tag pointing to another page.
- * @param text the link text.
- * @param url the destination url. must starts with {@code http} or {@code https}.
- * @param hoverText the text displayed when hovering the link.
- * @return a {@link Chat} that is a cliquable URL link.
- * @deprecated it uses String for displayed text. Use {@link Chat#clickableURL(Chat, String, Chat)} instead.
- */
- @Deprecated(forRemoval = true, since = "2022-07-27")
- public static FormatableChat createURLLink(String text, String url, String hoverText) {
- return Chat.clickableURL(text == null ? null : Chat.legacyText(text), url, hoverText == null ? null : Chat.legacyText(hoverText));
- }
-
- /**
- * Create a {@link Chat} that is a cliquable command link.
- * When the players clicks on it, they will execute the command.
- * @param text the link text.
- * @param commandWithSlash the command to execute when clicked.
- * @param hoverText the text displayed when hovering the link.
- * @return a {@link Chat} that is a cliquable command link.
- * @deprecated it uses String for displayed text. Use {@link Chat#clickableCommand(Chat, String, Chat)} instead.
- */
- @Deprecated(forRemoval = true, since = "2022-07-27")
- public static FormatableChat createCommandLink(String text, String commandWithSlash, String hoverText) {
- return Chat.clickableCommand(text == null ? null : Chat.legacyText(text), commandWithSlash, hoverText == null ? null : Chat.legacyText(hoverText));
- }
-
- /**
- * Create a {@link Chat} that is a cliquable command link.
- * When the players clicks on it, they will execute the command.
- * @param text the link text.
- * @param commandWithSlash the command to execute when clicked.
- * @param hoverText the text displayed when hovering the link.
- * @return a {@link Chat} that is a cliquable command link.
- * @deprecated it uses String for displayed text. Use {@link Chat#clickableCommand(Chat, String, Chat)} instead.
- */
- @Deprecated(forRemoval = true, since = "2022-07-27")
- public static FormatableChat createCommandLink(String text, String commandWithSlash, Chat hoverText) {
- return Chat.clickableCommand(text == null ? null : Chat.legacyText(text), commandWithSlash, hoverText);
- }
-
- /**
- * Create a {@link Chat} that is a cliquable command suggestion.
- * When the players clicks on it, they will execute the command.
- * @param inner the link text.
- * @param commandWithSlash the command to put in the chat box when clicked.
- * @param hover the text displayed when hovering the link.
- * @return a {@link Chat} that is a cliquable command suggestion.
- * @deprecated it uses String for displayed text. Use {@link Chat#clickableSuggest(Chat, String, Chat)} instead.
- */
- @Deprecated(forRemoval = true, since = "2022-07-27")
- public static FormatableChat createCommandSuggest(String inner, String commandWithSlash, String hover) {
- return Chat.clickableSuggest(inner == null ? null : Chat.legacyText(inner), commandWithSlash, hover == null ? null : Chat.legacyText(hover));
- }
-
- /**
- * Create a {@link Chat} that is a cliquable command suggestion.
- * When the players clicks on it, they will execute the command.
- * @param inner the link text.
- * @param commandWithSlash the command to put in the chat box when clicked.
- * @param hover the text displayed when hovering the link.
- * @return a {@link Chat} that is a cliquable command suggestion.
- * @deprecated it uses String for displayed text. Use {@link Chat#clickableSuggest(Chat, String, Chat)} instead.
- */
- @Deprecated(forRemoval = true, since = "2022-07-27")
- public static FormatableChat createCommandSuggest(String inner, String commandWithSlash, Chat hover) {
- return Chat.clickableSuggest(inner == null ? null : Chat.legacyText(inner), commandWithSlash, hover);
- }
-
-
-
-
-
-
- /**
- * Create a page navigator with clickable page numbers for the chat.
- * @param prefix the text to put before the
- * @param cmdFormat the command with %d inside to be replaced with the page number (must start with slash)
- * @param currentPage the current page number (it is highlighted, and the pages around are displayed, according to
- * {@code nbPagesToDisplay}).
- * @param nbPages the number of pages.
- * @param nbPagesToDisplay the number of pages to display around the first page, the last page and the
- * {@code currentPage}.
- * @return a {@link Chat} containging the created page navigator.
- */
- public static Chat createPagination(String prefix, String cmdFormat, int currentPage, int nbPages, int nbPagesToDisplay) {
- Set pagesToDisplay = new TreeSet<>();
-
- for (int i = 0; i < nbPagesToDisplay && i < nbPages && nbPages - i > 0; i++) {
- pagesToDisplay.add(i + 1);
- pagesToDisplay.add(nbPages - i);
- }
-
- for (int i = currentPage - nbPagesToDisplay + 1; i < currentPage + nbPagesToDisplay; i++) {
- if (i > 0 && i <= nbPages)
- pagesToDisplay.add(i);
- }
-
- Chat d = ChatStatic.chat().thenLegacyText(prefix);
- boolean first = true;
- int previous = 0;
-
- for (int page : pagesToDisplay) {
- if (!first) {
- if (page == previous + 1) {
- d.thenText(" ");
- }
- else {
- if (cmdFormat.endsWith("%d")) {
- d.thenText(" ");
- d.then(Chat.clickableSuggest(Chat.text("..."), cmdFormat.substring(0, cmdFormat.length() - 2), Chat.text("Choisir la page")));
- d.thenText(" ");
- }
- else
- d.thenText(" ... ");
- }
- }
- else
- first = false;
-
- FormatableChat pDisp = Chat.clickableCommand(Chat.text(page), String.format(cmdFormat, page), Chat.text("Aller à la page " + page));
- if (page == currentPage) {
- pDisp.highlightedCommandColor();
- }
- d.then(pDisp);
-
- previous = page;
- }
-
-
- return d;
- }
-
-
-
-
-
-
-
-
- /**
- * @param decorationColor support null values
- */
- public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean console) {
- return centerText(text, repeatedChar, decorationColor, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
- }
- public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean console, int maxWidth) {
- return centerText(text, repeatedChar, decorationColor, false, console, maxWidth);
- }
-
- /**
- * @param decorationColor support null values
- */
- public static Chat centerText(Chat text, char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console, int maxWidth) {
-
- int textWidth = componentWidth(text.getAdv(), console);
-
- if (textWidth > maxWidth)
- return text;
-
- int repeatedCharWidth = charW(repeatedChar, console, decorationBold);
- int sideWidth = (maxWidth - textWidth) / 2;
- int sideNbChar = sideWidth / repeatedCharWidth;
-
- if (sideNbChar == 0)
- return text;
-
- String sideChars = repeatedChar(repeatedChar, sideNbChar);
- FormatableChat side = ChatStatic.text(sideChars).color(decorationColor);
- if (decorationBold)
- side.bold();
-
- Chat d = Chat.chat()
- .then(side)
- .then(text);
- if (repeatedChar != ' ')
- d.then(side);
-
- return d;
-
- }
-
- public static Chat leftText(Chat text, char repeatedChar, TextColor decorationColor, int nbLeft, boolean console) {
- return leftText(text, repeatedChar, decorationColor, nbLeft, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
- }
-
- public static Chat leftText(Chat text, char repeatedChar, TextColor decorationColor, int nbLeft, boolean console, int maxWidth) {
-
- int textWidth = componentWidth(text.getAdv(), console);
- int repeatedCharWidth = charW(repeatedChar, console, false);
- int leftWidth = nbLeft * repeatedCharWidth;
-
- if (textWidth + leftWidth > maxWidth)
- return text;
-
- int rightNbChar = (maxWidth - (textWidth + leftWidth)) / repeatedCharWidth;
-
- Chat d = ChatStatic.chat()
- .then(ChatStatic.text(repeatedChar(repeatedChar, nbLeft)).color(decorationColor))
- .then(text);
- if (repeatedChar != ' ') {
- d.then(ChatStatic.text(repeatedChar(repeatedChar, rightNbChar)).color(decorationColor));
- }
- return d;
-
- }
-
- public static Chat rightText(Chat text, char repeatedChar, TextColor decorationColor, int nbRight, boolean console) {
- return rightText(text, repeatedChar, decorationColor, nbRight, console, console ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
- }
-
- public static Chat rightText(Chat text, char repeatedChar, TextColor decorationColor, int nbRight,
- boolean console, int maxWidth) {
-
- int textWidth = componentWidth(text.getAdv(), console);
- int repeatedCharWidth = charW(repeatedChar, console, false);
- int rightWidth = nbRight * repeatedCharWidth;
-
- if (textWidth + rightWidth > maxWidth)
- return text;
-
- int leftNbChar = (maxWidth - (textWidth + rightWidth)) / repeatedCharWidth;
-
- Chat d = ChatStatic.chat()
- .then(ChatStatic.text(repeatedChar(repeatedChar, leftNbChar)).color(decorationColor))
- .then(text);
- if (repeatedChar != ' ') {
- d.then(ChatStatic.text(repeatedChar(repeatedChar, nbRight)).color(decorationColor));
- }
- return d;
-
- }
-
- public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean console) {
- return emptyLine(repeatedChar, decorationColor, false, console);
- }
-
- public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console) {
- return emptyLine(repeatedChar, decorationColor, decorationBold, console, (console) ? CONSOLE_NB_CHAR_DEFAULT : DEFAULT_CHAT_WIDTH);
- }
-
- public static Chat emptyLine(char repeatedChar, TextColor decorationColor, boolean decorationBold, boolean console, int maxWidth) {
- int count = maxWidth / charW(repeatedChar, console, decorationBold);
- FormatableChat line = ChatStatic.text(repeatedChar(repeatedChar, count)).color(decorationColor);
- if (decorationBold)
- line.bold();
- return line;
- }
-
- private static String repeatedChar(char repeatedChar, int count) {
- char[] c = new char[count];
- Arrays.fill(c, repeatedChar);
- return new String(c);
- }
-
-
-
-
-
-
-
- public static int componentWidth(Component component, boolean console) {
- return componentWidth(component, console, false);
- }
-
- public static int componentWidth(Component component, boolean console, boolean parentBold) {
- if (component == null)
- return 0;
-
- int count = 0;
-
- State currentBold = component.style().decoration(TextDecoration.BOLD);
- boolean actuallyBold = childBold(parentBold, currentBold);
-
- if (component instanceof TextComponent) {
- count += strWidth(((TextComponent)component).content(), console, actuallyBold);
- }
- else if (component instanceof TranslatableComponent) {
- for (Component c : ((TranslatableComponent)component).args())
- count += componentWidth(c, console, actuallyBold);
- }
- for (Component c : component.children())
- count += componentWidth(c, console, actuallyBold);
+ /**
+ * Create a page navigator with clickable page numbers for the chat.
+ * @param prefix the text to put before the
+ * @param cmdFormat the command with %d inside to be replaced with the page number (must start with slash)
+ * @param currentPage the current page number (it is highlighted, and the pages around are displayed, according to
+ * {@code nbPagesToDisplay}).
+ * @param nbPages the number of pages.
+ * @param nbPagesToDisplay the number of pages to display around the first page, the last page and the
+ * {@code currentPage}.
+ * @return a {@link Chat} containging the created page navigator.
+ */
+ public static Chat createPagination(String prefix, String cmdFormat, int currentPage, int nbPages, int nbPagesToDisplay) {
+ Set pagesToDisplay = new TreeSet<>();
- return count;
- }
-
- private static boolean childBold(boolean parent, TextDecoration.State child) {
- return (parent && child != State.FALSE) || (!parent && child == State.TRUE);
- }
+ for (int i = 0; i < nbPagesToDisplay && i < nbPages && nbPages - i > 0; i++) {
+ pagesToDisplay.add(i + 1);
+ pagesToDisplay.add(nbPages - i);
+ }
- public static int strWidth(String str, boolean console, boolean bold) {
- int count = 0;
- for (char c : str.toCharArray())
- count += charW(c, console, bold);
- return Math.max(count, 0);
- }
+ for (int i = currentPage - nbPagesToDisplay + 1; i < currentPage + nbPagesToDisplay; i++) {
+ if (i > 0 && i <= nbPages)
+ pagesToDisplay.add(i);
+ }
- public static int charW(char c, boolean console, boolean bold) {
- if (console)
- return (c == '§') ? -1 : 1;
- return CHAR_SIZES.getOrDefault(c, DEFAULT_CHAR_SIZE) + (bold ? 1 : 0);
- }
-
-
-
-
-
-
-
-
- public static List wrapInLimitedPixelsToChat(String legacyText, int pixelWidth) {
- return wrapInLimitedPixels(legacyText, pixelWidth).stream()
- .map(ChatStatic::legacyText)
- .collect(Collectors.toList());
- }
-
- public static List wrapInLimitedPixels(String legacyText, int pixelWidth) {
- List lines = new ArrayList<>();
-
- legacyText += "\n"; // workaround to force algorithm to compute last lines;
-
- String currentLine = "";
- int currentLineSize = 0;
- int index = 0;
-
- StringBuilder currentWord = new StringBuilder();
- int currentWordSize = 0;
- boolean bold = false;
- boolean firstCharCurrentWordBold = false;
-
- do {
- char c = legacyText.charAt(index);
- if (c == ChatColor.COLOR_CHAR && index < legacyText.length() - 1) {
- currentWord.append(c);
- c = legacyText.charAt(++index);
- currentWord.append(c);
-
- if (c == 'l' || c == 'L') // bold
- bold = true;
- if ((c >= '0' && c <= '9') // reset bold
- || (c >= 'a' && c <= 'f')
- || (c >= 'A' && c <= 'F')
- || c == 'r' || c == 'R'
- || c == 'x' || c == 'X')
- bold = false;
-
- }
- else if (c == ' ') {
- if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
- lines.add(currentLine);
- String lastStyle = ChatColorUtil.getLastColors(currentLine);
- if (currentWord.charAt(0) == ' ') {
- currentWord = new StringBuilder(currentWord.substring(1));
- currentWordSize -= charW(' ', false, firstCharCurrentWordBold);
- }
- currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
- currentLineSize = currentWordSize;
- }
- else {
- currentLine += currentWord;
- currentLineSize += currentWordSize;
- }
- currentWord = new StringBuilder("" + c);
- currentWordSize = charW(c, false, bold);
- firstCharCurrentWordBold = bold;
- }
- else if (c == '\n') {
- if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
- lines.add(currentLine);
- String lastStyle = ChatColorUtil.getLastColors(currentLine);
- if (currentWord.charAt(0) == ' ') {
- currentWord = new StringBuilder(currentWord.substring(1));
- }
- currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
- }
- else {
- currentLine += currentWord;
- }
- // wrap after
- lines.add(currentLine);
- String lastStyle = ChatColorUtil.getLastColors(currentLine);
-
- currentLine = lastStyle.equals("§r") ? "" : lastStyle;
- currentLineSize = 0;
- currentWord = new StringBuilder();
- currentWordSize = 0;
- firstCharCurrentWordBold = bold;
- }
- else {
- currentWord.append(c);
- currentWordSize += charW(c, false, bold);
- }
-
- } while(++index < legacyText.length());
-
-
-
-
-
-
- return lines;
- }
-
-
-
-
+ Chat d = ChatStatic.chat().thenLegacyText(prefix);
+ boolean first = true;
+ int previous = 0;
- public static List renderTable(List> rows, String space, boolean console) {
- List> compRows = new ArrayList<>(rows.size());
- for (List row : rows) {
- List compRow = new ArrayList<>(row.size());
- for (Chat c : row) {
- compRow.add(c.getAdv());
- }
- compRows.add(compRow);
- }
- return renderTableComp(compRows, space, console);
- }
-
-
- public static List renderTableComp(List> rows, String space, boolean console) {
- // determine columns width
- List nbPixelPerColumn = new ArrayList<>();
- for (List row : rows) {
- for (int i = 0; i < row.size(); i++) {
- int w = componentWidth(row.get(i), console);
- if (nbPixelPerColumn.size() <= i)
- nbPixelPerColumn.add(w);
- else if (nbPixelPerColumn.get(i) < w)
- nbPixelPerColumn.set(i, w);
- }
- }
-
- // create the lines with appropriate spacing
- List spacedRows = new ArrayList<>(rows.size());
- for (List row : rows) {
- Chat spacedRow = Chat.chat();
- for (int i = 0; i < row.size() - 1; i++) {
- int w = componentWidth(row.get(i), console);
- int padding = nbPixelPerColumn.get(i) - w;
- spacedRow.then(row.get(i));
- spacedRow.then(customWidthSpace(padding, console));
- spacedRow.thenText(space);
- }
- if (!row.isEmpty())
- spacedRow.then(row.get(row.size() - 1));
- spacedRows.add(spacedRow.getAdv());
- }
-
- return spacedRows;
- }
-
-
-
-
- public static Component customWidthSpace(int width, boolean console) {
- if (console)
- return Chat.text(" ".repeat(width)).getAdv();
- return switch (width) {
- case 0, 1 -> Component.empty();
- case 2 -> Chat.text(".").black().getAdv();
- case 3 -> Chat.text("`").black().getAdv();
- case 6 -> Chat.text(". ").black().getAdv();
- case 7 -> Chat.text("` ").black().getAdv();
- case 11 -> Chat.text("` ").black().getAdv();
- default -> {
- int nbSpace = width / 4;
- int nbBold = width % 4;
- int nbNotBold = nbSpace - nbBold;
- if (nbNotBold > 0) {
- if (nbBold > 0) {
- yield Chat.text(" ".repeat(nbNotBold)).bold(false)
- .then(Chat.text(" ".repeat(nbBold)).bold(true))
- .getAdv();
- }
- else
- yield Chat.text(" ".repeat(nbNotBold)).bold(false).getAdv();
- }
- else if (nbBold > 0) {
- yield Chat.text(" ".repeat(nbBold)).bold(true).getAdv();
- }
- throw new IllegalStateException("Should not be here (width=" + width + "; nbSpace=" + nbSpace + "; nbBold=" + nbBold + "; nbNotBold=" + nbNotBold + ")");
- }
- };
- // "." is 2 px
- // "`" is 3 px
- // " " is 4 px
- // 0 ""
- // 1 ""
- // 2 "."
- // 3 "`"
- // 4 " "
- // 5 "§l "
- // 6 ". "
- // 7 "` "
- // 8 " "
- // 9 " §l "
- // 10 "§l "
- // 11 "` "
- // 12 " "
- }
-
-
-
-
-
+ for (int page : pagesToDisplay) {
+ if (!first) {
+ if (page == previous + 1) {
+ d.thenText(" ");
+ }
+ else {
+ if (cmdFormat.endsWith("%d")) {
+ d.thenText(" ");
+ d.thenCommandSuggest(Chat.text("..."), cmdFormat.substring(0, cmdFormat.length() - 2), Chat.text("Choisir la page"));
+ d.thenText(" ");
+ }
+ else
+ d.thenText(" ... ");
+ }
+ }
+ else
+ first = false;
- private static final String PROGRESS_BAR_START = "[";
- private static final String PROGRESS_BAR_END = "]";
- private static final TextColor PROGRESS_BAR_EMPTY_COLOR = NamedTextColor.DARK_GRAY;
- private static final char PROGRESS_BAR_EMPTY_CHAR = '.';
- private static final char PROGRESS_BAR_FULL_CHAR = '|';
-
- public static Chat progressBar(double[] values, TextColor[] colors, double total, int pixelWidth, boolean console) {
-
- // 1. Compute char size for each values
- int progressPixelWidth = pixelWidth - strWidth(PROGRESS_BAR_START + PROGRESS_BAR_END, console, false);
- int charPixelWidth = charW(PROGRESS_BAR_EMPTY_CHAR, console, false);
-
- assert charPixelWidth == charW(PROGRESS_BAR_FULL_CHAR, console, false) : "PROGRESS_BAR_EMPTY_CHAR and PROGRESS_BAR_FULL_CHAR should have the same pixel width according to #charW(...)";
-
- int progressCharWidth = progressPixelWidth / charPixelWidth;
-
- int[] sizes = new int[values.length];
- double sumValuesBefore = 0;
- int sumSizesBefore = 0;
+ FormatableChat pDisp = Chat.clickableCommand(Chat.text(page), String.format(cmdFormat, page), Chat.text("Aller à la page " + page));
+ if (page == currentPage) {
+ pDisp.highlightedCommandColor();
+ }
+ d.then(pDisp);
- for (int i = 0; i < values.length; i++) {
- sumValuesBefore += values[i];
- int charPosition = Math.min((int) Math.round(progressCharWidth * sumValuesBefore / total), progressCharWidth);
- sizes[i] = charPosition - sumSizesBefore;
- sumSizesBefore += sizes[i];
- }
-
- // 2. Generate rendered text
- Chat c = ChatStatic.text(PROGRESS_BAR_START);
-
- int sumSizes = 0;
- for (int i = 0; i < sizes.length; i++) {
- sumSizes += sizes[i];
+ previous = page;
+ }
- FormatableChat subC = ChatStatic.text(repeatedChar(PROGRESS_BAR_FULL_CHAR, sizes[i]));
- if (colors != null && i < colors.length && colors[i] != null)
- subC.color(colors[i]);
-
- c.then(subC);
- }
-
- return c
- .then(ChatStatic.text(repeatedChar(PROGRESS_BAR_EMPTY_CHAR, progressCharWidth - sumSizes))
- .color(PROGRESS_BAR_EMPTY_COLOR))
- .thenText(PROGRESS_BAR_END);
- }
-
- public static Chat progressBar(double value, TextColor color, double total, int pixelWidth, boolean console) {
- return progressBar(new double[] { value }, new TextColor[] { color }, total, pixelWidth, console);
- }
-
-
-
-
-
-
-
-
-
+ return d;
+ }
-
-
-
-
- public static String truncatePrefix(String prefix, int maxLength) {
- if (prefix.length() > maxLength) {
- String lastColor = ChatColorUtil.getLastColors(prefix);
- prefix = truncateAtLengthWithoutReset(prefix, maxLength);
- if (!ChatColorUtil.getLastColors(prefix).equals(lastColor))
- prefix = truncateAtLengthWithoutReset(prefix, maxLength - lastColor.length()) + lastColor;
- }
- return prefix;
- }
-
-
- public static String truncateAtLengthWithoutReset(String prefix, int l) {
- if (prefix.length() > l) {
- prefix = prefix.substring(0, l);
- if (prefix.endsWith("§"))
- prefix = prefix.substring(0, prefix.length()-1);
- }
- return prefix;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- private static final String TREE_MIDDLE_CONNECTED = "├";
- private static final String TREE_END_CONNECTED = "└";
- private static final String TREE_MIDDLE_OPEN = "│§0`§r";
- private static final String TREE_END_OPEN = "§0```§r";
- private static final String TREE_MIDDLE_OPEN_CONSOLE = "│";
- private static final String TREE_END_OPEN_CONSOLE = " "; // nbsp
-
- /**
- * Generate a tree view based on the tree structure {@code node}.
- *
- * Each element in the returned list represent 1 line of the tree view.
- * Thus, the caller may send each line separately or at once depending of the quantity of data.
- * @return A array of component, each element being a single line.
- */
- public static List treeView(DisplayTreeNode node, boolean console) {
- List ret = new ArrayList<>();
-
- ret.add(ChatStatic.chat()
- .then(node.component));
-
- for (int i = 0; i < node.children.size(); i++) {
- List childComponents = treeView(node.children.get(i), console);
- boolean last = i == node.children.size() - 1;
- for (int j = 0; j < childComponents.size(); j++) {
-
- String prefix = last ? (j == 0 ? TREE_END_CONNECTED : (console ? TREE_END_OPEN_CONSOLE : TREE_END_OPEN))
- : (j == 0 ? TREE_MIDDLE_CONNECTED : (console ? TREE_MIDDLE_OPEN_CONSOLE : TREE_MIDDLE_OPEN));
-
- ret.add(ChatStatic.text(prefix)
- .then(childComponents.get(j)));
- }
- }
-
-
- return ret;
- }
-
-
-
-
-
- public static class DisplayTreeNode {
- public final Chat component;
- public final List children = new ArrayList<>();
-
- public DisplayTreeNode(Chat cmp) {
- component = cmp;
- }
-
- public DisplayTreeNode addChild(DisplayTreeNode child) {
- children.add(child);
- return this;
- }
- }
+
+
+
+
+
+
+
+ /* package */ static String repeatedChar(char repeatedChar, int count) {
+ char[] c = new char[count];
+ Arrays.fill(c, repeatedChar);
+ return new String(c);
+ }
+
+
+ /**
+ * Compute the width of the provided component.
+ * @param component the component to compute the width.
+ * @param console true to compute the width when displayed on console (so it will count the characters),
+ * false to compute the width when displayed in game (so it will count the pixels).
+ * @return the width of the provided component.
+ */
+ public static int componentWidth(Component component, boolean console) {
+ return componentWidth(component, console, false);
+ }
+
+ /**
+ * Compute the width of the provided component, with extra information about the parent component.
+ * @param component the component to compute the width.
+ * @param console true to compute the width when displayed on console (so it will count the characters),
+ * false to compute the width when displayed in game (so it will count the pixels).
+ * @param parentBold if the component inherits a bold styling from an eventual parent component.
+ * @return the width of the provided component.
+ */
+ public static int componentWidth(Component component, boolean console, boolean parentBold) {
+ if (component == null)
+ return 0;
+
+ int count = 0;
+
+ State currentBold = component.style().decoration(TextDecoration.BOLD);
+ boolean actuallyBold = childBold(parentBold, currentBold);
+
+ if (component instanceof TextComponent) {
+ count += strWidth(((TextComponent)component).content(), console, actuallyBold);
+ }
+ else if (component instanceof TranslatableComponent) {
+ for (Component c : ((TranslatableComponent)component).args())
+ count += componentWidth(c, console, actuallyBold);
+ }
+
+ for (Component c : component.children())
+ count += componentWidth(c, console, actuallyBold);
+
+ return count;
+ }
+
+ private static boolean childBold(boolean parent, TextDecoration.State child) {
+ return (parent && child != State.FALSE) || (!parent && child == State.TRUE);
+ }
+
+ /**
+ * Compute the width of the provided text.
+ * @param str the text to compute the width.
+ * @param console true to compute the width when displayed on console (so it will count the characters),
+ * false to compute the width when displayed in game (so it will count the pixels).
+ * @param bold if the text is bold (may change its width).
+ * @return the width of the provided text.
+ */
+ public static int strWidth(String str, boolean console, boolean bold) {
+ int count = 0;
+ for (char c : str.toCharArray())
+ count += charW(c, console, bold);
+ return Math.max(count, 0);
+ }
+
+ /**
+ * Compute the width of the provided character.
+ *
+ * It uses the mapping in {@link #CHAR_SIZES} for in-game display. For console, every character is size 1.
+ * The {@code §} character is treated has a negative value, to make legacy codes take 0 width.
+ * @param c the character to compute the width.
+ * @param console true to compute the width when displayed on console (so it will count the characters),
+ * false to compute the width when displayed in game (so it will count the pixels).
+ * @param bold if the character is bold (may change its width).
+ * @return the width of the provided character.
+ */
+ public static int charW(char c, boolean console, boolean bold) {
+ if (console)
+ return (c == '§') ? -1 : 1;
+ return CHAR_SIZES.getOrDefault(c, DEFAULT_CHAR_SIZE) + (bold ? 1 : 0);
+ }
+
+
+ /**
+ * Wraps the provided text in multiple lines, taking into account the legacy formating.
+ *
+ * This method only takes into account IG text width. Use a regular text-wrapper for console instead.
+ * @param legacyText the text to wrap.
+ * @param pixelWidth the width in which the text must fit.
+ * @return the wrapped text in a {@link List} of {@link Chat} components.
+ */
+ public static List wrapInLimitedPixelsToChat(String legacyText, int pixelWidth) {
+ return wrapInLimitedPixels(legacyText, pixelWidth).stream()
+ .map(ChatStatic::legacyText)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Wraps the provided text in multiple lines, taking into account the legacy formating.
+ *
+ * This method only takes into account IG text width. Use a regular text-wrapper for console instead.
+ * @param legacyText the text to wrap.
+ * @param pixelWidth the width in which the text must fit.
+ * @return the wrapped text in a {@link List} of line.
+ */
+ public static List wrapInLimitedPixels(String legacyText, int pixelWidth) {
+ List lines = new ArrayList<>();
+
+ legacyText += "\n"; // workaround to force algorithm to compute last lines;
+
+ String currentLine = "";
+ int currentLineSize = 0;
+ int index = 0;
+
+ StringBuilder currentWord = new StringBuilder();
+ int currentWordSize = 0;
+ boolean bold = false;
+ boolean firstCharCurrentWordBold = false;
+
+ do {
+ char c = legacyText.charAt(index);
+ if (c == ChatColor.COLOR_CHAR && index < legacyText.length() - 1) {
+ currentWord.append(c);
+ c = legacyText.charAt(++index);
+ currentWord.append(c);
+
+ if (c == 'l' || c == 'L') // bold
+ bold = true;
+ if ((c >= '0' && c <= '9') // reset bold
+ || (c >= 'a' && c <= 'f')
+ || (c >= 'A' && c <= 'F')
+ || c == 'r' || c == 'R'
+ || c == 'x' || c == 'X')
+ bold = false;
+
+ }
+ else if (c == ' ') {
+ if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
+ lines.add(currentLine);
+ String lastStyle = ChatColorUtil.getLastColors(currentLine);
+ if (currentWord.charAt(0) == ' ') {
+ currentWord = new StringBuilder(currentWord.substring(1));
+ currentWordSize -= charW(' ', false, firstCharCurrentWordBold);
+ }
+ currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
+ currentLineSize = currentWordSize;
+ }
+ else {
+ currentLine += currentWord;
+ currentLineSize += currentWordSize;
+ }
+ currentWord = new StringBuilder("" + c);
+ currentWordSize = charW(c, false, bold);
+ firstCharCurrentWordBold = bold;
+ }
+ else if (c == '\n') {
+ if (currentLineSize + currentWordSize > pixelWidth && currentLineSize > 0) { // wrap before word
+ lines.add(currentLine);
+ String lastStyle = ChatColorUtil.getLastColors(currentLine);
+ if (currentWord.charAt(0) == ' ') {
+ currentWord = new StringBuilder(currentWord.substring(1));
+ }
+ currentLine = (lastStyle.equals("§r") ? "" : lastStyle) + currentWord;
+ }
+ else {
+ currentLine += currentWord;
+ }
+ // wrap after
+ lines.add(currentLine);
+ String lastStyle = ChatColorUtil.getLastColors(currentLine);
+
+ currentLine = lastStyle.equals("§r") ? "" : lastStyle;
+ currentLineSize = 0;
+ currentWord = new StringBuilder();
+ currentWordSize = 0;
+ firstCharCurrentWordBold = bold;
+ }
+ else {
+ currentWord.append(c);
+ currentWordSize += charW(c, false, bold);
+ }
+
+ } while(++index < legacyText.length());
+
+
+
+
+
+
+ return lines;
+ }
+
+
+ /**
+ * Try to render a matrix of {@link Chat} components into a table in the chat or console.
+ * @param data the component, in the form of {@link List} of {@link List} of {@link Chat}. The englobing list holds
+ * the table lines (line 0 being the top line). Each sublist holds the cells content (element 0 is the
+ * leftText one). The row lengths can be different.
+ * @param space a spacer to put between columns.
+ * @param console true to display the table on the console (character alignement), false in game chat (pixel
+ * alignment, much harder).
+ * @return a List containing each rendered line of the table.
+ */
+ public static List renderTable(List> data, String space, boolean console) {
+ List> compRows = new ArrayList<>(data.size());
+ for (List row : data) {
+ List compRow = new ArrayList<>(row.size());
+ for (Chat c : row) {
+ compRow.add(c.getAdv());
+ }
+ compRows.add(compRow);
+ }
+ return renderTableComp(compRows, space, console);
+ }
+
+
+ /**
+ * Try to render a matrix of {@link Component} components into a table in the chat or console.
+ * @param data the component, in the form of {@link List} of {@link List} of {@link Component}. The englobing list holds
+ * the table lines (line 0 being the top line). Each sublist holds the cells content (element 0 is the
+ * leftText one). The row lengths can be different.
+ * @param space a spacer to put between columns.
+ * @param console true to display the table on the console (character alignement), false in game chat (pixel
+ * alignment, much harder).
+ * @return a List containing each rendered line of the table.
+ */
+ public static List renderTableComp(List> data, String space, boolean console) {
+ // determine columns width
+ List nbPixelPerColumn = new ArrayList<>();
+ for (List row : data) {
+ for (int i = 0; i < row.size(); i++) {
+ int w = componentWidth(row.get(i), console);
+ if (nbPixelPerColumn.size() <= i)
+ nbPixelPerColumn.add(w);
+ else if (nbPixelPerColumn.get(i) < w)
+ nbPixelPerColumn.set(i, w);
+ }
+ }
+
+ // create the lines with appropriate spacing
+ List spacedRows = new ArrayList<>(data.size());
+ for (List row : data) {
+ Chat spacedRow = Chat.chat();
+ for (int i = 0; i < row.size() - 1; i++) {
+ int w = componentWidth(row.get(i), console);
+ int padding = nbPixelPerColumn.get(i) - w;
+ spacedRow.then(row.get(i));
+ spacedRow.then(customWidthSpace(padding, console));
+ spacedRow.thenText(space);
+ }
+ if (!row.isEmpty())
+ spacedRow.then(row.get(row.size() - 1));
+ spacedRows.add(spacedRow.getAdv());
+ }
+
+ return spacedRows;
+ }
+
+
+ /**
+ * Provides a component acting as a spacer of a specific width.
+ *
+ * The returned component contains mostly spaces. If it has visible characters, the component color will be set to
+ * black to be the least visible as possible.
+ *
+ * For console, the method returns a {@link Component} with a regular space repeated {@code width} times.
+ * For IG, the methods returns a {@link Component} with a combination of spaces and some small characters, with part
+ * of them bold. For some specific width, the returned {@link Component} may not have the intended width.
+ * @param width the width of the space to produce.
+ * @param console true if the spacer is intended to be displayed on the console, false if it’s in game chat.
+ * @return a component acting as a spacer of a specific width.
+ */
+ public static Component customWidthSpace(int width, boolean console) {
+ if (console)
+ return Chat.text(" ".repeat(width)).getAdv();
+ return switch (width) {
+ case 0, 1 -> Component.empty();
+ case 2 -> Chat.text(".").black().getAdv();
+ case 3 -> Chat.text("`").black().getAdv();
+ case 6 -> Chat.text(". ").black().getAdv();
+ case 7 -> Chat.text("` ").black().getAdv();
+ case 11 -> Chat.text("` ").black().getAdv();
+ default -> {
+ int nbSpace = width / 4;
+ int nbBold = width % 4;
+ int nbNotBold = nbSpace - nbBold;
+ if (nbNotBold > 0) {
+ if (nbBold > 0) {
+ yield Chat.text(" ".repeat(nbNotBold)).bold(false)
+ .then(Chat.text(" ".repeat(nbBold)).bold(true))
+ .getAdv();
+ }
+ else
+ yield Chat.text(" ".repeat(nbNotBold)).bold(false).getAdv();
+ }
+ else if (nbBold > 0) {
+ yield Chat.text(" ".repeat(nbBold)).bold(true).getAdv();
+ }
+ throw new IllegalStateException("Should not be here (width=" + width + "; nbSpace=" + nbSpace + "; nbBold=" + nbBold + "; nbNotBold=" + nbNotBold + ")");
+ }
+ };
+ // "." is 2 px
+ // "`" is 3 px
+ // " " is 4 px
+ // 0 ""
+ // 1 ""
+ // 2 "."
+ // 3 "`"
+ // 4 " "
+ // 5 "§l "
+ // 6 ". "
+ // 7 "` "
+ // 8 " "
+ // 9 " §l "
+ // 10 "§l "
+ // 11 "` "
+ // 12 " "
+ }
+
+
+
+
+
+
+ private static final String PROGRESS_BAR_START = "[";
+ private static final String PROGRESS_BAR_END = "]";
+ private static final TextColor PROGRESS_BAR_EMPTY_COLOR = NamedTextColor.DARK_GRAY;
+ private static final char PROGRESS_BAR_EMPTY_CHAR = '.';
+ private static final char PROGRESS_BAR_FULL_CHAR = '|';
+
+ /**
+ * Generate a (eventually multi-part) progress bar using text.
+ * @param values the values to render in the progress bar.
+ * @param colors the colors attributed to each values.
+ * @param total the total value of the progress bar.
+ * @param width the width in which the progress bar should fit (in pixel for IG, in character count for console)
+ * @param console true if the progress bar is intended to be displayed on the console, false if it’s in game chat.
+ * @return a progress bar using text.
+ */
+ public static Chat progressBar(double[] values, TextColor[] colors, double total, int width, boolean console) {
+
+ // 1. Compute char size for each values
+ int progressPixelWidth = width - strWidth(PROGRESS_BAR_START + PROGRESS_BAR_END, console, false);
+ int charPixelWidth = charW(PROGRESS_BAR_EMPTY_CHAR, console, false);
+
+ assert charPixelWidth == charW(PROGRESS_BAR_FULL_CHAR, console, false) : "PROGRESS_BAR_EMPTY_CHAR and PROGRESS_BAR_FULL_CHAR should have the same pixel width according to #charW(...)";
+
+ int progressCharWidth = progressPixelWidth / charPixelWidth;
+
+ int[] sizes = new int[values.length];
+ double sumValuesBefore = 0;
+ int sumSizesBefore = 0;
+
+ for (int i = 0; i < values.length; i++) {
+ sumValuesBefore += values[i];
+ int charPosition = Math.min((int) Math.round(progressCharWidth * sumValuesBefore / total), progressCharWidth);
+ sizes[i] = charPosition - sumSizesBefore;
+ sumSizesBefore += sizes[i];
+ }
+
+ // 2. Generate rendered text
+ Chat c = ChatStatic.text(PROGRESS_BAR_START);
+
+ int sumSizes = 0;
+ for (int i = 0; i < sizes.length; i++) {
+ sumSizes += sizes[i];
+
+ FormatableChat subC = ChatStatic.text(repeatedChar(PROGRESS_BAR_FULL_CHAR, sizes[i]));
+
+ if (colors != null && i < colors.length && colors[i] != null)
+ subC.color(colors[i]);
+
+ c.then(subC);
+ }
+
+ return c
+ .then(ChatStatic.text(repeatedChar(PROGRESS_BAR_EMPTY_CHAR, progressCharWidth - sumSizes))
+ .color(PROGRESS_BAR_EMPTY_COLOR))
+ .thenText(PROGRESS_BAR_END);
+ }
+
+ /**
+ * Generate a progress bar using text.
+ * @param value the value to render in the progress bar.
+ * @param color the color of the filled part of the bar.
+ * @param total the total value of the progress bar.
+ * @param width the width in which the progress bar should fit (in pixel for IG, in character count for console)
+ * @param console true if the progress bar is intended to be displayed on the console, false if it’s in game chat.
+ * @return a progress bar using text.
+ */
+ public static Chat progressBar(double value, TextColor color, double total, int width, boolean console) {
+ return progressBar(new double[] { value }, new TextColor[] { color }, total, width, console);
+ }
+
+
+ /**
+ * Truncate an eventually too long prefix (like team prefix or permission group prefix), keep the last color and
+ * format.
+ * @param prefix the prefix that eventually needs truncation.
+ * @param maxLength the maximum length of the prefix.
+ * @return a truncated prefix, with the last color kept.
+ */
+ public static String truncatePrefix(String prefix, int maxLength) {
+ if (prefix.length() > maxLength) {
+ String lastColor = ChatColorUtil.getLastColors(prefix);
+ prefix = truncateAtLengthWithoutReset(prefix, maxLength);
+ if (!ChatColorUtil.getLastColors(prefix).equals(lastColor))
+ prefix = truncateAtLengthWithoutReset(prefix, maxLength - lastColor.length()) + lastColor;
+ }
+ return prefix;
+ }
+
+ /**
+ * Truncate an eventually too long string, also taking care of removing an eventual {@code §} character leftText alone
+ * at the end.
+ * @param str the string to eventually truncate.
+ * @param maxLength the maximum length of the string.
+ * @return a truncated string.
+ */
+ public static String truncateAtLengthWithoutReset(String str, int maxLength) {
+ if (str.length() > maxLength) {
+ str = str.substring(0, maxLength);
+ if (str.endsWith("§"))
+ str = str.substring(0, str.length()-1);
+ }
+ return str;
+ }
+
+
}
diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java
index 576eaa3..dd67cf3 100644
--- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java
+++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java
@@ -7,7 +7,7 @@ import java.util.OptionalLong;
import java.util.Set;
import java.util.stream.LongStream;
-import fr.pandacube.lib.chat.ChatUtil.DisplayTreeNode;
+import fr.pandacube.lib.chat.ChatTreeNode;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedEntity;
import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
import fr.pandacube.lib.util.Log;
@@ -50,7 +50,7 @@ public abstract class PermEntity {
}
- public DisplayTreeNode debugPrefix() {
+ public ChatTreeNode debugPrefix() {
return Permissions.resolver.debugPrefix(name, type);
}
@@ -69,7 +69,7 @@ public abstract class PermEntity {
}
- public DisplayTreeNode debugSuffix() {
+ public ChatTreeNode debugSuffix() {
return Permissions.resolver.debugSuffix(name, type);
}
@@ -159,15 +159,15 @@ public abstract class PermEntity {
}
- public DisplayTreeNode debugPermission(String permission) {
+ public ChatTreeNode debugPermission(String permission) {
return debugPermission(permission, null, null);
}
- public DisplayTreeNode debugPermission(String permission, String server) {
+ public ChatTreeNode debugPermission(String permission, String server) {
return debugPermission(permission, server, null);
}
- public DisplayTreeNode debugPermission(String permission, String server, String world) {
+ public ChatTreeNode debugPermission(String permission, String server, String world) {
return Permissions.resolver.debugPermission(name, type, permission, server, world);
}
diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java
index 78b6204..3d7424d 100644
--- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java
+++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java
@@ -18,8 +18,7 @@ import com.google.common.cache.CacheBuilder;
import net.md_5.bungee.api.ChatColor;
import fr.pandacube.lib.chat.Chat;
-import fr.pandacube.lib.chat.ChatUtil;
-import fr.pandacube.lib.chat.ChatUtil.DisplayTreeNode;
+import fr.pandacube.lib.chat.ChatTreeNode;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedEntity;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedGroup;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer;
@@ -66,10 +65,10 @@ public class PermissionsResolver {
return getEffectiveData(name, type, DataType.SUFFIX);
}
- /* package */ DisplayTreeNode debugPrefix(String name, EntityType type) {
+ /* package */ ChatTreeNode debugPrefix(String name, EntityType type) {
return debugData(name, type, DataType.PREFIX);
}
- /* package */ DisplayTreeNode debugSuffix(String name, EntityType type) {
+ /* package */ ChatTreeNode debugSuffix(String name, EntityType type) {
return debugData(name, type, DataType.SUFFIX);
}
@@ -89,7 +88,7 @@ public class PermissionsResolver {
}
}
- private DisplayTreeNode debugData(String name, EntityType type, DataType dataType) {
+ private ChatTreeNode debugData(String name, EntityType type, DataType dataType) {
CachedEntity entity = (type == EntityType.User)
? backendReader.getCachedPlayer(UUID.fromString(name))
: backendReader.getCachedGroup(name);
@@ -104,7 +103,7 @@ public class PermissionsResolver {
if (resolutionResult.conflict) {
Log.warning("For data " + dataType + ":\n"
- + ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true).stream()
+ + resolutionResult.toDisplayTreeNode().render(true).stream()
.map(Chat::getLegacyText)
.collect(Collectors.joining(ChatColor.RESET + "\n")));
}
@@ -163,7 +162,7 @@ public class PermissionsResolver {
conflict = c != null;
}
- public DisplayTreeNode toDisplayTreeNode() {
+ public ChatTreeNode toDisplayTreeNode() {
Chat c = Chat.text(entity.name);
if (result == null)
c.then(Chat.text(" (non défini)").gray());
@@ -171,11 +170,11 @@ public class PermissionsResolver {
c.thenLegacyText(" \"" + ChatColor.RESET + result + ChatColor.RESET + "\"");
if (conflictMessage != null)
c.thenFailure(" " + conflictMessage);
- DisplayTreeNode node = new DisplayTreeNode(c);
+ ChatTreeNode node = new ChatTreeNode(c);
if (result == null && !conflict && !inheritances.isEmpty()) {
// there is nothing interesting to show on current or subnode
- node.children.add(new DisplayTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic()));
+ node.children.add(new ChatTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic()));
return node;
}
@@ -290,7 +289,7 @@ public class PermissionsResolver {
}
}
- /* package */ DisplayTreeNode debugPermission(String name, EntityType type, String permission, String server, String world) {
+ /* package */ ChatTreeNode debugPermission(String name, EntityType type, String permission, String server, String world) {
CachedEntity entity = (type == EntityType.User)
? backendReader.getCachedPlayer(UUID.fromString(name))
: backendReader.getCachedGroup(name);
@@ -306,7 +305,7 @@ public class PermissionsResolver {
if (resolutionResult.conflict) {
Log.warning("For permission " + permission + ":\n"
- + ChatUtil.treeView(resolutionResult.toDisplayTreeNode(), true).stream()
+ + resolutionResult.toDisplayTreeNode().render(true).stream()
.map(Chat::getLegacyText)
.collect(Collectors.joining(ChatColor.RESET + "\n")));
}
@@ -484,7 +483,7 @@ public class PermissionsResolver {
conflict = c != null;
}
- public DisplayTreeNode toDisplayTreeNode() {
+ public ChatTreeNode toDisplayTreeNode() {
Chat c = Chat.chat()
.then(result == PermState.UNDEFINED ? Chat.dataText("■") : result == PermState.GRANTED ? Chat.successText("✔") : Chat.failureText("✘"))
.then(Chat.text(entity instanceof CachedPlayer cp ? Permissions.playerNameGetter.apply(cp.playerId) : entity.name)
@@ -496,13 +495,13 @@ public class PermissionsResolver {
c.thenData(" w=" + world);
if (conflictMessage != null)
c.then(Chat.failureText(" " + conflictMessage));
- DisplayTreeNode node = new DisplayTreeNode(c);
+ ChatTreeNode node = new ChatTreeNode(c);
selfPermissions.forEach(p -> node.children.add(p.toDisplayTreeNode()));
if (result == PermState.UNDEFINED && !conflict && !inheritances.isEmpty()) {
// there is nothing interesting to show on current or subnode
- node.children.add(new DisplayTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic()));
+ node.children.add(new ChatTreeNode(Chat.text("(Inheritances hidden for brevety)").darkGray().italic()));
return node;
}
@@ -521,8 +520,8 @@ public class PermissionsResolver {
result = r;
type = t;
}
- public DisplayTreeNode toDisplayTreeNode() {
- return new DisplayTreeNode(Chat.chat()
+ public ChatTreeNode toDisplayTreeNode() {
+ return new ChatTreeNode(Chat.chat()
.then(result ? Chat.successText("✔") : Chat.failureText("✘"))
.then(Chat.text(permission).color(type == PermType.WILDCARD ? ChatColor.YELLOW : type == PermType.SPECIAL ? ChatColor.LIGHT_PURPLE : ChatColor.WHITE)));
}