mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-13 22:56:05 -05:00
BRU effects for analyzer
This commit is contained in:
@@ -38,5 +38,11 @@
|
||||
"SuperJump_ChargeFrm": [20.0, 35.0, 80.0],
|
||||
"SuperJump_MoveFrm": [96.6, 132.3, 138.0],
|
||||
"WallJumpChargeFrm": [0.0, 0.0, 0.0],
|
||||
"ReduceJumpSwerveRate": [1.0, 0.75, 0.0]
|
||||
"ReduceJumpSwerveRate": [1.0, 0.75, 0.0],
|
||||
"SpawnSpeedZSpecUp": [0.0, 0.0, 0.0],
|
||||
"PeriodFirst": [0.0, 0.0, 0.0],
|
||||
"PeriodSecond": [0.0, 0.0, 0.0],
|
||||
"MarkingFrameSubSpec": [0.0, 0.0, 0.0],
|
||||
"SensorRadius": [0.0, 0.0, 0.0],
|
||||
"MaxHP": [0.0, 0.0, 0.0]
|
||||
}
|
||||
|
||||
@@ -11,9 +11,15 @@ import type {
|
||||
import { DAMAGE_TYPE } from "./types";
|
||||
import { INK_CONSUME_TYPES } from "./types";
|
||||
import invariant from "tiny-invariant";
|
||||
import { abilityPointsToEffects, apFromMap, weaponParams } from "./utils";
|
||||
import {
|
||||
abilityPointsToEffects,
|
||||
apFromMap,
|
||||
hasEffect,
|
||||
weaponParams,
|
||||
} from "./utils";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
import { semiRandomId } from "~/utils/strings";
|
||||
import { roundToTwoDecimalPlaces } from "~/utils/number";
|
||||
|
||||
export function buildStats({
|
||||
abilityPoints,
|
||||
@@ -74,6 +80,7 @@ export function buildStats({
|
||||
quickRespawnTime: quickRespawnTime(input),
|
||||
superJumpTimeGroundFrames: superJumpTimeGroundFrames(input),
|
||||
superJumpTimeTotal: superJumpTimeTotal(input),
|
||||
...subStats(input),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -559,3 +566,71 @@ function framesBeforeTakingDamageInEnemyInk(
|
||||
modifiedBy: FRAMES_BEFORE_TAKING_DAMAGE_IN_ENEMY_INK_ABILITY,
|
||||
};
|
||||
}
|
||||
|
||||
const SUB_WEAPON_STATS = [
|
||||
{
|
||||
analyzedBuildKey: "subVelocity",
|
||||
abilityValuesKey: "SpawnSpeedZSpecUp",
|
||||
type: "NO_CHANGE",
|
||||
},
|
||||
{
|
||||
analyzedBuildKey: "subFirstPhaseDuration",
|
||||
abilityValuesKey: "PeriodFirst",
|
||||
type: "TIME",
|
||||
},
|
||||
{
|
||||
analyzedBuildKey: "subSecondPhaseDuration",
|
||||
abilityValuesKey: "PeriodSecond",
|
||||
type: "TIME",
|
||||
},
|
||||
{
|
||||
analyzedBuildKey: "subMarkingTimeInSeconds",
|
||||
abilityValuesKey: "MarkingFrameSubSpec",
|
||||
type: "TIME",
|
||||
},
|
||||
{
|
||||
analyzedBuildKey: "subMarkingRadius",
|
||||
abilityValuesKey: "SensorRadius",
|
||||
type: "NO_CHANGE",
|
||||
},
|
||||
{ analyzedBuildKey: "subHp", abilityValuesKey: "MaxHP", type: "HP" },
|
||||
] as const;
|
||||
function subStats(args: StatFunctionInput) {
|
||||
const result: Partial<AnalyzedBuild["stats"]> = {};
|
||||
const SUB_STATS_KEY = "BRU";
|
||||
|
||||
for (const { analyzedBuildKey, abilityValuesKey, type } of SUB_WEAPON_STATS) {
|
||||
if (!hasEffect({ key: abilityValuesKey, weapon: args.subWeaponParams })) {
|
||||
continue;
|
||||
}
|
||||
const { baseEffect, effect } = abilityPointsToEffects({
|
||||
abilityPoints: apFromMap({
|
||||
abilityPoints: args.abilityPoints,
|
||||
ability: SUB_STATS_KEY,
|
||||
}),
|
||||
key: abilityValuesKey,
|
||||
weapon: args.subWeaponParams,
|
||||
});
|
||||
|
||||
const toValue = (effect: number) => {
|
||||
switch (type) {
|
||||
case "NO_CHANGE":
|
||||
return roundToTwoDecimalPlaces(effect);
|
||||
case "HP":
|
||||
return roundToTwoDecimalPlaces(effect / 10);
|
||||
case "TIME":
|
||||
return framesToSeconds(effect);
|
||||
default:
|
||||
assertUnreachable(type);
|
||||
}
|
||||
};
|
||||
|
||||
result[analyzedBuildKey] = {
|
||||
baseValue: toValue(baseEffect),
|
||||
modifiedBy: SUB_STATS_KEY,
|
||||
value: toValue(effect),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
SubWeaponId,
|
||||
} from "~/modules/in-game-lists";
|
||||
import type { SPECIAL_EFFECTS } from "./specialEffects";
|
||||
import type abilityValues from "./ability-values.json";
|
||||
|
||||
export interface MainWeaponParams {
|
||||
subWeaponId: SubWeaponId;
|
||||
@@ -78,6 +79,7 @@ export interface DistanceDamage {
|
||||
}
|
||||
|
||||
export interface SubWeaponParams {
|
||||
overwrites?: Record<string, Partial<Record<"High" | "Mid" | "Low", number>>>;
|
||||
SubInkSaveLv: 0 | 1 | 2 | 3;
|
||||
/** How much ink one usage of the sub consumes */
|
||||
InkConsume: number;
|
||||
@@ -191,7 +193,15 @@ export interface AnalyzedBuild {
|
||||
quickRespawnTime: Stat;
|
||||
superJumpTimeGroundFrames: Stat;
|
||||
superJumpTimeTotal: Stat;
|
||||
subVelocity?: Stat;
|
||||
subFirstPhaseDuration?: Stat;
|
||||
subSecondPhaseDuration?: Stat;
|
||||
subMarkingTimeInSeconds?: Stat;
|
||||
subMarkingRadius?: Stat;
|
||||
subHp?: Stat;
|
||||
};
|
||||
}
|
||||
|
||||
export type SpecialEffectType = typeof SPECIAL_EFFECTS[number]["type"];
|
||||
|
||||
export type AbilityValuesKeys = keyof typeof abilityValues;
|
||||
|
||||
@@ -2,7 +2,12 @@ import type { Ability, BuildAbilitiesTupleWithUnknown } from "../in-game-lists";
|
||||
import { abilities } from "../in-game-lists";
|
||||
import weaponParamsJson from "./weapon-params.json";
|
||||
import abilityValuesJson from "./ability-values.json";
|
||||
import type { AbilityPoints, MainWeaponParams, ParamsJson } from "./types";
|
||||
import type {
|
||||
AbilityPoints,
|
||||
MainWeaponParams,
|
||||
ParamsJson,
|
||||
SubWeaponParams,
|
||||
} from "./types";
|
||||
import invariant from "tiny-invariant";
|
||||
import type { AbilityWithUnknown } from "../in-game-lists/types";
|
||||
|
||||
@@ -58,7 +63,7 @@ function abilityValues({
|
||||
weapon,
|
||||
}: {
|
||||
key: keyof typeof abilityValuesJson;
|
||||
weapon: MainWeaponParams;
|
||||
weapon: MainWeaponParams | SubWeaponParams;
|
||||
}): [number, number, number] {
|
||||
const overwrites = weapon.overwrites?.[key];
|
||||
|
||||
@@ -106,7 +111,7 @@ function abilityPointsToEffect({
|
||||
}: {
|
||||
key: keyof typeof abilityValuesJson;
|
||||
abilityPoints: number;
|
||||
weapon: MainWeaponParams;
|
||||
weapon: MainWeaponParams | SubWeaponParams;
|
||||
}) {
|
||||
const [high, mid, low] = abilityValues({ key, weapon });
|
||||
|
||||
@@ -126,10 +131,22 @@ export function abilityPointsToEffects({
|
||||
}: {
|
||||
key: keyof typeof abilityValuesJson;
|
||||
abilityPoints: number;
|
||||
weapon: MainWeaponParams;
|
||||
weapon: MainWeaponParams | SubWeaponParams;
|
||||
}) {
|
||||
return {
|
||||
baseEffect: abilityPointsToEffect({ key, abilityPoints: 0, weapon }),
|
||||
effect: abilityPointsToEffect({ key, abilityPoints, weapon }),
|
||||
};
|
||||
}
|
||||
|
||||
export function hasEffect({
|
||||
key,
|
||||
weapon,
|
||||
}: {
|
||||
key: keyof typeof abilityValuesJson;
|
||||
weapon: MainWeaponParams | SubWeaponParams;
|
||||
}) {
|
||||
const [high, mid, low] = abilityValues({ key, weapon });
|
||||
|
||||
return high !== mid || mid !== low;
|
||||
}
|
||||
|
||||
@@ -664,6 +664,13 @@
|
||||
},
|
||||
"subWeapons": {
|
||||
"0": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.68,
|
||||
"Low": 1.12,
|
||||
"Mid": 1.4
|
||||
}
|
||||
},
|
||||
"InkRecoverStop": 60,
|
||||
"DistanceDamage": [
|
||||
{
|
||||
@@ -677,6 +684,13 @@
|
||||
]
|
||||
},
|
||||
"1": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.68,
|
||||
"Low": 1.12,
|
||||
"Mid": 1.4
|
||||
}
|
||||
},
|
||||
"InkRecoverStop": 60,
|
||||
"DistanceDamage": [
|
||||
{
|
||||
@@ -690,6 +704,13 @@
|
||||
]
|
||||
},
|
||||
"2": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.68,
|
||||
"Low": 1.12,
|
||||
"Mid": 1.4
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 0,
|
||||
"InkConsume": 0.4,
|
||||
"InkRecoverStop": 50,
|
||||
@@ -705,15 +726,41 @@
|
||||
]
|
||||
},
|
||||
"3": {
|
||||
"overwrites": {
|
||||
"PeriodFirst": {
|
||||
"High": 600,
|
||||
"Low": 300,
|
||||
"Mid": 450
|
||||
},
|
||||
"PeriodSecond": {
|
||||
"High": 1020,
|
||||
"Low": 900,
|
||||
"Mid": 960
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 3,
|
||||
"InkConsume": 0.6,
|
||||
"InkRecoverStop": 60
|
||||
},
|
||||
"4": {
|
||||
"overwrites": {
|
||||
"MaxHP": {
|
||||
"High": 15000,
|
||||
"Low": 8000,
|
||||
"Mid": 11500
|
||||
}
|
||||
},
|
||||
"InkConsume": 0.6,
|
||||
"InkRecoverStop": 85
|
||||
},
|
||||
"5": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.84,
|
||||
"Low": 1.36,
|
||||
"Mid": 1.6
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 1,
|
||||
"InkConsume": 0.6,
|
||||
"InkRecoverStop": 70,
|
||||
@@ -751,6 +798,13 @@
|
||||
]
|
||||
},
|
||||
"6": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 0.52,
|
||||
"Low": 0.4,
|
||||
"Mid": 0.46
|
||||
}
|
||||
},
|
||||
"InkRecoverStop": 70,
|
||||
"DistanceDamage_BlastParamMaxCharge": [
|
||||
{
|
||||
@@ -775,6 +829,13 @@
|
||||
"DirectDamage": 200
|
||||
},
|
||||
"7": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.68,
|
||||
"Low": 1.12,
|
||||
"Mid": 1.4
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 1,
|
||||
"InkConsume": 0.55,
|
||||
"InkRecoverStop": 85,
|
||||
@@ -790,16 +851,36 @@
|
||||
]
|
||||
},
|
||||
"8": {
|
||||
"overwrites": {},
|
||||
"SubInkSaveLv": 3,
|
||||
"InkConsume": 0.75,
|
||||
"InkRecoverStop": 0
|
||||
},
|
||||
"9": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.87,
|
||||
"Low": 1.38,
|
||||
"Mid": 1.64
|
||||
},
|
||||
"MarkingFrameSubSpec": {
|
||||
"High": 960,
|
||||
"Low": 480,
|
||||
"Mid": 720
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 1,
|
||||
"InkConsume": 0.45,
|
||||
"InkRecoverStop": 75
|
||||
},
|
||||
"10": {
|
||||
"overwrites": {
|
||||
"SensorRadius": {
|
||||
"High": 4,
|
||||
"Low": 3,
|
||||
"Mid": 3.5
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 3,
|
||||
"InkConsume": 0.6,
|
||||
"InkRecoverStop": 0,
|
||||
@@ -815,17 +896,43 @@
|
||||
]
|
||||
},
|
||||
"11": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.68,
|
||||
"Low": 1.12,
|
||||
"Mid": 1.4
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 1,
|
||||
"InkConsume": 0.6,
|
||||
"InkRecoverStop": 75
|
||||
},
|
||||
"12": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 5.4,
|
||||
"Low": 5,
|
||||
"Mid": 5.2
|
||||
},
|
||||
"MarkingFrameSubSpec": {
|
||||
"High": 600,
|
||||
"Low": 300,
|
||||
"Mid": 450
|
||||
}
|
||||
},
|
||||
"SubInkSaveLv": 0,
|
||||
"InkConsume": 0.4,
|
||||
"InkRecoverStop": 50,
|
||||
"DirectDamage": 300
|
||||
},
|
||||
"13": {
|
||||
"overwrites": {
|
||||
"SpawnSpeedZSpecUp": {
|
||||
"High": 1.84,
|
||||
"Low": 1.36,
|
||||
"Mid": 1.6
|
||||
}
|
||||
},
|
||||
"InkConsume": 0.65,
|
||||
"InkRecoverStop": 88,
|
||||
"DistanceDamage_BlastParamChase": [
|
||||
|
||||
@@ -148,6 +148,46 @@ export default function BuildAnalyzerPage() {
|
||||
title={t("analyzer:stat.whiteInk")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
{analyzed.stats.subVelocity && (
|
||||
<StatCard
|
||||
stat={analyzed.stats.subVelocity}
|
||||
title={t("analyzer:stat.sub.velocity")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subFirstPhaseDuration && (
|
||||
<StatCard
|
||||
stat={analyzed.stats.subFirstPhaseDuration}
|
||||
title={t("analyzer:stat.sub.firstPhaseDuration")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subSecondPhaseDuration && (
|
||||
<StatCard
|
||||
stat={analyzed.stats.subSecondPhaseDuration}
|
||||
title={t("analyzer:stat.sub.secondPhaseDuration")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subMarkingTimeInSeconds && (
|
||||
<StatCard
|
||||
stat={analyzed.stats.subMarkingTimeInSeconds}
|
||||
title={t("analyzer:stat.sub.markingTimeInSeconds")}
|
||||
suffix={t("analyzer:suffix.seconds")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subMarkingRadius && (
|
||||
<StatCard
|
||||
stat={analyzed.stats.subMarkingRadius}
|
||||
title={t("analyzer:stat.sub.markingRadius")}
|
||||
/>
|
||||
)}
|
||||
{analyzed.stats.subHp && (
|
||||
<StatCard
|
||||
stat={analyzed.stats.subHp}
|
||||
title={t("analyzer:stat.sub.hp")}
|
||||
suffix={t("analyzer:suffix.hp")}
|
||||
/>
|
||||
)}
|
||||
</StatCategory>
|
||||
<StatCategory title={t("analyzer:stat.category.special")}>
|
||||
<StatCard
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
"stat.consumption.SPLATLING_CHARGE": "Full charges",
|
||||
"stat.consumption.SHIELD_LAUNCH": "Shield launches",
|
||||
"stat.consumption.DUALIE_ROLL": "Dodge Rolls",
|
||||
"stat.sub.velocity": "Velocity (decides range)",
|
||||
"stat.sub.firstPhaseDuration": "Full power phase duration",
|
||||
"stat.sub.secondPhaseDuration": "Mid phase duration",
|
||||
"stat.sub.markingTimeInSeconds": "Marking duration",
|
||||
"stat.sub.markingRadius": "Marking radius",
|
||||
"stat.sub.hp": "Durability",
|
||||
"damage.header.type": "Type",
|
||||
"damage.header.damage": "Damage",
|
||||
"damage.header.distance": "Distance",
|
||||
|
||||
@@ -218,6 +218,7 @@ function parametersToSubWeaponResult(params: any): SubWeaponParams {
|
||||
// );
|
||||
|
||||
return {
|
||||
overwrites: resolveSubWeaponOverwrites(params),
|
||||
// xxx: not every sub has this, why? e.g. Splash Wall
|
||||
SubInkSaveLv,
|
||||
InkConsume: params["WeaponParam"]["InkConsume"],
|
||||
@@ -337,6 +338,27 @@ function resolveOverwritesWithArbitraryKeys(
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSubWeaponOverwrites(params: any) {
|
||||
const result: SubWeaponParams["overwrites"] = {
|
||||
SpawnSpeedZSpecUp: params["MoveParam"]?.["SpawnSpeedZSpecUp"],
|
||||
PeriodFirst: params["MoveParam"]?.["PeriodFirst"],
|
||||
PeriodSecond: params["MoveParam"]?.["PeriodSecond"],
|
||||
MarkingFrameSubSpec:
|
||||
params["MoveParam"]?.["MarkingFrameSubSpec"] ??
|
||||
params["MoveParam"]?.["MarkingFrame"],
|
||||
SensorRadius: params["MoveParam"]?.["SensorRadius"],
|
||||
MaxHP: params["MoveParam"]?.["MaxHP"],
|
||||
// xxx: missing Squid Beakon AP up
|
||||
};
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(result).filter(
|
||||
([_key, value]) =>
|
||||
value && (value.High !== value.Mid || value.Low !== value.High)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const WEAPON_TYPES_TO_IGNORE = [
|
||||
"Mission",
|
||||
"Coop",
|
||||
|
||||
Reference in New Issue
Block a user