diff --git a/scripts/buildAssets.ts b/scripts/buildAssets.ts index 526eda156..a9c8fa8ec 100644 --- a/scripts/buildAssets.ts +++ b/scripts/buildAssets.ts @@ -8,16 +8,21 @@ import { SCPGStream } from '../src/core/blitz/SCPGStream'; import { readBase64DVPL } from '../src/core/blitz/readBase64DVPL'; import { readXMLDVPL } from '../src/core/blitz/readXMLDVPL'; import { readYAMLDVPL } from '../src/core/blitz/readYAMLDVPL'; +import { toUniqueId } from '../src/core/blitz/toUniqueId'; import commitMultipleFiles, { FileChange, } from '../src/core/blitzkrieg/commitMultipleFiles'; +import { BlitzkriegGunDefinitions } from '../src/core/blitzkrieg/definitions/guns'; import { - TankDefinitions, + BlitzkriegTankDefinitions, Tier, } from '../src/core/blitzkrieg/definitions/tanks'; +import { BlitzkriegTurretDefinitions } from '../src/core/blitzkrieg/definitions/turrets'; config(); +// WARNING! MOST OF THESE TYPES ARE NOT EXHAUSTIVE! + interface VehicleDefinitionList { [key: string]: { id: number; @@ -34,13 +39,41 @@ interface VehicleDefinitionList { configurationModes: string; }; } +interface TurretDefinitionsList { + nextAvailableId: number; + ids: Record; +} +interface GunDefinitionsList { + nextAvailableId: number; + ids: Record; + shared: { + [key: string]: { + userString: string; + tags: string; + level: number; + pitchLimits: string; + }; + }; +} +interface VehicleDefinitions { + turrets0: { + [key: string]: { + userString: number; + level: number; + yawLimits: string | string[]; + guns: { + [key: string]: { + maxAmmo: number; + pitchLimits?: string | string[]; + }; + }; + }; + }; +} interface Strings { [key: string]: string; } type BlitzTankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank'; -/** - * Warning! This is not exhaustive. - */ interface TankParameters { resourcesPath: { smallIconPath: string; @@ -76,10 +109,12 @@ const targets = argv if (!targets && !allTargets) throw new Error('No target(s) specified'); -if (allTargets || targets?.includes('tankDefinitions')) { +if (allTargets || targets?.includes('definitions')) { console.log('Building tank definitions...'); - const tankDefinitions: TankDefinitions = {}; + const tankDefinitions: BlitzkriegTankDefinitions = {}; + const turretDefinitions: BlitzkriegTurretDefinitions = {}; + const gunDefinitions: BlitzkriegGunDefinitions = {}; const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( (nations) => nations.filter((nation) => nation !== 'common'), ); @@ -89,65 +124,120 @@ if (allTargets || targets?.includes('tankDefinitions')) { await Promise.all( nations.map(async (nation) => { - const tanks = await readXMLDVPL( + const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>( `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, ); + const turretList = await readXMLDVPL<{ + root: TurretDefinitionsList; + }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/components/turrets.xml.dvpl`, + ); + const gunList = await readXMLDVPL<{ + root: GunDefinitionsList; + }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/components/guns.xml.dvpl`, + ); - for (const tankIndex in tanks) { - const tank = tanks[tankIndex]; - const nationVehicleId = tank.id; - const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; - const name = strings[tank.userString] ?? tankIndex; - const name_short = tank.shortUserString - ? strings[tank.shortUserString] === name - ? undefined - : strings[tank.shortUserString] - : undefined; - const isPremium = - typeof tank.price === 'number' ? false : 'gold' in tank.price; - const isCollector = tank.sellPrice ? 'gold' in tank.sellPrice : false; - const tier = tank.level as Tier; - const tags = tank.tags.split(' '); - const type = blitzTankTypeToBlitzkrieg[tags[0] as BlitzTankType]; - const testing = tags.includes('testTank') ? true : undefined; + for (const tankKey in tankList.root) { + const tank = tankList.root[tankKey]; + const tankDefinition = await readXMLDVPL<{ root: VehicleDefinitions }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/${tankKey}.xml.dvpl`, + ); + const tankId = toUniqueId(nation, tank.id); + const tankTags = tank.tags.split(' '); + const tankTurrets = Object.keys(tankDefinition.root.turrets0).map( + (turretKey) => { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + const turretGuns = Object.keys(turret.guns).map((gunKey) => + toUniqueId(nation, gunList.root.ids[gunKey]), + ); - if ((name ?? name_short) === undefined) { - console.warn(`Missing name for ${tankIndex}`); - } + turretDefinitions[turretId] = { + id: turretId, + name: + strings[turret.userString] ?? + turretKey + .replaceAll('_', ' ') + .replace(/^(Turret ([0-9] )?)+/, ''), + tier: turret.level, + yaw: (typeof turret.yawLimits === 'string' + ? turret.yawLimits + : turret.yawLimits[0] + ) + .split(' ') + .map(Number) as [number, number], + guns: turretGuns, + }; - tankDefinitions[id] = { - id, - name, - name_short, + return turretId; + }, + ); + + tankDefinitions[tankId] = { + id: tankId, + name: + (tank.shortUserString + ? strings[tank.shortUserString] + : undefined) ?? + strings[tank.userString] ?? + tankKey.replaceAll('_', ' '), nation, - tree_type: isCollector + tree_type: (tank.sellPrice ? 'gold' in tank.sellPrice : false) ? 'collector' - : isPremium + : (typeof tank.price === 'number' ? false : 'gold' in tank.price) ? 'premium' : 'researchable', - tier, - type, - testing, + tier: tank.level as Tier, + type: blitzTankTypeToBlitzkrieg[tankTags[0] as BlitzTankType], + testing: tankTags.includes('testTank') ? true : undefined, + turrets: tankTurrets, }; } + + Object.keys(gunList.root.ids).forEach((gunKey) => { + const gun = gunList.root.shared[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + + gunDefinitions[gunId] = { + id: gunId, + name: strings[gun.userString] ?? gunKey.replaceAll('_', ' '), + tier: gun.level, + pitch: gun.pitchLimits.split(' ').map(Number) as [number, number], + }; + }); }), ); - console.log('Committing tank definitions...'); - await commitMultipleFiles( - 'tresabhi', - 'blitzkrieg-assets', - 'main', - 'tank definitions', - [ - { - content: JSON.stringify(tankDefinitions), - encoding: 'utf-8', - path: 'definitions/tanks.json', - }, - ], - true, - ); + // rmSync('dist/definitions', { force: true, recursive: true }); + // mkdirSync('dist/definitions', { recursive: true }); + // writeFile( + // 'dist/definitions/tanks.json', + // JSON.stringify(tankDefinitions, null, 2), + // ); + // writeFile( + // 'dist/definitions/turrets.json', + // JSON.stringify(turretDefinitions, null, 2), + // ); + // writeFile( + // 'dist/definitions/guns.json', + // JSON.stringify(gunDefinitions, null, 2), + // ); + // console.log('Committing tank definitions...'); + // await commitMultipleFiles( + // 'tresabhi', + // 'blitzkrieg-assets', + // 'main', + // 'tank definitions', + // [ + // { + // content: JSON.stringify(tankDefinitions), + // encoding: 'utf-8', + // path: 'definitions/tanks.json', + // }, + // ], + // true, + // ); } if ( @@ -164,12 +254,12 @@ if ( await Promise.all( nations.map(async (nation) => { - const tanks = await readXMLDVPL( + const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, ); - for (const tankIndex in tanks) { - const tank = tanks[tankIndex]; + for (const tankIndex in tanks.root) { + const tank = tanks.root[tankIndex]; const nationVehicleId = tank.id; const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; const parameters = await readYAMLDVPL( @@ -289,12 +379,12 @@ if (allTargets || targets?.includes('tankModels')) { await Promise.all( nations.map(async (nation) => { - const tanks = await readXMLDVPL( + const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, ); - for (const tankIndex in tanks) { - const tank = tanks[tankIndex]; + for (const tankIndex in tanks.root) { + const tank = tanks.root[tankIndex]; const nationVehicleId = tank.id; const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; diff --git a/src/app/embeds/session/page.tsx b/src/app/embeds/session/page.tsx index 7d50336d0..181b7e999 100644 --- a/src/app/embeds/session/page.tsx +++ b/src/app/embeds/session/page.tsx @@ -7,7 +7,7 @@ import { TreeTypeEnum } from '../../../components/Tanks'; import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; import fetchBlitz from '../../../core/blitz/fetchBlitz'; import { - TankDefinition, + BlitzkriegTankDefinition, tankDefinitions, } from '../../../core/blitzkrieg/definitions/tanks'; import { tankAverages } from '../../../core/blitzstars/tankAverages'; @@ -30,7 +30,7 @@ export default function SessionPage() { list: { stats: IndividualTankStats; - tankDefinitions?: TankDefinition; + tankDefinitions?: BlitzkriegTankDefinition; career: IndividualTankStats; currentWN8?: number; careerWN8?: number; diff --git a/src/app/tools/tankopedia/components/PageTurner.tsx b/src/app/tools/tankopedia/components/PageTurner.tsx index 533268263..231a6c690 100644 --- a/src/app/tools/tankopedia/components/PageTurner.tsx +++ b/src/app/tools/tankopedia/components/PageTurner.tsx @@ -1,12 +1,12 @@ import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons'; import { Button, Flex, TextField } from '@radix-ui/themes'; import { useEffect, useRef } from 'react'; -import { TankDefinition } from '../../../../core/blitzkrieg/definitions/tanks'; +import { BlitzkriegTankDefinition } from '../../../../core/blitzkrieg/definitions/tanks'; interface PageTurnerProps { page: number; setPage: (page: number) => void; - searchedList: TankDefinition[]; + searchedList: BlitzkriegTankDefinition[]; tanksPerPage: number; } diff --git a/src/app/tools/tankopedia/page.tsx b/src/app/tools/tankopedia/page.tsx index 24fb6be16..7aa402b20 100644 --- a/src/app/tools/tankopedia/page.tsx +++ b/src/app/tools/tankopedia/page.tsx @@ -20,11 +20,11 @@ import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react'; import PageWrapper from '../../../components/PageWrapper'; import { asset } from '../../../core/blitzkrieg/asset'; import { + BlitzkriegTankDefinition, TANK_ICONS, TANK_ICONS_COLLECTOR, TANK_ICONS_PREMIUM, TIER_ROMAN_NUMERALS, - TankDefinition, tanksDefinitionsArray, } from '../../../core/blitzkrieg/definitions/tanks'; import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; @@ -125,7 +125,7 @@ export default function Page() { 'name', 'name_short', 'id', - ] satisfies (keyof TankDefinition)[], + ] satisfies (keyof BlitzkriegTankDefinition)[], }).map(({ obj }) => obj), ); } diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 22526695f..00fb6f345 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -10,8 +10,8 @@ import getTankStats from '../core/blitz/getTankStats'; import getTreeType from '../core/blitz/getTreeType'; import resolveTankName from '../core/blitz/resolveTankName'; import { + BlitzkriegTankDefinition, TIER_ROMAN_NUMERALS, - TankDefinition, Tier, tankDefinitions, } from '../core/blitzkrieg/definitions/tanks'; @@ -48,7 +48,7 @@ export const ownedTanksCommand: CommandRegistry = { ) ).filter((tank) => tank.tankDefinitions?.tier === tier); const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']); - const groupedTanks: Record = {}; + const groupedTanks: Record = {}; const nations: string[] = []; filteredTanks.forEach((tank) => { diff --git a/src/core/blitz/readXMLDVPL.ts b/src/core/blitz/readXMLDVPL.ts index b40031619..9ace498ad 100644 --- a/src/core/blitz/readXMLDVPL.ts +++ b/src/core/blitz/readXMLDVPL.ts @@ -5,5 +5,5 @@ export async function readXMLDVPL(file: string) { const parser = new XMLParser(); const xml = await readStringDVPL(file); - return parser.parse(xml).root as Type; + return parser.parse(xml) as Type; } diff --git a/src/core/blitz/toUniqueId.ts b/src/core/blitz/toUniqueId.ts new file mode 100644 index 000000000..53eb45c6f --- /dev/null +++ b/src/core/blitz/toUniqueId.ts @@ -0,0 +1,5 @@ +import { NATION_IDS } from '../../constants/nations'; + +export function toUniqueId(nation: string, id: number) { + return (id << 8) + (NATION_IDS[nation] << 4) + 1; +} diff --git a/src/core/blitzkrieg/definitions/guns.ts b/src/core/blitzkrieg/definitions/guns.ts new file mode 100644 index 000000000..182c883bb --- /dev/null +++ b/src/core/blitzkrieg/definitions/guns.ts @@ -0,0 +1,8 @@ +export interface BlitzkriegGunDefinition { + id: number; + name: string; + tier: number; + pitch: [number, number]; +} + +export type BlitzkriegGunDefinitions = Record; diff --git a/src/core/blitzkrieg/definitions/tanks.ts b/src/core/blitzkrieg/definitions/tanks.ts index 801e1ac27..c9ca0f57c 100644 --- a/src/core/blitzkrieg/definitions/tanks.ts +++ b/src/core/blitzkrieg/definitions/tanks.ts @@ -2,27 +2,31 @@ import { deburr } from 'lodash'; import { TankType, TreeTypeString } from '../../../components/Tanks'; import { asset } from '../asset'; -export interface TankDefinition { +export interface BlitzkriegTankDefinition { id: number; nation: string; name: string; - name_short?: string; tree_type: TreeTypeString; tier: Tier; type: TankType; testing?: boolean; + turrets: number[]; } - -export type TankDefinitions = Record; +export type BlitzkriegTankDefinitions = Record< + number, + BlitzkriegTankDefinition +>; export const tankDefinitions = fetch(asset('definitions/tanks.json'), { cache: 'no-cache', -}).then(async (response) => response.json() as Promise); +}).then( + async (response) => response.json() as Promise, +); -const entries = new Promise(async (resolve) => { +const entries = new Promise(async (resolve) => { resolve(Object.entries(await tankDefinitions).map(([, entry]) => entry)); }); -export const tanksDefinitionsArray = new Promise( +export const tanksDefinitionsArray = new Promise( async (resolve) => { resolve((await entries).map((entry) => entry)); }, diff --git a/src/core/blitzkrieg/definitions/turrets.ts b/src/core/blitzkrieg/definitions/turrets.ts new file mode 100644 index 000000000..48bb6c050 --- /dev/null +++ b/src/core/blitzkrieg/definitions/turrets.ts @@ -0,0 +1,12 @@ +export interface BlitzkriegTurretDefinition { + id: number; + name: string; + tier: number; + yaw: [number, number]; + guns: number[]; +} + +export type BlitzkriegTurretDefinitions = Record< + number, + BlitzkriegTurretDefinition +>;