From c0952a2ba30fdc910f9415fe4a3bca33f156f57a Mon Sep 17 00:00:00 2001 From: nossr50 Date: Sat, 13 Apr 2024 12:58:52 -0700 Subject: [PATCH] Add support for hex color codes in locale --- Changelog.txt | 8 ++ pom.xml | 6 ++ .../nossr50/commands/skills/AxesCommand.java | 2 +- .../nossr50/commands/skills/SkillCommand.java | 18 ++--- .../gmail/nossr50/locale/LocaleLoader.java | 67 +++++++++++++++- .../commands/CommandRegistrationManager.java | 5 +- .../util/experience/ExperienceBarWrapper.java | 1 + .../gmail/nossr50/util/skills/SkillTools.java | 3 +- .../util/text/TextComponentFactory.java | 7 +- .../resources/locale/locale_en_US.properties | 2 +- .../nossr50/locale/LocaleLoaderTest.java | 77 +++++++++++++++++++ 11 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 src/test/java/com/gmail/nossr50/locale/LocaleLoaderTest.java diff --git a/Changelog.txt b/Changelog.txt index 00c7f442f..871c4681f 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,4 +1,8 @@ Version 2.2.006 + Added support for hex color codes in the locale file, uses the format &#RRGGBB (see notes) + Fixed a bug where sometimes the locale name of a skill would get lowercased + Fixed en_US locale string 'Commands.Skill.Leaderboard' not being colored properly + Fixed skill commands incorrectly telling you to use their locale name, this isn't currently possible Updated outdated wiki URLs in commands to point to the new wiki Removed the msg about skills being migrated to a new system when using /mmoinfo command Added new config custom_item_support.yml @@ -7,6 +11,10 @@ Version 2.2.006 Added new locale entry 'Anvil.Salvage.Reject.CustomModelData' NOTES: + Hex Color support in locale files is here! + The hex color code format for the locale files is &#RRGGBB + An example entry applying yellow as a hex color code would look like this: + Axes.SkillName=&#FFFF00Axes Let me know in detail what kind of support you'd like to see in mcMMO regarding custom items, I'm open to suggestions. This update adds a new config file to allow server owners to disable repair or salvage on items with custom models, This prevention mechanism is not enabled by default, change the settings in custom_item_support.yml if you want to enable it. diff --git a/pom.xml b/pom.xml index e780ca2f8..711d57778 100644 --- a/pom.xml +++ b/pom.xml @@ -259,6 +259,12 @@ + + org.assertj + assertj-core + 3.25.3 + test + com.h2database h2 diff --git a/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java index 49346aca2..bcfb38ed0 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java @@ -106,7 +106,7 @@ public class AxesCommand extends SkillCommand { @Override protected List getTextComponents(Player player) { - List textComponents = new ArrayList<>(); + final List textComponents = new ArrayList<>(); TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkillType.AXES); diff --git a/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java index 61b2f7ae4..e8ab95e02 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java @@ -31,7 +31,6 @@ import java.util.Locale; public abstract class SkillCommand implements TabExecutor { protected PrimarySkillType skill; - private final String skillName; protected DecimalFormat percent = new DecimalFormat("##0.00%"); protected DecimalFormat decimal = new DecimalFormat("##0.00"); @@ -40,7 +39,6 @@ public abstract class SkillCommand implements TabExecutor { public SkillCommand(PrimarySkillType skill) { this.skill = skill; - skillName = mcMMO.p.getSkillTools().getLocalizedSkillName(skill); skillGuideCommand = new SkillGuideCommand(skill); } @@ -76,7 +74,8 @@ public abstract class SkillCommand implements TabExecutor { permissionsCheck(player); dataCalculations(player, skillValue); - sendSkillCommandHeader(player, mcMMOPlayer, (int) skillValue); + sendSkillCommandHeader(mcMMO.p.getSkillTools().getLocalizedSkillName(skill), + player, mcMMOPlayer, (int) skillValue); //Make JSON text components List subskillTextComponents = getTextComponents(player); @@ -139,15 +138,14 @@ public abstract class SkillCommand implements TabExecutor { } } - player.sendMessage(LocaleLoader.getString("Guides.Available", skillName, skillName.toLowerCase(Locale.ENGLISH))); + final String skillName = mcMMO.p.getSkillTools().getLocalizedSkillName(skill); + player.sendMessage(LocaleLoader.getString("Guides.Available", + skillName, + skillName.toLowerCase(Locale.ENGLISH))); } - private void sendSkillCommandHeader(Player player, McMMOPlayer mcMMOPlayer, int skillValue) { - ChatColor hd1 = ChatColor.DARK_AQUA; - ChatColor c1 = ChatColor.GOLD; - ChatColor c2 = ChatColor.RED; - - + private void sendSkillCommandHeader(String skillName, Player player, McMMOPlayer mcMMOPlayer, int skillValue) { + // send header player.sendMessage(LocaleLoader.getString("Skills.Overhaul.Header", skillName)); if(!SkillTools.isChildSkill(skill)) diff --git a/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java b/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java index 20b5e2631..e6a37d924 100644 --- a/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java +++ b/src/main/java/com/gmail/nossr50/locale/LocaleLoader.java @@ -16,6 +16,8 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public final class LocaleLoader { private static final String BUNDLE_ROOT = "com.gmail.nossr50.locale.locale"; @@ -24,6 +26,9 @@ public final class LocaleLoader { private static ResourceBundle bundle = null; private static ResourceBundle filesystemBundle = null; private static ResourceBundle enBundle = null; + // Matches the pattern &#RRGGBB + private static final Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})"); + private static final Pattern minecraftHexPattern = Pattern.compile("§x(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])(§[A-Fa-f0-9])"); private LocaleLoader() {} @@ -48,8 +53,6 @@ public final class LocaleLoader { return formatString(rawMessage, messageArguments); } - //TODO: Remove this hacky crap with something better later - /** * Gets the appropriate TextComponent representation of a formatted string from the Locale files. * @@ -258,9 +261,14 @@ public final class LocaleLoader { @NotNull private static String getExamples() { return """ - This.Is.An.Example.Put.Locale.Keys.Here.One=&aExample text using hex color codes + This.Is.An.Example.Put.Locale.Keys.Here.One=&aExample text using simplified minecraft color codes This.Is.An.Example.Put.Locale.Keys.Here.Two=[[DARK_AQUA]]Example text using our own color codes This.Is.An.Example.Put.Locale.Keys.Here.Three=Example text with no colors + This.Is.An.Example.Put.Locale.Keys.Here.Four=&#FF0000Example text with red color hex code + This.Is.An.Example.Put.Locale.Keys.Here.Five=�FF00Example text with green color hex code + This.Is.An.Example.Put.Locale.Keys.Here.Six=�FFExample text with blue color hex code + This.Is.An.Example.Put.Locale.Keys.Here.Seven=&#FFFF00Example text with yellow color hex code + This.Is.An.Example.Put.Locale.Keys.Here.Eight=&lExample text with bold using simplified minecraft color codes """; } @@ -304,6 +312,10 @@ public final class LocaleLoader { } public static String addColors(String input) { + // First check for hex color codes and insert them + input = translateHexColorCodes(input); + + // Then check for our own color codes input = input.replaceAll("\\Q[[BLACK]]\\E", ChatColor.BLACK.toString()); input = input.replaceAll("\\Q[[DARK_BLUE]]\\E", ChatColor.DARK_BLUE.toString()); input = input.replaceAll("\\Q[[DARK_GREEN]]\\E", ChatColor.DARK_GREEN.toString()); @@ -327,6 +339,7 @@ public final class LocaleLoader { input = input.replaceAll("\\Q[[MAGIC]]\\E", ChatColor.MAGIC.toString()); input = input.replaceAll("\\Q[[RESET]]\\E", ChatColor.RESET.toString()); + // Then check for the typical color codes input = input.replaceAll("\\Q&0\\E", ChatColor.BLACK.toString()); input = input.replaceAll("\\Q&1\\E", ChatColor.DARK_BLUE.toString()); input = input.replaceAll("\\Q&2\\E", ChatColor.DARK_GREEN.toString()); @@ -352,4 +365,52 @@ public final class LocaleLoader { return input; } + + /** + * Translates hex color codes to the appropriate Minecraft color codes. + *

+ * Hex color codes are in the format of &#RRGGBB + * Minecraft color codes are in the format of §x§R§R§G§G§B§B + * Where R, G, and B are the red, green, and blue values respectively. + * The §x is a special character that tells Minecraft to use the following color codes as hex values. + * The §R§R is the red value, the §G§G is the green value, and the §B§B is the blue value. + * Example: §x§R§R§G§G§B§B is the equivalent of the hex color code &#RRGGBB + *

+ * @param messageWithHex The message with hex color codes to translate + * @return The message with the hex color codes translated to Minecraft color codes + */ + public static String translateHexColorCodes(String messageWithHex) { + if(messageWithHex == null) { + return null; + } + + final Matcher matcher = hexPattern.matcher(messageWithHex); + final StringBuilder buffer = new StringBuilder(messageWithHex.length() + 4 * 8); + while (matcher.find()) { + String group = matcher.group(1); + String hexEquivalent = "§x" + + "§" + group.charAt(0) + "§" + group.charAt(1) + + "§" + group.charAt(2) + "§" + group.charAt(3) + + "§" + group.charAt(4) + "§" + group.charAt(5); + matcher.appendReplacement(buffer, hexEquivalent); + } + return matcher.appendTail(buffer).toString(); + } + + // Method to reverse the transformation from Minecraft color codes to hex codes + public static String reverseTranslateHexColorCodes(String minecraftColorString) { + // Matches the Minecraft color pattern: §x§R§R§G§G§B§B + Matcher matcher = minecraftHexPattern.matcher(minecraftColorString); + StringBuilder buffer = new StringBuilder(); + + while (matcher.find()) { + String hexColor = "#" + + matcher.group(1).substring(1) + matcher.group(2).substring(1) + + matcher.group(3).substring(1) + matcher.group(4).substring(1) + + matcher.group(5).substring(1) + matcher.group(6).substring(1); + matcher.appendReplacement(buffer, "&" + hexColor); + } + matcher.appendTail(buffer); + return buffer.toString(); + } } diff --git a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java index d272eeb76..69256e1a2 100644 --- a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java +++ b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java @@ -20,6 +20,7 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.text.StringUtils; +import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; import java.util.ArrayList; @@ -42,8 +43,8 @@ public final class CommandRegistrationManager { command.setDescription(LocaleLoader.getString("Commands.Description.Skill", StringUtils.getCapitalized(localizedName))); command.setPermission("mcmmo.commands." + commandName); command.setPermissionMessage(permissionsMessage); - command.setUsage(LocaleLoader.getString("Commands.Usage.0", localizedName)); - command.setUsage(command.getUsage() + "\n" + LocaleLoader.getString("Commands.Usage.2", localizedName, "?", "[" + LocaleLoader.getString("Commands.Usage.Page") + "]")); + command.setUsage(LocaleLoader.getString("Commands.Usage.0", commandName)); + command.setUsage(command.getUsage() + "\n" + LocaleLoader.getString("Commands.Usage.2", commandName, "?", "[" + LocaleLoader.getString("Commands.Usage.Page") + "]")); switch (skill) { case ACROBATICS: diff --git a/src/main/java/com/gmail/nossr50/util/experience/ExperienceBarWrapper.java b/src/main/java/com/gmail/nossr50/util/experience/ExperienceBarWrapper.java index 185026ed9..898d36a05 100644 --- a/src/main/java/com/gmail/nossr50/util/experience/ExperienceBarWrapper.java +++ b/src/main/java/com/gmail/nossr50/util/experience/ExperienceBarWrapper.java @@ -4,6 +4,7 @@ import com.gmail.nossr50.config.experience.ExperienceConfig; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.locale.LocaleLoader; +import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.player.PlayerLevelUtils; import com.gmail.nossr50.util.text.StringUtils; import org.bukkit.boss.BarColor; diff --git a/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java b/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java index 3f497af07..39ddfbd24 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java +++ b/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java @@ -349,8 +349,7 @@ public class SkillTools { * @return the localized name for a {@link PrimarySkillType} */ public String getLocalizedSkillName(PrimarySkillType primarySkillType) { - //TODO: Replace with current impl - return StringUtils.getCapitalized(LocaleLoader.getString(StringUtils.getCapitalized(primarySkillType.toString()) + ".SkillName")); + return LocaleLoader.getString(StringUtils.getCapitalized(primarySkillType.toString()) + ".SkillName"); } public boolean doesPlayerHaveSkillPermission(Player player, PrimarySkillType primarySkillType) { diff --git a/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java b/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java index debcf685b..6ba0fc622 100644 --- a/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java +++ b/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java @@ -236,7 +236,7 @@ public class TextComponentFactory { private static Component getSubSkillTextComponent(Player player, SubSkillType subSkillType) { //Get skill name - String skillName = subSkillType.getLocaleName(); + final String skillName = subSkillType.getLocaleName(); boolean skillUnlocked = RankUtils.hasUnlockedSubskill(player, subSkillType); @@ -290,6 +290,11 @@ public class TextComponentFactory { return textComponent; } + private static TextComponent.Builder detectLegacyColors(String msg) { + // TODO: Impl + return null; + } + private static Component getSubSkillHoverComponent(Player player, AbstractSubSkill abstractSubSkill) { return getSubSkillHoverEventJSON(abstractSubSkill, player); } diff --git a/src/main/resources/locale/locale_en_US.properties b/src/main/resources/locale/locale_en_US.properties index 7da72244d..2a8081761 100644 --- a/src/main/resources/locale/locale_en_US.properties +++ b/src/main/resources/locale/locale_en_US.properties @@ -784,7 +784,7 @@ Commands.XPBar.Reset=&6XP Bar settings for mcMMO have been reset. Commands.XPBar.SettingChanged=&6XP Bar setting for &a{0}&6 is now set to &a{1} Commands.Skill.Invalid=That is not a valid skillname! Commands.Skill.ChildSkill=Child skills are not valid for this command! -Commands.Skill.Leaderboard=--mcMMO &9{0}&e Leaderboard-- +Commands.Skill.Leaderboard=-&e-mcMMO &9{0}&e Leaderboard-- Commands.SkillInfo=&a- View detailed information about a skill Commands.Stats=&a- View your mcMMO stats Commands.ToggleAbility=&a- Toggle ability activation with right click diff --git a/src/test/java/com/gmail/nossr50/locale/LocaleLoaderTest.java b/src/test/java/com/gmail/nossr50/locale/LocaleLoaderTest.java new file mode 100644 index 000000000..cd3ca90dd --- /dev/null +++ b/src/test/java/com/gmail/nossr50/locale/LocaleLoaderTest.java @@ -0,0 +1,77 @@ +package com.gmail.nossr50.locale; + +import org.bukkit.ChatColor; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class LocaleLoaderTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @ParameterizedTest + @ValueSource(strings = {"§cTest", "[[RED]]Test"}) + void addColorsShouldAddColorRed(String testString) { + // When + final String result = LocaleLoader.addColors(testString); + + // Then + assertThat(result).isEqualTo(ChatColor.RED + "Test"); + } + + // hex colors test + @Test + void translateHexColorCodesShouldAddRed() { + // Given + final String testString = "&#FF0000Test"; + + // When + final String result = LocaleLoader.translateHexColorCodes(testString); + + // Then + final String expectedResult = "§x§F§F§0§0§0§0Test"; + assertThat(result).isEqualTo(expectedResult); + } + + @Test + void reverseTranslateHexColorCodesShouldRemoveRed() { + // Given + final String testString = "§x§F§F§0§0§0§0Test"; + + // When + final String result = LocaleLoader.reverseTranslateHexColorCodes(testString); + + // Then + final String expectedResult = "&#FF0000Test"; + assertThat(result).isEqualTo(expectedResult); + } + + @ParameterizedTest + @ValueSource(strings = {"&#FF0000Te�FFst", "&#FF0000Te[[RED]]st", "[[BLUE]]Te[[RED]]st", "§9Te§cst"}) + void addColorsShouldAddRedAndBlue(String testString) { + // When + final String result = LocaleLoader.addColors(testString); + + // TODO: Hacky, clean this up sometime in the future + // Then + // All legal representations of the same string + final List expectedResults = List.of("§x§F§F§0§0§0§0Te§x§0§0§0§0§F§Fst", + "§x§F§F§0§0§0§0Te§x§0§0§0§0§F§Fst", + "§x§F§F§0§0§0§0Te§cst", + "§9Te§cst"); + assertThat(expectedResults).contains(result); + } +} \ No newline at end of file