Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Molten Strike (+Zenith) ball average overlap calculations #8427

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 doesnt 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 doesnt 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