Skip to content

Commit df7f76f

Browse files
jay9297NightbladeLocalIdentityLocalIdentityPeechey
authored
Fix/hollow palm technique (#58)
* Chore: Bump action version numbers (PathOfBuildingCommunity#1993) * Add support for Legacy Grand Spectrum mods (PathOfBuildingCommunity#1994) Some old mods don't require the socketed in text Co-authored-by: LocalIdentity <localidentity2@gmail.com> * Add support for Ancestral Empowerment, Ancestral Call, Crescendo II and III, Infernal Cry, and "Ancestrally Boosted Attacks" passive nodes (PathOfBuildingCommunity#1839) * add support for Ancestral Call, Crescendo II and III, ancestral boost tree nodes * fix ancestral call inc dmg from tree * big ole refactor so we stop duplicating code one commit so I can revert if it goes boom * added test update CalcSection from "Exerted Warcries" to "Ancestral Boosts" * add data map for gemName given a modSource * boosted attacks configOption * add support for Ancestral Empowerment, including combining Ancestral Empowerment with Fist of War fix Fist of War/ancestrally boosted slams more AOE to inc AOE add test for Combined logic for Ancestral Empowerment and Fist of War * comment spelling * better label * fix avg aoe from fist of war * forgot AOE portion for Crescendo III * add support for Infernal Cry's empowered buff * refactor away from specific baseMod for FistOfWar fix Ancestral Empowerment not getting the double from fist of war III * empowered condition for all Ancestrally Boosted * ancestralBoostEffect * multiplier instead * pr comments also refactored Crescendo III to use the new logic instead of an extra isolated block of logic by introducing a way to conditionally max uptime for Boosted or Empowered attacks * Fix issues Move the Infernal Cry buff to calc offence so it doesn't rely on stale calc output Fix Ancestral Empowerment uptime calculation Move gain table so Infernal Cry still works * Fix spelling --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com> * Add support for 0.5 JSON skill tree (PathOfBuildingCommunity#1984) * Passive tree parser from ggg web data * enhanced escapeGGGString * Fix ascendancy unlock constraint check in node search parameters * Refactor DrawQuadAndRotate to handle different data lengths for vertex calculations * Add node overlay for Blighted Notable Frames in passive tree * Fix typos and improve comments in passive tree script * Fix typos in ascendancy handling and update inner radius calculations in passive tree script * Refactor passive tree node replacement logic and add switchable options for Druid nodes * Add support for alias notable maps and enhance jewel socket handling - Enhanced passivetree_ggg.lua to support aliasNotableMap for jewel sockets. * Rename aliasNotableMap to aliasPassiveSocket for clarity in passive tree data * Enhance jewel socket handling by adding noRadius property and updating related logic in passive tree * Enable Abyssal Lich parser * Fix tests Export ModCache Fix crash with class Ids Fix unarmed data for Ids * Add legacy class ID remap Old builds from 0.1 to 0.3 used the wrong id for the character class and opening them now would convert them to the wrong class. This maps the old builds so they now open correctly * Formatting + typos * AliasPassiveSocket fix Was using the wrong name from a previous commit * Export fixes Uses the tree data folder to look for the ggg assets and json files Fixes the tree file changing each time on export due to pairs usage * Use copy file command to speed up export Now runs in 0.1s instead of 8s --------- Co-authored-by: justjuangui <servicios@juacarvajal.com> Co-authored-by: LocalIdentity <localidentity2@gmail.com> * Remove aliasPassiveSocket temp code * Fix Hollow Palm Technique parsing and add Spectral Ward support - Make 'rating' optional in evasion suffix patterns (r?a?t?i?n?g?) - Remove dead-code keystone pattern with wrong parameter order - Add bounds check for hollowPalmAddedPhys table (levels 1-40) - Add descriptive line patterns for Hollow Palm Technique node - Add Spectral Ward pattern for 'per X item evasion on equipped body armour' - Update ModCache entries for all affected patterns --------- Co-authored-by: Nighty <Nightblade@users.noreply.github.com> Co-authored-by: LocalIdentity <31035929+LocalIdentity@users.noreply.github.com> Co-authored-by: LocalIdentity <localidentity2@gmail.com> Co-authored-by: Peechey <92683202+Peechey@users.noreply.github.com> Co-authored-by: Juangui <80857657+justjuangui@users.noreply.github.com> Co-authored-by: justjuangui <servicios@juacarvajal.com> Co-authored-by: jay9297 <jay9297@users.noreply.github.com>
1 parent 2c0242c commit df7f76f

130 files changed

Lines changed: 129048 additions & 1207 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.

.github/workflows/spellcheck.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
# CONFIG_URL: https://raw.githubusercontent.com/Nightblade/pob-dict/<commit-sha>
2828
steps:
2929
- name: Checkout
30-
uses: actions/checkout@v4 # v5 does not exist — was causing hard failure
30+
uses: actions/checkout@v6
3131
with:
3232
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
3333

@@ -53,7 +53,7 @@ jobs:
5353
"${{ env.CONFIG_URL }}/contribs-dict.txt"
5454
5555
- name: Run cspell
56-
uses: streetsidesoftware/cspell-action@v7
56+
uses: streetsidesoftware/cspell-action@v8
5757
with:
5858
files: '**'
5959
config: ".cspell-config/cspell.json"

spec/System/TestPassiveSpec_spec.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,27 @@ describe("TestPassiveSpec", function()
4444
assert.is_true(ok, err)
4545
end)
4646

47+
it("remaps legacy class ids only for trees before 0.4", function()
48+
local function loadClass(treeVersion, classId)
49+
local spec = new("PassiveSpec", build, latestTreeVersion)
50+
spec.treeVersion = treeVersion
51+
spec:Load({
52+
attrib = {
53+
title = "Legacy Class Test",
54+
classId = tostring(classId),
55+
ascendClassId = "0",
56+
nodes = "",
57+
}
58+
}, "legacy_class.xml")
59+
return spec.curClassName
60+
end
61+
62+
assert.are.equals("Witch", loadClass("0_1", 3))
63+
assert.are.equals("Huntress", loadClass("0_2", 1))
64+
assert.are.equals("Monk", loadClass("0_3", 6))
65+
assert.are.equals("Witch", loadClass("0_4", 1))
66+
end)
67+
4768
local function allocNode(spec, nodeId, allocMode)
4869
local node = spec.nodes[nodeId]
4970
spec.allocMode = allocMode

spec/System/TestSkills_spec.lua

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,4 +877,61 @@ describe("TestSkills", function()
877877
-- validate Flame Wall buff appears even when the Wall/default skillPart is active
878878
assert.are.equals("Flame Wall", build.calcsTab.calcsOutput.BuffList)
879879
end)
880+
881+
it("Test Ancestral Call - Ancestral Boost calcs", function()
882+
build.itemsTab:CreateDisplayItemFromRaw([[
883+
New Item
884+
Fanatic Greathammer
885+
Quality: 0
886+
]])
887+
build.itemsTab:AddDisplayItem()
888+
runCallback("OnFrame")
889+
890+
build.skillsTab:PasteSocketGroup("Boneshatter 20/0 1\nAncestral Call I 1/0 1")
891+
runCallback("OnFrame")
892+
893+
assert.True(build.calcsTab.calcsOutput.AvgAncestralCallDamageEffect ~= nil)
894+
assert.True(build.calcsTab.calcsOutput.AncestralCallUptimeRatio ~= nil)
895+
assert.are.equal(3, build.calcsTab.calcsOutput.StrikeTargets)
896+
end)
897+
898+
it("Test Combined Ancestral Boosts - Ancestral Empowerment and Fist of War", function()
899+
build.itemsTab:CreateDisplayItemFromRaw([[
900+
New Item
901+
Fanatic Greathammer
902+
Quality: 0
903+
]])
904+
build.itemsTab:AddDisplayItem()
905+
runCallback("OnFrame")
906+
build.skillsTab:PasteSocketGroup("Leap Slam 20/0 1\nFist of War I 1/0 1")
907+
runCallback("OnFrame")
908+
build.configTab.input.customMods = "every second slam skill you use yourself is ancestrally boosted"
909+
build.configTab:BuildModList()
910+
runCallback("OnFrame")
911+
local fistOfWarOneMaxDmgEffect = build.calcsTab.calcsOutput.MaxAncestralEmpowermentCombinedDamageEffect
912+
913+
-- test that we are using the calcCombinedAncestralBoost function and the calcSection triggers are correct
914+
assert.True(build.calcsTab.calcsOutput.AncestralEmpowermentCombinedUptimeRatio ~= nil)
915+
assert.True(build.calcsTab.calcsOutput.AncestralEmpowermentUptimeRatio == nil)
916+
assert.True(build.calcsTab.calcsOutput.FistOfWarUptimeRatio == nil)
917+
918+
newBuild()
919+
build.itemsTab:CreateDisplayItemFromRaw([[
920+
New Item
921+
Fanatic Greathammer
922+
Quality: 0
923+
]])
924+
build.itemsTab:AddDisplayItem()
925+
runCallback("OnFrame")
926+
build.skillsTab:PasteSocketGroup("Leap Slam 20/0 1\nFist of War III 1/0 1")
927+
runCallback("OnFrame")
928+
build.configTab.input.customMods = "every second slam skill you use yourself is ancestrally boosted"
929+
build.configTab:BuildModList()
930+
runCallback("OnFrame")
931+
932+
-- test doubled effects of Fist of War III with Ancestral Empowerment
933+
assert.True(fistOfWarOneMaxDmgEffect < build.calcsTab.calcsOutput.MaxAncestralEmpowermentCombinedDamageEffect)
934+
local expectedAverageEffect = 1 + (build.calcsTab.calcsOutput.MaxAncestralEmpowermentCombinedDamageEffect - 1) * build.calcsTab.calcsOutput.AncestralEmpowermentCombinedUptimeRatio / 100
935+
assert.are.equals(round(expectedAverageEffect, 4), round(build.calcsTab.calcsOutput.AvgAncestralEmpowermentCombinedDamageEffect, 4))
936+
end)
880937
end)

src/Classes/PassiveSpec.lua

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ local b_rshift = bit.rshift
1616
local band = AND64 -- bit.band
1717
local bor = OR64 -- bit.bor
1818

19+
local legacyClassIdMap = {
20+
["0_1"] = { [0] = 2, [1] = 6, [2] = 9, [3] = 1, [4] = 7, [5] = 10 },
21+
["0_2"] = { [0] = 2, [1] = 8, [2] = 6, [3] = 9, [4] = 1, [5] = 7, [6] = 10 },
22+
["0_3"] = { [0] = 2, [1] = 8, [2] = 6, [3] = 9, [4] = 1, [5] = 7, [6] = 10 },
23+
}
24+
1925
local PassiveSpecClass = newClass("PassiveSpec", "UndoHandler", function(self, build, treeVersion, convert)
2026
self.UndoHandler()
2127

@@ -24,7 +30,7 @@ local PassiveSpecClass = newClass("PassiveSpec", "UndoHandler", function(self, b
2430
-- Initialise and build all tables
2531
self:Init(treeVersion, convert)
2632

27-
self:SelectClass(0)
33+
self:SelectClass(self.tree.constants.classes.DexClass)
2834
end)
2935

3036
function PassiveSpecClass:Init(treeVersion, convert)
@@ -157,6 +163,8 @@ function PassiveSpecClass:Load(xml, dbFileName)
157163
if self.tree.classIntegerIdMap[classInternalId] then
158164
classId = self.tree.classIntegerIdMap[classInternalId]
159165
end
166+
elseif classId ~= -1 and legacyClassIdMap[self.treeVersion] then
167+
classId = legacyClassIdMap[self.treeVersion][classId] or classId
160168
end
161169
if xml.attrib.ascendancyInternalId then
162170
local ascendancyInternalId = tostring(xml.attrib.ascendancyInternalId)

src/Classes/PassiveTree.lua

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
7878

7979
self.size = m_min(self.max_x - self.min_x, self.max_y - self.min_y) * self.scaleImage * 1.1
8080

81-
for i = 0, 6 do
82-
self.classes[i] = self.classes[i + 1]
83-
self.classes[i + 1] = nil
81+
local classes = { }
82+
for _, class in pairs(self.classes) do
83+
classes[class.integerId] = class
8484
end
85+
self.classes = classes
8586

8687
-- Build maps of class name -> class table
8788
self.classNameMap = { }
@@ -119,6 +120,7 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
119120
self.orbitAnglesByOrbit = self.constants.orbitAnglesByOrbit
120121

121122
ConPrintf("Loading passive tree assets...")
123+
self.assets = self.assets or {}
122124
for name, data in pairs(self.assets) do
123125
self:LoadImage(data[1], data, "MIPMAP")
124126
end
@@ -139,6 +141,25 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
139141
end
140142
end
141143

144+
self.spriteMap = { }
145+
self.spriteCoords = self.spriteCoords or {}
146+
for file, fileInfo in pairs(self.spriteCoords) do
147+
local data = { }
148+
self:LoadImage(file, data, "CLAMP")
149+
for name, coords in pairs(fileInfo) do
150+
self.spriteMap[name] = {
151+
found = data.width > 0,
152+
handle = data.handle,
153+
width = coords.w,
154+
height = coords.h,
155+
[1] = coords.x / data.width,
156+
[2] = coords.y / data.height,
157+
[3] = (coords.x + coords.w) / data.width,
158+
[4] = (coords.y + coords.h) / data.height
159+
}
160+
end
161+
end
162+
142163
for type, data in pairs(self.nodeOverlay) do
143164
local asset = self:GetAssetByName(data.alloc)
144165
local artWidth = asset.width * self.scaleImage
@@ -172,7 +193,7 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
172193
node.o = node.orbit
173194
node.oidx = node.orbitIndex
174195
node.dn = node.name
175-
node.sd = node.stats
196+
node.sd = node.stats or {}
176197

177198
node.__index = node
178199
node.linkedId = { }
@@ -304,7 +325,7 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
304325
for nodeId, socket in pairs(self.sockets) do
305326
if socket.name == "Charm Socket" then
306327
socket.charmSocket = true
307-
elseif not socket.containJewelSocket then
328+
elseif not socket.containJewelSocket and not socket.noRadius then
308329
socket.nodesInRadius = { }
309330
socket.attributesInRadius = { }
310331
for radiusIndex, _ in ipairs(data.jewelRadius) do
@@ -741,8 +762,14 @@ function PassiveTreeClass:CalcOrbitAngles(nodesInOrbit)
741762
return orbitAngles
742763
end
743764

765+
local alreadyAlertMissingAssetName = {}
744766
function PassiveTreeClass:GetAssetByName(name, type)
745-
return self.ddsMap[name] or self.assets[name]
767+
local assetData = self.ddsMap[name] or self.assets[name] or self.spriteMap[name]
768+
if not assetData and not alreadyAlertMissingAssetName[name] then
769+
alreadyAlertMissingAssetName[name] = true
770+
ConPrintf("missing asset with name " .. name)
771+
end
772+
return assetData
746773
end
747774

748775
function PassiveTreeClass:GetNodeTargetSize(node)

src/Classes/PassiveTreeView.lua

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
12621262
SetDrawLayer(nil, 25)
12631263
for nodeId in pairs(tree.sockets) do
12641264
local node = spec.nodes[nodeId]
1265-
if node and node.name ~= "Charm Socket" and node.containJewelSocket ~= true and (not node.expansionJewel or node.expansionJewel.size == 2) then
1265+
if node and node.name ~= "Charm Socket" and node.containJewelSocket ~= true and (not node.expansionJewel or node.expansionJewel.size == 2) and (not node.noRadius) then
12661266
local scrX, scrY = treeToScreen(node.x, node.y)
12671267
local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(nodeId)
12681268
local compareNode = self.compareSpec and self.compareSpec.nodes[nodeId] or nil
@@ -1391,7 +1391,20 @@ function PassiveTreeViewClass:DrawQuadAndRotate(data, xTree, yTree, angleRad, tr
13911391
vertActive[3], vertActive[4] = xActive + widthActive, yActive - heightActive
13921392
vertActive[5], vertActive[6] = xActive + widthActive, yActive + heightActive
13931393
vertActive[7], vertActive[8] = xActive - widthActive, yActive + heightActive
1394-
vertActive[9] = data[1] -- s1
1394+
1395+
local lengthData = #data
1396+
if lengthData == 1 then
1397+
vertActive[9] = data[1] -- s1 (stack)
1398+
elseif lengthData == 4 then
1399+
vertActive[9], vertActive[10] = data[1], data[2] -- top-left
1400+
vertActive[11], vertActive[12] = data[3], data[2] -- top-right
1401+
vertActive[13], vertActive[14] = data[3], data[4] -- bottom-right
1402+
vertActive[15], vertActive[16] = data[1], data[4] -- bottom-left
1403+
else
1404+
for iData, vData in ipairs(data) do
1405+
vertActive[9 + (iData - 1)] = vData
1406+
end
1407+
end
13951408

13961409
-- rotate the quad
13971410
vertActive[1], vertActive[2] = treeToScreen(rotate(vertActive[1], vertActive[2], xActive, yActive, angleRad))
@@ -1499,7 +1512,7 @@ function PassiveTreeViewClass:DoesNodeMatchSearchParams(node)
14991512
end
15001513

15011514
-- Check unlock ascendancy
1502-
if node.unlockConstraint then
1515+
if node.unlockConstraint and node.unlockConstraint.ascendancy then
15031516
err, needMatches = PCall(search, node.unlockConstraint.ascendancy:lower(), needMatches)
15041517
if err then return false end
15051518
if #needMatches == 0 then

0 commit comments

Comments
 (0)