Skip to content

Commit 691a769

Browse files
author
LocalIdentity
committed
Merge branch 'dev'
2 parents 470dc53 + 99d2714 commit 691a769

85 files changed

Lines changed: 36311 additions & 32395 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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Changelog
22

3+
## [v0.19.0](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/tree/v0.19.0) (2026/06/05)
4+
5+
[Full Changelog](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/compare/v0.18.0...v0.19.0)
6+
7+
8+
## What's Changed
9+
### New to Path of Building
10+
- Update passive tree with 0.5.1 changes [\#2096](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2096) ([trompetin17](https://github.com/justjuangui))
11+
- Update skills with 0.5.1 changes by [LocalIdentity](https://github.com/LocalIdentity)
12+
- Add Facebreaker unique gloves support [\#2097](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2097) ([Anakior](https://github.com/Anakior), [LocalIdentity](https://github.com/LocalIdentity))
13+
- Add support for various Parry modifiers (Debuff Magnitude, Range, Duration) [\#2080](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2080) ([majochem](https://github.com/majochem))
14+
- Implement parsing for remnant effect mods [\#2093](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2093) ([moxaj](https://github.com/moxaj))
15+
### Fixed Crashes
16+
- Fix crash when importing a character that uses Facebreaker gloves [\#2098](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2098) ([LocalIdentity](https://github.com/LocalIdentity))
17+
- Fix crash when clicking on empty rune list [\#2091](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2091) ([vaisest](https://github.com/vaisest))
18+
### Fixed Calculations
19+
- Fix Deflect chance not being capped at 95% [\#2100](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2100) ([LocalIdentity](https://github.com/LocalIdentity))
20+
- Fix Hollow Form support effects being switched [\#2099](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2099) ([LocalIdentity](https://github.com/LocalIdentity))
21+
- Fix Virtuous Barrier not factoring the base 3 of each type of mote [\#2103](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2103) ([LocalIdentity](https://github.com/LocalIdentity))
22+
### User Interface
23+
- Fix hidden Oracle nodes being included in power report and missing connector lines [\#2090](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/2090) ([MrHB212](https://github.com/MrHB212))
24+
25+
326
## [v0.18.0](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/tree/v0.18.0) (2026/06/04)
427

528
[Full Changelog](https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/compare/v0.17.1...v0.18.0)

changelog.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
VERSION[0.19.0][2026/06/05]
2+
3+
--- New to Path of Building ---
4+
* Update passive tree with 0.5.1 changes (trompetin17)
5+
* Update skills with 0.5.1 changes (LocalIdentity)
6+
* Add Facebreaker unique gloves support (Anakior, LocalIdentity)
7+
* Add support for various Parry modifiers (Debuff Magnitude, Range, Duration) (majochem)
8+
* Implement parsing for remnant effect mods (moxaj)
9+
10+
--- Fixed Crashes ---
11+
* Fix crash when importing a character that uses Facebreaker gloves (LocalIdentity)
12+
* Fix crash when clicking on empty rune list (vaisest)
13+
14+
--- Fixed Calculations ---
15+
* Fix Deflect chance not being capped at 95% (LocalIdentity)
16+
* Fix Hollow Form support effects being switched (LocalIdentity)
17+
* Fix Virtuous Barrier not factoring the base 3 of each type of mote (LocalIdentity)
18+
19+
--- User Interface ---
20+
* Fix hidden Oracle nodes being included in power report and missing connector lines (MrHB212)
21+
122
VERSION[0.18.0][2026/06/04]
223

324
--- New to Path of Building ---

manifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version='1.0' encoding='UTF-8'?>
22
<PoBVersion>
3-
<Version number="0.18.0" />
3+
<Version number="0.19.0" />
44
<Source part="default" url="https://raw.githubusercontent.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/{branch}/" />
55
<Source part="runtime" platform="win32" url="https://raw.githubusercontent.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/{branch}/runtime/" />
66
<Source part="program" url="https://raw.githubusercontent.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/{branch}/src/" />
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
-- Tests for Facebreaker-style gloves: empty-handed gloves that grant their own base
2+
-- weapon damage and let you attack as though using a One Hand Mace.
3+
describe("TestFacebreaker", function()
4+
before_each(function()
5+
newBuild()
6+
end)
7+
8+
teardown(function()
9+
-- newBuild() takes care of resetting everything in setup()
10+
end)
11+
12+
-- Physical variant (Facebreaker)
13+
local function equipFacebreaker()
14+
build.itemsTab:CreateDisplayItemFromRaw([[
15+
New Item
16+
Stocky Mitts
17+
Has 8 to 12 Physical damage, +3 to +4 per Boss's Face Broken
18+
Can Attack as though using a One Handed Mace while both of your hand slots are empty
19+
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
20+
]])
21+
build.itemsTab:AddDisplayItem()
22+
runCallback("OnFrame")
23+
end
24+
25+
it("grants its base Physical damage to Unarmed attacks", function()
26+
equipFacebreaker()
27+
local weaponData1 = build.calcsTab.mainEnv.player.weaponData1
28+
assert.are.equals(8, weaponData1.FacebreakerPhysicalMin)
29+
assert.are.equals(12, weaponData1.FacebreakerPhysicalMax)
30+
end)
31+
32+
it("lets you attack as though using a One Hand Mace while unarmed", function()
33+
equipFacebreaker()
34+
local weaponData1 = build.calcsTab.mainEnv.player.weaponData1
35+
assert.is_true(weaponData1.asThoughUsing ~= nil and weaponData1.asThoughUsing["One Hand Mace"] == true)
36+
end)
37+
38+
it("scales its base damage per Boss's Face Broken", function()
39+
equipFacebreaker()
40+
build.configTab.input.configBossFaceBroken = 10
41+
build.configTab:BuildModList()
42+
runCallback("OnFrame")
43+
local weaponData1 = build.calcsTab.mainEnv.player.weaponData1
44+
-- 8 base + 3 per face broken * 10, 12 base + 4 per face broken * 10
45+
assert.are.equals(8 + 3 * 10, weaponData1.FacebreakerPhysicalMin)
46+
assert.are.equals(12 + 4 * 10, weaponData1.FacebreakerPhysicalMax)
47+
end)
48+
49+
it("matches the in-game resolved damage at 60 Boss's Faces Broken (188-252)", function()
50+
-- Real in-game Facebreaker shows "Physical Damage: 188-252" at 60 Boss's Faces Broken
51+
equipFacebreaker()
52+
build.configTab.input.configBossFaceBroken = 60
53+
build.configTab:BuildModList()
54+
runCallback("OnFrame")
55+
build.skillsTab:PasteSocketGroup("Boneshatter 1/0 1")
56+
runCallback("OnFrame")
57+
build.calcsTab:BuildOutput()
58+
runCallback("OnFrame")
59+
local mainHand = build.calcsTab.mainOutput.MainHand
60+
assert.are.equals(188, mainHand.PhysicalMinBase)
61+
assert.are.equals(252, mainHand.PhysicalMaxBase)
62+
end)
63+
64+
it("makes One Hand Mace skills usable unarmed and applies 'more Unarmed Damage per Strength' to them", function()
65+
build.itemsTab:CreateDisplayItemFromRaw([[
66+
New Item
67+
Stocky Mitts
68+
Has 8 to 12 Physical damage, +3 to +4 per Boss's Face Broken
69+
1% more Unarmed Damage per 5 Strength
70+
Can Attack as though using a One Handed Mace while both of your hand slots are empty
71+
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
72+
]])
73+
build.itemsTab:AddDisplayItem()
74+
-- strip enemy Armour so the small base damage still resolves to a positive hit
75+
build.configTab.input.customMods = "Nearby Enemies have 100% less Armour"
76+
build.configTab:BuildModList()
77+
runCallback("OnFrame")
78+
-- Boneshatter requires a One/Two Hand Mace (no "None"): only usable unarmed thanks to Facebreaker
79+
build.skillsTab:PasteSocketGroup("Boneshatter 1/0 1")
80+
runCallback("OnFrame")
81+
build.calcsTab:BuildOutput()
82+
runCallback("OnFrame")
83+
local mainSkill = build.calcsTab.mainEnv.player.mainSkill
84+
assert.is_truthy(mainSkill)
85+
-- the Mace-only skill resolves to a real (unarmed) attack producing a positive hit
86+
assert.are.equals("Boneshatter", mainSkill.activeEffect.grantedEffect.name)
87+
assert.is_truthy(build.calcsTab.mainOutput.MainHand)
88+
assert.is_true(build.calcsTab.mainOutput.MainHand.AverageHit > 0)
89+
local modDB = build.calcsTab.mainEnv.player.modDB
90+
-- 'more Unarmed Damage per 5 Strength' applies to unarmed Hits (which is what Facebreaker mace attacks are)...
91+
assert.is_true(modDB:Sum("MORE", { flags = ModFlag.Unarmed + ModFlag.Hit }, "Damage") > 0)
92+
-- ...but not to actual weapon (e.g. Sword) Hits
93+
assert.are.equals(0, modDB:Sum("MORE", { flags = ModFlag.Sword + ModFlag.Hit }, "Damage"))
94+
end)
95+
96+
it("auto-imports the Boss's Faces Broken count from character quest stats", function()
97+
build.importTab:ImportQuestRewardConfig({ "57 [BrokenFace|Broken Boss Faces]" })
98+
assert.is_nil(build.configTab.input.configBossFaceBroken)
99+
assert.are.equals(57, build.configTab.placeholder.configBossFaceBroken)
100+
end)
101+
102+
it("supports the Fire damage variant", function()
103+
build.itemsTab:CreateDisplayItemFromRaw([[
104+
New Item
105+
Stocky Mitts
106+
Has 9 to 14 Fire damage, +3 to +5 per Boss's Face Broken
107+
Can Attack as though using a One Handed Mace while both of your hand slots are empty
108+
Unarmed Attacks that would use an Equipped One Hand Mace's damage use this Item's damage
109+
]])
110+
build.itemsTab:AddDisplayItem()
111+
runCallback("OnFrame")
112+
local weaponData1 = build.calcsTab.mainEnv.player.weaponData1
113+
assert.are.equals(9, weaponData1.FacebreakerFireMin)
114+
assert.are.equals(14, weaponData1.FacebreakerFireMax)
115+
end)
116+
117+
it("uses Facebreaker item damage instead of Hollow Palm added Physical damage for mace-compatible staff skills", function()
118+
equipFacebreaker()
119+
build.configTab.input.customMods = "Hollow Palm Technique"
120+
build.configTab:BuildModList()
121+
runCallback("OnFrame")
122+
build.skillsTab:PasteSocketGroup("Rain of Blades 1/0 1")
123+
runCallback("OnFrame")
124+
local skillModList = build.calcsTab.mainEnv.player.mainSkill.skillModList
125+
assert.are.equals(0, skillModList:Sum("BASE", { flags = ModFlag.Attack }, "PhysicalMin"))
126+
assert.are.equals(0, skillModList:Sum("BASE", { flags = ModFlag.Attack }, "PhysicalMax"))
127+
end)
128+
129+
it("keeps Hollow Palm added Physical damage for quarterstaff-only skills", function()
130+
equipFacebreaker()
131+
build.configTab.input.customMods = "Hollow Palm Technique"
132+
build.configTab:BuildModList()
133+
runCallback("OnFrame")
134+
build.skillsTab:PasteSocketGroup("Quarterstaff Strike 1/0 1")
135+
runCallback("OnFrame")
136+
local skillModList = build.calcsTab.mainEnv.player.mainSkill.skillModList
137+
assert.is_true(skillModList:Sum("BASE", { flags = ModFlag.Attack }, "PhysicalMin") > 0)
138+
assert.is_true(skillModList:Sum("BASE", { flags = ModFlag.Attack }, "PhysicalMax") > 0)
139+
end)
140+
end)

spec/System/TestImportTab_spec.lua

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,29 @@ describe("ImportTab quest reward import", function()
159159
assert.are.equals(tribal.Options[1], input[tribalVar])
160160
assert.are.equals(seven.Options[3], input[sevenVar])
161161
end)
162+
163+
it("imports Boss's Faces Broken without affecting quest reward matching", function()
164+
local sevenVar, seven = findQuest("Seven Pillars")
165+
local input = importStats({
166+
"+20 to maximum Life",
167+
"5% increased maximum Life",
168+
"5% increased maximum Mana",
169+
"+100 to [Spirit|Spirit]",
170+
"30% increased [Charm] Effect Duration",
171+
"+5% to [Resistances|Fire Resistance]",
172+
"+15% to [Resistances|Cold Resistance]",
173+
"+15% to [Resistances|Lightning Resistance]",
174+
"30% increased Life Recovery from [Flask|Flasks]",
175+
"45% increased Global [Armour], [Evasion] and [EnergyShield|Energy Shield]",
176+
"25% increased [StunThreshold|Stun Threshold]",
177+
"+1 [Charm] Slot",
178+
"58 [BrokenFace|Broken Boss Faces]",
179+
})
180+
181+
assert.is_nil(input.configBossFaceBroken)
182+
assert.are.equals(58, build.configTab.placeholder.configBossFaceBroken)
183+
assert.are.equals(seven.Options[3], input[sevenVar])
184+
runCallback("OnFrame")
185+
assert.is_false(build.configTab.varControls.configBossFaceBroken.shown())
186+
end)
162187
end)

spec/System/TestSkills_spec.lua

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,4 +1054,56 @@ describe("TestSkills", function()
10541054
local expectedAverageEffect = 1 + (build.calcsTab.calcsOutput.MaxAncestralEmpowermentCombinedDamageEffect - 1) * build.calcsTab.calcsOutput.AncestralEmpowermentCombinedUptimeRatio / 100
10551055
assert.are.equals(round(expectedAverageEffect, 4), round(build.calcsTab.calcsOutput.AvgAncestralEmpowermentCombinedDamageEffect, 4))
10561056
end)
1057+
1058+
it("calculates effects of parry debuff correctly", function()
1059+
build.itemsTab:CreateDisplayItemFromRaw([[
1060+
Generic EV Shield
1061+
Desert Buckler
1062+
Evasion: 230
1063+
Quality: 20
1064+
LevelReq: 80
1065+
]])
1066+
build.itemsTab:AddDisplayItem()
1067+
runCallback("OnFrame")
1068+
build.skillsTab:PasteSocketGroup("Parry 20/0 1")
1069+
runCallback("OnFrame")
1070+
build.configTab:BuildModList()
1071+
runCallback("OnFrame")
1072+
build.calcsTab:BuildOutput()
1073+
runCallback("OnFrame")
1074+
1075+
-- Test general debuff
1076+
local preParryDmg = build.calcsTab.mainOutput.AverageDamage
1077+
build.configTab.configSets[1].input.parryActive = true
1078+
build.configTab:BuildModList()
1079+
build.calcsTab:BuildOutput()
1080+
runCallback("OnFrame")
1081+
local postParryDmg = build.calcsTab.mainOutput.AverageDamage
1082+
assert.True(postParryDmg > preParryDmg, "Damage should be higher with Parry active")
1083+
1084+
-- Test Magnitude
1085+
build.configTab.input.customMods = "50% increased parried debuff magnitude"
1086+
build.configTab:BuildModList()
1087+
runCallback("OnFrame")
1088+
build.calcsTab:BuildOutput()
1089+
runCallback("OnFrame")
1090+
local incMagnitudeDmg = build.calcsTab.mainOutput.AverageDamage
1091+
assert.True(incMagnitudeDmg > postParryDmg, "Damage should be higher with increased parried debuff magnitude")
1092+
1093+
-- Test effect on spells
1094+
build.skillsTab:PasteSocketGroup("Bone Cage 20/0 1")
1095+
runCallback("OnFrame")
1096+
selectActiveSkillById(build.skillsTab.socketGroupList[#build.skillsTab.socketGroupList], "BoneCagePlayer")
1097+
runCallback("OnFrame")
1098+
build.calcsTab:BuildOutput()
1099+
runCallback("OnFrame")
1100+
local withParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
1101+
build.configTab.configSets[1].input.parryActive = false
1102+
build.configTab:BuildModList()
1103+
runCallback("OnFrame")
1104+
build.calcsTab:BuildOutput()
1105+
runCallback("OnFrame")
1106+
local noParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
1107+
assert.equals(withParrySpellDmg, noParrySpellDmg, "Parry should not affect spell damage")
1108+
end)
10571109
end)

src/Classes/CalcsTab.lua

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,22 @@ function CalcsTabClass:PowerBuilder()
546546
for nodeId, node in pairs(self.build.spec.nodes) do
547547
wipeTable(node.power)
548548
if node.modKey ~= "" and not self.mainEnv.grantedPassives[nodeId] then
549-
distanceMap[node.pathDist or 1000] = distanceMap[node.pathDist or 1000] or { }
550-
distanceMap[node.pathDist or 1000][nodeId] = node
551-
if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then
552-
total = total + 1
549+
local hiddenByLockedAscendancyNode = false
550+
if node.unlockConstraint then
551+
for _, unlockNodeId in ipairs(node.unlockConstraint.nodes) do
552+
local unlockNode = self.build.spec.nodes[unlockNodeId]
553+
if unlockNode and unlockNode.ascendancyName and not unlockNode.alloc then
554+
hiddenByLockedAscendancyNode = true
555+
break
556+
end
557+
end
558+
end
559+
if not hiddenByLockedAscendancyNode then
560+
distanceMap[node.pathDist or 1000] = distanceMap[node.pathDist or 1000] or { }
561+
distanceMap[node.pathDist or 1000][nodeId] = node
562+
if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then
563+
total = total + 1
564+
end
553565
end
554566
end
555567
end

src/Classes/ImportTab.lua

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -656,9 +656,17 @@ function ImportTabClass:ImportQuestRewardConfig(questStats)
656656
end
657657

658658
local statTotals = {}
659+
local updated = false
659660
for _, stat in ipairs(questStats) do
660661
local key, value = statKey(stat)
661-
statTotals[key] = (statTotals[key] or 0) + value
662+
if key == "# broken boss faces" then
663+
if configTab.placeholder.configBossFaceBroken ~= value then
664+
configTab.placeholder.configBossFaceBroken = value
665+
updated = true
666+
end
667+
else
668+
statTotals[key] = (statTotals[key] or 0) + value
669+
end
662670
end
663671

664672
-- Stats shared by 3+ quests can't be split greedily (two +30 Spirit quests make 40/70 ambiguous),
@@ -703,7 +711,6 @@ function ImportTabClass:ImportQuestRewardConfig(questStats)
703711
return true
704712
end
705713

706-
local updated = false
707714
for _, quest in ipairs(data.questRewards) do
708715
if quest.useConfig == true then
709716
local var = "quest" .. quest.Description .. quest.Area .. quest.Info
@@ -956,10 +963,10 @@ function ImportTabClass:ImportItemsAndSkills(charData)
956963

957964
-- This could be done better with the character melee skills data at some point.
958965
if typeLine:match("Mace Strike") then
959-
local weapon1Sel = self.build.itemsTab.activeItemSet["Weapon 1"].selItemId or 0
960-
local weapon2Sel = self.build.itemsTab.activeItemSet["Weapon 2"].selItemId or 0
966+
local weapon1Sel = self.build.itemsTab.activeItemSet["Weapon 1"] and self.build.itemsTab.activeItemSet["Weapon 1"].selItemId or 0
967+
local weapon2Sel = self.build.itemsTab.activeItemSet["Weapon 2"] and self.build.itemsTab.activeItemSet["Weapon 2"].selItemId or 0
961968
if weapon2Sel == 0 then
962-
if self.build.itemsTab.items[weapon1Sel].base.type == "One Hand Mace" then
969+
if weapon1Sel == 0 or self.build.itemsTab.items[weapon1Sel].base.type == "One Hand Mace" then -- Facebreaker uses single handed mace strike
963970
gemId = "Metadata/Items/Gems/SkillGemPlayerDefault1HMace"
964971
elseif self.build.itemsTab.items[weapon1Sel].base.type == "Two Hand Mace" then
965972
gemId = "Metadata/Items/Gems/SkillGemPlayerDefault2HMace"

0 commit comments

Comments
 (0)