import RandomGen3Teams from '../gen3/random-teams'; import {PRNG, PRNGSeed} from '../../../sim/prng'; import type {MoveCounter} from '../../random-teams'; export class RandomGen2Teams extends RandomGen3Teams { constructor(format: string | Format, prng: PRNG | PRNGSeed | null) { super(format, prng); this.moveEnforcementCheckers = { Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric'), Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'), Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'), Ice: (movePool, moves, abilities, types, counter) => !counter.get('Ice'), Normal: (movePool, moves, abilities, types, counter) => !counter.get('Normal') && counter.setupType === 'Physical', Psychic: (movePool, moves, abilities, types, counter) => !counter.get('Psychic') && types.has('Grass'), Rock: (movePool, moves, abilities, types, counter, species) => !counter.get('Rock') && species.baseStats.atk > 60, Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'), }; } shouldCullMove( move: Move, types: Set, moves: Set, abilities = {}, counter: MoveCounter, movePool: string[], teamDetails: RandomTeamsTypes.TeamDetails, ): {cull: boolean, isSetup?: boolean} { const restTalk = moves.has('rest') && moves.has('sleeptalk'); switch (move.id) { // Set up once and only if we have the moves for it case 'bellydrum': case 'curse': case 'meditate': case 'screech': case 'swordsdance': return { cull: ( (counter.setupType !== 'Physical' || counter.get('physicalsetup') > 1) || (!counter.get('Physical') || counter.damagingMoves.size < 2 && !moves.has('batonpass') && !moves.has('sleeptalk')) ), isSetup: true, }; // Not very useful without their supporting moves case 'batonpass': return {cull: !counter.setupType && !counter.get('speedsetup') && !moves.has('meanlook')}; case 'meanlook': return {cull: movePool.includes('perishsong')}; case 'nightmare': return {cull: !moves.has('lovelykiss') && !moves.has('sleeppowder')}; case 'swagger': return {cull: !moves.has('substitute')}; // Bad after setup case 'charm': case 'counter': return {cull: !!counter.setupType}; case 'haze': return {cull: !!counter.setupType || restTalk}; case 'reflect': case 'lightscreen': return {cull: !!counter.setupType || moves.has('rest')}; // Ineffective to have both case 'doubleedge': return {cull: moves.has('bodyslam') || moves.has('return')}; case 'explosion': return {cull: moves.has('softboiled')}; case 'extremespeed': return {cull: moves.has('bodyslam') || restTalk}; case 'hyperbeam': return {cull: moves.has('rockslide')}; case 'quickattack': case 'selfdestruct': return {cull: moves.has('rest')}; case 'rapidspin': return {cull: !!teamDetails.rapidSpin || moves.has('sleeptalk')}; case 'return': return {cull: moves.has('bodyslam')}; case 'surf': return {cull: moves.has('hydropump')}; case 'thunder': return {cull: moves.has('thunderbolt')}; case 'gigadrain': return {cull: moves.has('razorleaf') || moves.has('swordsdance') && movePool.includes('sludgebomb')}; case 'icebeam': return {cull: moves.has('dragonbreath')}; case 'seismictoss': return {cull: moves.has('rest') || moves.has('sleeptalk')}; case 'destinybond': return {cull: moves.has('explosion')}; case 'pursuit': return {cull: moves.has('crunch') && moves.has('solarbeam')}; case 'thief': return {cull: moves.has('rest') || moves.has('substitute')}; case 'irontail': return {cull: types.has('Ground') && movePool.includes('earthquake')}; // Status and illegal move rejections case 'confuseray': case 'roar': case 'whirlwind': return {cull: restTalk}; case 'encore': return {cull: moves.has('bodyslam') || moves.has('surf') || restTalk}; case 'lovelykiss': return {cull: ['healbell', 'moonlight', 'morningsun'].some(m => moves.has(m)) || restTalk}; case 'sleeptalk': return {cull: moves.has('curse') && counter.get('stab') >= 2}; case 'softboiled': return {cull: movePool.includes('swordsdance')}; case 'spikes': return {cull: !!teamDetails.spikes || types.has('Ice') && moves.has('rapidspin')}; case 'substitute': return {cull: moves.has('agility') || moves.has('rest')}; case 'synthesis': return {cull: moves.has('explosion')}; case 'thunderwave': return {cull: moves.has('thunder') || moves.has('toxic')}; } return {cull: false}; } getItem( ability: string, types: Set, moves: Set, counter: MoveCounter, species: Species, ) { // First, the high-priority items if (species.name === 'Ditto') return this.sample(['Metal Powder', 'Quick Claw']); if (species.name === 'Farfetch\u2019d') return 'Stick'; if (species.name === 'Marowak') return 'Thick Club'; if (species.name === 'Pikachu') return 'Light Ball'; if (species.name === 'Unown') return 'Twisted Spoon'; if (moves.has('thief')) return ''; // Medium priority if (moves.has('rest') && !moves.has('sleeptalk')) return 'Mint Berry'; if ( (moves.has('bellydrum') || moves.has('swordsdance')) && species.baseStats.spe >= 60 && !types.has('Ground') && !moves.has('sleeptalk') && !moves.has('substitute') && this.randomChance(1, 2) ) { return 'Miracle Berry'; } // Default to Leftovers return 'Leftovers'; } randomSet(species: string | Species, teamDetails: RandomTeamsTypes.TeamDetails = {}): RandomTeamsTypes.RandomSet { species = this.dex.species.get(species); const movePool = (species.randomBattleMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice(); const rejectedPool: string[] = []; const moves = new Set(); let ivs = {hp: 30, atk: 30, def: 30, spa: 30, spd: 30, spe: 30}; let availableHP = 0; for (const setMoveid of movePool) { if (setMoveid.startsWith('hiddenpower')) availableHP++; } const types = new Set(species.types); let counter; // We use a special variable to track Hidden Power // so that we can check for all Hidden Powers at once let hasHiddenPower = false; do { // Choose next 4 moves from learnset/viable moves and add them to moves list: while (moves.size < this.maxMoveCount && movePool.length) { const moveid = this.sampleNoReplace(movePool); if (moveid.startsWith('hiddenpower')) { availableHP--; if (hasHiddenPower) continue; hasHiddenPower = true; } moves.add(moveid); } while (moves.size < this.maxMoveCount && rejectedPool.length) { const moveid = this.sampleNoReplace(rejectedPool); if (moveid.startsWith('hiddenpower')) { if (hasHiddenPower) continue; hasHiddenPower = true; } moves.add(moveid); } counter = this.queryMoves(moves, species.types, new Set(), movePool); // Iterate through the moves again, this time to cull them: for (const moveid of moves) { const move = this.dex.moves.get(moveid); let {cull, isSetup} = this.shouldCullMove(move, types, moves, {}, counter, movePool, teamDetails); // This move doesn't satisfy our setup requirements: if (counter.setupType === 'Physical' && move.category === 'Special' && !counter.get('Physical')) { cull = true; } // Reject Status, non-STAB, or low basepower moves const moveIsRejectable = ( (move.category !== 'Status' || !move.flags.heal) && // These moves cannot be rejected in favor of a forced move !['batonpass', 'sleeptalk', 'spikes', 'sunnyday'].includes(move.id) && (move.category === 'Status' || !types.has(move.type) || (move.basePower && move.basePower < 40)) ); if (!cull && !isSetup && moveIsRejectable && (counter.setupType || !move.stallingMove)) { // There may be more important moves that this Pokemon needs if ( // Pokemon should usually have at least one STAB move ( !counter.get('stab') && !counter.get('damage') && !types.has('Ghost') && counter.get('physicalpool') + counter.get('specialpool') > 0 ) || (movePool.includes('megahorn') || (movePool.includes('softboiled') && moves.has('present'))) || // Rest + Sleep Talk should be selected together ((moves.has('rest') && movePool.includes('sleeptalk')) || (moves.has('sleeptalk') && movePool.includes('rest'))) || // Sunny Day + Solar Beam should be selected together (moves.has('sunnyday') && movePool.includes('solarbeam') || (moves.has('solarbeam') && movePool.includes('sunnyday'))) || ['milkdrink', 'recover', 'spore'].some(m => movePool.includes(m)) ) { cull = true; } else { // Pokemon should have moves that benefit their typing for (const type of types) { if (this.moveEnforcementCheckers[type]?.(movePool, moves, new Set(), types, counter, species, teamDetails)) cull = true; } } } // Remove rejected moves from the move list if ( cull && (movePool.length - availableHP || availableHP && (move.id === 'hiddenpower' || !hasHiddenPower)) ) { if (move.category !== 'Status' && !move.damage && (move.id !== 'hiddenpower' || !availableHP)) { rejectedPool.push(moveid); } moves.delete(moveid); if (moveid.startsWith('hiddenpower')) hasHiddenPower = false; break; } if (cull && rejectedPool.length) { moves.delete(moveid); if (moveid.startsWith('hiddenpower')) hasHiddenPower = false; break; } } } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length)); // Adjust IVs for Hidden Power for (const setMoveid of moves) { if (!setMoveid.startsWith('hiddenpower')) continue; const hpType = setMoveid.substr(11, setMoveid.length); const hpIVs: {[k: string]: Partial} = { dragon: {def: 28}, ice: {def: 26}, psychic: {def: 24}, electric: {atk: 28}, grass: {atk: 28, def: 28}, water: {atk: 28, def: 26}, fire: {atk: 28, def: 24}, steel: {atk: 26}, ghost: {atk: 26, def: 28}, bug: {atk: 26, def: 26}, rock: {atk: 26, def: 24}, ground: {atk: 24}, poison: {atk: 24, def: 28}, flying: {atk: 24, def: 26}, fighting: {atk: 24, def: 24}, }; if (hpIVs[hpType]) { ivs = {...ivs, ...hpIVs[hpType]}; } if (ivs.atk === 28 || ivs.atk === 24) ivs.hp = 14; if (ivs.def === 28 || ivs.def === 24) ivs.hp -= 8; } const levelScale: {[k: string]: number} = { NU: 73, NUBL: 71, UU: 69, UUBL: 67, OU: 65, Uber: 61, }; const customScale: {[k: string]: number} = { Ditto: 83, Unown: 87, Wobbuffet: 83, }; const level = this.adjustLevel || customScale[species.name] || levelScale[species.tier] || 80; return { name: species.name, species: species.name, moves: Array.from(moves), ability: 'No Ability', evs: {hp: 255, atk: 255, def: 255, spa: 255, spd: 255, spe: 255}, ivs, item: this.getItem('None', types, moves, counter, species), level, // No shiny chance because Gen 2 shinies have bad IVs shiny: false, gender: species.gender ? species.gender : 'M', }; } } export default RandomGen2Teams;