Files
blitzkit/packages/scripts/src/buildAssets/tankIcons.ts
T

135 lines
3.7 KiB
TypeScript
Raw Normal View History

2025-10-13 23:15:32 -05:00
import { NATION_IDS } from "@blitzkit/core";
import sharp from "sharp";
import type { Vector3Tuple } from "three";
import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect";
2025-10-23 21:22:21 -05:00
import { AssetUploader } from "../core/github/assetUploader";
2025-11-26 13:18:44 -06:00
import { vfs } from "./constants";
2025-10-13 23:15:32 -05:00
import type { VehicleDefinitionList } from "./definitions";
2024-12-16 22:42:27 -06:00
export interface TankParameters {
resourcesPath: {
smallIconPath: string;
bigIconPath: string;
blitzModelPath: string;
};
collision: {
[key: string]: {
points: Vector3Tuple;
bbox: {
min: Vector3Tuple;
max: Vector3Tuple;
};
averageThickness?: Record<string, number>;
};
};
maskSlice?: {
[key: string]:
| {
enabled: boolean;
planePosition: Vector3Tuple;
planeNormal: Vector3Tuple;
planeAxis: Vector3Tuple;
frustumConeBaseRadius: number;
frustumConeAngleDegrees: number;
planeSliceMode: number;
frustumConeSliceMode: number;
}
| undefined;
};
}
2024-01-07 20:07:45 -06:00
async function extractPackedTankIcon(packedBuffer: Buffer, context: string) {
const texture = sharp(packedBuffer);
const spriteRect = parsePackedSpriteRect(packedBuffer);
if (!spriteRect) {
throw new Error(`Missing RIFF sprite bounds for ${context}`);
}
const [left, top, width, height] = spriteRect;
const bounds = [left, top, width, height];
if (!bounds.every(Number.isInteger)) {
throw new Error(
`Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`,
);
}
const metadata = await texture.metadata();
if (metadata.width === undefined || metadata.height === undefined) {
throw new Error(`Failed to read image dimensions for ${context}`);
}
if (
left < 0 ||
top < 0 ||
width <= 0 ||
height <= 0 ||
left + width > metadata.width ||
top + height > metadata.height
) {
throw new Error(
`Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`,
);
}
return await texture.extract({ left, top, width, height }).toBuffer();
}
2024-08-27 14:40:43 -05:00
export async function tankIcons() {
2025-10-13 23:15:32 -05:00
console.log("Building tank icons...");
2024-01-07 20:07:45 -06:00
2025-10-23 21:22:21 -05:00
using uploader = new AssetUploader("tank icons");
2026-04-01 21:40:15 -05:00
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
2024-01-07 20:07:45 -06:00
2025-10-23 21:22:21 -05:00
for (const nation of nations) {
2025-11-26 13:18:44 -06:00
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
2026-04-01 21:40:15 -05:00
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
2025-10-23 21:22:21 -05:00
);
for (const tankKey in tanks.root) {
const tank = tanks.root[tankKey];
if (tankKey.includes("tutorial_bot")) continue;
const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
2025-11-26 13:18:44 -06:00
const parameters = await vfs.yaml<TankParameters>(
2026-04-01 21:40:15 -05:00
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
2024-12-16 22:42:27 -06:00
);
2025-11-26 13:18:44 -06:00
const smallPath = `Data/${parameters.resourcesPath.smallIconPath
2025-10-23 21:22:21 -05:00
.replace(/~res:\//, "")
.replace(/\..+/, "")}.packed.webp`;
2025-11-26 13:18:44 -06:00
const bigPath = `Data/${parameters.resourcesPath.bigIconPath
2025-10-23 21:22:21 -05:00
.replace(/~res:\//, "")
.replace(/\..+/, "")}.packed.webp`;
const big = await extractPackedTankIcon(
await vfs.file(bigPath),
`tank ${tankKey} big`,
);
const small = await extractPackedTankIcon(
await vfs.file(smallPath),
`tank ${tankKey} small`,
);
2024-01-07 20:07:45 -06:00
2025-10-23 21:22:21 -05:00
if (big) {
await uploader.add({
content: big,
path: `icons/tanks/big/${id}.webp`,
});
}
if (small) {
await uploader.add({
content: small,
path: `icons/tanks/small/${id}.webp`,
});
}
}
}
2024-01-20 13:43:03 -06:00
2025-10-23 21:22:21 -05:00
await uploader.flush();
2024-01-07 20:07:45 -06:00
}