Delete the PS build and the pokemon-showdown tooling

deploy.json5 has two targets, assets and smogon. ps.build.ts has none: nothing
it produces has shipped from this repo, and the ani/ rsync it was written for
lives in the README as an example. Everything it pulled along came with it.
tools/sheet is its montage tool and had no other caller.
ps-pokemon.dexnums.mjs, 1125 generated lines, existed only to hand its sheet a
dex number after data/species.json went away. tools/spritedata/{dexnums,names,
pokedex,rename}.ts all read a pokemon-showdown checkout, which is a dependency
nothing in either surviving deploy has.

The branch ps-build points at the commit before this, so it is one checkout
away if the PS side ever comes back.

tools/deploy/coverage.ts is the exception and moves rather than goes. It reads
no PS data and generates no table; it reports what a deploy would delete off
the server, which is what makes --delete-after safe to run, and it only lived
in tools/spritedata because that is where I put it.

Both remaining deploys materialize unchanged: xy/ 1651 names, xyicons/ 1556,
assets byte for byte. psid() keeps its caller in the smogdex sheet, which still
needs PS's spelling of a name even with the PS deploy gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christopher Monsanto
2026-08-23 01:50:21 -04:00
parent 7493d948e6
commit a919602cc2
19 changed files with 7 additions and 3242 deletions

View File

@@ -65,7 +65,7 @@ reuses the build's digests. All state lives in `.build/`.
$ pnpm build # build every deploy's rules, GC stale state
$ pnpm deploy # list the deploys in deploy.json5
$ pnpm deploy assets # run a named deploy
$ node tools/deploy/index.ts build ps.build.ts # build one deploy's rules
$ node tools/deploy/index.ts build smogon.build.ts # build one deploy's rules
$ node tools/deploy/index.ts run smogon.build.ts -o deploy/smogon
$ node tools/deploy/index.ts inspect src/minisprites/items/ileftovers.png -o /tmp/out
```
@@ -99,10 +99,10 @@ for eyeballing what would ship.
{subset: ["**"], cmd: "smogonctl assets upload sprites"},
],
},
ps: {
buildFile: "ps.build.ts",
smogon: {
buildFile: "smogon.build.ts",
deploy: [
{subset: ["ani/**"], dir: true, cmd: "rsync -a %d/ani/ ps:sprites/ani/"},
{subset: ["xy/**"], dir: true, cmd: "rsync -a --delete-after %d/xy/ smogon:/smog2/sprites/xy"},
],
},
}

18
pnpm-lock.yaml generated
View File

@@ -58,12 +58,6 @@ importers:
specifier: ^3.0.0
version: 3.2.0
tools/sheet:
dependencies:
'@types/node':
specifier: ^24.10.7
version: 24.13.3
tools/smogdexspritesheet:
dependencies:
'@smogon/sprite-data':
@@ -79,18 +73,6 @@ importers:
specifier: ^3.4.1
version: 3.5.1
tools/spritedata:
dependencies:
'@smogon/sprite-data':
specifier: workspace:*
version: link:../../data/lib
'@smogon/sprite-root':
specifier: workspace:*
version: link:../../lib/root
'@types/node':
specifier: ^24.10.7
version: 24.13.3
tools/trim:
dependencies:
'@types/node':

View File

@@ -1,580 +0,0 @@
import fs from 'fs';
import path from 'path';
import * as spritedata from '@smogon/sprite-data/index.ts';
import root from '@smogon/sprite-root/index.ts';
const itemsDir = path.join(root, "src/minisprites/items");
// Derived from pokemon-showdown/data/items.ts
const ITEMS = {
"absorbbulb": 2,
"adamantorb": 4,
"aguavberry": 5,
"airballoon": 6,
"apicotberry": 10,
"armorfossil": 12,
"aspearberry": 13,
//"burntberry": 13,
"babiriberry": 17,
"belueberry": 21,
"berryjuice": 22,
"bignugget": 27,
"bigroot": 29,
"bindingband": 31,
"blackbelt": 32,
"blacksludge": 34,
"blackglasses": 35,
"blueorb": 41,
"blukberry": 44,
"brightpowder": 51,
"buggem": 53,
"burndrive": 54,
"cellbattery": 60,
"charcoal": 61,
"chartiberry": 62,
"cheriberry": 63,
//"przcureberry": 63,
"cherishball": 64,
"chestoberry": 65,
//"mintberry": 65,
"chilanberry": 66,
"chilldrive": 67,
"choiceband": 68,
"choicescarf": 69,
"choicespecs": 70,
"chopleberry": 71,
"clawfossil": 72,
"cobaberry": 76,
"colburberry": 78,
"cornnberry": 81,
"coverfossil": 85,
"custapberry": 86,
"damprock": 88,
"darkgem": 89,
"dawnstone": 92,
"deepseascale": 93,
"deepseatooth": 94,
"destinyknot": 95,
"diveball": 101,
"domefossil": 102,
"dousedrive": 103,
"dracoplate": 105,
"dragonfang": 106,
"dragongem": 107,
"dragonscale": 108,
"dreadplate": 110,
"dreamball": 111,
"dubiousdisc": 113,
"durinberry": 114,
"duskball": 115,
"duskstone": 116,
"earthplate": 117,
"ejectbutton": 118,
"electirizer": 119,
"electricgem": 120,
"enigmaberry": 124,
"eviolite": 130,
"expertbelt": 132,
"fastball": 137,
"fightinggem": 139,
"figyberry": 140,
"firegem": 141,
"firestone": 142,
"fistplate": 143,
"flameorb": 145,
"flameplate": 146,
"floatstone": 147,
"flyinggem": 149,
"focusband": 150,
"focussash": 151,
"friendball": 153,
"fullincense": 155,
"ganlonberry": 158,
"ghostgem": 161,
"grassgem": 172,
"greatball": 174,
"grepaberry": 178,
"gripclaw": 179,
"griseousorb": 180,
"groundgem": 182,
"habanberry": 185,
"hardstone": 187,
"healball": 188,
"heatrock": 193,
"heavyball": 194,
"helixfossil": 195,
"hondewberry": 213,
"iapapaberry": 217,
"icegem": 218,
"icicleplate": 220,
"icyrock": 221,
"insectplate": 223,
"ironball": 224,
"ironplate": 225,
"jabocaberry": 230,
"kasibberry": 233,
"kebiaberry": 234,
"kelpsyberry": 235,
"kingsrock": 236,
"laggingtail": 237,
"lansatberry": 238,
"laxincense": 240,
"leafstone": 241,
"leftovers": 242,
"leppaberry": 244,
//"mysteryberry": 244,
"levelball": 246,
"liechiberry": 248,
"lifeorb": 249,
"lightball": 251,
"lightclay": 252,
"loveball": 258,
"luckypunch": 261,
"lumberry": 262,
//"miracleberry": 262,
"lureball": 264,
"lustrousorb": 265,
"luxuryball": 266,
"machobrace": 269,
"magmarizer": 272,
"magnet": 273,
"magoberry": 274,
"magostberry": 275,
"masterball": 276,
"meadowplate": 282,
"mentalherb": 285,
"metalcoat": 286,
"metalpowder": 287,
"metronome": 289,
"micleberry": 290,
"mindplate": 291,
"miracleseed": 292,
"moonball": 294,
"moonstone": 295,
"muscleband": 297,
"mysticwater": 300,
"nanabberry": 302,
"nestball": 303,
"netball": 304,
"nevermeltice": 305,
"nomelberry": 306,
"normalgem": 307,
"strangeball": 308,
"occaberry": 311,
"oddincense": 312,
"oldamber": 314,
"oranberry": 319,
//"berry": 319,
"ovalstone": 321,
"pamtreberry": 323,
"parkball": 325,
"passhoberry": 329,
"payapaberry": 330,
"pechaberry": 333,
//"psncureberry": 333,
"persimberry": 334,
//"bitterberry": 334,
"petayaberry": 335,
"pinapberry": 337,
"plumefossil": 339,
"poisonbarb": 343,
"poisongem": 344,
"pokeball": 345,
"pomegberry": 351,
"poweranklet": 354,
"powerband": 355,
"powerbelt": 356,
"powerbracer": 357,
"powerherb": 358,
"powerlens": 359,
"powerweight": 360,
"premierball": 363,
"prismscale": 365,
"protector": 367,
"psychicgem": 369,
"qualotberry": 371,
"quickball": 372,
"quickclaw": 373,
"quickpowder": 374,
"rabutaberry": 375,
"rarebone": 379,
"rawstberry": 381,
//"iceberry": 381,
"razorclaw": 382,
"razorfang": 383,
"razzberry": 384,
"reapercloth": 385,
"redcard": 387,
"berserkgene": 388,
"redorb": 390,
"repeatball": 401,
"mail": 403,
"rindoberry": 409,
"ringtarget": 410,
"rockgem": 415,
"rockincense": 416,
"rockyhelmet": 417,
"rootfossil": 418,
"roseincense": 419,
"rowapberry": 420,
"safariball": 425,
"salacberry": 426,
"scopelens": 429,
"seaincense": 430,
"sharpbeak": 436,
"shedshell": 437,
"shellbell": 438,
"shinystone": 439,
"shockdrive": 442,
"shucaberry": 443,
"silkscarf": 444,
//"pinkbow": 444,
//"polkadotbow": 444,
"silverpowder": 447,
"sitrusberry": 448,
//"goldberry": 448,
"skullfossil": 449,
"skyplate": 450,
"smoothrock": 453,
"softsand": 456,
"souldew": 459,
"spelltag": 461,
"spelonberry": 462,
"splashplate": 463,
"spookyplate": 464,
"sportball": 465,
"starfberry": 472,
"steelgem": 473,
"leek": 475,
//"stick": 475,
"stickybarb": 476,
"stoneplate": 477,
"sunstone": 480,
"tamatoberry": 486,
"tangaberry": 487,
"thickclub": 491,
"thunderstone": 492,
"timerball": 494,
"toxicorb": 515,
"toxicplate": 516,
"twistedspoon": 520,
"ultraball": 521,
"upgrade": 523,
"wacanberry": 526,
"watergem": 528,
"waterstone": 529,
"watmelberry": 530,
"waveincense": 531,
"wepearberry": 533,
"whiteherb": 535,
"widelens": 537,
"wikiberry": 538,
"wiseglasses": 539,
"yacheberry": 567,
"zapplate": 572,
"zoomlens": 574,
"abomasite": 575,
"absolite": 576,
"aerodactylite": 577,
"crucibellite": 577,
"aggronite": 578,
"alakazite": 579,
"ampharosite": 580,
"assaultvest": 581,
"banettite": 582,
"blastoisinite": 583,
"blazikenite": 584,
"charizarditex": 585,
"charizarditey": 586,
"gardevoirite": 587,
"gengarite": 588,
// FIXME garchompite & gyaradosite should not be same spritenum
"garchompite": 589,
"gyaradosite": 589,
"heracronite": 590,
"houndoominite": 591,
"kangaskhanite": 592,
"keeberry": 593,
"lucarionite": 594,
"luminousmoss": 595,
"manectite": 596,
"marangaberry": 597,
"mawilite": 598,
"medichamite": 599,
"mewtwonitex": 600,
"mewtwonitey": 601,
"pinsirite": 602,
"roseliberry": 603,
"safetygoggles": 604,
"scizorite": 605,
"snowball": 606,
"tyranitarite": 607,
"venusaurite": 608,
"weaknesspolicy": 609,
"pixieplate": 610,
"fairygem": 611,
"swampertite": 612,
"sceptilite": 613,
"sablenite": 614,
"altarianite": 615,
"galladite": 616,
"audinite": 617,
"metagrossite": 618,
"sharpedonite": 619,
"slowbronite": 620,
"steelixite": 621,
"pidgeotite": 622,
"glalitite": 623,
"diancite": 624,
"cameruptite": 625,
"lopunnite": 626,
"salamencite": 627,
"beedrillite": 628,
"latiasite": 629,
"latiosite": 630,
"normaliumz": 631,
"firiumz": 632,
"wateriumz": 633,
"electriumz": 634,
"grassiumz": 635,
"iciumz": 636,
"fightiniumz": 637,
"poisoniumz": 638,
"groundiumz": 639,
"flyiniumz": 640,
"psychiumz": 641,
"buginiumz": 642,
"rockiumz": 643,
"ghostiumz": 644,
"dragoniumz": 645,
"darkiniumz": 646,
"steeliumz": 647,
"fairiumz": 648,
"pikaniumz": 649,
"decidiumz": 650,
"inciniumz": 651,
"primariumz": 652,
"tapuniumz": 653,
"marshadiumz": 654,
"aloraichiumz": 655,
"snorliumz": 656,
"eeviumz": 657,
"mewniumz": 658,
"pikashuniumz": 659,
"adrenalineorb": 660,
"beastball": 661,
"terrainextender": 662,
"protectivepads": 663,
"electricseed": 664,
"psychicseed": 665,
"mistyseed": 666,
"grassyseed": 667,
"fightingmemory": 668,
"flyingmemory": 669,
"poisonmemory": 670,
"groundmemory": 671,
"rockmemory": 672,
"bugmemory": 673,
"ghostmemory": 674,
"steelmemory": 675,
"firememory": 676,
"watermemory": 677,
"grassmemory": 678,
"electricmemory": 679,
"psychicmemory": 680,
"icememory": 681,
"dragonmemory": 682,
"darkmemory": 683,
"fairymemory": 684,
"solganiumz": 685,
"lunaliumz": 686,
"ultranecroziumz": 687,
"mimikiumz": 688,
"lycaniumz": 689,
"kommoniumz": 690,
"sachet": 691,
"whippeddream": 692,
"icestone": 693,
"jawfossil": 694,
"sailfossil": 695,
"bottlecap": 696,
"goldbottlecap": 697,
"rustedsword": 698,
"rustedshield": 699,
"fossilizedbird": 700,
"fossilizedfish": 701,
"fossilizeddrake": 702,
"fossilizeddino": 703,
"strawberrysweet": 704,
"lovesweet": 705,
"berrysweet": 706,
"cloversweet": 707,
"flowersweet": 708,
"starsweet": 709,
"ribbonsweet": 710,
"sweetapple": 711,
"tartapple": 712,
"throatspray": 713,
"ejectpack": 714,
"heavydutyboots": 715,
"blunderpolicy": 716,
"roomservice": 717,
"utilityumbrella": 718,
"crackedpot": 719,
"chippedpot": 720,
"tr00": 721,
"tr01": 721,
"tr13": 721,
"tr14": 721,
"tr19": 721,
"tr20": 721,
"tr26": 721,
"tr27": 721,
"tr29": 721,
"tr30": 721,
"tr35": 721,
"tr42": 721,
"tr85": 721,
"tr07": 722,
"tr21": 722,
"tr39": 722,
"tr48": 722,
"tr53": 722,
"tr56": 722,
"tr64": 722,
"tr99": 722,
"tr66": 723,
"tr89": 723,
"tr22": 724,
"tr54": 724,
"tr57": 724,
"tr73": 724,
"tr78": 724,
"tr91": 724,
"tr10": 725,
"tr23": 725,
"tr67": 725,
"tr87": 725,
"tr94": 725,
"tr63": 726,
"tr75": 726,
"tr76": 726,
"tr18": 727,
"tr28": 727,
"tr60": 727,
"tr61": 727,
"tr96": 727,
"tr33": 728,
"tr31": 729,
"tr46": 729,
"tr52": 729,
"tr70": 729,
"tr74": 729,
"tr79": 729,
"tr02": 730,
"tr15": 730,
"tr36": 730,
"tr41": 730,
"tr43": 730,
"tr55": 730,
"tr88": 730,
"tr03": 731,
"tr04": 731,
"tr16": 731,
"tr45": 731,
"tr84": 731,
"tr98": 731,
"tr50": 732,
"tr59": 732,
"tr65": 732,
"tr71": 732,
"tr72": 732,
"tr77": 732,
"tr08": 733,
"tr09": 733,
"tr80": 733,
"tr86": 733,
"tr11": 734,
"tr12": 734,
"tr17": 734,
"tr25": 734,
"tr34": 734,
"tr38": 734,
"tr40": 734,
"tr44": 734,
"tr49": 734,
"tr69": 734,
"tr82": 734,
"tr83": 734,
"tr97": 734,
"tr05": 735,
"tr06": 735,
"tr24": 736,
"tr47": 736,
"tr51": 736,
"tr62": 736,
"tr32": 737,
"tr37": 737,
"tr58": 737,
"tr68": 737,
"tr81": 737,
"tr93": 737,
"tr95": 737,
"tr90": 738,
"tr92": 738,
"galaricacuff": 739,
"galaricawreath": 740,
"adamantcrystal": 741,
"lustrousglobe": 742,
"griseouscore": 743,
"maliciousarmor": 744,
"boosterenergy": 745,
"abilityshield": 746,
"clearamulet": 747,
"mirrorherb": 748,
"punchingglove": 749,
"covertcloak": 750,
"loadeddice": 751,
"vilevial": 752,
"auspiciousarmor": 753,
"fairyfeather": 754,
"syrupyapple": 755,
"unremarkableteacup": 756,
"masterpieceteacup": 757,
"cornerstonemask": 758,
"wellspringmask": 759,
"hearthflamemask": 760,
"metalalloy": 761,
};
const found = new Map;
for (const filename of fs.readdirSync(itemsDir)) {
const sn = spritedata.parseFilename(path.parse(filename).name);
if (sn.kind !== 'i') continue;
for (const name of [sn.name, ...spritedata.ITEM_ALIASES[sn.name] ?? []]) {
found.set(spritedata.psid(name), path.join(itemsDir, filename));
}
}
const entries = [];
for (const [id, num] of Object.entries(ITEMS)) {
const filename = found.get(id);
if (!filename)
throw new Error(`can't find: ${id}`);
entries[num] = filename;
}
for (let i = 0; i < entries.length; i++) {
if (entries[i] === undefined)
entries[i] = null;
}
export default {
width: 24,
height: 24,
tile: 16,
entries
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,727 +0,0 @@
import fs from 'fs';
import path from 'path';
import * as spritedata from '@smogon/sprite-data/index.ts';
import root from '@smogon/sprite-root/index.ts';
import {DEX_NUMS} from './ps-pokemon.dexnums.mjs';
const srcDir = path.join(root, "src");
const spritesDir = path.join(srcDir, "minisprites/pokemon/gen6");
// Derived from pokemon-showdown-client/src/battle-dex-data.ts
const BattlePokemonIconIndexes = {
unknown: 0,
// alt forms
egg: 1032 + 1,
pikachubelle: 1032 + 2,
pikachulibre: 1032 + 3,
pikachuphd: 1032 + 4,
pikachupopstar: 1032 + 5,
pikachurockstar: 1032 + 6,
pikachucosplay: 1032 + 7,
unownexclamation: 1032 + 8,
unownquestion: 1032 + 9,
unownb: 1032 + 10,
unownc: 1032 + 11,
unownd: 1032 + 12,
unowne: 1032 + 13,
unownf: 1032 + 14,
unowng: 1032 + 15,
unownh: 1032 + 16,
unowni: 1032 + 17,
unownj: 1032 + 18,
unownk: 1032 + 19,
unownl: 1032 + 20,
unownm: 1032 + 21,
unownn: 1032 + 22,
unowno: 1032 + 23,
unownp: 1032 + 24,
unownq: 1032 + 25,
unownr: 1032 + 26,
unowns: 1032 + 27,
unownt: 1032 + 28,
unownu: 1032 + 29,
unownv: 1032 + 30,
unownw: 1032 + 31,
unownx: 1032 + 32,
unowny: 1032 + 33,
unownz: 1032 + 34,
castformrainy: 1032 + 35,
castformsnowy: 1032 + 36,
castformsunny: 1032 + 37,
deoxysattack: 1032 + 38,
deoxysdefense: 1032 + 39,
deoxysspeed: 1032 + 40,
burmysandy: 1032 + 41,
burmytrash: 1032 + 42,
wormadamsandy: 1032 + 43,
wormadamtrash: 1032 + 44,
cherrimsunshine: 1032 + 45,
shelloseast: 1032 + 46,
gastrodoneast: 1032 + 47,
rotomfan: 1032 + 48,
rotomfrost: 1032 + 49,
rotomheat: 1032 + 50,
rotommow: 1032 + 51,
rotomwash: 1032 + 52,
giratinaorigin: 1032 + 53,
shayminsky: 1032 + 54,
unfezantf: 1032 + 55,
basculinbluestriped: 1032 + 56,
darmanitanzen: 1032 + 57,
deerlingautumn: 1032 + 58,
deerlingsummer: 1032 + 59,
deerlingwinter: 1032 + 60,
sawsbuckautumn: 1032 + 61,
sawsbucksummer: 1032 + 62,
sawsbuckwinter: 1032 + 63,
frillishf: 1032 + 64,
jellicentf: 1032 + 65,
tornadustherian: 1032 + 66,
thundurustherian: 1032 + 67,
landorustherian: 1032 + 68,
kyuremblack: 1032 + 69,
kyuremwhite: 1032 + 70,
keldeoresolute: 1032 + 71,
meloettapirouette: 1032 + 72,
vivillonarchipelago: 1032 + 73,
vivilloncontinental: 1032 + 74,
vivillonelegant: 1032 + 75,
vivillonfancy: 1032 + 76,
vivillongarden: 1032 + 77,
vivillonhighplains: 1032 + 78,
vivillonicysnow: 1032 + 79,
vivillonjungle: 1032 + 80,
vivillonmarine: 1032 + 81,
vivillonmodern: 1032 + 82,
vivillonmonsoon: 1032 + 83,
vivillonocean: 1032 + 84,
vivillonpokeball: 1032 + 85,
vivillonpolar: 1032 + 86,
vivillonriver: 1032 + 87,
vivillonsandstorm: 1032 + 88,
vivillonsavanna: 1032 + 89,
vivillonsun: 1032 + 90,
vivillontundra: 1032 + 91,
pyroarf: 1032 + 92,
flabebeblue: 1032 + 93,
flabebeorange: 1032 + 94,
flabebewhite: 1032 + 95,
flabebeyellow: 1032 + 96,
floetteblue: 1032 + 97,
floetteeternal: 1032 + 98,
floetteorange: 1032 + 99,
floettewhite: 1032 + 100,
floetteyellow: 1032 + 101,
florgesblue: 1032 + 102,
florgesorange: 1032 + 103,
florgeswhite: 1032 + 104,
florgesyellow: 1032 + 105,
furfroudandy: 1032 + 106,
furfroudebutante: 1032 + 107,
furfroudiamond: 1032 + 108,
furfrouheart: 1032 + 109,
furfroukabuki: 1032 + 110,
furfroulareine: 1032 + 111,
furfroumatron: 1032 + 112,
furfroupharaoh: 1032 + 113,
furfroustar: 1032 + 114,
meowsticf: 1032 + 115,
aegislashblade: 1032 + 116,
xerneasneutral: 1032 + 117,
hoopaunbound: 1032 + 118,
rattataalola: 1032 + 119,
raticatealola: 1032 + 120,
raichualola: 1032 + 121,
sandshrewalola: 1032 + 122,
sandslashalola: 1032 + 123,
vulpixalola: 1032 + 124,
ninetalesalola: 1032 + 125,
diglettalola: 1032 + 126,
dugtrioalola: 1032 + 127,
meowthalola: 1032 + 128,
persianalola: 1032 + 129,
geodudealola: 1032 + 130,
graveleralola: 1032 + 131,
golemalola: 1032 + 132,
grimeralola: 1032 + 133,
mukalola: 1032 + 134,
exeggutoralola: 1032 + 135,
marowakalola: 1032 + 136,
greninjaash: 1032 + 137,
zygarde10: 1032 + 138,
zygardecomplete: 1032 + 139,
oricoriopompom: 1032 + 140,
oricoriopau: 1032 + 141,
oricoriosensu: 1032 + 142,
lycanrocmidnight: 1032 + 143,
wishiwashischool: 1032 + 144,
miniormeteor: 1032 + 145,
miniororange: 1032 + 146,
minioryellow: 1032 + 147,
miniorgreen: 1032 + 148,
miniorblue: 1032 + 149,
miniorindigo: 1032 + 150,
miniorviolet: 1032 + 151,
magearnaoriginal: 1032 + 152,
pikachuoriginal: 1032 + 153,
pikachuhoenn: 1032 + 154,
pikachusinnoh: 1032 + 155,
pikachuunova: 1032 + 156,
pikachukalos: 1032 + 157,
pikachualola: 1032 + 158,
pikachupartner: 1032 + 159,
lycanrocdusk: 1032 + 160,
necrozmaduskmane: 1032 + 161,
necrozmadawnwings: 1032 + 162,
necrozmaultra: 1032 + 163,
pikachustarter: 1032 + 164,
eeveestarter: 1032 + 165,
meowthgalar: 1032 + 166,
ponytagalar: 1032 + 167,
rapidashgalar: 1032 + 168,
farfetchdgalar: 1032 + 169,
weezinggalar: 1032 + 170,
mrmimegalar: 1032 + 171,
corsolagalar: 1032 + 172,
zigzagoongalar: 1032 + 173,
linoonegalar: 1032 + 174,
darumakagalar: 1032 + 175,
darmanitangalar: 1032 + 176,
darmanitangalarzen: 1032 + 177,
yamaskgalar: 1032 + 178,
stunfiskgalar: 1032 + 179,
cramorantgulping: 1032 + 180,
cramorantgorging: 1032 + 181,
toxtricitylowkey: 1032 + 182,
alcremierubycream: 1032 + 183,
alcremiematchacream: 1032 + 184,
alcremiemintcream: 1032 + 185,
alcremielemoncream: 1032 + 186,
alcremiesaltedcream: 1032 + 187,
alcremierubyswirl: 1032 + 188,
alcremiecaramelswirl: 1032 + 189,
alcremierainbowswirl: 1032 + 190,
eiscuenoice: 1032 + 191,
indeedeef: 1032 + 192,
morpekohangry: 1032 + 193,
zaciancrowned: 1032 + 194,
zamazentacrowned: 1032 + 195,
slowpokegalar: 1032 + 196,
slowbrogalar: 1032 + 197,
zarudedada: 1032 + 198,
pikachuworld: 1032 + 199,
articunogalar: 1032 + 200,
zapdosgalar: 1032 + 201,
moltresgalar: 1032 + 202,
slowkinggalar: 1032 + 203,
calyrexice: 1032 + 204,
calyrexshadow: 1032 + 205,
growlithehisui: 1032 + 206,
arcaninehisui: 1032 + 207,
voltorbhisui: 1032 + 208,
electrodehisui: 1032 + 209,
typhlosionhisui: 1032 + 210,
qwilfishhisui: 1032 + 211,
sneaselhisui: 1032 + 212,
samurotthisui: 1032 + 213,
lilliganthisui: 1032 + 214,
zoruahisui: 1032 + 215,
zoroarkhisui: 1032 + 216,
braviaryhisui: 1032 + 217,
sliggoohisui: 1032 + 218,
goodrahisui: 1032 + 219,
avalugghisui: 1032 + 220,
decidueyehisui: 1032 + 221,
basculegionf: 1032 + 222,
enamorustherian: 1032 + 223,
taurospaldeacombat: 1032 + 224,
taurospaldeablaze: 1032 + 225,
taurospaldeaaqua: 1032 + 226,
wooperpaldea: 1032 + 227,
oinkolognef: 1032 + 228,
palafinhero: 1032 + 229,
mausholdfour: 1032 + 230,
tatsugiridroopy: 1032 + 231,
tatsugiristretchy: 1032 + 232,
squawkabillyblue: 1032 + 233,
squawkabillyyellow: 1032 + 234,
squawkabillywhite: 1032 + 235,
gimmighoulroaming: 1032 + 236,
dialgaorigin: 1032 + 237,
palkiaorigin: 1032 + 238,
basculinwhitestriped: 1032 + 239,
ursalunabloodmoon: 1032 + 240,
ogerponwellspring: 1032 + 241,
ogerponhearthflame: 1032 + 242,
ogerponcornerstone: 1032 + 243,
terapagosterastal: 1032 + 244,
terapagosstellar: 1032 + 245,
arceusbug: 1032 + 246,
arceusdark: 1032 + 247,
arceusdragon: 1032 + 248,
arceuselectric: 1032 + 249,
arceusfairy: 1032 + 250,
arceusfighting: 1032 + 251,
arceusfire: 1032 + 252,
arceusflying: 1032 + 253,
arceusghost: 1032 + 254,
arceusgrass: 1032 + 255,
arceusground: 1032 + 256,
arceusice: 1032 + 257,
arceuspoison: 1032 + 258,
arceuspsychic: 1032 + 259,
arceusrock: 1032 + 260,
arceussteel: 1032 + 261,
arceuswater: 1032 + 262,
genesectdouse: 1032 + 263,
genesectshock: 1032 + 264,
genesectburn: 1032 + 265,
genesectchill: 1032 + 266,
silvallybug: 1032 + 267,
silvallydark: 1032 + 268,
silvallydragon: 1032 + 269,
silvallyelectric: 1032 + 270,
silvallyfairy: 1032 + 271,
silvallyfighting: 1032 + 272,
silvallyfire: 1032 + 273,
silvallyflying: 1032 + 274,
silvallyghost: 1032 + 275,
silvallygrass: 1032 + 276,
silvallyground: 1032 + 277,
silvallyice: 1032 + 278,
silvallypoison: 1032 + 279,
silvallypsychic: 1032 + 280,
silvallyrock: 1032 + 281,
silvallysteel: 1032 + 282,
silvallywater: 1032 + 283,
// alt forms with duplicate icons
// greninjabond: 658,
// gumshoostotem: 735,
// raticatealolatotem: 1032 + 120,
// marowakalolatotem: 1032 + 136,
// araquanidtotem: 752,
// lurantistotem: 754,
// salazzletotem: 758,
// vikavolttotem: 738,
// togedemarutotem: 777,
// mimikyutotem: 778,
// mimikyubustedtotem: 778,
// ribombeetotem: 743,
// kommoototem: 784,
// toxtricitylowkeygmax: 1320 + 69,
sinisteaantique: 854,
polteageistantique: 855,
urshifurapidstrike: 892,
dudunsparcethreesegment: 982,
poltchageistartisan: 1012,
sinistchamasterpiece: 1013,
ogerpontealtera: 1017,
ogerponwellspringtera: 1032 + 241,
ogerponhearthflametera: 1032 + 242,
ogerponcornerstonetera: 1032 + 243,
// Mega/G-Max
venusaurmega: 1320 + 0,
charizardmegax: 1320 + 1,
charizardmegay: 1320 + 2,
blastoisemega: 1320 + 3,
beedrillmega: 1320 + 4,
pidgeotmega: 1320 + 5,
alakazammega: 1320 + 6,
slowbromega: 1320 + 7,
gengarmega: 1320 + 8,
kangaskhanmega: 1320 + 9,
pinsirmega: 1320 + 10,
gyaradosmega: 1320 + 11,
aerodactylmega: 1320 + 12,
mewtwomegax: 1320 + 13,
mewtwomegay: 1320 + 14,
ampharosmega: 1320 + 15,
steelixmega: 1320 + 16,
scizormega: 1320 + 17,
heracrossmega: 1320 + 18,
houndoommega: 1320 + 19,
tyranitarmega: 1320 + 20,
sceptilemega: 1320 + 21,
blazikenmega: 1320 + 22,
swampertmega: 1320 + 23,
gardevoirmega: 1320 + 24,
sableyemega: 1320 + 25,
mawilemega: 1320 + 26,
aggronmega: 1320 + 27,
medichammega: 1320 + 28,
manectricmega: 1320 + 29,
sharpedomega: 1320 + 30,
cameruptmega: 1320 + 31,
altariamega: 1320 + 32,
banettemega: 1320 + 33,
absolmega: 1320 + 34,
glaliemega: 1320 + 35,
salamencemega: 1320 + 36,
metagrossmega: 1320 + 37,
latiasmega: 1320 + 38,
latiosmega: 1320 + 39,
kyogreprimal: 1320 + 40,
groudonprimal: 1320 + 41,
rayquazamega: 1320 + 42,
lopunnymega: 1320 + 43,
garchompmega: 1320 + 44,
lucariomega: 1320 + 45,
abomasnowmega: 1320 + 46,
gallademega: 1320 + 47,
audinomega: 1320 + 48,
dianciemega: 1320 + 49,
charizardgmax: 1320 + 50,
butterfreegmax: 1320 + 51,
pikachugmax: 1320 + 52,
meowthgmax: 1320 + 53,
machampgmax: 1320 + 54,
gengargmax: 1320 + 55,
kinglergmax: 1320 + 56,
laprasgmax: 1320 + 57,
eeveegmax: 1320 + 58,
snorlaxgmax: 1320 + 59,
garbodorgmax: 1320 + 60,
melmetalgmax: 1320 + 61,
corviknightgmax: 1320 + 62,
orbeetlegmax: 1320 + 63,
drednawgmax: 1320 + 64,
coalossalgmax: 1320 + 65,
flapplegmax: 1320 + 66,
appletungmax: 1320 + 67,
sandacondagmax: 1320 + 68,
toxtricitygmax: 1320 + 69,
centiskorchgmax: 1320 + 70,
hatterenegmax: 1320 + 71,
grimmsnarlgmax: 1320 + 72,
alcremiegmax: 1320 + 73,
copperajahgmax: 1320 + 74,
duraludongmax: 1320 + 75,
eternatuseternamax: 1320 + 76,
venusaurgmax: 1320 + 77,
blastoisegmax: 1320 + 78,
rillaboomgmax: 1320 + 79,
cinderacegmax: 1320 + 80,
inteleongmax: 1320 + 81,
urshifugmax: 1320 + 82,
urshifurapidstrikegmax: 1320 + 83,
clefablemega: 1320 + 84,
victreebelmega: 1320 + 85,
starmiemega: 1320 + 86,
dragonitemega: 1320 + 87,
meganiummega: 1320 + 88,
feraligatrmega: 1320 + 89,
skarmorymega: 1320 + 90,
froslassmega: 1320 + 91,
emboarmega: 1320 + 92,
excadrillmega: 1320 + 93,
scolipedemega: 1320 + 94,
scraftymega: 1320 + 95,
eelektrossmega: 1320 + 96,
chandeluremega: 1320 + 97,
chesnaughtmega: 1320 + 98,
delphoxmega: 1320 + 99,
greninjamega: 1320 + 100,
pyroarmega: 1320 + 101,
floettemega: 1320 + 102,
malamarmega: 1320 + 103,
barbaraclemega: 1320 + 104,
dragalgemega: 1320 + 105,
hawluchamega: 1320 + 106,
zygardemega: 1320 + 107,
drampamega: 1320 + 108,
falinksmega: 1320 + 109,
raichumegax: 1320 + 110,
raichumegay: 1320 + 111,
chimechomega: 1320 + 112,
absolmegaz: 1320 + 113,
staraptormega: 1320 + 114,
garchompmegaz: 1320 + 115,
lucariomegaz: 1320 + 116,
heatranmega: 1320 + 117,
darkraimega: 1320 + 118,
golurkmega: 1320 + 119,
meowsticmmega: 1320 + 120,
meowsticfmega: 1320 + 120,
crabominablemega: 1320 + 121,
golisopodmega: 1320 + 122,
magearnamega: 1320 + 123,
magearnaoriginalmega: 1320 + 124,
zeraoramega: 1320 + 125,
scovillainmega: 1320 + 126,
glimmoramega: 1320 + 127,
tatsugiricurlymega: 1320 + 128,
tatsugiridroopymega: 1320 + 129,
tatsugiristretchymega: 1320 + 130,
baxcaliburmega: 1320 + 131,
// CAP
syclant: 1560 + 0,
revenankh: 1560 + 1,
pyroak: 1560 + 2,
fidgit: 1560 + 3,
stratagem: 1560 + 4,
arghonaut: 1560 + 5,
kitsunoh: 1560 + 6,
cyclohm: 1560 + 7,
colossoil: 1560 + 8,
krilowatt: 1560 + 9,
voodoom: 1560 + 10,
tomohawk: 1560 + 11,
necturna: 1560 + 12,
mollux: 1560 + 13,
aurumoth: 1560 + 14,
malaconda: 1560 + 15,
cawmodore: 1560 + 16,
volkraken: 1560 + 17,
plasmanta: 1560 + 18,
naviathan: 1560 + 19,
crucibelle: 1560 + 20,
crucibellemega: 1560 + 21,
kerfluffle: 1560 + 22,
pajantom: 1560 + 23,
jumbao: 1560 + 24,
caribolt: 1560 + 25,
smokomodo: 1560 + 26,
snaelstrom: 1560 + 27,
equilibra: 1560 + 28,
astrolotl: 1560 + 29,
miasmaw: 1560 + 30,
chromera: 1560 + 31,
venomicon: 1560 + 32,
venomiconepilogue: 1560 + 33,
saharaja: 1560 + 34,
hemogoblin: 1560 + 35,
syclar: 1560 + 36,
embirch: 1560 + 37,
flarelm: 1560 + 38,
breezi: 1560 + 39,
scratchet: 1560 + 40,
necturine: 1560 + 41,
cupra: 1560 + 42,
argalis: 1560 + 43,
brattler: 1560 + 44,
cawdet: 1560 + 45,
volkritter: 1560 + 46,
snugglow: 1560 + 47,
floatoy: 1560 + 48,
caimanoe: 1560 + 49,
pluffle: 1560 + 50,
rebble: 1560 + 51,
tactite: 1560 + 52,
privatyke: 1560 + 53,
nohface: 1560 + 54,
monohm: 1560 + 55,
duohm: 1560 + 56,
protowatt: 1560 + 57,
voodoll: 1560 + 58,
mumbao: 1560 + 59,
fawnifer: 1560 + 60,
electrelk: 1560 + 61,
smogecko: 1560 + 62,
smoguana: 1560 + 63,
swirlpool: 1560 + 64,
coribalis: 1560 + 65,
justyke: 1560 + 66,
solotl: 1560 + 67,
miasmite: 1560 + 68,
dorsoil: 1560 + 69,
saharascal: 1560 + 70,
ababo: 1560 + 71,
scattervein: 1560 + 72,
cresceidon: 1560 + 73,
chuggalong: 1560 + 74,
shox: 1560 + 75,
chuggon: 1560 + 76,
draggalong: 1560 + 77,
ramnarok: 1560 + 78,
ramnarokradiant: 1560 + 79,
flox: 1560 + 80,
obliteryx: 1560 + 81,
};
const BattlePokemonIconIndexesLeft = {
pikachubelle: 1452 + 0,
pikachupopstar: 1452 + 1,
clefairy: 1452 + 2,
clefable: 1452 + 3,
jigglypuff: 1452 + 4,
wigglytuff: 1452 + 5,
dugtrioalola: 1452 + 6,
poliwhirl: 1452 + 7,
poliwrath: 1452 + 8,
mukalola: 1452 + 9,
kingler: 1452 + 10,
croconaw: 1452 + 11,
cleffa: 1452 + 12,
igglybuff: 1452 + 13,
politoed: 1452 + 14,
unownb: 1452 + 15,
unownc: 1452 + 16,
unownd: 1452 + 17,
unowne: 1452 + 18,
unownf: 1452 + 19,
unowng: 1452 + 20,
unownh: 1452 + 21,
unownj: 1452 + 22,
unownk: 1452 + 23,
unownl: 1452 + 24,
unownm: 1452 + 25,
unownn: 1452 + 26,
unownp: 1452 + 27,
unownq: 1452 + 28,
unownquestion: 1452 + 29,
unownr: 1452 + 30,
unowns: 1452 + 31,
unownt: 1452 + 32,
unownv: 1452 + 33,
unownz: 1452 + 34,
sneasel: 1452 + 35,
teddiursa: 1452 + 36,
roselia: 1452 + 37,
zangoose: 1452 + 38,
seviper: 1452 + 39,
castformsnowy: 1452 + 40,
absolmega: 1452 + 41,
absol: 1452 + 42,
regirock: 1452 + 43,
torterra: 1452 + 44,
budew: 1452 + 45,
roserade: 1452 + 46,
magmortar: 1452 + 47,
togekiss: 1452 + 48,
rotomwash: 1452 + 49,
shayminsky: 1452 + 50,
emboar: 1452 + 51,
pansear: 1452 + 52,
simisear: 1452 + 53,
drilbur: 1452 + 54,
excadrill: 1452 + 55,
sawk: 1452 + 56,
lilligant: 1452 + 57,
garbodor: 1452 + 58,
solosis: 1452 + 59,
vanilluxe: 1452 + 60,
amoonguss: 1452 + 61,
klink: 1452 + 62,
klang: 1452 + 63,
klinklang: 1452 + 64,
litwick: 1452 + 65,
golett: 1452 + 66,
golurk: 1452 + 67,
kyuremblack: 1452 + 68,
kyuremwhite: 1452 + 69,
kyurem: 1452 + 70,
keldeoresolute: 1452 + 71,
meloetta: 1452 + 72,
greninja: 1452 + 73,
// greninjabond: 1452 + 73,
greninjaash: 1452 + 74,
furfroudebutante: 1452 + 75,
barbaracle: 1452 + 76,
clauncher: 1452 + 77,
clawitzer: 1452 + 78,
sylveon: 1452 + 79,
klefki: 1452 + 80,
zygarde: 1452 + 81,
zygarde10: 1452 + 82,
zygardecomplete: 1452 + 83,
dartrix: 1452 + 84,
steenee: 1452 + 85,
tsareena: 1452 + 86,
comfey: 1452 + 87,
miniormeteor: 1452 + 88,
minior: 1452 + 89,
miniororange: 1452 + 90,
minioryellow: 1452 + 91,
miniorgreen: 1452 + 92,
miniorblue: 1452 + 93,
miniorviolet: 1452 + 94,
miniorindigo: 1452 + 95,
dhelmise: 1452 + 96,
necrozma: 1452 + 97,
marshadow: 1452 + 98,
pikachuoriginal: 1452 + 99,
pikachupartner: 1452 + 100,
necrozmaduskmane: 1452 + 101,
necrozmadawnwings: 1452 + 102,
necrozmaultra: 1452 + 103,
stakataka: 1452 + 104,
blacephalon: 1452 + 105,
};
const entries = [];
let errors = [];
// Need to figure out how to remove this readdir; tup doesn't like it
for (const name of fs.readdirSync(spritesDir)) {
const parsed = path.parse(name);
const sn = spritedata.parseFilename(parsed.name);
// PS runs a name and its forme together, unlike the published sprite name.
let id = spritedata.psid(sn.name) + spritedata.psid(sn.extra.get("o") ?? '');
if (sn.extra.has("f")) id += 'f';
if (sn.extra.has("g")) id += 'gmax';
let index;
if (sn.extra.has("a")) {
index = BattlePokemonIconIndexesLeft[id];
BattlePokemonIconIndexesLeft[id] = 'found';
} else {
index = BattlePokemonIconIndexes[id];
BattlePokemonIconIndexes[id] = 'found';
}
if (index === undefined) {
// A base species sits at the slot named by its dex number; only alt
// formes need the hand-kept table above.
if (sn.kind === 's' && !sn.extra.has("o")) {
index = DEX_NUMS[spritedata.psid(sn.name)];
} else {
errors.push(`can't find ${name}`);
continue;
}
}
entries[index] = path.join(spritesDir, name);
}
for (const [id, num] of Object.entries(BattlePokemonIconIndexes)) {
if (num !== 'found') {
errors.push(`didn't find ${id}`);
}
}
for (const [id, num] of Object.entries(BattlePokemonIconIndexesLeft)) {
if (num !== 'found') {
errors.push(`didn't find left ${id}`);
}
}
if (errors.length) {
for (let error of errors) {
console.error(error);
}
process.exit(1);
}
for (let i = 0; i < entries.length; i++) {
if (entries[i] === undefined)
entries[i] = null;
}
export default {
width: 40,
height: 30,
tile: 12,
entries
};

View File

@@ -1,318 +0,0 @@
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Derived from pokemon-showdown-client/src/battle-dex-data.ts
const BattleAvatarNumbers = {
1: 'lucas',
2: 'dawn',
3: 'youngster-gen4',
4: 'lass-gen4dp',
5: 'camper',
6: 'picnicker',
7: 'bugcatcher',
8: 'aromalady',
9: 'twins-gen4dp',
10: 'hiker-gen4',
11: 'battlegirl-gen4',
12: 'fisherman-gen4',
13: 'cyclist-gen4',
14: 'cyclistf-gen4',
15: 'blackbelt-gen4dp',
16: 'artist-gen4',
17: 'pokemonbreeder-gen4',
18: 'pokemonbreederf-gen4',
19: 'cowgirl',
20: 'jogger',
21: 'pokefan-gen4',
22: 'pokefanf-gen4',
23: 'pokekid',
24: 'youngcouple-gen4dp',
25: 'acetrainer-gen4dp',
26: 'acetrainerf-gen4dp',
27: 'waitress-gen4',
28: 'veteran-gen4',
29: 'ninjaboy',
30: 'dragontamer',
31: 'birdkeeper-gen4dp',
32: 'doubleteam',
33: 'richboy-gen4',
34: 'lady-gen4',
35: 'gentleman-gen4dp',
36: 'madame-gen4dp',
37: 'beauty-gen4dp',
38: 'collector',
39: 'policeman-gen4',
40: 'pokemonranger-gen4',
41: 'pokemonrangerf-gen4',
42: 'scientist-gen4dp',
43: 'swimmer-gen4dp',
44: 'swimmerf-gen4dp',
45: 'tuber',
46: 'tuberf',
47: 'sailor',
48: 'sisandbro',
49: 'ruinmaniac',
50: 'psychic-gen4',
51: 'psychicf-gen4',
52: 'gambler',
53: 'guitarist-gen4',
54: 'acetrainersnow',
55: 'acetrainersnowf',
56: 'skier',
57: 'skierf-gen4dp',
58: 'roughneck-gen4',
59: 'clown',
60: 'worker-gen4',
61: 'schoolkid-gen4dp',
62: 'schoolkidf-gen4',
63: 'roark',
64: 'barry',
65: 'byron',
66: 'aaron',
67: 'bertha',
68: 'flint',
69: 'lucian',
70: 'cynthia-gen4',
71: 'bellepa',
72: 'rancher',
73: 'mars',
74: 'galacticgrunt',
75: 'gardenia',
76: 'crasherwake',
77: 'maylene',
78: 'fantina',
79: 'candice',
80: 'volkner',
81: 'parasollady-gen4',
82: 'waiter-gen4dp',
83: 'interviewers',
84: 'cameraman',
85: 'reporter',
86: 'idol',
87: 'cyrus',
88: 'jupiter',
89: 'saturn',
90: 'galacticgruntf',
91: 'argenta',
92: 'palmer',
93: 'thorton',
94: 'buck',
95: 'darach',
96: 'marley',
97: 'mira',
98: 'cheryl',
99: 'riley',
100: 'dahlia',
101: 'ethan',
102: 'lyra',
103: 'twins-gen4',
104: 'lass-gen4',
105: 'acetrainer-gen4',
106: 'acetrainerf-gen4',
107: 'juggler',
108: 'sage',
109: 'li',
110: 'gentleman-gen4',
111: 'teacher',
112: 'beauty',
113: 'birdkeeper',
114: 'swimmer-gen4',
115: 'swimmerf-gen4',
116: 'kimonogirl',
117: 'scientist-gen4',
118: 'acetrainercouple',
119: 'youngcouple',
120: 'supernerd',
121: 'medium',
122: 'schoolkid-gen4',
123: 'blackbelt-gen4',
124: 'pokemaniac',
125: 'firebreather',
126: 'burglar',
127: 'biker-gen4',
128: 'skierf',
129: 'boarder',
130: 'rocketgrunt',
131: 'rocketgruntf',
132: 'archer',
133: 'ariana',
134: 'proton',
135: 'petrel',
136: 'eusine',
137: 'lucas-gen4pt',
138: 'dawn-gen4pt',
139: 'madame-gen4',
140: 'waiter-gen4',
141: 'falkner',
142: 'bugsy',
143: 'whitney',
144: 'morty',
145: 'chuck',
146: 'jasmine',
147: 'pryce',
148: 'clair',
149: 'will',
150: 'koga',
151: 'bruno',
152: 'karen',
153: 'lance',
154: 'brock',
155: 'misty',
156: 'ltsurge',
157: 'erika',
158: 'janine',
159: 'sabrina',
160: 'blaine',
161: 'blue',
162: 'red',
163: 'red',
164: 'silver',
165: 'giovanni',
166: 'unknownf',
167: 'unknown',
168: 'unknown',
169: 'hilbert',
170: 'hilda',
171: 'youngster',
172: 'lass',
173: 'schoolkid',
174: 'schoolkidf',
175: 'smasher',
176: 'linebacker',
177: 'waiter',
178: 'waitress',
179: 'chili',
180: 'cilan',
181: 'cress',
182: 'nurseryaide',
183: 'preschoolerf',
184: 'preschooler',
185: 'twins',
186: 'pokemonbreeder',
187: 'pokemonbreederf',
188: 'lenora',
189: 'burgh',
190: 'elesa',
191: 'clay',
192: 'skyla',
193: 'pokemonranger',
194: 'pokemonrangerf',
195: 'worker',
196: 'backpacker',
197: 'backpackerf',
198: 'fisherman',
199: 'musician',
200: 'dancer',
201: 'harlequin',
202: 'artist',
203: 'baker',
204: 'psychic',
205: 'psychicf',
206: 'cheren',
207: 'bianca',
208: 'plasmagrunt-gen5bw',
209: 'n',
210: 'richboy',
211: 'lady',
212: 'pilot',
213: 'workerice',
214: 'hoopster',
215: 'scientistf',
216: 'clerkf',
217: 'acetrainerf',
218: 'acetrainer',
219: 'blackbelt',
220: 'scientist',
221: 'striker',
222: 'brycen',
223: 'iris',
224: 'drayden',
225: 'roughneck',
226: 'janitor',
227: 'pokefan',
228: 'pokefanf',
229: 'doctor',
230: 'nurse',
231: 'hooligans',
232: 'battlegirl',
233: 'parasollady',
234: 'clerk',
235: 'clerk-boss',
236: 'backers',
237: 'backersf',
238: 'veteran',
239: 'veteranf',
240: 'biker',
241: 'infielder',
242: 'hiker',
243: 'madame',
244: 'gentleman',
245: 'plasmagruntf-gen5bw',
246: 'shauntal',
247: 'marshal',
248: 'grimsley',
249: 'caitlin',
250: 'ghetsis-gen5bw',
251: 'depotagent',
252: 'swimmer',
253: 'swimmerf',
254: 'policeman',
255: 'maid',
256: 'ingo',
257: 'alder',
258: 'cyclist',
259: 'cyclistf',
260: 'cynthia',
261: 'emmet',
262: 'hilbert-dueldisk',
263: 'hilda-dueldisk',
264: 'hugh',
265: 'rosa',
266: 'nate',
267: 'colress',
268: 'beauty-gen5bw2',
269: 'ghetsis',
270: 'plasmagrunt',
271: 'plasmagruntf',
272: 'iris-gen5bw2',
273: 'brycenman',
274: 'shadowtriad',
275: 'rood',
276: 'zinzolin',
277: 'cheren-gen5bw2',
278: 'marlon',
279: 'roxie',
280: 'roxanne',
281: 'brawly',
282: 'wattson',
283: 'flannery',
284: 'norman',
285: 'winona',
286: 'tate',
287: 'liza',
288: 'juan',
289: 'guitarist',
290: 'steven',
291: 'wallace',
292: 'bellelba',
293: 'benga',
// Not in the existing trainer sheet...
// 294: 'ash',
}
const entries = [];
for (const [num, id] of Object.entries(BattleAvatarNumbers)) {
entries[num - 1] = path.join(__dirname, "src/canonical/trainers", id + ".png");
}
export default {
width: 80,
height: 80,
tile: 16,
entries
};

View File

@@ -1,125 +0,0 @@
import * as spritedata from '@smogon/sprite-data/index.ts';
import {gen10Modelslike} from './rules/modelslike.ts';
import type {Sprite} from './rules/publish.ts';
import {forEachRule, rule} from './tools/build/artifact.ts';
import {PNG_DETERMINISTIC, base, compresspng, pad, spriteglob} from './tools/build/helpers.ts';
import {type DeployCtx, deploy} from './tools/deploy/api.ts';
// PS spritesheets. The sheet tools readdir the minisprite dirs and read the
// ids off the filenames, baking those names into the sheet layout, hence
// nameSensitive.
let sheetDeps = [
'data/lib/index.ts',
'lib/root/index.ts',
'tools/sheet/index.ts',
];
rule('ps-pokemon.sheet.mjs', {
display: 'ps pokemon sheet',
nameSensitive: true,
deps: ['src/minisprites/pokemon/gen6/*', 'ps-pokemon.dexnums.mjs', ...sheetDeps],
cmds: ['node tools/sheet/index.ts %f %o', compresspng({config: 'SPRITESHEET'})],
}, 'pokemonicons-sheet.png');
// TODO: reenable when trainers are moved
// rule("ps-trainers.sheet.mjs", {
// display: "ps trainers sheet",
// nameSensitive: true,
// cmds: ["node tools/sheet/index.ts %f %o", compresspng({config: "SPRITESHEET"})],
// }, "trainers-sheet.png");
rule('ps-items.sheet.mjs', {
display: 'ps items sheet',
nameSensitive: true,
deps: ['src/minisprites/items/*', ...sheetDeps],
cmds: ['node tools/sheet/index.ts %f %o', compresspng({config: 'SPRITESHEET'})],
}, 'itemicons-sheet.png');
// PS pokeball icons; input order is the sheet order.
rule([
'src/_uncategorized/noncanonical/ui/battle/Ball-Normal.png',
'src/_uncategorized/noncanonical/ui/battle/Ball-Sick.png',
'src/_uncategorized/noncanonical/ui/battle/Ball-Null.png',
], {
display: 'pokemonicons-pokeball-sheet',
cmds: [
`magick convert ${PNG_DETERMINISTIC} -background transparent -gravity center -extent 40x30 %f +append %o`,
compresspng({config: 'SPRITESHEET'}),
],
}, 'pokemonicons-pokeball-sheet.png');
// Padded Dex, plus missing CAPs backfilled from the gen5/model gifs.
let dex = forEachRule('src/dex/*', {
display: 'pad dex %f',
cmds: [pad({w: 120, h: 120}), compresspng({config: 'DEX'})],
}, '%b');
let dexSet = new Set(dex.map(base));
let dexMissing = [];
for (let file of spriteglob(['src/sprites/gen5/*.gif', 'src/models/*.gif'], {b: false, s: false})) {
if (!dexSet.has(base(file))) {
dexMissing.push(file);
dexSet.add(base(file));
}
}
forEachRule(dexMissing, {
display: 'missing dex %B',
cmds: [
`magick convert "%f[0]" ${PNG_DETERMINISTIC} -trim %o`,
`magick mogrify ${PNG_DETERMINISTIC} -background transparent -gravity center -resize "120x120>" -extent 120x120 %o`,
compresspng({config: 'DEX'}),
],
}, '%B.png');
// ani/: the models plus champions backfill, under PS ids.
let aniChampions = gen10Modelslike();
deploy(async ctx => {
let seenModels = new Set<string>();
for (let f of await ctx.list('src/models')) {
seenModels.add(f.name);
psSpritecopy(ctx, f, 'ani');
}
for (let f of aniChampions) {
if (seenModels.has(f.name)) {
continue;
}
seenModels.add(f.name);
psSpritecopy(ctx, f, 'ani');
}
// TODO: ship the padded dex, sheets, trainers, types/categories when
// the PS deploy is revived; the rules above keep them building.
});
// PS runs the words of a name together, where the smogon aliases dash them.
function psSpritecopy(ctx: DeployCtx, f: Sprite, dir: string): void {
let sn = spritedata.parseFilename(f.name);
// Skip asymmetrical for now
if (sn.extra.has('a') || sn.extra.has('b') || sn.extra.has('s')) {
return;
}
if (sn.kind !== 's') {
// Skip this, we don't use Unknown/Substitute
return;
}
let name = spritedata.publishedName(sn, spritedata.psid);
if (f.ext === null) {
throw new Error(`Sprite ${f.name} has no extension`);
}
ctx.copy(f, `${dir}/${name}.${f.ext}`);
}

View File

@@ -18,8 +18,8 @@ Formes ride in `-o`, so `Charizard-Mega-X`'s back sprite is
`scharizard-omega_x-b.gif` and every Charizard is `scharizard-*`. The forme
comes first so a forme's sprites sort together.
`tools/spritedata/names.ts` prints every filename pokemon-showdown implies, if
you need to check a directory against it.
`tools/deploy/coverage.ts` prints what a deploy would delete off the server, if
you need to check the build against what is actually being served.
# Flags

View File

@@ -11,9 +11,6 @@ import {spawnSync} from 'node:child_process';
// when these two sets published through Manifest, and the pre-rename spellings
// below that nothing has asked for in years. Anything else is a hole in the
// build, and this finds it before rsync does.
//
// names.ts answers the same question against pokemon-showdown; this one
// answers it against what is actually being served.
let dir = process.argv[2];
let remote = process.argv[3] ?? 'smogon:/smog2/sprites';
@@ -57,7 +54,7 @@ for (let set of ['xy', 'xyicons']) {
let stamped = 0;
let backlog = 0;
let unexplained = [];
let unexplained: string[] = [];
for (let line of rsync.stdout.split('\n')) {
let m = /^\*deleting +(.*)$/.exec(line);
if (!m) continue;

View File

@@ -1,58 +0,0 @@
import * as cp from 'node:child_process';
import * as path from 'node:path';
let sheetjs = process.argv[2];
let dest = process.argv[3];
if (!sheetjs || !dest) {
throw new Error(`node tools/sheet <file.sheet.js> <dest>`);
}
// Must have file:/// for Windows
let {default: sheet} = await import(path.join('file:///', process.cwd(), sheetjs));
for (let i = 0; i < sheet.entries.length; i++) {
if (sheet.entries[i] === undefined)
throw new Error(`gap: ${i}`);
if (sheet.entries[i] === null) {
// ImageMagick blank entry
sheet.entries[i] = 'xc:transparent';
}
}
// Write list of filenames to stdin, Windows can't handle large cli arg lists.
// Before we wrote a tmp file and deleted it afterwards, but apparently tup
// can't track unlinkSync on Windows?
let proc = cp.spawn('magick', [
'montage',
'@-',
// Keep sheet bytes deterministic for the content-addressed build.
'-define', 'png:exclude-chunks=date,time',
'-background', 'transparent',
'-geometry', `${sheet.width}x${sheet.height}>`,
'-gravity', 'center',
'-tile', `${sheet.tile}x`,
'-depth', '8',
dest
], {
stdio: ['pipe', 'inherit', 'inherit']
});
// Write one at a time to avoid "malloc(): corrupted top size" error
for (let entry of sheet.entries) {
if (!proc.stdin.write(entry + "\n")) {
// imagick doesn't like it when it receives the input too fast. This
// seems to avoid a realloc() invalid next size error.
await new Promise(resolve => {
proc.stdin.once('drain', resolve);
});
}
}
proc.stdin.end();
proc.on('exit', (code, signal) => {
if (code || signal) {
process.exitCode = 1;
}
});

View File

@@ -1,6 +0,0 @@
{
"type": "module",
"dependencies": {
"@types/node": "^24.10.7"
}
}

View File

@@ -1,3 +0,0 @@
{
"extends": "../../tsconfig-base"
}

View File

@@ -1,36 +0,0 @@
import {createRequire} from 'node:module';
import * as path from 'node:path';
// Regenerates ps-pokemon.dexnums.mjs from a pokemon-showdown checkout:
//
// node tools/spritedata/dexnums.ts ~/smogon/pokemon-showdown > ps-pokemon.dexnums.mjs
//
// The PS icon sheet puts a base species at the slot named by its dex number,
// and only alt formes need the hand-kept index table in ps-pokemon.sheet.mjs.
type PokedexEntry = {
num: number,
name: string,
baseSpecies?: string
};
let ps = process.argv[2];
if (!ps) {
throw new Error('usage: node tools/spritedata/dexnums.ts <pokemon-showdown checkout>');
}
let require = createRequire(import.meta.url);
let {Pokedex} = require(path.resolve(ps, 'dist/data/pokedex.js')) as
{Pokedex: Record<string, PokedexEntry>};
let out = '';
out += '// Generated by tools/spritedata/dexnums.ts; do not edit.\n';
out += '// PS id of every base species -> its national dex number.\n\n';
out += 'export const DEX_NUMS = {\n';
for (let {num, name, baseSpecies} of Object.values(Pokedex)) {
if (baseSpecies) continue;
out += `\t${name.toLowerCase().replace(/[^a-z0-9]+/g, '')}: ${num},\n`;
}
out += '};\n';
process.stdout.write(out);

View File

@@ -1,20 +0,0 @@
import {formatFilename} from '@smogon/sprite-data/index.ts';
import {spriteFiles} from './pokedex.ts';
// Every sprite filename pokemon-showdown implies, one per line:
//
// node tools/spritedata/names.ts ~/smogon/pokemon-showdown | sort > /tmp/want
// ls src/models | sed 's/\.[^.]*$//' | cut -d- -f1 | sort -u > /tmp/have
//
// Nothing in the build validates a filename any more, so this is how you check
// a directory against the source of truth.
let ps = process.argv[2];
if (!ps) {
throw new Error('usage: node tools/spritedata/names.ts <pokemon-showdown checkout>');
}
for (let file of spriteFiles(ps)) {
process.stdout.write(`${formatFilename(file)}\n`);
}

View File

@@ -1,8 +0,0 @@
{
"type": "module",
"dependencies": {
"@smogon/sprite-data": "workspace:*",
"@smogon/sprite-root": "workspace:*",
"@types/node": "^24.10.7"
}
}

View File

@@ -1,99 +0,0 @@
import {createRequire} from 'node:module';
import * as path from 'node:path';
import {encode, type InputSpriteFilename} from '@smogon/sprite-data/index.ts';
// Everything the sprite tree wants from a pokemon-showdown checkout: which
// sprites should exist, and what each one is called.
export type Specie = {
num: number,
formeNum: number,
base: string,
forme: string
};
export type Item = {
num: number,
names: string[]
};
type PokedexEntry = {
num: number,
name: string,
baseSpecies?: string,
formeOrder?: string[]
};
type ItemsEntry = {
num: number,
name: string
};
function load<T>(ps: string, file: string, key: string): Record<string, T> {
let require = createRequire(import.meta.url);
let mod = require(path.resolve(ps, 'dist/data', file)) as Record<string, Record<string, T>>;
let table = mod[key];
if (table === undefined) {
throw new Error(`${file} has no ${key} export; is ${ps} built?`);
}
return table;
}
export function species(ps: string): Specie[] {
let out: Specie[] = [];
for (let {num, name, baseSpecies, formeOrder} of Object.values(load<PokedexEntry>(ps, 'pokedex.js', 'Pokedex'))) {
// Formes are enumerated from their base species, not on their own.
if (baseSpecies) continue;
for (let [formeNum, formeName] of (formeOrder ?? [name]).entries()) {
// Gmax is a flag, not a forme.
if (formeName.endsWith('-Gmax')) continue;
out.push({num, formeNum, base: name, forme: formeName.slice(name.length + 1)});
}
}
return out;
}
export function items(ps: string): Item[] {
// One sprite per item number, so items sharing a number share a sprite and
// collect their names: Aspear Berry is also Burnt Berry.
let byNum = new Map<number, Item>();
for (let {num, name} of Object.values(load<ItemsEntry>(ps, 'items.js', 'Items'))) {
let item = byNum.get(num);
if (item) {
item.names.push(name);
} else {
byNum.set(num, {num, names: [name]});
}
}
return [...byNum.values()];
}
// The filename each sprite should have. PS's formeOrder repeats a name where
// the games gave one forme several slots (Zygarde's Power Construct pair,
// Minior's seven meteors), and nothing in the data tells those apart, so the
// lowest slot keeps the bare name and the rest carry the slot in -c.
export function spriteFiles(ps: string): InputSpriteFilename[] {
let out: InputSpriteFilename[] = [];
let seen = new Set<string>();
for (let {formeNum, base, forme} of species(ps)) {
let name = encode(base);
let extra = new Map<string, string>();
if (forme) {
extra.set('o', encode(forme));
}
let key = `${name}-${extra.get('o') ?? ''}`;
if (seen.has(key)) {
extra.set('c', String(formeNum));
}
seen.add(key);
out.push({kind: 's', name, extra});
}
for (let {names} of items(ps)) {
out.push({kind: 'i', name: encode(names[0]!)});
}
return out;
}

View File

@@ -1,100 +0,0 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import {encode, formatFilename, parseFilename, type InputSpriteFilename} from '@smogon/sprite-data/index.ts';
import root from '@smogon/sprite-root/index.ts';
import {items, species, spriteFiles} from './pokedex.ts';
// One-off: renames src/ off the packed numeric ids onto encoded names.
//
// node tools/spritedata/rename.ts ~/smogon/pokemon-showdown [--dry-run]
//
// Kept for the record rather than for reuse. The old id packed the dex number
// and the forme index into one number, `s((|num| << 5) + formeNum)` with bit 29
// marking the negative nums CAP and fakemon use, and `i(|num|)` for items;
// src/previews/gen8 wrote the same number with no prefix at all. Reconstruct
// those here and map each to the name the file should have had.
let ps = process.argv[2];
let dryRun = process.argv.includes('--dry-run');
if (!ps) {
throw new Error('usage: node tools/spritedata/rename.ts <pokemon-showdown checkout> [--dry-run]');
}
function legacy(num: number, formeNum: number): number {
let sid = (Math.abs(num) << 5) + formeNum;
return num < 0 ? sid | (1 << 29) : sid;
}
// spriteFiles() lists species then items, each in the order its own reader
// yields, so the ids line up positionally.
let files = spriteFiles(ps);
let sids = [
...species(ps).map(s => `s${legacy(s.num, s.formeNum)}`),
...items(ps).map(i => `i${i.num < 0 ? Math.abs(i.num) | (1 << 29) : i.num}`),
];
if (sids.length !== files.length) {
throw new Error(`${sids.length} ids for ${files.length} sprites`);
}
let byId = new Map<string, InputSpriteFilename>();
for (let [i, sid] of sids.entries()) {
byId.set(sid, files[i]!);
}
let renames: [string, string][] = [];
let missing = new Set<string>();
function walk(dir: string): void {
for (let ent of fs.readdirSync(dir, {withFileTypes: true})) {
let p = path.join(dir, ent.name);
if (ent.isDirectory()) {
// Its own naming scheme, and nothing parses it.
if (path.basename(p) !== '_uncategorized') walk(p);
continue;
}
if (path.extname(p) === '.md') continue;
let ext = path.extname(ent.name);
let stem = ent.name.slice(0, -ext.length);
let [id, ...flags] = stem.split('-');
let file: InputSpriteFilename;
if (id!.startsWith('x')) {
file = {kind: 'x', name: encode(id!.slice(1))};
} else {
let found = byId.get(/^[0-9]+$/.test(id!) ? `s${id}` : id!);
if (!found) {
missing.add(id!);
continue;
}
file = found;
}
let extra = new Map(file.extra);
for (let flag of flags) {
extra.set(flag.charAt(0), flag.slice(1));
}
let renamed = formatFilename({kind: file.kind, name: file.name, extra}) + ext;
if (renamed !== ent.name) {
renames.push([p, path.join(dir, renamed)]);
}
}
}
walk(path.join(root, 'src'));
if (missing.size) {
throw new Error(`no sprite for ${[...missing].join(', ')}`);
}
let taken = new Set(renames.map(([, to]) => to));
if (taken.size !== renames.length) {
throw new Error('renames collide');
}
for (let [from, to] of renames) {
if (!dryRun) fs.renameSync(from, to);
}
process.stderr.write(`${dryRun ? 'would rename' : 'renamed'} ${renames.length} files\n`);

View File

@@ -1,7 +0,0 @@
{
"extends": "../../tsconfig-base",
"references": [
{"path": "../../data/lib"},
{"path": "../../lib/root"}
]
}

View File

@@ -4,9 +4,7 @@
{"path": "./tsconfig.json"},
{"path": "tools/build"},
{"path": "tools/deploy"},
{"path": "tools/sheet"},
{"path": "tools/smogdexspritesheet"},
{"path": "tools/spritedata"},
{"path": "tools/trim"},
{"path": "lib/root"},
{"path": "data/lib"},