From 268296b57c322b41e38972ed87a5e6e8afe94500 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Tue, 12 May 2026 02:14:34 -0500 Subject: [PATCH 1/5] add support for Ancestral Call, Crescendo II and III, ancestral boost tree nodes --- src/Data/ModCache.lua | 12 +++--- src/Data/SkillStatMap.lua | 7 ++++ src/Data/Skills/sup_int.lua | 5 +++ src/Data/Skills/sup_str.lua | 18 +++++++++ src/Export/Skills/sup_int.txt | 5 +++ src/Export/Skills/sup_str.txt | 14 +++++++ src/Modules/CalcOffence.lua | 70 ++++++++++++++++++++++++++++++++--- src/Modules/CalcSections.lua | 9 ++++- src/Modules/ModParser.lua | 3 ++ 9 files changed, 129 insertions(+), 14 deletions(-) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index bab988d5c..d4f674cad 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -2328,7 +2328,7 @@ c["30% increased Accuracy Rating against Rare or Unique Enemies"]={{[1]={[1]={ac c["30% increased Accuracy Rating at Close Range"]={{[1]={[1]={type="Condition",var="AtCloseRange"},flags=0,keywordFlags=0,name="AccuracyVsEnemy",type="INC",value=30}},nil} c["30% increased Accuracy Rating while moving"]={{[1]={[1]={type="Condition",var="Moving"},flags=0,keywordFlags=0,name="Accuracy",type="INC",value=30}},nil} c["30% increased Archon Buff duration"]={{[1]={flags=0,keywordFlags=0,name="Duration",type="INC",value=30}}," Archon Buff "} -c["30% increased Area of Effect of Ancestrally Boosted Attacks"]={{[1]={flags=0,keywordFlags=0,name="AreaOfEffect",type="INC",value=30}}," of Ancestrally Boosted Attacks "} +c["30% increased Area of Effect of Ancestrally Boosted Attacks"]={{[1]={flags=1,keywordFlags=0,name="AncestralBoostAreaOfEffect",type="INC",value=30}},nil} c["30% increased Armour"]={{[1]={flags=0,keywordFlags=0,name="Armour",type="INC",value=30}},nil} c["30% increased Armour while Bleeding"]={{[1]={[1]={type="Condition",var="Bleeding"},flags=0,keywordFlags=0,name="Armour",type="INC",value=30}},nil} c["30% increased Armour while Surrounded"]={{[1]={[1]={type="Condition",var="Surrounded"},flags=0,keywordFlags=0,name="Armour",type="INC",value=30}},nil} @@ -2566,8 +2566,7 @@ c["4% chance that if you would gain Rage on Hit, you instead gain up to your max c["4% increased Area of Effect"]={{[1]={flags=0,keywordFlags=0,name="AreaOfEffect",type="INC",value=4}},nil} c["4% increased Area of Effect for Attacks"]={{[1]={flags=1,keywordFlags=0,name="AreaOfEffect",type="INC",value=4}},nil} c["4% increased Area of Effect for Attacks per Enemy you've Ignited in the last 8 seconds, up to 40%"]={{[1]={flags=1,keywordFlags=0,name="AreaOfEffect",type="INC",value=4}}," per Enemy you've Ignited in the last 8 seconds, up to 40% "} -c["4% increased Area of Effect of Ancestrally Boosted Attacks"]={{[1]={flags=0,keywordFlags=0,name="AreaOfEffect",type="INC",value=4}}," of Ancestrally Boosted Attacks "} -c["4% increased Area of Effect of Ancestrally Boosted Attacks Ancestrally Boosted Attacks deal 8% increased Damage"]={{[1]={flags=0,keywordFlags=0,name="AreaOfEffect",type="INC",value=4}}," of Ancestrally Boosted Attacks Ancestrally Boosted Attacks deal 8% increased Damage "} +c["4% increased Area of Effect of Ancestrally Boosted Attacks"]={{[1]={flags=1,keywordFlags=0,name="AncestralBoostAreaOfEffect",type="INC",value=4}},nil} c["4% increased Attack Damage per 75 Item Armour and Evasion Rating on Equipped Shield"]={{[1]={[1]={div=75,statList={[1]="ArmourOnWeapon 2",[2]="EvasionOnWeapon 2"},type="PerStat"},[2]={type="Condition",var="UsingShield"},flags=1,keywordFlags=0,name="Damage",type="INC",value=4}},nil} c["4% increased Attack Speed"]={{[1]={flags=1,keywordFlags=0,name="Speed",type="INC",value=4}},nil} c["4% increased Attack Speed while a Rare or Unique Enemy is in your Presence"]={{[1]={[1]={actor="enemy",type="ActorCondition",varList={[1]="NearbyRareOrUniqueEnemy",[2]="RareOrUnique"}},flags=1,keywordFlags=0,name="Speed",type="INC",value=4}},nil} @@ -4343,10 +4342,9 @@ c["Alternating every 5 seconds: Take 40% less Damage from Hits"]={nil,"Alternati c["Always Hits"]={{[1]={[1]={type="Condition",var="{Hand}Attack"},flags=0,keywordFlags=0,name="CannotBeEvaded",type="FLAG",value=true}},nil} c["Always Poison on Hit with this weapon"]={{[1]={[1]={type="Condition",var="{Hand}Attack"},[2]={neg=true,skillType=167,type="SkillType"},flags=8192,keywordFlags=0,name="PoisonChance",type="OVERRIDE",value=100}},nil} c["Always deals Critical Hits against Heavy Stunned Enemies"]={{[1]={[1]={actor="enemy",type="ActorCondition",var="HeavyStunned"},[2]={type="Condition",var="{Hand}Attack"},flags=0,keywordFlags=0,name="CritChance",type="OVERRIDE",value=100}},nil} -c["Ancestrally Boosted Attacks deal 16% increased Damage"]={nil,"Ancestrally Boosted Attacks deal 16% increased Damage "} -c["Ancestrally Boosted Attacks deal 30% increased Damage"]={nil,"Ancestrally Boosted Attacks deal 30% increased Damage "} -c["Ancestrally Boosted Attacks deal 30% increased Damage On Heavy Stunning a Rare or Unique Enemy, your next Attack within 4 seconds will be Ancestrally Boosted"]={nil,"Ancestrally Boosted Attacks deal 30% increased Damage On Heavy Stunning a Rare or Unique Enemy, your next Attack within 4 seconds will be Ancestrally Boosted "} -c["Ancestrally Boosted Attacks deal 8% increased Damage"]={nil,"Ancestrally Boosted Attacks deal 8% increased Damage "} +c["Ancestrally Boosted Attacks deal 16% increased Damage"]={{[1]={flags=1,keywordFlags=0,name="AncestralBoostDamage",type="INC",value=16}},nil} +c["Ancestrally Boosted Attacks deal 30% increased Damage"]={{[1]={flags=1,keywordFlags=0,name="AncestralBoostDamage",type="INC",value=30}},nil} +c["Ancestrally Boosted Attacks deal 8% increased Damage"]={{[1]={flags=1,keywordFlags=0,name="AncestralBoostDamage",type="INC",value=8}},nil} c["Any number of Poisons from this Weapon can affect a target at the same time"]={{[1]={flags=0,keywordFlags=0,name="PoisonCanStack",type="FLAG",value=true},[2]={[1]={type="Condition",var="{Hand}Attack"},[2]={neg=true,skillType=167,type="SkillType"},flags=0,keywordFlags=0,name="PoisonStacks",type="OVERRIDE",value=math.huge}},nil} c["Apply 10 Critical Weakness to Enemies when Consuming a Mark on them"]={{[1]={flags=0,keywordFlags=0,name="ApplyCriticalWeakness",type="FLAG",value=true}},nil} c["Apply Debilitate to Enemies 3 Metres in front of you while your Shield is raised"]={nil,"Apply Debilitate to Enemies 3 Metres in front of you while your Shield is raised "} diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index 1e2c80fc6..debf76f1b 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -2512,6 +2512,13 @@ return { ["slam_aftershock_chance_%"] = { mod("AftershockChance", "BASE", nil) }, +-- Final Strike +["final_strike_is_ancestrally_boosted"] = { + flag("FinalStrikeAncestrallyBoosted"), +}, +["is_final_strike"] = { + flag("Condition:FinalStrike"), +}, -- Curse ["curse_effect_+%"] = { mod("CurseEffect", "INC", nil), diff --git a/src/Data/Skills/sup_int.lua b/src/Data/Skills/sup_int.lua index 612992061..488c28a3f 100644 --- a/src/Data/Skills/sup_int.lua +++ b/src/Data/Skills/sup_int.lua @@ -2584,6 +2584,11 @@ skills["SupportCrescendoPlayerTwo"] = { label = "Crescendo II", incrementalEffectiveness = 0.054999999701977, statDescriptionScope = "gem_stat_descriptions", + statMap = { + ["support_crescendo_non_final_strike_attack_speed_+%_final"] = { + mod("Speed", "MORE", nil, ModFlag.Attack, 0, { type = "Condition", var = "FinalStrike", neg = true}) + }, + }, baseFlags = { }, constantStats = { diff --git a/src/Data/Skills/sup_str.lua b/src/Data/Skills/sup_str.lua index 4e04c7162..cc9d89a36 100644 --- a/src/Data/Skills/sup_str.lua +++ b/src/Data/Skills/sup_str.lua @@ -149,8 +149,17 @@ skills["SupportAncestralCallPlayer"] = { label = "Ancestral Call I", incrementalEffectiveness = 0.054999999701977, statDescriptionScope = "gem_stat_descriptions", + statMap = { + ["ancestral_call_spirit_strike_interval_ms"] = { + mod("AncestralCallCooldown", "BASE", nil), + div = 1000, + }, + }, baseFlags = { }, + baseMods = { + mod("AdditionalStrikeTarget", "BASE", 2), + }, constantStats = { { "ancestral_call_spirit_strike_interval_ms", 5000 }, }, @@ -181,8 +190,17 @@ skills["SupportAncestralCallPlayerTwo"] = { label = "Ancestral Call II", incrementalEffectiveness = 0.054999999701977, statDescriptionScope = "gem_stat_descriptions", + statMap = { + ["ancestral_call_spirit_strike_interval_ms"] = { + mod("AncestralCallCooldown", "BASE", nil), + div = 1000, + }, + }, baseFlags = { }, + baseMods = { + mod("AdditionalStrikeTarget", "BASE", 2), + }, constantStats = { { "ancestral_call_spirit_strike_interval_ms", 3000 }, }, diff --git a/src/Export/Skills/sup_int.txt b/src/Export/Skills/sup_int.txt index 0b1786e04..f35d9e4e0 100644 --- a/src/Export/Skills/sup_int.txt +++ b/src/Export/Skills/sup_int.txt @@ -436,6 +436,11 @@ statMap = { #skill SupportCrescendoPlayerTwo #set SupportCrescendoPlayerTwo +statMap = { + ["support_crescendo_non_final_strike_attack_speed_+%_final"] = { + mod("Speed", "MORE", nil, ModFlag.Attack, 0, { type = "Condition", var = "FinalStrike", neg = true}) + }, +}, #mods #skillEnd diff --git a/src/Export/Skills/sup_str.txt b/src/Export/Skills/sup_str.txt index 9af608efa..054e6ed8b 100644 --- a/src/Export/Skills/sup_str.txt +++ b/src/Export/Skills/sup_str.txt @@ -26,11 +26,25 @@ local skills, mod, flag, skill = ... #skill SupportAncestralCallPlayer #set SupportAncestralCallPlayer +statMap = { + ["ancestral_call_spirit_strike_interval_ms"] = { + mod("AncestralCallCooldown", "BASE", nil), + div = 1000, + }, +}, +#baseMod mod("AdditionalStrikeTarget", "BASE", 2) #mods #skillEnd #skill SupportAncestralCallPlayerTwo #set SupportAncestralCallPlayerTwo +statMap = { + ["ancestral_call_spirit_strike_interval_ms"] = { + mod("AncestralCallCooldown", "BASE", nil), + div = 1000, + }, +}, +#baseMod mod("AdditionalStrikeTarget", "BASE", 2) #mods #skillEnd diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 3e87cc8ac..ddf2dc7bf 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3296,6 +3296,7 @@ function calcs.offence(env, actor, activeSkill) end output.FistOfWarDamageEffect = 1 + output.AncestralCallDamageEffect = 1 if env.mode_combat then local ruthlessEffect = env.configInput.ruthlessSupportMode or "AVERAGE" local ruthlessBlowMaxCount = skillModList:Sum("BASE", cfg, "RuthlessBlowMaxCount") @@ -3314,6 +3315,16 @@ function calcs.offence(env, actor, activeSkill) local ruthlessBlowStunEffect = (ruthlessBlowChance / 100) * ruthlessBlowStunMultiplier skillModList:NewMod("EnemyHeavyStunBuildup", "MORE", ruthlessBlowStunEffect * 100, "Ruthless Blows") + -- passive nodes + local ancestrallyBoostedIncDamageMulti = 1 + modDB:Sum("INC", cfg, "AncestralBoostDamage") / 100 + local ancestrallyBoostedIncArea = modDB:Sum("INC", cfg, "AncestralBoostAreaOfEffect") + -- Final Strike calcs could be done in many other places, but clumping the Ancestral Boost things together made sense + if skillModList:Flag(cfg, "FinalStrikeAncestrallyBoosted") then + local modSource = skillModList:Tabulate("FLAG", cfg, "FinalStrikeAncestrallyBoosted")[1].mod.source -- e.g. Skill:SupportCrescendoPlayerThree + local sourceName = "Ancestral Boost - "..modSource:match("Support(.-)Player") -- crude way to grab Support name, there may be a way in data now or a way to create a map in data with the above structured key + skillModList:NewMod("Damage", "INC", modDB:Sum("INC", cfg, "AncestralBoostDamage"), sourceName, { type = "Condition", var = "FinalStrike" }) + end + globalOutput.FistOfWarCooldown = skillModList:Sum("BASE", cfg, "FistOfWarCooldown") or 0 -- If Fist of War & Active Skill is a Slam Skill & NOT a Vaal Skill & NOT used by mirage or other if globalOutput.FistOfWarCooldown ~= 0 and activeSkill.skillTypes[SkillType.Slam] and not activeSkill.skillTypes[SkillType.Vaal] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] then @@ -3326,8 +3337,8 @@ function calcs.offence(env, actor, activeSkill) s_format("= %d%%", globalOutput.FistOfWarUptimeRatio), } end - globalOutput.AvgFistOfWarDamage = globalOutput.FistOfWarDamageMultiplier - globalOutput.AvgFistOfWarDamageEffect = 1 + globalOutput.FistOfWarDamageMultiplier * (globalOutput.FistOfWarUptimeRatio / 100) + globalOutput.AvgFistOfWarDamage = globalOutput.FistOfWarDamageMultiplier * ancestrallyBoostedIncDamageMulti + globalOutput.AvgFistOfWarDamageEffect = 1 + globalOutput.FistOfWarDamageMultiplier * ancestrallyBoostedIncDamageMulti * (globalOutput.FistOfWarUptimeRatio / 100) if globalBreakdown then globalBreakdown.AvgFistOfWarDamageEffect = { s_format("1 + (%.2f ^8(fist of war damage multiplier)", globalOutput.FistOfWarDamageMultiplier), @@ -3335,13 +3346,15 @@ function calcs.offence(env, actor, activeSkill) s_format("= %.2f", globalOutput.AvgFistOfWarDamageEffect), } end - globalOutput.MaxFistOfWarDamageEffect = 1 + globalOutput.FistOfWarDamageMultiplier + globalOutput.MaxFistOfWarDamageEffect = 1 + globalOutput.FistOfWarDamageMultiplier * ancestrallyBoostedIncArea if activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then output.FistOfWarDamageEffect = globalOutput.MaxFistOfWarDamageEffect skillModList:NewMod("AreaOfEffect", "MORE", skillModList:Sum("BASE", nil, "FistOfWarMOREAoE"), "Max Fist of War Boosted AoE") + skillModList:NewMod("AreaOfEffect", "INC", ancestrallyBoostedIncArea, "Max Fist of War Boosted AoE") else output.FistOfWarDamageEffect = globalOutput.AvgFistOfWarDamageEffect - skillModList:NewMod("AreaOfEffect", "MORE", m_floor(skillModList:Sum("BASE", nil, "FistOfWarMOREAoE") / 100 * globalOutput.FistOfWarUptimeRatio), "Avg Fist Of War Boosted AoE") + skillModList:NewMod("AreaOfEffect", "MORE", m_floor(skillModList:Sum("BASE", nil, "FistOfWarMOREAoE") * globalOutput.FistOfWarUptimeRatio / 100), "Avg Fist Of War Boosted AoE") + skillModList:NewMod("AreaOfEffect", "INC", m_floor(ancestrallyBoostedIncArea * globalOutput.FistOfWarUptimeRatio / 100), "Avg Fist Of War Boosted AoE") end calcAreaOfEffect(skillModList, skillCfg, skillData, skillFlags, globalOutput, globalBreakdown) globalOutput.TheoreticalOffensiveWarcryEffect = globalOutput.TheoreticalOffensiveWarcryEffect * globalOutput.AvgFistOfWarDamageEffect @@ -3349,6 +3362,48 @@ function calcs.offence(env, actor, activeSkill) else output.FistOfWarDamageEffect = 1 end + + globalOutput.AncestralCallCooldown = skillModList:Sum("BASE", cfg, "AncestralCallCooldown") or 0 + -- If Ancestral Call & Active Skill is NOT a Vaal Skill & NOT used by mirage or other & NOT a Channel Skill + if globalOutput.AncestralCallCooldown ~= 0 and not activeSkill.skillTypes[SkillType.Vaal] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] and not activeSkill.skillTypes[SkillType.Channel] then + globalOutput.AncestralCallAdditionalStrike = skillModList:Sum("BASE", nil, "AncestralCallAdditionalStrike") + skillModList:NewMod("AdditionalStrikeTarget", "BASE", globalOutput.AncestralCallAdditionalStrike, "Ancestral Call when Ancestrally Boosted") + + globalOutput.AncestralCallDamageMultiplier = ancestrallyBoostedIncDamageMulti + globalOutput.AncestralCallUptimeRatio = m_min( (1 / globalOutput.Speed) / globalOutput.AncestralCallCooldown, 1) * 100 + if globalBreakdown then + globalBreakdown.AncestralCallUptimeRatio = { + s_format("min( (1 / %.2f) ^8(second per attack)", globalOutput.Speed), + s_format("/ %.2f, 1) ^8(ancestral call cooldown)", globalOutput.AncestralCallCooldown), + s_format("= %d%%", globalOutput.AncestralCallUptimeRatio), + } + end + globalOutput.AvgAncestralCallDamage = globalOutput.AncestralCallDamageMultiplier + globalOutput.AvgAncestralCallDamageEffect = 1 + if ancestrallyBoostedIncDamageMulti > 1 then -- if there is no increased damage, then Ancestrally Boosted Strikes do not have any damage portion + globalOutput.AvgAncestralCallDamageEffect = 1 + ancestrallyBoostedIncDamageMulti * (globalOutput.AncestralCallUptimeRatio / 100) + end + if globalBreakdown then + globalBreakdown.AvgAncestralCallDamageEffect = { + s_format("1 + (%.2f ^8(ancestral call damage multiplier)", ancestrallyBoostedIncDamageMulti > 1 and globalOutput.AncestralCallDamageMultiplier or 0), + s_format("x %.2f) ^8(ancestral call uptime ratio)", globalOutput.AncestralCallUptimeRatio / 100 or 0), + s_format("= %.2f", globalOutput.AvgAncestralCallDamageEffect), + } + end + globalOutput.MaxAncestralCallDamageEffect = ancestrallyBoostedIncDamageMulti + if activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then + output.AncestralCallDamageEffect = globalOutput.MaxAncestralCallDamageEffect + skillModList:NewMod("AreaOfEffect", "INC", ancestrallyBoostedIncArea, "Max Ancestral Call Boosted AoE") + else + output.AncestralCallDamageEffect = globalOutput.AvgAncestralCallDamageEffect + skillModList:NewMod("AreaOfEffect", "INC", m_floor(ancestrallyBoostedIncArea * globalOutput.AncestralCallUptimeRatio / 100), "Avg Ancestral Call Boosted AoE") + end + calcAreaOfEffect(skillModList, skillCfg, skillData, skillFlags, globalOutput, globalBreakdown) + globalOutput.TheoreticalOffensiveWarcryEffect = globalOutput.TheoreticalOffensiveWarcryEffect * globalOutput.AvgAncestralCallDamageEffect + globalOutput.TheoreticalMaxOffensiveWarcryEffect = globalOutput.TheoreticalMaxOffensiveWarcryEffect * globalOutput.MaxAncestralCallDamageEffect + else + output.AncestralCallDamageEffect = 1 + end end -- Calculate maximum sustainable fuses and explosion rate for Explosive Arrow @@ -3687,6 +3742,9 @@ function calcs.offence(env, actor, activeSkill) if output.FistOfWarDamageEffect ~= 1 then t_insert(breakdown[damageType], s_format("x %.2f ^8(fist of war effect modifier)", output.FistOfWarDamageEffect)) end + if output.AncestralCallDamageEffect ~= 1 then + t_insert(breakdown[damageType], s_format("x %.2f ^8(ancestral call effect modifier)", output.AncestralCallDamageEffect)) + end if globalOutput.OffensiveWarcryEffect ~= 1 and not activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then t_insert(breakdown[damageType], s_format("x %.2f ^8(aggregated warcry exerted effect modifier)", globalOutput.OffensiveWarcryEffect)) end @@ -3695,9 +3753,9 @@ function calcs.offence(env, actor, activeSkill) end end if activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then - output.allMult = output.ScaledDamageEffect * output.FistOfWarDamageEffect * globalOutput.MaxOffensiveWarcryEffect + output.allMult = output.ScaledDamageEffect * output.FistOfWarDamageEffect * output.AncestralCallDamageEffect * globalOutput.MaxOffensiveWarcryEffect else - output.allMult = output.ScaledDamageEffect * output.FistOfWarDamageEffect * globalOutput.OffensiveWarcryEffect + output.allMult = output.ScaledDamageEffect * output.FistOfWarDamageEffect * output.AncestralCallDamageEffect * globalOutput.OffensiveWarcryEffect end local allMult = output.allMult if pass == 1 then diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index ab1596c29..e2bd3f47b 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -417,11 +417,18 @@ return { }, { label = "Fist of War", bgCol = colorCodes.MAINHANDBG, haveOutput = "FistOfWarUptimeRatio", { format = "{2:output:AvgFistOfWarDamageEffect}", { breakdown = "AvgFistOfWarDamageEffect"}, }, - { format = "{2:output:AvgFistOfWarDamage}", { modName = "FistOfWarDamageMultiplier", cfg = "skill"}, }, + { format = "{2:output:AvgFistOfWarDamage}", { modName = "FistOfWarDamageMultiplier", cfg = "skill"}, { modName = "AncestralBoostDamage", cfg = "skill" }, }, { format = "{0:output:FistOfWarUptimeRatio}%", { breakdown = "FistOfWarUptimeRatio" }, }, { format = "" }, { format = "{2:output:MaxFistOfWarDamageEffect}" }, }, + { label = "Ancestral Call", bgCol = colorCodes.MAINHANDBG, haveOutput = "AncestralCallUptimeRatio", + { format = "{2:output:AvgAncestralCallDamageEffect}", { breakdown = "AvgAncestralCallDamageEffect"}, }, + { format = "{2:output:AvgAncestralCallDamage}", { modName = "AncestralCallDamageMultiplier", cfg = "skill"}, { modName = "AncestralBoostDamage", cfg = "skill" }, }, + { format = "{0:output:AncestralCallUptimeRatio}%", { breakdown = "AncestralCallUptimeRatio" }, }, + { format = "" }, + { format = "{2:output:MaxAncestralCallDamageEffect}" }, + }, } } } }, { 3, "Dot", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Skill Damage over Time", data = { diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index a13144ac8..35fb80bab 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -2617,6 +2617,9 @@ local specialModList = { -- Exerted Attacks ["exerted attacks deal (%d+)%% increased damage"] = function(num) return { mod("ExertIncrease", "INC", num, nil, ModFlag.Attack, 0) } end, ["exerted attacks have (%d+)%% chance to deal double damage"] = function(num) return { mod("ExertDoubleDamageChance", "BASE", num, nil, ModFlag.Attack, 0) } end, + -- Ancestrally Boosted + ["ancestrally boosted attacks deal (%d+)%% increased damage"] = function(num) return { mod("AncestralBoostDamage", "INC", num, nil, ModFlag.Attack, 0) } end, + ["(%d+)%% increased area of effect of ancestrally boosted attacks"] = function(num) return { mod("AncestralBoostAreaOfEffect", "INC", num, nil, ModFlag.Attack, 0) } end, -- Leech Related ["life leech is instant"] = { mod("InstantLifeLeech", "BASE", 100), }, ["mana leech is instant"] = { mod("InstantManaLeech", "BASE", 100), }, From 2673009c18e51f05ba7f4566f7a6e0417c95e16e Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Tue, 12 May 2026 02:40:03 -0500 Subject: [PATCH 2/5] fix ancestral call inc dmg from tree --- src/Modules/CalcOffence.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index ddf2dc7bf..ce58ea2b7 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3316,7 +3316,7 @@ function calcs.offence(env, actor, activeSkill) skillModList:NewMod("EnemyHeavyStunBuildup", "MORE", ruthlessBlowStunEffect * 100, "Ruthless Blows") -- passive nodes - local ancestrallyBoostedIncDamageMulti = 1 + modDB:Sum("INC", cfg, "AncestralBoostDamage") / 100 + local ancestrallyBoostedIncDamageMulti = modDB:Sum("INC", cfg, "AncestralBoostDamage") / 100 local ancestrallyBoostedIncArea = modDB:Sum("INC", cfg, "AncestralBoostAreaOfEffect") -- Final Strike calcs could be done in many other places, but clumping the Ancestral Boost things together made sense if skillModList:Flag(cfg, "FinalStrikeAncestrallyBoosted") then @@ -3380,17 +3380,17 @@ function calcs.offence(env, actor, activeSkill) end globalOutput.AvgAncestralCallDamage = globalOutput.AncestralCallDamageMultiplier globalOutput.AvgAncestralCallDamageEffect = 1 - if ancestrallyBoostedIncDamageMulti > 1 then -- if there is no increased damage, then Ancestrally Boosted Strikes do not have any damage portion + if ancestrallyBoostedIncDamageMulti > 0 then -- if there is no increased damage, then Ancestrally Boosted Strikes do not have any damage portion globalOutput.AvgAncestralCallDamageEffect = 1 + ancestrallyBoostedIncDamageMulti * (globalOutput.AncestralCallUptimeRatio / 100) end if globalBreakdown then globalBreakdown.AvgAncestralCallDamageEffect = { - s_format("1 + (%.2f ^8(ancestral call damage multiplier)", ancestrallyBoostedIncDamageMulti > 1 and globalOutput.AncestralCallDamageMultiplier or 0), + s_format("1 + (%.2f ^8(ancestral call damage multiplier)", ancestrallyBoostedIncDamageMulti > 0 and globalOutput.AncestralCallDamageMultiplier or 0), s_format("x %.2f) ^8(ancestral call uptime ratio)", globalOutput.AncestralCallUptimeRatio / 100 or 0), s_format("= %.2f", globalOutput.AvgAncestralCallDamageEffect), } end - globalOutput.MaxAncestralCallDamageEffect = ancestrallyBoostedIncDamageMulti + globalOutput.MaxAncestralCallDamageEffect = 1 + ancestrallyBoostedIncDamageMulti if activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then output.AncestralCallDamageEffect = globalOutput.MaxAncestralCallDamageEffect skillModList:NewMod("AreaOfEffect", "INC", ancestrallyBoostedIncArea, "Max Ancestral Call Boosted AoE") From 8dd845d1f09ff1387b18468111333a9b4f33741d Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Tue, 12 May 2026 09:17:41 -0500 Subject: [PATCH 3/5] big ole refactor so we stop duplicating code one commit so I can revert if it goes boom --- src/Modules/CalcOffence.lua | 93 +++++++++++++++---------------------- 1 file changed, 37 insertions(+), 56 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index ce58ea2b7..19d10c731 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3325,40 +3325,52 @@ function calcs.offence(env, actor, activeSkill) skillModList:NewMod("Damage", "INC", modDB:Sum("INC", cfg, "AncestralBoostDamage"), sourceName, { type = "Condition", var = "FinalStrike" }) end - globalOutput.FistOfWarCooldown = skillModList:Sum("BASE", cfg, "FistOfWarCooldown") or 0 - -- If Fist of War & Active Skill is a Slam Skill & NOT a Vaal Skill & NOT used by mirage or other - if globalOutput.FistOfWarCooldown ~= 0 and activeSkill.skillTypes[SkillType.Slam] and not activeSkill.skillTypes[SkillType.Vaal] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] then - globalOutput.FistOfWarDamageMultiplier = skillModList:Sum("BASE", nil, "FistOfWarDamageMultiplier") / 100 - globalOutput.FistOfWarUptimeRatio = m_min( (1 / globalOutput.Speed) / globalOutput.FistOfWarCooldown, 1) * 100 + -- dynamic way of calcing the Ancestral Boost for supports without duplicating the code for each unique support + local function calcAncestralBoost(skillName, moreDmg, moreArea) + local skillNameVar = skillName:gsub(" ", "") -- Fist Of War -> FistOfWar + local skillNameLabel = skillName:lower() + + if moreDmg then + globalOutput[skillNameVar.."DamageMultiplier"] = moreDmg * (1 + ancestrallyBoostedIncDamageMulti) + else + globalOutput[skillNameVar.."DamageMultiplier"] = ancestrallyBoostedIncDamageMulti + end + globalOutput[skillNameVar.."UptimeRatio"] = m_min( (1 / globalOutput.Speed) / globalOutput[skillNameVar.."Cooldown"], 1) * 100 if globalBreakdown then - globalBreakdown.FistOfWarUptimeRatio = { + globalBreakdown[skillNameVar.."UptimeRatio"] = { s_format("min( (1 / %.2f) ^8(second per attack)", globalOutput.Speed), - s_format("/ %.2f, 1) ^8(fist of war cooldown)", globalOutput.FistOfWarCooldown), - s_format("= %d%%", globalOutput.FistOfWarUptimeRatio), + s_format("/ %.2f, 1) ^8("..skillNameLabel.." cooldown)", globalOutput[skillNameVar.."Cooldown"]), + s_format("= %d%%", globalOutput[skillNameVar.."UptimeRatio"]), } end - globalOutput.AvgFistOfWarDamage = globalOutput.FistOfWarDamageMultiplier * ancestrallyBoostedIncDamageMulti - globalOutput.AvgFistOfWarDamageEffect = 1 + globalOutput.FistOfWarDamageMultiplier * ancestrallyBoostedIncDamageMulti * (globalOutput.FistOfWarUptimeRatio / 100) + globalOutput["Avg"..skillNameVar.."Damage"] = globalOutput[skillNameVar.."DamageMultiplier"] + globalOutput["Avg"..skillNameVar.."DamageEffect"] = 1 + globalOutput["Avg"..skillNameVar.."Damage"] * (globalOutput[skillNameVar.."UptimeRatio"] / 100) if globalBreakdown then - globalBreakdown.AvgFistOfWarDamageEffect = { - s_format("1 + (%.2f ^8(fist of war damage multiplier)", globalOutput.FistOfWarDamageMultiplier), - s_format("x %.2f) ^8(fist of war uptime ratio)", globalOutput.FistOfWarUptimeRatio / 100), - s_format("= %.2f", globalOutput.AvgFistOfWarDamageEffect), + globalBreakdown["Avg"..skillNameVar.."DamageEffect"] = { + s_format("1 + (%.2f ^8("..skillNameLabel.." damage multiplier)", globalOutput[skillNameVar.."DamageMultiplier"]), + s_format("x %.2f) ^8("..skillNameLabel.." uptime ratio)", globalOutput[skillNameVar.."UptimeRatio"] / 100), + s_format("= %.2f", globalOutput["Avg"..skillNameVar.."DamageEffect"]), } end - globalOutput.MaxFistOfWarDamageEffect = 1 + globalOutput.FistOfWarDamageMultiplier * ancestrallyBoostedIncArea + globalOutput["Max"..skillNameVar.."DamageEffect"] = 1 + globalOutput[skillNameVar.."DamageMultiplier"] if activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then - output.FistOfWarDamageEffect = globalOutput.MaxFistOfWarDamageEffect - skillModList:NewMod("AreaOfEffect", "MORE", skillModList:Sum("BASE", nil, "FistOfWarMOREAoE"), "Max Fist of War Boosted AoE") - skillModList:NewMod("AreaOfEffect", "INC", ancestrallyBoostedIncArea, "Max Fist of War Boosted AoE") + output[skillNameVar.."DamageEffect"] = globalOutput["Max"..skillNameVar.."DamageEffect"] + skillModList:NewMod("AreaOfEffect", "MORE", moreArea or 0, "Max "..skillName.." Boosted AoE") + skillModList:NewMod("AreaOfEffect", "INC", ancestrallyBoostedIncArea, "Max "..skillName.." Boosted AoE") else - output.FistOfWarDamageEffect = globalOutput.AvgFistOfWarDamageEffect - skillModList:NewMod("AreaOfEffect", "MORE", m_floor(skillModList:Sum("BASE", nil, "FistOfWarMOREAoE") * globalOutput.FistOfWarUptimeRatio / 100), "Avg Fist Of War Boosted AoE") - skillModList:NewMod("AreaOfEffect", "INC", m_floor(ancestrallyBoostedIncArea * globalOutput.FistOfWarUptimeRatio / 100), "Avg Fist Of War Boosted AoE") + output[skillNameVar.."DamageEffect"] = globalOutput["Avg"..skillNameVar.."DamageEffect"] + skillModList:NewMod("AreaOfEffect", "MORE", m_floor((moreArea or 0) * globalOutput[skillNameVar.."UptimeRatio"] / 100), "Avg "..skillName.." Boosted AoE") + skillModList:NewMod("AreaOfEffect", "INC", m_floor(ancestrallyBoostedIncArea * globalOutput[skillNameVar.."UptimeRatio"] / 100), "Avg "..skillName.." Boosted AoE") end calcAreaOfEffect(skillModList, skillCfg, skillData, skillFlags, globalOutput, globalBreakdown) - globalOutput.TheoreticalOffensiveWarcryEffect = globalOutput.TheoreticalOffensiveWarcryEffect * globalOutput.AvgFistOfWarDamageEffect - globalOutput.TheoreticalMaxOffensiveWarcryEffect = globalOutput.TheoreticalMaxOffensiveWarcryEffect * globalOutput.MaxFistOfWarDamageEffect + globalOutput.TheoreticalOffensiveWarcryEffect = globalOutput.TheoreticalOffensiveWarcryEffect * globalOutput["Avg"..skillNameVar.."DamageEffect"] + globalOutput.TheoreticalMaxOffensiveWarcryEffect = globalOutput.TheoreticalMaxOffensiveWarcryEffect * globalOutput["Max"..skillNameVar.."DamageEffect"] + end + + globalOutput.FistOfWarCooldown = skillModList:Sum("BASE", cfg, "FistOfWarCooldown") or 0 + -- If Fist of War & Active Skill is a Slam Skill & NOT a Vaal Skill & NOT used by mirage or other + if globalOutput.FistOfWarCooldown ~= 0 and activeSkill.skillTypes[SkillType.Slam] and not activeSkill.skillTypes[SkillType.Vaal] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] then + calcAncestralBoost("Fist Of War", (skillModList:Sum("BASE", nil, "FistOfWarDamageMultiplier") / 100), skillModList:Sum("BASE", nil, "FistOfWarMOREAoE")) else output.FistOfWarDamageEffect = 1 end @@ -3368,39 +3380,8 @@ function calcs.offence(env, actor, activeSkill) if globalOutput.AncestralCallCooldown ~= 0 and not activeSkill.skillTypes[SkillType.Vaal] and not activeSkill.skillTypes[SkillType.OtherThingUsesSkill] and not activeSkill.skillTypes[SkillType.Channel] then globalOutput.AncestralCallAdditionalStrike = skillModList:Sum("BASE", nil, "AncestralCallAdditionalStrike") skillModList:NewMod("AdditionalStrikeTarget", "BASE", globalOutput.AncestralCallAdditionalStrike, "Ancestral Call when Ancestrally Boosted") - - globalOutput.AncestralCallDamageMultiplier = ancestrallyBoostedIncDamageMulti - globalOutput.AncestralCallUptimeRatio = m_min( (1 / globalOutput.Speed) / globalOutput.AncestralCallCooldown, 1) * 100 - if globalBreakdown then - globalBreakdown.AncestralCallUptimeRatio = { - s_format("min( (1 / %.2f) ^8(second per attack)", globalOutput.Speed), - s_format("/ %.2f, 1) ^8(ancestral call cooldown)", globalOutput.AncestralCallCooldown), - s_format("= %d%%", globalOutput.AncestralCallUptimeRatio), - } - end - globalOutput.AvgAncestralCallDamage = globalOutput.AncestralCallDamageMultiplier - globalOutput.AvgAncestralCallDamageEffect = 1 - if ancestrallyBoostedIncDamageMulti > 0 then -- if there is no increased damage, then Ancestrally Boosted Strikes do not have any damage portion - globalOutput.AvgAncestralCallDamageEffect = 1 + ancestrallyBoostedIncDamageMulti * (globalOutput.AncestralCallUptimeRatio / 100) - end - if globalBreakdown then - globalBreakdown.AvgAncestralCallDamageEffect = { - s_format("1 + (%.2f ^8(ancestral call damage multiplier)", ancestrallyBoostedIncDamageMulti > 0 and globalOutput.AncestralCallDamageMultiplier or 0), - s_format("x %.2f) ^8(ancestral call uptime ratio)", globalOutput.AncestralCallUptimeRatio / 100 or 0), - s_format("= %.2f", globalOutput.AvgAncestralCallDamageEffect), - } - end - globalOutput.MaxAncestralCallDamageEffect = 1 + ancestrallyBoostedIncDamageMulti - if activeSkill.skillModList:Flag(nil, "Condition:WarcryMaxHit") then - output.AncestralCallDamageEffect = globalOutput.MaxAncestralCallDamageEffect - skillModList:NewMod("AreaOfEffect", "INC", ancestrallyBoostedIncArea, "Max Ancestral Call Boosted AoE") - else - output.AncestralCallDamageEffect = globalOutput.AvgAncestralCallDamageEffect - skillModList:NewMod("AreaOfEffect", "INC", m_floor(ancestrallyBoostedIncArea * globalOutput.AncestralCallUptimeRatio / 100), "Avg Ancestral Call Boosted AoE") - end - calcAreaOfEffect(skillModList, skillCfg, skillData, skillFlags, globalOutput, globalBreakdown) - globalOutput.TheoreticalOffensiveWarcryEffect = globalOutput.TheoreticalOffensiveWarcryEffect * globalOutput.AvgAncestralCallDamageEffect - globalOutput.TheoreticalMaxOffensiveWarcryEffect = globalOutput.TheoreticalMaxOffensiveWarcryEffect * globalOutput.MaxAncestralCallDamageEffect + -- for special cases, the logic ^ can be done outside the generic calc + calcAncestralBoost("Ancestral Call") else output.AncestralCallDamageEffect = 1 end From 06ba100c265ec624749a5b4bbc0b889b4436c701 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Tue, 12 May 2026 09:58:07 -0500 Subject: [PATCH 4/5] added test update CalcSection from "Exerted Warcries" to "Ancestral Boosts" --- spec/System/TestSkills_spec.lua | 17 +++++++++++++++++ src/Modules/CalcOffence.lua | 1 + src/Modules/CalcSections.lua | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index f0c02d4f3..3bbbfdd2b 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -84,4 +84,21 @@ describe("TestSkills", function() local finalCost = build.calcsTab.mainOutput.ManaCost assert.are.equals(16, round(finalCost)) end) + + it("Test Ancestral Call - Ancestral Boost calcs", function() + build.itemsTab:CreateDisplayItemFromRaw([[ + New Item + Fanatic Greathammer + Quality: 0 + ]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + + build.skillsTab:PasteSocketGroup("Boneshatter 20/0 1\nAncestral Call I 1/0 1") + runCallback("OnFrame") + + assert.True(build.calcsTab.calcsOutput.AvgAncestralCallDamageEffect ~= nil) + assert.True(build.calcsTab.calcsOutput.AncestralCallUptimeRatio ~= nil) + assert.are.equal(3, build.calcsTab.calcsOutput.StrikeTargets) + end) end) \ No newline at end of file diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 19d10c731..e659da8be 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3327,6 +3327,7 @@ function calcs.offence(env, actor, activeSkill) -- dynamic way of calcing the Ancestral Boost for supports without duplicating the code for each unique support local function calcAncestralBoost(skillName, moreDmg, moreArea) + globalOutput.CreateWarcryOffensiveCalcSection = true -- labels for the CalcSection local skillNameVar = skillName:gsub(" ", "") -- Fist Of War -> FistOfWar local skillNameLabel = skillName:lower() diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index e2bd3f47b..5e28589ba 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -355,7 +355,7 @@ return { { label = "Skill DPS", flag = "triggered", { format = "{1:output:TotalDPS}", { breakdown = "TotalDPS" }, { label = "DPS Multiplier", modName = "DPS", cfg = "skill" }, }, }, } } } }, -{ 3, "Warcries", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Exerting Warcries", data = { +{ 3, "Warcries", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Ancestral Boosts", data = { extra = "{2:output:TheoreticalOffensiveWarcryEffect} Avg Combined Impact | {2:output:TheoreticalMaxOffensiveWarcryEffect} Max Combined Impact", colWidth = 114, { From 057b46796adcc4ee79df4cebd2b998b7d928e53d Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Tue, 12 May 2026 10:16:40 -0500 Subject: [PATCH 5/5] add data map for gemName given a modSource --- src/Modules/CalcOffence.lua | 2 +- src/Modules/Data.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index e659da8be..fce2f6db4 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3321,7 +3321,7 @@ function calcs.offence(env, actor, activeSkill) -- Final Strike calcs could be done in many other places, but clumping the Ancestral Boost things together made sense if skillModList:Flag(cfg, "FinalStrikeAncestrallyBoosted") then local modSource = skillModList:Tabulate("FLAG", cfg, "FinalStrikeAncestrallyBoosted")[1].mod.source -- e.g. Skill:SupportCrescendoPlayerThree - local sourceName = "Ancestral Boost - "..modSource:match("Support(.-)Player") -- crude way to grab Support name, there may be a way in data now or a way to create a map in data with the above structured key + local sourceName = "Ancestral Boost - "..data.gemNameForModSource[modSource] skillModList:NewMod("Damage", "INC", modDB:Sum("INC", cfg, "AncestralBoostDamage"), sourceName, { type = "Condition", var = "FinalStrike" }) end diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index a92579ce6..8a9e98700 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -889,6 +889,7 @@ data.gems = LoadModule("Data/Gems") data.gemForSkill = { } data.gemForBaseName = { } data.gemsByGameId = { } +data.gemNameForModSource = { } -- Lookup table - [Gem.grantedEffectId] = VaalGemId data.gemGrantedEffectIdForVaalGemId = { } data.gemVaalGemIdForBaseGemId = { } @@ -898,6 +899,7 @@ local function setupGem(gem, gemId) data.gemForSkill[gem.grantedEffect] = gemId data.gemsByGameId[gem.gameId] = data.gemsByGameId[gem.gameId] or {} data.gemsByGameId[gem.gameId][gem.variantId] = gem + data.gemNameForModSource[gem.grantedEffect.modSource] = gem.name local baseName = gem.name if gem.grantedEffect.support and gem.grantedEffectId ~= "SupportBarrage" then baseName = baseName .. " Support"