-- 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") LoadModule("Modules/PantheonTools") --[[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