diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index 4dbab4dbb3..2d2a95fc9a 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -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 = { } @@ -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) @@ -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 diff --git a/src/Modules/BuildDisplayStats.lua b/src/Modules/BuildDisplayStats.lua index 67e037e0b6..fc6390ab37 100644 --- a/src/Modules/BuildDisplayStats.lua +++ b/src/Modules/BuildDisplayStats.lua @@ -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 }, diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 2eb2e9e7b8..0e67447455 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 596c088611..c505ebc8db 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -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 @@ -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 diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index a7f6045c7a..70736bad4a 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -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" }, @@ -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" }, diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index 0957924717..29bb526657 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -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 },