Skip to content

Commit

Permalink
Implement Guard Skill Mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Regisle committed Oct 22, 2024
1 parent 78b52c9 commit 309db73
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 34 deletions.
8 changes: 7 additions & 1 deletion src/Classes/ImportTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,10 @@ function ImportTabClass:ImportItem(itemData, slotName)
end
end

local defaultDisabledGems = {
ImmortalCall = true
}

function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName)
-- Build socket group list
local itemSocketGroupList = { }
Expand All @@ -1039,6 +1043,8 @@ function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName)
else
local normalizedBasename, qualityType = self.build.skillsTab:GetBaseNameAndQuality(socketedItem.typeLine, nil)
local gemId = self.build.data.gemForBaseName[normalizedBasename:lower()]
local enableGlobal1 = true
local enableGlobal2 = false
if socketedItem.hybrid then
-- Used by transfigured gems and dual-skill gems (currently just Stormbind)
normalizedBasename, qualityType = self.build.skillsTab:GetBaseNameAndQuality(socketedItem.hybrid.baseTypeName, nil)
Expand All @@ -1048,7 +1054,7 @@ function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName)
end
end
if gemId then
local gemInstance = { level = 20, quality = 0, enabled = true, enableGlobal1 = true, gemId = gemId }
local gemInstance = { level = 20, quality = 0, enabled = not defaultDisabledGems[gemId], enableGlobal1 = enableGlobal1, enableGlobal2 = enableGlobal2, gemId = gemId }
gemInstance.nameSpec = self.build.data.gems[gemId].name
gemInstance.support = socketedItem.support
gemInstance.qualityId = qualityType
Expand Down
3 changes: 2 additions & 1 deletion src/Modules/BuildDisplayStats.lua
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ local displayStats = {
{ },
{ stat = "Devotion", label = "Devotion", color = colorCodes.RARE, fmt = "d" },
{ },
{ stat = "TotalEHP", label = "Effective Hit Pool", fmt = ".0f", compPercent = true },
{ stat = "TotalEHP", label = "Effective Hit Pool", fmt = ".0f", compPercent = true, condFunc = function(v,o) return not (o.AnyGuard or o.AnyScaledGuard or o.OnlySharedGuard or o.OnlyScaledSharedGuard) end },
{ stat = "TotalEHP", label = "Effective Hit Pool ^8(Guard)", fmt = ".0f", compPercent = true, condFunc = function(v,o) return o.AnyGuard or o.AnyScaledGuard or o.OnlySharedGuard or o.OnlyScaledSharedGuard end },
{ stat = "PvPTotalTakenHit", label = "PvP Hit Taken", fmt = ".1f", flag = "isPvP", lowerIsBetter = true },
{ stat = "PhysicalMaximumHitTaken", label = "Phys Max Hit", fmt = ".0f", color = colorCodes.PHYS, compPercent = true, },
{ stat = "LightningMaximumHitTaken", label = "Elemental Max Hit", fmt = ".0f", color = colorCodes.LIGHTNING, compPercent = true, condFunc = function(v,o) return o.LightningMaximumHitTaken == o.ColdMaximumHitTaken and o.LightningMaximumHitTaken == o.FireMaximumHitTaken end },
Expand Down
148 changes: 116 additions & 32 deletions src/Modules/CalcDefence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,22 @@ function calcs.reducePoolsByDamage(poolTable, damageTable, actor)
end
local guard = poolTbl.Guard
if not guard then
guard = { shared = output.sharedGuardAbsorb or 0 }
for damageType in pairs(damageTable) do
guard[damageType] = output[damageType.."GuardAbsorb"] or 0
-- Assuming max hit guard mode ( EHP should set guard amount )
if output.maxHitGuardMode == "NONE" then
guard = { shared = 0 }
for damageType in pairs(damageTable) do
guard[damageType] = 0
end
elseif output.maxHitGuardMode == "MAX" then
guard = { shared = output.sharedGuardAbsorb or 0 }
for damageType in pairs(damageTable) do
guard[damageType] = output[damageType.."GuardAbsorb"] or 0
end
elseif output.maxHitGuardMode == "AVERAGE" then
guard = { shared = output.scaledSharedGuardAbsorb or 0 }
for damageType in pairs(damageTable) do
guard[damageType] = output["Scaled"..damageType.."GuardAbsorb"] or 0
end
end
end

Expand Down Expand Up @@ -2191,15 +2204,35 @@ function calcs.buildDefenceEstimations(env, actor)
if output["sharedGuardAbsorbRate"] > 0 then
output.OnlySharedGuard = true
output["sharedGuardAbsorb"] = calcLib.val(modDB, "GuardAbsorbLimit")
local lifeProtected = output["sharedGuardAbsorb"] / (output["sharedGuardAbsorbRate"] / 100) * (1 - output["sharedGuardAbsorbRate"] / 100)
if (output.ehpGuardMode == "AVERAGE" or output.maxHitGuardMode == "AVERAGE") then
output["scaledSharedGuardAbsorb"] = calcLib.val(modDB, "ScaledGuardAbsorbLimit")
if output.ehpGuardMode == "AVERAGE" then
output.OnlySharedGuard = false
output.OnlyScaledSharedGuard = true
end
end
if breakdown then
breakdown["sharedGuardAbsorb"] = {
s_format("Total life protected:"),
s_format("%d ^8(guard limit)", output["sharedGuardAbsorb"]),
s_format("/ %.2f ^8(portion taken from guard)", output["sharedGuardAbsorbRate"] / 100),
s_format("x %.2f ^8(portion taken from life and energy shield)", 1 - output["sharedGuardAbsorbRate"] / 100),
s_format("= %d", lifeProtected)
}
if output.OnlyScaledSharedGuard then
local lifeProtected = output["scaledSharedGuardAbsorb"] / (output["sharedGuardAbsorbRate"] / 100) * (1 - output["sharedGuardAbsorbRate"] / 100)
breakdown["scaledSharedGuardAbsorb"] = {
s_format("Total life protected:"),
s_format("%d ^8(normal guard limit)", output["sharedGuardAbsorb"]),
s_format("x %.2f%% ^8(guard uptime)", output["scaledSharedGuardAbsorb"] / output["sharedGuardAbsorb"] * 100),
s_format("= %d ^8(scaled guard limit)", output["scaledSharedGuardAbsorb"]),
s_format("/ %.2f ^8(portion taken from guard)", output["sharedGuardAbsorbRate"] / 100),
s_format("x %.2f ^8(portion taken from life and energy shield)", 1 - output["sharedGuardAbsorbRate"] / 100),
s_format("= %d", lifeProtected)
}
else
local lifeProtected = output["sharedGuardAbsorb"] / (output["sharedGuardAbsorbRate"] / 100) * (1 - output["sharedGuardAbsorbRate"] / 100)
breakdown["sharedGuardAbsorb"] = {
s_format("Total life protected:"),
s_format("%d ^8(guard limit)", output["sharedGuardAbsorb"]),
s_format("/ %.2f ^8(portion taken from guard)", output["sharedGuardAbsorbRate"] / 100),
s_format("x %.2f ^8(portion taken from life and energy shield)", 1 - output["sharedGuardAbsorbRate"] / 100),
s_format("= %d", lifeProtected)
}
end
end
end
for _, damageType in ipairs(dmgTypeList) do
Expand All @@ -2209,15 +2242,36 @@ function calcs.buildDefenceEstimations(env, actor)
output.AnyGuard = true
output.OnlySharedGuard = false
output[damageType.."GuardAbsorb"] = calcLib.val(modDB, damageType.."GuardAbsorbLimit")
local lifeProtected = output[damageType.."GuardAbsorb"] / (output[damageType.."GuardAbsorbRate"] / 100) * (1 - output[damageType.."GuardAbsorbRate"] / 100)
if (output.ehpGuardMode == "AVERAGE" or output.maxHitGuardMode == "AVERAGE") then
output["Scaled"..damageType.."GuardAbsorb"] = calcLib.val(modDB, "Scaled"..damageType.."GuardAbsorbLimit")
if output.ehpGuardMode == "AVERAGE" then
output.OnlyScaledSharedGuard = false
output.AnyGuard = false
output.AnyScaledGuard = true
end
end
if breakdown then
breakdown[damageType.."GuardAbsorb"] = {
s_format("Total life protected:"),
s_format("%d ^8(guard limit)", output[damageType.."GuardAbsorb"]),
s_format("/ %.2f ^8(portion taken from guard)", output[damageType.."GuardAbsorbRate"] / 100),
s_format("x %.2f ^8(portion taken from life and energy shield)", 1 - output[damageType.."GuardAbsorbRate"] / 100),
s_format("= %d", lifeProtected),
}
if output.AnyScaledGuard then
local lifeProtected = output["Scaled"..damageType.."GuardAbsorb"] / (output[damageType.."GuardAbsorbRate"] / 100) * (1 - output[damageType.."GuardAbsorbRate"] / 100)
breakdown["Scaled"..damageType.."GuardAbsorb"] = {
s_format("Total life protected:"),
s_format("%d ^8(normal guard limit)", output[damageType.."GuardAbsorb"]),
s_format("x %.2f%% ^8(guard uptime)", output["Scaled"..damageType.."GuardAbsorb"] / output[damageType.."GuardAbsorb"] * 100),
s_format("= %d ^8(scaled guard limit)", output["Scaled"..damageType.."GuardAbsorb"]),
s_format("/ %.2f ^8(portion taken from guard)", output[damageType.."GuardAbsorbRate"] / 100),
s_format("x %.2f ^8(portion taken from life and energy shield)", 1 - output[damageType.."GuardAbsorbRate"] / 100),
s_format("= %d", lifeProtected),
}
else
local lifeProtected = output[damageType.."GuardAbsorb"] / (output[damageType.."GuardAbsorbRate"] / 100) * (1 - output[damageType.."GuardAbsorbRate"] / 100)
breakdown["Scaled"..damageType.."GuardAbsorb"] = {
s_format("Total life protected:"),
s_format("%d ^8(guard limit)", output["Scaled"..damageType.."GuardAbsorb"]),
s_format("/ %.2f ^8(portion taken from guard)", output[damageType.."GuardAbsorbRate"] / 100),
s_format("x %.2f ^8(portion taken from life and energy shield)", 1 - output[damageType.."GuardAbsorbRate"] / 100),
s_format("= %d", lifeProtected),
}
end
end
end
end
Expand Down Expand Up @@ -2366,10 +2420,18 @@ function calcs.buildDefenceEstimations(env, actor)
aegis["shared"] = output["sharedAegis"] or 0
aegis["sharedElemental"] = output["sharedElementalAegis"] or 0
local guard = { }
guard["shared"] = output.sharedGuardAbsorb or 0
for _, damageType in ipairs(dmgTypeList) do
aegis[damageType] = output[damageType.."Aegis"] or 0
guard[damageType] = output[damageType.."GuardAbsorb"] or 0
if output.ehpGuardMode == "AVERAGE" then
guard["shared"] = output.scaledSharedGuardAbsorb or 0
for _, damageType in ipairs(dmgTypeList) do
aegis[damageType] = output[damageType.."Aegis"] or 0
guard[damageType] = output["Scaled"..damageType.."GuardAbsorb"] or 0
end
else
guard["shared"] = output.sharedGuardAbsorb or 0
for _, damageType in ipairs(dmgTypeList) do
aegis[damageType] = output[damageType.."Aegis"] or 0
guard[damageType] = output[damageType.."GuardAbsorb"] or 0
end
end
local alliesTakenBeforeYou = {}
if output.FrostShieldLife then
Expand Down Expand Up @@ -2414,7 +2476,7 @@ function calcs.buildDefenceEstimations(env, actor)
DamageIn["WardBypass"] = DamageIn["WardBypass"] or modDB:Sum("BASE", nil, "WardBypass") or 0

local VaalArcticArmourHitsLeft = output.VaalArcticArmourLife
if DamageIn["cycles"] > 1 then
if (DamageIn["cycles"] > 1) or (output.ehpGuardMode == "AVERAGE") then
VaalArcticArmourHitsLeft = 0
end

Expand Down Expand Up @@ -2899,8 +2961,13 @@ function calcs.buildDefenceEstimations(env, actor)
output[damageType.."TotalHitPool"] = output[damageType.."TotalHitPool"] + m_max(m_max(output[damageType.."Aegis"], output["sharedAegis"]), isElemental[damageType] and output[damageType.."AegisDisplay"] or 0)
-- guard skill
local GuardAbsorbRate = output["sharedGuardAbsorbRate"] or 0 + output[damageType.."GuardAbsorbRate"] or 0
if GuardAbsorbRate > 0 then
local GuardAbsorb = output["sharedGuardAbsorb"] or 0 + output[damageType.."GuardAbsorb"] or 0
if (GuardAbsorbRate > 0) and (output.maxHitGuardMode ~= "NONE") then
local GuardAbsorb = 0
if output.maxHitGuardMode == "MAX" then
GuardAbsorb = output["sharedGuardAbsorb"] or 0 + output[damageType.."GuardAbsorb"] or 0
elseif output.maxHitGuardMode == "AVERAGE" then
GuardAbsorb = output["scaledSharedGuardAbsorb"] or 0 + output["Scaled"..damageType.."GuardAbsorb"] or 0
end
if GuardAbsorbRate >= 100 then
output[damageType.."TotalHitPool"] = output[damageType.."TotalHitPool"] + GuardAbsorb
else
Expand Down Expand Up @@ -2938,6 +3005,12 @@ function calcs.buildDefenceEstimations(env, actor)
output[damageType.."TotalHitPool"] = m_max(output[damageType.."TotalHitPool"] - poolProtected, 0) + m_min(output[damageType.."TotalHitPool"], poolProtected) / (1 - output["SoulLinkMitigation"] / 100)
end
end

-- Disable Vaal Arctic Armour for max hit unless guard mode is max
if output.maxHitGuardMode ~= "MAX" then
output["VaalArcticArmourMitigation"] = 0
end


for _, damageType in ipairs(dmgTypeList) do
local partMin = m_huge
Expand Down Expand Up @@ -3107,12 +3180,23 @@ function calcs.buildDefenceEstimations(env, actor)
if receivedElemental and output.sharedElementalAegis and output.sharedElementalAegis > 0 then
t_insert(breakdown[maxHitCurType], s_format("\t%d "..colorCodes.GEM.."Elemental Aegis charge ^7(%d remaining)", output.sharedElementalAegis - poolsRemaining.Aegis.sharedElemental, poolsRemaining.Aegis.sharedElemental))
end
if output.sharedGuardAbsorb and output.sharedGuardAbsorb > 0 then
t_insert(breakdown[maxHitCurType], s_format("\t%d "..colorCodes.SCOURGE.."Shared Guard charge ^7(%d remaining)", output.sharedGuardAbsorb - poolsRemaining.Guard.shared, poolsRemaining.Guard.shared))
end
for takenType in pairs(takenDamages) do
if output[takenType.."GuardAbsorb"] and output[takenType.."GuardAbsorb"] > 0 then
t_insert(breakdown[maxHitCurType], s_format("\n\t%d "..colorCodes.SCOURGE.."%s Guard charge ^7(%d remaining)", output[takenType.."GuardAbsorb"] - poolsRemaining.Guard[takenType], takenType, poolsRemaining.Guard[takenType]))
if output.maxHitGuardMode == "MAX" then
if output.sharedGuardAbsorb and output.sharedGuardAbsorb > 0 then
t_insert(breakdown[maxHitCurType], s_format("\t%d "..colorCodes.SCOURGE.."Shared Guard charge ^7(%d remaining)", output.sharedGuardAbsorb - poolsRemaining.Guard.shared, poolsRemaining.Guard.shared))
end
for takenType in pairs(takenDamages) do
if output[takenType.."GuardAbsorb"] and output[takenType.."GuardAbsorb"] > 0 then
t_insert(breakdown[maxHitCurType], s_format("\n\t%d "..colorCodes.SCOURGE.."%s Guard charge ^7(%d remaining)", output[takenType.."GuardAbsorb"] - poolsRemaining.Guard[takenType], takenType, poolsRemaining.Guard[takenType]))
end
end
elseif output.maxHitGuardMode == "AVERAGE" then
if output.scaledSharedGuardAbsorb and output.scaledSharedGuardAbsorb > 0 then
t_insert(breakdown[maxHitCurType], s_format("\t%d "..colorCodes.SCOURGE.."Shared Guard charge ^7(%d remaining)", output.scaledSharedGuardAbsorb - poolsRemaining.Guard.shared, poolsRemaining.Guard.shared))
end
for takenType in pairs(takenDamages) do
if output["Scaled"..takenType.."GuardAbsorb"] and output["Scaled"..takenType.."GuardAbsorb"] > 0 then
t_insert(breakdown[maxHitCurType], s_format("\n\t%d "..colorCodes.SCOURGE.."%s Guard charge ^7(%d remaining)", output["Scaled"..takenType.."GuardAbsorb"] - poolsRemaining.Guard[takenType], takenType, poolsRemaining.Guard[takenType]))
end
end
end
if output.Ward and output.Ward > 0 then
Expand Down
13 changes: 13 additions & 0 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,7 @@ function calcs.perform(env, skipEHP)
end
end

output.ehpGuardMode, output.maxHitGuardMode = (env.configInput.guardMode or "AVERAGE,NONE"):match(("^([^%)]+),([^%)]+)"))
local appliedCombustion = false
for _, activeSkill in ipairs(env.player.activeSkillList) do
local skillModList = activeSkill.skillModList
Expand Down Expand Up @@ -1886,6 +1887,18 @@ function calcs.perform(env, skipEHP)
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", "BuffEffectOnSelf", "BuffEffectOnPlayer")
local more = modStore:More(skillCfg, "BuffEffect", "BuffEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
if (output.ehpGuardMode == "AVERAGE" or output.maxHitGuardMode == "AVERAGE") then
local full_duration = calcSkillDuration(modStore, skillCfg, activeSkill.skillData, env, enemyDB)
local cooldownOverride = modStore:Override(skillCfg, "CooldownRecovery")
local actual_cooldown = cooldownOverride or (activeSkill.skillData.cooldown + modStore:Sum("BASE", skillCfg, "CooldownRecovery")) / calcLib.mod(modStore, skillCfg, "CooldownRecovery")
local uptime = full_duration / (full_duration + actual_cooldown)
for _, mod in ipairs(buff.modList) do
if (mod.name == "GuardAbsorbLimit") then
srcList:NewMod("ScaledGuardAbsorbLimit", mod.type, m_floor(mod.value*uptime * 100) / 100, mod.source, mod[1], mod[2])
break
end
end
end
mergeBuff(srcList, guards, buff.name)
end
end
Expand Down
32 changes: 32 additions & 0 deletions src/Modules/CalcSections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2054,6 +2054,12 @@ return {
{ modName = { "GuardAbsorbRate", "GuardAbsorbLimit" } },
},
},
{ label = "Guard", haveOutput = "OnlyScaledSharedGuard",
{ format = "{0:output:scaledSharedGuardAbsorb}",
{ breakdown = "scaledSharedGuardAbsorb" },
{ modName = { "GuardAbsorbRate", "ScaledGuardAbsorbLimit" } },
},
},
{ label = "Guard", haveOutput = "AnyGuard",
{ format = "{0:output:sharedGuardAbsorb}",
{ breakdown = "sharedGuardAbsorb" },
Expand All @@ -2080,6 +2086,32 @@ return {
{ modName = { "ChaosGuardAbsorbRate", "ChaosGuardAbsorbLimit" } },
},
},
{ label = "Guard", haveOutput = "AnyScaledGuard",
{ format = "{0:output:scaledSharedGuardAbsorb}",
{ breakdown = "scaledSharedGuardAbsorb" },
{ modName = { "GuardAbsorbRate", "ScaledGuardAbsorbLimit" } },
},
{ format = "+{0:output:ScaledPhysicalGuardAbsorb}",
{ breakdown = "ScaledPhysicalGuardAbsorb" },
{ modName = { "PhysicalGuardAbsorbRate", "ScaledPhysicalGuardAbsorbLimit" } },
},
{ format = "+{0:output:ScaledLightningGuardAbsorb}",
{ breakdown = "ScaledLightningGuardAbsorb" },
{ modName = { "LightningGuardAbsorbRate", "ScaledLightningGuardAbsorbLimit" } },
},
{ format = "+{0:output:ScaledColdGuardAbsorb}",
{ breakdown = "ScaledColdGuardAbsorb" },
{ modName = { "ColdGuardAbsorbRate", "ScaledColdGuardAbsorbLimit" } },
},
{ format = "+{0:output:ScaledFireGuardAbsorb}",
{ breakdown = "ScaledFireGuardAbsorb" },
{ modName = { "FireGuardAbsorbRate", "ScaledFireGuardAbsorbLimit" } },
},
{ format = "+{0:output:ScaledChaosGuardAbsorb}",
{ breakdown = "ScaledChaosGuardAbsorb" },
{ modName = { "ChaosGuardAbsorbRate", "ScaledChaosGuardAbsorbLimit" } },
},
},
{ label = "Aegis", haveOutput = "AnyAegis",
{ format = "{0:output:sharedAegis}",
{ breakdown = "sharedAegis" },
Expand Down
1 change: 1 addition & 0 deletions src/Modules/ConfigOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ return {
modList:NewMod("Condition:WarcryMaxHit", "FLAG", true, "Config")
end
end },
{ var = "guardMode", type = "list", label = "Guard Skill calc mode:", ifSkill = { "Arcane Cloak", "Molten Shell", "Steelskin", "Bone Armour", "Vaal Arctic Armour" }, tooltip = "Controls how Guard Skills (excluding Immortal Call) and Vaal Arctic Armour are calculated:\nAverage / None: Averages out Guard skills for EHP but disables it for maximum hit.\nMaximum / None: Uses the maximum value of Guard skills for EHP but disables it for maximum hit.\nAverage / Maximum: Averages out Guard skills for EHP but uses the maximum value for maximum hit. (As if it was always up)\nMaximum / Maximum: Uses the maximum value for EHP and maximum hit. (As if it was always up)", list = {{val="AVERAGE,NONE",label="Average / None"},{val="MAX,NONE",label="Maximum / None"},{val="AVERAGE,MAX",label="Average / Maximum"},{val="MAX,MAX",label="Maximum / Maximum"}} },
{ var = "EVBypass", type = "check", label = "Disable Emperor's Vigilance Bypass", ifCond = "EVBypass", apply = function(val, modList, enemyModList)
modList:NewMod("Condition:EVBypass", "FLAG", true, "Config")
end },
Expand Down

0 comments on commit 309db73

Please sign in to comment.