Implement Mega Stones as {key: value} pairs (#11684)

* Implement Mega Stones as pairs {key: value}

* Fix Mega Evolution check

* Add constructor guards to Dex getters

* Update config/formats.ts

---------

Co-authored-by: Kris Johnson <11083252+KrisXV@users.noreply.github.com>
This commit is contained in:
André Bastos Dias 2026-01-08 21:59:22 +00:00 committed by GitHub
parent ee77cf98ae
commit 684150d9d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 278 additions and 528 deletions

View File

@ -2899,7 +2899,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
onValidateSet(set, format, setHas, teamHas) { onValidateSet(set, format, setHas, teamHas) {
if (set.item) { if (set.item) {
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaEvolves && !(this.ruleTable.has(`+item:${item.id}`) || this.ruleTable.has(`+pokemontag:mega`))) { if (item.megaStone && !(this.ruleTable.has(`+item:${item.id}`) || this.ruleTable.has(`+pokemontag:mega`))) {
return [`Mega Evolution is banned.`]; return [`Mega Evolution is banned.`];
} }
if (item.zMove && !(this.ruleTable.has(`+item:${item.id}`))) { if (item.zMove && !(this.ruleTable.has(`+item:${item.id}`))) {
@ -3678,8 +3678,8 @@ export const Formats: import('../sim/dex-formats').FormatList = [
} }
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (set.item && item.megaStone) { if (set.item && item.megaStone) {
const megaSpecies = this.dex.species.get(Array.isArray(item.megaStone) ? item.megaStone[0] : item.megaStone); const megaSpecies = this.dex.species.get(item.megaStone[species.baseSpecies]);
if (item.megaEvolves?.includes(species.baseSpecies) && megaSpecies.bst > 625) { if (megaSpecies.bst > 625) {
return [ return [
`${set.name || set.species}'s item ${item.name} is banned.`, `(Pok\u00e9mon with a BST higher than 625 are banned)`, `${set.name || set.species}'s item ${item.name} is banned.`, `(Pok\u00e9mon with a BST higher than 625 are banned)`,
]; ];

File diff suppressed because it is too large Load Diff

View File

@ -13,12 +13,10 @@ export const Items: import('../../../sim/dex-items').ModdedItemDataTable = {
masquerainite: { masquerainite: {
name: "Masquerainite", name: "Masquerainite",
spritenum: 1, spritenum: 1,
megaStone: "Masquerain-Mega", megaStone: { "Masquerain": "Masquerain-Mega" },
megaEvolves: "Masquerain",
itemUser: ["Masquerain"], itemUser: ["Masquerain"],
onTakeItem(item, source) { onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.baseSpecies) return false; return !item.megaStone?.[source.baseSpecies.baseSpecies];
return true;
}, },
num: -1, num: -1,
gen: 9, gen: 9,
@ -59,12 +57,10 @@ export const Items: import('../../../sim/dex-items').ModdedItemDataTable = {
typhlosionite: { typhlosionite: {
name: "Typhlosionite", name: "Typhlosionite",
spritenum: 1, spritenum: 1,
megaStone: "Typhlosion-Mega", megaStone: { "Typhlosion": "Typhlosion-Mega" },
megaEvolves: "Typhlosion",
itemUser: ["Typhlosion"], itemUser: ["Typhlosion"],
onTakeItem(item, source) { onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.baseSpecies) return false; return !item.megaStone?.[source.baseSpecies.baseSpecies];
return true;
}, },
num: -2, num: -2,
gen: 9, gen: 9,

View File

@ -92,10 +92,10 @@ export const Rulesets: import('../../../sim/dex-formats').ModdedFormatDataTable
if (["Zacian", "Zamazenta"].includes(species.baseSpecies) && this.toID(set.item).startsWith('rusted')) { if (["Zacian", "Zamazenta"].includes(species.baseSpecies) && this.toID(set.item).startsWith('rusted')) {
species = this.dex.species.get(set.species + "-Crowned"); species = this.dex.species.get(set.species + "-Crowned");
} }
if (set.item && this.dex.items.get(set.item).megaStone) { if (set.item) {
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaEvolves === species.baseSpecies) { if (item.megaStone?.[species.baseSpecies]) {
species = this.dex.species.get(Array.isArray(item.megaStone) ? item.megaStone[0] : item.megaStone); species = this.dex.species.get(item.megaStone[species.baseSpecies]);
} }
} }
if ( if (
@ -123,8 +123,8 @@ export const Rulesets: import('../../../sim/dex-formats').ModdedFormatDataTable
} }
if (set.item) { if (set.item) {
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaEvolves === set.species) { if (item.megaStone?.[set.species]) {
godSpecies = this.dex.species.get(Array.isArray(item.megaStone) ? item.megaStone[0] : item.megaStone); godSpecies = this.dex.species.get(item.megaStone[set.species]);
} }
if (["Zacian", "Zamazenta"].includes(godSpecies.baseSpecies) && item.id.startsWith('rusted')) { if (["Zacian", "Zamazenta"].includes(godSpecies.baseSpecies) && item.id.startsWith('rusted')) {
godSpecies = this.dex.species.get(set.species + "-Crowned"); godSpecies = this.dex.species.get(set.species + "-Crowned");

View File

@ -2,15 +2,15 @@ export const Items: import('../../../sim/dex-items').ModdedItemDataTable = {
slowbronite: { slowbronite: {
inherit: true, inherit: true,
onTakeItem(item, source) { onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.name || item.megaStone === source.baseSpecies.name) return false; return !item.megaStone || (!item.megaStone[source.baseSpecies.name] &&
return true; !Object.values(item.megaStone).includes(source.baseSpecies.name));
}, },
}, },
greninjite: { greninjite: {
inherit: true, inherit: true,
onTakeItem(item, source) { onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.name || item.megaStone === source.baseSpecies.name) return false; return !item.megaStone || (!item.megaStone[source.baseSpecies.name] &&
return true; !Object.values(item.megaStone).includes(source.baseSpecies.name));
}, },
}, },
zygardite: { zygardite: {

View File

@ -57,23 +57,8 @@ export const Scripts: ModdedBattleScriptsData = {
pokemon.baseMoves.includes(this.battle.toID(altForme.requiredMove)) && !item.zMove) { pokemon.baseMoves.includes(this.battle.toID(altForme.requiredMove)) && !item.zMove) {
return altForme.name; return altForme.name;
} }
if (Array.isArray(item.megaEvolves)) { if (!item.megaStone) return null;
if (!Array.isArray(item.megaStone)) { return item.megaStone[species.name];
throw new Error(`${item.name}#megaEvolves and ${item.name}#megaStone type mismatch`);
}
if (item.megaEvolves.length !== item.megaStone.length) {
throw new Error(`${item.name}#megaEvolves and ${item.name}#megaStone length mismatch`);
}
const index = item.megaEvolves.indexOf(species.name);
if (index < 0) return null;
return item.megaStone[index];
} else {
if (item.megaEvolves === species.name) {
if (Array.isArray(item.megaStone)) throw new Error(`${item.name}#megaEvolves and ${item.name}#megaStone type mismatch`);
return item.megaStone;
}
}
return null;
}, },
runMegaEvo(pokemon) { runMegaEvo(pokemon) {
const speciesid = pokemon.canMegaEvo || pokemon.canUltraBurst; const speciesid = pokemon.canMegaEvo || pokemon.canUltraBurst;

View File

@ -14,11 +14,9 @@ export const Items: import('../../../sim/dex-items').ModdedItemDataTable = {
name: "Flygonite", name: "Flygonite",
spritenum: 111, spritenum: 111,
itemUser: ["Flygon"], itemUser: ["Flygon"],
megaEvolves: "Flygon", megaStone: { "Trapinch": "Flygon" },
megaStone: "Trapinch",
onTakeItem(item, source) { onTakeItem(item, source) {
if (item.megaEvolves === source.baseSpecies.baseSpecies) return false; return !item.megaStone?.[source.baseSpecies.baseSpecies];
return true;
}, },
desc: "If held by a Flygon, this item allows it to Mega Evolve in battle.", desc: "If held by a Flygon, this item allows it to Mega Evolve in battle.",
}, },
@ -36,7 +34,7 @@ export const Items: import('../../../sim/dex-items').ModdedItemDataTable = {
gardevoirite: { gardevoirite: {
inherit: true, inherit: true,
itemUser: ["Ralts"], itemUser: ["Ralts"],
megaEvolves: "Ralts", megaStone: { "Ralts": "Gardevoir-Mega" },
desc: "If held by a Ralts, this item allows it to Mega Evolve in battle.", desc: "If held by a Ralts, this item allows it to Mega Evolve in battle.",
}, },
// Peary // Peary

View File

@ -963,26 +963,11 @@ export const Scripts: ModdedBattleScriptsData = {
pokemon.baseMoves.includes(this.battle.toID(altForme.requiredMove)) && !item.zMove) { pokemon.baseMoves.includes(this.battle.toID(altForme.requiredMove)) && !item.zMove) {
return altForme.name; return altForme.name;
} }
if (!item.megaStone) return null;
// a hacked-in Megazard X can mega evolve into Megazard Y, but not into Megazard X // a hacked-in Megazard X can mega evolve into Megazard Y, but not into Megazard X
if (Array.isArray(item.megaEvolves)) { // FIXME: Change to species.name when champions comes
if (!Array.isArray(item.megaStone)) { const megaEvolution = item.megaStone[species.baseSpecies];
throw new Error(`${item.name}#megaEvolves and ${item.name}#megaStone type mismatch`); return megaEvolution && megaEvolution !== species.name ? megaEvolution : null;
}
if (item.megaEvolves.length !== item.megaStone.length) {
throw new Error(`${item.name}#megaEvolves and ${item.name}#megaStone length mismatch`);
}
// FIXME: Change to species.name when champions comes
const index = item.megaEvolves.indexOf(species.baseSpecies);
if (index < 0) return null;
return item.megaStone[index];
// FIXME: Change to species.name when champions comes
} else {
if (item.megaEvolves === species.baseSpecies) {
if (Array.isArray(item.megaStone)) throw new Error(`${item.name}#megaEvolves and ${item.name}#megaStone type mismatch`);
return item.megaStone;
}
}
return null;
}, },
// 1 Z per pokemon // 1 Z per pokemon

View File

@ -385,12 +385,8 @@ export const Scripts: ModdedBattleScriptsData = {
if (pokemon.species.isMega) return null; if (pokemon.species.isMega) return null;
const item = pokemon.getItem(); const item = pokemon.getItem();
if (item.megaStone) { if (!item.megaStone || item.megaStone[pokemon.baseSpecies.name]) return null;
if (item.megaStone.includes(pokemon.baseSpecies.name)) return null; return Object.values(item.megaStone)[0];
return Array.isArray(item.megaStone) ? item.megaStone[0] : item.megaStone;
} else {
return null;
}
}, },
runMegaEvo(pokemon) { runMegaEvo(pokemon) {
if (pokemon.species.isMega) return false; if (pokemon.species.isMega) return false;

View File

@ -1607,8 +1607,7 @@ export class RandomGen7Teams extends RandomGen8Teams {
if (isMonotype) { if (isMonotype) {
// Prevents Mega Evolutions from breaking the type limits // Prevents Mega Evolutions from breaking the type limits
if (itemData.megaStone) { if (itemData.megaStone) {
const megaSpecies = this.dex.species.get(Array.isArray(itemData.megaStone) ? const megaSpecies = this.dex.species.get(Object.values(itemData.megaStone)[0]);
itemData.megaStone[0] : itemData.megaStone);
if (types.length > megaSpecies.types.length) types = [species.types[0]]; if (types.length > megaSpecies.types.length) types = [species.types[0]];
// Only check the second type because a Mega Evolution should always share the first type with its base forme. // Only check the second type because a Mega Evolution should always share the first type with its base forme.
if (megaSpecies.types[1] && types[1] && megaSpecies.types[1] !== types[1]) { if (megaSpecies.types[1] && types[1] && megaSpecies.types[1] !== types[1]) {

View File

@ -1507,17 +1507,9 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
typeTable = typeTable.filter(type => species.types.includes(type)); typeTable = typeTable.filter(type => species.types.includes(type));
} }
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaStone) { if (item.megaStone?.[species.name]) {
if (Array.isArray(item.megaStone)) { species = this.dex.species.get(item.megaStone[species.name]);
const index = (item.megaEvolves as string[]).indexOf(species.name); typeTable = typeTable.filter(type => species.types.includes(type));
if (index >= 0) {
species = this.dex.species.get(item.megaStone[index]);
typeTable = typeTable.filter(type => species.types.includes(type));
}
} else {
species = this.dex.species.get(item.megaStone);
typeTable = typeTable.filter(type => species.types.includes(type));
}
} }
if (item.id === "ultranecroziumz" && species.baseSpecies === "Necrozma") { if (item.id === "ultranecroziumz" && species.baseSpecies === "Necrozma") {
species = this.dex.species.get("Necrozma-Ultra"); species = this.dex.species.get("Necrozma-Ultra");
@ -1556,17 +1548,9 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
} }
color = species.color; color = species.color;
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaStone) { if (item.megaStone?.[species.name]) {
if (Array.isArray(item.megaStone)) { species = this.dex.species.get(item.megaStone[species.name]);
const index = (item.megaEvolves as string[]).indexOf(species.name); color = species.color;
if (index >= 0) {
species = this.dex.species.get(item.megaStone[index]);
color = species.color;
}
} else {
species = this.dex.species.get(item.megaStone);
color = species.color;
}
} }
if (item.id === "ultranecroziumz" && species.baseSpecies === "Necrozma") { if (item.id === "ultranecroziumz" && species.baseSpecies === "Necrozma") {
species = this.dex.species.get("Necrozma-Ultra"); species = this.dex.species.get("Necrozma-Ultra");
@ -2666,12 +2650,10 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
) { ) {
species = this.dex.species.get(`${species.baseSpecies}-Crowned`); species = this.dex.species.get(`${species.baseSpecies}-Crowned`);
} }
if (set.item && this.dex.items.get(set.item).megaStone) { if (set.item) {
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaEvolves?.includes(species.name)) { if (item.megaStone?.[species.name]) {
species = this.dex.species.get(Array.isArray(item.megaEvolves) ? species = this.dex.species.get(item.megaStone[species.name]);
(item.megaStone as string[])[item.megaEvolves.indexOf(species.name)] :
item.megaStone as string);
} }
} }
if (this.ruleTable.isRestrictedSpecies(species) || if (this.ruleTable.isRestrictedSpecies(species) ||
@ -2693,10 +2675,8 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
} }
if (set.item) { if (set.item) {
const item = this.dex.items.get(set.item); const item = this.dex.items.get(set.item);
if (item.megaEvolves?.includes(set.species)) { if (item.megaStone?.[set.species]) {
godSpecies = this.dex.species.get(Array.isArray(item.megaEvolves) ? godSpecies = this.dex.species.get(item.megaStone[set.species]);
(item.megaStone as string[])[item.megaEvolves.indexOf(set.species)] :
item.megaStone as string);
} }
if (["Zacian", "Zamazenta"].includes(godSpecies.baseSpecies) && item.id.startsWith('rusted')) { if (["Zacian", "Zamazenta"].includes(godSpecies.baseSpecies) && item.id.startsWith('rusted')) {
godSpecies = this.dex.species.get(set.species + "-Crowned"); godSpecies = this.dex.species.get(set.species + "-Crowned");

View File

@ -29,14 +29,23 @@ function getMegaStone(stone: string, mod = 'gen9'): Item | null {
id: move.id, id: move.id,
name: move.name, name: move.name,
fullname: move.name, fullname: move.name,
megaEvolves: 'Rayquaza', megaStone: { 'Rayquaza': 'Rayquaza-Mega' },
megaStone: 'Rayquaza-Mega',
exists: true, exists: true,
// Adding extra values to appease typescript // Adding extra values to appease typescript
gen: 6, gen: 6,
num: -1, num: -1,
effectType: 'Item', effectType: 'Item',
sourceEffect: '', sourceEffect: '',
isBerry: false,
ignoreKlutz: false,
isGem: false,
isPokeball: false,
isPrimalOrb: false,
shortDesc: "",
desc: "",
isNonstandard: null,
noCopy: false,
affectsFainted: false,
} as Item; } as Item;
} else { } else {
return null; return null;
@ -131,8 +140,8 @@ export const commands: Chat.ChatCommands = {
megaSpecies = dex.species.get(forcedForme); megaSpecies = dex.species.get(forcedForme);
baseSpecies = dex.species.get(forcedForme.split('-')[0]); baseSpecies = dex.species.get(forcedForme.split('-')[0]);
} else { } else {
megaSpecies = dex.species.get(Array.isArray(stone.megaStone) ? stone.megaStone[0] : stone.megaStone); megaSpecies = dex.species.get(Object.values(stone.megaStone!)[0]);
baseSpecies = dex.species.get(Array.isArray(stone.megaEvolves) ? stone.megaEvolves[0] : stone.megaEvolves); baseSpecies = dex.species.get(Object.keys(stone.megaStone!)[0]);
} }
break; break;
} }
@ -282,8 +291,8 @@ export const commands: Chat.ChatCommands = {
megaSpecies = dex.species.get(forcedForme); megaSpecies = dex.species.get(forcedForme);
baseSpecies = dex.species.get(forcedForme.split('-')[0]); baseSpecies = dex.species.get(forcedForme.split('-')[0]);
} else { } else {
megaSpecies = dex.species.get(Array.isArray(aStone.megaStone) ? aStone.megaStone[0] : aStone.megaStone); megaSpecies = dex.species.get(Object.values(aStone.megaStone!)[0]);
baseSpecies = dex.species.get(Array.isArray(aStone.megaEvolves) ? aStone.megaEvolves[0] : aStone.megaEvolves); baseSpecies = dex.species.get(Object.keys(aStone.megaStone!)[0]);
} }
break; break;
} }

View File

@ -373,7 +373,7 @@ class SSBSetsHTML extends Chat.JSX.Component<{ target: string }> {
<SSBInnateHTML name={setName} dex={dex} baseDex={baseDex} /> <SSBInnateHTML name={setName} dex={dex} baseDex={baseDex} />
<SSBPokemonHTML species={set.species} dex={dex} baseDex={baseDex} /> <SSBPokemonHTML species={set.species} dex={dex} baseDex={baseDex} />
{(!Array.isArray(set.item) && item.megaStone) && <SSBPokemonHTML {(!Array.isArray(set.item) && item.megaStone) && <SSBPokemonHTML
species={Array.isArray(item.megaStone) ? item.megaStone[0] : item.megaStone} dex={dex} baseDex={baseDex} species={Object.values(item.megaStone)[0]} dex={dex} baseDex={baseDex}
/>} />}
{/* keys and Kennedy have an itemless forme change */} {/* keys and Kennedy have an itemless forme change */}
{['Rayquaza'].includes(set.species) && <SSBPokemonHTML species={`${set.species}-Mega`} dex={dex} baseDex={baseDex} />} {['Rayquaza'].includes(set.species) && <SSBPokemonHTML species={`${set.species}-Mega`} dex={dex} baseDex={baseDex} />}

View File

@ -134,7 +134,7 @@ export function getSpeciesName(set: PokemonSet, format: Format) {
} else if (species === "Groudon" && item.name === "Red Orb") { } else if (species === "Groudon" && item.name === "Red Orb") {
return "Groudon-Primal"; return "Groudon-Primal";
} else if (item.megaStone) { } else if (item.megaStone) {
return Array.isArray(item.megaStone) ? item.megaStone[0] : item.megaStone; return Object.values(item.megaStone)[0];
} else if (species === "Rayquaza" && moves.includes('Dragon Ascent') && !item.zMove && megaRayquazaPossible) { } else if (species === "Rayquaza" && moves.includes('Dragon Ascent') && !item.zMove && megaRayquazaPossible) {
return "Rayquaza-Mega"; return "Rayquaza-Mega";
} else if (species === "Poltchageist-Artisan") { // Babymons from here on out } else if (species === "Poltchageist-Artisan") { // Babymons from here on out

View File

@ -1871,21 +1871,15 @@ export class BattleActions {
pokemon.baseMoves.includes(toID(altForme.requiredMove)) && !item.zMove) { pokemon.baseMoves.includes(toID(altForme.requiredMove)) && !item.zMove) {
return altForme.name; return altForme.name;
} }
if (!item.megaStone) return null;
// Temporary hardcode until generation shift // Temporary hardcode until generation shift
if ((species.baseSpecies === "Floette" || species.baseSpecies === "Zygarde") && item.megaEvolves === species.name) { if ((species.baseSpecies === "Floette" || species.baseSpecies === "Zygarde") && item.megaStone[species.name]) {
return item.megaStone as string; return item.megaStone[species.name];
} }
// a hacked-in Megazard X can mega evolve into Megazard Y, but not into Megazard X // a hacked-in Megazard X can mega evolve into Megazard Y, but not into Megazard X
if (Array.isArray(item.megaStone)) { // FIXME: Change to species.name when champions comes
// FIXME: Change to species.name when champions comes const megaEvolution = item.megaStone[species.baseSpecies];
const index = (item.megaEvolves as string[]).indexOf(species.baseSpecies); return megaEvolution && megaEvolution !== species.name ? megaEvolution : null;
if (index < 0) return null;
return item.megaStone[index];
// FIXME: Change to species.name when champions comes
} else if (item.megaEvolves === species.baseSpecies && item.megaStone !== species.name) {
return item.megaStone;
}
return null;
} }
canUltraBurst(pokemon: Pokemon) { canUltraBurst(pokemon: Pokemon) {

View File

@ -85,7 +85,7 @@ export class DexAbilities {
} }
getByID(id: ID): Ability { getByID(id: ID): Ability {
if (id === '') return EMPTY_ABILITY; if (id === '' || id === 'constructor') return EMPTY_ABILITY;
let ability = this.abilityCache.get(id); let ability = this.abilityCache.get(id);
if (ability) return ability; if (ability) return ability;

View File

@ -665,7 +665,7 @@ export class DexConditions {
} }
getByID(id: ID): Condition { getByID(id: ID): Condition {
if (id === '') return EMPTY_CONDITION; if (id === '' || id === 'constructor') return EMPTY_CONDITION;
let condition = this.conditionCache.get(id); let condition = this.conditionCache.get(id);
if (condition) return condition; if (condition) return condition;

View File

@ -172,7 +172,7 @@ export class DexNatures {
return this.getByID(toID(name)); return this.getByID(toID(name));
} }
getByID(id: ID): Nature { getByID(id: ID): Nature {
if (id === '') return EMPTY_NATURE; if (id === '' || id === 'constructor') return EMPTY_NATURE;
let nature = this.natureCache.get(id); let nature = this.natureCache.get(id);
if (nature) return nature; if (nature) return nature;
@ -293,7 +293,7 @@ export class DexTypes {
} }
getByID(id: ID): TypeInfo { getByID(id: ID): TypeInfo {
if (id === '') return EMPTY_TYPE_INFO; if (id === '' || id === 'constructor') return EMPTY_TYPE_INFO;
let type = this.typeCache.get(id); let type = this.typeCache.get(id);
if (type) return type; if (type) return type;

View File

@ -43,17 +43,11 @@ export class Item extends BasicEffect implements Readonly<BasicEffect> {
*/ */
readonly onMemory?: string; readonly onMemory?: string;
/** /**
* If this is a mega stone: The name (e.g. Charizard-Mega-X) of the * If this is a mega stone: A pair (e.g. Charizard: Charizard-Mega-X) of the
* forme this allows transformation into. * forme this allows transformation from and into.
* undefined, if not a mega stone. * undefined, if not a mega stone.
*/ */
readonly megaStone?: string | string[]; readonly megaStone?: { [megaEvolves: string]: string };
/**
* If this is a mega stone: The name (e.g. Charizard) of the
* forme this allows transformation from.
* undefined, if not a mega stone.
*/
readonly megaEvolves?: string | string[];
/** /**
* If this is a Z crystal: true if the Z Crystal is generic * If this is a Z crystal: true if the Z Crystal is generic
* (e.g. Firium Z). If species-specific, the name * (e.g. Firium Z). If species-specific, the name
@ -116,7 +110,6 @@ export class Item extends BasicEffect implements Readonly<BasicEffect> {
this.onDrive = data.onDrive || undefined; this.onDrive = data.onDrive || undefined;
this.onMemory = data.onMemory || undefined; this.onMemory = data.onMemory || undefined;
this.megaStone = data.megaStone || undefined; this.megaStone = data.megaStone || undefined;
this.megaEvolves = data.megaEvolves || undefined;
this.zMove = data.zMove || undefined; this.zMove = data.zMove || undefined;
this.zMoveType = data.zMoveType || undefined; this.zMoveType = data.zMoveType || undefined;
this.zMoveFrom = data.zMoveFrom || undefined; this.zMoveFrom = data.zMoveFrom || undefined;
@ -176,7 +169,7 @@ export class DexItems {
} }
getByID(id: ID): Item { getByID(id: ID): Item {
if (id === '') return EMPTY_ITEM; if (id === '' || id === 'constructor') return EMPTY_ITEM;
let item = this.itemCache.get(id); let item = this.itemCache.get(id);
if (item) return item; if (item) return item;
if (this.dex.getAlias(id)) { if (this.dex.getAlias(id)) {

View File

@ -621,7 +621,7 @@ export class DexMoves {
} }
getByID(id: ID): Move { getByID(id: ID): Move {
if (id === '') return EMPTY_MOVE; if (id === '' || id === 'constructor') return EMPTY_MOVE;
let move = this.moveCache.get(id); let move = this.moveCache.get(id);
if (move) return move; if (move) return move;
if (this.dex.getAlias(id)) { if (this.dex.getAlias(id)) {

View File

@ -436,7 +436,7 @@ export class DexSpecies {
} }
getByID(id: ID): Species { getByID(id: ID): Species {
if (id === '') return EMPTY_SPECIES; if (id === '' || id === 'constructor') return EMPTY_SPECIES;
let species: Mutable<Species> | undefined = this.speciesCache.get(id); let species: Mutable<Species> | undefined = this.speciesCache.get(id);
if (species) return species; if (species) return species;

View File

@ -526,14 +526,8 @@ export class TeamValidator {
if (ruleTable.has('obtainableformes')) { if (ruleTable.has('obtainableformes')) {
const canMegaEvo = dex.gen <= 7 || ruleTable.has('+pokemontag:past'); const canMegaEvo = dex.gen <= 7 || ruleTable.has('+pokemontag:past');
if (item.megaEvolves?.includes(species.name)) { if (item.megaStone?.[species.name]) {
if (!item.megaStone) throw new Error(`Item ${item.name} has no base form for mega evolution`); tierSpecies = dex.species.get(item.megaStone[species.name]);
if (Array.isArray(item.megaEvolves)) {
const idx = item.megaEvolves.indexOf(species.name);
tierSpecies = dex.species.get(item.megaStone[idx]);
} else {
tierSpecies = dex.species.get(item.megaStone as string);
}
} else if (item.id === 'redorb' && species.id === 'groudon') { } else if (item.id === 'redorb' && species.id === 'groudon') {
tierSpecies = dex.species.get('Groudon-Primal'); tierSpecies = dex.species.get('Groudon-Primal');
} else if (item.id === 'blueorb' && species.id === 'kyogre') { } else if (item.id === 'blueorb' && species.id === 'kyogre') {

View File

@ -137,8 +137,8 @@ export class ExhaustiveRunner {
const signatures = new Map(); const signatures = new Map();
for (const id of pools.items.possible) { for (const id of pools.items.possible) {
const item = dex.data.Items[id]; const item = dex.data.Items[id];
if (item.megaEvolves) { if (item.megaStone) {
const pokemon = toID(item.megaEvolves); const pokemon = toID(Object.keys(item.megaStone)[0]);
const combo = { item: id }; const combo = { item: id };
let combos = signatures.get(pokemon); let combos = signatures.get(pokemon);
if (!combos) { if (!combos) {

View File

@ -132,12 +132,6 @@ describe('Dex data', () => {
const entry = Items[itemid]; const entry = Items[itemid];
assert.equal(toID(entry.name), itemid, `Mismatched Item key "${itemid}" of "${entry.name}"`); assert.equal(toID(entry.name), itemid, `Mismatched Item key "${itemid}" of "${entry.name}"`);
assert.equal(typeof entry.num, 'number', `Item ${entry.name} should have a number`); assert.equal(typeof entry.num, 'number', `Item ${entry.name} should have a number`);
if (entry.megaStone) {
assert.equal(typeof entry.megaStone, typeof entry.megaEvolves, `Item ${entry.name} megaStone and megaEvolves should both be the same type`);
if (Array.isArray(entry.megaStone)) {
assert.equal(entry.megaStone.length, entry.megaEvolves.length, `Item ${entry.name} megaStone and megaEvolves arrays should be the same length`);
}
}
} }
}); });

View File

@ -312,7 +312,8 @@ function skip(dex: ModdedDex, format: Format, pokemon: string, set: DeepPartial<
if (pokemon === 'Rayquaza-Mega') { if (pokemon === 'Rayquaza-Mega') {
return format.id.includes('ubers') || !hasMove('Dragon Ascent'); return format.id.includes('ubers') || !hasMove('Dragon Ascent');
} else { } else {
return dex.items.get(set.item).megaStone !== pokemon; const item = dex.items.get(set.item);
return !item.megaStone || !Object.values(item.megaStone).includes(pokemon);
} }
} }
if (pokemon === 'Necrozma-Ultra' && set.item !== 'Ultranecrozium Z') return true; if (pokemon === 'Necrozma-Ultra' && set.item !== 'Ultranecrozium Z') return true;