From e5e89ceb4c1bed5c7bb5c435f16720fafd0b2c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Posp=C3=AD=C5=A1il?= Date: Thu, 13 Aug 2026 17:43:52 +0200 Subject: [PATCH 1/2] A synthesised base resolves under its bare name, and Heist Gear's own lines are not modifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADDED: two captures pinned against the bundle version each Discord report was filed against, and the regression cases that check them CHANGED: the base line's "Synthesised " prefix is stripped before base lookup, the same as "Superior " already is — the bundle only ever knows a synthesised weapon or armour base under its bare name CHANGED: "Heist member" is a usage-note needle, so the two boilerplate lines every Heist Gear item prints ("Any Heist member can equip this item.", "Can only be equipped to Heist members.") are no longer read as unmatched modifiers CHANGED: the test bundle slice gained Void Sceptre, Nebulis, Precise Arrowhead, Sceptres and Heist Gear plus the stats both captures need, regenerated from the installed bundle matching the reports (20260813.55 predates the checkout's own out/, which has since drifted on an unrelated pair of logbook stats) Co-Authored-By: Claude Sonnet 5 --- scripts/slice-test-bundle.py | 31 +++++++++- src/data/lexicon.cpp | 10 +++- src/data/lexicon.hpp | 2 + src/item/parse.cpp | 7 +++ tests/data/bundle/en-items-base.index.bin | Bin 104 -> 112 bytes tests/data/bundle/en-items-name.index.bin | Bin 400 -> 424 bytes tests/data/bundle/en-items-ref.index.bin | Bin 400 -> 424 bytes tests/data/bundle/en-items.ndjson | 3 + tests/data/bundle/en-stats-matcher.index.bin | Bin 840 -> 952 bytes tests/data/bundle/en-stats-ref.index.bin | Bin 656 -> 728 bytes tests/data/bundle/en-stats.ndjson | 9 +++ .../data/bundle/en-unique-mods-name.index.bin | Bin 48 -> 56 bytes tests/data/bundle/en-unique-mods.ndjson | 1 + tests/data/bundle/item-classes.ndjson | 2 + tests/data/items/heist-gear-rare-oblivion.txt | 26 +++++++++ .../data/items/unique-nebulis-synthesised.txt | 40 +++++++++++++ tests/item_pricing_test.cpp | 54 ++++++++++++++++++ 17 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 tests/data/items/heist-gear-rare-oblivion.txt create mode 100644 tests/data/items/unique-nebulis-synthesised.txt diff --git a/scripts/slice-test-bundle.py b/scripts/slice-test-bundle.py index cfbbb52..0a557f5 100755 --- a/scripts/slice-test-bundle.py +++ b/scripts/slice-test-bundle.py @@ -187,6 +187,24 @@ "# 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", ] ITEMS = [ @@ -288,6 +306,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 +324,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. @@ -326,7 +354,8 @@ 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..35c6c32 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", @@ -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"}; diff --git a/src/data/lexicon.hpp b/src/data/lexicon.hpp index a2bfed4..8063e01 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, 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 7ca51122c85362b430395d6d70151bea52cb2faf..38137755dbc38ed9fda8a73ebf95df9898bdc9a8 100644 GIT binary patch delta 14 Vcmc~OnBc*tBm8%NnA$`)B>*R~1t$Oi delta 7 OcmXTOnBXxnKmh;?>H@L= diff --git a/tests/data/bundle/en-items-name.index.bin b/tests/data/bundle/en-items-name.index.bin index 0958b0cc0a4b8085b50608eea1aeeeefc79eb59a..ec99677fe6bbad2bc7190faf011081d4db532d13 100644 GIT binary patch delta 38 wcmV+>0NMYL1E>R#s0QF(dR+Y`k*0bEtjp7xd?k|}0U8Fz1q8WPCX+S+T0b-n=>Px# delta 17 ZcmZ3%Jb`(_iisO4CL1tHOb%d-002IM21oz^ diff --git a/tests/data/bundle/en-items-ref.index.bin b/tests/data/bundle/en-items-ref.index.bin index 0958b0cc0a4b8085b50608eea1aeeeefc79eb59a..ec99677fe6bbad2bc7190faf011081d4db532d13 100644 GIT binary patch delta 38 wcmV+>0NMYL1E>R#s0QF(dR+Y`k*0bEtjp7xd?k|}0U8Fz1q8WPCX+S+T0b-n=>Px# delta 17 ZcmZ3%Jb`(_iisO4CL1tHOb%d-002IM21oz^ 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-stats-matcher.index.bin b/tests/data/bundle/en-stats-matcher.index.bin index d4b8825c0c9863195c0208f3745cf82f45bd88bc..d0caf50e9bc38e009d1b2a91f278a4c2e80c2946 100644 GIT binary patch delta 193 zcmV;y06zc72Dk?v;xGpQ$5;RWx3Hi9)Gh!334)FRH4^{;r2;Pls$G!|8V*@yN)Gj2 z005Ks$Pi>)ktRAG^;=pXWLy9MWpolDxhVhu&vjrT$5{XXwa#8C$61kPng)zbt5L^T zvHk)920Hy?p^jIR{sGDs79W_@09F71hMg$aWH-A-O4$o|>aT0RfXp0?0Oo zoha92H~;`iCyUsbA^-rS+d 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-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/item_pricing_test.cpp b/tests/item_pricing_test.cpp index 1021ac1..bb7e8c5 100644 --- a/tests/item_pricing_test.cpp +++ b/tests/item_pricing_test.cpp @@ -2608,3 +2608,57 @@ 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("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); +} + From 4492bf3186dbb86f81918c970ac25c8413e5b14d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Posp=C3=AD=C5=A1il?= Date: Thu, 13 Aug 2026 18:13:44 +0200 Subject: [PATCH 2/2] A modifier with no roll at all gets its own unscalable suffix, and it is a lexicon list now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADDED: an em-dash "Unscalable Value" suffix, and its Wine plain-hyphen fallback, to the lexicon's new UnscalableSuffixes list, so a Heist Contract's boolean affixes ("Monsters are Hexproof — Unscalable Value") strip cleanly before matching instead of coming back unrecognised. CHANGED: the numeric "(unscalable value)" parenthetical out of a bare constant and into that same lexicon list, so every spelling of "this roll is fixed" lives in one place that can grow without adding another constant. ADDED: a real capture (a Smuggler's Den contract) and regression tests at the stat-matcher, price-check and map-check layers. Reported by the maintainer, caught testing map check. Co-Authored-By: Claude Sonnet 5 --- scripts/slice-test-bundle.py | 13 +++++++ src/data/lexicon.cpp | 4 +- src/data/lexicon.hpp | 7 ++++ src/data/stat_matcher.cpp | 19 +++++++--- tests/data/bundle/en-mod-pools-ref.index.bin | Bin 112 -> 208 bytes tests/data/bundle/en-mod-pools.ndjson | 4 ++ tests/data/bundle/en-stats-matcher.index.bin | Bin 952 -> 992 bytes tests/data/bundle/en-stats-ref.index.bin | Bin 728 -> 760 bytes tests/data/bundle/en-stats.ndjson | 4 ++ .../heist-contract-rare-smugglers-den.txt | 30 +++++++++++++++ tests/game_data_test.cpp | 2 +- tests/item_pricing_test.cpp | 18 +++++++++ tests/mapcheck_test.cpp | 35 ++++++++++++++---- tests/stat_matcher_test.cpp | 22 +++++++++++ 14 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 tests/data/items/heist-contract-rare-smugglers-den.txt diff --git a/scripts/slice-test-bundle.py b/scripts/slice-test-bundle.py index 0a557f5..91f3a12 100755 --- a/scripts/slice-test-bundle.py +++ b/scripts/slice-test-bundle.py @@ -205,6 +205,13 @@ "# 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 = [ @@ -349,6 +356,12 @@ "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", diff --git a/src/data/lexicon.cpp b/src/data/lexicon.cpp index 35c6c32..5ddc5dc 100644 --- a/src/data/lexicon.cpp +++ b/src/data/lexicon.cpp @@ -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; @@ -211,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 8063e01..3acbb21 100644 --- a/src/data/lexicon.hpp +++ b/src/data/lexicon.hpp @@ -202,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/tests/data/bundle/en-mod-pools-ref.index.bin b/tests/data/bundle/en-mod-pools-ref.index.bin index 40ee4fbbc7237a580b4d9a2e7b5cd067f6ffbcd0..b975fe397fc2b502d4f36a93e88bf171570b2fd9 100644 GIT binary patch literal 208 zcmdn6)SHuqoq=Hkkj~_Q(EVHxdKou_p3e)Rr}8l{gw`~vfaKR!|I-Ai_u4Zv6eKUG z#-0z7SDzJ93(_~~Wp5M6To4UX2ckjxKr~Dqqz}Xg>025-qwNj@1H%ngv6*+785sEF x#ddsVVqnNN(K^`92BAUbZEHJtu$qN|A-BBY8Ax7HgY5-S8w1b&ncw(;d;qvVKYsuK delta 45 rcmcb>SfI9HsW&GJI|BoV&g5WVSX=#16GTsX+1msX2ho`v6WhE1ML7=V 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 d0caf50e9bc38e009d1b2a91f278a4c2e80c2946..159467297d08f8dfcae01fd7d2649ad0259658e2 100644 GIT binary patch delta 83 zcmV-Z0IdJG2jB;=xB(_ws6ln|VE_Oe&3SnJ6952gSgm~h1ONa-2V;FhI{*MFS4Mu$ pV3W=Pk_Pv|r>@RmlU@QE2Ic$sx({HJb^@9PA-W&b&0>=d1FB4dA#wl! delta 46 zcmV+}0MY;82e=2YxB-*E0T`3u0gx7Q%G0qdD*yl@;`+GbO8@|6E>OBGI+K1^5N9SOErFs6ln|VUtz?k{0*Dr>@Rm0045z)3Gco008Ct___~ZlkNd} O1|hm1)Xid(W&(1~y%|OT delta 26 icmeytdV_UC1molc#tCdir!H^Q(Voo4R5iJSsQ>_wx(X)% diff --git a/tests/data/bundle/en-stats.ndjson b/tests/data/bundle/en-stats.ndjson index 1171cd5..2347209 100644 --- a/tests/data/bundle/en-stats.ndjson +++ b/tests/data/bundle/en-stats.ndjson @@ -89,3 +89,7 @@ {"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/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/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 bb7e8c5..b915c4d 100644 --- a/tests/item_pricing_test.cpp +++ b/tests/item_pricing_test.cpp @@ -2631,6 +2631,24 @@ TEST_CASE("a synthesised base resolves under the name beneath its prefix") { 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 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);