diff --git a/.gitignore b/.gitignore index 8369c337..7ba880c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -*.xml +Settings.xml *.cfg Data/ +Builds/ # User-specific files *.suo diff --git a/Art/ring.png b/Assets/ring.png similarity index 100% rename from Art/ring.png rename to Assets/ring.png diff --git a/Classes/PassiveSpec.lua b/Classes/PassiveSpec.lua index 8b8acd4d..6038a63c 100644 --- a/Classes/PassiveSpec.lua +++ b/Classes/PassiveSpec.lua @@ -103,6 +103,7 @@ function SpecClass:SelectClass(classId) end self.curClassId = classId local class = self.tree.classes[classId] + self.curClassName = class.name local startNode = self.nodes[class.startNodeId] startNode.alloc = true self.allocNodes[startNode.id] = startNode @@ -112,6 +113,7 @@ end function SpecClass:SelectAscendClass(ascendClassId) self.curAscendClassId = ascendClassId local ascendClass = self.tree.classes[self.curClassId].classes[tostring(ascendClassId)] or { name = "" } + self.curAscendClassName = ascendClass.name for id, node in pairs(self.allocNodes) do if node.ascendancyName and node.ascendancyName ~= ascendClass.name then node.alloc = false diff --git a/Classes/PassiveTree.lua b/Classes/PassiveTree.lua index 5090192a..9abbe1ed 100644 --- a/Classes/PassiveTree.lua +++ b/Classes/PassiveTree.lua @@ -59,7 +59,7 @@ local TreeClass = common.NewClass("PassiveTree", function(self) self.classes = { } for id, data in pairs(common.json.decode(classText)) do self.classes[tonumber(id)] = data - data.classes["0"] = { name = "None "} + data.classes["0"] = { name = "None" } end self.size = m_min(self.max_x - self.min_x, self.max_y - self.min_y) * 1.1 @@ -180,7 +180,7 @@ local TreeClass = common.NewClass("PassiveTree", function(self) local line = node.sd[i] local list, extra if line:match("\n") then - list, extra = mod.parseMod(line:gsub("\n", " ")) + list, extra = modLib.parseMod(line:gsub("\n", " ")) if list and not extra then node.sd[i] = line:gsub("\n", " ") else @@ -190,10 +190,10 @@ local TreeClass = common.NewClass("PassiveTree", function(self) table.insert(node.sd, si, subLine) si = si + 1 end - list, extra = mod.parseMod(node.sd[i]) + list, extra = modLib.parseMod(node.sd[i]) end else - list, extra = mod.parseMod(line) + list, extra = modLib.parseMod(line) end if not list then node.unknown = true @@ -229,7 +229,7 @@ local TreeClass = common.NewClass("PassiveTree", function(self) for ascendClassId, ascendClass in pairs(class.classes) do self.ascendNameMap[ascendClass.name] = { classId = classId, - ascendClassId = ascendClassId + ascendClassId = tonumber(ascendClassId) } end end diff --git a/Classes/PassiveTreeView.lua b/Classes/PassiveTreeView.lua index 51a29945..bd38b138 100644 --- a/Classes/PassiveTreeView.lua +++ b/Classes/PassiveTreeView.lua @@ -14,7 +14,7 @@ local t_insert = table.insert local TreeViewClass = common.NewClass("PassiveTreeView", function(self) self.ring = NewImageHandle() - self.ring:Load("Art/ring.png") + self.ring:Load("Assets/ring.png") self.zoomLevel = 3 self.zoom = 1.2 ^ self.zoomLevel @@ -238,13 +238,11 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents) SetDrawColor(1, 1, 1) DrawImage(bg.handle, viewPort.x, viewPort.y, viewPort.width, viewPort.height, (self.zoomX + viewPort.width/2) / -bgSize, (self.zoomY + viewPort.height/2) / -bgSize, (viewPort.width/2 - self.zoomX) / bgSize, (viewPort.height/2 - self.zoomY) / bgSize) - local curAscendName = tree.classes[spec.curClassId].classes[tostring(spec.curAscendClassId)].name - for _, group in pairs(tree.groups) do local scrX, scrY = treeToScreen(group.x, group.y) if group.ascendancyName then if group.isAscendancyStart then - if group.ascendancyName ~= curAscendName then + if group.ascendancyName ~= spec.curAscendClassName then SetDrawColor(1, 1, 1, 0.25) end self:DrawAsset(tree.assets["Classes"..group.ascendancyName], scrX, scrY, scale) @@ -276,7 +274,7 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents) conn.c[7], conn.c[8] = treeToScreen(vert[7], vert[8]) if hoverDep and hoverDep[node1] and hoverDep[node2] then SetDrawColor(1, 0, 0) - elseif conn.ascendancyName and conn.ascendancyName ~= curAscendName then + elseif conn.ascendancyName and conn.ascendancyName ~= spec.curAscendClassName then SetDrawColor(0.75, 0.75, 0.75) end DrawImageQuad(tree.assets[conn.type..state].handle, unpack(conn.c)) @@ -318,7 +316,7 @@ function TreeViewClass:DrawTree(build, viewPort, inputEvents) end end local scrX, scrY = treeToScreen(node.x, node.y) - if node.ascendancyName and node.ascendancyName ~= curAscendName then + if node.ascendancyName and node.ascendancyName ~= spec.curAscendClassName then SetDrawColor(0.5, 0.5, 0.5) end if IsKeyDown("ALT") then diff --git a/Gems/act_dex.lua b/Gems/act_dex.lua index 435ffe80..51c75b33 100644 --- a/Gems/act_dex.lua +++ b/Gems/act_dex.lua @@ -414,7 +414,49 @@ gems["Cyclone"] = { } } gems["Desecrate"] = { - unsupported = true, + spell = true, + aoe = true, + duration = true, + chaos = true, + base = { + skill_castTime = 1, + skill_durationBase = 5, + }, + quality = { + castSpeedInc = 1, + }, + levels = { + [1] = { skill_manaCostBase = 8, skill_chaosDotBase = 8.2, }, + [2] = { skill_manaCostBase = 8, skill_chaosDotBase = 11.3, }, + [3] = { skill_manaCostBase = 9, skill_chaosDotBase = 15.4, }, + [4] = { skill_manaCostBase = 9, skill_chaosDotBase = 20.6, }, + [5] = { skill_manaCostBase = 10, skill_chaosDotBase = 25.5, }, + [6] = { skill_manaCostBase = 11, skill_chaosDotBase = 31.4, }, + [7] = { skill_manaCostBase = 12, skill_chaosDotBase = 38.5, }, + [8] = { skill_manaCostBase = 12, skill_chaosDotBase = 46.9, }, + [9] = { skill_manaCostBase = 13, skill_chaosDotBase = 57, }, + [10] = { skill_manaCostBase = 14, skill_chaosDotBase = 69, }, + [11] = { skill_manaCostBase = 15, skill_chaosDotBase = 83.4, }, + [12] = { skill_manaCostBase = 16, skill_chaosDotBase = 100.5, }, + [13] = { skill_manaCostBase = 17, skill_chaosDotBase = 120.7, }, + [14] = { skill_manaCostBase = 18, skill_chaosDotBase = 144.8, }, + [15] = { skill_manaCostBase = 18, skill_chaosDotBase = 163.2, }, + [16] = { skill_manaCostBase = 18, skill_chaosDotBase = 183.9, }, + [17] = { skill_manaCostBase = 19, skill_chaosDotBase = 207, }, + [18] = { skill_manaCostBase = 19, skill_chaosDotBase = 232.8, }, + [19] = { skill_manaCostBase = 20, skill_chaosDotBase = 261.7, }, + [20] = { skill_manaCostBase = 20, skill_chaosDotBase = 294, }, + [21] = { skill_manaCostBase = 21, skill_chaosDotBase = 330.1, }, + [22] = { skill_manaCostBase = 22, skill_chaosDotBase = 370.3, }, + [23] = { skill_manaCostBase = 22, skill_chaosDotBase = 415.2, }, + [24] = { skill_manaCostBase = 22, skill_chaosDotBase = 465.3, }, + [25] = { skill_manaCostBase = 23, skill_chaosDotBase = 521.2, }, + [26] = { skill_manaCostBase = 23, skill_chaosDotBase = 583.5, }, + [27] = { skill_manaCostBase = 24, skill_chaosDotBase = 653, }, + [28] = { skill_manaCostBase = 25, skill_chaosDotBase = 730.4, }, + [29] = { skill_manaCostBase = 25, skill_chaosDotBase = 816.6, }, + [30] = { skill_manaCostBase = 26, skill_chaosDotBase = 912.6, }, + } } gems["Detonate Dead"] = { unsupported = true, diff --git a/Gems/act_str.lua b/Gems/act_str.lua index 7e8f4f08..f283ef7f 100644 --- a/Gems/act_str.lua +++ b/Gems/act_str.lua @@ -541,7 +541,48 @@ gems["Infernal Blow"] = { } } gems["Leap Slam"] = { - unsupported = true, + attack = true, + melee = true, + aoe = true, + movement = true, + base = { + skill_attackTime = 1.4, + skill_manaCostBase = 15, + }, + quality = { + }, + levels = { + [1] = { attack_damageMore = 1, }, + [2] = { attack_damageMore = 1.012, }, + [3] = { attack_damageMore = 1.024, }, + [4] = { attack_damageMore = 1.036, }, + [5] = { attack_damageMore = 1.048, }, + [6] = { attack_damageMore = 1.06, }, + [7] = { attack_damageMore = 1.072, }, + [8] = { attack_damageMore = 1.084, }, + [9] = { attack_damageMore = 1.096, }, + [10] = { attack_damageMore = 1.108, }, + [11] = { attack_damageMore = 1.12, }, + [12] = { attack_damageMore = 1.132, }, + [13] = { attack_damageMore = 1.144, }, + [14] = { attack_damageMore = 1.156, }, + [15] = { attack_damageMore = 1.168, }, + [16] = { attack_damageMore = 1.18, }, + [17] = { attack_damageMore = 1.192, }, + [18] = { attack_damageMore = 1.204, }, + [19] = { attack_damageMore = 1.216, }, + [20] = { attack_damageMore = 1.228, }, + [21] = { attack_damageMore = 1.24, }, + [22] = { attack_damageMore = 1.252, }, + [23] = { attack_damageMore = 1.264, }, + [24] = { attack_damageMore = 1.276, }, + [25] = { attack_damageMore = 1.288, }, + [26] = { attack_damageMore = 1.3, }, + [27] = { attack_damageMore = 1.312, }, + [28] = { attack_damageMore = 1.324, }, + [29] = { attack_damageMore = 1.336, }, + [30] = { attack_damageMore = 1.348, }, + } } gems["Molten Shell"] = { unsupported = true, diff --git a/Gems/other.lua b/Gems/other.lua index acc95a04..7075df5a 100644 --- a/Gems/other.lua +++ b/Gems/other.lua @@ -1,6 +1,6 @@ -- Path of Building -- --- Other skills +-- Other active skills -- Skill gem data (c) Grinding Gear Games -- local gems = ... @@ -42,7 +42,7 @@ gems["Detonate Mines"] = { gems["Portal"] = { spell = true, base = { - spell_castTine = 2.5, + spell_castTime = 2.5, }, quality = { castSpeedInc = 3, diff --git a/Launch.lua b/Launch.lua index 0e3be1f5..b519bee7 100644 --- a/Launch.lua +++ b/Launch.lua @@ -9,8 +9,6 @@ SetWindowTitle("PathOfBuilding") ConExecute("vid_mode 1") ConExecute("vid_resizable 3") -LoadModule("Common") - local launch = { } SetMainObject(launch) @@ -47,7 +45,7 @@ function launch:OnFrame() end if self.promptMsg then local r, g, b = unpack(self.promptCol) - common.drawPopup(r, g, b, "^0%s", self.promptMsg) + self:DrawPopup(r, g, b, "^0%s", self.promptMsg) end if self.doReload then local screenW, screenH = GetScreenSize() @@ -128,3 +126,21 @@ function launch:ShowErrMsg(fmt, ...) end end) end + +function launch:DrawPopup(r, g, b, fmt, ...) + local screenW, screenH = GetScreenSize() + SetDrawColor(0, 0, 0, 0.5) + DrawImage(nil, 0, 0, screenW, screenH) + local txt = string.format(fmt, ...) + local w = DrawStringWidth(20, "VAR", txt) + 20 + local h = (#txt:gsub("[^\n]","") + 2) * 20 + local ox = (screenW - w) / 2 + local oy = (screenH - h) / 2 + SetDrawColor(1, 1, 1) + DrawImage(nil, ox, oy, w, h) + SetDrawColor(r, g, b) + DrawImage(nil, ox + 2, oy + 2, w - 4, h - 4) + SetDrawColor(1, 1, 1) + DrawImage(nil, ox + 4, oy + 4, w - 8, h - 8) + DrawString(0, oy + 10, "CENTER", 20, "VAR", txt) +end diff --git a/Modules/Build.lua b/Modules/Build.lua index c0bbcb0d..88b8f82c 100644 --- a/Modules/Build.lua +++ b/Modules/Build.lua @@ -10,7 +10,7 @@ local t_insert = table.insert local buildMode = { } -function buildMode:Init(dbFileName) +function buildMode:Init(dbFileName, buildName) self.abortSave = true self.items = LoadModule("Modules/Items", launch, main) @@ -23,7 +23,7 @@ function buildMode:Init(dbFileName) self.controls = { } t_insert(self.controls, common.New("ButtonControl", 4, 4, 60, 20, "<< Back", function() - main:SetMode("LIST", self.dbFileName) + main:SetMode("LIST", self.buildName) end)) t_insert(self.controls, common.New("ButtonControl", 4 + 68, 4, 60, 20, "Tree", function() self.viewMode = "TREE" @@ -44,14 +44,13 @@ function buildMode:Init(dbFileName) x = 4 + 68*4, y = 4, Draw = function(control) - local buildName = self.dbFileName:gsub(".xml","") - local bnw = DrawStringWidth(16, "VAR", buildName) + local bnw = DrawStringWidth(16, "VAR", self.buildName) SetDrawColor(0.5, 0.5, 0.5) DrawImage(nil, control.x + 91, control.y, bnw + 6, 20) SetDrawColor(0, 0, 0) DrawImage(nil, control.x + 92, control.y + 1, bnw + 4, 18) SetDrawColor(1, 1, 1) - DrawString(control.x, control.y + 2, "LEFT", 16, "VAR", "Current build: "..buildName.." "..((self.calcs.modFlag or self.spec.modFlag or self.items.modFlag) and "(Unsaved)" or "")) + DrawString(control.x, control.y + 2, "LEFT", 16, "VAR", "Current build: "..self.buildName.." "..((self.calcs.modFlag or self.spec.modFlag or self.items.modFlag) and "(Unsaved)" or "")) end, }) self.controls.pointDisplay = { @@ -123,6 +122,8 @@ function buildMode:Init(dbFileName) { mod = "total_evasion", label = "Evasion rating", fmt = "d" }, { mod = "total_armour", label = "Armour", fmt = "d" }, { mod = "total_blockChance", label = "Block Chance", fmt = "d%%" }, + { mod = "total_dodgeAttacks", label = "Attack Dodge Chance", fmt = "d%%" }, + { mod = "total_dodgeSpells", label = "Spell Dodge Chance", fmt = "d%%" }, { }, { mod = "total_fireResist", label = "Fire Resistance", fmt = "d%%" }, { mod = "total_coldResist", label = "Cold Resistance", fmt = "d%%" }, @@ -133,6 +134,7 @@ function buildMode:Init(dbFileName) self.viewMode = "TREE" self.dbFileName = dbFileName + self.buildName = buildName ConPrintf("Loading '%s'...", dbFileName) self.savers = { @@ -187,16 +189,14 @@ function buildMode:OnFrame(inputEvents) end end - local class = main.tree.classes[self.spec.curClassId] - local ascendClass = class.classes[tostring(self.spec.curAscendClassId)] wipeTable(self.controls.ascendDrop.list) for _, ascendClass in pairs(main.tree.classes[self.spec.curClassId].classes) do t_insert(self.controls.ascendDrop.list, ascendClass.name) end table.sort(self.controls.ascendDrop.list) - self.controls.classDrop:SelByValue(class.name) - self.controls.ascendDrop:SelByValue(ascendClass and ascendClass.name or "None") + self.controls.classDrop:SelByValue(self.spec.curClassName) + self.controls.ascendDrop:SelByValue(self.spec.curAscendClassName) self.controls.pointDisplay.x = main.screenW / 2 + 6 self.controls.classDrop.x = self.controls.pointDisplay.x + 154 @@ -276,6 +276,7 @@ function buildMode:LoadDB() end end end + function buildMode:SaveDB() local dbXML = { elem = "PathOfBuilding" } for elem, saver in pairs(self.savers) do @@ -295,12 +296,13 @@ function buildMode:Load(xml, fileName) self.viewMode = xml.attrib.viewMode end end + function buildMode:Save(xml) xml.attrib = { viewMode = self.viewMode, - className = self.tree.classes[self.spec.curClassId].name, - ascendClassName = self.spec.curAscendClassId > 0 and self.tree.classes[self.spec.curClassId].classes[tostring(self.spec.curAscendClassId)].name, - level = tostring(self.calcs.input.player_level or 1) + level = tostring(self.calcs.input.player_level or 1), + className = self.spec.curClassName, + ascendClassName = self.spec.curAscendClassName, } end diff --git a/Modules/BuildList.lua b/Modules/BuildList.lua index c05a38cf..8768dc1a 100644 --- a/Modules/BuildList.lua +++ b/Modules/BuildList.lua @@ -5,34 +5,38 @@ -- local launch, main = ... +local ipairs = ipairs local t_insert = table.insert -local vfs = require("vfs") - local listMode = { } -function listMode:Init(selFileName) +function listMode:Init(selBuildName) self:BuildList() - self:SelFileByName(selFileName) + self:SelByFileName(selBuildName..".xml") self.controls = { } - t_insert(self.controls, common.New("ButtonControl", 2, 2, 60, 20, "New", function() - listMode:New() - end)) - t_insert(self.controls, common.New("ButtonControl", 66, 2, 60, 20, "Copy", function() - listMode:CopySel() + t_insert(self.controls, common.New("ButtonControl", 4, 4, 60, 20, "Open", function() + self:LoadSel() end, function() - return listMode.sel ~= nil + return self.sel ~= nil end)) - t_insert(self.controls, common.New("ButtonControl", 130, 2, 60, 20, "Rename", function() - listMode:RenameSel() - end, function() - return listMode.sel ~= nil + t_insert(self.controls, common.New("ButtonControl", 4 + 68, 4, 60, 20, "New", function() + self:New() end)) - t_insert(self.controls, common.New("ButtonControl", 194, 2, 60, 20, "Delete", function() - listMode:DeleteSel() + t_insert(self.controls, common.New("ButtonControl", 4 + 68*2, 4, 60, 20, "Copy", function() + self:CopySel() end, function() - return listMode.sel ~= nil + return self.sel ~= nil + end)) + t_insert(self.controls, common.New("ButtonControl", 4 + 68*3, 4, 60, 20, "Rename", function() + self:RenameSel() + end, function() + return self.sel ~= nil + end)) + t_insert(self.controls, common.New("ButtonControl", 4 + 68*4, 4, 60, 20, "Delete", function() + self:DeleteSel() + end, function() + return self.sel ~= nil end)) end @@ -54,7 +58,7 @@ function listMode:OnFrame(inputEvents) end common.controlsDraw(self) for index, build in ipairs(self.list) do - local y = 4 + index * 20 + local y = 8 + index * 20 if self.sel == index then SetDrawColor(1, 1, 1) else @@ -75,8 +79,8 @@ function listMode:OnFrame(inputEvents) else SetDrawColor(0.8, 0.8, 0.8) end - DrawString(4, y + 2, "LEFT", 16, "VAR", build.fileName:gsub(".xml","")) - DrawString(304, y + 2, "LEFT", 16, "VAR", string.format("Level %d %s", build.level, build.ascendClassName or build.className or "?")) + DrawString(4, y + 2, "LEFT", 16, "VAR", build.fileName:gsub("%.xml$","")) + DrawString(304, y + 2, "LEFT", 16, "VAR", string.format("Level %d %s", build.level, (build.ascendClassName ~= "None" and build.ascendClassName) or build.className or "?")) end end end @@ -102,19 +106,18 @@ function listMode:OnKeyDown(key, doubleClick) end self.sel = nil for index, fileName in ipairs(self.list) do - local y = 4 + index * 20 + local y = 8 + index * 20 if cy >= y and cy < y + 20 then + self.sel = index if doubleClick then - main:SetMode("BUILD", self.list[index].fileName) - else - self.sel = index + self:LoadSel() end return end end elseif key == "RETURN" then if self.sel then - main:SetMode("BUILD", self.list[self.sel].fileName) + self:LoadSel() end elseif key == "UP" then if not self.sel then @@ -155,23 +158,28 @@ function listMode:OnChar(key) end function listMode:BuildList() - self.list = { } - vfs.scan(true) - for _, file in ipairs(vfs.root.files) do - if file.name:lower():match("%.xml$") and file.name:lower() ~= "settings.xml" then - local build = { } - build.fileName = file.name - local dbXML, errMsg = common.xml.LoadXMLFile(file.name) - if dbXML and dbXML[1].elem == "PathOfBuilding" then - for _, node in ipairs(dbXML[1]) do - if type(node) == "table" and node.elem == "Build" then - build.className = node.attrib.className - build.ascendClassName = node.attrib.ascendClassName - build.level = tonumber(node.attrib.level) or 1 - end - end + self.list = wipeTable(self.list) + local handle = NewFileSearch(main.buildPath.."*.xml") + if not handle then + return + end + while true do + local fileName = handle:GetFileName() + local build = { } + build.fileName = fileName + local dbXML = common.xml.LoadXMLFile(main.buildPath..fileName) + if dbXML and dbXML[1].elem == "PathOfBuilding" then + for _, node in ipairs(dbXML[1]) do + if type(node) == "table" and node.elem == "Build" then + build.level = tonumber(node.attrib.level) or 1 + build.className = node.attrib.className + build.ascendClassName = node.attrib.ascendClassName + end end - table.insert(self.list, build) + end + table.insert(self.list, build) + if not handle:NextFile() then + break end end self:SortList() @@ -181,11 +189,11 @@ function listMode:SortList() local oldSelFileName = self.sel and self.list[self.sel].fileName table.sort(self.list, function(a, b) return a.fileName:upper() < b.fileName:upper() end) if oldSelFileName then - self:SelFileByName(oldSelFileName) + self:SelByFileName(oldSelFileName) end end -function listMode:SelFileByName(selFileName) +function listMode:SelByFileName(selFileName) self.sel = nil for index, build in ipairs(self.list) do if build.fileName == selFileName then @@ -198,7 +206,7 @@ end function listMode:EditInit(finFunc) self.edit = self.sel self.editFinFunc = finFunc - self.editField = common.newEditField(self.list[self.sel].fileName:gsub(".xml$",""), nil, "[%w _+.()]") + self.editField = common.newEditField(self.list[self.sel].fileName:gsub("%.xml$",""), nil, "[%w _+.()]") self.editField.x = 2 self.editField.y = 6 + self.sel * 20 self.editField.width = main.screenW @@ -246,11 +254,15 @@ function listMode:New() end) end +function listMode:LoadSel() + main:SetMode("BUILD", main.buildPath..self.list[self.sel].fileName, self.list[self.sel].fileName:gsub("%.xml$","")) +end + function listMode:CopySel() local srcName = self.list[self.sel].fileName table.insert(self.list, self.sel + 1, copyTable(self.list[self.sel])) self.sel = self.sel + 1 - self.list[self.sel].fileName = srcName:gsub(".xml$","") .. " (copy)" + self.list[self.sel].fileName = srcName:gsub("%.xml$","") .. " (copy)" self:EditInit(function(buf) if #buf < 1 then return "No name entered" diff --git a/Modules/CalcsControl.lua b/Modules/CalcsControl.lua index f91897a8..73848e77 100644 --- a/Modules/CalcsControl.lua +++ b/Modules/CalcsControl.lua @@ -5,20 +5,20 @@ -- local grid = ... +local pairs = pairs +local ipairs = ipairs local m_abs = math.abs local m_ceil = math.ceil local m_floor = math.floor local m_min = math.min local m_max = math.max -local pairs = pairs -local ipairs = ipairs local t_insert = table.insert -local mod_listMerge = mod.listMerge -local mod_dbMerge = mod.dbMerge -local mod_dbUnmerge = mod.dbUnmerge -local mod_dbMergeList = mod.dbMergeList -local mod_dbUnmergeList = mod.dbUnmergeList +local mod_listMerge = modLib.listMerge +local mod_dbMerge = modLib.dbMerge +local mod_dbUnmerge = modLib.dbUnmerge +local mod_dbMergeList = modLib.dbMergeList +local mod_dbUnmergeList = modLib.dbUnmergeList local setViewMode = LoadModule("Modules/CalcsView", grid) @@ -31,14 +31,15 @@ local function parseGemSpec(spec, out) for nameSpec, numSpec in spec:gmatch("(%a[%a ]*)%s+([%d/\\]+)") do -- Search for gem name using increasingly broad search patterns local patternList = { - "^"..nameSpec.."$", -- Exact match - "^"..nameSpec:gsub("%l", "%l*%0"), -- Abbreviated words ("CldFr" -> "Cold to Fire") + "^ "..nameSpec.."$", -- Exact match + "^"..nameSpec:gsub("%a", " %0%%l+").."$", -- Simple abbreviation ("CtF" -> "Cold to Fire") + "^"..nameSpec:gsub("%l", "%%l*%0").."%l+$", -- Abbreviated words ("CldFr" -> "Cold to Fire") "^"..nameSpec:gsub("%a", ".*%0") -- Global abbreviation ("CtoF" -> "Cold to Fire") } local gemName, gemData - for _, pattern in ipairs(patternList) do + for i, pattern in ipairs(patternList) do for name, data in pairs(data.gems) do - if name:match(pattern) then + if (" "..name):match(pattern) then if gemName then return "Ambiguous gem name '"..nameSpec.."'\nMatches '"..gemName.."', '"..name.."'" end @@ -793,7 +794,7 @@ local function calcSetup(env, output) env.condModList = condModList if modDB.condMod then for k, v in pairs(modDB.condMod) do - local isNot, condName, modName = mod.getCondName(k) + local isNot, condName, modName = modLib.getCondName(k) if (isNot and not condList[condName]) or (not isNot and condList[condName]) then mod_listMerge(condModList, modName, v) end @@ -854,7 +855,7 @@ local function calcSetup(env, output) local auraEffectMod = 1 + getMiscVal(modDB, nil, "auraEffectInc", 0) / 100 for k, v in pairs(env.auraSkillModList) do if not k:match("skill_") then - if mod.isModMult[k] then + if modLib.isModMult[k] then mod_dbMerge(modDB, nil, k, m_floor(v * auraEffectMod * 100) / 100) elseif k:match("Inc$") then mod_dbMerge(modDB, nil, k, m_floor(v * auraEffectMod)) @@ -928,6 +929,8 @@ local function calcPrimary(env, output) end end output.total_blockChance = sumMods(modDB, false, "blockChance") + output.total_dodgeAttacks = sumMods(modDB, false, "dodgeAttacks") + output.total_dodgeSpells = sumMods(modDB, false, "dodgeSpells") buildSpaceTable(modDB) endWatch(env, "otherDef") end @@ -1010,7 +1013,14 @@ local function calcPrimary(env, output) if startWatch(env, "dps_speed") then -- Calculate skill speed if isAttack then - local baseSpeed = getMiscVal(modDB, "weapon1", "attackRate", 0) + local baseSpeed + local attackTime = getMiscVal(modDB, "skill", "attackTime", 0) + if attackTime > 0 then + -- Skill is overriding weapon attack speed + baseSpeed = 1 / attackTime * (1 + getMiscVal(modDB, "weapon1", "attackSpeedInc", 0) / 100) + else + baseSpeed = getMiscVal(modDB, "weapon1", "attackRate", 0) + end output.total_speed = baseSpeed * (1 + sumMods(modDB, false, "speedInc", "attackSpeedInc") / 100) * sumMods(modDB, true, "speedMore", "attackSpeedMore") else local baseSpeed = 1 / getMiscVal(modDB, "skill", "castTime", 0) @@ -1235,7 +1245,7 @@ local function getCalculator(input, build, fullInit, modFunc) for _, watchList in pairs(env.watchers) do for k, default in pairs(watchList) do -- Add this watcher to the mod's watcher list - local spaceName, modName = mod.getSpaceName(k) + local spaceName, modName = modLib.getSpaceName(k) if not env.watchedModList[spaceName] then env.watchedModList[spaceName] = { } end @@ -1392,20 +1402,20 @@ function control.buildOutput(input, output, build) end end ConPrintf("== Namespaces ==") - mod.listPrint(env.skillSpaceFlags) + modLib.listPrint(env.skillSpaceFlags) ConPrintf("== Skill Mods ==") - mod.listPrint(env.skillModList) + modLib.listPrint(env.skillModList) ConPrintf("== Spec Mods ==") - mod.listPrint(env.specModList) + modLib.listPrint(env.specModList) ConPrintf("== Item Mods ==") - mod.listPrint(env.itemModList) + modLib.listPrint(env.itemModList) ConPrintf("== Conditions ==") - mod.listPrint(env.condList) + modLib.listPrint(env.condList) ConPrintf("== Conditional Modifiers ==") - mod.listPrint(env.condModList) + modLib.listPrint(env.condModList) if env.buffModList then ConPrintf("== Buff Mods ==") - mod.listPrint(env.buffModList) + modLib.listPrint(env.buffModList) end end diff --git a/Common.lua b/Modules/Common.lua similarity index 87% rename from Common.lua rename to Modules/Common.lua index cb952cd4..8bba098a 100644 --- a/Common.lua +++ b/Modules/Common.lua @@ -144,25 +144,6 @@ function common.controlsDraw(host, ...) end --- Draw simple popup message box -function common.drawPopup(r, g, b, fmt, ...) - local screenW, screenH = GetScreenSize() - SetDrawColor(0, 0, 0, 0.5) - DrawImage(nil, 0, 0, screenW, screenH) - local txt = string.format(fmt, ...) - local w = DrawStringWidth(20, "VAR", txt) + 20 - local h = (#txt:gsub("[^\n]","") + 2) * 20 - local ox = (screenW - w) / 2 - local oy = (screenH - h) / 2 - SetDrawColor(1, 1, 1) - DrawImage(nil, ox, oy, w, h) - SetDrawColor(r, g, b) - DrawImage(nil, ox + 2, oy + 2, w - 4, h - 4) - SetDrawColor(1, 1, 1) - DrawImage(nil, ox + 4, oy + 4, w - 8, h - 8) - DrawString(0, oy + 10, "CENTER", 20, "VAR", txt) -end - -- Make a copy of a table and all subtables function copyTable(tbl) local out = {} diff --git a/Modules/ItemTools.lua b/Modules/ItemTools.lua new file mode 100644 index 00000000..4a51d073 --- /dev/null +++ b/Modules/ItemTools.lua @@ -0,0 +1,212 @@ +-- Path of Building +-- +-- Module: Item Tools +-- Various functions for dealing with items +-- + +local m_floor = math.floor +local t_insert = table.insert + +itemLib = { } + +-- Apply range value (0 to 1) to a modifier that has a range: (x to x) or (x-x to x-x) +function itemLib.applyRange(line, range) + return line:gsub("%((%d+)%-(%d+) to (%d+)%-(%d+)%)", function(minMin, maxMin, minMax, maxMax) return string.format("%d-%d", tonumber(minMin) + range * (tonumber(minMax) - tonumber(minMin)), tonumber(maxMin) + range * (tonumber(maxMax) - tonumber(maxMin))) end) + :gsub("%((%d+) to (%d+)%)", function(min, max) return tostring(tonumber(min) + range * (tonumber(max) - tonumber(min))) end) +end + +-- Parse raw item data and extract item name, base type, quality, and modifiers +function itemLib.parseItemRaw(item) + item.name = "?" + item.rarity = "UNIQUE" + item.rawLines = { } + for line in string.gmatch(item.raw .. "\r\n", "([^\r\n]*)\r?\n") do + line = line:gsub("^%s+",""):gsub("%s+$","") + if #line > 0 then + t_insert(item.rawLines, line) + end + end + local mode = "WIKI" + local l = 1 + if item.rawLines[l] then + local rarity = item.rawLines[l]:match("^Rarity: (%a+)") + if rarity then + mode = "GAME" + item.rarity = rarity:upper() + l = l + 1 + end + end + if item.rawLines[l] then + item.name = item.rawLines[l] + l = l + 1 + end + if item.rarity == "NORMAL" or item.rarity == "MAGIC" then + for baseName, baseData in pairs(data.itemBases) do + if item.name:find(baseName, 1, true) then + item.baseName = baseName + item.type = baseData.type + break + end + end + elseif item.rawLines[l] and not item.rawLines[l]:match("^%-") and data.itemBases[item.rawLines[l]] then + item.baseName = item.rawLines[l] + item.title = item.name + item.name = item.title .. ", " .. item.baseName + item.type = data.itemBases[item.baseName].type + end + item.modLines = { } + item.implicitLines = 0 + local gameModeStage = "FINDIMPLICIT" + local gameModeSection = 1 + local foundExplicit + while item.rawLines[l] do + local line = item.rawLines[l] + if line == "--------" then + gameModeSection = gameModeSection + 1 + if gameModeStage == "IMPLICIT" then + item.implicitLines = #item.modLines + gameModeStage = "FINDEXPLICIT" + elseif gameModeStage == "EXPLICIT" then + gameModeStage = "DONE" + end + elseif line == "Corrupted" then + item.corrupted = true + elseif data.weaponTypeInfo[line] then + item.weaponType = line + else + local specName, specVal = line:match("^([%a ]+): %+?([%d%-%.]+)") + if not specName then + specName, specVal = line:match("^([%a ]+): (.+)") + end + if specName then + if specName == "Radius" and item.type == "Jewel" then + for index, data in pairs(data.jewelRadius) do + if specVal == data.label then + item.radius = index + break + end + end + end + else + local rangedLine + if line:match("%(%d+%-%d+ to %d+%-%d+%)") or line:match("%(%d+ to %d+%)") then + rangedLine = itemLib.applyRange(line, 1) + end + local modList, extra = modLib.parseMod(rangedLine or line) + if modList then + t_insert(item.modLines, { line = line, extra = extra, mods = modList, range = rangedLine and 1 }) + if mode == "GAME" then + if gameModeStage == "FINDIMPLICIT" then + gameModeStage = "IMPLICIT" + elseif gameModeStage == "FINDEXPLICIT" then + foundExplicit = true + gameModeStage = "EXPLICIT" + end + end + elseif mode == "GAME" then + if gameModeStage == "IMPLICIT" or gameModeStage == "EXPLICIT" then + t_insert(item.modLines, { line = line, extra = line, mods = { } }) + elseif gameModeStage == "FINDEXPLICIT" then + gameModeStage = "DONE" + end + end + end + end + l = l + 1 + end + local base = data.itemBases[item.baseName] + if base and base.implicit then + if item.implicitLines == 0 then + item.implicitLines = 1 + end + elseif mode == "GAME" and not foundExplicit then + item.implicitLines = 0 + end + itemLib.buildItemModList(item) +end + +-- Build list of modifiers for an item while applying local modifers and adding quality +function itemLib.buildItemModList(item) + local modList = { } + item.modList = modList + for _, modLine in ipairs(item.modLines) do + if not modLine.extra then + if modLine.range then + local line = itemLib.applyRange(modLine.line, modLine.range) + local list, extra = modLib.parseMod(line) + if list and not extra then + modLine.mods = list + end + end + for k, v in pairs(modLine.mods) do + modLib.listMerge(modList, k, v) + end + end + end + local base = data.itemBases[item.baseName] + if not base then + return + end + if base.weapon then + modList.weaponX_type = base.type + modList.weaponX_name = item.name + for _, dmgType in pairs({"physical","lightning","cold","fire","chaos"}) do + local min = (base.weapon[dmgType.."Min"] or 0) + (modList["attack_"..dmgType.."Min"] or 0) + local max = (base.weapon[dmgType.."Max"] or 0) + (modList["attack_"..dmgType.."Max"] or 0) + if dmgType == "physical" then + if modList.weaponNoPhysical then + min, max = 0, 0 + else + min = m_floor(min * (1 + (modList["physicalInc"] or 0) / 100 + .2) + 0.5) + max = m_floor(max * (1 + (modList["physicalInc"] or 0) / 100 + .2) + 0.5) + end + modList["physicalInc"] = nil + end + if min > 0 and max > 0 then + modList["weaponX_"..dmgType.."Min"] = min + modList["weaponX_"..dmgType.."Max"] = max + end + modList["attack_"..dmgType.."Min"] = nil + modList["attack_"..dmgType.."Max"] = nil + end + modList.weaponX_attackRate = m_floor(base.weapon.attackRateBase * (1 + (modList.attackSpeedInc or 0) / 100) * 100 + 0.5) / 100 + modList.weaponX_attackSpeedInc = modList.attackSpeedInc + modList.attackSpeedInc = nil + if modList.weaponAlwaysCrit then + modList.weaponX_critChanceBase = 100 + else + modList.weaponX_critChanceBase = m_floor(base.weapon.critChanceBase * (1 + (modList.critChanceInc or 0) / 100) * 100 + 0.5) / 100 + end + modList.critChanceInc = nil + elseif base.armour then + if base.type == "Shield" then + modList.weaponX_type = "Shield" + end + if base.armour.armourBase then + modList.armourBase = m_floor((base.armour.armourBase + (modList.armourBase or 0)) * (1 + ((modList.armourInc or 0) + (modList.armourAndEvasionInc or 0) + (modList.armourAndESInc or 0) + 20) / 100) + 0.5) + end + if base.armour.evasionBase then + modList.evasionBase = m_floor((base.armour.evasionBase + (modList.evasionBase or 0)) * (1 + ((modList.evasionInc or 0) + (modList.armourAndEvasionInc or 0) + (modList.evasionAndEnergyShieldInc or 0) + 20) / 100) + 0.5) + end + if base.armour.energyShieldBase then + modList.energyShieldBase = m_floor((base.armour.energyShieldBase + (modList.energyShieldBase or 0)) * (1 + ((modList.energyShieldInc or 0) + (modList.armourAndEnergyShieldInc or 0) + (modList.evasionAndEnergyShieldInc or 0) + 20) / 100) + 0.5) + end + if base.armour.blockChance then + if modList.shieldNoBlock then + modList.blockChance = 0 + else + modList.blockChance = base.armour.blockChance + (modList.blockChance or 0) + end + end + modList.armourInc = nil + modList.evasionInc = nil + modList.energyShieldInc = nil + modList.armourAndEvasionInc = nil + modList.armourAndESInc = nil + modList.evasionAndEnergyShieldInc = nil + elseif item.type == "Jewel" then + item.jewelFunc = modList.jewelFunc + modList.jewelFunc = nil + end +end + diff --git a/Modules/Items.lua b/Modules/Items.lua index 3093f32e..7ffcf09f 100644 --- a/Modules/Items.lua +++ b/Modules/Items.lua @@ -6,7 +6,6 @@ local launch, main = ... local t_insert = table.insert -local m_floor = math.floor local s_format = string.format local baseSlots = { "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2", "Belt", "Weapon 1", "Weapon 2" } @@ -49,11 +48,11 @@ function items:Load(xml, dbFileName) local item = { } item.raw = "" item.id = tonumber(node.attrib.id) - self:ParseItemRaw(item) + itemLib.parseItemRaw(item) for _, child in ipairs(node) do if type(child) == "string" then item.raw = child - self:ParseItemRaw(item) + itemLib.parseItemRaw(item) elseif child.elem == "ModRange" then local id = tonumber(child.attrib.id) or 0 local range = tonumber(child.attrib.range) or 1 @@ -62,7 +61,7 @@ function items:Load(xml, dbFileName) end end end - self:BuildItemModList(item) + itemLib.buildItemModList(item) self.list[item.id] = item t_insert(self.orderList, item.id) elseif node.elem == "Slot" then @@ -102,7 +101,7 @@ function items:DrawItems(viewPort, inputEvents) self.displayItem = { raw = newItem:gsub("^%s+",""):gsub("%s+$",""):gsub("–","-"):gsub("%b<>",""):gsub("ö","o") } - self:ParseItemRaw(self.displayItem) + itemLib.parseItemRaw(self.displayItem) if not self.displayItem.baseName then self.displayItem = nil end @@ -181,7 +180,11 @@ function items:AddDisplayItem() return end end - if not self.list[self.displayItem.id] then + if not self.displayItem.id then + self.displayItem.id = 1 + while self.list[self.displayItem.id] do + self.displayItem.id = self.displayItem.id + 1 + end t_insert(self.orderList, self.displayItem.id) end self.list[self.displayItem.id] = self.displayItem @@ -189,11 +192,6 @@ function items:AddDisplayItem() self:PopulateSlots() end -function items:ApplyRange(line, range) - return line:gsub("%((%d+)%-(%d+) to (%d+)%-(%d+)%)", function(minMin, maxMin, minMax, maxMax) return string.format("%d-%d", tonumber(minMin) + range * (tonumber(minMax) - tonumber(minMin)), tonumber(maxMin) + range * (tonumber(maxMax) - tonumber(maxMin))) end) - :gsub("%((%d+) to (%d+)%)", function(min, max) return tostring(tonumber(min) + range * (tonumber(max) - tonumber(min))) end) -end - function items:IsItemValidForSlot(item, slotName) if item.type == slotName:gsub(" %d+","") then return true @@ -210,204 +208,6 @@ function items:IsItemValidForSlot(item, slotName) end end -function items:ParseItemRaw(item) - if not item.id then - item.id = 1 - while self.list[item.id] do - item.id = item.id + 1 - end - end - item.name = "?" - item.rarity = "UNIQUE" - item.rawLines = { } - for line in string.gmatch(item.raw .. "\r\n", "([^\r\n]*)\r?\n") do - line = line:gsub("^%s+",""):gsub("%s+$","") - if #line > 0 then - t_insert(item.rawLines, line) - end - end - local mode = "WIKI" - local l = 1 - if item.rawLines[l] then - local rarity = item.rawLines[l]:match("^Rarity: (%a+)") - if rarity then - mode = "GAME" - item.rarity = rarity:upper() - l = l + 1 - end - end - if item.rawLines[l] then - item.name = item.rawLines[l] - l = l + 1 - end - if item.rarity == "NORMAL" or item.rarity == "MAGIC" then - for baseName, baseData in pairs(data.itemBases) do - if item.name:find(baseName, 1, true) then - item.baseName = baseName - item.type = baseData.type - break - end - end - elseif item.rawLines[l] and not item.rawLines[l]:match("^%-") and data.itemBases[item.rawLines[l]] then - item.baseName = item.rawLines[l] - item.title = item.name - item.name = item.title .. ", " .. item.baseName - item.type = data.itemBases[item.baseName].type - end - item.modLines = { } - item.implicitLines = 0 - local gameModeStage = "FINDIMPLICIT" - local gameModeSection = 1 - local foundExplicit - while item.rawLines[l] do - local line = item.rawLines[l] - if line == "--------" then - gameModeSection = gameModeSection + 1 - if gameModeStage == "IMPLICIT" then - item.implicitLines = #item.modLines - gameModeStage = "FINDEXPLICIT" - elseif gameModeStage == "EXPLICIT" then - gameModeStage = "DONE" - end - elseif line == "Corrupted" then - item.corrupted = true - elseif data.weaponTypeInfo[line] then - item.weaponType = line - else - local specName, specVal = line:match("^([%a ]+): %+?([%d%-%.]+)") - if not specName then - specName, specVal = line:match("^([%a ]+): (.+)") - end - if specName then - if specName == "Radius" and item.type == "Jewel" then - for index, data in pairs(data.jewelRadius) do - if specVal == data.label then - item.radius = index - break - end - end - end - else - local rangedLine - if line:match("%(%d+%-%d+ to %d+%-%d+%)") or line:match("%(%d+ to %d+%)") then - rangedLine = self:ApplyRange(line, 1) - end - local modList, extra = mod.parseMod(rangedLine or line) - if modList then - t_insert(item.modLines, { line = line, extra = extra, mods = modList, range = rangedLine and 1 }) - if mode == "GAME" then - if gameModeStage == "FINDIMPLICIT" then - gameModeStage = "IMPLICIT" - elseif gameModeStage == "FINDEXPLICIT" then - foundExplicit = true - gameModeStage = "EXPLICIT" - end - end - elseif mode == "GAME" then - if gameModeStage == "IMPLICIT" or gameModeStage == "EXPLICIT" then - t_insert(item.modLines, { line = line, extra = line, mods = { } }) - elseif gameModeStage == "FINDEXPLICIT" then - gameModeStage = "DONE" - end - end - end - end - l = l + 1 - end - local base = data.itemBases[item.baseName] - if base and base.implicit then - if item.implicitLines == 0 then - item.implicitLines = 1 - end - elseif mode == "GAME" and not foundExplicit then - item.implicitLines = 0 - end - self:BuildItemModList(item) -end - -function items:BuildItemModList(item) - local modList = { } - item.modList = modList - for _, modLine in ipairs(item.modLines) do - if not modLine.extra then - if modLine.range then - local line = self:ApplyRange(modLine.line, modLine.range) - local list, extra = mod.parseMod(line) - if list and not extra then - mod.mods = list - end - end - for k, v in pairs(modLine.mods) do - mod.listMerge(modList, k, v) - end - end - end - local base = data.itemBases[item.baseName] - if not base then - return - end - if base.weapon then - modList.weaponX_type = base.type - modList.weaponX_name = item.name - for _, elem in pairs({"physical","lightning","cold","fire","chaos"}) do - local min = (base.weapon[elem.."Min"] or 0) + (modList["attack_"..elem.."Min"] or 0) - local max = (base.weapon[elem.."Max"] or 0) + (modList["attack_"..elem.."Max"] or 0) - if elem == "physical" then - if modList.weaponNoPhysical then - min, max = 0, 0 - else - min = m_floor(min * (1 + (modList["physicalInc"] or 0) / 100 + .2) + 0.5) - max = m_floor(max * (1 + (modList["physicalInc"] or 0) / 100 + .2) + 0.5) - end - modList["physicalInc"] = nil - end - if min > 0 and max > 0 then - modList["weaponX_"..elem.."Min"] = min - modList["weaponX_"..elem.."Max"] = max - end - modList["attack_"..elem.."Min"] = nil - modList["attack_"..elem.."Max"] = nil - end - modList.weaponX_attackRate = m_floor(base.weapon.attackRateBase * (1 + (modList.attackSpeedInc or 0) / 100) * 100 + 0.5) / 100 - modList.attackSpeedInc = nil - if modList.weaponAlwaysCrit then - modList.weaponX_critChanceBase = 100 - else - modList.weaponX_critChanceBase = m_floor(base.weapon.critChanceBase * (1 + (modList.critChanceInc or 0) / 100) * 100 + 0.5) / 100 - end - modList.critChanceInc = nil - elseif base.armour then - if base.type == "Shield" then - modList.weaponX_type = "Shield" - end - if base.armour.armourBase then - modList.armourBase = m_floor((base.armour.armourBase + (modList.armourBase or 0)) * (1 + ((modList.armourInc or 0) + (modList.armourAndEvasionInc or 0) + (modList.armourAndESInc or 0) + 20) / 100) + 0.5) - end - if base.armour.evasionBase then - modList.evasionBase = m_floor((base.armour.evasionBase + (modList.evasionBase or 0)) * (1 + ((modList.evasionInc or 0) + (modList.armourAndEvasionInc or 0) + (modList.evasionAndEnergyShieldInc or 0) + 20) / 100) + 0.5) - end - if base.armour.energyShieldBase then - modList.energyShieldBase = m_floor((base.armour.energyShieldBase + (modList.energyShieldBase or 0)) * (1 + ((modList.energyShieldInc or 0) + (modList.armourAndEnergyShieldInc or 0) + (modList.evasionAndEnergyShieldInc or 0) + 20) / 100) + 0.5) - end - if base.armour.blockChance then - if modList.shieldNoBlock then - modList.blockChance = 0 - else - modList.blockChance = base.armour.blockChance + (modList.blockChance or 0) - end - end - modList.armourInc = nil - modList.evasionInc = nil - modList.energyShieldInc = nil - modList.armourAndEvasionInc = nil - modList.armourAndESInc = nil - modList.evasionAndEnergyShieldInc = nil - elseif item.type == "Jewel" then - item.jewelFunc = modList.jewelFunc - modList.jewelFunc = nil - end -end - function items:AddItemTooltip(item) local rarityCode = data.colorCodes[item.rarity] if item.title then @@ -477,7 +277,7 @@ function items:AddItemTooltip(item) if item.modLines[1] then main:AddTooltipSeperator(10) for index, modLine in pairs(item.modLines) do - local line = modLine.range and self:ApplyRange(modLine.line, modLine.range) or modLine.line + local line = modLine.range and itemLib.applyRange(modLine.line, modLine.range) or modLine.line main:AddTooltipLine(16, (modLine.extra and data.colorCodes.NORMAL or data.colorCodes.MAGIC)..line) if index == item.implicitLines and item.modLines[index + 1] then main:AddTooltipSeperator(10) diff --git a/Modules/Main.lua b/Modules/Main.lua index d221e5f5..0d30a4fb 100644 --- a/Modules/Main.lua +++ b/Modules/Main.lua @@ -10,8 +10,10 @@ local m_floor = math.floor local m_max = math.max local t_insert = table.insert +LoadModule("Modules/Common") LoadModule("Modules/Data") LoadModule("Modules/ModTools") +LoadModule("Modules/ItemTools") local main = { } @@ -25,6 +27,8 @@ function main:Init() self.modes = { } self.modes["LIST"] = LoadModule("Modules/BuildList", launch, self) self.modes["BUILD"] = LoadModule("Modules/Build", launch, self) + + self.buildPath = "Builds/" self.tree = common.New("PassiveTree") @@ -114,6 +118,14 @@ function main:LoadSettings() end end self:SetMode(node.attrib.mode, unpack(args)) + elseif node.elem == "BuildPath" then + if not node.attrib.path then + launch:ShowErrMsg("^1Error parsing 'Settings.xml': 'BuildPath' element missing 'path' attribute") + return true + end + self.buildPath = node.attrib.path + elseif node.elem == "DevMode" then + self.devMode = node.attrib.enable == "true" end end end @@ -134,6 +146,8 @@ function main:SaveSettings() t_insert(mode, child) end t_insert(setXML, mode) + t_insert(setXML, { elem = "BuildPath", attrib = { path = self.buildPath } }) + t_insert(setXML, { elem = "DevMode", attrib = { enable = self.devMode and "true" or "false" } }) local res, errMsg = common.xml.SaveXMLFile(setXML, "Settings.xml") if not res then launch:ShowErrMsg("Error saving 'Settings.xml': %s", errMsg) diff --git a/Modules/ModParser.lua b/Modules/ModParser.lua index fa6912f2..6d4e2ba4 100644 --- a/Modules/ModParser.lua +++ b/Modules/ModParser.lua @@ -12,6 +12,7 @@ local formList = { ["^(%d+)%% less"] = "LESS", ["^([%+%-][%d%.]+)%%?"] = "BASE", ["^([%+%-][%d%.]+)%%? to"] = "BASE", + ["^([%+%-][%d%.]+)%%? base"] = "BASE", ["^you gain ([%d%.]+)"] = "BASE", ["^([%+%-]?%d+)%% chance"] = "CHANCE", ["^([%+%-]?%d+)%% additional chance"] = "CHANCE", @@ -21,6 +22,9 @@ local formList = { ["penetrates (%d+)%% of enemy"] = "PEN", ["^([%d%.]+)%% of (.+) regenerated per second"] = "REGENPERCENT", ["^([%d%.]+) (.+) regenerated per second"] = "REGENFLAT", + ["adds (%d+)%-(%d+) (%a+) damage"] = "DMGATTACKS", + ["adds (%d+)%-(%d+) (%a+) damage to attacks"] = "DMGATTACKS", + ["adds (%d+)%-(%d+) (%a+) damage to spells"] = "DMGSPELLS", } -- Map of modifier names @@ -48,7 +52,6 @@ local modNameList = { ["energy shield recharge rate"] = "energyShieldRecharge{suf}", ["armour"] = "armour{suf}", ["evasion rating"] = "evasion{suf}", - ["global evasion rating"] = "global_evasion{suf}", ["energy shield"] = "energyShield{suf}", ["armour and evasion"] = "armourAndEvasion{suf}", ["armour and evasion rating"] = "armourAndEvasion{suf}", @@ -56,7 +59,6 @@ local modNameList = { ["armour and energy shield"] = "armourAndEnergyShield{suf}", ["evasion and energy shield"] = "evasionAndEnergyShield{suf}", ["defences"] = "defences{suf}", - ["global defences"] = "defences{suf}", -- Resistances ["fire resistance"] = "fireResist", ["maximum fire resistance"] = "fireResistMax", @@ -72,9 +74,9 @@ local modNameList = { ["all maximum resistances"] = { "fireResistMax", "coldResistMax", "lightningResistMax", "chaosResistMax" }, ["chaos resistance"] = "chaosResist", -- Other defences - ["to dodge attacks"] = "dodgeAttack", - ["to dodge spells"] = "dodgeSpell", - ["to dodge spell damage"] = "dodgeSpell", + ["to dodge attacks"] = "dodgeAttacks", + ["to dodge spells"] = "dodgeSpells", + ["to dodge spell damage"] = "dodgeSpells", ["to block"] = "blockChance", ["to block spells"] = "spellBlockChance", ["maximum block chance"] = "blockChanceMax", @@ -163,9 +165,7 @@ local modNameList = { ["burning damage"] = "degen_fire{suf}", -- Crit/accuracy/speed modifiers ["critical strike chance"] = "critChance{suf}", - ["global critical strike chance"] = "global_critChance{suf}", ["critical strike multiplier"] = "critMultiplier", - ["global critical strike multiplier"] = "critMultiplier", ["accuracy rating"] = "accuracy{suf}", ["attack speed"] = "attackSpeed{suf}", ["cast speed"] = "castSpeed{suf}", @@ -233,6 +233,7 @@ local namespaceList = { ["with fire skills"] = "fire_", ["with chaos skills"] = "chaos_", -- Other + ["global"] = "global_", ["from equipped shield"] = "Shield_", } @@ -269,6 +270,8 @@ local specialSpaceList = { ["while phasing"] = "condMod_Phasing_", ["while using a flask"] = "condMod_UsingFlask_", ["while on consecrated ground"] = "condMod_OnConsecratedGround_", + ["if you've killed recently"] = "condMod_KilledRecently_", + ["if you haven't killed recently"] = "condMod_notKilledRecently_", ["if you've attacked recently"] = "condMod_AttackedRecently_", ["if you've cast a spell recently"] = "condMod_CastSpellRecently_", ["if you've summoned a totem recently"] = "condMod_SummonedTotemRecently_", @@ -281,6 +284,8 @@ local specialSpaceList = { ["against poisoned enemies"] = "condMod_EnemyPoisoned_", ["against burning enemies"] = "condMod_EnemyBurning_", ["against ignited enemies"] = "condMod_EnemyIgnited_", + ["against shocked enemies"] = "condMod_EnemyShocked_", + ["against frozen enemies"] = "condMod_EnemyFrozen_", ["enemies which are chilled"] = "condMod_EnemyChilled_", ["against frozen, shocked or ignited enemies"] = "condMod_EnemyFrozenShockedIgnited_", ["against enemies that are affected by elemental status ailments"] = "condMod_EnemyElementalStatus_", @@ -295,7 +300,7 @@ local specialModList = { ["no critical strike multiplier"] = { noCritMult = true }, ["the increase to physical damage from strength applies to projectile attacks as well as melee attacks"] = { ironGrip = true }, ["converts all evasion rating to armour%. dexterity provides no bonus to evasion rating"] = { ironReflexes = true }, - ["30%% chance to dodge attacks%. 50%% less armour and energy shield, 30%% less chance to block spells and attacks"] = { dodgeAttack = 30, armourMore = 0.5, energyShieldMore = 0.5 }, + ["30%% chance to dodge attacks%. 50%% less armour and energy shield, 30%% less chance to block spells and attacks"] = { dodgeAttacks = 30, armourMore = 0.5, energyShieldMore = 0.5 }, ["maximum life becomes 1, immune to chaos damage"] = { chaosInoculation = true }, ["deal no non%-fire damage"] = { physicalFinalMore = 0, lightningFinalMore = 0, coldFinalMore = 0, chaosFinalMore = 0 }, -- Ascendancy notables @@ -313,9 +318,9 @@ local specialModList = { ["(%d+)%% faster start of energy shield recharge"] = function(num) return { energyShieldRechargeFaster = num } end, ["(%d+)%% additional block chance while dual wielding or holding a shield"] = function(num) return { condMod_DualWielding_blockChance = num, condMod_UsingShield_blockChance = num } end, -- Other modifiers - ["adds (%d+)%-(%d+) (%a+) damage ?t?o? ?a?t?t?a?c?k?s?"] = function(_, min, max, type) local pre = "attack_"..type return { [pre.."Min"] = tonumber(min), [pre.."Max"] = tonumber(max) } end, - ["adds (%d+)%-(%d+) (%a+) damage to attacks with bows"] = function(_, min, max, type) local pre = "bow_"..type return { [pre.."Min"] = tonumber(min), [pre.."Max"] = tonumber(max) } end, - ["adds (%d+)%-(%d+) (%a+) damage to spells"] = function(_, min, max, type) local pre = "spell_"..type return { [pre.."Min"] = tonumber(min), [pre.."Max"] = tonumber(max) } end, +-- ["adds (%d+)%-(%d+) (%a+) damage ?t?o? ?a?t?t?a?c?k?s?"] = function(_, min, max, type) local pre = "attack_"..type return { [pre.."Min"] = tonumber(min), [pre.."Max"] = tonumber(max) } end, +-- ["adds (%d+)%-(%d+) (%a+) damage to attacks with bows"] = function(_, min, max, type) local pre = "bow_"..type return { [pre.."Min"] = tonumber(min), [pre.."Max"] = tonumber(max) } end, +-- ["adds (%d+)%-(%d+) (%a+) damage to spells"] = function(_, min, max, type) local pre = "spell_"..type return { [pre.."Min"] = tonumber(min), [pre.."Max"] = tonumber(max) } end, ["cannot be shocked"] = { avoidShock = 100 }, ["cannot be frozen"] = { avoidFreeze = 100 }, ["cannot be chilled"] = { avoidChill = 100 }, @@ -383,7 +388,7 @@ end local function getSimpleConv(src, dst, factor) return function(mods, allMods, data) if mods and mods[src] then - mod.listMerge(allMods, dst, mods[src] * factor) + modLib.listMerge(allMods, dst, mods[src] * factor) mods[src] = nil end end @@ -394,7 +399,7 @@ local function getMatchConv(others, dst) for k, v in pairs(mods) do for _, other in pairs(others) do if k:match(other) then - mod.listMerge(allMods, k:gsub(other, dst), v) + modLib.listMerge(allMods, k:gsub(other, dst), v) mods[k] = nil end end @@ -407,7 +412,7 @@ local function getPerStat(dst, stat, factor) if mods then data[stat] = (data[stat] or 0) + (mods[stat] or 0) else - mod.listMerge(allMods, dst, math.floor(data[stat] * factor + 0.5)) + modLib.listMerge(allMods, dst, math.floor(data[stat] * factor + 0.5)) end end end @@ -438,7 +443,7 @@ local jewelFuncs = { data.dexBase = (data.dexBase or 0) + (mods.dexBase or 0) data.intBase = (data.intBase or 0) + (mods.intBase or 0) else - mod.listMerge(allMods, "dexIntToMeleeBonus", data.dexBase + data.intBase) + modLib.listMerge(allMods, "dexIntToMeleeBonus", data.dexBase + data.intBase) end end, } @@ -492,9 +497,6 @@ return function(line) -- Scan for modifier name local modName modName, line = scan(line, modNameList, true) - if not modName and line:match("%S") then - return { }, line - end -- Scan for skill name local skillSpace @@ -539,24 +541,32 @@ return function(line) elseif modForm == "REGENPERCENT" then val = num suffix = "Percent" - modName = regenTypes[formCap[2]:lower()] + modName = regenTypes[formCap[2]] if not modName then return { }, line end elseif modForm == "REGENFLAT" then val = num suffix = "Base" - modName = regenTypes[formCap[2]:lower()] + modName = regenTypes[formCap[2]] if not modName then return { }, line end + elseif modForm == "DMGATTACKS" then + val = { tonumber(formCap[1]), tonumber(formCap[2]) } + modName = { formCap[3].."Min", formCap[3].."Max" } + space = space or "attack_" + elseif modForm == "DMGSPELLS" then + val = { tonumber(formCap[1]), tonumber(formCap[2]) } + modName = { formCap[3].."Min", formCap[3].."Max" } + space = "spell_" end -- Generate modifier list local nameList = modName or "" local modList = { } for i, name in ipairs(type(nameList) == "table" and nameList or { nameList }) do - modList[(skillSpace or "") .. (specialSpace or "") .. (space or "") .. name:gsub("{suf}", suffix or "")] = val + modList[(skillSpace or "") .. (specialSpace or "") .. (space or "") .. name:gsub("{suf}", suffix or "")] = type(val) == "table" and val[i] or val end return modList, line:match("%S") and line end \ No newline at end of file diff --git a/Modules/ModTools.lua b/Modules/ModTools.lua index 15fe8d19..1c9f00a9 100644 --- a/Modules/ModTools.lua +++ b/Modules/ModTools.lua @@ -1,18 +1,18 @@ -- Path of Building -- --- Module: ModTools +-- Module: Mod Tools -- Various functions for dealing with modifier lists and databases -- local t_insert = table.insert -mod = { } +modLib = { } -mod.parseMod = LoadModule("Modules/ModParser") +modLib.parseMod = LoadModule("Modules/ModParser") -- Break modifier name into namespace and mod name local spaceLookup = { } -function mod.getSpaceName(modName) +function modLib.getSpaceName(modName) if not spaceLookup[modName] then local space, mod = modName:match("^([^_]+)_(.+)$") if not space then @@ -27,7 +27,7 @@ end -- Extract condition name from modifier name local condLookup = { } -function mod.getCondName(modName) +function modLib.getCondName(modName) if not condLookup[modName] then local isNot, condName, mod = modName:match("^(n?o?t?)(%w+)_(.+)$") isNot = (isNot == "not") @@ -38,23 +38,23 @@ function mod.getCondName(modName) end -- Magic table to check if a modifier is multiplicative (contains 'More' in the modifier name) -mod.isModMult = { } -mod.isModMult.__index = function(t, modName) +modLib.isModMult = { } +modLib.isModMult.__index = function(t, modName) local val = (modName:match("More") ~= nil) t[modName] = val return val end -setmetatable(mod.isModMult, mod.isModMult) +setmetatable(modLib.isModMult, modLib.isModMult) -- Merge modifier with existing mod list, respecting additivity/multiplicativity -function mod.listMerge(modList, modName, modVal) +function modLib.listMerge(modList, modName, modVal) if modList[modName] then if type(modVal) == "boolean" then modList[modName] = modList[modName] or modVal elseif type(modVal) == "function" then local orig = modList[modName] modList[modName] = function(...) orig(...) modVal(...) end - elseif mod.isModMult[modName] then + elseif modLib.isModMult[modName] then modList[modName] = modList[modName] * modVal else modList[modName] = modList[modName] + modVal @@ -65,14 +65,14 @@ function mod.listMerge(modList, modName, modVal) end -- Unmerge modifier from existing mod list, respecting additivity/multiplicativity -function mod.listUnmerge(modList, modName, modVal) +function modLib.listUnmerge(modList, modName, modVal) if type(modVal) == "boolean" then if modVal == true then modList[modName] = false end elseif type(modVal) == "string" then modList[modName] = nil - elseif mod.isModMult[modName] then + elseif modLib.isModMult[modName] then if modVal == 0 then modList[modName] = 1 else @@ -84,55 +84,55 @@ function mod.listUnmerge(modList, modName, modVal) end -- Merge modifier with mod database -function mod.dbMerge(modDB, spaceName, modName, modVal) +function modLib.dbMerge(modDB, spaceName, modName, modVal) if not spaceName then - spaceName, modName = mod.getSpaceName(modName) + spaceName, modName = modLib.getSpaceName(modName) elseif spaceName == "" then spaceName = "global" end if not modDB[spaceName] then modDB[spaceName] = { } end - mod.listMerge(modDB[spaceName], modName, modVal) + modLib.listMerge(modDB[spaceName], modName, modVal) end -- Unmerge modifier from mod database -function mod.dbUnmerge(modDB, spaceName, modName, modVal) +function modLib.dbUnmerge(modDB, spaceName, modName, modVal) if not spaceName then - spaceName, modName = mod.getSpaceName(modName) + spaceName, modName = modLib.getSpaceName(modName) elseif spaceName == "" then spaceName = "global" end if not modDB[spaceName] then modDB[spaceName] = { } end - mod.listUnmerge(modDB[spaceName], modName, modVal) + modLib.listUnmerge(modDB[spaceName], modName, modVal) end -- Merge modifier list with mod database -function mod.dbMergeList(modDB, modList) +function modLib.dbMergeList(modDB, modList) for k, modVal in pairs(modList) do - local spaceName, modName = mod.getSpaceName(k) + local spaceName, modName = modLib.getSpaceName(k) if not modDB[spaceName] then modDB[spaceName] = { } end - mod.listMerge(modDB[spaceName], modName, modVal) + modLib.listMerge(modDB[spaceName], modName, modVal) end end -- Unmerge modifier list from mod database -function mod.dbUnmergeList(modDB, modList) +function modLib.dbUnmergeList(modDB, modList) for k, modVal in pairs(modList) do - local spaceName, modName = mod.getSpaceName(k) + local spaceName, modName = modLib.getSpaceName(k) if not modDB[spaceName] then modDB[spaceName] = { } end - mod.listUnmerge(modDB[spaceName], modName, modVal) + modLib.listUnmerge(modDB[spaceName], modName, modVal) end end -- Print modifier list to the console -function mod.listPrint(modList, tab) +function modLib.listPrint(modList, tab) local names = { } for k in pairs(modList) do if type(k) == "string" then @@ -146,7 +146,7 @@ function mod.listPrint(modList, tab) end -- Print modifier database to the console -function mod.dbPrint(modDB) +function modLib.dbPrint(modDB) local spaceNames = { } for k in pairs(modDB) do t_insert(spaceNames, k) @@ -154,7 +154,7 @@ function mod.dbPrint(modDB) table.sort(spaceNames) for _, spaceName in pairs(spaceNames) do ConPrintf("%s = {", spaceName) - mod.listPrint(modDB[spaceName], 1) + modLib.listPrint(modDB[spaceName], 1) ConPrintf("},") end end \ No newline at end of file diff --git a/PathOfBuilding.sln b/PathOfBuilding.sln index da2a5f59..6d9aeb9c 100644 --- a/PathOfBuilding.sln +++ b/PathOfBuilding.sln @@ -5,7 +5,6 @@ VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AE7B8DC6-833A-4817-915D-AAB9B8F17C1B}" ProjectSection(SolutionItems) = preProject - Common.lua = Common.lua Launch.lua = Launch.lua EndProjectSection EndProject @@ -45,8 +44,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{35D8 Modules\Calcs.lua = Modules\Calcs.lua Modules\CalcsControl.lua = Modules\CalcsControl.lua Modules\CalcsView.lua = Modules\CalcsView.lua + Modules\Common.lua = Modules\Common.lua Modules\Data.lua = Modules\Data.lua Modules\Items.lua = Modules\Items.lua + Modules\ItemTools.lua = Modules\ItemTools.lua Modules\Main.lua = Modules\Main.lua Modules\ModParser.lua = Modules\ModParser.lua Modules\ModTools.lua = Modules\ModTools.lua diff --git a/manifest.xml b/manifest.xml new file mode 100644 index 00000000..f4544743 --- /dev/null +++ b/manifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +