diff --git a/Classes/CalcsTab.lua b/Classes/CalcsTab.lua index cca625b98..8e4c793ea 100644 --- a/Classes/CalcsTab.lua +++ b/Classes/CalcsTab.lua @@ -350,13 +350,36 @@ function CalcsTabClass:BuildOutput() self.itemCalculator = { self.calcs.getItemCalculator(self.build) } end --- Estimate the offensive and defensive power of all unallocated nodes +-- Controls the coroutine that calculations node power function CalcsTabClass:BuildPower() + if self.powerBuildFlag then + self.powerBuildFlag = false + self.powerBuilder = coroutine.create(self.PowerBuilder) + end + if self.powerBuilder then + collectgarbage("stop") -- This is necessary to work around a bug in the JIT + coroutine.resume(self.powerBuilder, self) + if coroutine.status(self.powerBuilder) == "dead" then + self.powerBuilder = nil + end + collectgarbage("restart") + end +end + +-- Estimate the offensive and defensive power of all unallocated nodes +function CalcsTabClass:PowerBuilder() local calcFunc, calcBase = self:GetNodeCalculator() local cache = { } - self.powerMax = { } + local newPowerMax = { + dps = 0, + def = 0 + } + if not self.powerMax then + self.powerMax = newPowerMax + end + local start = GetTime() for _, node in pairs(self.build.spec.nodes) do - node.power = wipeTable(node.power) + wipeTable(node.power) if not node.alloc and node.modKey ~= "" then if not cache[node.modKey] then cache[node.modKey] = calcFunc({node}) @@ -370,12 +393,16 @@ function CalcsTabClass:BuildPower() (output.LifeRegen - calcBase.LifeRegen) / 500 + (output.EnergyShieldRegen - calcBase.EnergyShieldRegen) / 1000 if node.path then - self.powerMax.dps = m_max(self.powerMax.dps or 0, node.power.dps) - self.powerMax.def = m_max(self.powerMax.def or 0, node.power.def) + newPowerMax.dps = m_max(newPowerMax.dps, node.power.dps) + newPowerMax.def = m_max(newPowerMax.def, node.power.def) end end - end - self.powerBuildFlag = false + if coroutine.running() and GetTime() - start > 100 then + coroutine.yield() + start = GetTime() + end + end + self.powerMax = newPowerMax end function CalcsTabClass:GetNodeCalculator() diff --git a/Classes/CheckBoxControl.lua b/Classes/CheckBoxControl.lua index be74edbdf..89b0ffa4a 100644 --- a/Classes/CheckBoxControl.lua +++ b/Classes/CheckBoxControl.lua @@ -64,10 +64,10 @@ function CheckBoxClass:Draw(viewPort) local tooltip = self:GetProperty("tooltip") if tooltip then main:AddTooltipLine(16, tooltip) + SetDrawLayer(nil, 100) + main:DrawTooltip(x, y, size, size, viewPort) + SetDrawLayer(nil, 0) end - SetDrawLayer(nil, 100) - main:DrawTooltip(x, y, size, size, viewPort) - SetDrawLayer(nil, 0) end end diff --git a/Classes/ConfigTab.lua b/Classes/ConfigTab.lua index 3a8e63b5a..93da6d0d5 100644 --- a/Classes/ConfigTab.lua +++ b/Classes/ConfigTab.lua @@ -11,10 +11,10 @@ local m_max = math.max local varList = { { section = "General" }, { var = "enemyLevel", type = "number", label = "Enemy Level:", tooltip = "This overrides the default enemy level used to estimate your hit and evade chances.\nThe default level is your character level, capped at 84, which is the same value\nused in-game to calculate the stats on the character sheet." }, - { var = "conditionLowLife", type = "check", label = "Are you always on Low Life?", tooltip = "You will automatically be considered to be on Low Life if you have at least 65% life reserved,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) + { var = "conditionLowLife", type = "check", label = "Are you always on Low Life?", ifCond = "LowLife", tooltip = "You will automatically be considered to be on Low Life if you have at least 65% life reserved,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "LowLife" }, "Config") end }, - { var = "conditionFullLife", type = "check", label = "Are you always on Full Life?", tooltip = "You will automatically be considered to be on Full Life if you have Chaos Innoculation,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) + { var = "conditionFullLife", type = "check", label = "Are you always on Full Life?", ifCond = "FullLife", tooltip = "You will automatically be considered to be on Full Life if you have Chaos Innoculation,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "FullLife" }, "Config") end }, { var = "igniteMode", type = "list", label = "Ignite calculation mode:", tooltip = "Controls how the base damage for ignite is calculated:\nAverage Damage: Ignite is based on the average damage dealt, factoring in crits and non-crits.\nCrit Damage: Ignite is based on crit damage only.", list = {{val="AVERAGE",label="Average Damage"},{val="CRIT",label="Crit Damage"}} }, @@ -28,43 +28,52 @@ local varList = { { var = "buffUnholyMight", type = "check", label = "Do you have Unholy Might?", tooltip = "This will enable the Unholy Might buff. (Gain 30% of Physical Damage as Extra Chaos Damage)", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "UnholyMight" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "buffPhasing", type = "check", label = "Do you have Phasing?", apply = function(val, modList, enemyModList) + { var = "buffPhasing", type = "check", label = "Do you have Phasing?", ifCond = "Phasing", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "Phasing" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "buffFortify", type = "check", label = "Do you have Fortify?", apply = function(val, modList, enemyModList) + { var = "buffFortify", type = "check", label = "Do you have Fortify?", ifCond = "Fortify", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "Fortify" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionUsingFlask", type = "check", label = "Do you have a Flask active?", apply = function(val, modList, enemyModList) + { var = "conditionUsingFlask", type = "check", label = "Do you have a Flask active?", ifCond = "UsingFlask", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "UsingFlask" }, "Config", { type = "Condition", var = "Combat" }) end }, { var = "conditionOnConsecratedGround", type = "check", label = "Are you on Consecrated Ground?", tooltip = "In addition to allowing any 'while on Consecrated Ground' modifiers to apply,\nthis will apply the 4% life regen modifier granted by Consecrated Ground.", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "OnConsecratedGround" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionIgnited", type = "check", label = "Are you Ignited?", apply = function(val, modList, enemyModList) + { var = "conditionOnBurningGround", type = "check", label = "Are you on Burning Ground?", ifCond = "OnBurningGround", apply = function(val, modList, enemyModList) + modList:NewMod("Misc", "LIST", { type = "Condition", var = "OnBurningGround" }, "Config", { type = "Condition", var = "Combat" }) + end }, + { var = "conditionOnChilledGround", type = "check", label = "Are you on Chilled Ground?", ifCond = "OnChilledGround", apply = function(val, modList, enemyModList) + modList:NewMod("Misc", "LIST", { type = "Condition", var = "OnChilledGround" }, "Config", { type = "Condition", var = "Combat" }) + end }, + { var = "conditionOnShockedGround", type = "check", label = "Are you on Shocked Ground?", ifCond = "OnShockedGround", apply = function(val, modList, enemyModList) + modList:NewMod("Misc", "LIST", { type = "Condition", var = "OnShockedGround" }, "Config", { type = "Condition", var = "Combat" }) + end }, + { var = "conditionIgnited", type = "check", label = "Are you Ignited?", ifCond = "PlayerIgnited", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "PlayerIgnited" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionFrozen", type = "check", label = "Are you Frozen?", apply = function(val, modList, enemyModList) + { var = "conditionFrozen", type = "check", label = "Are you Frozen?", ifCond = "PlayerFrozen", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "PlayerFrozen" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionShocked", type = "check", label = "Are you Shocked?", apply = function(val, modList, enemyModList) + { var = "conditionShocked", type = "check", label = "Are you Shocked?", ifCond = "PlayerShocked", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "PlayerShocked" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionHitRecently", type = "check", label = "Have you Hit Recently?", tooltip = "You will automatically be considered to have Hit Recently if your main skill is self-cast,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) + { var = "conditionHitRecently", type = "check", label = "Have you Hit Recently?", ifCond = "HitRecently", tooltip = "You will automatically be considered to have Hit Recently if your main skill is self-cast,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "HitRecently" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionCritRecently", type = "check", label = "Have you Crit Recently?", apply = function(val, modList, enemyModList) + { var = "conditionCritRecently", type = "check", label = "Have you Crit Recently?", ifCond = "CritRecently", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "CritRecently" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionKilledRecently", type = "check", label = "Have you Killed Recently?", apply = function(val, modList, enemyModList) + { var = "conditionKilledRecently", type = "check", label = "Have you Killed Recently?", ifCond = "KilledRecently", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "KilledRecently" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionTotemsKilledRecently", type = "check", label = "Have your Totems Killed Recently?", apply = function(val, modList, enemyModList) + { var = "conditionTotemsKilledRecently", type = "check", label = "Have your Totems Killed Recently?", ifCond = "TotemsKilledRecently", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "TotemsKilledRecently" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionBeenHitRecently", type = "check", label = "Have you been Hit Recently?", apply = function(val, modList, enemyModList) + { var = "conditionBeenHitRecently", type = "check", label = "Have you been Hit Recently?", ifCond = "BeenHitRecently", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "BeenHitRecently" }, "Config", { type = "Condition", var = "Combat" }) end }, - { var = "conditionBeenSavageHitRecently", type = "check", label = "Have you been Savage Hit Recently?", apply = function(val, modList, enemyModList) + { var = "conditionBeenSavageHitRecently", type = "check", label = "Have you been Savage Hit Recently?", ifCond = "BeenSavageHitRecently", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "BeenSavageHitRecently" }, "Config", { type = "Condition", var = "Combat" }) end }, { var = "buffPendulum", type = "check", label = "Is Pendulum of Destruction active?", ifNode = 57197, apply = function(val, modList, enemyModList) @@ -92,40 +101,40 @@ local varList = { { var = "critChanceLucky", type = "check", label = "Is your Crit Chance Lucky?", apply = function(val, modList, enemyModList) modList:NewMod("CritChanceLucky", "FLAG", true, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyFullLife", type = "check", label = "Is the enemy on Full Life?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyFullLife", type = "check", label = "Is the enemy on Full Life?", ifCond = "EnemyFullLife", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyFullLife" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyLowLife", type = "check", label = "Is the enemy on Low Life?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyLowLife", type = "check", label = "Is the enemy on Low Life?", ifCond = "EnemyLowLife", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyLowLife" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionAtCloseRange", type = "check", label = "Is the enemy at Close Range?", apply = function(val, modList, enemyModList) + { var = "conditionAtCloseRange", type = "check", label = "Is the enemy at Close Range?", ifCond = "AtCloseRange", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "AtCloseRange" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyCursed", type = "check", label = "Is the enemy Cursed?", tooltip = "Your enemy will automatically be considered to be Cursed if you have at least one curse enabled,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) + { var = "conditionEnemyCursed", type = "check", label = "Is the enemy Cursed?", ifCond = "EnemyCursed", tooltip = "Your enemy will automatically be considered to be Cursed if you have at least one curse enabled,\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyCursed" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyBleeding", type = "check", label = "Is the enemy Bleeding?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyBleeding", type = "check", label = "Is the enemy Bleeding?", ifCond = "EnemyBleeding", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyBleeding" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyPoisoned", type = "check", label = "Is the enemy Poisoned?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyPoisoned", type = "check", label = "Is the enemy Poisoned?", ifCond = "EnemyPoisoned", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyPoisoned" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyMaimed", type = "check", label = "Is the enemy Maimed?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyMaimed", type = "check", label = "Is the enemy Maimed?", ifCond = "EnemyMaimed", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyMaimed" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyHindered", type = "check", label = "Is the enemy Hindered?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyHindered", type = "check", label = "Is the enemy Hindered?", ifCond = "EnemyHindered", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyHindered" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyBurning", type = "check", label = "Is the enemy Burning?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyBurning", type = "check", label = "Is the enemy Burning?", ifCond = "EnemyBurning", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyBurning" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyIgnited", type = "check", label = "Is the enemy Ignited?", tooltip = "This also implies that the enemy is Burning.", apply = function(val, modList, enemyModList) + { var = "conditionEnemyIgnited", type = "check", label = "Is the enemy Ignited?", ifCond = "EnemyIgnited", tooltip = "This also implies that the enemy is Burning.", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyIgnited" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyChilled", type = "check", label = "Is the enemy Chilled?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyChilled", type = "check", label = "Is the enemy Chilled?", ifCond = "EnemyChilled", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyChilled" }, "Config", { type = "Condition", var = "Effective" }) end }, - { var = "conditionEnemyFrozen", type = "check", label = "Is the enemy Frozen?", apply = function(val, modList, enemyModList) + { var = "conditionEnemyFrozen", type = "check", label = "Is the enemy Frozen?", ifCond = "EnemyFrozen", apply = function(val, modList, enemyModList) modList:NewMod("Misc", "LIST", { type = "Condition", var = "EnemyFrozen" }, "Config", { type = "Condition", var = "Effective" }) end }, { var = "conditionEnemyShocked", type = "check", label = "Is the enemy Shocked?", tooltip = "In addition to allowing any 'against Shocked Enemies' modifiers to apply,\nthis will apply Shock's Damage Taken modifier to the enemy.", apply = function(val, modList, enemyModList) @@ -229,6 +238,21 @@ local ConfigTabClass = common.NewClass("ConfigTab", "UndoHandler", "ControlHost" control.tooltip = function() return "This option is specific to '"..self.build.spec.nodes[varData.ifNode].dn.."'."..(varData.tooltip and "\n"..varData.tooltip or "") end + elseif varData.ifCond then + control.shown = function() + return self.build.calcsTab.mainEnv.conditionsUsed[varData.ifCond] + end + control.tooltip = function() + if launch.devMode and IsKeyDown("CTRL") then + local out = varData.tooltip or "" + for _, mod in ipairs(self.build.calcsTab.mainEnv.conditionsUsed[varData.ifCond]) do + out = (#out > 0 and out.."\n" or out) .. modLib.formatMod(mod) .. "|" .. mod.source + end + return out + else + return varData.tooltip + end + end else control.tooltip = varData.tooltip end diff --git a/Classes/ImportTab.lua b/Classes/ImportTab.lua index c34a7ea96..654d35ded 100644 --- a/Classes/ImportTab.lua +++ b/Classes/ImportTab.lua @@ -34,7 +34,6 @@ local ImportTabClass = common.NewClass("ImportTab", "ControlHost", "Control", fu self.controls.accountNameGo.enabled = function() return self.controls.accountName.buf:match("%S") end - self.controls.accountNameTip = common.New("LabelControl", {"TOPLEFT",self.controls.accountName,"BOTTOMLEFT"}, 0, 4, 0, 14, "^7Note: the account name must have the correct capitalisation, or the character import will fail.") -- Stage: input POESESSID self.controls.sessionHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 14) @@ -205,22 +204,45 @@ function ImportTabClass:DownloadCharacterList() self.charImportMode = "GETACCOUNTNAME" return end - self.charImportStatus = "Character list successfully retrieved." - self.charImportMode = "SELECTCHAR" - main.lastAccountName = accountName - if sessionID then - main.accountSessionIDs[accountName] = sessionID - end - wipeTable(self.controls.charSelect.list) - for i, char in ipairs(charList) do - t_insert(self.controls.charSelect.list, { - val = char, - label = string.format("%s: Level %d %s in %s", char.name or "?", char.level or 0, char.class or "?", char.league or "?") - }) + --ConPrintTable(charList) + if #charList == 0 then + self.charImportStatus = data.colorCodes.NEGATIVE.."The account has no characters to import." + self.charImportMode = "GETACCOUNTNAME" + return end - table.sort(self.controls.charSelect.list, function(a,b) - return a.val.name:lower() < b.val.name:lower() - end) + -- GGG's character API has an issue where for /get-characters the account name is not case-sensitive, but for /get-passive-skills and /get-items it is. + -- This workaround grabs the profile page and extracts the correct account name from one of the URLs. + launch:DownloadPage("https://www.pathofexile.com/account/view-profile/"..accountName, function(page, errMsg) + if errMsg then + self.charImportStatus = data.colorCodes.NEGATIVE.."Error retrieving character list, try again ("..errMsg:gsub("\n"," ")..")" + self.charImportMode = "GETACCOUNTNAME" + return + end + local realAccountName = page:match("/account/view%-profile/([^/]+)/characters") + if not realAccountName then + self.charImportStatus = data.colorCodes.NEGATIVE.."Failed to retrieve character list." + self.charImportMode = "GETSESSIONID" + return + end + self.controls.accountName:SetText(realAccountName) + accountName = realAccountName + self.charImportStatus = "Character list successfully retrieved." + self.charImportMode = "SELECTCHAR" + main.lastAccountName = accountName + if sessionID then + main.accountSessionIDs[accountName] = sessionID + end + wipeTable(self.controls.charSelect.list) + for i, char in ipairs(charList) do + t_insert(self.controls.charSelect.list, { + val = char, + label = string.format("%s: Level %d %s in %s", char.name or "?", char.level or 0, char.class or "?", char.league or "?") + }) + end + table.sort(self.controls.charSelect.list, function(a,b) + return a.val.name:lower() < b.val.name:lower() + end) + end, sessionID and "POESESSID="..sessionID) end, sessionID and "POESESSID="..sessionID) end diff --git a/Classes/PassiveSpec.lua b/Classes/PassiveSpec.lua index cb98c9b1a..54481abb6 100644 --- a/Classes/PassiveSpec.lua +++ b/Classes/PassiveSpec.lua @@ -23,7 +23,8 @@ local PassiveSpecClass = common.NewClass("PassiveSpec", "UndoHandler", function( self.nodes = { } for _, treeNode in ipairs(self.tree.nodes) do self.nodes[treeNode.id] = setmetatable({ - linked = { } + linked = { }, + power = { } }, treeNode.meta) end for id, node in pairs(self.nodes) do diff --git a/Classes/PassiveTreeView.lua b/Classes/PassiveTreeView.lua index 81cc5c4fa..b2a6d6cc5 100644 --- a/Classes/PassiveTreeView.lua +++ b/Classes/PassiveTreeView.lua @@ -309,6 +309,11 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) SetDrawColor(1, 1, 1) end + if self.showHeatMap then + -- Build the power numbers if needed + build.calcsTab:BuildPower() + end + -- Draw the nodes for nodeId, node in pairs(spec.nodes) do -- Determine the base and overlay images for this node based on type and state @@ -360,10 +365,6 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) SetDrawColor(0.5, 0.5, 0.5) end if self.showHeatMap then - if build.calcsTab.powerBuildFlag then - -- Build the power numbers if needed - build.calcsTab:BuildPower() - end if not node.alloc and node.type ~= "classStart" and node.type ~= "ascendClassStart" then -- Calculate color based on DPS and defensive powers local dps = m_max(node.power.dps or 0, 0) diff --git a/Data/Uniques/gloves.lua b/Data/Uniques/gloves.lua index d8f1491aa..b676f385a 100644 --- a/Data/Uniques/gloves.lua +++ b/Data/Uniques/gloves.lua @@ -297,7 +297,7 @@ Energy Shield: 24 Requires Level 51, 40 Str, 40 Int Adds (30-36 to 44-50) Cold Damage to Attacks (12 to 16)% increased maximum Life -(40 to 50)% to Cold Resistance ++(40 to 50)% to Cold Resistance 25% increased Freeze Duration on Enemies Your Hits can only Kill Frozen enemies ]], diff --git a/Launch.lua b/Launch.lua index fe5d470fd..08c2c042d 100644 --- a/Launch.lua +++ b/Launch.lua @@ -190,7 +190,9 @@ function launch:OnSubError(errMsg) end function launch:OnSubFinished(...) - if self.subScriptType == "UPDATE" then + local type = self.subScriptType + self.subScriptType = nil + if type == "UPDATE" then local ret = (...) self.updateAvailable = ret if self.updateChecking then @@ -203,14 +205,14 @@ function launch:OnSubFinished(...) end self.updateChecking = false end - elseif self.subScriptType == "DOWNLOAD" then - local errMsg = PCall(self.downloadCallback, ...) + elseif type == "DOWNLOAD" then + local callback = self.downloadCallback + self.downloadCallback = nil + local errMsg = PCall(callback, ...) if errMsg then self:ShowErrMsg("In download callback: %s", errMsg) end - self.downloadCallback = nil end - self.subScriptType = nil end function launch:DownloadPage(url, callback, cookies) diff --git a/Modules/Build.lua b/Modules/Build.lua index 16f8da8a5..2915ce3c9 100644 --- a/Modules/Build.lua +++ b/Modules/Build.lua @@ -341,7 +341,7 @@ function buildMode:Init(dbFileName, buildName) local start = GetTime() SetProfiling(true) for i = 1, 10 do - self.calcsTab:BuildPower(self) + self.calcsTab:PowerBuilder() end SetProfiling(false) ConPrintf("Power build time: %d msec", GetTime() - start) diff --git a/Modules/CalcSections.lua b/Modules/CalcSections.lua index f45ba3ceb..efa951977 100644 --- a/Modules/CalcSections.lua +++ b/Modules/CalcSections.lua @@ -209,7 +209,7 @@ return { { label = "Total Increased", { format = "{0:mod:1}%", { modName = { "Damage", "FireDamage", "ElementalDamage" }, modType = "INC", cfg = "ignite" }, }, }, { label = "Total More", { format = "{0:mod:1}%", { modName = { "Damage", "FireDamage", "ElementalDamage" }, modType = "MORE", cfg = "ignite" }, }, }, { label = "Effective DPS Mod", flag = "effective", { format = "x {3:output:IgniteEffMult}", { breakdown = "IgniteEffMult" }, { label = "Enemy modifiers", modName = { "FireResist", "ElementalResist", "DamageTaken", "DotTaken", "FireDamageTaken", "BurningDamageTaken", "ElementalDamageTaken" }, enemy = true }, }, }, - { label = "Ignite DPS", { format = "{1:output:IgniteDPS}", { breakdown = "IgniteDPS" }, }, }, + { label = "Ignite DPS", { format = "{1:output:IgniteDPS}", { breakdown = "IgniteDPS" }, { modName = { "IgniteBurnRate" }, cfg = "skill" }, }, }, { label = "Ignite Duration", { format = "{2:output:IgniteDuration}s", { breakdown = "IgniteDuration" }, { label = "Player modifiers", modName = "EnemyIgniteDuration", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfIgniteDuration", enemy = true }, }, }, { label = "Dmg. per Ignite", flag = "igniteCanStack", { format = "{1:output:IgniteDamage}", { breakdown = "IgniteDamage" }, }, }, } }, diff --git a/Modules/Calcs.lua b/Modules/Calcs.lua index e41308805..5ba8ee75a 100644 --- a/Modules/Calcs.lua +++ b/Modules/Calcs.lua @@ -3,7 +3,7 @@ -- Module: Calcs -- Performs all the offense and defense calculations. -- Here be dragons! --- This file is 2700 lines long, over half of which is in one function... +-- This file is 2800 lines long, over half of which is in one function... -- local pairs = pairs @@ -1277,7 +1277,7 @@ local function performCalcs(env) end return out end - dotBreakdown = function(out, baseVal, inc, more, effMult, total) + dotBreakdown = function(out, baseVal, inc, more, rate, effMult, total) t_insert(out, s_format("%.1f ^8(base damage per second)", baseVal)) if inc ~= 0 then t_insert(out, s_format("x %.2f ^8(increased/reduced)", 1 + inc/100)) @@ -1285,6 +1285,9 @@ local function performCalcs(env) if more ~= 1 then t_insert(out, s_format("x %.2f ^8(more/less)", more)) end + if rate and rate ~= 1 then + t_insert(out, s_format("x %.2f ^8(rate modifier)", rate)) + end if effMult ~= 1 then t_insert(out, s_format("x %.3f ^8(effective DPS modifier)", effMult)) end @@ -2170,7 +2173,7 @@ local function performCalcs(env) output.TotalDot = output.TotalDot + total if breakdown then breakdown[damageType.."Dot"] = { } - dotBreakdown(breakdown[damageType.."Dot"], baseVal, inc, more, effMult, total) + dotBreakdown(breakdown[damageType.."Dot"], baseVal, inc, more, nil, effMult, total) end end end @@ -2312,7 +2315,7 @@ local function performCalcs(env) t_insert(breakdown.BleedDPS, "x 0.1 ^8(bleed deals 10% per second)") t_insert(breakdown.BleedDPS, s_format("= %.1f", baseVal)) t_insert(breakdown.BleedDPS, "Bleed DPS:") - dotBreakdown(breakdown.BleedDPS, baseVal, inc, more, effMult, output.BleedDPS) + dotBreakdown(breakdown.BleedDPS, baseVal, inc, more, nil, effMult, output.BleedDPS) if output.BleedDuration ~= 5 then breakdown.BleedDuration = { "5.00s ^8(base duration)" @@ -2373,7 +2376,7 @@ local function performCalcs(env) t_insert(breakdown.PoisonDPS, "x 0.08 ^8(poison deals 8% per second)") t_insert(breakdown.PoisonDPS, s_format("= %.1f", baseVal, 1)) t_insert(breakdown.PoisonDPS, "Poison DPS:") - dotBreakdown(breakdown.PoisonDPS, baseVal, inc, more, effMult, output.PoisonDPS) + dotBreakdown(breakdown.PoisonDPS, baseVal, inc, more, nil, effMult, output.PoisonDPS) if output.PoisonDuration ~= 2 then breakdown.PoisonDuration = { s_format("%.2fs ^8(base duration)", durationBase) @@ -2439,9 +2442,10 @@ local function performCalcs(env) end local inc = modDB:Sum("INC", dotCfg, "Damage", "FireDamage", "ElementalDamage") local more = round(modDB:Sum("MORE", dotCfg, "Damage", "FireDamage", "ElementalDamage"), 2) - output.IgniteDPS = baseVal * (1 + inc/100) * more * effMult + local burnRateMod = calcMod(modDB, skillCfg, "IgniteBurnRate") + output.IgniteDPS = baseVal * (1 + inc/100) * more * burnRateMod * effMult local incDur = modDB:Sum("INC", dotCfg, "EnemyIgniteDuration") + enemyDB:Sum("INC", nil, "SelfIgniteDuration") - output.IgniteDuration = 4 * (1 + incDur / 100) * debuffDurationMult + output.IgniteDuration = 4 * (1 + incDur / 100) / burnRateMod * debuffDurationMult if modDB:Sum("FLAG", nil, "IgniteCanStack") then output.IgniteDamage = output.IgniteDPS * output.IgniteDuration skillFlags.igniteCanStack = true @@ -2450,7 +2454,7 @@ local function performCalcs(env) t_insert(breakdown.IgniteDPS, "x 0.2 ^8(ignite deals 20% per second)") t_insert(breakdown.IgniteDPS, s_format("= %.1f", baseVal, 1)) t_insert(breakdown.IgniteDPS, "Ignite DPS:") - dotBreakdown(breakdown.IgniteDPS, baseVal, inc, more, effMult, output.IgniteDPS) + dotBreakdown(breakdown.IgniteDPS, baseVal, inc, more, burnRateMod, effMult, output.IgniteDPS) if skillFlags.igniteCanStack then breakdown.IgniteDamage = { s_format("%.1f ^8(damage per second)", output.IgniteDPS), @@ -2465,6 +2469,9 @@ local function performCalcs(env) if incDur ~= 0 then t_insert(breakdown.IgniteDuration, s_format("x %.2f ^8(increased/reduced duration)", 1 + incDur/100)) end + if burnRateMod ~= 1 then + t_insert(breakdown.IgniteDuration, s_format("/ %.2f ^8(rate modifier)", burnRateMod)) + end if debuffDurationMult ~= 1 then t_insert(breakdown.IgniteDuration, s_format("/ %.2f ^8(debuff expires slower/faster)", 1 / debuffDurationMult)) end @@ -2725,6 +2732,32 @@ function calcs.buildOutput(build, mode) for _, stat in pairs({"Life", "Mana", "Armour", "Evasion", "EnergyShield"}) do output["Spec:"..stat.."Inc"] = env.modDB:Sum("INC", specCfg, stat) end + + env.conditionsUsed = { } + local function addCond(var, mod) + if not env.conditionsUsed[var] then + env.conditionsUsed[var] = { } + end + t_insert(env.conditionsUsed[var], mod) + end + for _, db in ipairs{env.modDB, env.enemyDB} do + for modName, modList in pairs(db.mods) do + for _, mod in ipairs(modList) do + for _, tag in ipairs(mod.tagList) do + if tag.type == "Condition" then + if tag.varList then + for _, var in ipairs(tag.varList) do + addCond(var, mod) + end + else + addCond(tag.var, mod) + end + end + end + end + end + end + ConPrintTable(env.conditionsUsed) elseif mode == "CALCS" then local buffList = { } local combatList = { } diff --git a/Modules/ModParser.lua b/Modules/ModParser.lua index 22616a497..a64b0d992 100644 --- a/Modules/ModParser.lua +++ b/Modules/ModParser.lua @@ -367,6 +367,9 @@ local modTagList = { ["while using a flask"] = { tag = { type = "Condition", var = "UsingFlask" } }, ["during flask effect"] = { tag = { type = "Condition", var = "UsingFlask" } }, ["while on consecrated ground"] = { tag = { type = "Condition", var = "OnConsecratedGround" } }, + ["on burning ground"] = { tag = { type = "Condition", var = "OnBurningGround" } }, + ["on chilled ground"] = { tag = { type = "Condition", var = "OnChilledGround" } }, + ["on shocked ground"] = { tag = { type = "Condition", var = "OnShockedGround" } }, ["while ignited"] = { tag = { type = "Condition", var = "PlayerIgnited" } }, ["while frozen"] = { tag = { type = "Condition", var = "PlayerFrozen" } }, ["while shocked"] = { tag = { type = "Condition", var = "PlayerShocked" } }, @@ -582,6 +585,8 @@ local specialModList = { ["attacks with this weapon deal double damage to chilled enemies"] = { mod("Damage", "MORE", 100, nil, ModFlag.Hit, { type = "Condition", var = "XHandAttack" }, { type = "Condition", var = "EnemyChilled" }) }, ["(%d+)%% of maximum life converted to energy shield"] = function(num) return { mod("LifeConvertToEnergyShield", "BASE", num) } end, ["non%-critical strikes deal (%d+)%% damage"] = function(num) return { mod("Damage", "MORE", -100+num, nil, ModFlag.Hit, { type = "Condition", var = "CriticalStrike", neg = true }) } end, + ["ignited enemies burn (%d+)%% faster"] = function(num) return { mod("IgniteBurnRate", "INC", num) } end, + ["enemies ignited by an attack burn (%d+)%% faster"] = function(num) return { mod("IgniteBurnRate", "INC", num, nil, ModFlag.Attack) } end, } local keystoneList = { -- List of keystones that can be found on uniques diff --git a/README.md b/README.md index 32d7df8d6..1badc85ef 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ Head over to the [Releases](https://github.com/Openarl/PathOfBuilding/releases) ![ss3](https://cloud.githubusercontent.com/assets/19189971/18089780/f0ff234a-6f04-11e6-8c88-6193fe59a5c4.png) ## Changelog +### 1.2.36 - 2017/01/31 + * Condition toggles in the Configuration tab will now only appear if the condition is actually used by the build + * Added support for "Ignited Enemies Burn faster" modifiers + * Added options to the Configuration tab for "Are you on Shocked/Burning/Chilled Ground" + * Character imports will now work even if the capitalisation of the account name is incorrect + ### 1.2.35 - 2017/01/29 With this update, the way the program handles the calculation of crit damage has been improved. Damage for crits and non-crits are now calculated and tallied separately, and combined later, instead of only diff --git a/changelog.txt b/changelog.txt index aae9d047e..54b6f2296 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +VERSION[1.2.36][2017/01/31] + * Condition toggles in the Configuration tab will now only appear if the condition is actually used by the build + * Added support for "Ignited Enemies Burn faster" modifiers + * Added options to the Configuration tab for "Are you on Shocked/Burning/Chilled Ground" + * Character imports will now work even if the capitalisation of the account name is incorrect VERSION[1.2.35][2017/01/29] With this update, the way the program handles the calculation of crit damage has been improved. Damage for crits and non-crits are now calculated and tallied separately, and combined later, instead of only diff --git a/manifest.xml b/manifest.xml index 08e1e3dec..45036bc85 100644 --- a/manifest.xml +++ b/manifest.xml @@ -1,26 +1,26 @@ - + - + - + - - - + + + - + @@ -29,9 +29,9 @@ - + - + @@ -42,15 +42,15 @@ - + - - + + - + @@ -89,7 +89,7 @@ - + @@ -99,12 +99,12 @@ - - - - - - + + + + + + diff --git a/runtime-win32.zip b/runtime-win32.zip index 20a3eba78..7633beb03 100644 Binary files a/runtime-win32.zip and b/runtime-win32.zip differ