@pietrovich/wot-utils 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/index.js +448 -129
- package/package.json +3 -3
- package/scripts/bake-color-dmg-fsr-vr-rld.sh +0 -81
- package/scripts/bake-pogs-clear.sh +0 -7
- package/scripts/bake-pogs-color-dmg-fsr-vr-rld.sh +0 -7
- package/scripts/baker-lib.sh +0 -165
- package/scripts/extract-atlas-assets.ps1 +0 -120
- package/scripts/extract-atlas-assets.sh +0 -116
- package/scripts/replace-suffixed-with-base.sh +0 -44
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import "module";
|
|
5
5
|
import { config } from "dotenv";
|
|
6
|
-
import { resolve as
|
|
7
|
-
import { Command as
|
|
6
|
+
import { resolve as resolve6 } from "path";
|
|
7
|
+
import { Command as Command23 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/lib/WGData.ts
|
|
10
10
|
import { mkdirSync } from "fs";
|
|
@@ -382,10 +382,10 @@ function printVehiclesTable(vehicles) {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
// src/commands/vehicle/list.ts
|
|
385
|
-
function listVehiclesCommand(
|
|
385
|
+
function listVehiclesCommand(app) {
|
|
386
386
|
return new Command("list").description("List vehicles from the WoT encyclopedia").option("--all", "show all vehicles (default: first 3)").option("--json", "output as JSON").action(async (options) => {
|
|
387
387
|
try {
|
|
388
|
-
const vehicles = await
|
|
388
|
+
const vehicles = await app.getVehicles();
|
|
389
389
|
const items = options.all ? vehicles : vehicles.slice(0, 3);
|
|
390
390
|
if (options.json) {
|
|
391
391
|
printJson(items);
|
|
@@ -405,10 +405,10 @@ function listVehiclesCommand(app2) {
|
|
|
405
405
|
|
|
406
406
|
// src/commands/vehicle/export.ts
|
|
407
407
|
import { Command as Command2 } from "commander";
|
|
408
|
-
function exportCommand(
|
|
408
|
+
function exportCommand(app) {
|
|
409
409
|
return new Command2("export").description("Export all vehicles to a JSON file").option("--output <path>", "output file path (default: wg-export-<timestamp>.json)").option("--no-cache", "bypass cache and fetch fresh data").action(async (options) => {
|
|
410
410
|
try {
|
|
411
|
-
await
|
|
411
|
+
await app.exportVehicles({ output: options.output, useCache: options.cache });
|
|
412
412
|
} catch (error) {
|
|
413
413
|
if (error instanceof WGApiError) {
|
|
414
414
|
console.error(`API error [${error.code}] ${error.field}: ${error.message}`);
|
|
@@ -422,20 +422,20 @@ function exportCommand(app2) {
|
|
|
422
422
|
|
|
423
423
|
// src/commands/cache/purge.ts
|
|
424
424
|
import { Command as Command3 } from "commander";
|
|
425
|
-
function cachePurgeCommand(
|
|
425
|
+
function cachePurgeCommand(app) {
|
|
426
426
|
return new Command3("purge").description("Delete all cached API responses").action(async () => {
|
|
427
|
-
await
|
|
427
|
+
await app.purgeCache();
|
|
428
428
|
console.error("Cache purged.");
|
|
429
429
|
});
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// src/commands/vehicle/best-config.ts
|
|
433
433
|
import { Command as Command4 } from "commander";
|
|
434
|
-
function bestConfigCommand(
|
|
434
|
+
function bestConfigCommand(app) {
|
|
435
435
|
return new Command4("best-config").description("Infer best module configuration for a vehicle").argument("<query>", "tank_id (number), tag, or short_name").action(async (query) => {
|
|
436
436
|
try {
|
|
437
|
-
const result = await
|
|
438
|
-
console.log(
|
|
437
|
+
const result = await app.inferBestConfig(query);
|
|
438
|
+
console.log(app.configToProfileId(result));
|
|
439
439
|
} catch (error) {
|
|
440
440
|
if (error instanceof WGApiError) {
|
|
441
441
|
console.error(`API error [${error.code}] ${error.field}: ${error.message}`);
|
|
@@ -488,11 +488,11 @@ function printTable(rows) {
|
|
|
488
488
|
function extractProfile(data) {
|
|
489
489
|
return Object.values(data)[0];
|
|
490
490
|
}
|
|
491
|
-
function vehicleStatsCommand(
|
|
491
|
+
function vehicleStatsCommand(app) {
|
|
492
492
|
return new Command5("stats").description("Fetch stats for the best module configuration of a vehicle").argument("[query]", "tank_id (number), tag, or short_name").option("--all", "fetch stats for all vehicles and print as a table").option("-q, --quiet", "suppress progress output").option("--json", "output results as JSON array instead of a table (use with --all)").option("--raw", "print full JSON response (single vehicle only)").action(async (query, options) => {
|
|
493
493
|
try {
|
|
494
494
|
if (!query || options.all) {
|
|
495
|
-
const vehicles = await
|
|
495
|
+
const vehicles = await app.getVehicles();
|
|
496
496
|
const sorted = [...vehicles].sort((a, b) => a.short_name.localeCompare(b.short_name));
|
|
497
497
|
const targets = options.all ? sorted : sorted.slice(0, 10);
|
|
498
498
|
const rows = [];
|
|
@@ -505,7 +505,7 @@ function vehicleStatsCommand(app2) {
|
|
|
505
505
|
const results = await Promise.all(
|
|
506
506
|
batch.map(async (v) => {
|
|
507
507
|
try {
|
|
508
|
-
const data = await
|
|
508
|
+
const data = await app.getStatsForBestConfig(v.tank_id);
|
|
509
509
|
const profile = extractProfile(data);
|
|
510
510
|
return {
|
|
511
511
|
name: v.short_name,
|
|
@@ -532,7 +532,7 @@ function vehicleStatsCommand(app2) {
|
|
|
532
532
|
}
|
|
533
533
|
return;
|
|
534
534
|
}
|
|
535
|
-
const result = await
|
|
535
|
+
const result = await app.getStatsForBestConfig(query);
|
|
536
536
|
if (options.raw) {
|
|
537
537
|
console.log(JSON.stringify(result, null, 2));
|
|
538
538
|
return;
|
|
@@ -551,10 +551,10 @@ function vehicleStatsCommand(app2) {
|
|
|
551
551
|
|
|
552
552
|
// src/commands/vehicle/chars.ts
|
|
553
553
|
import { Command as Command6 } from "commander";
|
|
554
|
-
function charsCommand(
|
|
554
|
+
function charsCommand(app) {
|
|
555
555
|
return new Command6("chars").description("List unique characters found in vehicle short names").action(async () => {
|
|
556
556
|
try {
|
|
557
|
-
const { uniqueCharacters, maxLength, longestShortName, avgLength, medianLength, p80Length, p90Length } = await
|
|
557
|
+
const { uniqueCharacters, maxLength, longestShortName, avgLength, medianLength, p80Length, p90Length } = await app.getShortNameStats();
|
|
558
558
|
console.log("uniqueCharacters:", uniqueCharacters);
|
|
559
559
|
console.log("maxLength:", maxLength);
|
|
560
560
|
console.log("longestShortName:", longestShortName);
|
|
@@ -621,10 +621,10 @@ function save() {
|
|
|
621
621
|
}
|
|
622
622
|
|
|
623
623
|
// src/commands/vehicle/long-aliases.ts
|
|
624
|
-
function longAliasesCommand(
|
|
624
|
+
function longAliasesCommand(app) {
|
|
625
625
|
return new Command7("long-aliases").description("List vehicles whose shortened alias exceeds 10 characters").option("--update", "back-fill dictionary with aliases that are too long or unresolved").action(async (options) => {
|
|
626
626
|
try {
|
|
627
|
-
const vehicles = await
|
|
627
|
+
const vehicles = await app.getVehicles();
|
|
628
628
|
let dirty = false;
|
|
629
629
|
for (const vehicle2 of vehicles) {
|
|
630
630
|
const alias = lookupShortName(vehicle2);
|
|
@@ -1067,9 +1067,11 @@ var AtlasManager = class {
|
|
|
1067
1067
|
};
|
|
1068
1068
|
|
|
1069
1069
|
// src/commands/dds/decode.ts
|
|
1070
|
+
import { Command as Command13 } from "commander";
|
|
1071
|
+
|
|
1072
|
+
// src/lib/utils/dds.ts
|
|
1070
1073
|
import { readFile as readFile4, writeFile as writeFile6 } from "fs/promises";
|
|
1071
1074
|
import { extname as extname2, join as join4, dirname as dirname2, basename as basename3 } from "path";
|
|
1072
|
-
import { Command as Command13 } from "commander";
|
|
1073
1075
|
import { PNG as PNG3 } from "pngjs";
|
|
1074
1076
|
|
|
1075
1077
|
// src/lib/utex-mod/DDSUtils.ts
|
|
@@ -1636,22 +1638,26 @@ var DDSUtils = class _DDSUtils {
|
|
|
1636
1638
|
}
|
|
1637
1639
|
};
|
|
1638
1640
|
|
|
1641
|
+
// src/lib/utils/dds.ts
|
|
1642
|
+
async function convertToPngFile(file, pngFileName) {
|
|
1643
|
+
const buffer = await readFile4(file);
|
|
1644
|
+
const frames = new DDSUtils().decode(buffer.buffer);
|
|
1645
|
+
if (frames.length === 0) {
|
|
1646
|
+
throw new Error("No frames decoded from DDS file");
|
|
1647
|
+
}
|
|
1648
|
+
const frame = frames[0];
|
|
1649
|
+
const png = new PNG3({ width: frame.width, height: frame.height });
|
|
1650
|
+
png.data = Buffer.from(frame.image);
|
|
1651
|
+
const outName = pngFileName ?? basename3(file, extname2(file)) + ".png";
|
|
1652
|
+
const outPath = join4(dirname2(file), outName);
|
|
1653
|
+
await writeFile6(outPath, PNG3.sync.write(png));
|
|
1654
|
+
return outPath;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1639
1657
|
// src/commands/dds/decode.ts
|
|
1640
1658
|
function ddsDecodeCommand() {
|
|
1641
1659
|
return new Command13("decode").description("Decode a DDS texture to a 32-bit RGBA PNG").argument("<file>", "path to the .dds file").action(async (file) => {
|
|
1642
|
-
const
|
|
1643
|
-
const frames = new DDSUtils().decode(buffer.buffer);
|
|
1644
|
-
if (frames.length === 0) {
|
|
1645
|
-
console.error("No frames decoded from DDS file");
|
|
1646
|
-
process.exit(1);
|
|
1647
|
-
}
|
|
1648
|
-
const frame = frames[0];
|
|
1649
|
-
const png = new PNG3({ width: frame.width, height: frame.height });
|
|
1650
|
-
png.data = Buffer.from(frame.image);
|
|
1651
|
-
const ext = extname2(file);
|
|
1652
|
-
const outName = basename3(file, ext) + ".png";
|
|
1653
|
-
const outPath = join4(dirname2(file), outName);
|
|
1654
|
-
await writeFile6(outPath, PNG3.sync.write(png));
|
|
1660
|
+
const outPath = await convertToPngFile(file);
|
|
1655
1661
|
console.log(`Decoded \u2192 ${outPath}`);
|
|
1656
1662
|
});
|
|
1657
1663
|
}
|
|
@@ -2224,10 +2230,10 @@ function nameText() {
|
|
|
2224
2230
|
// src/lib/icons/pogs/PogsClear.ts
|
|
2225
2231
|
var iconAligner = createAligner(PogsConstants, "bl.+", [18, 1]);
|
|
2226
2232
|
var PogsClear = class {
|
|
2227
|
-
createBaker(
|
|
2233
|
+
createBaker(app) {
|
|
2228
2234
|
return new ImageBaker(
|
|
2229
2235
|
PogsConstants,
|
|
2230
|
-
[barAndShield(), vehicleIcon(
|
|
2236
|
+
[barAndShield(), vehicleIcon(app, iconAligner), tierText(), nameText()]
|
|
2231
2237
|
);
|
|
2232
2238
|
}
|
|
2233
2239
|
};
|
|
@@ -2235,13 +2241,13 @@ var PogsClear = class {
|
|
|
2235
2241
|
// src/lib/icons/pogs/PogsColor.ts
|
|
2236
2242
|
var iconAligner2 = createAligner(PogsConstants, "bl.+", [18, 1]);
|
|
2237
2243
|
var PogsColor = class {
|
|
2238
|
-
createBaker(
|
|
2244
|
+
createBaker(app) {
|
|
2239
2245
|
return new ImageBaker(
|
|
2240
2246
|
PogsConstants,
|
|
2241
2247
|
[
|
|
2242
2248
|
gradientBackground(),
|
|
2243
2249
|
barAndShield(),
|
|
2244
|
-
vehicleIcon(
|
|
2250
|
+
vehicleIcon(app, iconAligner2),
|
|
2245
2251
|
tierText(),
|
|
2246
2252
|
nameText()
|
|
2247
2253
|
],
|
|
@@ -2290,12 +2296,12 @@ function preRenderedBackground(version2, flavor = "") {
|
|
|
2290
2296
|
var iconAligner3 = createAligner(PogsConstants, "bl.+", [18, "(bh - 1).+"]);
|
|
2291
2297
|
var PogsClearV1 = class {
|
|
2292
2298
|
version = 1;
|
|
2293
|
-
createBaker(
|
|
2299
|
+
createBaker(app) {
|
|
2294
2300
|
return new ImageBaker(
|
|
2295
2301
|
PogsConstants,
|
|
2296
2302
|
[
|
|
2297
2303
|
preRenderedBackground(this.version, "clear"),
|
|
2298
|
-
vehicleIcon(
|
|
2304
|
+
vehicleIcon(app, iconAligner3),
|
|
2299
2305
|
tierText(),
|
|
2300
2306
|
nameText()
|
|
2301
2307
|
]
|
|
@@ -2310,9 +2316,9 @@ var PogsClearV2 = class extends PogsClearV1 {
|
|
|
2310
2316
|
|
|
2311
2317
|
// src/lib/icons/layers/text-view-range.ts
|
|
2312
2318
|
var defaultAligner3 = createAligner(PogsConstants, "br.+", [37, "bh - 1"]);
|
|
2313
|
-
function textViewRange(
|
|
2319
|
+
function textViewRange(app, aligner = defaultAligner3) {
|
|
2314
2320
|
return async (_box, _prev, vehicle2) => {
|
|
2315
|
-
const profiles = await
|
|
2321
|
+
const profiles = await app.getStatsForBestConfig(vehicle2);
|
|
2316
2322
|
const viewRange = profiles[vehicle2.tank_id]?.turret?.view_range;
|
|
2317
2323
|
if (viewRange === void 0) {
|
|
2318
2324
|
return null;
|
|
@@ -2331,9 +2337,9 @@ function textViewRange(app2, aligner = defaultAligner3) {
|
|
|
2331
2337
|
|
|
2332
2338
|
// src/lib/icons/layers/text-reload.ts
|
|
2333
2339
|
var defaultAligner4 = createAligner(PogsConstants, "br.+", [37, "bh - 8"]);
|
|
2334
|
-
function textReload(
|
|
2340
|
+
function textReload(app, aligner = defaultAligner4) {
|
|
2335
2341
|
return async (_box, _prev, vehicle2) => {
|
|
2336
|
-
const profiles = await
|
|
2342
|
+
const profiles = await app.getStatsForBestConfig(vehicle2);
|
|
2337
2343
|
const reloadTime = profiles[vehicle2.tank_id]?.gun?.reload_time;
|
|
2338
2344
|
if (reloadTime === void 0) {
|
|
2339
2345
|
return null;
|
|
@@ -2352,9 +2358,9 @@ function textReload(app2, aligner = defaultAligner4) {
|
|
|
2352
2358
|
|
|
2353
2359
|
// src/lib/icons/layers/text-hull-armor.ts
|
|
2354
2360
|
var defaultAligner5 = createAligner(PogsConstants, "br", ["r", "b - 1"]);
|
|
2355
|
-
function textHullArmor(
|
|
2361
|
+
function textHullArmor(app, aligner = defaultAligner5) {
|
|
2356
2362
|
return async (_box, _prev, vehicle2) => {
|
|
2357
|
-
const profiles = await
|
|
2363
|
+
const profiles = await app.getStatsForBestConfig(vehicle2);
|
|
2358
2364
|
const hull = profiles[vehicle2.tank_id]?.armor?.hull;
|
|
2359
2365
|
if (hull === void 0) {
|
|
2360
2366
|
return null;
|
|
@@ -2374,9 +2380,9 @@ function textHullArmor(app2, aligner = defaultAligner5) {
|
|
|
2374
2380
|
|
|
2375
2381
|
// src/lib/icons/layers/text-turret-armor.ts
|
|
2376
2382
|
var defaultAligner6 = createAligner(PogsConstants, "br", ["r", "b - 8"]);
|
|
2377
|
-
function textTurretArmor(
|
|
2383
|
+
function textTurretArmor(app, aligner = defaultAligner6) {
|
|
2378
2384
|
return async (_box, prev, vehicle2) => {
|
|
2379
|
-
const profiles = await
|
|
2385
|
+
const profiles = await app.getStatsForBestConfig(vehicle2);
|
|
2380
2386
|
const turret = profiles[vehicle2.tank_id]?.armor?.turret;
|
|
2381
2387
|
if (!turret) {
|
|
2382
2388
|
return null;
|
|
@@ -2398,9 +2404,9 @@ function textTurretArmor(app2, aligner = defaultAligner6) {
|
|
|
2398
2404
|
|
|
2399
2405
|
// src/lib/icons/layers/text-penetration.ts
|
|
2400
2406
|
var defaultAligner7 = createAligner(PogsConstants, "rt", ["r", 2]);
|
|
2401
|
-
function textPenetration(
|
|
2407
|
+
function textPenetration(app, aligner = defaultAligner7) {
|
|
2402
2408
|
return async (_box, _prev, vehicle2) => {
|
|
2403
|
-
const profiles = await
|
|
2409
|
+
const profiles = await app.getStatsForBestConfig(vehicle2);
|
|
2404
2410
|
const text = profiles[vehicle2.tank_id]?.ammo?.[0].penetration?.[1];
|
|
2405
2411
|
if (text === void 0) {
|
|
2406
2412
|
return null;
|
|
@@ -2419,9 +2425,9 @@ function textPenetration(app2, aligner = defaultAligner7) {
|
|
|
2419
2425
|
|
|
2420
2426
|
// src/lib/icons/layers/text-damage.ts
|
|
2421
2427
|
var defaultAligner8 = createAligner(PogsConstants, "rt", ["r", 9]);
|
|
2422
|
-
function textDamage(
|
|
2428
|
+
function textDamage(app, aligner = defaultAligner8) {
|
|
2423
2429
|
return async (_box, _prev, vehicle2) => {
|
|
2424
|
-
const profiles = await
|
|
2430
|
+
const profiles = await app.getStatsForBestConfig(vehicle2);
|
|
2425
2431
|
const text = profiles[vehicle2.tank_id]?.ammo?.[0].damage?.[1];
|
|
2426
2432
|
if (text === void 0) {
|
|
2427
2433
|
return null;
|
|
@@ -2473,7 +2479,7 @@ async function renderOneToFile(vehicle2, baker, outDir) {
|
|
|
2473
2479
|
const info = await (await baker.bake(vehicle2)).png().toFile(outPath);
|
|
2474
2480
|
console.log(`${outPath} \u2014 ${info.width}\xD7${info.height}px`);
|
|
2475
2481
|
}
|
|
2476
|
-
function iconRenderCommand(
|
|
2482
|
+
function iconRenderCommand(app) {
|
|
2477
2483
|
return new Command16("render").description("Render a vehicle icon with its short name label composited over a type background").argument("[query]", "tank_id (number), tag, or short_name").option("--all", "render all vehicles").option("--color", "use color variant (default)").option("--clear", "use clear variant (no color background)").option("--bg <version>", "use pre-rendered background at given version").option("--pre-rendered-bg <version>", "alias for --bg").option("--to <dir>", "output directory (default: current working directory)").option("--create", "create output directory if it does not exist").action(async (query, options) => {
|
|
2478
2484
|
try {
|
|
2479
2485
|
if (!query && !options.all) {
|
|
@@ -2502,8 +2508,8 @@ Provide an existing path or add --create to create it.`);
|
|
|
2502
2508
|
} else {
|
|
2503
2509
|
builder = useColor ? new PogsColor() : new PogsClear();
|
|
2504
2510
|
}
|
|
2505
|
-
const vehicles = options.all ? await
|
|
2506
|
-
const bakers = Array.from({ length: CONCURRENCY }, () => builder.createBaker(
|
|
2511
|
+
const vehicles = options.all ? await app.getVehicles() : [await app.findVehicle(query)];
|
|
2512
|
+
const bakers = Array.from({ length: CONCURRENCY }, () => builder.createBaker(app));
|
|
2507
2513
|
let idx = 0;
|
|
2508
2514
|
await Promise.all(
|
|
2509
2515
|
bakers.map(async (baker) => {
|
|
@@ -2543,14 +2549,14 @@ function parseSize(input) {
|
|
|
2543
2549
|
throw new Error(`Invalid size "${input}". Use xs, s/small, m/medium, or l/large.`);
|
|
2544
2550
|
}
|
|
2545
2551
|
}
|
|
2546
|
-
function iconFetchCommand(
|
|
2552
|
+
function iconFetchCommand(app) {
|
|
2547
2553
|
return new Command17("fetch").description("Download vehicle icons into .data/icons/{size}/").argument("[query]", "tank_id (number), tag, or short_name \u2014 omit to fetch all").option("--size <size>", "icon size: s/small, m/medium (default), l/large", "medium").option("--all", "fetch icons for all vehicles").option("--force", "re-download icons that already exist locally").option("--concurrency <n>", "parallel downloads", "10").action(async (query, options) => {
|
|
2548
2554
|
if (!query && !options.all) {
|
|
2549
2555
|
console.error("Provide a query to fetch a single vehicle, or pass --all to fetch every vehicle.");
|
|
2550
2556
|
process.exit(1);
|
|
2551
2557
|
}
|
|
2552
2558
|
try {
|
|
2553
|
-
await
|
|
2559
|
+
await app.fetchIcons({
|
|
2554
2560
|
query,
|
|
2555
2561
|
size: parseSize(options.size),
|
|
2556
2562
|
force: options.force,
|
|
@@ -2569,14 +2575,14 @@ function iconFetchCommand(app2) {
|
|
|
2569
2575
|
|
|
2570
2576
|
// src/commands/icon/shrink.ts
|
|
2571
2577
|
import { Command as Command18 } from "commander";
|
|
2572
|
-
function iconShrinkCommand(
|
|
2578
|
+
function iconShrinkCommand(app) {
|
|
2573
2579
|
return new Command18("shrink").description("Fetch medium icons, trim transparent borders, save as xs in .data/icons/xs/").argument("[query]", "tank_id (number), tag, or short_name \u2014 omit to process all").option("--all", "process icons for all vehicles").option("--force", "re-process icons that are already cached").option("--concurrency <n>", "parallel downloads", "10").action(async (query, options) => {
|
|
2574
2580
|
if (!query && !options.all) {
|
|
2575
2581
|
console.error("Provide a query to shrink a single vehicle icon, or pass --all to process every vehicle.");
|
|
2576
2582
|
process.exit(1);
|
|
2577
2583
|
}
|
|
2578
2584
|
try {
|
|
2579
|
-
await
|
|
2585
|
+
await app.fetchIcons({
|
|
2580
2586
|
query,
|
|
2581
2587
|
size: "xs",
|
|
2582
2588
|
force: options.force ?? false,
|
|
@@ -2624,8 +2630,8 @@ var TomatoApi = class {
|
|
|
2624
2630
|
if (!forceUpdate && await this.hasData(vehicleId, filename)) {
|
|
2625
2631
|
return { vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data: await this.loadData(vehicleId, filename) };
|
|
2626
2632
|
}
|
|
2627
|
-
return new Promise((
|
|
2628
|
-
this.queue.push(() => this.runVehicleVisuals(vehicleId, filename,
|
|
2633
|
+
return new Promise((resolve7) => {
|
|
2634
|
+
this.queue.push(() => this.runVehicleVisuals(vehicleId, filename, resolve7));
|
|
2629
2635
|
void this.drain();
|
|
2630
2636
|
});
|
|
2631
2637
|
}
|
|
@@ -2635,8 +2641,8 @@ var TomatoApi = class {
|
|
|
2635
2641
|
if (!forceUpdate && await this.hasData(vehicleId, filename)) {
|
|
2636
2642
|
return { vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data: await this.loadData(vehicleId, filename) };
|
|
2637
2643
|
}
|
|
2638
|
-
return new Promise((
|
|
2639
|
-
this.queue.push(() => this.runVehicleLoadouts(vehicleId, filename,
|
|
2644
|
+
return new Promise((resolve7) => {
|
|
2645
|
+
this.queue.push(() => this.runVehicleLoadouts(vehicleId, filename, resolve7));
|
|
2640
2646
|
void this.drain();
|
|
2641
2647
|
});
|
|
2642
2648
|
}
|
|
@@ -2646,47 +2652,47 @@ var TomatoApi = class {
|
|
|
2646
2652
|
if (!forceUpdate && await this.hasData(vehicleId, filename)) {
|
|
2647
2653
|
return { vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data: await this.loadData(vehicleId, filename) };
|
|
2648
2654
|
}
|
|
2649
|
-
return new Promise((
|
|
2650
|
-
this.queue.push(() => this.runVehicleProLoadouts(vehicleId, filename,
|
|
2655
|
+
return new Promise((resolve7) => {
|
|
2656
|
+
this.queue.push(() => this.runVehicleProLoadouts(vehicleId, filename, resolve7));
|
|
2651
2657
|
void this.drain();
|
|
2652
2658
|
});
|
|
2653
2659
|
}
|
|
2654
|
-
async runVehicleVisuals(vehicleId, filename,
|
|
2660
|
+
async runVehicleVisuals(vehicleId, filename, resolve7) {
|
|
2655
2661
|
const t0 = Date.now();
|
|
2656
2662
|
const url = `https://tomato.gg/wot/vehicles/visuals/${vehicleId}.json`;
|
|
2657
2663
|
try {
|
|
2658
2664
|
const data = await this.request(url);
|
|
2659
2665
|
await this.saveResponse(vehicleId, filename, data);
|
|
2660
|
-
|
|
2666
|
+
resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
|
|
2661
2667
|
} catch (err) {
|
|
2662
|
-
|
|
2668
|
+
resolve7({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
|
|
2663
2669
|
throw err;
|
|
2664
2670
|
}
|
|
2665
2671
|
}
|
|
2666
|
-
async runVehicleLoadouts(vehicleId, filename,
|
|
2672
|
+
async runVehicleLoadouts(vehicleId, filename, resolve7) {
|
|
2667
2673
|
const t0 = Date.now();
|
|
2668
2674
|
const url = `https://api.tomato.gg/api/tank/loadout-performance/${vehicleId}?cache=true`;
|
|
2669
2675
|
try {
|
|
2670
2676
|
const data = await this.request(url);
|
|
2671
2677
|
await this.saveResponse(vehicleId, filename, data);
|
|
2672
|
-
|
|
2678
|
+
resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
|
|
2673
2679
|
} catch (err) {
|
|
2674
|
-
|
|
2680
|
+
resolve7({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
|
|
2675
2681
|
throw err;
|
|
2676
2682
|
}
|
|
2677
2683
|
}
|
|
2678
|
-
async runVehicleProLoadouts(vehicleId, filename,
|
|
2684
|
+
async runVehicleProLoadouts(vehicleId, filename, resolve7) {
|
|
2679
2685
|
const t0 = Date.now();
|
|
2680
2686
|
const url = `https://api.tomato.gg/api/tank/top-loadouts/${vehicleId}?cache=true`;
|
|
2681
2687
|
try {
|
|
2682
2688
|
const data = await this.request(url);
|
|
2683
2689
|
await this.saveResponse(vehicleId, filename, data);
|
|
2684
|
-
|
|
2690
|
+
resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
|
|
2685
2691
|
} catch (err) {
|
|
2686
2692
|
if (err instanceof HttpError && err.status === 404) {
|
|
2687
2693
|
await this.saveResponse(vehicleId, filename, null);
|
|
2688
2694
|
}
|
|
2689
|
-
|
|
2695
|
+
resolve7({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
|
|
2690
2696
|
throw err;
|
|
2691
2697
|
}
|
|
2692
2698
|
}
|
|
@@ -2818,11 +2824,11 @@ function printSummary(succeeded, failed) {
|
|
|
2818
2824
|
const summary = failed === 0 ? `${total} requests, ${succeeded} succeeded` : `${total} requests, ${succeeded} succeeded, ${failed} failed`;
|
|
2819
2825
|
console.log(summary);
|
|
2820
2826
|
}
|
|
2821
|
-
function tomatoFetchCommand(
|
|
2827
|
+
function tomatoFetchCommand(app, tomato2) {
|
|
2822
2828
|
return new Command19("fetch").description("Fetch Tomato.gg data for one vehicle or a filtered batch").argument("[query]", "tank_id (number), tag, or short_name").option("--tier <n>", "tier filter for batch mode", Number, 11).option("--lt", "include light tanks").option("--mt", "include medium tanks").option("--ht", "include heavy tanks").option("--td", "include tank destroyers (AT-SPG)").option("--at", "include SPGs (artillery)").action(async (query, options) => {
|
|
2823
2829
|
try {
|
|
2824
2830
|
if (query) {
|
|
2825
|
-
const vehicle2 = await
|
|
2831
|
+
const vehicle2 = await app.findVehicle(query);
|
|
2826
2832
|
console.error(`Fetching data for ${vehicle2.short_name} (${vehicle2.tank_id})\u2026`);
|
|
2827
2833
|
const results = await fetchVehicle(tomato2, vehicle2);
|
|
2828
2834
|
printErrors(results);
|
|
@@ -2841,7 +2847,7 @@ function tomatoFetchCommand(app2, tomato2) {
|
|
|
2841
2847
|
}
|
|
2842
2848
|
const selectedTypes = new Set(activeAliases.map(fromTypeAlias));
|
|
2843
2849
|
const tier = options.tier;
|
|
2844
|
-
const vehicles = await
|
|
2850
|
+
const vehicles = await app.getVehicles();
|
|
2845
2851
|
const targets = vehicles.filter((v) => v.tier === tier && selectedTypes.has(v.type));
|
|
2846
2852
|
if (targets.length === 0) {
|
|
2847
2853
|
console.error(`No vehicles match tier ${tier} with the given type filters.`);
|
|
@@ -2876,68 +2882,378 @@ function tomatoFetchCommand(app2, tomato2) {
|
|
|
2876
2882
|
}
|
|
2877
2883
|
|
|
2878
2884
|
// src/commands/bake/run.ts
|
|
2879
|
-
import {
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
import {
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2885
|
+
import { Command as Command21 } from "commander";
|
|
2886
|
+
|
|
2887
|
+
// src/commands/bake/all.ts
|
|
2888
|
+
import { join as join12 } from "path";
|
|
2889
|
+
|
|
2890
|
+
// src/lib/pipeline/pogs.ts
|
|
2891
|
+
import { access as access3, copyFile, mkdir as mkdir5, readdir as readdir3, rename, rm as rm2, unlink, writeFile as writeFile10 } from "fs/promises";
|
|
2892
|
+
import { join as join10 } from "path";
|
|
2893
|
+
|
|
2894
|
+
// src/lib/utils/game-resources.ts
|
|
2895
|
+
import { access as access2, mkdir as mkdir4, readdir as readdir2, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
2896
|
+
import { basename as basename5, join as join9 } from "path";
|
|
2897
|
+
import { unzip } from "fflate";
|
|
2898
|
+
var GUI_PKG_RE = /gui-part\d+\.pkg$/;
|
|
2899
|
+
var ICON_ATLAS_FILES = /* @__PURE__ */ new Set([
|
|
2900
|
+
"gui/flash/atlases/battleAtlas.dds",
|
|
2901
|
+
"gui/flash/atlases/battleAtlas.xml",
|
|
2902
|
+
"gui/flash/atlases/vehicleMarkerAtlas.dds",
|
|
2903
|
+
"gui/flash/atlases/vehicleMarkerAtlas.xml"
|
|
2904
|
+
]);
|
|
2905
|
+
async function extractIconAtlases(srcDir, outDir, onProgress) {
|
|
2906
|
+
await mkdir4(outDir, { recursive: true });
|
|
2907
|
+
const expectedFiles = [...ICON_ATLAS_FILES].map((f) => join9(outDir, basename5(f)));
|
|
2908
|
+
const allPresent = await Promise.all(expectedFiles.map((f) => access2(f).then(() => true, () => false)));
|
|
2909
|
+
if (allPresent.every(Boolean)) {
|
|
2910
|
+
return { pkgsFound: 0, filesExtracted: 0, skipped: true };
|
|
2911
|
+
}
|
|
2912
|
+
const entries = await readdir2(srcDir, { recursive: true });
|
|
2913
|
+
const pkgFiles = entries.filter((e) => GUI_PKG_RE.test(e)).map((e) => join9(srcDir, e));
|
|
2914
|
+
if (pkgFiles.length === 0) {
|
|
2915
|
+
throw new Error(`No gui-partN.pkg files found in: ${srcDir}`);
|
|
2916
|
+
}
|
|
2917
|
+
let filesExtracted = 0;
|
|
2918
|
+
for (const pkgFile of pkgFiles) {
|
|
2919
|
+
onProgress?.(`Checking: ${pkgFile}`);
|
|
2920
|
+
let data;
|
|
2904
2921
|
try {
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2922
|
+
data = await readFile8(pkgFile);
|
|
2923
|
+
} catch {
|
|
2924
|
+
onProgress?.(` Warning: failed to read package file, skipping.`);
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
const extracted = await new Promise((resolve7, reject) => {
|
|
2928
|
+
unzip(data, { filter: (file) => ICON_ATLAS_FILES.has(file.name) }, (err, files) => {
|
|
2929
|
+
if (err) {
|
|
2930
|
+
reject(err);
|
|
2931
|
+
} else {
|
|
2932
|
+
resolve7(files);
|
|
2933
|
+
}
|
|
2908
2934
|
});
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2935
|
+
});
|
|
2936
|
+
const names = Object.keys(extracted);
|
|
2937
|
+
if (names.length === 0) {
|
|
2938
|
+
onProgress?.(` No target files.`);
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
onProgress?.(` Extracting ${names.length} file(s): ${names.join(" ")}`);
|
|
2942
|
+
await Promise.all(
|
|
2943
|
+
names.map((name) => writeFile9(join9(outDir, basename5(name)), extracted[name]))
|
|
2944
|
+
);
|
|
2945
|
+
filesExtracted += names.length;
|
|
2946
|
+
}
|
|
2947
|
+
return { pkgsFound: pkgFiles.length, filesExtracted, skipped: false };
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
// src/lib/pipeline/pogs.ts
|
|
2951
|
+
var ATLAS_NAMES = ["battleAtlas", "vehicleMarkerAtlas"];
|
|
2952
|
+
var RENDER_CONCURRENCY = 5;
|
|
2953
|
+
var STATS_BATCH_SIZE = 5;
|
|
2954
|
+
var REQUIRED_SRC_FILES = ATLAS_NAMES.flatMap((name) => [`${name}.dds`, `${name}.xml`]);
|
|
2955
|
+
async function ensureAtlasAssets(srcDir, gameDir) {
|
|
2956
|
+
const present = await Promise.all(REQUIRED_SRC_FILES.map((f) => pathExists(join10(srcDir, f))));
|
|
2957
|
+
if (present.every(Boolean)) {
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
const missing = REQUIRED_SRC_FILES.filter((_, i) => !present[i]);
|
|
2961
|
+
if (!gameDir) {
|
|
2962
|
+
throw new Error(
|
|
2963
|
+
`Missing atlas files in ${srcDir}: ${missing.join(", ")}
|
|
2964
|
+
Run with --game-dir <wot-dir> to extract them automatically.`
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
console.log("Some source atlas files are missing \u2014 extracting from game directory...");
|
|
2968
|
+
await extractIconAtlases(gameDir, srcDir, (msg) => console.log(msg));
|
|
2969
|
+
}
|
|
2970
|
+
async function pathExists(p) {
|
|
2971
|
+
return access3(p).then(() => true, () => false);
|
|
2972
|
+
}
|
|
2973
|
+
async function copyPngs(fromDir, toDir) {
|
|
2974
|
+
const entries = await readdir3(fromDir);
|
|
2975
|
+
await Promise.all(
|
|
2976
|
+
entries.filter((e) => e.endsWith(".png")).map((e) => copyFile(join10(fromDir, e), join10(toDir, e)))
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
async function cleanBuild(outDir) {
|
|
2980
|
+
await rm2(join10(outDir, ".build"), { recursive: true, force: true });
|
|
2981
|
+
await rm2(join10(outDir, "res_mods"), { recursive: true, force: true });
|
|
2982
|
+
const entries = await readdir3(outDir).catch(() => []);
|
|
2983
|
+
await Promise.all(
|
|
2984
|
+
entries.filter((e) => /\.(png|xml|dds)$/.test(e)).map((e) => unlink(join10(outDir, e)))
|
|
2985
|
+
);
|
|
2986
|
+
}
|
|
2987
|
+
async function decodeDds(srcDir) {
|
|
2988
|
+
for (const name of ATLAS_NAMES) {
|
|
2989
|
+
const ddsPath = join10(srcDir, `${name}.dds`);
|
|
2990
|
+
const pngPath = join10(srcDir, `${name}.png`);
|
|
2991
|
+
if (await pathExists(ddsPath) && !await pathExists(pngPath)) {
|
|
2992
|
+
const out = await convertToPngFile(ddsPath);
|
|
2993
|
+
console.log(`Decoded \u2192 ${out}`);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
async function extractAtlases(srcDir, buildDir, atlasManager2) {
|
|
2998
|
+
const atlasesDir = join10(buildDir, "atlases");
|
|
2999
|
+
await mkdir5(atlasesDir, { recursive: true });
|
|
3000
|
+
for (const name of ATLAS_NAMES) {
|
|
3001
|
+
const destDir = join10(atlasesDir, name);
|
|
3002
|
+
if (await pathExists(destDir)) {
|
|
3003
|
+
console.log(`skipping ${name} extraction \u2014 ${destDir} already exists`);
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
const xmlPath = join10(srcDir, `${name}.xml`);
|
|
3007
|
+
const pngPath = join10(srcDir, `${name}.png`);
|
|
3008
|
+
const count = await atlasManager2.extractAll(xmlPath, pngPath, destDir);
|
|
3009
|
+
console.log(`Extracted ${count} textures \u2192 ${destDir}`);
|
|
3010
|
+
await Promise.all([
|
|
3011
|
+
copyFile(pngPath, join10(atlasesDir, `${name}.png`)),
|
|
3012
|
+
copyFile(xmlPath, join10(atlasesDir, `${name}.xml`))
|
|
3013
|
+
]);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
async function warmCache(app) {
|
|
3017
|
+
console.log("warm up vehicle data cache");
|
|
3018
|
+
const vehicles = await app.getVehicles();
|
|
3019
|
+
console.log(` ${vehicles.length} vehicles`);
|
|
3020
|
+
console.log("warm up vehicle profile cache");
|
|
3021
|
+
let profilesCount = 0;
|
|
3022
|
+
for (let i = 0; i < vehicles.length; i += STATS_BATCH_SIZE) {
|
|
3023
|
+
const batch = vehicles.slice(i, i + STATS_BATCH_SIZE);
|
|
3024
|
+
await Promise.all(
|
|
3025
|
+
batch.map(async (v) => {
|
|
3026
|
+
try {
|
|
3027
|
+
await app.getStatsForBestConfig(v.tank_id);
|
|
3028
|
+
profilesCount++;
|
|
3029
|
+
} catch {
|
|
3030
|
+
}
|
|
3031
|
+
})
|
|
3032
|
+
);
|
|
3033
|
+
}
|
|
3034
|
+
console.log(` ${profilesCount} profiles`);
|
|
3035
|
+
return { vehiclesCount: vehicles.length, profilesCount };
|
|
3036
|
+
}
|
|
3037
|
+
async function renderOneToFile2(vehicle2, baker, outDir) {
|
|
3038
|
+
const outPath = join10(outDir, `${vehicle2.nation}-${vehicle2.tag}.png`);
|
|
3039
|
+
const info = await (await baker.bake(vehicle2)).png().toFile(outPath);
|
|
3040
|
+
console.log(`${outPath} \u2014 ${info.width}\xD7${info.height}px`);
|
|
3041
|
+
}
|
|
3042
|
+
async function renderIcons(app, builder, buildDir, vehiclesCount) {
|
|
3043
|
+
const iconsDir = join10(buildDir, "icons");
|
|
3044
|
+
const existing = await readdir3(iconsDir).catch(() => []);
|
|
3045
|
+
const pngCount = existing.filter((e) => e.endsWith(".png")).length;
|
|
3046
|
+
if (pngCount >= vehiclesCount) {
|
|
3047
|
+
console.log(`skipping icons render \u2014 ${iconsDir} already has ${pngCount} icons`);
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
console.log("generating icons");
|
|
3051
|
+
await mkdir5(iconsDir, { recursive: true });
|
|
3052
|
+
const vehicles = await app.getVehicles();
|
|
3053
|
+
const bakers = Array.from({ length: RENDER_CONCURRENCY }, () => builder.createBaker(app));
|
|
3054
|
+
let idx = 0;
|
|
3055
|
+
await Promise.all(
|
|
3056
|
+
bakers.map(async (baker) => {
|
|
3057
|
+
while (idx < vehicles.length) {
|
|
3058
|
+
const vehicle2 = vehicles[idx++];
|
|
3059
|
+
await renderOneToFile2(vehicle2, baker, iconsDir);
|
|
3060
|
+
}
|
|
3061
|
+
})
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
async function overlayIcons(buildDir) {
|
|
3065
|
+
console.log("overlaying generated icons into atlas directories");
|
|
3066
|
+
const iconsDir = join10(buildDir, "icons");
|
|
3067
|
+
for (const name of ATLAS_NAMES) {
|
|
3068
|
+
await copyPngs(iconsDir, join10(buildDir, "atlases", name));
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
var SUFFIX_RE = /_(7x7|bob|IGR)\.png$/;
|
|
3072
|
+
var SUFFIX_EXCLUDED = /* @__PURE__ */ new Set(["battleLoadingFormBgTips.png", "battleLoadingFormBgTips_7x7.png"]);
|
|
3073
|
+
async function replaceSuffixed(buildDir) {
|
|
3074
|
+
console.log("replacing suffixed variants in atlas with base icons");
|
|
3075
|
+
const iconsDir = join10(buildDir, "icons");
|
|
3076
|
+
for (const name of ATLAS_NAMES) {
|
|
3077
|
+
const atlasDir = join10(buildDir, "atlases", name);
|
|
3078
|
+
const entries = await readdir3(atlasDir);
|
|
3079
|
+
for (const filename of entries) {
|
|
3080
|
+
if (!SUFFIX_RE.test(filename) || SUFFIX_EXCLUDED.has(filename)) {
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3083
|
+
const base = filename.replace(SUFFIX_RE, ".png");
|
|
3084
|
+
if (SUFFIX_EXCLUDED.has(base)) {
|
|
3085
|
+
continue;
|
|
3086
|
+
}
|
|
3087
|
+
const basePath = join10(atlasDir, base);
|
|
3088
|
+
if (!await pathExists(basePath)) {
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
await copyFile(basePath, join10(atlasDir, filename));
|
|
3092
|
+
await copyFile(basePath, join10(iconsDir, filename));
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
async function packAtlases(outDir, buildDir, atlasManager2) {
|
|
3097
|
+
const atlasesOutDir = join10(outDir, "res_mods", "version", "gui", "flash", "atlases");
|
|
3098
|
+
await mkdir5(atlasesOutDir, { recursive: true });
|
|
3099
|
+
for (const name of ATLAS_NAMES) {
|
|
3100
|
+
const result = await atlasManager2.pack(join10(buildDir, "atlases", name));
|
|
3101
|
+
if (result.bins > 1) {
|
|
3102
|
+
console.warn(`Warning: ${name} textures span ${result.bins} bins \u2014 only the first will be written`);
|
|
3103
|
+
}
|
|
3104
|
+
const pngPath = join10(atlasesOutDir, `${name}.png`);
|
|
3105
|
+
await writeFile10(pngPath, result.pngBuffer);
|
|
3106
|
+
await writeFile10(join10(atlasesOutDir, `${name}.xml`), result.xml);
|
|
3107
|
+
await rename(pngPath, join10(atlasesOutDir, `${name}.dds`));
|
|
3108
|
+
console.log(`Packed ${result.count} textures \u2192 ${name}.dds (${result.width}\xD7${result.height})`);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
async function copyContour(outDir, buildDir) {
|
|
3112
|
+
const contourDir = join10(outDir, "res_mods", "version", "gui", "flash", "maps", "icons", "vehicle", "contour");
|
|
3113
|
+
await mkdir5(contourDir, { recursive: true });
|
|
3114
|
+
await copyPngs(join10(buildDir, "icons"), contourDir);
|
|
3115
|
+
}
|
|
3116
|
+
async function pogsPipeline(app, atlasManager2, builder, options) {
|
|
3117
|
+
const { srcDir, outDir, buildDir, clean, cleanAtlasDir, gameDir, prune } = options;
|
|
3118
|
+
if (clean) {
|
|
3119
|
+
await cleanBuild(outDir);
|
|
3120
|
+
if (cleanAtlasDir) {
|
|
3121
|
+
await rm2(srcDir, { recursive: true, force: true });
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
await ensureAtlasAssets(srcDir, gameDir);
|
|
3125
|
+
await decodeDds(srcDir);
|
|
3126
|
+
await extractAtlases(srcDir, buildDir, atlasManager2);
|
|
3127
|
+
const { vehiclesCount } = await warmCache(app);
|
|
3128
|
+
await renderIcons(app, builder, buildDir, vehiclesCount);
|
|
3129
|
+
await overlayIcons(buildDir);
|
|
3130
|
+
await replaceSuffixed(buildDir);
|
|
3131
|
+
await packAtlases(outDir, buildDir, atlasManager2);
|
|
3132
|
+
await copyContour(outDir, buildDir);
|
|
3133
|
+
if (prune) {
|
|
3134
|
+
await rm2(buildDir, { recursive: true, force: true });
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// src/commands/bake/bake-command.ts
|
|
3139
|
+
import { join as join11 } from "path";
|
|
3140
|
+
import { Command as Command20 } from "commander";
|
|
3141
|
+
function resolveBakeOptions(options) {
|
|
3142
|
+
const srcDir = options.atlasDir ?? join11(options.out, ".atlases");
|
|
3143
|
+
const clean = options.clean ?? options.fresh;
|
|
3144
|
+
const prune = options.prune ?? options.tidy;
|
|
3145
|
+
return {
|
|
3146
|
+
srcDir,
|
|
3147
|
+
outDir: options.out,
|
|
3148
|
+
buildDir: join11(options.out, ".build"),
|
|
3149
|
+
gameDir: options.gameDir,
|
|
3150
|
+
cleanAtlasDir: !options.atlasDir,
|
|
3151
|
+
clean,
|
|
3152
|
+
prune
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
function bakeSubcommand(name, description) {
|
|
3156
|
+
return new Command20(name).description(description).requiredOption("--out <dir>", "output directory for the final mod files").option("--atlas-dir <dir>", "directory with extracted atlas DDS/PNG/XML files (default: <out>/.atlases)").option("--game-dir <dir>", "WoT game directory \u2014 used to extract missing atlas files automatically").option("--clean", "wipe build artefacts before starting").option("--fresh", "alias for --clean").option("--prune", "remove intermediate build directory after completion").option("--tidy", "alias for --prune");
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
// src/commands/bake/all.ts
|
|
3160
|
+
function bakeAllCommand(app, atlasManager2) {
|
|
3161
|
+
return bakeSubcommand("all", "Bake all icon sets (clear + color)").action(async (options) => {
|
|
3162
|
+
const resolved = resolveBakeOptions(options);
|
|
3163
|
+
await pogsPipeline(app, atlasManager2, new PogsClearV2(), {
|
|
3164
|
+
...resolved,
|
|
3165
|
+
outDir: join12(options.out, "clear"),
|
|
3166
|
+
buildDir: join12(options.out, "clear", ".build")
|
|
3167
|
+
});
|
|
3168
|
+
await pogsPipeline(app, atlasManager2, new PogsColorV1(), {
|
|
3169
|
+
...resolved,
|
|
3170
|
+
outDir: join12(options.out, "color"),
|
|
3171
|
+
buildDir: join12(options.out, "color", ".build"),
|
|
3172
|
+
clean: false,
|
|
3173
|
+
cleanAtlasDir: false
|
|
3174
|
+
});
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
// src/commands/bake/clear.ts
|
|
3179
|
+
function bakeClearCommand(app, atlasManager2) {
|
|
3180
|
+
return bakeSubcommand("clear", "Bake PogS clear icon set (no colour background)").action(async (options) => {
|
|
3181
|
+
await pogsPipeline(app, atlasManager2, new PogsClearV2(), resolveBakeOptions(options));
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// src/commands/bake/color.ts
|
|
3186
|
+
function bakeColorCommand(app, atlasManager2) {
|
|
3187
|
+
return bakeSubcommand("color", "Bake PogS colour icon set (DMG/FSR/VR/RLD labels, pre-rendered background v1)").action(async (options) => {
|
|
3188
|
+
await pogsPipeline(app, atlasManager2, new PogsColorV1(), resolveBakeOptions(options));
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
// src/commands/bake/run.ts
|
|
3193
|
+
function bakeCommand(app, atlasManager2) {
|
|
3194
|
+
const bake = new Command21("bake").description("Bake PogS icon sets");
|
|
3195
|
+
bake.addCommand(bakeAllCommand(app, atlasManager2));
|
|
3196
|
+
bake.addCommand(bakeClearCommand(app, atlasManager2));
|
|
3197
|
+
bake.addCommand(bakeColorCommand(app, atlasManager2));
|
|
3198
|
+
return bake;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
// src/commands/game/extract-icon-assets.ts
|
|
3202
|
+
import { join as join13 } from "path";
|
|
3203
|
+
import { Command as Command22 } from "commander";
|
|
3204
|
+
function extractIconAssetsCommand() {
|
|
3205
|
+
return new Command22("extract-icon-assets").alias("extract-icons").description(
|
|
3206
|
+
"Extract battle and vehicle marker atlas files from a WoT installation directory"
|
|
3207
|
+
).argument("<src-dir>", "WoT game directory (searched recursively for gui-partN.pkg files)").argument("<out-dir>", "destination directory for extracted files (created if absent)").action(async (srcDir, outDir) => {
|
|
3208
|
+
const { pkgsFound, filesExtracted, skipped } = await extractIconAtlases(
|
|
3209
|
+
srcDir,
|
|
3210
|
+
outDir,
|
|
3211
|
+
(msg) => console.log(msg)
|
|
3212
|
+
);
|
|
3213
|
+
if (skipped) {
|
|
3214
|
+
console.log(`All atlas files already present in ${outDir}, nothing to do.`);
|
|
3215
|
+
} else {
|
|
3216
|
+
console.log(`Done. Scanned ${pkgsFound} package(s), extracted ${filesExtracted} file(s) \u2192 ${outDir}`);
|
|
3217
|
+
}
|
|
3218
|
+
const ddsFiles = ["battleAtlas.dds", "vehicleMarkerAtlas.dds"];
|
|
3219
|
+
for (const ddsFile of ddsFiles) {
|
|
3220
|
+
const ddsPath = join13(outDir, ddsFile);
|
|
3221
|
+
try {
|
|
3222
|
+
const pngPath = await convertToPngFile(ddsPath);
|
|
3223
|
+
console.log(`Converted \u2192 ${pngPath}`);
|
|
3224
|
+
} catch (err) {
|
|
3225
|
+
console.error(`Warning: failed to convert ${ddsFile}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3226
|
+
}
|
|
2911
3227
|
}
|
|
2912
3228
|
});
|
|
2913
3229
|
}
|
|
2914
3230
|
|
|
2915
3231
|
// src/index.ts
|
|
2916
3232
|
var _version = "";
|
|
2917
|
-
var version = _version || "0.2.
|
|
2918
|
-
config({ path:
|
|
2919
|
-
var
|
|
3233
|
+
var version = _version || "0.2.2";
|
|
3234
|
+
config({ path: resolve6(process.env.PIE_WOT_CWD ?? process.cwd(), ".env") });
|
|
3235
|
+
var wgData = new WGData();
|
|
2920
3236
|
var atlasManager = new AtlasManager();
|
|
2921
3237
|
var tomatoApi = new TomatoApi();
|
|
2922
|
-
var program = new
|
|
3238
|
+
var program = new Command23();
|
|
2923
3239
|
program.name("pie-wot").description("CLI utilities for World of Tanks data and assets").version(version).enablePositionalOptions();
|
|
2924
|
-
var vehicle = new
|
|
2925
|
-
vehicle.addCommand(listVehiclesCommand(
|
|
2926
|
-
vehicle.addCommand(exportCommand(
|
|
2927
|
-
vehicle.addCommand(vehicleStatsCommand(
|
|
2928
|
-
vehicle.addCommand(bestConfigCommand(
|
|
2929
|
-
vehicle.addCommand(charsCommand(
|
|
2930
|
-
vehicle.addCommand(longAliasesCommand(
|
|
2931
|
-
var atlas = new
|
|
3240
|
+
var vehicle = new Command23("vehicle").description("WoT vehicle data");
|
|
3241
|
+
vehicle.addCommand(listVehiclesCommand(wgData));
|
|
3242
|
+
vehicle.addCommand(exportCommand(wgData));
|
|
3243
|
+
vehicle.addCommand(vehicleStatsCommand(wgData));
|
|
3244
|
+
vehicle.addCommand(bestConfigCommand(wgData));
|
|
3245
|
+
vehicle.addCommand(charsCommand(wgData));
|
|
3246
|
+
vehicle.addCommand(longAliasesCommand(wgData));
|
|
3247
|
+
var atlas = new Command23("atlas").description("Texture atlas tools");
|
|
2932
3248
|
atlas.addCommand(inspectAtlasCommand(atlasManager));
|
|
2933
3249
|
atlas.addCommand(pickCommand(atlasManager));
|
|
2934
3250
|
atlas.addCommand(extractAtlasCommand(atlasManager));
|
|
2935
3251
|
atlas.addCommand(packAtlasCommand(atlasManager));
|
|
2936
|
-
var font = new
|
|
3252
|
+
var font = new Command23("font").description("Pixel font tools");
|
|
2937
3253
|
font.addCommand(renderCommand());
|
|
2938
|
-
var cache = new
|
|
2939
|
-
cache.addCommand(cachePurgeCommand(
|
|
2940
|
-
var dds = new
|
|
3254
|
+
var cache = new Command23("cache").description("API response cache");
|
|
3255
|
+
cache.addCommand(cachePurgeCommand(wgData));
|
|
3256
|
+
var dds = new Command23("dds").description("DDS texture tools");
|
|
2941
3257
|
dds.addCommand(ddsDecodeCommand());
|
|
2942
3258
|
dds.addCommand(ddsEncodeCommand());
|
|
2943
3259
|
program.addCommand(vehicle);
|
|
@@ -2945,14 +3261,17 @@ program.addCommand(atlas);
|
|
|
2945
3261
|
program.addCommand(font);
|
|
2946
3262
|
program.addCommand(cache);
|
|
2947
3263
|
program.addCommand(dds);
|
|
2948
|
-
var icon = new
|
|
3264
|
+
var icon = new Command23("icon").description("Vehicle icon generation tools");
|
|
2949
3265
|
icon.addCommand(dumpBackgroundCommand());
|
|
2950
|
-
icon.addCommand(iconRenderCommand(
|
|
2951
|
-
icon.addCommand(iconFetchCommand(
|
|
2952
|
-
icon.addCommand(iconShrinkCommand(
|
|
3266
|
+
icon.addCommand(iconRenderCommand(wgData));
|
|
3267
|
+
icon.addCommand(iconFetchCommand(wgData));
|
|
3268
|
+
icon.addCommand(iconShrinkCommand(wgData));
|
|
2953
3269
|
program.addCommand(icon);
|
|
2954
|
-
var tomato = new
|
|
2955
|
-
tomato.addCommand(tomatoFetchCommand(
|
|
3270
|
+
var tomato = new Command23("tomato").description("Tomato.gg data fetcher");
|
|
3271
|
+
tomato.addCommand(tomatoFetchCommand(wgData, tomatoApi));
|
|
2956
3272
|
program.addCommand(tomato);
|
|
2957
|
-
program.addCommand(bakeCommand());
|
|
3273
|
+
program.addCommand(bakeCommand(wgData, atlasManager));
|
|
3274
|
+
var game = new Command23("game").description("WoT game installation tools");
|
|
3275
|
+
game.addCommand(extractIconAssetsCommand());
|
|
3276
|
+
program.addCommand(game);
|
|
2958
3277
|
await program.parseAsync();
|