// @ts-nocheck // 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 // 4) SplPlayer.game__GameParameterTable.json inside dicts // 5) params (weapon folder) inside dicts import fs from "node:fs"; 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"; import playersParams from "./dicts/SplPlayer.game__GameParameterTable.json"; import weapons from "./dicts/WeaponInfoMain.json"; import specialWeapons from "./dicts/WeaponInfoSpecial.json"; import subWeapons from "./dicts/WeaponInfoSub.json"; import { LANG_JSONS_TO_CREATE, loadLangDicts, translationJsonFolderName, } from "./utils"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const CURRENT_SEASON = 9; type MainWeapon = (typeof weapons)[number]; 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 allMainWeaponParams: Record = {}; const subWeaponsResult: Record = {}; const specialWeaponsResult: any = {}; const translations: TranslationArray = []; const langDicts = await loadLangDicts(); const hasLangDicts = langDicts.length > 0; for (const weapon of weapons) { if (mainWeaponShouldBeSkipped(weapon)) continue; const rawParams = loadWeaponParamsObject(weapon); const params = combineSwingsIfSame( parametersToMainWeaponResult(weapon, rawParams), ); if (hasLangDicts) { translationsToArray({ arr: translations, internalName: weapon.__RowId, weaponId: weapon.Id, type: "Main", translations: langDicts, }); } 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); if (hasLangDicts) { translationsToArray({ arr: translations, internalName: subWeapon.__RowId, weaponId: subWeapon.Id, type: "Sub", translations: langDicts, }); } subWeaponsResult[subWeapon.Id] = params; } for (const specialWeapon of specialWeapons) { if (specialWeaponShouldBeSkipped(specialWeapon)) continue; const rawParams = loadWeaponParamsObject(specialWeapon); const params = parametersToSpecialWeaponResult(rawParams); if (hasLangDicts) { translationsToArray({ arr: translations, internalName: specialWeapon.__RowId, weaponId: specialWeapon.Id, type: "Special", translations: langDicts, }); } specialWeaponsResult[specialWeapon.Id] = params; } const toFile: ParamsJson = { baseWeaponStats, weaponKits, subWeapons: subWeaponsResult, specialWeapons: specialWeaponsResult, }; const weaponParamsTs = `export const weaponParams = ${JSON.stringify(toFile, null, "\t")} as const;\n`; fs.writeFileSync( path.join( __dirname, "..", "app", "features", "build-analyzer", "core", "weapon-params.ts", ), weaponParamsTs, ); if (hasLangDicts) { writeTranslationsJsons(translations); } logWeaponIds(weaponKits); } function splitIntoBaseStatsAndKits( allParams: Record, ): { baseWeaponStats: Record; weaponKits: Record; } { 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 = {}; const weaponKits: Record = {}; 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; const sharedProps: Partial = {}; 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( weapon: MainWeapon, params: any, ): MainWeaponParams { const isSplatling = params.WeaponParam?.$type === "spl__WeaponSpinnerParam"; const isSlosher = params.WeaponParam?.$type === "spl__WeaponSlosherParam"; const InkConsume = !isSplatling && !isSlosher ? params.WeaponParam?.InkConsume : undefined; const InkConsumeFullChargeSplatling = isSplatling ? params.WeaponParam?.InkConsume : undefined; const InkConsumeSlosher = isSlosher ? params.WeaponParam?.InkConsume : undefined; // for blasters these values are the same and represent damage caused by direct const DamageParam_ValueDirect = params.DamageParam?.ValueMax && params.DamageParam?.ValueMax === params.DamageParam?.ValueMin ? params.DamageParam?.ValueMax : undefined; const MoveSpeed_Charge = () => { if (weapon.Id === 4010) return 0.062; return params.WeaponParam?.MoveSpeed_Charge; }; const DamageParam_ValueMax = () => { if (DamageParam_ValueDirect) return undefined; return ( params.DamageParam?.ValueMax ?? params.spl__BulletStringerParam?.DamageParam?.DirectHitDamageMax ?? params.spl__BulletShelterShotgunParam?.DamageEffectiveTotalMax ); }; const InkConsume_WeaponShelterShotgunParam = () => { return params.spl__WeaponShelterShotgunParam?.InkConsume; }; const BlastParam_DistanceDamage = () => { // REEF-LUX has distance damage listed in params // but actually doesn't deal it in game if (weapon.Id === 7020 || weapon.Id === 7021 || weapon.Id === 7022) return undefined; return ( params.BlastParam?.DistanceDamage ?? params.BlastParam?.BlastParam?.DistanceDamage ?? params.spl__BulletStringerParam?.DetonationParam?.BlastParam ?.DistanceDamage ); }; const slosherDirectDamage = () => { const DamageParam_ValueDirectMax = params.UnitGroupParam?.Unit?.[0]?.DamageParam?.ValueMax; const DamageParam_ValueDirectMin = params.UnitGroupParam?.Unit?.[0]?.DamageParam?.ValueMin; if ( DamageParam_ValueDirectMax && DamageParam_ValueDirectMax === DamageParam_ValueDirectMin ) { return { DamageParam_ValueDirect: DamageParam_ValueDirectMax, }; } return { DamageParam_ValueDirectMax, DamageParam_ValueDirectMin, }; }; const slosherDirectDamageSecondary = () => { const isDreadWringer = weapon.Id === 3050 || weapon.Id === 3051 || weapon.Id === 3052; if (!isDreadWringer) return; const DamageParam_Secondary_ValueDirectMax = params.UnitGroupParam?.Unit?.[1]?.DamageParam?.ValueMax; const DamageParam_Secondary_ValueDirectMin = params.UnitGroupParam?.Unit?.[1]?.DamageParam?.ValueMin; return { DamageParam_Secondary_ValueDirectMax, DamageParam_Secondary_ValueDirectMin, }; }; const KeepChargeFullFrame = params.WeaponKeepChargeParam?.KeepChargeFullFrame ?? params.spl__WeaponStringerParam?.ChargeKeepParam?.KeepChargeFullFrame; const isSloshingMachine = weapon.Id === 3020 || weapon.Id === 3021; const DamageParam_SplatanaHorizontalDirect = params.BulletSaberHorizontalParam?.DamageParam?.HitDamage + params.BulletSaberSlashHorizontalParam?.DamageParam?.DamageValue; const resolveMin = ( valueOne: number | null | undefined, valueTwo: number | null | undefined, ) => { if (typeof valueOne !== "number" && typeof valueTwo !== "number") return undefined; if (typeof valueOne !== "number") return valueTwo; if (typeof valueTwo !== "number") return valueOne; return Math.min(valueOne, valueTwo); }; const resolveMax = ( valueOne: number | null | undefined, valueTwo: number | null | undefined, ) => { if (typeof valueOne !== "number" && typeof valueTwo !== "number") return undefined; if (typeof valueOne !== "number") return valueTwo; if (typeof valueTwo !== "number") return valueOne; return Math.max(valueOne, valueTwo); }; return { SpecialPoint: weapon.SpecialPoint, subWeaponId: resolveSubWeaponId(weapon), specialWeaponId: resolveSpecialWeaponId(weapon), overwrites: resolveOverwrites(params), TripleShotSpanFrame: params.WeaponParam?.TripleShotSpanFrame, WeaponSpeedType: params.MainWeaponSetting?.WeaponSpeedType === "Mid" ? undefined : params.MainWeaponSetting?.WeaponSpeedType, MoveSpeed: params.WeaponParam?.MoveSpeed ?? params.spl__WeaponShelterShotgunParam?.MoveSpeed, MoveSpeed_Charge: MoveSpeed_Charge(), MoveSpeedFullCharge: params.WeaponParam?.MoveSpeedFullCharge ?? params.spl__WeaponStringerParam?.ChargeParam?.MoveSpeedFullCharge, MoveSpeedVariable: params.VariableShotParam?.MoveSpeed, DamageParam_ValueMax: DamageParam_ValueMax(), DamageParam_ValueMin: !DamageParam_ValueDirect ? (params.DamageParam?.ValueMin ?? params.spl__BulletStringerParam?.DamageParam?.DirectHitDamageMin) : undefined, DamageParam_ValueDirect, ...slosherDirectDamage(), ...slosherDirectDamageSecondary(), // Glooga turret mode damage DamageLapOverParam_ValueMax: params.DamageLapOverParam?.ValueMax, DamageLapOverParam_ValueMin: params.DamageLapOverParam?.ValueMin, BlastParam_SplashDamage: isSloshingMachine ? params.UnitGroupParam?.Unit?.[1]?.DamageParam?.ValueMax : undefined, BlastParam_DistanceDamage: BlastParam_DistanceDamage(), DamageParam_ValueFullCharge: params.DamageParam?.ValueFullCharge, DamageParam_ValueFullChargeMax: params.DamageParam?.ValueFullChargeMax !== DamageParam_ValueMax() ? params.DamageParam?.ValueFullChargeMax : undefined, DamageParam_ValueMaxCharge: params.DamageParam?.ValueMaxCharge, DamageParam_ValueMinCharge: params.DamageParam?.ValueMinCharge, DamageParam_SplatanaVerticalDirect: params.BulletSaberSlashVerticalParam?.DamageParam?.DamageValue, DamageParam_SplatanaVertical: params.BulletSaberVerticalParam?.DamageParam?.HitDamage, DamageParam_SplatanaHorizontalDirect: Number.isNaN( DamageParam_SplatanaHorizontalDirect, ) ? undefined : DamageParam_SplatanaHorizontalDirect, DamageParam_SplatanaHorizontal: params.BulletSaberHorizontalParam?.DamageParam?.HitDamage, BodyParam_Damage: params.BodyParam?.Damage, Variable_Damage_ValueMax: params.VariableDamageParam?.ValueMax, Variable_Damage_ValueMin: params.VariableDamageParam?.ValueMin, SwingUnitGroupParam_DamageParam_DamageMinValue: resolveMin( params.SwingUnitGroupParam?.DamageParam?.Inside?.DamageMinValue, params.SwingUnitGroupParam?.DamageParam?.Outside?.DamageMinValue, ), SwingUnitGroupParam_DamageParam_DamageMaxValue: resolveMax( params.SwingUnitGroupParam?.DamageParam?.Inside?.DamageMaxValue, params.SwingUnitGroupParam?.DamageParam?.Outside?.DamageMaxValue, ), // roller splash damages VerticalSwingUnitGroupParam_DamageParam_DamageMinValue: resolveMin( params.VerticalSwingUnitGroupParam?.DamageParam?.Inside?.DamageMinValue, params.VerticalSwingUnitGroupParam?.DamageParam?.Outside?.DamageMinValue, ), VerticalSwingUnitGroupParam_DamageParam_DamageMaxValue: resolveMax( params.VerticalSwingUnitGroupParam?.DamageParam?.Inside?.DamageMaxValue, params.VerticalSwingUnitGroupParam?.DamageParam?.Outside?.DamageMaxValue, ), WideSwingUnitGroupParam_DamageParam_DamageMinValue: resolveMin( params.WideSwingUnitGroupParam?.DamageParam?.Inside?.DamageMinValue, params.WideSwingUnitGroupParam?.DamageParam?.Outside?.DamageMinValue, ), WideSwingUnitGroupParam_DamageParam_DamageMaxValue: resolveMax( params.WideSwingUnitGroupParam?.DamageParam?.Inside?.DamageMaxValue, params.WideSwingUnitGroupParam?.DamageParam?.Outside?.DamageMaxValue, ), CanopyHP: params.spl__BulletShelterCanopyParam?.CanopyHP ?? (weapon.Id === 6000 || weapon.Id === 6001 || weapon.Id === 6005 ? 5000 : undefined), ChargeFrameFullCharge: params.WeaponParam?.ChargeFrameFullCharge ?? params.spl__WeaponStringerParam?.ChargeParam?.ChargeFrameFullCharge, KeepChargeFullFrame: KeepChargeFullFrame !== 1 ? KeepChargeFullFrame : undefined, Jump_DegSwerve: params.WeaponParam?.Jump_DegSwerve, Stand_DegSwerve: params.WeaponParam?.Stand_DegSwerve, Variable_Jump_DegSwerve: params.VariableWeaponParam?.Jump_DegSwerve ?? params.VariableShotParam?.Jump_DegSwerve, Variable_Stand_DegSwerve: params.VariableWeaponParam?.Stand_DegSwerve ?? params.VariableShotParam?.Stand_DegSwerve, InkRecoverStop: params.WeaponParam?.InkRecoverStop, InkConsume, InkConsumeSlosher, InkConsumeFullCharge: params.WeaponParam?.InkConsumeFullCharge, InkConsumeMinCharge: params.WeaponParam?.InkConsumeMinCharge, InkConsumeFullChargeSplatling, InkConsume_WeaponSwingParam: params.WeaponSwingParam?.InkConsume, InkConsume_WeaponVerticalSwingParam: params.WeaponVerticalSwingParam?.InkConsume, InkConsume_WeaponWideSwingParam: params.WeaponWideSwingParam?.InkConsume, InkConsumeUmbrella_WeaponShelterCanopyParam: params.spl__WeaponShelterCanopyParam?.InkConsumeUmbrella !== 0 ? params.spl__WeaponShelterCanopyParam?.InkConsumeUmbrella : undefined, InkConsume_WeaponShelterShotgunParam: InkConsume_WeaponShelterShotgunParam(), InkConsume_SideStepParam: params.SideStepParam?.InkConsume, InkConsume_SwingParam: params.spl__WeaponSaberParam?.SwingParam?.InkConsume, InkConsumeFullCharge_ChargeParam: params.spl__WeaponSaberParam?.ChargeParam?.InkConsumeFullCharge ?? params.spl__WeaponStringerParam?.ChargeParam?.InkConsumeFullCharge, }; } function combineSwingsIfSame(params: MainWeaponParams): MainWeaponParams { if ( !params.InkConsume_WeaponVerticalSwingParam || params.InkConsume_WeaponVerticalSwingParam !== params.InkConsume_WeaponWideSwingParam ) { return params; } return { ...params, InkConsume_WeaponSwingParam: params.InkConsume_WeaponVerticalSwingParam, InkConsume_WeaponVerticalSwingParam: undefined, InkConsume_WeaponWideSwingParam: undefined, }; } function parametersToSubWeaponResult( subWeapon: SubWeapon, params: any, ): SubWeaponParams { const SubInkSaveLv = params.SubWeaponSetting?.SubInkSaveLv ?? 2; return { overwrites: resolveSubWeaponOverwrites(subWeapon, params), SubInkSaveLv, InkConsume: params.WeaponParam.InkConsume ?? 0.7, InkRecoverStop: params.WeaponParam.InkRecoverStop, DistanceDamage: params.BlastParamMaxCharge?.DistanceDamage ? // curling bomb difference charge to same key [ params.BlastParamMaxCharge.DistanceDamage, params.BlastParamMinCharge?.DistanceDamage, ] : params.BlastParam?.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, }; } // some specials lack damage values in the params // so they are instead hardcoded here as a workaround function parametersToSpecialWeaponResult(params: any) { const result: any = {}; for (const parentValue of Object.values(params)) { for (const entries of Object.entries(parentValue as any)) { const [key, value]: any = entries; if ( key === "SubSpecialSpecUpList" || value.High !== value.Mid || value.Mid !== value.Low ) { result[key] = value; } for (const innerEntries of Object.entries(value)) { const [innerMostKey, innerMostValue]: any = innerEntries; if (typeof innerMostValue !== "object") continue; if ( innerMostKey === "SubSpecialSpecUpList" || innerMostValue.High !== innerMostValue.Mid || innerMostValue.Mid !== innerMostValue.Low ) { result[innerMostKey] = innerMostValue; } } } } // for Ultra Splashdown if (params.BlastParamDokanWarp) { result.SubSpecialSpecUpList = params.BlastParamDokanWarp.SubSpecialSpecUpList; } const resultUnwrapped = unwrapSubSpecialSpecUpList(result); const specialDurationFrameKeyAlises = [ "LaserFrame", "RainyFrame", "SpecialTotalFrame", ]; for (const key of specialDurationFrameKeyAlises) { if (!resultUnwrapped[key]) continue; resultUnwrapped.SpecialDurationFrame = resultUnwrapped[key]; resultUnwrapped[key] = undefined; } if (resultUnwrapped.SplashAroundPaintRadius) { // Inkjet if (params.BlastParam?.PaintRadius) { const regularPaintRadius = params.BlastParam.PaintRadius; resultUnwrapped.PaintRadius = { High: resultUnwrapped.SplashAroundPaintRadius.High + regularPaintRadius, Mid: resultUnwrapped.SplashAroundPaintRadius.Mid + regularPaintRadius, Low: resultUnwrapped.SplashAroundPaintRadius.Low + regularPaintRadius, }; // Reefslider } else { resultUnwrapped.PaintRadius = { High: resultUnwrapped.SplashAroundPaintRadius.High + resultUnwrapped.PaintRadius.High, Mid: resultUnwrapped.SplashAroundPaintRadius.Mid + resultUnwrapped.PaintRadius.Mid, Low: resultUnwrapped.SplashAroundPaintRadius.Low + resultUnwrapped.PaintRadius.Low, }; } resultUnwrapped.SplashAroundPaintRadius = undefined; } // Bubble Patch 7.2.0 change hack if (resultUnwrapped.MaxFieldHP) { resultUnwrapped.MaxFieldHP = { High: 28800, Low: 24000, Mid: 26400, }; } const isUltraStamp = () => !!params.SwingBigBlastParam; const isCrabTank = () => !!params.CannonParam; const isKraken = () => !!params.BodyParam?.DamageJumpValue; const isInkjet = () => !!params.JetParam; const isScreen = () => !!params.WallParam; const isInkStorm = () => !!params.CloudParam; const isInkstrike = () => !!params.MotherParam; const isBooyahBomb = () => params.BlastParam?.$type === "spl__BulletSpNiceBallBlastParam"; const SwingDamage = () => { if (!isUltraStamp()) return; return [ { Damage: 1000, Distance: 0 }, ...params.SwingBigBlastParam.DistanceDamage, ]; }; const ThrowDamage = () => { if (!isUltraStamp()) return; return params.ThrowBlastParam.DistanceDamage; }; const Cannon = () => { if (!isCrabTank()) return; invariant( params.CannonParam.BlastParam.DistanceDamage, "Could not find cannon distance damage", ); return [ { Damage: 600, Distance: 1, }, ...params.CannonParam.BlastParam.DistanceDamage, ]; }; const KrakenDirectDamage = () => { if (!isKraken()) return; return 1200; }; const InkjetDirectDamage = () => { if (!isInkjet()) return; return 1200; }; const ScreenDirectDamage = () => { if (!isScreen()) return; return 400; }; const BooyahBombTickDamage = () => { if (!isBooyahBomb()) return; return 33; }; const InkStormTickDamage = () => { if (!isInkStorm()) return; return 4; }; const InkstrikeTickDamage = () => { if (!isInkstrike()) return; return 75; }; return { ArmorHP: params.WeaponSpChariotParam?.ArmorHP, overwrites: resultUnwrapped, DistanceDamage: params.BlastParam?.DistanceDamage ?? params.HookBlastParam?.DistanceDamage ?? params.spl__BulletBlastParam?.DistanceDamage ?? params.BulletBlastParam?.DistanceDamage ?? params.IceParam?.BlastParam?.DistanceDamage ?? params.BlastParamNormal?.DistanceDamage, DirectDamage: params.DamageParam?.DirectHitDamage ?? params.spl__BulletSpShockSonarParam?.GeneratorParam?.HitDamage ?? KrakenDirectDamage() ?? InkjetDirectDamage() ?? ScreenDirectDamage(), WaveDamage: params.spl__BulletSpShockSonarParam?.WaveParam?.Damage, ExhaleBlastParamMinChargeDistanceDamage: params.ExhaleBlastParamMinCharge?.DistanceDamage, ExhaleBlastParamMaxChargeDistanceDamage: params.ExhaleBlastParamMaxCharge?.DistanceDamage, SwingDamage: SwingDamage(), ThrowDamage: ThrowDamage(), ThrowDirectDamage: params.ThrowMoveParam?.DirectDamageValue, BulletDamageMin: params.ShooterDamageParam?.ValueMin, BulletDamageMax: params.ShooterDamageParam?.ValueMax, CannonDamage: Cannon(), BumpDamage: isCrabTank() ? 400 : undefined, JumpDamage: params.BodyParam?.DamageJumpValue, TickDamage: BooyahBombTickDamage() ?? InkStormTickDamage() ?? InkstrikeTickDamage() ?? params.spl__BulletSpMicroLaserBitParam?.LaserParam?.LaserDamage, }; } function unwrapSubSpecialSpecUpList(result: any) { return Object.fromEntries( Object.entries(result).flatMap((entries) => { const [key, value]: any = entries; if (Array.isArray(value)) { return value.map((v: any) => { if ( !v.SpecUpType || (v.Value.Low === v.Value.Mid && v.Value.Mid === v.Value.High) ) { return []; } return [v.SpecUpType, v.Value]; }); } return [[key, value]]; }), ); } 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 as SpecialWeaponId; } 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); resolveOverwritesWithArbitraryKeys(result, 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 resolveOverwritesWithArbitraryKeys( result: NonNullable, paramsObj: unknown, ) { for (const [key, value] of Object.entries( paramsObj as Record, )) { if (!key.startsWith("Overwrite_")) continue; if (value === -1) continue; let abilityKey = key.replace("Overwrite_", ""); for (const type of ["High", "Mid", "Low"] as const) { const suffix = `_${type}`; if (!abilityKey.endsWith(suffix)) continue; abilityKey = abilityKey.replace(suffix, ""); if (!result[abilityKey]) result[abilityKey] = {}; result[abilityKey]![type] = value; } } } function resolveSubWeaponOverwrites(subWeapon: SubWeapon, 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 ?? params.AreaParam?.MarkingFrameSubSpec, SensorRadius: params.MoveParam?.SensorRadius, ExplosionRadius: params.AreaParam?.Distance, MaxHP: params.MoveParam?.MaxHP, SubSpecUpParam: (subWeapon.Id === SQUID_BEAKON_ID ? { Low: 0.0, ...playersParams.GameParameters.spl__PlayerBeaconSubSpecUpParam .SubSpecUpParam, } : undefined) as any, }; 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", "Hero", "Rival", "SalmonBuddy", "Sdodr", ]; const INTERNAL_WEAPON_NAMES_TO_IGNORE: readonly string[] = ["Free"] as const; function mainWeaponShouldBeSkipped(mainWeapon: MainWeapon) { if ( WEAPON_TYPES_TO_IGNORE.includes(mainWeapon.Type) || INTERNAL_WEAPON_NAMES_TO_IGNORE.includes(mainWeapon.__RowId) || mainWeapon.Season > CURRENT_SEASON ) { return true; } return false; } function subWeaponShouldBeSkipped(subWeapon: SubWeapon) { if (subWeapon.Id === 10000) return true; if (WEAPON_TYPES_TO_IGNORE.some((val) => subWeapon.__RowId.includes(val))) { return true; } return false; } function specialWeaponShouldBeSkipped(specialWeapon: SpecialWeapon) { if ( WEAPON_TYPES_TO_IGNORE.some((val) => specialWeapon.Type.includes(val)) || WEAPON_TYPES_TO_IGNORE.some((val) => specialWeapon.__RowId.includes(val)) ) { return true; } if (specialWeapon.__RowId === "SpGachihoko") return true; if (specialWeapon.__RowId === "SpGachihokoForEventMatch") return true; return false; } function loadWeaponParamsObject( weapon: MainWeapon | SubWeapon | SpecialWeapon, ) { return JSON.parse( fs.readFileSync( path.join(__dirname, "dicts", "weapon", weaponRowIdToFileName(weapon)), "utf8", ), ).GameParameters; } function weaponRowIdToFileName(weapon: MainWeapon | SubWeapon | SpecialWeapon) { const [category, codeName] = weapon.__RowId.split("_"); invariant(category); return `Weapon${category}${codeName ?? ""}.game__GameParameterTable.json`; } function translationsToArray({ arr, internalName, weaponId, type, translations, }: { arr: TranslationArray; internalName: string; weaponId: number; type: "Main" | "Sub" | "Special"; translations: [ langCode: string, translations: Record>, ][]; }) { for (const langCode of LANG_JSONS_TO_CREATE) { const translationOfLanguage = translations.find((t) => t[0] === langCode); invariant( translationOfLanguage, `Could not find translation for '${langCode}'`, ); const value = translationOfLanguage[1][`CommonMsg/Weapon/WeaponName_${type}`]?.[ internalName ]; invariant(value, `Could not find translation for '${internalName}'`); arr.push({ key: `${type.toUpperCase()}_${weaponId}`, language: langCode, value, }); } } function writeTranslationsJsons(arr: TranslationArray) { for (const langCode of LANG_JSONS_TO_CREATE) { fs.writeFileSync( path.join( __dirname, "..", "locales", translationJsonFolderName(langCode), "weapons.json", ), `${JSON.stringify( Object.fromEntries( arr .filter((val) => val.language === langCode) .map(({ key, value }) => [key, value]), ), null, "\t", )}\n`, ); } } function logWeaponIds(weapons: Record) { logger.info(JSON.stringify(Object.keys(weapons).map(Number))); } void main();