920 lines
31 KiB
Lua
920 lines
31 KiB
Lua
-- Path of Building
|
|
--
|
|
-- Module: Main
|
|
-- Main module of program.
|
|
--
|
|
local ipairs = ipairs
|
|
local t_insert = table.insert
|
|
local t_remove = table.remove
|
|
local m_ceil = math.ceil
|
|
local m_floor = math.floor
|
|
local m_max = math.max
|
|
local m_min = math.min
|
|
local m_sin = math.sin
|
|
local m_cos = math.cos
|
|
local m_pi = math.pi
|
|
|
|
LoadModule("GameVersions")
|
|
LoadModule("Modules/Common")
|
|
LoadModule("Modules/Data")
|
|
LoadModule("Modules/ModTools")
|
|
LoadModule("Modules/ItemTools")
|
|
LoadModule("Modules/CalcTools")
|
|
|
|
--[[if launch.devMode then
|
|
for skillName, skill in pairs(data["3_0"].enchantments.Helmet) do
|
|
for _, mod in ipairs(skill.ENDGAME) do
|
|
local modList, extra = modLib.parseMod["3_0"](mod)
|
|
if not modList or extra then
|
|
ConPrintf("%s: '%s' '%s'", skillName, mod, extra or "")
|
|
end
|
|
end
|
|
end
|
|
end]]
|
|
|
|
local tempTable1 = { }
|
|
local tempTable2 = { }
|
|
|
|
main = new("ControlHost")
|
|
|
|
function main:Init()
|
|
self.modes = { }
|
|
self.modes["LIST"] = LoadModule("Modules/BuildList")
|
|
self.modes["BUILD"] = LoadModule("Modules/Build")
|
|
|
|
if launch.devMode or GetScriptPath() == GetRuntimePath() then
|
|
-- If running in dev mode or standalone mode, put user data in the script path
|
|
self.userPath = GetScriptPath().."/"
|
|
else
|
|
self.userPath = GetUserPath().."/Path of Building/"
|
|
MakeDir(self.userPath)
|
|
end
|
|
self.defaultBuildPath = self.userPath.."Builds/"
|
|
self.buildPath = self.defaultBuildPath
|
|
MakeDir(self.buildPath)
|
|
|
|
if launch.devMode and IsKeyDown("CTRL") then
|
|
self.rebuildModCache = true
|
|
else
|
|
-- Load mod caches
|
|
for _, targetVersion in ipairs(targetVersionList) do
|
|
LoadModule("Data/"..targetVersion.."/ModCache", modLib.parseModCache[targetVersion])
|
|
end
|
|
end
|
|
|
|
self.tree = { }
|
|
for _, versionData in pairs(targetVersions) do
|
|
for _, treeVersion in ipairs(versionData.treeVersionList) do
|
|
self.tree[treeVersion] = new("PassiveTree", treeVersion)
|
|
end
|
|
end
|
|
|
|
ConPrintf("Loading item databases...")
|
|
self.uniqueDB = { }
|
|
self.rareDB = { }
|
|
for _, targetVersion in ipairs(targetVersionList) do
|
|
self.uniqueDB[targetVersion] = { list = { } }
|
|
for type, typeList in pairs(data.uniques) do
|
|
for _, raw in pairs(typeList) do
|
|
local newItem = new("Item", targetVersion, "Rarity: Unique\n"..raw)
|
|
if newItem.base then
|
|
newItem:NormaliseQuality()
|
|
self.uniqueDB[targetVersion].list[newItem.name] = newItem
|
|
elseif launch.devMode then
|
|
ConPrintf("Unique DB unrecognised item of type '%s':\n%s", type, raw)
|
|
end
|
|
end
|
|
end
|
|
self.rareDB[targetVersion] = { list = { } }
|
|
for _, raw in pairs(data[targetVersion].rares) do
|
|
local newItem = new("Item", targetVersion, "Rarity: Rare\n"..raw)
|
|
if newItem.base then
|
|
newItem:NormaliseQuality()
|
|
if newItem.crafted then
|
|
if newItem.base.implicit and (#newItem.modLines == 0 or newItem.modLines[1].custom) then
|
|
newItem.implicitLines = 0
|
|
for line in newItem.base.implicit:gmatch("[^\n]+") do
|
|
newItem.implicitLines = newItem.implicitLines + 1
|
|
t_insert(newItem.modLines, newItem.implicitLines, { line = line })
|
|
end
|
|
end
|
|
newItem:Craft()
|
|
end
|
|
self.rareDB[targetVersion].list[newItem.name] = newItem
|
|
elseif launch.devMode then
|
|
ConPrintf("Rare DB unrecognised item:\n%s", raw)
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.rebuildModCache then
|
|
-- Update mod caches
|
|
for _, targetVersion in ipairs(targetVersionList) do
|
|
local out = io.open("Data/"..targetVersion.."/ModCache.lua", "w")
|
|
out:write('local c=...')
|
|
for line, dat in pairs(modLib.parseModCache[targetVersion]) do
|
|
if not dat[1] or not dat[1][1] or dat[1][1].name ~= "JewelFunc" then
|
|
out:write('c["', line:gsub("\n","\\n"), '"]={')
|
|
if dat[1] then
|
|
writeLuaTable(out, dat[1])
|
|
else
|
|
out:write('nil')
|
|
end
|
|
if dat[2] then
|
|
out:write(',"', dat[2]:gsub("\n","\\n"), '"}')
|
|
else
|
|
out:write(',nil}')
|
|
end
|
|
end
|
|
end
|
|
out:close()
|
|
end
|
|
end
|
|
|
|
self.sharedItemList = { }
|
|
self.sharedItemSetList = { }
|
|
|
|
self.anchorMain = new("Control", nil, 4, 0, 0, 0)
|
|
self.anchorMain.y = function()
|
|
return self.screenH - 4
|
|
end
|
|
self.controls.options = new("ButtonControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 0, 0, 70, 20, "Options", function()
|
|
self:OpenOptionsPopup()
|
|
end)
|
|
self.controls.patreon = new("ButtonControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 112, 0, 74, 20, "", function()
|
|
OpenURL("https://www.patreon.com/openarl")
|
|
end)
|
|
self.controls.patreon:SetImage("Assets/patreon_logo.png")
|
|
self.controls.patreon.tooltipText = "Help support the development of Path of Building by pledging a monthly donation!"
|
|
self.controls.about = new("ButtonControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 228, 0, 70, 20, "About", function()
|
|
self:OpenAboutPopup()
|
|
end)
|
|
self.controls.applyUpdate = new("ButtonControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 0, -24, 140, 20, "^x50E050Update Ready", function()
|
|
self:OpenUpdatePopup()
|
|
end)
|
|
self.controls.applyUpdate.shown = function()
|
|
return launch.updateAvailable and launch.updateAvailable ~= "none"
|
|
end
|
|
self.controls.checkUpdate = new("ButtonControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 0, -24, 140, 20, "", function()
|
|
launch:CheckForUpdate()
|
|
end)
|
|
self.controls.checkUpdate.shown = function()
|
|
return not launch.devMode and (not launch.updateAvailable or launch.updateAvailable == "none")
|
|
end
|
|
self.controls.checkUpdate.label = function()
|
|
return launch.updateCheckRunning and launch.updateProgress or "Check for Update"
|
|
end
|
|
self.controls.checkUpdate.enabled = function()
|
|
return not launch.updateCheckRunning
|
|
end
|
|
self.controls.versionLabel = new("LabelControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 144, -27, 0, 14, "")
|
|
self.controls.versionLabel.label = function()
|
|
return "^8Version: "..launch.versionNumber..(launch.versionBranch == "dev" and " (Dev)" or "")
|
|
end
|
|
self.controls.devMode = new("LabelControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 0, -26, 0, 20, "^1Dev Mode")
|
|
self.controls.devMode.shown = function()
|
|
return launch.devMode
|
|
end
|
|
self.controls.dismissToast = new("ButtonControl", {"BOTTOMLEFT",self.anchorMain,"BOTTOMLEFT"}, 0, function() return -self.mainBarHeight + self.toastHeight end, 80, 20, "Dismiss", function()
|
|
self.toastMode = "HIDING"
|
|
self.toastStart = GetTime()
|
|
end)
|
|
self.controls.dismissToast.shown = function()
|
|
return self.toastMode == "SHOWN"
|
|
end
|
|
|
|
self.mainBarHeight = 58
|
|
self.toastMessages = { }
|
|
|
|
if launch.devMode and GetTime() >= 0 and GetTime() < 15000 then
|
|
t_insert(self.toastMessages, [[
|
|
^xFF7700Warning: ^7Developer Mode active!
|
|
The program is currently running in developer
|
|
mode, which is not intended for normal use.
|
|
If you are not expecting this, then you may have
|
|
set up the program from the source .zip instead
|
|
of using one of the installers. If that is the case,
|
|
please reinstall using one of the installers from
|
|
the "Releases" section of the GitHub page.]])
|
|
end
|
|
|
|
self.inputEvents = { }
|
|
self.popups = { }
|
|
self.tooltipLines = { }
|
|
|
|
self.gameAccounts = { }
|
|
|
|
self.buildSortMode = "NAME"
|
|
self.nodePowerTheme = "RED/BLUE"
|
|
|
|
self:SetMode("BUILD", false, "Unnamed build")
|
|
|
|
self:LoadSettings()
|
|
end
|
|
|
|
function main:CanExit()
|
|
local ret = self:CallMode("CanExit", "EXIT")
|
|
if ret ~= nil then
|
|
return ret
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
function main:Shutdown()
|
|
self:CallMode("Shutdown")
|
|
|
|
self:SaveSettings()
|
|
end
|
|
|
|
function main:OnFrame()
|
|
self.screenW, self.screenH = GetScreenSize()
|
|
|
|
while self.newMode do
|
|
if self.mode then
|
|
self:CallMode("Shutdown")
|
|
end
|
|
self.mode = self.newMode
|
|
self.newMode = nil
|
|
self:CallMode("Init", unpack(self.newModeArgs))
|
|
end
|
|
|
|
self.viewPort = { x = 0, y = 0, width = self.screenW, height = self.screenH }
|
|
|
|
if self.popups[1] then
|
|
self.popups[1]:ProcessInput(self.inputEvents, self.viewPort)
|
|
wipeTable(self.inputEvents)
|
|
else
|
|
self:ProcessControlsInput(self.inputEvents, self.viewPort)
|
|
end
|
|
|
|
self:CallMode("OnFrame", self.inputEvents, self.viewPort)
|
|
|
|
if launch.updateErrMsg then
|
|
t_insert(self.toastMessages, string.format("Update check failed!\n%s", launch.updateErrMsg))
|
|
launch.updateErrMsg = nil
|
|
end
|
|
if launch.updateAvailable then
|
|
if launch.updateAvailable == "none" then
|
|
t_insert(self.toastMessages, "No update available\nYou are running the latest version.")
|
|
launch.updateAvailable = nil
|
|
elseif not self.updateAvailableShown then
|
|
t_insert(self.toastMessages, "Update Available\nAn update has been downloaded and is ready\nto be applied.")
|
|
self.updateAvailableShown = true
|
|
end
|
|
end
|
|
|
|
-- Run toasts
|
|
if self.toastMessages[1] then
|
|
if not self.toastMode then
|
|
self.toastMode = "SHOWING"
|
|
self.toastStart = GetTime()
|
|
self.toastHeight = #self.toastMessages[1]:gsub("[^\n]","") * 16 + 20 + 40
|
|
end
|
|
if self.toastMode == "SHOWING" then
|
|
local now = GetTime()
|
|
if now >= self.toastStart + 250 then
|
|
self.toastMode = "SHOWN"
|
|
else
|
|
self.mainBarHeight = 58 + self.toastHeight * (now - self.toastStart) / 250
|
|
end
|
|
end
|
|
if self.toastMode == "SHOWN" then
|
|
self.mainBarHeight = 58 + self.toastHeight
|
|
elseif self.toastMode == "HIDING" then
|
|
local now = GetTime()
|
|
if now >= self.toastStart + 75 then
|
|
self.toastMode = nil
|
|
self.mainBarHeight = 58
|
|
t_remove(self.toastMessages, 1)
|
|
else
|
|
self.mainBarHeight = 58 + self.toastHeight * (1 - (now - self.toastStart) / 75)
|
|
end
|
|
end
|
|
if self.toastMode then
|
|
SetDrawColor(0.85, 0.85, 0.85)
|
|
DrawImage(nil, 0, self.screenH - self.mainBarHeight, 312, self.mainBarHeight)
|
|
SetDrawColor(0.1, 0.1, 0.1)
|
|
DrawImage(nil, 0, self.screenH - self.mainBarHeight + 4, 308, self.mainBarHeight - 4)
|
|
SetDrawColor(1, 1, 1)
|
|
DrawString(4, self.screenH - self.mainBarHeight + 8, "LEFT", 20, "VAR", self.toastMessages[1]:gsub("\n.*",""))
|
|
DrawString(4, self.screenH - self.mainBarHeight + 28, "LEFT", 16, "VAR", self.toastMessages[1]:gsub("^[^\n]*\n?",""))
|
|
end
|
|
end
|
|
|
|
-- Draw main controls
|
|
SetDrawColor(0.85, 0.85, 0.85)
|
|
DrawImage(nil, 0, self.screenH - 58, 312, 58)
|
|
SetDrawColor(0.1, 0.1, 0.1)
|
|
DrawImage(nil, 0, self.screenH - 54, 308, 54)
|
|
self:DrawControls(self.viewPort)
|
|
|
|
if self.popups[1] then
|
|
SetDrawLayer(10)
|
|
SetDrawColor(0, 0, 0, 0.5)
|
|
DrawImage(nil, 0, 0, self.screenW, self.screenH)
|
|
self.popups[1]:Draw(self.viewPort)
|
|
SetDrawLayer(0)
|
|
end
|
|
|
|
if self.showDragText then
|
|
local cursorX, cursorY = GetCursorPos()
|
|
local strWidth = DrawStringWidth(16, "VAR", self.showDragText)
|
|
SetDrawLayer(20, 0)
|
|
SetDrawColor(0.15, 0.15, 0.15, 0.75)
|
|
DrawImage(nil, cursorX, cursorY - 8, strWidth + 2, 18)
|
|
SetDrawColor(1, 1, 1)
|
|
DrawString(cursorX + 1, cursorY - 7, "LEFT", 16, "VAR", self.showDragText)
|
|
self.showDragText = nil
|
|
end
|
|
|
|
--[[local par = 300
|
|
for x = 0, 750 do
|
|
for y = 0, 750 do
|
|
local dpsCol = (x / par * 1.5) ^ 0.5
|
|
local defCol = (y / par * 1.5) ^ 0.5
|
|
local mixCol = (m_max(dpsCol - 0.5, 0) + m_max(defCol - 0.5, 0)) / 2
|
|
if main.nodePowerTheme == "RED/BLUE" then
|
|
SetDrawColor(dpsCol, mixCol, defCol)
|
|
elseif main.nodePowerTheme == "RED/GREEN" then
|
|
SetDrawColor(dpsCol, defCol, mixCol)
|
|
elseif main.nodePowerTheme == "GREEN/BLUE" then
|
|
SetDrawColor(mixCol, dpsCol, defCol)
|
|
end
|
|
DrawImage(nil, x + 500, y + 200, 1, 1)
|
|
end
|
|
end
|
|
SetDrawColor(0, 0, 0)
|
|
DrawImage(nil, par + 500, 200, 2, 750)
|
|
DrawImage(nil, 500, par + 200, 759, 2)]]
|
|
|
|
wipeTable(self.inputEvents)
|
|
end
|
|
|
|
function main:OnKeyDown(key, doubleClick)
|
|
t_insert(self.inputEvents, { type = "KeyDown", key = key, doubleClick = doubleClick })
|
|
end
|
|
|
|
function main:OnKeyUp(key)
|
|
t_insert(self.inputEvents, { type = "KeyUp", key = key })
|
|
end
|
|
|
|
function main:OnChar(key)
|
|
t_insert(self.inputEvents, { type = "Char", key = key })
|
|
end
|
|
|
|
function main:SetMode(newMode, ...)
|
|
self.newMode = newMode
|
|
self.newModeArgs = {...}
|
|
end
|
|
|
|
function main:CallMode(func, ...)
|
|
local modeTbl = self.modes[self.mode]
|
|
if modeTbl and modeTbl[func] then
|
|
return modeTbl[func](modeTbl, ...)
|
|
end
|
|
end
|
|
|
|
function main:LoadSettings()
|
|
local setXML, errMsg = common.xml.LoadXMLFile(self.userPath.."Settings.xml")
|
|
if not setXML then
|
|
return true
|
|
elseif setXML[1].elem ~= "PathOfBuilding" then
|
|
launch:ShowErrMsg("^1Error parsing 'Settings.xml': 'PathOfBuilding' root element missing")
|
|
return true
|
|
end
|
|
for _, node in ipairs(setXML[1]) do
|
|
if type(node) == "table" then
|
|
if node.elem == "Mode" then
|
|
if not node.attrib.mode or not self.modes[node.attrib.mode] then
|
|
launch:ShowErrMsg("^1Error parsing 'Settings.xml': Invalid mode attribute in 'Mode' element")
|
|
return true
|
|
end
|
|
local args = { }
|
|
for _, child in ipairs(node) do
|
|
if type(child) == "table" then
|
|
if child.elem == "Arg" then
|
|
if child.attrib.number then
|
|
t_insert(args, tonumber(child.attrib.number))
|
|
elseif child.attrib.string then
|
|
t_insert(args, child.attrib.string)
|
|
elseif child.attrib.boolean then
|
|
t_insert(args, child.attrib.boolean == "true")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self:SetMode(node.attrib.mode, unpack(args))
|
|
elseif node.elem == "Accounts" then
|
|
self.lastAccountName = node.attrib.lastAccountName
|
|
self.lastRealm = node.attrib.lastRealm
|
|
for _, child in ipairs(node) do
|
|
if child.elem == "Account" then
|
|
self.gameAccounts[child.attrib.accountName] = {
|
|
sessionID = child.attrib.sessionID,
|
|
}
|
|
end
|
|
end
|
|
elseif node.elem == "SharedItems" then
|
|
for _, child in ipairs(node) do
|
|
if child.elem == "Item" then
|
|
local verItem = { raw = "" }
|
|
for _, subChild in ipairs(child) do
|
|
if type(subChild) == "string" then
|
|
verItem.raw = subChild
|
|
end
|
|
end
|
|
for _, targetVersion in ipairs(targetVersionList) do
|
|
verItem[targetVersion] = new("Item", targetVersion, verItem.raw)
|
|
end
|
|
t_insert(self.sharedItemList, verItem)
|
|
elseif child.elem == "ItemSet" then
|
|
local sharedItemSet = { title = child.attrib.title, slots = { } }
|
|
for _, grandChild in ipairs(child) do
|
|
if grandChild.elem == "Item" then
|
|
local verItem = { raw = "" }
|
|
for _, subChild in ipairs(grandChild) do
|
|
if type(subChild) == "string" then
|
|
verItem.raw = subChild
|
|
end
|
|
end
|
|
for _, targetVersion in ipairs(targetVersionList) do
|
|
verItem[targetVersion] = new("Item", targetVersion, verItem.raw)
|
|
end
|
|
sharedItemSet.slots[grandChild.attrib.slotName] = verItem
|
|
end
|
|
end
|
|
t_insert(self.sharedItemSetList, sharedItemSet)
|
|
end
|
|
end
|
|
elseif node.elem == "Misc" then
|
|
if node.attrib.buildSortMode then
|
|
self.buildSortMode = node.attrib.buildSortMode
|
|
end
|
|
launch.proxyURL = node.attrib.proxyURL
|
|
if node.attrib.buildPath then
|
|
self.buildPath = node.attrib.buildPath
|
|
end
|
|
if node.attrib.nodePowerTheme then
|
|
self.nodePowerTheme = node.attrib.nodePowerTheme
|
|
end
|
|
self.showThousandsSidebar = node.attrib.showThousandsSidebar == "true"
|
|
self.showThousandsCalcs = node.attrib.showThousandsCalcs == "true"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function main:SaveSettings()
|
|
local setXML = { elem = "PathOfBuilding" }
|
|
local mode = { elem = "Mode", attrib = { mode = self.mode } }
|
|
for _, val in ipairs({ self:CallMode("GetArgs") }) do
|
|
local child = { elem = "Arg", attrib = { } }
|
|
if type(val) == "number" then
|
|
child.attrib.number = tostring(val)
|
|
elseif type(val) == "boolean" then
|
|
child.attrib.boolean = tostring(val)
|
|
else
|
|
child.attrib.string = tostring(val)
|
|
end
|
|
t_insert(mode, child)
|
|
end
|
|
t_insert(setXML, mode)
|
|
local accounts = { elem = "Accounts", attrib = { lastAccountName = self.lastAccountName, lastRealm = self.lastRealm } }
|
|
for accountName, account in pairs(self.gameAccounts) do
|
|
t_insert(accounts, { elem = "Account", attrib = { accountName = accountName, sessionID = account.sessionID } })
|
|
end
|
|
t_insert(setXML, accounts)
|
|
local sharedItemList = { elem = "SharedItems" }
|
|
for _, verItem in ipairs(self.sharedItemList) do
|
|
t_insert(sharedItemList, { elem = "Item", [1] = verItem.raw })
|
|
end
|
|
for _, sharedItemSet in ipairs(self.sharedItemSetList) do
|
|
local set = { elem = "ItemSet", attrib = { title = sharedItemSet.title } }
|
|
for slotName, verItem in pairs(sharedItemSet.slots) do
|
|
t_insert(set, { elem = "Item", attrib = { slotName = slotName }, [1] = verItem.raw })
|
|
end
|
|
t_insert(sharedItemList, set)
|
|
end
|
|
t_insert(setXML, sharedItemList)
|
|
t_insert(setXML, { elem = "Misc", attrib = {
|
|
buildSortMode = self.buildSortMode,
|
|
proxyURL = launch.proxyURL,
|
|
buildPath = (self.buildPath ~= self.defaultBuildPath and self.buildPath or nil),
|
|
nodePowerTheme = self.nodePowerTheme,
|
|
showThousandsSidebar = tostring(self.showThousandsSidebar),
|
|
showThousandsCalcs = tostring(self.showThousandsCalcs),
|
|
} })
|
|
local res, errMsg = common.xml.SaveXMLFile(setXML, self.userPath.."Settings.xml")
|
|
if not res then
|
|
launch:ShowErrMsg("Error saving 'Settings.xml': %s", errMsg)
|
|
return true
|
|
end
|
|
end
|
|
|
|
function main:OpenOptionsPopup()
|
|
local controls = { }
|
|
controls.proxyType = new("DropDownControl", {"TOPLEFT",nil,"TOPLEFT"}, 150, 20, 80, 18, {
|
|
{ label = "HTTP", scheme = "http" },
|
|
{ label = "SOCKS", scheme = "socks5" },
|
|
})
|
|
controls.proxyLabel = new("LabelControl", {"RIGHT",controls.proxyType,"LEFT"}, -4, 0, 0, 16, "^7Proxy server:")
|
|
controls.proxyURL = new("EditControl", {"LEFT",controls.proxyType,"RIGHT"}, 4, 0, 206, 18)
|
|
if launch.proxyURL then
|
|
local scheme, url = launch.proxyURL:match("(%w+)://(.+)")
|
|
controls.proxyType:SelByValue(scheme, "scheme")
|
|
controls.proxyURL:SetText(url)
|
|
end
|
|
controls.buildPath = new("EditControl", {"TOPLEFT",nil,"TOPLEFT"}, 150, 44, 290, 18)
|
|
controls.buildPathLabel = new("LabelControl", {"RIGHT",controls.buildPath,"LEFT"}, -4, 0, 0, 16, "^7Build save path:")
|
|
if self.buildPath ~= self.defaultBuildPath then
|
|
controls.buildPath:SetText(self.buildPath)
|
|
end
|
|
controls.buildPath.tooltipText = "Overrides the default save location for builds.\nThe default location is: '"..self.defaultBuildPath.."'"
|
|
controls.nodePowerTheme = new("DropDownControl", {"TOPLEFT",nil,"TOPLEFT"}, 150, 68, 100, 18, {
|
|
{ label = "Red & Blue", theme = "RED/BLUE" },
|
|
{ label = "Red & Green", theme = "RED/GREEN" },
|
|
{ label = "Green & Blue", theme = "GREEN/BLUE" },
|
|
}, function(index, value)
|
|
self.nodePowerTheme = value.theme
|
|
end)
|
|
controls.nodePowerThemeLabel = new("LabelControl", {"RIGHT",controls.nodePowerTheme,"LEFT"}, -4, 0, 0, 16, "^7Node Power colours:")
|
|
controls.nodePowerTheme.tooltipText = "Changes the colour scheme used for the node power display on the passive tree."
|
|
controls.nodePowerTheme:SelByValue(self.nodePowerTheme, "theme")
|
|
controls.thousandsLabel = new("LabelControl", {"TOPRIGHT",nil,"TOPLEFT"}, 200, 94, 0, 16, "^7Show thousands separators in:")
|
|
controls.thousandsSidebar = new("CheckBoxControl", {"TOPLEFT",nil,"TOPLEFT"}, 270, 92, 20, "Sidebar:", function(state)
|
|
self.showThousandsSidebar = state
|
|
end)
|
|
controls.thousandsSidebar.state = self.showThousandsSidebar
|
|
controls.thousandsCalcs = new("CheckBoxControl", {"TOPLEFT",nil,"TOPLEFT"}, 370, 92, 20, "Calcs tab:", function(state)
|
|
self.showThousandsCalcs = state
|
|
end)
|
|
controls.thousandsCalcs.state = self.showThousandsCalcs
|
|
local initialNodePowerTheme = self.nodePowerTheme
|
|
local initialThousandsSidebar = self.showThousandsSidebar
|
|
local initialThousandsCalcs = self.showThousandsCalcs
|
|
controls.save = new("ButtonControl", nil, -45, 120, 80, 20, "Save", function()
|
|
if controls.proxyURL.buf:match("%w") then
|
|
launch.proxyURL = controls.proxyType.list[controls.proxyType.selIndex].scheme .. "://" .. controls.proxyURL.buf
|
|
else
|
|
launch.proxyURL = nil
|
|
end
|
|
if controls.buildPath.buf:match("%S") then
|
|
self.buildPath = controls.buildPath.buf
|
|
if not self.buildPath:match("[\\/]$") then
|
|
self.buildPath = self.buildPath .. "/"
|
|
end
|
|
else
|
|
self.buildPath = self.defaultBuildPath
|
|
end
|
|
if self.mode == "LIST" then
|
|
self.modes.LIST:BuildList()
|
|
end
|
|
main:ClosePopup()
|
|
end)
|
|
controls.cancel = new("ButtonControl", nil, 45, 120, 80, 20, "Cancel", function()
|
|
self.nodePowerTheme = initialNodePowerTheme
|
|
self.showThousandsSidebar = initialThousandsSidebar
|
|
self.showThousandsCalcs = initialThousandsCalcs
|
|
main:ClosePopup()
|
|
end)
|
|
self:OpenPopup(450, 150, "Options", controls, "save", nil, "cancel")
|
|
end
|
|
|
|
function main:OpenUpdatePopup()
|
|
local changeList = { }
|
|
for line in io.lines("changelog.txt") do
|
|
local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$")
|
|
if ver then
|
|
if ver == launch.versionNumber then
|
|
break
|
|
end
|
|
if #changeList > 0 then
|
|
t_insert(changeList, { height = 12 })
|
|
end
|
|
t_insert(changeList, { height = 20, "^7Version "..ver.." ("..date..")" })
|
|
else
|
|
t_insert(changeList, { height = 14, "^7"..line })
|
|
end
|
|
end
|
|
local controls = { }
|
|
controls.changeLog = new("TextListControl", nil, 0, 20, 780, 192, nil, changeList)
|
|
controls.update = new("ButtonControl", nil, -45, 220, 80, 20, "Update", function()
|
|
self:ClosePopup()
|
|
local ret = self:CallMode("CanExit", "UPDATE")
|
|
if ret == nil or ret == true then
|
|
launch:ApplyUpdate(launch.updateAvailable)
|
|
end
|
|
end)
|
|
controls.cancel = new("ButtonControl", nil, 45, 220, 80, 20, "Cancel", function()
|
|
self:ClosePopup()
|
|
end)
|
|
controls.patreon = new("ButtonControl", {"BOTTOMLEFT",nil,"BOTTOMLEFT"}, 10, -10, 82, 22, "", function()
|
|
OpenURL("https://www.patreon.com/openarl")
|
|
end)
|
|
controls.patreon:SetImage("Assets/patreon_logo.png")
|
|
controls.patreon.tooltipText = "Help support the development of Path of Building by pledging a monthly donation!"
|
|
self:OpenPopup(800, 250, "Update Available", controls)
|
|
end
|
|
|
|
function main:OpenAboutPopup()
|
|
local changeList = { }
|
|
for line in io.lines("changelog.txt") do
|
|
local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$")
|
|
if ver then
|
|
if #changeList > 0 then
|
|
t_insert(changeList, { height = 10 })
|
|
end
|
|
t_insert(changeList, { height = 18, "^7Version "..ver.." ("..date..")" })
|
|
else
|
|
t_insert(changeList, { height = 12, "^7"..line })
|
|
end
|
|
end
|
|
local controls = { }
|
|
controls.close = new("ButtonControl", {"TOPRIGHT",nil,"TOPRIGHT"}, -10, 10, 50, 20, "Close", function()
|
|
self:ClosePopup()
|
|
end)
|
|
controls.version = new("LabelControl", nil, 0, 18, 0, 18, "Path of Building v"..launch.versionNumber.." by Openarl")
|
|
controls.forum = new("ButtonControl", nil, 0, 42, 420, 18, "Forum Thread: ^x4040FFhttps://www.pathofexile.com/forum/view-thread/1716360", function(control)
|
|
OpenURL("https://www.pathofexile.com/forum/view-thread/1716360")
|
|
end)
|
|
controls.github = new("ButtonControl", nil, 0, 64, 340, 18, "GitHub page: ^x4040FFhttps://github.com/Openarl/PathOfBuilding", function(control)
|
|
OpenURL("https://github.com/Openarl/PathOfBuilding")
|
|
end)
|
|
controls.patreon = new("ButtonControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 10, 82, 22, "", function()
|
|
OpenURL("https://www.patreon.com/openarl")
|
|
end)
|
|
controls.patreon:SetImage("Assets/patreon_logo.png")
|
|
controls.patreon.tooltipText = "Help support the development of Path of Building by pledging a monthly donation!"
|
|
controls.verLabel = new("LabelControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 82, 0, 18, "^7Version history:")
|
|
controls.changelog = new("TextListControl", nil, 0, 100, 630, 290, nil, changeList)
|
|
self:OpenPopup(650, 400, "About", controls)
|
|
end
|
|
|
|
function main:DrawBackground(viewPort)
|
|
SetDrawLayer(nil, -100)
|
|
SetDrawColor(0.5, 0.5, 0.5)
|
|
DrawImage(self.tree["2_6"].assets.Background1.handle, viewPort.x, viewPort.y, viewPort.width, viewPort.height, 0, 0, viewPort.width / 100, viewPort.height / 100)
|
|
SetDrawLayer(nil, 0)
|
|
end
|
|
|
|
function main:DrawArrow(x, y, width, height, dir)
|
|
local x1 = x - width / 2
|
|
local x2 = x + width / 2
|
|
local xMid = (x1 + x2) / 2
|
|
local y1 = y - height / 2
|
|
local y2 = y + height / 2
|
|
local yMid = (y1 + y2) / 2
|
|
if dir == "UP" then
|
|
DrawImageQuad(nil, xMid, y1, xMid, y1, x2, y2, x1, y2)
|
|
elseif dir == "RIGHT" then
|
|
DrawImageQuad(nil, x1, y1, x2, yMid, x2, yMid, x1, y2)
|
|
elseif dir == "DOWN" then
|
|
DrawImageQuad(nil, x1, y1, x2, y1, xMid, y2, xMid, y2)
|
|
elseif dir == "LEFT" then
|
|
DrawImageQuad(nil, x1, yMid, x2, y1, x2, y2, x1, yMid)
|
|
end
|
|
end
|
|
|
|
function main:DrawCheckMark(x, y, size)
|
|
size = size / 0.8
|
|
x = x - size / 2
|
|
y = y - size / 2
|
|
DrawImageQuad(nil, x + size * 0.15, y + size * 0.50, x + size * 0.30, y + size * 0.45, x + size * 0.50, y + size * 0.80, x + size * 0.40, y + size * 0.90)
|
|
DrawImageQuad(nil, x + size * 0.40, y + size * 0.90, x + size * 0.35, y + size * 0.75, x + size * 0.80, y + size * 0.10, x + size * 0.90, y + size * 0.20)
|
|
end
|
|
|
|
do
|
|
local cos45 = m_cos(m_pi / 4)
|
|
local cos35 = m_cos(m_pi * 0.195)
|
|
local sin35 = m_sin(m_pi * 0.195)
|
|
function main:WorldToScreen(x, y, z, width, height)
|
|
-- World -> camera
|
|
local cx = (x - y) * cos45
|
|
local cy = -5.33 - (y + x) * cos45 * cos35 - z * sin35
|
|
local cz = 122 + (y + x) * cos45 * sin35 - z * cos35
|
|
-- Camera -> screen
|
|
local sx = width * 0.5 + cx / cz * 1.27 * height
|
|
local sy = height * 0.5 + cy / cz * 1.27 * height
|
|
return round(sx), round(sy)
|
|
end
|
|
end
|
|
|
|
function main:RenderCircle(x, y, width, height, oX, oY, radius)
|
|
local minX = wipeTable(tempTable1)
|
|
local maxX = wipeTable(tempTable2)
|
|
local minY = height
|
|
local maxY = 0
|
|
for d = 0, 360, 0.15 do
|
|
local r = d / 180 * m_pi
|
|
local px, py = main:WorldToScreen(oX + m_sin(r) * radius, oY + m_cos(r) * radius, 0, width, height)
|
|
if py >= 0 and py < height then
|
|
px = m_min(width, m_max(0, px))
|
|
minY = m_min(minY, py)
|
|
maxY = m_max(maxY, py)
|
|
minX[py] = m_min(minX[py] or px, px)
|
|
maxX[py] = m_max(maxX[py] or px, px)
|
|
end
|
|
end
|
|
for ly = minY, maxY do
|
|
if minX[ly] then
|
|
DrawImage(nil, x + minX[ly], y + ly, maxX[ly] - minX[ly] + 1, 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
function main:RenderRing(x, y, width, height, oX, oY, radius, size)
|
|
local lastX, lastY
|
|
for d = 0, 360, 0.2 do
|
|
local r = d / 180 * m_pi
|
|
local px, py = main:WorldToScreen(oX + m_sin(r) * radius, oY + m_cos(r) * radius, 0, width, height)
|
|
if px >= -size/2 and px < width + size/2 and py >= -size/2 and py < height + size/2 and (px ~= lastX or py ~= lastY) then
|
|
DrawImage(nil, x + px - size/2, y + py, size, size)
|
|
lastX, lastY = px, py
|
|
end
|
|
end
|
|
end
|
|
|
|
function main:StatColor(stat, base, limit)
|
|
if limit and stat > limit then
|
|
return colorCodes.NEGATIVE
|
|
elseif base and stat ~= base then
|
|
return colorCodes.MAGIC
|
|
else
|
|
return "^7"
|
|
end
|
|
end
|
|
|
|
function main:MoveFolder(name, srcPath, dstPath)
|
|
-- Create destination folder
|
|
local res, msg = MakeDir(dstPath..name)
|
|
if not res then
|
|
self:OpenMessagePopup("Error", "Couldn't move '"..name.."' to '"..dstPath.."' : "..msg)
|
|
return
|
|
end
|
|
|
|
-- Move subfolders
|
|
local handle = NewFileSearch(srcPath..name.."/*", true)
|
|
while handle do
|
|
self:MoveFolder(handle:GetFileName(), srcPath..name.."/", dstPath..name.."/")
|
|
if not handle:NextFile() then
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Move files
|
|
handle = NewFileSearch(srcPath..name.."/*")
|
|
while handle do
|
|
local fileName = handle:GetFileName()
|
|
local srcName = srcPath..name.."/"..fileName
|
|
local dstName = dstPath..name.."/"..fileName
|
|
local res, msg = os.rename(srcName, dstName)
|
|
if not res then
|
|
self:OpenMessagePopup("Error", "Couldn't move '"..srcName.."' to '"..dstName.."': "..msg)
|
|
return
|
|
end
|
|
if not handle:NextFile() then
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Remove source folder
|
|
local res, msg = RemoveDir(srcPath..name)
|
|
if not res then
|
|
self:OpenMessagePopup("Error", "Couldn't delete '"..dstPath..name.."' : "..msg)
|
|
return
|
|
end
|
|
end
|
|
|
|
function main:CopyFolder(srcName, dstName)
|
|
-- Create destination folder
|
|
local res, msg = MakeDir(dstName)
|
|
if not res then
|
|
self:OpenMessagePopup("Error", "Couldn't copy '"..srcName.."' to '"..dstName.."' : "..msg)
|
|
return
|
|
end
|
|
|
|
-- Copy subfolders
|
|
local handle = NewFileSearch(srcName.."/*", true)
|
|
while handle do
|
|
local fileName = handle:GetFileName()
|
|
self:CopyFolder(srcName.."/"..fileName, dstName.."/"..fileName)
|
|
if not handle:NextFile() then
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Copy files
|
|
handle = NewFileSearch(srcName.."/*")
|
|
while handle do
|
|
local fileName = handle:GetFileName()
|
|
local srcName = srcName.."/"..fileName
|
|
local dstName = dstName.."/"..fileName
|
|
local res, msg = copyFile(srcName, dstName)
|
|
if not res then
|
|
self:OpenMessagePopup("Error", "Couldn't copy '"..srcName.."' to '"..dstName.."': "..msg)
|
|
return
|
|
end
|
|
if not handle:NextFile() then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
function main:OpenPopup(width, height, title, controls, enterControl, defaultControl, escapeControl)
|
|
local popup = new("PopupDialog", width, height, title, controls, enterControl, defaultControl, escapeControl)
|
|
t_insert(self.popups, 1, popup)
|
|
return popup
|
|
end
|
|
|
|
function main:ClosePopup()
|
|
t_remove(self.popups, 1)
|
|
end
|
|
|
|
function main:OpenMessagePopup(title, msg)
|
|
local controls = { }
|
|
local numMsgLines = 0
|
|
for line in string.gmatch(msg .. "\n", "([^\n]*)\n") do
|
|
t_insert(controls, new("LabelControl", nil, 0, 20 + numMsgLines * 16, 0, 16, line))
|
|
numMsgLines = numMsgLines + 1
|
|
end
|
|
controls.close = new("ButtonControl", nil, 0, 40 + numMsgLines * 16, 80, 20, "Ok", function()
|
|
main:ClosePopup()
|
|
end)
|
|
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "close")
|
|
end
|
|
|
|
function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm)
|
|
local controls = { }
|
|
local numMsgLines = 0
|
|
for line in string.gmatch(msg .. "\n", "([^\n]*)\n") do
|
|
t_insert(controls, new("LabelControl", nil, 0, 20 + numMsgLines * 16, 0, 16, line))
|
|
numMsgLines = numMsgLines + 1
|
|
end
|
|
local confirmWidth = m_max(80, DrawStringWidth(16, "VAR", confirmLabel) + 10)
|
|
controls.confirm = new("ButtonControl", nil, -5 - m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20, confirmLabel, function()
|
|
main:ClosePopup()
|
|
onConfirm()
|
|
end)
|
|
t_insert(controls, new("ButtonControl", nil, 5 + m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20, "Cancel", function()
|
|
main:ClosePopup()
|
|
end))
|
|
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "confirm")
|
|
end
|
|
|
|
function main:OpenNewFolderPopup(path, onClose)
|
|
local controls = { }
|
|
controls.label = new("LabelControl", nil, 0, 20, 0, 16, "^7Enter folder name:")
|
|
controls.edit = new("EditControl", nil, 0, 40, 350, 20, nil, nil, "\\/:%*%?\"<>|%c", 100, function(buf)
|
|
controls.create.enabled = buf:match("%S")
|
|
end)
|
|
controls.create = new("ButtonControl", nil, -45, 70, 80, 20, "Create", function()
|
|
local newFolderName = controls.edit.buf
|
|
local res, msg = MakeDir(path..newFolderName)
|
|
if not res then
|
|
main:OpenMessagePopup("Error", "Couldn't create '"..newFolderName.."': "..msg)
|
|
return
|
|
end
|
|
if onClose then
|
|
onClose(newFolderName)
|
|
end
|
|
main:ClosePopup()
|
|
end)
|
|
controls.create.enabled = false
|
|
controls.cancel = new("ButtonControl", nil, 45, 70, 80, 20, "Cancel", function()
|
|
if onClose then
|
|
onClose()
|
|
end
|
|
main:ClosePopup()
|
|
end)
|
|
main:OpenPopup(370, 100, "New Folder", controls, "create", "edit", "cancel")
|
|
end
|
|
|
|
do
|
|
local wrapTable = { }
|
|
function main:WrapString(str, height, width)
|
|
wipeTable(wrapTable)
|
|
local lineStart = 1
|
|
local lastSpace, lastBreak
|
|
while true do
|
|
local s, e = str:find("%s+", lastSpace)
|
|
if not s then
|
|
s = #str + 1
|
|
e = #str + 1
|
|
end
|
|
if DrawStringWidth(height, "VAR", str:sub(lineStart, s - 1)) > width then
|
|
t_insert(wrapTable, str:sub(lineStart, lastBreak))
|
|
lineStart = lastSpace
|
|
end
|
|
if s > #str then
|
|
t_insert(wrapTable, str:sub(lineStart, -1))
|
|
break
|
|
end
|
|
lastBreak = s - 1
|
|
lastSpace = e + 1
|
|
end
|
|
return wrapTable
|
|
end
|
|
end
|
|
|
|
return main |