diff --git a/scripts/slice-test-bundle.py b/scripts/slice-test-bundle.py index cfbbb52..91f3a12 100755 --- a/scripts/slice-test-bundle.py +++ b/scripts/slice-test-bundle.py @@ -187,6 +187,31 @@ "# to Dexterity", "# to Intelligence", "#% increased Stun and Block Recovery", + # Nebulis on a synthesised base: three implicits with a per-unique fixed magnitude and two + # unique-only explicits, one of them shared by no other stat in the slice at all. The case + # for the base line's "Synthesised " prefix, which is not part of any base's own name and + # has to come off before "Void Sceptre" resolves. + "#% increased Critical Strike Chance", + "#% increased Chaos Damage", + "Bleeding you inflict deals Damage #% faster", + "#% increased Implicit Modifier magnitudes", + "#% increased Cast Speed", + "#% increased Elemental Damage per 1% Fire, Cold, or Lightning Resistance above 75%", + # A rare Heist Gear item's affixes. Grants Level # is what proves the suffix reads past its + # own tier line; the other two also cover the two boilerplate lines Heist Gear prints around + # its affixes ("Any Heist member can equip this item.", "Can only be equipped to Heist + # members."), which carry no roll and must not be read as unmatched modifiers. + "#% increased Projectile Attack Damage", + "# to # added Lightning Damage\n" + "Players and their Minions have # to # added Lightning Damage", + "Grants Level # Purity of Ice Skill", + # Four Heist Contract affixes with no roll at all, which is what the game marks with a + # title-case, em-dash "Unscalable Value" rather than the lowercase, parenthesized suffix a + # numeric roll gets — a wording no matcher above already covers. + "Monsters are Hexproof", + "Reward Rooms have #% increased Monsters", + "Monsters Poison on Hit", + "The Ring takes no Cut", ] ITEMS = [ @@ -288,6 +313,15 @@ "ITEM::Onyx Amulet", "UNIQUE::Bound Fate", "ITEM::Cloth Belt", + # A synthesised base and the unique that rolls on it. The base line the client prints is + # "Synthesised Void Sceptre", never a name of its own — the record here is filed under + # "Void Sceptre" alone, which is the case for stripping the prefix before lookup. + "ITEM::Void Sceptre", + "UNIQUE::Nebulis", + # A Heist Gear base, rare rather than unique — the case for the class's own boilerplate + # lines ("Any Heist member can equip this item.", "Can only be equipped to Heist members.") + # reading as usage text rather than as unmatched modifiers. + "ITEM::Precise Arrowhead", ] UNIQUE_MODS = [ @@ -297,6 +331,7 @@ "The Dark Monarch", "Replica Dragonfang's Flight", "Bound Fate", + "Nebulis", ] # Keyed on the first of each entry's mod ids, which is stable and is what the debug log names. @@ -321,12 +356,19 @@ "HeistContractBurningGround", "HeistContractBurningGround1", "HeistContractMonsterPatrolAdditionalElite1", + # The four "Unscalable Value" affixes above, so map check can rate a contract that rolls + # them rather than drawing it unrateable. + "HeistContractHexproof", + "HeistContractSideAreaIncreasedMonsters1_", + "HeistContractPoisoning", + "HeistContractNoGangCut1", ] ITEM_CLASSES = ["Rings", "Boots", "Gloves", "Body Armours", "Stackable Currency", "Divination Cards", "Jewels", "Utility Flasks", "Maps", "Skill Gems", "Support Gems", "Chart", "Misc Map Items", "Contracts", "Blueprints", - "Sanctum Research", "Expedition Logbooks", "Helmets", "Amulets", "Belts"] + "Sanctum Research", "Expedition Logbooks", "Helmets", "Amulets", "Belts", + "Sceptres", "Heist Gear"] LANG = "en" diff --git a/src/data/lexicon.cpp b/src/data/lexicon.cpp index 7bfb012..5ddc5dc 100644 --- a/src/data/lexicon.cpp +++ b/src/data/lexicon.cpp @@ -13,7 +13,7 @@ namespace { /// translates and nothing else, so these are the whole of its vocabulary. constexpr std::array(Term::Count)> kTermKeys{ "item_class_label", "rarity_label", "requirements_label", "sockets_label", - "note_label", "superior_prefix", "map_tier_prefix", "blighted_map", + "note_label", "superior_prefix", "synthesised_prefix", "map_tier_prefix", "blighted_map", "blight_ravaged_map", "foulborn_prefix", "foulborn_word", "vaal_prefix", "influence_suffix", "tier_prefix", "rank_prefix", "modifier_word", "prefix_word", "suffix_word", "unique_word", "increased_word", @@ -26,7 +26,7 @@ constexpr std::array(Term::Count)> kTermKe constexpr std::array(TermList::Count)> kListKeys{ "rarities", "influences", "flags", "mod_suffixes", "generations", "value_annotations", "usage_needles", "quest_rarity", "chart_shapes", "ultimatum_challenges", - "ultimatum_rewards", "heist_jobs", "heist_objective_values"}; + "ultimatum_rewards", "heist_jobs", "heist_objective_values", "unscalable_suffixes"}; struct PropertyName { std::string_view key; @@ -123,6 +123,7 @@ void Lexicon::assign_english() { set(terms_, Term::SocketsLabel, "Sockets"); set(terms_, Term::NoteLabel, "Note"); set(terms_, Term::SuperiorPrefix, "Superior "); + set(terms_, Term::SynthesisedPrefix, "Synthesised "); set(terms_, Term::MapTierPrefix, " (Tier "); set(terms_, Term::BlightedMap, "Blighted Map"); set(terms_, Term::BlightRavagedMap, "Blight-ravaged Map"); @@ -188,7 +189,12 @@ void Lexicon::assign_english() { // opening with "Take this item", the same shape a chart's does — and without a needle // of its own it came back as a fourth unrecognised modifier, since a logbook is gear // and gear needs a positive signal before prose is read as anything but a mod. - "Dannig"}; + "Dannig", + // Heist Gear prints two of its own boilerplate lines that carry no roll: "Any Heist + // member can equip this item." above the requirements, and "Can only be equipped to + // Heist members." at the bottom. Neither reads as a mod section on its own signal, so + // both came back as unrecognised modifiers — one needle catches both. + "Heist member"}; // "Quest Item" and "Divination Card" are printed with a trailing noun on some items, so // the rarity line is matched on a prefix as well as whole. lists_[static_cast(TermList::QuestRarity)] = {"Quest"}; @@ -205,6 +211,8 @@ void Lexicon::assign_english() { "Trap Disarmament", "Agility", "Deception", "Engineering"}; lists_[static_cast(TermList::HeistObjectiveValues)] = { "Moderate Value", "High Value", "Precious", "Priceless"}; + lists_[static_cast(TermList::UnscalableSuffixes)] = { + " (unscalable value)", " \xe2\x80\x94 Unscalable Value", " - Unscalable Value"}; quality_prefix_ = "Quality ("; properties_ = { diff --git a/src/data/lexicon.hpp b/src/data/lexicon.hpp index a2bfed4..3acbb21 100644 --- a/src/data/lexicon.hpp +++ b/src/data/lexicon.hpp @@ -122,6 +122,8 @@ enum class Term : uint8_t { NoteLabel, ///< "Note" SuperiorPrefix, ///< "Superior ", worn by a quality item nothing else has named — a white ///< one or any unidentified one — and known to no lookup + SynthesisedPrefix,///< "Synthesised ", how the client prints a synthesised base's own type + ///< line — the bundle knows the base only under the name beneath it MapTierPrefix, ///< " (Tier ", closed by ')' — "Map (Tier 16)" BlightedMap, ///< the whole base line of a blighted map BlightRavagedMap, @@ -200,6 +202,13 @@ enum class TermList : uint8_t { /// the game prints in the parenthetical after the target's name, so "Precious" and not /// "(Precious)". HeistObjectiveValues, + /// free — what the game appends to a roll item level cannot scale: a numeric one gets a + /// lowercase parenthetical, a modifier with no roll at all (a Heist Contract's own boolean + /// effects among them, "Monsters are Hexproof") gets an em-dash "Unscalable Value" instead, + /// and Wine's clipboard fallback can turn that em dash into a plain hyphen the same way it + /// does for an info line's tags (see `kEmDash` in parse.cpp) — three spellings of one + /// wording, so a suffix can be added here without touching the code that checks for it. + UnscalableSuffixes, Count }; diff --git a/src/data/stat_matcher.cpp b/src/data/stat_matcher.cpp index 612c04e..9d81b65 100644 --- a/src/data/stat_matcher.cpp +++ b/src/data/stat_matcher.cpp @@ -18,8 +18,18 @@ namespace { /// always wins, because the loop returns at the first `end` that resolves. constexpr size_t kMaxModLines = 8; -/// The game appends this to a roll that item level cannot scale. -constexpr std::string_view kUnscalableSuffix = " (unscalable value)"; +/// Strips whichever spelling of "this roll is fixed" the lexicon's `UnscalableSuffixes` list +/// carries `line` ending in — see that list for why there is more than one. Sets `unscalable` +/// and shortens `line` when found. +bool strip_unscalable_suffix(std::string_view& line, const Lexicon& lex) { + for (const std::string& suffix : lex.list(TermList::UnscalableSuffixes)) { + if (!suffix.empty() && line.size() > suffix.size() && line.ends_with(suffix)) { + line.remove_suffix(suffix.size()); + return true; + } + } + return false; +} double pow10i(int n) { double p = 1.0; @@ -54,10 +64,7 @@ std::optional match_stat(const GameData& gd, std::span kUnscalableSuffix.size() && line.ends_with(kUnscalableSuffix)) { - line.remove_suffix(kUnscalableSuffix.size()); - unscalable = true; - } + if (strip_unscalable_suffix(line, gd.lexicon())) unscalable = true; if (!join.empty()) join.push_back('\n'); join.append(line); diff --git a/src/item/parse.cpp b/src/item/parse.cpp index 65b9673..85da99c 100644 --- a/src/item/parse.cpp +++ b/src/item/parse.cpp @@ -320,6 +320,13 @@ void parse_header(const Section& sec, Item& it, const data::Lexicon& lex) { const std::string_view superior = lex.term(data::Term::SuperiorPrefix); if (!superior.empty() && it.base_type.starts_with(superior)) it.base_type.erase(0, superior.size()); + // A synthesised weapon or armour piece prints "Synthesised " ahead of its base's own type + // line, and the bundle carries the base only under the name beneath it — "Void Sceptre", + // never "Synthesised Void Sceptre". Superior can still be outermost ("Superior Synthesised + // Void Sceptre"), which is why this runs after that strip rather than instead of it. + const std::string_view synthesised = lex.term(data::Term::SynthesisedPrefix); + if (!synthesised.empty() && it.base_type.starts_with(synthesised)) + it.base_type.erase(0, synthesised.size()); // A map prints its tier on the base line — "Map (Tier 16)", and on a magic one after the // affixes, "Map of Impedance (Tier 16)". It is the thing the map is searched on and it is // not part of the base's name, which is a bare "Map" in every bundle and on trade. diff --git a/tests/data/bundle/en-items-base.index.bin b/tests/data/bundle/en-items-base.index.bin index 7ca5112..3813775 100644 Binary files a/tests/data/bundle/en-items-base.index.bin and b/tests/data/bundle/en-items-base.index.bin differ diff --git a/tests/data/bundle/en-items-name.index.bin b/tests/data/bundle/en-items-name.index.bin index 0958b0c..ec99677 100644 Binary files a/tests/data/bundle/en-items-name.index.bin and b/tests/data/bundle/en-items-name.index.bin differ diff --git a/tests/data/bundle/en-items-ref.index.bin b/tests/data/bundle/en-items-ref.index.bin index 0958b0c..ec99677 100644 Binary files a/tests/data/bundle/en-items-ref.index.bin and b/tests/data/bundle/en-items-ref.index.bin differ diff --git a/tests/data/bundle/en-items.ndjson b/tests/data/bundle/en-items.ndjson index 0d9386a..054db4f 100644 --- a/tests/data/bundle/en-items.ndjson +++ b/tests/data/bundle/en-items.ndjson @@ -48,3 +48,6 @@ {"craftable":{"category":"Amulets"},"domain":1,"dropLevel":25,"h":1,"metadataId":"Metadata/Items/Amulets/Amulet7","name":"Onyx Amulet","namespace":"ITEM","refName":"Onyx Amulet","w":1} {"art":"Art/2DItems/Belts/HinekoraBelt.png","name":"Bound Fate","namespace":"UNIQUE","refName":"Bound Fate","unique":{"base":"Cloth Belt"}} {"craftable":{"category":"Belts"},"domain":1,"dropLevel":20,"h":1,"metadataId":"Metadata/Items/Belts/Belt5","name":"Cloth Belt","namespace":"ITEM","refName":"Cloth Belt","w":2} +{"craftable":{"category":"Sceptres"},"domain":1,"dropLevel":68,"h":3,"metadataId":"Metadata/Items/Weapons/OneHandWeapons/OneHandMaces/Sceptre22","name":"Void Sceptre","namespace":"ITEM","refName":"Void Sceptre","w":2} +{"art":"Art/2DItems/Weapons/OneHandWeapons/Scepters/SceptreOfMemories.png","name":"Nebulis","namespace":"UNIQUE","refName":"Nebulis","unique":{"base":"Void Sceptre"}} +{"craftable":{"category":"Heist Gear"},"domain":23,"dropLevel":83,"h":2,"metadataId":"Metadata/Items/Heist/HeistEquipmentRanged4","name":"Precise Arrowhead","namespace":"ITEM","refName":"Precise Arrowhead","w":2} diff --git a/tests/data/bundle/en-mod-pools-ref.index.bin b/tests/data/bundle/en-mod-pools-ref.index.bin index 40ee4fb..b975fe3 100644 Binary files a/tests/data/bundle/en-mod-pools-ref.index.bin and b/tests/data/bundle/en-mod-pools-ref.index.bin differ diff --git a/tests/data/bundle/en-mod-pools.ndjson b/tests/data/bundle/en-mod-pools.ndjson index 78aa2d8..c3947ed 100644 --- a/tests/data/bundle/en-mod-pools.ndjson +++ b/tests/data/bundle/en-mod-pools.ndjson @@ -7,3 +7,7 @@ {"domain":22,"gen":2,"mods":["HeistContractBurningGround"],"stats":[{"ref":"Area has patches of Burning Ground"}],"tiers":1} {"domain":22,"gen":2,"mods":["HeistContractBurningGround1","HeistContractBurningGround2","HeistContractBurningGround3___","HeistContractBurningGround4"],"name":"of Flames","stats":[{"ref":"Area has patches of Burning Ground"},{"max":-4,"min":-4,"ref":"#% more raising of Alert Level"},{"max":4,"min":4,"ref":"#% increased time before Lockdown"}],"tiers":4} {"domain":22,"gen":1,"mods":["HeistContractMonsterPatrolAdditionalElite1","HeistContractMonsterPatrolAdditionalElite2","HeistContractMonsterPatrolAdditionalElite3","HeistContractMonsterPatrolAdditionalElite4","HeistContractMonsterPatrolAdditionalElite5"],"name":"Elite","stats":[{"max":30,"min":10,"ref":"Patrol Packs have #% increased chance to be replaced by an Elite Patrol Pack","trade":"explicit.stat_1982436039"},{"max":-7,"min":-7,"ref":"#% more raising of Alert Level"},{"max":7,"min":7,"ref":"#% increased time before Lockdown"}],"tiers":5} +{"domain":22,"gen":1,"mods":["HeistContractHexproof"],"name":"Hexproof","stats":[{"ref":"Monsters are Hexproof","trade":"explicit.stat_4154059009"},{"max":-6,"min":-6,"ref":"#% more raising of Alert Level"},{"max":6,"min":6,"ref":"#% increased time before Lockdown"}],"tiers":1} +{"domain":22,"gen":1,"mods":["HeistContractSideAreaIncreasedMonsters1_","HeistContractSideAreaIncreasedMonsters2__","HeistContractSideAreaIncreasedMonsters3_","HeistContractSideAreaIncreasedMonsters4","HeistContractSideAreaIncreasedMonsters5"],"name":"Deployed","stats":[{"max":30,"min":13,"ref":"Reward Rooms have #% increased Monsters","trade":"explicit.stat_4056408881"},{"max":-5,"min":-5,"ref":"#% more raising of Alert Level"},{"max":5,"min":5,"ref":"#% increased time before Lockdown"}],"tiers":5} +{"domain":22,"gen":2,"mods":["HeistContractPoisoning"],"name":"of Venom","stats":[{"ref":"Monsters Poison on Hit"},{"max":-6,"min":-6,"ref":"#% more raising of Alert Level"},{"max":6,"min":6,"ref":"#% increased time before Lockdown"}],"tiers":1} +{"domain":22,"gen":2,"mods":["HeistContractNoGangCut1"],"name":"of Selfishness","stats":[{"ref":"The Ring takes no Cut","trade":"explicit.stat_616993076"},{"max":0,"min":0,"ref":"#% more raising of Alert Level"},{"max":0,"min":0,"ref":"#% increased time before Lockdown"}],"tiers":1} diff --git a/tests/data/bundle/en-stats-matcher.index.bin b/tests/data/bundle/en-stats-matcher.index.bin index d4b8825..1594672 100644 Binary files a/tests/data/bundle/en-stats-matcher.index.bin and b/tests/data/bundle/en-stats-matcher.index.bin differ diff --git a/tests/data/bundle/en-stats-ref.index.bin b/tests/data/bundle/en-stats-ref.index.bin index ad910b8..d307a22 100644 Binary files a/tests/data/bundle/en-stats-ref.index.bin and b/tests/data/bundle/en-stats-ref.index.bin differ diff --git a/tests/data/bundle/en-stats.ndjson b/tests/data/bundle/en-stats.ndjson index ed48f98..2347209 100644 --- a/tests/data/bundle/en-stats.ndjson +++ b/tests/data/bundle/en-stats.ndjson @@ -80,3 +80,16 @@ {"better":1,"matchers":[{"string":"# to Dexterity"}],"ref":"# to Dexterity","trade":{"ids":{"crafted":["crafted.stat_3261801346"],"explicit":["explicit.stat_3261801346"],"fractured":["fractured.stat_3261801346"],"implicit":["implicit.stat_3261801346"],"scourge":["scourge.stat_3261801346"]}}} {"better":1,"matchers":[{"string":"# to Intelligence"}],"ref":"# to Intelligence","trade":{"ids":{"crafted":["crafted.stat_328541901"],"explicit":["explicit.stat_328541901"],"fractured":["fractured.stat_328541901"],"implicit":["implicit.stat_328541901"],"scourge":["scourge.stat_328541901"]}}} {"better":1,"matchers":[{"string":"#% increased Stun and Block Recovery"},{"negate":true,"string":"#% reduced Stun and Block Recovery"}],"ref":"#% increased Stun and Block Recovery","trade":{"ids":{"explicit":["explicit.stat_2511217560"],"fractured":["fractured.stat_2511217560"],"implicit":["implicit.stat_2511217560"],"scourge":["scourge.stat_2511217560"]}}} +{"better":1,"matchers":[{"string":"#% increased Critical Strike Chance"},{"negate":true,"string":"#% reduced Critical Strike Chance"}],"ref":"#% increased Critical Strike Chance","trade":{"ids":{"crafted":["crafted.stat_2375316951"],"enchant":["enchant.stat_2375316951"],"explicit":["explicit.stat_2375316951"],"fractured":["fractured.stat_2375316951"],"implicit":["implicit.stat_2375316951"]}}} +{"better":1,"matchers":[{"string":"#% increased Chaos Damage"},{"negate":true,"string":"#% reduced Chaos Damage"}],"ref":"#% increased Chaos Damage","trade":{"ids":{"crafted":["crafted.stat_736967255"],"enchant":["enchant.stat_736967255"],"explicit":["explicit.stat_736967255"],"fractured":["fractured.stat_736967255"],"implicit":["implicit.stat_736967255"],"scourge":["scourge.stat_736967255"]}}} +{"better":1,"matchers":[{"string":"Bleeding you inflict deals Damage #% faster"}],"ref":"Bleeding you inflict deals Damage #% faster","trade":{"ids":{"explicit":["explicit.stat_3828375170"],"fractured":["fractured.stat_3828375170"],"implicit":["implicit.stat_3828375170"],"scourge":["scourge.stat_3828375170"]}}} +{"better":1,"matchers":[{"string":"#% increased Implicit Modifier magnitudes"},{"negate":true,"string":"#% reduced Implicit Modifier magnitudes"}],"ref":"#% increased Implicit Modifier magnitudes","trade":{"ids":{"explicit":["explicit.stat_2304729532"]}}} +{"better":1,"matchers":[{"string":"#% increased Cast Speed"},{"negate":true,"string":"#% reduced Cast Speed"}],"ref":"#% increased Cast Speed","trade":{"ids":{"crafted":["crafted.stat_2891184298"],"enchant":["enchant.stat_2891184298"],"explicit":["explicit.stat_2891184298"],"fractured":["fractured.stat_2891184298"],"implicit":["implicit.stat_2891184298"],"scourge":["scourge.stat_2891184298"]}}} +{"better":1,"matchers":[{"string":"#% increased Elemental Damage per 1% Fire, Cold, or Lightning Resistance above 75%"}],"ref":"#% increased Elemental Damage per 1% Fire, Cold, or Lightning Resistance above 75%","trade":{"ids":{"explicit":["explicit.stat_1138456002"]}}} +{"better":1,"matchers":[{"string":"#% increased Projectile Attack Damage"},{"negate":true,"string":"#% reduced Projectile Attack Damage"}],"ref":"#% increased Projectile Attack Damage","trade":{"ids":{"explicit":["explicit.stat_2162876159"],"fractured":["fractured.stat_2162876159"],"implicit":["implicit.stat_2162876159"]}}} +{"better":1,"matchers":[{"string":"# to # added Lightning Damage\nPlayers and their Minions have # to # added Lightning Damage"}],"ref":"# to # added Lightning Damage\nPlayers and their Minions have # to # added Lightning Damage","trade":{"ids":{"explicit":["explicit.stat_2697534676"],"fractured":["fractured.stat_2697534676"]}}} +{"better":1,"matchers":[{"string":"Grants Level # Purity of Ice Skill"}],"ref":"Grants Level # Purity of Ice Skill","trade":{"ids":{"explicit":["explicit.stat_4193390599","explicit.stat_151975117"],"fractured":["fractured.stat_151975117"],"implicit":["implicit.stat_4193390599"]}}} +{"better":1,"matchers":[{"string":"Monsters are Hexproof"}],"ref":"Monsters are Hexproof","trade":{"ids":{"explicit":["explicit.stat_4154059009"],"fractured":["fractured.stat_4154059009"]}}} +{"better":1,"matchers":[{"string":"Reward Rooms have #% increased Monsters"},{"negate":true,"string":"Reward Rooms have #% reduced Monsters"}],"ref":"Reward Rooms have #% increased Monsters","trade":{"ids":{"explicit":["explicit.stat_4056408881"],"fractured":["fractured.stat_4056408881"]}}} +{"better":1,"matchers":[{"string":"Monsters Poison on Hit"}],"ref":"Monsters Poison on Hit","trade":{"ids":{"explicit":["explicit.stat_3350803563","explicit.stat_394639761"],"fractured":["fractured.stat_3350803563"]}}} +{"better":1,"matchers":[{"string":"The Ring takes no Cut"}],"ref":"The Ring takes no Cut","trade":{"ids":{"explicit":["explicit.stat_616993076"],"fractured":["fractured.stat_616993076"]}}} diff --git a/tests/data/bundle/en-unique-mods-name.index.bin b/tests/data/bundle/en-unique-mods-name.index.bin index 39b2514..1a8128e 100644 Binary files a/tests/data/bundle/en-unique-mods-name.index.bin and b/tests/data/bundle/en-unique-mods-name.index.bin differ diff --git a/tests/data/bundle/en-unique-mods.ndjson b/tests/data/bundle/en-unique-mods.ndjson index d1f262e..6226ec9 100644 --- a/tests/data/bundle/en-unique-mods.ndjson +++ b/tests/data/bundle/en-unique-mods.ndjson @@ -4,3 +4,4 @@ {"base":"Lich's Circlet","fixed":[{"filters":[{"range":[[27,37]],"ref":"#% to Chaos Resistance","tradeId":"explicit.stat_2923486259"}],"mod":"ChaosResistUniqueHelmetInt__1"},{"filters":[{"range":[[1,1]],"ref":"# to Level of all Minion Skill Gems","tradeId":"explicit.stat_2162097452"}],"mod":"GlobalIncreaseMinionSpellSkillGemLevelUnique__4"},{"filters":[{"range":[[-50,-50]],"ref":"#% increased Light Radius","tradeId":"explicit.stat_1263695895"}],"mod":"LightRadiusUnique__10"},{"filters":[{"range":[[50,100]],"ref":"# to maximum Energy Shield","tradeId":"explicit.stat_3489782002"}],"mod":"LocalIncreasedEnergyShieldUniqueHelmetInt_1"}],"name":"The Dark Monarch","pools":[{"count":[1,1],"hint":"Maximum number of random minion type is Doubled / Cannot have Minions other than random minion type","mods":[{"filters":[{"range":[],"ref":"Maximum number of Animated Weapons is Doubled\nCannot have Minions other than Animated Weapons","tradeId":"explicit.stat_56473917|1"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Golems is Doubled\nCannot have Minions other than Summoned Golems","tradeId":"explicit.stat_56473917|2"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Raging Spirits is Doubled\nCannot have Minions other than Summoned Raging Spirits","tradeId":"explicit.stat_56473917|3"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Raised Spectres is Doubled\nCannot have Minions other than Raised Spectres","tradeId":"explicit.stat_56473917|4"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Raised Spiders is Doubled\nCannot have Minions other than Raised Spiders","tradeId":"explicit.stat_56473917|5"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Raised Zombies is Doubled\nCannot have Minions other than Raised Zombies","tradeId":"explicit.stat_56473917|6"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Reapers is Doubled\nCannot have Minions other than Summoned Reapers","tradeId":"explicit.stat_56473917|7"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Sentinels of Absolution is Doubled\nCannot have Minions other than Sentinels of Absolution","tradeId":"explicit.stat_56473917|8"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Sentinels of Dominance is Doubled\nCannot have Minions other than Sentinels of Dominance","tradeId":"explicit.stat_56473917|9"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Sentinels of Purity is Doubled\nCannot have Minions other than Sentinels of Purity","tradeId":"explicit.stat_56473917|10"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Holy Relics is Doubled\nCannot have Minions other than Summoned Holy Relics","tradeId":"explicit.stat_56473917|11"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Phantasms is Doubled\nCannot have Minions other than Summoned Phantasms","tradeId":"explicit.stat_56473917|12"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Skeletons is Doubled\nCannot have Minions other than Summoned Skeletons","tradeId":"explicit.stat_56473917|13"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Summoned Spectral Wolves is Doubled\nCannot have Minions other than Summoned Spectral Wolves","tradeId":"explicit.stat_56473917|14"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Living Lightning is Doubled\nCannot have Minions other than Living Lightning","tradeId":"explicit.stat_56473917|15"}],"mod":"DoubleMinionLimitsUnique_1"},{"filters":[{"range":[],"ref":"Maximum number of Holy Armaments is Doubled\nCannot have Minions other than Holy Armaments","tradeId":"explicit.stat_56473917|16"}],"mod":"DoubleMinionLimitsUnique_1"}]}]} {"base":"Onyx Amulet","fixed":[{"filters":[{"range":[[10,16]],"ref":"# to all Attributes","tradeId":"implicit.stat_1379411836"}],"implicit":true,"mod":"AllAttributesImplicitAmulet1"},{"filters":[{"range":[[5,10]],"ref":"#% to all Elemental Resistances","tradeId":"explicit.stat_2901986750"}],"mod":"AllResistancesUnique__29"},{"filters":[{"range":[[-10,-5]],"ref":"Items and Gems have #% increased Attribute Requirements","tradeId":"explicit.stat_752930724"}],"mod":"GlobalItemAttributeRequirementsUnique__3"},{"filters":[{"range":[[5,10]],"ref":"#% increased Reservation Efficiency of Skills"}],"mod":"ReservationEfficiencyUnique__5"}],"name":"Replica Dragonfang's Flight","pools":[{"count":[1,1],"hint":"+3 to Level of all Random Skill Gems","mods":[{"filters":[{"range":[[3,3]],"ref":"# to Level of all Fireball Gems","tradeId":"explicit.indexable_skill_1"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ice Nova Gems","tradeId":"explicit.indexable_skill_2"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Leap Slam Gems","tradeId":"explicit.indexable_skill_3"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Holy Sweep Gems","tradeId":"explicit.indexable_skill_4"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ground Slam Gems","tradeId":"explicit.indexable_skill_5"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Cleave Gems","tradeId":"explicit.indexable_skill_6"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shield Charge Gems","tradeId":"explicit.indexable_skill_7"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Enduring Cry Gems","tradeId":"explicit.indexable_skill_8"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Double Strike Gems","tradeId":"explicit.indexable_skill_9"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Elemental Hit Gems","tradeId":"explicit.indexable_skill_10"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Immortal Call Gems","tradeId":"explicit.indexable_skill_11"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Dual Strike Gems","tradeId":"explicit.indexable_skill_12"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Whirling Blades Gems","tradeId":"explicit.indexable_skill_13"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frenzy Gems","tradeId":"explicit.indexable_skill_14"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Cold Snap Gems","tradeId":"explicit.indexable_skill_15"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Raise Zombie Gems"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Detonate Dead Gems","tradeId":"explicit.indexable_skill_17"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Caustic Arrow Gems","tradeId":"explicit.indexable_skill_18"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Creeping Frost Gems","tradeId":"explicit.indexable_skill_19"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ice Shot Gems","tradeId":"explicit.indexable_skill_20"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Static Strike Gems","tradeId":"explicit.indexable_skill_21"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Split Arrow Gems","tradeId":"explicit.indexable_skill_22"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blood Rage Gems","tradeId":"explicit.indexable_skill_23"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Righteous Fire Gems","tradeId":"explicit.indexable_skill_24"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Discharge Gems","tradeId":"explicit.indexable_skill_25"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flicker Strike Gems","tradeId":"explicit.indexable_skill_26"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Spark Gems","tradeId":"explicit.indexable_skill_27"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ice Spear Gems","tradeId":"explicit.indexable_skill_28"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Raise Spectre Gems"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Infernal Blow Gems","tradeId":"explicit.indexable_skill_30"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Glacial Hammer Gems","tradeId":"explicit.indexable_skill_31"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frost Wall Gems","tradeId":"explicit.indexable_skill_32"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Freezing Pulse Gems","tradeId":"explicit.indexable_skill_33"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shock Nova Gems","tradeId":"explicit.indexable_skill_34"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Viper Strike Gems","tradeId":"explicit.indexable_skill_35"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Phase Run Gems","tradeId":"explicit.indexable_skill_36"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Explosive Arrow Gems","tradeId":"explicit.indexable_skill_37"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Temporal Chains Gems","tradeId":"explicit.indexable_skill_38"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Elemental Weakness Gems","tradeId":"explicit.indexable_skill_39"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Warlord's Mark Gems","tradeId":"explicit.indexable_skill_40"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Punishment Gems","tradeId":"explicit.indexable_skill_41"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Enfeeble Gems","tradeId":"explicit.indexable_skill_42"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Assassin's Mark Gems","tradeId":"explicit.indexable_skill_43"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Sniper's Mark Gems","tradeId":"explicit.indexable_skill_44"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Despair Gems","tradeId":"explicit.indexable_skill_45"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Warp Gems","tradeId":"explicit.indexable_skill_46"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Skeletons Gems","tradeId":"explicit.indexable_skill_47"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Glacial Shield Swipe Gems","tradeId":"explicit.indexable_skill_48"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Crushing Fist Gems","tradeId":"explicit.indexable_skill_49"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Swordstorm Gems","tradeId":"explicit.indexable_skill_50"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Heavy Strike Gems","tradeId":"explicit.indexable_skill_51"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Dominating Blow Gems","tradeId":"explicit.indexable_skill_52"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Rain of Arrows Gems","tradeId":"explicit.indexable_skill_53"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Firestorm Gems","tradeId":"explicit.indexable_skill_54"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Strike Gems","tradeId":"explicit.indexable_skill_55"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Tempest Shield Gems","tradeId":"explicit.indexable_skill_56"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Molten Shell Gems","tradeId":"explicit.indexable_skill_57"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Power Siphon Gems","tradeId":"explicit.indexable_skill_58"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Puncture Gems","tradeId":"explicit.indexable_skill_59"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Arrow Gems","tradeId":"explicit.indexable_skill_60"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Arc Gems","tradeId":"explicit.indexable_skill_61"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Haste Gems","tradeId":"explicit.indexable_skill_62"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Purity of Elements Gems","tradeId":"explicit.indexable_skill_63"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Vitality Gems","tradeId":"explicit.indexable_skill_64"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Discipline Gems","tradeId":"explicit.indexable_skill_65"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Grace Gems","tradeId":"explicit.indexable_skill_66"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Determination Gems","tradeId":"explicit.indexable_skill_67"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Anger Gems","tradeId":"explicit.indexable_skill_68"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Hatred Gems","tradeId":"explicit.indexable_skill_69"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Wrath Gems","tradeId":"explicit.indexable_skill_70"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Burning Arrow Gems","tradeId":"explicit.indexable_skill_71"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Clarity Gems","tradeId":"explicit.indexable_skill_72"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shockwave Totem Gems","tradeId":"explicit.indexable_skill_73"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Rejuvenation Totem Gems","tradeId":"explicit.indexable_skill_74"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Conversion Trap Gems","tradeId":"explicit.indexable_skill_75"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Bear Trap Gems","tradeId":"explicit.indexable_skill_76"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Fire Trap Gems","tradeId":"explicit.indexable_skill_77"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Decoy Totem Gems","tradeId":"explicit.indexable_skill_78"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Devouring Totem Gems","tradeId":"explicit.indexable_skill_79"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ethereal Knives Gems","tradeId":"explicit.indexable_skill_80"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Arctic Armour Gems","tradeId":"explicit.indexable_skill_81"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Holy Flame Totem Gems","tradeId":"explicit.indexable_skill_82"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flammability Gems","tradeId":"explicit.indexable_skill_83"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frostbite Gems","tradeId":"explicit.indexable_skill_84"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Conductivity Gems","tradeId":"explicit.indexable_skill_85"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Incinerate Gems","tradeId":"explicit.indexable_skill_86"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Cyclone Gems","tradeId":"explicit.indexable_skill_87"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Searing Bond Gems","tradeId":"explicit.indexable_skill_88"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Reave Gems","tradeId":"explicit.indexable_skill_89"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Trap Gems","tradeId":"explicit.indexable_skill_90"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Pyroclast Mine Gems","tradeId":"explicit.indexable_skill_91"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Smoke Mine Gems","tradeId":"explicit.indexable_skill_92"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Icicle Mine Gems","tradeId":"explicit.indexable_skill_93"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Stormblast Mine Gems","tradeId":"explicit.indexable_skill_94"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Animate Guardian Gems","tradeId":"explicit.indexable_skill_95"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Spectral Throw Gems","tradeId":"explicit.indexable_skill_96"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Animate Weapon Gems","tradeId":"explicit.indexable_skill_97"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Purity of Fire Gems","tradeId":"explicit.indexable_skill_98"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Purity of Ice Gems","tradeId":"explicit.indexable_skill_99"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Purity of Lightning Gems","tradeId":"explicit.indexable_skill_100"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Storm Call Gems","tradeId":"explicit.indexable_skill_101"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flameblast Gems","tradeId":"explicit.indexable_skill_102"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Barrage Gems","tradeId":"explicit.indexable_skill_103"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ball Lightning Gems","tradeId":"explicit.indexable_skill_104"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Raging Spirit Gems","tradeId":"explicit.indexable_skill_105"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flame Surge Gems","tradeId":"explicit.indexable_skill_106"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Desecrate Gems","tradeId":"explicit.indexable_skill_107"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flesh Offering Gems","tradeId":"explicit.indexable_skill_108"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Bone Offering Gems","tradeId":"explicit.indexable_skill_109"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Glacial Cascade Gems","tradeId":"explicit.indexable_skill_110"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Convocation Gems","tradeId":"explicit.indexable_skill_111"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Molten Strike Gems","tradeId":"explicit.indexable_skill_112"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Tornado Shot Gems","tradeId":"explicit.indexable_skill_113"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Herald of Ash Gems","tradeId":"explicit.indexable_skill_114"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Herald of Ice Gems","tradeId":"explicit.indexable_skill_115"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Herald of Thunder Gems","tradeId":"explicit.indexable_skill_116"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Poacher's Mark Gems","tradeId":"explicit.indexable_skill_117"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Tendrils Gems","tradeId":"explicit.indexable_skill_118"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Mirror Arrow Gems","tradeId":"explicit.indexable_skill_119"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blink Arrow Gems","tradeId":"explicit.indexable_skill_120"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Kinetic Blast Gems","tradeId":"explicit.indexable_skill_121"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Chaos Golem Gems","tradeId":"explicit.indexable_skill_122"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Ice Golem Gems","tradeId":"explicit.indexable_skill_123"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Flame Golem Gems","tradeId":"explicit.indexable_skill_124"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Lightning Golem Gems","tradeId":"explicit.indexable_skill_125"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ice Crash Gems","tradeId":"explicit.indexable_skill_126"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Rallying Cry Gems","tradeId":"explicit.indexable_skill_127"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Infernal Cry Gems","tradeId":"explicit.indexable_skill_128"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Vigilant Strike Gems","tradeId":"explicit.indexable_skill_129"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Rolling Magma Gems","tradeId":"explicit.indexable_skill_130"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flame Dash Gems","tradeId":"explicit.indexable_skill_131"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frost Blades Gems","tradeId":"explicit.indexable_skill_132"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Wild Strike Gems","tradeId":"explicit.indexable_skill_133"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Galvanic Arrow Gems","tradeId":"explicit.indexable_skill_134"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blast Rain Gems","tradeId":"explicit.indexable_skill_135"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Bladefall Gems","tradeId":"explicit.indexable_skill_136"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Siege Ballista Gems","tradeId":"explicit.indexable_skill_137"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blade Vortex Gems","tradeId":"explicit.indexable_skill_138"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Contagion Gems","tradeId":"explicit.indexable_skill_139"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Wither Gems","tradeId":"explicit.indexable_skill_140"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Essence Drain Gems","tradeId":"explicit.indexable_skill_141"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ice Trap Gems","tradeId":"explicit.indexable_skill_142"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Orb of Storms Gems","tradeId":"explicit.indexable_skill_143"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frost Bomb Gems","tradeId":"explicit.indexable_skill_144"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Stone Golem Gems","tradeId":"explicit.indexable_skill_145"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Eviscerate Gems","tradeId":"explicit.indexable_skill_146"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Snipe Gems","tradeId":"explicit.indexable_skill_147"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Vengeful Cry Gems","tradeId":"explicit.indexable_skill_148"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Earthquake Gems","tradeId":"explicit.indexable_skill_149"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Sunder Gems","tradeId":"explicit.indexable_skill_150"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lacerate Gems","tradeId":"explicit.indexable_skill_151"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Spirit Offering Gems","tradeId":"explicit.indexable_skill_152"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frostbolt Gems","tradeId":"explicit.indexable_skill_153"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Vortex Gems","tradeId":"explicit.indexable_skill_154"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blight Gems","tradeId":"explicit.indexable_skill_155"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Scorching Ray Gems","tradeId":"explicit.indexable_skill_156"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blade Flurry Gems","tradeId":"explicit.indexable_skill_157"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Charged Dash Gems","tradeId":"explicit.indexable_skill_158"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Dark Bargain Gems","tradeId":"explicit.indexable_skill_159"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Storm Burst Gems","tradeId":"explicit.indexable_skill_160"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Cremation Gems","tradeId":"explicit.indexable_skill_161"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Bodyswap Gems","tradeId":"explicit.indexable_skill_162"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Volatile Dead Gems","tradeId":"explicit.indexable_skill_163"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Unearth Gems","tradeId":"explicit.indexable_skill_164"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Explosive Trap Gems","tradeId":"explicit.indexable_skill_165"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Siphoning Trap Gems","tradeId":"explicit.indexable_skill_166"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flamethrower Trap Gems","tradeId":"explicit.indexable_skill_167"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Spire Trap Gems","tradeId":"explicit.indexable_skill_168"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Seismic Trap Gems","tradeId":"explicit.indexable_skill_169"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Vulnerability Gems","tradeId":"explicit.indexable_skill_170"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Tectonic Slam Gems","tradeId":"explicit.indexable_skill_171"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Spectral Shield Throw Gems","tradeId":"explicit.indexable_skill_172"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Herald of Purity Gems","tradeId":"explicit.indexable_skill_173"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Herald of Agony Gems","tradeId":"explicit.indexable_skill_174"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Consecrated Path Gems","tradeId":"explicit.indexable_skill_175"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Smite Gems","tradeId":"explicit.indexable_skill_176"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Scourge Arrow Gems","tradeId":"explicit.indexable_skill_177"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Toxic Rain Gems","tradeId":"explicit.indexable_skill_178"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Holy Relic Gems","tradeId":"explicit.indexable_skill_179"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Winter Orb Gems","tradeId":"explicit.indexable_skill_180"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Storm Brand Gems","tradeId":"explicit.indexable_skill_181"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Armageddon Brand Gems","tradeId":"explicit.indexable_skill_182"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Brand Recall Gems","tradeId":"explicit.indexable_skill_183"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all War Banner Gems","tradeId":"explicit.indexable_skill_184"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Dread Banner Gems","tradeId":"explicit.indexable_skill_185"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shattering Steel Gems","tradeId":"explicit.indexable_skill_186"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lancing Steel Gems","tradeId":"explicit.indexable_skill_187"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Purifying Flame Gems","tradeId":"explicit.indexable_skill_188"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Soulrend Gems","tradeId":"explicit.indexable_skill_189"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Bane Gems","tradeId":"explicit.indexable_skill_190"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Divine Ire Gems","tradeId":"explicit.indexable_skill_191"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Wave of Conviction Gems","tradeId":"explicit.indexable_skill_192"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Zealotry Gems","tradeId":"explicit.indexable_skill_193"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Malevolence Gems","tradeId":"explicit.indexable_skill_194"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Precision Gems","tradeId":"explicit.indexable_skill_195"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Steelskin Gems","tradeId":"explicit.indexable_skill_196"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Dash Gems","tradeId":"explicit.indexable_skill_197"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Bladestorm Gems","tradeId":"explicit.indexable_skill_198"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blood and Sand Gems","tradeId":"explicit.indexable_skill_199"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Berserk Gems","tradeId":"explicit.indexable_skill_200"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Perforate Gems","tradeId":"explicit.indexable_skill_201"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Chain Hook Gems","tradeId":"explicit.indexable_skill_202"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frostblink Gems","tradeId":"explicit.indexable_skill_203"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flesh and Stone Gems","tradeId":"explicit.indexable_skill_204"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Pride Gems","tradeId":"explicit.indexable_skill_205"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Cobra Lash Gems","tradeId":"explicit.indexable_skill_206"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Withering Step Gems","tradeId":"explicit.indexable_skill_207"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Venom Gyre Gems","tradeId":"explicit.indexable_skill_208"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Skitterbots Gems","tradeId":"explicit.indexable_skill_209"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Plague Bearer Gems","tradeId":"explicit.indexable_skill_210"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Pestilent Strike Gems","tradeId":"explicit.indexable_skill_211"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Carrion Golem Gems","tradeId":"explicit.indexable_skill_212"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Artillery Ballista Gems","tradeId":"explicit.indexable_skill_213"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shrapnel Ballista Gems","tradeId":"explicit.indexable_skill_214"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ensnaring Arrow Gems","tradeId":"explicit.indexable_skill_215"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Stormbind Gems","tradeId":"explicit.indexable_skill_216"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blade Blast Gems","tradeId":"explicit.indexable_skill_217"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Spellslinger Gems","tradeId":"explicit.indexable_skill_218"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Kinetic Bolt Gems","tradeId":"explicit.indexable_skill_219"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Arcane Cloak Gems","tradeId":"explicit.indexable_skill_220"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Intimidating Cry Gems","tradeId":"explicit.indexable_skill_221"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ancestral Cry Gems","tradeId":"explicit.indexable_skill_222"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Seismic Cry Gems","tradeId":"explicit.indexable_skill_223"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all General's Cry Gems","tradeId":"explicit.indexable_skill_224"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Arcanist Brand Gems","tradeId":"explicit.indexable_skill_225"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Penance Brand Gems","tradeId":"explicit.indexable_skill_226"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Wintertide Brand Gems","tradeId":"explicit.indexable_skill_227"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Earthshatter Gems","tradeId":"explicit.indexable_skill_228"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Sigil of Power Gems","tradeId":"explicit.indexable_skill_229"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Splitting Steel Gems","tradeId":"explicit.indexable_skill_230"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flame Wall Gems","tradeId":"explicit.indexable_skill_231"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blazing Salvo Gems","tradeId":"explicit.indexable_skill_232"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Crackling Lance Gems","tradeId":"explicit.indexable_skill_233"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Void Sphere Gems","tradeId":"explicit.indexable_skill_234"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frost Shield Gems","tradeId":"explicit.indexable_skill_235"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Hydrosphere Gems","tradeId":"explicit.indexable_skill_236"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Hexblast Gems","tradeId":"explicit.indexable_skill_237"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Exsanguinate Gems","tradeId":"explicit.indexable_skill_238"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Corrupting Fever Gems","tradeId":"explicit.indexable_skill_239"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Petrified Blood Gems","tradeId":"explicit.indexable_skill_240"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Reap Gems","tradeId":"explicit.indexable_skill_241"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Defiance Banner Gems","tradeId":"explicit.indexable_skill_242"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Storm Rain Gems","tradeId":"explicit.indexable_skill_243"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Rage Vortex Gems","tradeId":"explicit.indexable_skill_244"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shield Crush Gems","tradeId":"explicit.indexable_skill_245"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Summon Reaper Gems","tradeId":"explicit.indexable_skill_246"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Boneshatter Gems","tradeId":"explicit.indexable_skill_247"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Ambush Gems","tradeId":"explicit.indexable_skill_248"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Voltaxic Burst Gems","tradeId":"explicit.indexable_skill_249"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Battlemage's Cry Gems","tradeId":"explicit.indexable_skill_250"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Absolution Gems","tradeId":"explicit.indexable_skill_251"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Eye of Winter Gems","tradeId":"explicit.indexable_skill_252"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Spectral Helix Gems","tradeId":"explicit.indexable_skill_253"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Forbidden Rite Gems","tradeId":"explicit.indexable_skill_254"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Blade Trap Gems","tradeId":"explicit.indexable_skill_255"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Manabond Gems","tradeId":"explicit.indexable_skill_256"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Explosive Concoction Gems","tradeId":"explicit.indexable_skill_257"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Poisonous Concoction Gems","tradeId":"explicit.indexable_skill_258"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Temporal Rift Gems","tradeId":"explicit.indexable_skill_259"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Energy Blade Gems","tradeId":"explicit.indexable_skill_260"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Tornado Gems","tradeId":"explicit.indexable_skill_261"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Soul Link Gems","tradeId":"explicit.indexable_skill_262"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Flame Link Gems","tradeId":"explicit.indexable_skill_263"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Intuitive Link Gems","tradeId":"explicit.indexable_skill_264"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Protective Link Gems","tradeId":"explicit.indexable_skill_265"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Vampiric Link Gems","tradeId":"explicit.indexable_skill_266"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Destructive Link Gems","tradeId":"explicit.indexable_skill_267"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Galvanic Field Gems","tradeId":"explicit.indexable_skill_268"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Lightning Conduit Gems","tradeId":"explicit.indexable_skill_269"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Alchemist's Mark Gems","tradeId":"explicit.indexable_skill_270"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Frozen Legion Gems","tradeId":"explicit.indexable_skill_271"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Volcanic Fissure Gems","tradeId":"explicit.indexable_skill_272"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Automation Gems","tradeId":"explicit.indexable_skill_273"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Autoexertion Gems","tradeId":"explicit.indexable_skill_274"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Divine Retribution Gems","tradeId":"explicit.indexable_skill_275"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Conflagration Gems","tradeId":"explicit.indexable_skill_276"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Kinetic Fusillade Gems"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Kinetic Rain Gems","tradeId":"explicit.indexable_skill_278"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Somatic Shell Gems","tradeId":"explicit.indexable_skill_279"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Wall of Force Gems","tradeId":"explicit.indexable_skill_280"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Thunderstorm Gems","tradeId":"explicit.indexable_skill_281"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Kinetic Fusillade Gems"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Holy Hammers Gems","tradeId":"explicit.indexable_skill_283"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Shield of Light Gems","tradeId":"explicit.indexable_skill_284"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Holy Strike Gems","tradeId":"explicit.indexable_skill_285"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Divine Blast Gems","tradeId":"explicit.indexable_skill_286"}],"mod":"RandomSkillUnique__1"},{"filters":[{"range":[[3,3]],"ref":"# to Level of all Mana-Infused Staff Gems","tradeId":"explicit.indexable_skill_287"}],"mod":"RandomSkillUnique__1"}]}]} {"base":"Cloth Belt","fixed":[{"filters":[{"range":[[20,30]],"ref":"# to Dexterity","tradeId":"explicit.stat_3261801346"}],"mod":"DexterityUnique__29"},{"filters":[{"range":[[1,1]],"ref":"Every 5 seconds, gain one of the following for 5 seconds:\nYour Hits are always Critical Strikes\nHits against you are always Critical Strikes\nAttacks cannot Hit you\nAttacks against you always Hit\nYour Damage with Hits is Lucky\nDamage of Hits against you is Lucky","tradeId":"explicit.stat_2501671832"}],"mod":"HinekoraButterflyEffectUnique__1"},{"filters":[{"range":[[60,80]],"ref":"# to maximum Life","tradeId":"explicit.stat_3299347043"}],"mod":"IncreasedLifeUnique__120"},{"filters":[{"range":[[20,30]],"ref":"# to Intelligence","tradeId":"explicit.stat_328541901"}],"mod":"IntelligenceUnique__32"},{"filters":[{"range":[[15,25]],"ref":"#% increased Stun and Block Recovery","tradeId":"implicit.stat_2511217560"}],"implicit":true,"mod":"StunRecoveryImplicitBelt1"}],"name":"Bound Fate"} +{"base":"Void Sceptre","fixed":[{"filters":[{"range":[[60,120]],"ref":"#% increased Implicit Modifier magnitudes","tradeId":"explicit.stat_2304729532"}],"mod":"ClassicNebulisImplicitModifierMagnitudeUnique_1"},{"filters":[{"range":[[15,20]],"ref":"#% increased Cold Damage per 1% Cold Resistance above 75%","tradeId":"explicit.stat_2517031897"}],"mod":"ColdDamagePerResistanceAbove75Unique__1"},{"filters":[{"range":[[6,8]],"ref":"#% increased Elemental Damage per 1% Fire, Cold, or Lightning Resistance above 75%","tradeId":"explicit.stat_1138456002"}],"mod":"ElementalDamagePerResistanceAbove75Unique_1"},{"filters":[{"range":[[40,40]],"ref":"#% increased Elemental Damage","tradeId":"implicit.stat_3141070085"}],"implicit":true,"mod":"ElementalDamagePercentImplicitSceptreNew22"},{"filters":[{"range":[[15,20]],"ref":"#% increased Cast Speed","tradeId":"explicit.stat_2891184298"}],"mod":"IncreasedCastSpeedUnique__16"},{"filters":[{"range":[[15,20]],"ref":"#% increased Lightning Damage per 1% Lightning Resistance above 75%","tradeId":"explicit.stat_2642525868"}],"mod":"LightningDamagePerResistanceAbove75Unique__1"}],"name":"Nebulis","unlisted":["(15-20)% increased Cast Speed","Three random synthesis implicit modifiers"]} diff --git a/tests/data/bundle/item-classes.ndjson b/tests/data/bundle/item-classes.ndjson index 66512ce..eb94282 100644 --- a/tests/data/bundle/item-classes.ndjson +++ b/tests/data/bundle/item-classes.ndjson @@ -18,3 +18,5 @@ {"domain":1,"id":"Helmet","itemClass":"Helmets","tradeCategory":"armour.helmet"} {"domain":1,"id":"Amulet","itemClass":"Amulets","tradeCategory":"accessory.amulet"} {"domain":1,"id":"Belt","itemClass":"Belts","tradeCategory":"accessory.belt"} +{"domain":1,"id":"Sceptre","itemClass":"Sceptres","tradeCategory":"weapon.sceptre"} +{"domain":23,"id":"HeistEquipmentWeapon","itemClass":"Heist Gear","tradeCategory":"heistequipment.heistweapon"} diff --git a/tests/data/items/heist-contract-rare-smugglers-den.txt b/tests/data/items/heist-contract-rare-smugglers-den.txt new file mode 100644 index 0000000..8e39eac --- /dev/null +++ b/tests/data/items/heist-contract-rare-smugglers-den.txt @@ -0,0 +1,30 @@ +Item Class: Contracts +Rarity: Rare +Rapture Manifesto +Contract: Smuggler's Den +-------- +Client: Count Geonor +Heist Target: Urn of the Original Ashes (Moderate Value) +Area Level: 83 +Requires Deception (Level 5) +Item Quantity: +45% (augmented) +Item Rarity: +26% (augmented) +Alert Level Reduction: +17% (augmented) +Time Before Lockdown: +17% (augmented) +Maximum Alive Reinforcements: +17% (augmented) +-------- +Item Level: 83 +-------- +{ Prefix Modifier "Hexproof" (Tier: 1) — Caster, Curse } +Monsters are Hexproof — Unscalable Value +{ Prefix Modifier "Deployed" (Tier: 1) } +Reward Rooms have 29(27-30)% increased Monsters +{ Suffix Modifier "of Venom" (Tier: 1) — Chaos, Ailment } +Monsters Poison on Hit — Unscalable Value +{ Suffix Modifier "of Selfishness" (Tier: 1) } +The Ring takes no Cut — Unscalable Value +-------- +"A great fire once swept the land, and our leaders were turned to ash where they stood. +The urn contains exactly what you think it does, and serves as proof of our long lineage." +-------- +Give this Contract to Adiyah in the Rogue Harbour to embark on the Heist. diff --git a/tests/data/items/heist-gear-rare-oblivion.txt b/tests/data/items/heist-gear-rare-oblivion.txt new file mode 100644 index 0000000..6a8745f --- /dev/null +++ b/tests/data/items/heist-gear-rare-oblivion.txt @@ -0,0 +1,26 @@ +Item Class: Heist Gear +Rarity: Rare +Oblivion Gear +Precise Arrowhead +-------- +Any Heist member can equip this item. +-------- +Requirements: +Level 5 in Any Job +-------- +Item Level: 85 +-------- +{ Implicit Modifier — Damage, Attack } +30(26-30)% increased Projectile Attack Damage +-------- +{ Prefix Modifier "Buzzing" (Tier: 3) — Damage, Elemental, Lightning } +1 to 46(41-47) added Lightning Damage +Players and their Minions have 1 to 46(41-47) added Lightning Damage +{ Prefix Modifier "Poacher's" (Tier: 3) — Damage, Attack } +29(26-30)% increased Projectile Attack Damage +{ Suffix Modifier "of Coordination" (Tier: 2) — Aura } +Grants Level 10 Purity of Ice Skill +{ Suffix Modifier "of Personality" (Tier: 4) } +7(7-6)% reduced Hiring Fee +-------- +Can only be equipped to Heist members. diff --git a/tests/data/items/unique-nebulis-synthesised.txt b/tests/data/items/unique-nebulis-synthesised.txt new file mode 100644 index 0000000..c437b75 --- /dev/null +++ b/tests/data/items/unique-nebulis-synthesised.txt @@ -0,0 +1,40 @@ +Item Class: Sceptres +Rarity: Unique +Nebulis +Synthesised Void Sceptre +-------- +Sceptre +Physical Damage: 50-76 +Critical Strike Chance: 8.83% (augmented) +Attacks per Second: 1.25 +Weapon Range: 1.1 metres +-------- +Requirements: +Level: 68 +Str: 104 (unmet) +Int: 122 (unmet) +-------- +Sockets: W W W +-------- +Item Level: 87 +-------- +{ Implicit Modifier — Attack, Critical — 118% Increased } +10(9-10)% increased Critical Strike Chance +{ Implicit Modifier — Damage, Chaos — 118% Increased } +20(18-20)% increased Chaos Damage +{ Implicit Modifier — Damage, Physical, Attack, Ailment — 118% Increased } +Bleeding you inflict deals Damage 16(15-20)% faster +(They will deal the same total damage over a shorter duration) +-------- +{ Unique Modifier } +118(60-120)% increased Implicit Modifier magnitudes +(Implicit Modifiers are those that come from an item's type, rather than its random properties) +{ Unique Modifier — Caster, Speed } +20(15-20)% increased Cast Speed +{ Unique Modifier — Damage } +8(6-8)% increased Elemental Damage per 1% Fire, Cold, or Lightning Resistance above 75% +-------- +The vastness of the cosmos holds energies beyond comprehension, +should one have the fortitude to grasp them. +-------- +Synthesised Item diff --git a/tests/game_data_test.cpp b/tests/game_data_test.cpp index d2e38be..71b37de 100644 --- a/tests/game_data_test.cpp +++ b/tests/game_data_test.cpp @@ -230,7 +230,7 @@ TEST_CASE("a pool answers for a whole mod domain, not for an item") { // Three pools, one file: the domain is what separates them, and asking for one of them // never brings back another's entries even where the wording is the same string. CHECK(gd->mod_pool(39).size() == 2); - CHECK(gd->mod_pool(22).size() == 3); + CHECK(gd->mod_pool(22).size() == 7); // A domain the bundle publishes no pool for is empty, which is not the same answer as a // bundle that has no pools at all — `has_mod_pools()` is what tells those apart. CHECK(gd->mod_pool(1).empty()); diff --git a/tests/item_pricing_test.cpp b/tests/item_pricing_test.cpp index 1021ac1..b915c4d 100644 --- a/tests/item_pricing_test.cpp +++ b/tests/item_pricing_test.cpp @@ -2608,3 +2608,75 @@ TEST_CASE("a modifier that enumerates its alternatives is one modifier, however CHECK(filter_for(p, "implicit.stat_2511217560") != nullptr); // the belt's implicit } +TEST_CASE("a synthesised base resolves under the name beneath its prefix") { + auto gd = fixture(); + // The client prints a synthesised weapon or armour's type line as "Synthesised ", and + // no bundle carries a base under that whole name — only "Void Sceptre" itself. Reported as + // Nebulis coming back with no base record at all. + const Item it = resolved(*gd, capture("unique-nebulis-synthesised.txt")); + + CHECK(it.synthesised); + REQUIRE(it.base != nullptr); + CHECK(it.base->name == "Void Sceptre"); + REQUIRE(it.unique_entry != nullptr); + CHECK(it.unique_entry->name == "Nebulis"); + + const Derived d = derive(gd.get(), it); + const SearchPlan p = build_plan(*gd, it, d); + CHECK(p.name == "Nebulis"); + // The base's own name, not the printed line: trade's type filter takes "Void Sceptre" and + // the "Synthesised Item" checkbox separately, which `synthesised_item` above already ticks. + CHECK(p.type == "Void Sceptre"); + for (const std::string& n : p.notes) + CHECK(n.find("unrecognised modifier") == std::string::npos); +} + +TEST_CASE("a modifier with no roll at all is not read past its em-dash unscalable suffix") { + auto gd = fixture(); + // A Heist Contract's own boolean affixes print no number, and Advanced Mod Descriptions + // marks them "\xe2\x80\x94 Unscalable Value" rather than the numeric "(unscalable value)" + // parenthetical — a spelling nothing stripped, so all four affixes came back unrecognised. + const Item it = resolved(*gd, capture("heist-contract-rare-smugglers-den.txt")); + const Derived d = derive(gd.get(), it); + const SearchPlan p = build_plan(*gd, it, d); + + for (const std::string& n : p.notes) + CHECK(n.find("unrecognised modifier") == std::string::npos); + + CHECK(filter_for(p, "explicit.stat_4154059009") != nullptr); // Monsters are Hexproof + CHECK(filter_for(p, "explicit.stat_4056408881") != nullptr); // Reward Rooms increased Monsters + CHECK(filter_for(p, "explicit.stat_3350803563") != nullptr); // Monsters Poison on Hit + CHECK(filter_for(p, "explicit.stat_616993076") != nullptr); // The Ring takes no Cut +} + +TEST_CASE("Heist Gear's own boilerplate lines are not unrecognised modifiers") { + auto gd = fixture(); + // "Any Heist member can equip this item." sits above the requirements and "Can only be + // equipped to Heist members." sits at the bottom — neither carries a roll, and before the + // needle both came back as unrecognised modifiers of their own. + const Item it = resolved(*gd, capture("heist-gear-rare-oblivion.txt")); + const Derived d = derive(gd.get(), it); + const SearchPlan p = build_plan(*gd, it, d); + + CHECK(p.strategy == Strategy::Modifiers); + CHECK(p.category == "heistequipment.heistweapon"); + for (const std::string& n : p.notes) { + CHECK(n.find("Heist member") == std::string::npos); + CHECK(n.find("equipped") == std::string::npos); + } + + CHECK(filter_for(p, "implicit.stat_2162876159") != nullptr); // Projectile Attack Damage + CHECK(filter_for(p, "explicit.stat_2162876159") != nullptr); // the Poacher's prefix + CHECK(filter_for(p, "explicit.stat_2697534676") != nullptr); // the Buzzing prefix + CHECK(filter_for(p, "explicit.stat_4193390599") != nullptr); // Grants Level 10 Purity of Ice + + // "of Personality"'s reduced Hiring Fee is real and still unmatched: the bundle only names + // job-specific variants ("... for Deception Jobs", "... of Rogues"), not a bare one, and + // that gap is in the data build rather than in anything this layer decides. + const bool hiring_fee_noted = + std::any_of(p.notes.begin(), p.notes.end(), [](const std::string& n) { + return n.find("reduced Hiring Fee") != std::string::npos; + }); + CHECK(hiring_fee_noted); +} + diff --git a/tests/mapcheck_test.cpp b/tests/mapcheck_test.cpp index e859825..95acf71 100644 --- a/tests/mapcheck_test.cpp +++ b/tests/mapcheck_test.cpp @@ -529,6 +529,23 @@ TEST_CASE("a heist contract is rated from the heist pool, not from the map's") { CHECK(assess(tally(rate(contract, store, gd.get()))) == Outlook::Fatal); } +TEST_CASE("an affix with no roll at all is still rated, past its em-dash unscalable suffix") { + // Four boolean affixes ("Monsters are Hexproof \xe2\x80\x94 Unscalable Value" among them) + // reported as unrateable: the suffix a numeric roll gets is a lowercase parenthetical, and + // this one is not, so the wording it was glued to matched nothing in the pool. + const auto gd = fixture(); + const ppc::item::Item contract = resolved(*gd, "heist-contract-rare-smugglers-den.txt"); + REQUIRE(is_rateable_item(contract, gd.get())); + + const TempDir tmp("heist-unscalable"); + Store store; + store.open(tmp.path, {}, ""); + + const std::vector rows = rate(contract, store, gd.get()); + REQUIRE(rows.size() == 4); + CHECK(std::count_if(rows.begin(), rows.end(), [](const Row& r) { return r.rateable(); }) == 4); +} + TEST_CASE("a heist item the bundle knows the base of is placed by the bundle") { const auto gd = fixture(); // "Contract: Tunnels" is in the slice and its record states domain 22, so nothing here @@ -548,12 +565,12 @@ TEST_CASE("an affix two pools both grant is one row, because it is one decision" const auto gd = fixture(); const std::vector groups = pool_groups(*gd); - // The slice holds nine entries in seven groups: `of Impedance` is in it twice — the map's and - // the chart's, identically worded — and `Area has patches of Burning Ground` three times, of - // which the map's and one contract's are the same set. The store keys on the ref set with no - // domain in it, so entries sharing one can never hold different verdicts and drawing them - // apart is drawing one decision twice. - CHECK(groups.size() == 7); + // The slice holds thirteen entries in eleven groups: `of Impedance` is in it twice — the + // map's and the chart's, identically worded — and `Area has patches of Burning Ground` three + // times, of which the map's and one contract's are the same set. The store keys on the ref + // set with no domain in it, so entries sharing one can never hold different verdicts and + // drawing them apart is drawing one decision twice. + CHECK(groups.size() == 11); const auto flames = std::find_if(groups.begin(), groups.end(), [](const PoolGroup& g) { return g.refs == std::vector{"Area has patches of Burning Ground"}; }); @@ -563,10 +580,12 @@ TEST_CASE("an affix two pools both grant is one row, because it is one decision" CHECK(flames->mod->domain == kMapDomain); CHECK(flames->all[1]->domain == kHeistDomain); // The contract entry granting the same wording *plus* the alert-level pair is not folded in: - // it is a bigger affix, and one decision about it is not the same decision. + // it is a bigger affix, and one decision about it is not the same decision. Six, not two: the + // four "Unscalable Value" heist affixes above (Hexproof, Deployed, of Venom, of Selfishness) + // are each their own group too. CHECK(std::count_if(groups.begin(), groups.end(), [](const PoolGroup& g) { return g.mod->domain == kHeistDomain; - }) == 2); + }) == 6); const auto imp = std::find_if(groups.begin(), groups.end(), [](const PoolGroup& g) { return g.mod->name == "of Impedance"; diff --git a/tests/stat_matcher_test.cpp b/tests/stat_matcher_test.cpp index 3e263fd..beb3da0 100644 --- a/tests/stat_matcher_test.cpp +++ b/tests/stat_matcher_test.cpp @@ -125,6 +125,28 @@ TEST_CASE("an unscalable suffix is stripped and recorded") { CHECK(m->value == doctest::Approx(42.0)); } +TEST_CASE("a modifier with no roll at all gets the em-dash unscalable suffix instead") { + // A Heist Contract's own boolean effects: "Monsters are Hexproof \xe2\x80\x94 Unscalable + // Value" is the em-dash, title-case form the numeric "(unscalable value)" parenthetical + // never covered — both spellings mean the same thing and are looked up from the same + // lexicon list, see UnscalableSuffixes. + auto gd = fixture(); + const auto m = match(*gd, {"Monsters are Hexproof \xe2\x80\x94 Unscalable Value"}); + REQUIRE(m.has_value()); + CHECK(m->stat->ref == "Monsters are Hexproof"); + CHECK(m->unscalable); +} + +TEST_CASE("Wine's plain-hyphen clipboard fallback is accepted for the same suffix") { + // The same fallback risk parse.cpp's info-line tags carry: right after a copy PoE has + // often rendered only CF_TEXT, and every em dash arrives as a plain hyphen. + auto gd = fixture(); + const auto m = match(*gd, {"Monsters are Hexproof - Unscalable Value"}); + REQUIRE(m.has_value()); + CHECK(m->stat->ref == "Monsters are Hexproof"); + CHECK(m->unscalable); +} + TEST_CASE("roll_incr scales the roll, but never an unscalable one") { auto gd = fixture(); const auto scaled = match(*gd, {"+100 to maximum Life"}, ModType::Explicit, 20.0);