Add January 2026 rotational formats

This commit is contained in:
Kris Johnson 2026-01-01 02:47:40 -07:00
parent 6a2b1ad82e
commit 61a7a3a675
3 changed files with 125 additions and 101 deletions

View File

@ -534,35 +534,39 @@ export const Formats: import('../sim/dex-formats').FormatList = [
column: 2,
},
{
name: "[Gen 9] Convergence",
desc: `Allows all Pokémon that have identical types to share moves and abilities.`,
name: "[Gen 9] Tier Shift",
desc: `Pokémon below OU get their stats, excluding HP, boosted. UU/RUBL get +15, RU/NUBL get +20, NU/PUBL get +25, and PU or lower get +30.`,
mod: 'gen9',
// searchShow: false,
ruleset: ['Standard OMs', 'Ability Clause = 1', 'Sleep Moves Clause', 'Convergence Legality', 'Terastal Clause', '!Obtainable Abilities'],
ruleset: ['Standard OMs', 'Sleep Moves Clause', 'Terastal Clause', 'Evasion Clause', 'Tier Shift Mod'],
banlist: [
'Arceus', 'Baxcalibur', 'Calyrex-Ice', 'Calyrex-Shadow', 'Chi-Yu', 'Chien-Pao', 'Comfey', 'Darkrai', 'Deoxys-Normal', 'Deoxys-Attack', 'Deoxys-Speed',
'Dialga', 'Dialga-Origin', 'Dondozo', 'Eternatus', 'Flutter Mane', 'Giratina', 'Giratina-Origin', 'Groudon', 'Haxorus', 'Ho-oh', 'Inteleon',
'Iron Bundle', 'Iron Hands', 'Koraidon', 'Kyogre', 'Kyurem-Black', 'Kyurem-White', 'Landorus-Incarnate', 'Lilligant-Hisui', 'Lugia', 'Lunala',
'Magearna', 'Manaphy', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Ogerpon-Hearthflame', 'Palafin', 'Palkia', 'Palkia-Origin',
'Porygon-Z', 'Primarina', 'Rayquaza', 'Regieleki', 'Regigigas', 'Reshiram', 'Roaring Moon', 'Shaymin-Sky', 'Solgaleo', 'Slaking', 'Smeargle', 'Sneasler',
'Spectrier', 'Umbreon', 'Urshifu-Single-Strike', 'Urshifu-Rapid-Strike', 'Walking Wake', 'Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned',
'Zekrom', 'Arena Trap', 'Comatose', 'Contrary', 'Drizzle', 'Drought', 'Imposter', 'Moody', 'Pure Power', 'Shadow Tag', 'Speed Boost', 'Unburden',
'King\'s Rock', 'Light Clay', 'Razor Fang', 'Baton Pass', 'Belly Drum', 'Boomburst', 'Extreme Speed', 'Final Gambit', 'Last Respects', 'Population Bomb',
'Quiver Dance', 'Rage Fist', 'Shed Tail', 'Shell Smash', 'Spore', 'Transform',
'Arceus', 'Calyrex-Shadow', 'Koraidon', 'Kyogre', 'Medicham', 'Miraidon', 'Necrozma-Dusk-Mane', 'Zacian-Crowned', 'Drizzle', 'Moody', 'Arena Trap', 'Shadow Tag',
'Baton Pass', 'Last Respects', 'Shed Tail', 'Heat Rock', 'King\'s Rock', 'Light Clay', 'Razor Fang',
],
unbanlist: ['Arceus-Bug', 'Arceus-Grass', 'Arceus-Ice'],
},
{
name: "[Gen 9] VoltTurn Mayhem",
desc: `Every move that targets a foe causes the user to switch out after use.`,
name: "[Gen 9] NatDex Camove Chaos",
desc: `National Dex-based format where Pokemon can use almost any move in the game, and their first two move types determine their type.`,
mod: 'gen9',
ruleset: ['Standard OMs', 'Sleep Moves Clause', 'VoltTurn Mayhem Mod'],
ruleset: ['Standard NatDex', 'Terastal Clause', '!Obtainable Moves', '!Team Preview', 'Camomons Mod', 'CFZ Clause', 'Sleep Moves Clause', 'Team Type Preview'],
banlist: [
'Arceus', 'Chi-Yu', 'Chien-Pao', 'Calyrex-Ice', 'Calyrex-Shadow', 'Darkrai', 'Deoxys-Attack', 'Deoxys-Normal', 'Deoxys-Speed', 'Dialga', 'Dialga-Origin', 'Dragonite',
'Eternatus', 'Flutter Mane', 'Giratina', 'Giratina-Origin', 'Gouging Fire', 'Groudon', 'Ho-Oh', 'Iron Bundle', 'Koraidon', 'Kyogre', 'Kyurem-Black', 'Kyurem-White',
'Lunala', 'Magearna', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Palafin', 'Palkia', 'Palkia-Origin', 'Raging Bolt', 'Rayquaza', 'Regieleki',
'Reshiram', 'Shaymin-Sky', 'Solgaleo', 'Terapagos', 'Urshifu-Single-Strike', 'Zacian', 'Zacian-Crowned', 'Zekrom', 'King\'s Rock', 'Razor Fang', 'Fake Out',
'Last Respects', 'Revival Blessing',
'Aerodactyl-Mega', 'Alakazam', 'Arceus', 'Beedrill-Mega', 'Blaziken-Mega', 'Calyrex-Ice', 'Calyrex-Shadow', 'Charizard-Mega-Y', 'Chi-Yu',
'Chien-Pao', 'Comfey', 'Darmanitan', 'Darmanitan-Galar', 'Darmanitan-Galar-Zen', 'Deoxys', 'Deoxys-Attack', 'Dialga', 'Dondozo', 'Enamorus',
'Eternatus', 'Flutter Mane', 'Genesect', 'Gengar-Mega', 'Giratina', 'Groudon', 'Ho-Oh', 'Hoopa-Unbound', 'Kangaskhan-Mega', 'Kartana', 'Komala',
'Koraidon', 'Kyogre', 'Kyurem', 'Kyurem-Black', 'Kyurem-White', 'Landorus', 'Lucario-Mega', 'Lugia', 'Lunala', 'Marshadow', 'Mawile-Mega',
'Medicham-Mega', 'Melmetal', 'Metagross-Mega', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Necrozma-Ultra', 'Palafin',
'Palkia', 'Pheromosa', 'Porygon-Z', 'Rayquaza', 'Regieleki', 'Reshiram', 'Salamence-Mega', 'Sceptile-Mega', 'Serperior', 'Shedinja', 'Sneasler',
'Solgaleo', 'Spectrier', 'Tapu Koko', 'Ting-Lu', 'Ursaluna', 'Ursaluna-Bloodmoon', 'Xerneas', 'Xurkitree', 'Yveltal', 'Zacian', 'Zacian-Crowned',
'Zamazenta', 'Zamazenta-Crowned', 'Zekrom', 'Zoroark', 'Arena Trap', 'Moody', 'Magnet Pull', 'Regenerator > 2', 'Power Construct', 'Shadow Tag',
'Damp Rock', 'Heat Rock', 'King\'s Rock', 'Quick Claw', 'Razor Fang', 'Smooth Rock', 'Assist', 'Astral Barrage', 'Baneful Bunker', 'Baton Pass',
'Belly Drum', 'Bolt Beak', 'Boomburst', 'Burning Bulwark', 'Ceaseless Edge', 'Chatter', 'Clangorous Soul', 'Dire Claw', 'Double Iron Bash',
'Dragon Energy', 'Eruption', 'Extreme Speed', 'Electrify', 'Electro Shot', 'Final Gambit', 'Fillet Away', 'Fishious Rend', 'Geomancy', 'Heal Order',
'Jet Punch', 'Last Respects', 'Lumina Crash', 'No Retreat', 'Octolock', 'Population Bomb', 'Power Trip', 'Quiver Dance', 'Rage Fist', 'Revival Blessing',
'Rising Voltage', 'Salt Cure', 'Shed Tail', 'Shell Smash', 'Shift Gear', 'Stored Power', 'Substitute', 'Surging Strikes', 'Tail Glow', 'Take Heart',
'Thousand Arrows', 'Transform', 'Triple Arrows', 'V-create', 'Victory Dance', 'Water Spout', 'Wicked Blow',
],
unbanlist: ['Blazing Torque', 'Combat Torque', 'Light of Ruin', 'Magical Torque', 'Noxious Torque', 'Wicked Torque'],
},
// Other Metagames
@ -912,6 +916,24 @@ export const Formats: import('../sim/dex-formats').FormatList = [
'Razor Fang', 'Baton Pass', 'Draco Meteor', 'Last Respects', 'Overheat', 'Shed Tail',
],
},
{
name: "[Gen 9] Convergence",
desc: `Allows all Pokémon that have identical types to share moves and abilities.`,
mod: 'gen9',
searchShow: false,
ruleset: ['Standard OMs', 'Ability Clause = 1', 'Sleep Moves Clause', 'Convergence Legality', 'Terastal Clause', '!Obtainable Abilities'],
banlist: [
'Arceus', 'Baxcalibur', 'Calyrex-Ice', 'Calyrex-Shadow', 'Chi-Yu', 'Chien-Pao', 'Comfey', 'Darkrai', 'Deoxys-Normal', 'Deoxys-Attack', 'Deoxys-Speed',
'Dialga', 'Dialga-Origin', 'Dondozo', 'Eternatus', 'Flutter Mane', 'Giratina', 'Giratina-Origin', 'Groudon', 'Haxorus', 'Ho-oh', 'Inteleon',
'Iron Bundle', 'Iron Hands', 'Koraidon', 'Kyogre', 'Kyurem-Black', 'Kyurem-White', 'Landorus-Incarnate', 'Lilligant-Hisui', 'Lugia', 'Lunala',
'Magearna', 'Manaphy', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Ogerpon-Hearthflame', 'Palafin', 'Palkia', 'Palkia-Origin',
'Porygon-Z', 'Primarina', 'Rayquaza', 'Regieleki', 'Regigigas', 'Reshiram', 'Roaring Moon', 'Shaymin-Sky', 'Solgaleo', 'Slaking', 'Smeargle', 'Sneasler',
'Spectrier', 'Umbreon', 'Urshifu-Single-Strike', 'Urshifu-Rapid-Strike', 'Walking Wake', 'Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned',
'Zekrom', 'Arena Trap', 'Comatose', 'Contrary', 'Drizzle', 'Drought', 'Imposter', 'Moody', 'Pure Power', 'Shadow Tag', 'Speed Boost', 'Unburden',
'King\'s Rock', 'Light Clay', 'Razor Fang', 'Baton Pass', 'Belly Drum', 'Boomburst', 'Extreme Speed', 'Final Gambit', 'Last Respects', 'Population Bomb',
'Quiver Dance', 'Rage Fist', 'Shed Tail', 'Shell Smash', 'Spore', 'Transform',
],
},
{
name: "[Gen 9] Cross Evolution",
desc: `Give a Pokémon a Pokémon name of the next evolution stage as a nickname to inherit stat changes, typing, abilities, and moves from the next stage Pokémon.`,
@ -2395,18 +2417,6 @@ export const Formats: import('../sim/dex-formats').FormatList = [
},
},
},
{
name: "[Gen 9] Tier Shift",
desc: `Pokémon below OU get their stats, excluding HP, boosted. UU/RUBL get +15, RU/NUBL get +20, NU/PUBL get +25, and PU or lower get +30.`,
mod: 'gen9',
searchShow: false,
ruleset: ['Standard OMs', 'Sleep Moves Clause', 'Terastal Clause', 'Evasion Clause', 'Tier Shift Mod'],
banlist: [
'Arceus', 'Calyrex-Shadow', 'Koraidon', 'Kyogre', 'Medicham', 'Miraidon', 'Necrozma-Dusk-Mane', 'Zacian-Crowned', 'Drizzle', 'Moody', 'Arena Trap', 'Shadow Tag',
'Baton Pass', 'Last Respects', 'Shed Tail', 'Heat Rock', 'King\'s Rock', 'Light Clay', 'Razor Fang',
],
unbanlist: ['Arceus-Bug', 'Arceus-Grass', 'Arceus-Ice'],
},
{
name: "[Gen 9] Trademarked",
desc: `Sacrifice your Pokémon's ability for a status move that activates on switch-in.`,
@ -2515,6 +2525,20 @@ export const Formats: import('../sim/dex-formats').FormatList = [
}
},
},
{
name: "[Gen 9] VoltTurn Mayhem",
desc: `Every move that targets a foe causes the user to switch out after use.`,
mod: 'gen9',
searchShow: false,
ruleset: ['Standard OMs', 'Sleep Moves Clause', 'VoltTurn Mayhem Mod'],
banlist: [
'Arceus', 'Chi-Yu', 'Chien-Pao', 'Calyrex-Ice', 'Calyrex-Shadow', 'Darkrai', 'Deoxys-Attack', 'Deoxys-Normal', 'Deoxys-Speed', 'Dialga', 'Dialga-Origin', 'Dragonite',
'Eternatus', 'Flutter Mane', 'Giratina', 'Giratina-Origin', 'Gouging Fire', 'Groudon', 'Ho-Oh', 'Iron Bundle', 'Koraidon', 'Kyogre', 'Kyurem-Black', 'Kyurem-White',
'Lunala', 'Magearna', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Palafin', 'Palkia', 'Palkia-Origin', 'Raging Bolt', 'Rayquaza', 'Regieleki',
'Reshiram', 'Shaymin-Sky', 'Solgaleo', 'Terapagos', 'Urshifu-Single-Strike', 'Zacian', 'Zacian-Crowned', 'Zekrom', 'King\'s Rock', 'Razor Fang', 'Fake Out',
'Last Respects', 'Revival Blessing',
],
},
{
name: "[Gen 6] Pure Hackmons",
desc: `Anything that can be hacked in-game and is usable in local battles is allowed.`,
@ -2747,7 +2771,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
name: "[Gen 9] National Dex Doubles Ubers",
mod: 'gen9',
gameType: 'doubles',
// searchShow: false,
searchShow: false,
ruleset: ['Standard Doubles', 'NatDex Mod', '!Gravity Sleep Clause'],
banlist: ['Shedinja', 'Assist'],
},
@ -2894,7 +2918,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
{
name: "[Gen 8] National Dex Doubles",
mod: 'gen8',
searchShow: false,
// searchShow: false,
gameType: 'doubles',
ruleset: ['Standard Doubles', 'NatDex Mod', 'Evasion Abilities Clause'],
banlist: [
@ -3755,34 +3779,32 @@ export const Formats: import('../sim/dex-formats').FormatList = [
column: 4,
},
{
name: "[Gen 1] Ubers",
mod: 'gen1',
name: "[Gen 2] Ubers",
mod: 'gen2',
// searchShow: false,
ruleset: ['Standard'],
},
{
name: "[Gen 3] Orre Colosseum",
mod: 'gen3colosseum',
name: "[Gen 5] LC",
mod: 'gen5',
// searchShow: false,
gameType: 'doubles',
ruleset: [
'Obtainable', 'Team Preview', 'Species Clause', 'Stadium Sleep Clause', 'Freeze Clause Mod', 'Max Team Size = 6', 'VGC Timer',
'Nickname Clause', 'Endless Battle Clause', 'Cancel Mod', 'Picked Team Size = 4', 'Exact HP Mod', 'Item Clause = 1', 'Open Team Sheets',
ruleset: ['Standard', 'Little Cup', 'Sleep Moves Clause'],
banlist: [
'Gligar', 'Meditite', 'Misdreavus', 'Murkrow', 'Scraggy', 'Scyther', 'Sneasel', 'Tangela', 'Vulpix', 'Yanma',
'Sand Rush', 'Sand Veil', 'Berry Juice', 'Soul Dew', 'Baton Pass', 'Dragon Rage', 'Sonic Boom', 'Swagger',
],
banlist: ['Soul Dew', 'Deoxys-Defense', 'Deoxys-Attack', 'Deoxys-Speed', 'Restricted Legendary', 'Mythical'],
unbanlist: ['Latios', 'Latias', 'Wobbuffet', 'Wynaut'],
bestOfDefault: true,
onBegin() {
this.add('rule', 'Self-KO Clause: If your last Pok\u00e9mon faints to a self-KO move or effect, you will lose the battle');
},
},
{
name: "[Gen 6] VGC 2014",
mod: 'gen6xy',
name: "[Gen 7] VGC 2017",
mod: 'gen7sm',
gameType: 'doubles',
// searchShow: false,
bestOfDefault: true,
ruleset: ['Flat Rules', 'Kalos Pokedex', 'Min Source Gen = 6'],
ruleset: [
'Flat Rules', 'Old Alola Pokedex', '!! Adjust Level = 50', 'Min Source Gen = 7',
'VGC Timer', '!! Timer Starting = 900',
],
banlist: ['Mega', 'Custap Berry', 'Enigma Berry', 'Jaboca Berry', 'Micle Berry', 'Rowap Berry'],
},
// Past Gens OU
@ -4271,18 +4293,6 @@ export const Formats: import('../sim/dex-formats').FormatList = [
ruleset: ['Flat Rules', '!! Adjust Level = 50', 'Min Source Gen = 7', 'VGC Timer', '!! Timer Starting = 300'],
banlist: ['Oranguru + Symbiosis', 'Passimian + Defiant', 'Unown', 'Custap Berry', 'Enigma Berry', 'Jaboca Berry', 'Micle Berry', 'Rowap Berry', 'Battle Bond'],
},
{
name: "[Gen 7] VGC 2017",
mod: 'gen7sm',
gameType: 'doubles',
searchShow: false,
bestOfDefault: true,
ruleset: [
'Flat Rules', 'Old Alola Pokedex', '!! Adjust Level = 50', 'Min Source Gen = 7',
'VGC Timer', '!! Timer Starting = 900',
],
banlist: ['Mega', 'Custap Berry', 'Enigma Berry', 'Jaboca Berry', 'Micle Berry', 'Rowap Berry'],
},
{
name: "[Gen 7] Battle Spot Doubles",
mod: 'gen7',
@ -4457,6 +4467,14 @@ export const Formats: import('../sim/dex-formats').FormatList = [
ruleset: ['Flat Rules', 'Min Source Gen = 6'],
banlist: ['Soul Dew', 'Articuno + Snow Cloak', 'Zapdos + Static', 'Moltres + Flame Body', 'Dragonite + Barrier'],
},
{
name: "[Gen 6] VGC 2014",
mod: 'gen6xy',
gameType: 'doubles',
searchShow: false,
bestOfDefault: true,
ruleset: ['Flat Rules', 'Kalos Pokedex', 'Min Source Gen = 6'],
},
{
name: "[Gen 6] Battle Spot Doubles",
mod: 'gen6',
@ -4536,16 +4554,6 @@ export const Formats: import('../sim/dex-formats').FormatList = [
ruleset: ['[Gen 5] NU', 'Sleep Moves Clause'],
banlist: ['NU', 'PUBL', 'Damp Rock'],
},
{
name: "[Gen 5] LC",
mod: 'gen5',
searchShow: false,
ruleset: ['Standard', 'Little Cup', 'Sleep Moves Clause'],
banlist: [
'Gligar', 'Meditite', 'Misdreavus', 'Murkrow', 'Scraggy', 'Scyther', 'Sneasel', 'Tangela', 'Vulpix', 'Yanma',
'Sand Rush', 'Sand Veil', 'Berry Juice', 'Soul Dew', 'Baton Pass', 'Dragon Rage', 'Sonic Boom', 'Swagger',
],
},
{
name: "[Gen 5] Monotype",
desc: `All the Pokémon on a team must share a type.`,
@ -4954,6 +4962,22 @@ export const Formats: import('../sim/dex-formats').FormatList = [
banlist: ['Uber', 'Quick Claw', 'Soul Dew', 'Swagger'],
unbanlist: ['Wobbuffet', 'Wynaut'],
},
{
name: "[Gen 3] Orre Colosseum",
mod: 'gen3colosseum',
searchShow: false,
gameType: 'doubles',
ruleset: [
'Obtainable', 'Team Preview', 'Species Clause', 'Stadium Sleep Clause', 'Freeze Clause Mod', 'Max Team Size = 6', 'VGC Timer',
'Nickname Clause', 'Endless Battle Clause', 'Cancel Mod', 'Picked Team Size = 4', 'Exact HP Mod', 'Item Clause = 1', 'Open Team Sheets',
],
banlist: ['Soul Dew', 'Deoxys-Defense', 'Deoxys-Attack', 'Deoxys-Speed', 'Restricted Legendary', 'Mythical'],
unbanlist: ['Latios', 'Latias', 'Wobbuffet', 'Wynaut'],
bestOfDefault: true,
onBegin() {
this.add('rule', 'Self-KO Clause: If your last Pok\u00e9mon faints to a self-KO move or effect, you will lose the battle');
},
},
{
name: "[Gen 3] Custom Game",
mod: 'gen3',
@ -4970,12 +4994,6 @@ export const Formats: import('../sim/dex-formats').FormatList = [
debug: true,
ruleset: ['HP Percentage Mod', 'Cancel Mod', 'Max Team Size = 24', 'Max Move Count = 24', 'Max Level = 9999', 'Default Level = 100'],
},
{
name: "[Gen 2] Ubers",
mod: 'gen2',
searchShow: false,
ruleset: ['Standard'],
},
{
name: "[Gen 2] UU",
mod: 'gen2',
@ -5048,6 +5066,12 @@ export const Formats: import('../sim/dex-formats').FormatList = [
battle: { trunc: Math.trunc },
ruleset: ['HP Percentage Mod', 'Cancel Mod', 'Max Team Size = 24', 'Max Move Count = 24', 'Max Level = 9999', 'Default Level = 100'],
},
{
name: "[Gen 1] Ubers",
mod: 'gen1',
searchShow: false,
ruleset: ['Standard'],
},
{
name: "[Gen 1] UU",
mod: 'gen1',

View File

@ -1999,7 +1999,7 @@
"ability": "Unseen Fist",
"nature": "Adamant",
"evs": {"hp": 160, "atk": 92, "def": 16, "spd": 160, "spe": 80},
"moves": [["Wicked Blow"], ["Low Kick"], ["Rock Tomb"], ["Iron Head", "Sucker Punch", "U-Turn"]],
"moves": [["Wicked Blow"], ["Low Kick"], ["Rock Tomb"], ["Iron Head", "Sucker Punch", "U-turn"]],
"weight": 20
}
]

View File

@ -465,7 +465,7 @@ describe('[Gen 9] BSS Factory data should be valid (slow)', () => {
describe('[Gen 9] 1v1 Factory data should be valid (slow)', () => {
it(`gen9/1v1-factory-sets.json should contain valid sets`, function () {
this.timeout(0);
// this.timeout(0);
const setsJSON = require(`../../dist/data/random-battles/gen9/1v1-factory-sets.json`);
const mod = 'gen9';
@ -473,57 +473,57 @@ describe('[Gen 9] 1v1 Factory data should be valid (slow)', () => {
const speciesData = setsJSON[speciesName];
for (const set of speciesData.sets) {
const species = Dex.species.get(set.species);
assert(species.exists, `invalid species "${set.species}" of ${species}`);
assert.equal(species.name, set.species, `miscapitalized species "${set.species}" of ${species}`);
assert(species.exists, `invalid species "${set.species}" of ${speciesName}`);
assert.equal(species.name, set.species, `miscapitalized species "${set.species}" of ${speciesName}`);
assert(species.id.startsWith(toID(species.baseSpecies)), `non-matching species "${set.species}" of ${species}`);
assert(species.id.startsWith(toID(species.baseSpecies)), `non-matching species "${set.species}" of ${speciesName}`);
for (const itemName of [].concat(set.item)) {
if (!itemName) continue;
const item = Dex.items.get(itemName);
assert(item.exists, `invalid item "${itemName}" of ${species}`);
assert.equal(item.name, itemName, `miscapitalized item "${itemName}" of ${species}`);
assert(item.exists, `invalid item "${itemName}" of ${speciesName}`);
assert.equal(item.name, itemName, `miscapitalized item "${itemName}" of ${speciesName}`);
}
for (const abilityName of [].concat(set.ability)) {
const ability = Dex.abilities.get(abilityName);
assert(ability.exists, `invalid ability "${abilityName}" of ${species}`);
assert.equal(ability.name, abilityName, `miscapitalized ability "${abilityName}" of ${species}`);
assert(ability.exists, `invalid ability "${abilityName}" of ${speciesName}`);
assert.equal(ability.name, abilityName, `miscapitalized ability "${abilityName}" of ${speciesName}`);
const allowedAbilities = new Set(Object.values((species.battleOnly && !species.requiredAbility) ? Dex.species.get(species.battleOnly).abilities : species.abilities));
if (species.unreleasedHidden) allowedAbilities.delete(species.abilities.H);
assert(allowedAbilities.has(abilityName), `${species.name} can't have ${abilityName}`);
assert(allowedAbilities.has(abilityName), `${speciesName} can't have ${abilityName}`);
}
for (const natureName of [].concat(set.nature)) {
const nature = Dex.natures.get(natureName);
assert(nature.exists, `invalid nature "${natureName}" of ${species}`);
assert.equal(nature.name, natureName, `miscapitalized nature "${natureName}" of ${species}`);
assert(nature.exists, `invalid nature "${natureName}" of ${speciesName}`);
assert.equal(nature.name, natureName, `miscapitalized nature "${natureName}" of ${speciesName}`);
}
for (const moveSpec of set.moves) {
for (const moveName of [].concat(moveSpec)) {
const move = Dex.moves.get(moveName);
assert(move.exists, `invalid move "${moveName}" of ${species}`);
assert.equal(move.name, moveName, `miscapitalized move "${moveName}" ≠ "${move.name}" of ${species}`);
assert(validateLearnset(move, set, '1v1', mod), `illegal move "${moveName}" of ${species}`);
assert(move.exists, `invalid move "${moveName}" of ${speciesName}`);
assert.equal(move.name, moveName, `miscapitalized move "${moveName}" ≠ "${move.name}" of ${speciesName}`);
assert(validateLearnset(move, set, '1v1', mod), `illegal move "${moveName}" of ${speciesName}`);
}
}
// Check that no moves appear more than once in a set
assert.equal(set.moves.flat(1).length, new Set(set.moves.flat(1)).size, `${species} has repeat moves`);
assert.equal(set.moves.flat(1).length, new Set(set.moves.flat(1)).size, `${speciesName} has repeat moves`);
if (species.name === 'Carbink') continue;
assert(!!set.evs, `Set of ${species} has no EVs specified`);
if (speciesName === 'Carbink') continue;
assert(!!set.evs, `Set of ${speciesName} has no EVs specified`);
const keys = Object.keys(set.evs);
let totalEVs = 0;
for (const ev of keys) {
assert(Dex.stats.ids().includes(ev), `Invalid EV key (${ev}) on set of ${species}`);
assert(Dex.stats.ids().includes(ev), `Invalid EV key (${ev}) on set of ${speciesName}`);
totalEVs += set.evs[ev];
assert.equal(set.evs[ev] % 4, 0, `EVs of ${ev} not divisible by 4 on ${species}`);
assert.equal(set.evs[ev] % 4, 0, `EVs of ${ev} not divisible by 4 on ${speciesName}`);
}
const sortedKeys = Utils.sortBy([...keys], ev => Dex.stats.ids().indexOf(ev));
assert.deepEqual(keys, sortedKeys, `EVs out of order on set of ${species}, possibly because one of them is for the wrong stat`);
assert(totalEVs <= 510, `more than 510 EVs on set of ${species}`);
assert.deepEqual(keys, sortedKeys, `EVs out of order on set of ${speciesName}, possibly because one of them is for the wrong stat`);
assert(totalEVs <= 510, `more than 510 EVs on set of ${speciesName}`);
}
let totalWeight = 0;
for (const set of speciesData.sets) {