diff --git a/src/main/java/net/tfminecraft/geminfusion/ConfigLoader.java b/src/main/java/net/tfminecraft/geminfusion/ConfigLoader.java index d6f8c52..33c4881 100644 --- a/src/main/java/net/tfminecraft/geminfusion/ConfigLoader.java +++ b/src/main/java/net/tfminecraft/geminfusion/ConfigLoader.java @@ -65,14 +65,9 @@ public Gemstone getGemFromConfig(FileConfiguration config, String key) { gem.setSocketColour(config.getConfigurationSection("gems."+key).getString("socket_colour")); gem.setSocketNameColour(ChatColor.valueOf(config.getConfigurationSection("gems."+key).getString("socket_name_colour").toUpperCase())); gem.setMMOItem(config.getConfigurationSection("gems."+key).getString("gem")); - Set set = config.getConfigurationSection("gems."+key+".rarities").getKeys(false); - - List list = new ArrayList(set); - for(String id : list) { - GemStat stat = GemStat.parse(key, id, config.getConfigurationSection("gems."+key+".rarities."+id)); - if (stat != null) { - gem.addStat(stat); - } + for (GemStat stat : GemStat.parseGem(key, config.getConfigurationSection("gems."+key), + GemStat.ownSection(config, "stat-bands"))) { + gem.addStat(stat); } return gem; } diff --git a/src/main/java/net/tfminecraft/geminfusion/GemStat.java b/src/main/java/net/tfminecraft/geminfusion/GemStat.java index 8c8eedc..637d6d3 100644 --- a/src/main/java/net/tfminecraft/geminfusion/GemStat.java +++ b/src/main/java/net/tfminecraft/geminfusion/GemStat.java @@ -1,11 +1,17 @@ package net.tfminecraft.geminfusion; +import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.bukkit.configuration.ConfigurationSection; public class GemStat { + /** {@code min-max}, or one number for a fixed stat. */ + private static final Pattern RANGE = Pattern.compile("(\\d+(?:\\.\\d+)?)(?:\\s*-\\s*(\\d+(?:\\.\\d+)?))?"); + private final String id; private final String statId; private final double min; @@ -35,7 +41,70 @@ public double getMax() { } /** - * New shape: {@code stat} / {@code min} / {@code max}. + * Reads a gem's stat for each rarity. Compact shape: one {@code stat} for the gem and {@code bands}, either the + * name of a set under the top-level {@code stat-bands} or a section of {@code : min-max}. Older configs + * list {@code rarities.} blocks instead (see {@link #parse}), which still load. + * Only keys written in this config count: Bukkit falls back to the bundled config.yml for missing keys, which + * would hand an older server config the bundled bands (or empty sections made from them). + */ + public static List parseGem(String gemId, ConfigurationSection gem, ConfigurationSection statBands) { + List stats = new ArrayList<>(); + Object bandsValue = gem.get("bands", null); + if (bandsValue != null) { + if (gem.get("rarities", null) != null) { + warn("Gem '" + gemId + "' has both bands and rarities; using bands."); + } + String statId = gem.getString("stat", null); + if (statId == null || statId.isBlank()) { + warn("Gem '" + gemId + "' has bands but no stat, skipping its stats."); + return stats; + } + ConfigurationSection bands = bandsValue instanceof ConfigurationSection own ? own + : ownSection(statBands, String.valueOf(bandsValue)); + if (bands == null) { + warn("Gem '" + gemId + "' uses unknown stat-bands set '" + bandsValue + "', skipping its stats."); + return stats; + } + for (String rarityId : bands.getKeys(false)) { + GemStat stat = parseRange(gemId, rarityId, statId, bands.getString(rarityId, null)); + if (stat != null) { + stats.add(stat); + } + } + return stats; + } + ConfigurationSection rarities = ownSection(gem, "rarities"); + if (rarities == null) { + warn("Gem '" + gemId + "' has neither bands nor rarities, skipping its stats."); + return stats; + } + for (String rarityId : rarities.getKeys(false)) { + GemStat stat = parse(gemId, rarityId, rarities.getConfigurationSection(rarityId)); + if (stat != null) { + stats.add(stat); + } + } + return stats; + } + + /** The section written at this key of this config, ignoring the bundled defaults; null when there is none. */ + static ConfigurationSection ownSection(ConfigurationSection parent, String key) { + return parent != null && parent.get(key, null) instanceof ConfigurationSection own ? own : null; + } + + private static GemStat parseRange(String gemId, String rarityId, String statId, String text) { + Matcher range = RANGE.matcher(String.valueOf(text).trim()); + if (!range.matches()) { + warn("Gem '" + gemId + "' " + rarityId + " band '" + text + "' is not min-max, skipping."); + return null; + } + double min = Double.parseDouble(range.group(1)); + double max = range.group(2) == null ? min : Double.parseDouble(range.group(2)); + return new GemStat(rarityId, statId, min, max); + } + + /** + * Old per-rarity shape: {@code stat} / {@code min} / {@code max}. * Legacy: first entry of {@code stats: - type(min-max)}. * Returns null when the block cannot be used. */ diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ff85a28..b0a3939 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -72,6 +72,38 @@ rarities: name: "ยง6Legendary" chance: 3 announce: true + +# Stat ranges for each rarity, shared by gems that roll the same numbers. Each range is min-max, +# before the d20 spread. +stat-bands: + tier-1: + common: 1.00-1.20 + rare: 1.30-1.50 + epic: 1.60-1.80 + legendary: 1.90-2.00 + tier-2: + common: 2.00-2.20 + rare: 2.30-2.50 + epic: 2.60-2.80 + legendary: 2.90-3.00 + tier-3: + common: 3.00-3.20 + rare: 3.30-3.50 + epic: 3.60-3.80 + legendary: 3.90-4.00 + tier-4: + common: 4.00-4.20 + rare: 4.30-4.50 + epic: 4.60-4.80 + legendary: 4.90-5.00 + tier-5: + common: 5.00-5.20 + rare: 5.30-5.50 + epic: 5.60-5.80 + legendary: 5.90-6.00 + +# Each gem gives one stat. bands names a stat-bands set, or lists the gem's own min-max for each rarity. +# Older configs that list stat, min and max under rarities: still load. gems: agate: gem: gemstones.agate @@ -81,917 +113,349 @@ gems: colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: max_health - min: 0.20 - max: 0.30 - rare: - stat: max_health - min: 0.30 - max: 0.40 - epic: - stat: max_health - min: 0.40 - max: 0.50 - legendary: - stat: max_health - min: 0.50 - max: 0.60 + stat: max_health + bands: + common: 0.20-0.30 + rare: 0.30-0.40 + epic: 0.40-0.50 + legendary: 0.50-0.60 jasper: gem: gemstones.jasper name: "Jasper" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: armor - min: 0.05 - max: 0.10 - rare: - stat: armor - min: 0.10 - max: 0.15 - epic: - stat: armor - min: 0.15 - max: 0.20 - legendary: - stat: armor - min: 0.20 - max: 0.25 + stat: armor + bands: + common: 0.05-0.10 + rare: 0.10-0.15 + epic: 0.15-0.20 + legendary: 0.20-0.25 onyx: gem: gemstones.onyx name: "Onyx" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage_reduction - min: 1.00 - max: 1.20 - rare: - stat: physical_damage_reduction - min: 1.30 - max: 1.50 - epic: - stat: physical_damage_reduction - min: 1.60 - max: 1.80 - legendary: - stat: physical_damage_reduction - min: 1.90 - max: 2.00 + stat: physical_damage_reduction + bands: tier-1 tourmaline: gem: gemstones.tourmaline name: "Tourmaline" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage_reduction - min: 1.00 - max: 1.20 - rare: - stat: projectile_damage_reduction - min: 1.30 - max: 1.50 - epic: - stat: projectile_damage_reduction - min: 1.60 - max: 1.80 - legendary: - stat: projectile_damage_reduction - min: 1.90 - max: 2.00 + stat: projectile_damage_reduction + bands: tier-1 pearl: gem: gemstones.pearl name: "Pearl" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage_reduction - min: 1.00 - max: 1.20 - rare: - stat: magic_damage_reduction - min: 1.30 - max: 1.50 - epic: - stat: magic_damage_reduction - min: 1.60 - max: 1.80 - legendary: - stat: magic_damage_reduction - min: 1.90 - max: 2.00 + stat: magic_damage_reduction + bands: tier-1 coral: gem: gemstones.coral name: "Coral" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage - min: 2.00 - max: 2.20 - rare: - stat: physical_damage - min: 2.30 - max: 2.50 - epic: - stat: physical_damage - min: 2.60 - max: 2.80 - legendary: - stat: physical_damage - min: 2.90 - max: 3.00 + stat: physical_damage + bands: tier-2 chrysoprase: gem: gemstones.chrysoprase name: "Chrysoprase" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage - min: 2.00 - max: 2.20 - rare: - stat: projectile_damage - min: 2.30 - max: 2.50 - epic: - stat: projectile_damage - min: 2.60 - max: 2.80 - legendary: - stat: projectile_damage - min: 2.90 - max: 3.00 + stat: projectile_damage + bands: tier-2 larimar: gem: gemstones.larimar name: "Larimar" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage - min: 2.00 - max: 2.20 - rare: - stat: magic_damage - min: 2.30 - max: 2.50 - epic: - stat: magic_damage - min: 2.60 - max: 2.80 - legendary: - stat: magic_damage - min: 2.90 - max: 3.00 + stat: magic_damage + bands: tier-2 rhodonite: gem: gemstones.rhodonite name: "Rhodonite" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: spell_vampirism - min: 2.00 - max: 2.20 - rare: - stat: spell_vampirism - min: 2.30 - max: 2.50 - epic: - stat: spell_vampirism - min: 2.60 - max: 2.80 - legendary: - stat: spell_vampirism - min: 2.90 - max: 3.00 + stat: spell_vampirism + bands: tier-2 vesuvianite: gem: gemstones.vesuvianite name: "Vesuvianite" colour: white socket_colour: Basic Gemstone socket_name_colour: white - rarities: - common: - stat: lifesteal - min: 2.00 - max: 2.20 - rare: - stat: lifesteal - min: 2.30 - max: 2.50 - epic: - stat: lifesteal - min: 2.60 - max: 2.80 - legendary: - stat: lifesteal - min: 2.90 - max: 3.00 + stat: lifesteal + bands: tier-2 turquoise: gem: gemstones.turquoise name: "Turquoise" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: max_health - min: 0.60 - max: 0.70 - rare: - stat: max_health - min: 0.70 - max: 0.80 - epic: - stat: max_health - min: 0.80 - max: 0.90 - legendary: - stat: max_health - min: 0.90 - max: 1.00 + stat: max_health + bands: + common: 0.60-0.70 + rare: 0.70-0.80 + epic: 0.80-0.90 + legendary: 0.90-1.00 peridot: gem: gemstones.peridot name: "Peridot" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: armor - min: 0.25 - max: 0.30 - rare: - stat: armor - min: 0.30 - max: 0.35 - epic: - stat: armor - min: 0.35 - max: 0.40 - legendary: - stat: armor - min: 0.40 - max: 0.45 + stat: armor + bands: + common: 0.25-0.30 + rare: 0.30-0.35 + epic: 0.35-0.40 + legendary: 0.40-0.45 malachite: gem: gemstones.malachite name: "Malachite" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage_reduction - min: 2.00 - max: 2.20 - rare: - stat: physical_damage_reduction - min: 2.30 - max: 2.50 - epic: - stat: physical_damage_reduction - min: 2.60 - max: 2.80 - legendary: - stat: physical_damage_reduction - min: 2.90 - max: 3.00 + stat: physical_damage_reduction + bands: tier-2 zircon: gem: gemstones.zircon name: "Zircon" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage_reduction - min: 2.00 - max: 2.20 - rare: - stat: projectile_damage_reduction - min: 2.30 - max: 2.50 - epic: - stat: projectile_damage_reduction - min: 2.60 - max: 2.80 - legendary: - stat: projectile_damage_reduction - min: 2.90 - max: 3.00 + stat: projectile_damage_reduction + bands: tier-2 apatite: gem: gemstones.apatite name: "Apatite" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage_reduction - min: 2.00 - max: 2.20 - rare: - stat: magic_damage_reduction - min: 2.30 - max: 2.50 - epic: - stat: magic_damage_reduction - min: 2.60 - max: 2.80 - legendary: - stat: magic_damage_reduction - min: 2.90 - max: 3.00 + stat: magic_damage_reduction + bands: tier-2 carnelian: gem: gemstones.carnelian name: "Carnelian" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage - min: 3.00 - max: 3.20 - rare: - stat: physical_damage - min: 3.30 - max: 3.50 - epic: - stat: physical_damage - min: 3.60 - max: 3.80 - legendary: - stat: physical_damage - min: 3.90 - max: 4.00 + stat: physical_damage + bands: tier-3 labradorite: gem: gemstones.labradorite name: "Labradorite" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage - min: 3.00 - max: 3.20 - rare: - stat: projectile_damage - min: 3.30 - max: 3.50 - epic: - stat: projectile_damage - min: 3.60 - max: 3.80 - legendary: - stat: projectile_damage - min: 3.90 - max: 4.00 + stat: projectile_damage + bands: tier-3 sardonyx: gem: gemstones.sardonyx name: "Sardonyx" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage - min: 3.00 - max: 3.20 - rare: - stat: magic_damage - min: 3.30 - max: 3.50 - epic: - stat: magic_damage - min: 3.60 - max: 3.80 - legendary: - stat: magic_damage - min: 3.90 - max: 4.00 + stat: magic_damage + bands: tier-3 variscite: gem: gemstones.variscite name: "Variscite" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: spell_vampirism - min: 3.00 - max: 3.20 - rare: - stat: spell_vampirism - min: 3.30 - max: 3.50 - epic: - stat: spell_vampirism - min: 3.60 - max: 3.80 - legendary: - stat: spell_vampirism - min: 3.90 - max: 4.00 + stat: spell_vampirism + bands: tier-3 wulfenite: gem: gemstones.wulfenite name: "Wulfenite" colour: white socket_colour: Polished Gemstone socket_name_colour: white - rarities: - common: - stat: lifesteal - min: 3.00 - max: 3.20 - rare: - stat: lifesteal - min: 3.30 - max: 3.50 - epic: - stat: lifesteal - min: 3.60 - max: 3.80 - legendary: - stat: lifesteal - min: 3.90 - max: 4.00 + stat: lifesteal + bands: tier-3 aquamarine: gem: gemstones.aquamarine name: "Aquamarine" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: max_health - min: 1.00 - max: 1.10 - rare: - stat: max_health - min: 1.10 - max: 1.20 - epic: - stat: max_health - min: 1.20 - max: 1.30 - legendary: - stat: max_health - min: 1.40 - max: 1.50 + stat: max_health + bands: + common: 1.00-1.10 + rare: 1.10-1.20 + epic: 1.20-1.30 + legendary: 1.40-1.50 garnet: gem: gemstones.garnet name: "Garnet" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: armor - min: 0.45 - max: 0.50 - rare: - stat: armor - min: 0.50 - max: 0.55 - epic: - stat: armor - min: 0.55 - max: 0.60 - legendary: - stat: armor - min: 0.60 - max: 0.65 + stat: armor + bands: + common: 0.45-0.50 + rare: 0.50-0.55 + epic: 0.55-0.60 + legendary: 0.60-0.65 opal: gem: gemstones.opal name: "Opal" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage_reduction - min: 3.00 - max: 3.20 - rare: - stat: physical_damage_reduction - min: 3.30 - max: 3.50 - epic: - stat: physical_damage_reduction - min: 3.60 - max: 3.80 - legendary: - stat: physical_damage_reduction - min: 3.90 - max: 4.00 + stat: physical_damage_reduction + bands: tier-3 tanzanite: gem: gemstones.tanzanite name: "Tanzanite" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage_reduction - min: 3.00 - max: 3.20 - rare: - stat: projectile_damage_reduction - min: 3.30 - max: 3.50 - epic: - stat: projectile_damage_reduction - min: 3.60 - max: 3.80 - legendary: - stat: projectile_damage_reduction - min: 3.90 - max: 4.00 + stat: projectile_damage_reduction + bands: tier-3 moonstone: gem: gemstones.moonstone name: "Moonstone" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage_reduction - min: 3.00 - max: 3.20 - rare: - stat: magic_damage_reduction - min: 3.30 - max: 3.50 - epic: - stat: magic_damage_reduction - min: 3.60 - max: 3.80 - legendary: - stat: magic_damage_reduction - min: 3.90 - max: 4.00 + stat: magic_damage_reduction + bands: tier-3 sunstone: gem: gemstones.sunstone name: "Sunstone" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage - min: 4.00 - max: 4.20 - rare: - stat: physical_damage - min: 4.30 - max: 4.50 - epic: - stat: physical_damage - min: 4.60 - max: 4.80 - legendary: - stat: physical_damage - min: 4.90 - max: 5.00 + stat: physical_damage + bands: tier-4 spinel: gem: gemstones.spinel name: "Spinel" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage - min: 4.00 - max: 4.20 - rare: - stat: projectile_damage - min: 4.30 - max: 4.50 - epic: - stat: projectile_damage - min: 4.60 - max: 4.80 - legendary: - stat: projectile_damage - min: 4.90 - max: 5.00 + stat: projectile_damage + bands: tier-4 alexandrite: gem: gemstones.alexandrite name: "Alexandrite" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage - min: 4.00 - max: 4.20 - rare: - stat: magic_damage - min: 4.30 - max: 4.50 - epic: - stat: magic_damage - min: 4.60 - max: 4.80 - legendary: - stat: magic_damage - min: 4.90 - max: 5.00 + stat: magic_damage + bands: tier-4 firestone: gem: gemstones.firestone name: "Direstone" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: spell_vampirism - min: 4.00 - max: 4.20 - rare: - stat: spell_vampirism - min: 4.30 - max: 4.50 - epic: - stat: spell_vampirism - min: 4.60 - max: 4.80 - legendary: - stat: spell_vampirism - min: 4.90 - max: 5.00 + stat: spell_vampirism + bands: tier-4 cloudstone: gem: gemstones.cloudstone name: "Cloudstone" colour: white socket_colour: Radiant Gemstone socket_name_colour: white - rarities: - common: - stat: lifesteal - min: 4.00 - max: 4.20 - rare: - stat: lifesteal - min: 4.30 - max: 4.50 - epic: - stat: lifesteal - min: 4.60 - max: 4.80 - legendary: - stat: lifesteal - min: 4.90 - max: 5.00 + stat: lifesteal + bands: tier-4 ruby: gem: gemstones.ruby name: "Ruby" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: max_health - min: 1.50 - max: 1.60 - rare: - stat: max_health - min: 1.60 - max: 1.70 - epic: - stat: max_health - min: 1.70 - max: 1.80 - legendary: - stat: max_health - min: 1.80 - max: 2.00 + stat: max_health + bands: + common: 1.50-1.60 + rare: 1.60-1.70 + epic: 1.70-1.80 + legendary: 1.80-2.00 sapphire: gem: gemstones.sapphire name: "Sapphire" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: armor - min: 0.65 - max: 0.70 - rare: - stat: armor - min: 0.70 - max: 0.75 - epic: - stat: armor - min: 0.75 - max: 0.80 - legendary: - stat: armor - min: 0.80 - max: 1.00 + stat: armor + bands: + common: 0.65-0.70 + rare: 0.70-0.75 + epic: 0.75-0.80 + legendary: 0.80-1.00 topaz: gem: gemstones.topaz name: "Topaz" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage_reduction - min: 4.00 - max: 4.20 - rare: - stat: physical_damage_reduction - min: 4.30 - max: 4.50 - epic: - stat: physical_damage_reduction - min: 4.60 - max: 4.80 - legendary: - stat: physical_damage_reduction - min: 4.90 - max: 5.00 + stat: physical_damage_reduction + bands: tier-4 citrine: gem: gemstones.citrine name: "Citrine" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage_reduction - min: 4.00 - max: 4.20 - rare: - stat: projectile_damage_reduction - min: 4.30 - max: 4.50 - epic: - stat: projectile_damage_reduction - min: 4.60 - max: 4.80 - legendary: - stat: projectile_damage_reduction - min: 4.90 - max: 5.00 + stat: projectile_damage_reduction + bands: tier-4 morganite: gem: gemstones.morganite name: "Morganite" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage_reduction - min: 4.00 - max: 4.2 - rare: - stat: magic_damage_reduction - min: 4.30 - max: 4.50 - epic: - stat: magic_damage_reduction - min: 4.60 - max: 4.80 - legendary: - stat: magic_damage_reduction - min: 4.90 - max: 5.00 + stat: magic_damage_reduction + bands: tier-4 crystallite: gem: gemstones.crystallite name: "Crystallite" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: physical_damage - min: 5.00 - max: 5.20 - rare: - stat: physical_damage - min: 5.30 - max: 5.50 - epic: - stat: physical_damage - min: 5.60 - max: 5.80 - legendary: - stat: physical_damage - min: 5.90 - max: 6.00 + stat: physical_damage + bands: tier-5 tigers_eye: gem: gemstones.tigers_eye name: "Tiger's Eye" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: projectile_damage - min: 5.00 - max: 5.20 - rare: - stat: projectile_damage - min: 5.30 - max: 5.50 - epic: - stat: projectile_damage - min: 5.60 - max: 5.80 - legendary: - stat: projectile_damage - min: 5.90 - max: 6.00 + stat: projectile_damage + bands: tier-5 serpents_eye: gem: gemstones.serpents_eye name: "Serpent's Eye" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: magic_damage - min: 5.00 - max: 5.20 - rare: - stat: magic_damage - min: 5.30 - max: 5.50 - epic: - stat: magic_damage - min: 5.60 - max: 5.80 - legendary: - stat: magic_damage - min: 5.90 - max: 6.00 + stat: magic_damage + bands: tier-5 musgravite: gem: gemstones.musgravite name: "Musgravite" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: spell_vampirism - min: 5.00 - max: 5.20 - rare: - stat: spell_vampirism - min: 5.30 - max: 5.50 - epic: - stat: spell_vampirism - min: 5.60 - max: 5.80 - legendary: - stat: spell_vampirism - min: 5.90 - max: 6.00 + stat: spell_vampirism + bands: tier-5 taaffeite: gem: gemstones.taaffeite name: "Taaffeite" colour: white socket_colour: Mythical Gemstone socket_name_colour: white - rarities: - common: - stat: lifesteal - min: 5.00 - max: 5.20 - rare: - stat: lifesteal - min: 5.30 - max: 5.50 - epic: - stat: lifesteal - min: 5.60 - max: 5.80 - legendary: - stat: lifesteal - min: 5.90 - max: 6.00 + stat: lifesteal + bands: tier-5 diff --git a/src/test/java/net/tfminecraft/geminfusion/GemStatBandsTest.java b/src/test/java/net/tfminecraft/geminfusion/GemStatBandsTest.java new file mode 100644 index 0000000..69ccd2f --- /dev/null +++ b/src/test/java/net/tfminecraft/geminfusion/GemStatBandsTest.java @@ -0,0 +1,195 @@ +package net.tfminecraft.geminfusion; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.File; +import java.util.*; +import java.util.logging.Logger; +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.jupiter.api.*; + +class GemStatBandsTest { + InfusionMain previous; + Logger logger; + + @BeforeEach + void setup() { + previous = InfusionMain.plugin; + InfusionMain.plugin = mock(InfusionMain.class); + logger = mock(Logger.class); + when(InfusionMain.plugin.getLogger()).thenReturn(logger); + } + + @AfterEach + void cleanup() { + InfusionMain.plugin = previous; + ConfigLoader.loadedGems.clear(); + ConfigLoader.loadedRarities.clear(); + GemNameCutoffs.load(List.of(), List.of()); + } + + static YamlConfiguration config(String text) throws Exception { + YamlConfiguration c = new YamlConfiguration(); + c.loadFromString(text); + return c; + } + + static List describe(List stats) { + return stats.stream() + .map(s -> s.getId() + " " + s.getStatId() + " " + s.getMin() + "-" + s.getMax()) + .toList(); + } + + static final String BANDS = + "stat-bands:\n" + + " tier-1:\n" + + " common: 1.0-1.2\n" + + " legendary: 1.9 - 2\n"; + + @Test + void gemUsesANamedSetOrItsOwnBands() throws Exception { + var c = + config( + BANDS + + "gems:\n" + + " onyx:\n" + + " stat: armor\n" + + " bands: tier-1\n" + + " agate:\n" + + " stat: max_health\n" + + " bands:\n" + + " common: 0.20-0.30\n" + + " rare: 0.5\n"); + assertEquals( + List.of("common armor 1.0-1.2", "legendary armor 1.9-2.0"), + describe( + GemStat.parseGem( + "onyx", c.getConfigurationSection("gems.onyx"), c.getConfigurationSection("stat-bands")))); + // A single number is a fixed stat. + assertEquals( + List.of("common max_health 0.2-0.3", "rare max_health 0.5-0.5"), + describe(GemStat.parseGem("agate", c.getConfigurationSection("gems.agate"), null))); + verify(logger, never()).warning(anyString()); + } + + @Test + void brokenBandsAreSkippedWithAWarning() throws Exception { + var c = + config( + BANDS + + "gems:\n" + + " unknown:\n" + + " stat: armor\n" + + " bands: tier-9\n" + + " nostat:\n" + + " bands: tier-1\n" + + " blank:\n" + + " stat: ' '\n" + + " bands: tier-1\n" + + " bad:\n" + + " stat: armor\n" + + " bands:\n" + + " common: lots\n" + + " rare: {min: 1}\n" + + " epic: 2-3\n" + + " both:\n" + + " stat: armor\n" + + " bands: tier-1\n" + + " rarities:\n" + + " common: {stat: armor, min: 9}\n" + + " none:\n" + + " name: None\n"); + var sets = c.getConfigurationSection("stat-bands"); + assertTrue(GemStat.parseGem("unknown", c.getConfigurationSection("gems.unknown"), sets).isEmpty()); + assertTrue(GemStat.parseGem("unknown", c.getConfigurationSection("gems.unknown"), null).isEmpty()); + verify(logger, times(2)).warning(contains("unknown stat-bands set 'tier-9'")); + assertTrue(GemStat.parseGem("nostat", c.getConfigurationSection("gems.nostat"), sets).isEmpty()); + assertTrue(GemStat.parseGem("blank", c.getConfigurationSection("gems.blank"), sets).isEmpty()); + verify(logger, times(2)).warning(contains("has bands but no stat")); + assertEquals( + List.of("epic armor 2.0-3.0"), + describe(GemStat.parseGem("bad", c.getConfigurationSection("gems.bad"), sets))); + verify(logger).warning(contains("common band 'lots' is not min-max")); + verify(logger).warning(contains("'bad' rare band")); + assertEquals( + List.of("common armor 1.0-1.2", "legendary armor 1.9-2.0"), + describe(GemStat.parseGem("both", c.getConfigurationSection("gems.both"), sets))); + verify(logger).warning(contains("has both bands and rarities")); + assertTrue(GemStat.parseGem("none", c.getConfigurationSection("gems.none"), sets).isEmpty()); + verify(logger).warning(contains("has neither bands nor rarities")); + } + + @Test + void olderRaritiesBlocksStillLoad() throws Exception { + var c = + config( + "gems:\n" + + " ruby:\n" + + " rarities:\n" + + " common: {stat: ATTACK_DAMAGE, min: 2, max: 4}\n" + + " rare: {stat: ATTACK_DAMAGE, min: 5}\n" + + " bad: {}\n"); + assertEquals( + List.of("common ATTACK_DAMAGE 2.0-4.0", "rare ATTACK_DAMAGE 5.0-5.0"), + describe(GemStat.parseGem("ruby", c.getConfigurationSection("gems.ruby"), null))); + } + + @Test + void serverConfigIgnoresTheBundledBandsItFallsBackTo() throws Exception { + // Bukkit's getConfig() uses the bundled config.yml as defaults for every key the server's file lacks. + var bundled = YamlConfiguration.loadConfiguration(new File("src/main/resources/config.yml")); + var older = + config( + "gems:\n" + + " agate:\n" + + " rarities:\n" + + " common: {stat: max_health, min: 0.21, max: 0.31}\n" + + " coral:\n" + + " stat: physical_damage\n" + + " bands: tier-2\n"); + older.setDefaults(bundled); + var sets = GemStat.ownSection(older, "stat-bands"); + assertNull(sets); + assertEquals( + List.of("common max_health 0.21-0.31"), + describe(GemStat.parseGem("agate", older.getConfigurationSection("gems.agate"), sets))); + // A set the server's file doesn't define is unknown, even though the bundled file has it. + assertTrue(GemStat.parseGem("coral", older.getConfigurationSection("gems.coral"), sets).isEmpty()); + verify(logger).warning(contains("unknown stat-bands set 'tier-2'")); + verify(logger, never()).warning(contains("both bands and rarities")); + } + + @Test + void bundledConfigGivesEveryGemFourBandsOfOneStat() { + new ConfigLoader() + .loadConfig(YamlConfiguration.loadConfiguration(new File("src/main/resources/config.yml"))); + assertEquals(40, ConfigLoader.loadedGems.size()); + for (Gemstone gem : ConfigLoader.loadedGems) { + assertEquals( + List.of("common", "rare", "epic", "legendary"), + gem.getStats().stream().map(GemStat::getId).toList(), + gem.getId()); + assertEquals(1, gem.getStats().stream().map(GemStat::getStatId).distinct().count(), gem.getId()); + } + Map byId = new HashMap<>(); + for (Gemstone gem : ConfigLoader.loadedGems) byId.put(gem.getId(), gem); + assertEquals( + List.of( + "common max_health 0.2-0.3", + "rare max_health 0.3-0.4", + "epic max_health 0.4-0.5", + "legendary max_health 0.5-0.6"), + describe(byId.get("agate").getStats())); + assertEquals( + List.of( + "common physical_damage 2.0-2.2", + "rare physical_damage 2.3-2.5", + "epic physical_damage 2.6-2.8", + "legendary physical_damage 2.9-3.0"), + describe(byId.get("coral").getStats())); + assertEquals( + "legendary armor 0.8-1.0", describe(byId.get("sapphire").getStats()).getLast()); + verify(logger, never()).warning(anyString()); + } +}