From d10d4bf0c513629a222a0abbdea35b9fd809e665 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 18 Sep 2022 12:50:00 +0300 Subject: [PATCH] Calculate own use of RP --- app/modules/analyzer/specialEffects.test.ts | 46 ++++++++++++++------- app/modules/analyzer/specialEffects.ts | 11 +++-- app/modules/analyzer/stats.ts | 22 +++++++++- app/modules/analyzer/types.ts | 5 ++- app/modules/analyzer/utils.test.ts | 6 +-- app/modules/analyzer/utils.ts | 7 +++- app/routes/analyzer.tsx | 8 ++-- 7 files changed, 76 insertions(+), 29 deletions(-) diff --git a/app/modules/analyzer/specialEffects.test.ts b/app/modules/analyzer/specialEffects.test.ts index 5bdb82732..e03737e63 100644 --- a/app/modules/analyzer/specialEffects.test.ts +++ b/app/modules/analyzer/specialEffects.test.ts @@ -4,6 +4,11 @@ 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"], @@ -12,7 +17,7 @@ ApplySpecialEffects("Adds an effect to empty build", () => { }); assert.equal(aps.size, 6); - assert.equal(aps.get("ISM"), 10); + assert.equal(aps.get("ISM")?.ap, 10); }); ApplySpecialEffects( @@ -20,33 +25,33 @@ ApplySpecialEffects( () => { const aps = applySpecialEffects({ effects: ["CB"], - abilityPoints: new Map([["SPU", 10]]), + abilityPoints: new Map([["SPU", valueToAps(10)]]), ldeIntensity: 0, }); assert.equal(aps.size, 7); - assert.equal(aps.get("SPU"), 10); + assert.equal(aps.get("SPU")?.ap, 10); } ); ApplySpecialEffects("Does not boost ability beyond 57", () => { const aps = applySpecialEffects({ effects: ["CB"], - abilityPoints: new Map([["ISM", 57]]), + abilityPoints: new Map([["ISM", valueToAps(57)]]), ldeIntensity: 0, }); - assert.equal(aps.get("ISM"), 57); + assert.equal(aps.get("ISM")?.ap, 57); }); ApplySpecialEffects("Tacticooler doesn't boost swim speed beyond 29", () => { const aps = applySpecialEffects({ effects: ["TACTICOOLER"], - abilityPoints: new Map([["SSU", 28]]), + abilityPoints: new Map([["SSU", valueToAps(28)]]), ldeIntensity: 0, }); - assert.equal(aps.get("SSU"), 29); + assert.equal(aps.get("SSU")?.ap, 29); }); ApplySpecialEffects( @@ -54,42 +59,53 @@ ApplySpecialEffects( () => { const aps = applySpecialEffects({ effects: ["TACTICOOLER"], - abilityPoints: new Map([["SSU", 30]]), + abilityPoints: new Map([["SSU", valueToAps(30)]]), ldeIntensity: 0, }); - assert.equal(aps.get("SSU"), 30); + assert.equal(aps.get("SSU")?.ap, 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", 1]]), + abilityPoints: new Map([["SSU", valueToAps(1)]]), ldeIntensity: 0, }); - assert.equal(aps.get("SSU"), 21); + assert.equal(aps.get("SSU")?.ap, 21); }); ApplySpecialEffects("Applies LDE", () => { const aps = applySpecialEffects({ effects: ["LDE"], - abilityPoints: new Map([["ISM", 1]]), + abilityPoints: new Map([["ISM", valueToAps(1)]]), ldeIntensity: 1, }); - assert.equal(aps.get("ISM"), 2); + assert.equal(aps.get("ISM")?.ap, 2); }); ApplySpecialEffects("Applies LDE (intensity != aps given)", () => { const aps = applySpecialEffects({ effects: ["LDE"], - abilityPoints: new Map([["ISM", 1]]), + abilityPoints: new Map([["ISM", valueToAps(1)]]), ldeIntensity: 15, }); - assert.equal(aps.get("ISM"), 18); + assert.equal(aps.get("ISM")?.ap, 18); }); ApplySpecialEffects.run(); diff --git a/app/modules/analyzer/specialEffects.ts b/app/modules/analyzer/specialEffects.ts index 767ae0f43..b7bca3288 100644 --- a/app/modules/analyzer/specialEffects.ts +++ b/app/modules/analyzer/specialEffects.ts @@ -147,12 +147,17 @@ export function applySpecialEffects({ for (const value of valuesArr) { const boostsBeyond = "boostsBeyond" in value ? value.boostsBeyond : true; - const currentAP = result.get(value.type) ?? 0; - const newAP = boostsBeyond + const currentAP = result.get(value.type)?.ap ?? 0; + const newAPUnlimited = boostsBeyond ? currentAP + value.ap : Math.max(currentAP, value.ap); + const newAP = Math.min(newAPUnlimited, MAX_AP); - result.set(value.type, Math.min(newAP, MAX_AP)); + result.set(value.type, { + ap: newAP, + apBeforeTacticooler: + effectObj.type === "TACTICOOLER" ? currentAP : newAP, + }); } } diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index 5fb60462a..9301bcefc 100644 --- a/app/modules/analyzer/stats.ts +++ b/app/modules/analyzer/stats.ts @@ -121,11 +121,18 @@ function specialPoint({ }; } +const OWN_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST = 0.225; function specialSavedAfterDeath({ abilityPoints, mainWeaponParams, + mainOnlyAbilities, }: StatFunctionInput): AnalyzedBuild["stats"]["specialPoint"] { const SPECIAL_SAVED_AFTER_DEATH_ABILITY = "SS"; + const hasRespawnPunisher = mainOnlyAbilities.includes("RP"); + const extraSpecialLost = hasRespawnPunisher + ? OWN_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST + : 0; + const specialSavedAfterDeathForDisplay = (effect: number) => Number(((1.0 - effect) * 100).toFixed(2)); @@ -133,6 +140,7 @@ function specialSavedAfterDeath({ abilityPoints: apFromMap({ abilityPoints: abilityPoints, ability: SPECIAL_SAVED_AFTER_DEATH_ABILITY, + key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap", }), key: "SpecialGaugeRt_Restart", weapon: mainWeaponParams, @@ -140,7 +148,7 @@ function specialSavedAfterDeath({ return { baseValue: specialSavedAfterDeathForDisplay(baseEffect), - value: specialSavedAfterDeathForDisplay(effect), + value: specialSavedAfterDeathForDisplay(effect - extraSpecialLost), modifiedBy: SPECIAL_SAVED_AFTER_DEATH_ABILITY, }; } @@ -436,15 +444,18 @@ function swimSpeed( } const RESPAWN_CHASE_FRAME = 150; +const OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 68; function quickRespawnTime( args: StatFunctionInput ): AnalyzedBuild["stats"]["quickRespawnTime"] { const QUICK_RESPAWN_TIME_ABILITY = "QR"; + const hasRespawnPunisher = args.mainOnlyAbilities.includes("RP"); const chase = abilityPointsToEffects({ abilityPoints: apFromMap({ abilityPoints: args.abilityPoints, ability: QUICK_RESPAWN_TIME_ABILITY, + key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap", }), key: "Dying_ChaseFrm", weapon: args.mainWeaponParams, @@ -453,16 +464,23 @@ function quickRespawnTime( abilityPoints: apFromMap({ abilityPoints: args.abilityPoints, ability: QUICK_RESPAWN_TIME_ABILITY, + key: hasRespawnPunisher ? "apBeforeTacticooler" : "ap", }), key: "Dying_AroundFrm", weapon: args.mainWeaponParams, }); + const extraFrames = hasRespawnPunisher + ? OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES + : 0; + return { baseValue: framesToSeconds( RESPAWN_CHASE_FRAME + chase.baseEffect + around.baseEffect ), - value: framesToSeconds(RESPAWN_CHASE_FRAME + chase.effect + around.effect), + value: framesToSeconds( + RESPAWN_CHASE_FRAME + chase.effect + around.effect + extraFrames + ), modifiedBy: QUICK_RESPAWN_TIME_ABILITY, }; } diff --git a/app/modules/analyzer/types.ts b/app/modules/analyzer/types.ts index 3467d56eb..5b2d2eab8 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -107,7 +107,10 @@ export interface Stat { modifiedBy: Ability; } -export type AbilityPoints = Map; +export type AbilityPoints = Map< + Ability, + { ap: number; apBeforeTacticooler: number } +>; export interface StatFunctionInput { mainWeaponParams: MainWeaponParams; diff --git a/app/modules/analyzer/utils.test.ts b/app/modules/analyzer/utils.test.ts index 99dac92c4..a2e1ad83a 100644 --- a/app/modules/analyzer/utils.test.ts +++ b/app/modules/analyzer/utils.test.ts @@ -25,8 +25,8 @@ BuildToAbilityPoints("Calculates ability points", () => { EMPTY_ROW, ]); - assert.equal(aps.get("SS"), 13); - assert.equal(aps.get("RSU"), 6); + assert.equal(aps.get("SS")?.ap, 13); + assert.equal(aps.get("RSU")?.ap, 6); }); BuildToAbilityPoints("Handles ability doubler", () => { @@ -36,7 +36,7 @@ BuildToAbilityPoints("Handles ability doubler", () => { EMPTY_ROW, ]); - assert.equal(aps.get("SS"), 6); + assert.equal(aps.get("SS")?.ap, 6); }); BuildToAbilityPoints("Does not calculate AP for main only abilities", () => { diff --git a/app/modules/analyzer/utils.ts b/app/modules/analyzer/utils.ts index ad495d38e..e8f7506c1 100644 --- a/app/modules/analyzer/utils.ts +++ b/app/modules/analyzer/utils.ts @@ -31,8 +31,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; - result.set(ability, (result.get(ability) ?? 0) + apsDoubled); + result.set(ability, { ap: newAp, apBeforeTacticooler: newAp }); } } @@ -50,11 +51,13 @@ 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) ?? 0; + return abilityPoints.get(ability)?.[key] ?? 0; } function abilityValues({ diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index fffc046cb..5731676b7 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -487,15 +487,17 @@ function AbilityPointsDetails({ {t("abilityPoints")}
{abilities - .filter((a) => (abilityPoints.get(a.name) ?? 0) > 0) + .filter((a) => (abilityPoints.get(a.name)?.ap ?? 0) > 0) .sort((a, b) => { - return abilityPoints.get(b.name)! - abilityPoints.get(a.name)!; + return ( + abilityPoints.get(b.name)!.ap - abilityPoints.get(a.name)!.ap + ); }) .map((a) => (
- {abilityPoints.get(a.name)} + {abilityPoints.get(a.name)?.ap}
))}