Adds pantheons from temmings' pull request Adds partial Impale support from baranio's pull request Adds updated uniques from PJacek's pull request and my own Adds more tree highlighting options for node power from coldino's pull request Adds support for fossil mods in the crafting window. Including correct parsing for some mods that previously didn't work
1308 lines
52 KiB
Lua
1308 lines
52 KiB
Lua
-- Path of Building
|
|
--
|
|
-- Module: Build
|
|
-- Loads and manages the current build.
|
|
--
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local t_insert = table.insert
|
|
local m_min = math.min
|
|
local m_max = math.max
|
|
local m_floor = math.floor
|
|
local m_abs = math.abs
|
|
local s_format = string.format
|
|
|
|
local normalBanditDropList = {
|
|
{ label = "Passive point", banditId = "None" },
|
|
{ label = "Oak (Life)", banditId = "Oak" },
|
|
{ label = "Kraityn (Resists)", banditId = "Kraityn" },
|
|
{ label = "Alira (Mana)", banditId = "Alira" },
|
|
}
|
|
local cruelBanditDropList = {
|
|
{ label = "Passive point", banditId = "None" },
|
|
{ label = "Oak (Endurance)", banditId = "Oak" },
|
|
{ label = "Kraityn (Frenzy)", banditId = "Kraityn" },
|
|
{ label = "Alira (Power)", banditId = "Alira" },
|
|
}
|
|
local mercilessBanditDropList = {
|
|
{ label = "Passive point", banditId = "None" },
|
|
{ label = "Oak (Phys Dmg)", banditId = "Oak" },
|
|
{ label = "Kraityn (Att. Speed)", banditId = "Kraityn" },
|
|
{ label = "Alira (Cast Speed)", banditId = "Alira" },
|
|
}
|
|
local fooBanditDropList = {
|
|
{ label = "2 Passive Points", banditId = "None" },
|
|
{ label = "Oak (Life Regen, Phys.Dmg. Reduction, Phys.Dmg)", banditId = "Oak" },
|
|
{ label = "Kraityn (Attack/Cast Speed, Attack Dodge, Move Speed)", banditId = "Kraityn" },
|
|
{ label = "Alira (Mana Regen, Crit Multiplier, Resists)", banditId = "Alira" },
|
|
}
|
|
|
|
local PantheonMajorGodDropList = {
|
|
{ label = "Nothing", id = "None" },
|
|
{ label = "Soul of the Brine King", id = "TheBrineKing" },
|
|
{ label = "Soul of Lunaris", id = "Lunaris" },
|
|
{ label = "Soul of Solaris", id = "Solaris" },
|
|
{ label = "Soul of Arakaali", id = "Arakaali" },
|
|
}
|
|
|
|
local PantheonMinorGodDropList = {
|
|
{ label = "Nothing", id = "None" },
|
|
{ label = "Soul of Gruthkul", id = "Gruthkul" },
|
|
{ label = "Soul of Yugul", id = "Yugul" },
|
|
{ label = "Soul of Abberath", id = "Abberath" },
|
|
{ label = "Soul of Tukohama", id = "Tukohama" },
|
|
{ label = "Soul of Garukhan", id = "Garukhan" },
|
|
{ label = "Soul of Ralakesh", id = "Ralakesh" },
|
|
{ label = "Soul of Ryslatha", id = "Ryslatha" },
|
|
{ label = "Soul of Shakari", id = "Shakari" },
|
|
}
|
|
|
|
local buildMode = new("ControlHost")
|
|
|
|
function buildMode:Init(dbFileName, buildName, buildXML, targetVersion)
|
|
self.dbFileName = dbFileName
|
|
self.buildName = buildName
|
|
if dbFileName then
|
|
self.dbFileSubPath = self.dbFileName:sub(#main.buildPath + 1, -#self.buildName - 5)
|
|
else
|
|
self.dbFileSubPath = main.modes.LIST.subPath or ""
|
|
end
|
|
if not buildName then
|
|
main:SetMode("LIST")
|
|
end
|
|
|
|
if not dbFileName and not targetVersion and not buildXML then
|
|
targetVersion = liveTargetVersion
|
|
--self.targetVersion = nil
|
|
--self:OpenTargetVersionPopup(true)
|
|
--return
|
|
end
|
|
|
|
self.abortSave = true
|
|
|
|
wipeTable(self.controls)
|
|
|
|
local miscTooltip = new("Tooltip")
|
|
|
|
-- Controls: top bar, left side
|
|
self.anchorTopBarLeft = new("Control", nil, 4, 4, 0, 20)
|
|
self.controls.back = new("ButtonControl", {"LEFT",self.anchorTopBarLeft,"RIGHT"}, 0, 0, 60, 20, "<< Back", function()
|
|
if self.unsaved then
|
|
self:OpenSavePopup("LIST")
|
|
else
|
|
self:CloseBuild()
|
|
end
|
|
end)
|
|
self.controls.buildName = new("Control", {"LEFT",self.controls.back,"RIGHT"}, 8, 0, 0, 20)
|
|
self.controls.buildName.width = function(control)
|
|
local limit = self.anchorTopBarRight:GetPos() - 98 - 40 - self.controls.back:GetSize() - self.controls.save:GetSize() - self.controls.saveAs:GetSize()
|
|
local bnw = DrawStringWidth(16, "VAR", self.buildName)
|
|
self.strWidth = m_min(bnw, limit)
|
|
self.strLimited = bnw > limit
|
|
return self.strWidth + 98
|
|
end
|
|
self.controls.buildName.Draw = function(control)
|
|
local x, y = control:GetPos()
|
|
local width, height = control:GetSize()
|
|
SetDrawColor(0.5, 0.5, 0.5)
|
|
DrawImage(nil, x + 91, y, self.strWidth + 6, 20)
|
|
SetDrawColor(0, 0, 0)
|
|
DrawImage(nil, x + 92, y + 1, self.strWidth + 4, 18)
|
|
SetDrawColor(1, 1, 1)
|
|
SetViewport(x, y + 2, self.strWidth + 94, 16)
|
|
DrawString(0, 0, "LEFT", 16, "VAR", "Current build: "..self.buildName)
|
|
SetViewport()
|
|
if control:IsMouseInBounds() then
|
|
SetDrawLayer(nil, 10)
|
|
miscTooltip:Clear()
|
|
if self.dbFileSubPath and self.dbFileSubPath ~= "" then
|
|
miscTooltip:AddLine(16, self.dbFileSubPath..self.buildName)
|
|
elseif self.strLimited then
|
|
miscTooltip:AddLine(16, self.buildName)
|
|
end
|
|
miscTooltip:Draw(x, y, width, height, main.viewPort)
|
|
SetDrawLayer(nil, 0)
|
|
end
|
|
end
|
|
self.controls.save = new("ButtonControl", {"LEFT",self.controls.buildName,"RIGHT"}, 8, 0, 50, 20, "Save", function()
|
|
self:SaveDBFile()
|
|
end)
|
|
self.controls.save.enabled = function()
|
|
return not self.dbFileName or self.unsaved
|
|
end
|
|
self.controls.saveAs = new("ButtonControl", {"LEFT",self.controls.save,"RIGHT"}, 8, 0, 70, 20, "Save As", function()
|
|
self:OpenSaveAsPopup()
|
|
end)
|
|
self.controls.saveAs.enabled = function()
|
|
return self.dbFileName
|
|
end
|
|
|
|
-- Controls: top bar, right side
|
|
self.anchorTopBarRight = new("Control", nil, function() return main.screenW / 2 + 6 end, 4, 0, 20)
|
|
self.controls.pointDisplay = new("Control", {"LEFT",self.anchorTopBarRight,"RIGHT"}, -12, 0, 0, 20)
|
|
self.controls.pointDisplay.x = function(control)
|
|
local width, height = control:GetSize()
|
|
if self.controls.saveAs:GetPos() + self.controls.saveAs:GetSize() < self.anchorTopBarRight:GetPos() - width - 16 then
|
|
return -12 - width
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
self.controls.pointDisplay.width = function(control)
|
|
local used, ascUsed = self.spec:CountAllocNodes()
|
|
local usedMax = 99 + (self.targetVersion == "2_6" and 21 or 22) + (self.calcsTab.mainOutput.ExtraPoints or 0)
|
|
local ascMax = 8
|
|
control.str = string.format("%s%3d / %3d %s%d / %d", used > usedMax and "^1" or "^7", used, usedMax, ascUsed > ascMax and "^1" or "^7", ascUsed, ascMax)
|
|
control.req = "Required level: "..m_max(1, (100 + used - usedMax))
|
|
return DrawStringWidth(16, "FIXED", control.str) + 8
|
|
end
|
|
self.controls.pointDisplay.Draw = function(control)
|
|
local x, y = control:GetPos()
|
|
local width, height = control:GetSize()
|
|
SetDrawColor(1, 1, 1)
|
|
DrawImage(nil, x, y, width, height)
|
|
SetDrawColor(0, 0, 0)
|
|
DrawImage(nil, x + 1, y + 1, width - 2, height - 2)
|
|
SetDrawColor(1, 1, 1)
|
|
DrawString(x + 4, y + 2, "LEFT", 16, "FIXED", control.str)
|
|
if control:IsMouseInBounds() then
|
|
SetDrawLayer(nil, 10)
|
|
miscTooltip:Clear()
|
|
miscTooltip:AddLine(16, control.req)
|
|
miscTooltip:Draw(x, y, width, height, main.viewPort)
|
|
SetDrawLayer(nil, 0)
|
|
end
|
|
end
|
|
self.controls.characterLevel = new("EditControl", {"LEFT",self.controls.pointDisplay,"RIGHT"}, 12, 0, 106, 20, "", "Level", "%D", 3, function(buf)
|
|
self.characterLevel = m_min(tonumber(buf) or 1, 100)
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.characterLevel.tooltipFunc = function(tooltip)
|
|
if tooltip:CheckForUpdate(self.characterLevel) then
|
|
tooltip:AddLine(16, "Experience multiplier:")
|
|
local playerLevel = self.characterLevel
|
|
local safeZone = 3 + m_floor(playerLevel / 16)
|
|
for level, expLevel in ipairs(self.data.monsterExperienceLevelMap) do
|
|
local diff = m_abs(playerLevel - expLevel) - safeZone
|
|
local mult
|
|
if diff <= 0 then
|
|
mult = 1
|
|
else
|
|
mult = ((playerLevel + 5) / (playerLevel + 5 + diff ^ 2.5)) ^ 1.5
|
|
end
|
|
if playerLevel >= 95 then
|
|
mult = mult * (1 / (1 + 0.1 * (playerLevel - 94)))
|
|
end
|
|
if mult > 0.01 then
|
|
local line = level
|
|
if level >= 68 then
|
|
line = line .. string.format(" (Tier %d)", level - 67)
|
|
end
|
|
line = line .. string.format(": %.1f%%", mult * 100)
|
|
tooltip:AddLine(14, line)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.controls.classDrop = new("DropDownControl", {"LEFT",self.controls.characterLevel,"RIGHT"}, 8, 0, 100, 20, nil, function(index, value)
|
|
if value.classId ~= self.spec.curClassId then
|
|
if self.spec:CountAllocNodes() == 0 or self.spec:IsClassConnected(value.classId) then
|
|
self.spec:SelectClass(value.classId)
|
|
self.spec:AddUndoState()
|
|
self.buildFlag = true
|
|
else
|
|
main:OpenConfirmPopup("Class Change", "Changing class to "..value.label.." will reset your passive tree.\nThis can be avoided by connecting one of the "..value.label.." starting nodes to your tree.", "Continue", function()
|
|
self.spec:SelectClass(value.classId)
|
|
self.spec:AddUndoState()
|
|
self.buildFlag = true
|
|
end)
|
|
end
|
|
end
|
|
end)
|
|
self.controls.ascendDrop = new("DropDownControl", {"LEFT",self.controls.classDrop,"RIGHT"}, 8, 0, 120, 20, nil, function(index, value)
|
|
self.spec:SelectAscendClass(value.ascendClassId)
|
|
self.spec:AddUndoState()
|
|
self.buildFlag = true
|
|
end)
|
|
|
|
-- List of display stats
|
|
-- This defines the stats in the side bar, and also which stats show in node/item comparisons
|
|
-- This may be user-customisable in the future
|
|
self.displayStats = {
|
|
{ stat = "ActiveMinionLimit", label = "Active Minion Limit", fmt = "d" },
|
|
{ stat = "AverageHit", label = "Average Hit", fmt = ".1f", compPercent = true },
|
|
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compPercent = true, flag = "attack" },
|
|
{ stat = "Speed", label = "Attack Rate", fmt = ".2f", compPercent = true, flag = "attack" },
|
|
{ stat = "Speed", label = "Cast Rate", fmt = ".2f", compPercent = true, flag = "spell" },
|
|
{ stat = "HitSpeed", label = "Hit Rate", fmt = ".2f", compPercent = true },
|
|
{ stat = "TrapThrowingTime", label = "Trap Throwing Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, },
|
|
{ stat = "TrapCooldown", label = "Trap Cooldown", fmt = ".2fs", lowerIsBetter = true },
|
|
{ stat = "MineLayingTime", label = "Mine Laying Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, },
|
|
{ stat = "TotemPlacementTime", label = "Totem Placement Time", fmt = ".2fs", compPercent = true, lowerIsBetter = true, },
|
|
{ stat = "PreEffectiveCritChance", label = "Crit Chance", fmt = ".2f%%" },
|
|
{ stat = "CritChance", label = "Effective Crit Chance", fmt = ".2f%%", condFunc = function(v,o) return v ~= o.PreEffectiveCritChance end },
|
|
{ stat = "CritMultiplier", label = "Crit Multiplier", fmt = "d%%", pc = true, condFunc = function(v,o) return (o.CritChance or 0) > 0 end },
|
|
{ stat = "HitChance", label = "Hit Chance", fmt = ".0f%%", flag = "attack" },
|
|
{ stat = "TotalDPS", label = "Total DPS", fmt = ".1f", compPercent = true, flag = "notAverage" },
|
|
{ stat = "TotalDot", label = "DoT DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "BleedDPS", label = "Bleed DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "IgniteDPS", label = "Ignite DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "IgniteDamage", label = "Total Damage per Ignite", fmt = ".1f", compPercent = true },
|
|
{ stat = "WithIgniteDPS", label = "Total DPS inc. Ignite", fmt = ".1f", compPercent = true },
|
|
{ stat = "WithIgniteAverageDamage", label = "Average Dmg. inc. Ignite", fmt = ".1f", compPercent = true },
|
|
{ stat = "PoisonDPS", label = "Poison DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "PoisonDamage", label = "Total Damage per Poison", fmt = ".1f", compPercent = true },
|
|
{ stat = "WithPoisonDPS", label = "Total DPS inc. Poison", fmt = ".1f", compPercent = true, flag = "poison", condFunc = function(v,o) return v ~= o.TotalDPS end },
|
|
{ stat = "WithPoisonAverageDamage", label = "Average Dmg. inc. Poison", fmt = ".1f", compPercent = true, flag = "poison", condFunc = function(v,o) return v ~= o.AverageDamage end },
|
|
{ stat = "DecayDPS", label = "Decay DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "Cooldown", label = "Skill Cooldown", fmt = ".2fs", lowerIsBetter = true },
|
|
{ stat = "AreaOfEffectRadius", label = "AoE Radius", fmt = "d" },
|
|
{ stat = "ManaCost", label = "Mana Cost", fmt = "d", compPercent = true, lowerIsBetter = true, condFunc = function() return true end },
|
|
{ },
|
|
{ stat = "Str", label = "Strength", fmt = "d" },
|
|
{ stat = "ReqStr", label = "Strength Required", fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Str end },
|
|
{ stat = "Dex", label = "Dexterity", fmt = "d" },
|
|
{ stat = "ReqDex", label = "Dexterity Required", fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Dex end },
|
|
{ stat = "Int", label = "Intelligence", fmt = "d" },
|
|
{ stat = "ReqInt", label = "Intelligence Required", fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Int end },
|
|
{ },
|
|
{ stat = "Life", label = "Total Life", fmt = "d", compPercent = true },
|
|
{ stat = "Spec:LifeInc", label = "%Inc Life from Tree", fmt = "d%%", condFunc = function(v,o) return v > 0 and o.Life > 1 end },
|
|
{ stat = "LifeUnreserved", label = "Unreserved Life", fmt = "d", condFunc = function(v,o) return v < o.Life end, compPercent = true },
|
|
{ stat = "LifeUnreservedPercent", label = "Unreserved Life", fmt = "d%%", condFunc = function(v,o) return v < 100 end },
|
|
{ stat = "LifeRegen", label = "Life Regen", fmt = ".1f" },
|
|
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", compPercent = true },
|
|
{ stat = "LifeLeechGainPerHit", label = "Life Leech/Gain per Hit", fmt = ".1f", compPercent = true },
|
|
{ },
|
|
{ stat = "Mana", label = "Total Mana", fmt = "d", compPercent = true },
|
|
{ stat = "Spec:ManaInc", label = "%Inc Mana from Tree", fmt = "d%%" },
|
|
{ stat = "ManaUnreserved", label = "Unreserved Mana", fmt = "d", condFunc = function(v,o) return v < o.Mana end, compPercent = true },
|
|
{ stat = "ManaUnreservedPercent", label = "Unreserved Mana", fmt = "d%%", condFunc = function(v,o) return v < 100 end },
|
|
{ stat = "ManaRegen", label = "Mana Regen", fmt = ".1f" },
|
|
{ stat = "ManaLeechGainRate", label = "Mana Leech/On Hit Rate", fmt = ".1f", compPercent = true },
|
|
{ stat = "ManaLeechGainPerHit", label = "Mana Leech/Gain per Hit", fmt = ".1f", compPercent = true },
|
|
{ },
|
|
{ stat = "TotalDegen", label = "Total Degen", fmt = ".1f", lowerIsBetter = true },
|
|
{ stat = "NetRegen", label = "Net Regen", fmt = "+.1f" },
|
|
{ stat = "NetLifeRegen", label = "Net Life Regen", fmt = "+.1f" },
|
|
{ stat = "NetManaRegen", label = "Net Mana Regen", fmt = "+.1f" },
|
|
{ },
|
|
{ stat = "EnergyShield", label = "Energy Shield", fmt = "d", compPercent = true },
|
|
{ stat = "Spec:EnergyShieldInc", label = "%Inc ES from Tree", fmt = "d%%" },
|
|
{ stat = "EnergyShieldRegen", label = "Energy Shield Regen", fmt = ".1f" },
|
|
{ stat = "EnergyShieldLeechGainRate", label = "ES Leech/On Hit Rate", fmt = ".1f", compPercent = true },
|
|
{ stat = "EnergyShieldLeechGainPerHit", label = "ES Leech/Gain per Hit", fmt = ".1f", compPercent = true },
|
|
{ stat = "Evasion", label = "Evasion rating", fmt = "d", compPercent = true },
|
|
{ stat = "Spec:EvasionInc", label = "%Inc Evasion from Tree", fmt = "d%%" },
|
|
{ stat = "MeleeEvadeChance", label = "Evade Chance", fmt = "d%%", condFunc = function(v,o) return v > 0 and o.MeleeEvadeChance == o.ProjectileEvadeChance end },
|
|
{ stat = "MeleeEvadeChance", label = "Melee Evade Chance", fmt = "d%%", condFunc = function(v,o) return v > 0 and o.MeleeEvadeChance ~= o.ProjectileEvadeChance end },
|
|
{ stat = "ProjectileEvadeChance", label = "Projectile Evade Chance", fmt = "d%%", condFunc = function(v,o) return v > 0 and o.MeleeEvadeChance ~= o.ProjectileEvadeChance end },
|
|
{ stat = "Armour", label = "Armour", fmt = "d", compPercent = true },
|
|
{ stat = "Spec:ArmourInc", label = "%Inc Armour from Tree", fmt = "d%%" },
|
|
{ stat = "PhysicalDamageReduction", label = "Phys. Damage Reduction", fmt = "d%%" },
|
|
{ stat = "EffectiveMovementSpeedMod", label = "Movement Speed Modifier", fmt = "+d%%", mod = true, condFunc = function() return true end },
|
|
{ stat = "BlockChance", label = "Block Chance", fmt = "d%%" },
|
|
{ stat = "SpellBlockChance", label = "Spell Block Chance", fmt = "d%%" },
|
|
{ stat = "AttackDodgeChance", label = "Attack Dodge Chance", fmt = "d%%" },
|
|
{ stat = "SpellDodgeChance", label = "Spell Dodge Chance", fmt = "d%%" },
|
|
{ },
|
|
{ stat = "FireResist", label = "Fire Resistance", fmt = "d%%", condFunc = function() return true end },
|
|
{ stat = "ColdResist", label = "Cold Resistance", fmt = "d%%", condFunc = function() return true end },
|
|
{ stat = "LightningResist", label = "Lightning Resistance", fmt = "d%%", condFunc = function() return true end },
|
|
{ stat = "ChaosResist", label = "Chaos Resistance", fmt = "d%%", condFunc = function() return true end },
|
|
{ stat = "FireResistOverCap", label = "Fire Res. Over Max", fmt = "d%%" },
|
|
{ stat = "ColdResistOverCap", label = "Cold Res. Over Max", fmt = "d%%" },
|
|
{ stat = "LightningResistOverCap", label = "Lightning Res. Over Max", fmt = "d%%" },
|
|
{ stat = "ChaosResistOverCap", label = "Chaos Res. Over Max", fmt = "d%%" },
|
|
}
|
|
self.minionDisplayStats = {
|
|
{ stat = "AverageDamage", label = "Average Damage", fmt = ".1f", compPercent = true },
|
|
{ stat = "Speed", label = "Attack/Cast Rate", fmt = ".2f", compPercent = true },
|
|
{ stat = "HitSpeed", label = "Hit Rate", fmt = ".2f" },
|
|
{ stat = "TotalDPS", label = "Total DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "TotalDot", label = "DoT DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "BleedDPS", label = "Bleed DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "IgniteDPS", label = "Ignite DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "WithPoisonDPS", label = "DPS inc. Poison", fmt = ".1f", compPercent = true },
|
|
{ stat = "DecayDPS", label = "Decay DPS", fmt = ".1f", compPercent = true },
|
|
{ stat = "Cooldown", label = "Skill Cooldown", fmt = ".2fs", lowerIsBetter = true },
|
|
{ stat = "Life", label = "Total Life", fmt = ".1f", compPercent = true },
|
|
{ stat = "LifeRegen", label = "Life Regen", fmt = ".1f" },
|
|
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", compPercent = true },
|
|
}
|
|
self.extraSaveStats = {
|
|
"PowerCharges",
|
|
"PowerChargesMax",
|
|
"FrenzyCharges",
|
|
"FrenzyChargesMax",
|
|
"EnduranceCharges",
|
|
"EnduranceChargesMax",
|
|
"ActiveTotemLimit",
|
|
"ActiveMinionLimit",
|
|
}
|
|
|
|
self.viewMode = "TREE"
|
|
|
|
self.targetVersion = defaultTargetVersion
|
|
self.characterLevel = 1
|
|
self.controls.characterLevel:SetText(tostring(self.characterLevel))
|
|
self.banditNormal = "None"
|
|
self.banditCruel = "None"
|
|
self.banditMerciless = "None"
|
|
self.pantheonMajorGod = "None"
|
|
self.pantheonMinorGod = "None"
|
|
self.spectreList = { }
|
|
|
|
-- Load build file
|
|
self.xmlSectionList = { }
|
|
if buildXML then
|
|
if self:LoadDB(buildXML, "Unnamed build") then
|
|
self:CloseBuild()
|
|
return
|
|
end
|
|
self.modFlag = true
|
|
else
|
|
if self:LoadDBFile() then
|
|
self:CloseBuild()
|
|
return
|
|
end
|
|
self.modFlag = false
|
|
end
|
|
|
|
if targetVersion then
|
|
self.targetVersion = targetVersion
|
|
end
|
|
self.targetVersionData = targetVersions[self.targetVersion]
|
|
|
|
if buildName == "~~temp~~" then
|
|
-- Remove temporary build file
|
|
os.remove(self.dbFileName)
|
|
self.buildName = "Unnamed build"
|
|
self.dbFileName = false
|
|
self.dbFileSubPath = nil
|
|
self.modFlag = true
|
|
end
|
|
|
|
-- Controls: Side bar
|
|
self.anchorSideBar = new("Control", nil, 4, 36, 0, 0)
|
|
self.controls.modeImport = new("ButtonControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 0, 134, 20, "Import/Export Build", function()
|
|
self.viewMode = "IMPORT"
|
|
end)
|
|
self.controls.modeImport.locked = function() return self.viewMode == "IMPORT" end
|
|
self.controls.modeNotes = new("ButtonControl", {"LEFT",self.controls.modeImport,"RIGHT"}, 4, 0, 58, 20, "Notes", function()
|
|
self.viewMode = "NOTES"
|
|
end)
|
|
self.controls.modeNotes.locked = function() return self.viewMode == "NOTES" end
|
|
self.controls.modeConfig = new("ButtonControl", {"TOPRIGHT",self.anchorSideBar,"TOPLEFT"}, 300, 0, 100, 20, "Configuration", function()
|
|
self.viewMode = "CONFIG"
|
|
end)
|
|
self.controls.modeConfig.locked = function() return self.viewMode == "CONFIG" end
|
|
self.controls.modeTree = new("ButtonControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 26, 72, 20, "Tree", function()
|
|
self.viewMode = "TREE"
|
|
end)
|
|
self.controls.modeTree.locked = function() return self.viewMode == "TREE" end
|
|
self.controls.modeSkills = new("ButtonControl", {"LEFT",self.controls.modeTree,"RIGHT"}, 4, 0, 72, 20, "Skills", function()
|
|
self.viewMode = "SKILLS"
|
|
end)
|
|
self.controls.modeSkills.locked = function() return self.viewMode == "SKILLS" end
|
|
self.controls.modeItems = new("ButtonControl", {"LEFT",self.controls.modeSkills,"RIGHT"}, 4, 0, 72, 20, "Items", function()
|
|
self.viewMode = "ITEMS"
|
|
end)
|
|
self.controls.modeItems.locked = function() return self.viewMode == "ITEMS" end
|
|
self.controls.modeCalcs = new("ButtonControl", {"LEFT",self.controls.modeItems,"RIGHT"}, 4, 0, 72, 20, "Calcs", function()
|
|
self.viewMode = "CALCS"
|
|
end)
|
|
self.controls.modeCalcs.locked = function() return self.viewMode == "CALCS" end
|
|
if self.targetVersion == "2_6" then
|
|
self.controls.banditNormal = new("DropDownControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 70, 100, 16, normalBanditDropList, function(index, value)
|
|
self.banditNormal = value.banditId
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.banditNormalLabel = new("LabelControl", {"BOTTOMLEFT",self.controls.banditNormal,"TOPLEFT"}, 0, 0, 0, 14, "^7Normal Bandit:")
|
|
self.controls.banditCruel = new("DropDownControl", {"LEFT",self.controls.banditNormal,"RIGHT"}, 0, 0, 100, 16, mercilessBanditDropList, function(index, value)
|
|
self.banditCruel = value.banditId
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.banditCruelLabel = new("LabelControl", {"BOTTOMLEFT",self.controls.banditCruel,"TOPLEFT"}, 0, 0, 0, 14, "^7Cruel Bandit:")
|
|
self.controls.banditMerciless = new("DropDownControl", {"LEFT",self.controls.banditCruel,"RIGHT"}, 0, 0, 100, 16, cruelBanditDropList, function(index, value)
|
|
self.banditMerciless = value.banditId
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.banditMercilessLabel = new("LabelControl", {"BOTTOMLEFT",self.controls.banditMerciless,"TOPLEFT"}, 0, 0, 0, 14, "^7Merciless Bandit:")
|
|
else
|
|
self.controls.bandit = new("DropDownControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 70, 300, 16, fooBanditDropList, function(index, value)
|
|
self.bandit = value.banditId
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.banditLabel = new("LabelControl", {"BOTTOMLEFT",self.controls.bandit,"TOPLEFT"}, 0, 0, 0, 14, "^7Bandit:")
|
|
-- The Pantheon
|
|
local function applyPantheonDescription(tooltip, mode, index, value)
|
|
tooltip:Clear()
|
|
if value.id == "None" then
|
|
return
|
|
end
|
|
local applyModes = { BODY = true, HOVER = true }
|
|
if applyModes[mode] then
|
|
local god = self.data.pantheons[value.id]
|
|
for _, soul in ipairs(god.souls) do
|
|
local name = soul.name
|
|
local lines = { }
|
|
for _, mod in ipairs(soul.mods) do
|
|
t_insert(lines, mod.line)
|
|
end
|
|
tooltip:AddLine(20, '^8'..name)
|
|
tooltip:AddLine(14, '^6'..table.concat(lines, '\n'))
|
|
tooltip:AddSeparator(10)
|
|
end
|
|
end
|
|
end
|
|
self.controls.pantheonMajorGod = new("DropDownControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 110, 300, 16, PantheonMajorGodDropList, function(index, value)
|
|
self.pantheonMajorGod = value.id
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.pantheonMajorGod.tooltipFunc = applyPantheonDescription
|
|
self.controls.pantheonMinorGod = new("DropDownControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, 130, 300, 16, PantheonMinorGodDropList, function(index, value)
|
|
self.pantheonMinorGod = value.id
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.pantheonMinorGod.tooltipFunc = applyPantheonDescription
|
|
self.controls.pantheonLabel = new("LabelControl", {"BOTTOMLEFT",self.controls.pantheonMajorGod,"TOPLEFT"}, 0, 0, 0, 14, "^7The Pantheon:")
|
|
end
|
|
local mainSkillPosY = (self.targetVersion == "2_6") and 95 or 155 -- The Pantheon's DropDown space
|
|
self.controls.mainSkillLabel = new("LabelControl", {"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, 0, mainSkillPosY, 300, 16, "^7Main Skill:")
|
|
self.controls.mainSocketGroup = new("DropDownControl", {"TOPLEFT",self.controls.mainSkillLabel,"BOTTOMLEFT"}, 0, 2, 300, 16, nil, function(index, value)
|
|
self.mainSocketGroup = index
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.mainSocketGroup.tooltipFunc = function(tooltip, mode, index, value)
|
|
local socketGroup = self.skillsTab.socketGroupList[index]
|
|
if socketGroup and tooltip:CheckForUpdate(socketGroup, self.outputRevision) then
|
|
self.skillsTab:AddSocketGroupTooltip(tooltip, socketGroup)
|
|
end
|
|
end
|
|
self.controls.mainSkill = new("DropDownControl", {"TOPLEFT",self.controls.mainSocketGroup,"BOTTOMLEFT"}, 0, 2, 300, 16, nil, function(index, value)
|
|
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
|
|
mainSocketGroup.mainActiveSkill = index
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.mainSkillPart = new("DropDownControl", {"TOPLEFT",self.controls.mainSkill,"BOTTOMLEFT",true}, 0, 2, 200, 18, nil, function(index, value)
|
|
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
|
|
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
|
|
srcInstance.skillPart = index
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.mainSkillMinion = new("DropDownControl", {"TOPLEFT",self.controls.mainSkillPart,"BOTTOMLEFT",true}, 0, 2, 178, 18, nil, function(index, value)
|
|
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
|
|
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
|
|
if value.itemSetId then
|
|
srcInstance.skillMinionItemSet = value.itemSetId
|
|
else
|
|
srcInstance.skillMinion = value.minionId
|
|
end
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
function self.controls.mainSkillMinion.CanReceiveDrag(control, type, value)
|
|
if type == "Item" and control.list[control.selIndex] and control.list[control.selIndex].itemSetId then
|
|
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
|
|
local minionUses = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.grantedEffect.minionUses
|
|
return minionUses and minionUses[value:GetPrimarySlot()] -- O_O
|
|
end
|
|
end
|
|
function self.controls.mainSkillMinion.ReceiveDrag(control, type, value, source)
|
|
self.itemsTab:EquipItemInSet(value, control.list[control.selIndex].itemSetId)
|
|
end
|
|
function self.controls.mainSkillMinion.tooltipFunc(tooltip, mode, index, value)
|
|
tooltip:Clear()
|
|
if value.itemSetId then
|
|
self.itemsTab:AddItemSetTooltip(tooltip, self.itemsTab.itemSets[value.itemSetId])
|
|
tooltip:AddSeparator(14)
|
|
tooltip:AddLine(14, colorCodes.TIP.."Tip: You can drag items from the Items tab onto this dropdown to equip them onto the minion.")
|
|
end
|
|
end
|
|
self.controls.mainSkillMinionLibrary = new("ButtonControl", {"LEFT",self.controls.mainSkillMinion,"RIGHT"}, 2, 0, 120, 18, "Manage Spectres...", function()
|
|
self:OpenSpectreLibrary()
|
|
end)
|
|
self.controls.mainSkillMinionSkill = new("DropDownControl", {"TOPLEFT",self.controls.mainSkillMinion,"BOTTOMLEFT",true}, 0, 2, 200, 16, nil, function(index, value)
|
|
local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup]
|
|
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
|
|
srcInstance.skillMinionSkill = index
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
end)
|
|
self.controls.statBoxAnchor = new("Control", {"TOPLEFT",self.controls.mainSkillMinionSkill,"BOTTOMLEFT",true}, 0, 2, 0, 0)
|
|
self.controls.statBox = new("TextListControl", {"TOPLEFT",self.controls.statBoxAnchor,"BOTTOMLEFT"}, 0, 2, 300, 0, {{x=170,align="RIGHT_X"},{x=174,align="LEFT"}})
|
|
self.controls.statBox.height = function(control)
|
|
local x, y = control:GetPos()
|
|
return main.screenH - main.mainBarHeight - 4 - y
|
|
end
|
|
|
|
-- Initialise build components
|
|
self.data = data[self.targetVersion]
|
|
self.latestTree = main.tree[self.targetVersionData.latestTreeVersion]
|
|
self.importTab = new("ImportTab", self)
|
|
self.notesTab = new("NotesTab", self)
|
|
self.configTab = new("ConfigTab", self)
|
|
self.itemsTab = new("ItemsTab", self)
|
|
self.treeTab = new("TreeTab", self)
|
|
self.skillsTab = new("SkillsTab", self)
|
|
self.calcsTab = new("CalcsTab", self)
|
|
|
|
-- Load sections from the build file
|
|
self.savers = {
|
|
["Config"] = self.configTab,
|
|
["Notes"] = self.notesTab,
|
|
["Tree"] = self.treeTab,
|
|
["TreeView"] = self.treeTab.viewer,
|
|
["Items"] = self.itemsTab,
|
|
["Skills"] = self.skillsTab,
|
|
["Calcs"] = self.calcsTab,
|
|
["Import"] = self.importTab,
|
|
}
|
|
self.legacyLoaders = { -- Special loaders for legacy sections
|
|
["Spec"] = self.treeTab,
|
|
}
|
|
for _, node in ipairs(self.xmlSectionList) do
|
|
-- Check if there is a saver that can load this section
|
|
local saver = self.savers[node.elem] or self.legacyLoaders[node.elem]
|
|
if saver then
|
|
if saver:Load(node, self.dbFileName) then
|
|
self:CloseBuild()
|
|
return
|
|
end
|
|
end
|
|
end
|
|
for _, saver in pairs(self.savers) do
|
|
if saver.PostLoad then
|
|
saver:PostLoad()
|
|
end
|
|
end
|
|
|
|
if next(self.configTab.input) == nil then
|
|
-- Check for old calcs tab settings
|
|
self.configTab:ImportCalcSettings()
|
|
end
|
|
|
|
-- Initialise class dropdown
|
|
for classId, class in pairs(self.latestTree.classes) do
|
|
t_insert(self.controls.classDrop.list, {
|
|
label = class.name,
|
|
classId = classId,
|
|
})
|
|
end
|
|
table.sort(self.controls.classDrop.list, function(a, b) return a.label < b.label end)
|
|
|
|
-- Build calculation output tables
|
|
self.outputRevision = 1
|
|
self.calcsTab:BuildOutput()
|
|
self:RefreshStatList()
|
|
self.buildFlag = false
|
|
|
|
--[[
|
|
local testTooltip = new("Tooltip")
|
|
for _, item in pairs(main.uniqueDB.list) do
|
|
ConPrintf("%s", item.name)
|
|
self.itemsTab:AddItemTooltip(testTooltip, item)
|
|
testTooltip:Clear()
|
|
end
|
|
for _, item in pairs(main.rareDB.list) do
|
|
ConPrintf("%s", item.name)
|
|
self.itemsTab:AddItemTooltip(testTooltip, item)
|
|
testTooltip:Clear()
|
|
end
|
|
--]]
|
|
|
|
--[[
|
|
local start = GetTime()
|
|
SetProfiling(true)
|
|
for i = 1, 10 do
|
|
self.calcsTab:PowerBuilder()
|
|
end
|
|
SetProfiling(false)
|
|
ConPrintf("Power build time: %d msec", GetTime() - start)
|
|
--]]
|
|
|
|
self.abortSave = false
|
|
end
|
|
|
|
function buildMode:CanExit(mode)
|
|
if not self.targetVersion or not self.unsaved then
|
|
return true
|
|
end
|
|
self:OpenSavePopup(mode)
|
|
return false
|
|
end
|
|
|
|
function buildMode:Shutdown()
|
|
if launch.devMode and self.targetVersion and not self.abortSave then
|
|
if self.dbFileName then
|
|
self:SaveDBFile()
|
|
elseif self.unsaved then
|
|
self.dbFileName = main.buildPath.."~~temp~~.xml"
|
|
self.buildName = "~~temp~~"
|
|
self.dbFileSubPath = ""
|
|
self:SaveDBFile()
|
|
end
|
|
end
|
|
self.abortSave = nil
|
|
|
|
self.savers = nil
|
|
end
|
|
|
|
function buildMode:GetArgs()
|
|
return self.dbFileName, self.buildName
|
|
end
|
|
|
|
function buildMode:CloseBuild()
|
|
main:SetMode("LIST", self.dbFileName and self.buildName, self.dbFileSubPath)
|
|
end
|
|
|
|
function buildMode:Load(xml, fileName)
|
|
self.targetVersion = data[xml.attrib.targetVersion] and xml.attrib.targetVersion or defaultTargetVersion
|
|
if xml.attrib.viewMode then
|
|
self.viewMode = xml.attrib.viewMode
|
|
end
|
|
self.characterLevel = tonumber(xml.attrib.level) or 1
|
|
self.controls.characterLevel:SetText(tostring(self.characterLevel))
|
|
for _, diff in pairs({"bandit","banditNormal","banditCruel","banditMerciless","pantheonMajorGod","pantheonMinorGod"}) do
|
|
self[diff] = xml.attrib[diff] or "None"
|
|
end
|
|
self.mainSocketGroup = tonumber(xml.attrib.mainSkillIndex) or tonumber(xml.attrib.mainSocketGroup) or 1
|
|
wipeTable(self.spectreList)
|
|
for _, child in ipairs(xml) do
|
|
if child.elem == "Spectre" then
|
|
if child.attrib.id and data[self.targetVersion].minions[child.attrib.id] then
|
|
t_insert(self.spectreList, child.attrib.id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function buildMode:Save(xml)
|
|
xml.attrib = {
|
|
targetVersion = self.targetVersion,
|
|
viewMode = self.viewMode,
|
|
level = tostring(self.characterLevel),
|
|
className = self.spec.curClassName,
|
|
ascendClassName = self.spec.curAscendClassName,
|
|
bandit = self.bandit,
|
|
banditNormal = self.banditNormal,
|
|
banditCruel = self.banditCruel,
|
|
banditMerciless = self.banditMerciless,
|
|
pantheonMajorGod = self.pantheonMajorGod,
|
|
pantheonMinorGod = self.pantheonMinorGod,
|
|
mainSocketGroup = tostring(self.mainSocketGroup),
|
|
}
|
|
for _, id in ipairs(self.spectreList) do
|
|
t_insert(xml, { elem = "Spectre", attrib = { id = id } })
|
|
end
|
|
for index, statData in ipairs(self.displayStats) do
|
|
if statData.stat then
|
|
local statVal = self.calcsTab.mainOutput[statData.stat]
|
|
if statVal then
|
|
t_insert(xml, { elem = "PlayerStat", attrib = { stat = statData.stat, value = tostring(statVal) } })
|
|
end
|
|
end
|
|
end
|
|
for index, stat in ipairs(self.extraSaveStats) do
|
|
local statVal = self.calcsTab.mainOutput[stat]
|
|
if statVal then
|
|
t_insert(xml, { elem = "PlayerStat", attrib = { stat = stat, value = tostring(statVal) } })
|
|
end
|
|
end
|
|
if self.calcsTab.mainEnv.minion then
|
|
for index, statData in ipairs(self.minionDisplayStats) do
|
|
if statData.stat then
|
|
local statVal = self.calcsTab.mainOutput.Minion[statData.stat]
|
|
if statVal then
|
|
t_insert(xml, { elem = "MinionStat", attrib = { stat = statData.stat, value = tostring(statVal) } })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.modFlag = false
|
|
end
|
|
|
|
function buildMode:OnFrame(inputEvents)
|
|
if not self.targetVersion then
|
|
main:DrawBackground(main.viewPort)
|
|
return
|
|
end
|
|
|
|
if self.abortSave and not launch.devMode then
|
|
self:CloseBuild()
|
|
end
|
|
|
|
for id, event in ipairs(inputEvents) do
|
|
if event.type == "KeyDown" then
|
|
if event.key == "MOUSE4" then
|
|
if self.unsaved then
|
|
self:OpenSavePopup("LIST")
|
|
else
|
|
self:CloseBuild()
|
|
end
|
|
elseif IsKeyDown("CTRL") then
|
|
if event.key == "s" then
|
|
self:SaveDBFile()
|
|
inputEvents[id] = nil
|
|
elseif event.key == "w" then
|
|
if self.unsaved then
|
|
self:OpenSavePopup("LIST")
|
|
else
|
|
self:CloseBuild()
|
|
end
|
|
elseif event.key == "1" then
|
|
self.viewMode = "TREE"
|
|
elseif event.key == "2" then
|
|
self.viewMode = "SKILLS"
|
|
elseif event.key == "3" then
|
|
self.viewMode = "ITEMS"
|
|
elseif event.key == "4" then
|
|
self.viewMode = "CALCS"
|
|
elseif event.key == "5" then
|
|
self.viewMode = "CONFIG"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self:ProcessControlsInput(inputEvents, main.viewPort)
|
|
|
|
-- Update contents of ascendancy class dropdown
|
|
wipeTable(self.controls.ascendDrop.list)
|
|
for i = 0, #self.spec.curClass.classes do
|
|
local ascendClass = self.spec.curClass.classes[i]
|
|
t_insert(self.controls.ascendDrop.list, {
|
|
label = ascendClass.name,
|
|
ascendClassId = i,
|
|
})
|
|
end
|
|
|
|
self.controls.classDrop:SelByValue(self.spec.curClassId, "classId")
|
|
self.controls.ascendDrop:SelByValue(self.spec.curAscendClassId, "ascendClassId")
|
|
|
|
for _, diff in pairs({"bandit","banditNormal","banditCruel","banditMerciless"}) do
|
|
if self.controls[diff] then
|
|
self.controls[diff]:SelByValue(self[diff], "banditId")
|
|
end
|
|
end
|
|
for _, diff in pairs({"pantheonMajorGod","pantheonMinorGod"}) do
|
|
if self.controls[diff] then
|
|
self.controls[diff]:SelByValue(self[diff], "id")
|
|
end
|
|
end
|
|
|
|
if self.buildFlag then
|
|
-- Rebuild calculation output tables
|
|
self.outputRevision = self.outputRevision + 1
|
|
self.buildFlag = false
|
|
self.calcsTab:BuildOutput()
|
|
self:RefreshStatList()
|
|
end
|
|
if main.showThousandsSidebar ~= self.lastShowThousandsSidebar then
|
|
self:RefreshStatList()
|
|
end
|
|
|
|
-- Update contents of main skill dropdowns
|
|
self:RefreshSkillSelectControls(self.controls, self.mainSocketGroup, "")
|
|
|
|
-- Draw contents of current tab
|
|
local sideBarWidth = 312
|
|
local tabViewPort = {
|
|
x = sideBarWidth,
|
|
y = 32,
|
|
width = main.screenW - sideBarWidth,
|
|
height = main.screenH - 32
|
|
}
|
|
if self.viewMode == "IMPORT" then
|
|
self.importTab:Draw(tabViewPort, inputEvents)
|
|
elseif self.viewMode == "NOTES" then
|
|
self.notesTab:Draw(tabViewPort, inputEvents)
|
|
elseif self.viewMode == "CONFIG" then
|
|
self.configTab:Draw(tabViewPort, inputEvents)
|
|
elseif self.viewMode == "TREE" then
|
|
self.treeTab:Draw(tabViewPort, inputEvents)
|
|
elseif self.viewMode == "SKILLS" then
|
|
self.skillsTab:Draw(tabViewPort, inputEvents)
|
|
elseif self.viewMode == "ITEMS" then
|
|
self.itemsTab:Draw(tabViewPort, inputEvents)
|
|
elseif self.viewMode == "CALCS" then
|
|
self.calcsTab:Draw(tabViewPort, inputEvents)
|
|
end
|
|
|
|
self.unsaved = self.modFlag or self.notesTab.modFlag or self.configTab.modFlag or self.treeTab.modFlag or self.spec.modFlag or self.skillsTab.modFlag or self.itemsTab.modFlag or self.calcsTab.modFlag
|
|
|
|
SetDrawLayer(5)
|
|
|
|
-- Draw top bar background
|
|
SetDrawColor(0.2, 0.2, 0.2)
|
|
DrawImage(nil, 0, 0, main.screenW, 28)
|
|
SetDrawColor(0.85, 0.85, 0.85)
|
|
DrawImage(nil, 0, 28, main.screenW, 4)
|
|
DrawImage(nil, main.screenW/2 - 2, 0, 4, 28)
|
|
|
|
-- Draw side bar background
|
|
SetDrawColor(0.1, 0.1, 0.1)
|
|
DrawImage(nil, 0, 32, sideBarWidth - 4, main.screenH - 32)
|
|
SetDrawColor(0.85, 0.85, 0.85)
|
|
DrawImage(nil, sideBarWidth - 4, 32, 4, main.screenH - 32)
|
|
|
|
self:DrawControls(main.viewPort)
|
|
end
|
|
|
|
-- Opens the game version selection popup
|
|
function buildMode:OpenTargetVersionPopup(initial)
|
|
local controls = { }
|
|
local function setVersion(version)
|
|
if version == self.targetVersion then
|
|
main:ClosePopup()
|
|
return
|
|
end
|
|
if initial then
|
|
main:ClosePopup()
|
|
self:Shutdown()
|
|
self:Init(false, self.buildName, nil, version)
|
|
end
|
|
end
|
|
controls.label = new("LabelControl", nil, 0, 20, 0, 16, "^7Which game version will this build use?")
|
|
controls.version2_6 = new("ButtonControl", nil, -90, 50, 170, 20, "2.6 (Atlas of Worlds)", function()
|
|
setVersion("2_6")
|
|
end)
|
|
controls.version3_0 = new("ButtonControl", nil, 90, 50, 170, 20, "3.0 (Fall of Oriath)", function()
|
|
setVersion("3_0")
|
|
end)
|
|
controls.note = new("LabelControl", nil, 0, 80, 0, 14, "^7Tip: Existing builds can be converted between versions\nusing the 'Game Version' option in the Configuration tab.")
|
|
controls.cancel = new("ButtonControl", nil, 0, 120, 80, 20, "Cancel", function()
|
|
main:ClosePopup()
|
|
if initial then
|
|
self:CloseBuild()
|
|
end
|
|
end)
|
|
main:OpenPopup(370, 150, "Game Version", controls, nil, nil, "cancel")
|
|
end
|
|
|
|
function buildMode:OpenSavePopup(mode, newVersion)
|
|
local modeDesc = {
|
|
["LIST"] = "now?",
|
|
["EXIT"] = "before exiting?",
|
|
["UPDATE"] = "before updating?",
|
|
["VERSION"] = "before converting?",
|
|
}
|
|
local controls = { }
|
|
controls.label = new("LabelControl", nil, 0, 20, 0, 16, "^7This build has unsaved changes.\nDo you want to save them "..modeDesc[mode])
|
|
controls.save = new("ButtonControl", nil, -90, 70, 80, 20, "Save", function()
|
|
main:ClosePopup()
|
|
self.actionOnSave = mode
|
|
self.versionOnSave = newVersion
|
|
self:SaveDBFile()
|
|
end)
|
|
controls.noSave = new("ButtonControl", nil, 0, 70, 80, 20, "Don't Save", function()
|
|
main:ClosePopup()
|
|
if mode == "LIST" then
|
|
self:CloseBuild()
|
|
elseif mode == "EXIT" then
|
|
Exit()
|
|
elseif mode == "UPDATE" then
|
|
launch:ApplyUpdate(launch.updateAvailable)
|
|
elseif mode == "VERSION" then
|
|
self:Shutdown()
|
|
self:Init(self.dbFileName, self.buildName, nil, newVersion)
|
|
end
|
|
end)
|
|
controls.close = new("ButtonControl", nil, 90, 70, 80, 20, "Cancel", function()
|
|
main:ClosePopup()
|
|
end)
|
|
main:OpenPopup(300, 100, "Save Changes", controls)
|
|
end
|
|
|
|
function buildMode:OpenSaveAsPopup()
|
|
local newFileName, newBuildName
|
|
local controls = { }
|
|
local function updateBuildName()
|
|
local buf = controls.edit.buf
|
|
newFileName = main.buildPath..controls.folder.subPath..buf..".xml"
|
|
newBuildName = buf
|
|
controls.save.enabled = false
|
|
if buf:match("%S") then
|
|
local out = io.open(newFileName, "r")
|
|
if out then
|
|
out:close()
|
|
else
|
|
controls.save.enabled = true
|
|
end
|
|
end
|
|
end
|
|
controls.label = new("LabelControl", nil, 0, 20, 0, 16, "^7Enter new build name:")
|
|
controls.edit = new("EditControl", nil, 0, 40, 450, 20, self.dbFileName and self.buildName, nil, "\\/:%*%?\"<>|%c", 100, function(buf)
|
|
updateBuildName()
|
|
end)
|
|
controls.folderLabel = new("LabelControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 70, 0, 16, "^7Folder:")
|
|
controls.newFolder = new("ButtonControl", {"TOPLEFT",nil,"TOPLEFT"}, 100, 67, 94, 20, "New Folder...", function()
|
|
main:OpenNewFolderPopup(main.buildPath..controls.folder.subPath, function(newFolderName)
|
|
if newFolderName then
|
|
controls.folder:OpenFolder(newFolderName)
|
|
end
|
|
end)
|
|
end)
|
|
controls.folder = new("FolderListControl", nil, 0, 115, 450, 100, self.dbFileSubPath, function(subPath)
|
|
updateBuildName()
|
|
end)
|
|
controls.save = new("ButtonControl", nil, -45, 225, 80, 20, "Save", function()
|
|
main:ClosePopup()
|
|
self.dbFileName = newFileName
|
|
self.buildName = newBuildName
|
|
self.dbFileSubPath = controls.folder.subPath
|
|
self:SaveDBFile()
|
|
end)
|
|
controls.save.enabled = false
|
|
controls.close = new("ButtonControl", nil, 45, 225, 80, 20, "Cancel", function()
|
|
main:ClosePopup()
|
|
self.actionOnSave = nil
|
|
self.versionOnSave = nil
|
|
end)
|
|
main:OpenPopup(470, 255, self.dbFileName and "Save As" or "Save", controls, "save", "edit", "close")
|
|
end
|
|
|
|
-- Open the spectre library popup
|
|
function buildMode:OpenSpectreLibrary()
|
|
local destList = copyTable(self.spectreList)
|
|
local sourceList = { }
|
|
for id in pairs(self.data.spectres) do
|
|
t_insert(sourceList, id)
|
|
end
|
|
table.sort(sourceList, function(a,b)
|
|
if self.data.minions[a].name == self.data.minions[b].name then
|
|
return a < b
|
|
else
|
|
return self.data.minions[a].name < self.data.minions[b].name
|
|
end
|
|
end)
|
|
local controls = { }
|
|
controls.list = new("MinionListControl", nil, -100, 40, 190, 250, self.data, destList)
|
|
controls.source = new("MinionListControl", nil, 100, 40, 190, 250, self.data, sourceList, controls.list)
|
|
controls.save = new("ButtonControl", nil, -45, 300, 80, 20, "Save", function()
|
|
self.spectreList = destList
|
|
self.modFlag = true
|
|
self.buildFlag = true
|
|
main:ClosePopup()
|
|
end)
|
|
controls.cancel = new("ButtonControl", nil, 45, 300, 80, 20, "Cancel", function()
|
|
main:ClosePopup()
|
|
end)
|
|
main:OpenPopup(410, 330, "Spectre Library", controls)
|
|
end
|
|
|
|
-- Refresh the set of controls used to select main group/skill/minion
|
|
function buildMode:RefreshSkillSelectControls(controls, mainGroup, suffix)
|
|
controls.mainSocketGroup.selIndex = mainGroup
|
|
wipeTable(controls.mainSocketGroup.list)
|
|
for i, socketGroup in pairs(self.skillsTab.socketGroupList) do
|
|
controls.mainSocketGroup.list[i] = { val = i, label = socketGroup.displayLabel }
|
|
end
|
|
if #controls.mainSocketGroup.list == 0 then
|
|
controls.mainSocketGroup.list[1] = { val = 1, label = "<No skills added yet>" }
|
|
controls.mainSkill.shown = false
|
|
controls.mainSkillPart.shown = false
|
|
controls.mainSkillMinion.shown = false
|
|
controls.mainSkillMinionSkill.shown = false
|
|
else
|
|
local mainSocketGroup = self.skillsTab.socketGroupList[mainGroup]
|
|
local displaySkillList = mainSocketGroup["displaySkillList"..suffix]
|
|
local mainActiveSkill = mainSocketGroup["mainActiveSkill"..suffix] or 1
|
|
wipeTable(controls.mainSkill.list)
|
|
for i, activeSkill in ipairs(displaySkillList) do
|
|
t_insert(controls.mainSkill.list, { val = i, label = activeSkill.activeEffect.grantedEffect.name })
|
|
end
|
|
controls.mainSkill.enabled = #displaySkillList > 1
|
|
controls.mainSkill.selIndex = mainActiveSkill
|
|
controls.mainSkill.shown = true
|
|
controls.mainSkillPart.shown = false
|
|
controls.mainSkillMinion.shown = false
|
|
controls.mainSkillMinionLibrary.shown = false
|
|
controls.mainSkillMinionSkill.shown = false
|
|
if displaySkillList[1] then
|
|
local activeSkill = displaySkillList[mainActiveSkill]
|
|
local activeEffect = activeSkill.activeEffect
|
|
if activeEffect then
|
|
if activeEffect.grantedEffect.parts and #activeEffect.grantedEffect.parts > 1 then
|
|
controls.mainSkillPart.shown = true
|
|
wipeTable(controls.mainSkillPart.list)
|
|
for i, part in ipairs(activeEffect.grantedEffect.parts) do
|
|
t_insert(controls.mainSkillPart.list, { val = i, label = part.name })
|
|
end
|
|
controls.mainSkillPart.selIndex = activeEffect.srcInstance["skillPart"..suffix] or 1
|
|
end
|
|
if not activeSkill.skillFlags.disable and (activeEffect.grantedEffect.minionList or activeSkill.minionList[1]) then
|
|
wipeTable(controls.mainSkillMinion.list)
|
|
if activeEffect.grantedEffect.minionHasItemSet then
|
|
for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do
|
|
local itemSet = self.itemsTab.itemSets[itemSetId]
|
|
t_insert(controls.mainSkillMinion.list, {
|
|
label = itemSet.title or "Default Item Set",
|
|
itemSetId = itemSetId,
|
|
})
|
|
end
|
|
controls.mainSkillMinion:SelByValue(activeEffect.srcInstance["skillMinionItemSet"..suffix] or 1, "itemSetId")
|
|
else
|
|
controls.mainSkillMinionLibrary.shown = (activeEffect.grantedEffect.minionList and not activeEffect.grantedEffect.minionList[1])
|
|
for _, minionId in ipairs(activeSkill.minionList) do
|
|
t_insert(controls.mainSkillMinion.list, {
|
|
label = self.data.minions[minionId].name,
|
|
minionId = minionId,
|
|
})
|
|
end
|
|
controls.mainSkillMinion:SelByValue(activeEffect.srcInstance["skillMinion"..suffix] or controls.mainSkillMinion.list[1], "minionId")
|
|
end
|
|
controls.mainSkillMinion.enabled = #controls.mainSkillMinion.list > 1
|
|
controls.mainSkillMinion.shown = true
|
|
wipeTable(controls.mainSkillMinionSkill.list)
|
|
if activeSkill.minion then
|
|
for _, minionSkill in ipairs(activeSkill.minion.activeSkillList) do
|
|
t_insert(controls.mainSkillMinionSkill.list, minionSkill.activeEffect.grantedEffect.name)
|
|
end
|
|
controls.mainSkillMinionSkill.selIndex = activeEffect.srcInstance["skillMinionSkill"..suffix] or 1
|
|
controls.mainSkillMinionSkill.shown = true
|
|
controls.mainSkillMinionSkill.enabled = #controls.mainSkillMinionSkill.list > 1
|
|
else
|
|
t_insert(controls.mainSkillMinion.list, "<No spectres in build>")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function buildMode:FormatStat(statData, statVal)
|
|
local val = statVal * ((statData.pc or statData.mod) and 100 or 1) - (statData.mod and 100 or 0)
|
|
local color = (statVal >= 0 and "^7" or colorCodes.NEGATIVE)
|
|
local valStr = s_format("%"..statData.fmt, val)
|
|
if main.showThousandsSidebar then
|
|
valStr = color .. formatNumSep(valStr)
|
|
else
|
|
valStr = color .. valStr
|
|
end
|
|
self.lastShowThousandsSidebar = main.showThousandsSidebar
|
|
return valStr
|
|
end
|
|
|
|
-- Add stat list for given actor
|
|
function buildMode:AddDisplayStatList(statList, actor)
|
|
local statBoxList = self.controls.statBox.list
|
|
for index, statData in ipairs(statList) do
|
|
if statData.stat then
|
|
if not statData.flag or actor.mainSkill.skillFlags[statData.flag] then
|
|
local statVal = actor.output[statData.stat]
|
|
if statVal and ((statData.condFunc and statData.condFunc(statVal,actor.output)) or (not statData.condFunc and statVal ~= 0)) then
|
|
t_insert(statBoxList, {
|
|
height = 16,
|
|
"^7"..statData.label..":",
|
|
self:FormatStat(statData, statVal),
|
|
})
|
|
end
|
|
end
|
|
elseif not statBoxList[#statBoxList] or statBoxList[#statBoxList][1] then
|
|
t_insert(statBoxList, { height = 10 })
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Build list of side bar stats
|
|
function buildMode:RefreshStatList()
|
|
local statBoxList = wipeTable(self.controls.statBox.list)
|
|
if self.calcsTab.mainEnv.minion then
|
|
t_insert(statBoxList, { height = 18, "^7Minion:" })
|
|
self:AddDisplayStatList(self.minionDisplayStats, self.calcsTab.mainEnv.minion)
|
|
t_insert(statBoxList, { height = 10 })
|
|
t_insert(statBoxList, { height = 18, "^7Player:" })
|
|
end
|
|
if self.calcsTab.mainEnv.player.mainSkill.skillFlags.disable then
|
|
t_insert(statBoxList, { height = 16, "^7Skill disabled:" })
|
|
t_insert(statBoxList, { height = 14, align = "CENTER_X", x = 140, self.calcsTab.mainEnv.player.mainSkill.disableReason })
|
|
end
|
|
self:AddDisplayStatList(self.displayStats, self.calcsTab.mainEnv.player)
|
|
end
|
|
|
|
function buildMode:CompareStatList(tooltip, statList, actor, baseOutput, compareOutput, header, nodeCount)
|
|
local count = 0
|
|
for _, statData in ipairs(statList) do
|
|
if statData.stat and (not statData.flag or actor.mainSkill.skillFlags[statData.flag]) then
|
|
local statVal1 = compareOutput[statData.stat] or 0
|
|
local statVal2 = baseOutput[statData.stat] or 0
|
|
local diff = statVal1 - statVal2
|
|
if (diff > 0.001 or diff < -0.001) and (not statData.condFunc or statData.condFunc(statVal1,compareOutput) or statData.condFunc(statVal2,baseOutput)) then
|
|
if count == 0 then
|
|
tooltip:AddLine(14, header)
|
|
end
|
|
local color = ((statData.lowerIsBetter and diff < 0) or (not statData.lowerIsBetter and diff > 0)) and colorCodes.POSITIVE or colorCodes.NEGATIVE
|
|
local line = string.format("%s%+"..statData.fmt.." %s", color, diff * ((statData.pc or statData.mod) and 100 or 1), statData.label)
|
|
local pcPerPt = ""
|
|
if statData.compPercent and statVal1 ~= 0 and statVal2 ~= 0 then
|
|
local pc = statVal1 / statVal2 * 100 - 100
|
|
line = line .. string.format(" (%+.1f%%)", pc)
|
|
if nodeCount then
|
|
pcPerPt = string.format(" (%+.1f%%)", pc / nodeCount)
|
|
end
|
|
end
|
|
if nodeCount then
|
|
line = line .. string.format(" ^8[%+"..statData.fmt.."%s per point]", diff * ((statData.pc or statData.mod) and 100 or 1) / nodeCount, pcPerPt)
|
|
end
|
|
tooltip:AddLine(14, line)
|
|
count = count + 1
|
|
end
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
-- Compare values of all display stats between the two output tables, and add any changed stats to the tooltip
|
|
-- Adds the provided header line before the first stat line, if any are added
|
|
-- Returns the number of stat lines added
|
|
function buildMode:AddStatComparesToTooltip(tooltip, baseOutput, compareOutput, header, nodeCount)
|
|
local count = 0
|
|
if baseOutput.Minion and compareOutput.Minion then
|
|
count = count + self:CompareStatList(tooltip, self.minionDisplayStats, self.calcsTab.mainEnv.minion, baseOutput.Minion, compareOutput.Minion, header.."\n^7Minion:", nodeCount)
|
|
if count > 0 then
|
|
header = "^7Player:"
|
|
else
|
|
header = header.."\n^7Player:"
|
|
end
|
|
end
|
|
count = count + self:CompareStatList(tooltip, self.displayStats, self.calcsTab.mainEnv.player, baseOutput, compareOutput, header, nodeCount)
|
|
return count
|
|
end
|
|
|
|
-- Add requirements to tooltip
|
|
do
|
|
local req = { }
|
|
function buildMode:AddRequirementsToTooltip(tooltip, level, str, dex, int, strBase, dexBase, intBase)
|
|
if level and level > 0 then
|
|
t_insert(req, s_format("^x7F7F7FLevel %s%d", main:StatColor(level, nil, self.characterLevel), level))
|
|
end
|
|
if str and (str > 14 or str > self.calcsTab.mainOutput.Str) then
|
|
t_insert(req, s_format("%s%d ^x7F7F7FStr", main:StatColor(str, strBase, self.calcsTab.mainOutput.Str), str))
|
|
end
|
|
if dex and (dex > 14 or dex > self.calcsTab.mainOutput.Dex) then
|
|
t_insert(req, s_format("%s%d ^x7F7F7FDex", main:StatColor(dex, dexBase, self.calcsTab.mainOutput.Dex), dex))
|
|
end
|
|
if int and (int > 14 or int > self.calcsTab.mainOutput.Int) then
|
|
t_insert(req, s_format("%s%d ^x7F7F7FInt", main:StatColor(int, intBase, self.calcsTab.mainOutput.Int), int))
|
|
end
|
|
if req[1] then
|
|
tooltip:AddLine(16, "^x7F7F7FRequires "..table.concat(req, "^x7F7F7F, "))
|
|
tooltip:AddSeparator(10)
|
|
end
|
|
wipeTable(req)
|
|
end
|
|
end
|
|
|
|
function buildMode:LoadDB(xmlText, fileName)
|
|
-- Parse the XML
|
|
local dbXML, errMsg = common.xml.ParseXML(xmlText)
|
|
if not dbXML then
|
|
launch:ShowErrMsg("^1Error loading '%s': %s", fileName, errMsg)
|
|
return true
|
|
elseif dbXML[1].elem ~= "PathOfBuilding" then
|
|
launch:ShowErrMsg("^1Error parsing '%s': 'PathOfBuilding' root element missing", fileName)
|
|
return true
|
|
end
|
|
|
|
-- Load Build section first
|
|
for _, node in ipairs(dbXML[1]) do
|
|
if type(node) == "table" and node.elem == "Build" then
|
|
self:Load(node, self.dbFileName)
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Store other sections for later processing
|
|
for _, node in ipairs(dbXML[1]) do
|
|
if type(node) == "table" then
|
|
t_insert(self.xmlSectionList, node)
|
|
end
|
|
end
|
|
end
|
|
|
|
function buildMode:LoadDBFile()
|
|
if not self.dbFileName then
|
|
return
|
|
end
|
|
ConPrintf("Loading '%s'...", self.dbFileName)
|
|
local file = io.open(self.dbFileName, "r")
|
|
if not file then
|
|
return true
|
|
end
|
|
local xmlText = file:read("*a")
|
|
file:close()
|
|
return self:LoadDB(xmlText, self.dbFileName)
|
|
end
|
|
|
|
function buildMode:SaveDB(fileName)
|
|
local dbXML = { elem = "PathOfBuilding" }
|
|
|
|
-- Save Build section first
|
|
do
|
|
local node = { elem = "Build" }
|
|
self:Save(node)
|
|
t_insert(dbXML, node)
|
|
end
|
|
|
|
-- Call on all savers to save their data in their respective sections
|
|
for elem, saver in pairs(self.savers) do
|
|
local node = { elem = elem }
|
|
saver:Save(node)
|
|
t_insert(dbXML, node)
|
|
end
|
|
|
|
-- Compose the XML
|
|
local xmlText, errMsg = common.xml.ComposeXML(dbXML)
|
|
if not xmlText then
|
|
launch:ShowErrMsg("Error saving '%s': %s", fileName, errMsg)
|
|
else
|
|
return xmlText
|
|
end
|
|
end
|
|
|
|
function buildMode:SaveDBFile()
|
|
if not self.dbFileName then
|
|
self:OpenSaveAsPopup()
|
|
return
|
|
end
|
|
if self.versionOnSave then
|
|
self.targetVersion = self.versionOnSave
|
|
self.versionOnSave = nil
|
|
end
|
|
local xmlText = self:SaveDB(self.dbFileName)
|
|
if not xmlText then
|
|
return true
|
|
end
|
|
local file = io.open(self.dbFileName, "w+")
|
|
if not file then
|
|
main:OpenMessagePopup("Error", "Couldn't save the build file:\n"..self.dbFileName.."\nMake sure the save folder exists and is writable.")
|
|
return true
|
|
end
|
|
file:write(xmlText)
|
|
file:close()
|
|
local action = self.actionOnSave
|
|
self.actionOnSave = nil
|
|
if action == "LIST" then
|
|
self:CloseBuild()
|
|
elseif action == "EXIT" then
|
|
Exit()
|
|
elseif action == "UPDATE" then
|
|
launch:ApplyUpdate(launch.updateAvailable)
|
|
elseif action == "VERSION" then
|
|
self:Shutdown()
|
|
self:Init(self.dbFileName, self.buildName)
|
|
end
|
|
end
|
|
|
|
return buildMode |