Skip to content

Instantly share code, notes, and snippets.

@mzcydev
Created April 22, 2025 23:56
Show Gist options
  • Select an option

  • Save mzcydev/8c3ee8d07d3d2029d9a7bad533c491eb to your computer and use it in GitHub Desktop.

Select an option

Save mzcydev/8c3ee8d07d3d2029d9a7bad533c491eb to your computer and use it in GitHub Desktop.
package dev.mzcy.adventure;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
import lombok.experimental.NonFinal;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.util.RGBLike;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Represents a customizable chat message that supports various text formatting,
* colors, and events. Provides utility methods for creating, appending, and
* sending messages to audiences.
* <p>
* This class is designed to be used with the Adventure API for Minecraft
* plugins. It allows for the creation of complex chat messages with hover
* events, click events, and text decorations.
*
* @author mzcy_
* @version 1.1
* @implSpec This class is immutable and thread-safe. It uses the Builder pattern to
* create instances. Once created, the instances cannot be modified. Instead, methods
* that return new instances with the desired modifications are provided.
*/
@Getter
@FieldDefaults(level = lombok.AccessLevel.PRIVATE, makeFinal = true)
public class ChatMessage implements Cloneable {
public static final String MINI_PREFIX = "<dark_gray>» <b><gradient:#D4E6EA:#D4E6EA>PvP</gradient><gradient:#7D53DE:#7D53DE>Duels</gradient></b> • <white>";
public static final String MINI_NO_PERMISSION = MINI_PREFIX + "<red>You do not have permission to do this.";
public static final String MINI_OFFLINE = MINI_PREFIX + "<red>This player is not online on duels.";
static MiniMessage miniMessage = MiniMessage.miniMessage();
@NonFinal
List<Component> components = new ArrayList<>();
/**
* Constructs a new ChatMessage with the specified message.
*
* @param message The message to be included in the chat message.
*/
private ChatMessage(String message) {
this.components.add(Component.text(message));
}
/**
* Constructs a new ChatMessage with the specified component.
*
* @param component The component to be included in the chat message.
*/
private ChatMessage(Component component) {
this.components.add(component);
}
/**
* Constructs a new ChatMessage by copying the components from another ChatMessage.
*
* @param message The ChatMessage to copy components from.
*/
private ChatMessage(ChatMessage message) {
this.components.addAll(message.components);
}
/**
* Constructs a new ChatMessage with the predefined prefix as an initial message.
*/
public static ChatMessage ofPrefix() {
return new ChatMessage(miniMessage.deserialize(MINI_PREFIX));
}
/**
* Constructs a new ChatMessage with the predefined "player offline" message.
*/
public static ChatMessage ofOffline() {
return new ChatMessage(miniMessage.deserialize(MINI_OFFLINE));
}
/**
* Constructs a new ChatMessage with the predefined "no permission" message.
*/
public static ChatMessage ofNoPermission() {
return new ChatMessage(miniMessage.deserialize(MINI_NO_PERMISSION));
}
/**
* Constructs a new ChatMessage with the specified MiniMessage string.
*
* @param message The MiniMessage string to be included in the chat message.
*/
public static ChatMessage ofMini(String message) {
return new ChatMessage(miniMessage.deserialize(message));
}
/**
* Constructs a new ChatMessage with the specified Component.
*
* @param component The component to be included in the chat message.
*/
public static ChatMessage of(Component component) {
return new ChatMessage(component);
}
/**
* Constructs a new ChatMessage by copying the components from another ChatMessage.
*
* @param message The ChatMessage to copy components from.
*/
public static ChatMessage of(ChatMessage message) {
return new ChatMessage(message);
}
/**
* Constructs a new empty ChatMessage.
*/
public static ChatMessage ofEmpty() {
return new ChatMessage("");
}
/**
* Constructs a new ChatMessage with the specified components.
*
* @param components The components to be included in the chat message.
*/
public static ChatMessage of(Component... components) {
ChatMessage message = new ChatMessage(components[0]);
message.components.addAll(Arrays.asList(components).subList(1, components.length));
return message;
}
/**
* Constructs a new ChatMessage with the specified string message.
*
* @param message The string message to be included in the chat message.
* @apiNote This method is not converting the message to mini message.
*/
public static ChatMessage of(String message) {
return new ChatMessage(message);
}
/**
* Appends the specified components to the chat message.
*
* @param components The components to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(Component... components) {
Collections.addAll(this.components, Arrays.stream(components).map(component -> component.colorIfAbsent(NamedTextColor.GRAY)).toArray(Component[]::new));
return this;
}
/**
* Appends the specified string message to the chat message.
* @param message The string message to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(String message) {
this.components.add(Component.text(message).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified integer to the chat message.
* @param number The integer to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(int number) {
this.components.add(Component.text(number).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified long to the chat message.
* @param number The long to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(long number) {
this.components.add(Component.text(number).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified float to the chat message.
* @param number The float to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(float number) {
this.components.add(Component.text(number).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified double to the chat message.
* @param number The double to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(double number) {
this.components.add(Component.text(number).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified boolean to the chat message.
* @param bool The boolean to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(boolean bool) {
this.components.add(Component.text(bool).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified character to the chat message.
* @param character The character to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(char character) {
this.components.add(Component.text(character).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified MiniMessage string to the chat message.
* @param message The MiniMessage string to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage appendMini(String message) {
this.components.add(miniMessage.deserialize(message).colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified Component to the chat message.
* @param component The Component to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(Component component) {
this.components.add(component.colorIfAbsent(NamedTextColor.GRAY));
return this;
}
/**
* Appends the specified ChatMessage to the chat message.
* @param message The ChatMessage to be appended.
* @return The updated ChatMessage instance.
*/
public ChatMessage append(ChatMessage message) {
this.components.addAll(message.components);
return this;
}
/**
* Adds a hover event to the chat message.
* @param event The hover event to be added.
* @param onlyLast If true, the event will be added only to the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage hoverEvent(HoverEvent<?> event, boolean onlyLast) {
if (onlyLast) {
Component last = this.components.getLast();
this.components.remove(last);
this.components.add(last.hoverEvent(event));
} else {
this.components = this.components.stream().map(component -> component.hoverEvent(event)).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
return this;
}
/**
* Adds a hover event to the chat message.
* @param event The hover event to be added.
* @return The updated ChatMessage instance.
*/
public ChatMessage hoverEvent(HoverEvent<?> event) {
return this.hoverEvent(event, false);
}
/**
* Sets the color of the chat message.
* @param color The color to be set.
* @param onlyLast If true, the color will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage color(TextColor color, boolean onlyLast) {
if (onlyLast) {
Component last = this.components.getLast();
this.components.remove(last);
this.components.add(last.color(color));
} else {
this.components = this.components.stream().map(component -> component.color(color)).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
return this;
}
/**
* Sets the color of the chat message.
* @param color The color to be set.
* @return The updated ChatMessage instance.
*/
public ChatMessage color(TextColor color) {
return this.color(color, false);
}
/**
* Sets the click event for the chat message.
* @param event The click event to be set.
* @param onlyLast If true, the event will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage clickEvent(ClickEvent event, boolean onlyLast) {
if (onlyLast) {
Component last = this.components.getLast();
this.components.remove(last);
this.components.add(last.clickEvent(event));
} else {
this.components = this.components.stream().map(component -> component.clickEvent(event)).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
return this;
}
/**
* Sets the click event for the chat message.
* @param event The click event to be set.
* @return The updated ChatMessage instance.
*/
public ChatMessage clickEvent(ClickEvent event) {
return this.clickEvent(event, false);
}
/**
* Sets the decoration for the chat message.
* @param decoration The decoration to be set.
* @param flag The flag indicating whether to enable or disable the decoration.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage decoration(TextDecoration decoration, boolean flag, boolean onlyLast) {
if (onlyLast) {
Component last = this.components.getLast();
this.components.remove(last);
this.components.add(last.decoration(decoration, flag));
} else {
this.components = this.components.stream().map(component -> component.decoration(decoration, flag)).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
return this;
}
/**
* Sets the decoration for the chat message.
* @param decoration The decoration to be set.
* @param flag The flag indicating whether to enable or disable the decoration.
* @return The updated ChatMessage instance.
*/
public ChatMessage decoration(TextDecoration decoration, boolean flag) {
return this.decoration(decoration, flag, false);
}
/**
* Sets the decoration state for the chat message.
* @param decoration The decoration to be set.
* @param state The state of the decoration.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage decoration(TextDecoration decoration, TextDecoration.State state, boolean onlyLast) {
if (onlyLast) {
Component last = this.components.getLast();
this.components.remove(last);
this.components.add(last.decoration(decoration, state));
} else {
this.components = this.components.stream().map(component -> component.decoration(decoration, state)).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
return this;
}
/**
* Sets the decoration state for the chat message.
* @param decoration The decoration to be set.
* @param state The state of the decoration.
* @return The updated ChatMessage instance.
*/
public ChatMessage decoration(TextDecoration decoration, TextDecoration.State state) {
return this.decoration(decoration, state, false);
}
/**
* Sets the decoration state for "bold" text.
* @param flag The flag indicating whether to enable or disable bold text.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage bold(boolean flag, boolean onlyLast) {
return this.decoration(TextDecoration.BOLD, flag, onlyLast);
}
/**
* Sets the decoration state for "bold" text.
* @param flag The flag indicating whether to enable or disable bold text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage bold(boolean flag) {
return this.bold(flag, false);
}
/**
* Sets the decoration state for "italic" text.
* @param flag The flag indicating whether to enable or disable italic text.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, boolean, boolean)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage italic(boolean flag, boolean onlyLast) {
return this.decoration(TextDecoration.ITALIC, flag, onlyLast);
}
/**
* Sets the decoration state for "italic" text.
* @param flag The flag indicating whether to enable or disable italic text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage italic(boolean flag) {
return this.italic(flag, false);
}
/**
* Sets the decoration state for "underlined" text.
* @param flag The flag indicating whether to enable or disable underlined text.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, boolean, boolean)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage underlined(boolean flag, boolean onlyLast) {
return this.decoration(TextDecoration.UNDERLINED, flag, onlyLast);
}
/**
* Sets the decoration state for "underlined" text.
* @param flag The flag indicating whether to enable or disable underlined text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage underlined(boolean flag) {
return this.underlined(flag, false);
}
/**
* Sets the decoration state for "strikethrough" text.
* @param flag The flag indicating whether to enable or disable strikethrough text.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, boolean, boolean)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage strikethrough(boolean flag, boolean onlyLast) {
return this.decoration(TextDecoration.STRIKETHROUGH, flag, onlyLast);
}
/**
* Sets the decoration state for "strikethrough" text.
* @param flag The flag indicating whether to enable or disable strikethrough text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage strikethrough(boolean flag) {
return this.strikethrough(flag, false);
}
/**
* Sets the decoration state for "obfuscated" text.
* @param flag The flag indicating whether to enable or disable obfuscated text.
* @param onlyLast If true, the decoration will be set only for the last component.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, boolean, boolean)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage obfuscated(boolean flag, boolean onlyLast) {
return this.decoration(TextDecoration.OBFUSCATED, flag, onlyLast);
}
/**
* Sets the decoration state for "obfuscated" text.
* @param flag The flag indicating whether to enable or disable obfuscated text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage obfuscated(boolean flag) {
return this.obfuscated(flag, false);
}
/**
* Sets the color of the chat message using a Color object.
* @param color The Color object to be used.
* @param onlyLast If true, the color will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage color(Color color, boolean onlyLast) {
return this.color(TextColor.color(color.getRed(), color.getGreen(), color.getBlue()), onlyLast);
}
/**
* Sets the color of the chat message using a Color object.
* @param color The Color object to be used.
* @return The updated ChatMessage instance.
*/
public ChatMessage color(Color color) {
return this.color(color, false);
}
/**
* Sets the color of the chat message using an RGBLike object.
* @param rgb The RGBLike object to be used.
* @param onlyLast If true, the color will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage rgb(RGBLike rgb, boolean onlyLast) {
return this.color(TextColor.color(rgb.red(), rgb.green(), rgb.blue()), onlyLast);
}
/**
* Sets the color of the chat message using an RGBLike object.
* @param rgb The RGBLike object to be used.
* @return The updated ChatMessage instance.
*/
public ChatMessage rgb(RGBLike rgb) {
return this.rgb(rgb, false);
}
/**
* Sets the color of the chat message using RGB values.
* @param red The red component (0-255).
* @param green The green component (0-255).
* @param blue The blue component (0-255).
* @param onlyLast If true, the color will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage rgb(int red, int green, int blue, boolean onlyLast) {
return this.color(TextColor.color(red, green, blue), onlyLast);
}
/**
* Sets the color of the chat message using RGB values.
* @param red The red component (0-255).
* @param green The green component (0-255).
* @param blue The blue component (0-255).
* @return The updated ChatMessage instance.
*/
public ChatMessage rgb(int red, int green, int blue) {
return this.rgb(red, green, blue, false);
}
/**
* Sets the color of the chat message using a hex string.
* @param hex The hex string to be used.
* @param onlyLast If true, the color will be set only for the last component.
* @return The updated ChatMessage instance.
*/
public ChatMessage rgb(String hex, boolean onlyLast) {
return this.color(TextColor.fromHexString(hex), onlyLast);
}
/**
* Sets the color of the chat message using a hex string.
* @param hex The hex string to be used.
* @return The updated ChatMessage instance.
*/
public ChatMessage rgb(String hex) {
return this.rgb(hex, false);
}
/**
* Sets the decoration state for "bold" text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage bold() {
return this.bold(true);
}
/**
* Sets the decoration state for "italic" text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage italic() {
return this.italic(true);
}
/**
* Sets the decoration state for "underlined" text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage underlined() {
return this.underlined(true);
}
/**
* Sets the decoration state for "strikethrough" text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage strikethrough() {
return this.strikethrough(true);
}
/**
* Sets the decoration state for "obfuscated" text.
* @return The updated ChatMessage instance.
* @apiNote Deprecated; Use {@link #decoration(TextDecoration, TextDecoration.State)} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public ChatMessage obfuscated() {
return this.obfuscated(true);
}
/**
* Converts the chat message to a Component.
* @return The Component representation of the chat message.
*/
public Component asComponent() {
return this.components.stream().reduce(Component.empty(), Component::append);
}
/**
* Converts the chat message to a MiniMessage string.
* @return The MiniMessage string representation of the chat message.
*/
public String asString() {
return miniMessage.serialize(this.components.stream().reduce(Component.empty(), Component::append));
}
/**
* Sends the chat message to the specified audiences.
* @param audiences The audiences to send the message to.
*/
public void send(Audience... audiences) {
for (Audience audience : audiences) {
audience.sendMessage(this.components.stream().reduce(Component.empty(), Component::append));
}
}
/**
* Clones the ChatMessage instance.
* @return A new ChatMessage instance with the same components.
*/
@Override
public ChatMessage clone() {
try {
ChatMessage chatMessage = (ChatMessage) super.clone();
ChatMessage clone = new ChatMessage(chatMessage);
clone.components = new ArrayList<>(this.components);
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Failed to clone ChatMessage", e);
}
}
/**
* Converts the chat message to a Component.
* @return The Component representation of the chat message.
* @apiNote Deprecated; Use {@link #asComponent()} instead.
*/
@Deprecated(since = "1.1", forRemoval = true)
public @NotNull Component toComponent() {
return this.asComponent();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment