Deduplicate weapon-params.ts (#2695)

This commit is contained in:
Kalle 2026-01-04 17:44:37 +02:00 committed by GitHub
parent ee4f3ef4b9
commit 38d80d670f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1043 additions and 1851 deletions

View File

@ -14,12 +14,9 @@ type Overwrites = Record<
Partial<Record<"High" | "Mid" | "Low", number>>
>;
export interface MainWeaponParams {
subWeaponId: SubWeaponId;
specialWeaponId: SpecialWeaponId;
export interface BaseWeaponStats {
/** Replacing default values of the ability json for this specific weapon */
overwrites?: Overwrites;
SpecialPoint: number;
/** Weapon's weight class. "Light/Heavy weapon" */
WeaponSpeedType?: "Slow" | "Fast";
/** Total frames it takes the weapon to shoot out three times */
@ -105,6 +102,14 @@ export interface MainWeaponParams {
// SpeedInkConsumeMin_WeaponRollParam?: number;
}
export interface WeaponKit {
subWeaponId: SubWeaponId;
specialWeaponId: SpecialWeaponId;
SpecialPoint: number;
}
export type MainWeaponParams = BaseWeaponStats & WeaponKit;
export interface DistanceDamage {
Damage: number;
Distance: number;
@ -148,7 +153,8 @@ export type SpecialWeaponParams = SpecialWeaponParamsObject[SpecialWeaponId] & {
};
export type ParamsJson = {
mainWeapons: Record<MainWeaponId, MainWeaponParams>;
baseWeaponStats: Record<MainWeaponId, BaseWeaponStats>;
weaponKits: Record<MainWeaponId, WeaponKit>;
subWeapons: Record<SubWeaponId, SubWeaponParams>;
specialWeapons: SpecialWeaponParamsObject;
};

View File

@ -6,9 +6,9 @@ import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { SendouButton } from "../../../components/elements/Button";
import { SendouPopover } from "../../../components/elements/Popover";
import { MAX_AP } from "../analyzer-constants";
import type { FullInkTankOption } from "../analyzer-types";
import type { FullInkTankOption, SpecialWeaponParams } from "../analyzer-types";
import { fullInkTankOptions } from "../core/stats";
import { weaponParams } from "../core/utils";
import { mainWeaponParams, weaponParams } from "../core/utils";
interface PerInkTankGridProps {
weaponSplId: MainWeaponId;
@ -243,13 +243,13 @@ function inkTankOptionsWhenNSubsUsed({
subsUsed: number;
weaponSplId: MainWeaponId;
}) {
const mainWeaponParams = weaponParams().mainWeapons[weaponSplId];
const mainParams = mainWeaponParams(weaponSplId);
const subWeaponParams =
weaponParams().subWeapons[mainWeaponParams.subWeaponId];
const subWeaponParams = weaponParams().subWeapons[mainParams.subWeaponId];
const specialWeaponParams =
weaponParams().specialWeapons[mainWeaponParams.specialWeaponId];
const specialWeaponParams = weaponParams().specialWeapons[
mainParams.specialWeaponId
] as SpecialWeaponParams;
const options = fullInkTankOptions({
abilityPoints: new Map([
@ -259,7 +259,7 @@ function inkTankOptionsWhenNSubsUsed({
weaponSplId,
hasTacticooler: false,
mainOnlyAbilities: [],
mainWeaponParams,
mainWeaponParams: mainParams,
specialWeaponParams,
subWeaponParams,
});

View File

@ -45,6 +45,7 @@ import {
abilityPointsToEffects,
abilityValues,
apFromMap,
mainWeaponParams as getMainWeaponParams,
hasEffect,
hpDivided,
weaponIdToMultiShotCount,
@ -62,8 +63,7 @@ export function buildStats({
mainOnlyAbilities?: Array<Ability>;
hasTacticooler: boolean;
}): AnalyzedBuild {
const mainWeaponParams = weaponParams().mainWeapons[weaponSplId];
invariant(mainWeaponParams, `Weapon with splId ${weaponSplId} not found`);
const mainWeaponParams = getMainWeaponParams(weaponSplId);
const subWeaponParams =
weaponParams().subWeapons[mainWeaponParams.subWeaponId];
@ -72,8 +72,9 @@ export function buildStats({
`Sub weapon with splId ${mainWeaponParams.subWeaponId} not found`,
);
const specialWeaponParams =
weaponParams().specialWeapons[mainWeaponParams.specialWeaponId];
const specialWeaponParams = weaponParams().specialWeapons[
mainWeaponParams.specialWeaponId
] as SpecialWeaponParams;
invariant(
specialWeaponParams,
`Special weapon with splId ${mainWeaponParams.specialWeaponId} not found`,

View File

@ -34,7 +34,16 @@ import { abilityValues as abilityValuesJson } from "./ability-values";
import { weaponParams as rawWeaponParams } from "./weapon-params";
export function weaponParams(): ParamsJson {
return rawWeaponParams as ParamsJson;
return rawWeaponParams as unknown as ParamsJson;
}
export function mainWeaponParams(weaponId: MainWeaponId): MainWeaponParams {
const params = rawWeaponParams as unknown as ParamsJson;
const baseId = weaponIdToBaseWeaponId(weaponId);
const baseStats = params.baseWeaponStats[baseId];
const kit = params.weaponKits[weaponId];
return { ...baseStats, ...kit } as MainWeaponParams;
}
export function buildToAbilityPoints(build: BuildAbilitiesTupleWithUnknown) {

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,11 @@ import {
specialFieldHp,
subStats,
} from "~/features/build-analyzer/core/stats";
import { hpDivided } from "~/features/build-analyzer/core/utils";
import { weaponParams } from "~/features/build-analyzer/core/weapon-params";
import {
hpDivided,
mainWeaponParams,
weaponParams,
} from "~/features/build-analyzer/core/utils";
import {
BIG_BUBBLER_ID,
CRAB_TANK_ID,
@ -30,19 +33,21 @@ const SUPER_CHUMP_HP = 60;
const TRIPLE_SPLASHDOWN_HP = 100;
export const objectHitPoints = (abilityPoints: AbilityPoints): HitPoints => {
const params = weaponParams();
const Wsb_Shield = subStats({
abilityPoints,
subWeaponParams: weaponParams.subWeapons[SPLASH_WALL_ID] as SubWeaponParams,
subWeaponParams: params.subWeapons[SPLASH_WALL_ID] as SubWeaponParams,
}).subHp?.value;
const GreatBarrier_Barrier = specialFieldHp({
abilityPoints,
specialWeaponParams: weaponParams.specialWeapons[
specialWeaponParams: params.specialWeapons[
BIG_BUBBLER_ID
] as SpecialWeaponParams,
})?.value;
const GreatBarrier_WeakPoint = specialDeviceHp({
abilityPoints,
specialWeaponParams: weaponParams.specialWeapons[
specialWeaponParams: params.specialWeapons[
BIG_BUBBLER_ID
] as SpecialWeaponParams,
})?.value;
@ -53,25 +58,19 @@ export const objectHitPoints = (abilityPoints: AbilityPoints): HitPoints => {
return {
BulletUmbrellaCanopyNormal: SPLAT_BRELLA_SHIELD_HP,
BulletUmbrellaCanopyWide: hpDivided(
weaponParams.mainWeapons[6010].CanopyHP,
),
BulletUmbrellaCanopyCompact: hpDivided(
weaponParams.mainWeapons[6020].CanopyHP,
),
BulletShelterCanopyFocus: hpDivided(
weaponParams.mainWeapons[6030].CanopyHP,
),
BulletUmbrellaCanopyWide: hpDivided(mainWeaponParams(6010).CanopyHP!),
BulletUmbrellaCanopyCompact: hpDivided(mainWeaponParams(6020).CanopyHP!),
BulletShelterCanopyFocus: hpDivided(mainWeaponParams(6030).CanopyHP!),
BulletUmbrellaCanopyNormal_Launched: SPLAT_BRELLA_SHIELD_HP * 2,
BulletUmbrellaCanopyWide_Launched: hpDivided(
weaponParams.mainWeapons[6010].CanopyHP * (10 / 6),
mainWeaponParams(6010).CanopyHP! * (10 / 6),
),
BulletShelterCanopyFocus_Launched: hpDivided(
weaponParams.mainWeapons[6030].CanopyHP * (10 / 6),
mainWeaponParams(6030).CanopyHP! * (10 / 6),
),
Wsb_Shield,
Bomb_TorpedoBullet: TORPEDO_HP,
Chariot: hpDivided(weaponParams.specialWeapons[CRAB_TANK_ID].ArmorHP),
Chariot: hpDivided(params.specialWeapons[CRAB_TANK_ID].ArmorHP),
Gachihoko_Barrier: RAINMAKER_HP,
GreatBarrier_Barrier,
GreatBarrier_WeakPoint,

View File

@ -12,15 +12,18 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";
import type {
BaseWeaponStats,
MainWeaponParams,
ParamsJson,
SubWeaponParams,
WeaponKit,
} from "~/features/build-analyzer/analyzer-types";
import {
type SpecialWeaponId,
SQUID_BEAKON_ID,
type SubWeaponId,
subWeaponIds,
weaponIdToBaseWeaponId,
} from "~/modules/in-game-lists/weapon-ids";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
@ -44,13 +47,20 @@ type SubWeapon = (typeof subWeapons)[number];
type SpecialWeapon = (typeof specialWeapons)[number];
type TranslationArray = Array<{ language: string; key: string; value: string }>;
const KIT_PROPERTIES = [
"SpecialPoint",
"subWeaponId",
"specialWeaponId",
] as const;
async function main() {
const mainWeaponsResult: Record<number, MainWeaponParams> = {};
const allMainWeaponParams: Record<number, MainWeaponParams> = {};
const subWeaponsResult: Record<number, SubWeaponParams> = {};
const specialWeaponsResult: any = {};
const translations: TranslationArray = [];
const langDicts = await loadLangDicts();
const hasLangDicts = langDicts.length > 0;
for (const weapon of weapons) {
if (mainWeaponShouldBeSkipped(weapon)) continue;
@ -60,30 +70,37 @@ async function main() {
parametersToMainWeaponResult(weapon, rawParams),
);
translationsToArray({
arr: translations,
internalName: weapon.__RowId,
weaponId: weapon.Id,
type: "Main",
translations: langDicts,
});
if (hasLangDicts) {
translationsToArray({
arr: translations,
internalName: weapon.__RowId,
weaponId: weapon.Id,
type: "Main",
translations: langDicts,
});
}
mainWeaponsResult[weapon.Id] = params;
allMainWeaponParams[weapon.Id] = params;
}
const { baseWeaponStats, weaponKits } =
splitIntoBaseStatsAndKits(allMainWeaponParams);
for (const subWeapon of subWeapons) {
if (subWeaponShouldBeSkipped(subWeapon)) continue;
const rawParams = loadWeaponParamsObject(subWeapon);
const params = parametersToSubWeaponResult(subWeapon, rawParams);
translationsToArray({
arr: translations,
internalName: subWeapon.__RowId,
weaponId: subWeapon.Id,
type: "Sub",
translations: langDicts,
});
if (hasLangDicts) {
translationsToArray({
arr: translations,
internalName: subWeapon.__RowId,
weaponId: subWeapon.Id,
type: "Sub",
translations: langDicts,
});
}
subWeaponsResult[subWeapon.Id] = params;
}
@ -94,30 +111,107 @@ async function main() {
const rawParams = loadWeaponParamsObject(specialWeapon);
const params = parametersToSpecialWeaponResult(rawParams);
translationsToArray({
arr: translations,
internalName: specialWeapon.__RowId,
weaponId: specialWeapon.Id,
type: "Special",
translations: langDicts,
});
if (hasLangDicts) {
translationsToArray({
arr: translations,
internalName: specialWeapon.__RowId,
weaponId: specialWeapon.Id,
type: "Special",
translations: langDicts,
});
}
specialWeaponsResult[specialWeapon.Id] = params;
}
const toFile: ParamsJson = {
mainWeapons: mainWeaponsResult,
baseWeaponStats,
weaponKits,
subWeapons: subWeaponsResult,
specialWeapons: specialWeaponsResult,
};
const weaponParamsTs = `export const weaponParams = ${JSON.stringify(toFile, null, "\t")} as const;\n`;
fs.writeFileSync(
path.join(__dirname, "output", "params.json"),
`${JSON.stringify(toFile, null, 2)}\n`,
path.join(
__dirname,
"..",
"app",
"features",
"build-analyzer",
"core",
"weapon-params.ts",
),
weaponParamsTs,
);
writeTranslationsJsons(translations);
logWeaponIds(mainWeaponsResult);
if (hasLangDicts) {
writeTranslationsJsons(translations);
}
logWeaponIds(weaponKits);
}
function splitIntoBaseStatsAndKits(
allParams: Record<number, MainWeaponParams>,
): {
baseWeaponStats: Record<number, BaseWeaponStats>;
weaponKits: Record<number, WeaponKit>;
} {
const weaponGroups: Record<
number,
Array<{ id: number; params: MainWeaponParams }>
> = {};
for (const [idStr, params] of Object.entries(allParams)) {
const id = Number(idStr);
const baseId = weaponIdToBaseWeaponId(id);
if (!weaponGroups[baseId]) weaponGroups[baseId] = [];
weaponGroups[baseId].push({ id, params });
}
const baseWeaponStats: Record<number, BaseWeaponStats> = {};
const weaponKits: Record<number, WeaponKit> = {};
for (const [baseIdStr, variants] of Object.entries(weaponGroups)) {
const baseId = Number(baseIdStr);
const firstVariant = variants[0].params;
const nonKitProps = Object.keys(firstVariant).filter(
(k) => !KIT_PROPERTIES.includes(k as (typeof KIT_PROPERTIES)[number]),
) as Array<keyof MainWeaponParams>;
const sharedProps: Partial<MainWeaponParams> = {};
for (const prop of nonKitProps) {
const firstVal = JSON.stringify(firstVariant[prop]);
const allSame = variants.every(
(v) => JSON.stringify(v.params[prop]) === firstVal,
);
if (allSame && firstVariant[prop] !== undefined) {
(sharedProps as any)[prop] = firstVariant[prop];
}
}
baseWeaponStats[baseId] = sharedProps as BaseWeaponStats;
for (const variant of variants) {
const kit: WeaponKit = {
SpecialPoint: variant.params.SpecialPoint,
subWeaponId: variant.params.subWeaponId,
specialWeaponId: variant.params.specialWeaponId,
};
for (const prop of nonKitProps) {
if (!(prop in sharedProps) && variant.params[prop] !== undefined) {
(kit as any)[prop] = variant.params[prop];
}
}
weaponKits[variant.id] = kit;
}
}
return { baseWeaponStats, weaponKits };
}
function parametersToMainWeaponResult(
@ -167,7 +261,8 @@ function parametersToMainWeaponResult(
const BlastParam_DistanceDamage = () => {
// REEF-LUX has distance damage listed in params
// but actually doesn't deal it in game
if (weapon.Id === 7020) return undefined;
if (weapon.Id === 7020 || weapon.Id === 7021 || weapon.Id === 7022)
return undefined;
return (
params.BlastParam?.DistanceDamage ??
@ -199,7 +294,8 @@ function parametersToMainWeaponResult(
};
const slosherDirectDamageSecondary = () => {
const isDreadWringer = weapon.Id === 3050 || weapon.Id === 3051;
const isDreadWringer =
weapon.Id === 3050 || weapon.Id === 3051 || weapon.Id === 3052;
if (!isDreadWringer) return;
const DamageParam_Secondary_ValueDirectMax =
@ -217,7 +313,7 @@ function parametersToMainWeaponResult(
params.WeaponKeepChargeParam?.KeepChargeFullFrame ??
params.spl__WeaponStringerParam?.ChargeKeepParam?.KeepChargeFullFrame;
const isSloshingMachine = weapon.Id === 3020;
const isSloshingMachine = weapon.Id === 3020 || weapon.Id === 3021;
const DamageParam_SplatanaHorizontalDirect =
params.BulletSaberHorizontalParam?.DamageParam?.HitDamage +
@ -330,7 +426,7 @@ function parametersToMainWeaponResult(
),
CanopyHP:
params.spl__BulletShelterCanopyParam?.CanopyHP ??
(weapon.Id === 6000 || weaponId === 6001 || weaponId === 6005
(weapon.Id === 6000 || weapon.Id === 6001 || weapon.Id === 6005
? 5000
: undefined),
ChargeFrameFullCharge:
@ -890,7 +986,7 @@ function writeTranslationsJsons(arr: TranslationArray) {
}
}
function logWeaponIds(weapons: Record<number, MainWeaponParams>) {
function logWeaponIds(weapons: Record<number, WeaponKit>) {
logger.info(JSON.stringify(Object.keys(weapons).map(Number)));
}