From d139d32f2be1924d6adae4a240f7e0ba699ff5af Mon Sep 17 00:00:00 2001 From: postyizhan <185839426@qq.com> Date: Sun, 29 Jun 2025 19:10:47 +0800 Subject: [PATCH 1/2] hex color support --- .../advancedban/bukkit/BukkitMethods.java | 29 +- .../advancedban/bungee/BungeeMethods.java | 5 +- .../advancedban/manager/MessageManager.java | 10 +- .../leoko/advancedban/utils/ColorUtils.java | 270 ++++++++++++++++++ core/src/main/resources/Messages.yml | 6 +- core/src/main/resources/config.yml | 17 ++ .../advancedban/utils/ColorUtilsTest.java | 224 +++++++++++++++ pom.xml | 3 +- 8 files changed, 558 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/me/leoko/advancedban/utils/ColorUtils.java create mode 100644 core/src/test/java/me/leoko/advancedban/utils/ColorUtilsTest.java diff --git a/bukkit/src/main/java/me/leoko/advancedban/bukkit/BukkitMethods.java b/bukkit/src/main/java/me/leoko/advancedban/bukkit/BukkitMethods.java index 932967ee..fcd81245 100644 --- a/bukkit/src/main/java/me/leoko/advancedban/bukkit/BukkitMethods.java +++ b/bukkit/src/main/java/me/leoko/advancedban/bukkit/BukkitMethods.java @@ -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; @@ -49,6 +50,23 @@ public class BukkitMethods implements MethodInterface { private YamlConfiguration mysql; private BiFunction 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() { + 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) { @@ -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); } @@ -390,7 +415,9 @@ public void notify(String perm, List 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 diff --git a/bungee/src/main/java/me/leoko/advancedban/bungee/BungeeMethods.java b/bungee/src/main/java/me/leoko/advancedban/bungee/BungeeMethods.java index bea888a2..9d99c4be 100644 --- a/bungee/src/main/java/me/leoko/advancedban/bungee/BungeeMethods.java +++ b/bungee/src/main/java/me/leoko/advancedban/bungee/BungeeMethods.java @@ -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; @@ -429,7 +430,9 @@ public void notify(String perm, List 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 diff --git a/core/src/main/java/me/leoko/advancedban/manager/MessageManager.java b/core/src/main/java/me/leoko/advancedban/manager/MessageManager.java index 864922e7..f3424cdf 100644 --- a/core/src/main/java/me/leoko/advancedban/manager/MessageManager.java +++ b/core/src/main/java/me/leoko/advancedban/manager/MessageManager.java @@ -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; @@ -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; } @@ -75,7 +78,10 @@ public static List getLayout(Object file, String path, String... paramet if (mi.contains(file, path)) { List 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; } diff --git a/core/src/main/java/me/leoko/advancedban/utils/ColorUtils.java b/core/src/main/java/me/leoko/advancedban/utils/ColorUtils.java new file mode 100644 index 00000000..ce9d958d --- /dev/null +++ b/core/src/main/java/me/leoko/advancedban/utils/ColorUtils.java @@ -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}"); + } +} diff --git a/core/src/main/resources/Messages.yml b/core/src/main/resources/Messages.yml index 0a67f05c..46aa738d 100644 --- a/core/src/main/resources/Messages.yml +++ b/core/src/main/resources/Messages.yml @@ -1,5 +1,9 @@ General: - Prefix: "&c&lAdvancedBan &8&l»" + # Hex color prefix example: &#FF5555 is red, &#FFAA00 is orange + # Gradient example: {#FF5555>AdvancedBan>#FFAA00} creates red to orange gradient + Prefix: "&#FF5555&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 diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 24f8d5dc..47f7f4d8 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -136,3 +136,20 @@ Disable Prefix: false # Off by default, so AdvancedBan can override /ban from other plugins # This is a Bukkit-specific option. It has no meaning on BungeeCord Friendly Register Commands: false + +# Hex Color Support Settings +# These settings control how hex colors are handled in messages +HexColors: + # Enable hex color support (&#RRGGBB format) + # Set to false to disable hex color processing + Enabled: true + + # Enable gradient color support ({#RRGGBB>text>#RRGGBB} format) + # This allows creating smooth color transitions in text + Gradients: true + + # Fallback behavior for unsupported clients + # 'strip' - Remove hex colors completely + # 'closest' - Convert to closest traditional color (future feature) + # 'keep' - Keep hex codes as-is + Fallback: 'strip' diff --git a/core/src/test/java/me/leoko/advancedban/utils/ColorUtilsTest.java b/core/src/test/java/me/leoko/advancedban/utils/ColorUtilsTest.java new file mode 100644 index 00000000..c24cd8e9 --- /dev/null +++ b/core/src/test/java/me/leoko/advancedban/utils/ColorUtilsTest.java @@ -0,0 +1,224 @@ +package me.leoko.advancedban.utils; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the color processing utility class + */ +public class ColorUtilsTest { + + @BeforeEach + void setUp() { + // Setup before tests + } + + @Test + void testTranslateHexColorCodes_ValidHex() { + // Test valid hex color code conversion + String input = "&#FF5555Hello World"; + String result = ColorUtils.translateHexColorCodes(input); + + // Verify result contains correct Minecraft color format + assertTrue(result.contains("§x§F§F§5§5§5§5")); + assertTrue(result.contains("Hello World")); + } + + @Test + void testTranslateHexColorCodes_MultipleHex() { + // Test multiple hex color codes + String input = "&#FF5555Red �FF00Green �FFBlue"; + String result = ColorUtils.translateHexColorCodes(input); + + // Verify all colors are correctly converted + assertTrue(result.contains("§x§F§F§5§5§5§5")); + assertTrue(result.contains("§x§0§0§F§F§0§0")); + assertTrue(result.contains("§x§0§0§0§0§F§F")); + } + + @Test + void testTranslateHexColorCodes_TraditionalColors() { + // Test traditional color code conversion + String input = "&cRed &aGreen &9Blue"; + String result = ColorUtils.translateHexColorCodes(input); + + assertEquals("§cRed §aGreen §9Blue", result); + } + + @Test + void testTranslateHexColorCodes_MixedColors() { + // Test mixed color codes + String input = "&c&#FF5555Mixed �FF00Colors&a"; + String result = ColorUtils.translateHexColorCodes(input); + + assertTrue(result.contains("§c")); + assertTrue(result.contains("§x§F§F§5§5§5§5")); + assertTrue(result.contains("§x§0§0§F§F§0§0")); + assertTrue(result.contains("§a")); + } + + @Test + void testTranslateGradientColors_ValidGradient() { + // Test valid gradient + String input = "{#FF0000>Hello>#00FF00}"; + String result = ColorUtils.translateGradientColors(input); + + // Verify result contains gradient color format + assertFalse(result.contains("{#")); + assertFalse(result.contains("}")); + assertTrue(result.contains("Hello")); + assertTrue(result.contains("§x")); + } + + @Test + void testTranslateGradientColors_MultipleGradients() { + // Test multiple gradients + String input = "{#FF0000>Red>#FFFF00} and {#00FF00>Green>#0000FF}"; + String result = ColorUtils.translateGradientColors(input); + + assertTrue(result.contains("Red")); + assertTrue(result.contains("Green")); + assertTrue(result.contains("and")); + assertFalse(result.contains("{#")); + } + + @Test + void testStripColors_AllColorTypes() { + // Test removing all types of color codes + String input = "§cTraditional &#FF5555Hex {#FF0000>Gradient>#00FF00} Normal"; + String result = ColorUtils.stripColors(input); + + assertEquals("Traditional Hex Gradient Normal", result); + } + + @Test + void testStripColors_OnlyText() { + // Test plain text (no color codes) + String input = "Just normal text"; + String result = ColorUtils.stripColors(input); + + assertEquals(input, result); + } + + @Test + void testHasColors_WithColors() { + // Test text with color codes + assertTrue(ColorUtils.hasColors("§cRed text")); + assertTrue(ColorUtils.hasColors("&#FF5555Hex text")); + assertTrue(ColorUtils.hasColors("{#FF0000>Gradient>#00FF00}")); + } + + @Test + void testHasColors_WithoutColors() { + // Test text without color codes + assertFalse(ColorUtils.hasColors("Normal text")); + assertFalse(ColorUtils.hasColors("")); + assertFalse(ColorUtils.hasColors(null)); + } + + @Test + void testIsValidHex_ValidCodes() { + // Test valid hex codes + assertTrue(ColorUtils.isValidHex("FF5555")); + assertTrue(ColorUtils.isValidHex("#FF5555")); + assertTrue(ColorUtils.isValidHex("00ff00")); + assertTrue(ColorUtils.isValidHex("#00ff00")); + assertTrue(ColorUtils.isValidHex("ABCDEF")); + } + + @Test + void testIsValidHex_InvalidCodes() { + // Test invalid hex codes + assertFalse(ColorUtils.isValidHex("GG5555")); // Invalid characters + assertFalse(ColorUtils.isValidHex("FF55")); // Too short + assertFalse(ColorUtils.isValidHex("FF55555")); // Too long + assertFalse(ColorUtils.isValidHex("")); // Empty string + assertFalse(ColorUtils.isValidHex(null)); // null + } + + @Test + void testTranslateColors_CompleteProcessing() { + // Test complete color processing flow + String input = "&c{#FF0000>Gradient>#FFFF00} �FF00Normal &aText"; + String result = ColorUtils.translateColors(input); + + // Verify all types of colors are processed + assertTrue(result.contains("§c")); + assertTrue(result.contains("§x")); + assertTrue(result.contains("§a")); + assertTrue(result.contains("Normal")); + assertTrue(result.contains("Text")); + assertFalse(result.contains("{#")); + assertFalse(result.contains("&#")); + } + + @Test + void testTranslateColors_NullAndEmpty() { + // Test null and empty strings + assertNull(ColorUtils.translateColors(null)); + assertEquals("", ColorUtils.translateColors("")); + } + + @Test + void testTranslateColors_NoColors() { + // Test text without color codes + String input = "Just normal text without colors"; + String result = ColorUtils.translateColors(input); + + assertEquals(input, result); + } + + @Test + void testGradientCreation_SingleCharacter() { + // Test single character gradient + String input = "{#FF0000>A>#00FF00}"; + String result = ColorUtils.translateGradientColors(input); + + assertTrue(result.contains("A")); + assertTrue(result.contains("§x")); + } + + @Test + void testGradientCreation_EmptyContent() { + // Test empty content gradient + String input = "{#FF0000>>#00FF00}"; + String result = ColorUtils.translateGradientColors(input); + + // Should remove gradient format, keep only empty content + assertEquals("", result); + } + + @Test + void testHexConversion_LowerAndUpperCase() { + // Test upper and lower case hex codes + String inputLower = "&#ff5555lower"; + String inputUpper = "&#FF5555UPPER"; + + String resultLower = ColorUtils.translateHexColorCodes(inputLower); + String resultUpper = ColorUtils.translateHexColorCodes(inputUpper); + + // Both should produce same color format (case insensitive) + assertTrue(resultLower.contains("§x§f§f§5§5§5§5") || resultLower.contains("§x§F§F§5§5§5§5")); + assertTrue(resultUpper.contains("§x§F§F§5§5§5§5")); + } + + @Test + void testComplexMessage_RealWorldExample() { + // Test real-world complex message + String input = "&c&lAdvancedBan &8&l» &#FF5555Player &#FFAA00banned &afor {#FF0000>cheating>#FFFF00}!"; + String result = ColorUtils.translateColors(input); + + // Verify all elements exist and format is correct + assertTrue(result.contains("§c§l")); + assertTrue(result.contains("§8§l")); + assertTrue(result.contains("§x")); + assertTrue(result.contains("§a")); + assertTrue(result.contains("Player")); + assertTrue(result.contains("banned")); + assertTrue(result.contains("for")); + assertTrue(result.contains("cheating")); + assertFalse(result.contains("&#")); + assertFalse(result.contains("{#")); + } +} diff --git a/pom.xml b/pom.xml index a2e99318..0d2ddbe7 100644 --- a/pom.xml +++ b/pom.xml @@ -40,10 +40,11 @@ org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.11.0 1.8 1.8 + 8 From 8fa22f755153f0be176bca534ae1829eb3f266ba Mon Sep 17 00:00:00 2001 From: postyizhan <185839426@qq.com> Date: Sun, 29 Jun 2025 23:57:53 +0800 Subject: [PATCH 2/2] revert Messages.yml Prefix --- core/src/main/resources/Messages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/Messages.yml b/core/src/main/resources/Messages.yml index 46aa738d..2a75c3a8 100644 --- a/core/src/main/resources/Messages.yml +++ b/core/src/main/resources/Messages.yml @@ -1,7 +1,7 @@ General: # Hex color prefix example: &#FF5555 is red, &#FFAA00 is orange # Gradient example: {#FF5555>AdvancedBan>#FFAA00} creates red to orange gradient - Prefix: "&#FF5555&lAdvancedBan &8&l»" + 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!"