diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index e109e15bc1..a140c2bdc5 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -129,7 +129,12 @@ function PassiveSpecClass:Load(xml, dbFileName) end local hashList = { } for hash in xml.attrib.nodes:gmatch("%d+") do - t_insert(hashList, tonumber(hash)) + hash = tonumber(hash) + -- if cluster node and old node flag is true + if not self.oldClusterGeneration and (hash >= 65536) and (band(hash, 0x8000) == 0) then + self.oldClusterGeneration = true + end + t_insert(hashList, hash) end local masteryEffects = { } if xml.attrib.masteryEffects then @@ -476,39 +481,81 @@ function PassiveSpecClass:EncodeURL(prefix) local clusterCount = 0 local masteryCount = 0 + local nodeIds = {} local clusterNodeIds = {} - local masteryNodeIds = {} + local masteryNodes = {} + + local computeClusterId = function(id, oidx) + local socketType = band(b_rshift(id, 11), 3) -- 0 if socketed into large socket, 1 for medium, 2 for small + local groupSize = band(b_rshift(id, 4), 3) + local largeIndex = band(b_rshift(id, 6), 7) + local mediumIndex = band(b_rshift(id, 9), 3) + local nodeIndex + if socketType == 0 and groupSize > 0 then + nodeIndex = ({[0] = 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11})[oidx] + elseif socketType == 1 and groupSize == 1 then + nodeIndex = ({[0] = 0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5})[oidx] -- index mapping for 2 and 10 are estimated, could not figure out mapping based on testing + elseif socketType ~= 2 and groupSize == 0 then + nodeIndex = math.floor(oidx/2) + else + nodeIndex = oidx + end + -- Extended hash numbering logic + -- TLDR: Depth first numbering using cluster jewel socket indexes + -- Starting from the large cluster jewel socket index 0, there are 12 possible nodes, 3 of which can be medium cluster jewel sockets (Voices) + -- Then for each possible medium cluster socket there are 6 possible nodes, one of which can be a small cluster jewel socket + -- A small cluster jewel socket can have 3 nodes + -- 12 + 3 * (6 + 3) = 39 so large cluster jewel sockets start indexing from large index * 39 + -- Medium cluster jewel sockets start indexing from parent start index + 12 (to account for the 12 possible nodes) + -- Small cluster jewel sockets start indexing from parent start index + 6 (to account for the 6 possible nodes) + nodeIndex = nodeIndex + largeIndex * 39 + if socketType > 0 then + nodeIndex = nodeIndex + 12 + mediumIndex * 9 + end + if socketType > 1 then + nodeIndex = nodeIndex + 6 + end + return nodeIndex + end for id, node in pairs(self.allocNodes) do - if node.type ~= "ClassStart" and node.type ~= "AscendClassStart" and id < 65536 and nodeCount < 255 then - t_insert(a, m_floor(id / 256)) - t_insert(a, id % 256) + if node.type ~= "ClassStart" and node.type ~= "AscendClassStart" and id < 65536 and nodeCount < 255 and not node.hashId then + t_insert(nodeIds, id) nodeCount = nodeCount + 1 if self.masterySelections[node.id] then - local effect_id = self.masterySelections[node.id] - t_insert(masteryNodeIds, m_floor(effect_id / 256)) - t_insert(masteryNodeIds, effect_id % 256) - t_insert(masteryNodeIds, m_floor(node.id / 256)) - t_insert(masteryNodeIds, node.id % 256) + t_insert(masteryNodes, {nodeId = node.id, effectId = self.masterySelections[node.id]}) masteryCount = masteryCount + 1 end - elseif id >= 65536 then - local clusterId = id - 65536 - t_insert(clusterNodeIds, m_floor(clusterId / 256)) - t_insert(clusterNodeIds, clusterId % 256) + elseif id >= 65536 or node.hashId then + local clusterId = computeClusterId(node.hashId or id, node.oidx) + t_insert(clusterNodeIds, clusterId) clusterCount = clusterCount + 1 end end - t_insert(a, 7, nodeCount) + + -- Sort ids to ensure generated URL is the same as PoE + table.sort(nodeIds) + table.sort(masteryNodes, function(a, b) return a.nodeId < b.nodeId end) + table.sort(clusterNodeIds) + + t_insert(a, nodeCount) + for _, id in pairs(nodeIds) do + t_insert(a, m_floor(id / 256)) + t_insert(a, id % 256) + end t_insert(a, clusterCount) for _, id in pairs(clusterNodeIds) do - t_insert(a, id) + t_insert(a, m_floor(id / 256)) + t_insert(a, id % 256) end t_insert(a, masteryCount) - for _, id in pairs(masteryNodeIds) do - t_insert(a, id) + for _, node in pairs(masteryNodes) do + t_insert(a, m_floor(node.effectId / 256)) + t_insert(a, node.effectId % 256) + t_insert(a, m_floor(node.nodeId / 256)) + t_insert(a, node.nodeId % 256) end return (prefix or "")..common.base64.encode(string.char(unpack(a))):gsub("+","-"):gsub("/","_") @@ -1369,9 +1416,17 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe -- 4-5: Group size (0-2) -- 6-8: Large index (0-5) -- 9-10: Medium index (0-2) - -- 11-15: Unused + -- 11-12: Socket type - 0 for large, 1 for medium, or 2 for small + -- This is to differentiate between a small/medium cluster jewel socketed into a large/medium/small cluster socket + -- 13-14: Unused + -- 15: 1 since version 2.38.5? to be used for backwards compatibility, 0 otherwise + -- This bit is used to check when builds are later imported to see if the old method of generating nodeIds need to be applied -- 16: 1 (signal bit, to prevent conflict with node hashes) - id = id or 0x10000 + if self.oldClusterGeneration then + id = id or 0x10000 + else + id = (id and id + 0x00800) or 0x18000 -- Increment the socket type when called with an existing id + end if expansionJewel.size == 2 then id = id + b_lshift(expansionJewel.index, 6) elseif expansionJewel.size == 1 then @@ -1556,6 +1611,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe group = subGraph.group, o = nodeOrbit, oidx = nodeIndex, + hashId = nodeId + nodeIndex -- Property used when generating the extended hash for export URL } t_insert(subGraph.nodes, node) indicies[nodeIndex] = node