From d8b5dbade61e080a4b9a73fb43da2ff7455284e0 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 21 Jan 2023 14:53:55 +0200 Subject: [PATCH] Fix RP + new Tacticooler calculation --- app/features/build-analyzer/analyzer-hooks.ts | 3 ++ app/features/build-analyzer/analyzer-types.ts | 6 +-- .../core/specialEffects.test.ts | 46 ++++++------------- .../build-analyzer/core/specialEffects.ts | 8 +--- .../build-analyzer/core/stats.test.ts | 33 +++++++++++++ app/features/build-analyzer/core/stats.ts | 36 ++++++++++----- .../build-analyzer/core/utils.test.ts | 8 ++-- app/features/build-analyzer/core/utils.ts | 8 ++-- .../build-analyzer/routes/analyzer.tsx | 13 +++--- .../calculator-hooks.ts | 5 +- .../core/objectDamage.test.ts | 5 +- 11 files changed, 100 insertions(+), 71 deletions(-) diff --git a/app/features/build-analyzer/analyzer-hooks.ts b/app/features/build-analyzer/analyzer-hooks.ts index 353519434..db4aea0c7 100644 --- a/app/features/build-analyzer/analyzer-hooks.ts +++ b/app/features/build-analyzer/analyzer-hooks.ts @@ -68,12 +68,14 @@ export function useAnalyzeBuild() { effects, ldeIntensity, }); + const hasTacticooler = effects.includes("TACTICOOLER"); const analyzed = buildStats({ abilityPoints, weaponSplId: mainWeaponId, mainOnlyAbilities: build .map((row) => row[0]) .filter(filterMainOnlyAbilities), + hasTacticooler, }); const buildAbilityPoints2 = buildToAbilityPoints(build2); @@ -88,6 +90,7 @@ export function useAnalyzeBuild() { mainOnlyAbilities: build2 .map((row) => row[0]) .filter(filterMainOnlyAbilities), + hasTacticooler, }); return { diff --git a/app/features/build-analyzer/analyzer-types.ts b/app/features/build-analyzer/analyzer-types.ts index cbfeaf6ff..528e1531e 100644 --- a/app/features/build-analyzer/analyzer-types.ts +++ b/app/features/build-analyzer/analyzer-types.ts @@ -134,10 +134,7 @@ export interface Stat { modifiedBy: Ability | Array; } -export type AbilityPoints = Map< - AbilityWithUnknown, - { ap: number; apBeforeTacticooler: number } ->; +export type AbilityPoints = Map; export type AbilityChunks = Map; @@ -148,6 +145,7 @@ export interface StatFunctionInput { specialWeaponParams: SpecialWeaponParams; abilityPoints: AbilityPoints; mainOnlyAbilities: Array; + hasTacticooler: boolean; } export type InkConsumeType = typeof INK_CONSUME_TYPES[number]; diff --git a/app/features/build-analyzer/core/specialEffects.test.ts b/app/features/build-analyzer/core/specialEffects.test.ts index e03737e63..5bdb82732 100644 --- a/app/features/build-analyzer/core/specialEffects.test.ts +++ b/app/features/build-analyzer/core/specialEffects.test.ts @@ -4,11 +4,6 @@ import { applySpecialEffects } from "./specialEffects"; const ApplySpecialEffects = suite("applySpecialEffects()"); -const valueToAps = (value: number) => ({ - ap: value, - apBeforeTacticooler: value, -}); - ApplySpecialEffects("Adds an effect to empty build", () => { const aps = applySpecialEffects({ effects: ["CB"], @@ -17,7 +12,7 @@ ApplySpecialEffects("Adds an effect to empty build", () => { }); assert.equal(aps.size, 6); - assert.equal(aps.get("ISM")?.ap, 10); + assert.equal(aps.get("ISM"), 10); }); ApplySpecialEffects( @@ -25,33 +20,33 @@ ApplySpecialEffects( () => { const aps = applySpecialEffects({ effects: ["CB"], - abilityPoints: new Map([["SPU", valueToAps(10)]]), + abilityPoints: new Map([["SPU", 10]]), ldeIntensity: 0, }); assert.equal(aps.size, 7); - assert.equal(aps.get("SPU")?.ap, 10); + assert.equal(aps.get("SPU"), 10); } ); ApplySpecialEffects("Does not boost ability beyond 57", () => { const aps = applySpecialEffects({ effects: ["CB"], - abilityPoints: new Map([["ISM", valueToAps(57)]]), + abilityPoints: new Map([["ISM", 57]]), ldeIntensity: 0, }); - assert.equal(aps.get("ISM")?.ap, 57); + assert.equal(aps.get("ISM"), 57); }); ApplySpecialEffects("Tacticooler doesn't boost swim speed beyond 29", () => { const aps = applySpecialEffects({ effects: ["TACTICOOLER"], - abilityPoints: new Map([["SSU", valueToAps(28)]]), + abilityPoints: new Map([["SSU", 28]]), ldeIntensity: 0, }); - assert.equal(aps.get("SSU")?.ap, 29); + assert.equal(aps.get("SSU"), 29); }); ApplySpecialEffects( @@ -59,53 +54,42 @@ ApplySpecialEffects( () => { const aps = applySpecialEffects({ effects: ["TACTICOOLER"], - abilityPoints: new Map([["SSU", valueToAps(30)]]), + abilityPoints: new Map([["SSU", 30]]), ldeIntensity: 0, }); - assert.equal(aps.get("SSU")?.ap, 30); + assert.equal(aps.get("SSU"), 30); } ); -ApplySpecialEffects("Tacticooler remembers AP before it was applied", () => { - const aps = applySpecialEffects({ - effects: ["TACTICOOLER"], - abilityPoints: new Map([["QR", valueToAps(10)]]), - ldeIntensity: 0, - }); - - assert.equal(aps.get("QR")?.ap, 57); - assert.equal(aps.get("QR")?.apBeforeTacticooler, 10); -}); - ApplySpecialEffects("Applies many effects", () => { const aps = applySpecialEffects({ effects: ["DR", "CB"], - abilityPoints: new Map([["SSU", valueToAps(1)]]), + abilityPoints: new Map([["SSU", 1]]), ldeIntensity: 0, }); - assert.equal(aps.get("SSU")?.ap, 21); + assert.equal(aps.get("SSU"), 21); }); ApplySpecialEffects("Applies LDE", () => { const aps = applySpecialEffects({ effects: ["LDE"], - abilityPoints: new Map([["ISM", valueToAps(1)]]), + abilityPoints: new Map([["ISM", 1]]), ldeIntensity: 1, }); - assert.equal(aps.get("ISM")?.ap, 2); + assert.equal(aps.get("ISM"), 2); }); ApplySpecialEffects("Applies LDE (intensity != aps given)", () => { const aps = applySpecialEffects({ effects: ["LDE"], - abilityPoints: new Map([["ISM", valueToAps(1)]]), + abilityPoints: new Map([["ISM", 1]]), ldeIntensity: 15, }); - assert.equal(aps.get("ISM")?.ap, 18); + assert.equal(aps.get("ISM"), 18); }); ApplySpecialEffects.run(); diff --git a/app/features/build-analyzer/core/specialEffects.ts b/app/features/build-analyzer/core/specialEffects.ts index 210c67195..71aa2d0e2 100644 --- a/app/features/build-analyzer/core/specialEffects.ts +++ b/app/features/build-analyzer/core/specialEffects.ts @@ -147,17 +147,13 @@ export function applySpecialEffects({ for (const value of valuesArr) { const boostsBeyond = "boostsBeyond" in value ? value.boostsBeyond : true; - const currentAP = result.get(value.type)?.ap ?? 0; + const currentAP = result.get(value.type) ?? 0; const newAPUnlimited = boostsBeyond ? currentAP + value.ap : Math.max(currentAP, value.ap); const newAP = Math.min(newAPUnlimited, MAX_AP); - result.set(value.type, { - ap: newAP, - apBeforeTacticooler: - effectObj.type === "TACTICOOLER" ? currentAP : newAP, - }); + result.set(value.type, newAP); } } diff --git a/app/features/build-analyzer/core/stats.test.ts b/app/features/build-analyzer/core/stats.test.ts index 875fdbebf..a142f6ca6 100644 --- a/app/features/build-analyzer/core/stats.test.ts +++ b/app/features/build-analyzer/core/stats.test.ts @@ -17,6 +17,7 @@ AnalyzeBuild("Every main weapon has damage", () => { for (const weaponSplId of mainWeaponIds) { const analyzed = buildStats({ weaponSplId, + hasTacticooler: false, }); const hasDamage = @@ -38,11 +39,13 @@ AnalyzeBuild("Every main weapon has damage", () => { AnalyzeBuild("Ninja Squid decreases swim speed", () => { const analyzed = buildStats({ weaponSplId: 0, + hasTacticooler: false, }); const analyzedWithNS = buildStats({ weaponSplId: 0, mainOnlyAbilities: ["NS"], + hasTacticooler: false, }); assert.ok( @@ -50,4 +53,34 @@ AnalyzeBuild("Ninja Squid decreases swim speed", () => { ); }); +AnalyzeBuild("Tacticooler / RP calculated correctly", () => { + const fullQR = buildStats({ + weaponSplId: 0, + abilityPoints: new Map([["QR", 57]]), + hasTacticooler: false, + }); + + const tacticooler = buildStats({ + weaponSplId: 0, + abilityPoints: new Map([["QR", 57]]), + hasTacticooler: true, + }); + + assert.ok( + fullQR.stats.quickRespawnTime.value === + tacticooler.stats.quickRespawnTime.value, + "Base QR should be same whether 57AP of QR or Tacticooler" + ); + assert.ok( + fullQR.stats.quickRespawnTimeSplattedByRP.value > + tacticooler.stats.quickRespawnTimeSplattedByRP.value, + "Tacticooler splatted by RP should respawn faster than 57AP of QR" + ); + assert.ok( + tacticooler.stats.quickRespawnTime.value < + tacticooler.stats.quickRespawnTimeSplattedByRP.value, + "Tacticooler should respawn faster than Tacticooler splatted by RP" + ); +}); + AnalyzeBuild.run(); diff --git a/app/features/build-analyzer/core/stats.ts b/app/features/build-analyzer/core/stats.ts index b1a86a2c8..edf902f99 100644 --- a/app/features/build-analyzer/core/stats.ts +++ b/app/features/build-analyzer/core/stats.ts @@ -32,10 +32,12 @@ export function buildStats({ weaponSplId, abilityPoints = new Map(), mainOnlyAbilities = [], + hasTacticooler, }: { weaponSplId: MainWeaponId; abilityPoints?: AbilityPoints; mainOnlyAbilities?: Array; + hasTacticooler: boolean; }): AnalyzedBuild { const mainWeaponParams = weaponParams().mainWeapons[weaponSplId]; invariant(mainWeaponParams, `Weapon with splId ${weaponSplId} not found`); @@ -61,6 +63,7 @@ export function buildStats({ specialWeaponParams, abilityPoints, mainOnlyAbilities, + hasTacticooler, }; return { @@ -196,7 +199,6 @@ function specialLost( abilityPoints: apFromMap({ abilityPoints: abilityPoints, ability: SPECIAL_SAVED_AFTER_DEATH_ABILITY, - key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap", }), key: "SpecialGaugeRt_Restart", weapon: mainWeaponParams, @@ -593,6 +595,14 @@ function swimSpeedHoldingRainmaker( }; } +const qrApAfterRespawnPunish = ({ + ap, + hasTacticooler, +}: { + ap: number; + hasTacticooler: boolean; +}) => (hasTacticooler ? ap : Math.ceil(ap * 0.15)); + const RESPAWN_CHASE_FRAME = 150; const OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 68; const ENEMY_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 45; @@ -604,21 +614,25 @@ function respawnTime( const QUICK_RESPAWN_TIME_ABILITY = "QR"; const hasRespawnPunisher = args.mainOnlyAbilities.includes("RP"); + const ap = apFromMap({ + abilityPoints: args.abilityPoints, + ability: QUICK_RESPAWN_TIME_ABILITY, + }); + const abilityPoints = + splattedByRP || hasRespawnPunisher + ? qrApAfterRespawnPunish({ + ap, + hasTacticooler: args.hasTacticooler, + }) + : ap; + const chase = abilityPointsToEffects({ - abilityPoints: apFromMap({ - abilityPoints: args.abilityPoints, - ability: QUICK_RESPAWN_TIME_ABILITY, - key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap", - }), + abilityPoints, key: "Dying_ChaseFrm", weapon: args.mainWeaponParams, }); const around = abilityPointsToEffects({ - abilityPoints: apFromMap({ - abilityPoints: args.abilityPoints, - ability: QUICK_RESPAWN_TIME_ABILITY, - key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap", - }), + abilityPoints, key: "Dying_AroundFrm", weapon: args.mainWeaponParams, }); diff --git a/app/features/build-analyzer/core/utils.test.ts b/app/features/build-analyzer/core/utils.test.ts index b860acc43..7a072578f 100644 --- a/app/features/build-analyzer/core/utils.test.ts +++ b/app/features/build-analyzer/core/utils.test.ts @@ -19,9 +19,9 @@ BuildToAbilityPoints("Calculates ability points", () => { EMPTY_ROW, ]); - assert.equal(aps.get("SS")?.ap, 13); - assert.equal(aps.get("RSU")?.ap, 6); - assert.equal(aps.get("UNKNOWN")?.ap, 38); + assert.equal(aps.get("SS"), 13); + assert.equal(aps.get("RSU"), 6); + assert.equal(aps.get("UNKNOWN"), 38); }); BuildToAbilityPoints("Handles ability doubler", () => { @@ -31,7 +31,7 @@ BuildToAbilityPoints("Handles ability doubler", () => { EMPTY_ROW, ]); - assert.equal(aps.get("SS")?.ap, 6); + assert.equal(aps.get("SS"), 6); }); BuildToAbilityPoints("Does not calculate AP for main only abilities", () => { diff --git a/app/features/build-analyzer/core/utils.ts b/app/features/build-analyzer/core/utils.ts index fe85b50aa..fbddfef6d 100644 --- a/app/features/build-analyzer/core/utils.ts +++ b/app/features/build-analyzer/core/utils.ts @@ -37,9 +37,9 @@ export function buildToAbilityPoints(build: BuildAbilitiesTupleWithUnknown) { const aps = i === 0 ? 10 : 3; const apsDoubled = aps * (abilityDoublerActive ? 2 : 1); - const newAp = (result.get(ability)?.ap ?? 0) + apsDoubled; + const newAp = (result.get(ability) ?? 0) + apsDoubled; - result.set(ability, { ap: newAp, apBeforeTacticooler: newAp }); + result.set(ability, newAp); } } @@ -57,13 +57,11 @@ function isStackableAbility(ability: AbilityWithUnknown): ability is Ability { export function apFromMap({ abilityPoints, ability, - key = "ap", }: { abilityPoints: AbilityPoints; ability: Ability; - key?: "ap" | "apBeforeTacticooler"; }) { - return abilityPoints.get(ability)?.[key] ?? 0; + return abilityPoints.get(ability) ?? 0; } function abilityValues({ diff --git a/app/features/build-analyzer/routes/analyzer.tsx b/app/features/build-analyzer/routes/analyzer.tsx index a63317fb6..53641c324 100644 --- a/app/features/build-analyzer/routes/analyzer.tsx +++ b/app/features/build-analyzer/routes/analyzer.tsx @@ -697,7 +697,7 @@ export default function BuildAnalyzerPage() { > {/** Hack the :has ;) */} {(["ISM", "ISS"] as const).some( - (ability) => (abilityPoints.get(ability)?.ap ?? 0) > 0 + (ability) => (abilityPoints.get(ability) ?? 0) > 0 ) ? (
) : null} @@ -855,6 +855,7 @@ export default function BuildAnalyzerPage() { ); } +// xxx: no main abilities? function APCompare({ abilityPoints, abilityPoints2, @@ -867,13 +868,13 @@ function APCompare({ return (
{([...abilitiesShort, "UNKNOWN"] as const).map((ability) => { - const ap = abilityPoints.get(ability)?.ap ?? 0; - const ap2 = abilityPoints2.get(ability)?.ap ?? 0; + const ap = abilityPoints.get(ability) ?? 0; + const ap2 = abilityPoints2.get(ability) ?? 0; if (!ap && !ap2) return null; return ( - <> +
- + ); })}
@@ -1076,7 +1077,7 @@ function StatCard({ // you have Ninja Squid and stack swim speed // -> we still want to show the build value return [stat[0].modifiedBy].flat().some((ability) => { - const hasStackable = (abilityPoints.get(ability)?.ap ?? 0) > 0; + const hasStackable = (abilityPoints.get(ability) ?? 0) > 0; const hasEffect = baseValue !== stat[0].value && baseValue !== stat[1].value; diff --git a/app/features/object-damage-calculator/calculator-hooks.ts b/app/features/object-damage-calculator/calculator-hooks.ts index 32361aa89..941904e0b 100644 --- a/app/features/object-damage-calculator/calculator-hooks.ts +++ b/app/features/object-damage-calculator/calculator-hooks.ts @@ -23,6 +23,7 @@ export function useObjectDamage() { const isMultiShot = validatedMultiShotFromSearchParams(searchParams); const analyzed = buildStats({ weaponSplId: mainWeaponId, + hasTacticooler: false, }); const damageType = validatedDamageTypeFromSearchParams({ @@ -62,8 +63,8 @@ export function useObjectDamage() { damagesToReceivers: damageType ? calculateDamage({ abilityPoints: new Map([ - ["BRU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], - ["SPU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], + ["BRU", abilityPoints], + ["SPU", abilityPoints], ]), analyzed, mainWeaponId, diff --git a/app/features/object-damage-calculator/core/objectDamage.test.ts b/app/features/object-damage-calculator/core/objectDamage.test.ts index 2f15b2d3e..c7ce5ccd0 100644 --- a/app/features/object-damage-calculator/core/objectDamage.test.ts +++ b/app/features/object-damage-calculator/core/objectDamage.test.ts @@ -19,6 +19,7 @@ function calculate({ }) { const analyzed = buildStats({ weaponSplId: mainWeaponId, + hasTacticooler: false, }); return calculateDamage({ @@ -35,7 +36,7 @@ const CalculateDamage = suite("calculateDamage()"); CalculateDamage("BRU increases Splash Wall hitpoints", () => { const withoutBRU = calculate({}); const withBRU = calculate({ - abilityPoints: new Map([["BRU", { ap: 10, apBeforeTacticooler: 10 }]]), + abilityPoints: new Map([["BRU", 10]]), }); const hpWithoutBRU = withoutBRU.find( @@ -51,7 +52,7 @@ CalculateDamage("BRU increases Splash Wall hitpoints", () => { CalculateDamage("SPU increases Big Bubbler hitpoints", () => { const withoutSPU = calculate({}); const withSPU = calculate({ - abilityPoints: new Map([["SPU", { ap: 10, apBeforeTacticooler: 10 }]]), + abilityPoints: new Map([["SPU", 10]]), }); const hpWithoutSPU = withoutSPU.find(