diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 831ac254660..5d1516a561f 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -150,4 +150,66 @@ describe("TestAttacks", function() assert.True(preAdrenalineMaxStages < build.calcsTab.mainEnv.player.activeSkillList[1].skillModList:Sum("BASE", nil, "Multiplier:BlightMaxStages")) end) + it("Test cost efficiency modifiers", function() + -- Test Mana Cost Efficiency + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + runCallback("OnFrame") + + -- Get base mana cost (Ball Lightning level 1 has 12 mana cost) + local baseCost = build.calcsTab.mainOutput.ManaCost + assert.are.equals(12, baseCost) + + -- Add 50% mana cost efficiency (should reduce cost to 12/1.5 = 8) + build.configTab.input.customMods = "50% increased Mana Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + local reducedCost = build.calcsTab.mainOutput.ManaCost + assert.are.equals(8, reducedCost) + + -- Test generic cost efficiency (should also affect mana) + newBuild() + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + build.configTab.input.customMods = "25% increased Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + local genericEfficiencyCost = build.calcsTab.mainOutput.ManaCost + -- Test actual behavior: 12/1.25 = 9.6 (not rounded) + assert.True(math.abs(genericEfficiencyCost - 9.6) < 0.001) + + -- Test multiple efficiency sources stacking additively + build.configTab.input.customMods = "25% increased Cost Efficiency\n25% increased Mana Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + local stackedCost = build.calcsTab.mainOutput.ManaCost + assert.are.equals(8, stackedCost) -- 12/(1 + 0.25 + 0.25) = 12/1.5 = 8 + end) + + it("Test cost efficiency with cost modifiers", function() + -- Test interaction between cost efficiency and cost multipliers + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + + -- Add cost multiplier and efficiency + build.configTab.input.customMods = "50% increased Mana Cost\n50% increased Mana Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + local finalCost = build.calcsTab.mainOutput.ManaCost + assert.True(math.abs(finalCost - 12) < 0.1) -- floor(12 * 1.5) / 1.5 + end) + + it("Test mana cost efficiency with support gems", function() + -- Test interaction between cost efficiency and cost multipliers + build.skillsTab:PasteSocketGroup("Contagion 6/0 1\nMagnified Area I 1/0 1") + + -- Add efficiency + build.configTab.input.customMods = "36% increased Mana Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + local finalCost = build.calcsTab.mainOutput.ManaCost + assert.are.equals(7, round(finalCost)) + end) end) \ No newline at end of file diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index f78d34f7d31..8bfee00bf86 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -1747,12 +1747,13 @@ function calcs.offence(env, actor, activeSkill) local moreType = 1 local moreCost = 1 local inc = 0 + local costEfficiency = calcLib.mod(skillModList, skillCfg, val.type .. "CostEfficiency", "CostEfficiency") if not val.unaffectedByGenericCostMults then output[costName] = val.finalBaseCost moreType = skillModList:More(skillCfg, val.type.."Cost") moreCost = skillModList:More(skillCfg, "Cost") inc = skillModList:Sum("INC", skillCfg, val.type.."Cost", "Cost") - output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * val.baseCostRaw * moreType * moreCost) + val.totalCost) + output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * val.baseCostRaw * moreType * moreCost / costEfficiency) + val.totalCost) if inc < 0 then output[costName] = m_max(0, m_ceil((1 + inc / 100) * output[costName])) else @@ -1768,6 +1769,8 @@ function calcs.offence(env, actor, activeSkill) else output[costName] = m_max(0, m_floor(moreCost * output[costName])) end + -- Apply cost efficiency (similar to reservation efficiency) + output[costName] = m_max(0, output[costName] / costEfficiency) output[costName] = m_max(0, output[costName] + val.totalCost) if val.type == "Mana" and hybridLifeCost > 0 then -- Life/Mana Mastery output[costName] = m_max(0, m_floor((1 - hybridLifeCost) * output[costName])) @@ -1779,8 +1782,10 @@ function calcs.offence(env, actor, activeSkill) output[costName] = m_floor(val.baseCost + val.baseCostNoMult) output[costName] = m_max(0, (1 + inc / 100) * output[costName]) output[costName] = m_max(0, moreType * output[costName]) + -- Apply cost efficiency for unaffected costs too + output[costName] = m_max(0, output[costName] / costEfficiency) output[costName] = m_max(0, output[costName] + val.totalCost) - output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * (val.baseCostRaw + val.baseCostNoMult) * moreType) + val.totalCost) + output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * (val.baseCostRaw + val.baseCostNoMult) * moreType / costEfficiency) + val.totalCost) end if breakdown and hasCost then breakdown[costName] = { @@ -1807,6 +1812,9 @@ function calcs.offence(env, actor, activeSkill) if moreType ~= 1 then t_insert(breakdown[costName], s_format("x %.2f ^8(more/less "..val.text.." cost)", moreType)) end + if costEfficiency ~= 1 then + t_insert(breakdown[costName], s_format("/ %.2f ^8(" .. val.text .. " cost efficiency)", costEfficiency)) + end if val.totalCost ~= 0 then t_insert(breakdown[costName], s_format("%+d ^8(total "..val.text.." cost)", val.totalCost)) end diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 6bfa462f1ae..e69fbe554c0 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -705,7 +705,7 @@ local function doActorMisc(env, actor) if modDB:Flag(nil, "Fanaticism") and actor.mainSkill and actor.mainSkill.skillFlags.selfCast then local effect = m_floor(75 * (1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100)) modDB:NewMod("Speed", "MORE", effect, "Fanaticism", ModFlag.Cast) - modDB:NewMod("Cost", "INC", -effect, "Fanaticism", ModFlag.Cast) + modDB:NewMod("Cost", "MORE", -effect, "Fanaticism", ModFlag.Cast) modDB:NewMod("AreaOfEffect", "INC", effect, "Fanaticism", ModFlag.Cast) end if modDB:Flag(nil, "UnholyMight") then diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 2438ab4ec87..382cb42f108 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -188,6 +188,27 @@ local modNameList = { ["cost of skills"] = "Cost", ["cost of attacks"] = { "Cost", tag = { type = "SkillType", skillType = SkillType.Attack } }, ["cost of retaliation skills"] = { "Cost", tag = { type = "SkillType", skillType = SkillType.Retaliation } }, + -- Cost efficiency + ["cost efficiency"] = "CostEfficiency", + ["cost efficiency of skills"] = "CostEfficiency", + ["cost efficiency of attacks"] = { "CostEfficiency", tag = { type = "SkillType", skillType = SkillType.Attack } }, + ["cost efficiency of spells"] = { "CostEfficiency", tag = { type = "SkillType", skillType = SkillType.Spell } }, + ["mana cost efficiency"] = "ManaCostEfficiency", + ["mana cost efficiency of skills"] = "ManaCostEfficiency", + ["mana cost efficiency of attacks"] = { "ManaCostEfficiency", tag = { type = "SkillType", skillType = SkillType.Attack } }, + ["mana cost efficiency of spells"] = { "ManaCostEfficiency", tag = { type = "SkillType", skillType = SkillType.Spell } }, + ["life cost efficiency"] = "LifeCostEfficiency", + ["life cost efficiency of skills"] = "LifeCostEfficiency", + ["life cost efficiency of attacks"] = { "LifeCostEfficiency", tag = { type = "SkillType", skillType = SkillType.Attack } }, + ["life cost efficiency of spells"] = { "LifeCostEfficiency", tag = { type = "SkillType", skillType = SkillType.Spell } }, + ["energy shield cost efficiency"] = "ESCostEfficiency", + ["energy shield cost efficiency of skills"] = "ESCostEfficiency", + ["es cost efficiency"] = "ESCostEfficiency", + ["es cost efficiency of skills"] = "ESCostEfficiency", + ["soul cost efficiency"] = "SoulCostEfficiency", + ["soul cost efficiency of skills"] = "SoulCostEfficiency", + ["rage cost efficiency"] = "RageCostEfficiency", + ["rage cost efficiency of skills"] = "RageCostEfficiency", ["mana reserved"] = "ManaReserved", ["mana reservation"] = "ManaReserved", ["mana reservation of skills"] = { "ManaReserved", tag = { type = "SkillType", skillType = SkillType.Aura } }, @@ -1293,6 +1314,7 @@ local modTagList = { ["per siphoning charge"] = { tag = { type = "Multiplier", var = "SiphoningCharge" } }, ["per spirit charge"] = { tag = { type = "Multiplier", var = "SpiritCharge" } }, ["per challenger charge"] = { tag = { type = "Multiplier", var = "ChallengerCharge" } }, + ["per maximum power charge"] = { tag = { type = "Multiplier", var = "PowerChargeMax" } }, ["per gale force"] = { tag = { type = "Multiplier", var = "GaleForce" } }, ["per intensity"] = { tag = { type = "Multiplier", var = "Intensity" } }, ["per brand"] = { tag = { type = "Multiplier", var = "ActiveBrand" } },