@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/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 resolve7 } from "path";
7
- import { Command as Command21 } from "commander";
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(app2) {
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 app2.getVehicles();
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(app2) {
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 app2.exportVehicles({ output: options.output, useCache: options.cache });
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(app2) {
425
+ function cachePurgeCommand(app) {
426
426
  return new Command3("purge").description("Delete all cached API responses").action(async () => {
427
- await app2.purgeCache();
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(app2) {
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 app2.inferBestConfig(query);
438
- console.log(app2.configToProfileId(result));
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(app2) {
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 app2.getVehicles();
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 app2.getStatsForBestConfig(v.tank_id);
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 app2.getStatsForBestConfig(query);
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(app2) {
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 app2.getShortNameStats();
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(app2) {
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 app2.getVehicles();
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 buffer = await readFile4(file);
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(app2) {
2233
+ createBaker(app) {
2228
2234
  return new ImageBaker(
2229
2235
  PogsConstants,
2230
- [barAndShield(), vehicleIcon(app2, iconAligner), tierText(), nameText()]
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(app2) {
2244
+ createBaker(app) {
2239
2245
  return new ImageBaker(
2240
2246
  PogsConstants,
2241
2247
  [
2242
2248
  gradientBackground(),
2243
2249
  barAndShield(),
2244
- vehicleIcon(app2, iconAligner2),
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(app2) {
2299
+ createBaker(app) {
2294
2300
  return new ImageBaker(
2295
2301
  PogsConstants,
2296
2302
  [
2297
2303
  preRenderedBackground(this.version, "clear"),
2298
- vehicleIcon(app2, iconAligner3),
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(app2, aligner = defaultAligner3) {
2319
+ function textViewRange(app, aligner = defaultAligner3) {
2314
2320
  return async (_box, _prev, vehicle2) => {
2315
- const profiles = await app2.getStatsForBestConfig(vehicle2);
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(app2, aligner = defaultAligner4) {
2340
+ function textReload(app, aligner = defaultAligner4) {
2335
2341
  return async (_box, _prev, vehicle2) => {
2336
- const profiles = await app2.getStatsForBestConfig(vehicle2);
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(app2, aligner = defaultAligner5) {
2361
+ function textHullArmor(app, aligner = defaultAligner5) {
2356
2362
  return async (_box, _prev, vehicle2) => {
2357
- const profiles = await app2.getStatsForBestConfig(vehicle2);
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(app2, aligner = defaultAligner6) {
2383
+ function textTurretArmor(app, aligner = defaultAligner6) {
2378
2384
  return async (_box, prev, vehicle2) => {
2379
- const profiles = await app2.getStatsForBestConfig(vehicle2);
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(app2, aligner = defaultAligner7) {
2407
+ function textPenetration(app, aligner = defaultAligner7) {
2402
2408
  return async (_box, _prev, vehicle2) => {
2403
- const profiles = await app2.getStatsForBestConfig(vehicle2);
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(app2, aligner = defaultAligner8) {
2428
+ function textDamage(app, aligner = defaultAligner8) {
2423
2429
  return async (_box, _prev, vehicle2) => {
2424
- const profiles = await app2.getStatsForBestConfig(vehicle2);
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(app2) {
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 app2.getVehicles() : [await app2.findVehicle(query)];
2506
- const bakers = Array.from({ length: CONCURRENCY }, () => builder.createBaker(app2));
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(app2) {
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 app2.fetchIcons({
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(app2) {
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 app2.fetchIcons({
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((resolve8) => {
2628
- this.queue.push(() => this.runVehicleVisuals(vehicleId, filename, resolve8));
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((resolve8) => {
2639
- this.queue.push(() => this.runVehicleLoadouts(vehicleId, filename, resolve8));
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((resolve8) => {
2650
- this.queue.push(() => this.runVehicleProLoadouts(vehicleId, filename, resolve8));
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, resolve8) {
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
- resolve8({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2666
+ resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2661
2667
  } catch (err) {
2662
- resolve8({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
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, resolve8) {
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
- resolve8({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2678
+ resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2673
2679
  } catch (err) {
2674
- resolve8({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
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, resolve8) {
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
- resolve8({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
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
- resolve8({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
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(app2, tomato2) {
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 app2.findVehicle(query);
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 app2.getVehicles();
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 { execFileSync } from "child_process";
2880
- import { existsSync as existsSync3 } from "fs";
2881
- import { resolve as resolve6 } from "path";
2882
- import { Command as Command20 } from "commander";
2883
- var SCRIPTS = {
2884
- "clear": "bake-pogs-clear.sh",
2885
- "color": "bake-pogs-color-dmg-fsr-vr-rld.sh",
2886
- "simple": "bake-pogs-color-dmg-fsr-vr-rld.sh"
2887
- };
2888
- var scriptsDir = resolve6(findPkgRoot(new URL(import.meta.url)), "scripts");
2889
- function bakeCommand() {
2890
- const names = Object.keys(SCRIPTS).join(", ");
2891
- return new Command20("bake").description("Run a bundled build script").argument("<script>", `available: ${names}`).allowUnknownOption(true).allowExcessArguments(true).passThroughOptions(true).action((scriptName, _options, cmd) => {
2892
- const filename = SCRIPTS[scriptName];
2893
- if (filename === void 0) {
2894
- console.error(`Unknown script: ${scriptName}
2895
- Available: ${names}`);
2896
- process.exit(1);
2897
- }
2898
- const scriptPath = resolve6(scriptsDir, filename);
2899
- if (!existsSync3(scriptPath)) {
2900
- console.error(`Script not found: ${scriptPath}`);
2901
- process.exit(1);
2902
- }
2903
- const extraArgs = cmd.args.slice(1);
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
- execFileSync("bash", [scriptPath, ...extraArgs], {
2906
- stdio: "inherit",
2907
- env: { ...process.env, PIE_WOT_CWD: process.cwd() }
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
- } catch (err) {
2910
- process.exit(err.status ?? 1);
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.1";
2918
- config({ path: resolve7(process.env.PIE_WOT_CWD ?? process.cwd(), ".env") });
2919
- var app = new WGData();
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 Command21();
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 Command21("vehicle").description("WoT vehicle data");
2925
- vehicle.addCommand(listVehiclesCommand(app));
2926
- vehicle.addCommand(exportCommand(app));
2927
- vehicle.addCommand(vehicleStatsCommand(app));
2928
- vehicle.addCommand(bestConfigCommand(app));
2929
- vehicle.addCommand(charsCommand(app));
2930
- vehicle.addCommand(longAliasesCommand(app));
2931
- var atlas = new Command21("atlas").description("Texture atlas tools");
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 Command21("font").description("Pixel font tools");
3252
+ var font = new Command23("font").description("Pixel font tools");
2937
3253
  font.addCommand(renderCommand());
2938
- var cache = new Command21("cache").description("API response cache");
2939
- cache.addCommand(cachePurgeCommand(app));
2940
- var dds = new Command21("dds").description("DDS texture tools");
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 Command21("icon").description("Vehicle icon generation tools");
3264
+ var icon = new Command23("icon").description("Vehicle icon generation tools");
2949
3265
  icon.addCommand(dumpBackgroundCommand());
2950
- icon.addCommand(iconRenderCommand(app));
2951
- icon.addCommand(iconFetchCommand(app));
2952
- icon.addCommand(iconShrinkCommand(app));
3266
+ icon.addCommand(iconRenderCommand(wgData));
3267
+ icon.addCommand(iconFetchCommand(wgData));
3268
+ icon.addCommand(iconShrinkCommand(wgData));
2953
3269
  program.addCommand(icon);
2954
- var tomato = new Command21("tomato").description("Tomato.gg data fetcher");
2955
- tomato.addCommand(tomatoFetchCommand(app, tomatoApi));
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();