diff --git a/package.json b/package.json index 2e0991d8a..38cc2b880 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build:website": "next build", "build:docs": "vitepress build docs", "build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard", - "build:assets": "vite-node --script scripts/buildAssets", + "build:assets": "vite-node --script scripts/buildAssets/index", "build:test": "vite-node --script scripts/buildTest", "build:ratingsplot": "vite-node --script scripts/buildRatingsPlot", "dev:bot": "yarn build:bot && yarn start:bot", diff --git a/scripts/buildAssets/circleFlags.ts b/scripts/buildAssets/circleFlags.ts new file mode 100644 index 000000000..27c70fdf8 --- /dev/null +++ b/scripts/buildAssets/circleFlags.ts @@ -0,0 +1,36 @@ +import { readdir } from 'fs/promises'; +import { DATA, DOI } from '.'; +import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL'; +import commitMultipleFiles, { + FileChange, +} from '../../src/core/blitzkrieg/commitMultipleFiles'; + +export async function circleFlags() { + const flags = await readdir(`${DATA}/${DOI.flags}`); + + commitMultipleFiles( + 'tresabhi', + 'blitzkrieg-assets', + 'main', + 'circle flags', + await Promise.all( + flags + .filter( + (flag) => + flag.startsWith('flag_profile-stat_') && + !flag.endsWith('@2x.packed.webp.dvpl'), + ) + .map(async (flag) => { + const content = await readBase64DVPL(`${DATA}/${DOI.flags}/${flag}`); + const name = flag.match(/flag_profile-stat_(.+)\.packed\.webp/)![1]; + + return { + content, + encoding: 'base64', + path: `flags/circle/${name}.webp`, + } satisfies FileChange; + }), + ), + true, + ); +} diff --git a/scripts/buildAssets.ts b/scripts/buildAssets/definitions.ts similarity index 55% rename from scripts/buildAssets.ts rename to scripts/buildAssets/definitions.ts index b40e22ddc..e2943e70c 100644 --- a/scripts/buildAssets.ts +++ b/scripts/buildAssets/definitions.ts @@ -1,78 +1,24 @@ -import { NodeIO } from '@gltf-transform/core'; -import { config } from 'dotenv'; -import { readdir, writeFile } from 'fs/promises'; +import { readdir } from 'fs/promises'; import { parse } from 'path'; -import { argv } from 'process'; -import sharp from 'sharp'; import { Vector3Tuple } from 'three'; -import { TankType } from '../src/components/Tanks'; -import { NATION_IDS } from '../src/constants/nations'; -import { extractModel } from '../src/core/blitz/extractModel'; -import { readBase64DVPL } from '../src/core/blitz/readBase64DVPL'; -import { readDVPLFile } from '../src/core/blitz/readDVPLFile'; -import { readStringDVPL } from '../src/core/blitz/readStringDVPL'; -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 { ModelDefinitions } from '../src/core/blitzkrieg/modelDefinitions'; +import { DATA, DOI } from '.'; +import { TankType } from '../../src/components/Tanks'; +import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; +import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; +import { toUniqueId } from '../../src/core/blitz/toUniqueId'; +import commitMultipleFiles from '../../src/core/blitzkrieg/commitMultipleFiles'; +import { ModelDefinitions } from '../../src/core/blitzkrieg/modelDefinitions'; import { GunDefinition, ShellType, TankDefinitionPrice, TankDefinitions, Tier, -} from '../src/core/blitzkrieg/tankDefinitions'; +} from '../../src/core/blitzkrieg/tankDefinitions'; -config(); - -// WARNING! MOST OF THESE TYPES ARE NOT EXHAUSTIVE! - -interface VehicleCustomization { - armorColor: string; -} - -interface VehicleDefinitionList { - [key: string]: { - id: number; - userString: string; - shortUserString?: string; - description: string; - price: number | { gold: ''; '#text': number }; - sellPrice?: { gold: ''; '#text': number }; - enrichmentPermanentCost: number; - notInShop?: boolean; - tags: string; - level: number; - combatRole?: Record; - 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; - burst?: { count: number; rate: number }; - clip?: { count: number; rate: number }; - shots: { - [key: string]: { - speed: number; - piercingPower: string; - }; - }; - }; - }; +type BlitzTankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank'; +interface Strings { + [key: string]: string; } type ShellKind = | 'ARMOR_PIERCING' @@ -126,15 +72,44 @@ interface VehicleDefinitions { }; }; } -interface Strings { - [key: string]: string; +export interface VehicleDefinitionList { + [key: string]: { + id: number; + userString: string; + shortUserString?: string; + description: string; + price: number | { gold: ''; '#text': number }; + sellPrice?: { gold: ''; '#text': number }; + enrichmentPermanentCost: number; + notInShop?: boolean; + tags: string; + level: number; + combatRole?: Record; + configurationModes: string; + }; } -type BlitzTankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank'; -interface TankParameters { - resourcesPath: { - smallIconPath: string; - bigIconPath: string; - blitzModelPath: string; +interface TurretDefinitionsList { + nextAvailableId: number; + ids: Record; +} +interface GunDefinitionsList { + nextAvailableId: number; + ids: Record; + shared: { + [key: string]: { + userString: string; + tags: string; + level: number; + pitchLimits: string; + burst?: { count: number; rate: number }; + clip?: { count: number; rate: number }; + shots: { + [key: string]: { + speed: number; + piercingPower: string; + }; + }; + }; }; } @@ -150,30 +125,9 @@ const blitzShellKindToBLitzkrieg: Record = { HIGH_EXPLOSIVE: 'he', HOLLOW_CHARGE: 'hc', }; -const DATA = - 'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data'; const LANGUAGE = 'en'; -const DOI = { - vehicleDefinitions: 'XML/item_defs/vehicles', - strings: 'Strings', - tankParameters: '3d/Tanks/Parameters', - smallIcons: 'Gfx/UI/BattleScreenHUD/SmallTankIcons', - bigIcons: 'Gfx/UI/BigTankIcons', - flags: 'Gfx/Lobby/flags', - '3d': '3d', - bigShellIcons: 'Gfx/Shared/tank-supply/ammunition/big', - moduleIcons: 'Gfx/UI/ModulesTechTree', -}; -const allTargets = argv.includes('--all-targets'); -const targets = argv - .find((argument) => argument.startsWith('--target')) - ?.split('=')[1] - .split(','); - -if (!targets && !allTargets) throw new Error('No target(s) specified'); - -if (allTargets || targets?.includes('definitions')) { +export async function buildDefinitions() { console.log('Building tank definitions...'); const tankDefinitions: TankDefinitions = {}; @@ -435,279 +389,3 @@ if (allTargets || targets?.includes('definitions')) { true, ); } - -if ( - allTargets || - targets?.includes('bigTankIcons') || - targets?.includes('smallTankIcons') -) { - console.log('Building tank icons...'); - - const changes: FileChange[] = []; - const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( - (nations) => nations.filter((nation) => nation !== 'common'), - ); - - await Promise.all( - nations.map(async (nation) => { - const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( - `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, - ); - - 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( - `${DATA}/${DOI.tankParameters}/${nation}/${tankIndex}.yaml.dvpl`, - ); - const small = `${DATA}/${parameters.resourcesPath.smallIconPath - .replace(/~res:\//, '') - .replace(/\..+/, '')}.packed.webp.dvpl`; - const big = `${DATA}/${parameters.resourcesPath.bigIconPath - .replace(/~res:\//, '') - .replace(/\..+/, '')}.packed.webp.dvpl`; - - if (allTargets || targets?.includes('bigTankIcons')) { - changes.push({ - content: await readBase64DVPL(big), - encoding: 'base64', - path: `icons/tank/big/${id}.webp`, - }); - } - if (allTargets || targets?.includes('smallTankIcons')) { - changes.push({ - content: await readBase64DVPL(small), - encoding: 'base64', - path: `icons/tank/small/${id}.webp`, - }); - } - } - }), - ); - - console.log('Committing tank icons...'); - await commitMultipleFiles( - 'tresabhi', - 'blitzkrieg-assets', - 'main', - 'tank icons', - changes, - true, - ); -} - -if (allTargets || targets?.includes('scratchedFlags')) { - console.log('Building scratched flags...'); - - const flags = await readdir(`${DATA}/${DOI.flags}`); - - console.log('Committing scratched flags...'); - commitMultipleFiles( - 'tresabhi', - 'blitzkrieg-assets', - 'main', - 'scratched flags', - await Promise.all( - flags - .filter( - (flag) => - flag.startsWith('flag_tutor-tank_') && - !flag.endsWith('@2x.packed.webp.dvpl'), - ) - .map(async (flag) => { - const content = await readBase64DVPL(`${DATA}/${DOI.flags}/${flag}`); - const name = flag.match(/flag_tutor-tank_(.+)\.packed\.webp/)![1]; - - return { - content, - encoding: 'base64', - path: `flags/scratched/${name}.webp`, - } satisfies FileChange; - }), - ), - true, - ); -} - -if (allTargets || targets?.includes('circleFlags')) { - const flags = await readdir(`${DATA}/${DOI.flags}`); - - commitMultipleFiles( - 'tresabhi', - 'blitzkrieg-assets', - 'main', - 'circle flags', - await Promise.all( - flags - .filter( - (flag) => - flag.startsWith('flag_profile-stat_') && - !flag.endsWith('@2x.packed.webp.dvpl'), - ) - .map(async (flag) => { - const content = await readBase64DVPL(`${DATA}/${DOI.flags}/${flag}`); - const name = flag.match(/flag_profile-stat_(.+)\.packed\.webp/)![1]; - - return { - content, - encoding: 'base64', - path: `flags/circle/${name}.webp`, - } satisfies FileChange; - }), - ), - true, - ); -} - -if (allTargets || targets?.includes('tankModels')) { - console.log('Building tank models...'); - - // if (existsSync('dist/assets/models')) { - // await rm('dist/assets/models', { recursive: true }); - // } - - const nodeIO = new NodeIO(); - const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( - (nations) => nations.filter((nation) => nation !== 'common'), - ); - - await Promise.all( - nations.map(async (nation) => { - const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( - `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, - ); - const customization = await readXMLDVPL<{ root: VehicleCustomization }>( - `${DATA}/${DOI.vehicleDefinitions}/${nation}/customization.xml.dvpl`, - ); - const baseColor = customization.root.armorColor - .split(' ') - .slice(0, 3) - .map(Number) - .map((channel) => channel / 255) as Vector3Tuple; - - for (const tankIndex in tanks.root) { - const tank = tanks.root[tankIndex]; - const nationVehicleId = tank.id; - const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; - - // if (id !== 15697) continue; // chieftain - // if (id !== 24609) continue; // concept 1b - // if (id !== 16401) continue; // waffle - // if (id !== 7425) continue; // isu 152 - // if (id !== 10369) continue; // mino - // if (id !== 4417) continue; // amx m4 mle - // if (id !== 7297) continue; // 60tp - // if (id !== 1) continue; // t-34 - // if (id !== 6753) continue; // type 71 - // if (id !== 5137) continue; // tiger ii - // if (id !== 11633) continue; // forest witch - // if (id !== 6225) continue; // fv215b - // if (id !== 4481) continue; // kran - // if (id !== 9489) continue; // e100 - // if (id !== 12049) continue; // jg e100 - if (id !== 13569) continue; // jg e100 - - console.log(`Building model ${id} @ ${nation}/${tankIndex}`); - - const parameters = await readYAMLDVPL( - `${DATA}/${DOI.tankParameters}/${nation}/${tankIndex}.yaml.dvpl`, - ); - const model = await extractModel( - DATA, - parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''), - baseColor, - ); - - writeFile(`public/test/${id}.glb`, await nodeIO.writeBinary(model)); - // await mkdir(`dist/assets/models/${id}`, { recursive: true }); - // nodeIO.write(`dist/assets/models/${id}/index.gltf`, model); - } - }), - ); -} - -if (allTargets || targets?.includes('shellIcons')) { - console.log('Building shell icons...'); - - const image = sharp( - await readDVPLFile( - `${DATA}/${DOI.bigShellIcons}/texture0.packed.webp.dvpl`, - ), - ); - - const changes = await Promise.all( - (await readdir(`${DATA}/${DOI.bigShellIcons}`)) - .filter((file) => file.endsWith('_l.txt.dvpl')) - .map(async (file) => { - const name = file.match(/(.+)_l\.txt\.dvpl/)![1]; - const sizes = ( - await readStringDVPL(`${DATA}/${DOI.bigShellIcons}/${file}`) - ) - .split('\n')[4] - .split(' ') - .map(Number); - - return { - content: ( - await image - .clone() - .extract({ - left: sizes[0], - top: sizes[1], - width: sizes[2], - height: sizes[3], - }) - .toBuffer() - ).toString('base64'), - encoding: 'base64', - path: `icons/shells/${name}.webp`, - } satisfies FileChange; - }), - ); - - console.log('Committing shell icons...'); - commitMultipleFiles( - 'tresabhi', - 'blitzkrieg-assets', - 'main', - 'shell icons', - changes, - true, - ); -} - -if (allTargets || targets?.includes('moduleIcons')) { - console.log('Building module icons...'); - - const changes = await Promise.all( - (await readdir(`${DATA}/${DOI.moduleIcons}`)) - .filter( - (file) => - !file.endsWith('@2x.packed.webp.dvpl') && file.startsWith('vehicle'), - ) - .map(async (file) => { - const nameRaw = file.match(/vehicle(.+)\.packed\.webp\.dvpl/)![1]; - const name = nameRaw[0].toLowerCase() + nameRaw.slice(1); - const content = await readBase64DVPL( - `${DATA}/${DOI.moduleIcons}/${file}`, - ); - - return { - content, - path: `icons/modules/${name}.webp`, - encoding: 'base64', - } satisfies FileChange; - }), - ); - - console.log('Committing module icons...'); - commitMultipleFiles( - 'tresabhi', - 'blitzkrieg-assets', - 'main', - 'module icons', - changes, - true, - ); -} diff --git a/scripts/buildAssets/index.ts b/scripts/buildAssets/index.ts new file mode 100644 index 000000000..f76454b69 --- /dev/null +++ b/scripts/buildAssets/index.ts @@ -0,0 +1,74 @@ +import { config } from 'dotenv'; +import { argv } from 'process'; +import { circleFlags } from './circleFlags'; +import { buildDefinitions } from './definitions'; +import { buildModuleIcons } from './moduleIcons'; +import { buildScratchedFlags } from './scratchedFlags'; +import { buildShellIcons } from './shellIcons'; +import { buildTankArmors } from './tankArmors'; +import { buildTankIcons } from './tankIcons'; +import { buildTankModels } from './tankModels'; + +config(); + +export const DATA = + 'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data'; +export const DOI = { + vehicleDefinitions: 'XML/item_defs/vehicles', + strings: 'Strings', + tankParameters: '3d/Tanks/Parameters', + smallIcons: 'Gfx/UI/BattleScreenHUD/SmallTankIcons', + bigIcons: 'Gfx/UI/BigTankIcons', + flags: 'Gfx/Lobby/flags', + '3d': '3d', + bigShellIcons: 'Gfx/Shared/tank-supply/ammunition/big', + moduleIcons: 'Gfx/UI/ModulesTechTree', + collisionMeshes: '3d/Tanks/CollisionMeshes', +} as const; + +const allTargets = argv.includes('--all-targets'); +const targets = argv + .find((argument) => argument.startsWith('--target')) + ?.split('=')[1] + .split(','); + +if (!targets && !allTargets) throw new Error('No target(s) specified'); + +if (allTargets || targets?.includes('definitions')) { + await buildDefinitions(); +} + +if ( + allTargets || + targets?.includes('bigTankIcons') || + targets?.includes('smallTankIcons') +) { + await buildTankIcons( + allTargets || targets?.includes('bigTankIcons'), + allTargets || targets?.includes('smallTankIcons'), + ); +} + +if (allTargets || targets?.includes('scratchedFlags')) { + await buildScratchedFlags(); +} + +if (allTargets || targets?.includes('circleFlags')) { + await circleFlags(); +} + +if (allTargets || targets?.includes('tankModels')) { + await buildTankModels(); +} + +if (allTargets || targets?.includes('shellIcons')) { + await buildShellIcons(); +} + +if (allTargets || targets?.includes('moduleIcons')) { + await buildModuleIcons(); +} + +if (allTargets || targets?.includes('tankArmor')) { + await buildTankArmors(); +} diff --git a/scripts/buildAssets/moduleIcons.ts b/scripts/buildAssets/moduleIcons.ts new file mode 100644 index 000000000..8900f8a0f --- /dev/null +++ b/scripts/buildAssets/moduleIcons.ts @@ -0,0 +1,41 @@ +import { readdir } from 'fs/promises'; +import { DATA, DOI } from '.'; +import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL'; +import commitMultipleFiles, { + FileChange, +} from '../../src/core/blitzkrieg/commitMultipleFiles'; + +export async function buildModuleIcons() { + console.log('Building module icons...'); + + const changes = await Promise.all( + (await readdir(`${DATA}/${DOI.moduleIcons}`)) + .filter( + (file) => + !file.endsWith('@2x.packed.webp.dvpl') && file.startsWith('vehicle'), + ) + .map(async (file) => { + const nameRaw = file.match(/vehicle(.+)\.packed\.webp\.dvpl/)![1]; + const name = nameRaw[0].toLowerCase() + nameRaw.slice(1); + const content = await readBase64DVPL( + `${DATA}/${DOI.moduleIcons}/${file}`, + ); + + return { + content, + path: `icons/modules/${name}.webp`, + encoding: 'base64', + } satisfies FileChange; + }), + ); + + console.log('Committing module icons...'); + commitMultipleFiles( + 'tresabhi', + 'blitzkrieg-assets', + 'main', + 'module icons', + changes, + true, + ); +} diff --git a/scripts/buildAssets/scratchedFlags.ts b/scripts/buildAssets/scratchedFlags.ts new file mode 100644 index 000000000..1df8d2671 --- /dev/null +++ b/scripts/buildAssets/scratchedFlags.ts @@ -0,0 +1,39 @@ +import { readdir } from 'fs/promises'; +import { DATA, DOI } from '.'; +import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL'; +import commitMultipleFiles, { + FileChange, +} from '../../src/core/blitzkrieg/commitMultipleFiles'; + +export async function buildScratchedFlags() { + console.log('Building scratched flags...'); + + const flags = await readdir(`${DATA}/${DOI.flags}`); + + console.log('Committing scratched flags...'); + commitMultipleFiles( + 'tresabhi', + 'blitzkrieg-assets', + 'main', + 'scratched flags', + await Promise.all( + flags + .filter( + (flag) => + flag.startsWith('flag_tutor-tank_') && + !flag.endsWith('@2x.packed.webp.dvpl'), + ) + .map(async (flag) => { + const content = await readBase64DVPL(`${DATA}/${DOI.flags}/${flag}`); + const name = flag.match(/flag_tutor-tank_(.+)\.packed\.webp/)![1]; + + return { + content, + encoding: 'base64', + path: `flags/scratched/${name}.webp`, + } satisfies FileChange; + }), + ), + true, + ); +} diff --git a/scripts/buildAssets/shellIcons.ts b/scripts/buildAssets/shellIcons.ts new file mode 100644 index 000000000..3ff88207e --- /dev/null +++ b/scripts/buildAssets/shellIcons.ts @@ -0,0 +1,58 @@ +import { readdir } from 'fs/promises'; +import sharp from 'sharp'; +import { DATA, DOI } from '.'; +import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; +import { readStringDVPL } from '../../src/core/blitz/readStringDVPL'; +import commitMultipleFiles, { + FileChange, +} from '../../src/core/blitzkrieg/commitMultipleFiles'; + +export async function buildShellIcons() { + console.log('Building shell icons...'); + + const image = sharp( + await readDVPLFile( + `${DATA}/${DOI.bigShellIcons}/texture0.packed.webp.dvpl`, + ), + ); + + const changes = await Promise.all( + (await readdir(`${DATA}/${DOI.bigShellIcons}`)) + .filter((file) => file.endsWith('_l.txt.dvpl')) + .map(async (file) => { + const name = file.match(/(.+)_l\.txt\.dvpl/)![1]; + const sizes = ( + await readStringDVPL(`${DATA}/${DOI.bigShellIcons}/${file}`) + ) + .split('\n')[4] + .split(' ') + .map(Number); + + return { + content: ( + await image + .clone() + .extract({ + left: sizes[0], + top: sizes[1], + width: sizes[2], + height: sizes[3], + }) + .toBuffer() + ).toString('base64'), + encoding: 'base64', + path: `icons/shells/${name}.webp`, + } satisfies FileChange; + }), + ); + + console.log('Committing shell icons...'); + commitMultipleFiles( + 'tresabhi', + 'blitzkrieg-assets', + 'main', + 'shell icons', + changes, + true, + ); +} diff --git a/scripts/buildAssets/tankArmors.ts b/scripts/buildAssets/tankArmors.ts new file mode 100644 index 000000000..f60d1ae9a --- /dev/null +++ b/scripts/buildAssets/tankArmors.ts @@ -0,0 +1,37 @@ +import { readdir } from 'fs/promises'; +import { DATA, DOI } from '.'; +import { NATION_IDS } from '../../src/constants/nations'; +import { extractModel } from '../../src/core/blitz/extractModel'; +import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; +import { VehicleDefinitionList } from './definitions'; + +export async function buildTankArmors() { + console.log('Building tank armors...'); + + const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( + (nations) => nations.filter((nation) => nation !== 'common'), + ); + + await Promise.all( + nations.map(async (nation) => { + const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, + ); + + for (const tankKey in tanks.root) { + const tank = tanks.root[tankKey]; + const nationVehicleId = tank.id; + const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; + + if (id !== 24609) continue; // concept 1b + + const model = extractModel( + DATA, + `${DOI.collisionMeshes}/${nation}-${tankKey}`, + ); + + console.log(`Building armor ${id} @ ${nation}/${tankKey}`); + } + }), + ); +} diff --git a/scripts/buildAssets/tankIcons.ts b/scripts/buildAssets/tankIcons.ts new file mode 100644 index 000000000..a8ef3847e --- /dev/null +++ b/scripts/buildAssets/tankIcons.ts @@ -0,0 +1,75 @@ +import { readdir } from 'fs/promises'; +import { DATA, DOI } from '.'; +import { NATION_IDS } from '../../src/constants/nations'; +import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL'; +import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; +import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; +import commitMultipleFiles, { + FileChange, +} from '../../src/core/blitzkrieg/commitMultipleFiles'; +import { VehicleDefinitionList } from './definitions'; + +export interface TankParameters { + resourcesPath: { + smallIconPath: string; + bigIconPath: string; + blitzModelPath: string; + }; +} + +export async function buildTankIcons(big?: boolean, small?: boolean) { + console.log('Building tank icons...'); + + const changes: FileChange[] = []; + const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( + (nations) => nations.filter((nation) => nation !== 'common'), + ); + + await Promise.all( + nations.map(async (nation) => { + const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, + ); + + 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( + `${DATA}/${DOI.tankParameters}/${nation}/${tankIndex}.yaml.dvpl`, + ); + const small = `${DATA}/${parameters.resourcesPath.smallIconPath + .replace(/~res:\//, '') + .replace(/\..+/, '')}.packed.webp.dvpl`; + const big = `${DATA}/${parameters.resourcesPath.bigIconPath + .replace(/~res:\//, '') + .replace(/\..+/, '')}.packed.webp.dvpl`; + + if (big) { + changes.push({ + content: await readBase64DVPL(big), + encoding: 'base64', + path: `icons/tank/big/${id}.webp`, + }); + } + if (small) { + changes.push({ + content: await readBase64DVPL(small), + encoding: 'base64', + path: `icons/tank/small/${id}.webp`, + }); + } + } + }), + ); + + console.log('Committing tank icons...'); + await commitMultipleFiles( + 'tresabhi', + 'blitzkrieg-assets', + 'main', + 'tank icons', + changes, + true, + ); +} diff --git a/scripts/buildAssets/tankModels.ts b/scripts/buildAssets/tankModels.ts new file mode 100644 index 000000000..9cb4b3fa5 --- /dev/null +++ b/scripts/buildAssets/tankModels.ts @@ -0,0 +1,77 @@ +import { NodeIO } from '@gltf-transform/core'; +import { readdir, writeFile } from 'fs/promises'; +import { Vector3Tuple } from 'three'; +import { DATA, DOI } from '.'; +import { NATION_IDS } from '../../src/constants/nations'; +import { extractModel } from '../../src/core/blitz/extractModel'; +import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; +import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; +import { VehicleDefinitionList } from './definitions'; +import { TankParameters } from './tankIcons'; + +interface VehicleCustomization { + armorColor: string; +} + +export async function buildTankModels() { + console.log('Building tank models...'); + + const nodeIO = new NodeIO(); + const nations = await readdir(`${DATA}/${DOI.vehicleDefinitions}`).then( + (nations) => nations.filter((nation) => nation !== 'common'), + ); + + await Promise.all( + nations.map(async (nation) => { + const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`, + ); + const customization = await readXMLDVPL<{ root: VehicleCustomization }>( + `${DATA}/${DOI.vehicleDefinitions}/${nation}/customization.xml.dvpl`, + ); + const baseColor = customization.root.armorColor + .split(' ') + .slice(0, 3) + .map(Number) + .map((channel) => channel / 255) as Vector3Tuple; + + for (const tankKey in tanks.root) { + const tank = tanks.root[tankKey]; + const nationVehicleId = tank.id; + const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; + + // if (id !== 15697) continue; // chieftain + // if (id !== 24609) continue; // concept 1b + // if (id !== 16401) continue; // waffle + // if (id !== 7425) continue; // isu 152 + // if (id !== 10369) continue; // mino + // if (id !== 4417) continue; // amx m4 mle + // if (id !== 7297) continue; // 60tp + // if (id !== 1) continue; // t-34 + // if (id !== 6753) continue; // type 71 + // if (id !== 5137) continue; // tiger ii + // if (id !== 11633) continue; // forest witch + // if (id !== 6225) continue; // fv215b + // if (id !== 4481) continue; // kran + // if (id !== 9489) continue; // e100 + // if (id !== 12049) continue; // jg e100 + if (id !== 13569) continue; // jg e100 + + console.log(`Building model ${id} @ ${nation}/${tankKey}`); + + const parameters = await readYAMLDVPL( + `${DATA}/${DOI.tankParameters}/${nation}/${tankKey}.yaml.dvpl`, + ); + const model = await extractModel( + DATA, + parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''), + baseColor, + ); + + writeFile(`public/test/${id}.glb`, await nodeIO.writeBinary(model)); + // await mkdir(`dist/assets/models/${id}`, { recursive: true }); + // nodeIO.write(`dist/assets/models/${id}/index.gltf`, model); + } + }), + ); +} diff --git a/src/core/blitz/extractModel.ts b/src/core/blitz/extractModel.ts index fb8292873..991260554 100644 --- a/src/core/blitz/extractModel.ts +++ b/src/core/blitz/extractModel.ts @@ -43,7 +43,7 @@ const omitMeshNames = { export async function extractModel( data: string, path: string, - baseColor: Vector3Tuple, + baseColor?: Vector3Tuple, ) { const sc2Path = `${data}/3d/${path}.sc2.dvpl`; const scgPath = `${data}/3d/${path}.scg.dvpl`; @@ -85,7 +85,9 @@ export async function extractModel( .setImage( await readTexture( `${data}/3d/${dirname(path)}/${textures.albedo}`, - { mutation: TextureMutation.BaseColor, baseColor }, + baseColor + ? { mutation: TextureMutation.BaseColor, baseColor } + : undefined, ), ), );