Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import me.leoko.advancedban.manager.DatabaseManager;
import me.leoko.advancedban.manager.PunishmentManager;
import me.leoko.advancedban.manager.UUIDManager;
import me.leoko.advancedban.utils.ColorUtils;
import me.leoko.advancedban.utils.Permissionable;
import me.leoko.advancedban.utils.Punishment;
import me.leoko.advancedban.utils.tabcompletion.TabCompleter;
Expand Down Expand Up @@ -49,6 +50,23 @@ public class BukkitMethods implements MethodInterface {
private YamlConfiguration mysql;
private BiFunction<OfflinePlayer, String, Boolean> permissionVault;

// Check if native hex colors are supported (1.16+)
private static final boolean SUPPORTS_HEX_COLORS = checkHexColorSupport();

/**
* Check if current Bukkit version supports native hex colors
* @return true if supported
*/
private static boolean checkHexColorSupport() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this 100% viable? Can this cause issues with other plugins trying to implement this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't know for sure, but I don't think it will cause issues.

try {
// Try to access ChatColor.of method from 1.16+
ChatColor.class.getMethod("of", String.class);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}

public BukkitMethods() {
// Vault support
if (Bukkit.getServer().getPluginManager().getPlugin("Vault") != null) {
Expand Down Expand Up @@ -175,6 +193,13 @@ public void setCommandExecutor(String cmd, String permission, TabCompleter tabCo

@Override
public void sendMessage(Object player, String msg) {
// If native hex colors are supported, use ChatColor.translateAlternateColorCodes
// Otherwise, message has already been processed by ColorUtils in MessageManager
if (SUPPORTS_HEX_COLORS) {
// For 1.16+, use native color processing
msg = ChatColor.translateAlternateColorCodes('&', msg);
}
// Message has already been processed by ColorUtils, send directly
((CommandSender) player).sendMessage(msg);
}

Expand Down Expand Up @@ -390,7 +415,9 @@ public void notify(String perm, List<String> notification) {

@Override
public void log(String msg) {
Bukkit.getServer().getConsoleSender().sendMessage(msg.replaceAll("&", "§"));
// Use ColorUtils to process all color codes
msg = ColorUtils.translateColors(msg);
Bukkit.getServer().getConsoleSender().sendMessage(msg);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import me.leoko.advancedban.manager.DatabaseManager;
import me.leoko.advancedban.manager.PunishmentManager;
import me.leoko.advancedban.manager.UUIDManager;
import me.leoko.advancedban.utils.ColorUtils;
import me.leoko.advancedban.utils.Permissionable;
import me.leoko.advancedban.utils.Punishment;
import me.leoko.advancedban.utils.tabcompletion.TabCompleter;
Expand Down Expand Up @@ -429,7 +430,9 @@ public void notify(String perm, List<String> notification) {

@Override
public void log(String msg) {
ProxyServer.getInstance().getConsole().sendMessage(TextComponent.fromLegacyText(msg.replaceAll("&", "§")));
// Use ColorUtils to process all color codes
msg = ColorUtils.translateColors(msg);
ProxyServer.getInstance().getConsole().sendMessage(TextComponent.fromLegacyText(msg));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import me.leoko.advancedban.MethodInterface;
import me.leoko.advancedban.Universal;
import me.leoko.advancedban.utils.ColorUtils;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -36,7 +37,9 @@ public static String getMessage(String path, String... parameters) {
+ "\n - Visit yamllint.com to validate your Message.yml"
+ "\n - Delete the message file and restart the server");
} else {
str = replace(str, parameters).replace('&', '§');
str = replace(str, parameters);
// Use ColorUtils to process all color codes (including hex colors)
str = ColorUtils.translateColors(str);
}
return str;
}
Expand Down Expand Up @@ -75,7 +78,10 @@ public static List<String> getLayout(Object file, String path, String... paramet
if (mi.contains(file, path)) {
List<String> list = new ArrayList<>();
for (String str : mi.getStringList(file, path)) {
list.add(replace(str, parameters).replace('&', '§'));
str = replace(str, parameters);
// Use ColorUtils to process all color codes (including hex colors)
str = ColorUtils.translateColors(str);
list.add(str);
}
return list;
}
Expand Down
270 changes: 270 additions & 0 deletions core/src/main/java/me/leoko/advancedban/utils/ColorUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
package me.leoko.advancedban.utils;

import me.leoko.advancedban.Universal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Color processing utility class that provides conversion and processing functions for hex colors and traditional color codes
* Supports Minecraft 1.16+ hex color format
*/
public class ColorUtils {

// Hex color matching pattern: &#RRGGBB or &#rrggbb
private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})");

// Gradient color matching pattern: {#RRGGBB>text>#RRGGBB}
private static final Pattern GRADIENT_PATTERN = Pattern.compile("\\{#([A-Fa-f0-9]{6})>([^>]+)>#([A-Fa-f0-9]{6})\\}");

// Traditional color codes
private static final char COLOR_CHAR = '§';
private static final char ALT_COLOR_CHAR = '&';

/**
* Check if hex color support is enabled
* @return true if enabled
*/
private static boolean isHexColorsEnabled() {
try {
return Universal.get().getMethods().getBoolean(
Universal.get().getMethods().getConfig(),
"HexColors.Enabled",
true
);
} catch (Exception e) {
// Default to enabled if config is not available
return true;
}
}

/**
* Check if gradient color support is enabled
* @return true if enabled
*/
private static boolean isGradientsEnabled() {
try {
return Universal.get().getMethods().getBoolean(
Universal.get().getMethods().getConfig(),
"HexColors.Gradients",
true
);
} catch (Exception e) {
// Default to enabled if config is not available
return true;
}
}

/**
* Convert text containing hex color codes to Minecraft color format
* Supported format: &#RRGGBB
*
* @param text text containing color codes
* @return converted text
*/
public static String translateHexColorCodes(String text) {
if (text == null || text.isEmpty()) {
return text;
}

// Process traditional color codes
text = text.replace(ALT_COLOR_CHAR, COLOR_CHAR);

// Check if hex color support is enabled
if (!isHexColorsEnabled()) {
// If disabled, remove hex color codes
return HEX_PATTERN.matcher(text).replaceAll("");
}

// Process hex color codes
Matcher matcher = HEX_PATTERN.matcher(text);
StringBuffer buffer = new StringBuffer();

while (matcher.find()) {
String hexColor = matcher.group(1);
String replacement = convertHexToMinecraft(hexColor);
matcher.appendReplacement(buffer, replacement);
}
matcher.appendTail(buffer);

return buffer.toString();
}

/**
* Convert hex color code to Minecraft format
*
* @param hex 6-digit hex color code (without #)
* @return Minecraft color format string
*/
private static String convertHexToMinecraft(String hex) {
StringBuilder result = new StringBuilder();
result.append(COLOR_CHAR).append('x');

for (char c : hex.toCharArray()) {
result.append(COLOR_CHAR).append(c);
}

return result.toString();
}

/**
* Process gradient color text
* Supported format: {#RRGGBB>text>#RRGGBB}
*
* @param text text containing gradient color codes
* @return processed text
*/
public static String translateGradientColors(String text) {
if (text == null || text.isEmpty()) {
return text;
}

// Check if gradient color support is enabled
if (!isGradientsEnabled() || !isHexColorsEnabled()) {
// If disabled, keep only text content and remove gradient format
return GRADIENT_PATTERN.matcher(text).replaceAll("$2");
}

Matcher matcher = GRADIENT_PATTERN.matcher(text);
StringBuffer buffer = new StringBuffer();

while (matcher.find()) {
String startHex = matcher.group(1);
String content = matcher.group(2);
String endHex = matcher.group(3);

String gradientText = createGradient(startHex, endHex, content);
matcher.appendReplacement(buffer, gradientText);
}
matcher.appendTail(buffer);

return buffer.toString();
}

/**
* Create gradient color text
*
* @param startHex starting color
* @param endHex ending color
* @param text text to apply gradient to
* @return gradient color text
*/
private static String createGradient(String startHex, String endHex, String text) {
if (text.length() <= 1) {
return convertHexToMinecraft(startHex) + text;
}

int[] startRgb = hexToRgb(startHex);
int[] endRgb = hexToRgb(endHex);

StringBuilder result = new StringBuilder();
int length = text.length();

for (int i = 0; i < length; i++) {
double ratio = (double) i / (length - 1);

int r = (int) (startRgb[0] + (endRgb[0] - startRgb[0]) * ratio);
int g = (int) (startRgb[1] + (endRgb[1] - startRgb[1]) * ratio);
int b = (int) (startRgb[2] + (endRgb[2] - startRgb[2]) * ratio);

String hex = String.format("%02x%02x%02x", r, g, b);
result.append(convertHexToMinecraft(hex)).append(text.charAt(i));
}

return result.toString();
}

/**
* Convert hex color to RGB array
*
* @param hex 6-digit hex color code
* @return RGB array [r, g, b]
*/
private static int[] hexToRgb(String hex) {
int r = Integer.parseInt(hex.substring(0, 2), 16);
int g = Integer.parseInt(hex.substring(2, 4), 16);
int b = Integer.parseInt(hex.substring(4, 6), 16);
return new int[]{r, g, b};
}

/**
* Remove all color codes from text
*
* @param text text containing color codes
* @return plain text with color codes removed
*/
public static String stripColors(String text) {
if (text == null || text.isEmpty()) {
return text;
}

// Remove traditional color codes
text = text.replaceAll("§[0-9a-fk-or]", "");

// Remove hex color codes
text = text.replaceAll("§x(§[0-9a-f]){6}", "");

// Remove original hex format
text = HEX_PATTERN.matcher(text).replaceAll("");

// Remove gradient color format
text = GRADIENT_PATTERN.matcher(text).replaceAll("$2");

return text;
}

/**
* Check if text contains color codes
*
* @param text text to check
* @return true if contains color codes
*/
public static boolean hasColors(String text) {
if (text == null || text.isEmpty()) {
return false;
}

return text.contains(String.valueOf(COLOR_CHAR)) ||
HEX_PATTERN.matcher(text).find() ||
GRADIENT_PATTERN.matcher(text).find();
}

/**
* Process all types of color codes
* This is the main public method that handles traditional color codes, hex colors, and gradients
*
* @param text text containing color codes
* @return processed text
*/
public static String translateColors(String text) {
if (text == null || text.isEmpty()) {
return text;
}

// First process gradient colors
text = translateGradientColors(text);

// Then process hex colors and traditional color codes
text = translateHexColorCodes(text);

return text;
}

/**
* Validate if hex color code is valid
*
* @param hex hex color code (may or may not include #)
* @return true if valid
*/
public static boolean isValidHex(String hex) {
if (hex == null) {
return false;
}

// Remove possible # prefix
if (hex.startsWith("#")) {
hex = hex.substring(1);
}

return hex.matches("[A-Fa-f0-9]{6}");
}
}
4 changes: 4 additions & 0 deletions core/src/main/resources/Messages.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
General:
# Hex color prefix example: &#FF5555 is red, &#FFAA00 is orange
# Gradient example: {#FF5555>AdvancedBan>#FFAA00} creates red to orange gradient
Prefix: "&c&lAdvancedBan &8&l»"
# Gradient prefix example (optional):
# Prefix: "{#FF5555>Advanced>#FFAA00}{#FFAA00>Ban>#FF5555} &8&l»"
NoPerms: "&cYou don't have perms for that!"
LayoutNotFound: "&cThere is no layout called %NAME%"
# This will be the replacement for the %DURATION% variable
Expand Down
Loading