// To run this script you need from https://github.com/Leanny/leanny.github.io // 1) WeaponInfoMain.json inside dicts // 2) WeaponInfoSub.json inside dicts // 3) WeaponInfoSpecial.json inside dicts // xxx: internal name can be deleted when to prod import { type SubWeaponId, subWeaponIds, mainWeaponIds, } from "~/modules/in-game-lists"; import weapons from "./dicts/WeaponInfoMain.json"; // xxx: for example suction missing ink consume level, ink saver lvl... we are not considering default? import subWeapons from "./dicts/WeaponInfoSub.json"; import specialWeapons from "./dicts/WeaponInfoSpecial.json"; import fs from "node:fs"; import path from "node:path"; import invariant from "tiny-invariant"; import type { MainWeaponParams, SubWeaponParams } from "~/modules/analyzer"; import type { ParamsJson } from "~/modules/analyzer/types"; import { z } from "zod"; const CURRENT_SEASON = 1; type MainWeapon = typeof weapons[number]; type SubWeapon = typeof subWeapons[number]; function main() { const mainWeaponsResult: Record = {}; const subWeaponsResult: Record = {}; for (const weapon of weapons) { if (mainWeaponShouldBeSkipped(weapon)) continue; const rawParams = loadWeaponParamsObject(weapon); const params = parametersToMainWeaponResult(weapon, rawParams); mainWeaponsResult[weapon.Id] = params; } for (const subWeapon of subWeapons) { if (subWeaponShouldBeSkipped(subWeapon)) continue; const rawParams = loadWeaponParamsObject(subWeapon); const params = parametersToSubWeaponResult(subWeapon, rawParams); subWeaponsResult[subWeapon.Id] = params; } const toFile: ParamsJson = { mainWeapons: mainWeaponsResult, subWeapons: subWeaponsResult, }; fs.writeFileSync( path.join(__dirname, "output", `params.json`), JSON.stringify(toFile, null, 2) + "\n" ); } function parametersToMainWeaponResult( weapon: MainWeapon, params: any ): MainWeaponParams { return { SpecialPoint: weapon.SpecialPoint, subWeaponId: resolveSubWeaponId(weapon), specialWeaponId: resolveSpecialWeaponId(weapon), overwrites: resolveOverwrites(params), internalName: weapon.__RowId.replace("_00", ""), InkConsume: params["WeaponParam"]?.["InkConsume"], InkConsumeFullCharge: params["WeaponParam"]?.["InkConsumeFullCharge"], InkConsumeMinCharge: params["WeaponParam"]?.["InkConsumeMinCharge"], InkConsume_WeaponSwingParam: params["WeaponSwingParam"]?.["InkConsume"], InkConsumeMaxPerFrame_WeaponRollParam: params["WeaponRollParam"]?.["InkConsumeMaxPerFrame"], InkConsumeMinPerFrame_WeaponRollParam: params["WeaponRollParam"]?.["InkConsumeMinPerFrame"], InkConsume_WeaponVerticalSwingParam: params["WeaponVerticalSwingParam"]?.["InkConsume"], InkConsume_WeaponWideSwingParam: params["WeaponWideSwingParam"]?.["InkConsume"], InkConsumeUmbrella_WeaponShelterCanopyParam: params["spl__WeaponShelterCanopyParam"]?.["InkConsumeUmbrella"], InkConsume_WeaponShelterShotgunParam: params["spl__WeaponShelterShotgunParam"]?.["InkConsume"], InkConsume_SideStepParam: params["SideStepParam"]?.["InkConsume"], InkConsume_SwingParam: params["spl__WeaponSaberParam"]?.["SwingParam"]?.["InkConsume"], InkConsumeFullCharge_ChargeParam: params["spl__WeaponSaberParam"]?.["ChargeParam"]?.[ "InkConsumeFullCharge" ] ?? params["spl__WeaponStringerParam"]?.["ChargeParam"]?.[ "InkConsumeFullCharge" ], InkConsumeMinCharge_ChargeParam: params["spl__WeaponSaberParam"]?.["ChargeParam"]?.[ "InkConsumeMinCharge" ] ?? params["spl__WeaponStringerParam"]?.["ChargeParam"]?.[ "InkConsumeMinCharge" ], }; } function parametersToSubWeaponResult( subWeapon: SubWeapon, params: any ): SubWeaponParams { return { internalName: subWeapon.__RowId, // xxx: not every sub has this, why? e.g. Splash Wall SubInkSaveLv: params["SubWeaponSetting"]?.["SubInkSaveLv"], InkConsume: params["WeaponParam"]["InkConsume"], InkRecoverStop: params["WeaponParam"]["InkRecoverStop"], DistanceDamage: params["BlastParam"]?.["DistanceDamage"], DistanceDamage_BlastParamMaxCharge: params["BlastParamMaxCharge"]?.["DistanceDamage"], DistanceDamage_BlastParamMinCharge: params["BlastParamMinCharge"]?.["DistanceDamage"], DirectDamage: params["MoveParam"]?.["DirectDamage"] ?? params["MoveParam"]?.["DamageDirectHit"], DistanceDamage_BlastParamArray: params["MoveParam"]?.[ "BlastParamArray" ]?.map((b: any) => b.DistanceDamage), DistanceDamage_BlastParamChase: params["BlastParamChase"]?.["DistanceDamage"], DistanceDamage_SplashBlastParam: params["BlastParamChase"]?.["SplashBlastParam"]?.["DistanceDamage"], }; } function resolveSubWeaponId(weapon: MainWeapon) { const codeName = weapon.SubWeapon.replace("Work/Gyml/", "").replace( ".spl__WeaponInfoSub.gyml", "" ); const subWeaponObj = subWeapons.find((wpn) => codeName === wpn.__RowId); invariant(subWeaponObj, `Could not find sub weapon for '${weapon.__RowId}'`); invariant( subWeaponIds.includes(subWeaponObj.Id as any), `Invalid sub weapon id` ); return subWeaponObj.Id as SubWeaponId; } function resolveSpecialWeaponId(weapon: MainWeapon) { const codeName = weapon.SpecialWeapon.replace("Work/Gyml/", "").replace( ".spl__WeaponInfoSpecial.gyml", "" ); const specialWeaponObj = specialWeapons.find( (wpn) => codeName === wpn.__RowId ); invariant( specialWeaponObj, `Could not find special weapon for '${codeName}'` ); return specialWeaponObj.Id; } const overwriteSchema = z.object({ High: z.number().optional(), Mid: z.number().optional(), Low: z.number().optional(), }); function resolveOverwrites(params: any) { const result: MainWeaponParams["overwrites"] = {}; for (const [key, value] of Object.entries(params)) { const parsed = overwriteSchema.safeParse(value); // each object has a $type property which we ignore if ( key.includes("PlayerGearSkillParam") && parsed.success && Object.keys(parsed).length > 1 ) { const abilityKey = key.split("_").at(-1); invariant(abilityKey, `Could not find ability key for '${key}'`); if (!parsed.data.High && !parsed.data.Mid && !parsed.data.Low) { continue; } result[abilityKey] = { High: parsed.data.High, Mid: parsed.data.Mid, Low: parsed.data.Low, }; } } if (Object.keys(result).length === 0) return; return result; } function mainWeaponShouldBeSkipped(weapon: MainWeapon) { if (!mainWeaponIds.includes(weapon.Id as any)) return true; if (weapon.Season > CURRENT_SEASON) return true; return false; } function subWeaponShouldBeSkipped(subWeapon: SubWeapon) { if (subWeapon.Id === 10000) return true; if ( ["Mission", "Coop", "Hero", "Rival", "SalmonBuddy"].some((val) => subWeapon.__RowId.includes(val) ) ) { return true; } return false; } function loadWeaponParamsObject(weapon: MainWeapon | SubWeapon) { return JSON.parse( fs.readFileSync( path.join(__dirname, "dicts", "weapon", weaponRowIdToFileName(weapon)), "utf8" ) )["GameParameters"]; } function weaponRowIdToFileName(weapon: MainWeapon | SubWeapon) { const [category, codeName] = weapon.__RowId.split("_"); invariant(category); return `Weapon${category}${codeName ?? ""}.game__GameParameterTable.json`; } main();