Skip to content

Commit 8d16827

Browse files
Wires77LocalIdentityPeechey
authored
Tattoo implementation (#6396)
* Initial tattoo implementation * Tattoo spec update * Tattoo export * Update spec * Fix spec * New dat files * More fixes * Add artwork, restrict Makanga tattoos Signed-off-by: Wires77 <Wires77@users.noreply.github.com> * Fix bug with saving/loading, cleanup code * Fixed import bug and matched format for save files * Fixing logic issues preventing proper tattoos from appearing * Fixed bug with linked node count and supported new tattoo jewel * Temp fix for effectSprites * Can't tattoo changed nodes * Fix tattoo interaction with Timeless jewels * Update tree, fix more bugs * Add dropdown search * Add help text to tattoo dropdown * Fix stat description error * Dynamically set popup width * Made popup width fully dynamic * Truncate mod lines in tattoo popup * text wrap in popup * Dynamic word wrapping, ignoring GGG wrapping * Revert tree merge --------- Signed-off-by: Wires77 <Wires77@users.noreply.github.com> Co-authored-by: LocalIdentity <localidentity2@gmail.com> Co-authored-by: Peechey <92683202+Peechey@users.noreply.github.com>
1 parent 11dc60e commit 8d16827

File tree

14 files changed

+14589
-11942
lines changed

14 files changed

+14589
-11942
lines changed

src/Classes/ImportTab.lua

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,18 @@ function ImportTabClass:ImportPassiveTreeAndJewels(json, charData)
568568
end
569569
end
570570

571+
if charPassiveData.skill_overrides then
572+
for nodeId, override in pairs(charPassiveData.skill_overrides) do
573+
override.id = nodeId
574+
local modCount = 0
575+
for _, statLine in ipairs(override.stats) do
576+
self.build.spec:NodeAdditionOrReplacementFromString(override, statLine, modCount == 0)
577+
modCount = modCount + 1
578+
end
579+
override.dn = override.name
580+
end
581+
end
582+
571583
if errMsg then
572584
self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later."
573585
return
@@ -602,7 +614,7 @@ function ImportTabClass:ImportPassiveTreeAndJewels(json, charData)
602614
end
603615
end
604616
end
605-
self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, charPassiveData.hashes, charPassiveData.mastery_effects or {}, latestTreeVersion .. (charData.league:match("Ruthless") and "_ruthless" or ""))
617+
self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, charPassiveData.hashes, charPassiveData.skill_overrides, charPassiveData.mastery_effects or {}, latestTreeVersion .. (charData.league:match("Ruthless") and "_ruthless" or ""))
606618
self.build.spec:AddUndoState()
607619
self.build.characterLevel = charData.level
608620
self.build.characterLevelAutoMode = false

src/Classes/PassiveSpec.lua

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ function PassiveSpecClass:Init(treeVersion, convert)
7474

7575
-- Keys are mastery node IDs, values are mastery effect IDs
7676
self.masterySelections = { }
77+
78+
-- Keys are node IDs, values are the replacement node
79+
self.hashOverrides = { }
7780
end
7881

7982
function PassiveSpecClass:Load(xml, dbFileName)
@@ -130,7 +133,35 @@ function PassiveSpecClass:Load(xml, dbFileName)
130133
masteryEffects[tonumber(mastery)] = tonumber(effect)
131134
end
132135
end
133-
self:ImportFromNodeList(tonumber(xml.attrib.classId), tonumber(xml.attrib.ascendClassId), hashList, masteryEffects)
136+
for _, node in pairs(xml) do
137+
if type(node) == "table" then
138+
if node.elem == "Overrides" then
139+
for _, child in ipairs(node) do
140+
if not child.attrib.nodeId then
141+
launch:ShowErrMsg("^1Error parsing '%s': 'Override' element missing 'nodeId' attribute", dbFileName)
142+
return true
143+
end
144+
145+
local nodeId = tonumber(child.attrib.nodeId)
146+
147+
self.hashOverrides[nodeId] = copyTable(self.nodes[nodeId], true)
148+
self.hashOverrides[nodeId].id = nodeId
149+
self.hashOverrides[nodeId].isTattoo = true
150+
self.hashOverrides[nodeId].icon = child.attrib.icon
151+
self.hashOverrides[nodeId].activeEffectImage = child.attrib.activeEffectImage
152+
self.hashOverrides[nodeId].dn = child.attrib.dn
153+
local modCount = 0
154+
for _, modLine in ipairs(child) do
155+
for line in string.gmatch(modLine .. "\r\n", "([^\r\n\t]*)\r?\n") do
156+
self:NodeAdditionOrReplacementFromString(self.hashOverrides[nodeId], line, modCount == 0)
157+
modCount = modCount + 1
158+
end
159+
end
160+
end
161+
end
162+
end
163+
end
164+
self:ImportFromNodeList(tonumber(xml.attrib.classId), tonumber(xml.attrib.ascendClassId), hashList, self.hashOverrides, masteryEffects)
134165
elseif url then
135166
self:DecodeURL(url)
136167
end
@@ -172,6 +203,20 @@ function PassiveSpecClass:Save(xml)
172203
end
173204
end
174205
t_insert(xml, sockets)
206+
207+
local overrides = {
208+
elem = "Overrides"
209+
}
210+
if self.hashOverrides then
211+
for nodeId, node in pairs(self.hashOverrides) do
212+
local override = { elem = "Override", attrib = { nodeId = tostring(nodeId), icon = tostring(node.icon), activeEffectImage = tostring(node.activeEffectImage), dn = tostring(node.dn) } }
213+
for _, modLine in ipairs(node.sd) do
214+
t_insert(override, modLine)
215+
end
216+
t_insert(overrides, override)
217+
end
218+
end
219+
t_insert(xml, overrides)
175220

176221
end
177222

@@ -180,13 +225,14 @@ function PassiveSpecClass:PostLoad()
180225
end
181226

182227
-- Import passive spec from the provided class IDs and node hash list
183-
function PassiveSpecClass:ImportFromNodeList(classId, ascendClassId, hashList, masteryEffects, treeVersion)
228+
function PassiveSpecClass:ImportFromNodeList(classId, ascendClassId, hashList, hashOverrides, masteryEffects, treeVersion)
184229
if treeVersion and treeVersion ~= self.treeVersion then
185230
self:Init(treeVersion)
186231
self.build.treeTab.showConvert = self.treeVersion ~= latestTreeVersion
187232
end
188233
self:ResetNodes()
189234
self:SelectClass(classId)
235+
self.hashOverrides = hashOverrides
190236
-- move above setting allocNodes so we can compare mastery with selection
191237
wipeTable(self.masterySelections)
192238
for mastery, effect in pairs(masteryEffects) do
@@ -195,6 +241,14 @@ function PassiveSpecClass:ImportFromNodeList(classId, ascendClassId, hashList, m
195241
self.masterySelections[mastery] = effect
196242
end
197243
end
244+
for id, override in pairs(hashOverrides) do
245+
local node = self.nodes[id]
246+
if node then
247+
override.effectSprites = self.tree.spriteMap[override.activeEffectImage]
248+
override.sprites = self.tree.spriteMap[override.icon]
249+
self:ReplaceNode(node, override)
250+
end
251+
end
198252
for _, id in pairs(hashList) do
199253
local node = self.nodes[id]
200254
if node then
@@ -706,6 +760,11 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
706760
end
707761

708762
for id, node in pairs(self.nodes) do
763+
-- If node is tattooed, replace it
764+
if self.hashOverrides[node.id] then
765+
self:ReplaceNode(node, self.hashOverrides[node.id])
766+
end
767+
709768
-- If node is conquered, replace it or add mods
710769
if node.conqueredBy and node.type ~= "Socket" then
711770
local conqueredBy = node.conqueredBy
@@ -863,10 +922,10 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
863922
end
864923
end
865924
elseif conqueredBy.conqueror.type == "karui" then
866-
local str = isValueInArray(attributes, node.dn) and "2" or "4"
925+
local str = (isValueInArray(attributes, node.dn) or node.isTattoo) and "2" or "4"
867926
self:NodeAdditionOrReplacementFromString(node, " \n+" .. str .. " to Strength")
868927
elseif conqueredBy.conqueror.type == "maraketh" then
869-
local dex = isValueInArray(attributes, node.dn) and "2" or "4"
928+
local dex = (isValueInArray(attributes, node.dn) or node.isTattoo) and "2" or "4"
870929
self:NodeAdditionOrReplacementFromString(node, " \n+" .. dex .. " to Dexterity")
871930
elseif conqueredBy.conqueror.type == "templar" then
872931
if isValueInArray(attributes, node.dn) then
@@ -1048,9 +1107,12 @@ function PassiveSpecClass:ReplaceNode(old, newNode)
10481107
old.modList = new("ModList")
10491108
old.modList:AddList(newNode.modList)
10501109
old.sprites = newNode.sprites
1110+
old.effectSprites = newNode.effectSprites
1111+
old.isTattoo = newNode.isTattoo
10511112
old.keystoneMod = newNode.keystoneMod
10521113
old.icon = newNode.icon
10531114
old.spriteId = newNode.spriteId
1115+
old.activeEffectImage = newNode.activeEffectImage
10541116
old.reminderText = newNode.reminderText or { }
10551117
end
10561118

@@ -1563,13 +1625,14 @@ function PassiveSpecClass:CreateUndoState()
15631625
classId = self.curClassId,
15641626
ascendClassId = self.curAscendClassId,
15651627
hashList = allocNodeIdList,
1628+
hashOverrides = self.hashOverrides,
15661629
masteryEffects = selections,
15671630
treeVersion = self.treeVersion
15681631
}
15691632
end
15701633

15711634
function PassiveSpecClass:RestoreUndoState(state, treeVersion)
1572-
self:ImportFromNodeList(state.classId, state.ascendClassId, state.hashList, state.masteryEffects, treeVersion or state.treeVersion)
1635+
self:ImportFromNodeList(state.classId, state.ascendClassId, state.hashList, state.hashOverrides, state.masteryEffects, treeVersion or state.treeVersion)
15731636
self:SetWindowTitleWithBuildClass()
15741637
end
15751638

src/Classes/PassiveTree.lua

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
5252
local versionNum = treeVersions[treeVersion].num
5353

5454
self.legion = LoadModule("Data/TimelessJewelData/LegionPassives")
55+
self.tattoo = LoadModule("Data/TattooPassives")
5556

5657
MakeDir("TreeData")
5758

@@ -304,8 +305,6 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
304305
if versionNum <= 3.09 and node.passivePointsGranted > 0 then
305306
t_insert(node.sd, "Grants "..node.passivePointsGranted.." Passive Skill Point"..(node.passivePointsGranted > 1 and "s" or ""))
306307
end
307-
node.conquered = false
308-
node.alternative = {}
309308
node.__index = node
310309
node.linkedId = { }
311310
nodeMap[node.id] = node
@@ -507,6 +506,33 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
507506
self:ProcessStats(node)
508507
end
509508

509+
-- Build ModList for tattoos
510+
for _, node in pairs(self.tattoo.nodes) do
511+
-- Determine node type
512+
if node.m then
513+
node.type = "Mastery"
514+
elseif node.ks then
515+
node.type = "Keystone"
516+
if not self.keystoneMap[node.dn] then -- Don't override good tree data with legacy keystones
517+
self.keystoneMap[node.dn] = node
518+
end
519+
elseif node["not"] then
520+
node.type = "Notable"
521+
else
522+
node.type = "Normal"
523+
end
524+
525+
-- Assign node artwork assets
526+
node.sprites = self.spriteMap[node.icon]
527+
node.effectSprites = self.spriteMap[node.activeEffectImage]
528+
if not node.sprites then
529+
--error("missing sprite "..node.icon)
530+
node.sprites = { }
531+
end
532+
533+
self:ProcessStats(node)
534+
end
535+
510536
-- Late load the Generated data so we can take advantage of a tree existing
511537
buildTreeDependentUniques(self)
512538
end)

src/Classes/PassiveTreeView.lua

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
291291
build.itemsTab:SelectControl(slot)
292292
build.viewMode = "ITEMS"
293293
end
294+
elseif hoverNode and (hoverNode.isTattoo
295+
or (hoverNode.type == "Normal" and (hoverNode.dn == "Strength" or hoverNode.dn == "Dexterity" or hoverNode.dn == "Intelligence"))
296+
or (hoverNode.type == "Notable" and (hoverNode.sd[1]:match("+30 to Dexterity") or hoverNode.sd[1]:match("+30 to Strength") or hoverNode.sd[1]:match("+30 to Intelligence"))))
297+
then
298+
build.treeTab:ModifyNodePopup(hoverNode, viewPort)
299+
build.buildFlag = true
294300
elseif hoverNode and hoverNode.alloc and hoverNode.type == "Mastery" and hoverNode.masteryEffects then
295301
build.treeTab:OpenMasteryPopup(hoverNode, viewPort)
296302
build.buildFlag = true
@@ -520,6 +526,9 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
520526
SetDrawLayer(nil, 15)
521527
else
522528
-- Normal node (includes keystones and notables)
529+
if node.isTattoo and node.effectSprites then -- trees < 3.22.0 don't have effectSprites
530+
effect = node.effectSprites["tattooActiveEffect"]
531+
end
523532
base = node.sprites[node.type:lower()..(isAlloc and "Active" or "Inactive")]
524533
overlay = node.overlay[state .. (node.ascendancyName and "Ascend" or "") .. (node.isBlighted and "Blighted" or "")]
525534
end
@@ -599,9 +608,11 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
599608
end
600609
end
601610

602-
-- Draw master effect artwork
611+
-- Draw mastery/tattoo effect artwork
603612
if effect then
613+
SetDrawLayer(nil, 15)
604614
self:DrawAsset(effect, scrX, scrY, scale)
615+
SetDrawLayer(nil, 25)
605616
end
606617

607618
-- Draw base artwork
@@ -844,6 +855,15 @@ function PassiveTreeViewClass:DoesNodeMatchSearchParams(node)
844855
if #needMatches == 0 then
845856
return true
846857
end
858+
859+
-- Check node id for devs
860+
if launch.devMode then
861+
err, needMatches = PCall(search, tostring(node.id), needMatches)
862+
if err then return false end
863+
if #needMatches == 0 then
864+
return true
865+
end
866+
end
847867
end
848868

849869
function PassiveTreeViewClass:AddNodeName(tooltip, node, build)
@@ -997,6 +1017,15 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build)
9971017
end
9981018
end
9991019

1020+
-- Tattoo Editing
1021+
if node and (node.isTattoo
1022+
or (node.type == "Normal" and (node.dn == "Strength" or node.dn == "Dexterity" or node.dn == "Intelligence"))
1023+
or (node.type == "Notable" and (node.sd[1]:match("+30 to Dexterity") or node.sd[1]:match("+30 to Strength") or node.sd[1]:match("+30 to Intelligence"))))
1024+
then
1025+
tooltip:AddSeparator(14)
1026+
tooltip:AddLine(14, colorCodes.TIP.."Tip: Right click to edit the tattoo for this node")
1027+
end
1028+
10001029
-- Mod differences
10011030
if self.showStatDifferences then
10021031
local calcFunc, calcBase = build.calcsTab:GetMiscCalculator(build)

0 commit comments

Comments
 (0)