Fixed secondary and tertiary radii calcs for Molten Strike
This commit updates the secondary (inner deadzone) and tertiary (max
ball landing distance) radii for the Molten Strike skill. It also
overhauls most of the breakdown display for the tertiary radius. The
new calculations are based on the derivation from the standalone
Molten Strike DPS Calculator (same author as this commit). Look for
the maxBallLandingDist variable here for details:
https://gitlab.com/MoldyDwarf/MoltenStrikeDPS/-/blob/master/moltenstrike.html
Notable math changes:
- Projectile speed modifies the distance traveled, not the landing
area (making it basically a multiplier on radius, not on
area). It's easy to verify this with swapping faster vs. slower
projectiles in Haku's hideout and seeing how many tiles away the
balls land under the two conditions.
- The inner deadzone radius ("secondary radius" in PoB) cannot be
modified in any way. This makes all of the tertiary radius
calculations slightly trickier. See the aforementioned external
calculator link for more details.
Notable functions added or changed:
- calcMoltenStrikeTertiaryRadius (new): actual calculation, including
rounding (but using a simple assumption about how rounding is done
for the skill)
- setMoltenStrikeTertiaryRadiusBreakdown (new): does all the
calculating and rendering of the breakdown display for the tertiary
radius. We do both in the same place because we compute and display
so many intermediate values.
- calcs.offence (modified): now calls the two functions above when
skillData.projectileSpeedAppliesToMSAreaOfEffect exists, instead of
inlining some math that wasn't quite correct. Also, disables the
breakpoint calculations for the secondary radius for Molten Strike
since no mods affect that radius.
Also:
- Moved the label to the top of breakdown.area display for better
readability and to be more consistent with other breakdowns.
- Allow devs to put vscode workspace config files inside the repo
directory.
Future work:
- The external calculator will be simplified after these changes are
released to the community. Many of its form elements will be
replaced by having users just type in the skill-specific stats from
PoB.
- If we're ever able to reliably datamine boss radii, then the DPS
calculations from the external calculator could be integrated into
PoB. If that ever happens, the external calculator will be retired.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# Development files
|
||||
*.lnk
|
||||
|
||||
@@ -95,6 +95,7 @@ end
|
||||
|
||||
function breakdown.area(base, areaMod, total, incBreakpoint, moreBreakpoint, redBreakpoint, lessBreakpoint, label)
|
||||
local out = {}
|
||||
t_insert(out, label)
|
||||
if base ~= total then
|
||||
t_insert(out, s_format("%d ^8(base radius)", base))
|
||||
t_insert(out, s_format("x %.2f ^8(square root of area of effect modifier)", m_floor(100 * m_sqrt(areaMod)) / 100))
|
||||
@@ -104,7 +105,6 @@ function breakdown.area(base, areaMod, total, incBreakpoint, moreBreakpoint, red
|
||||
t_insert(out, s_format("^8Next breakpoint: %d%% increased AoE / a %d%% more AoE multiplier", incBreakpoint, moreBreakpoint))
|
||||
t_insert(out, s_format("^8Previous breakpoint: %d%% reduced AoE / a %d%% less AoE multiplier", redBreakpoint, lessBreakpoint))
|
||||
end
|
||||
t_insert(out, label)
|
||||
out.radius = total
|
||||
return out
|
||||
end
|
||||
|
||||
@@ -134,6 +134,18 @@ local function calcRadius(baseRadius, areaMod)
|
||||
return m_floor(baseRadius * m_floor(100 * m_sqrt(areaMod)) / 100)
|
||||
end
|
||||
|
||||
---Calculates the tertiary radius for Molten Strike, correctly handling the deadzone.
|
||||
---@param baseRadius number
|
||||
---@param deadzoneRadius number
|
||||
---@param areaMod number
|
||||
---@param speedMod number
|
||||
local function calcMoltenStrikeTertiaryRadius(baseRadius, deadzoneRadius, areaMod, speedMod)
|
||||
-- For now, we assume that PoE only rounds at the end.
|
||||
local maxDistIgnoringSpeed = m_sqrt(baseRadius * baseRadius * areaMod - deadzoneRadius * deadzoneRadius * (areaMod - 1))
|
||||
local maxDist = m_floor((maxDistIgnoringSpeed - deadzoneRadius) * speedMod + deadzoneRadius)
|
||||
return maxDist
|
||||
end
|
||||
|
||||
---Calculates modifiers needed to reach the next and previous radius breakpoints
|
||||
---@param baseRadius number
|
||||
---@param incArea number @Additive modifier
|
||||
@@ -167,6 +179,76 @@ local function calcRadiusBreakpoints(baseRadius, incArea, moreArea)
|
||||
return incAreaBreakpoint, moreAreaBreakpoint, redAreaBreakpoint, lessAreaBreakpoint
|
||||
end
|
||||
|
||||
---Computes and sets the breakdown for Molten Strike's tertiary radius.
|
||||
---@param breakdown table
|
||||
---@param deadzoneRadius number min ball landing distance (cannot be changed by any mods)
|
||||
---@param baseRadius number default max landing distance with no aoe or proj. speed modifiers
|
||||
---@param label string top level label to use for the breakdown
|
||||
---@param incArea number current net increased area modifier
|
||||
---@param moreArea number current product of all "more" and "less" area modifiers
|
||||
---@param incSpd number current net increased projectile speed modifier
|
||||
---@param moreSpd number current product of all "more" and "less" projectile speed modifiers
|
||||
local function setMoltenStrikeTertiaryRadiusBreakdown(breakdown, deadzoneRadius, baseRadius, label, incArea, moreArea, incSpd, moreSpd)
|
||||
-- nil -> 1 (no multiplier)
|
||||
incArea = incArea or 1
|
||||
moreArea = moreArea or 1
|
||||
incSpd = incSpd or 1
|
||||
moreSpd = moreSpd or 1
|
||||
---Helper that calculates the tertiary radius with incremental modifiers to the 4 relevant pools.
|
||||
---This helps declutter the code below.
|
||||
local function calc(extraIncAoePct, extraMoreAoePct, extraIncSpdPct, extraMoreSpdPct)
|
||||
local areaMod = round(round((incArea + extraIncAoePct / 100) * moreArea * (1 + extraMoreAoePct / 100), 10), 2)
|
||||
local speedMod = round(round((incSpd + extraIncSpdPct / 100) * moreSpd * (1 + extraMoreSpdPct / 100), 10), 2)
|
||||
local dist = calcMoltenStrikeTertiaryRadius(baseRadius, deadzoneRadius, areaMod, speedMod)
|
||||
return dist, areaMod, speedMod
|
||||
end
|
||||
-- Current settings.
|
||||
local currentDist, currentAreaMod, currentSpeedMod = calc(0, 0, 0, 0)
|
||||
-- Create the detailed breakdown. This includes:
|
||||
-- * the complete formula as an algebraic expression (ignoring rounding),
|
||||
-- * the final value,
|
||||
-- * breakpoints on the 4 modifier pools (increased vs. more crossed with aoe and projectile speed), and
|
||||
-- * the input variables for the algebraic expression.
|
||||
local breakdownRadius = breakdown.AreaOfEffectRadiusTertiary or { }
|
||||
breakdown.AreaOfEffectRadiusTertiary = breakdownRadius
|
||||
t_insert(breakdownRadius, label)
|
||||
t_insert(breakdownRadius, " = (sqrt(R*R*a - r*r*(a-1)) - r) * s + r")
|
||||
t_insert(breakdownRadius, s_format(" = %d", currentDist))
|
||||
if currentDist > 0 then
|
||||
---Helper for finding one tertiary radius breakpoint value. This is a little slower than what
|
||||
---we do in the generic calcRadiusBreakpoints, but this approach requires a lot less code and
|
||||
---should be more maintainable given that we need to search for 8 different breakpoints.
|
||||
---@param sign number +1 (for increased and more breakpoints) or -1 (for reduced and less breakpoints)
|
||||
---@param argIdx number which argument to the calc function we're modifying
|
||||
local function findBreakpoint(sign, argIdx)
|
||||
local args = {0, 0, 0, 0} -- starter args for the calc function
|
||||
repeat
|
||||
args[argIdx] = args[argIdx] + sign -- increment or decrement the desired arg
|
||||
local newDist, _, _ = calc(unpack(args))
|
||||
until (newDist ~= currentDist) or (newDist == 0) -- stop once we've hit a new radius breakpoint
|
||||
return args[argIdx] * sign -- remove the sign since we want all positive numbers
|
||||
end
|
||||
t_insert(breakdownRadius, s_format("^8Next AoE breakpoint: %d%% increased or %d%% more", findBreakpoint(1, 1), findBreakpoint(1, 2)))
|
||||
t_insert(breakdownRadius, s_format("^8Next Proj. Speed breakpoint: %d%% increased or %d%% more", findBreakpoint(1, 3), findBreakpoint(1, 4)))
|
||||
t_insert(breakdownRadius, s_format("^8Previous AoE breakpoint: %d%% increased or %d%% more", findBreakpoint(-1, 1), findBreakpoint(-1, 2)))
|
||||
t_insert(breakdownRadius, s_format("^8Previous Proj. Speed breakpoint: %d%% increased or %d%% more", findBreakpoint(-1, 3), findBreakpoint(-1, 4)))
|
||||
end
|
||||
-- This is the input variable table.
|
||||
breakdownRadius.label = "Inputs"
|
||||
breakdownRadius.rowList = { }
|
||||
breakdownRadius.colList = {
|
||||
{ label = "Variable", key = "name" },
|
||||
{ label = "Value", key = "value"},
|
||||
{ label = "Description", key = "description" }
|
||||
}
|
||||
t_insert(breakdownRadius.rowList, { name = "r", value = s_format("%d", deadzoneRadius), description = "fixed deadzone radius" })
|
||||
t_insert(breakdownRadius.rowList, { name = "R", value = s_format("%d", baseRadius), description = "base outer radius" })
|
||||
t_insert(breakdownRadius.rowList, { name = "a", value = s_format("%.2f", currentAreaMod), description = "net AoE multiplier (scales area)" })
|
||||
t_insert(breakdownRadius.rowList, { name = "s", value = s_format("%.2f", currentSpeedMod), description = "net projectile speed multiplier (scales range)" })
|
||||
-- Trigger the inclusion of the radius display.
|
||||
breakdownRadius.radius = currentDist
|
||||
end
|
||||
|
||||
function calcSkillCooldown(skillModList, skillCfg, skillData)
|
||||
local cooldownOverride = skillModList:Override(skillCfg, "CooldownRecovery")
|
||||
local cooldown = cooldownOverride or (skillData.cooldown + skillModList:Sum("BASE", skillCfg, "CooldownRecovery")) / calcLib.mod(skillModList, skillCfg, "CooldownRecovery")
|
||||
@@ -249,7 +331,10 @@ function calcs.offence(env, actor, activeSkill)
|
||||
baseRadius = skillData.radiusSecondary + (skillData.radiusExtra or 0)
|
||||
output.AreaOfEffectRadiusSecondary = calcRadius(baseRadius, output.AreaOfEffectModSecondary)
|
||||
if breakdown then
|
||||
local incAreaBreakpointSecondary, moreAreaBreakpointSecondary, redAreaBreakpointSecondary, lessAreaBreakpointSecondary = calcRadiusBreakpoints(baseRadius, incAreaSecondary, moreAreaSecondary)
|
||||
local incAreaBreakpointSecondary, moreAreaBreakpointSecondary, redAreaBreakpointSecondary, lessAreaBreakpointSecondary
|
||||
if not skillData.projectileSpeedAppliesToMSAreaOfEffect then
|
||||
local incAreaBreakpointSecondary, moreAreaBreakpointSecondary, redAreaBreakpointSecondary, lessAreaBreakpointSecondary = calcRadiusBreakpoints(baseRadius, incAreaSecondary, moreAreaSecondary)
|
||||
end
|
||||
breakdown.AreaOfEffectRadiusSecondary = breakdown.area(baseRadius, output.AreaOfEffectModSecondary, output.AreaOfEffectRadiusSecondary, incAreaBreakpointSecondary, moreAreaBreakpointSecondary, redAreaBreakpointSecondary, lessAreaBreakpointSecondary, skillData.radiusSecondaryLabel)
|
||||
end
|
||||
end
|
||||
@@ -257,10 +342,22 @@ function calcs.offence(env, actor, activeSkill)
|
||||
local incAreaTertiary, moreAreaTertiary = calcLib.mods(skillModList, skillCfg, "AreaOfEffect", "AreaOfEffectTertiary")
|
||||
output.AreaOfEffectModTertiary = round(round(incAreaTertiary * moreAreaTertiary, 10), 2)
|
||||
baseRadius = skillData.radiusTertiary + (skillData.radiusExtra or 0)
|
||||
output.AreaOfEffectRadiusTertiary = calcRadius(baseRadius, output.AreaOfEffectModTertiary)
|
||||
if breakdown then
|
||||
local incAreaBreakpointTertiary, moreAreaBreakpointTertiary, redAreaBreakpointTertiary, lessAreaBreakpointTertiary = calcRadiusBreakpoints(baseRadius, incAreaTertiary, moreAreaTertiary)
|
||||
breakdown.AreaOfEffectRadiusTertiary = breakdown.area(baseRadius, output.AreaOfEffectModTertiary, output.AreaOfEffectRadiusTertiary, incAreaBreakpointTertiary, moreAreaBreakpointTertiary, redAreaBreakpointTertiary, lessAreaBreakpointTertiary, skillData.radiusTertiaryLabel)
|
||||
if skillData.projectileSpeedAppliesToMSAreaOfEffect then
|
||||
local incSpeedTertiary, moreSpeedTertiary = calcLib.mods(skillModList, skillCfg, "ProjectileSpeed")
|
||||
output.SpeedModTertiary = round(round(incSpeedTertiary * moreSpeedTertiary, 10), 2)
|
||||
output.AreaOfEffectRadiusTertiary = calcMoltenStrikeTertiaryRadius(baseRadius, skillData.radiusSecondary, output.AreaOfEffectModTertiary, output.SpeedModTertiary)
|
||||
if breakdown then
|
||||
setMoltenStrikeTertiaryRadiusBreakdown(
|
||||
breakdown, skillData.radiusSecondary, baseRadius, skillData.radiusTertiaryLabel,
|
||||
incAreaTertiary, moreAreaTertiary, incSpeedTertiary, moreSpeedTertiary
|
||||
)
|
||||
end
|
||||
else
|
||||
output.AreaOfEffectRadiusTertiary = calcRadius(baseRadius, output.AreaOfEffectModTertiary)
|
||||
if breakdown then
|
||||
local incAreaBreakpointTertiary, moreAreaBreakpointTertiary, redAreaBreakpointTertiary, lessAreaBreakpointTertiary = calcRadiusBreakpoints(baseRadius, incAreaTertiary, moreAreaTertiary)
|
||||
breakdown.AreaOfEffectRadiusTertiary = breakdown.area(baseRadius, output.AreaOfEffectModTertiary, output.AreaOfEffectRadiusTertiary, incAreaBreakpointTertiary, moreAreaBreakpointTertiary, redAreaBreakpointTertiary, lessAreaBreakpointTertiary, skillData.radiusTertiaryLabel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -509,19 +606,6 @@ function calcs.offence(env, actor, activeSkill)
|
||||
skillModList:NewMod("AreaOfEffect", "INC", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod))
|
||||
end
|
||||
end
|
||||
if skillData.projectileSpeedAppliesToMSAreaOfEffect then
|
||||
-- Projectile Speed conversion for Molten Stikes Projectile Range
|
||||
for i, value in ipairs(skillModList:Tabulate("INC", { }, "ProjectileSpeed")) do
|
||||
local mod = value.mod
|
||||
skillModList:NewMod("AreaOfEffectSecondary", "INC", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod))
|
||||
skillModList:NewMod("AreaOfEffectTertiary", "INC", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod))
|
||||
end
|
||||
for i, value in ipairs(skillModList:Tabulate("MORE", { }, "ProjectileSpeed")) do
|
||||
local mod = value.mod
|
||||
skillModList:NewMod("AreaOfEffectSecondary", "MORE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod))
|
||||
skillModList:NewMod("AreaOfEffectTertiary", "MORE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod))
|
||||
end
|
||||
end
|
||||
if skillModList:Flag(nil, "SequentialProjectiles") and not skillModList:Flag(nil, "OneShotProj") and not skillModList:Flag(nil,"NoAdditionalProjectiles") and not skillModList:Flag(nil, "TriggeredBySnipe") then
|
||||
-- Applies DPS multiplier based on projectile count
|
||||
skillData.dpsMultiplier = skillModList:Sum("BASE", skillCfg, "ProjectileCount")
|
||||
|
||||
@@ -1002,7 +1002,7 @@ return {
|
||||
{ label = "Player modifiers", modName = "EnemyShockChance", cfg = "skill" },
|
||||
{ label = "Enemy modifiers", modName = "SelfShockChance", enemy = true },
|
||||
}, },
|
||||
{ label = "Shock Dur. Mod", flag = "shock", { format = "x {2:output:ShockDurationMod}",
|
||||
{ label = "Shock Dur. Mod", flag = "shock", { format = "x {2:output:ShockDurationMod}",
|
||||
{ breakdown = "ShockDurationMod" },
|
||||
{ breakdown = "MainHand.ShockDPS" },
|
||||
{ breakdown = "OffHand.ShockDPS" },
|
||||
@@ -1010,10 +1010,10 @@ return {
|
||||
{ label = "Player modifiers", modName = { "EnemyShockDuration", "ShockAsThoughDealing" }, cfg = "skill" },
|
||||
{ label = "Enemy modifiers", modName = "SelfShockDuration", enemy = true },
|
||||
}, },
|
||||
{ label = "Maximum Shock", flag = "shock", { format = "{0:output:MaximumShock}%",
|
||||
{ label = "Maximum Shock", flag = "shock", { format = "{0:output:MaximumShock}%",
|
||||
{ modName = "ShockMax" },
|
||||
}, },
|
||||
{ label = "Current Shock", haveOutput = "CurrentShock", { format = "{0:output:CurrentShock}%",
|
||||
{ label = "Current Shock", haveOutput = "CurrentShock", { format = "{0:output:CurrentShock}%",
|
||||
{ label = "Configured Shock", modName = "ShockVal", enemy = true, modType = "BASE" },
|
||||
{ label = "Guaranteed Shocks", modName = "ShockOverride", modType = "BASE" },
|
||||
}, },
|
||||
|
||||
Reference in New Issue
Block a user