@pietrovich/wot-utils 0.2.2 → 0.2.4

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
  }
@@ -2221,35 +2227,6 @@ function nameText() {
2221
2227
  };
2222
2228
  }
2223
2229
 
2224
- // src/lib/icons/pogs/PogsClear.ts
2225
- var iconAligner = createAligner(PogsConstants, "bl.+", [18, 1]);
2226
- var PogsClear = class {
2227
- createBaker(app2) {
2228
- return new ImageBaker(
2229
- PogsConstants,
2230
- [barAndShield(), vehicleIcon(app2, iconAligner), tierText(), nameText()]
2231
- );
2232
- }
2233
- };
2234
-
2235
- // src/lib/icons/pogs/PogsColor.ts
2236
- var iconAligner2 = createAligner(PogsConstants, "bl.+", [18, 1]);
2237
- var PogsColor = class {
2238
- createBaker(app2) {
2239
- return new ImageBaker(
2240
- PogsConstants,
2241
- [
2242
- gradientBackground(),
2243
- barAndShield(),
2244
- vehicleIcon(app2, iconAligner2),
2245
- tierText(),
2246
- nameText()
2247
- ],
2248
- (s) => s.removeAlpha()
2249
- );
2250
- }
2251
- };
2252
-
2253
2230
  // src/lib/icons/layers/pre-rendered-background.ts
2254
2231
  import { readFile as readFile6 } from "fs/promises";
2255
2232
  import { resolve as resolve4 } from "path";
@@ -2286,16 +2263,49 @@ function preRenderedBackground(version2, flavor = "") {
2286
2263
  };
2287
2264
  }
2288
2265
 
2266
+ // src/lib/icons/pogs/PogsBase.ts
2267
+ var PogsBase = class {
2268
+ version = 1;
2269
+ createBaker(data) {
2270
+ return new ImageBaker(
2271
+ PogsConstants,
2272
+ this.getBaseLayers(data)
2273
+ );
2274
+ }
2275
+ getBaseLayers(_data) {
2276
+ return [];
2277
+ }
2278
+ };
2279
+
2280
+ // src/lib/icons/pogs/PogsClearSimpleV1.ts
2281
+ var iconAligner = createAligner(PogsConstants, "bl.+", [18, 24]);
2282
+ var PogsClearSimpleV1 = class extends PogsBase {
2283
+ createBaker(data) {
2284
+ return new ImageBaker(
2285
+ PogsConstants,
2286
+ this.getBaseLayers(data)
2287
+ );
2288
+ }
2289
+ getBaseLayers(data) {
2290
+ return [
2291
+ preRenderedBackground(this.version, "clear"),
2292
+ vehicleIcon(data, iconAligner),
2293
+ tierText(),
2294
+ nameText()
2295
+ ];
2296
+ }
2297
+ };
2298
+
2289
2299
  // src/lib/icons/pogs/PogsClearV1.ts
2290
- var iconAligner3 = createAligner(PogsConstants, "bl.+", [18, "(bh - 1).+"]);
2300
+ var iconAligner2 = createAligner(PogsConstants, "bl.+", [18, "(bh - 1).+"]);
2291
2301
  var PogsClearV1 = class {
2292
2302
  version = 1;
2293
- createBaker(app2) {
2303
+ createBaker(app) {
2294
2304
  return new ImageBaker(
2295
2305
  PogsConstants,
2296
2306
  [
2297
2307
  preRenderedBackground(this.version, "clear"),
2298
- vehicleIcon(app2, iconAligner3),
2308
+ vehicleIcon(app, iconAligner2),
2299
2309
  tierText(),
2300
2310
  nameText()
2301
2311
  ]
@@ -2303,16 +2313,36 @@ var PogsClearV1 = class {
2303
2313
  }
2304
2314
  };
2305
2315
 
2306
- // src/lib/icons/pogs/PogsClearV2.ts
2307
- var PogsClearV2 = class extends PogsClearV1 {
2316
+ // src/lib/icons/pogs/PogsClearSimpleV2.ts
2317
+ var PogsClearSimpleV2 = class extends PogsClearSimpleV1 {
2308
2318
  version = 2;
2309
2319
  };
2310
2320
 
2321
+ // src/lib/icons/pogs/PogsColorSimpleV1.ts
2322
+ var iconAligner3 = createAligner(PogsConstants, "bl.+", [18, 24]);
2323
+ var PogsColorSimpleV1 = class extends PogsBase {
2324
+ createBaker(data) {
2325
+ return new ImageBaker(
2326
+ PogsConstants,
2327
+ this.getBaseLayers(data),
2328
+ (s) => s.removeAlpha()
2329
+ );
2330
+ }
2331
+ getBaseLayers(data) {
2332
+ return [
2333
+ preRenderedBackground(this.version, ""),
2334
+ vehicleIcon(data, iconAligner3),
2335
+ tierText(),
2336
+ nameText()
2337
+ ];
2338
+ }
2339
+ };
2340
+
2311
2341
  // src/lib/icons/layers/text-view-range.ts
2312
2342
  var defaultAligner3 = createAligner(PogsConstants, "br.+", [37, "bh - 1"]);
2313
- function textViewRange(app2, aligner = defaultAligner3) {
2343
+ function textViewRange(app, aligner = defaultAligner3) {
2314
2344
  return async (_box, _prev, vehicle2) => {
2315
- const profiles = await app2.getStatsForBestConfig(vehicle2);
2345
+ const profiles = await app.getStatsForBestConfig(vehicle2);
2316
2346
  const viewRange = profiles[vehicle2.tank_id]?.turret?.view_range;
2317
2347
  if (viewRange === void 0) {
2318
2348
  return null;
@@ -2331,9 +2361,9 @@ function textViewRange(app2, aligner = defaultAligner3) {
2331
2361
 
2332
2362
  // src/lib/icons/layers/text-reload.ts
2333
2363
  var defaultAligner4 = createAligner(PogsConstants, "br.+", [37, "bh - 8"]);
2334
- function textReload(app2, aligner = defaultAligner4) {
2364
+ function textReload(app, aligner = defaultAligner4) {
2335
2365
  return async (_box, _prev, vehicle2) => {
2336
- const profiles = await app2.getStatsForBestConfig(vehicle2);
2366
+ const profiles = await app.getStatsForBestConfig(vehicle2);
2337
2367
  const reloadTime = profiles[vehicle2.tank_id]?.gun?.reload_time;
2338
2368
  if (reloadTime === void 0) {
2339
2369
  return null;
@@ -2350,17 +2380,16 @@ function textReload(app2, aligner = defaultAligner4) {
2350
2380
  };
2351
2381
  }
2352
2382
 
2353
- // src/lib/icons/layers/text-hull-armor.ts
2354
- var defaultAligner5 = createAligner(PogsConstants, "br", ["r", "b - 1"]);
2355
- function textHullArmor(app2, aligner = defaultAligner5) {
2383
+ // src/lib/icons/layers/text-penetration.ts
2384
+ var defaultAligner5 = createAligner(PogsConstants, "rt", ["r", 2]);
2385
+ function textPenetration(app, aligner = defaultAligner5) {
2356
2386
  return async (_box, _prev, vehicle2) => {
2357
- const profiles = await app2.getStatsForBestConfig(vehicle2);
2358
- const hull = profiles[vehicle2.tank_id]?.armor?.hull;
2359
- if (hull === void 0) {
2387
+ const profiles = await app.getStatsForBestConfig(vehicle2);
2388
+ const text = profiles[vehicle2.tank_id]?.ammo?.[0].penetration?.[1];
2389
+ if (text === void 0) {
2360
2390
  return null;
2361
2391
  }
2362
- const text = `${hull.front}*${hull.sides}*${hull.rear}`;
2363
- const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.white);
2392
+ const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.beige);
2364
2393
  const { left, top } = aligner.align({ width, height });
2365
2394
  return {
2366
2395
  input: data,
@@ -2372,20 +2401,17 @@ function textHullArmor(app2, aligner = defaultAligner5) {
2372
2401
  };
2373
2402
  }
2374
2403
 
2375
- // src/lib/icons/layers/text-turret-armor.ts
2376
- var defaultAligner6 = createAligner(PogsConstants, "br", ["r", "b - 8"]);
2377
- function textTurretArmor(app2, aligner = defaultAligner6) {
2378
- return async (_box, prev, vehicle2) => {
2379
- const profiles = await app2.getStatsForBestConfig(vehicle2);
2380
- const turret = profiles[vehicle2.tank_id]?.armor?.turret;
2381
- if (!turret) {
2404
+ // src/lib/icons/layers/text-damage.ts
2405
+ var defaultAligner6 = createAligner(PogsConstants, "rt", ["r", 9]);
2406
+ function textDamage(app, aligner = defaultAligner6) {
2407
+ return async (_box, _prev, vehicle2) => {
2408
+ const profiles = await app.getStatsForBestConfig(vehicle2);
2409
+ const text = profiles[vehicle2.tank_id]?.ammo?.[0].damage?.[1];
2410
+ if (text === void 0) {
2382
2411
  return null;
2383
2412
  }
2384
- const text = `${turret.front}*`;
2385
- const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.white);
2386
- const offsetX = -1 * prev.meta.width + width;
2387
- const shifted = aligner.shift(offsetX, 0);
2388
- const { left, top } = shifted.align({ width, height });
2413
+ const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.beige);
2414
+ const { left, top } = aligner.align({ width, height });
2389
2415
  return {
2390
2416
  input: data,
2391
2417
  raw: { width, height, channels: 4 },
@@ -2396,16 +2422,17 @@ function textTurretArmor(app2, aligner = defaultAligner6) {
2396
2422
  };
2397
2423
  }
2398
2424
 
2399
- // src/lib/icons/layers/text-penetration.ts
2400
- var defaultAligner7 = createAligner(PogsConstants, "rt", ["r", 2]);
2401
- function textPenetration(app2, aligner = defaultAligner7) {
2425
+ // src/lib/icons/layers/text-hull-armor.ts
2426
+ var defaultAligner7 = createAligner(PogsConstants, "br", ["r", "b - 1"]);
2427
+ function textHullArmor(app, aligner = defaultAligner7) {
2402
2428
  return async (_box, _prev, vehicle2) => {
2403
- const profiles = await app2.getStatsForBestConfig(vehicle2);
2404
- const text = profiles[vehicle2.tank_id]?.ammo?.[0].penetration?.[1];
2405
- if (text === void 0) {
2429
+ const profiles = await app.getStatsForBestConfig(vehicle2);
2430
+ const hull = profiles[vehicle2.tank_id]?.armor?.hull;
2431
+ if (hull === void 0) {
2406
2432
  return null;
2407
2433
  }
2408
- const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.beige);
2434
+ const text = `${hull.front}*${hull.sides}*${hull.rear}`;
2435
+ const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.white);
2409
2436
  const { left, top } = aligner.align({ width, height });
2410
2437
  return {
2411
2438
  input: data,
@@ -2417,17 +2444,20 @@ function textPenetration(app2, aligner = defaultAligner7) {
2417
2444
  };
2418
2445
  }
2419
2446
 
2420
- // src/lib/icons/layers/text-damage.ts
2421
- var defaultAligner8 = createAligner(PogsConstants, "rt", ["r", 9]);
2422
- function textDamage(app2, aligner = defaultAligner8) {
2423
- return async (_box, _prev, vehicle2) => {
2424
- const profiles = await app2.getStatsForBestConfig(vehicle2);
2425
- const text = profiles[vehicle2.tank_id]?.ammo?.[0].damage?.[1];
2426
- if (text === void 0) {
2447
+ // src/lib/icons/layers/text-turret-armor.ts
2448
+ var defaultAligner8 = createAligner(PogsConstants, "br", ["r", "b - 8"]);
2449
+ function textTurretArmor(app, aligner = defaultAligner8) {
2450
+ return async (_box, prev, vehicle2) => {
2451
+ const profiles = await app.getStatsForBestConfig(vehicle2);
2452
+ const turret = profiles[vehicle2.tank_id]?.armor?.turret;
2453
+ if (!turret) {
2427
2454
  return null;
2428
2455
  }
2429
- const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.beige);
2430
- const { left, top } = aligner.align({ width, height });
2456
+ const text = `${turret.front}*`;
2457
+ const { data, width, height } = await renderWithShadow("pogs4px", text, Colors.white);
2458
+ const offsetX = -1 * prev.meta.width + width;
2459
+ const shifted = aligner.shift(offsetX, 0);
2460
+ const { left, top } = shifted.align({ width, height });
2431
2461
  return {
2432
2462
  input: data,
2433
2463
  raw: { width, height, channels: 4 },
@@ -2438,23 +2468,26 @@ function textDamage(app2, aligner = defaultAligner8) {
2438
2468
  };
2439
2469
  }
2440
2470
 
2471
+ // src/lib/icons/pogs/max-rld-fsr-vr.ts
2472
+ function getLayers(data) {
2473
+ return [
2474
+ textViewRange(data),
2475
+ textReload(data),
2476
+ textPenetration(data),
2477
+ textDamage(data),
2478
+ textHullArmor(data),
2479
+ textTurretArmor(data)
2480
+ ];
2481
+ }
2482
+
2441
2483
  // src/lib/icons/pogs/PogsColorV1.ts
2442
- var PogsColorV1 = class {
2443
- version = 1;
2484
+ var PogsColorV1 = class extends PogsColorSimpleV1 {
2444
2485
  createBaker(data) {
2445
2486
  return new ImageBaker(
2446
2487
  PogsConstants,
2447
2488
  [
2448
- preRenderedBackground(this.version, ""),
2449
- vehicleIcon(data),
2450
- textViewRange(data),
2451
- textReload(data),
2452
- textPenetration(data),
2453
- textDamage(data),
2454
- textHullArmor(data),
2455
- textTurretArmor(data),
2456
- tierText(),
2457
- nameText()
2489
+ ...this.getBaseLayers(data),
2490
+ ...getLayers(data)
2458
2491
  ],
2459
2492
  (s) => s.removeAlpha()
2460
2493
  );
@@ -2466,6 +2499,24 @@ var PogsColorV2 = class extends PogsColorV1 {
2466
2499
  version = 2;
2467
2500
  };
2468
2501
 
2502
+ // src/lib/icons/pogs/PogsColorSimpleV2.ts
2503
+ var PogsColorSimpleV2 = class extends PogsColorSimpleV1 {
2504
+ version = 2;
2505
+ };
2506
+
2507
+ // src/lib/icons/pogs/PogsClearV2.ts
2508
+ var PogsClearV2 = class extends PogsClearSimpleV2 {
2509
+ createBaker(data) {
2510
+ return new ImageBaker(
2511
+ PogsConstants,
2512
+ [
2513
+ ...this.getBaseLayers(data),
2514
+ ...getLayers(data)
2515
+ ]
2516
+ );
2517
+ }
2518
+ };
2519
+
2469
2520
  // src/commands/icon/render.ts
2470
2521
  var CONCURRENCY = 5;
2471
2522
  async function renderOneToFile(vehicle2, baker, outDir) {
@@ -2473,8 +2524,8 @@ async function renderOneToFile(vehicle2, baker, outDir) {
2473
2524
  const info = await (await baker.bake(vehicle2)).png().toFile(outPath);
2474
2525
  console.log(`${outPath} \u2014 ${info.width}\xD7${info.height}px`);
2475
2526
  }
2476
- function iconRenderCommand(app2) {
2477
- 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) => {
2527
+ function iconRenderCommand(app) {
2528
+ 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("--simple", "use simple icon variant (no extra-data)").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
2529
  try {
2479
2530
  if (!query && !options.all) {
2480
2531
  console.error("Provide a query argument or use --all to render all vehicles.");
@@ -2489,21 +2540,29 @@ Provide an existing path or add --create to create it.`);
2489
2540
  }
2490
2541
  mkdirSync2(outDir, { recursive: true });
2491
2542
  }
2492
- const bgVersion = options.bg ?? options.preRenderedBg ?? (options.clear ? "v2" : void 0);
2493
2543
  let builder;
2494
2544
  const useColor = !options.clear;
2495
- if (bgVersion !== void 0) {
2496
- const version2 = parseInt(bgVersion.replace(/\D+/g, ""), 10);
2497
- if (version2 === 1) {
2545
+ const useSimple = options.simple;
2546
+ let bgVersion = options.bg ?? options.preRenderedBg ?? "v1";
2547
+ if (options.clear) {
2548
+ bgVersion = "v2";
2549
+ }
2550
+ const version2 = parseInt(bgVersion.replace(/\D+/g, ""), 10);
2551
+ if (version2 === 1) {
2552
+ if (useSimple) {
2553
+ builder = useColor ? new PogsColorSimpleV1() : new PogsClearSimpleV1();
2554
+ } else {
2498
2555
  builder = useColor ? new PogsColorV1() : new PogsClearV1();
2556
+ }
2557
+ } else {
2558
+ if (useSimple) {
2559
+ builder = useColor ? new PogsColorSimpleV2() : new PogsClearSimpleV2();
2499
2560
  } else {
2500
2561
  builder = useColor ? new PogsColorV2() : new PogsClearV2();
2501
2562
  }
2502
- } else {
2503
- builder = useColor ? new PogsColor() : new PogsClear();
2504
2563
  }
2505
- const vehicles = options.all ? await app2.getVehicles() : [await app2.findVehicle(query)];
2506
- const bakers = Array.from({ length: CONCURRENCY }, () => builder.createBaker(app2));
2564
+ const vehicles = options.all ? await app.getVehicles() : [await app.findVehicle(query)];
2565
+ const bakers = Array.from({ length: CONCURRENCY }, () => builder.createBaker(app));
2507
2566
  let idx = 0;
2508
2567
  await Promise.all(
2509
2568
  bakers.map(async (baker) => {
@@ -2543,14 +2602,14 @@ function parseSize(input) {
2543
2602
  throw new Error(`Invalid size "${input}". Use xs, s/small, m/medium, or l/large.`);
2544
2603
  }
2545
2604
  }
2546
- function iconFetchCommand(app2) {
2605
+ function iconFetchCommand(app) {
2547
2606
  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
2607
  if (!query && !options.all) {
2549
2608
  console.error("Provide a query to fetch a single vehicle, or pass --all to fetch every vehicle.");
2550
2609
  process.exit(1);
2551
2610
  }
2552
2611
  try {
2553
- await app2.fetchIcons({
2612
+ await app.fetchIcons({
2554
2613
  query,
2555
2614
  size: parseSize(options.size),
2556
2615
  force: options.force,
@@ -2569,14 +2628,14 @@ function iconFetchCommand(app2) {
2569
2628
 
2570
2629
  // src/commands/icon/shrink.ts
2571
2630
  import { Command as Command18 } from "commander";
2572
- function iconShrinkCommand(app2) {
2631
+ function iconShrinkCommand(app) {
2573
2632
  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
2633
  if (!query && !options.all) {
2575
2634
  console.error("Provide a query to shrink a single vehicle icon, or pass --all to process every vehicle.");
2576
2635
  process.exit(1);
2577
2636
  }
2578
2637
  try {
2579
- await app2.fetchIcons({
2638
+ await app.fetchIcons({
2580
2639
  query,
2581
2640
  size: "xs",
2582
2641
  force: options.force ?? false,
@@ -2624,8 +2683,8 @@ var TomatoApi = class {
2624
2683
  if (!forceUpdate && await this.hasData(vehicleId, filename)) {
2625
2684
  return { vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data: await this.loadData(vehicleId, filename) };
2626
2685
  }
2627
- return new Promise((resolve8) => {
2628
- this.queue.push(() => this.runVehicleVisuals(vehicleId, filename, resolve8));
2686
+ return new Promise((resolve7) => {
2687
+ this.queue.push(() => this.runVehicleVisuals(vehicleId, filename, resolve7));
2629
2688
  void this.drain();
2630
2689
  });
2631
2690
  }
@@ -2635,8 +2694,8 @@ var TomatoApi = class {
2635
2694
  if (!forceUpdate && await this.hasData(vehicleId, filename)) {
2636
2695
  return { vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data: await this.loadData(vehicleId, filename) };
2637
2696
  }
2638
- return new Promise((resolve8) => {
2639
- this.queue.push(() => this.runVehicleLoadouts(vehicleId, filename, resolve8));
2697
+ return new Promise((resolve7) => {
2698
+ this.queue.push(() => this.runVehicleLoadouts(vehicleId, filename, resolve7));
2640
2699
  void this.drain();
2641
2700
  });
2642
2701
  }
@@ -2646,47 +2705,47 @@ var TomatoApi = class {
2646
2705
  if (!forceUpdate && await this.hasData(vehicleId, filename)) {
2647
2706
  return { vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data: await this.loadData(vehicleId, filename) };
2648
2707
  }
2649
- return new Promise((resolve8) => {
2650
- this.queue.push(() => this.runVehicleProLoadouts(vehicleId, filename, resolve8));
2708
+ return new Promise((resolve7) => {
2709
+ this.queue.push(() => this.runVehicleProLoadouts(vehicleId, filename, resolve7));
2651
2710
  void this.drain();
2652
2711
  });
2653
2712
  }
2654
- async runVehicleVisuals(vehicleId, filename, resolve8) {
2713
+ async runVehicleVisuals(vehicleId, filename, resolve7) {
2655
2714
  const t0 = Date.now();
2656
2715
  const url = `https://tomato.gg/wot/vehicles/visuals/${vehicleId}.json`;
2657
2716
  try {
2658
2717
  const data = await this.request(url);
2659
2718
  await this.saveResponse(vehicleId, filename, data);
2660
- resolve8({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2719
+ resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2661
2720
  } 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)) });
2721
+ resolve7({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
2663
2722
  throw err;
2664
2723
  }
2665
2724
  }
2666
- async runVehicleLoadouts(vehicleId, filename, resolve8) {
2725
+ async runVehicleLoadouts(vehicleId, filename, resolve7) {
2667
2726
  const t0 = Date.now();
2668
2727
  const url = `https://api.tomato.gg/api/tank/loadout-performance/${vehicleId}?cache=true`;
2669
2728
  try {
2670
2729
  const data = await this.request(url);
2671
2730
  await this.saveResponse(vehicleId, filename, data);
2672
- resolve8({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2731
+ resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2673
2732
  } 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)) });
2733
+ resolve7({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
2675
2734
  throw err;
2676
2735
  }
2677
2736
  }
2678
- async runVehicleProLoadouts(vehicleId, filename, resolve8) {
2737
+ async runVehicleProLoadouts(vehicleId, filename, resolve7) {
2679
2738
  const t0 = Date.now();
2680
2739
  const url = `https://api.tomato.gg/api/tank/top-loadouts/${vehicleId}?cache=true`;
2681
2740
  try {
2682
2741
  const data = await this.request(url);
2683
2742
  await this.saveResponse(vehicleId, filename, data);
2684
- resolve8({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2743
+ resolve7({ vehicleId, fileName: filename, success: true, elapsed: Date.now() - t0, data });
2685
2744
  } catch (err) {
2686
2745
  if (err instanceof HttpError && err.status === 404) {
2687
2746
  await this.saveResponse(vehicleId, filename, null);
2688
2747
  }
2689
- resolve8({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
2748
+ resolve7({ vehicleId, fileName: filename, success: false, elapsed: Date.now() - t0, data: void 0, error: err instanceof Error ? err : new Error(String(err)) });
2690
2749
  throw err;
2691
2750
  }
2692
2751
  }
@@ -2818,11 +2877,11 @@ function printSummary(succeeded, failed) {
2818
2877
  const summary = failed === 0 ? `${total} requests, ${succeeded} succeeded` : `${total} requests, ${succeeded} succeeded, ${failed} failed`;
2819
2878
  console.log(summary);
2820
2879
  }
2821
- function tomatoFetchCommand(app2, tomato2) {
2880
+ function tomatoFetchCommand(app, tomato2) {
2822
2881
  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
2882
  try {
2824
2883
  if (query) {
2825
- const vehicle2 = await app2.findVehicle(query);
2884
+ const vehicle2 = await app.findVehicle(query);
2826
2885
  console.error(`Fetching data for ${vehicle2.short_name} (${vehicle2.tank_id})\u2026`);
2827
2886
  const results = await fetchVehicle(tomato2, vehicle2);
2828
2887
  printErrors(results);
@@ -2841,7 +2900,7 @@ function tomatoFetchCommand(app2, tomato2) {
2841
2900
  }
2842
2901
  const selectedTypes = new Set(activeAliases.map(fromTypeAlias));
2843
2902
  const tier = options.tier;
2844
- const vehicles = await app2.getVehicles();
2903
+ const vehicles = await app.getVehicles();
2845
2904
  const targets = vehicles.filter((v) => v.tier === tier && selectedTypes.has(v.type));
2846
2905
  if (targets.length === 0) {
2847
2906
  console.error(`No vehicles match tier ${tier} with the given type filters.`);
@@ -2876,68 +2935,395 @@ function tomatoFetchCommand(app2, tomato2) {
2876
2935
  }
2877
2936
 
2878
2937
  // 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);
2938
+ import { Command as Command21 } from "commander";
2939
+
2940
+ // src/commands/bake/all.ts
2941
+ import { join as join12 } from "path";
2942
+
2943
+ // src/lib/pipeline/pogs.ts
2944
+ import { access as access3, copyFile, mkdir as mkdir5, readdir as readdir3, rename, rm as rm2, unlink, writeFile as writeFile10 } from "fs/promises";
2945
+ import { join as join10 } from "path";
2946
+
2947
+ // src/lib/utils/game-resources.ts
2948
+ import { access as access2, mkdir as mkdir4, readdir as readdir2, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
2949
+ import { basename as basename5, join as join9 } from "path";
2950
+ import { unzip } from "fflate";
2951
+ var GUI_PKG_RE = /gui-part\d+\.pkg$/;
2952
+ var ICON_ATLAS_FILES = /* @__PURE__ */ new Set([
2953
+ "gui/flash/atlases/battleAtlas.dds",
2954
+ "gui/flash/atlases/battleAtlas.xml",
2955
+ "gui/flash/atlases/vehicleMarkerAtlas.dds",
2956
+ "gui/flash/atlases/vehicleMarkerAtlas.xml"
2957
+ ]);
2958
+ async function extractIconAtlases(srcDir, outDir, onProgress) {
2959
+ await mkdir4(outDir, { recursive: true });
2960
+ const expectedFiles = [...ICON_ATLAS_FILES].map((f) => join9(outDir, basename5(f)));
2961
+ const allPresent = await Promise.all(expectedFiles.map((f) => access2(f).then(() => true, () => false)));
2962
+ if (allPresent.every(Boolean)) {
2963
+ return { pkgsFound: 0, filesExtracted: 0, skipped: true };
2964
+ }
2965
+ const entries = await readdir2(srcDir, { recursive: true });
2966
+ const pkgFiles = entries.filter((e) => GUI_PKG_RE.test(e)).map((e) => join9(srcDir, e));
2967
+ if (pkgFiles.length === 0) {
2968
+ throw new Error(`No gui-partN.pkg files found in: ${srcDir}`);
2969
+ }
2970
+ let filesExtracted = 0;
2971
+ for (const pkgFile of pkgFiles) {
2972
+ onProgress?.(`Checking: ${pkgFile}`);
2973
+ let data;
2904
2974
  try {
2905
- execFileSync("bash", [scriptPath, ...extraArgs], {
2906
- stdio: "inherit",
2907
- env: { ...process.env, PIE_WOT_CWD: process.cwd() }
2975
+ data = await readFile8(pkgFile);
2976
+ } catch {
2977
+ onProgress?.(` Warning: failed to read package file, skipping.`);
2978
+ continue;
2979
+ }
2980
+ const extracted = await new Promise((resolve7, reject) => {
2981
+ unzip(data, { filter: (file) => ICON_ATLAS_FILES.has(file.name) }, (err, files) => {
2982
+ if (err) {
2983
+ reject(err);
2984
+ } else {
2985
+ resolve7(files);
2986
+ }
2908
2987
  });
2909
- } catch (err) {
2910
- process.exit(err.status ?? 1);
2988
+ });
2989
+ const names = Object.keys(extracted);
2990
+ if (names.length === 0) {
2991
+ onProgress?.(` No target files.`);
2992
+ continue;
2993
+ }
2994
+ onProgress?.(` Extracting ${names.length} file(s): ${names.join(" ")}`);
2995
+ await Promise.all(
2996
+ names.map((name) => writeFile9(join9(outDir, basename5(name)), extracted[name]))
2997
+ );
2998
+ filesExtracted += names.length;
2999
+ }
3000
+ return { pkgsFound: pkgFiles.length, filesExtracted, skipped: false };
3001
+ }
3002
+
3003
+ // src/lib/pipeline/pogs.ts
3004
+ var ATLAS_NAMES = ["battleAtlas", "vehicleMarkerAtlas"];
3005
+ var RENDER_CONCURRENCY = 5;
3006
+ var STATS_BATCH_SIZE = 5;
3007
+ var REQUIRED_SRC_FILES = ATLAS_NAMES.flatMap((name) => [`${name}.dds`, `${name}.xml`]);
3008
+ async function ensureAtlasAssets(srcDir, gameDir) {
3009
+ const present = await Promise.all(REQUIRED_SRC_FILES.map((f) => pathExists(join10(srcDir, f))));
3010
+ if (present.every(Boolean)) {
3011
+ return;
3012
+ }
3013
+ const missing = REQUIRED_SRC_FILES.filter((_, i) => !present[i]);
3014
+ if (!gameDir) {
3015
+ throw new Error(
3016
+ `Missing atlas files in ${srcDir}: ${missing.join(", ")}
3017
+ Run with --game-dir <wot-dir> to extract them automatically.`
3018
+ );
3019
+ }
3020
+ console.log("Some source atlas files are missing \u2014 extracting from game directory...");
3021
+ await extractIconAtlases(gameDir, srcDir, (msg) => console.log(msg));
3022
+ }
3023
+ async function pathExists(p) {
3024
+ return access3(p).then(() => true, () => false);
3025
+ }
3026
+ async function copyPngs(fromDir, toDir) {
3027
+ const entries = await readdir3(fromDir);
3028
+ await Promise.all(
3029
+ entries.filter((e) => e.endsWith(".png")).map((e) => copyFile(join10(fromDir, e), join10(toDir, e)))
3030
+ );
3031
+ }
3032
+ async function cleanBuild(outDir) {
3033
+ await rm2(join10(outDir, ".build"), { recursive: true, force: true });
3034
+ await rm2(join10(outDir, "res_mods"), { recursive: true, force: true });
3035
+ const entries = await readdir3(outDir).catch(() => []);
3036
+ await Promise.all(
3037
+ entries.filter((e) => /\.(png|xml|dds)$/.test(e)).map((e) => unlink(join10(outDir, e)))
3038
+ );
3039
+ }
3040
+ async function decodeDds(srcDir) {
3041
+ for (const name of ATLAS_NAMES) {
3042
+ const ddsPath = join10(srcDir, `${name}.dds`);
3043
+ const pngPath = join10(srcDir, `${name}.png`);
3044
+ if (await pathExists(ddsPath) && !await pathExists(pngPath)) {
3045
+ const out = await convertToPngFile(ddsPath);
3046
+ console.log(`Decoded \u2192 ${out}`);
3047
+ }
3048
+ }
3049
+ }
3050
+ async function extractAtlases(srcDir, buildDir, atlasManager2) {
3051
+ const atlasesDir = join10(buildDir, "atlases");
3052
+ await mkdir5(atlasesDir, { recursive: true });
3053
+ for (const name of ATLAS_NAMES) {
3054
+ const destDir = join10(atlasesDir, name);
3055
+ if (await pathExists(destDir)) {
3056
+ console.log(`skipping ${name} extraction \u2014 ${destDir} already exists`);
3057
+ continue;
3058
+ }
3059
+ const xmlPath = join10(srcDir, `${name}.xml`);
3060
+ const pngPath = join10(srcDir, `${name}.png`);
3061
+ const count = await atlasManager2.extractAll(xmlPath, pngPath, destDir);
3062
+ console.log(`Extracted ${count} textures \u2192 ${destDir}`);
3063
+ await Promise.all([
3064
+ copyFile(pngPath, join10(atlasesDir, `${name}.png`)),
3065
+ copyFile(xmlPath, join10(atlasesDir, `${name}.xml`))
3066
+ ]);
3067
+ }
3068
+ }
3069
+ async function warmCache(app) {
3070
+ console.log("warm up vehicle data cache");
3071
+ const vehicles = await app.getVehicles();
3072
+ console.log(` ${vehicles.length} vehicles`);
3073
+ console.log("warm up vehicle profile cache");
3074
+ let profilesCount = 0;
3075
+ for (let i = 0; i < vehicles.length; i += STATS_BATCH_SIZE) {
3076
+ const batch = vehicles.slice(i, i + STATS_BATCH_SIZE);
3077
+ await Promise.all(
3078
+ batch.map(async (v) => {
3079
+ try {
3080
+ await app.getStatsForBestConfig(v.tank_id);
3081
+ profilesCount++;
3082
+ } catch {
3083
+ }
3084
+ })
3085
+ );
3086
+ }
3087
+ console.log(` ${profilesCount} profiles`);
3088
+ return { vehiclesCount: vehicles.length, profilesCount };
3089
+ }
3090
+ async function renderOneToFile2(vehicle2, baker, outDir) {
3091
+ const outPath = join10(outDir, `${vehicle2.nation}-${vehicle2.tag}.png`);
3092
+ const info = await (await baker.bake(vehicle2)).png().toFile(outPath);
3093
+ console.log(`${outPath} \u2014 ${info.width}\xD7${info.height}px`);
3094
+ }
3095
+ async function renderIcons(app, builder, buildDir, vehiclesCount, limit) {
3096
+ const iconsDir = join10(buildDir, "icons");
3097
+ const existing = await readdir3(iconsDir).catch(() => []);
3098
+ const pngCount = existing.filter((e) => e.endsWith(".png")).length;
3099
+ const targetCount = limit !== void 0 ? Math.min(limit, vehiclesCount) : vehiclesCount;
3100
+ if (pngCount >= targetCount) {
3101
+ console.log(`skipping icons render \u2014 ${iconsDir} already has ${pngCount} icons`);
3102
+ return;
3103
+ }
3104
+ console.log("generating icons");
3105
+ await mkdir5(iconsDir, { recursive: true });
3106
+ const vehicles = (await app.getVehicles()).slice(0, targetCount);
3107
+ const bakers = Array.from({ length: RENDER_CONCURRENCY }, () => builder.createBaker(app));
3108
+ let idx = 0;
3109
+ await Promise.all(
3110
+ bakers.map(async (baker) => {
3111
+ while (idx < vehicles.length) {
3112
+ const vehicle2 = vehicles[idx++];
3113
+ await renderOneToFile2(vehicle2, baker, iconsDir);
3114
+ }
3115
+ })
3116
+ );
3117
+ }
3118
+ async function overlayIcons(buildDir) {
3119
+ console.log("overlaying generated icons into atlas directories");
3120
+ const iconsDir = join10(buildDir, "icons");
3121
+ for (const name of ATLAS_NAMES) {
3122
+ await copyPngs(iconsDir, join10(buildDir, "atlases", name));
3123
+ }
3124
+ }
3125
+ var SUFFIX_RE = /_(7x7|bob|IGR)\.png$/;
3126
+ var SUFFIX_EXCLUDED = /* @__PURE__ */ new Set(["battleLoadingFormBgTips.png", "battleLoadingFormBgTips_7x7.png"]);
3127
+ async function replaceSuffixed(buildDir) {
3128
+ console.log("replacing suffixed variants in atlas with base icons");
3129
+ const iconsDir = join10(buildDir, "icons");
3130
+ for (const name of ATLAS_NAMES) {
3131
+ const atlasDir = join10(buildDir, "atlases", name);
3132
+ const entries = await readdir3(atlasDir);
3133
+ for (const filename of entries) {
3134
+ if (!SUFFIX_RE.test(filename) || SUFFIX_EXCLUDED.has(filename)) {
3135
+ continue;
3136
+ }
3137
+ const base = filename.replace(SUFFIX_RE, ".png");
3138
+ if (SUFFIX_EXCLUDED.has(base)) {
3139
+ continue;
3140
+ }
3141
+ const basePath = join10(atlasDir, base);
3142
+ if (!await pathExists(basePath)) {
3143
+ continue;
3144
+ }
3145
+ await copyFile(basePath, join10(atlasDir, filename));
3146
+ await copyFile(basePath, join10(iconsDir, filename));
3147
+ }
3148
+ }
3149
+ }
3150
+ async function packAtlases(outDir, buildDir, atlasManager2) {
3151
+ const atlasesOutDir = join10(outDir, "res_mods", "version", "gui", "flash", "atlases");
3152
+ await mkdir5(atlasesOutDir, { recursive: true });
3153
+ for (const name of ATLAS_NAMES) {
3154
+ const result = await atlasManager2.pack(join10(buildDir, "atlases", name));
3155
+ if (result.bins > 1) {
3156
+ console.warn(`Warning: ${name} textures span ${result.bins} bins \u2014 only the first will be written`);
3157
+ }
3158
+ const pngPath = join10(atlasesOutDir, `${name}.png`);
3159
+ await writeFile10(pngPath, result.pngBuffer);
3160
+ await writeFile10(join10(atlasesOutDir, `${name}.xml`), result.xml);
3161
+ await rename(pngPath, join10(atlasesOutDir, `${name}.dds`));
3162
+ console.log(`Packed ${result.count} textures \u2192 ${name}.dds (${result.width}\xD7${result.height})`);
3163
+ }
3164
+ }
3165
+ async function copyContour(outDir, buildDir) {
3166
+ const contourDir = join10(outDir, "res_mods", "version", "gui", "flash", "maps", "icons", "vehicle", "contour");
3167
+ await mkdir5(contourDir, { recursive: true });
3168
+ await copyPngs(join10(buildDir, "icons"), contourDir);
3169
+ }
3170
+ async function pogsPipeline(app, atlasManager2, builder, options) {
3171
+ const { srcDir, outDir, buildDir, clean, cleanAtlasDir, gameDir, prune, limit } = options;
3172
+ if (clean) {
3173
+ await cleanBuild(outDir);
3174
+ if (cleanAtlasDir) {
3175
+ await rm2(srcDir, { recursive: true, force: true });
3176
+ }
3177
+ }
3178
+ await ensureAtlasAssets(srcDir, gameDir);
3179
+ await decodeDds(srcDir);
3180
+ await extractAtlases(srcDir, buildDir, atlasManager2);
3181
+ const { vehiclesCount } = await warmCache(app);
3182
+ await renderIcons(app, builder, buildDir, vehiclesCount, limit);
3183
+ await overlayIcons(buildDir);
3184
+ await replaceSuffixed(buildDir);
3185
+ await packAtlases(outDir, buildDir, atlasManager2);
3186
+ await copyContour(outDir, buildDir);
3187
+ if (prune) {
3188
+ await rm2(buildDir, { recursive: true, force: true });
3189
+ }
3190
+ }
3191
+
3192
+ // src/commands/bake/bake-command.ts
3193
+ import { join as join11 } from "path";
3194
+ import { Command as Command20 } from "commander";
3195
+ function resolveBakeOptions(options) {
3196
+ const srcDir = options.atlasDir ?? join11(options.out, ".atlases");
3197
+ const clean = options.clean ?? options.fresh;
3198
+ const prune = options.prune ?? options.tidy;
3199
+ const limit = options.limit !== void 0 ? parseInt(options.limit, 10) : void 0;
3200
+ return {
3201
+ srcDir,
3202
+ outDir: options.out,
3203
+ buildDir: join11(options.out, ".build"),
3204
+ gameDir: options.gameDir,
3205
+ cleanAtlasDir: !options.atlasDir,
3206
+ clean,
3207
+ prune,
3208
+ limit
3209
+ };
3210
+ }
3211
+ function bakeSubcommand(name, description) {
3212
+ 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").option("--limit [n]", "render only the first N icons and stop (default N: 10)", "10");
3213
+ }
3214
+
3215
+ // src/commands/bake/all.ts
3216
+ function bakeAllCommand(app, atlasManager2) {
3217
+ return bakeSubcommand("all", "Bake all icon sets (clear + color)").action(async (options) => {
3218
+ const resolved = resolveBakeOptions(options);
3219
+ await pogsPipeline(app, atlasManager2, new PogsClearSimpleV2(), {
3220
+ ...resolved,
3221
+ outDir: join12(options.out, "clear-simple"),
3222
+ buildDir: join12(options.out, "clear-simple", ".build")
3223
+ });
3224
+ await pogsPipeline(app, atlasManager2, new PogsClearV2(), {
3225
+ ...resolved,
3226
+ outDir: join12(options.out, "clear-DMG-RLD-FSR-VR"),
3227
+ buildDir: join12(options.out, "clear-DMG-RLD-FSR-VR", ".build"),
3228
+ clean: false,
3229
+ cleanAtlasDir: false
3230
+ });
3231
+ await pogsPipeline(app, atlasManager2, new PogsColorSimpleV1(), {
3232
+ ...resolved,
3233
+ outDir: join12(options.out, "color-simple"),
3234
+ buildDir: join12(options.out, "color-simple", ".build"),
3235
+ clean: false,
3236
+ cleanAtlasDir: false
3237
+ });
3238
+ await pogsPipeline(app, atlasManager2, new PogsColorV1(), {
3239
+ ...resolved,
3240
+ outDir: join12(options.out, "color-DMG-RLD-FSR-VR"),
3241
+ buildDir: join12(options.out, "color-DMG-RLD-FSR-VR", ".build"),
3242
+ clean: false,
3243
+ cleanAtlasDir: false
3244
+ });
3245
+ });
3246
+ }
3247
+
3248
+ // src/commands/bake/clear.ts
3249
+ function bakeClearCommand(app, atlasManager2) {
3250
+ return bakeSubcommand("clear", "Bake PogS clear icon set (no colour background)").action(async (options) => {
3251
+ await pogsPipeline(app, atlasManager2, new PogsClearSimpleV2(), resolveBakeOptions(options));
3252
+ });
3253
+ }
3254
+
3255
+ // src/commands/bake/color.ts
3256
+ function bakeColorCommand(app, atlasManager2) {
3257
+ return bakeSubcommand("color", "Bake PogS colour icon set (DMG/FSR/VR/RLD labels, pre-rendered background v1)").action(async (options) => {
3258
+ await pogsPipeline(app, atlasManager2, new PogsColorV1(), resolveBakeOptions(options));
3259
+ });
3260
+ }
3261
+
3262
+ // src/commands/bake/run.ts
3263
+ function bakeCommand(app, atlasManager2) {
3264
+ const bake = new Command21("bake").description("Bake PogS icon sets");
3265
+ bake.addCommand(bakeAllCommand(app, atlasManager2));
3266
+ bake.addCommand(bakeClearCommand(app, atlasManager2));
3267
+ bake.addCommand(bakeColorCommand(app, atlasManager2));
3268
+ return bake;
3269
+ }
3270
+
3271
+ // src/commands/game/extract-icon-assets.ts
3272
+ import { join as join13 } from "path";
3273
+ import { Command as Command22 } from "commander";
3274
+ function extractIconAssetsCommand() {
3275
+ return new Command22("extract-icon-assets").alias("extract-icons").description(
3276
+ "Extract battle and vehicle marker atlas files from a WoT installation directory"
3277
+ ).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) => {
3278
+ const { pkgsFound, filesExtracted, skipped } = await extractIconAtlases(
3279
+ srcDir,
3280
+ outDir,
3281
+ (msg) => console.log(msg)
3282
+ );
3283
+ if (skipped) {
3284
+ console.log(`All atlas files already present in ${outDir}, nothing to do.`);
3285
+ } else {
3286
+ console.log(`Done. Scanned ${pkgsFound} package(s), extracted ${filesExtracted} file(s) \u2192 ${outDir}`);
3287
+ }
3288
+ const ddsFiles = ["battleAtlas.dds", "vehicleMarkerAtlas.dds"];
3289
+ for (const ddsFile of ddsFiles) {
3290
+ const ddsPath = join13(outDir, ddsFile);
3291
+ try {
3292
+ const pngPath = await convertToPngFile(ddsPath);
3293
+ console.log(`Converted \u2192 ${pngPath}`);
3294
+ } catch (err) {
3295
+ console.error(`Warning: failed to convert ${ddsFile}: ${err instanceof Error ? err.message : String(err)}`);
3296
+ }
2911
3297
  }
2912
3298
  });
2913
3299
  }
2914
3300
 
2915
3301
  // src/index.ts
2916
3302
  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();
3303
+ var version = _version || "0.2.3";
3304
+ config({ path: resolve6(process.env.PIE_WOT_CWD ?? process.cwd(), ".env") });
3305
+ var wgData = new WGData();
2920
3306
  var atlasManager = new AtlasManager();
2921
3307
  var tomatoApi = new TomatoApi();
2922
- var program = new Command21();
3308
+ var program = new Command23();
2923
3309
  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");
3310
+ var vehicle = new Command23("vehicle").description("WoT vehicle data");
3311
+ vehicle.addCommand(listVehiclesCommand(wgData));
3312
+ vehicle.addCommand(exportCommand(wgData));
3313
+ vehicle.addCommand(vehicleStatsCommand(wgData));
3314
+ vehicle.addCommand(bestConfigCommand(wgData));
3315
+ vehicle.addCommand(charsCommand(wgData));
3316
+ vehicle.addCommand(longAliasesCommand(wgData));
3317
+ var atlas = new Command23("atlas").description("Texture atlas tools");
2932
3318
  atlas.addCommand(inspectAtlasCommand(atlasManager));
2933
3319
  atlas.addCommand(pickCommand(atlasManager));
2934
3320
  atlas.addCommand(extractAtlasCommand(atlasManager));
2935
3321
  atlas.addCommand(packAtlasCommand(atlasManager));
2936
- var font = new Command21("font").description("Pixel font tools");
3322
+ var font = new Command23("font").description("Pixel font tools");
2937
3323
  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");
3324
+ var cache = new Command23("cache").description("API response cache");
3325
+ cache.addCommand(cachePurgeCommand(wgData));
3326
+ var dds = new Command23("dds").description("DDS texture tools");
2941
3327
  dds.addCommand(ddsDecodeCommand());
2942
3328
  dds.addCommand(ddsEncodeCommand());
2943
3329
  program.addCommand(vehicle);
@@ -2945,14 +3331,17 @@ program.addCommand(atlas);
2945
3331
  program.addCommand(font);
2946
3332
  program.addCommand(cache);
2947
3333
  program.addCommand(dds);
2948
- var icon = new Command21("icon").description("Vehicle icon generation tools");
3334
+ var icon = new Command23("icon").description("Vehicle icon generation tools");
2949
3335
  icon.addCommand(dumpBackgroundCommand());
2950
- icon.addCommand(iconRenderCommand(app));
2951
- icon.addCommand(iconFetchCommand(app));
2952
- icon.addCommand(iconShrinkCommand(app));
3336
+ icon.addCommand(iconRenderCommand(wgData));
3337
+ icon.addCommand(iconFetchCommand(wgData));
3338
+ icon.addCommand(iconShrinkCommand(wgData));
2953
3339
  program.addCommand(icon);
2954
- var tomato = new Command21("tomato").description("Tomato.gg data fetcher");
2955
- tomato.addCommand(tomatoFetchCommand(app, tomatoApi));
3340
+ var tomato = new Command23("tomato").description("Tomato.gg data fetcher");
3341
+ tomato.addCommand(tomatoFetchCommand(wgData, tomatoApi));
2956
3342
  program.addCommand(tomato);
2957
- program.addCommand(bakeCommand());
3343
+ program.addCommand(bakeCommand(wgData, atlasManager));
3344
+ var game = new Command23("game").description("WoT game installation tools");
3345
+ game.addCommand(extractIconAssetsCommand());
3346
+ program.addCommand(game);
2958
3347
  await program.parseAsync();