Adding class library

This commit is contained in:
Openarl
2016-05-05 17:53:33 +10:00
parent 3b6700f259
commit 3f887d62a8
15 changed files with 875 additions and 827 deletions

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@
*.xml
*.cfg
*.lnk
Data/
# User-specific files

82
Classes/ButtonControl.lua Normal file
View File

@@ -0,0 +1,82 @@
-- Path of Building
--
-- Class: Button Control
-- Basic button control.
--
local ButtonClass = common.NewClass("ButtonControl", function(self, x, y, width, height, label, onClick, enableFunc)
self.x = x
self.y = y
self.width = width
self.height = height
self.label = label
self.onClick = onClick
self.enableFunc = enableFunc
end)
function ButtonClass:IsMouseOver()
if self.hidden then
return false
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local cx, cy = GetCursorPos()
return cx >= x and cy >= y and cx < x + self.width and cy < y + self.height
end
function ButtonClass:Draw()
if self.hidden then
return
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local enabled = not self.enableFunc or self.enableFunc()
local mOver = self:IsMouseOver()
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, self.width, self.height)
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.clicked and mOver then
SetDrawColor(0.5, 0.5, 0.5)
elseif mOver then
SetDrawColor(0.33, 0.33, 0.33)
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, self.width - 2, self.height - 2)
if enabled then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.33, 0.33, 0.33)
end
DrawString(x + self.width / 2, y + 2, "CENTER_X", self.height - 4, "VAR", self.label)
end
function ButtonClass:OnKeyDown(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
self.clicked = true
end
return false
end
function ButtonClass:OnKeyUp(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
if self:IsMouseOver() then
self.onClick()
end
end
self.clicked = false
return true
end

151
Classes/DropDownControl.lua Normal file
View File

@@ -0,0 +1,151 @@
-- Path of Building
--
-- Class: DropDown Control
-- Basic drop down control.
--
local DropDownClass = common.NewClass("DropDownControl", function(self, x, y, width, height, list, selFunc, enableFunc)
self.x = x
self.y = y
self.width = width
self.height = height
self.list = list or { }
self.sel = 1
self.selFunc = selFunc
self.enableFunc = enableFunc
end)
function DropDownClass:SelByValue(val)
for index, listVal in ipairs(self.list) do
if listVal == val then
self.sel = index
return
end
end
end
function DropDownClass:IsMouseOver()
if self.hidden then
return false
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local cx, cy = GetCursorPos()
local dropExtra = self.dropped and (self.height - 4) * #self.list + 2 or 0
return cx >= x and cy >= y and cx < x + self.width and cy < y + self.height + dropExtra, cy < y + self.height
end
function DropDownClass:Draw()
if self.hidden then
return false
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local enabled = not self.enableFunc or self.enableFunc()
local mOver, mOverBody = self:IsMouseOver()
local dropExtra = (self.height - 4) * #self.list + 4
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver or self.dropped then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, self.width, self.height)
if self.dropped then
DrawImage(nil, x, y + self.height, self.width, dropExtra)
end
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.dropped then
SetDrawColor(0.5, 0.5, 0.5)
elseif mOver then
SetDrawColor(0.33, 0.33, 0.33)
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, self.width - 2, self.height - 2)
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver or self.dropped then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
local x1 = x + self.width - self.height / 2 - self.height / 4
local x2 = x1 + self.height / 2
local y1 = y + self.height / 4
local y2 = y1 + self.height / 2
DrawImageQuad(nil, x1, y1, x2, y1, (x1+x2)/2, y2, (x1+x2)/2, y2)
if self.dropped then
SetDrawColor(0, 0, 0)
DrawImage(nil, x + 1, y + self.height + 1, self.width - 2, dropExtra - 2)
end
if enabled then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.66, 0.66, 0.66)
end
DrawString(x + 2, y + 2, "LEFT", self.height - 4, "VAR", self.list[self.sel] or "")
if self.dropped then
self.hoverSel = mOver and math.floor((select(2,GetCursorPos()) - y - self.height) / (self.height - 4)) + 1
if self.hoverSel and self.hoverSel < 1 then
self.hoverSel = nil
end
for index, val in ipairs(self.list) do
local y = y + self.height + 2 + (index - 1) * (self.height - 4)
if index == self.hoverSel then
SetDrawColor(0.5, 0.4, 0.3)
DrawImage(nil, x + 2, y, self.width - 4, self.height - 4)
end
if index == self.hoverSel or index == self.sel then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.66, 0.66, 0.66)
end
DrawString(x + 2, y, "LEFT", self.height - 4, "VAR", StripEscapes(val))
end
end
end
function DropDownClass:OnKeyDown(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
local all, body = self:IsMouseOver()
if not all or (self.dropped and body) then
self.dropped = false
return true
end
self.dropped = true
return false
elseif key == "ESCAPE" then
self.dropped = false
return true
end
return false
end
function DropDownClass:OnKeyUp(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
local all, body = self:IsMouseOver()
if not all then
self.dropped = false
return true
elseif not body then
local y = type(self.y) == "function" and self:y() or self.y
local sel = math.max(1, math.floor((select(2,GetCursorPos()) - y - self.height) / (self.height - 4)) + 1)
self.sel = sel
if self.selFunc then
self.selFunc(sel, self.list[sel])
end
self.dropped = false
return true
end
end
return false
end

80
Classes/EditControl.lua Normal file
View File

@@ -0,0 +1,80 @@
-- Path of Building
--
-- Class: Edit Control
-- Wrapper for the basic edit field.
--
local EditClass = common.NewClass("EditControl", function(self, x, y, width, height, ...)
self.x = x
self.y = y
self.width = width
self.height = height
self.edit = common.newEditField(...)
self.edit.width = width - 4
self.edit.height = height - 4
self.edit.leader = ""
end)
function EditClass:SetText(text)
self.edit:SetText(text)
end
function EditClass:UpdatePosition()
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
self.edit.x = x + 2
self.edit.y = y + 2
return x, y
end
function EditClass:IsMouseOver()
if self.hidden then
return false
end
self:UpdatePosition()
return self.edit:IsMouseOver()
end
function EditClass:Draw()
if self.hidden then
return
end
local x, y = self:UpdatePosition()
local enabled = not self.edit.enableFunc or self.edit.enableFunc()
local mOver = self:IsMouseOver()
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, self.width, self.height)
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.active or mOver then
SetDrawColor(0.15, 0.15, 0.15)
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, self.width - 2, self.height - 2)
self.edit:Draw(nil, nil, nil, not self.active)
end
function EditClass:OnKeyDown(key, doubleClick)
self:UpdatePosition()
self.active = not self.edit:OnKeyDown(key, doubleClick)
return not self.active
end
function EditClass:OnKeyUp(key)
self:UpdatePosition()
self.active = not self.edit:OnKeyUp(key)
return not self.active
end
function EditClass:OnChar(key)
self:UpdatePosition()
self.active = not self.edit:OnChar(key)
return not self.active
end

View File

@@ -1,9 +1,9 @@
-- Path of Building
--
-- Module: Spec
-- Class: Passive Spec
-- Passive tree spec class.
--
local launch, cfg, main = ...
local launch = ...
local pairs = pairs
local ipairs = ipairs
@@ -12,87 +12,144 @@ local m_max = math.max
local m_floor = math.floor
local t_insert = table.insert
local SpecClass = { }
SpecClass.__index = SpecClass
local SpecClass = common.NewClass("PassiveSpec", function(self, tree)
self.tree = tree
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
-- Make a local copy of the passive tree that we can modify
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)
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
queue[i] = other
i = i + 1
self:DecodeURL(node[1])
self.undo = { node[1] }
self.redo = { }
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
self.modFlag = false
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
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: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: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: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:BuildAllDepends()
local visited = { }
function SpecClass:ResetNodes()
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
if node.type ~= "class" and node.type ~= "ascendClass" then
node.alloc = false
self.allocNodes[id] = nil
end
end
end
@@ -104,7 +161,7 @@ function SpecClass:AllocNode(node, altPath)
for _, pathNode in ipairs(altPath or node.path) do
pathNode.alloc = true
self.allocNodes[pathNode.id] = pathNode
buildPathFromNode(pathNode)
self:BuildPathFromNode(pathNode)
end
if node.isMultipleChoiceOption then
local parent = node.linked[1]
@@ -118,6 +175,7 @@ function SpecClass:AllocNode(node, altPath)
end
self:BuildAllDepends()
end
function SpecClass:DeallocNode(node)
for _, depNode in ipairs(node.depends) do
depNode.alloc = false
@@ -143,93 +201,88 @@ function SpecClass:CountAllocNodes()
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
function SpecClass: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: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
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
self:BuildPathFromNode(node)
end
end
function SpecClass:FindStartFromNode(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 self:FindStartFromNode(other, visited, noAscend))) then
if not noAscend or other.type ~= "ascendClass" 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
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 self:FindStartFromNode(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
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)
@@ -241,6 +294,7 @@ function SpecClass:AddUndoState(noClearRedo)
self.redo = { }
end
end
function SpecClass:Undo()
if self.undo[2] then
t_insert(self.redo, 1, table.remove(self.undo, 1))
@@ -250,6 +304,7 @@ function SpecClass:Undo()
self.buildFlag = true
end
end
function SpecClass:Redo()
if self.redo[1] then
self:DecodeURL(table.remove(self. redo, 1))
@@ -258,58 +313,3 @@ function SpecClass:Redo()
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

View File

@@ -1,9 +1,9 @@
-- Path of Building
--
-- Module: Tree
-- Class: Passive Tree
-- Passive skill tree class.
--
local launch, cfg, main = ...
local launch = ...
local pairs = pairs
local ipairs = ipairs
@@ -16,83 +16,7 @@ local m_tan = math.tan
local m_sqrt = math.sqrt
local t_insert = table.insert
local function cacheImage(imgName, url)
local imgFile = io.open(imgName, "r")
if imgFile then
imgFile:close()
else
ConPrintf("Downloading '%s'...", imgName)
imgFile = io.open(imgName, "wb")
local easy = common.curl.easy()
easy:setopt_url(url)
easy:setopt_writefunction(imgFile)
easy:perform()
easy:close()
imgFile:close()
end
end
local TreeClass = { }
TreeClass.__index = TreeClass
function TreeClass:BuildConnector(node1, node2)
local conn = {
ascendancyName = node1.ascendancyName,
nodeId1 = node1.id,
nodeId2 = node2.id,
c = { }
}
if node1.g == node2.g and node1.o == node2.o then
conn.type = "Orbit" .. node1.o
if node1.angle > node2.angle then
node1, node2 = node2, node1
end
local span = node2.angle - node1.angle
if span > m_pi then
node1, node2 = node2, node1
span = m_pi * 2 - span
end
local clipAngle = m_pi / 4 - span / 2
local p = 1 - m_max(m_tan(clipAngle), 0)
local angle = node1.angle - clipAngle
local norm, act = { }, { }
for tbl, state in pairs({[norm] = "Normal", [act] = "Active"}) do
local art = self.assets[conn.type..state]
local size = art.width * 2 * 1.33
local oX, oY = size * m_sqrt(2) * m_sin(angle + m_pi/4), size * m_sqrt(2) * -m_cos(angle + m_pi/4)
local cX, cY = node1.group.x + oX, node1.group.y + oY
tbl[1], tbl[2] = node1.group.x, node1.group.y
tbl[3], tbl[4] = cX + (size * m_sin(angle) - oX) * p, cY + (size * -m_cos(angle) - oY) * p
tbl[5], tbl[6] = cX, cY
tbl[7], tbl[8] = cX + (size * m_cos(angle) - oX) * p, cY + (size * m_sin(angle) - oY) * p
end
conn.vert = { Normal = norm, Intermediate = norm, Active = act }
conn.c[9], conn.c[10] = 1, 1
conn.c[11], conn.c[12] = 0, p
conn.c[13], conn.c[14] = 0, 0
conn.c[15], conn.c[16] = p, 0
else
conn.type = "LineConnector"
local art = self.assets.LineConnectorNormal
local vX, vY = node2.x - node1.x, node2.y - node1.y
local dist = m_sqrt(vX * vX + vY * vY)
local scale = art.height * 1.33 / dist
local nX, nY = vX * scale, vY * scale
local endS = dist / (art.width * 1.33)
conn[1], conn[2] = node1.x - nY, node1.y + nX
conn[3], conn[4] = node1.x + nY, node1.y - nX
conn[5], conn[6] = node2.x + nY, node2.y - nX
conn[7], conn[8] = node2.x - nY, node2.y + nX
conn.vert = { Normal = conn, Intermediate = conn, Active = conn }
conn.c[9], conn.c[10] = 0, 1
conn.c[11], conn.c[12] = 0, 0
conn.c[13], conn.c[14] = endS, 0
conn.c[15], conn.c[16] = endS, 1
end
return conn
end
function TreeClass.NewTree()
local TreeClass = common.NewClass("PassiveTree", function(self)
os.execute("mkdir Data")
ConPrintf("Loading JSON...")
@@ -129,20 +53,21 @@ function TreeClass.NewTree()
classFile:write(classText)
classFile:close()
end
local self = common.json.decode(treeText)
for k, v in pairs(common.json.decode(treeText)) do
self[k] = v
end
self.classes = { }
for id, data in pairs(common.json.decode(classText)) do
self.classes[tonumber(id)] = data
data.classes["0"] = { name = "None "}
end
setmetatable(self, TreeClass)
self.size = m_min(self.max_x - self.min_x, self.max_y - self.min_y) * 1.1
ConPrintf("Loading assets...")
for name, data in pairs(self.assets) do
local imgName = "Data/"..name..".png"
cacheImage(imgName, data["0.3835"] or data["1"])
self:CacheImage(imgName, data["0.3835"] or data["1"])
data.handle = NewImageHandle()
data.handle:Load(imgName)
data.width, data.height = data.handle:ImageSize()
@@ -156,7 +81,7 @@ function TreeClass.NewTree()
if not sheet then
sheet = { }
local imgName = "Data/"..maxZoom.filename
cacheImage(imgName, self.imageRoot.."build-gen/passive-skill-sprite/"..maxZoom.filename)
self:CacheImage(imgName, self.imageRoot.."build-gen/passive-skill-sprite/"..maxZoom.filename)
sheet.handle = NewImageHandle()
sheet.handle:Load(imgName, "CLAMP")
sheet.width, sheet.height = sheet.handle:ImageSize()
@@ -336,8 +261,77 @@ function TreeClass.NewTree()
end
end
end
end)
return self
function TreeClass:CacheImage(imgName, url)
local imgFile = io.open(imgName, "r")
if imgFile then
imgFile:close()
else
ConPrintf("Downloading '%s'...", imgName)
imgFile = io.open(imgName, "wb")
local easy = common.curl.easy()
easy:setopt_url(url)
easy:setopt_writefunction(imgFile)
easy:perform()
easy:close()
imgFile:close()
end
end
return TreeClass
function TreeClass:BuildConnector(node1, node2)
local conn = {
ascendancyName = node1.ascendancyName,
nodeId1 = node1.id,
nodeId2 = node2.id,
c = { }
}
if node1.g == node2.g and node1.o == node2.o then
conn.type = "Orbit" .. node1.o
if node1.angle > node2.angle then
node1, node2 = node2, node1
end
local span = node2.angle - node1.angle
if span > m_pi then
node1, node2 = node2, node1
span = m_pi * 2 - span
end
local clipAngle = m_pi / 4 - span / 2
local p = 1 - m_max(m_tan(clipAngle), 0)
local angle = node1.angle - clipAngle
local norm, act = { }, { }
for tbl, state in pairs({[norm] = "Normal", [act] = "Active"}) do
local art = self.assets[conn.type..state]
local size = art.width * 2 * 1.33
local oX, oY = size * m_sqrt(2) * m_sin(angle + m_pi/4), size * m_sqrt(2) * -m_cos(angle + m_pi/4)
local cX, cY = node1.group.x + oX, node1.group.y + oY
tbl[1], tbl[2] = node1.group.x, node1.group.y
tbl[3], tbl[4] = cX + (size * m_sin(angle) - oX) * p, cY + (size * -m_cos(angle) - oY) * p
tbl[5], tbl[6] = cX, cY
tbl[7], tbl[8] = cX + (size * m_cos(angle) - oX) * p, cY + (size * m_sin(angle) - oY) * p
end
conn.vert = { Normal = norm, Intermediate = norm, Active = act }
conn.c[9], conn.c[10] = 1, 1
conn.c[11], conn.c[12] = 0, p
conn.c[13], conn.c[14] = 0, 0
conn.c[15], conn.c[16] = p, 0
else
conn.type = "LineConnector"
local art = self.assets.LineConnectorNormal
local vX, vY = node2.x - node1.x, node2.y - node1.y
local dist = m_sqrt(vX * vX + vY * vY)
local scale = art.height * 1.33 / dist
local nX, nY = vX * scale, vY * scale
local endS = dist / (art.width * 1.33)
conn[1], conn[2] = node1.x - nY, node1.y + nX
conn[3], conn[4] = node1.x + nY, node1.y - nX
conn[5], conn[6] = node2.x + nY, node2.y - nX
conn[7], conn[8] = node2.x - nY, node2.y + nX
conn.vert = { Normal = conn, Intermediate = conn, Active = conn }
conn.c[9], conn.c[10] = 0, 1
conn.c[11], conn.c[12] = 0, 0
conn.c[13], conn.c[14] = endS, 0
conn.c[15], conn.c[16] = endS, 1
end
return conn
end

View File

@@ -1,9 +1,9 @@
-- Path of Building
--
-- Module: TreeView
-- Passive skill tree view for the active build
-- Class: Passive Tree View
-- Passive skill tree viewer.
--
local launch, cfg, main = ...
local launch = ...
local pairs = pairs
local ipairs = ipairs
@@ -12,143 +12,84 @@ local m_min = math.min
local m_max = math.max
local m_floor = math.floor
local function drawAsset(data, x, y, scale, isHalf)
local width = data.width * scale * 1.33
local height = data.height * scale * 1.33
if isHalf then
DrawImage(data.handle, x - width, y - height * 2, width * 2, height * 2)
DrawImage(data.handle, x - width, y, width * 2, height * 2, 0, 1, 1, 0)
else
DrawImage(data.handle, x - width, y - height, width * 2, height * 2, unpack(data))
end
end
local TreeViewClass = common.NewClass("PassiveTreeView", function(self, main)
self.main = main
local TreeViewClass = { }
TreeViewClass.__index = TreeViewClass
self.ring = NewImageHandle()
self.ring:Load("Art/ring.png")
function TreeViewClass:Zoom(level, viewPort)
self.zoomLevel = m_max(0, m_min(12, self.zoomLevel + level))
local oldZoom = self.zoom
self.zoomLevel = 3
self.zoom = 1.2 ^ self.zoomLevel
local factor = self.zoom / oldZoom
local cursorX, cursorY = GetCursorPos()
local relX = cursorX - viewPort.x - viewPort.width/2
local relY = cursorY - viewPort.y - viewPort.height/2
self.zoomX = relX + (self.zoomX - relX) * factor
self.zoomY = relY + (self.zoomY - relY) * factor
self.zoomX = 0
self.zoomY = 0
self.searchStr = ""
self.controls = { }
t_insert(self.controls, common.New("ButtonControl", function() return self.viewPort.x + 4 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 60, 20, "Reset", function()
launch:ShowPrompt(1, 0, 0, "Are you sure to want to reset your tree?\nPress Y to continue.", function(key)
if key == "y" then
self.build.spec:ResetNodes()
self.build.spec:AddUndoState()
end
return true
end)
end))
t_insert(self.controls, common.New("ButtonControl", function() return self.viewPort.x + 4 + 68*1 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 60, 20, "Import", function()
launch:ShowPrompt(0, 0, 0, "Press Ctrl+V to import passive tree link.", function(key)
if key == "v" and IsKeyDown("CTRL") then
local url = Paste()
if url and #url > 0 then
self.build.spec:DecodeURL(url)
self.build.spec:AddUndoState()
end
return true
elseif key == "ESCAPE" then
return true
end
end)
end))
t_insert(self.controls, common.New("ButtonControl", function() return self.viewPort.x + 4 + 68*2 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 60, 20, "Export", function()
launch:ShowPrompt(0, 0, 0, "Press Ctrl+C to copy passive tree link.", function(key)
if key == "c" and IsKeyDown("CTRL") then
Copy(self.build.spec:EncodeURL("https://www.pathofexile.com/passive-skill-tree/"))
return true
elseif key == "ESCAPE" then
return true
end
end)
end))
self.controls.treeSearch = common.New("EditControl", function() return self.viewPort.x + 4 + 68*3 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 400, 20, "", "Search", "[^%c%(%)]", 100, nil, function(buf)
self.searchStr = buf
end)
end)
function TreeViewClass:Load(xml, fileName)
if xml.attrib.zoomLevel then
self.zoomLevel = tonumber(xml.attrib.zoomLevel)
self.zoom = 1.2 ^ self.zoomLevel
end
if xml.attrib.zoomX and xml.attrib.zoomY then
self.zoomX = tonumber(xml.attrib.zoomX)
self.zoomY = tonumber(xml.attrib.zoomY)
end
if xml.attrib.searchStr then
self.searchStr = xml.attrib.searchStr
self.controls.treeSearch:SetText(self.searchStr)
end
if xml.attrib.showHeatMap then
self.showHeatMap = xml.attrib.showHeatMap == "true"
end
end
function TreeViewClass:AddNodeTooltip(node, build)
-- Special case for sockets
if node.type == "socket" and node.alloc then
local socket, jewel = build.items:GetSocketJewel(node.id)
if jewel then
build.items:AddItemTooltip(jewel, build)
else
main:AddTooltipLine(24, "^7"..node.dn..(IsKeyDown("ALT") and " ["..node.id.."]" or ""))
end
main:AddTooltipSeperator(14)
main:AddTooltipLine(14, "^x80A080Tip: Right click this socket to go to the items page and choose the jewel for this socket.")
return
end
-- Node name
main:AddTooltipLine(24, "^7"..node.dn..(IsKeyDown("ALT") and " ["..node.id.."]" or ""))
if IsKeyDown("ALT") and node.power and node.power.dps then
main:AddTooltipLine(16, string.format("DPS power: %g Defence power: %g", node.power.dps, node.power.def))
end
-- Node description
if node.sd[1] then
main:AddTooltipLine(16, "")
for i, line in ipairs(node.sd) do
if node.mods[i].list then
if IsKeyDown("ALT") then
local modStr
for k, v in pairs(node.mods[i].list) do
modStr = (modStr and modStr..", " or "^2") .. string.format("%s = %s", k, tostring(v))
end
if node.mods[i].extra then
modStr = (modStr and modStr.." " or "") .. "^1" .. node.mods[i].extra
end
if modStr then
line = line .. " " .. modStr
end
end
end
main:AddTooltipLine(16, "^7"..line)
end
end
-- Reminder text
if node.reminderText then
main:AddTooltipSeperator(14)
for _, line in ipairs(node.reminderText) do
main:AddTooltipLine(14, "^xA0A080"..line)
end
end
-- Mod differences
local calcFunc, calcBase = build.calcs:GetNodeCalculator(build)
if calcFunc then
main:AddTooltipSeperator(14)
local count
local nodeOutput, pathOutput
if node.alloc then
count = #node.depends
nodeOutput = calcFunc({node}, true)
pathOutput = calcFunc(node.depends, true)
else
local path = self.tracePath or node.path or { }
count = #path
nodeOutput = calcFunc({node})
pathOutput = calcFunc(path)
end
local none = true
local header = false
for _, data in ipairs(build.displayStats) do
if data.mod then
local diff = (nodeOutput[data.mod] or 0) - (calcBase[data.mod] or 0)
if diff > 0.001 or diff < -0.001 then
none = false
if not header then
main:AddTooltipLine(14, string.format("^7%s this node will give you:", node.alloc and "Unallocating" or "Allocating"))
header = true
end
main:AddTooltipLine(14, string.format("%s%+"..data.fmt.." %s", diff > 0 and "^x00FF44" or "^xFF3300", diff * (data.pc and 100 or 1), data.label))
end
end
end
if count > 1 then
header = false
for _, data in ipairs(build.displayStats) do
if data.mod then
local diff = (pathOutput[data.mod] or 0) - (calcBase[data.mod] or 0)
if diff > 0.001 or diff < -0.001 then
none = false
if not header then
main:AddTooltipLine(14, string.format("^7%s this node and all nodes %s will give you:", node.alloc and "Unallocating" or "Allocating", node.alloc and "depending on it" or "leading to it"))
header = true
end
main:AddTooltipLine(14, string.format("%s%+"..data.fmt.." %s", diff > 0 and "^x00FF44" or "^xFF3300", diff * (data.pc and 100 or 1), data.label))
end
end
end
end
if none then
main:AddTooltipLine(14, string.format("^7No changes from %s this node%s.", node.alloc and "unallocating" or "allocating", count > 1 and " or the nodes leading to it" or ""))
end
end
-- Pathing distance
if node.path and #node.path > 0 then
main:AddTooltipSeperator(14)
main:AddTooltipLine(14, "^7"..#node.path .. " points to node")
if #node.path > 1 then
main:AddTooltipLine(14, "^x80A080")
main:AddTooltipLine(14, "Tip: To reach this node by a different path, hold Shift, then trace the path and click this node")
end
end
function TreeViewClass:Save(xml)
xml.attrib = {
zoomLevel = tostring(self.zoomLevel),
zoomX = tostring(self.zoomX),
zoomY = tostring(self.zoomY),
searchStr = self.searchStr,
showHeatMap = tostring(self.showHeatMap),
}
end
function TreeViewClass:DrawTree(build, viewPort, inputEvents)
@@ -308,15 +249,15 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents)
if group.ascendancyName ~= curAscendName then
SetDrawColor(1, 1, 1, 0.25)
end
drawAsset(tree.assets["Classes"..group.ascendancyName], scrX, scrY, scale)
self:DrawAsset(tree.assets["Classes"..group.ascendancyName], scrX, scrY, scale)
SetDrawColor(1, 1, 1)
end
elseif group.oo["3"] then
drawAsset(tree.assets.PSGroupBackground3, scrX, scrY, scale, true)
self:DrawAsset(tree.assets.PSGroupBackground3, scrX, scrY, scale, true)
elseif group.oo["2"] then
drawAsset(tree.assets.PSGroupBackground2, scrX, scrY, scale)
self:DrawAsset(tree.assets.PSGroupBackground2, scrX, scrY, scale)
elseif group.oo["1"] then
drawAsset(tree.assets.PSGroupBackground1, scrX, scrY, scale)
self:DrawAsset(tree.assets.PSGroupBackground1, scrX, scrY, scale)
end
end
@@ -406,7 +347,7 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents)
end
end
if base then
drawAsset(base, scrX, scrY, scale)
self:DrawAsset(base, scrX, scrY, scale)
end
if overlay then
if node.type ~= "class" and node.type ~= "ascendClass" then
@@ -449,7 +390,7 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents)
end
end
end
drawAsset(tree.assets[overlay], scrX, scrY, scale)
self:DrawAsset(tree.assets[overlay], scrX, scrY, scale)
SetDrawColor(1, 1, 1)
end
end
@@ -479,7 +420,7 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents)
self:AddNodeTooltip(hoverNode, build)
local scrX, scrY = treeToScreen(hoverNode.x, hoverNode.y)
local size = m_floor(hoverNode.size * scale)
main:DrawTooltip(m_floor(scrX - size), m_floor(scrY - size), size * 2, size * 2, viewPort)
self.main:DrawTooltip(m_floor(scrX - size), m_floor(scrY - size), size * 2, size * 2, viewPort)
end
SetDrawColor(0.05, 0.05, 0.05)
@@ -489,85 +430,138 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents)
common.controlsDraw(self, viewPort)
end
function TreeViewClass:Load(xml, fileName)
if xml.attrib.zoomLevel then
self.zoomLevel = tonumber(xml.attrib.zoomLevel)
self.zoom = 1.2 ^ self.zoomLevel
end
if xml.attrib.zoomX and xml.attrib.zoomY then
self.zoomX = tonumber(xml.attrib.zoomX)
self.zoomY = tonumber(xml.attrib.zoomY)
end
if xml.attrib.searchStr then
self.searchStr = xml.attrib.searchStr
self.controls.treeSearch:SetText(self.searchStr)
end
if xml.attrib.showHeatMap then
self.showHeatMap = xml.attrib.showHeatMap == "true"
function TreeViewClass:DrawAsset(data, x, y, scale, isHalf)
local width = data.width * scale * 1.33
local height = data.height * scale * 1.33
if isHalf then
DrawImage(data.handle, x - width, y - height * 2, width * 2, height * 2)
DrawImage(data.handle, x - width, y, width * 2, height * 2, 0, 1, 1, 0)
else
DrawImage(data.handle, x - width, y - height, width * 2, height * 2, unpack(data))
end
end
function TreeViewClass:Save(xml)
xml.attrib = {
zoomLevel = tostring(self.zoomLevel),
zoomX = tostring(self.zoomX),
zoomY = tostring(self.zoomY),
searchStr = self.searchStr,
showHeatMap = tostring(self.showHeatMap),
}
end
function TreeViewClass.NewTreeView()
local self = setmetatable({}, TreeViewClass)
self.ring = NewImageHandle()
self.ring:Load("ring.png")
self.zoomLevel = 3
function TreeViewClass:Zoom(level, viewPort)
self.zoomLevel = m_max(0, m_min(12, self.zoomLevel + level))
local oldZoom = self.zoom
self.zoom = 1.2 ^ self.zoomLevel
self.zoomX = 0
self.zoomY = 0
self.searchStr = ""
self.controls = { }
t_insert(self.controls, common.newButton(function() return self.viewPort.x + 4 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 60, 20, "Reset", function()
launch:ShowPrompt(1, 0, 0, "Are you sure to want to reset your tree?\nPress Y to continue.", function(key)
if key == "y" then
self.build.spec:ResetNodes()
self.build.spec:AddUndoState()
end
return true
end)
end))
t_insert(self.controls, common.newButton(function() return self.viewPort.x + 4 + 68*1 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 60, 20, "Import", function()
launch:ShowPrompt(0, 0, 0, "Press Ctrl+V to import passive tree link.", function(key)
if key == "v" and IsKeyDown("CTRL") then
local url = Paste()
if url and #url > 0 then
self.build.spec:DecodeURL(url)
self.build.spec:AddUndoState()
end
return true
elseif key == "ESCAPE" then
return true
end
end)
end))
t_insert(self.controls, common.newButton(function() return self.viewPort.x + 4 + 68*2 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 60, 20, "Export", function()
launch:ShowPrompt(0, 0, 0, "Press Ctrl+C to copy passive tree link.", function(key)
if key == "c" and IsKeyDown("CTRL") then
Copy(self.build.spec:EncodeURL("https://www.pathofexile.com/passive-skill-tree/"))
return true
elseif key == "ESCAPE" then
return true
end
end)
end))
self.controls.treeSearch = common.newEditControl(function() return self.viewPort.x + 4 + 68*3 end, function() return self.viewPort.y + self.viewPort.height + 8 end, 400, 20, "", "Search", "[^%c%(%)]", 100, nil, function(buf)
self.searchStr = buf
end)
return self
local factor = self.zoom / oldZoom
local cursorX, cursorY = GetCursorPos()
local relX = cursorX - viewPort.x - viewPort.width/2
local relY = cursorY - viewPort.y - viewPort.height/2
self.zoomX = relX + (self.zoomX - relX) * factor
self.zoomY = relY + (self.zoomY - relY) * factor
end
return TreeViewClass
function TreeViewClass:AddNodeTooltip(node, build)
-- Special case for sockets
if node.type == "socket" and node.alloc then
local socket, jewel = build.items:GetSocketJewel(node.id)
if jewel then
build.items:AddItemTooltip(jewel, build)
else
self.main:AddTooltipLine(24, "^7"..node.dn..(IsKeyDown("ALT") and " ["..node.id.."]" or ""))
end
self.main:AddTooltipSeperator(14)
self.main:AddTooltipLine(14, "^x80A080Tip: Right click this socket to go to the items page and choose the jewel for this socket.")
return
end
-- Node name
self.main:AddTooltipLine(24, "^7"..node.dn..(IsKeyDown("ALT") and " ["..node.id.."]" or ""))
if IsKeyDown("ALT") and node.power and node.power.dps then
self.main:AddTooltipLine(16, string.format("DPS power: %g Defence power: %g", node.power.dps, node.power.def))
end
-- Node description
if node.sd[1] then
self.main:AddTooltipLine(16, "")
for i, line in ipairs(node.sd) do
if node.mods[i].list then
if IsKeyDown("ALT") then
local modStr
for k, v in pairs(node.mods[i].list) do
modStr = (modStr and modStr..", " or "^2") .. string.format("%s = %s", k, tostring(v))
end
if node.mods[i].extra then
modStr = (modStr and modStr.." " or "") .. "^1" .. node.mods[i].extra
end
if modStr then
line = line .. " " .. modStr
end
end
end
self.main:AddTooltipLine(16, "^7"..line)
end
end
-- Reminder text
if node.reminderText then
self.main:AddTooltipSeperator(14)
for _, line in ipairs(node.reminderText) do
self.main:AddTooltipLine(14, "^xA0A080"..line)
end
end
-- Mod differences
local calcFunc, calcBase = build.calcs:GetNodeCalculator(build)
if calcFunc then
self.main:AddTooltipSeperator(14)
local count
local nodeOutput, pathOutput
if node.alloc then
count = #node.depends
nodeOutput = calcFunc({node}, true)
pathOutput = calcFunc(node.depends, true)
else
local path = self.tracePath or node.path or { }
count = #path
nodeOutput = calcFunc({node})
pathOutput = calcFunc(path)
end
local none = true
local header = false
for _, data in ipairs(build.displayStats) do
if data.mod then
local diff = (nodeOutput[data.mod] or 0) - (calcBase[data.mod] or 0)
if diff > 0.001 or diff < -0.001 then
none = false
if not header then
self.main:AddTooltipLine(14, string.format("^7%s this node will give you:", node.alloc and "Unallocating" or "Allocating"))
header = true
end
self.main:AddTooltipLine(14, string.format("%s%+"..data.fmt.." %s", diff > 0 and "^x00FF44" or "^xFF3300", diff * (data.pc and 100 or 1), data.label))
end
end
end
if count > 1 then
header = false
for _, data in ipairs(build.displayStats) do
if data.mod then
local diff = (pathOutput[data.mod] or 0) - (calcBase[data.mod] or 0)
if diff > 0.001 or diff < -0.001 then
none = false
if not header then
self.main:AddTooltipLine(14, string.format("^7%s this node and all nodes %s will give you:", node.alloc and "Unallocating" or "Allocating", node.alloc and "depending on it" or "leading to it"))
header = true
end
self.main:AddTooltipLine(14, string.format("%s%+"..data.fmt.." %s", diff > 0 and "^x00FF44" or "^xFF3300", diff * (data.pc and 100 or 1), data.label))
end
end
end
end
if none then
self.main:AddTooltipLine(14, string.format("^7No changes from %s this node%s.", node.alloc and "unallocating" or "allocating", count > 1 and " or the nodes leading to it" or ""))
end
end
-- Pathing distance
if node.path and #node.path > 0 then
self.main:AddTooltipSeperator(14)
self.main:AddTooltipLine(14, "^7"..#node.path .. " points to node")
if #node.path > 1 then
self.main:AddTooltipLine(14, "^x80A080")
self.main:AddTooltipLine(14, "Tip: To reach this node by a different path, hold Shift, then trace the path and click this node")
end
end
end

View File

@@ -11,6 +11,34 @@ common.xml = require("xml")
common.json = require("dkjson")
common.base64 = require("base64")
-- Basic edit field
common.newEditField = require("simplegraphic/editfield")
-- Class library
common.classes = { }
function common.NewClass(className, initFunc)
local class = { }
class.__index = class
class._init = initFunc
common.classes[className] = class
return class
end
function common.New(className, ...)
local class = common.classes[className]
if not class then
error("Class '"..className.."' not defined")
end
local object = setmetatable({ }, class)
class._init(object, ...)
return object
end
-- UI Controls
LoadModule("Classes/EditControl")
LoadModule("Classes/ButtonControl")
LoadModule("Classes/DropDownControl")
-- Process input events for a host object with a list of controls
function common.controlsInput(host, inputEvents)
for id, event in ipairs(inputEvents) do
if event.type == "KeyDown" then
@@ -49,6 +77,7 @@ function common.controlsInput(host, inputEvents)
end
end
-- Draw host object's controls
function common.controlsDraw(host, ...)
for _, control in pairs(host.controls) do
if control ~= host.selControl then
@@ -61,302 +90,7 @@ function common.controlsDraw(host, ...)
end
common.newEditField = require("simplegraphic/editfield")
local editMeta = { }
editMeta.__index = editMeta
function editMeta:IsMouseOver()
if self.hidden then
return false
end
self.edit.x = type(self.x) == "function" and self:x() + 2 or self.x + 2
self.edit.y = type(self.y) == "function" and self:y() + 2 or self.y + 2
return self.edit:IsMouseOver()
end
function editMeta:OnKeyDown(key, doubleClick)
self.edit.x = type(self.x) == "function" and self:x() + 2 or self.x + 2
self.edit.y = type(self.y) == "function" and self:y() + 2 or self.y + 2
self.active = not self.edit:OnKeyDown(key, doubleClick)
return not self.active
end
function editMeta:OnKeyUp(key)
self.edit.x = type(self.x) == "function" and self:x() + 2 or self.x + 2
self.edit.y = type(self.y) == "function" and self:y() + 2 or self.y + 2
self.active = not self.edit:OnKeyUp(key)
return not self.active
end
function editMeta:OnChar(key)
self.edit.x = type(self.x) == "function" and self:x() + 2 or self.x + 2
self.edit.y = type(self.y) == "function" and self:y() + 2 or self.y + 2
self.active = not self.edit:OnChar(key)
return not self.active
end
function editMeta:Draw()
if self.hidden then
return
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local enabled = not self.edit.enableFunc or self.edit.enableFunc()
local mOver = self:IsMouseOver()
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, self.width, self.height)
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.active or mOver then
SetDrawColor(0.15, 0.15, 0.15)
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, self.width - 2, self.height - 2)
self.edit.x = x + 2
self.edit.y = y + 2
self.edit:Draw(nil, nil, nil, not self.active)
end
function editMeta:SetText(text)
self.edit:SetText(text)
end
function common.newEditControl(x, y, width, height, ...)
local control = { }
control.x = x
control.y = y
control.width = width
control.height = height
control.edit = common.newEditField(...)
control.edit.width = width - 4
control.edit.height = height - 4
control.edit.leader = ""
return setmetatable(control, editMeta)
end
local buttonMeta = { }
buttonMeta.__index = buttonMeta
function buttonMeta:IsMouseOver()
if self.hidden then
return false
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local cx, cy = GetCursorPos()
return cx >= x and cy >= y and cx < x + self.width and cy < y + self.height
end
function buttonMeta:OnKeyDown(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
self.clicked = true
end
return false
end
function buttonMeta:OnKeyUp(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
if self:IsMouseOver() then
self.onClick()
end
end
self.clicked = false
return true
end
function buttonMeta:Draw()
if self.hidden then
return
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local enabled = not self.enableFunc or self.enableFunc()
local mOver = self:IsMouseOver()
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, self.width, self.height)
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.clicked and mOver then
SetDrawColor(0.5, 0.5, 0.5)
elseif mOver then
SetDrawColor(0.33, 0.33, 0.33)
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, self.width - 2, self.height - 2)
if enabled then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.33, 0.33, 0.33)
end
DrawString(x + self.width / 2, y + 2, "CENTER_X", self.height - 4, "VAR", self.label)
end
function common.newButton(x, y, width, height, label, onClick, enableFunc)
local control = { }
control.x = x
control.y = y
control.width = width
control.height = height
control.label = label
control.onClick = onClick
control.enableFunc = enableFunc
return setmetatable(control, buttonMeta)
end
local dropDownMeta = { }
dropDownMeta.__index = dropDownMeta
function dropDownMeta:IsMouseOver()
if self.hidden then
return false
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local cx, cy = GetCursorPos()
local dropExtra = self.dropped and (self.height - 4) * #self.list + 2 or 0
return cx >= x and cy >= y and cx < x + self.width and cy < y + self.height + dropExtra, cy < y + self.height
end
function dropDownMeta:OnKeyDown(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
local all, body = self:IsMouseOver()
if not all or (self.dropped and body) then
self.dropped = false
return true
end
self.dropped = true
return false
elseif key == "ESCAPE" then
self.dropped = false
return true
end
return false
end
function dropDownMeta:OnKeyUp(key)
if self.enableFunc and not self.enableFunc() then
return true
end
if key == "LEFTBUTTON" then
local all, body = self:IsMouseOver()
if not all then
self.dropped = false
return true
elseif not body then
local y = type(self.y) == "function" and self:y() or self.y
local sel = math.max(1, math.floor((select(2,GetCursorPos()) - y - self.height) / (self.height - 4)) + 1)
self.sel = sel
if self.selFunc then
self.selFunc(sel, self.list[sel])
end
self.dropped = false
return true
end
end
return false
end
function dropDownMeta:Draw()
if self.hidden then
return false
end
local x = type(self.x) == "function" and self:x() or self.x
local y = type(self.y) == "function" and self:y() or self.y
local enabled = not self.enableFunc or self.enableFunc()
local mOver, mOverBody = self:IsMouseOver()
local dropExtra = (self.height - 4) * #self.list + 4
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver or self.dropped then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, self.width, self.height)
if self.dropped then
DrawImage(nil, x, y + self.height, self.width, dropExtra)
end
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.dropped then
SetDrawColor(0.5, 0.5, 0.5)
elseif mOver then
SetDrawColor(0.33, 0.33, 0.33)
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, self.width - 2, self.height - 2)
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver or self.dropped then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
local x1 = x + self.width - self.height / 2 - self.height / 4
local x2 = x1 + self.height / 2
local y1 = y + self.height / 4
local y2 = y1 + self.height / 2
DrawImageQuad(nil, x1, y1, x2, y1, (x1+x2)/2, y2, (x1+x2)/2, y2)
if self.dropped then
SetDrawColor(0, 0, 0)
DrawImage(nil, x + 1, y + self.height + 1, self.width - 2, dropExtra - 2)
end
if enabled then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.66, 0.66, 0.66)
end
DrawString(x + 2, y + 2, "LEFT", self.height - 4, "VAR", self.list[self.sel] or "")
if self.dropped then
self.hoverSel = mOver and math.floor((select(2,GetCursorPos()) - y - self.height) / (self.height - 4)) + 1
if self.hoverSel and self.hoverSel < 1 then
self.hoverSel = nil
end
for index, val in ipairs(self.list) do
local y = y + self.height + 2 + (index - 1) * (self.height - 4)
if index == self.hoverSel then
SetDrawColor(0.5, 0.4, 0.3)
DrawImage(nil, x + 2, y, self.width - 4, self.height - 4)
end
if index == self.hoverSel or index == self.sel then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.66, 0.66, 0.66)
end
DrawString(x + 2, y, "LEFT", self.height - 4, "VAR", StripEscapes(val))
end
end
end
function dropDownMeta:SelByValue(val)
for index, listVal in ipairs(self.list) do
if listVal == val then
self.sel = index
return
end
end
end
function common.newDropDown(x, y, width, height, list, selFunc, enableFunc)
local control = { }
control.x = x
control.y = y
control.width = width
control.height = height
control.list = list or { }
control.sel = 1
control.selFunc = selFunc
control.enableFunc = enableFunc
return setmetatable(control, dropDownMeta)
end
-- Draw simple popup message box
function common.drawPopup(r, g, b, fmt, ...)
local screenW, screenH = GetScreenSize()
SetDrawColor(0, 0, 0, 0.5)
@@ -375,6 +109,7 @@ function common.drawPopup(r, g, b, fmt, ...)
DrawString(0, oy + 10, "CENTER", 20, "VAR", txt)
end
-- Make a copy of a table and all subtables
function copyTable(tbl)
local out = {}
for k, v in pairs(tbl) do
@@ -387,6 +122,7 @@ function copyTable(tbl)
return out
end
-- Wipe all keys from the table and return it, or return a new table if no table provided
function wipeTable(tbl)
if not tbl then
return { }
@@ -397,6 +133,7 @@ function wipeTable(tbl)
return tbl
end
-- Search a table for a value, and return the corresponding key
function isValueInTable(tbl, val)
for k, v in pairs(tbl) do
if val == v then
@@ -405,6 +142,7 @@ function isValueInTable(tbl, val)
end
end
-- Search an array for a value, and return the corresponding array index
function isValueInArray(tbl, val)
for i, v in ipairs(tbl) do
if val == v then

View File

@@ -12,23 +12,23 @@ local buildMode = { }
buildMode.controls = { }
t_insert(buildMode.controls, common.newButton(4, 4, 60, 20, "<< Back", function()
t_insert(buildMode.controls, common.New("ButtonControl", 4, 4, 60, 20, "<< Back", function()
main:SetMode("LIST", buildMode.dbFileName)
end))
t_insert(buildMode.controls, common.newButton(4 + 68, 4, 60, 20, "Tree", function()
t_insert(buildMode.controls, common.New("ButtonControl", 4 + 68, 4, 60, 20, "Tree", function()
buildMode.viewMode = "TREE"
end, function()
return buildMode.viewMode ~= "TREE"
end))
t_insert(buildMode.controls, common.newButton(4 + 68*2, 4, 60, 20, "Items", function()
t_insert(buildMode.controls, common.New("ButtonControl", 4 + 68*2, 4, 60, 20, "Items", function()
buildMode.viewMode = "ITEMS"
end, function()
return buildMode.viewMode ~= "ITEMS"
end))
t_insert(buildMode.controls, common.newButton(4 + 68*3, 4, 60, 20, "Calcs", function()
t_insert(buildMode.controls, common.New("ButtonControl", 4 + 68*3, 4, 60, 20, "Calcs", function()
buildMode.viewMode = "CALCS"
end, function()
return buildMode.viewMode ~= "CALCS"
@@ -67,7 +67,7 @@ buildMode.controls.pointDisplay = {
end,
}
buildMode.controls.classDrop = common.newDropDown(0, 4, 100, 20, nil, function(index, val)
buildMode.controls.classDrop = common.New("DropDownControl", 0, 4, 100, 20, nil, function(index, val)
local classId = main.tree.classNameMap[val]
if classId ~= buildMode.spec.curClassId then
if buildMode.spec:IsClassConnected(classId) or buildMode.spec:CountAllocNodes() == 0 then
@@ -87,7 +87,7 @@ end, function()
return buildMode.viewMode == "TREE"
end)
buildMode.controls.ascendDrop = common.newDropDown(0, 4, 100, 20, nil, function(index, val)
buildMode.controls.ascendDrop = common.New("DropDownControl", 0, 4, 100, 20, nil, function(index, val)
local ascendClassId = main.tree.ascendNameMap[val].ascendClassId
buildMode.spec:SelectAscendClass(ascendClassId)
buildMode.spec:AddUndoState()
@@ -164,8 +164,8 @@ function buildMode:Init(dbFileName)
self.calcs = LoadModule("Modules/Calcs", launch, cfg, main)
self.calcs:Init(self)
self.tree = main.tree
self.spec = main.SpecClass.NewSpec(main.tree)
self.treeView = main.TreeViewClass.NewTreeView()
self.spec = common.New("PassiveSpec", main.tree)
self.treeView = common.New("PassiveTreeView")
wipeTable(self.controls.classDrop.list)
for classId, class in pairs(self.tree.classes) do

View File

@@ -10,20 +10,20 @@ local vfs = require("vfs")
local listMode = { }
listMode.controls = {
common.newButton(2, 2, 60, 20, "New", function()
common.New("ButtonControl", 2, 2, 60, 20, "New", function()
listMode:New()
end),
common.newButton(66, 2, 60, 20, "Copy", function()
common.New("ButtonControl", 66, 2, 60, 20, "Copy", function()
listMode:CopySel()
end, function()
return listMode.sel ~= nil
end),
common.newButton(130, 2, 60, 20, "Rename", function()
common.New("ButtonControl", 130, 2, 60, 20, "Rename", function()
listMode:RenameSel()
end, function()
return listMode.sel ~= nil
end),
common.newButton(194, 2, 60, 20, "Delete", function()
common.New("ButtonControl", 194, 2, 60, 20, "Delete", function()
listMode:DeleteSel()
end, function()
return listMode.sel ~= nil

View File

@@ -48,7 +48,7 @@ elem.input.cellCol = { 0.1, 0.1, 0.4 }
function elem.input:Init()
local grid = self.grid
if self.format == "choice" then
self.dropDown = common.newDropDown(0, 0, 0, 0, self.list, function(index, val)
self.dropDown = common.New("DropDownControl", 0, 0, 0, 0, self.list, function(index, val)
if val ~= grid.input[self.name] then
grid.input[self.name] = val
grid.changeFlag = true

View File

@@ -10,6 +10,8 @@ data.gems = { }
data.gems["_default"] = {
attack = true,
melee = true,
bow = true,
projectile = true,
base = {
},
quality = {
@@ -95,5 +97,5 @@ local itemTypes = {
"misc"
}
for _, type in pairs(itemTypes) do
LoadModule("items/"..type, data.itemBases)
LoadModule("Items/"..type, data.itemBases)
end

View File

@@ -19,7 +19,7 @@ end
items.slots = { }
items.controls = { }
items.controls.addDisplayItem = common.newButton(0, 0, 60, 20, "Add", function()
items.controls.addDisplayItem = common.New("ButtonControl", 0, 0, 60, 20, "Add", function()
items:AddDisplayItem()
end)
@@ -33,7 +33,7 @@ local function mkItemSlot(x, y, slotName, slotLabel)
slot.height = 20
slot.slotName = slotName
slot.label = slotLabel or slotName
slot.dropDown = common.newDropDown(x, y, slot.width, slot.height, slot.list, function(sel)
slot.dropDown = common.New("DropDownControl", x, y, slot.width, slot.height, slot.list, function(sel)
if slot.items[sel] ~= slot.selItem then
slot.selItem = slot.items[sel]
items:PopulateSlots()

View File

@@ -10,6 +10,13 @@ local m_floor = math.floor
local m_max = math.max
local t_insert = table.insert
LoadModule("Modules/Data")
LoadModule("Modules/ModTools")
LoadModule("Classes/PassiveTree", launch)
LoadModule("Classes/PassiveSpec", launch)
LoadModule("Classes/PassiveTreeView", launch)
local cfg = { }
local main = { }
@@ -80,13 +87,6 @@ function main:DrawTooltip(x, y, w, h, viewPort, col, center)
end
end
LoadModule("Modules/Data")
LoadModule("Modules/ModTools")
main.TreeClass = LoadModule("Modules/Tree", launch, cfg, main)
main.SpecClass = LoadModule("Modules/Spec", launch, cfg, main)
main.TreeViewClass = LoadModule("Modules/TreeView", launch, cfg, main)
main.modes = { }
main.modes["LIST"] = LoadModule("Modules/BuildList", launch, cfg, main)
main.modes["BUILD"] = LoadModule("Modules/Build", launch, cfg, main)
@@ -192,7 +192,7 @@ end
function main:Init()
self.inputEvents = { }
self.tree = self.TreeClass.NewTree()
self.tree = common.New("PassiveTree")
self:SetMode("LIST")

View File

@@ -49,9 +49,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{35D8
Modules\Main.lua = Modules\Main.lua
Modules\ModParser.lua = Modules\ModParser.lua
Modules\ModTools.lua = Modules\ModTools.lua
Modules\Spec.lua = Modules\Spec.lua
Modules\Tree.lua = Modules\Tree.lua
Modules\TreeView.lua = Modules\TreeView.lua
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Classes", "Classes", "{7EE4DE67-DABD-4A3C-8772-37D4A9DB6D59}"
ProjectSection(SolutionItems) = preProject
Classes\ButtonControl.lua = Classes\ButtonControl.lua
Classes\DropDownControl.lua = Classes\DropDownControl.lua
Classes\EditControl.lua = Classes\EditControl.lua
Classes\PassiveSpec.lua = Classes\PassiveSpec.lua
Classes\PassiveTree.lua = Classes\PassiveTree.lua
Classes\PassiveTreeView.lua = Classes\PassiveTreeView.lua
EndProjectSection
EndProject
Global
@@ -62,5 +69,6 @@ Global
{EE9D06F5-7310-4567-AB18-6923BCF0A2A1} = {AE7B8DC6-833A-4817-915D-AAB9B8F17C1B}
{5BCB69A7-EFE8-4C72-B5E3-DF2C1E227290} = {AE7B8DC6-833A-4817-915D-AAB9B8F17C1B}
{35D8BE82-CD83-4C2D-AD31-47026E724F46} = {AE7B8DC6-833A-4817-915D-AAB9B8F17C1B}
{7EE4DE67-DABD-4A3C-8772-37D4A9DB6D59} = {AE7B8DC6-833A-4817-915D-AAB9B8F17C1B}
EndGlobalSection
EndGlobal