-- Path of Building -- -- Module: Spec -- Passive tree spec class. -- local launch, cfg, main = ... local pairs = pairs local ipairs = ipairs local m_min = math.min local m_max = math.max local m_floor = math.floor local t_insert = table.insert local SpecClass = { } SpecClass.__index = SpecClass local function buildPathFromNode(root) root.pathDist = 0 root.path = { } local queue = { root } local o, i = 1, 2 while o < i do local node = queue[o] o = o + 1 local curDist = node.pathDist + 1 for _, other in ipairs(node.linked) do if other.type ~= "class" and other.type ~= "ascendClass" and other.pathDist > curDist and (node.ascendancyName == other.ascendancyName or (curDist == 1 and not other.ascendancyName)) then other.pathDist = curDist other.path = wipeTable(other.path) other.path[1] = other for i, n in ipairs(node.path) do other.path[i+1] = n end queue[i] = other i = i + 1 end end end end function SpecClass:BuildAllPaths() for id, node in pairs(self.nodes) do node.pathDist = node.alloc and 0 or 1000 node.path = nil end for id, node in pairs(self.allocNodes) do buildPathFromNode(node) end end local function findStart(node, visited, noAscend) node.visited = true t_insert(visited, node) for _, other in ipairs(node.linked) do if other.alloc and (other.type == "class" or other.type == "ascendClass" or (not other.visited and findStart(other, visited, noAscend))) then if not noAscend or other.type ~= "ascendClass" then return true end end end end function SpecClass:BuildAllDepends() local visited = { } for id, node in pairs(self.nodes) do node.depends = wipeTable(node.depends) if node.alloc then node.depends[1] = node node.visited = true local anyStartFound = (node.type == "class" or node.type == "ascendClass") for _, other in ipairs(node.linked) do if other.alloc then if other.type == "class" or other.type == "ascendClass" then anyStartFound = true elseif findStart(other, visited) then anyStartFound = true for i, n in ipairs(visited) do n.visited = false visited[i] = nil end else for i, n in ipairs(visited) do t_insert(node.depends, n) n.visited = false visited[i] = nil end end end end node.visited = false if not anyStartFound then for _, depNode in ipairs(node.depends) do depNode.alloc = false self.allocNodes[depNode.id] = nil end end end end end function SpecClass:AllocNode(node, altPath) if not node.path then return end for _, pathNode in ipairs(altPath or node.path) do pathNode.alloc = true self.allocNodes[pathNode.id] = pathNode buildPathFromNode(pathNode) end if node.isMultipleChoiceOption then local parent = node.linked[1] for _, optNode in ipairs(parent.linked) do if optNode.isMultipleChoiceOption and optNode.alloc and optNode ~= node then optNode.alloc = false self.allocNodes[optNode.id] = nil self:BuildAllPaths() end end end self:BuildAllDepends() end function SpecClass:DeallocNode(node) for _, depNode in ipairs(node.depends) do depNode.alloc = false self.allocNodes[depNode.id] = nil end self:BuildAllDepends() self:BuildAllPaths() end function SpecClass:CountAllocNodes() local used, ascUsed = 0, 0 for _, node in pairs(self.allocNodes) do if node.type ~= "class" and node.type ~= "ascendClass" then if node.ascendancyName then if not node.isMultipleChoiceOption then ascUsed = ascUsed + 1 end else used = used + 1 end end end return used, ascUsed end function SpecClass:ResetNodes() for id, node in pairs(self.nodes) do if node.type ~= "class" and node.type ~= "ascendClass" then node.alloc = false self.allocNodes[id] = nil end end end function SpecClass:IsClassConnected(classId) for _, other in ipairs(self.nodes[self.tree.classes[classId].startNodeId].linked) do if other.alloc then other.visited = true local visited = { } local found = findStart(other, visited, true) for i, n in ipairs(visited) do n.visited = false end other.visited = false if found then return true end end end return false end function SpecClass:SelectClass(classId) if self.curClassId then local oldStartNodeId = self.tree.classes[self.curClassId].startNodeId self.nodes[oldStartNodeId].alloc = false self.allocNodes[oldStartNodeId] = nil end self.curClassId = classId local class = self.tree.classes[classId] local startNode = self.nodes[class.startNodeId] startNode.alloc = true self.allocNodes[startNode.id] = startNode self:SelectAscendClass(0) end function SpecClass:SelectAscendClass(ascendClassId) self.curAscendClassId = ascendClassId local ascendClass = self.tree.classes[self.curClassId].classes[tostring(ascendClassId)] or { name = "" } for id, node in pairs(self.allocNodes) do if node.ascendancyName and node.ascendancyName ~= ascendClass.name then node.alloc = false self.allocNodes[id] = nil end end if ascendClass.startNodeId then local startNode = self.nodes[ascendClass.startNodeId] startNode.alloc = true self.allocNodes[startNode.id] = startNode end self:BuildAllDepends() self:BuildAllPaths() end function SpecClass:DecodeURL(url) self:ResetNodes() local b = common.base64.decode(url:gsub("^.+/",""):gsub("-","+"):gsub("_","/")) local ver = b:byte(4) local classId = b:byte(5) local ascendClassId = (ver >= 4) and b:byte(6) or 0 self:SelectClass(classId) for i = (ver >= 4) and 8 or 7, #b-1, 2 do local id = b:byte(i) * 256 + b:byte(i + 1) local node = self.nodes[id] if node then node.alloc = true self.allocNodes[id] = node if ascendClassId == 0 and node.ascendancyName then ascendClassId = self.tree.ascendNameMap[node.ascendancyName].ascendClassId end end end self:SelectAscendClass(ascendClassId) end function SpecClass:EncodeURL(prefix) local a = { 0, 0, 0, 4, self.curClassId, self.curAscendClassId, 0 } for id, node in pairs(self.allocNodes) do if node.type ~= "class" and node.type ~= "ascendClass" then t_insert(a, m_floor(id / 256)) t_insert(a, id % 256) end end return (prefix or "")..common.base64.encode(string.char(unpack(a))):gsub("+","-"):gsub("/","_") end function SpecClass:AddUndoState(noClearRedo) t_insert(self.undo, 1, self:EncodeURL()) self.undo[102] = nil self.modFlag = true self.buildFlag = true if not noClearRedo then self.redo = { } end end function SpecClass:Undo() if self.undo[2] then t_insert(self.redo, 1, table.remove(self.undo, 1)) self:DecodeURL(table.remove(self.undo, 1)) self:AddUndoState(true) self.modFlag = true self.buildFlag = true end end function SpecClass:Redo() if self.redo[1] then self:DecodeURL(table.remove(self. redo, 1)) self:AddUndoState(true) self.modFlag = true self.buildFlag = true end end function SpecClass:Load(xml, dbFileName) for _, node in pairs(xml) do if type(node) == "table" then if node.elem == "URL" then if type(node[1]) ~= "string" then launch:ShowErrMsg("^1Error parsing '%s': 'URL' element missing content", fileName) return true end self:DecodeURL(node[1]) self.undo = { node[1] } self.redo = { } end end end self.modFlag = false end function SpecClass:Save(xml) t_insert(xml, { elem = "URL", [1] = self:EncodeURL("https://www.pathofexile.com/passive-skill-tree/") }) self.modFlag = false end function SpecClass.NewSpec(tree) local self = setmetatable({}, SpecClass) self.tree = tree self.nodes = { } for _, treeNode in ipairs(tree.nodes) do self.nodes[treeNode.id] = setmetatable({ rsq = treeNode.overlay and treeNode.overlay.rsq, size = treeNode.overlay and treeNode.overlay.size, linked = { } }, { __index = treeNode }) end for id, node in pairs(self.nodes) do for _, otherId in ipairs(node.linkedId) do t_insert(node.linked, self.nodes[otherId]) end end self.allocNodes = { } self.undo = { } self.redo = { } self:SelectClass(0) return self end return SpecClass