Browse Source

Move logic for config format into ConfigStringUtils

nossr50 8 months ago
parent
commit
39b0e87b9a

+ 7 - 4
src/main/java/com/gmail/nossr50/config/GeneralConfig.java

@@ -18,6 +18,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getConfigPartyFeatureString;
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
+
 public class GeneralConfig extends BukkitConfig {
 
     public GeneralConfig(@NotNull File dataFolder) {
@@ -122,7 +125,7 @@ public class GeneralConfig extends BukkitConfig {
 
         for (PartyFeature partyFeature : PartyFeature.values()) {
             if (getPartyFeatureUnlockLevel(partyFeature) < 0) {
-                reason.add("Party.Leveling." + StringUtils.getPrettyPartyFeatureString(partyFeature).replace(" ", "") + "_UnlockLevel should be at least 0!");
+                reason.add("Party.Leveling." + getConfigPartyFeatureString(partyFeature) + "_UnlockLevel should be at least 0!");
             }
         }
 
@@ -627,7 +630,7 @@ public class GeneralConfig extends BukkitConfig {
     }
 
     public int getPartyFeatureUnlockLevel(PartyFeature partyFeature) {
-        return config.getInt("Party.Leveling." + StringUtils.getPrettyPartyFeatureString(partyFeature).replace(" ", "") + "_UnlockLevel", 0);
+        return config.getInt("Party.Leveling." + getConfigPartyFeatureString(partyFeature) + "_UnlockLevel", 0);
     }
 
     /* Party Teleport Settings */
@@ -711,7 +714,7 @@ public class GeneralConfig extends BukkitConfig {
         if (material.toString().equalsIgnoreCase("LILY_PAD"))
             return false;
 
-        return config.getBoolean("Bonus_Drops." + StringUtils.getCapitalized(skill.toString()) + "." + StringUtils.getFormattedMaterialString(material).replace(" ", "_"));
+        return config.getBoolean("Bonus_Drops." + StringUtils.getCapitalized(skill.toString()) + "." + getMaterialConfigString(material).replace(" ", "_"));
     }
 
     public boolean getDoubleDropsDisabled(PrimarySkillType skill) {
@@ -892,7 +895,7 @@ public class GeneralConfig extends BukkitConfig {
     /* Woodcutting */
     public boolean getWoodcuttingDoubleDropsEnabled(BlockData blockData) {
         return config.getBoolean("Bonus_Drops.Woodcutting."
-                + StringUtils.getFormattedMaterialString(blockData.getMaterial()));
+                + getMaterialConfigString(blockData.getMaterial()));
     }
 
     public boolean getTreeFellerSoundsEnabled() {

+ 7 - 5
src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java

@@ -20,6 +20,8 @@ import java.util.List;
 import java.util.Map;
 
 import static com.gmail.nossr50.util.skills.SkillTools.isChildSkill;
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getConfigEntityTypeString;
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
 
 public class ExperienceConfig extends BukkitConfig {
     private static ExperienceConfig instance;
@@ -327,11 +329,11 @@ public class ExperienceConfig extends BukkitConfig {
     }
 
     public double getCombatXP(EntityType entity) {
-        return config.getDouble("Experience_Values.Combat.Multiplier." + StringUtils.getPrettyEntityTypeString(entity).replace(" ", "_"));
+        return config.getDouble("Experience_Values.Combat.Multiplier." + getConfigEntityTypeString(entity).replace(" ", "_"));
     }
 
     public double getAnimalsXP(EntityType entity) {
-        return config.getDouble("Experience_Values.Combat.Multiplier." + StringUtils.getPrettyEntityTypeString(entity).replace(" ", "_"), getAnimalsXP());
+        return config.getDouble("Experience_Values.Combat.Multiplier." + getConfigEntityTypeString(entity).replace(" ", "_"), getAnimalsXP());
     }
 
     public double getAnimalsXP() {
@@ -339,7 +341,7 @@ public class ExperienceConfig extends BukkitConfig {
     }
 
     public boolean hasCombatXP(EntityType entity) {
-        return config.contains("Experience_Values.Combat.Multiplier." + StringUtils.getPrettyEntityTypeString(entity).replace(" ", "_"));
+        return config.contains("Experience_Values.Combat.Multiplier." + getConfigEntityTypeString(entity).replace(" ", "_"));
     }
 
     /* Materials  */
@@ -349,7 +351,7 @@ public class ExperienceConfig extends BukkitConfig {
             return 0;
 
         final String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        final String configPath = baseString + StringUtils.getFormattedMaterialString(material);
+        final String configPath = baseString + getMaterialConfigString(material);
         return config.getInt(configPath, 0);
     }
 
@@ -479,7 +481,7 @@ public class ExperienceConfig extends BukkitConfig {
 
     /* Taming */
     public int getTamingXP(EntityType type) {
-        return config.getInt("Experience_Values.Taming.Animal_Taming." + StringUtils.getPrettyEntityTypeString(type));
+        return config.getInt("Experience_Values.Taming.Animal_Taming." + getConfigEntityTypeString(type));
     }
 
     public boolean preventStoneLavaFarming() {

+ 3 - 2
src/main/java/com/gmail/nossr50/config/party/ItemWeightConfig.java

@@ -1,12 +1,13 @@
 package com.gmail.nossr50.config.party;
 
 import com.gmail.nossr50.config.BukkitConfig;
-import com.gmail.nossr50.util.text.StringUtils;
 import org.bukkit.Material;
 
 import java.util.HashSet;
 import java.util.Locale;
 
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
+
 public class ItemWeightConfig extends BukkitConfig {
     private static ItemWeightConfig instance;
 
@@ -23,7 +24,7 @@ public class ItemWeightConfig extends BukkitConfig {
     }
 
     public int getItemWeight(Material material) {
-        return config.getInt("Item_Weights." + StringUtils.getFormattedMaterialString(material).replace(" ", "_"), config.getInt("Item_Weights.Default"));
+        return config.getInt("Item_Weights." + getMaterialConfigString(material).replace(" ", "_"), config.getInt("Item_Weights.Default"));
     }
 
     public HashSet<Material> getMiscItems() {

+ 16 - 15
src/main/java/com/gmail/nossr50/config/treasure/TreasureConfig.java

@@ -7,7 +7,6 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.BlockUtils;
 import com.gmail.nossr50.util.LogUtils;
 import com.gmail.nossr50.util.PotionUtil;
-import com.gmail.nossr50.util.text.StringUtils;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
 import org.bukkit.Tag;
@@ -22,6 +21,8 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
+
 public class TreasureConfig extends BukkitConfig {
 
     public static final String FILENAME = "treasures.yml";
@@ -239,29 +240,29 @@ public class TreasureConfig extends BukkitConfig {
 
                     for (String dropper : dropList) {
                         if (dropper.equals("Bushes")) {
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.FERN), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(BlockUtils.getShortGrass()), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.FERN), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(BlockUtils.getShortGrass()), hylianTreasure);
                             for (Material species : Tag.SAPLINGS.getValues())
-                                AddHylianTreasure(StringUtils.getFormattedMaterialString(species), hylianTreasure);
+                                AddHylianTreasure(getMaterialConfigString(species), hylianTreasure);
 
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.DEAD_BUSH), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.DEAD_BUSH), hylianTreasure);
                             continue;
                         }
                         if (dropper.equals("Flowers")) {
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.POPPY), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.DANDELION), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.BLUE_ORCHID), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.ALLIUM), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.AZURE_BLUET), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.ORANGE_TULIP), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.PINK_TULIP), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.RED_TULIP), hylianTreasure);
-                            AddHylianTreasure(StringUtils.getFormattedMaterialString(Material.WHITE_TULIP), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.POPPY), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.DANDELION), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.BLUE_ORCHID), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.ALLIUM), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.AZURE_BLUET), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.ORANGE_TULIP), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.PINK_TULIP), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.RED_TULIP), hylianTreasure);
+                            AddHylianTreasure(getMaterialConfigString(Material.WHITE_TULIP), hylianTreasure);
                             continue;
                         }
                         if (dropper.equals("Pots")) {
                             for (Material species : Tag.FLOWER_POTS.getValues())
-                                AddHylianTreasure(StringUtils.getFormattedMaterialString(species), hylianTreasure);
+                                AddHylianTreasure(getMaterialConfigString(species), hylianTreasure);
                             continue;
                         }
                         AddHylianTreasure(dropper, hylianTreasure);

+ 4 - 3
src/main/java/com/gmail/nossr50/datatypes/party/PartyFeature.java

@@ -4,9 +4,10 @@ import com.gmail.nossr50.commands.party.PartySubcommandType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.text.StringUtils;
 import org.bukkit.entity.Player;
 
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getConfigPartyFeatureString;
+
 public enum PartyFeature {
     CHAT,
     TELEPORT,
@@ -15,11 +16,11 @@ public enum PartyFeature {
     XP_SHARE;
 
     public String getLocaleString() {
-        return LocaleLoader.getString("Party.Feature." + StringUtils.getPrettyPartyFeatureString(this).replace(" ", ""));
+        return LocaleLoader.getString("Party.Feature." + getConfigPartyFeatureString(this));
     }
 
     public String getFeatureLockedLocaleString() {
-        return LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Party.Feature.Locked." + StringUtils.getPrettyPartyFeatureString(this).replace(" ", ""), mcMMO.p.getGeneralConfig().getPartyFeatureUnlockLevel(this)));
+        return LocaleLoader.getString("Ability.Generic.Template.Lock", LocaleLoader.getString("Party.Feature.Locked." + getConfigPartyFeatureString(this), mcMMO.p.getGeneralConfig().getPartyFeatureUnlockLevel(this)));
     }
 
     public boolean hasPermission(Player player) {

+ 3 - 2
src/main/java/com/gmail/nossr50/skills/alchemy/AlchemyManager.java

@@ -10,11 +10,12 @@ import com.gmail.nossr50.datatypes.skills.alchemy.PotionStage;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.util.skills.RankUtils;
-import com.gmail.nossr50.util.text.StringUtils;
 import org.bukkit.inventory.ItemStack;
 
 import java.util.List;
 
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
+
 public class AlchemyManager extends SkillManager {
     private final double LUCKY_MODIFIER = 4.0 / 3.0;
 
@@ -34,7 +35,7 @@ public class AlchemyManager extends SkillManager {
         StringBuilder list = new StringBuilder();
 
         for (ItemStack ingredient : getIngredients()) {
-            String string = StringUtils.getFormattedMaterialString(ingredient.getType());
+            String string = getMaterialConfigString(ingredient.getType());
 
             list.append(", ").append(string);
         }

+ 3 - 2
src/main/java/com/gmail/nossr50/skills/excavation/Excavation.java

@@ -5,12 +5,13 @@ import com.gmail.nossr50.config.treasure.TreasureConfig;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.treasure.ExcavationTreasure;
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.util.text.StringUtils;
 import org.bukkit.block.BlockState;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
+
 public class Excavation {
     /**
      * Get the list of possible {@link ExcavationTreasure|ExcavationTreasures} obtained from a given block.
@@ -19,7 +20,7 @@ public class Excavation {
      * @return the list of treasures that could be found
      */
     protected static List<ExcavationTreasure> getTreasures(BlockState blockState) {
-        String friendly = StringUtils.getFormattedMaterialString(blockState.getBlockData().getMaterial());
+        String friendly = getMaterialConfigString(blockState.getBlockData().getMaterial());
         if (TreasureConfig.getInstance().excavationMap.containsKey(friendly))
             return TreasureConfig.getInstance().excavationMap.get(friendly);
         return new ArrayList<>();

+ 6 - 5
src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java

@@ -43,6 +43,7 @@ import java.util.*;
 
 import static com.gmail.nossr50.util.ItemUtils.hasItemIncludingOffHand;
 import static com.gmail.nossr50.util.ItemUtils.removeItemIncludingOffHand;
+import static com.gmail.nossr50.util.text.ConfigStringUtils.getMaterialConfigString;
 import static java.util.Objects.requireNonNull;
 
 public class HerbalismManager extends SkillManager {
@@ -714,10 +715,10 @@ public class HerbalismManager extends SkillManager {
             return false;
         }
 
-        final String friendly = StringUtils.getFormattedMaterialString(blockState.getBlockData().getMaterial());
-        if (!TreasureConfig.getInstance().hylianMap.containsKey(friendly))
+        final String materialConfigString = getMaterialConfigString(blockState.getBlockData().getMaterial());
+        if (!TreasureConfig.getInstance().hylianMap.containsKey(materialConfigString))
             return false;
-        List<HylianTreasure> treasures = TreasureConfig.getInstance().hylianMap.get(friendly);
+        List<HylianTreasure> treasures = TreasureConfig.getInstance().hylianMap.get(materialConfigString);
 
         if (treasures.isEmpty()) {
             return false;
@@ -750,12 +751,12 @@ public class HerbalismManager extends SkillManager {
         PlayerInventory playerInventory = getPlayer().getInventory();
         
         if (!playerInventory.contains(Material.BROWN_MUSHROOM, 1)) {
-            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getFormattedMaterialString(Material.BROWN_MUSHROOM));
+            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyMaterialString(Material.BROWN_MUSHROOM));
             return false;
         }
 
         if (!playerInventory.contains(Material.RED_MUSHROOM, 1)) {
-            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getFormattedMaterialString(Material.RED_MUSHROOM));
+            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyMaterialString(Material.RED_MUSHROOM));
             return false;
         }
 

+ 3 - 3
src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java

@@ -96,7 +96,7 @@ public class RepairManager extends SkillManager {
 
         // Level check
         if (skillLevel < minimumRepairableLevel) {
-            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Repair.Skills.Adept", String.valueOf(minimumRepairableLevel), StringUtils.getFormattedMaterialString(item.getType()));
+            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Repair.Skills.Adept", String.valueOf(minimumRepairableLevel), StringUtils.getPrettyMaterialString(item.getType()));
             return;
         }
 
@@ -115,7 +115,7 @@ public class RepairManager extends SkillManager {
 
         // Check if they have the proper material to repair with
         if (!inventory.contains(repairMaterial)) {
-            String prettyName = repairable.getRepairMaterialPrettyName() == null ? StringUtils.getFormattedMaterialString(repairMaterial) : repairable.getRepairMaterialPrettyName();
+            String prettyName = repairable.getRepairMaterialPrettyName() == null ? StringUtils.getPrettyMaterialString(repairMaterial) : repairable.getRepairMaterialPrettyName();
 
             String materialsNeeded = "";
 
@@ -156,7 +156,7 @@ public class RepairManager extends SkillManager {
 
                 // Fail out with "you need material" if we don't find a suitable alternative.
                 if (possibleMaterial.isEmpty()) {
-                    String prettyName = repairable.getRepairMaterialPrettyName() == null ? StringUtils.getFormattedMaterialString(repairMaterial) : repairable.getRepairMaterialPrettyName();
+                    String prettyName = repairable.getRepairMaterialPrettyName() == null ? StringUtils.getPrettyMaterialString(repairMaterial) : repairable.getRepairMaterialPrettyName();
 
                     String materialsNeeded = "";
 

+ 4 - 4
src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java

@@ -96,7 +96,7 @@ public class SalvageManager extends SkillManager {
         if (getSkillLevel() < minimumSalvageableLevel) {
             NotificationManager.sendPlayerInformation(player, NotificationType.REQUIREMENTS_NOT_MET,
                     "Salvage.Skills.Adept.Level",
-                    String.valueOf(minimumSalvageableLevel), StringUtils.getFormattedMaterialString(item.getType()));
+                    String.valueOf(minimumSalvageableLevel), StringUtils.getPrettyMaterialString(item.getType()));
             return;
         }
 
@@ -143,11 +143,11 @@ public class SalvageManager extends SkillManager {
 
         // We only send a confirmation message after processing the event (fixes #4694)
         if (lotteryResults == potentialSalvageYield && potentialSalvageYield != 1 && RankUtils.isPlayerMaxRankInSubSkill(player, SubSkillType.SALVAGE_ARCANE_SALVAGE)) {
-            NotificationManager.sendPlayerInformationChatOnly(player, "Salvage.Skills.Lottery.Perfect", String.valueOf(lotteryResults), StringUtils.getFormattedMaterialString(item.getType()));
+            NotificationManager.sendPlayerInformationChatOnly(player, "Salvage.Skills.Lottery.Perfect", String.valueOf(lotteryResults), StringUtils.getPrettyMaterialString(item.getType()));
         } else if (salvageable.getMaximumQuantity() == 1 || getSalvageLimit() >= salvageable.getMaximumQuantity()) {
-            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.Lottery.Normal", String.valueOf(lotteryResults), StringUtils.getFormattedMaterialString(item.getType()));
+            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.Lottery.Normal", String.valueOf(lotteryResults), StringUtils.getPrettyMaterialString(item.getType()));
         } else {
-            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.Lottery.Untrained", String.valueOf(lotteryResults), StringUtils.getFormattedMaterialString(item.getType()));
+            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.Lottery.Untrained", String.valueOf(lotteryResults), StringUtils.getPrettyMaterialString(item.getType()));
         }
 
         player.getInventory().setItemInMainHand(new ItemStack(Material.AIR));

+ 1 - 1
src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java

@@ -370,7 +370,7 @@ public class TamingManager extends SkillManager {
             } else {
                 //Player did not have enough of the item in their main hand
                 int difference = tamingSummon.getItemAmountRequired() - itemInMainHand.getAmount();
-                NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.NeedMoreItems", String.valueOf(difference), StringUtils.getFormattedMaterialString(itemInMainHand.getType()));
+                NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.NeedMoreItems", String.valueOf(difference), StringUtils.getPrettyMaterialString(itemInMainHand.getType()));
             }
         }
     }

+ 81 - 0
src/main/java/com/gmail/nossr50/util/text/ConfigStringUtils.java

@@ -0,0 +1,81 @@
+package com.gmail.nossr50.util.text;
+
+import com.gmail.nossr50.datatypes.party.PartyFeature;
+import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
+import org.bukkit.Material;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+import static com.gmail.nossr50.util.text.StringUtils.getCapitalized;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Utility class for String operations, including formatting and caching deterministic results to improve performance.
+ */
+public class ConfigStringUtils {
+    public static final String UNDERSCORE = "_";
+    public static final String SPACE = " ";
+
+    // Using concurrent hash maps to avoid concurrency issues (Folia)
+    private static final Map<EntityType, String> configEntityStrings = new ConcurrentHashMap<>();
+    private static final Map<SuperAbilityType, String> configSuperAbilityStrings = new ConcurrentHashMap<>();
+    private static final Map<Material, String> configMaterialStrings = new ConcurrentHashMap<>();
+    private static final Map<PartyFeature, String> configPartyFeatureStrings = new ConcurrentHashMap<>();
+    
+    public static String getConfigSuperAbilityString(SuperAbilityType superAbilityType) {
+        requireNonNull(superAbilityType, "superAbilityType cannot be null");
+        return configSuperAbilityStrings.computeIfAbsent(superAbilityType,
+                ConfigStringUtils::createConfigFriendlyString);
+    }
+
+    public static String getMaterialConfigString(Material material) {
+        return configMaterialStrings.computeIfAbsent(material, ConfigStringUtils::createConfigFriendlyString);
+    }
+
+    public static String getConfigEntityTypeString(EntityType entityType) {
+        return configEntityStrings.computeIfAbsent(entityType, ConfigStringUtils::createConfigFriendlyString);
+    }
+
+    public static String getConfigPartyFeatureString(PartyFeature partyFeature) {
+        return configPartyFeatureStrings.computeIfAbsent(partyFeature,
+                // For whatever dumb reason, party feature enums got formatted like this...
+                pf -> createConfigFriendlyString(pf.name()).replace(UNDERSCORE, ""));
+    }
+
+    private static String createConfigFriendlyString(String baseString) {
+        return CONFIG_FRIENDLY_STRING_FORMATTER.apply(baseString);
+    }
+
+    private static final Function<String, String> CONFIG_FRIENDLY_STRING_FORMATTER = baseString -> {
+        if (baseString.contains(UNDERSCORE) && !baseString.contains(SPACE)) {
+            return asConfigFormat(baseString.split(UNDERSCORE));
+        } else {
+            if(baseString.contains(SPACE)) {
+                return asConfigFormat(baseString.split(SPACE));
+            } else{
+                return getCapitalized(baseString);
+            }
+        }
+    };
+
+    private static @NotNull String asConfigFormat(String[] substrings) {
+        final StringBuilder configString = new StringBuilder();
+
+        for (int i = 0; i < substrings.length; i++) {
+            configString.append(getCapitalized(substrings[i]));
+            if (i < substrings.length - 1) {
+                configString.append(UNDERSCORE);
+            }
+        }
+
+        return configString.toString();
+    }
+
+    private static String createConfigFriendlyString(Object object) {
+        return createConfigFriendlyString(object.toString());
+    }
+}

+ 9 - 42
src/main/java/com/gmail/nossr50/util/text/StringUtils.java

@@ -1,16 +1,14 @@
 package com.gmail.nossr50.util.text;
 
-import com.gmail.nossr50.datatypes.party.PartyFeature;
 import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
 import org.bukkit.Material;
-import org.bukkit.block.data.BlockData;
 import org.bukkit.entity.EntityType;
 import org.jetbrains.annotations.NotNull;
 
 import java.text.DecimalFormat;
-import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
 import static java.util.Objects.requireNonNull;
@@ -24,10 +22,9 @@ public class StringUtils {
     protected static final DecimalFormat shortDecimal = new DecimalFormat("##0.0");
 
     // Using concurrent hash maps to avoid concurrency issues (Folia)
-    private static final Map<EntityType, String> formattedEntityStrings = new HashMap<>();
-    private static final Map<SuperAbilityType, String> formattedSuperAbilityStrings = new HashMap<>();
-    private static final Map<Material, String> formattedMaterialStrings = new HashMap<>();
-    private static final Map<PartyFeature, String> prettyPartyFeatureStringCache = new HashMap<>();
+    private static final Map<EntityType, String> formattedEntityStrings = new ConcurrentHashMap<>();
+    private static final Map<SuperAbilityType, String> formattedSuperAbilityStrings = new ConcurrentHashMap<>();
+    private static final Map<Material, String> formattedMaterialStrings = new ConcurrentHashMap<>();
 
     /**
      * Gets a capitalized version of the target string.
@@ -73,7 +70,10 @@ public class StringUtils {
      * @return The "trimmed" string
      */
     public static String buildStringAfterNthElement(@NotNull String @NotNull [] args, int index) {
-        StringBuilder trimMessage = new StringBuilder();
+        if (index < 0)
+            throw new IllegalArgumentException("Index must be greater than or equal to 0");
+
+        final StringBuilder trimMessage = new StringBuilder();
 
         for (int i = index; i < args.length; i++) {
             if (i > index) {
@@ -92,7 +92,7 @@ public class StringUtils {
      * @param material Material to convert
      * @return Pretty string representation of the Material
      */
-    public static String getFormattedMaterialString(Material material) {
+    public static String getPrettyMaterialString(Material material) {
         return formattedMaterialStrings.computeIfAbsent(material, StringUtils::createPrettyString);
     }
 
@@ -107,39 +107,6 @@ public class StringUtils {
         return formattedEntityStrings.computeIfAbsent(entityType, StringUtils::createPrettyString);
     }
 
-    /**
-     * Gets a wildcard configuration string for BlockData.
-     * Results are cached to improve performance.
-     *
-     * @param blockData BlockData to convert
-     * @return Wildcard configuration string
-     */
-    public static String getWildcardConfigBlockDataString(BlockData blockData) {
-        return getFormattedMaterialString(blockData.getMaterial());
-    }
-
-    /**
-     * Gets an explicit configuration string for BlockData.
-     * Results are cached to improve performance.
-     *
-     * @param data BlockData to convert
-     * @return Explicit configuration string
-     */
-    public static String getExplicitConfigBlockDataString(BlockData data) {
-        return getFormattedMaterialString(data.getMaterial());
-    }
-
-    /**
-     * Gets a pretty string representation of a PartyFeature.
-     * Results are cached to improve performance.
-     *
-     * @param partyFeature PartyFeature to convert
-     * @return Pretty string representation
-     */
-    public static String getPrettyPartyFeatureString(PartyFeature partyFeature) {
-        return prettyPartyFeatureStringCache.computeIfAbsent(partyFeature, StringUtils::createPrettyString);
-    }
-
     /**
      * Creates a pretty string from a base string by splitting underscores and capitalizing words.
      *

+ 323 - 0
src/test/java/com/gmail/nossr50/util/text/StringUtilsTest.java

@@ -0,0 +1,323 @@
+package com.gmail.nossr50.util.text;
+
+import org.bukkit.Material;
+import org.bukkit.entity.EntityType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.gmail.nossr50.datatypes.party.PartyFeature;
+import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class StringUtilsTest {
+
+    @BeforeEach
+    void setUp() {
+        // Clear caches before each test to ensure test isolation
+        clearCaches();
+    }
+
+    /**
+     * Utility method to clear all caches in StringUtils.
+     * Reflection is used since the caches are private.
+     */
+    private void clearCaches() {
+        try {
+            java.lang.reflect.Field entityCache = StringUtils.class.getDeclaredField("formattedEntityStrings");
+            entityCache.setAccessible(true);
+            ((java.util.Map<?, ?>) entityCache.get(null)).clear();
+
+            java.lang.reflect.Field superAbilityCache = StringUtils.class.getDeclaredField("formattedSuperAbilityStrings");
+            superAbilityCache.setAccessible(true);
+            ((java.util.Map<?, ?>) superAbilityCache.get(null)).clear();
+
+            java.lang.reflect.Field materialCache = StringUtils.class.getDeclaredField("formattedMaterialStrings");
+            materialCache.setAccessible(true);
+            ((java.util.Map<?, ?>) materialCache.get(null)).clear();
+
+            java.lang.reflect.Field partyFeatureCache = StringUtils.class.getDeclaredField("prettyPartyFeatureStringCache");
+            partyFeatureCache.setAccessible(true);
+            ((java.util.Map<?, ?>) partyFeatureCache.get(null)).clear();
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            fail("Failed to clear caches: " + e.getMessage());
+        }
+    }
+
+    // Tests for getCapitalized(String target)
+    @Test
+    void testGetCapitalized_NullInput() {
+        assertNull(StringUtils.getCapitalized(null));
+    }
+
+    @Test
+    void testGetCapitalized_EmptyString() {
+        assertEquals("", StringUtils.getCapitalized(""));
+    }
+
+    @Test
+    void testGetCapitalized_SingleCharacter() {
+        assertEquals("A", StringUtils.getCapitalized("a"));
+        assertEquals("Z", StringUtils.getCapitalized("Z"));
+    }
+
+    @Test
+    void testGetCapitalized_AllUppercase() {
+        assertEquals("Test", StringUtils.getCapitalized("TEST"));
+    }
+
+    @Test
+    void testGetCapitalized_AllLowercase() {
+        assertEquals("Test", StringUtils.getCapitalized("test"));
+    }
+
+    @Test
+    void testGetCapitalized_MixedCase() {
+        assertEquals("Test", StringUtils.getCapitalized("tEsT"));
+    }
+
+    @Test
+    void testGetCapitalized_NonASCII() {
+        assertEquals("Ñandú", StringUtils.getCapitalized("ñandú"));
+    }
+
+    // Tests for ticksToSeconds(double ticks)
+    @Test
+    void testTicksToSeconds_PositiveTicks() {
+        assertEquals("1.5", StringUtils.ticksToSeconds(30));
+    }
+
+    @Test
+    void testTicksToSeconds_ZeroTicks() {
+        assertEquals("0.0", StringUtils.ticksToSeconds(0));
+    }
+
+    @Test
+    void testTicksToSeconds_FractionalTicks() {
+        assertEquals("1.5", StringUtils.ticksToSeconds(30.0));
+        assertEquals("1.5", StringUtils.ticksToSeconds(30.0));
+        assertEquals("1.0", StringUtils.ticksToSeconds(20.0));
+        assertEquals("0.1", StringUtils.ticksToSeconds(2.0));
+    }
+
+    @Test
+    void testTicksToSeconds_NegativeTicks() {
+        assertEquals("-1.0", StringUtils.ticksToSeconds(-20));
+    }
+
+    // Tests for getPrettySuperAbilityString(SuperAbilityType superAbilityType)
+    @Test
+    void testGetPrettySuperAbilityString_NullInput() {
+        assertThrows(NullPointerException.class, () -> {
+            StringUtils.getPrettySuperAbilityString(null);
+        });
+    }
+
+    @Test
+    void testGetPrettySuperAbilityString_ValidInput() {
+        SuperAbilityType superAbilityType = SuperAbilityType.SUPER_BREAKER;
+        String expected = "Super Breaker";
+        String actual = StringUtils.getPrettySuperAbilityString(superAbilityType);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testGetPrettySuperAbilityString_Caching() {
+        SuperAbilityType superAbilityType = SuperAbilityType.SUPER_BREAKER;
+
+        // First call should compute and cache
+        String firstCall = StringUtils.getPrettySuperAbilityString(superAbilityType);
+
+        // Second call should retrieve from cache
+        String secondCall = StringUtils.getPrettySuperAbilityString(superAbilityType);
+
+        assertSame(firstCall, secondCall, "Cached value should be the same instance");
+    }
+
+    // Tests for getPrettyEntityTypeString(EntityType entityType)
+    @Test
+    void testGetPrettyEntityTypeString_ValidInput() {
+        EntityType zombie = EntityType.ZOMBIE;
+        String expected = "Zombie";
+        String actual = StringUtils.getPrettyEntityTypeString(zombie);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testGetPrettyEntityTypeString_WithUnderscores() {
+        EntityType entity = EntityType.SKELETON_HORSE;
+        String expected = "Skeleton Horse";
+        String actual = StringUtils.getPrettyEntityTypeString(entity);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testGetPrettyEntityTypeString_Caching() {
+        EntityType skeleton = EntityType.SKELETON;
+
+        // First call should compute and cache
+        String firstCall = StringUtils.getPrettyEntityTypeString(skeleton);
+
+        // Second call should retrieve from cache
+        String secondCall = StringUtils.getPrettyEntityTypeString(skeleton);
+
+        assertSame(firstCall, secondCall, "Cached value should be the same instance");
+    }
+
+    // Tests for getFormattedMaterialString(Material material)
+    @Test
+    void testGetPrettyMaterialString_ValidInput() {
+        Material diamondSword = Material.DIAMOND_SWORD;
+        String expected = "Diamond Sword";
+        String actual = StringUtils.getPrettyMaterialString(diamondSword);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testGetPrettyMaterialString_WithUnderscores() {
+        Material goldenApple = Material.GOLDEN_APPLE;
+        String expected = "Golden Apple";
+        String actual = StringUtils.getPrettyMaterialString(goldenApple);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testGetPrettyMaterialString_Caching() {
+        Material ironPickaxe = Material.IRON_PICKAXE;
+
+        // First call should compute and cache
+        String firstCall = StringUtils.getPrettyMaterialString(ironPickaxe);
+
+        // Second call should retrieve from cache
+        String secondCall = StringUtils.getPrettyMaterialString(ironPickaxe);
+
+        assertSame(firstCall, secondCall, "Cached value should be the same instance");
+    }
+
+    // Tests for buildStringAfterNthElement(String[] args, int index)
+    @Test
+    void testBuildStringAfterNthElement_IndexZero() {
+        String[] args = {"Hello", "World", "Test"};
+        String expected = "Hello World Test";
+        String actual = StringUtils.buildStringAfterNthElement(args, 0);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testBuildStringAfterNthElement_IndexMiddle() {
+        String[] args = {"This", "is", "a", "test"};
+        String expected = "a test";
+        String actual = StringUtils.buildStringAfterNthElement(args, 2);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testBuildStringAfterNthElement_IndexLast() {
+        String[] args = {"Only", "One"};
+        String expected = "One";
+        String actual = StringUtils.buildStringAfterNthElement(args, 1);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testBuildStringAfterNthElement_IndexOutOfBounds() {
+        String[] args = {"Too", "Short"};
+        String expected = "";
+        String actual = StringUtils.buildStringAfterNthElement(args, 5);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testBuildStringAfterNthElement_EmptyArray() {
+        String[] args = {};
+        String expected = "";
+        String actual = StringUtils.buildStringAfterNthElement(args, 0);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testBuildStringAfterNthElement_ArgsWithSpaces() {
+        String[] args = {"Multiple", " ", "Spaces"};
+        String expected = "  Spaces";
+        String actual = StringUtils.buildStringAfterNthElement(args, 1);
+        assertEquals(expected, actual);
+    }
+
+    // Tests for isInt(String string)
+    @Test
+    void testIsInt_ValidIntegers() {
+        assertTrue(StringUtils.isInt("123"));
+        assertTrue(StringUtils.isInt("-456"));
+        assertTrue(StringUtils.isInt("0"));
+    }
+
+    @Test
+    void testIsInt_InvalidIntegers() {
+        assertFalse(StringUtils.isInt("123.45"));
+        assertFalse(StringUtils.isInt("abc"));
+        assertFalse(StringUtils.isInt(""));
+        assertFalse(StringUtils.isInt(" "));
+        assertFalse(StringUtils.isInt(null)); // This will throw NullPointerException
+    }
+
+    // Tests for isDouble(String string)
+    @Test
+    void testIsDouble_ValidDoubles() {
+        assertTrue(StringUtils.isDouble("123.45"));
+        assertTrue(StringUtils.isDouble("-456.78"));
+        assertTrue(StringUtils.isDouble("0.0"));
+        assertTrue(StringUtils.isDouble("1e10"));
+    }
+
+    @Test
+    void testIsDouble_InvalidDoubles() {
+        assertFalse(StringUtils.isDouble("abc"));
+        assertFalse(StringUtils.isDouble(""));
+        assertFalse(StringUtils.isDouble(" "));
+        assertFalse(StringUtils.isDouble("123.45.67"));
+    }
+
+    @Test
+    void testIsDouble_NullInput() {
+        assertThrows(NullPointerException.class, () -> {
+            StringUtils.isDouble(null);
+        });
+    }
+
+    @Test
+    void testCachingMechanism_EntityType() {
+        EntityType zombie = EntityType.ZOMBIE;
+
+        String firstCall = StringUtils.getPrettyEntityTypeString(zombie);
+        String secondCall = StringUtils.getPrettyEntityTypeString(zombie);
+
+        assertSame(firstCall, secondCall, "EntityType caching failed");
+    }
+
+    @Test
+    void testCachingMechanism_Material() {
+        Material diamondSword = Material.DIAMOND_SWORD;
+
+        String firstCall = StringUtils.getPrettyMaterialString(diamondSword);
+        String secondCall = StringUtils.getPrettyMaterialString(diamondSword);
+
+        assertSame(firstCall, secondCall, "Material caching failed");
+    }
+
+    // Tests for createPrettyString via public methods
+    @Test
+    void testCreatePrettyString_Spaces() {
+        String[] args = {"hello", "world"};
+        String expected = "hello world";
+        String actual = StringUtils.buildStringAfterNthElement(args, 0);
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    void testPrettify_Substrings() {
+        Material goldenApple = Material.GOLDEN_APPLE;
+        String expected = "Golden Apple";
+        String actual = StringUtils.getPrettyMaterialString(goldenApple);
+        assertEquals(expected, actual);
+    }
+}