From 4ec4ecf8d7ce5754b21dfcf8a61630d7963e3809 Mon Sep 17 00:00:00 2001 From: EtherealCarnivore <42915554+EtherealCarnivore@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:02:36 +0200 Subject: [PATCH 1/4] Add Essence of Desolation with ring slot support 3.28 essence with slot-specific ring mods (left ring: Unleash seals, right ring: Shockwave cooldown). Adds mod definitions, parser patterns, and calc wiring for most of its mods across all item types. Extends FindModifierSubstring to check GGPK modTags for defence modifier detection on the body armour mod. --- src/Classes/Item.lua | 8 ++++++++ src/Classes/ItemsTab.lua | 31 ++++++++++++++++++++++------- src/Data/Essence.lua | 1 + src/Data/ModItem.lua | 12 ++++++++++++ src/Modules/CalcPerform.lua | 11 +++++++++++ src/Modules/CalcSetup.lua | 11 +++++++++++ src/Modules/ConfigOptions.lua | 6 ++++++ src/Modules/Data.lua | 15 ++++++++++++++ src/Modules/ModParser.lua | 37 +++++++++++++++++++++++++++++++++++ 9 files changed, 125 insertions(+), 7 deletions(-) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index e9f7cbc75c..c9f8c0d6e2 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -277,6 +277,14 @@ function ItemClass:FindModifierSubstring(substring, itemSlotName) end end end + -- Also check GGPK-exported mod tags (e.g. "defences" tag catches mods that don't mention defence in text) + if v.modTags then + for _, tag in ipairs(v.modTags) do + if tag:lower():find(substring) then + return true + end + end + end end end return false diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 9a8162b911..0224d2f489 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -2670,13 +2670,30 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() elseif sourceId == "ESSENCE" then for _, essence in pairs(self.build.data.essences) do local modId = essence.mods[self.displayItem.type] - local mod = self.displayItem.affixes[modId] - t_insert(modList, { - label = essence.name .. " " .. "^8[" .. table.concat(mod, "/") .. "]" .. " (" .. mod.type .. ")", - mod = mod, - type = "custom", - essence = essence, - }) + if modId then + local mod = self.displayItem.affixes[modId] + t_insert(modList, { + label = essence.name .. " " .. "^8[" .. table.concat(mod, "/") .. "]" .. " (" .. mod.type .. ")", + mod = mod, + type = "custom", + essence = essence, + }) + end + -- Some essences (Desolation) have different mods per ring slot + if self.displayItem.type == "Ring" then + for _, slotKey in ipairs({"Ring 1", "Ring 2"}) do + local slotModId = essence.mods[slotKey] + if slotModId then + local mod = self.displayItem.affixes[slotModId] + t_insert(modList, { + label = essence.name .. " (" .. slotKey .. ") " .. "^8[" .. table.concat(mod, "/") .. "]" .. " (" .. mod.type .. ")", + mod = mod, + type = "custom", + essence = essence, + }) + end + end + end end table.sort(modList, function(a, b) if a.essence.type ~= b.essence.type then diff --git a/src/Data/Essence.lua b/src/Data/Essence.lua index 733cf5d6dc..5a64ade346 100644 --- a/src/Data/Essence.lua +++ b/src/Data/Essence.lua @@ -106,4 +106,5 @@ return { ["Metadata/Items/Currency/CurrencyEssenceInsanity1"] = { name = "Essence of Insanity", type = 21, tier = 8, mods = { ["Amulet"] = "ChanceToRecoverManaOnSkillUseEssence1", ["Belt"] = "MovementVelocityDuringFlaskEffectEssence1", ["Body Armour"] = "OnslaughtWhenHitNewEssence1", ["Boots"] = "ManaRegenerationWhileShockedEssence1", ["Bow"] = "SpiritMinionEssence1", ["Claw"] = "SpiritMinionEssence1", ["Dagger"] = "SpiritMinionEssence1", ["Gloves"] = "SocketedGemsHaveMoreAttackAndCastSpeedEssenceNew1", ["Helmet"] = "SocketedGemsAddPercentageOfPhysicalAsLightningEssence1", ["One Handed Axe"] = "SpiritMinionEssence1", ["One Handed Mace"] = "SpiritMinionEssence1", ["One Handed Sword"] = "SpiritMinionEssence1", ["Quiver"] = "AdditionalPierceEssence7", ["Ring"] = "ReflectDamageTakenEssence1", ["Sceptre"] = "SpiritMinionEssence1", ["Shield"] = "PowerChargeOnBlockEssence1", ["Staff"] = "SpiritMinionEssence1", ["Thrusting One Handed Sword"] = "SpiritMinionEssence1", ["Two Handed Axe"] = "SpiritMinionEssence1", ["Two Handed Mace"] = "SpiritMinionEssence1", ["Two Handed Sword"] = "SpiritMinionEssence1", ["Wand"] = "SpiritMinionEssence1", }, }, ["Metadata/Items/Currency/CurrencyEssenceHorror1"] = { name = "Essence of Horror", type = 22, tier = 8, mods = { ["Amulet"] = "CrushOnHitChanceEssence1", ["Belt"] = "AlchemistsGeniusOnFlaskEssence1_", ["Body Armour"] = "ReducedDamageFromCriticalStrikesPerEnduranceChargeEssence1", ["Boots"] = "ElementalDamageTakenWhileStationaryEssence1", ["Bow"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Claw"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Dagger"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Gloves"] = "SocketedSkillsCriticalChanceEssence1", ["Helmet"] = "SocketedGemsDealMoreElementalDamageEssence1", ["One Handed Axe"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["One Handed Mace"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["One Handed Sword"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Quiver"] = "AddedColdDamagePerFrenzyChargeEssenceQuiver1", ["Ring"] = "AddedColdDamagePerFrenzyChargeEssence1", ["Sceptre"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Shield"] = "NearbyEnemiesChilledOnBlockEssence1", ["Staff"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Thrusting One Handed Sword"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Two Handed Axe"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Two Handed Mace"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Two Handed Sword"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Wand"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", }, }, ["Metadata/Items/Currency/CurrencyEssenceDelirium1"] = { name = "Essence of Delirium", type = 23, tier = 8, mods = { ["Amulet"] = "SpellBlockAmuletEssence1", ["Belt"] = "ChaosResistanceWhileUsingFlaskEssence1", ["Body Armour"] = "ChaosDamageOverTimeTakenEssence1", ["Boots"] = "CannotBePoisonedEssence1", ["Bow"] = "DecayOnHitEssence1", ["Claw"] = "DecayOnHitEssence1", ["Dagger"] = "DecayOnHitEssence1", ["Gloves"] = "SupportDamageOverTimeEssence1", ["Helmet"] = "SocketedAuraGemLevelsEssence1", ["One Handed Axe"] = "DecayOnHitEssence1", ["One Handed Mace"] = "DecayOnHitEssence1", ["One Handed Sword"] = "DecayOnHitEssence1", ["Quiver"] = "MarkEffectEssence1", ["Ring"] = "GlobalDamageOverTimeMultiplierRingEssence1", ["Sceptre"] = "DecayOnHitEssence1", ["Shield"] = "SpellBlockOnLowLifeEssence1", ["Staff"] = "DecayOnHitEssence1", ["Thrusting One Handed Sword"] = "DecayOnHitEssence1", ["Two Handed Axe"] = "DecayOnHitEssence1", ["Two Handed Mace"] = "DecayOnHitEssence1", ["Two Handed Sword"] = "DecayOnHitEssence1", ["Wand"] = "DecayOnHitEssence1", }, }, + ["Metadata/Items/Currency/CurrencyEssenceDesolation1"] = { name = "Essence of Desolation", type = 24, tier = 8, mods = { ["Amulet"] = "AilmentDurationEssenceDesolation1", ["Belt"] = "MagicFlaskEffectEssenceDesolation1", ["Body Armour"] = "GlobalDefencesEssenceDesolation1", ["Boots"] = "MovementSpeedPerEnemyEssenceDesolation1", ["Gloves"] = "AttackCastSpeedPerEnemyEssenceDesolation1", ["Helmet"] = "SocketedGemLevelEssenceDesolation1", ["Quiver"] = "ProjectileChainCloseRangeEssenceDesolation1", ["Ring 1"] = "UnleashSealsEssenceDesolation1", ["Ring 2"] = "ShockwaveCooldownEssenceDesolation1", ["Shield"] = "ArmourAppliesToEleDamageEssenceDesolation1", ["Claw"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Dagger"] = "DoubleDamagePerAilmentEssenceDesolation1", ["One Handed Axe"] = "DoubleDamagePerAilmentEssenceDesolation1", ["One Handed Mace"] = "DoubleDamagePerAilmentEssenceDesolation1", ["One Handed Sword"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Thrusting One Handed Sword"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Wand"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Sceptre"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Bow"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Staff"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Two Handed Axe"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Two Handed Mace"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Two Handed Sword"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", }, }, } \ No newline at end of file diff --git a/src/Data/ModItem.lua b/src/Data/ModItem.lua index ed03273684..bcb76415c4 100644 --- a/src/Data/ModItem.lua +++ b/src/Data/ModItem.lua @@ -1934,6 +1934,18 @@ return { ["MovementSpeedOnBurningChilledShockedGroundEssence1"] = { type = "Suffix", affix = "of the Essence", "12% increased Movement speed while on Burning, Chilled or Shocked ground", statOrder = { 9245 }, level = 63, group = "MovementSpeedOnBurningChilledShockedGround", weightKey = { "default", }, weightVal = { 0 }, modTags = { "speed" }, }, ["ManaRegenerationWhileShockedEssence1"] = { type = "Suffix", affix = "of the Essence", "70% increased Mana Regeneration Rate while Shocked", statOrder = { 2419 }, level = 63, group = "ManaRegenerationWhileShocked", weightKey = { "default", }, weightVal = { 0 }, modTags = { "resource", "mana" }, }, ["ManaGainedOnBlockEssence1"] = { type = "Suffix", affix = "of the Essence", "Recover 5% of your maximum Mana when you Block", statOrder = { 8028 }, level = 63, group = "ManaGainedOnBlock", weightKey = { "default", }, weightVal = { 0 }, modTags = { "block", "resource", "mana" }, }, + ["DoubleDamagePerAilmentEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "1% chance to deal Double Damage against Enemies for each type of Ailment you have inflicted on them", statOrder = { 9900 }, level = 63, group = "DoubleDamagePerAilmentEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "damage" }, }, + ["DoubleDamagePerAilmentEssenceDesolationTwoHand1"] = { type = "Suffix", affix = "of the Essence", "2% chance to deal Double Damage against Enemies for each type of Ailment you have inflicted on them", statOrder = { 9900 }, level = 63, group = "DoubleDamagePerAilmentEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "damage" }, }, + ["MovementSpeedPerEnemyEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "5% increased Movement Speed for each nearby Enemy, up to a maximum of 50%", statOrder = { 9901 }, level = 63, group = "MovementSpeedPerEnemyEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "speed" }, }, + ["GlobalDefencesEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "(70-90)% increased Global Defences if there are no Defence Modifiers on other Equipped Items", statOrder = { 9902 }, level = 63, group = "GlobalDefencesEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "defences" }, }, + ["SocketedGemLevelEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "+6 to Level of Socketed Gems while there is a single Gem Socketed in this Item", statOrder = { 9903 }, level = 63, group = "SocketedGemLevelEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "gem" }, }, + ["ArmourAppliesToEleDamageEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "(2-4)% of Armour applies to Fire, Cold and Lightning Damage taken from Hits if you have Blocked Recently", statOrder = { 9904 }, level = 63, group = "ArmourAppliesToEleDamageEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "armour", "defences", "elemental" }, }, + ["ProjectileChainCloseRangeEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Projectiles can Chain from any number of additional targets in Close Range", statOrder = { 9905 }, level = 63, group = "ProjectileChainCloseRangeEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "attack" }, }, + ["AilmentDurationEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "(40-60)% increased Duration of Ailments of types you haven't inflicted Recently", statOrder = { 9906 }, level = 63, group = "AilmentDurationEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "ailment" }, }, + ["UnleashSealsEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Left ring slot: Skills supported by Unleash have +1 to maximum number of Seals", statOrder = { 9907 }, level = 63, group = "UnleashSealsEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "caster" }, }, + ["ShockwaveCooldownEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Right ring slot: Shockwave has +1 to Cooldown Uses", statOrder = { 9908 }, level = 63, group = "ShockwaveCooldownEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "attack" }, }, + ["AttackCastSpeedPerEnemyEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "5% increased Attack and Cast Speed for each nearby Enemy, up to a maximum of 30%", statOrder = { 9910 }, level = 63, group = "AttackCastSpeedPerEnemyEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "speed", "attack", "caster" }, }, + ["MagicFlaskEffectEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Equipped Magic Flasks have 30% increased effect on you if no Flasks are Adjacent to them", statOrder = { 9909 }, level = 63, group = "MagicFlaskEffectEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "flask" }, }, ["BleedDuration1"] = { type = "Suffix", affix = "of Agony", "(8-12)% increased Bleeding Duration", statOrder = { 4893 }, level = 30, group = "BleedDuration", weightKey = { "bow", "sword", "axe", "mace", "default", }, weightVal = { 0, 0, 0, 0, 0 }, modTags = { "bleed", "physical", "attack", "ailment" }, }, ["BleedDuration2"] = { type = "Suffix", affix = "of Torment", "(13-18)% increased Bleeding Duration", statOrder = { 4893 }, level = 60, group = "BleedDuration", weightKey = { "bow", "sword", "axe", "mace", "default", }, weightVal = { 0, 0, 0, 0, 0 }, modTags = { "bleed", "physical", "attack", "ailment" }, }, ["ChanceToIgnite1"] = { type = "Suffix", affix = "of Ignition", "(18-24)% chance to Ignite", statOrder = { 1937 }, level = 15, group = "ChanceToIgnite", weightKey = { "sceptre", "wand", "default", }, weightVal = { 1000, 1000, 0 }, modTags = { "elemental", "fire", "ailment" }, }, diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 05b2d161b5..fdf47d2f62 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1434,6 +1434,7 @@ function calcs.perform(env, skipEHP) local effectInc = modDB:Sum("INC", {actor = "player"}, "FlaskEffect") local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicUtilityFlaskEffect") + local effectIncMagicNoAdjacent = modDB:Sum("INC", {actor = "player"}, "MagicFlaskEffect") local effectIncNonPlayer = modDB:Sum("INC", nil, "FlaskEffect") local effectIncMagicNonPlayer = modDB:Sum("INC", nil, "MagicUtilityFlaskEffect") local flasksApplyToMinion = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "FlasksApplyToMinion") @@ -1523,6 +1524,16 @@ function calcs.perform(env, skipEHP) flaskEffectInc = flaskEffectInc + effectIncMagic flaskEffectIncNonPlayer = flaskEffectIncNonPlayer + effectIncMagicNonPlayer end + -- Essence of Desolation belt mod: bonus for magic flasks with no flask in an adjacent slot (1-5) + if item.rarity == "MAGIC" and effectIncMagicNoAdjacent ~= 0 then + local flaskSlotNum = env.flaskSlotMap and env.flaskSlotMap[item] + if flaskSlotNum then + local hasAdjacent = (env.flaskSlotOccupied[flaskSlotNum - 1] or env.flaskSlotOccupied[flaskSlotNum + 1]) + if not hasAdjacent then + flaskEffectInc = flaskEffectInc + effectIncMagicNoAdjacent + end + end + end local effectMod = 1 + (flaskEffectInc) / 100 local effectModNonPlayer = 1 + (flaskEffectIncNonPlayer) / 100 diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index e33109c645..422c111dfe 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -862,12 +862,20 @@ function calcs.initEnv(build, mode, override, specEnv) end end + -- Track which flask slot (1-5) each flask is in, for adjacency checks + env.flaskSlotMap = { } + env.flaskSlotOccupied = { } for _, slot in pairs(build.itemsTab.orderedSlots) do local slotName = slot.slotName local item = items[slotName] if item and item.type == "Flask" then if slot.active then env.flasks[item] = true + local flaskNum = tonumber(slotName:match("Flask (%d+)")) + if flaskNum then + env.flaskSlotMap[item] = flaskNum + env.flaskSlotOccupied[flaskNum] = true + end end if item.base.subType == "Life" then local highestLifeRecovery = env.itemModDB.multipliers["LifeFlaskRecovery"] or 0 @@ -1162,6 +1170,9 @@ function calcs.initEnv(build, mode, override, specEnv) end end env.itemModDB.multipliers["SocketedGemsIn"..slotName] = (env.itemModDB.multipliers["SocketedGemsIn"..slotName] or 0) + math.min(slotGemSocketsCount, socketedGems) + if socketedGems == 1 then + env.itemModDB.conditions["SingleGemSocketedIn"..slotName] = true -- for Essence of Desolation helmet mod + end env.itemModDB.multipliers.EmptyRedSocketsInAnySlot = (env.itemModDB.multipliers.EmptyRedSocketsInAnySlot or 0) + slotEmptySocketsCount.R env.itemModDB.multipliers.EmptyGreenSocketsInAnySlot = (env.itemModDB.multipliers.EmptyGreenSocketsInAnySlot or 0) + slotEmptySocketsCount.G env.itemModDB.multipliers.EmptyBlueSocketsInAnySlot = (env.itemModDB.multipliers.EmptyBlueSocketsInAnySlot or 0) + slotEmptySocketsCount.B diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index feac95a2b0..f6123b6314 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -1237,6 +1237,12 @@ Huge sets the radius to 11. { var = "multiplierPoisonAppliedRecently", type = "count", label = "# of Poisons applied Recently:", ifMult = "PoisonAppliedRecently", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:PoisonAppliedRecently", "BASE", val, "Config", { type = "Condition", var = "Combat" }) end }, + { var = "conditionCausedBleedingRecently", type = "check", label = "Have you caused ^xE05030Bleeding ^7Recently?", ifCond = "CausedBleedingRecently", apply = function(val, modList, enemyModList) + modList:NewMod("Condition:CausedBleedingRecently", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) + end }, + { var = "conditionPoisonedEnemyRecently", type = "check", label = "Have you ^x60A060Poisoned ^7an enemy Recently?", ifCond = "PoisonedEnemyRecently", apply = function(val, modList, enemyModList) + modList:NewMod("Condition:PoisonedEnemyRecently", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) + end }, { var = "multiplierLifeSpentRecently", type = "count", label = "# of ^xE05030Life ^7spent Recently:", ifMult = "LifeSpentRecently", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:LifeSpentRecently", "BASE", val, "Config", { type = "Condition", var = "Combat" }) end }, diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index be484e0c32..49eaa97e4a 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -646,6 +646,21 @@ data.itemTagSpecial = { "Cannot Evade", }, }, + -- Text patterns for ItemCondition "no Defence Modifiers"; also backed by modTags in FindModifierSubstring + ["defence"] = (function() + local defencePatterns = { + "[Aa]rmour", + "[Ee]vasion", + "[Ee]nergy [Ss]hield", + "[Ww]ard", + } + local slots = { "weapon 1", "weapon 2", "helmet", "body armour", "gloves", "boots", "amulet", "ring 1", "ring 2", "belt", "shield" } + local t = {} + for _, slot in ipairs(slots) do + t[slot] = defencePatterns + end + return t + end)(), } data.itemTagSpecialExclusionPattern = { ["life"] = { diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index fd91130b3e..dc81d3d9bd 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -5344,6 +5344,43 @@ local specialModList = { ["(%d+)%% more frozen legion and general's cry cooldown recovery rate"] = function(num) return { mod("CooldownRecovery", "MORE", num, { type = "SkillName", skillNameList = { "Frozen Legion", "General's Cry" }, includeTransfigured = true }) } end, ["flamethrower, seismic and lightning spire trap have (%d+)%% increased cooldown recovery rate"] = function(num) return { mod("CooldownRecovery", "INC", num, { type = "SkillName", skillNameList = { "Flamethrower Trap", "Seismic Trap", "Lightning Spire Trap" }, includeTransfigured = true }) } end, ["flamethrower, seismic and lightning spire trap have %-(%d+) cooldown uses?"] = function(num) return { mod("AdditionalCooldownUses", "BASE", -num, { type = "SkillName", skillNameList = { "Flamethrower Trap", "Seismic Trap", "Lightning Spire Trap" }, includeTransfigured = true }) } end, + ["shockwave has %+(%d+) to cooldown uses?"] = function(num) return { mod("AdditionalCooldownUses", "BASE", num, { type = "SkillName", skillName = "Shockwave", includeTransfigured = true }) } end, + -- Stacks per ailment type on enemy; one entry per ailment so they each contribute independently + ["(%d+)%% chance to deal double damage against enemies for each type of ailment you have inflicted on them"] = function(num) return { + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Frozen" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Chilled" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Ignited" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Shocked" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Scorched" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Brittle" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Sapped" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Bleeding" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Poisoned" }), + } end, + ["(%d+)%% increased movement speed for each nearby enemy, up to a maximum of (%d+)%%"] = function(num, _, limit) return { mod("MovementSpeed", "INC", num, { type = "Multiplier", var = "NearbyEnemies", limit = tonumber(limit), limitTotal = true }) } end, + ["(%d+)%% increased attack and cast speed for each nearby enemy, up to a maximum of (%d+)%%"] = function(num, _, limit) return { mod("Speed", "INC", num, { type = "Multiplier", var = "NearbyEnemies", limit = tonumber(limit), limitTotal = true }) } end, + -- {SlotName} gets replaced with the actual slot (e.g. "Helmet") by Item.lua when the mod is applied + ["%+(%d+) to level of socketed gems while there is a single gem socketed in this item"] = function(num) return { + mod("GemProperty", "LIST", { keyword = "all", key = "level", value = num }, { type = "SocketedIn", slotName = "{SlotName}" }, { type = "Condition", var = "SingleGemSocketedIn{SlotName}" }), + } end, + ["(%d+)%% of armour applies to fire, cold and lightning damage taken from hits if you have blocked recently"] = function(num) return { + mod("ArmourAppliesToFireDamageTaken", "BASE", num, { type = "Condition", var = "BlockedRecently" }), + mod("ArmourAppliesToColdDamageTaken", "BASE", num, { type = "Condition", var = "BlockedRecently" }), + mod("ArmourAppliesToLightningDamageTaken", "BASE", num, { type = "Condition", var = "BlockedRecently" }), + } end, + -- "any number" modeled as +99; displays oddly in tooltip but calcs are correct + ["projectiles can chain from any number of additional targets in close range"] = { mod("ChainCountMax", "BASE", 99, nil, ModFlag.Projectile, { type = "Condition", var = "AtCloseRange" }) }, + -- Per-ailment duration; freeze/chill share a condition since they're both cold-based + ["(%d+)%% increased duration of ailments of types you haven't inflicted recently"] = function(num) return { + mod("EnemyFreezeDuration", "INC", num, { type = "Condition", var = "FrozenEnemyRecently", neg = true }), + mod("EnemyChillDuration", "INC", num, { type = "Condition", var = "FrozenEnemyRecently", neg = true }), + mod("EnemyIgniteDuration", "INC", num, { type = "Condition", var = "IgnitedEnemyRecently", neg = true }), + mod("EnemyShockDuration", "INC", num, { type = "Condition", var = "ShockedEnemyRecently", neg = true }), + mod("EnemyBleedDuration", "INC", num, { type = "Condition", var = "CausedBleedingRecently", neg = true }), + mod("EnemyPoisonDuration", "INC", num, { type = "Condition", var = "PoisonedEnemyRecently", neg = true }), + } end, + -- Adjacency checked in CalcPerform using flask slot positions (Flask 1-5) + ["equipped magic flasks have (%d+)%% increased effect on you if no flasks are adjacent to them"] = function(num) return { mod("MagicFlaskEffect", "INC", num, { type = "Condition", var = "NoAdjacentFlasks" }) } end, ["flameblast starts with (%d+) additional stages"] = function(num) return { mod("Multiplier:FlameblastMinimumStage", "BASE", num, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }) } end, ["incinerate starts with (%d+) additional stages"] = function(num) return { mod("Multiplier:IncinerateMinimumStage", "BASE", num, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }) } end, ["%+([%d%.]+) seconds to flameblast and incinerate cooldown"] = function(num) return { From 0188434a2bfe0991c0ccdf4fdddf036e4b5910a3 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 10 Mar 2026 00:41:11 +1100 Subject: [PATCH 2/4] Revert file changes --- src/Classes/ItemsTab.lua | 31 +++++++------------------------ src/Data/Essence.lua | 1 - src/Data/ModItem.lua | 12 ------------ src/Modules/CalcSetup.lua | 3 --- 4 files changed, 7 insertions(+), 40 deletions(-) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 0224d2f489..9a8162b911 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -2670,30 +2670,13 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() elseif sourceId == "ESSENCE" then for _, essence in pairs(self.build.data.essences) do local modId = essence.mods[self.displayItem.type] - if modId then - local mod = self.displayItem.affixes[modId] - t_insert(modList, { - label = essence.name .. " " .. "^8[" .. table.concat(mod, "/") .. "]" .. " (" .. mod.type .. ")", - mod = mod, - type = "custom", - essence = essence, - }) - end - -- Some essences (Desolation) have different mods per ring slot - if self.displayItem.type == "Ring" then - for _, slotKey in ipairs({"Ring 1", "Ring 2"}) do - local slotModId = essence.mods[slotKey] - if slotModId then - local mod = self.displayItem.affixes[slotModId] - t_insert(modList, { - label = essence.name .. " (" .. slotKey .. ") " .. "^8[" .. table.concat(mod, "/") .. "]" .. " (" .. mod.type .. ")", - mod = mod, - type = "custom", - essence = essence, - }) - end - end - end + local mod = self.displayItem.affixes[modId] + t_insert(modList, { + label = essence.name .. " " .. "^8[" .. table.concat(mod, "/") .. "]" .. " (" .. mod.type .. ")", + mod = mod, + type = "custom", + essence = essence, + }) end table.sort(modList, function(a, b) if a.essence.type ~= b.essence.type then diff --git a/src/Data/Essence.lua b/src/Data/Essence.lua index 5a64ade346..733cf5d6dc 100644 --- a/src/Data/Essence.lua +++ b/src/Data/Essence.lua @@ -106,5 +106,4 @@ return { ["Metadata/Items/Currency/CurrencyEssenceInsanity1"] = { name = "Essence of Insanity", type = 21, tier = 8, mods = { ["Amulet"] = "ChanceToRecoverManaOnSkillUseEssence1", ["Belt"] = "MovementVelocityDuringFlaskEffectEssence1", ["Body Armour"] = "OnslaughtWhenHitNewEssence1", ["Boots"] = "ManaRegenerationWhileShockedEssence1", ["Bow"] = "SpiritMinionEssence1", ["Claw"] = "SpiritMinionEssence1", ["Dagger"] = "SpiritMinionEssence1", ["Gloves"] = "SocketedGemsHaveMoreAttackAndCastSpeedEssenceNew1", ["Helmet"] = "SocketedGemsAddPercentageOfPhysicalAsLightningEssence1", ["One Handed Axe"] = "SpiritMinionEssence1", ["One Handed Mace"] = "SpiritMinionEssence1", ["One Handed Sword"] = "SpiritMinionEssence1", ["Quiver"] = "AdditionalPierceEssence7", ["Ring"] = "ReflectDamageTakenEssence1", ["Sceptre"] = "SpiritMinionEssence1", ["Shield"] = "PowerChargeOnBlockEssence1", ["Staff"] = "SpiritMinionEssence1", ["Thrusting One Handed Sword"] = "SpiritMinionEssence1", ["Two Handed Axe"] = "SpiritMinionEssence1", ["Two Handed Mace"] = "SpiritMinionEssence1", ["Two Handed Sword"] = "SpiritMinionEssence1", ["Wand"] = "SpiritMinionEssence1", }, }, ["Metadata/Items/Currency/CurrencyEssenceHorror1"] = { name = "Essence of Horror", type = 22, tier = 8, mods = { ["Amulet"] = "CrushOnHitChanceEssence1", ["Belt"] = "AlchemistsGeniusOnFlaskEssence1_", ["Body Armour"] = "ReducedDamageFromCriticalStrikesPerEnduranceChargeEssence1", ["Boots"] = "ElementalDamageTakenWhileStationaryEssence1", ["Bow"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Claw"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Dagger"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Gloves"] = "SocketedSkillsCriticalChanceEssence1", ["Helmet"] = "SocketedGemsDealMoreElementalDamageEssence1", ["One Handed Axe"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["One Handed Mace"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["One Handed Sword"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Quiver"] = "AddedColdDamagePerFrenzyChargeEssenceQuiver1", ["Ring"] = "AddedColdDamagePerFrenzyChargeEssence1", ["Sceptre"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Shield"] = "NearbyEnemiesChilledOnBlockEssence1", ["Staff"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Thrusting One Handed Sword"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Two Handed Axe"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Two Handed Mace"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Two Handed Sword"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", ["Wand"] = "PowerFrenzyOrEnduranceChargeOnKillEssence1", }, }, ["Metadata/Items/Currency/CurrencyEssenceDelirium1"] = { name = "Essence of Delirium", type = 23, tier = 8, mods = { ["Amulet"] = "SpellBlockAmuletEssence1", ["Belt"] = "ChaosResistanceWhileUsingFlaskEssence1", ["Body Armour"] = "ChaosDamageOverTimeTakenEssence1", ["Boots"] = "CannotBePoisonedEssence1", ["Bow"] = "DecayOnHitEssence1", ["Claw"] = "DecayOnHitEssence1", ["Dagger"] = "DecayOnHitEssence1", ["Gloves"] = "SupportDamageOverTimeEssence1", ["Helmet"] = "SocketedAuraGemLevelsEssence1", ["One Handed Axe"] = "DecayOnHitEssence1", ["One Handed Mace"] = "DecayOnHitEssence1", ["One Handed Sword"] = "DecayOnHitEssence1", ["Quiver"] = "MarkEffectEssence1", ["Ring"] = "GlobalDamageOverTimeMultiplierRingEssence1", ["Sceptre"] = "DecayOnHitEssence1", ["Shield"] = "SpellBlockOnLowLifeEssence1", ["Staff"] = "DecayOnHitEssence1", ["Thrusting One Handed Sword"] = "DecayOnHitEssence1", ["Two Handed Axe"] = "DecayOnHitEssence1", ["Two Handed Mace"] = "DecayOnHitEssence1", ["Two Handed Sword"] = "DecayOnHitEssence1", ["Wand"] = "DecayOnHitEssence1", }, }, - ["Metadata/Items/Currency/CurrencyEssenceDesolation1"] = { name = "Essence of Desolation", type = 24, tier = 8, mods = { ["Amulet"] = "AilmentDurationEssenceDesolation1", ["Belt"] = "MagicFlaskEffectEssenceDesolation1", ["Body Armour"] = "GlobalDefencesEssenceDesolation1", ["Boots"] = "MovementSpeedPerEnemyEssenceDesolation1", ["Gloves"] = "AttackCastSpeedPerEnemyEssenceDesolation1", ["Helmet"] = "SocketedGemLevelEssenceDesolation1", ["Quiver"] = "ProjectileChainCloseRangeEssenceDesolation1", ["Ring 1"] = "UnleashSealsEssenceDesolation1", ["Ring 2"] = "ShockwaveCooldownEssenceDesolation1", ["Shield"] = "ArmourAppliesToEleDamageEssenceDesolation1", ["Claw"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Dagger"] = "DoubleDamagePerAilmentEssenceDesolation1", ["One Handed Axe"] = "DoubleDamagePerAilmentEssenceDesolation1", ["One Handed Mace"] = "DoubleDamagePerAilmentEssenceDesolation1", ["One Handed Sword"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Thrusting One Handed Sword"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Wand"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Sceptre"] = "DoubleDamagePerAilmentEssenceDesolation1", ["Bow"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Staff"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Two Handed Axe"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Two Handed Mace"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", ["Two Handed Sword"] = "DoubleDamagePerAilmentEssenceDesolationTwoHand1", }, }, } \ No newline at end of file diff --git a/src/Data/ModItem.lua b/src/Data/ModItem.lua index bcb76415c4..ed03273684 100644 --- a/src/Data/ModItem.lua +++ b/src/Data/ModItem.lua @@ -1934,18 +1934,6 @@ return { ["MovementSpeedOnBurningChilledShockedGroundEssence1"] = { type = "Suffix", affix = "of the Essence", "12% increased Movement speed while on Burning, Chilled or Shocked ground", statOrder = { 9245 }, level = 63, group = "MovementSpeedOnBurningChilledShockedGround", weightKey = { "default", }, weightVal = { 0 }, modTags = { "speed" }, }, ["ManaRegenerationWhileShockedEssence1"] = { type = "Suffix", affix = "of the Essence", "70% increased Mana Regeneration Rate while Shocked", statOrder = { 2419 }, level = 63, group = "ManaRegenerationWhileShocked", weightKey = { "default", }, weightVal = { 0 }, modTags = { "resource", "mana" }, }, ["ManaGainedOnBlockEssence1"] = { type = "Suffix", affix = "of the Essence", "Recover 5% of your maximum Mana when you Block", statOrder = { 8028 }, level = 63, group = "ManaGainedOnBlock", weightKey = { "default", }, weightVal = { 0 }, modTags = { "block", "resource", "mana" }, }, - ["DoubleDamagePerAilmentEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "1% chance to deal Double Damage against Enemies for each type of Ailment you have inflicted on them", statOrder = { 9900 }, level = 63, group = "DoubleDamagePerAilmentEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "damage" }, }, - ["DoubleDamagePerAilmentEssenceDesolationTwoHand1"] = { type = "Suffix", affix = "of the Essence", "2% chance to deal Double Damage against Enemies for each type of Ailment you have inflicted on them", statOrder = { 9900 }, level = 63, group = "DoubleDamagePerAilmentEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "damage" }, }, - ["MovementSpeedPerEnemyEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "5% increased Movement Speed for each nearby Enemy, up to a maximum of 50%", statOrder = { 9901 }, level = 63, group = "MovementSpeedPerEnemyEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "speed" }, }, - ["GlobalDefencesEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "(70-90)% increased Global Defences if there are no Defence Modifiers on other Equipped Items", statOrder = { 9902 }, level = 63, group = "GlobalDefencesEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "defences" }, }, - ["SocketedGemLevelEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "+6 to Level of Socketed Gems while there is a single Gem Socketed in this Item", statOrder = { 9903 }, level = 63, group = "SocketedGemLevelEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "gem" }, }, - ["ArmourAppliesToEleDamageEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "(2-4)% of Armour applies to Fire, Cold and Lightning Damage taken from Hits if you have Blocked Recently", statOrder = { 9904 }, level = 63, group = "ArmourAppliesToEleDamageEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "armour", "defences", "elemental" }, }, - ["ProjectileChainCloseRangeEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Projectiles can Chain from any number of additional targets in Close Range", statOrder = { 9905 }, level = 63, group = "ProjectileChainCloseRangeEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "attack" }, }, - ["AilmentDurationEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "(40-60)% increased Duration of Ailments of types you haven't inflicted Recently", statOrder = { 9906 }, level = 63, group = "AilmentDurationEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "ailment" }, }, - ["UnleashSealsEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Left ring slot: Skills supported by Unleash have +1 to maximum number of Seals", statOrder = { 9907 }, level = 63, group = "UnleashSealsEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "caster" }, }, - ["ShockwaveCooldownEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Right ring slot: Shockwave has +1 to Cooldown Uses", statOrder = { 9908 }, level = 63, group = "ShockwaveCooldownEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "attack" }, }, - ["AttackCastSpeedPerEnemyEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "5% increased Attack and Cast Speed for each nearby Enemy, up to a maximum of 30%", statOrder = { 9910 }, level = 63, group = "AttackCastSpeedPerEnemyEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "speed", "attack", "caster" }, }, - ["MagicFlaskEffectEssenceDesolation1"] = { type = "Suffix", affix = "of the Essence", "Equipped Magic Flasks have 30% increased effect on you if no Flasks are Adjacent to them", statOrder = { 9909 }, level = 63, group = "MagicFlaskEffectEssence", weightKey = { "default", }, weightVal = { 0 }, modTags = { "flask" }, }, ["BleedDuration1"] = { type = "Suffix", affix = "of Agony", "(8-12)% increased Bleeding Duration", statOrder = { 4893 }, level = 30, group = "BleedDuration", weightKey = { "bow", "sword", "axe", "mace", "default", }, weightVal = { 0, 0, 0, 0, 0 }, modTags = { "bleed", "physical", "attack", "ailment" }, }, ["BleedDuration2"] = { type = "Suffix", affix = "of Torment", "(13-18)% increased Bleeding Duration", statOrder = { 4893 }, level = 60, group = "BleedDuration", weightKey = { "bow", "sword", "axe", "mace", "default", }, weightVal = { 0, 0, 0, 0, 0 }, modTags = { "bleed", "physical", "attack", "ailment" }, }, ["ChanceToIgnite1"] = { type = "Suffix", affix = "of Ignition", "(18-24)% chance to Ignite", statOrder = { 1937 }, level = 15, group = "ChanceToIgnite", weightKey = { "sceptre", "wand", "default", }, weightVal = { 1000, 1000, 0 }, modTags = { "elemental", "fire", "ailment" }, }, diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 422c111dfe..2b215ca498 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -1170,9 +1170,6 @@ function calcs.initEnv(build, mode, override, specEnv) end end env.itemModDB.multipliers["SocketedGemsIn"..slotName] = (env.itemModDB.multipliers["SocketedGemsIn"..slotName] or 0) + math.min(slotGemSocketsCount, socketedGems) - if socketedGems == 1 then - env.itemModDB.conditions["SingleGemSocketedIn"..slotName] = true -- for Essence of Desolation helmet mod - end env.itemModDB.multipliers.EmptyRedSocketsInAnySlot = (env.itemModDB.multipliers.EmptyRedSocketsInAnySlot or 0) + slotEmptySocketsCount.R env.itemModDB.multipliers.EmptyGreenSocketsInAnySlot = (env.itemModDB.multipliers.EmptyGreenSocketsInAnySlot or 0) + slotEmptySocketsCount.G env.itemModDB.multipliers.EmptyBlueSocketsInAnySlot = (env.itemModDB.multipliers.EmptyBlueSocketsInAnySlot or 0) + slotEmptySocketsCount.B From 6b1682f0d1ca7a9f006db126880ffddf90fe4700 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 10 Mar 2026 01:47:31 +1100 Subject: [PATCH 3/4] Move mods + fix parsing + remove defences mod code Fix adjacent flask only checking active flasks Remove the defences mod searching as it's not the proper way to handle it Move all the mods in ModParser to their correct position and fix the parsing --- src/Classes/Item.lua | 8 ----- src/Modules/CalcPerform.lua | 2 +- src/Modules/CalcSetup.lua | 10 +++--- src/Modules/Data.lua | 19 +++--------- src/Modules/ModParser.lua | 62 +++++++++++++++---------------------- 5 files changed, 35 insertions(+), 66 deletions(-) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 6a41cbdc2b..ae3c0acd93 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -277,14 +277,6 @@ function ItemClass:FindModifierSubstring(substring, itemSlotName) end end end - -- Also check GGPK-exported mod tags (e.g. "defences" tag catches mods that don't mention defence in text) - if v.modTags then - for _, tag in ipairs(v.modTags) do - if tag:lower():find(substring) then - return true - end - end - end end end return false diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 9c5821f47b..584235561d 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1443,7 +1443,7 @@ function calcs.perform(env, skipEHP) local effectInc = modDB:Sum("INC", {actor = "player"}, "FlaskEffect") local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicUtilityFlaskEffect") - local effectIncMagicNoAdjacent = modDB:Sum("INC", {actor = "player"}, "MagicFlaskEffect") + local effectIncMagicNoAdjacent = modDB:Sum("INC", {actor = "player"}, "MagicFlaskNoAdjacentEffect") local effectIncNonPlayer = modDB:Sum("INC", nil, "FlaskEffect") local effectIncMagicNonPlayer = modDB:Sum("INC", nil, "MagicUtilityFlaskEffect") local flasksApplyToMinion = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "FlasksApplyToMinion") diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 448a8ccad6..a4b750d3b7 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -884,11 +884,11 @@ function calcs.initEnv(build, mode, override, specEnv) if item and item.type == "Flask" then if slot.active then env.flasks[item] = true - local flaskNum = tonumber(slotName:match("Flask (%d+)")) - if flaskNum then - env.flaskSlotMap[item] = flaskNum - env.flaskSlotOccupied[flaskNum] = true - end + end + local flaskNum = tonumber(slotName:match("Flask (%d+)")) + if flaskNum then + env.flaskSlotMap[item] = flaskNum + env.flaskSlotOccupied[flaskNum] = true end if item.base.subType == "Life" then local highestLifeRecovery = env.itemModDB.multipliers["LifeFlaskRecovery"] or 0 diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index 43204dbd9a..7f206e5cf1 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -646,21 +646,8 @@ data.itemTagSpecial = { "Cannot Evade", }, }, - -- Text patterns for ItemCondition "no Defence Modifiers"; also backed by modTags in FindModifierSubstring - ["defence"] = (function() - local defencePatterns = { - "[Aa]rmour", - "[Ee]vasion", - "[Ee]nergy [Ss]hield", - "[Ww]ard", - } - local slots = { "weapon 1", "weapon 2", "helmet", "body armour", "gloves", "boots", "amulet", "ring 1", "ring 2", "belt", "shield" } - local t = {} - for _, slot in ipairs(slots) do - t[slot] = defencePatterns - end - return t - end)(), + ["defence"] = { + }, } data.itemTagSpecialExclusionPattern = { ["life"] = { @@ -756,6 +743,8 @@ data.itemTagSpecialExclusionPattern = { ["ring"] = { }, }, + ["defence"] = { + }, } -- Cluster jewel data diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index c5c37edb95..59e9be1cbf 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -1824,6 +1824,7 @@ local modTagList = { ["to enemies they're attached to"] = { tag = { type = "MultiplierThreshold", var = "BrandsAttachedToEnemy", threshold = 1 } }, ["for each hit you've taken recently up to a maximum of (%d+)%%"] = function(num) return { tag = { type = "Multiplier", var = "BeenHitRecently", limit = num, limitTotal = true } } end, ["for each nearby enemy, up to (%d+)%%"] = function(num) return { tag = { type = "Multiplier", var = "NearbyEnemies", limit = num, limitTotal = true } } end, + ["for each nearby enemy, up to a maximum of (%d+)%%"] = function(num) return { tag = { type = "Multiplier", var = "NearbyEnemies", limit = num, limitTotal = true } } end, ["while you have iron reflexes"] = { tag = { type = "Condition", var = "HaveIronReflexes" } }, ["while you do not have iron reflexes"] = { tag = { type = "Condition", var = "HaveIronReflexes", neg = true } }, ["while you have elemental overload"] = { tag = { type = "Condition", var = "HaveElementalOverload" } }, @@ -3028,6 +3029,7 @@ local specialModList = { ["([%+%-]%d+) to level of all (%a+) skill gems if a?t? ?l?e?a?s?t? ?(%d+) (%a+) (%a+) items are equipped"] = function(num, _, element, thresh, influence, influence2) return { mod("GemProperty", "LIST", { keyword = element, key = "level", value = num, keyOfScaledMod = "value" }, { type = "MultiplierThreshold", var = firstToUpper(influence) .. firstToUpper(influence2) .. "Item", threshold = tonumber(thresh) }) } end, ["([%+%-]%d+) to level of all ?([%a%- ]*) support gems if (%d+) (%a+) items are equipped"] = function(num, _, element, thresh, influence) return { mod("GemProperty", "LIST", { keyword = element, key = "level", value = num, keyOfScaledMod = "value" }, { type = "MultiplierThreshold", var = firstToUpper(influence) .. "Item", threshold = tonumber(thresh) }) } end, ["([%+%-]%d+) to level of socketed skill gems per (%d+) player levels"] = function(num, _, div) return { mod("GemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = num }, { type = "SocketedIn", slotName = "{SlotName}" }, { type = "Multiplier", var = "Level", div = tonumber(div) }) } end, + ["([%+%-]%d+) to level of socketed gems while there is a single gem socketed in this item"] = function(num) return { mod("GemProperty", "LIST", { keyword = "all", key = "level", value = num }, { type = "SocketedIn", slotName = "{SlotName}" }, { type = "MultiplierThreshold", var = "SocketedGemsIn{SlotName}", threshold = 1, equals = true }) } end, ["socketed gems fire an additional projectile"] = { mod("ExtraSkillMod", "LIST", { mod = mod("ProjectileCount", "BASE", 1) }, { type = "SocketedIn", slotName = "{SlotName}" }) }, ["socketed gems fire (%d+) additional projectiles"] = function(num) return { mod("ExtraSkillMod", "LIST", { mod = mod("ProjectileCount", "BASE", num) }, { type = "SocketedIn", slotName = "{SlotName}" }) } end, ["socketed gems reserve no mana"] = { mod("ManaReserved", "MORE", -100, { type = "SocketedIn", slotName = "{SlotName}" }) }, @@ -3388,6 +3390,17 @@ local specialModList = { mod("EnemyModifier", "LIST", { mod = mod("DamageTaken", "INC", num) }, { type = "ActorCondition", actor = "enemy", var = "Bleeding" }), mod("EnemyModifier", "LIST", { mod = mod("DamageTaken", "INC", num) }, { type = "ActorCondition", actor = "enemy", var = "Poisoned" }), } end, + ["(%d+)%% chance to deal double damage against enemies for each type of ailment you have inflicted on them"] = function(num) return { + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Frozen" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Chilled" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Ignited" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Shocked" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Scorched" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Brittle" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Sapped" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Bleeding" }), + mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Poisoned" }), + } end, -- Elemental Ailments ["(%d+)%% increased elemental damage with hits and ailments for each type of elemental ailment on enemy"] = function(num) return { mod("ElementalDamage", "INC", num, nil, 0, bor(KeywordFlag.Hit, KeywordFlag.Ailment), { type = "ActorCondition", actor = "enemy", var = "Frozen" }), @@ -3600,6 +3613,14 @@ local specialModList = { ["gain adrenaline when you become flame%-touched"] = { flag("Condition:Adrenaline", { type = "Condition", var = "AffectedByApproachingFlames" }) }, ["lose adrenaline when you cease to be flame%-touched"] = { }, ["modifiers to ignite duration on you apply to all elemental ailments"] = { flag("IgniteDurationAppliesToElementalAilments") }, + ["(%d+)%% increased duration of ailments of types you haven't inflicted recently"] = function(num) return { + mod("EnemyFreezeDuration", "INC", num, { type = "Condition", var = "FrozenEnemyRecently", neg = true }), + mod("EnemyChillDuration", "INC", num, { type = "Condition", var = "FrozenEnemyRecently", neg = true }), + mod("EnemyIgniteDuration", "INC", num, { type = "Condition", var = "IgnitedEnemyRecently", neg = true }), + mod("EnemyShockDuration", "INC", num, { type = "Condition", var = "ShockedEnemyRecently", neg = true }), + mod("EnemyBleedDuration", "INC", num, { type = "Condition", var = "CausedBleedingRecently", neg = true }), + mod("EnemyPoisonDuration", "INC", num, { type = "Condition", var = "PoisonedEnemyRecently", neg = true }), + } end, ["chance to avoid being shocked applies to all elemental ailments"] = { flag("ShockAvoidAppliesToElementalAilments") }, -- typo / old wording change ["modifiers to chance to avoid being shocked apply to all elemental ailments"] = { flag("ShockAvoidAppliesToElementalAilments") }, ["enemies permanently take (%d+)%% increased damage for each second they've ever been frozen by you, up to a maximum of (%d+)%%"] = function(num, _, limit) return { mod("EnemyModifier", "LIST", { mod = mod("DamageTaken", "INC", num, { type = "Condition", var = "FrozenByYou" }, { type = "Multiplier", var = "FrozenByYouSeconds", limit = limit / num }) }) } end, @@ -4305,6 +4326,7 @@ local specialModList = { ["attacks chain an additional time when in main hand"] = { mod("ChainCountMax", "BASE", 1, nil, ModFlag.Attack, { type = "SlotNumber", num = 1 }) }, ["attacks fire an additional projectile when in off hand"] = { mod("ProjectileCount", "BASE", 1, nil, ModFlag.Attack, { type = "SlotNumber", num = 2 }) }, ["projectiles chain %+(%d) times while you have phasing"] = function(num) return { mod("ChainCountMax", "BASE", num, nil, ModFlag.Projectile, { type = "Condition", var = "Phasing" }) } end, + ["projectiles can chain from any number of additional targets in close range"] = { mod("ChainCountMax", "BASE", m_huge, nil, ModFlag.Projectile, { type = "Condition", var = "AtCloseRange" }) }, ["projectiles split towards %+(%d) targets"] = function(num) return { mod("SplitCount", "BASE", num) } end, ["adds an additional arrow"] = { mod("ProjectileCount", "BASE", 1, nil, 0, KeywordFlag.Arrow) }, ["(%d+) additional arrows"] = function(num) return { mod("ProjectileCount", "BASE", num, nil, 0, KeywordFlag.Arrow) } end, @@ -4948,6 +4970,7 @@ local specialModList = { ["spectres have a base duration of (%d+) seconds"] = { mod("SkillData", "LIST", { key = "duration", value = 6 }, { type = "SkillName", skillName = "Raise Spectre", includeTransfigured = true }) }, ["flasks applied to you have (%d+)%% increased effect"] = function(num) return { mod("FlaskEffect", "INC", num, { type = "ActorCondition", actor = "player"}) } end, ["flasks applied to you have (%d+)%% increased effect per level"] = function(num) return { mod("FlaskEffect", "INC", num, { type = "ActorCondition", actor = "player"}, { type = "Multiplier", var = "Level" }) } end, + ["equipped magic flasks have (%d+)%% increased effect on you if no flasks are adjacent to them"] = function(num) return { mod("MagicFlaskNoAdjacentEffect", "INC", num) } end, ["while a unique enemy is in your presence, flasks applied to you have (%d+)%% increased effect"] = function(num) return { mod("FlaskEffect", "INC", num, { type = "ActorCondition", actor = "enemy", var = "RareOrUnique" }, { type = "ActorCondition", actor = "player"}) } end, ["while a pinnacle atlas boss is in your presence, flasks applied to you have (%d+)%% increased effect"] = function(num) return { mod("FlaskEffect", "INC", num, { type = "ActorCondition", actor = "enemy", var = "PinnacleBoss" }, { type = "ActorCondition", actor = "player"}) } end, ["magic utility flasks applied to you have (%d+)%% increased effect"] = function(num) return { mod("MagicUtilityFlaskEffect", "INC", num, { type = "ActorCondition", actor = "player"}) } end, @@ -5365,6 +5388,7 @@ local specialModList = { ["elemental skills deal triple damage"] = { mod("TripleDamageChance", "BASE", 100, { type = "SkillType", skillTypeList = { SkillType.Cold, SkillType.Fire, SkillType.Lightning } }), }, ["deal triple damage with elemental skills"] = { mod("TripleDamageChance", "BASE", 100, { type = "SkillType", skillTypeList = { SkillType.Cold, SkillType.Fire, SkillType.Lightning } }), }, ["skills supported by unleash have %+(%d) to maximum number of seals"] = function(num) return { mod("SealCount", "BASE", num) } end, + ["left ring slot: skills supported by unleash have %+(%d) to maximum number of seals"] = function(num) return { mod("SealCount", "BASE", num, { type = "SlotNumber", num = 1 }) } end, ["skills supported by unleash have (%d+)%% increased seal gain frequency"] = function(num) return { mod("SealGainFrequency", "INC", num) } end, ["(%d+)%% increased critical strike chance with spells which remove the maximum number of seals"] = function(num) return { mod("MaxSealCrit", "INC", num) } end, ["gain elusive on critical strike"] = { flag("Condition:CanBeElusive") }, @@ -5406,43 +5430,7 @@ local specialModList = { ["(%d+)%% more frozen legion and general's cry cooldown recovery rate"] = function(num) return { mod("CooldownRecovery", "MORE", num, { type = "SkillName", skillNameList = { "Frozen Legion", "General's Cry" }, includeTransfigured = true }) } end, ["flamethrower, seismic and lightning spire trap have (%d+)%% increased cooldown recovery rate"] = function(num) return { mod("CooldownRecovery", "INC", num, { type = "SkillName", skillNameList = { "Flamethrower Trap", "Seismic Trap", "Lightning Spire Trap" }, includeTransfigured = true }) } end, ["flamethrower, seismic and lightning spire trap have %-(%d+) cooldown uses?"] = function(num) return { mod("AdditionalCooldownUses", "BASE", -num, { type = "SkillName", skillNameList = { "Flamethrower Trap", "Seismic Trap", "Lightning Spire Trap" }, includeTransfigured = true }) } end, - ["shockwave has %+(%d+) to cooldown uses?"] = function(num) return { mod("AdditionalCooldownUses", "BASE", num, { type = "SkillName", skillName = "Shockwave", includeTransfigured = true }) } end, - -- Stacks per ailment type on enemy; one entry per ailment so they each contribute independently - ["(%d+)%% chance to deal double damage against enemies for each type of ailment you have inflicted on them"] = function(num) return { - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Frozen" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Chilled" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Ignited" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Shocked" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Scorched" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Brittle" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Sapped" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Bleeding" }), - mod("DoubleDamageChance", "BASE", num, { type = "ActorCondition", actor = "enemy", var = "Poisoned" }), - } end, - ["(%d+)%% increased movement speed for each nearby enemy, up to a maximum of (%d+)%%"] = function(num, _, limit) return { mod("MovementSpeed", "INC", num, { type = "Multiplier", var = "NearbyEnemies", limit = tonumber(limit), limitTotal = true }) } end, - ["(%d+)%% increased attack and cast speed for each nearby enemy, up to a maximum of (%d+)%%"] = function(num, _, limit) return { mod("Speed", "INC", num, { type = "Multiplier", var = "NearbyEnemies", limit = tonumber(limit), limitTotal = true }) } end, - -- {SlotName} gets replaced with the actual slot (e.g. "Helmet") by Item.lua when the mod is applied - ["%+(%d+) to level of socketed gems while there is a single gem socketed in this item"] = function(num) return { - mod("GemProperty", "LIST", { keyword = "all", key = "level", value = num }, { type = "SocketedIn", slotName = "{SlotName}" }, { type = "Condition", var = "SingleGemSocketedIn{SlotName}" }), - } end, - ["(%d+)%% of armour applies to fire, cold and lightning damage taken from hits if you have blocked recently"] = function(num) return { - mod("ArmourAppliesToFireDamageTaken", "BASE", num, { type = "Condition", var = "BlockedRecently" }), - mod("ArmourAppliesToColdDamageTaken", "BASE", num, { type = "Condition", var = "BlockedRecently" }), - mod("ArmourAppliesToLightningDamageTaken", "BASE", num, { type = "Condition", var = "BlockedRecently" }), - } end, - -- "any number" modeled as +99; displays oddly in tooltip but calcs are correct - ["projectiles can chain from any number of additional targets in close range"] = { mod("ChainCountMax", "BASE", 99, nil, ModFlag.Projectile, { type = "Condition", var = "AtCloseRange" }) }, - -- Per-ailment duration; freeze/chill share a condition since they're both cold-based - ["(%d+)%% increased duration of ailments of types you haven't inflicted recently"] = function(num) return { - mod("EnemyFreezeDuration", "INC", num, { type = "Condition", var = "FrozenEnemyRecently", neg = true }), - mod("EnemyChillDuration", "INC", num, { type = "Condition", var = "FrozenEnemyRecently", neg = true }), - mod("EnemyIgniteDuration", "INC", num, { type = "Condition", var = "IgnitedEnemyRecently", neg = true }), - mod("EnemyShockDuration", "INC", num, { type = "Condition", var = "ShockedEnemyRecently", neg = true }), - mod("EnemyBleedDuration", "INC", num, { type = "Condition", var = "CausedBleedingRecently", neg = true }), - mod("EnemyPoisonDuration", "INC", num, { type = "Condition", var = "PoisonedEnemyRecently", neg = true }), - } end, - -- Adjacency checked in CalcPerform using flask slot positions (Flask 1-5) - ["equipped magic flasks have (%d+)%% increased effect on you if no flasks are adjacent to them"] = function(num) return { mod("MagicFlaskEffect", "INC", num, { type = "Condition", var = "NoAdjacentFlasks" }) } end, + ["right ring slot: shockwave has %+(%d+) to cooldown uses?"] = function(num) return { mod("AdditionalCooldownUses", "BASE", num, { type = "SkillName", skillName = "Shockwave" }, { type = "SlotNumber", num = 2 }) } end, ["flameblast starts with (%d+) additional stages"] = function(num) return { mod("Multiplier:FlameblastMinimumStage", "BASE", num, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }) } end, ["incinerate starts with (%d+) additional stages"] = function(num) return { mod("Multiplier:IncinerateMinimumStage", "BASE", num, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }) } end, ["%+([%d%.]+) seconds to flameblast and incinerate cooldown"] = function(num) return { From b472f66fe3812143dc34fc7ab7bf3863bd0bb5ad Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 10 Mar 2026 01:52:11 +1100 Subject: [PATCH 4/4] Move configs --- src/Modules/ConfigOptions.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index e4501ab725..9980437a83 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -1299,15 +1299,12 @@ Huge sets the radius to 11. { var = "conditionStunnedRecently", type = "check", label = "Have you been Stunned Recently?", ifCond = "StunnedRecently", apply = function(val, modList, enemyModList) modList:NewMod("Condition:StunnedRecently", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "multiplierPoisonAppliedRecently", type = "count", label = "# of Poisons applied Recently:", ifMult = "PoisonAppliedRecently", apply = function(val, modList, enemyModList) - modList:NewMod("Multiplier:PoisonAppliedRecently", "BASE", val, "Config", { type = "Condition", var = "Combat" }) - end }, - { var = "conditionCausedBleedingRecently", type = "check", label = "Have you caused ^xE05030Bleeding ^7Recently?", ifCond = "CausedBleedingRecently", apply = function(val, modList, enemyModList) - modList:NewMod("Condition:CausedBleedingRecently", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) - end }, { var = "conditionPoisonedEnemyRecently", type = "check", label = "Have you ^x60A060Poisoned ^7an enemy Recently?", ifCond = "PoisonedEnemyRecently", apply = function(val, modList, enemyModList) modList:NewMod("Condition:PoisonedEnemyRecently", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) end }, + { var = "multiplierPoisonAppliedRecently", type = "count", label = "# of Poisons applied Recently:", ifMult = "PoisonAppliedRecently", implyCond = "PoisonedEnemyRecently", apply = function(val, modList, enemyModList) + modList:NewMod("Multiplier:PoisonAppliedRecently", "BASE", val, "Config", { type = "Condition", var = "Combat" }) + end }, { var = "multiplierLifeSpentRecently", type = "count", label = "# of ^xE05030Life ^7spent Recently:", ifMult = "LifeSpentRecently", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:LifeSpentRecently", "BASE", val, "Config", { type = "Condition", var = "Combat" }) end }, @@ -1596,6 +1593,9 @@ Huge sets the radius to 11. { var = "multiplierImpalesOnEnemy", type = "countAllowZero", label = "# of Impales on enemy (if not maximum):", ifFlag = "impale", apply = function(val, modList, enemyModList) enemyModList:NewMod("Multiplier:ImpaleStacks", "BASE", val, "Config", { type = "Condition", var = "Combat" }) end }, + { var = "conditionCausedBleedingRecently", type = "check", label = "Have you caused ^xE05030Bleeding ^7Recently?", ifCond = "CausedBleedingRecently", apply = function(val, modList, enemyModList) + modList:NewMod("Condition:CausedBleedingRecently", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) + end }, { var = "multiplierBleedsOnEnemy", type = "count", label = "# of Bleeds on enemy (if not maximum):", ifFlag = "Condition:HaveCrimsonDance", tooltip = "Sets current number of Bleeds on the enemy if using the Crimson Dance keystone.\nThis also implies that the enemy is Bleeding.", apply = function(val, modList, enemyModList) enemyModList:NewMod("Multiplier:BleedStacks", "BASE", val, "Config", { type = "Condition", var = "Combat" }) enemyModList:NewMod("Condition:Bleeding", "FLAG", true, "Config", { type = "Condition", var = "Effective" })