2024-01-07 20:07:45 -06:00
|
|
|
import { readdir } from 'fs/promises';
|
2024-01-01 14:00:41 -06:00
|
|
|
import { parse } from 'path';
|
2023-12-28 20:58:18 -06:00
|
|
|
import { Vector3Tuple } from 'three';
|
2024-01-07 20:07:45 -06:00
|
|
|
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';
|
2024-01-09 18:35:42 -06:00
|
|
|
import {
|
|
|
|
|
ModelArmor,
|
|
|
|
|
ModelDefinitions,
|
|
|
|
|
} from '../../src/core/blitzkrieg/modelDefinitions';
|
2023-12-12 15:12:09 -06:00
|
|
|
import {
|
2023-12-14 22:19:01 -06:00
|
|
|
GunDefinition,
|
2023-12-12 15:12:09 -06:00
|
|
|
ShellType,
|
2023-12-17 20:47:30 -06:00
|
|
|
TankDefinitionPrice,
|
2023-12-14 22:19:01 -06:00
|
|
|
TankDefinitions,
|
2023-12-05 17:36:18 -06:00
|
|
|
Tier,
|
2024-01-07 20:07:45 -06:00
|
|
|
} from '../../src/core/blitzkrieg/tankDefinitions';
|
2024-01-16 16:58:00 -06:00
|
|
|
import { DATA, POI } from './constants';
|
2023-12-04 10:13:14 -06:00
|
|
|
|
2024-01-07 20:07:45 -06:00
|
|
|
type BlitzTankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank';
|
|
|
|
|
interface Strings {
|
|
|
|
|
[key: string]: string;
|
2023-12-12 13:40:58 -06:00
|
|
|
}
|
2023-12-12 15:12:09 -06:00
|
|
|
type ShellKind =
|
|
|
|
|
| 'ARMOR_PIERCING'
|
|
|
|
|
| 'ARMOR_PIERCING_CR'
|
|
|
|
|
| 'HIGH_EXPLOSIVE'
|
|
|
|
|
| 'HOLLOW_CHARGE';
|
|
|
|
|
type ShellDefinitionsList = Record<
|
|
|
|
|
string,
|
|
|
|
|
{
|
|
|
|
|
id: number;
|
|
|
|
|
userString: string;
|
|
|
|
|
icon: string;
|
|
|
|
|
kind: ShellKind;
|
|
|
|
|
caliber: number;
|
|
|
|
|
damage: { armor: number; devices: number };
|
|
|
|
|
normalizationAngle: number;
|
|
|
|
|
ricochetAngle: number;
|
|
|
|
|
}
|
|
|
|
|
> & {
|
|
|
|
|
icons: Record<string, string>;
|
|
|
|
|
};
|
2024-01-09 18:35:42 -06:00
|
|
|
type VehicleDefinitionArmor = Record<
|
|
|
|
|
string,
|
|
|
|
|
number | { vehicleDamageFactor: 0; '#text': number }
|
|
|
|
|
>;
|
2023-12-12 13:40:58 -06:00
|
|
|
interface VehicleDefinitions {
|
2023-12-17 20:47:30 -06:00
|
|
|
invisibility: { moving: number; still: number; firePenalty: number };
|
2024-01-16 16:58:00 -06:00
|
|
|
optDevicePreset: string;
|
2023-12-31 19:26:08 -06:00
|
|
|
hull: {
|
2024-01-09 18:35:42 -06:00
|
|
|
armor: VehicleDefinitionArmor;
|
2024-01-03 18:52:22 -06:00
|
|
|
turretPositions: { turret: string };
|
|
|
|
|
turretInitialRotation?: { yaw: 0; pitch: 6.5; roll: 0 };
|
2023-12-31 19:26:08 -06:00
|
|
|
};
|
2024-01-16 08:40:27 -06:00
|
|
|
chassis: {
|
|
|
|
|
[key: string]: {
|
|
|
|
|
armor: {
|
|
|
|
|
leftTrack:
|
|
|
|
|
| number
|
|
|
|
|
| {
|
|
|
|
|
chanceToHitByProjectile: number;
|
|
|
|
|
chanceToHitByExplosion: number;
|
|
|
|
|
'#text': number;
|
|
|
|
|
};
|
|
|
|
|
rightTrack:
|
|
|
|
|
| number
|
|
|
|
|
| {
|
|
|
|
|
chanceToHitByProjectile: number;
|
|
|
|
|
chanceToHitByExplosion: number;
|
|
|
|
|
'#text': number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-12-12 13:40:58 -06:00
|
|
|
turrets0: {
|
|
|
|
|
[key: string]: {
|
2024-01-09 18:35:42 -06:00
|
|
|
armor: VehicleDefinitionArmor;
|
2023-12-12 13:40:58 -06:00
|
|
|
userString: number;
|
|
|
|
|
level: number;
|
|
|
|
|
yawLimits: string | string[];
|
2023-12-31 19:26:08 -06:00
|
|
|
gunPosition: string | string[];
|
2024-01-01 14:00:41 -06:00
|
|
|
models: { undamaged: string };
|
2023-12-12 13:40:58 -06:00
|
|
|
guns: {
|
|
|
|
|
[key: string]: {
|
2024-01-16 08:40:27 -06:00
|
|
|
armor: VehicleDefinitionArmor & {
|
|
|
|
|
gun?:
|
|
|
|
|
| number
|
|
|
|
|
| {
|
|
|
|
|
chanceToHitByProjectile: number;
|
|
|
|
|
chanceToHitByExplosion: number;
|
|
|
|
|
'#text': number;
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-12-14 22:19:01 -06:00
|
|
|
reloadTime: number;
|
2023-12-12 13:40:58 -06:00
|
|
|
maxAmmo: number;
|
2024-01-02 20:17:10 -06:00
|
|
|
extraPitchLimits?: {
|
|
|
|
|
front?: string;
|
|
|
|
|
back?: string;
|
|
|
|
|
transition?: number | number[];
|
|
|
|
|
};
|
2023-12-12 13:40:58 -06:00
|
|
|
pitchLimits?: string | string[];
|
2023-12-14 22:19:01 -06:00
|
|
|
pumpGunMode?: boolean;
|
|
|
|
|
pumpGunReloadTimes?: string;
|
|
|
|
|
clip?: { count: number; rate: number };
|
2024-01-01 14:48:46 -06:00
|
|
|
models: { undamaged: string };
|
2023-12-12 13:40:58 -06:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-01-07 20:07:45 -06:00
|
|
|
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<string, string>;
|
|
|
|
|
configurationModes: string;
|
|
|
|
|
};
|
2023-12-05 19:14:53 -06:00
|
|
|
}
|
2024-01-07 20:07:45 -06:00
|
|
|
interface TurretDefinitionsList {
|
|
|
|
|
nextAvailableId: number;
|
|
|
|
|
ids: Record<string, number>;
|
|
|
|
|
}
|
|
|
|
|
interface GunDefinitionsList {
|
|
|
|
|
nextAvailableId: number;
|
|
|
|
|
ids: Record<string, number>;
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-12-06 10:17:42 -06:00
|
|
|
};
|
|
|
|
|
}
|
2023-12-05 19:14:53 -06:00
|
|
|
|
2024-01-16 16:58:00 -06:00
|
|
|
export interface OptionalDevices {
|
|
|
|
|
[key: string]: {
|
|
|
|
|
id: number;
|
|
|
|
|
userString: string;
|
|
|
|
|
description: string;
|
|
|
|
|
icon: string;
|
|
|
|
|
script: unknown;
|
|
|
|
|
display_params: unknown;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface OptionalDeviceSlots {
|
|
|
|
|
presets: {
|
|
|
|
|
[key: string]: {
|
|
|
|
|
level0: OptionalDeviceSlotRow;
|
|
|
|
|
level1: OptionalDeviceSlotRow;
|
|
|
|
|
level2: OptionalDeviceSlotRow;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface OptionalDeviceSlotRow {
|
|
|
|
|
attack: {
|
|
|
|
|
device0: string;
|
|
|
|
|
device1: string;
|
|
|
|
|
};
|
|
|
|
|
defence: {
|
|
|
|
|
device0: string;
|
|
|
|
|
device1: string;
|
|
|
|
|
};
|
|
|
|
|
support: {
|
|
|
|
|
device0: string;
|
|
|
|
|
device1: string;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-05 19:14:53 -06:00
|
|
|
const blitzTankTypeToBlitzkrieg: Record<BlitzTankType, TankType> = {
|
|
|
|
|
'AT-SPG': 'tank_destroyer',
|
|
|
|
|
lightTank: 'light',
|
|
|
|
|
mediumTank: 'medium',
|
|
|
|
|
heavyTank: 'heavy',
|
|
|
|
|
};
|
2023-12-12 15:12:09 -06:00
|
|
|
const blitzShellKindToBLitzkrieg: Record<ShellKind, ShellType> = {
|
|
|
|
|
ARMOR_PIERCING: 'ap',
|
2023-12-12 23:35:08 -06:00
|
|
|
ARMOR_PIERCING_CR: 'ap_cr',
|
2023-12-12 15:12:09 -06:00
|
|
|
HIGH_EXPLOSIVE: 'he',
|
2023-12-12 23:35:08 -06:00
|
|
|
HOLLOW_CHARGE: 'hc',
|
2023-12-12 15:12:09 -06:00
|
|
|
};
|
2023-11-27 17:50:02 -06:00
|
|
|
const LANGUAGE = 'en';
|
|
|
|
|
|
2024-01-07 20:07:45 -06:00
|
|
|
export async function buildDefinitions() {
|
2023-12-05 19:14:53 -06:00
|
|
|
console.log('Building tank definitions...');
|
2023-11-06 23:02:26 -06:00
|
|
|
|
2023-12-31 19:26:08 -06:00
|
|
|
const tankDefinitions: TankDefinitions = {};
|
2024-01-02 20:17:10 -06:00
|
|
|
const modelDefinitions: ModelDefinitions = {};
|
2024-01-16 16:58:00 -06:00
|
|
|
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
|
2023-12-05 19:14:53 -06:00
|
|
|
(nations) => nations.filter((nation) => nation !== 'common'),
|
|
|
|
|
);
|
|
|
|
|
const strings = await readYAMLDVPL<Strings>(
|
2024-01-16 16:58:00 -06:00
|
|
|
`${DATA}/${POI.strings}/${LANGUAGE}.yaml.dvpl`,
|
2023-12-05 17:36:18 -06:00
|
|
|
);
|
2024-01-16 16:58:00 -06:00
|
|
|
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
|
|
|
|
|
`${DATA}/${POI.optionalDevices}`,
|
|
|
|
|
);
|
|
|
|
|
const optionalDeviceSlots = await readXMLDVPL<{
|
|
|
|
|
root: OptionalDeviceSlots;
|
|
|
|
|
}>(`${DATA}/${POI.optionalDeviceSlots}`);
|
2023-12-05 17:36:18 -06:00
|
|
|
|
2023-12-05 19:14:53 -06:00
|
|
|
await Promise.all(
|
|
|
|
|
nations.map(async (nation) => {
|
2023-12-12 13:40:58 -06:00
|
|
|
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
2024-01-16 16:58:00 -06:00
|
|
|
`${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
|
2023-12-05 19:14:53 -06:00
|
|
|
);
|
2023-12-12 13:40:58 -06:00
|
|
|
const turretList = await readXMLDVPL<{
|
|
|
|
|
root: TurretDefinitionsList;
|
|
|
|
|
}>(
|
2024-01-16 16:58:00 -06:00
|
|
|
`${DATA}/${POI.vehicleDefinitions}/${nation}/components/turrets.xml.dvpl`,
|
2023-12-12 13:40:58 -06:00
|
|
|
);
|
|
|
|
|
const gunList = await readXMLDVPL<{
|
|
|
|
|
root: GunDefinitionsList;
|
|
|
|
|
}>(
|
2024-01-16 16:58:00 -06:00
|
|
|
`${DATA}/${POI.vehicleDefinitions}/${nation}/components/guns.xml.dvpl`,
|
2023-12-12 13:40:58 -06:00
|
|
|
);
|
2023-12-12 15:12:09 -06:00
|
|
|
const shellList = await readXMLDVPL<{
|
|
|
|
|
root: ShellDefinitionsList;
|
|
|
|
|
}>(
|
2024-01-16 16:58:00 -06:00
|
|
|
`${DATA}/${POI.vehicleDefinitions}/${nation}/components/shells.xml.dvpl`,
|
2023-12-12 15:12:09 -06:00
|
|
|
);
|
2023-12-05 19:14:53 -06:00
|
|
|
|
2023-12-12 13:40:58 -06:00
|
|
|
for (const tankKey in tankList.root) {
|
|
|
|
|
const tank = tankList.root[tankKey];
|
2023-12-17 20:47:30 -06:00
|
|
|
const tankPrice: TankDefinitionPrice =
|
|
|
|
|
typeof tank.price === 'number'
|
|
|
|
|
? { type: 'credits', value: tank.price }
|
|
|
|
|
: { type: 'gold', value: tank.price['#text'] };
|
2023-12-12 13:40:58 -06:00
|
|
|
const tankDefinition = await readXMLDVPL<{ root: VehicleDefinitions }>(
|
2024-01-16 16:58:00 -06:00
|
|
|
`${DATA}/${POI.vehicleDefinitions}/${nation}/${tankKey}.xml.dvpl`,
|
2023-12-12 13:40:58 -06:00
|
|
|
);
|
2023-12-31 19:26:08 -06:00
|
|
|
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
|
|
|
|
|
.split(' ')
|
|
|
|
|
.map(Number) as Vector3Tuple;
|
2023-12-12 13:40:58 -06:00
|
|
|
const tankId = toUniqueId(nation, tank.id);
|
|
|
|
|
const tankTags = tank.tags.split(' ');
|
2024-01-09 18:35:42 -06:00
|
|
|
const hullArmor: ModelArmor = { thickness: {} };
|
2024-01-16 08:40:27 -06:00
|
|
|
const trackArmorRaw = Object.values(tankDefinition.root.chassis).at(-1)!
|
|
|
|
|
.armor.leftTrack;
|
2024-01-16 16:58:00 -06:00
|
|
|
const equipment = tankDefinition.root.optDevicePreset;
|
2024-01-09 18:35:42 -06:00
|
|
|
|
|
|
|
|
Object.keys(tankDefinition.root.hull.armor)
|
|
|
|
|
.filter((name) => name.startsWith('armor_'))
|
|
|
|
|
.forEach((name) => {
|
|
|
|
|
const armorIdString = name.match(/armor_(\d+)/)?.[1];
|
|
|
|
|
|
|
|
|
|
if (armorIdString === undefined) {
|
|
|
|
|
throw new SyntaxError(`Invalid armor id: ${name}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const armorId = parseInt(armorIdString);
|
|
|
|
|
const armorRaw = tankDefinition.root.hull.armor[name];
|
|
|
|
|
|
|
|
|
|
if (typeof armorRaw === 'number') {
|
|
|
|
|
hullArmor.thickness[armorId] = armorRaw;
|
|
|
|
|
} else {
|
|
|
|
|
if (!hullArmor.spaced) hullArmor.spaced = [];
|
|
|
|
|
hullArmor.thickness[armorId] = armorRaw['#text'];
|
|
|
|
|
hullArmor.spaced.push(armorId);
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-01-02 20:17:10 -06:00
|
|
|
|
|
|
|
|
tankDefinitions[tankId] = {
|
|
|
|
|
id: tankId,
|
2024-01-16 16:58:00 -06:00
|
|
|
equipment,
|
2024-01-02 20:17:10 -06:00
|
|
|
name:
|
|
|
|
|
(tank.shortUserString
|
|
|
|
|
? strings[tank.shortUserString]
|
|
|
|
|
: undefined) ??
|
|
|
|
|
strings[tank.userString] ??
|
|
|
|
|
tankKey.replaceAll('_', ' '),
|
|
|
|
|
nation,
|
|
|
|
|
tree_type: (tank.sellPrice ? 'gold' in tank.sellPrice : false)
|
|
|
|
|
? 'collector'
|
|
|
|
|
: (typeof tank.price === 'number' ? false : 'gold' in tank.price)
|
|
|
|
|
? 'premium'
|
|
|
|
|
: 'researchable',
|
|
|
|
|
tier: tank.level as Tier,
|
|
|
|
|
type: blitzTankTypeToBlitzkrieg[tankTags[0] as BlitzTankType],
|
|
|
|
|
testing: tankTags.includes('testTank') ? true : undefined,
|
|
|
|
|
price: tankPrice,
|
|
|
|
|
camouflage: {
|
|
|
|
|
still: tankDefinition.root.invisibility.still,
|
|
|
|
|
moving: tankDefinition.root.invisibility.moving,
|
|
|
|
|
firing: tankDefinition.root.invisibility.firePenalty,
|
|
|
|
|
},
|
|
|
|
|
turrets: [],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
modelDefinitions[tankId] = {
|
2024-01-09 18:35:42 -06:00
|
|
|
armor: hullArmor,
|
2024-01-16 08:40:27 -06:00
|
|
|
trackThickness:
|
|
|
|
|
typeof trackArmorRaw === 'number'
|
|
|
|
|
? trackArmorRaw
|
|
|
|
|
: trackArmorRaw['#text'],
|
2024-01-02 20:17:10 -06:00
|
|
|
turretOrigin,
|
2024-01-03 18:52:22 -06:00
|
|
|
turretRotation: tankDefinition.root.hull.turretInitialRotation
|
|
|
|
|
? {
|
|
|
|
|
yaw: tankDefinition.root.hull.turretInitialRotation.yaw,
|
|
|
|
|
pitch: tankDefinition.root.hull.turretInitialRotation.pitch,
|
|
|
|
|
roll: tankDefinition.root.hull.turretInitialRotation.roll,
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
2024-01-02 20:17:10 -06:00
|
|
|
turrets: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Object.keys(tankDefinition.root.turrets0).forEach(
|
|
|
|
|
(turretKey, turretIndex) => {
|
2023-12-12 13:40:58 -06:00
|
|
|
const turret = tankDefinition.root.turrets0[turretKey];
|
2024-01-01 14:48:46 -06:00
|
|
|
const turretModel = Number(
|
|
|
|
|
parse(turret.models.undamaged).name.split('_')[1],
|
|
|
|
|
);
|
2023-12-12 13:40:58 -06:00
|
|
|
const turretId = toUniqueId(nation, turretList.root.ids[turretKey]);
|
2023-12-17 20:47:30 -06:00
|
|
|
const turretYaw = (
|
|
|
|
|
typeof turret.yawLimits === 'string'
|
|
|
|
|
? turret.yawLimits
|
|
|
|
|
: turret.yawLimits.at(-1)!
|
|
|
|
|
)
|
|
|
|
|
.split(' ')
|
|
|
|
|
.map(Number) as [number, number];
|
2023-12-31 19:26:08 -06:00
|
|
|
const gunOrigin = (
|
|
|
|
|
typeof turret.gunPosition === 'string'
|
|
|
|
|
? turret.gunPosition
|
|
|
|
|
: turret.gunPosition[0]
|
|
|
|
|
)
|
|
|
|
|
.split(' ')
|
|
|
|
|
.map(Number) as Vector3Tuple;
|
2024-01-09 18:35:42 -06:00
|
|
|
const turretArmor: ModelArmor = { thickness: {} };
|
|
|
|
|
|
|
|
|
|
Object.keys(turret.armor)
|
|
|
|
|
.filter((name) => name.startsWith('armor_'))
|
|
|
|
|
.forEach((name) => {
|
|
|
|
|
const armorIdString = name.match(/armor_(\d+)/)?.[1];
|
|
|
|
|
|
|
|
|
|
if (armorIdString === undefined) {
|
|
|
|
|
throw new SyntaxError(`Invalid armor id: ${name}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const armorId = parseInt(armorIdString);
|
|
|
|
|
const armorRaw = turret.armor[name];
|
|
|
|
|
|
|
|
|
|
if (typeof armorRaw === 'number') {
|
|
|
|
|
turretArmor.thickness[armorId] = armorRaw;
|
|
|
|
|
} else {
|
|
|
|
|
if (!turretArmor.spaced) turretArmor.spaced = [];
|
|
|
|
|
turretArmor.thickness[armorId] = armorRaw['#text'];
|
|
|
|
|
turretArmor.spaced.push(armorId);
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-01-02 20:17:10 -06:00
|
|
|
|
|
|
|
|
tankDefinitions[tankId].turrets.push({
|
|
|
|
|
id: turretId,
|
|
|
|
|
name:
|
|
|
|
|
strings[turret.userString] ??
|
|
|
|
|
turretKey
|
|
|
|
|
.replaceAll('_', ' ')
|
|
|
|
|
.replace(/^(Turret ([0-9] )?)+/, ''),
|
|
|
|
|
tier: turret.level as Tier,
|
|
|
|
|
guns: [],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
modelDefinitions[tankId].turrets[turretId] = {
|
2024-01-09 18:35:42 -06:00
|
|
|
armor: turretArmor,
|
2024-01-02 20:17:10 -06:00
|
|
|
gunOrigin,
|
|
|
|
|
model: turretModel,
|
|
|
|
|
yaw:
|
|
|
|
|
-turretYaw[0] + turretYaw[1] === 360
|
|
|
|
|
? undefined
|
|
|
|
|
: { min: turretYaw[0], max: turretYaw[1] },
|
|
|
|
|
guns: {},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Object.keys(turret.guns).forEach((gunKey, gunIndex) => {
|
2023-12-12 14:14:26 -06:00
|
|
|
const turretGunEntry = turret.guns[gunKey];
|
|
|
|
|
const gunId = toUniqueId(nation, gunList.root.ids[gunKey]);
|
2024-01-10 17:36:11 -06:00
|
|
|
const gunListEntry = gunList.root.shared[gunKey];
|
2023-12-12 14:14:26 -06:00
|
|
|
const pitchLimitsRaw =
|
2024-01-10 17:36:11 -06:00
|
|
|
turretGunEntry.pitchLimits ?? gunListEntry.pitchLimits;
|
2023-12-14 22:19:01 -06:00
|
|
|
const gunPitch = (
|
|
|
|
|
typeof pitchLimitsRaw === 'string'
|
|
|
|
|
? pitchLimitsRaw
|
|
|
|
|
: pitchLimitsRaw.at(-1)!
|
|
|
|
|
)
|
|
|
|
|
.split(' ')
|
|
|
|
|
.map(Number) as [number, number];
|
2024-01-01 14:48:46 -06:00
|
|
|
const gunModel = Number(
|
|
|
|
|
parse(turretGunEntry.models.undamaged).name.split('_')[1],
|
|
|
|
|
);
|
2023-12-14 22:19:01 -06:00
|
|
|
const gunName =
|
2024-01-10 17:36:11 -06:00
|
|
|
strings[gunListEntry.userString] ?? gunKey.replaceAll('_', ' ');
|
2023-12-14 22:19:01 -06:00
|
|
|
const gunType =
|
|
|
|
|
'clip' in turretGunEntry
|
|
|
|
|
? turretGunEntry.pumpGunMode
|
|
|
|
|
? 'auto_reloader'
|
|
|
|
|
: 'auto_loader'
|
|
|
|
|
: 'regular';
|
|
|
|
|
const gunReload =
|
|
|
|
|
gunType === 'auto_reloader'
|
|
|
|
|
? turretGunEntry.pumpGunReloadTimes!.split(' ').map(Number)
|
|
|
|
|
: turretGunEntry.reloadTime;
|
|
|
|
|
const gunClipCount =
|
|
|
|
|
gunType === 'regular' ? undefined : turretGunEntry.clip!.count;
|
|
|
|
|
const gunInterClip =
|
|
|
|
|
gunType === 'regular'
|
|
|
|
|
? undefined
|
|
|
|
|
: 60 / turretGunEntry.clip!.rate;
|
2024-01-02 20:17:10 -06:00
|
|
|
const front = turretGunEntry.extraPitchLimits?.front
|
|
|
|
|
? turretGunEntry.extraPitchLimits.front.split(' ').map(Number)
|
|
|
|
|
: undefined;
|
|
|
|
|
const back = turretGunEntry.extraPitchLimits?.back
|
|
|
|
|
? turretGunEntry.extraPitchLimits.back.split(' ').map(Number)
|
|
|
|
|
: undefined;
|
|
|
|
|
const transition = turretGunEntry.extraPitchLimits?.transition
|
|
|
|
|
? typeof turretGunEntry.extraPitchLimits.transition === 'number'
|
|
|
|
|
? turretGunEntry.extraPitchLimits.transition
|
|
|
|
|
: turretGunEntry.extraPitchLimits.transition.at(-1)!
|
|
|
|
|
: undefined;
|
2024-01-09 18:35:42 -06:00
|
|
|
const gunArmor: ModelArmor = { thickness: {} };
|
|
|
|
|
|
|
|
|
|
Object.keys(turretGunEntry.armor)
|
|
|
|
|
.filter((name) => name.startsWith('armor_'))
|
|
|
|
|
.forEach((name) => {
|
|
|
|
|
const armorIdString = name.match(/armor_(\d+)/)?.[1];
|
|
|
|
|
if (armorIdString === undefined) {
|
|
|
|
|
throw new SyntaxError(`Invalid armor id: ${name}`);
|
|
|
|
|
}
|
|
|
|
|
const armorId = parseInt(armorIdString);
|
|
|
|
|
const armorRaw = turretGunEntry.armor[name];
|
|
|
|
|
if (typeof armorRaw === 'number') {
|
|
|
|
|
gunArmor.thickness[armorId] = armorRaw;
|
|
|
|
|
} else {
|
|
|
|
|
if (!gunArmor.spaced) gunArmor.spaced = [];
|
|
|
|
|
gunArmor.thickness[armorId] = armorRaw['#text'];
|
|
|
|
|
gunArmor.spaced.push(armorId);
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-01-02 20:17:10 -06:00
|
|
|
|
|
|
|
|
tankDefinitions[tankId].turrets[turretIndex].guns.push({
|
|
|
|
|
id: gunId,
|
|
|
|
|
name: gunName,
|
2024-01-10 17:36:11 -06:00
|
|
|
tier: gunListEntry.level as Tier,
|
2024-01-02 20:17:10 -06:00
|
|
|
shells: [],
|
|
|
|
|
type: gunType,
|
|
|
|
|
reload: gunReload,
|
|
|
|
|
count: gunClipCount,
|
|
|
|
|
interClip: gunInterClip,
|
|
|
|
|
} as GunDefinition);
|
|
|
|
|
|
|
|
|
|
modelDefinitions[tankId].turrets[turretId].guns[gunId] = {
|
2024-01-09 18:35:42 -06:00
|
|
|
armor: gunArmor,
|
2024-01-02 20:17:10 -06:00
|
|
|
model: gunModel,
|
2024-01-16 08:40:27 -06:00
|
|
|
barrelThickness:
|
|
|
|
|
turretGunEntry.armor.gun === undefined
|
|
|
|
|
? 0
|
|
|
|
|
: typeof turretGunEntry.armor.gun === 'number'
|
|
|
|
|
? turretGunEntry.armor.gun
|
|
|
|
|
: turretGunEntry.armor.gun['#text'],
|
2024-01-02 20:17:10 -06:00
|
|
|
pitch: {
|
|
|
|
|
min: gunPitch[0],
|
|
|
|
|
max: gunPitch[1],
|
|
|
|
|
|
|
|
|
|
front: front
|
|
|
|
|
? {
|
|
|
|
|
min: front[0],
|
|
|
|
|
max: front[1],
|
|
|
|
|
range: front[2],
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
back: back
|
|
|
|
|
? {
|
|
|
|
|
min: back[0],
|
|
|
|
|
max: back[1],
|
|
|
|
|
range: back[2],
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
transition,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-10 17:36:11 -06:00
|
|
|
Object.keys(gunListEntry.shots).forEach((shellKey) => {
|
|
|
|
|
const gunShellEntry = gunListEntry.shots[shellKey];
|
2023-12-12 15:12:09 -06:00
|
|
|
const shell = shellList.root[shellKey];
|
|
|
|
|
const shellId = toUniqueId(nation, shell.id);
|
2023-12-14 22:19:01 -06:00
|
|
|
const shellName =
|
|
|
|
|
strings[shell.userString] ?? shellKey.replaceAll('_', ' ');
|
2024-01-10 17:36:11 -06:00
|
|
|
const penetrationRaw = gunShellEntry.piercingPower
|
|
|
|
|
.split(' ')
|
|
|
|
|
.filter((penetrationString) => penetrationString !== '')
|
|
|
|
|
.map(Number);
|
2023-12-12 15:12:09 -06:00
|
|
|
|
2024-01-02 20:17:10 -06:00
|
|
|
tankDefinitions[tankId].turrets[turretIndex].guns[
|
|
|
|
|
gunIndex
|
|
|
|
|
].shells.push({
|
2023-12-12 15:12:09 -06:00
|
|
|
id: shellId,
|
2023-12-14 22:19:01 -06:00
|
|
|
name: shellName,
|
2024-01-10 17:36:11 -06:00
|
|
|
speed: gunShellEntry.speed,
|
2023-12-12 15:12:09 -06:00
|
|
|
damage: {
|
|
|
|
|
armor: shell.damage.armor,
|
|
|
|
|
devices: shell.damage.devices,
|
|
|
|
|
},
|
|
|
|
|
caliber: shell.caliber,
|
|
|
|
|
normalization: shell.normalizationAngle,
|
|
|
|
|
ricochet: shell.ricochetAngle,
|
|
|
|
|
type: blitzShellKindToBLitzkrieg[shell.kind],
|
2023-12-12 23:35:08 -06:00
|
|
|
icon: shell.icon,
|
2024-01-10 17:36:11 -06:00
|
|
|
penetration:
|
|
|
|
|
penetrationRaw[0] === penetrationRaw[1]
|
|
|
|
|
? penetrationRaw[0]
|
|
|
|
|
: [penetrationRaw[0], penetrationRaw[1]],
|
2024-01-02 20:17:10 -06:00
|
|
|
});
|
2023-12-12 15:12:09 -06:00
|
|
|
});
|
2023-12-12 14:14:26 -06:00
|
|
|
});
|
2023-12-12 13:40:58 -06:00
|
|
|
},
|
|
|
|
|
);
|
2023-12-05 19:14:53 -06:00
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
2024-01-16 16:58:00 -06:00
|
|
|
return;
|
2024-01-03 13:50:36 -06:00
|
|
|
console.log('Committing definitions...');
|
2024-01-03 18:52:22 -06:00
|
|
|
await commitMultipleFiles(
|
|
|
|
|
'tresabhi',
|
|
|
|
|
'blitzkrieg-assets',
|
|
|
|
|
'main',
|
|
|
|
|
'definitions',
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
content: JSON.stringify(tankDefinitions),
|
|
|
|
|
encoding: 'utf-8',
|
|
|
|
|
path: 'definitions/tanks.json',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
content: JSON.stringify(modelDefinitions),
|
|
|
|
|
encoding: 'utf-8',
|
|
|
|
|
path: 'definitions/models.json',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
true,
|
|
|
|
|
);
|
2023-12-05 19:14:53 -06:00
|
|
|
}
|