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.md_5.bungee.api.ChatColor; public class ChatColorUtil { public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoPpRr"; public static final String ALL_COLORS = "0123456789AaBbCcDdEeFf"; 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. * 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. */ 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 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 char colorChar = legacyText.charAt(index + 1); ChatColor legacyColor = getChatColorByChar(colorChar); if (legacyColor != null) { result.insert(0, legacyColor); // 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') { break; } } } } return result.toString(); } 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.
*/
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
{
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)
{
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]);
}
}
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