From 906ec6b955cdb60d16505167344184fd6afe693f Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sun, 30 Aug 2026 10:38:38 +0000 Subject: [PATCH 1/2] Apply changes from https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/10262 --- spec/System/TestSkills_spec.lua.rej | 75 +++++++++++++++++++++++++++++ src/Modules/CalcOffence.lua.rej | 36 ++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 spec/System/TestSkills_spec.lua.rej create mode 100644 src/Modules/CalcOffence.lua.rej diff --git a/spec/System/TestSkills_spec.lua.rej b/spec/System/TestSkills_spec.lua.rej new file mode 100644 index 0000000000..fe6cd9cd43 --- /dev/null +++ b/spec/System/TestSkills_spec.lua.rej @@ -0,0 +1,75 @@ +diff a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua (rejected hunks) +@@ -381,6 +381,46 @@ describe("TestSkills", function() + assert.equals(18, build.calcsTab.mainOutput.LifeCost) + end) + ++ it("converts and rounds flat mana cost separately from base cost", function() ++ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") ++ build.configTab.input.customMods = "Skills Cost Life instead of 15% of Mana Cost\n+4 to Total Mana Cost" ++ build.configTab:BuildModList() ++ runCallback("OnFrame") ++ ++ assert.equals(3, build.calcsTab.mainOutput.LifeCost) ++ assert.equals(13, build.calcsTab.mainOutput.ManaCost) ++ end) ++ ++ it("moves flat mana cost when all costs are converted", function() ++ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") ++ build.configTab.input.customMods = "Skills Cost Life instead of Mana\n+4 to Total Mana Cost" ++ build.configTab:BuildModList() ++ runCallback("OnFrame") ++ ++ assert.equals(16, build.calcsTab.mainOutput.LifeCost) ++ assert.equals(0, build.calcsTab.mainOutput.ManaCost) ++ end) ++ ++ it("does not move reduced flat mana cost to life", function() ++ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") ++ build.configTab.input.customMods = "Skills Cost Life instead of Mana\nNon-Channelling Skills have -7 to Total Mana Cost" ++ build.configTab:BuildModList() ++ runCallback("OnFrame") ++ ++ assert.equals(12, build.calcsTab.mainOutput.LifeCost) ++ assert.equals(0, build.calcsTab.mainOutput.ManaCost) ++ end) ++ ++ it("does not partially convert reduced flat mana cost to life", function() ++ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") ++ build.configTab.input.customMods = "Skills Cost Life instead of 15% of Mana Cost\nNon-Channelling Skills have -7 to Total Mana Cost" ++ build.configTab:BuildModList() ++ runCallback("OnFrame") ++ ++ assert.equals(2, build.calcsTab.mainOutput.LifeCost) ++ assert.equals(4, build.calcsTab.mainOutput.ManaCost) ++ end) ++ + it("Test flat cost is added after cost efficiency for energy shield costs", function() + build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") + +@@ -392,6 +432,26 @@ describe("TestSkills", function() + -- 12 / 1.5 + 10 = 18 + assert.equals(18, build.calcsTab.mainOutput.ESCost) + end) ++ ++ it("moves flat mana cost to energy shield with the base cost", function() ++ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") ++ build.configTab.input.customMods = "Skills Cost Energy Shield instead of Mana or Life\n+4 to Total Mana Cost" ++ build.configTab:BuildModList() ++ runCallback("OnFrame") ++ ++ assert.equals(16, build.calcsTab.mainOutput.ESCost) ++ assert.equals(0, build.calcsTab.mainOutput.ManaCost) ++ end) ++ ++ it("does not move reduced flat mana cost to energy shield", function() ++ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") ++ build.configTab.input.customMods = "Skills Cost Energy Shield instead of Mana or Life\nNon-Channelling Skills have -7 to Total Mana Cost" ++ build.configTab:BuildModList() ++ runCallback("OnFrame") ++ ++ assert.equals(12, build.calcsTab.mainOutput.ESCost) ++ assert.equals(0, build.calcsTab.mainOutput.ManaCost) ++ 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") diff --git a/src/Modules/CalcOffence.lua.rej b/src/Modules/CalcOffence.lua.rej new file mode 100644 index 0000000000..c0b5834e49 --- /dev/null +++ b/src/Modules/CalcOffence.lua.rej @@ -0,0 +1,36 @@ +diff a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua (rejected hunks) +@@ -1707,6 +1707,11 @@ function calcs.offence(env, actor, activeSkill) + if val.type == "Life" then + local manaType = resource:gsub("Life", "Mana") + if skillModList:Flag(skillCfg, "CostLifeInsteadOfMana") then -- Blood Magic / Lifetap ++ if val.upfront then ++ local manaTotalCost = skillModList:Sum("BASE", skillCfg, manaType.."Cost") ++ val.totalCost = val.totalCost + m_max(0, manaTotalCost) ++ costs[manaType].totalCost = costs[manaType].totalCost - manaTotalCost ++ end + val.baseCost = val.baseCost + costs[manaType].baseCost + val.baseCostNoMult = val.baseCostNoMult + costs[manaType].baseCostNoMult + val.finalBaseCost = val.finalBaseCost + costs[manaType].finalBaseCost +@@ -1717,11 +1722,22 @@ function calcs.offence(env, actor, activeSkill) + elseif (additionalLifeCost > 0 or hybridLifeCost > 0) and not skillModList:Flag(skillCfg, "CostESInsteadOfManaOrLife") then + val.baseCost = costs[manaType].baseCost + val.finalBaseCost = val.finalBaseCost + round(costs[manaType].finalBaseCost * (hybridLifeCost + additionalLifeCost)) ++ if val.upfront and hybridLifeCost > 0 then ++ -- Only positive flat mana cost is converted, and it is rounded separately from base cost. ++ val.totalCost = val.totalCost + round(m_max(0, skillModList:Sum("BASE", skillCfg, manaType.."Cost")) * hybridLifeCost) ++ end + end + elseif val.type == "ES" then + local manaType = resource:gsub("ES", "Mana") + local lifeType = resource:gsub("ES", "Life") + if skillModList:Flag(skillCfg, "CostESInsteadOfManaOrLife") then -- Whispers of Infinity ++ if val.upfront then ++ local manaTotalCost = skillModList:Sum("BASE", skillCfg, manaType.."Cost") ++ local lifeTotalCost = skillModList:Sum("BASE", skillCfg, lifeType.."Cost") ++ val.totalCost = val.totalCost + m_max(0, manaTotalCost) + m_max(0, lifeTotalCost) ++ costs[manaType].totalCost = costs[manaType].totalCost - manaTotalCost ++ costs[lifeType].totalCost = costs[lifeType].totalCost - lifeTotalCost ++ end + val.baseCost = val.baseCost + costs[manaType].baseCost+ costs[lifeType].baseCost + val.baseCostNoMult = val.baseCostNoMult + costs[manaType].baseCostNoMult + costs[lifeType].baseCostNoMult + val.finalBaseCost = val.finalBaseCost + costs[manaType].finalBaseCost + costs[lifeType].finalBaseCost From ccdc3bde9e2fdb5701947fdb781fb9e8b5ac78dc Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Mon, 31 Aug 2026 01:21:49 +1000 Subject: [PATCH 2/2] Fix port --- spec/System/TestSkills_spec.lua | 58 ++++++++++++++++++++++ spec/System/TestSkills_spec.lua.rej | 75 ----------------------------- src/Modules/CalcOffence.lua | 9 ++++ src/Modules/CalcOffence.lua.rej | 36 -------------- 4 files changed, 67 insertions(+), 111 deletions(-) delete mode 100644 spec/System/TestSkills_spec.lua.rej delete mode 100644 src/Modules/CalcOffence.lua.rej diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 66fe9623fd..d8fb3f90a2 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -490,6 +490,64 @@ describe("TestSkills", function() assert.are.equals(9, finalCost) -- round(floor(9 * 1.5) / 1.5) end) + it("converts positive flat Mana cost to partial Life cost", function() + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + build.configTab.input.customMods = "Skills Cost Life instead of 15% of Mana Cost\n+4 to Total Mana Cost" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(2, build.calcsTab.mainOutput.LifeCost) + assert.are.equals(11, build.calcsTab.mainOutput.ManaCost) + end) + + it("converts positive flat Mana cost to full Life cost", function() + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + build.configTab.input.customMods = "Skill Mana Costs Converted to Life Costs\n+4 to Total Mana Cost" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(13, build.calcsTab.mainOutput.LifeCost) + assert.are.equals(0, build.calcsTab.mainOutput.ManaCost) + end) + + it("does not convert negative flat Mana cost to partial Life cost", function() + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + build.configTab.input.customMods = "Skills Cost Life instead of 15% of Mana Cost\nNon-Channelling Skills have -7 to Total Mana Cost" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(1, build.calcsTab.mainOutput.LifeCost) + assert.are.equals(1, build.calcsTab.mainOutput.ManaCost) + end) + + it("does not convert negative flat Mana cost to full Life cost", function() + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + build.configTab.input.customMods = "Skill Mana Costs Converted to Life Costs\nNon-Channelling Skills have -7 to Total Mana Cost" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(9, build.calcsTab.mainOutput.LifeCost) + assert.are.equals(0, build.calcsTab.mainOutput.ManaCost) + end) + + it("moves only positive flat Mana cost when skills cost Life instead", function() + build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") + runCallback("OnFrame") + local baseManaCost = build.calcsTab.mainOutput.ManaCost + + build.configTab.input.customMods = "Skills Cost Life instead of Mana\n+4 to Total Mana Cost" + build.configTab:BuildModList() + runCallback("OnFrame") + assert.are.equals(baseManaCost + 4, build.calcsTab.mainOutput.LifeCost) + assert.are.equals(0, build.calcsTab.mainOutput.ManaCost) + + build.configTab.input.customMods = "Skills Cost Life instead of Mana\nNon-Channelling Skills have -7 to Total Mana Cost" + build.configTab:BuildModList() + runCallback("OnFrame") + assert.are.equals(baseManaCost, build.calcsTab.mainOutput.LifeCost) + assert.are.equals(0, build.calcsTab.mainOutput.ManaCost) + end) + it("Test socket group pasting with corruption levels and count", function() build.skillsTab:PasteSocketGroup("Wave of Frost 20/0 3 C+1\n Culmination I 1/0 1") assert.are.equals(3, build.skillsTab.socketGroupList[1].gemList[1].count) diff --git a/spec/System/TestSkills_spec.lua.rej b/spec/System/TestSkills_spec.lua.rej deleted file mode 100644 index fe6cd9cd43..0000000000 --- a/spec/System/TestSkills_spec.lua.rej +++ /dev/null @@ -1,75 +0,0 @@ -diff a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua (rejected hunks) -@@ -381,6 +381,46 @@ describe("TestSkills", function() - assert.equals(18, build.calcsTab.mainOutput.LifeCost) - end) - -+ it("converts and rounds flat mana cost separately from base cost", function() -+ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") -+ build.configTab.input.customMods = "Skills Cost Life instead of 15% of Mana Cost\n+4 to Total Mana Cost" -+ build.configTab:BuildModList() -+ runCallback("OnFrame") -+ -+ assert.equals(3, build.calcsTab.mainOutput.LifeCost) -+ assert.equals(13, build.calcsTab.mainOutput.ManaCost) -+ end) -+ -+ it("moves flat mana cost when all costs are converted", function() -+ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") -+ build.configTab.input.customMods = "Skills Cost Life instead of Mana\n+4 to Total Mana Cost" -+ build.configTab:BuildModList() -+ runCallback("OnFrame") -+ -+ assert.equals(16, build.calcsTab.mainOutput.LifeCost) -+ assert.equals(0, build.calcsTab.mainOutput.ManaCost) -+ end) -+ -+ it("does not move reduced flat mana cost to life", function() -+ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") -+ build.configTab.input.customMods = "Skills Cost Life instead of Mana\nNon-Channelling Skills have -7 to Total Mana Cost" -+ build.configTab:BuildModList() -+ runCallback("OnFrame") -+ -+ assert.equals(12, build.calcsTab.mainOutput.LifeCost) -+ assert.equals(0, build.calcsTab.mainOutput.ManaCost) -+ end) -+ -+ it("does not partially convert reduced flat mana cost to life", function() -+ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") -+ build.configTab.input.customMods = "Skills Cost Life instead of 15% of Mana Cost\nNon-Channelling Skills have -7 to Total Mana Cost" -+ build.configTab:BuildModList() -+ runCallback("OnFrame") -+ -+ assert.equals(2, build.calcsTab.mainOutput.LifeCost) -+ assert.equals(4, build.calcsTab.mainOutput.ManaCost) -+ end) -+ - it("Test flat cost is added after cost efficiency for energy shield costs", function() - build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") - -@@ -392,6 +432,26 @@ describe("TestSkills", function() - -- 12 / 1.5 + 10 = 18 - assert.equals(18, build.calcsTab.mainOutput.ESCost) - end) -+ -+ it("moves flat mana cost to energy shield with the base cost", function() -+ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") -+ build.configTab.input.customMods = "Skills Cost Energy Shield instead of Mana or Life\n+4 to Total Mana Cost" -+ build.configTab:BuildModList() -+ runCallback("OnFrame") -+ -+ assert.equals(16, build.calcsTab.mainOutput.ESCost) -+ assert.equals(0, build.calcsTab.mainOutput.ManaCost) -+ end) -+ -+ it("does not move reduced flat mana cost to energy shield", function() -+ build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") -+ build.configTab.input.customMods = "Skills Cost Energy Shield instead of Mana or Life\nNon-Channelling Skills have -7 to Total Mana Cost" -+ build.configTab:BuildModList() -+ runCallback("OnFrame") -+ -+ assert.equals(12, build.calcsTab.mainOutput.ESCost) -+ assert.equals(0, build.calcsTab.mainOutput.ManaCost) -+ 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") diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 40425570cc..1c8ac6f040 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -2100,6 +2100,11 @@ function calcs.offence(env, actor, activeSkill) val.baseCost = val.baseCost + costs[manaType].baseCost val.baseCostNoMult = val.baseCostNoMult + costs[manaType].baseCostNoMult val.finalBaseCost = val.finalBaseCost + costs[manaType].finalBaseCost + if val.upfront then + local manaTotalCost = skillModList:Sum("BASE", skillCfg, manaType.."Cost") + val.totalCost = val.totalCost + m_max(0, manaTotalCost) + costs[manaType].totalCost = costs[manaType].totalCost - manaTotalCost + end costs[manaType].baseCost = 0 costs[manaType].baseCostRaw = 0 costs[manaType].finalBaseCost = 0 @@ -2107,6 +2112,10 @@ function calcs.offence(env, actor, activeSkill) elseif additionalLifeCost > 0 or hybridLifeCost > 0 then val.baseCost = costs[manaType].baseCost val.finalBaseCost = round(finalBaseCostRaw + round(costs[manaType].finalBaseCost * hybridLifeCost) + m_floor(val.baseCost * mult) * additionalLifeCost) + if val.upfront and hybridLifeCost > 0 then + -- Positive flat Mana cost is converted and rounded separately from the base cost. + val.totalCost = val.totalCost + round(m_max(0, skillModList:Sum("BASE", skillCfg, manaType.."Cost")) * hybridLifeCost) + end end elseif val.type == "ES" then local manaType = resource:gsub("ES", "Mana") diff --git a/src/Modules/CalcOffence.lua.rej b/src/Modules/CalcOffence.lua.rej deleted file mode 100644 index c0b5834e49..0000000000 --- a/src/Modules/CalcOffence.lua.rej +++ /dev/null @@ -1,36 +0,0 @@ -diff a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua (rejected hunks) -@@ -1707,6 +1707,11 @@ function calcs.offence(env, actor, activeSkill) - if val.type == "Life" then - local manaType = resource:gsub("Life", "Mana") - if skillModList:Flag(skillCfg, "CostLifeInsteadOfMana") then -- Blood Magic / Lifetap -+ if val.upfront then -+ local manaTotalCost = skillModList:Sum("BASE", skillCfg, manaType.."Cost") -+ val.totalCost = val.totalCost + m_max(0, manaTotalCost) -+ costs[manaType].totalCost = costs[manaType].totalCost - manaTotalCost -+ end - val.baseCost = val.baseCost + costs[manaType].baseCost - val.baseCostNoMult = val.baseCostNoMult + costs[manaType].baseCostNoMult - val.finalBaseCost = val.finalBaseCost + costs[manaType].finalBaseCost -@@ -1717,11 +1722,22 @@ function calcs.offence(env, actor, activeSkill) - elseif (additionalLifeCost > 0 or hybridLifeCost > 0) and not skillModList:Flag(skillCfg, "CostESInsteadOfManaOrLife") then - val.baseCost = costs[manaType].baseCost - val.finalBaseCost = val.finalBaseCost + round(costs[manaType].finalBaseCost * (hybridLifeCost + additionalLifeCost)) -+ if val.upfront and hybridLifeCost > 0 then -+ -- Only positive flat mana cost is converted, and it is rounded separately from base cost. -+ val.totalCost = val.totalCost + round(m_max(0, skillModList:Sum("BASE", skillCfg, manaType.."Cost")) * hybridLifeCost) -+ end - end - elseif val.type == "ES" then - local manaType = resource:gsub("ES", "Mana") - local lifeType = resource:gsub("ES", "Life") - if skillModList:Flag(skillCfg, "CostESInsteadOfManaOrLife") then -- Whispers of Infinity -+ if val.upfront then -+ local manaTotalCost = skillModList:Sum("BASE", skillCfg, manaType.."Cost") -+ local lifeTotalCost = skillModList:Sum("BASE", skillCfg, lifeType.."Cost") -+ val.totalCost = val.totalCost + m_max(0, manaTotalCost) + m_max(0, lifeTotalCost) -+ costs[manaType].totalCost = costs[manaType].totalCost - manaTotalCost -+ costs[lifeType].totalCost = costs[lifeType].totalCost - lifeTotalCost -+ end - val.baseCost = val.baseCost + costs[manaType].baseCost+ costs[lifeType].baseCost - val.baseCostNoMult = val.baseCostNoMult + costs[manaType].baseCostNoMult + costs[lifeType].baseCostNoMult - val.finalBaseCost = val.finalBaseCost + costs[manaType].finalBaseCost + costs[lifeType].finalBaseCost