Skip to content

Commit

Permalink
Add Molten Strike (+Zenith) ball average overlap calculations (#8427)
Browse files Browse the repository at this point in the history
* add average ball overlap calcs for molten strike

* add average ball overlap calcs for molten strike of the zenith as well as a total weighted average of normal balls and 5th attack balls

* copy molten strike overlap changes to export scripts

* Fix spelling

---------

Co-authored-by: Wires77 <[email protected]>
  • Loading branch information
andyli00 and Wires77 authored Feb 20, 2025
1 parent 4236d88 commit 969810d
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 35 deletions.
130 changes: 114 additions & 16 deletions src/Data/Skills/act_str.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6975,10 +6975,89 @@ skills["MoltenStrike"] = {
projectile = true,
area = true,
},
{
name = "Average Balls Hitting",
melee = false,
projectile = true,
area = true,
},
},
preDamageFunc = function(activeSkill, output, breakdown)
local skillCfg = activeSkill.skillCfg
local skillData = activeSkill.skillData
local skillPart = activeSkill.skillPart
local skillModList = activeSkill.skillModList
local t_insert = table.insert
local s_format = string.format

-- melee part doesn't need to calc balls
if skillPart == 1 then
return
end

local enemyRadius = skillModList:Override(skillCfg, "EnemyRadius") or skillModList:Sum("BASE", skillCfg, "EnemyRadius")
local ballRadius = output.AreaOfEffectRadius
local innerRadius = output.AreaOfEffectRadiusSecondary
local outerRadius = output.AreaOfEffectRadiusTertiary

-- logic adapted from MoldyDwarf's calculator
local hitRange = enemyRadius + ballRadius - innerRadius
local landingRange = outerRadius - innerRadius
local overlapChance = math.min(1, hitRange / landingRange)
output.OverlapChance = overlapChance * 100

if breakdown then
breakdown.OverlapChance = { }
t_insert(breakdown.OverlapChance, "Chance for individual balls to land on the enemy:")
t_insert(breakdown.OverlapChance, "^8= (area where a ball can land on enemy) / (total area)")
t_insert(breakdown.OverlapChance, "^8= (enemy radius + ball radius - min travel) / (max travel - min travel)")
t_insert(breakdown.OverlapChance, s_format("^8= (^7%d^8 + ^7%d^8 - ^7%d) / (^7%d^8 - ^7%d)",
enemyRadius, ballRadius, innerRadius, outerRadius, innerRadius))
t_insert(breakdown.OverlapChance, s_format("^8=^7 %.2f^8%%", output.OverlapChance))
end

local numProjectiles = skillModList:Sum("BASE", skillCfg, "ProjectileCount")
local dpsMult = 1
if skillPart == 3 or skillPart == 5 or skillPart == 6 then
dpsMult = overlapChance * numProjectiles

if skillPart ~= 6 then
if breakdown then
breakdown.SkillDPSMultiplier = {}
t_insert(breakdown.SkillDPSMultiplier, "DPS multiplier")
t_insert(breakdown.SkillDPSMultiplier, "^8= number of projectiles * overlap chance")
t_insert(breakdown.SkillDPSMultiplier, s_format("^8=^7 %d^8 *^7 %.3f^8", numProjectiles, overlapChance))
t_insert(breakdown.SkillDPSMultiplier, s_format("^8=^7 %.3f", dpsMult))
end
else
-- zenith: make an effective dpsMult for the weighted average of normal and 5th attack balls
local gemQuality = activeSkill.activeEffect.quality
local fifthAttackMulti = 1 + 8 + 0.1 * gemQuality
local fifthAttackOverallMulti = fifthAttackMulti * overlapChance * (numProjectiles + 5)
dpsMult = 0.8 * dpsMult + 0.2 * fifthAttackOverallMulti

if breakdown then
breakdown.SkillDPSMultiplier = {}
t_insert(breakdown.SkillDPSMultiplier, "Weighted average DPS multiplier for balls")
t_insert(breakdown.SkillDPSMultiplier, "^8= (0.8 * balls dps) + (0.2 * 5th attack balls dps)")
t_insert(breakdown.SkillDPSMultiplier, "^8= (0.8 * normal ball hit * overlap chance * number of projectiles) " ..
"+ (0.2 * ball hit * 5th attack multiplier * overlap chance * (number of projectiles + 5))")
t_insert(breakdown.SkillDPSMultiplier, "^8= ball hit * overlap chance * (0.8 * number of projectiles " ..
"+ 0.2 * 5th attack multiplier * (number of projectiles + 5))")
t_insert(breakdown.SkillDPSMultiplier, s_format("^8= ball hit * ^7%.3f ^8* (0.8 * ^7%d ^8+ 0.2 * ^7%.1f ^8* ^7%d^8)",
overlapChance, numProjectiles, fifthAttackMulti, numProjectiles + 5))
t_insert(breakdown.SkillDPSMultiplier, s_format("^8= ball hit * ^7 %.3f", dpsMult))
end
end
end
if dpsMult ~= 1 then
skillData.dpsMultiplier = (skillData.dpsMultiplier or 1) * dpsMult
output.SkillDPSMultiplier = (output.SkillDPSMultiplier or 1) * dpsMult
end
end,
statMap = {
["active_skill_hit_ailment_damage_with_projectile_+%_final"] = {
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPart = 2 })
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 2, 3 } })
},
},
baseFlags = {
Expand All @@ -6989,12 +7068,12 @@ skills["MoltenStrike"] = {
},
baseMods = {
skill("projectileSpeedAppliesToMSAreaOfEffect", true),
skill("radius", 9, { type = "SkillPart", skillPart = 2 }),
skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPart = 2 }),
skill("radiusSecondary", 2, { type = "SkillPart", skillPart = 2 }),
skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPart = 2 }),
skill("radiusTertiary", 25, { type = "SkillPart", skillPart = 2 }),
skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPart = 2 }),
skill("radius", 9, { type = "SkillPart", skillPartList = { 2, 3 }}),
skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusSecondary", 2, { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusTertiary", 25, { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } }),
flag("CannotSplit"),
},
qualityStats = {
Expand Down Expand Up @@ -7081,22 +7160,41 @@ skills["MoltenStrikeAltX"] = {
projectile = true,
area = true,
},
{
name = "Average Balls Hitting",
melee = false,
projectile = true,
area = true,
},
{
name = "Magma Balls (5th attack)",
melee = false,
projectile = true,
area = true,
},
{
name = "Average Balls (5th attack)",
melee = false,
projectile = true,
area = true,
},
{
name = "Total Weighted Ball Average",
melee = false,
projectile = true,
area = true,
},
},
preDamageFunc = skills.MoltenStrike.preDamageFunc,
statMap = {
["active_skill_hit_ailment_damage_with_projectile_+%_final"] = {
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 2, 3 } })
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
},
["molten_strike_every_5th_attack_projectiles_damage_+%_final"] = {
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPart = 3 })
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 4, 5 } })
},
["molten_strike_every_5th_attack_fire_X_additional_projectiles"] = {
mod("ProjectileCount", "BASE", nil, 0, 0, { type = "SkillPart", skillPart = 3 })
mod("ProjectileCount", "BASE", nil, 0, 0, { type = "SkillPart", skillPartList = { 4, 5 } })
},
},
baseFlags = {
Expand All @@ -7107,12 +7205,12 @@ skills["MoltenStrikeAltX"] = {
},
baseMods = {
skill("projectileSpeedAppliesToMSAreaOfEffect", true),
skill("radius", 9, { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusSecondary", 2, { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusTertiary", 25, { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } }),
skill("radius", 9, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } }),
skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } }),
skill("radiusSecondary", 2, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } }),
skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } }),
skill("radiusTertiary", 25, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } }),
skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } }),
flag("CannotSplit"),
},
qualityStats = {
Expand Down
130 changes: 114 additions & 16 deletions src/Export/Skills/act_str.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1246,19 +1246,98 @@ local skills, mod, flag, skill = ...
projectile = true,
area = true,
},
{
name = "Average Balls Hitting",
melee = false,
projectile = true,
area = true,
},
},
preDamageFunc = function(activeSkill, output, breakdown)
local skillCfg = activeSkill.skillCfg
local skillData = activeSkill.skillData
local skillPart = activeSkill.skillPart
local skillModList = activeSkill.skillModList
local t_insert = table.insert
local s_format = string.format

-- melee part doesn't need to calc balls
if skillPart == 1 then
return
end

local enemyRadius = skillModList:Override(skillCfg, "EnemyRadius") or skillModList:Sum("BASE", skillCfg, "EnemyRadius")
local ballRadius = output.AreaOfEffectRadius
local innerRadius = output.AreaOfEffectRadiusSecondary
local outerRadius = output.AreaOfEffectRadiusTertiary

-- logic adapted from MoldyDwarf's calculator
local hitRange = enemyRadius + ballRadius - innerRadius
local landingRange = outerRadius - innerRadius
local overlapChance = math.min(1, hitRange / landingRange)
output.OverlapChance = overlapChance * 100

if breakdown then
breakdown.OverlapChance = { }
t_insert(breakdown.OverlapChance, "Chance for individual balls to land on the enemy:")
t_insert(breakdown.OverlapChance, "^8= (area where a ball can land on enemy) / (total area)")
t_insert(breakdown.OverlapChance, "^8= (enemy radius + ball radius - min travel) / (max travel - min travel)")
t_insert(breakdown.OverlapChance, s_format("^8= (^7%d^8 + ^7%d^8 - ^7%d) / (^7%d^8 - ^7%d)",
enemyRadius, ballRadius, innerRadius, outerRadius, innerRadius))
t_insert(breakdown.OverlapChance, s_format("^8=^7 %.2f^8%%", output.OverlapChance))
end

local numProjectiles = skillModList:Sum("BASE", skillCfg, "ProjectileCount")
local dpsMult = 1
if skillPart == 3 or skillPart == 5 or skillPart == 6 then
dpsMult = overlapChance * numProjectiles

if skillPart ~= 6 then
if breakdown then
breakdown.SkillDPSMultiplier = {}
t_insert(breakdown.SkillDPSMultiplier, "DPS multiplier")
t_insert(breakdown.SkillDPSMultiplier, "^8= number of projectiles * overlap chance")
t_insert(breakdown.SkillDPSMultiplier, s_format("^8=^7 %d^8 *^7 %.3f^8", numProjectiles, overlapChance))
t_insert(breakdown.SkillDPSMultiplier, s_format("^8=^7 %.3f", dpsMult))
end
else
-- zenith: make an effective dpsMult for the weighted average of normal and 5th attack balls
local gemQuality = activeSkill.activeEffect.quality
local fifthAttackMulti = 1 + 8 + 0.1 * gemQuality
local fifthAttackOverallMulti = fifthAttackMulti * overlapChance * (numProjectiles + 5)
dpsMult = 0.8 * dpsMult + 0.2 * fifthAttackOverallMulti

if breakdown then
breakdown.SkillDPSMultiplier = {}
t_insert(breakdown.SkillDPSMultiplier, "Weighted average DPS multiplier for balls")
t_insert(breakdown.SkillDPSMultiplier, "^8= (0.8 * balls dps) + (0.2 * 5th attack balls dps)")
t_insert(breakdown.SkillDPSMultiplier, "^8= (0.8 * normal ball hit * overlap chance * number of projectiles) " ..
"+ (0.2 * ball hit * 5th attack multiplier * overlap chance * (number of projectiles + 5))")
t_insert(breakdown.SkillDPSMultiplier, "^8= ball hit * overlap chance * (0.8 * number of projectiles " ..
"+ 0.2 * 5th attack multiplier * (number of projectiles + 5))")
t_insert(breakdown.SkillDPSMultiplier, s_format("^8= ball hit * ^7%.3f ^8* (0.8 * ^7%d ^8+ 0.2 * ^7%.1f ^8* ^7%d^8)",
overlapChance, numProjectiles, fifthAttackMulti, numProjectiles + 5))
t_insert(breakdown.SkillDPSMultiplier, s_format("^8= ball hit * ^7 %.3f", dpsMult))
end
end
end
if dpsMult ~= 1 then
skillData.dpsMultiplier = (skillData.dpsMultiplier or 1) * dpsMult
output.SkillDPSMultiplier = (output.SkillDPSMultiplier or 1) * dpsMult
end
end,
statMap = {
["active_skill_hit_ailment_damage_with_projectile_+%_final"] = {
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPart = 2 })
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 2, 3 } })
},
},
#baseMod skill("projectileSpeedAppliesToMSAreaOfEffect", true)
#baseMod skill("radius", 9, { type = "SkillPart", skillPart = 2 })
#baseMod skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPart = 2 })
#baseMod skill("radiusSecondary", 2, { type = "SkillPart", skillPart = 2 })
#baseMod skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPart = 2 })
#baseMod skill("radiusTertiary", 25, { type = "SkillPart", skillPart = 2 })
#baseMod skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPart = 2 })
#baseMod skill("radius", 9, { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusSecondary", 2, { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusTertiary", 25, { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod flag("CannotSplit")
#mods

Expand All @@ -1277,31 +1356,50 @@ local skills, mod, flag, skill = ...
projectile = true,
area = true,
},
{
name = "Average Balls Hitting",
melee = false,
projectile = true,
area = true,
},
{
name = "Magma Balls (5th attack)",
melee = false,
projectile = true,
area = true,
},
{
name = "Average Balls (5th attack)",
melee = false,
projectile = true,
area = true,
},
{
name = "Total Weighted Ball Average",
melee = false,
projectile = true,
area = true,
},
},
preDamageFunc = skills.MoltenStrike.preDamageFunc,
statMap = {
["active_skill_hit_ailment_damage_with_projectile_+%_final"] = {
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 2, 3 } })
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
},
["molten_strike_every_5th_attack_projectiles_damage_+%_final"] = {
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPart = 3 })
mod("Damage", "MORE", nil, bit.band(ModFlag.Hit, ModFlag.Ailment), 0, { type = "SkillPart", skillPartList = { 4, 5 } })
},
["molten_strike_every_5th_attack_fire_X_additional_projectiles"] = {
mod("ProjectileCount", "BASE", nil, 0, 0, { type = "SkillPart", skillPart = 3 })
mod("ProjectileCount", "BASE", nil, 0, 0, { type = "SkillPart", skillPartList = { 4, 5 } })
},
},
#baseMod skill("projectileSpeedAppliesToMSAreaOfEffect", true)
#baseMod skill("radius", 9, { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusSecondary", 2, { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusTertiary", 25, { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPartList = { 2, 3 } })
#baseMod skill("radius", 9, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
#baseMod skill("radiusLabel", "Ball area:", { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
#baseMod skill("radiusSecondary", 2, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
#baseMod skill("radiusSecondaryLabel", "Chain Minimum Distance:", { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
#baseMod skill("radiusTertiary", 25, { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
#baseMod skill("radiusTertiaryLabel", "Chain Maximum Distance:", { type = "SkillPart", skillPartList = { 2, 3, 4, 5, 6 } })
#baseMod flag("CannotSplit")
#mods

Expand Down
6 changes: 3 additions & 3 deletions src/Modules/ConfigOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,8 @@ return {
{ var = "VaalMoltenShellDamageMitigated", type = "count", label = "Damage mitigated:", tooltip = "Vaal Molten Shell reflects damage to the enemy,\nbased on the amount of damage it has mitigated in the last second.", ifSkill = "Vaal Molten Shell", apply = function(val, modList, enemyModList)
modList:NewMod("SkillData", "LIST", { key = "VaalMoltenShellDamageMitigated", value = val }, "Config", { type = "SkillName", skillName = "Molten Shell" })
end },
{ label = "Multi-part area skills:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap" }, includeTransfigured = true },
{ var = "enemySizePreset", type = "list", label = "Enemy size preset:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap" }, includeTransfigured = true, defaultIndex = 2, tooltip = [[
{ label = "Multi-part area skills:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap", "Molten Strike" }, includeTransfigured = true },
{ var = "enemySizePreset", type = "list", label = "Enemy size preset:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap", "Molten Strike" }, includeTransfigured = true, defaultIndex = 2, tooltip = [[
Configure the radius of an enemy hitbox which is used in calculating some area multi-hitting (shotgunning) effects.
Small sets the radius to 2.
Expand All @@ -693,7 +693,7 @@ Huge sets the radius to 11.
modList:NewMod("EnemyRadius", "BASE", 11, "Config")
end
end },
{ var = "enemyRadius", type = "integer", label = "Enemy radius:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap" }, includeTransfigured = true, tooltip = "Configure the radius of an enemy hitbox to calculate some area overlapping (shotgunning) effects.", apply = function(val, modList, enemyModList)
{ var = "enemyRadius", type = "integer", label = "Enemy radius:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap", "Molten Strike" }, includeTransfigured = true, tooltip = "Configure the radius of an enemy hitbox to calculate some area overlapping (shotgunning) effects.", apply = function(val, modList, enemyModList)
modList:NewMod("EnemyRadius", "OVERRIDE", m_max(val, 1), "Config")
end },
{ var = "TotalSpectreLife", type = "integer", label = "Total Spectre Life:", ifMod = "takenFromSpectresBeforeYou", ifSkill = "Raise Spectre", includeTransfigured = true, tooltip = "The total life of your Spectres that can be taken before yours (used by jinxed juju)", apply = function(val, modList, enemyModList)
Expand Down

0 comments on commit 969810d

Please sign in to comment.