diff --git a/data/lib/formes.ts b/data/lib/formes.ts new file mode 100644 index 00000000..365407ed --- /dev/null +++ b/data/lib/formes.ts @@ -0,0 +1,418 @@ +// Generated by tools/spritedata/formes.ts; do not edit. +// Each forme's published name -> the name it falls back on for art. + +export const FORME_PARENTS: Record = { + 'venusaur-mega': 'venusaur', + 'charizard-mega-x': 'charizard', + 'charizard-mega-y': 'charizard', + 'blastoise-mega': 'blastoise', + 'beedrill-mega': 'beedrill', + 'pidgeot-mega': 'pidgeot', + 'rattata-alola': 'rattata', + 'raticate-alola': 'raticate', + 'raticate-alola-totem': 'raticate-alola', + 'pikachu-original': 'pikachu', + 'pikachu-hoenn': 'pikachu', + 'pikachu-sinnoh': 'pikachu', + 'pikachu-unova': 'pikachu', + 'pikachu-kalos': 'pikachu', + 'pikachu-alola': 'pikachu', + 'pikachu-partner': 'pikachu', + 'pikachu-starter': 'pikachu', + 'pikachu-world': 'pikachu', + 'pikachu-rock-star': 'pikachu', + 'pikachu-belle': 'pikachu', + 'pikachu-pop-star': 'pikachu', + 'pikachu-phd': 'pikachu', + 'pikachu-libre': 'pikachu', + 'pikachu-cosplay': 'pikachu', + 'raichu-alola': 'raichu', + 'raichu-mega-x': 'raichu', + 'raichu-mega-y': 'raichu', + 'sandshrew-alola': 'sandshrew', + 'sandslash-alola': 'sandslash', + 'clefable-mega': 'clefable', + 'vulpix-alola': 'vulpix', + 'ninetales-alola': 'ninetales', + 'diglett-alola': 'diglett', + 'dugtrio-alola': 'dugtrio', + 'meowth-alola': 'meowth', + 'meowth-galar': 'meowth', + 'persian-alola': 'persian', + 'growlithe-hisui': 'growlithe', + 'arcanine-hisui': 'arcanine', + 'alakazam-mega': 'alakazam', + 'victreebel-mega': 'victreebel', + 'geodude-alola': 'geodude', + 'graveler-alola': 'graveler', + 'golem-alola': 'golem', + 'ponyta-galar': 'ponyta', + 'rapidash-galar': 'rapidash', + 'slowpoke-galar': 'slowpoke', + 'slowbro-mega': 'slowbro', + 'slowbro-galar': 'slowbro', + 'farfetchd-galar': 'farfetchd', + 'grimer-alola': 'grimer', + 'muk-alola': 'muk', + 'gengar-mega': 'gengar', + 'voltorb-hisui': 'voltorb', + 'electrode-hisui': 'electrode', + 'exeggutor-alola': 'exeggutor', + 'marowak-alola': 'marowak', + 'marowak-alola-totem': 'marowak-alola', + 'weezing-galar': 'weezing', + 'kangaskhan-mega': 'kangaskhan', + 'starmie-mega': 'starmie', + 'mr-mime-galar': 'mr-mime', + 'pinsir-mega': 'pinsir', + 'tauros-paldea-combat': 'tauros', + 'tauros-paldea-blaze': 'tauros', + 'tauros-paldea-aqua': 'tauros', + 'gyarados-mega': 'gyarados', + 'eevee-starter': 'eevee', + 'aerodactyl-mega': 'aerodactyl', + 'articuno-galar': 'articuno', + 'zapdos-galar': 'zapdos', + 'moltres-galar': 'moltres', + 'dragonite-mega': 'dragonite', + 'mewtwo-mega-x': 'mewtwo', + 'mewtwo-mega-y': 'mewtwo', + 'meganium-mega': 'meganium', + 'typhlosion-hisui': 'typhlosion', + 'feraligatr-mega': 'feraligatr', + 'pichu-spiky-eared': 'pichu', + 'ampharos-mega': 'ampharos', + 'wooper-paldea': 'wooper', + 'slowking-galar': 'slowking', + 'unown-b': 'unown', + 'unown-c': 'unown', + 'unown-d': 'unown', + 'unown-e': 'unown', + 'unown-f': 'unown', + 'unown-g': 'unown', + 'unown-h': 'unown', + 'unown-i': 'unown', + 'unown-j': 'unown', + 'unown-k': 'unown', + 'unown-l': 'unown', + 'unown-m': 'unown', + 'unown-n': 'unown', + 'unown-o': 'unown', + 'unown-p': 'unown', + 'unown-q': 'unown', + 'unown-r': 'unown', + 'unown-s': 'unown', + 'unown-t': 'unown', + 'unown-u': 'unown', + 'unown-v': 'unown', + 'unown-w': 'unown', + 'unown-x': 'unown', + 'unown-y': 'unown', + 'unown-z': 'unown', + 'unown-exclamation': 'unown', + 'unown-question': 'unown', + 'steelix-mega': 'steelix', + 'qwilfish-hisui': 'qwilfish', + 'scizor-mega': 'scizor', + 'heracross-mega': 'heracross', + 'sneasel-hisui': 'sneasel', + 'corsola-galar': 'corsola', + 'skarmory-mega': 'skarmory', + 'houndoom-mega': 'houndoom', + 'tyranitar-mega': 'tyranitar', + 'sceptile-mega': 'sceptile', + 'blaziken-mega': 'blaziken', + 'swampert-mega': 'swampert', + 'zigzagoon-galar': 'zigzagoon', + 'linoone-galar': 'linoone', + 'gardevoir-mega': 'gardevoir', + 'sableye-mega': 'sableye', + 'mawile-mega': 'mawile', + 'aggron-mega': 'aggron', + 'medicham-mega': 'medicham', + 'manectric-mega': 'manectric', + 'sharpedo-mega': 'sharpedo', + 'camerupt-mega': 'camerupt', + 'altaria-mega': 'altaria', + 'castform-sunny': 'castform', + 'castform-rainy': 'castform', + 'castform-snowy': 'castform', + 'banette-mega': 'banette', + 'chimecho-mega': 'chimecho', + 'absol-mega': 'absol', + 'absol-mega-z': 'absol-mega', + 'glalie-mega': 'glalie', + 'salamence-mega': 'salamence', + 'metagross-mega': 'metagross', + 'latias-mega': 'latias', + 'latios-mega': 'latios', + 'kyogre-primal': 'kyogre', + 'groudon-primal': 'groudon', + 'rayquaza-mega': 'rayquaza', + 'deoxys-attack': 'deoxys', + 'deoxys-defense': 'deoxys', + 'deoxys-speed': 'deoxys', + 'staraptor-mega': 'staraptor', + 'burmy-sandy': 'burmy', + 'burmy-trash': 'burmy', + 'wormadam-sandy': 'wormadam', + 'wormadam-trash': 'wormadam', + 'cherrim-sunshine': 'cherrim', + 'shellos-east': 'shellos', + 'gastrodon-east': 'gastrodon', + 'lopunny-mega': 'lopunny', + 'garchomp-mega': 'garchomp', + 'garchomp-mega-z': 'garchomp-mega', + 'lucario-mega': 'lucario', + 'lucario-mega-z': 'lucario-mega', + 'abomasnow-mega': 'abomasnow', + 'gallade-mega': 'gallade', + 'froslass-mega': 'froslass', + 'rotom-heat': 'rotom', + 'rotom-wash': 'rotom', + 'rotom-frost': 'rotom', + 'rotom-fan': 'rotom', + 'rotom-mow': 'rotom', + 'dialga-origin': 'dialga', + 'palkia-origin': 'palkia', + 'heatran-mega': 'heatran', + 'giratina-origin': 'giratina', + 'darkrai-mega': 'darkrai', + 'shaymin-sky': 'shaymin', + 'arceus-fighting': 'arceus', + 'arceus-flying': 'arceus', + 'arceus-poison': 'arceus', + 'arceus-ground': 'arceus', + 'arceus-rock': 'arceus', + 'arceus-bug': 'arceus', + 'arceus-ghost': 'arceus', + 'arceus-steel': 'arceus', + 'arceus-fire': 'arceus', + 'arceus-water': 'arceus', + 'arceus-grass': 'arceus', + 'arceus-electric': 'arceus', + 'arceus-psychic': 'arceus', + 'arceus-ice': 'arceus', + 'arceus-dragon': 'arceus', + 'arceus-dark': 'arceus', + 'arceus-fairy': 'arceus', + 'emboar-mega': 'emboar', + 'samurott-hisui': 'samurott', + 'excadrill-mega': 'excadrill', + 'audino-mega': 'audino', + 'scolipede-mega': 'scolipede', + 'lilligant-hisui': 'lilligant', + 'basculin-blue-striped': 'basculin', + 'basculin-white-striped': 'basculin', + 'darumaka-galar': 'darumaka', + 'darmanitan-zen': 'darmanitan', + 'darmanitan-galar': 'darmanitan', + 'darmanitan-galar-zen': 'darmanitan-galar', + 'scrafty-mega': 'scrafty', + 'yamask-galar': 'yamask', + 'zorua-hisui': 'zorua', + 'zoroark-hisui': 'zoroark', + 'deerling-summer': 'deerling', + 'deerling-autumn': 'deerling', + 'deerling-winter': 'deerling', + 'sawsbuck-summer': 'sawsbuck', + 'sawsbuck-autumn': 'sawsbuck', + 'sawsbuck-winter': 'sawsbuck', + 'eelektross-mega': 'eelektross', + 'chandelure-mega': 'chandelure', + 'stunfisk-galar': 'stunfisk', + 'golurk-mega': 'golurk', + 'braviary-hisui': 'braviary', + 'tornadus-therian': 'tornadus', + 'thundurus-therian': 'thundurus', + 'landorus-therian': 'landorus', + 'kyurem-white': 'kyurem', + 'kyurem-black': 'kyurem', + 'keldeo-resolute': 'keldeo', + 'meloetta-pirouette': 'meloetta', + 'genesect-douse': 'genesect', + 'genesect-shock': 'genesect', + 'genesect-burn': 'genesect', + 'genesect-chill': 'genesect', + 'chesnaught-mega': 'chesnaught', + 'delphox-mega': 'delphox', + 'greninja-bond': 'greninja', + 'greninja-ash': 'greninja', + 'greninja-mega': 'greninja', + 'vivillon-icy-snow': 'vivillon', + 'vivillon-polar': 'vivillon', + 'vivillon-tundra': 'vivillon', + 'vivillon-continental': 'vivillon', + 'vivillon-garden': 'vivillon', + 'vivillon-elegant': 'vivillon', + 'vivillon-modern': 'vivillon', + 'vivillon-marine': 'vivillon', + 'vivillon-archipelago': 'vivillon', + 'vivillon-high-plains': 'vivillon', + 'vivillon-sandstorm': 'vivillon', + 'vivillon-river': 'vivillon', + 'vivillon-monsoon': 'vivillon', + 'vivillon-savanna': 'vivillon', + 'vivillon-sun': 'vivillon', + 'vivillon-ocean': 'vivillon', + 'vivillon-jungle': 'vivillon', + 'vivillon-fancy': 'vivillon', + 'vivillon-pokeball': 'vivillon', + 'pyroar-mega': 'pyroar', + 'flabebe-yellow': 'flabebe', + 'flabebe-orange': 'flabebe', + 'flabebe-blue': 'flabebe', + 'flabebe-white': 'flabebe', + 'floette-yellow': 'floette', + 'floette-orange': 'floette', + 'floette-blue': 'floette', + 'floette-white': 'floette', + 'floette-eternal': 'floette', + 'floette-mega': 'floette', + 'florges-yellow': 'florges', + 'florges-orange': 'florges', + 'florges-blue': 'florges', + 'florges-white': 'florges', + 'furfrou-heart': 'furfrou', + 'furfrou-star': 'furfrou', + 'furfrou-diamond': 'furfrou', + 'furfrou-debutante': 'furfrou', + 'furfrou-matron': 'furfrou', + 'furfrou-dandy': 'furfrou', + 'furfrou-la-reine': 'furfrou', + 'furfrou-kabuki': 'furfrou', + 'furfrou-pharaoh': 'furfrou', + 'meowstic-f': 'meowstic', + 'meowstic-m-mega': 'meowstic', + 'meowstic-f-mega': 'meowstic-f', + 'aegislash-blade': 'aegislash', + 'malamar-mega': 'malamar', + 'barbaracle-mega': 'barbaracle', + 'dragalge-mega': 'dragalge', + 'hawlucha-mega': 'hawlucha', + 'sliggoo-hisui': 'sliggoo', + 'goodra-hisui': 'goodra', + 'pumpkaboo-small': 'pumpkaboo', + 'pumpkaboo-large': 'pumpkaboo', + 'pumpkaboo-super': 'pumpkaboo', + 'gourgeist-small': 'gourgeist', + 'gourgeist-large': 'gourgeist', + 'gourgeist-super': 'gourgeist', + 'avalugg-hisui': 'avalugg', + 'xerneas-neutral': 'xerneas', + 'zygarde-10': 'zygarde', + 'zygarde-complete': 'zygarde', + 'zygarde-mega': 'zygarde', + 'diancie-mega': 'diancie', + 'hoopa-unbound': 'hoopa', + 'decidueye-hisui': 'decidueye', + 'gumshoos-totem': 'gumshoos', + 'vikavolt-totem': 'vikavolt', + 'crabominable-mega': 'crabominable', + 'oricorio-pom-pom': 'oricorio', + 'oricorio-pau': 'oricorio', + 'oricorio-sensu': 'oricorio', + 'ribombee-totem': 'ribombee', + 'rockruff-dusk': 'rockruff', + 'lycanroc-midnight': 'lycanroc', + 'lycanroc-dusk': 'lycanroc', + 'wishiwashi-school': 'wishiwashi', + 'araquanid-totem': 'araquanid', + 'lurantis-totem': 'lurantis', + 'salazzle-totem': 'salazzle', + 'golisopod-mega': 'golisopod', + 'silvally-fighting': 'silvally', + 'silvally-flying': 'silvally', + 'silvally-poison': 'silvally', + 'silvally-ground': 'silvally', + 'silvally-rock': 'silvally', + 'silvally-bug': 'silvally', + 'silvally-ghost': 'silvally', + 'silvally-steel': 'silvally', + 'silvally-fire': 'silvally', + 'silvally-water': 'silvally', + 'silvally-grass': 'silvally', + 'silvally-electric': 'silvally', + 'silvally-psychic': 'silvally', + 'silvally-ice': 'silvally', + 'silvally-dragon': 'silvally', + 'silvally-dark': 'silvally', + 'silvally-fairy': 'silvally', + 'minior-meteor': 'minior', + 'minior-orange': 'minior', + 'minior-yellow': 'minior', + 'minior-green': 'minior', + 'minior-blue': 'minior', + 'minior-indigo': 'minior', + 'minior-violet': 'minior', + 'togedemaru-totem': 'togedemaru', + 'mimikyu-busted': 'mimikyu', + 'mimikyu-totem': 'mimikyu', + 'mimikyu-busted-totem': 'mimikyu-busted', + 'drampa-mega': 'drampa', + 'kommo-o-totem': 'kommo-o', + 'necrozma-dusk-mane': 'necrozma', + 'necrozma-dawn-wings': 'necrozma', + 'necrozma-ultra': 'necrozma', + 'magearna-original': 'magearna', + 'magearna-mega': 'magearna', + 'magearna-original-mega': 'magearna-original', + 'zeraora-mega': 'zeraora', + 'cramorant-gulping': 'cramorant', + 'cramorant-gorging': 'cramorant', + 'toxtricity-low-key': 'toxtricity', + 'sinistea-antique': 'sinistea', + 'polteageist-antique': 'polteageist', + 'alcremie-ruby-cream': 'alcremie', + 'alcremie-matcha-cream': 'alcremie', + 'alcremie-mint-cream': 'alcremie', + 'alcremie-lemon-cream': 'alcremie', + 'alcremie-salted-cream': 'alcremie', + 'alcremie-ruby-swirl': 'alcremie', + 'alcremie-caramel-swirl': 'alcremie', + 'alcremie-rainbow-swirl': 'alcremie', + 'falinks-mega': 'falinks', + 'eiscue-noice': 'eiscue', + 'indeedee-f': 'indeedee', + 'morpeko-hangry': 'morpeko', + 'zacian-crowned': 'zacian', + 'zamazenta-crowned': 'zamazenta', + 'eternatus-eternamax': 'eternatus', + 'urshifu-rapid-strike': 'urshifu', + 'zarude-dada': 'zarude', + 'calyrex-ice': 'calyrex', + 'calyrex-shadow': 'calyrex', + 'ursaluna-bloodmoon': 'ursaluna', + 'basculegion-f': 'basculegion', + 'enamorus-therian': 'enamorus', + 'oinkologne-f': 'oinkologne', + 'maushold-four': 'maushold', + 'squawkabilly-blue': 'squawkabilly', + 'squawkabilly-yellow': 'squawkabilly', + 'squawkabilly-white': 'squawkabilly', + 'scovillain-mega': 'scovillain', + 'palafin-hero': 'palafin', + 'glimmora-mega': 'glimmora', + 'tatsugiri-droopy': 'tatsugiri', + 'tatsugiri-stretchy': 'tatsugiri', + 'tatsugiri-curly-mega': 'tatsugiri', + 'tatsugiri-droopy-mega': 'tatsugiri-droopy', + 'tatsugiri-stretchy-mega': 'tatsugiri-stretchy', + 'dudunsparce-three-segment': 'dudunsparce', + 'baxcalibur-mega': 'baxcalibur', + 'gimmighoul-roaming': 'gimmighoul', + 'poltchageist-artisan': 'poltchageist', + 'sinistcha-masterpiece': 'sinistcha', + 'ogerpon-wellspring': 'ogerpon', + 'ogerpon-hearthflame': 'ogerpon', + 'ogerpon-cornerstone': 'ogerpon', + 'ogerpon-teal-tera': 'ogerpon', + 'ogerpon-wellspring-tera': 'ogerpon-wellspring', + 'ogerpon-hearthflame-tera': 'ogerpon-hearthflame', + 'ogerpon-cornerstone-tera': 'ogerpon-cornerstone', + 'terapagos-terastal': 'terapagos', + 'terapagos-stellar': 'terapagos', + 'crucibelle-mega': 'crucibelle', + 'venomicon-epilogue': 'venomicon', + 'ramnarok-radiant': 'ramnarok', +}; diff --git a/smogon.build.ts b/smogon.build.ts index e614834e..91259e7f 100644 --- a/smogon.build.ts +++ b/smogon.build.ts @@ -1,4 +1,6 @@ +import {FORME_PARENTS} from '@smogon/sprite-data/formes.ts'; + import {gen10Modelslike} from './rules/modelslike.ts'; import {type Sprite, publishedNames} from './rules/publish.ts'; import {forEachRule} from './tools/build/artifact.ts'; @@ -75,8 +77,31 @@ let xyIcons = forEachRule('src/minisprites/pokemon/gen6/*.png', { }, '%b'); deploy(ctx => { + let byName = new Map(); for (let f of xyIcons) { - smogonSpritecopy(ctx, f, 'xyicons', publishedNames(f)); + for (let name of publishedNames(f)) { + byName.set(name, f); + } + } + // The games drew no icon for some formes, so PS's own sheet sends them to + // the nearest one that has art: Gourgeist-Super to Gourgeist, + // Mimikyu-Busted-Totem to Mimikyu-Busted and on to Mimikyu. Walk the same + // chain, so a name the data knows gets an icon rather than a 404. Only + // this set needs it, since xy/ has real art for every one of them. + for (let name of Object.keys(FORME_PARENTS)) { + if (byName.has(name)) { + continue; + } + for (let p = FORME_PARENTS[name]; p !== undefined; p = FORME_PARENTS[p]) { + let f = byName.get(p); + if (f) { + byName.set(name, f); + break; + } + } + } + for (let [name, f] of byName) { + smogonSpritecopy(ctx, f, 'xyicons', [name]); } }); diff --git a/tools/spritedata/formes.ts b/tools/spritedata/formes.ts new file mode 100644 index 00000000..1e6b7f55 --- /dev/null +++ b/tools/spritedata/formes.ts @@ -0,0 +1,79 @@ +import {encode, publishedName, smogon} from '@smogon/sprite-data/index.ts'; + +import {species} from './pokedex.ts'; + +// Regenerates data/lib/formes.ts from a pokemon-showdown checkout: +// +// node tools/spritedata/formes.ts ~/smogon/pokemon-showdown > data/lib/formes.ts +// +// The games drew some formes no art of their own, and a set that has none +// falls back on the nearest forme it is a refinement of, up to the base +// species: PS's own icon sheet points Gourgeist-Super at Gourgeist's slot and +// Mimikyu-Busted-Totem at Mimikyu's. That chain can't be recovered from a +// published name alone, since a base name carries dashes too (kommo-o, +// mr-mime, porygon-z), so it is written down here. +// +// The parent is the longest run of leading words that names a forme of its +// own, and the base species otherwise. Dropping one word blindly would strand +// Toxtricity-Low-Key on a Toxtricity-Low that does not exist. + +let ps = process.argv[2]; +if (!ps) { + throw new Error('usage: node tools/spritedata/formes.ts '); +} + +// `forme` comes in encoded; `base` does not. +function published(base: string, forme: string): string { + let extra = new Map(); + if (forme) { + extra.set('o', forme); + } + return publishedName({kind: 's', name: encode(base), extra}, smogon); +} + +let all = species(ps); + +// Which formes each base species actually has, so a parent can be checked. +let formes = new Map>(); +for (let {base, forme} of all) { + let set = formes.get(base); + if (!set) { + set = new Set(); + formes.set(base, set); + } + set.add(encode(forme)); +} + +function parent(base: string, forme: string): string { + let words = forme.split('_'); + for (let n = words.length - 1; n > 0; n--) { + let shorter = words.slice(0, n).join('_'); + if (formes.get(base)?.has(shorter)) { + return shorter; + } + } + return ''; +} + +let out = ''; +out += '// Generated by tools/spritedata/formes.ts; do not edit.\n'; +out += "// Each forme's published name -> the name it falls back on for art.\n\n"; +out += 'export const FORME_PARENTS: Record = {\n'; + +let seen = new Set(); +for (let {base, forme} of all) { + if (!forme) { + continue; + } + let name = published(base, encode(forme)); + // Where formeOrder gives one name to several slots, they share a parent. + if (seen.has(name)) { + continue; + } + seen.add(name); + out += ` '${name}': '${published(base, parent(base, encode(forme)))}',\n`; +} + +out += '};\n'; + +process.stdout.write(out);