diff --git a/packages/core/src/blitzkit/api/server.ts b/packages/core/src/blitzkit/api/server.ts index d0ea56e14..5162f345e 100644 --- a/packages/core/src/blitzkit/api/server.ts +++ b/packages/core/src/blitzkit/api/server.ts @@ -1,29 +1,47 @@ import { AbstractVFS, + Armor, + BlitzCrewType, + BlitzModuleType, BlitzStrings, CamouflageDefinitions, CamouflagesXml, CamouflagesYaml, + ChassisDefinitionsList, CombatRolesYaml, ConsumableDefinitions, ConsumablesCommon, + Crew, + CrewType, + EngineDefinitionsList, EquipmentDefinitions, + GunDefinitionsList, I18nString, MapDefinitions, MapsYaml, ModelDefinitions, + ModuleType, OptionalDevices, OptionalDeviceSlots, ProvisionDefinitions, ProvisionsCommon, ResearchCost, + ShellDefinitionsList, SkillDefinitions, sluggify, SquadBattleTypeStylesYaml, TankDefinitions, TankmenAvatar, + TankParameters, + TankPrice, + TankPriceType, toUniqueId, + TurretDefinitionsList, + Unlock, + UnlocksListing, + VehicleDefinitionArmor, VehicleDefinitionList, + VehicleDefinitions, } from "@blitzkit/core"; import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n"; import locales from "@blitzkit/i18n/locales.json"; @@ -44,6 +62,28 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { usa: "us", ussr: "ru", }; + private blitzModuleTypeToBlitzkit: Record = + { + chassis: ModuleType.MODULE_TYPE_TRACKS, + engine: ModuleType.MODULE_TYPE_ENGINE, + gun: ModuleType.MODULE_TYPE_GUN, + turret: ModuleType.MODULE_TYPE_TURRET, + vehicle: ModuleType.MODULE_TYPE_VEHICLE, + }; + private blitzkitCrewTypeToBlitz: Record = { + [CrewType.CREW_TYPE_COMMANDER]: "commander", + [CrewType.CREW_TYPE_DRIVER]: "driver", + [CrewType.CREW_TYPE_GUNNER]: "gunner", + [CrewType.CREW_TYPE_LOADER]: "loader", + [CrewType.CREW_TYPE_RADIOMAN]: "radioman", + }; + private blitzCrewTypeToBlitzkit: Record = { + commander: CrewType.CREW_TYPE_COMMANDER, + driver: CrewType.CREW_TYPE_DRIVER, + gunner: CrewType.CREW_TYPE_GUNNER, + loader: CrewType.CREW_TYPE_LOADER, + radioman: CrewType.CREW_TYPE_RADIOMAN, + }; private stringsI18n: Record> = {}; private nationsDir?: string[]; @@ -179,6 +219,22 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { return { locales: collection } satisfies I18nString; } + private assignArmor( + raw: VehicleDefinitionArmor[string], + id: number, + armor: Armor, + ) { + if (typeof raw === "number") { + armor.thickness[id] = raw; + } else if (Array.isArray(raw)) { + armor.thickness[id] = raw.at(-1)!; + } else { + if (!armor.spaced) armor.spaced = []; + armor.thickness[id] = raw["#text"]; + armor.spaced.push(id); + } + } + async tankDefinitions() { const tankDefinitions = TankDefinitions.create(); @@ -292,6 +348,598 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { const tankXps = new Map(); + for (const nation of this.nationsDir!) { + const tankList = await this.vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const turretList = await this.vfs.xml<{ + root: TurretDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); + const gunList = await this.vfs.xml<{ + root: GunDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); + const shellList = await this.vfs.xml<{ + root: ShellDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); + const enginesList = await this.vfs.xml<{ + root: EngineDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); + const chassisList = await this.vfs.xml<{ + root: ChassisDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); + + function resolveUnlocks( + blitzModuleTypeToBlitzkit: Record, + unlocks?: BlitzModuleType, + ) { + if (!unlocks) return []; + + return Object.entries(unlocks) + .map(([type, list]) => + (Array.isArray(list) ? list : [list]).map((item) => { + const typeTyped = type as keyof BlitzModuleType; + let rawId: number; + + if (typeTyped === "chassis") { + rawId = chassisList.root.ids[item["#text"]]; + } else if (typeTyped === "engine") { + rawId = enginesList.root.ids[item["#text"]]; + } else if (typeTyped === "gun") { + rawId = gunList.root.ids[item["#text"]]; + } else if (typeTyped === "turret") { + rawId = turretList.root.ids[item["#text"]]; + } else if (typeTyped === "vehicle") { + rawId = tankList.root[item["#text"]].id; + } + + return { + id: toUniqueId(nation, rawId!), + type: blitzModuleTypeToBlitzkit[typeTyped], + cost: { + type: + typeof item.cost === "number" + ? "xp" + : item.cost.split(":")[0], + value: + typeof item.cost === "number" + ? item.cost + : Number(item.cost.split(":")[1]), + }, + } satisfies Unlock; + }), + ) + .flat(); + } + + for (const tankKey in tankList.root) { + if (botPattern.test(tankKey)) continue; + + const gunXps = new Map(); + const turretXps = new Map(); + const engineXps = new Map(); + const trackXps = new Map(); + const tank = tankList.root[tankKey]; + let tankPrice: TankPrice; + const tankDefinition = await this.vfs.xml<{ root: VehicleDefinitions }>( + `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, + ); + const tankParameters = await this.vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const tankId = toUniqueId(nation, tank.id); + + const tankTags = tank.tags.split(" "); + const hullArmor: Armor = { spaced: [], thickness: {} }; + const equipment = tankDefinition.root.optDevicePreset; + this.tankStringIdMap[`${nation}:${tankKey}`] = tankId; + const slug = slugs.get(tankId); + + if (slug === undefined) { + throw new Error( + `Could not find slug for ${nation}/${tankKey} (${tankId})`, + ); + } + + if (tank.sellPrice) { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_GOLD, + value: tank.sellPrice["#text"] * 2, + }; + } else if (typeof tank.price === "number") { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price, + }; + } else { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price["#text"] * 400, + }; + } + + 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]; + + this.assignArmor(armorRaw, armorId, hullArmor); + }); + const crew: Crew[] = []; + const fixedCamouflage = tankTags.includes("eventCamouflage_user"); + const totalUnlocks: UnlocksListing[] = []; + + for (const crewKey in tankDefinition.root.crew) { + const value = tankDefinition.root.crew[crewKey as BlitzCrewType]; + + { + let entry: Crew; + const index = crew.findIndex( + ({ type }) => this.blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: this.blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return this.blitzCrewTypeToBlitzkit[ + member.trim() as BlitzCrewType + ]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } + } + } + } + + const camouflages = Object.entries(camouflagesXml.root.camouflages) + .filter(([, camo]) => { + if (!camo.vehicleFilter.include) return false; + if (camo.unlockCostCategory !== "legendary-skins-gold") + return false; + + const includeArray = Array.isArray(camo.vehicleFilter.include) + ? camo.vehicleFilter.include + : [camo.vehicleFilter.include]; + + return includeArray.some((filter) => { + if ("vehicle" in filter && filter.vehicle?.name) { + return filter.vehicle.name === `${nation}:${tankKey}`; + } + + return false; + }); + }) + .map(([, camo]) => camo.id); + + let equalizerEntry = tierEqualizer.find( + (line) => line[0] === `${nation}:${tankKey}`, + ); + + let equalizer: Equalizer | undefined; + + if (equalizerEntry) { + const [health, penetration, module_health, damage, armor] = + equalizerEntry?.slice(1).map(Number); + equalizer = { health, penetration, module_health, damage, armor }; + } + + tankDefinitions.tanks[tankId] = { + ancestors: [], + successors: [], + id: tankId, + dev_name: tankKey, + roles: {}, + camouflages: camouflages, + fixed_camouflage: fixedCamouflage, + crew, + weight: tankDefinition.root.hull.weight, + health: tankDefinition.root.hull.maxHealth, + speed_forwards: tankDefinition.root.speedLimits.forward, + speed_backwards: tankDefinition.root.speedLimits.backward, + equipment_preset: + typeof equipment === "string" ? equipment : equipment.at(-1)!, + max_consumables: tankDefinition.root.consumableSlots, + max_provisions: tankDefinition.root.provisionSlots, + name: + (tank.shortUserString + ? getString(tank.shortUserString) + : undefined) ?? getString(tank.userString), + slug, + nation, + type: tankTags.includes("collectible") + ? TankType.TANK_TYPE_COLLECTOR + : (typeof tank.price === "number" ? false : "gold" in tank.price) + ? TankType.TANK_TYPE_PREMIUM + : TankType.TANK_TYPE_RESEARCHABLE, + tier: tank.level, + class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], + testing: tankTags.includes("testTank"), + deprecated: tankTags.includes("deprecated"), + price: tankPrice, + camouflage_still: tankDefinition.root.invisibility.still, + camouflage_moving: tankDefinition.root.invisibility.moving, + camouflage_on_fire: tankDefinition.root.invisibility.firePenalty, + turrets: [], + engines: [], + tracks: [], + equalizer, + }; + + if (tank.combatRole) { + Object.entries(tank.combatRole).forEach(([gameMode, role]) => { + const id = Object.entries(gameModeNativeNames).find( + ([key]) => key.toLowerCase() === gameMode.toLowerCase(), + )?.[1]; + + if (id === undefined) { + throw new Error( + `Unknown game mode in tank ${tankKey}: ${gameMode}`, + ); + } + + tankDefinitions.tanks[tankId].roles[id] = combatRoles[role].id; + }); + } + + Object.keys(tankDefinition.root.chassis).forEach((key) => { + const track = tankDefinition.root.chassis[key]; + const trackId = toUniqueId(nation, chassisList.root.ids[key]); + const terrainResistances = track.terrainResistance + .split(" ") + .map(Number); + + totalUnlocks.push(track.unlocks); + tankDefinitions.tanks[tankId].tracks.push({ + id: trackId, + weight: track.weight, + name: getString(track.userString), + traverse_speed: track.rotationSpeed, + dispersion_move: track.shotDispersionFactors.vehicleMovement, + dispersion_traverse: track.shotDispersionFactors.vehicleRotation, + resistance_hard: terrainResistances[0], + resistance_medium: terrainResistances[1], + resistance_soft: terrainResistances[2], + tier: track.level, + unlocks: resolveUnlocks(track.unlocks), + }); + }); + + Object.keys(tankDefinition.root.engines).forEach((engineKey) => { + const engine = tankDefinition.root.engines[engineKey]; + const engineListEntry = enginesList.root.shared[engineKey]; + const engineId = toUniqueId(nation, enginesList.root.ids[engineKey]); + + totalUnlocks.push(engine.unlocks); + tankDefinitions.tanks[tankId].engines.push({ + id: engineId, + name: getString(engineListEntry.userString), + fire_chance: engineListEntry.fireStartingChance, + tier: engineListEntry.level, + weight: engineListEntry.weight, + power: engineListEntry.power, + unlocks: resolveUnlocks(engine.unlocks), + }); + }); + + Object.keys(tankDefinition.root.turrets0).forEach( + (turretKey, turretIndex) => { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + + const turretArmor: Armor = { thickness: {}, spaced: [] }; + + totalUnlocks.push(turret.unlocks); + 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]; + + assignArmor(armorRaw, armorId, turretArmor); + }); + + turret.userString; + + tankDefinitions.tanks[tankId].turrets.push({ + id: turretId, + traverse_speed: turret.rotationSpeed, + name: getString(turret.userString), + tier: turret.level, + guns: [], + health: turret.maxHealth, + view_range: turret.circularVisionRadius, + weight: turret.weight, + unlocks: resolveUnlocks(turret.unlocks), + }); + + Object.keys(turret.guns).forEach((gunKey, gunIndex) => { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const pitchLimitsRaw = + gun.pitchLimits ?? gunListEntry.pitchLimits; + const gunPitch = ( + typeof pitchLimitsRaw === "string" + ? pitchLimitsRaw + : pitchLimitsRaw.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunModel = Number( + parsePath(gun.models.undamaged).name.split("_")[1], + ); + const gunName = getString(gunListEntry.userString); + const gunType = + "clip" in gun + ? gun.pumpGunMode + ? "autoReloader" + : "autoLoader" + : "regular"; + // const gunReload = gun.reloadTime; + // const shellReloads = + const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; + const gunArmor: Armor = { thickness: {}, spaced: [] }; + const shotDispersionFactors = + gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; + const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; + const maskEnabled = + tankParameters.maskSlice?.[maskName]?.enabled ?? false; + let assault_ranges: AssaultRanges | undefined; + + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); + + if (trimmed in blitzShellKindToBlitzkit) { + return blitzShellKindToBlitzkit[trimmed as ShellKind]; + } + + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); + + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); + } + + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; + } + + if (maskEnabled) { + const maskRaw = tankParameters.maskSlice![maskName]!; + mask = maskRaw.planePosition[1]; + } else { + mask = undefined; + } + + totalUnlocks.push(gun.unlocks); + Object.keys(gun.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 = gun.armor[name]; + + assignArmor(armorRaw, armorId, gunArmor); + }); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ + id: gunId, + weight: gunListEntry.weight, + rotation_speed: gunListEntry.rotationSpeed, + name: gunName, + tier: gunListEntry.level, + shells: [], + camouflage_loss: + typeof gun.invisibilityFactorAtShot === "number" + ? gun.invisibilityFactorAtShot + : gun.invisibilityFactorAtShot.at(-1)!, + aim_time: gun.aimingTime ?? gunListEntry.aimingTime, + dispersion_base: + gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, + dispersion_damaged: shotDispersionFactors.whileGunDamaged, + dispersion_shot: shotDispersionFactors.afterShot, + dispersion_traverse: shotDispersionFactors.turretRotation, + unlocks: resolveUnlocks(gun.unlocks), + shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, + assault_ranges, + burst: + gun.burst && gun.burst.count > 1 + ? { + count: gun.burst.count, + interval: 60 / gun.burst.rate, + } + : undefined, + gun_type: + gunType === "regular" + ? { + $case: "regular", + value: { + reload: gun.reloadTime, + }, + } + : gunType === "autoReloader" + ? { + $case: "auto_reloader", + value: { + intra_clip: 60 / gun.clip!.rate, + shell_count: gunClipCount, + shell_reloads: gun + .pumpGunReloadTimes!.split(" ") + .map(Number), + }, + } + : { + $case: "auto_loader", + value: { + intra_clip: 60 / gun.clip!.rate, + clip_reload: gun.reloadTime, + shell_count: gunClipCount, + }, + }, + } satisfies GunDefinition); + + Object.keys(gunListEntry.shots).forEach((shellKey) => { + const gunShellEntry = gunListEntry.shots[shellKey]; + const shell = shellList.root[shellKey]; + const shellId = toUniqueId(nation, shell.id); + const shellName = getString(shell.userString); + const penetrationRaw = gunShellEntry.piercingPower + .split(" ") + .filter((penetrationString) => penetrationString !== "") + .map(Number); + + tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ + gunIndex + ].shells.push({ + id: shellId, + name: shellName, + velocity: gunShellEntry.speed, + armor_damage: shell.damage.armor, + module_damage: shell.damage.devices, + caliber: shell.caliber, + normalization: shell.normalizationAngle, + ricochet: shell.ricochetAngle, + type: blitzShellKindToBlitzkit[shell.kind], + explosion_radius: + shell.kind === "HIGH_EXPLOSIVE" + ? (shell.explosionRadius ?? 0) + : undefined, + icon: shell.icon, + penetration: { + near: penetrationRaw[0], + far: penetrationRaw[1], + }, + range: gunShellEntry.maxDistance, + }); + }); + }); + }, + ); + + totalUnlocks.forEach((unlocks) => { + if (unlocks === undefined) return; + + Object.entries(unlocks).forEach(([key, value]) => { + (Array.isArray(value) ? value : [value]).forEach((vehicle) => { + switch (key as keyof BlitzModuleType) { + case "vehicle": { + const tankListEntry = tankList.root[vehicle["#text"]]; + const currentTank = tankDefinitions.tanks[tankId]; + const successorId = toUniqueId(nation, tankListEntry.id); + + tankXps.set(successorId, parseResearchCost(vehicle.cost)); + + if (currentTank.successors === undefined) { + currentTank.successors = []; + } + if (!currentTank.successors!.includes(successorId)) { + currentTank.successors!.push(successorId); + } + break; + } + + case "gun": { + gunXps.set( + toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "turret": { + turretXps.set( + toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "engine": { + engineXps.set( + toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "chassis": { + trackXps.set( + toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + } + }); + }); + }); + + Object.values(tankDefinitions.tanks[tankId].turrets).forEach( + (turret) => { + turret.research_cost = turretXps.get(turret.id); + + Object.values(turret.guns).forEach((gunRaw) => { + gunRaw.research_cost = gunXps.get(gunRaw.id); + }); + }, + ); + + Object.values(tankDefinitions.tanks[tankId].engines).forEach( + (engine) => { + engine.research_cost = engineXps.get(engine.id); + }, + ); + + Object.values(tankDefinitions.tanks[tankId].tracks).forEach((track) => { + track.research_cost = trackXps.get(track.id); + }); + } + } + return tankDefinitions; } @@ -326,6 +974,432 @@ export class ServerBlitzKitAPI extends BlitzKitAPI { async modelDefinitions() { const modelDefinitions = ModelDefinitions.create(); + for (const nation of nationsDir) { + const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( + `Data/XML/item_defs/vehicles/${nation}/list.xml`, + ); + const turretList = await vfs.xml<{ + root: TurretDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); + const gunList = await vfs.xml<{ + root: GunDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); + const shellList = await vfs.xml<{ + root: ShellDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); + const enginesList = await vfs.xml<{ + root: EngineDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); + const chassisList = await vfs.xml<{ + root: ChassisDefinitionsList; + }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); + + for (const tankKey in tankList.root) { + if (botPattern.test(tankKey)) continue; + + const gunXps = new Map(); + const turretXps = new Map(); + const engineXps = new Map(); + const trackXps = new Map(); + const tank = tankList.root[tankKey]; + const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>( + `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, + ); + const tankParameters = await vfs.yaml( + `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, + ); + const turretOrigin = tankDefinition.root.hull.turretPositions.turret + .split(" ") + .map(Number) as Vector3Tuple; + const tankId = toUniqueId(nation, tank.id); + + const tankTags = tank.tags.split(" "); + const hullArmor: Armor = { spaced: [], thickness: {} }; + tankStringIdMap[`${nation}:${tankKey}`] = tankId; + const slug = slugs.get(tankId); + + if (slug === undefined) { + throw new Error( + `Could not find slug for ${nation}/${tankKey} (${tankId})`, + ); + } + + if (tank.sellPrice) { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_GOLD, + value: tank.sellPrice["#text"] * 2, + }; + } else if (typeof tank.price === "number") { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price, + }; + } else { + tankPrice = { + type: TankPriceType.TANK_PRICE_TYPE_CREDITS, + value: tank.price["#text"] * 400, + }; + } + + 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]; + + assignArmor(armorRaw, armorId, hullArmor); + }); + const crew: Crew[] = []; + const totalUnlocks: UnlocksListing[] = []; + + Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => { + let entry: Crew; + const index = crew.findIndex( + ({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey, + ); + if (index === -1) { + if (crewKey === "#text") return; + entry = { + type: blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], + count: 0, + substitute: [], + }; + crew.push(entry); + } else { + entry = crew[index]; + } + + if (typeof value === "string") { + entry.count++; + + if (value !== "") { + entry.substitute = value.split(/\n| /).map((member) => { + return blitzCrewTypeToBlitzkit[member.trim() as BlitzCrewType]; + }); + } + } else { + if (entry.count === undefined) { + entry.count = value.length; + } else { + entry.count += value.length; + } + } + }); + + let equalizerEntry = tierEqualizer.find( + (line) => line[0] === `${nation}:${tankKey}`, + ); + + if (equalizerEntry) { + const [health, penetration, module_health, damage, armor] = + equalizerEntry?.slice(1).map(Number); + equalizer = { health, penetration, module_health, damage, armor }; + } + + if (tank.combatRole) { + Object.entries(tank.combatRole).forEach(([gameMode, role]) => { + const id = Object.entries(gameModeNativeNames).find( + ([key]) => key.toLowerCase() === gameMode.toLowerCase(), + )?.[1]; + + if (id === undefined) { + throw new Error( + `Unknown game mode in tank ${tankKey}: ${gameMode}`, + ); + } + }); + } + + modelDefinitions.models[tankId] = { + armor: hullArmor, + turret_origin: vector3TupleToBlitzkit(turretOrigin), + initial_turret_rotation: tankDefinition.root.hull + .turretInitialRotation + ? { + yaw: tankDefinition.root.hull.turretInitialRotation.yaw, + pitch: tankDefinition.root.hull.turretInitialRotation.pitch, + roll: tankDefinition.root.hull.turretInitialRotation.roll, + } + : undefined, + bounding_box: { + min: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.min), + max: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.max), + }, + turrets: {}, + tracks: {}, + }; + + Object.keys(tankDefinition.root.chassis).forEach((key) => { + const track = tankDefinition.root.chassis[key]; + const trackId = toUniqueId(nation, chassisList.root.ids[key]); + const trackArmorRaw = track.armor.leftTrack; + const hullOrigin = track.hullPosition + .split(" ") + .map(Number) as Vector3Tuple; + + totalUnlocks.push(track.unlocks); + + modelDefinitions.models[tankId].tracks[trackId] = { + thickness: + typeof trackArmorRaw === "number" + ? trackArmorRaw + : trackArmorRaw["#text"], + origin: vector3TupleToBlitzkit(hullOrigin), + }; + }); + + Object.keys(tankDefinition.root.engines).forEach((engineKey) => { + const engine = tankDefinition.root.engines[engineKey]; + + totalUnlocks.push(engine.unlocks); + }); + + Object.keys(tankDefinition.root.turrets0).forEach((turretKey) => { + const turret = tankDefinition.root.turrets0[turretKey]; + const turretModel = Number( + parsePath(turret.models.undamaged).name.split("_")[1], + ); + const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); + const turretYaw = ( + typeof turret.yawLimits === "string" + ? turret.yawLimits + : turret.yawLimits.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunOrigin = ( + typeof turret.gunPosition === "string" + ? turret.gunPosition + : turret.gunPosition[0] + ) + .split(" ") + .map(Number) as Vector3Tuple; + const turretArmor: Armor = { thickness: {}, spaced: [] }; + + totalUnlocks.push(turret.unlocks); + 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]; + + assignArmor(armorRaw, armorId, turretArmor); + }); + + turret.userString; + + modelDefinitions.models[tankId].turrets[turretId] = { + armor: turretArmor, + gun_origin: vector3TupleToBlitzkit(gunOrigin), + model_id: turretModel, + yaw: + -turretYaw[0] + turretYaw[1] === 360 + ? undefined + : { min: turretYaw[0], max: turretYaw[1] }, + guns: {}, + bounding_box: { + min: vector3TupleToBlitzkit( + tankParameters.collision[ + parsePath(turret.hitTester.collisionModel).name.toLowerCase() + ].bbox.min, + ), + max: vector3TupleToBlitzkit( + tankParameters.collision[ + parsePath(turret.hitTester.collisionModel).name.toLowerCase() + ].bbox.max, + ), + }, + }; + + Object.keys(turret.guns).forEach((gunKey, gunIndex) => { + const gun = turret.guns[gunKey]; + const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); + const gunListEntry = gunList.root.shared[gunKey]; + const pitchLimitsRaw = gun.pitchLimits ?? gunListEntry.pitchLimits; + const gunPitch = ( + typeof pitchLimitsRaw === "string" + ? pitchLimitsRaw + : pitchLimitsRaw.at(-1)! + ) + .split(" ") + .map(Number) as [number, number]; + const gunModel = Number( + parsePath(gun.models.undamaged).name.split("_")[1], + ); + // const gunReload = gun.reloadTime; + // const shellReloads = + const front = gun.extraPitchLimits?.front + ? gun.extraPitchLimits.front.split(" ").map(Number) + : undefined; + const back = gun.extraPitchLimits?.back + ? gun.extraPitchLimits.back.split(" ").map(Number) + : undefined; + const transition = gun.extraPitchLimits?.transition + ? typeof gun.extraPitchLimits.transition === "number" + ? gun.extraPitchLimits.transition + : gun.extraPitchLimits.transition.at(-1)! + : undefined; + const gunArmor: Armor = { thickness: {}, spaced: [] }; + const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; + const maskEnabled = + tankParameters.maskSlice?.[maskName]?.enabled ?? false; + let mask: number | undefined; + + if (gun.extras?.trayShell) { + const types = gun.extras.trayShell.kinds + .split(" ") + .map((string) => { + const trimmed = string.trim(); + + if (trimmed in blitzShellKindToBlitzkit) { + return blitzShellKindToBlitzkit[trimmed as ShellKind]; + } + + throw new SyntaxError(`Invalid shell kind: ${trimmed}`); + }); + const sectorNames = Object.keys(gun.extras.trayShell.sectors); + + if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { + throw new SyntaxError("Invalid tray shell sector"); + } + + const sector = gun.extras.trayShell.sectors.sector; + + assault_ranges = { + types, + ranges: sector.map((value) => ({ + factor: value.factor, + distance: value.distance, + })), + }; + } + + if (maskEnabled) { + const maskRaw = tankParameters.maskSlice![maskName]!; + mask = maskRaw.planePosition[1]; + } else { + mask = undefined; + } + + totalUnlocks.push(gun.unlocks); + Object.keys(gun.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 = gun.armor[name]; + + assignArmor(armorRaw, armorId, gunArmor); + }); + + modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { + armor: gunArmor, + model_id: gunModel, + mask, + thickness: + gun.armor.gun === undefined + ? 0 + : typeof gun.armor.gun === "number" + ? gun.armor.gun + : gun.armor.gun["#text"], + 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, + }, + }; + }); + }); + + totalUnlocks.forEach((unlocks) => { + if (unlocks === undefined) return; + + Object.entries(unlocks).forEach(([key, value]) => { + (Array.isArray(value) ? value : [value]).forEach((vehicle) => { + switch (key as keyof BlitzModuleType) { + case "vehicle": { + const tankListEntry = tankList.root[vehicle["#text"]]; + const successorId = toUniqueId(nation, tankListEntry.id); + + tankXps.set(successorId, parseResearchCost(vehicle.cost)); + + if (currentTank.successors === undefined) { + currentTank.successors = []; + } + if (!currentTank.successors!.includes(successorId)) { + currentTank.successors!.push(successorId); + } + break; + } + + case "gun": { + gunXps.set( + toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "turret": { + turretXps.set( + toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "engine": { + engineXps.set( + toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + + case "chassis": { + trackXps.set( + toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), + parseResearchCost(vehicle.cost), + ); + break; + } + } + }); + }); + }); + } + } + return modelDefinitions; } diff --git a/packages/core/src/types/blitzModuleType.ts b/packages/core/src/types/blitzModuleType.ts new file mode 100644 index 000000000..3a8ac4774 --- /dev/null +++ b/packages/core/src/types/blitzModuleType.ts @@ -0,0 +1,10 @@ +export type BlitzModuleType = { + [key in "vehicle" | "engine" | "chassis" | "turret" | "gun"]: + | UnlocksInner + | UnlocksInner[]; +}; + +interface UnlocksInner { + cost: number | string; + "#text": number; +} diff --git a/packages/core/src/types/chassisDefinitionsList.ts b/packages/core/src/types/chassisDefinitionsList.ts new file mode 100644 index 000000000..b41ba53ea --- /dev/null +++ b/packages/core/src/types/chassisDefinitionsList.ts @@ -0,0 +1,4 @@ +export interface ChassisDefinitionsList { + nextAvailableId: number; + ids: Record; +} diff --git a/packages/core/src/types/engineDefinitionsList.ts b/packages/core/src/types/engineDefinitionsList.ts new file mode 100644 index 000000000..8707ca1b1 --- /dev/null +++ b/packages/core/src/types/engineDefinitionsList.ts @@ -0,0 +1,13 @@ +export interface EngineDefinitionsList { + nextAvailableId: number; + ids: Record; + shared: { + [key: string]: { + userString: string; + level: number; + fireStartingChance: number; + weight: number; + power: number; + }; + }; +} diff --git a/packages/core/src/types/gunDefinitionsList.ts b/packages/core/src/types/gunDefinitionsList.ts new file mode 100644 index 000000000..005238d10 --- /dev/null +++ b/packages/core/src/types/gunDefinitionsList.ts @@ -0,0 +1,31 @@ +export interface GunDefinitionsList { + nextAvailableId: number; + ids: Record; + shared: { + [key: string]: { + rotationSpeed: number; + weight: number; + shotDispersionFactors: { + turretRotation: number; + afterShot: number; + whileGunDamaged: number; + }; + maxAmmo: number; + aimingTime: number; + shotDispersionRadius: number; + 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; + maxDistance: number; + }; + }; + }; + }; +} diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 83bea7b00..22bdfb454 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,16 +1,25 @@ export * from "./availableNationsYaml"; +export * from "./blitzModuleType"; export * from "./blitzStrings"; export * from "./camouflagesXml"; export * from "./camouflagesYaml"; +export * from "./chassisDefinitionsList"; export * from "./combatRolesYaml"; export * from "./consumablesCommon"; export * from "./consumablesVehicleFilter"; +export * from "./engineDefinitionsList"; export * from "./glossary"; +export * from "./gunDefinitionsList"; export * from "./mapsYaml"; export * from "./optionalDevices"; export * from "./optionalDeviceSlots"; export * from "./provisionsCommon"; +export * from "./shellDefinitionsList"; export * from "./squadBattleTypeStylesYaml"; export * from "./tankmenAvatar"; +export * from "./tankParameters"; export * from "./tanksStats"; +export * from "./turretDefinitionsList"; +export * from "./vehicleDefinitionArmor"; export * from "./vehicleDefinitionList"; +export * from "./vehicleDefinitions"; diff --git a/packages/core/src/types/shellDefinitionsList.ts b/packages/core/src/types/shellDefinitionsList.ts new file mode 100644 index 000000000..be8681432 --- /dev/null +++ b/packages/core/src/types/shellDefinitionsList.ts @@ -0,0 +1,22 @@ +export type ShellDefinitionsList = Record< + string, + { + id: number; + userString: string; + icon: string; + kind: ShellKind; + caliber: number; + damage: { armor: number; devices: number }; + normalizationAngle?: number; + ricochetAngle?: number; + explosionRadius?: number; + } +> & { + icons: Record; +}; + +type ShellKind = + | "ARMOR_PIERCING" + | "ARMOR_PIERCING_CR" + | "HIGH_EXPLOSIVE" + | "HOLLOW_CHARGE"; diff --git a/packages/core/src/types/tankParameters.ts b/packages/core/src/types/tankParameters.ts new file mode 100644 index 000000000..3a7ac082e --- /dev/null +++ b/packages/core/src/types/tankParameters.ts @@ -0,0 +1,33 @@ +import { Vector3Tuple } from "three"; + +export interface TankParameters { + resourcesPath: { + smallIconPath: string; + bigIconPath: string; + blitzModelPath: string; + }; + collision: { + [key: string]: { + points: Vector3Tuple; + bbox: { + min: Vector3Tuple; + max: Vector3Tuple; + }; + averageThickness?: Record; + }; + }; + maskSlice?: { + [key: string]: + | { + enabled: boolean; + planePosition: Vector3Tuple; + planeNormal: Vector3Tuple; + planeAxis: Vector3Tuple; + frustumConeBaseRadius: number; + frustumConeAngleDegrees: number; + planeSliceMode: number; + frustumConeSliceMode: number; + } + | undefined; + }; +} diff --git a/packages/core/src/types/turretDefinitionsList.ts b/packages/core/src/types/turretDefinitionsList.ts new file mode 100644 index 000000000..3e2ead959 --- /dev/null +++ b/packages/core/src/types/turretDefinitionsList.ts @@ -0,0 +1,4 @@ +export interface TurretDefinitionsList { + nextAvailableId: number; + ids: Record; +} diff --git a/packages/core/src/types/vehicleDefinitionArmor.ts b/packages/core/src/types/vehicleDefinitionArmor.ts new file mode 100644 index 000000000..9f92b26ec --- /dev/null +++ b/packages/core/src/types/vehicleDefinitionArmor.ts @@ -0,0 +1,4 @@ +export type VehicleDefinitionArmor = Record< + string, + number | { vehicleDamageFactor: 0; "#text": number } | number[] +>; diff --git a/packages/core/src/types/vehicleDefinitions.ts b/packages/core/src/types/vehicleDefinitions.ts new file mode 100644 index 000000000..e0ff86532 --- /dev/null +++ b/packages/core/src/types/vehicleDefinitions.ts @@ -0,0 +1,152 @@ +import { BlitzModuleType } from "@blitzkit/core"; + +export interface VehicleDefinitions { + invisibility: { + moving: number; + still: number; + firePenalty: number; + }; + crew: Record; + speedLimits: { + forward: number; + backward: number; + }; + consumableSlots: number; + provisionSlots: number; + optDevicePreset: string | string[]; + hull: { + armor: VehicleDefinitionArmor; + turretPositions: { turret: string }; + turretInitialRotation?: { yaw: 0; pitch: 6.5; roll: 0 }; + maxHealth: number; + weight: number; + }; + chassis: { + [key: string]: { + unlocks: UnlocksListing; + weight: number; + terrainResistance: string; + rotationSpeed: number; + userString: string; + shotDispersionFactors: { + vehicleMovement: number; + vehicleRotation: number; + }; + level: number; + hullPosition: string; + armor: { + leftTrack: + | number + | { + chanceToHitByProjectile: number; + chanceToHitByExplosion: number; + "#text": number; + }; + rightTrack: + | number + | { + chanceToHitByProjectile: number; + chanceToHitByExplosion: number; + "#text": number; + }; + }; + }; + }; + engines: { + [key: string]: { + unlocks: UnlocksListing; + }; + }; + turrets0: { + [key: string]: { + unlocks: UnlocksListing; + rotationSpeed: number; + weight: number; + circularVisionRadius: number; + maxHealth: number; + armor: VehicleDefinitionArmor; + userString: string; + level: number; + yawLimits: string | string[]; + gunPosition: string | string[]; + models: { undamaged: string }; + hitTester: { + collisionModel: string; + }; + guns: { + [key: string]: { + unlocks: UnlocksListing; + armor: VehicleDefinitionArmor & { + gun?: + | number + | { + chanceToHitByProjectile: number; + chanceToHitByExplosion: number; + "#text": number; + }; + }; + invisibilityFactorAtShot: number | number[]; + reloadTime: number; + aimingTime?: number; + shotDispersionRadius: number; + shotDispersionFactors?: { + turretRotation: number; + afterShot: number; + whileGunDamaged: number; + }; + maxAmmo: number; + extraPitchLimits?: { + front?: string; + back?: string; + transition?: number | number[]; + }; + pitchLimits?: string | string[]; + pumpGunMode?: boolean; + pumpGunReloadTimes?: string; + clip?: { count: number; rate: number }; + burst?: { count: number; rate: number }; + models: { undamaged: string }; + extras?: { + trayShell?: { + kinds: string; + sectors: Record< + string, + { id: number; distance: number; factor: number }[] + >; + }; + }; + }; + }; + }; + }; + extras?: { + armorsStatesController?: { + state: [ + { + id: 0; + armors: string; + type: "default"; + enabled: true; + factorsModifiers: ""; + default: ""; + }, + { + id: 1; + armors: string; + type: "default"; + enabled: false; + factorsModifiers: ""; + }, + ]; + }; + }; +} + +export type BlitzCrewType = + | "commander" + | "radioman" + | "gunner" + | "driver" + | "loader"; + +export type UnlocksListing = BlitzModuleType | undefined; diff --git a/packages/scripts/src/buildAssets/definitions.ts b/packages/scripts/src/buildAssets/definitions.ts index dfbc375ea..4b6ad7329 100644 --- a/packages/scripts/src/buildAssets/definitions.ts +++ b/packages/scripts/src/buildAssets/definitions.ts @@ -1,20 +1,14 @@ import { - Armor, - AssaultRanges, type BlitzTankClass, CamouflageDefinitions, Consumable, ConsumableDefinitions, ConsumableTankCategoryFilterCategory, - Crew, CrewType, - Equalizer, EquipmentDefinitions, EquipmentSlot, - GunDefinition, MapDefinitions, ModelDefinitions, - ModuleType, Provision, ProvisionDefinitions, ResearchCost, @@ -22,17 +16,9 @@ import { SkillDefinitions, TankClass, TankDefinitions, - TankPrice, - TankPriceType, - TankType, - toUniqueId, - Unlock, Vector3, } from "@blitzkit/core"; -import { parse as parsePath } from "path"; import type { Vector3Tuple } from "three"; -import { vfs } from "./constants"; -import type { TankParameters } from "./tankIcons"; function parseResearchCost(raw: number | string) { if (typeof raw === "number") { @@ -71,249 +57,9 @@ const blitzTankClassToBlitzkit: Record = { export interface BlitzStrings { [key: string]: string; } -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; - explosionRadius?: number; - } -> & { - icons: Record; -}; -interface EngineDefinitionsList { - nextAvailableId: number; - ids: Record; - shared: { - [key: string]: { - userString: string; - level: number; - fireStartingChance: number; - weight: number; - power: number; - }; - }; -} -export interface ChassisDefinitionsList { - nextAvailableId: number; - ids: Record; -} -type VehicleDefinitionArmor = Record< - string, - number | { vehicleDamageFactor: 0; "#text": number } | number[] ->; -interface UnlocksInner { - cost: number | string; - "#text": number; -} -type BlitzModuleType = { - [key in "vehicle" | "engine" | "chassis" | "turret" | "gun"]: - | UnlocksInner - | UnlocksInner[]; -}; -type UnlocksListing = BlitzModuleType | undefined; -type BlitzCrewType = "commander" | "radioman" | "gunner" | "driver" | "loader"; -const blitzCrewTypeToBlitzkit: Record = { - commander: CrewType.CREW_TYPE_COMMANDER, - driver: CrewType.CREW_TYPE_DRIVER, - gunner: CrewType.CREW_TYPE_GUNNER, - loader: CrewType.CREW_TYPE_LOADER, - radioman: CrewType.CREW_TYPE_RADIOMAN, -}; -const blitzkitCrewTypeToBlitz: Record = { - [CrewType.CREW_TYPE_COMMANDER]: "commander", - [CrewType.CREW_TYPE_DRIVER]: "driver", - [CrewType.CREW_TYPE_GUNNER]: "gunner", - [CrewType.CREW_TYPE_LOADER]: "loader", - [CrewType.CREW_TYPE_RADIOMAN]: "radioman", -}; -interface VehicleDefinitions { - invisibility: { - moving: number; - still: number; - firePenalty: number; - }; - crew: Record; - speedLimits: { - forward: number; - backward: number; - }; - consumableSlots: number; - provisionSlots: number; - optDevicePreset: string | string[]; - hull: { - armor: VehicleDefinitionArmor; - turretPositions: { turret: string }; - turretInitialRotation?: { yaw: 0; pitch: 6.5; roll: 0 }; - maxHealth: number; - weight: number; - }; - chassis: { - [key: string]: { - unlocks: UnlocksListing; - weight: number; - terrainResistance: string; - rotationSpeed: number; - userString: string; - shotDispersionFactors: { - vehicleMovement: number; - vehicleRotation: number; - }; - level: number; - hullPosition: string; - armor: { - leftTrack: - | number - | { - chanceToHitByProjectile: number; - chanceToHitByExplosion: number; - "#text": number; - }; - rightTrack: - | number - | { - chanceToHitByProjectile: number; - chanceToHitByExplosion: number; - "#text": number; - }; - }; - }; - }; - engines: { - [key: string]: { - unlocks: UnlocksListing; - }; - }; - turrets0: { - [key: string]: { - unlocks: UnlocksListing; - rotationSpeed: number; - weight: number; - circularVisionRadius: number; - maxHealth: number; - armor: VehicleDefinitionArmor; - userString: string; - level: number; - yawLimits: string | string[]; - gunPosition: string | string[]; - models: { undamaged: string }; - hitTester: { - collisionModel: string; - }; - guns: { - [key: string]: { - unlocks: UnlocksListing; - armor: VehicleDefinitionArmor & { - gun?: - | number - | { - chanceToHitByProjectile: number; - chanceToHitByExplosion: number; - "#text": number; - }; - }; - invisibilityFactorAtShot: number | number[]; - reloadTime: number; - aimingTime?: number; - shotDispersionRadius: number; - shotDispersionFactors?: { - turretRotation: number; - afterShot: number; - whileGunDamaged: number; - }; - maxAmmo: number; - extraPitchLimits?: { - front?: string; - back?: string; - transition?: number | number[]; - }; - pitchLimits?: string | string[]; - pumpGunMode?: boolean; - pumpGunReloadTimes?: string; - clip?: { count: number; rate: number }; - burst?: { count: number; rate: number }; - models: { undamaged: string }; - extras?: { - trayShell?: { - kinds: string; - sectors: Record< - string, - { id: number; distance: number; factor: number }[] - >; - }; - }; - }; - }; - }; - }; - extras?: { - armorsStatesController?: { - state: [ - { - id: 0; - armors: string; - type: "default"; - enabled: true; - factorsModifiers: ""; - default: ""; - }, - { - id: 1; - armors: string; - type: "default"; - enabled: false; - factorsModifiers: ""; - }, - ]; - }; - }; -} -interface TurretDefinitionsList { - nextAvailableId: number; - ids: Record; -} -interface GunDefinitionsList { - nextAvailableId: number; - ids: Record; - shared: { - [key: string]: { - rotationSpeed: number; - weight: number; - shotDispersionFactors: { - turretRotation: number; - afterShot: number; - whileGunDamaged: number; - }; - maxAmmo: number; - aimingTime: number; - shotDispersionRadius: number; - 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; - maxDistance: number; - }; - }; - }; - }; -} + + + export interface ConsumablesCommon { [key: string]: { id: number; @@ -376,724 +122,8 @@ const blitzShellKindToBlitzkit: Record = { HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE, HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT, }; -const blitzModuleTypeToBlitzkit: Record = { - chassis: ModuleType.MODULE_TYPE_TRACKS, - engine: ModuleType.MODULE_TYPE_ENGINE, - gun: ModuleType.MODULE_TYPE_GUN, - turret: ModuleType.MODULE_TYPE_TURRET, - vehicle: ModuleType.MODULE_TYPE_VEHICLE, -}; - -function assignArmor( - raw: VehicleDefinitionArmor[string], - id: number, - armor: Armor, -) { - if (typeof raw === "number") { - armor.thickness[id] = raw; - } else if (Array.isArray(raw)) { - armor.thickness[id] = raw.at(-1)!; - } else { - if (!armor.spaced) armor.spaced = []; - armor.thickness[id] = raw["#text"]; - armor.spaced.push(id); - } -} export async function definitions() { - for (const nation of nationsDir) { - const tankList = await vfs.xml<{ root: VehicleDefinitionList }>( - `Data/XML/item_defs/vehicles/${nation}/list.xml`, - ); - const turretList = await vfs.xml<{ - root: TurretDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/turrets.xml`); - const gunList = await vfs.xml<{ - root: GunDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/guns.xml`); - const shellList = await vfs.xml<{ - root: ShellDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/shells.xml`); - const enginesList = await vfs.xml<{ - root: EngineDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/engines.xml`); - const chassisList = await vfs.xml<{ - root: ChassisDefinitionsList; - }>(`Data/XML/item_defs/vehicles/${nation}/components/chassis.xml`); - - function resolveUnlocks(unlocks?: BlitzModuleType) { - if (!unlocks) return []; - - return Object.entries(unlocks) - .map(([type, list]) => - (Array.isArray(list) ? list : [list]).map((item) => { - const typeTyped = type as keyof BlitzModuleType; - let rawId: number; - - if (typeTyped === "chassis") { - rawId = chassisList.root.ids[item["#text"]]; - } else if (typeTyped === "engine") { - rawId = enginesList.root.ids[item["#text"]]; - } else if (typeTyped === "gun") { - rawId = gunList.root.ids[item["#text"]]; - } else if (typeTyped === "turret") { - rawId = turretList.root.ids[item["#text"]]; - } else if (typeTyped === "vehicle") { - rawId = tankList.root[item["#text"]].id; - } - - return { - id: toUniqueId(nation, rawId!), - type: blitzModuleTypeToBlitzkit[typeTyped], - cost: { - type: - typeof item.cost === "number" - ? "xp" - : item.cost.split(":")[0], - value: - typeof item.cost === "number" - ? item.cost - : Number(item.cost.split(":")[1]), - }, - } satisfies Unlock; - }), - ) - .flat(); - } - - for (const tankKey in tankList.root) { - if (botPattern.test(tankKey)) continue; - - const gunXps = new Map(); - const turretXps = new Map(); - const engineXps = new Map(); - const trackXps = new Map(); - const tank = tankList.root[tankKey]; - let tankPrice: TankPrice; - const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>( - `Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`, - ); - const tankParameters = await vfs.yaml( - `Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, - ); - const turretOrigin = tankDefinition.root.hull.turretPositions.turret - .split(" ") - .map(Number) as Vector3Tuple; - const tankId = toUniqueId(nation, tank.id); - - const tankTags = tank.tags.split(" "); - const hullArmor: Armor = { spaced: [], thickness: {} }; - const equipment = tankDefinition.root.optDevicePreset; - tankStringIdMap[`${nation}:${tankKey}`] = tankId; - const slug = slugs.get(tankId); - - if (slug === undefined) { - throw new Error( - `Could not find slug for ${nation}/${tankKey} (${tankId})`, - ); - } - - if (tank.sellPrice) { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_GOLD, - value: tank.sellPrice["#text"] * 2, - }; - } else if (typeof tank.price === "number") { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price, - }; - } else { - tankPrice = { - type: TankPriceType.TANK_PRICE_TYPE_CREDITS, - value: tank.price["#text"] * 400, - }; - } - - 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]; - - assignArmor(armorRaw, armorId, hullArmor); - }); - const crew: Crew[] = []; - const fixedCamouflage = tankTags.includes("eventCamouflage_user"); - const totalUnlocks: UnlocksListing[] = []; - - Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => { - let entry: Crew; - const index = crew.findIndex( - ({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey, - ); - if (index === -1) { - if (crewKey === "#text") return; - entry = { - type: blitzCrewTypeToBlitzkit[crewKey as BlitzCrewType], - count: 0, - substitute: [], - }; - crew.push(entry); - } else { - entry = crew[index]; - } - - if (typeof value === "string") { - entry.count++; - - if (value !== "") { - entry.substitute = value.split(/\n| /).map((member) => { - return blitzCrewTypeToBlitzkit[member.trim() as BlitzCrewType]; - }); - } - } else { - if (entry.count === undefined) { - entry.count = value.length; - } else { - entry.count += value.length; - } - } - }); - - const camouflages = Object.entries(camouflagesXml.root.camouflages) - .filter(([, camo]) => { - if (!camo.vehicleFilter.include) return false; - if (camo.unlockCostCategory !== "legendary-skins-gold") return false; - - const includeArray = Array.isArray(camo.vehicleFilter.include) - ? camo.vehicleFilter.include - : [camo.vehicleFilter.include]; - - return includeArray.some((filter) => { - if ("vehicle" in filter && filter.vehicle?.name) { - return filter.vehicle.name === `${nation}:${tankKey}`; - } - - return false; - }); - }) - .map(([, camo]) => camo.id); - - let equalizerEntry = tierEqualizer.find( - (line) => line[0] === `${nation}:${tankKey}`, - ); - - let equalizer: Equalizer | undefined; - - if (equalizerEntry) { - const [health, penetration, module_health, damage, armor] = - equalizerEntry?.slice(1).map(Number); - equalizer = { health, penetration, module_health, damage, armor }; - } - - tankDefinitions.tanks[tankId] = { - ancestors: [], - successors: [], - id: tankId, - dev_name: tankKey, - roles: {}, - camouflages: camouflages, - fixed_camouflage: fixedCamouflage, - crew, - weight: tankDefinition.root.hull.weight, - health: tankDefinition.root.hull.maxHealth, - speed_forwards: tankDefinition.root.speedLimits.forward, - speed_backwards: tankDefinition.root.speedLimits.backward, - equipment_preset: - typeof equipment === "string" ? equipment : equipment.at(-1)!, - max_consumables: tankDefinition.root.consumableSlots, - max_provisions: tankDefinition.root.provisionSlots, - name: - (tank.shortUserString - ? getString(tank.shortUserString) - : undefined) ?? getString(tank.userString), - slug, - nation, - type: tankTags.includes("collectible") - ? TankType.TANK_TYPE_COLLECTOR - : (typeof tank.price === "number" ? false : "gold" in tank.price) - ? TankType.TANK_TYPE_PREMIUM - : TankType.TANK_TYPE_RESEARCHABLE, - tier: tank.level, - class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass], - testing: tankTags.includes("testTank"), - deprecated: tankTags.includes("deprecated"), - price: tankPrice, - camouflage_still: tankDefinition.root.invisibility.still, - camouflage_moving: tankDefinition.root.invisibility.moving, - camouflage_on_fire: tankDefinition.root.invisibility.firePenalty, - turrets: [], - engines: [], - tracks: [], - equalizer, - }; - - if (tank.combatRole) { - Object.entries(tank.combatRole).forEach(([gameMode, role]) => { - const id = Object.entries(gameModeNativeNames).find( - ([key]) => key.toLowerCase() === gameMode.toLowerCase(), - )?.[1]; - - if (id === undefined) { - throw new Error( - `Unknown game mode in tank ${tankKey}: ${gameMode}`, - ); - } - - tankDefinitions.tanks[tankId].roles[id] = combatRoles[role].id; - }); - } - - modelDefinitions.models[tankId] = { - armor: hullArmor, - turret_origin: vector3TupleToBlitzkit(turretOrigin), - initial_turret_rotation: tankDefinition.root.hull.turretInitialRotation - ? { - yaw: tankDefinition.root.hull.turretInitialRotation.yaw, - pitch: tankDefinition.root.hull.turretInitialRotation.pitch, - roll: tankDefinition.root.hull.turretInitialRotation.roll, - } - : undefined, - bounding_box: { - min: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.min), - max: vector3TupleToBlitzkit(tankParameters.collision.hull.bbox.max), - }, - turrets: {}, - tracks: {}, - }; - - Object.keys(tankDefinition.root.chassis).forEach((key) => { - const track = tankDefinition.root.chassis[key]; - const trackId = toUniqueId(nation, chassisList.root.ids[key]); - const terrainResistances = track.terrainResistance - .split(" ") - .map(Number); - const trackArmorRaw = track.armor.leftTrack; - const hullOrigin = track.hullPosition - .split(" ") - .map(Number) as Vector3Tuple; - - totalUnlocks.push(track.unlocks); - tankDefinitions.tanks[tankId].tracks.push({ - id: trackId, - weight: track.weight, - name: getString(track.userString), - traverse_speed: track.rotationSpeed, - dispersion_move: track.shotDispersionFactors.vehicleMovement, - dispersion_traverse: track.shotDispersionFactors.vehicleRotation, - resistance_hard: terrainResistances[0], - resistance_medium: terrainResistances[1], - resistance_soft: terrainResistances[2], - tier: track.level, - unlocks: resolveUnlocks(track.unlocks), - }); - - modelDefinitions.models[tankId].tracks[trackId] = { - thickness: - typeof trackArmorRaw === "number" - ? trackArmorRaw - : trackArmorRaw["#text"], - origin: vector3TupleToBlitzkit(hullOrigin), - }; - }); - - Object.keys(tankDefinition.root.engines).forEach((engineKey) => { - const engine = tankDefinition.root.engines[engineKey]; - const engineListEntry = enginesList.root.shared[engineKey]; - const engineId = toUniqueId(nation, enginesList.root.ids[engineKey]); - - totalUnlocks.push(engine.unlocks); - tankDefinitions.tanks[tankId].engines.push({ - id: engineId, - name: getString(engineListEntry.userString), - fire_chance: engineListEntry.fireStartingChance, - tier: engineListEntry.level, - weight: engineListEntry.weight, - power: engineListEntry.power, - unlocks: resolveUnlocks(engine.unlocks), - }); - }); - - Object.keys(tankDefinition.root.turrets0).forEach( - (turretKey, turretIndex) => { - const turret = tankDefinition.root.turrets0[turretKey]; - const turretModel = Number( - parsePath(turret.models.undamaged).name.split("_")[1], - ); - const turretId = toUniqueId(nation, turretList.root.ids[turretKey]); - const turretYaw = ( - typeof turret.yawLimits === "string" - ? turret.yawLimits - : turret.yawLimits.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunOrigin = ( - typeof turret.gunPosition === "string" - ? turret.gunPosition - : turret.gunPosition[0] - ) - .split(" ") - .map(Number) as Vector3Tuple; - const turretArmor: Armor = { thickness: {}, spaced: [] }; - - totalUnlocks.push(turret.unlocks); - 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]; - - assignArmor(armorRaw, armorId, turretArmor); - }); - - turret.userString; - - tankDefinitions.tanks[tankId].turrets.push({ - id: turretId, - traverse_speed: turret.rotationSpeed, - name: getString(turret.userString), - tier: turret.level, - guns: [], - health: turret.maxHealth, - view_range: turret.circularVisionRadius, - weight: turret.weight, - unlocks: resolveUnlocks(turret.unlocks), - }); - - modelDefinitions.models[tankId].turrets[turretId] = { - armor: turretArmor, - gun_origin: vector3TupleToBlitzkit(gunOrigin), - model_id: turretModel, - yaw: - -turretYaw[0] + turretYaw[1] === 360 - ? undefined - : { min: turretYaw[0], max: turretYaw[1] }, - guns: {}, - bounding_box: { - min: vector3TupleToBlitzkit( - tankParameters.collision[ - parsePath(turret.hitTester.collisionModel).name.toLowerCase() - ].bbox.min, - ), - max: vector3TupleToBlitzkit( - tankParameters.collision[ - parsePath(turret.hitTester.collisionModel).name.toLowerCase() - ].bbox.max, - ), - }, - }; - - Object.keys(turret.guns).forEach((gunKey, gunIndex) => { - const gun = turret.guns[gunKey]; - const gunId = toUniqueId(nation, gunList.root.ids[gunKey]); - const gunListEntry = gunList.root.shared[gunKey]; - const pitchLimitsRaw = gun.pitchLimits ?? gunListEntry.pitchLimits; - const gunPitch = ( - typeof pitchLimitsRaw === "string" - ? pitchLimitsRaw - : pitchLimitsRaw.at(-1)! - ) - .split(" ") - .map(Number) as [number, number]; - const gunModel = Number( - parsePath(gun.models.undamaged).name.split("_")[1], - ); - const gunName = getString(gunListEntry.userString); - const gunType = - "clip" in gun - ? gun.pumpGunMode - ? "autoReloader" - : "autoLoader" - : "regular"; - // const gunReload = gun.reloadTime; - // const shellReloads = - const gunClipCount = gunType === "regular" ? 1 : gun.clip!.count; - const front = gun.extraPitchLimits?.front - ? gun.extraPitchLimits.front.split(" ").map(Number) - : undefined; - const back = gun.extraPitchLimits?.back - ? gun.extraPitchLimits.back.split(" ").map(Number) - : undefined; - const transition = gun.extraPitchLimits?.transition - ? typeof gun.extraPitchLimits.transition === "number" - ? gun.extraPitchLimits.transition - : gun.extraPitchLimits.transition.at(-1)! - : undefined; - const gunArmor: Armor = { thickness: {}, spaced: [] }; - const shotDispersionFactors = - gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; - const maskName = `gun_${gunModel.toString().padStart(2, "0")}`; - const maskEnabled = - tankParameters.maskSlice?.[maskName]?.enabled ?? false; - let mask: number | undefined; - let assault_ranges: AssaultRanges | undefined; - - if (gun.extras?.trayShell) { - const types = gun.extras.trayShell.kinds - .split(" ") - .map((string) => { - const trimmed = string.trim(); - - if (trimmed in blitzShellKindToBlitzkit) { - return blitzShellKindToBlitzkit[trimmed as ShellKind]; - } - - throw new SyntaxError(`Invalid shell kind: ${trimmed}`); - }); - const sectorNames = Object.keys(gun.extras.trayShell.sectors); - - if (sectorNames.length !== 1 || sectorNames[0] !== "sector") { - throw new SyntaxError("Invalid tray shell sector"); - } - - const sector = gun.extras.trayShell.sectors.sector; - - assault_ranges = { - types, - ranges: sector.map((value) => ({ - factor: value.factor, - distance: value.distance, - })), - }; - } - - if (maskEnabled) { - const maskRaw = tankParameters.maskSlice![maskName]!; - mask = maskRaw.planePosition[1]; - } else { - mask = undefined; - } - - totalUnlocks.push(gun.unlocks); - Object.keys(gun.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 = gun.armor[name]; - - assignArmor(armorRaw, armorId, gunArmor); - }); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({ - id: gunId, - weight: gunListEntry.weight, - rotation_speed: gunListEntry.rotationSpeed, - name: gunName, - tier: gunListEntry.level, - shells: [], - camouflage_loss: - typeof gun.invisibilityFactorAtShot === "number" - ? gun.invisibilityFactorAtShot - : gun.invisibilityFactorAtShot.at(-1)!, - aim_time: gun.aimingTime ?? gunListEntry.aimingTime, - dispersion_base: - gun.shotDispersionRadius ?? gunListEntry.shotDispersionRadius, - dispersion_damaged: shotDispersionFactors.whileGunDamaged, - dispersion_shot: shotDispersionFactors.afterShot, - dispersion_traverse: shotDispersionFactors.turretRotation, - unlocks: resolveUnlocks(gun.unlocks), - shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo, - assault_ranges, - burst: - gun.burst && gun.burst.count > 1 - ? { - count: gun.burst.count, - interval: 60 / gun.burst.rate, - } - : undefined, - gun_type: - gunType === "regular" - ? { - $case: "regular", - value: { - reload: gun.reloadTime, - }, - } - : gunType === "autoReloader" - ? { - $case: "auto_reloader", - value: { - intra_clip: 60 / gun.clip!.rate, - shell_count: gunClipCount, - shell_reloads: gun - .pumpGunReloadTimes!.split(" ") - .map(Number), - }, - } - : { - $case: "auto_loader", - value: { - intra_clip: 60 / gun.clip!.rate, - clip_reload: gun.reloadTime, - shell_count: gunClipCount, - }, - }, - } satisfies GunDefinition); - - modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = { - armor: gunArmor, - model_id: gunModel, - mask, - thickness: - gun.armor.gun === undefined - ? 0 - : typeof gun.armor.gun === "number" - ? gun.armor.gun - : gun.armor.gun["#text"], - 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, - }, - }; - - Object.keys(gunListEntry.shots).forEach((shellKey) => { - const gunShellEntry = gunListEntry.shots[shellKey]; - const shell = shellList.root[shellKey]; - const shellId = toUniqueId(nation, shell.id); - const shellName = getString(shell.userString); - const penetrationRaw = gunShellEntry.piercingPower - .split(" ") - .filter((penetrationString) => penetrationString !== "") - .map(Number); - - tankDefinitions.tanks[tankId].turrets[turretIndex].guns[ - gunIndex - ].shells.push({ - id: shellId, - name: shellName, - velocity: gunShellEntry.speed, - armor_damage: shell.damage.armor, - module_damage: shell.damage.devices, - caliber: shell.caliber, - normalization: shell.normalizationAngle, - ricochet: shell.ricochetAngle, - type: blitzShellKindToBlitzkit[shell.kind], - explosion_radius: - shell.kind === "HIGH_EXPLOSIVE" - ? (shell.explosionRadius ?? 0) - : undefined, - icon: shell.icon, - penetration: { - near: penetrationRaw[0], - far: penetrationRaw[1], - }, - range: gunShellEntry.maxDistance, - }); - }); - }); - }, - ); - - totalUnlocks.forEach((unlocks) => { - if (unlocks === undefined) return; - - Object.entries(unlocks).forEach(([key, value]) => { - (Array.isArray(value) ? value : [value]).forEach((vehicle) => { - switch (key as keyof BlitzModuleType) { - case "vehicle": { - const tankListEntry = tankList.root[vehicle["#text"]]; - const currentTank = tankDefinitions.tanks[tankId]; - const successorId = toUniqueId(nation, tankListEntry.id); - - tankXps.set(successorId, parseResearchCost(vehicle.cost)); - - if (currentTank.successors === undefined) { - currentTank.successors = []; - } - if (!currentTank.successors!.includes(successorId)) { - currentTank.successors!.push(successorId); - } - break; - } - - case "gun": { - gunXps.set( - toUniqueId(nation, gunList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "turret": { - turretXps.set( - toUniqueId(nation, turretList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "engine": { - engineXps.set( - toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - - case "chassis": { - trackXps.set( - toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]), - parseResearchCost(vehicle.cost), - ); - break; - } - } - }); - }); - }); - - Object.values(tankDefinitions.tanks[tankId].turrets).forEach((turret) => { - turret.research_cost = turretXps.get(turret.id); - - Object.values(turret.guns).forEach((gunRaw) => { - gunRaw.research_cost = gunXps.get(gunRaw.id); - }); - }); - - Object.values(tankDefinitions.tanks[tankId].engines).forEach((engine) => { - engine.research_cost = engineXps.get(engine.id); - }); - - Object.values(tankDefinitions.tanks[tankId].tracks).forEach((track) => { - track.research_cost = trackXps.get(track.id); - }); - } - } - Object.values(tankDefinitions.tanks).forEach((tank) => { tank.research_cost = tankXps.get(tank.id); }); diff --git a/packages/scripts/src/buildAssets/tankIcons.ts b/packages/scripts/src/buildAssets/tankIcons.ts index fb74388d9..3e8995f8d 100644 --- a/packages/scripts/src/buildAssets/tankIcons.ts +++ b/packages/scripts/src/buildAssets/tankIcons.ts @@ -1,43 +1,10 @@ import { NATION_IDS } from "@blitzkit/core"; import sharp from "sharp"; -import type { Vector3Tuple } from "three"; import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect"; import { AssetUploader } from "../core/github/assetUploader"; import { vfs } from "./constants"; import type { VehicleDefinitionList } from "./definitions"; -export interface TankParameters { - resourcesPath: { - smallIconPath: string; - bigIconPath: string; - blitzModelPath: string; - }; - collision: { - [key: string]: { - points: Vector3Tuple; - bbox: { - min: Vector3Tuple; - max: Vector3Tuple; - }; - averageThickness?: Record; - }; - }; - maskSlice?: { - [key: string]: - | { - enabled: boolean; - planePosition: Vector3Tuple; - planeNormal: Vector3Tuple; - planeAxis: Vector3Tuple; - frustumConeBaseRadius: number; - frustumConeAngleDegrees: number; - planeSliceMode: number; - frustumConeSliceMode: number; - } - | undefined; - }; -} - async function extractPackedTankIcon(packedBuffer: Buffer, context: string) { const texture = sharp(packedBuffer); const spriteRect = parsePackedSpriteRect(packedBuffer);