Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3337,7 +3337,9 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode)
local lifeMore = modDB:More(nil, "FlaskLifeRecovery")
local lifeRateInc = modDB:Sum("INC", nil, "FlaskLifeRecoveryRate")
local inst = flaskData.lifeBase * instantPerc / 100 * (1 + lifeInc / 100) * lifeMore * (1 + effectInc / 100)
local grad = flaskData.lifeBase * (1 - instantPerc / 100) * (1 + lifeInc / 100) * lifeMore * (1 + effectInc / 100) * (1 + durInc / 100) * output.LifeRecoveryRateMod
local base = flaskData.lifeBase * (1 - instantPerc / 100) * (1 + lifeInc / 100) * lifeMore * (1 + effectInc / 100) * (1 + durInc / 100)
local grad = base * output.LifeRecoveryRateMod
local esGrad = base * output.EnergyShieldRecoveryRateMod
lifeDur = flaskData.duration * (1 + durInc / 100) / (1 + rateInc / 100) / (1 + lifeRateInc / 100)

-- LocalLifeFlaskAdditionalLifeRecovery flask mods
Expand Down Expand Up @@ -3374,21 +3376,24 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode)
end
end
if modDB:Flag(nil, "LifeFlaskAppliesToEnergyShield") then
if inst > 0 and grad > 0 then
t_insert(stats, s_format("^8Energy Shield recovered: ^7%d ^8(^7%d^8 instantly, plus ^7%d ^8over^7 %.2fs^8)", inst + grad, inst, grad, lifeDur))
elseif inst > 0 and grad == 0 then
if inst > 0 and esGrad > 0 then
t_insert(stats, s_format("^8Energy Shield recovered: ^7%d ^8(^7%d^8 instantly, plus ^7%d ^8over^7 %.2fs^8)", inst + esGrad, inst, esGrad, lifeDur))
elseif inst > 0 and esGrad == 0 then
t_insert(stats, s_format("^8Energy Shield recovered: ^7%d ^8instantly", inst))
elseif inst == 0 and grad > 0 then
t_insert(stats, s_format("^8Energy Shield recovered: ^7%d ^8over ^7%.2fs", grad, lifeDur))
elseif inst == 0 and esGrad > 0 then
t_insert(stats, s_format("^8Energy Shield recovered: ^7%d ^8over ^7%.2fs", esGrad, lifeDur))
end
end
end
if item.base.flask.mana then
local manaInc = modDB:Sum("INC", nil, "FlaskManaRecovery")
local manaRateInc = modDB:Sum("INC", nil, "FlaskManaRecoveryRate")
local inst = flaskData.manaBase * instantPerc / 100 * (1 + manaInc / 100) * (1 + effectInc / 100)
local grad = flaskData.manaBase * (1 - instantPerc / 100) * (1 + manaInc / 100) * (1 + effectInc / 100) * (1 + durInc / 100) * output.ManaRecoveryRateMod
local base = flaskData.manaBase * (1 - instantPerc / 100) * (1 + manaInc / 100) * (1 + effectInc / 100) * (1 + durInc / 100)
local grad = base * output.ManaRecoveryRateMod
local lifeGrad = base * output.LifeRecoveryRateMod
manaDur = flaskData.duration * (1 + durInc / 100) / (1 + rateInc / 100) / (1 + manaRateInc / 100)

if inst > 0 and grad > 0 then
t_insert(stats, s_format("^8Mana recovered: ^7%d ^8(^7%d^8 instantly, plus ^7%d ^8over^7 %.2fs^8)", inst + grad, inst, grad, manaDur))
elseif inst + grad ~= flaskData.manaTotal or (inst == 0 and manaDur ~= flaskData.duration) then
Expand All @@ -3399,6 +3404,11 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode)
t_insert(stats, s_format("^8Mana recovered: ^7%d ^8over ^7%.2fs", grad, manaDur))
end
end
if modDB:Flag(nil, "ManaFlaskAppliesToLife") then
if lifeGrad > 0 then
t_insert(stats, s_format("^8Life recovered: ^7%d ^8over ^7%.2fs", lifeGrad, manaDur))
end
end
end
else
if durInc ~= 0 then
Expand Down
21 changes: 12 additions & 9 deletions src/Modules/Build.lua
Original file line number Diff line number Diff line change
Expand Up @@ -328,22 +328,25 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild)
{ stat = "LifeUnreserved", label = "Unreserved Life", fmt = "d", color = colorCodes.LIFE, condFunc = function(v,o) return v < o.Life end, compPercent = true, warnFunc = function(v) return v <= 0 and "Your unreserved Life is below 1" end },
{ stat = "LifeRecoverable", label = "Life Recoverable", fmt = "d", color = colorCodes.LIFE, condFunc = function(v,o) return v < o.LifeUnreserved end, },
{ stat = "LifeUnreservedPercent", label = "Unreserved Life", fmt = "d%%", color = colorCodes.LIFE, condFunc = function(v,o) return v < 100 end },
{ stat = "LifeRegenRecovery", label = "Life Regen", fmt = ".1f", color = colorCodes.LIFE },
{ stat = "LifeRegenRecovery", label = "Life Regen", fmt = ".1f", color = colorCodes.LIFE, condFunc = function(v,o) return o.LifeRecovery <= 0 and o.LifeRegenRecovery ~= 0 end },
{ stat = "LifeRegenRecovery", label = "Life Recovery", fmt = ".1f", color = colorCodes.LIFE, condFunc = function(v,o) return o.LifeRecovery > 0 and o.LifeRegenRecovery ~= 0 end },
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ stat = "LifeLeechGainPerHit", label = "Life Leech/Gain per Hit", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ },
{ stat = "Mana", label = "Total Mana", fmt = "d", color = colorCodes.MANA, compPercent = true },
{ stat = "Spec:ManaInc", label = "%Inc Mana from Tree", color = colorCodes.MANA, fmt = "d%%" },
{ stat = "ManaUnreserved", label = "Unreserved Mana", fmt = "d", color = colorCodes.MANA, condFunc = function(v,o) return v < o.Mana end, compPercent = true, warnFunc = function(v) return v < 0 and "Your unreserved Mana is negative" end },
{ stat = "ManaUnreservedPercent", label = "Unreserved Mana", fmt = "d%%", color = colorCodes.MANA, condFunc = function(v,o) return v < 100 end },
{ stat = "ManaRegenRecovery", label = "Mana Regen", fmt = ".1f", color = colorCodes.MANA },
{ stat = "ManaRegenRecovery", label = "Mana Regen", fmt = ".1f", color = colorCodes.MANA, condFunc = function(v,o) return o.ManaRecovery <= 0 and o.ManaRegenRecovery ~= 0 end },
{ stat = "ManaRegenRecovery", label = "Mana Recovery", fmt = ".1f", color = colorCodes.MANA, condFunc = function(v,o) return o.ManaRecovery > 0 and o.ManaRegenRecovery ~= 0 end },
{ stat = "ManaLeechGainRate", label = "Mana Leech/On Hit Rate", fmt = ".1f", color = colorCodes.MANA, compPercent = true },
{ stat = "ManaLeechGainPerHit", label = "Mana Leech/Gain per Hit", fmt = ".1f", color = colorCodes.MANA, compPercent = true },
{ },
{ stat = "EnergyShield", label = "Energy Shield", fmt = "d", color = colorCodes.ES, compPercent = true },
{ stat = "EnergyShieldRecoveryCap", label = "Recoverable ES", color = colorCodes.ES, fmt = "d", condFunc = function(v,o) return o.CappingES end },
{ stat = "Spec:EnergyShieldInc", label = "%Inc ES from Tree", color = colorCodes.ES, fmt = "d%%" },
{ stat = "EnergyShieldRegenRecovery", label = "Energy Shield Regen", color = colorCodes.ES, fmt = ".1f" },
{ stat = "EnergyShieldRegenRecovery", label = "ES Regen", color = colorCodes.ES, fmt = ".1f", condFunc = function(v,o) return o.EnergyShieldRecovery <= 0 and o.EnergyShieldRegenRecovery ~= 0 end },
{ stat = "EnergyShieldRegenRecovery", label = "ES Recovery", color = colorCodes.ES, fmt = ".1f", condFunc = function(v,o) return o.EnergyShieldRecovery > 0 and o.EnergyShieldRegenRecovery ~= 0 end },
{ stat = "EnergyShieldLeechGainRate", label = "ES Leech/On Hit Rate", color = colorCodes.ES, fmt = ".1f", compPercent = true },
{ stat = "EnergyShieldLeechGainPerHit", label = "ES Leech/Gain per Hit", color = colorCodes.ES, fmt = ".1f", compPercent = true },
{ },
Expand All @@ -353,10 +356,10 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild)
{ stat = "RageRegenRecovery", label = "Rage Regen", fmt = ".1f", color = colorCodes.RAGE, compPercent = true },
{ },
{ stat = "TotalDegen", label = "Total Degen", fmt = ".1f", lowerIsBetter = true },
{ stat = "TotalNetRegen", label = "Total Net Regen", fmt = "+.1f" },
{ stat = "NetLifeRegen", label = "Net Life Regen", fmt = "+.1f", color = colorCodes.LIFE },
{ stat = "NetManaRegen", label = "Net Mana Regen", fmt = "+.1f", color = colorCodes.MANA },
{ stat = "NetEnergyShieldRegen", label = "Net Energy Shield Regen", fmt = "+.1f", color = colorCodes.ES },
{ stat = "TotalNetRegen", label = "Total Net Recovery", fmt = "+.1f" },
{ stat = "NetLifeRegen", label = "Net Life Recovery", fmt = "+.1f", color = colorCodes.LIFE },
{ stat = "NetManaRegen", label = "Net Mana Recovery", fmt = "+.1f", color = colorCodes.MANA },
{ stat = "NetEnergyShieldRegen", label = "Net ES Recovery", fmt = "+.1f", color = colorCodes.ES },
{ },
{ stat = "Evasion", label = "Evasion rating", fmt = "d", color = colorCodes.EVASION, compPercent = true },
{ stat = "Spec:EvasionInc", label = "%Inc Evasion from Tree", color = colorCodes.EVASION, fmt = "d%%" },
Expand Down Expand Up @@ -416,10 +419,10 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild)
{ stat = "CombinedDPS", label = "Combined DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return v ~= ((o.TotalDPS or 0) + (o.TotalDot or 0)) and v ~= o.WithImpaleDPS and v ~= o.WithPoisonDPS and v ~= o.WithIgniteDPS and v ~= o.WithBleedDPS end},
{ stat = "Cooldown", label = "Skill Cooldown", fmt = ".3fs", lowerIsBetter = true },
{ stat = "Life", label = "Total Life", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ stat = "LifeRegenRecovery", label = "Life Regen", fmt = ".1f", color = colorCodes.LIFE },
{ stat = "LifeRegenRecovery", label = "Life Recovery", fmt = ".1f", color = colorCodes.LIFE },
{ stat = "LifeLeechGainRate", label = "Life Leech/On Hit Rate", fmt = ".1f", color = colorCodes.LIFE, compPercent = true },
{ stat = "EnergyShield", label = "Energy Shield", fmt = "d", color = colorCodes.ES, compPercent = true },
{ stat = "EnergyShieldRegenRecovery", label = "Energy Shield Regen", fmt = ".1f", color = colorCodes.ES },
{ stat = "EnergyShieldRegenRecovery", label = "ES Recovery", fmt = ".1f", color = colorCodes.ES },
{ stat = "EnergyShieldLeechGainRate", label = "ES Leech/On Hit Rate", fmt = ".1f", color = colorCodes.ES, compPercent = true },
}
self.extraSaveStats = {
Expand Down
138 changes: 99 additions & 39 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1560,8 +1560,16 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP)
end
end

-- flask breakdown
local effectInc = modDB:Sum("INC", {actor = "player"}, "FlaskEffect")
local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicUtilityFlaskEffect")
local effectIncNonPlayer = modDB:Sum("INC", nil, "FlaskEffect")
local effectIncMagicNonPlayer = modDB:Sum("INC", nil, "MagicUtilityFlaskEffect")
local flasksApplyToMinion = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "FlasksApplyToMinion")
local quickSilverAppliesToAllies = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "QuickSilverAppliesToAllies")
local flaskTotalRateInc = modDB:Sum("INC", nil, "FlaskRecoveryRate")
local flaskDurInc = modDB:Sum("INC", nil, "FlaskDuration")

-- flask breakdown
if breakdown then
local chargesGenerated = modDB:Sum("BASE", nil, "FlaskChargesGenerated")
local usedFlasks = 0
Expand All @@ -1586,76 +1594,116 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP)
end

-- Merge flask modifiers
if env.mode_combat then
local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicUtilityFlaskEffect")
local effectIncNonPlayer = modDB:Sum("INC", nil, "FlaskEffect")
local effectIncMagicNonPlayer = modDB:Sum("INC", nil, "MagicUtilityFlaskEffect")
local function calcFlaskRecovery(type, item)
local out = {}
local lType = type:lower()

if not item.flaskData[lType.."EffectNotRemoved"] and not modDB:Flag(nil, type.."FlaskEffectNotRemoved") then
return out
end

local name = item.name
local base = item.flaskData[lType.."Base"]
local dur = item.flaskData.duration
local instPerc = item.flaskData.instantPerc
local flaskRecInc = modDB:Sum("INC", nil, "Flask"..type.."Recovery")
local flaskRecMore = modDB:More(nil, "Flask"..type.."Recovery")
local flaskRateInc = modDB:Sum("INC", nil, "Flask"..type.."RecoveryRate")
local flaskTotal = base * (1 - instPerc / 100) * (1 + flaskRecInc / 100) * flaskRecMore * (1 + flaskDurInc / 100)
local flaskDur = dur * (1 + flaskDurInc / 100) / (1 + flaskTotalRateInc / 100) / (1 + flaskRateInc / 100)

t_insert(out, modLib.createMod(type.."Recovery", "BASE", flaskTotal / flaskDur, name))

if (modDB:Flag(nil, type.."FlaskAppliesToEnergyShield")) then
t_insert(out, modLib.createMod("EnergyShieldRecovery", "BASE", flaskTotal / flaskDur, name))
end

if (modDB:Flag(nil, type.."FlaskAppliesToLife")) then
t_insert(out, modLib.createMod("LifeRecovery", "BASE", flaskTotal / flaskDur, name))
end

return out
end

local function mergeFlasks(flasks, onlyRecovery)
local flaskBuffs = { }
local flaskConditions = {}
local flaskBuffsPerBase = {}
local flaskBuffsNonPlayer = {}
local flaskBuffsPerBaseNonPlayer = {}
local flasksApplyToMinion = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "FlasksApplyToMinion")
local quickSilverAppliesToAllies = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "QuickSilverAppliesToAllies")

for item in pairs(env.flasks) do
flaskBuffsPerBase[item.baseName] = flaskBuffsPerBase[item.baseName] or {}
flaskBuffsPerBaseNonPlayer[item.baseName] = flaskBuffsPerBaseNonPlayer[item.baseName] or {}
flaskConditions["UsingFlask"] = true
if item.base.flask.life then
flaskConditions["UsingLifeFlask"] = true
end
if item.base.flask.mana then
flaskConditions["UsingManaFlask"] = true
end
flaskConditions["Using"..item.baseName:gsub("%s+", "")] = true

local flaskEffectInc = item.flaskData.effectInc
local flaskEffectIncNonPlayer = flaskEffectInc
if item.rarity == "MAGIC" and not (flaskConditions["UsingLifeFlask"] or flaskConditions["UsingManaFlask"]) then
local function calcFlaskMods(item, baseName, buffModList, modList)
local flaskEffectInc = effectInc + item.flaskData.effectInc
local flaskEffectIncNonPlayer = effectIncNonPlayer + flaskEffectInc
if item.rarity == "MAGIC" and not (item.base.flask.life or item.base.flask.mana) then
flaskEffectInc = flaskEffectInc + effectIncMagic
flaskEffectIncNonPlayer = effectIncNonPlayer + effectIncMagicNonPlayer
end
local effectMod = 1 + (flaskEffectInc) / 100
local effectModNonPlayer = 1 + (flaskEffectIncNonPlayer) / 100

-- Avert thine eyes, lest they be forever scarred
-- I have no idea how to determine which buff is applied by a given flask,
-- I have no idea how to determine which buff is applied by a given flask,
-- so utility flasks are grouped by base, unique flasks are grouped by name, and magic flasks by their modifiers
local effectMod = 1 + (effectInc + flaskEffectInc) / 100
local effectModNonPlayer = 1 + (effectIncNonPlayer + flaskEffectIncNonPlayer) / 100
if item.buffModList[1] then
if buffModList[1] then
local srcList = new("ModList")
srcList:ScaleAddList(item.buffModList, effectMod)
mergeBuff(srcList, flaskBuffs, item.baseName)
mergeBuff(srcList, flaskBuffsPerBase[item.baseName], item.baseName)
srcList:ScaleAddList(buffModList, effectMod)
mergeBuff(srcList, flaskBuffs, baseName)
mergeBuff(srcList, flaskBuffsPerBase[item.baseName], baseName)
if (flasksApplyToMinion or quickSilverAppliesToAllies) then
srcList = new("ModList")
srcList:ScaleAddList(item.buffModList, effectModNonPlayer)
mergeBuff(srcList, flaskBuffsNonPlayer, item.baseName)
mergeBuff(srcList, flaskBuffsPerBaseNonPlayer[item.baseName], item.baseName)
srcList:ScaleAddList(buffModList, effectModNonPlayer)
mergeBuff(srcList, flaskBuffsNonPlayer, baseName)
mergeBuff(srcList, flaskBuffsPerBaseNonPlayer[item.baseName], baseName)
end
end
if item.modList[1] then

if modList[1] then
local srcList = new("ModList")
srcList:ScaleAddList(item.modList, effectMod)
srcList:ScaleAddList(modList, effectMod)
local key
if item.rarity == "UNIQUE" then
key = item.title
else
key = ""
for _, mod in ipairs(item.modList) do
for _, mod in ipairs(modList) do
key = key .. modLib.formatModParams(mod) .. "&"
end
end
mergeBuff(srcList, flaskBuffs, key)
mergeBuff(srcList, flaskBuffsPerBase[item.baseName], key)
if (flasksApplyToMinion or quickSilverAppliesToAllies) then
srcList = new("ModList")
srcList:ScaleAddList(item.modList, effectModNonPlayer)
srcList:ScaleAddList(modList, effectModNonPlayer)
mergeBuff(srcList, flaskBuffsNonPlayer, key)
mergeBuff(srcList, flaskBuffsPerBaseNonPlayer[item.baseName], key)
end
end
end

for item in pairs(flasks) do
flaskBuffsPerBase[item.baseName] = flaskBuffsPerBase[item.baseName] or {}
flaskBuffsPerBaseNonPlayer[item.baseName] = flaskBuffsPerBaseNonPlayer[item.baseName] or {}
flaskConditions["UsingFlask"] = true
flaskConditions["Using"..item.baseName:gsub("%s+", "")] = true
if item.base.flask.life then
flaskConditions["UsingLifeFlask"] = true
end
if item.base.flask.mana then
flaskConditions["UsingManaFlask"] = true
end

if onlyRecovery then
if item.base.flask.life then
calcFlaskMods(item, "LifeFlask", calcFlaskRecovery("Life", item), {})
end
if item.base.flask.mana then
calcFlaskMods(item, "ManaFlask", calcFlaskRecovery("Mana", item), {})
end
else
calcFlaskMods(item, item.baseName, item.buffModList, item.modList)
end
end
if not modDB:Flag(nil, "FlasksDoNotApplyToPlayer") then
for flaskCond, status in pairs(flaskConditions) do
modDB.conditions[flaskCond] = status
Expand All @@ -1674,7 +1722,7 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP)
minionModDB:AddList(buffModList)
end
else -- Not all flasks apply to minions. Check if some flasks need to be selectively applied
if quickSilverAppliesToAllies and flaskBuffsPerBaseNonPlayer["Quicksilver Flask"] then
if quickSilverAppliesToAllies and flaskBuffsPerBaseNonPlayer["Quicksilver Flask"] then
local minionModDB = env.minion.modDB
minionModDB.conditions["UsingQuicksilverFlask"] = flaskConditions["UsingQuicksilverFlask"]
minionModDB.conditions["UsingFlask"] = flaskConditions["UsingFlask"]
Expand All @@ -1686,8 +1734,14 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP)
end
end

-- Merge keystones again to catch any that were added by flasks
mergeKeystones(env)
if env.mode_combat then
-- This needs to be done in 2 steps to account for effects affecting life recovery from flasks
-- For example Sorrow of the Divine and buffs (like flask recovery watchers eye)
mergeFlasks(env.flasks, false)

-- Merge keystones again to catch any that were added by flasks
mergeKeystones(env)
end

-- Calculate attributes and life/mana pools
doActorAttribsConditions(env, env.player)
Expand Down Expand Up @@ -2339,6 +2393,12 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP)
end
end

if env.mode_combat then
-- This needs to be done in 2 steps to account for effects affecting life recovery from flasks
-- For example Sorrow of the Divine and buffs (like flask recovery watchers eye)
mergeFlasks(env.flasks, true)
end

-- Check for extra curses
for dest, modDB in pairs({[curses] = modDB, [minionCurses] = env.minion and env.minion.modDB}) do
for _, value in ipairs(modDB:List(nil, "ExtraCurse")) do
Expand Down
Loading