mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
ESLint has a whole new config format, so I figure it's a good time to make the config system saner. - First, we no longer have separate eslint-no-types configs. Lint performance shouldn't be enough of a problem to justify the relevant maintenance complexity. - Second, our base config should work out-of-the-box now. `npx eslint` will work as expected, without any CLI flags. You should still use `npm run lint` which adds the `--cached` flag for performance. - Third, whatever updates I did fixed style linting, which apparently has been bugged for quite some time, considering all the obvious mixed-tabs-and-spaces issues I found in the upgrade. Also here are some changes to our style rules. In particular: - Curly brackets (for objects etc) now have spaces inside them. Sorry for the huge change. ESLint doesn't support our old style, and most projects use Prettier style, so we might as well match them in this way. See https://github.com/eslint-stylistic/eslint-stylistic/issues/415 - String + number concatenation is no longer allowed. We now consistently use template strings for this.
984 lines
37 KiB
TypeScript
984 lines
37 KiB
TypeScript
import RandomGen6Teams from '../gen6/teams';
|
|
import type { PRNG } from '../../../sim';
|
|
import type { MoveCounter } from '../gen8/teams';
|
|
import { toID } from '../../../sim/dex';
|
|
|
|
// Moves that restore HP:
|
|
const RECOVERY_MOVES = [
|
|
'healorder', 'milkdrink', 'moonlight', 'morningsun', 'recover', 'roost', 'slackoff', 'softboiled', 'synthesis',
|
|
];
|
|
// Moves that boost Attack:
|
|
const PHYSICAL_SETUP = [
|
|
'bellydrum', 'bulkup', 'coil', 'curse', 'dragondance', 'honeclaws', 'howl', 'meditate', 'screech', 'swordsdance',
|
|
];
|
|
// Some moves that only boost Speed:
|
|
const SPEED_SETUP = [
|
|
'agility', 'autotomize', 'flamecharge', 'rockpolish',
|
|
];
|
|
// Conglomerate for ease of access
|
|
const SETUP = [
|
|
'acidarmor', 'agility', 'autotomize', 'bellydrum', 'bulkup', 'calmmind', 'coil', 'curse', 'dragondance', 'flamecharge',
|
|
'growth', 'honeclaws', 'howl', 'irondefense', 'meditate', 'nastyplot', 'quiverdance', 'raindance', 'rockpolish',
|
|
'shellsmash', 'shiftgear', 'sunnyday', 'swordsdance', 'tailglow', 'workup',
|
|
];
|
|
// Moves that shouldn't be the only STAB moves:
|
|
const NO_STAB = [
|
|
'aquajet', 'bulletpunch', 'chatter', 'clearsmog', 'dragontail', 'eruption', 'explosion', 'fakeout', 'flamecharge',
|
|
'futuresight', 'iceshard', 'icywind', 'incinerate', 'knockoff', 'machpunch', 'pluck', 'pursuit', 'quickattack',
|
|
'rapidspin', 'reversal', 'selfdestruct', 'shadowsneak', 'skyattack', 'skydrop', 'snarl', 'suckerpunch',
|
|
'uturn', 'vacuumwave', 'voltswitch', 'waterspout',
|
|
];
|
|
// Hazard-setting moves
|
|
const HAZARDS = [
|
|
'spikes', 'stealthrock', 'toxicspikes',
|
|
];
|
|
// Moves that switch the user out
|
|
const PIVOT_MOVES = [
|
|
'uturn', 'voltswitch',
|
|
];
|
|
|
|
// Moves that should be paired together when possible
|
|
const MOVE_PAIRS = [
|
|
['lightscreen', 'reflect'],
|
|
['sleeptalk', 'rest'],
|
|
['protect', 'wish'],
|
|
['leechseed', 'substitute'],
|
|
];
|
|
|
|
/** Pokemon who always want priority STAB, and are fine with it as its only STAB move of that type */
|
|
const PRIORITY_POKEMON = [
|
|
'bisharp', 'breloom', 'cacturne', 'dusknoir', 'honchkrow', 'scizor', 'shedinja', 'shiftry',
|
|
];
|
|
|
|
export class RandomGen5Teams extends RandomGen6Teams {
|
|
randomSets: { [species: string]: RandomTeamsTypes.RandomSpeciesData } = require('./sets.json');
|
|
|
|
constructor(format: string | Format, prng: PRNG | PRNGSeed | null) {
|
|
super(format, prng);
|
|
this.noStab = NO_STAB;
|
|
this.priorityPokemon = PRIORITY_POKEMON;
|
|
|
|
this.moveEnforcementCheckers = {
|
|
Bug: (movePool, moves, abilities, types, counter) => (
|
|
!counter.get('Bug') && (movePool.includes('megahorn') || abilities.includes('Tinted Lens'))
|
|
),
|
|
Dark: (movePool, moves, abilities, types, counter) => !counter.get('Dark'),
|
|
Dragon: (movePool, moves, abilities, types, counter) => !counter.get('Dragon'),
|
|
Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric'),
|
|
Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting'),
|
|
Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'),
|
|
Flying: (movePool, moves, abilities, types, counter, species) => (
|
|
!counter.get('Flying') && !['aerodactyl', 'mantine', 'murkrow'].includes(species.id) &&
|
|
!movePool.includes('hiddenpowerflying')
|
|
),
|
|
Ghost: (movePool, moves, abilities, types, counter) => !counter.get('Ghost'),
|
|
Grass: (movePool, moves, abilities, types, counter, species) => (
|
|
!counter.get('Grass') && (species.baseStats.atk >= 100 || movePool.includes('leafstorm'))
|
|
),
|
|
Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'),
|
|
Ice: (movePool, moves, abilities, types, counter) => !counter.get('Ice'),
|
|
Poison: (movePool, moves, abilities, types, counter) => (
|
|
!counter.get('Poison') && (types.has('Grass') || types.has('Ground'))
|
|
),
|
|
Psychic: (movePool, moves, abilities, types, counter) => (
|
|
!counter.get('Psychic') && (types.has('Fighting') || movePool.includes('calmmind'))
|
|
),
|
|
Rock: (movePool, moves, abilities, types, counter, species) => (!counter.get('Rock') && species.baseStats.atk >= 80),
|
|
Steel: (movePool, moves, abilities, types, counter, species) => (
|
|
!counter.get('Steel') && ['aggron', 'metagross'].includes(species.id)
|
|
),
|
|
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
|
|
};
|
|
// Nature Power is Earthquake this gen
|
|
this.cachedStatusMoves = this.dex.moves.all()
|
|
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
|
|
.map(move => move.id);
|
|
}
|
|
|
|
cullMovePool(
|
|
types: string[],
|
|
moves: Set<string>,
|
|
abilities: string[],
|
|
counter: MoveCounter,
|
|
movePool: string[],
|
|
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
species: Species,
|
|
isLead: boolean,
|
|
preferredType: string,
|
|
role: RandomTeamsTypes.Role,
|
|
): void {
|
|
// Pokemon cannot have multiple Hidden Powers in any circumstance
|
|
let hasHiddenPower = false;
|
|
for (const move of moves) {
|
|
if (move.startsWith('hiddenpower')) hasHiddenPower = true;
|
|
}
|
|
if (hasHiddenPower) {
|
|
let movePoolHasHiddenPower = true;
|
|
while (movePoolHasHiddenPower) {
|
|
movePoolHasHiddenPower = false;
|
|
for (const moveid of movePool) {
|
|
if (moveid.startsWith('hiddenpower')) {
|
|
this.fastPop(movePool, movePool.indexOf(moveid));
|
|
movePoolHasHiddenPower = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
// If we have two unfilled moves and only one unpaired move, cull the unpaired move.
|
|
if (moves.size === this.maxMoveCount - 2) {
|
|
const unpairedMoves = [...movePool];
|
|
for (const pair of MOVE_PAIRS) {
|
|
if (movePool.includes(pair[0]) && movePool.includes(pair[1])) {
|
|
this.fastPop(unpairedMoves, unpairedMoves.indexOf(pair[0]));
|
|
this.fastPop(unpairedMoves, unpairedMoves.indexOf(pair[1]));
|
|
}
|
|
}
|
|
if (unpairedMoves.length === 1) {
|
|
this.fastPop(movePool, movePool.indexOf(unpairedMoves[0]));
|
|
}
|
|
}
|
|
|
|
// These moves are paired, and shouldn't appear if there is not room for them both.
|
|
if (moves.size === this.maxMoveCount - 1) {
|
|
for (const pair of MOVE_PAIRS) {
|
|
if (movePool.includes(pair[0]) && movePool.includes(pair[1])) {
|
|
this.fastPop(movePool, movePool.indexOf(pair[0]));
|
|
this.fastPop(movePool, movePool.indexOf(pair[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Team-based move culls
|
|
if (teamDetails.screens && movePool.length >= this.maxMoveCount + 2) {
|
|
if (movePool.includes('reflect')) this.fastPop(movePool, movePool.indexOf('reflect'));
|
|
if (movePool.includes('lightscreen')) this.fastPop(movePool, movePool.indexOf('lightscreen'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
if (teamDetails.stealthRock) {
|
|
if (movePool.includes('stealthrock')) this.fastPop(movePool, movePool.indexOf('stealthrock'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
if (teamDetails.rapidSpin) {
|
|
if (movePool.includes('rapidspin')) this.fastPop(movePool, movePool.indexOf('rapidspin'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
if (teamDetails.toxicSpikes) {
|
|
if (movePool.includes('toxicspikes')) this.fastPop(movePool, movePool.indexOf('toxicspikes'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
if (teamDetails.spikes && teamDetails.spikes >= 2) {
|
|
if (movePool.includes('spikes')) this.fastPop(movePool, movePool.indexOf('spikes'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
if (teamDetails.statusCure) {
|
|
if (movePool.includes('aromatherapy')) this.fastPop(movePool, movePool.indexOf('aromatherapy'));
|
|
if (movePool.includes('healbell')) this.fastPop(movePool, movePool.indexOf('healbell'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
|
|
// Develop additional move lists
|
|
const badWithSetup = ['healbell', 'pursuit', 'toxic'];
|
|
const statusMoves = this.cachedStatusMoves;
|
|
|
|
// General incompatibilities
|
|
const incompatiblePairs = [
|
|
// These moves don't mesh well with other aspects of the set
|
|
[statusMoves, ['healingwish', 'switcheroo', 'trick']],
|
|
[SETUP, PIVOT_MOVES],
|
|
[SETUP, HAZARDS],
|
|
[SETUP, badWithSetup],
|
|
[PHYSICAL_SETUP, PHYSICAL_SETUP],
|
|
[['fakeout', 'uturn'], ['switcheroo', 'trick']],
|
|
['substitute', PIVOT_MOVES],
|
|
['rest', 'substitute'],
|
|
|
|
// These attacks are redundant with each other
|
|
['psychic', 'psyshock'],
|
|
[['scald', 'surf'], 'hydropump'],
|
|
[['bodyslam', 'return'], ['bodyslam', 'doubleedge']],
|
|
[['gigadrain', 'leafstorm'], ['leafstorm', 'petaldance', 'powerwhip']],
|
|
[['drainpunch', 'focusblast'], ['closecombat', 'highjumpkick', 'superpower']],
|
|
['payback', 'pursuit'],
|
|
|
|
// Assorted hardcodes go here:
|
|
// Zebstrika
|
|
['wildcharge', 'thunderbolt'],
|
|
// Manectric
|
|
['flamethrower', 'overheat'],
|
|
// Meganium
|
|
['leechseed', 'dragontail'],
|
|
// Volcarona and Heatran
|
|
[['fierydance', 'lavaplume'], 'fireblast'],
|
|
// Walrein
|
|
['encore', 'roar'],
|
|
// Lunatone
|
|
['moonlight', 'rockpolish'],
|
|
// Smeargle
|
|
['memento', 'whirlwind'],
|
|
// Seviper
|
|
['switcheroo', 'suckerpunch'],
|
|
// Jirachi
|
|
['bodyslam', 'healingwish'],
|
|
];
|
|
|
|
for (const pair of incompatiblePairs) this.incompatibleMoves(moves, movePool, pair[0], pair[1]);
|
|
|
|
if (species.id === 'dugtrio') this.incompatibleMoves(moves, movePool, statusMoves, 'memento');
|
|
|
|
const statusInflictingMoves = ['stunspore', 'thunderwave', 'toxic', 'willowisp', 'yawn'];
|
|
if (!abilities.includes('Prankster') && role !== 'Staller') {
|
|
this.incompatibleMoves(moves, movePool, statusInflictingMoves, statusInflictingMoves);
|
|
}
|
|
|
|
if (abilities.includes('Guts')) this.incompatibleMoves(moves, movePool, 'protect', 'swordsdance');
|
|
|
|
// Cull filler moves for otherwise fixed set Stealth Rock users
|
|
if (!teamDetails.stealthRock) {
|
|
if (species.id === 'registeel' && role === 'Staller') {
|
|
if (movePool.includes('thunderwave')) this.fastPop(movePool, movePool.indexOf('thunderwave'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
if (species.baseSpecies === 'Wormadam' && role === 'Staller') {
|
|
if (movePool.includes('suckerpunch')) this.fastPop(movePool, movePool.indexOf('suckerpunch'));
|
|
if (moves.size + movePool.length <= this.maxMoveCount) return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate random moveset for a given species, role, preferred type.
|
|
randomMoveset(
|
|
types: string[],
|
|
abilities: string[],
|
|
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
species: Species,
|
|
isLead: boolean,
|
|
movePool: string[],
|
|
preferredType: string,
|
|
role: RandomTeamsTypes.Role,
|
|
): Set<string> {
|
|
const moves = new Set<string>();
|
|
let counter = this.newQueryMoves(moves, species, preferredType, abilities);
|
|
this.cullMovePool(types, moves, abilities, counter, movePool, teamDetails, species, isLead,
|
|
preferredType, role);
|
|
|
|
// If there are only four moves, add all moves and return early
|
|
if (movePool.length <= this.maxMoveCount) {
|
|
// Still need to ensure that multiple Hidden Powers are not added (if maxMoveCount is increased)
|
|
while (movePool.length) {
|
|
const moveid = this.sample(movePool);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
return moves;
|
|
}
|
|
|
|
const runEnforcementChecker = (checkerName: string) => {
|
|
if (!this.moveEnforcementCheckers[checkerName]) return false;
|
|
return this.moveEnforcementCheckers[checkerName](
|
|
movePool, moves, abilities, new Set(types), counter, species, teamDetails
|
|
);
|
|
};
|
|
|
|
// Add required move (e.g. Relic Song for Meloetta-P)
|
|
if (species.requiredMove) {
|
|
const move = this.dex.moves.get(species.requiredMove).id;
|
|
counter = this.addMove(move, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
|
|
// Add other moves you really want to have, e.g. STAB, recovery, setup.
|
|
|
|
// Enforce Facade if Guts is a possible ability
|
|
if (movePool.includes('facade') && abilities.includes('Guts')) {
|
|
counter = this.addMove('facade', moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
|
|
// Enforce Seismic Toss and Spore
|
|
for (const moveid of ['seismictoss', 'spore']) {
|
|
if (movePool.includes(moveid)) {
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// Enforce Thunder Wave on Prankster users
|
|
if (movePool.includes('thunderwave') && abilities.includes('Prankster')) {
|
|
counter = this.addMove('thunderwave', moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
|
|
// Enforce hazard removal on Bulky Support and Spinner if the team doesn't already have it
|
|
if (['Bulky Support', 'Spinner'].includes(role) && !teamDetails.rapidSpin) {
|
|
if (movePool.includes('rapidspin')) {
|
|
counter = this.addMove('rapidspin', moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// Enforce STAB priority
|
|
if (['Bulky Attacker', 'Bulky Setup'].includes(role) || this.priorityPokemon.includes(species.id)) {
|
|
const priorityMoves = [];
|
|
for (const moveid of movePool) {
|
|
const move = this.dex.moves.get(moveid);
|
|
const moveType = this.getMoveType(move, species, abilities, preferredType);
|
|
if (types.includes(moveType) && move.priority > 0 && (move.basePower || move.basePowerCallback)) {
|
|
priorityMoves.push(moveid);
|
|
}
|
|
}
|
|
if (priorityMoves.length) {
|
|
const moveid = this.sample(priorityMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// Enforce STAB
|
|
for (const type of types) {
|
|
// Check if a STAB move of that type should be required
|
|
const stabMoves = [];
|
|
for (const moveid of movePool) {
|
|
const move = this.dex.moves.get(moveid);
|
|
const moveType = this.getMoveType(move, species, abilities, preferredType);
|
|
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && type === moveType) {
|
|
stabMoves.push(moveid);
|
|
}
|
|
}
|
|
while (runEnforcementChecker(type)) {
|
|
if (!stabMoves.length) break;
|
|
const moveid = this.sampleNoReplace(stabMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// Enforce Preferred Type
|
|
if (!counter.get('preferred')) {
|
|
const stabMoves = [];
|
|
for (const moveid of movePool) {
|
|
const move = this.dex.moves.get(moveid);
|
|
const moveType = this.getMoveType(move, species, abilities, preferredType);
|
|
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && preferredType === moveType) {
|
|
stabMoves.push(moveid);
|
|
}
|
|
}
|
|
if (stabMoves.length) {
|
|
const moveid = this.sample(stabMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// If no STAB move was added, add a STAB move
|
|
if (!counter.get('stab')) {
|
|
const stabMoves = [];
|
|
for (const moveid of movePool) {
|
|
const move = this.dex.moves.get(moveid);
|
|
const moveType = this.getMoveType(move, species, abilities, preferredType);
|
|
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && types.includes(moveType)) {
|
|
stabMoves.push(moveid);
|
|
}
|
|
}
|
|
if (stabMoves.length) {
|
|
const moveid = this.sample(stabMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
} else {
|
|
// If they have no regular STAB move, enforce U-turn on Bug types.
|
|
if (movePool.includes('uturn') && types.includes('Bug')) {
|
|
counter = this.addMove('uturn', moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enforce recovery
|
|
if (['Bulky Support', 'Bulky Attacker', 'Bulky Setup', 'Spinner', 'Staller'].includes(role)) {
|
|
const recoveryMoves = movePool.filter(moveid => RECOVERY_MOVES.includes(moveid));
|
|
if (recoveryMoves.length) {
|
|
const moveid = this.sample(recoveryMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// Enforce Staller moves
|
|
if (role === 'Staller') {
|
|
const enforcedMoves = ['protect', 'toxic'];
|
|
for (const move of enforcedMoves) {
|
|
if (movePool.includes(move)) {
|
|
counter = this.addMove(move, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enforce setup
|
|
if (role.includes('Setup')) {
|
|
// First, try to add a non-Speed setup move
|
|
const nonSpeedSetupMoves = movePool.filter(moveid => SETUP.includes(moveid) && !SPEED_SETUP.includes(moveid));
|
|
if (nonSpeedSetupMoves.length) {
|
|
const moveid = this.sample(nonSpeedSetupMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
} else {
|
|
// No non-Speed setup moves, so add any (Speed) setup move
|
|
const setupMoves = movePool.filter(moveid => SETUP.includes(moveid));
|
|
if (setupMoves.length) {
|
|
const moveid = this.sample(setupMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enforce a move not on the noSTAB list
|
|
if (!counter.damagingMoves.size && !(moves.has('uturn') && types.includes('Bug'))) {
|
|
// Choose an attacking move
|
|
const attackingMoves = [];
|
|
for (const moveid of movePool) {
|
|
const move = this.dex.moves.get(moveid);
|
|
if (!this.noStab.includes(moveid) && (move.category !== 'Status')) attackingMoves.push(moveid);
|
|
}
|
|
if (attackingMoves.length) {
|
|
const moveid = this.sample(attackingMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
|
|
// Enforce coverage move
|
|
if (['Fast Attacker', 'Setup Sweeper', 'Bulky Attacker', 'Wallbreaker'].includes(role)) {
|
|
if (counter.damagingMoves.size === 1) {
|
|
// Find the type of the current attacking move
|
|
const currentAttackType = counter.damagingMoves.values().next().value!.type;
|
|
// Choose an attacking move that is of different type to the current single attack
|
|
const coverageMoves = [];
|
|
for (const moveid of movePool) {
|
|
const move = this.dex.moves.get(moveid);
|
|
const moveType = this.getMoveType(move, species, abilities, preferredType);
|
|
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback)) {
|
|
if (currentAttackType !== moveType) coverageMoves.push(moveid);
|
|
}
|
|
}
|
|
if (coverageMoves.length) {
|
|
const moveid = this.sample(coverageMoves);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Choose remaining moves randomly from movepool and add them to moves list:
|
|
while (moves.size < this.maxMoveCount && movePool.length) {
|
|
const moveid = this.sample(movePool);
|
|
counter = this.addMove(moveid, moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
for (const pair of MOVE_PAIRS) {
|
|
if (moveid === pair[0] && movePool.includes(pair[1])) {
|
|
counter = this.addMove(pair[1], moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
if (moveid === pair[1] && movePool.includes(pair[0])) {
|
|
counter = this.addMove(pair[0], moves, types, abilities, teamDetails, species, isLead,
|
|
movePool, preferredType, role);
|
|
}
|
|
}
|
|
}
|
|
return moves;
|
|
}
|
|
|
|
shouldCullAbility(
|
|
ability: string,
|
|
types: Set<string>,
|
|
moves: Set<string>,
|
|
abilities: string[],
|
|
counter: MoveCounter,
|
|
movePool: string[],
|
|
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
species: Species,
|
|
preferredType: string,
|
|
role: RandomTeamsTypes.Role
|
|
): boolean {
|
|
switch (ability) {
|
|
case 'Chlorophyll': case 'Solar Power':
|
|
return !teamDetails.sun;
|
|
case 'Hydration': case 'Swift Swim':
|
|
return !teamDetails.rain;
|
|
case 'Iron Fist': case 'Sheer Force':
|
|
return !counter.get(toID(ability));
|
|
case 'Overgrow':
|
|
return !counter.get('Grass');
|
|
case 'Rock Head':
|
|
return !counter.get('recoil');
|
|
case 'Sand Force': case 'Sand Rush':
|
|
return !teamDetails.sand;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getAbility(
|
|
types: Set<string>,
|
|
moves: Set<string>,
|
|
abilities: string[],
|
|
counter: MoveCounter,
|
|
movePool: string[],
|
|
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
species: Species,
|
|
preferredType: string,
|
|
role: RandomTeamsTypes.Role,
|
|
): string {
|
|
if (abilities.length <= 1) return abilities[0];
|
|
|
|
// Hard-code abilities here
|
|
if (species.id === 'marowak' && counter.get('recoil')) return 'Rock Head';
|
|
if (species.id === 'kingler' && counter.get('sheerforce')) return 'Sheer Force';
|
|
if (species.id === 'golduck' && teamDetails.rain) return 'Swift Swim';
|
|
|
|
const abilityAllowed: string[] = [];
|
|
// Obtain a list of abilities that are allowed (not culled)
|
|
for (const ability of abilities) {
|
|
if (!this.shouldCullAbility(
|
|
ability, types, moves, abilities, counter, movePool, teamDetails, species, preferredType, role
|
|
)) {
|
|
abilityAllowed.push(ability);
|
|
}
|
|
}
|
|
|
|
// Pick a random allowed ability
|
|
if (abilityAllowed.length >= 1) return this.sample(abilityAllowed);
|
|
|
|
// If all abilities are rejected, prioritize weather abilities over non-weather abilities
|
|
if (!abilityAllowed.length) {
|
|
const weatherAbilities = abilities.filter(
|
|
a => ['Chlorophyll', 'Hydration', 'Sand Force', 'Sand Rush', 'Solar Power', 'Swift Swim'].includes(a)
|
|
);
|
|
if (weatherAbilities.length) return this.sample(weatherAbilities);
|
|
}
|
|
|
|
// Pick a random ability
|
|
return this.sample(abilities);
|
|
}
|
|
|
|
getPriorityItem(
|
|
ability: string,
|
|
types: string[],
|
|
moves: Set<string>,
|
|
counter: MoveCounter,
|
|
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
species: Species,
|
|
isLead: boolean,
|
|
preferredType: string,
|
|
role: RandomTeamsTypes.Role,
|
|
): string | undefined {
|
|
if (species.requiredItems) return this.sample(species.requiredItems);
|
|
if (species.id === 'farfetchd') return 'Stick';
|
|
if (species.id === 'latias' || species.id === 'latios') return 'Soul Dew';
|
|
if (species.id === 'marowak') return 'Thick Club';
|
|
if (species.id === 'pikachu') return 'Light Ball';
|
|
if (species.id === 'shedinja' || species.id === 'smeargle') return 'Focus Sash';
|
|
if (species.id === 'unown') return 'Choice Specs';
|
|
if (species.id === 'wobbuffet') return 'Custap Berry';
|
|
if (ability === 'Harvest') return 'Sitrus Berry';
|
|
if (species.id === 'ditto') return 'Choice Scarf';
|
|
if (species.id === 'exploud' && role === 'Bulky Attacker') return 'Choice Band';
|
|
if (ability === 'Poison Heal' || moves.has('facade')) return 'Toxic Orb';
|
|
if (ability === 'Speed Boost' && species.id !== 'ninjask') return 'Life Orb';
|
|
if (species.nfe) return 'Eviolite';
|
|
if (['healingwish', 'memento', 'switcheroo', 'trick'].some(m => moves.has(m))) {
|
|
if (
|
|
species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && role !== 'Wallbreaker' && !counter.get('priority')
|
|
) {
|
|
return 'Choice Scarf';
|
|
} else {
|
|
return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
|
|
}
|
|
}
|
|
if (moves.has('bellydrum')) return 'Sitrus Berry';
|
|
if (moves.has('waterspout')) return 'Choice Scarf';
|
|
if (moves.has('shellsmash')) return 'White Herb';
|
|
if (moves.has('psychoshift')) return 'Flame Orb';
|
|
if (ability === 'Magic Guard') return moves.has('counter') ? 'Focus Sash' : 'Life Orb';
|
|
if (species.id === 'rampardos' && role === 'Fast Attacker') return 'Choice Scarf';
|
|
if (ability === 'Sheer Force' && counter.get('sheerforce')) return 'Life Orb';
|
|
if (moves.has('acrobatics')) return 'Flying Gem';
|
|
if (species.id === 'hitmonlee' && ability === 'Unburden') return moves.has('fakeout') ? 'Normal Gem' : 'Fighting Gem';
|
|
if (moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
|
|
if (moves.has('rest') && !moves.has('sleeptalk') && !['Natural Cure', 'Shed Skin'].includes(ability)) {
|
|
return (moves.has('raindance') && ability === 'Hydration') ? 'Damp Rock' : 'Chesto Berry';
|
|
}
|
|
if (role === 'Staller') return 'Leftovers';
|
|
}
|
|
|
|
getItem(
|
|
ability: string,
|
|
types: string[],
|
|
moves: Set<string>,
|
|
counter: MoveCounter,
|
|
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
species: Species,
|
|
isLead: boolean,
|
|
preferredType: string,
|
|
role: RandomTeamsTypes.Role,
|
|
): string {
|
|
const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
|
|
|
|
const scarfReqs = (
|
|
role !== 'Wallbreaker' &&
|
|
species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
|
|
!counter.get('priority') && !moves.has('pursuit')
|
|
);
|
|
|
|
if (
|
|
moves.has('pursuit') && moves.has('suckerpunch') && counter.get('Dark') &&
|
|
(!this.priorityPokemon.includes(species.id) || counter.get('Dark') >= 2)
|
|
) return 'Black Glasses';
|
|
if (counter.get('Special') === 4) {
|
|
return (
|
|
scarfReqs && species.baseStats.spa >= 90 && this.randomChance(1, 2)
|
|
) ? 'Choice Scarf' : 'Choice Specs';
|
|
}
|
|
if (counter.get('Special') === 3 && moves.has('uturn')) return 'Choice Specs';
|
|
if (counter.get('Physical') === 4 && species.id !== 'jirachi' && species.id !== 'spinda' &&
|
|
['dragontail', 'fakeout', 'rapidspin'].every(m => !moves.has(m))
|
|
) {
|
|
return (
|
|
scarfReqs && (species.baseStats.atk >= 100 || ability === 'Pure Power' || ability === 'Huge Power') &&
|
|
this.randomChance(1, 2)
|
|
) ? 'Choice Scarf' : 'Choice Band';
|
|
}
|
|
|
|
if (ability === 'Sturdy' && moves.has('explosion')) return 'Custap Berry';
|
|
if (types.includes('Normal') && moves.has('fakeout') && !!counter.get('Normal')) return 'Silk Scarf';
|
|
if (species.id === 'palkia') return 'Lustrous Orb';
|
|
if (moves.has('outrage') && counter.get('setup')) return 'Lum Berry';
|
|
if (
|
|
(ability === 'Rough Skin') || (species.id !== 'hooh' && role !== 'Wallbreaker' &&
|
|
ability === 'Regenerator' && species.baseStats.hp + species.baseStats.def >= 180 && this.randomChance(1, 2))
|
|
) return 'Rocky Helmet';
|
|
if (['protect', 'substitute'].some(m => moves.has(m))) return 'Leftovers';
|
|
if (
|
|
this.dex.getEffectiveness('Ground', species) >= 2 &&
|
|
ability !== 'Levitate'
|
|
) {
|
|
return 'Air Balloon';
|
|
}
|
|
if (
|
|
role === 'Fast Support' && isLead && defensiveStatTotal < 255 && !counter.get('recovery') &&
|
|
(counter.get('hazards') || counter.get('setup')) && (!counter.get('recoil') || ability === 'Rock Head')
|
|
) return 'Focus Sash';
|
|
|
|
// Default Items
|
|
if (role === 'Fast Support') {
|
|
return (
|
|
counter.get('Physical') + counter.get('Special') >= 3 &&
|
|
['rapidspin', 'uturn', 'voltswitch'].every(m => !moves.has(m)) &&
|
|
this.dex.getEffectiveness('Rock', species) < 2
|
|
) ? 'Life Orb' : 'Leftovers';
|
|
}
|
|
// noStab moves that should reject Expert Belt
|
|
const noExpertBeltMoves = (
|
|
this.noStab.filter(moveid => ['Dragon', 'Normal', 'Poison'].includes(this.dex.moves.get(moveid).type))
|
|
);
|
|
const expertBeltReqs = (
|
|
!counter.get('Dragon') && !counter.get('Normal') && !counter.get('Poison') &&
|
|
noExpertBeltMoves.every(m => !moves.has(m))
|
|
);
|
|
if (
|
|
!counter.get('Status') && expertBeltReqs &&
|
|
(moves.has('uturn') || moves.has('voltswitch') || role === 'Fast Attacker')
|
|
) return 'Expert Belt';
|
|
if (
|
|
['Fast Attacker', 'Setup Sweeper', 'Wallbreaker'].some(m => role === m) &&
|
|
this.dex.getEffectiveness('Rock', species) < 2 && ability !== 'Sturdy'
|
|
) return 'Life Orb';
|
|
return 'Leftovers';
|
|
}
|
|
|
|
randomSet(
|
|
species: string | Species,
|
|
teamDetails: RandomTeamsTypes.TeamDetails = {},
|
|
isLead = false
|
|
): RandomTeamsTypes.RandomSet {
|
|
species = this.dex.species.get(species);
|
|
const forme = this.getForme(species);
|
|
const sets = this.randomSets[species.id]["sets"];
|
|
const possibleSets = [];
|
|
// Check if the Pokemon has a Spinner set
|
|
let canSpinner = false;
|
|
for (const set of sets) {
|
|
if (!teamDetails.rapidSpin && set.role === 'Spinner') canSpinner = true;
|
|
}
|
|
for (const set of sets) {
|
|
// Prevent Spinner if the team already has removal
|
|
if (teamDetails.rapidSpin && set.role === 'Spinner') continue;
|
|
// Enforce Spinner if the team does not have removal
|
|
if (canSpinner && set.role !== 'Spinner') continue;
|
|
possibleSets.push(set);
|
|
}
|
|
const set = this.sampleIfArray(possibleSets);
|
|
const role = set.role;
|
|
const movePool: string[] = Array.from(set.movepool);
|
|
const preferredTypes = set.preferredTypes;
|
|
const preferredType = this.sampleIfArray(preferredTypes) || '';
|
|
|
|
let ability = '';
|
|
let item = undefined;
|
|
|
|
const evs = { hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85 };
|
|
const ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
|
|
|
|
const types = species.types;
|
|
const abilities = set.abilities!;
|
|
|
|
// Get moves
|
|
const moves = this.randomMoveset(types, abilities, teamDetails, species, isLead, movePool,
|
|
preferredType, role);
|
|
const counter = this.newQueryMoves(moves, species, preferredType, abilities);
|
|
|
|
// Get ability
|
|
ability = this.getAbility(new Set(types), moves, abilities, counter, movePool, teamDetails, species,
|
|
preferredType, role);
|
|
|
|
// Get items
|
|
item = this.getPriorityItem(ability, types, moves, counter, teamDetails, species, isLead, preferredType, role);
|
|
if (item === undefined) {
|
|
item = this.getItem(ability, types, moves, counter, teamDetails, species, isLead, preferredType, role);
|
|
}
|
|
|
|
// For Trick / Switcheroo
|
|
if (item === 'Leftovers' && types.includes('Poison')) {
|
|
item = 'Black Sludge';
|
|
}
|
|
|
|
const level = this.getLevel(species);
|
|
|
|
// We use a special variable to track Hidden Power
|
|
// so that we can check for all Hidden Powers at once
|
|
let hasHiddenPower = false;
|
|
for (const move of moves) {
|
|
if (move.startsWith('hiddenpower')) hasHiddenPower = true;
|
|
}
|
|
|
|
if (hasHiddenPower) {
|
|
let hpType;
|
|
for (const move of moves) {
|
|
if (move.startsWith('hiddenpower')) hpType = move.substr(11);
|
|
}
|
|
if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
|
|
const HPivs = this.dex.types.get(hpType).HPivs;
|
|
let iv: StatID;
|
|
for (iv in HPivs) {
|
|
ivs[iv] = HPivs[iv]!;
|
|
}
|
|
}
|
|
|
|
// Prepare optimal HP
|
|
const srImmunity = ability === 'Magic Guard';
|
|
const srWeakness = srImmunity ? 0 : this.dex.getEffectiveness('Rock', species);
|
|
while (evs.hp > 1) {
|
|
const hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
|
|
if (moves.has('substitute') && !['Black Sludge', 'Leftovers'].includes(item)) {
|
|
if (item === 'Sitrus Berry') {
|
|
// Two Substitutes should activate Sitrus Berry
|
|
if (hp % 4 === 0) break;
|
|
} else {
|
|
// Should be able to use Substitute four times from full HP without fainting
|
|
if (hp % 4 > 0) break;
|
|
}
|
|
} else if (moves.has('bellydrum') && item === 'Sitrus Berry') {
|
|
// Belly Drum should activate Sitrus Berry
|
|
if (hp % 2 === 0) break;
|
|
} else if (['highjumpkick', 'jumpkick'].some(m => moves.has(m))) {
|
|
// Crash damage move users want an odd HP to survive two misses
|
|
if (hp % 2 > 0) break;
|
|
} else {
|
|
// Maximize number of Stealth Rock switch-ins
|
|
if (srWeakness <= 0 || ability === 'Regenerator') break;
|
|
if (srWeakness === 1 && ['Black Sludge', 'Leftovers', 'Life Orb'].includes(item)) break;
|
|
if (item !== 'Sitrus Berry' && hp % (4 / srWeakness) > 0) break;
|
|
// Minimise number of Stealth Rock switch-ins to activate Sitrus Berry
|
|
if (item === 'Sitrus Berry' && hp % (4 / srWeakness) === 0) break;
|
|
}
|
|
evs.hp -= 4;
|
|
}
|
|
|
|
// Minimize confusion damage, including if Foul Play is its only physical attack
|
|
if (
|
|
(!counter.get('Physical') || (counter.get('Physical') <= 1 && (moves.has('foulplay') || moves.has('rapidspin')))) &&
|
|
!moves.has('transform')
|
|
) {
|
|
evs.atk = 0;
|
|
ivs.atk = hasHiddenPower ? (ivs.atk || 31) - 28 : 0;
|
|
}
|
|
|
|
if (['gyroball', 'metalburst', 'trickroom'].some(m => moves.has(m))) {
|
|
evs.spe = 0;
|
|
ivs.spe = hasHiddenPower ? (ivs.spe || 31) - 28 : 0;
|
|
}
|
|
|
|
// shuffle moves to add more randomness to camomons
|
|
const shuffledMoves = Array.from(moves);
|
|
this.prng.shuffle(shuffledMoves);
|
|
|
|
return {
|
|
name: species.baseSpecies,
|
|
species: forme,
|
|
gender: species.gender,
|
|
shiny: this.randomChance(1, 1024),
|
|
level,
|
|
moves: shuffledMoves,
|
|
ability,
|
|
evs,
|
|
ivs,
|
|
item,
|
|
role,
|
|
};
|
|
}
|
|
|
|
randomTeam() {
|
|
this.enforceNoDirectCustomBanlistChanges();
|
|
|
|
const seed = this.prng.getSeed();
|
|
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
const pokemon: RandomTeamsTypes.RandomSet[] = [];
|
|
|
|
// For Monotype
|
|
const isMonotype = !!this.forceMonotype || ruleTable.has('sametypeclause');
|
|
const typePool = this.dex.types.names();
|
|
const type = this.forceMonotype || this.sample(typePool);
|
|
|
|
const baseFormes: { [k: string]: number } = {};
|
|
const typeCount: { [k: string]: number } = {};
|
|
const typeWeaknesses: { [k: string]: number } = {};
|
|
const typeDoubleWeaknesses: { [k: string]: number } = {};
|
|
const teamDetails: RandomTeamsTypes.TeamDetails = {};
|
|
let numMaxLevelPokemon = 0;
|
|
|
|
const pokemonList = Object.keys(this.randomSets);
|
|
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
|
|
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
|
|
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
|
|
const species = this.dex.species.get(this.sample(pokemonPool[baseSpecies]));
|
|
if (!species.exists) continue;
|
|
|
|
// Limit to one of each species (Species Clause)
|
|
if (baseFormes[species.baseSpecies]) continue;
|
|
|
|
// Illusion shouldn't be in the last slot
|
|
if (species.name === 'Zoroark' && pokemon.length >= (this.maxTeamSize - 1)) continue;
|
|
|
|
// Prevent Shedinja from generating after Sandstorm/Hail setters
|
|
if (species.name === 'Shedinja' && (teamDetails.sand || teamDetails.hail)) continue;
|
|
|
|
// Dynamically scale limits for different team sizes. The default and minimum value is 1.
|
|
const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
|
|
|
|
const types = species.types;
|
|
|
|
if (!isMonotype && !this.forceMonotype) {
|
|
let skip = false;
|
|
|
|
// Limit two of any type
|
|
for (const typeName of types) {
|
|
if (typeCount[typeName] >= 2 * limitFactor) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (skip) continue;
|
|
|
|
// Limit three weak to any type, and one double weak to any type
|
|
for (const typeName of this.dex.types.names()) {
|
|
// it's weak to the type
|
|
if (this.dex.getEffectiveness(typeName, species) > 0) {
|
|
if (!typeWeaknesses[typeName]) typeWeaknesses[typeName] = 0;
|
|
if (typeWeaknesses[typeName] >= 3 * limitFactor) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (this.dex.getEffectiveness(typeName, species) > 1) {
|
|
if (!typeDoubleWeaknesses[typeName]) typeDoubleWeaknesses[typeName] = 0;
|
|
if (typeDoubleWeaknesses[typeName] >= limitFactor) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (skip) continue;
|
|
|
|
// Count Dry Skin as a Fire weakness
|
|
if (this.dex.getEffectiveness('Fire', species) === 0 && Object.values(species.abilities).includes('Dry Skin')) {
|
|
if (!typeWeaknesses['Fire']) typeWeaknesses['Fire'] = 0;
|
|
if (typeWeaknesses['Fire'] >= 3 * limitFactor) continue;
|
|
}
|
|
|
|
// Limit one level 100 Pokemon
|
|
if (!this.adjustLevel && (this.getLevel(species) === 100) && numMaxLevelPokemon >= limitFactor) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const set = this.randomSet(species, teamDetails, pokemon.length === 0);
|
|
|
|
// Okay, the set passes, add it to our team
|
|
pokemon.push(set);
|
|
|
|
// Don't bother tracking details for the last Pokemon
|
|
if (pokemon.length === this.maxTeamSize) break;
|
|
|
|
// Now that our Pokemon has passed all checks, we can increment our counters
|
|
baseFormes[species.baseSpecies] = 1;
|
|
|
|
// Increment type counters
|
|
for (const typeName of types) {
|
|
if (typeName in typeCount) {
|
|
typeCount[typeName]++;
|
|
} else {
|
|
typeCount[typeName] = 1;
|
|
}
|
|
}
|
|
|
|
// Increment weakness counter
|
|
for (const typeName of this.dex.types.names()) {
|
|
// it's weak to the type
|
|
if (this.dex.getEffectiveness(typeName, species) > 0) {
|
|
typeWeaknesses[typeName]++;
|
|
}
|
|
if (this.dex.getEffectiveness(typeName, species) > 1) {
|
|
typeDoubleWeaknesses[typeName]++;
|
|
}
|
|
}
|
|
// Count Dry Skin as a Fire weakness
|
|
if (set.ability === 'Dry Skin' && this.dex.getEffectiveness('Fire', species) === 0) typeWeaknesses['Fire']++;
|
|
|
|
// Increment level 100 counter
|
|
if (set.level === 100) numMaxLevelPokemon++;
|
|
|
|
// Team details
|
|
if (set.ability === 'Snow Warning' || set.moves.includes('hail')) teamDetails.hail = 1;
|
|
if (set.ability === 'Drizzle' || set.moves.includes('raindance')) teamDetails.rain = 1;
|
|
if (set.ability === 'Sand Stream') teamDetails.sand = 1;
|
|
if (set.ability === 'Drought' || set.moves.includes('sunnyday')) teamDetails.sun = 1;
|
|
if (set.moves.includes('aromatherapy') || set.moves.includes('healbell')) teamDetails.statusCure = 1;
|
|
if (set.moves.includes('spikes')) teamDetails.spikes = (teamDetails.spikes || 0) + 1;
|
|
if (set.moves.includes('stealthrock')) teamDetails.stealthRock = 1;
|
|
if (set.moves.includes('toxicspikes')) teamDetails.toxicSpikes = 1;
|
|
if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1;
|
|
if (set.moves.includes('reflect') && set.moves.includes('lightscreen')) teamDetails.screens = 1;
|
|
}
|
|
if (pokemon.length < this.maxTeamSize && pokemon.length < 12) {
|
|
throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
|
|
}
|
|
|
|
return pokemon;
|
|
}
|
|
}
|
|
|
|
export default RandomGen5Teams;
|