Refactor color support for cosmetic formes (#11442)

* Refactor color support for cosmetic formes

* Oops

* Oop2s

* Fix build

* Fix build ?
This commit is contained in:
Kris Johnson 2025-09-15 12:15:22 -06:00 committed by GitHub
parent 8af9d9cfab
commit ae6d677edc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 514 additions and 8 deletions

View File

@ -148,6 +148,14 @@ export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable
inherit: true,
color: "Gray",
},
burmysandy: {
inherit: true,
color: "Gray",
},
burmytrash: {
inherit: true,
color: "Gray",
},
wormadam: {
inherit: true,
color: "Gray",
@ -164,6 +172,14 @@ export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable
inherit: true,
color: "Pink",
},
shelloseast: {
inherit: true,
color: "Purple",
},
gastrodoneast: {
inherit: true,
color: "Purple",
},
arceus: {
inherit: true,
color: "Gray",
@ -297,6 +313,18 @@ export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable
inherit: true,
color: "Yellow",
},
deerlingsummer: {
inherit: true,
color: "Yellow",
},
deerlingautumn: {
inherit: true,
color: "Yellow",
},
deerlingwinter: {
inherit: true,
color: "Yellow",
},
cubchoo: {
inherit: true,
abilities: { 0: "Snow Cloak", H: "Rattled" },
@ -318,6 +346,82 @@ export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable
inherit: true,
color: "Black",
},
vivillonicysnow: {
inherit: true,
color: "Black",
},
vivillonpolar: {
inherit: true,
color: "Black",
},
vivillontundra: {
inherit: true,
color: "Black",
},
vivilloncontinental: {
inherit: true,
color: "Black",
},
vivillongarden: {
inherit: true,
color: "Black",
},
vivillonelegant: {
inherit: true,
color: "Black",
},
vivillonmodern: {
inherit: true,
color: "Black",
},
vivillonmarine: {
inherit: true,
color: "Black",
},
vivillonarchipelago: {
inherit: true,
color: "Black",
},
vivillonhighplains: {
inherit: true,
color: "Black",
},
vivillonsandstorm: {
inherit: true,
color: "Black",
},
vivillonriver: {
inherit: true,
color: "Black",
},
vivillonmonsoon: {
inherit: true,
color: "Black",
},
vivillonsavanna: {
inherit: true,
color: "Black",
},
vivillonsun: {
inherit: true,
color: "Black",
},
vivillonocean: {
inherit: true,
color: "Black",
},
vivillonjungle: {
inherit: true,
color: "Black",
},
vivillonfancy: {
inherit: true,
color: "Black",
},
vivillonpokeball: {
inherit: true,
color: "Black",
},
meowstic: {
inherit: true,
color: "White",

View File

@ -59,6 +59,14 @@ export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable
inherit: true,
eggGroups: ["Bug"],
},
burmysandy: {
inherit: true,
color: "Green",
},
burmytrash: {
inherit: true,
color: "Green",
},
magnezone: {
inherit: true,
evoType: "levelExtra",
@ -83,6 +91,94 @@ export const Pokedex: import('../../../sim/dex-species').ModdedSpeciesDataTable
abilities: { 0: "Flash Fire", H: "Flame Body" },
unreleasedHidden: true,
},
deerlingsummer: {
inherit: true,
color: "Pink",
},
deerlingautumn: {
inherit: true,
color: "Pink",
},
deerlingwinter: {
inherit: true,
color: "Pink",
},
vivillonicysnow: {
inherit: true,
color: "White",
},
vivillonpolar: {
inherit: true,
color: "White",
},
vivillontundra: {
inherit: true,
color: "White",
},
vivilloncontinental: {
inherit: true,
color: "White",
},
vivillongarden: {
inherit: true,
color: "White",
},
vivillonelegant: {
inherit: true,
color: "White",
},
vivillonmodern: {
inherit: true,
color: "White",
},
vivillonmarine: {
inherit: true,
color: "White",
},
vivillonarchipelago: {
inherit: true,
color: "White",
},
vivillonhighplains: {
inherit: true,
color: "White",
},
vivillonsandstorm: {
inherit: true,
color: "White",
},
vivillonriver: {
inherit: true,
color: "White",
},
vivillonmonsoon: {
inherit: true,
color: "White",
},
vivillonsavanna: {
inherit: true,
color: "White",
},
vivillonsun: {
inherit: true,
color: "White",
},
vivillonocean: {
inherit: true,
color: "White",
},
vivillonjungle: {
inherit: true,
color: "White",
},
vivillonfancy: {
inherit: true,
color: "White",
},
vivillonpokeball: {
inherit: true,
color: "White",
},
aegislash: {
inherit: true,
baseStats: { hp: 60, atk: 50, def: 150, spa: 50, spd: 150, spe: 60 },

View File

@ -3,6 +3,7 @@ export const Scripts: ModdedBattleScriptsData = {
inherit: 'gen9',
init() {
for (const id in this.data.Pokedex) {
if (this.species.get(id).isCosmeticForme) continue;
const types = Array.from(new Set(this.data.Pokedex[id].types.map(type => (
type.replace(/(Ghost|Fairy)/g, 'Psychic')
.replace(/Bug/g, 'Grass')

View File

@ -7414,6 +7414,20 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
cosmeticFormes: ["Burmy-Sandy", "Burmy-Trash"],
formeOrder: ["Burmy", "Burmy-Sandy", "Burmy-Trash"],
},
burmysandy: {
isCosmeticForme: true,
name: "Burmy-Sandy",
baseSpecies: "Burmy",
forme: "Sandy",
color: "Brown",
},
burmytrash: {
isCosmeticForme: true,
name: "Burmy-Trash",
baseSpecies: "Burmy",
forme: "Trash",
color: "Red",
},
wormadam: {
num: 413,
name: "Wormadam",
@ -7598,6 +7612,13 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
cosmeticFormes: ["Shellos-East"],
formeOrder: ["Shellos", "Shellos-East"],
},
shelloseast: {
isCosmeticForme: true,
name: "Shellos-East",
baseSpecies: "Shellos",
forme: "East",
color: "Blue",
},
gastrodon: {
num: 423,
name: "Gastrodon",
@ -7614,6 +7635,13 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
cosmeticFormes: ["Gastrodon-East"],
formeOrder: ["Gastrodon", "Gastrodon-East"],
},
gastrodoneast: {
isCosmeticForme: true,
name: "Gastrodon-East",
baseSpecies: "Gastrodon",
forme: "East",
color: "Blue",
},
ambipom: {
num: 424,
name: "Ambipom",
@ -10480,6 +10508,27 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
cosmeticFormes: ["Deerling-Summer", "Deerling-Autumn", "Deerling-Winter"],
formeOrder: ["Deerling", "Deerling-Summer", "Deerling-Autumn", "Deerling-Winter"],
},
deerlingsummer: {
isCosmeticForme: true,
name: "Deerling-Summer",
baseSpecies: "Deerling",
forme: "Summer",
color: "Green",
},
deerlingautumn: {
isCosmeticForme: true,
name: "Deerling-Autumn",
baseSpecies: "Deerling",
forme: "Autumn",
color: "Red",
},
deerlingwinter: {
isCosmeticForme: true,
name: "Deerling-Winter",
baseSpecies: "Deerling",
forme: "Winter",
color: "Brown",
},
sawsbuck: {
num: 586,
name: "Sawsbuck",
@ -11784,7 +11833,7 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
abilities: { 0: "Shield Dust", 1: "Compound Eyes", H: "Friend Guard" },
heightm: 1.2,
weightkg: 17,
color: "White",
color: "Pink",
prevo: "Spewpa",
evoLevel: 12,
eggGroups: ["Bug"],
@ -11813,6 +11862,125 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
"Vivillon-Pokeball",
],
},
vivillonicysnow: {
isCosmeticForme: true,
name: "Vivillon-Icy Snow",
baseSpecies: "Vivillon",
forme: "Icy Snow",
color: "White",
},
vivillonpolar: {
isCosmeticForme: true,
name: "Vivillon-Polar",
baseSpecies: "Vivillon",
forme: "Polar",
color: "Blue",
},
vivillontundra: {
isCosmeticForme: true,
name: "Vivillon-Tundra",
baseSpecies: "Vivillon",
forme: "Tundra",
color: "Blue",
},
vivilloncontinental: {
isCosmeticForme: true,
name: "Vivillon-Continental",
baseSpecies: "Vivillon",
forme: "Continental",
color: "Yellow",
},
vivillongarden: {
isCosmeticForme: true,
name: "Vivillon-Garden",
baseSpecies: "Vivillon",
forme: "Garden",
color: "Green",
},
vivillonelegant: {
isCosmeticForme: true,
name: "Vivillon-Elegant",
baseSpecies: "Vivillon",
forme: "Elegant",
color: "Purple",
},
vivillonmodern: {
isCosmeticForme: true,
name: "Vivillon-Modern",
baseSpecies: "Vivillon",
forme: "Modern",
color: "Red",
},
vivillonmarine: {
isCosmeticForme: true,
name: "Vivillon-Marine",
baseSpecies: "Vivillon",
forme: "Marine",
color: "Blue",
},
vivillonarchipelago: {
isCosmeticForme: true,
name: "Vivillon-Archipelago",
baseSpecies: "Vivillon",
forme: "Archipelago",
color: "Brown",
},
vivillonhighplains: {
isCosmeticForme: true,
name: "Vivillon-High Plains",
baseSpecies: "Vivillon",
forme: "High Plains",
color: "Brown",
},
vivillonsandstorm: {
isCosmeticForme: true,
name: "Vivillon-Sandstorm",
baseSpecies: "Vivillon",
forme: "Sandstorm",
color: "Brown",
},
vivillonriver: {
isCosmeticForme: true,
name: "Vivillon-River",
baseSpecies: "Vivillon",
forme: "River",
color: "Brown",
},
vivillonmonsoon: {
isCosmeticForme: true,
name: "Vivillon-Monsoon",
baseSpecies: "Vivillon",
forme: "Monsoon",
color: "Gray",
},
vivillonsavanna: {
isCosmeticForme: true,
name: "Vivillon-Savanna",
baseSpecies: "Vivillon",
forme: "Savanna",
color: "Green",
},
vivillonsun: {
isCosmeticForme: true,
name: "Vivillon-Sun",
baseSpecies: "Vivillon",
forme: "Sun",
color: "Red",
},
vivillonocean: {
isCosmeticForme: true,
name: "Vivillon-Ocean",
baseSpecies: "Vivillon",
forme: "Ocean",
color: "Red",
},
vivillonjungle: {
isCosmeticForme: true,
name: "Vivillon-Jungle",
baseSpecies: "Vivillon",
forme: "Jungle",
color: "Green",
},
vivillonfancy: {
num: 666,
name: "Vivillon-Fancy",
@ -11823,7 +11991,7 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
abilities: { 0: "Shield Dust", 1: "Compound Eyes", H: "Friend Guard" },
heightm: 1.2,
weightkg: 17,
color: "Black",
color: "Pink",
prevo: "Spewpa",
evoLevel: 12,
eggGroups: ["Bug"],
@ -11838,7 +12006,7 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
abilities: { 0: "Shield Dust", 1: "Compound Eyes", H: "Friend Guard" },
heightm: 1.2,
weightkg: 17,
color: "Black",
color: "Red",
eggGroups: ["Bug"],
},
litleo: {
@ -14063,6 +14231,48 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
"Minior", "Minior-Orange", "Minior-Yellow", "Minior-Green", "Minior-Blue", "Minior-Indigo", "Minior-Violet",
],
},
miniororange: {
isCosmeticForme: true,
name: "Minior-Orange",
baseSpecies: "Minior",
forme: "Orange",
color: "Red",
},
minioryellow: {
isCosmeticForme: true,
name: "Minior-Yellow",
baseSpecies: "Minior",
forme: "Yellow",
color: "Yellow",
},
miniorgreen: {
isCosmeticForme: true,
name: "Minior-Green",
baseSpecies: "Minior",
forme: "Green",
color: "Green",
},
miniorblue: {
isCosmeticForme: true,
name: "Minior-Blue",
baseSpecies: "Minior",
forme: "Blue",
color: "Blue",
},
miniorindigo: {
isCosmeticForme: true,
name: "Minior-Indigo",
baseSpecies: "Minior",
forme: "Indigo",
color: "Blue",
},
miniorviolet: {
isCosmeticForme: true,
name: "Minior-Violet",
baseSpecies: "Minior",
forme: "Violet",
color: "Purple",
},
miniormeteor: {
num: 774,
name: "Minior-Meteor",
@ -15805,6 +16015,55 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
formeOrder: ["Alcremie", "Alcremie-Ruby-Cream", "Alcremie-Matcha-Cream", "Alcremie-Mint-Cream", "Alcremie-Lemon-Cream", "Alcremie-Salted-Cream", "Alcremie-Ruby-Swirl", "Alcremie-Caramel-Swirl", "Alcremie-Rainbow-Swirl"],
canGigantamax: "G-Max Finale",
},
alcremierubycream: {
isCosmeticForme: true,
name: "Alcremie-Ruby-Cream",
baseSpecies: "Alcremie",
forme: "Ruby-Cream",
color: "Pink",
},
alcremiematchacream: {
isCosmeticForme: true,
name: "Alcremie-Matcha-Cream",
baseSpecies: "Alcremie",
forme: "Matcha-Cream",
color: "Green",
},
alcremiemintcream: {
isCosmeticForme: true,
name: "Alcremie-Mint-Cream",
baseSpecies: "Alcremie",
forme: "Mint-Cream",
color: "Blue",
},
alcremielemoncream: {
isCosmeticForme: true,
name: "Alcremie-Lemon-Cream",
baseSpecies: "Alcremie",
forme: "Lemon-Cream",
color: "Yellow",
},
alcremierubyswirl: {
isCosmeticForme: true,
name: "Alcremie-Ruby-Swirl",
baseSpecies: "Alcremie",
forme: "Ruby-Swirl",
color: "Yellow",
},
alcremiecaramelswirl: {
isCosmeticForme: true,
name: "Alcremie-Caramel-Swirl",
baseSpecies: "Alcremie",
forme: "Caramel-Swirl",
color: "Yellow",
},
alcremierainbowswirl: {
isCosmeticForme: true,
name: "Alcremie-Rainbow-Swirl",
baseSpecies: "Alcremie",
forme: "Rainbow-Swirl",
color: "Yellow",
},
alcremiegmax: {
num: 869,
name: "Alcremie-Gmax",
@ -17619,6 +17878,20 @@ export const Pokedex: import('../sim/dex-species').SpeciesDataTable = {
formeOrder: ["Tatsugiri", "Tatsugiri-Droopy", "Tatsugiri-Stretchy"],
eggGroups: ["Water 2"],
},
tatsugiridroopy: {
isCosmeticForme: true,
name: "Tatsugiri-Droopy",
baseSpecies: "Tatsugiri",
forme: "Droopy",
color: "Pink",
},
tatsugiristretchy: {
isCosmeticForme: true,
name: "Tatsugiri-Stretchy",
baseSpecies: "Tatsugiri",
forme: "Stretchy",
color: "Yellow",
},
annihilape: {
num: 979,
name: "Annihilape",

View File

@ -22,8 +22,17 @@ export interface SpeciesData extends Partial<Species> {
eggGroups: string[];
weightkg: number;
}
export interface CosmeticFormeData {
isCosmeticForme: boolean;
name: string;
baseSpecies: string;
forme: string;
color: string;
}
export type ModdedSpeciesData = SpeciesData | Partial<Omit<SpeciesData, 'name'>> & { inherit: true };
export type ModdedSpeciesData = SpeciesData | CosmeticFormeData |
Partial<Omit<SpeciesData, 'name'>> & { inherit: true } |
Partial<Omit<CosmeticFormeData, 'isCosmeticForme'>> & { inherit: true };
export interface SpeciesFormatsData {
doublesTier?: TierTypes.Doubles | TierTypes.Other;
@ -50,7 +59,7 @@ export interface PokemonGoData {
LGPERestrictiveMoves?: { [moveid: string]: number | null };
}
export interface SpeciesDataTable { [speciesid: IDEntry]: SpeciesData }
export interface SpeciesDataTable { [speciesid: IDEntry]: SpeciesData | CosmeticFormeData }
export interface ModdedSpeciesDataTable { [speciesid: IDEntry]: ModdedSpeciesData }
export interface SpeciesFormatsDataTable { [speciesid: IDEntry]: SpeciesFormatsData }
export interface ModdedSpeciesFormatsDataTable { [speciesid: IDEntry]: ModdedSpeciesFormatsData }
@ -181,6 +190,8 @@ export class Species extends BasicEffect implements Readonly<BasicEffect & Speci
readonly eggGroups: string[];
/** True if this species can hatch from an Egg. */
readonly canHatch: boolean;
/** True if this species is a purely cosmetic forme. */
readonly isCosmeticForme: boolean;
/**
* Gender. M = always male, F = always female, N = always
* genderless, '' = sometimes male sometimes female.
@ -313,6 +324,7 @@ export class Species extends BasicEffect implements Readonly<BasicEffect & Speci
this.weighthg = this.weightkg * 10;
this.heightm = data.heightm || 0;
this.color = data.color || '';
this.isCosmeticForme = data.isCosmeticForme || undefined;
this.tags = data.tags || [];
this.unreleasedHidden = data.unreleasedHidden || false;
this.maleOnlyHidden = !!data.maleOnlyHidden;
@ -431,15 +443,28 @@ export class DexSpecies {
species.abilities = { 0: species.abilities['S']! };
} else {
species = this.get(alias);
if (this.dex.data.Pokedex?.[id]?.isCosmeticForme) {
const cosmeticForme = this.dex.data.Pokedex[id];
species = new Species({
...species,
...cosmeticForme,
name: species.baseSpecies + '-' + cosmeticForme.forme!, // Forme always exists on cosmetic forme entries
baseForme: "",
otherFormes: null,
cosmeticFormes: null,
});
}
if (species.cosmeticFormes) {
for (const forme of species.cosmeticFormes) {
if (this.dex.data.Pokedex.hasOwnProperty(toID(forme))) continue;
if (toID(forme) === id) {
species = new Species({
...species,
name: forme,
forme: forme.slice(species.name.length + 1),
baseForme: "",
baseSpecies: species.name,
baseForme: "",
isCosmeticForme: true,
otherFormes: null,
cosmeticFormes: null,
});

View File

@ -13,14 +13,16 @@ describe('Dex data', () => {
assert.equal(entry.name, entry.name.trim(), `Pokemon name "${entry.name}" should not start or end with whitespace`);
assert(entry.color, `Pokemon ${entry.name} must have a color.`);
assert(entry.heightm, `Pokemon ${entry.name} must have a height.`);
if (!entry.isCosmeticForme) assert(entry.heightm, `Pokemon ${entry.name} must have a height.`);
if (entry.forme) {
// entry is a forme of a base species
const baseEntry = Pokedex[toID(entry.baseSpecies)];
assert(baseEntry && !baseEntry.forme, `Forme ${entry.name} should have a valid baseSpecies`);
// Gmax formes are not actually formes, they are only included in pokedex.ts for convenience
if (!entry.name.includes('Gmax')) assert((baseEntry.otherFormes || []).includes(entry.name), `Base species ${entry.baseSpecies} should have ${entry.name} listed as an otherForme`);
if (!entry.name.includes('Gmax') && !entry.isCosmeticForme) {
assert((baseEntry.otherFormes || []).includes(entry.name), `Base species ${entry.baseSpecies} should have ${entry.name} listed as an otherForme`);
}
assert.false(entry.otherFormes, `Forme ${entry.baseSpecies} should not have a forme list (the list goes in baseSpecies).`);
assert.false(entry.cosmeticFormes, `Forme ${entry.baseSpecies} should not have a cosmetic forme list (the list goes in baseSpecies).`);
assert.false(entry.baseForme, `Forme ${entry.baseSpecies} should not have a baseForme (its forme name goes in forme) (did you mean baseSpecies?).`);
@ -65,6 +67,10 @@ describe('Dex data', () => {
assert.equal(Dex.data.FormatsData[toID(forme)], undefined, `Cosmetic forme "${forme}" should not have its own tier`);
}
}
if (entry.isCosmeticForme) {
assert.equal(Dex.getAlias(pokemonid), toID(entry.baseSpecies), `Misspelled/nonexistent alias "${pokemonid}" of ${entry.baseSpecies}`);
assert.equal(Dex.data.FormatsData[pokemonid], undefined, `Cosmetic forme "${entry.name}" should not have its own tier`);
}
if (entry.battleOnly) {
const battleOnly = Array.isArray(entry.battleOnly) ? entry.battleOnly : [entry.battleOnly];
for (const battleForme of battleOnly) {
@ -308,6 +314,7 @@ describe('Dex data', () => {
const count = { species: 0, formes: 0 };
for (const pkmn of dex.species.all()) {
if (!existenceFunction(pkmn)) continue;
if (pkmn.isCosmeticForme) continue;
if (pkmn.name !== pkmn.baseSpecies) {
count.formes++;
} else {