Spaces:
Running
Running
import type { PRNG } from '../../../sim'; | |
import { type MoveCounter, RandomGen8Teams, type OldRandomBattleSpecies } from '../gen8/teams'; | |
export class RandomLetsGoTeams extends RandomGen8Teams { | |
randomData: { [species: string]: OldRandomBattleSpecies } = require('./data.json'); | |
constructor(format: Format | string, prng: PRNG | PRNGSeed | null) { | |
super(format, prng); | |
this.moveEnforcementCheckers = { | |
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') && (!!counter.setupType || !counter.get('Status')) | |
), | |
Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'), | |
Ghost: (movePool, moves, abilities, types, counter) => !types.has('Dark') && !counter.get('Ghost'), | |
Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'), | |
Ice: (movePool, moves, abilities, types, counter) => !counter.get('Ice'), | |
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water') || !counter.get('stab'), | |
}; | |
} | |
shouldCullMove( | |
move: Move, | |
types: Set<string>, | |
moves: Set<string>, | |
abilities: string[], | |
counter: MoveCounter, | |
movePool: string[], | |
teamDetails: RandomTeamsTypes.TeamDetails, | |
): { cull: boolean, isSetup?: boolean } { | |
switch (move.id) { | |
// Set up once and only if we have the moves for it | |
case 'bulkup': case 'swordsdance': | |
return { | |
cull: ( | |
counter.setupType !== 'Physical' || | |
counter.get('physicalsetup') > 1 || | |
counter.get('Physical') + counter.get('physicalpool') < 2 | |
), | |
isSetup: true, | |
}; | |
case 'calmmind': case 'nastyplot': case 'quiverdance': | |
return { | |
cull: ( | |
counter.setupType !== 'Special' || | |
counter.get('specialsetup') > 1 || | |
counter.get('Special') + counter.get('specialpool') < 2 | |
), | |
isSetup: true, | |
}; | |
case 'growth': case 'shellsmash': | |
return { | |
cull: ( | |
counter.setupType !== 'Mixed' || | |
(counter.damagingMoves.size + counter.get('physicalpool') + counter.get('specialpool')) < 2 | |
), | |
isSetup: true, | |
}; | |
case 'agility': | |
return { | |
cull: counter.damagingMoves.size < 2 && !counter.setupType, | |
isSetup: !counter.setupType, | |
}; | |
// Bad after setup | |
case 'dragontail': | |
return { cull: ( | |
!!counter.setupType || !!counter.get('speedsetup') || ['encore', 'roar', 'whirlwind'].some(m => moves.has(m)) | |
) }; | |
case 'fakeout': case 'uturn': case 'teleport': | |
return { cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('substitute') }; | |
case 'haze': case 'leechseed': case 'roar': case 'whirlwind': | |
return { cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('dragontail') }; | |
case 'protect': | |
return { cull: !!counter.setupType || ['rest', 'lightscreen', 'reflect'].some(m => moves.has(m)) }; | |
case 'seismictoss': | |
return { cull: counter.damagingMoves.size > 1 || !!counter.setupType }; | |
case 'stealthrock': | |
return { cull: !!counter.setupType || !!counter.get('speedsetup') || !!teamDetails.stealthRock }; | |
// Bit redundant to have both | |
case 'leechlife': case 'substitute': | |
return { cull: moves.has('uturn') }; | |
case 'dragonpulse': | |
return { cull: moves.has('dragontail') || moves.has('outrage') }; | |
case 'thunderbolt': | |
return { cull: moves.has('thunder') }; | |
case 'flareblitz': case 'flamethrower': | |
return { cull: moves.has('fireblast') || moves.has('firepunch') }; | |
case 'megadrain': | |
return { cull: moves.has('petaldance') || moves.has('powerwhip') }; | |
case 'bonemerang': | |
return { cull: moves.has('earthquake') }; | |
case 'icebeam': | |
return { cull: moves.has('blizzard') }; | |
case 'rockslide': | |
return { cull: moves.has('stoneedge') }; | |
case 'hydropump': case 'willowisp': | |
return { cull: moves.has('scald') }; | |
case 'surf': | |
return { cull: moves.has('hydropump') || moves.has('scald') }; | |
} | |
// Increased/decreased priority moves are unneeded with moves that boost only speed | |
if (move.priority !== 0 && !!counter.get('speedsetup')) return { cull: true }; | |
// This move doesn't satisfy our setup requirements: | |
if ( | |
(move.category === 'Physical' && counter.setupType === 'Special') || | |
(move.category === 'Special' && counter.setupType === 'Physical') | |
) { | |
// Reject STABs last in case the setup type changes later on | |
if (!types.has(move.type) || counter.get('stab') > 1 || counter.get(move.category) < 2) return { cull: true }; | |
} | |
return { cull: false }; | |
} | |
randomSet(species: string | Species, teamDetails: RandomTeamsTypes.TeamDetails = {}): RandomTeamsTypes.RandomSet { | |
species = this.dex.species.get(species); | |
let forme = species.name; | |
if (typeof species.battleOnly === 'string') { | |
// Only change the forme. The species has custom moves, and may have different typing and requirements. | |
forme = species.battleOnly; | |
} | |
const data = this.randomData[species.id]; | |
const movePool: string[] = [...(data.moves || this.dex.species.getMovePool(species.id))]; | |
const types = new Set(species.types); | |
const moves = new Set<string>(); | |
let counter; | |
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); | |
moves.add(moveid); | |
} | |
counter = this.queryMoves(moves, species.types, [], 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); | |
if ( | |
!isSetup && | |
counter.setupType && counter.setupType !== 'Mixed' && | |
move.category !== counter.setupType && | |
counter.get(counter.setupType) < 2 && ( | |
// Mono-attacking with setup and RestTalk is allowed | |
// Reject Status moves only if there is nothing else to reject | |
move.category !== 'Status' || ( | |
counter.get(counter.setupType) + counter.get('Status') > 3 && | |
counter.get('physicalsetup') + counter.get('specialsetup') < 2 | |
) | |
) | |
) { | |
cull = true; | |
} | |
const moveIsRejectable = !move.damage && (move.category !== 'Status' || !move.flags.heal) && ( | |
move.category === 'Status' || | |
!types.has(move.type) || | |
move.selfSwitch || | |
move.basePower && move.basePower < 40 && !move.multihit | |
); | |
// Pokemon should have moves that benefit their Type, as well as moves required by its forme | |
if (moveIsRejectable && !cull && !isSetup && counter.get('physicalsetup') + counter.get('specialsetup') < 2 && ( | |
!counter.setupType || counter.setupType === 'Mixed' || | |
(move.category !== counter.setupType && move.category !== 'Status') || | |
counter.get(counter.setupType) + counter.get('Status') > 3 | |
)) { | |
if ( | |
(counter.damagingMoves.size === 0 || !counter.get('stab')) && | |
(counter.get('physicalpool') || counter.get('specialpool')) | |
) { | |
cull = true; | |
} else { | |
for (const type of types) { | |
if (this.moveEnforcementCheckers[type]?.(movePool, moves, [], types, counter, species, teamDetails)) cull = true; | |
} | |
} | |
} | |
// Remove rejected moves from the move list | |
if (cull && movePool.length) { | |
moves.delete(moveid); | |
break; | |
} | |
} | |
} while (moves.size < this.maxMoveCount && movePool.length); | |
const ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; | |
// Minimize confusion damage | |
if (!counter.get('Physical') && !moves.has('transform')) ivs.atk = 0; | |
const requiredItem = species.requiredItem || (species.requiredItems ? this.sample(species.requiredItems) : null); | |
return { | |
name: species.baseSpecies, | |
species: forme, | |
level: this.adjustLevel || 100, | |
gender: species.gender, | |
happiness: 70, | |
shiny: this.randomChance(1, 1024), | |
item: (requiredItem || ''), | |
ability: 'No Ability', | |
evs: { hp: 20, atk: 20, def: 20, spa: 20, spd: 20, spe: 20 }, | |
moves: Array.from(moves), | |
ivs, | |
}; | |
} | |
randomTeam() { | |
this.enforceNoDirectCustomBanlistChanges(); | |
const pokemon: RandomTeamsTypes.RandomSet[] = []; | |
const pokemonPool: string[] = []; | |
for (const id in this.dex.data.FormatsData) { | |
const species = this.dex.species.get(id); | |
if ( | |
species.num < 1 || | |
(species.num > 151 && ![808, 809].includes(species.num)) || | |
species.gen > 7 || | |
species.nfe || | |
!this.randomData[species.id]?.moves || | |
(this.forceMonotype && !species.types.includes(this.forceMonotype)) | |
) { | |
continue; | |
} | |
pokemonPool.push(id); | |
} | |
const typeCount: { [k: string]: number } = {}; | |
const typeComboCount: { [k: string]: number } = {}; | |
const baseFormes: { [k: string]: number } = {}; | |
const teamDetails: RandomTeamsTypes.TeamDetails = {}; | |
while (pokemonPool.length && pokemon.length < this.maxTeamSize) { | |
const species = this.dex.species.get(this.sampleNoReplace(pokemonPool)); | |
if (!species.exists) continue; | |
// Limit to one of each species (Species Clause) | |
if (baseFormes[species.baseSpecies]) continue; | |
const types = species.types; | |
// Once we have 2 Pokémon of a given type we reject more Pokémon of that type 80% of the time | |
let skip = false; | |
for (const type of species.types) { | |
if (typeCount[type] > 1 && this.randomChance(4, 5)) { | |
skip = true; | |
break; | |
} | |
} | |
if (skip) continue; | |
// Limit 1 of any type combination | |
const typeCombo = types.slice().sort().join(); | |
if (!this.forceMonotype && typeComboCount[typeCombo] >= 1) continue; | |
// Okay, the set passes, add it to our team | |
const set = this.randomSet(species, teamDetails); | |
pokemon.push(set); | |
// Now that our Pokemon has passed all checks, we can increment our counters | |
baseFormes[species.baseSpecies] = 1; | |
// Increment type counters | |
for (const type of types) { | |
if (type in typeCount) { | |
typeCount[type]++; | |
} else { | |
typeCount[type] = 1; | |
} | |
} | |
if (typeCombo in typeComboCount) { | |
typeComboCount[typeCombo]++; | |
} else { | |
typeComboCount[typeCombo] = 1; | |
} | |
// Team details | |
if (set.moves.includes('stealthrock')) teamDetails.stealthRock = 1; | |
if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1; | |
} | |
return pokemon; | |
} | |
} | |
export default RandomLetsGoTeams; | |