package fr.pandacube.lib.chat; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentBuilder; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration.State; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import org.jetbrains.annotations.NotNull; import java.awt.*; import java.util.Objects; import java.util.function.Consumer; import java.util.function.UnaryOperator; /** * 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 implementation without using the final methods of this builder. *
* The unique possible concrete subclass of this class, {@link FormatableChat}, takes care of the formatting of the
* built component. The rationale for this design is explained in the documentation of {@link FormatableChat}.
*/
public abstract sealed class Chat extends ChatStatic implements HoverEventSource
* 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line 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 chat line with the configured decoration character and color.
* @return a new {@link FormatableChat} filling a chat line 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):
*
* In our design, we want the formatting to apply to the currently built component, not the last appended one.
* The purpose is to make the component structure clearer and have better control of the formatting over the
* component hierarchy.
* Here is the equivalent of the above code, with the {@link Chat} API:
* {@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 formatting method (like {@code bold(boolean)} or {@code color(ChatColor)}) after the
* {@code append(String)} method, the formatting apply to the last subcomponent appended.
* {@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, cast
* to {@link Chat}, to prevent future formatting (that the programmer would think it formats the previously appended
* subcomponent). If the formatting of the currently built 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