Skip to content

Commit 8322165

Browse files
committed
Merge branch 'dev' into runic-ward
2 parents be5e3d6 + 78e2079 commit 8322165

137 files changed

Lines changed: 28544 additions & 19248 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## [v0.21.1](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/tree/v0.21.1) (2026/06/20)
4+
5+
[Full Changelog](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/compare/v0.21.0...v0.21.1)
6+
7+
8+
## What's Changed
9+
### Fixed Crashes
10+
- Fix crash when trying to use PoB trader after 0.5.3 [\#2238](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2238) ([vaisest](https://github.com/vaisest))
11+
### Fixed Calculations
12+
- Fix more Maximum Rage mod not working [\#2228](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2228) ([Blitz54](https://github.com/Blitz54))
13+
- Fix general Grenade mods not working [\#2196](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2196) ([LocalIdentity](https://github.com/LocalIdentity))
14+
- Fix Blazing Critical not applying as a global buff [\#2221](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2221) ([LocalIdentity](https://github.com/LocalIdentity))
15+
- Fix Sine Aequo Immobilisation buildup calculation [\#2222](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2222) ([LocalIdentity](https://github.com/LocalIdentity))
16+
- Fix Daze not applying 50% more Stun Buildup [\#2223](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2223) ([LocalIdentity](https://github.com/LocalIdentity))
17+
- Fix Overwhelming Presence quality mod multiplying Aura magnitude [\#2224](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2224) ([LocalIdentity](https://github.com/LocalIdentity))
18+
- Fix "+% to Critical Damage Bonus" mods on weapons applying to Spells [\#2247](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2247) ([luther-rotmg](https://github.com/luther-rotmg))
19+
### Fixed Behaviours
20+
- Fix Ice Bite and Innervate only supporting Attacks [\#2207](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2207) ([vaisest](https://github.com/vaisest))
21+
- Fix power report and trade search not working with Minions [\#2219](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2219) ([LocalIdentity](https://github.com/LocalIdentity))
22+
- Fix Bloodhounds Mark not counting the enemy as Marked [\#2220](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2220) ([LocalIdentity](https://github.com/LocalIdentity))
23+
24+
325
## [v0.21.0](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/tree/v0.21.0) (2026/06/13)
426

527
[Full Changelog](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/compare/v0.20.0...v0.21.0)

changelog.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
VERSION[0.21.1][2026/06/20]
2+
3+
--- Fixed Crashes ---
4+
* Fix crash when trying to use PoB trader after 0.5.3 (vaisest)
5+
6+
--- Fixed Calculations ---
7+
* Fix more Maximum Rage mod not working (Blitz54)
8+
* Fix general Grenade mods not working (LocalIdentity)
9+
* Fix Blazing Critical not applying as a global buff (LocalIdentity)
10+
* Fix Sine Aequo Immobilisation buildup calculation (LocalIdentity)
11+
* Fix Daze not applying 50% more Stun Buildup (LocalIdentity)
12+
* Fix Overwhelming Presence quality mod multiplying Aura magnitude (LocalIdentity)
13+
* Fix "+% to Critical Damage Bonus" mods on weapons applying to Spells (luther-rotmg)
14+
15+
--- Fixed Behaviours ---
16+
* Fix Ice Bite and Innervate only supporting Attacks (vaisest)
17+
* Fix power report and trade search not working with Minions (LocalIdentity)
18+
* Fix Bloodhounds Mark not counting the enemy as Marked (LocalIdentity)
19+
120
VERSION[0.21.0][2026/06/13]
221

322
--- New to Path of Building ---

manifest.xml

Lines changed: 45 additions & 44 deletions
Large diffs are not rendered by default.

spec/System/TestAttacks_spec.lua

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,35 @@ describe("TestAttacks", function()
3030
assert.are.equals(2 + 0.25, build.calcsTab.mainOutput.CritMultiplier)
3131
end)
3232

33+
it("local Critical Damage Bonus on a weapon does not apply to spells (issue #2199)", function()
34+
-- baseline: a spell's crit multi with a plain quarterstaff (no local crit damage)
35+
build.itemsTab:CreateDisplayItemFromRaw([[
36+
New Item
37+
Razor Quarterstaff
38+
]])
39+
build.itemsTab:AddDisplayItem()
40+
build.skillsTab:PasteSocketGroup("Fireball 20/0 1")
41+
build.mainSocketGroup = 1
42+
runCallback("OnFrame")
43+
local baseSpellCritMulti = build.calcsTab.mainOutput.CritMultiplier
44+
45+
newBuild()
46+
47+
-- same spell + quarterstaff, now with a local "+X% to Critical Damage Bonus" affix
48+
build.itemsTab:CreateDisplayItemFromRaw([[
49+
New Item
50+
Razor Quarterstaff
51+
+50% to Critical Damage Bonus
52+
]])
53+
build.itemsTab:AddDisplayItem()
54+
build.skillsTab:PasteSocketGroup("Fireball 20/0 1")
55+
build.mainSocketGroup = 1
56+
runCallback("OnFrame")
57+
58+
-- the weapon-local crit damage must NOT reach the spell (it isn't using the weapon)
59+
assert.are.equals(baseSpellCritMulti, build.calcsTab.mainOutput.CritMultiplier)
60+
end)
61+
3362
it("correctly converts spell damage per stat to attack damage", function()
3463
assert.are.equals(0, build.calcsTab.mainEnv.player.modDB:Sum("INC", { flags = ModFlag.Attack }, "Damage"))
3564
build.itemsTab:CreateDisplayItemFromRaw([[
@@ -389,4 +418,166 @@ describe("TestAttacks", function()
389418
assert.is_true(math.abs(bifurcateChance - build.calcsTab.mainOutput.MainHand.CritBifurcates) < 0.000001)
390419
assert.are.equals(1 + critBonusMultiplier, build.calcsTab.mainOutput.MainHand.AverageHit)
391420
end)
421+
422+
-- Dual Wield tests
423+
local setupDualWieldTestConditions = function()
424+
local slowHighDmgMace = [[
425+
Slow High Crit High Damage Mace
426+
Marauding Mace
427+
Quality: 0
428+
200% increased physical damage
429+
100% increased critical hit chance
430+
-25% increased attack speed
431+
]]
432+
433+
local fastLowDmgMace = [[
434+
Fast Low Crit Low Damage Mace
435+
Marauding Mace
436+
Quality: 0
437+
-50% increased physical damage
438+
-100% increased critical hit chance
439+
50% increased attack speed
440+
]]
441+
442+
build.itemsTab:CreateDisplayItemFromRaw(slowHighDmgMace)
443+
build.itemsTab:AddDisplayItem()
444+
runCallback("OnFrame")
445+
build.itemsTab.slots["Weapon 1"]:SetSelItemId(build.itemsTab.items[1].id)
446+
447+
build.itemsTab:CreateDisplayItemFromRaw(fastLowDmgMace)
448+
build.itemsTab:AddDisplayItem()
449+
runCallback("OnFrame")
450+
build.itemsTab.slots["Weapon 2"]:SetSelItemId(build.itemsTab.items[2].id)
451+
452+
build.configTab.input.customMods = [[
453+
nearby enemies have 100% less armour
454+
your hits can't be evaded
455+
]]
456+
build.configTab:BuildModList()
457+
runCallback("OnFrame")
458+
end
459+
460+
local function harmonicMean(a, b)
461+
return 2 / (1/a + 1/b)
462+
end
463+
464+
it("correctly calculates dual wield DPS for double hits", function()
465+
setupDualWieldTestConditions()
466+
build.skillsTab:PasteSocketGroup("skillId:MeleeMaceMacePlayer Mace Strike 20/0 1")
467+
runCallback("OnFrame")
468+
build.calcsTab:BuildOutput()
469+
runCallback("OnFrame")
470+
471+
-- Attack Speed
472+
local mainHandSpeed = build.calcsTab.mainOutput.MainHand.Speed
473+
local offHandSpeed = build.calcsTab.mainOutput.OffHand.Speed
474+
local combinedSpeed = harmonicMean(mainHandSpeed, offHandSpeed)
475+
assert.are.equals(round(combinedSpeed, 4), round(build.calcsTab.mainOutput.Speed, 4))
476+
477+
-- Average Hit
478+
local mainHandAvgDmg = build.calcsTab.mainOutput.MainHand.AverageDamage
479+
local offHandAvgDmg = build.calcsTab.mainOutput.OffHand.AverageDamage
480+
local combinedAvgDmg = build.calcsTab.mainOutput.AverageDamage
481+
assert.are.equals(round((mainHandAvgDmg + offHandAvgDmg) / 2, 4), round(combinedAvgDmg, 4))
482+
483+
-- DPS (hits twice per attack)
484+
local combinedDPS = build.calcsTab.mainOutput.TotalDPS
485+
assert.are.equals(round(combinedAvgDmg * combinedSpeed * 2,4), round(combinedDPS,4))
486+
end)
487+
488+
it("correctly calculates dual wield crit chance for double hits", function()
489+
setupDualWieldTestConditions()
490+
build.skillsTab:PasteSocketGroup("skillId:MeleeMaceMacePlayer Mace Strike 20/0 1")
491+
runCallback("OnFrame")
492+
build.calcsTab:BuildOutput()
493+
runCallback("OnFrame")
494+
495+
-- Double hits roll crit individually per weapon, so should be average
496+
local mainHandCritChance = build.calcsTab.mainOutput.MainHand.CritChance
497+
local offHandCritChance = build.calcsTab.mainOutput.OffHand.CritChance
498+
local combinedCritChance = (mainHandCritChance + offHandCritChance) / 2
499+
assert.are.equals(combinedCritChance, build.calcsTab.mainOutput.CritChance)
500+
end)
501+
502+
it("correctly calculates dual wield DPS for alternating hits", function()
503+
setupDualWieldTestConditions()
504+
build.skillsTab:PasteSocketGroup("Armour Breaker 20/0 1")
505+
runCallback("OnFrame")
506+
build.calcsTab:BuildOutput()
507+
runCallback("OnFrame")
508+
509+
-- Attack Speed
510+
local mainHandSpeed = build.calcsTab.mainOutput.MainHand.Speed
511+
local offHandSpeed = build.calcsTab.mainOutput.OffHand.Speed
512+
local combinedSpeed = harmonicMean(mainHandSpeed, offHandSpeed)
513+
assert.are.equals(round(combinedSpeed, 4), round(build.calcsTab.mainOutput.Speed, 4))
514+
515+
-- Average Hit
516+
local mainHandAvgDmg = build.calcsTab.mainOutput.MainHand.AverageDamage
517+
local offHandAvgDmg = build.calcsTab.mainOutput.OffHand.AverageDamage
518+
local combinedAvgDmg = build.calcsTab.mainOutput.AverageDamage
519+
assert.are.equals(round((mainHandAvgDmg + offHandAvgDmg) / 2, 4), round(combinedAvgDmg, 4))
520+
521+
-- DPS (hits once per attack)
522+
local combinedDPS = build.calcsTab.mainOutput.TotalDPS
523+
assert.are.equals(round(combinedAvgDmg * combinedSpeed, 4), round(combinedDPS, 4))
524+
end)
525+
526+
it("correctly calculates dual wield crit chance for alternating hits", function()
527+
setupDualWieldTestConditions()
528+
build.skillsTab:PasteSocketGroup("Armour Breaker 20/0 1")
529+
runCallback("OnFrame")
530+
build.calcsTab:BuildOutput()
531+
runCallback("OnFrame")
532+
533+
-- Alternating hits roll crit individually per weapon, so should be average
534+
local mainHandCritChance = build.calcsTab.mainOutput.MainHand.CritChance
535+
local offHandCritChance = build.calcsTab.mainOutput.OffHand.CritChance
536+
local combinedCritChance = (mainHandCritChance + offHandCritChance) / 2
537+
assert.are.equals(combinedCritChance, build.calcsTab.mainOutput.CritChance)
538+
end)
539+
540+
--[[
541+
NOTE: the following section contains tests for "combined hits", which PoE2 doesn't have as of 2026-06-02,
542+
which means the tests were written for a temporary test skill that will not be committed.
543+
The test can be updated by simply replacing `"skillId:MeleeMaceMacePlayerCombinedTEST Mace Strike TEST 20/0 1"`
544+
with actual skill data once available
545+
]]
546+
--[[ it("correctly calculates dual wield DPS for combined hits", function()
547+
setupDualWieldTestConditions()
548+
build.skillsTab:PasteSocketGroup("skillId:MeleeMaceMacePlayerCombinedTEST Mace Strike TEST 20/0 1")
549+
runCallback("OnFrame")
550+
build.calcsTab:BuildOutput()
551+
runCallback("OnFrame")
552+
553+
-- Attack Speed
554+
local mainHandSpeed = build.calcsTab.mainOutput.MainHand.Speed
555+
local offHandSpeed = build.calcsTab.mainOutput.OffHand.Speed
556+
local combinedSpeed = harmonicMean(mainHandSpeed, offHandSpeed)
557+
assert.are.equals(round(combinedSpeed, 4), round(build.calcsTab.mainOutput.Speed, 4))
558+
559+
-- Average Hit
560+
local mainHandAvgDmg = build.calcsTab.mainOutput.MainHand.AverageDamage
561+
local offHandAvgDmg = build.calcsTab.mainOutput.OffHand.AverageDamage
562+
local combinedAvgDmg = build.calcsTab.mainOutput.AverageDamage
563+
assert.are.equals(round((mainHandAvgDmg + offHandAvgDmg), 4), round(combinedAvgDmg, 4))
564+
565+
-- DPS (hits twice per attack)
566+
local combinedDPS = build.calcsTab.mainOutput.TotalDPS
567+
assert.are.equals(round(combinedAvgDmg * combinedSpeed, 4), round(combinedDPS,4))
568+
end)
569+
570+
it("correctly calculates dual wield crit chance for combined hits", function()
571+
setupDualWieldTestConditions()
572+
build.skillsTab:PasteSocketGroup("skillId:MeleeMaceMacePlayerCombinedTEST Mace Strike TEST 20/0 1")
573+
runCallback("OnFrame")
574+
build.calcsTab:BuildOutput()
575+
runCallback("OnFrame")
576+
577+
-- combined hits count whole attack as crit, as long as one hand rolls crit)
578+
local mainHandCritChance = build.calcsTab.mainOutput.MainHand.CritChance
579+
local offHandCritChance = build.calcsTab.mainOutput.OffHand.CritChance
580+
local combinedCritChance = mainHandCritChance + offHandCritChance - (mainHandCritChance * offHandCritChance / 100)
581+
assert.are.equals(combinedCritChance, build.calcsTab.mainOutput.CritChance)
582+
end) ]]
392583
end)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
describe("Buy similar mod stat matching", function()
2+
local bs = LoadModule("Classes/CompareBuySimilar")
3+
4+
describe("addModEntries mod matching", function()
5+
it("matches from nothing mods as options", function()
6+
local fromNothing = new("Item", [[
7+
From Nothing
8+
Diamond
9+
LevelReq: 0
10+
Radius: Small
11+
Limited to: 1
12+
Implicits: 0
13+
Passives in radius of Zealot's Oath can be Allocated without being connected to your tree
14+
Corrupted]])
15+
16+
local modSources = {
17+
{ list = fromNothing.explicitModLines, type = "explicit" }
18+
}
19+
local modEntries = bs.addModEntries(fromNothing, modSources)
20+
assert.equal(1, #modEntries)
21+
assert.same(
22+
{
23+
formattedLines = { colorCodes.MAGIC.."Passives in radius of Zealot's Oath can be Allocated without being connected to your tree" },
24+
type =
25+
"explicit",
26+
isOption = true,
27+
invert = false,
28+
tradeIds = { "explicit.stat_2422708892|52" },
29+
value = nil
30+
},
31+
modEntries[1])
32+
end)
33+
34+
it("combines mods that are the same stat", function()
35+
local lifeDiamond = new("Item", [[
36+
Test Subject
37+
Diamond
38+
Implicits: 0
39+
+100 to Maximum Life
40+
+50 to Maximum Life
41+
+50% to Fire Resistance]])
42+
43+
local entries = bs.addModEntries(lifeDiamond, { { list = lifeDiamond.explicitModLines, type = "explicit" } })
44+
assert.equal(2, #entries)
45+
assert.equal(2, #entries[1].formattedLines)
46+
assert.equal("+100 to Maximum Life", StripEscapes(entries[1].formattedLines[1]))
47+
assert.equal("+50 to Maximum Life", StripEscapes(entries[1].formattedLines[2]))
48+
assert.equal(150, entries[1].value)
49+
50+
local lifelessDiamond = new("Item", [[
51+
Test Subject
52+
Diamond
53+
Implicits: 0
54+
-100 to Maximum Life
55+
+50 to Maximum Life
56+
+50% to Fire Resistance]])
57+
local entries = bs.addModEntries(lifelessDiamond,
58+
{ { list = lifelessDiamond.explicitModLines, type = "explicit" } })
59+
assert.equal(2, #entries)
60+
assert.equal(2, #entries[1].formattedLines)
61+
assert.equal(-50, entries[1].value)
62+
end)
63+
64+
it("is not case-sensitive", function ()
65+
local funnyItem = new("Item", [[
66+
Test Subject
67+
Diamond
68+
Implicits: 1
69+
+50 tO MaxIMum lifE]])
70+
71+
local entries = bs.addModEntries(funnyItem, {{list = funnyItem.implicitModLines, type = "implicit"}})
72+
assert.equal(1, #entries)
73+
end)
74+
75+
it("does not combine implicit and explicit mods", function()
76+
local lifelessDiamond = new("Item", [[
77+
Test Subject
78+
Diamond
79+
Implicits: 1
80+
-100 to Maximum Life
81+
+50 to Maximum Life]])
82+
local entries = bs.addModEntries(lifelessDiamond,
83+
{ { list = lifelessDiamond.implicitModLines, type = "implicit" }, { list = lifelessDiamond.explicitModLines, type = "explicit" } })
84+
assert.equal(2, #entries)
85+
assert.equal(-100, entries[1].value)
86+
assert.equal(50, entries[2].value)
87+
end)
88+
end)
89+
end)

0 commit comments

Comments
 (0)