From bdcd531f505b2a52b84671fb700263c9596f8bb1 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 24 Sep 2026 20:23:13 +0300 Subject: [PATCH] Add Brush/Roller rolling time to analyzer --- app/features/build-analyzer/analyzer-types.ts | 4 +++ .../build-analyzer/core/stats.test.ts | 28 ++++++++++++++++++ app/features/build-analyzer/core/stats.ts | 29 +++++++++++++++++++ .../build-analyzer/data/weapon-params.ts | 8 +++++ .../build-analyzer/routes/analyzer.tsx | 10 +++++++ changelog/2026-09-24-analyzer-roll-time.md | 5 ++++ locales/da/analyzer.json | 2 ++ locales/de/analyzer.json | 2 ++ locales/en/analyzer.json | 2 ++ locales/es-ES/analyzer.json | 2 ++ locales/es-US/analyzer.json | 2 ++ locales/fr-CA/analyzer.json | 2 ++ locales/fr-EU/analyzer.json | 2 ++ locales/he/analyzer.json | 2 ++ locales/it/analyzer.json | 2 ++ locales/ja/analyzer.json | 2 ++ locales/ko/analyzer.json | 2 ++ locales/nl/analyzer.json | 2 ++ locales/pl/analyzer.json | 2 ++ locales/pt-BR/analyzer.json | 2 ++ locales/ru/analyzer.json | 2 ++ locales/zh/analyzer.json | 2 ++ scripts/create-analyzer-json.ts | 2 ++ 23 files changed, 118 insertions(+) create mode 100644 changelog/2026-09-24-analyzer-roll-time.md diff --git a/app/features/build-analyzer/analyzer-types.ts b/app/features/build-analyzer/analyzer-types.ts index a965be6da..47b01070c 100644 --- a/app/features/build-analyzer/analyzer-types.ts +++ b/app/features/build-analyzer/analyzer-types.ts @@ -96,6 +96,8 @@ export interface BaseWeaponStats { InkConsume_WeaponShelterShotgunParam?: number; /** How much ink a dualie dodge roll consumes? */ InkConsume_SideStepParam?: number; + /** How much ink rolling with a roller or brush consumes per frame at full rolling speed? */ + InkConsumeMaxPerFrame_WeaponRollParam?: number; /** How much ink a fully charged Splatana shot consumes? */ InkConsumeFullCharge_ChargeParam?: number; @@ -293,6 +295,8 @@ export interface AnalyzedBuild { mainWeaponWhiteInkSeconds?: number; subWeaponWhiteInkSeconds: number; subWeaponInkConsumptionPercentage: Stat; + /** Seconds a full ink tank lasts rolling at full speed (rollers and brushes) */ + mainWeaponRollSeconds?: Stat; fullInkTankOptions: Array; damages: Array; specialWeaponDamages: Array; diff --git a/app/features/build-analyzer/core/stats.test.ts b/app/features/build-analyzer/core/stats.test.ts index a598dee59..8e1e2f80a 100644 --- a/app/features/build-analyzer/core/stats.test.ts +++ b/app/features/build-analyzer/core/stats.test.ts @@ -190,6 +190,34 @@ describe("Analyze build", () => { expect(statWithISM.value).toBeLessThan(statWithISM.baseValue); }); + test("Rolling time on a full tank only exists for rollers and brushes", () => { + const splatRoller = buildStats({ + weaponSplId: 1010, + hasTacticooler: false, + }); + const inkbrush = buildStats({ weaponSplId: 1100, hasTacticooler: false }); + const splattershot = buildStats({ weaponSplId: 40, hasTacticooler: false }); + + expect(splatRoller.stats.mainWeaponRollSeconds?.baseValue).toBe(16.667); + expect(inkbrush.stats.mainWeaponRollSeconds?.baseValue).toBe(13.333); + expect(splattershot.stats.mainWeaponRollSeconds).toBeUndefined(); + }); + + test("ISM increases rolling time on a full tank", () => { + const analyzed = buildStats({ weaponSplId: 1100, hasTacticooler: false }); + const analyzedWithISM = buildStats({ + weaponSplId: 1100, + abilityPoints: new Map([["ISM", 20]]), + hasTacticooler: false, + }); + + const stat = analyzed.stats.mainWeaponRollSeconds!; + const statWithISM = analyzedWithISM.stats.mainWeaponRollSeconds!; + + expect(statWithISM.baseValue).toBe(stat.baseValue); + expect(statWithISM.value).toBeGreaterThan(statWithISM.baseValue); + }); + test("Accounts for Jr. big ink tank with main weapon ink consumption %", () => { const analyzedJr = buildStats({ weaponSplId: 10, diff --git a/app/features/build-analyzer/core/stats.ts b/app/features/build-analyzer/core/stats.ts index 721f313e0..c29de271c 100644 --- a/app/features/build-analyzer/core/stats.ts +++ b/app/features/build-analyzer/core/stats.ts @@ -125,6 +125,7 @@ export function buildStats({ subWeaponInkConsumptionPercentage: subWeaponInkConsumptionPercentage(input), ...mainWeaponInkConsumptionPercentages(input), + mainWeaponRollSeconds: mainWeaponRollSeconds(input), squidFormInkRecoverySeconds: squidFormInkRecoverySeconds(input), humanoidFormInkRecoverySeconds: humanoidFormInkRecoverySeconds(input), runSpeed: runSpeed(input), @@ -388,6 +389,34 @@ function mainWeaponInkConsumptionPercentages( return result; } +function mainWeaponRollSeconds({ + mainWeaponParams, + abilityPoints, + weaponSplId, +}: StatFunctionInput): AnalyzedBuild["stats"]["mainWeaponRollSeconds"] { + const inkConsumePerFrame = + mainWeaponParams.InkConsumeMaxPerFrame_WeaponRollParam; + if (typeof inkConsumePerFrame !== "number") return; + + const { baseEffect, effect } = abilityPointsToEffects({ + abilityPoints: apFromMap({ + abilityPoints, + ability: "ISM", + }), + key: "ConsumeRt_Main", + weapon: mainWeaponParams, + }); + + const rollFrames = (consumeRate: number) => + inkTankSize(weaponSplId) / (inkConsumePerFrame * consumeRate); + + return { + baseValue: framesToSeconds(rollFrames(baseEffect)), + value: framesToSeconds(rollFrames(effect)), + modifiedBy: "ISM", + }; +} + function mainWeaponInkConsumeByType({ mainWeaponParams, abilityPoints, diff --git a/app/features/build-analyzer/data/weapon-params.ts b/app/features/build-analyzer/data/weapon-params.ts index c1a5fbcc1..dbe5ccc93 100644 --- a/app/features/build-analyzer/data/weapon-params.ts +++ b/app/features/build-analyzer/data/weapon-params.ts @@ -430,6 +430,7 @@ export const weaponParams = { WideSwingUnitGroupParam_DamageParam_DamageMinValue: 250, WideSwingUnitGroupParam_DamageParam_DamageMaxValue: 1000, InkConsume_WeaponSwingParam: 0.0396, + InkConsumeMaxPerFrame_WeaponRollParam: 0.001, }, "1010": { Range_SpawnSpeed: 1.8338, @@ -443,6 +444,7 @@ export const weaponParams = { WideSwingUnitGroupParam_DamageParam_DamageMinValue: 350, WideSwingUnitGroupParam_DamageParam_DamageMaxValue: 1500, InkConsume_WeaponSwingParam: 0.085, + InkConsumeMaxPerFrame_WeaponRollParam: 0.001, }, "1020": { Range_SpawnSpeed: 2.0289, @@ -457,6 +459,7 @@ export const weaponParams = { WideSwingUnitGroupParam_DamageParam_DamageMinValue: 400, WideSwingUnitGroupParam_DamageParam_DamageMaxValue: 1800, InkConsume_WeaponSwingParam: 0.21, + InkConsumeMaxPerFrame_WeaponRollParam: 0.001, }, "1030": { Range_SpawnSpeed: 1.7026, @@ -471,6 +474,7 @@ export const weaponParams = { WideSwingUnitGroupParam_DamageParam_DamageMaxValue: 1500, InkConsume_WeaponVerticalSwingParam: 0.12, InkConsume_WeaponWideSwingParam: 0.08, + InkConsumeMaxPerFrame_WeaponRollParam: 0.001, }, "1040": { Range_SpawnSpeed: 2.2749, @@ -484,6 +488,7 @@ export const weaponParams = { WideSwingUnitGroupParam_DamageParam_DamageMinValue: 400, WideSwingUnitGroupParam_DamageParam_DamageMaxValue: 700, InkConsume_WeaponSwingParam: 0.09, + InkConsumeMaxPerFrame_WeaponRollParam: 0.0005, }, "1100": { Range_SpawnSpeed: 0.72, @@ -494,6 +499,7 @@ export const weaponParams = { SwingUnitGroupParam_DamageParam_DamageMinValue: 150, SwingUnitGroupParam_DamageParam_DamageMaxValue: 300, InkConsume_WeaponSwingParam: 0.02, + InkConsumeMaxPerFrame_WeaponRollParam: 0.00125, }, "1110": { Range_SpawnSpeed: 0.84, @@ -503,6 +509,7 @@ export const weaponParams = { SwingUnitGroupParam_DamageParam_DamageMinValue: 200, SwingUnitGroupParam_DamageParam_DamageMaxValue: 400, InkConsume_WeaponSwingParam: 0.027, + InkConsumeMaxPerFrame_WeaponRollParam: 0.0015, }, "1120": { Range_SpawnSpeed: 1.05, @@ -512,6 +519,7 @@ export const weaponParams = { SwingUnitGroupParam_DamageParam_DamageMinValue: 310, SwingUnitGroupParam_DamageParam_DamageMaxValue: 600, InkConsume_WeaponSwingParam: 0.048, + InkConsumeMaxPerFrame_WeaponRollParam: 0.0018, }, "2000": { DistanceFullCharge: 16.765, diff --git a/app/features/build-analyzer/routes/analyzer.tsx b/app/features/build-analyzer/routes/analyzer.tsx index a9929e90c..938cc4989 100644 --- a/app/features/build-analyzer/routes/analyzer.tsx +++ b/app/features/build-analyzer/routes/analyzer.tsx @@ -214,6 +214,16 @@ function BuildAnalyzerPage() { suffix="%" /> )), + analyzed.stats.mainWeaponRollSeconds ? ( + + ) : null, typeof analyzed.stats.mainWeaponWhiteInkSeconds === "number" ? (