@pkmn/randoms 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gen8.ts DELETED
@@ -1,3122 +0,0 @@
1
- import {Utils} from './utils';
2
- import {
3
- Ability,
4
- AnyObject,
5
- BasicEffect,
6
- Dex,
7
- Format,
8
- Item,
9
- ModdedDex,
10
- Move,
11
- Nature,
12
- PRNG,
13
- PRNGSeed,
14
- PlayerOptions,
15
- PokemonSet,
16
- RandomTeamsTypes,
17
- Species,
18
- StatID,
19
- StatsTable,
20
- Tags,
21
- toID,
22
- } from '@pkmn/sim';
23
-
24
- export interface TeamData {
25
- typeCount: {[k: string]: number};
26
- typeComboCount: {[k: string]: number};
27
- baseFormes: {[k: string]: number};
28
- megaCount?: number;
29
- zCount?: number;
30
- has: {[k: string]: number};
31
- forceResult: boolean;
32
- weaknesses: {[k: string]: number};
33
- resistances: {[k: string]: number};
34
- weather?: string;
35
- eeveeLimCount?: number;
36
- gigantamax?: boolean;
37
- }
38
- export interface BattleFactorySpecies {
39
- flags: {limEevee?: 1};
40
- sets: BattleFactorySet[];
41
- }
42
- interface BattleFactorySet {
43
- species: string;
44
- item: string;
45
- ability: string;
46
- nature: string;
47
- moves: string[];
48
- evs?: Partial<StatsTable>;
49
- ivs?: Partial<StatsTable>;
50
- }
51
- export class MoveCounter extends Utils.Multiset<string> {
52
- damagingMoves: Set<Move>;
53
- setupType: string;
54
-
55
- constructor() {
56
- super();
57
- this.damagingMoves = new Set();
58
- this.setupType = '';
59
- }
60
-
61
- get(key: string): number {
62
- return super.get(key) || 0;
63
- }
64
- }
65
-
66
- type MoveEnforcementChecker = (
67
- movePool: string[], moves: Set<string>, abilities: Set<string>, types: Set<string>,
68
- counter: MoveCounter, species: Species, teamDetails: RandomTeamsTypes.TeamDetails
69
- ) => boolean;
70
-
71
- // Moves that restore HP:
72
- const RecoveryMove = [
73
- 'healorder', 'milkdrink', 'moonlight', 'morningsun', 'recover', 'roost', 'shoreup', 'slackoff', 'softboiled', 'strengthsap', 'synthesis',
74
- ];
75
- // Moves that drop stats:
76
- const ContraryMoves = [
77
- 'closecombat', 'leafstorm', 'overheat', 'superpower', 'vcreate',
78
- ];
79
- // Moves that boost Attack:
80
- const PhysicalSetup = [
81
- 'bellydrum', 'bulkup', 'coil', 'curse', 'dragondance', 'honeclaws', 'howl', 'meditate', 'poweruppunch', 'swordsdance',
82
- ];
83
- // Moves which boost Special Attack:
84
- const SpecialSetup = [
85
- 'calmmind', 'chargebeam', 'geomancy', 'nastyplot', 'quiverdance', 'tailglow',
86
- ];
87
- // Moves that boost Attack AND Special Attack:
88
- const MixedSetup = [
89
- 'clangoroussoul', 'growth', 'happyhour', 'holdhands', 'noretreat', 'shellsmash', 'workup',
90
- ];
91
- // Some moves that only boost Speed:
92
- const SpeedSetup = [
93
- 'agility', 'autotomize', 'flamecharge', 'rockpolish',
94
- ];
95
- // Moves that shouldn't be the only STAB moves:
96
- const NoStab = [
97
- 'accelerock', 'aquajet', 'beakblast', 'bounce', 'breakingswipe', 'chatter', 'clearsmog', 'dragontail', 'eruption', 'explosion',
98
- 'fakeout', 'firstimpression', 'flamecharge', 'flipturn', 'iceshard', 'icywind', 'incinerate', 'machpunch',
99
- 'meteorbeam', 'pluck', 'pursuit', 'quickattack', 'reversal', 'selfdestruct', 'skydrop', 'snarl', 'suckerpunch', 'uturn', 'watershuriken',
100
- 'vacuumwave', 'voltswitch', 'waterspout',
101
- ];
102
- // Hazard-setting moves
103
- const Hazards = [
104
- 'spikes', 'stealthrock', 'stickyweb', 'toxicspikes',
105
- ];
106
-
107
- function sereneGraceBenefits(move: Move) {
108
- return move.secondary?.chance && move.secondary.chance >= 20 && move.secondary.chance < 100;
109
- }
110
-
111
- export class RandomTeams {
112
- dex: ModdedDex;
113
- gen: number;
114
- factoryTier: string;
115
- format: Format;
116
- prng: PRNG;
117
- noStab: string[];
118
- readonly maxTeamSize: number;
119
- readonly adjustLevel: number | null;
120
- readonly maxMoveCount: number;
121
- readonly forceMonotype: string | undefined;
122
-
123
- /**
124
- * Checkers for move enforcement based on a Pokémon's types or other factors
125
- *
126
- * returns true to reject one of its other moves to try to roll the forced move, false otherwise.
127
- */
128
- moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker};
129
-
130
- constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
131
- this.dex = dex;
132
- this.gen = this.dex.gen;
133
- this.noStab = NoStab;
134
-
135
- const ruleTable = this.dex.formats.getRuleTable(format);
136
- this.maxTeamSize = ruleTable.maxTeamSize;
137
- this.adjustLevel = ruleTable.adjustLevel;
138
- this.maxMoveCount = ruleTable.maxMoveCount;
139
- const forceMonotype = ruleTable.valueRules.get('forcemonotype');
140
- this.forceMonotype = forceMonotype && this.dex.types.get(forceMonotype).exists ?
141
- this.dex.types.get(forceMonotype).name : undefined;
142
-
143
- this.factoryTier = '';
144
- this.format = format;
145
- this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
146
-
147
- this.moveEnforcementCheckers = {
148
- screens: (movePool, moves, abilities, types, counter, species, teamDetails) => {
149
- if (teamDetails.screens) return false;
150
- return (
151
- (moves.has('lightscreen') && movePool.includes('reflect')) ||
152
- (moves.has('reflect') && movePool.includes('lightscreen'))
153
- );
154
- },
155
- recovery: (movePool, moves, abilities, types, counter, species, teamDetails) => (
156
- !!counter.get('Status') &&
157
- !counter.setupType &&
158
- ['morningsun', 'recover', 'roost', 'slackoff', 'softboiled'].some(moveid => movePool.includes(moveid)) &&
159
- ['healingwish', 'switcheroo', 'trick', 'trickroom'].every(moveid => !moves.has(moveid))
160
- ),
161
- misc: (movePool, moves, abilities, types, counter, species, teamDetails) => {
162
- if (movePool.includes('milkdrink') || movePool.includes('quiverdance')) return true;
163
- return movePool.includes('stickyweb') && !counter.setupType && !teamDetails.stickyWeb;
164
- },
165
- lead: (movePool, moves, abilities, types, counter) => (
166
- movePool.includes('stealthrock') &&
167
- !!counter.get('Status') &&
168
- !counter.setupType &&
169
- !counter.get('speedsetup') &&
170
- !moves.has('substitute')
171
- ),
172
- leechseed: (movePool, moves) => (
173
- !moves.has('calmmind') &&
174
- ['protect', 'substitute', 'spikyshield'].some(m => movePool.includes(m))
175
- ),
176
- Bug: (movePool) => movePool.includes('megahorn'),
177
- Dark: (movePool, moves, abilities, types, counter) => {
178
- if (!counter.get('Dark')) return true;
179
- return moves.has('suckerpunch') && (movePool.includes('knockoff') || movePool.includes('wickedblow'));
180
- },
181
- Dragon: (movePool, moves, abilities, types, counter) => (
182
- !counter.get('Dragon') &&
183
- !moves.has('dragonascent') &&
184
- !moves.has('substitute') &&
185
- !(moves.has('rest') && moves.has('sleeptalk'))
186
- ),
187
- Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric') || movePool.includes('thunder'),
188
- Fairy: (movePool, moves, abilities, types, counter) => (
189
- !counter.get('Fairy') &&
190
- ['dazzlinggleam', 'moonblast', 'fleurcannon', 'playrough', 'strangesteam'].some(moveid => movePool.includes(moveid))
191
- ),
192
- Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting') || !counter.get('stab'),
193
- Fire: (movePool, moves, abilities, types, counter, species) => {
194
- // Entei should never reject Extreme Speed even if Flare Blitz could be rolled instead
195
- const enteiException = moves.has('extremespeed') && species.id === 'entei';
196
- return !moves.has('bellydrum') && (!counter.get('Fire') || (!enteiException && movePool.includes('flareblitz')));
197
- },
198
- Flying: (movePool, moves, abilities, types, counter) => (
199
- !counter.get('Flying') && !types.has('Dragon') && [
200
- 'airslash', 'bravebird', 'dualwingbeat', 'oblivionwing',
201
- ].some(moveid => movePool.includes(moveid))
202
- ),
203
- Ghost: (movePool, moves, abilities, types, counter) => {
204
- if (moves.has('nightshade')) return false;
205
- if (!counter.get('Ghost') && !types.has('Dark')) return true;
206
- if (movePool.includes('poltergeist')) return true;
207
- return movePool.includes('spectralthief') && !counter.get('Dark');
208
- },
209
- Grass: (movePool, moves, abilities, types, counter, species) => {
210
- if (movePool.includes('leafstorm') || movePool.includes('grassyglide')) return true;
211
- return !counter.get('Grass') && species.baseStats.atk >= 100;
212
- },
213
- Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'),
214
- Ice: (movePool, moves, abilities, types, counter) => {
215
- if (!counter.get('Ice')) return true;
216
- if (movePool.includes('iciclecrash')) return true;
217
- return abilities.has('Snow Warning') && movePool.includes('blizzard');
218
- },
219
- Normal: (movePool, moves, abilities, types, counter) => (
220
- (abilities.has('Guts') && movePool.includes('facade')) || (abilities.has('Pixilate') && !counter.get('Normal'))
221
- ),
222
- Poison: (movePool, moves, abilities, types, counter) => {
223
- if (counter.get('Poison')) return false;
224
- return types.has('Ground') || types.has('Psychic') || types.has('Grass') || !!counter.setupType || movePool.includes('gunkshot');
225
- },
226
- Psychic: (movePool, moves, abilities, types, counter) => {
227
- if (counter.get('Psychic')) return false;
228
- if (types.has('Ghost') || types.has('Steel')) return false;
229
- return abilities.has('Psychic Surge') || !!counter.setupType || movePool.includes('psychicfangs');
230
- },
231
- Rock: (movePool, moves, abilities, types, counter, species) => !counter.get('Rock') && species.baseStats.atk >= 80,
232
- Steel: (movePool, moves, abilities, types, counter, species) => {
233
- if (species.baseStats.atk < 95) return false;
234
- if (movePool.includes('meteormash')) return true;
235
- return !counter.get('Steel');
236
- },
237
- Water: (movePool, moves, abilities, types, counter, species) => {
238
- if (!counter.get('Water') && !moves.has('hypervoice')) return true;
239
- if (['hypervoice', 'liquidation', 'surgingstrikes'].some(m => movePool.includes(m))) return true;
240
- return abilities.has('Huge Power') && movePool.includes('aquajet');
241
- },
242
- };
243
- }
244
-
245
- setSeed(prng?: PRNG | PRNGSeed) {
246
- this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
247
- }
248
-
249
- getTeam(options?: PlayerOptions | null): PokemonSet[] {
250
- const generatorName = (
251
- typeof this.format.team === 'string' && this.format.team.startsWith('random')
252
- ) ? this.format.team + 'Team' : '';
253
- // @ts-ignore
254
- return this[generatorName || 'randomTeam'](options);
255
- }
256
-
257
- randomChance(numerator: number, denominator: number) {
258
- return this.prng.randomChance(numerator, denominator);
259
- }
260
-
261
- sample<T>(items: readonly T[]): T {
262
- return this.prng.sample(items);
263
- }
264
-
265
- sampleIfArray<T>(item: T | T[]): T {
266
- if (Array.isArray(item)) {
267
- return this.sample(item);
268
- }
269
- return item;
270
- }
271
-
272
- random(m?: number, n?: number) {
273
- return this.prng.next(m, n);
274
- }
275
-
276
- /**
277
- * Remove an element from an unsorted array significantly faster
278
- * than .splice
279
- */
280
- fastPop(list: any[], index: number) {
281
- // If an array doesn't need to be in order, replacing the
282
- // element at the given index with the removed element
283
- // is much, much faster than using list.splice(index, 1).
284
- const length = list.length;
285
- if (index < 0 || index >= list.length) {
286
- // sanity check
287
- throw new Error(`Index ${index} out of bounds for given array`);
288
- }
289
-
290
- const element = list[index];
291
- list[index] = list[length - 1];
292
- list.pop();
293
- return element;
294
- }
295
-
296
- /**
297
- * Remove a random element from an unsorted array and return it.
298
- * Uses the battle's RNG if in a battle.
299
- */
300
- sampleNoReplace(list: any[]) {
301
- const length = list.length;
302
- if (length === 0) return null;
303
- const index = this.random(length);
304
- return this.fastPop(list, index);
305
- }
306
-
307
- /**
308
- * Removes n random elements from an unsorted array and returns them.
309
- * If n is less than the array's length, randomly removes and returns all the elements
310
- * in the array (so the returned array could have length < n).
311
- */
312
- multipleSamplesNoReplace<T>(list: T[], n: number): T[] {
313
- const samples = [];
314
- while (samples.length < n && list.length) {
315
- samples.push(this.sampleNoReplace(list));
316
- }
317
-
318
- return samples;
319
- }
320
-
321
- /**
322
- * Check if user has directly tried to ban/unban/restrict things in a custom battle.
323
- * Doesn't count bans nested inside other formats/rules.
324
- */
325
- private hasDirectCustomBanlistChanges() {
326
- if (!this.format.customRules) return false;
327
- for (const rule of this.format.customRules) {
328
- for (const banlistOperator of ['-', '+', '*']) {
329
- if (rule.startsWith(banlistOperator)) return true;
330
- }
331
- }
332
- return false;
333
- }
334
-
335
- /**
336
- * Inform user when custom bans are unsupported in a team generator.
337
- */
338
- protected enforceNoDirectCustomBanlistChanges() {
339
- if (this.hasDirectCustomBanlistChanges()) {
340
- throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
341
- }
342
- }
343
-
344
- /**
345
- * Inform user when complex bans are unsupported in a team generator.
346
- */
347
- protected enforceNoDirectComplexBans() {
348
- if (!this.format.customRules) return false;
349
- for (const rule of this.format.customRules) {
350
- if (rule.includes('+') && !rule.startsWith('+')) {
351
- throw new Error(`Complex bans are not currently supported in ${this.format.name}.`);
352
- }
353
- }
354
- }
355
-
356
- /**
357
- * Validate set element pool size is sufficient to support size requirements after simple bans.
358
- */
359
- private enforceCustomPoolSizeNoComplexBans(
360
- effectTypeName: string,
361
- basicEffectPool: BasicEffect[],
362
- requiredCount: number,
363
- requiredCountExplanation: string
364
- ) {
365
- if (basicEffectPool.length >= requiredCount) return;
366
- throw new Error(`Legal ${effectTypeName} count is insufficient to support ${requiredCountExplanation} (${basicEffectPool.length} / ${requiredCount}).`);
367
- }
368
-
369
- unrejectableMovesInSingles(move: Move) {
370
- // These moves cannot be rejected in favor of a forced move in singles
371
- return (move.category !== 'Status' || !move.flags.heal) && ![
372
- 'facade', 'leechseed', 'lightscreen', 'reflect', 'sleeptalk', 'spore', 'substitute', 'switcheroo',
373
- 'teleport', 'toxic', 'trick',
374
- ].includes(move.id);
375
- }
376
-
377
- unrejectableMovesInDoubles(move: Move) {
378
- // These moves cannot be rejected in favor of a forced move in doubles
379
- return move.id !== 'bodypress';
380
- }
381
-
382
- randomCCTeam(): RandomTeamsTypes.RandomSet[] {
383
- this.enforceNoDirectCustomBanlistChanges();
384
-
385
- const dex = this.dex;
386
- const team = [];
387
-
388
- const natures = this.dex.natures.all();
389
- const items = this.dex.items.all();
390
-
391
- const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype);
392
-
393
- for (let forme of randomN) {
394
- let species = dex.species.get(forme);
395
- if (species.isNonstandard) species = dex.species.get(species.baseSpecies);
396
-
397
- // Random legal item
398
- let item = '';
399
- if (this.gen >= 2) {
400
- do {
401
- item = this.sample(items).name;
402
- } while (this.dex.items.get(item).gen > this.gen || this.dex.items.get(item).isNonstandard);
403
- }
404
-
405
- // Make sure forme is legal
406
- if (species.battleOnly) {
407
- if (typeof species.battleOnly === 'string') {
408
- species = dex.species.get(species.battleOnly);
409
- } else {
410
- species = dex.species.get(this.sample(species.battleOnly));
411
- }
412
- forme = species.name;
413
- } else if (species.requiredItems && !species.requiredItems.some(req => toID(req) === item)) {
414
- if (!species.changesFrom) throw new Error(`${species.name} needs a changesFrom value`);
415
- species = dex.species.get(species.changesFrom);
416
- forme = species.name;
417
- }
418
-
419
- // Make sure that a base forme does not hold any forme-modifier items.
420
- let itemData = this.dex.items.get(item);
421
- if (itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies) {
422
- do {
423
- itemData = this.sample(items);
424
- item = itemData.name;
425
- } while (
426
- itemData.gen > this.gen ||
427
- itemData.isNonstandard ||
428
- (itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies)
429
- );
430
- }
431
-
432
- // Random legal ability
433
- const abilities = Object.values(species.abilities).filter(a => this.dex.abilities.get(a).gen <= this.gen);
434
- const ability: string = this.gen <= 2 ? 'No Ability' : this.sample(abilities);
435
-
436
- // Four random unique moves from the movepool
437
- let pool = ['struggle'];
438
- if (forme === 'Smeargle') {
439
- pool = this.dex.moves
440
- .all()
441
- .filter(move => !(move.isNonstandard || move.isZ || move.isMax || move.realMove))
442
- .map(m => m.id);
443
- } else {
444
- const formes = ['gastrodoneast', 'pumpkaboosuper', 'zygarde10'];
445
- let learnset = this.dex.species.getLearnset(species.id);
446
- if (formes.includes(species.id) || !learnset) {
447
- learnset = this.dex.species.getLearnset(this.dex.species.get(species.baseSpecies).id);
448
- }
449
- if (learnset) {
450
- pool = Object.keys(learnset).filter(
451
- moveid => learnset![moveid].find(learned => learned.startsWith(String(this.gen)))
452
- );
453
- }
454
- if (species.changesFrom) {
455
- learnset = this.dex.species.getLearnset(toID(species.changesFrom));
456
- const basePool = Object.keys(learnset!).filter(
457
- moveid => learnset![moveid].find(learned => learned.startsWith(String(this.gen)))
458
- );
459
- pool = [...new Set(pool.concat(basePool))];
460
- }
461
- }
462
-
463
- const moves = this.multipleSamplesNoReplace(pool, this.maxMoveCount);
464
-
465
- // Random EVs
466
- const evs: StatsTable = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
467
- const s: StatID[] = ["hp", "atk", "def", "spa", "spd", "spe"];
468
- let evpool = 510;
469
- do {
470
- const x = this.sample(s);
471
- const y = this.random(Math.min(256 - evs[x], evpool + 1));
472
- evs[x] += y;
473
- evpool -= y;
474
- } while (evpool > 0);
475
-
476
- // Random IVs
477
- const ivs = {
478
- hp: this.random(32),
479
- atk: this.random(32),
480
- def: this.random(32),
481
- spa: this.random(32),
482
- spd: this.random(32),
483
- spe: this.random(32),
484
- };
485
-
486
- // Random nature
487
- const nature = this.sample(natures).name;
488
-
489
- // Level balance--calculate directly from stats rather than using some silly lookup table
490
- const mbstmin = 1307; // Sunkern has the lowest modified base stat total, and that total is 807
491
-
492
- let stats = species.baseStats;
493
- // If Wishiwashi, use the school-forme's much higher stats
494
- if (species.baseSpecies === 'Wishiwashi') stats = this.dex.species.get('wishiwashischool').baseStats;
495
-
496
- // Modified base stat total assumes 31 IVs, 85 EVs in every stat
497
- let mbst = (stats["hp"] * 2 + 31 + 21 + 100) + 10;
498
- mbst += (stats["atk"] * 2 + 31 + 21 + 100) + 5;
499
- mbst += (stats["def"] * 2 + 31 + 21 + 100) + 5;
500
- mbst += (stats["spa"] * 2 + 31 + 21 + 100) + 5;
501
- mbst += (stats["spd"] * 2 + 31 + 21 + 100) + 5;
502
- mbst += (stats["spe"] * 2 + 31 + 21 + 100) + 5;
503
-
504
- let level;
505
- if (this.adjustLevel) {
506
- level = this.adjustLevel;
507
- } else {
508
- level = Math.floor(100 * mbstmin / mbst); // Initial level guess will underestimate
509
-
510
- while (level < 100) {
511
- mbst = Math.floor((stats["hp"] * 2 + 31 + 21 + 100) * level / 100 + 10);
512
- // Since damage is roughly proportional to level
513
- mbst += Math.floor(((stats["atk"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
514
- mbst += Math.floor((stats["def"] * 2 + 31 + 21 + 100) * level / 100 + 5);
515
- mbst += Math.floor(((stats["spa"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
516
- mbst += Math.floor((stats["spd"] * 2 + 31 + 21 + 100) * level / 100 + 5);
517
- mbst += Math.floor((stats["spe"] * 2 + 31 + 21 + 100) * level / 100 + 5);
518
-
519
- if (mbst >= mbstmin) break;
520
- level++;
521
- }
522
- }
523
-
524
- // Random happiness
525
- const happiness = this.random(256);
526
-
527
- // Random shininess
528
- const shiny = this.randomChance(1, 1024);
529
-
530
- team.push({
531
- name: species.baseSpecies,
532
- species: species.name,
533
- gender: species.gender,
534
- item,
535
- ability,
536
- moves,
537
- evs,
538
- ivs,
539
- nature,
540
- level,
541
- happiness,
542
- shiny,
543
- });
544
- }
545
-
546
- return team;
547
- }
548
-
549
- randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: Dex.RuleTable) {
550
- // Picks `n` random pokemon--no repeats, even among formes
551
- // Also need to either normalize for formes or select formes at random
552
- // Unreleased are okay but no CAP
553
- const last = [0, 151, 251, 386, 493, 649, 721, 807, 890][this.gen];
554
-
555
- if (n <= 0 || n > last) throw new Error(`n must be a number between 1 and ${last} (got ${n})`);
556
- if (requiredType && !this.dex.types.get(requiredType).exists) {
557
- throw new Error(`"${requiredType}" is not a valid type.`);
558
- }
559
-
560
- const isNotCustom = !ruleTable;
561
-
562
- const pool: number[] = [];
563
- let speciesPool: Species[] = [];
564
- if (isNotCustom) {
565
- speciesPool = [...this.dex.species.all()];
566
- for (const species of speciesPool) {
567
- if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
568
- if (requiredType && !species.types.includes(requiredType)) continue;
569
- if (minSourceGen && species.gen < minSourceGen) continue;
570
- const num = species.num;
571
- if (num <= 0 || pool.includes(num)) continue;
572
- if (num > last) break;
573
- pool.push(num);
574
- }
575
- } else {
576
- const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
577
- const nonexistentBanReason = ruleTable.check('nonexistent');
578
- // Assume tierSpecies does not differ from species here (mega formes can be used without their stone, etc)
579
- for (const species of this.dex.species.all()) {
580
- if (requiredType && !species.types.includes(requiredType)) continue;
581
-
582
- let banReason = ruleTable.check('pokemon:' + species.id);
583
- if (banReason) continue;
584
- if (banReason !== '') {
585
- if (species.isMega && ruleTable.check('pokemontag:mega')) continue;
586
-
587
- banReason = ruleTable.check('basepokemon:' + toID(species.baseSpecies));
588
- if (banReason) continue;
589
- if (banReason !== '' || this.dex.species.get(species.baseSpecies).isNonstandard !== species.isNonstandard) {
590
- const nonexistentCheck = Tags.nonexistent.genericFilter!(species) && nonexistentBanReason;
591
- let tagWhitelisted = false;
592
- let tagBlacklisted = false;
593
- for (const ruleid of ruleTable.tagRules) {
594
- if (ruleid.startsWith('*')) continue;
595
- const tagid = ruleid.slice(12);
596
- const tag = Tags[tagid];
597
- if ((tag.speciesFilter || tag.genericFilter)!(species)) {
598
- const existenceTag = EXISTENCE_TAG.includes(tagid);
599
- if (ruleid.startsWith('+')) {
600
- if (!existenceTag && nonexistentCheck) continue;
601
- tagWhitelisted = true;
602
- break;
603
- }
604
- tagBlacklisted = true;
605
- break;
606
- }
607
- }
608
- if (tagBlacklisted) continue;
609
- if (!tagWhitelisted) {
610
- if (ruleTable.check('pokemontag:allpokemon')) continue;
611
- }
612
- }
613
- }
614
- speciesPool.push(species);
615
- const num = species.num;
616
- if (pool.includes(num)) continue;
617
- pool.push(num);
618
- }
619
- }
620
-
621
- const hasDexNumber: {[k: string]: number} = {};
622
- for (let i = 0; i < n; i++) {
623
- const num = this.sampleNoReplace(pool);
624
- hasDexNumber[num] = i;
625
- }
626
-
627
- const formes: string[][] = [];
628
- for (const species of speciesPool) {
629
- if (!(species.num in hasDexNumber)) continue;
630
- if (isNotCustom && (species.gen > this.gen ||
631
- (species.isNonstandard && species.isNonstandard !== 'Unobtainable'))) continue;
632
- if (!formes[hasDexNumber[species.num]]) formes[hasDexNumber[species.num]] = [];
633
- formes[hasDexNumber[species.num]].push(species.name);
634
- }
635
-
636
- if (formes.length < n) {
637
- throw new Error(`Legal Pokemon forme count insufficient to support Max Team Size: (${formes.length} / ${n}).`);
638
- }
639
-
640
- const nPokemon = [];
641
- for (let i = 0; i < n; i++) {
642
- if (!formes[i].length) {
643
- throw new Error(`Invalid pokemon gen ${this.gen}: ${JSON.stringify(formes)} numbers ${JSON.stringify(hasDexNumber)}`);
644
- }
645
- nPokemon.push(this.sample(formes[i]));
646
- }
647
- return nPokemon;
648
- }
649
-
650
- randomHCTeam(): PokemonSet[] {
651
- const hasCustomBans = this.hasDirectCustomBanlistChanges();
652
- const ruleTable = this.dex.formats.getRuleTable(this.format);
653
- const hasNonexistentBan = hasCustomBans && ruleTable.check('nonexistent');
654
- const hasNonexistentWhitelist = hasCustomBans && (hasNonexistentBan === '');
655
-
656
- if (hasCustomBans) {
657
- this.enforceNoDirectComplexBans();
658
- }
659
-
660
- // Item Pool
661
- const doItemsExist = this.gen > 1;
662
- let itemPool: Item[] = [];
663
- if (doItemsExist) {
664
- if (!hasCustomBans) {
665
- itemPool = [...this.dex.items.all()].filter(item => (item.gen <= this.gen && !item.isNonstandard));
666
- } else {
667
- const hasAllItemsBan = ruleTable.check('pokemontag:allitems');
668
- for (const item of this.dex.items.all()) {
669
- let banReason = ruleTable.check('item:' + item.id);
670
- if (banReason) continue;
671
- if (banReason !== '' && item.id) {
672
- if (hasAllItemsBan) continue;
673
- if (item.isNonstandard) {
674
- banReason = ruleTable.check('pokemontag:' + toID(item.isNonstandard));
675
- if (banReason) continue;
676
- if (banReason !== '' && item.isNonstandard !== 'Unobtainable') {
677
- if (hasNonexistentBan) continue;
678
- if (!hasNonexistentWhitelist) continue;
679
- }
680
- }
681
- }
682
- itemPool.push(item);
683
- }
684
- if (ruleTable.check('item:noitem')) {
685
- this.enforceCustomPoolSizeNoComplexBans('item', itemPool, this.maxTeamSize, 'Max Team Size');
686
- }
687
- }
688
- }
689
-
690
- // Ability Pool
691
- const doAbilitiesExist = (this.gen > 2) && (this.dex.currentMod !== 'gen7letsgo');
692
- let abilityPool: Ability[] = [];
693
- if (doAbilitiesExist) {
694
- if (!hasCustomBans) {
695
- abilityPool = [...this.dex.abilities.all()].filter(ability => (ability.gen <= this.gen && !ability.isNonstandard));
696
- } else {
697
- const hasAllAbilitiesBan = ruleTable.check('pokemontag:allabilities');
698
- for (const ability of this.dex.abilities.all()) {
699
- let banReason = ruleTable.check('ability:' + ability.id);
700
- if (banReason) continue;
701
- if (banReason !== '') {
702
- if (hasAllAbilitiesBan) continue;
703
- if (ability.isNonstandard) {
704
- banReason = ruleTable.check('pokemontag:' + toID(ability.isNonstandard));
705
- if (banReason) continue;
706
- if (banReason !== '') {
707
- if (hasNonexistentBan) continue;
708
- if (!hasNonexistentWhitelist) continue;
709
- }
710
- }
711
- }
712
- abilityPool.push(ability);
713
- }
714
- if (ruleTable.check('ability:noability')) {
715
- this.enforceCustomPoolSizeNoComplexBans('ability', abilityPool, this.maxTeamSize, 'Max Team Size');
716
- }
717
- }
718
- }
719
-
720
- // Move Pool
721
- const setMoveCount = ruleTable.maxMoveCount;
722
- let movePool: Move[] = [];
723
- if (!hasCustomBans) {
724
- movePool = [...this.dex.moves.all()].filter(move =>
725
- (move.gen <= this.gen && !move.isNonstandard && !move.name.startsWith('Hidden Power ')));
726
- } else {
727
- const hasAllMovesBan = ruleTable.check('pokemontag:allmoves');
728
- for (const move of this.dex.moves.all()) {
729
- // Legality of specific HP types can't be altered in built formats anyway
730
- if (move.name.startsWith('Hidden Power ')) continue;
731
- let banReason = ruleTable.check('move:' + move.id);
732
- if (banReason) continue;
733
- if (banReason !== '') {
734
- if (hasAllMovesBan) continue;
735
- if (move.isNonstandard) {
736
- banReason = ruleTable.check('pokemontag:' + toID(move.isNonstandard));
737
- if (banReason) continue;
738
- if (banReason !== '' && move.isNonstandard !== 'Unobtainable') {
739
- if (hasNonexistentBan) continue;
740
- if (!hasNonexistentWhitelist) continue;
741
- }
742
- }
743
- }
744
- movePool.push(move);
745
- }
746
- this.enforceCustomPoolSizeNoComplexBans('move', movePool, this.maxTeamSize * setMoveCount, 'Max Team Size * Max Move Count');
747
- }
748
-
749
- // Nature Pool
750
- const doNaturesExist = this.gen > 2;
751
- let naturePool: Nature[] = [];
752
- if (doNaturesExist) {
753
- if (!hasCustomBans) {
754
- if (!hasCustomBans) {
755
- naturePool = [...this.dex.natures.all()];
756
- } else {
757
- const hasAllNaturesBan = ruleTable.check('pokemontag:allnatures');
758
- for (const nature of this.dex.natures.all()) {
759
- let banReason = ruleTable.check('nature:' + nature.id);
760
- if (banReason) continue;
761
- if (banReason !== '' && nature.id) {
762
- if (hasAllNaturesBan) continue;
763
- if (nature.isNonstandard) {
764
- banReason = ruleTable.check('pokemontag:' + toID(nature.isNonstandard));
765
- if (banReason) continue;
766
- if (banReason !== '' && nature.isNonstandard !== 'Unobtainable') {
767
- if (hasNonexistentBan) continue;
768
- if (!hasNonexistentWhitelist) continue;
769
- }
770
- }
771
- }
772
- naturePool.push(nature);
773
- }
774
- // There is no 'nature:nonature' rule so do not constrain pool size
775
- }
776
- }
777
- }
778
-
779
- const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, undefined,
780
- hasCustomBans ? ruleTable : undefined);
781
-
782
- const team = [];
783
- for (const forme of randomN) {
784
- // Choose forme
785
- const species = this.dex.species.get(forme);
786
-
787
- // Random unique item
788
- let item = '';
789
- let itemData;
790
- if (doItemsExist) {
791
- itemData = this.sampleNoReplace(itemPool);
792
- item = itemData?.name;
793
- }
794
-
795
- // Random unique ability
796
- let ability = 'No Ability';
797
- let abilityData;
798
- if (doAbilitiesExist) {
799
- abilityData = this.sampleNoReplace(abilityPool);
800
- ability = abilityData?.name;
801
- }
802
-
803
- // Random unique moves
804
- const m = [];
805
- do {
806
- const move = this.sampleNoReplace(movePool);
807
- m.push(move.id);
808
- } while (m.length < setMoveCount);
809
-
810
- // Random EVs
811
- const evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
812
- if (this.gen === 6) {
813
- let evpool = 510;
814
- do {
815
- const x = this.sample(this.dex.stats.ids());
816
- const y = this.random(Math.min(256 - evs[x], evpool + 1));
817
- evs[x] += y;
818
- evpool -= y;
819
- } while (evpool > 0);
820
- } else {
821
- for (const x of this.dex.stats.ids()) {
822
- evs[x] = this.random(256);
823
- }
824
- }
825
-
826
- // Random IVs
827
- const ivs: StatsTable = {
828
- hp: this.random(32),
829
- atk: this.random(32),
830
- def: this.random(32),
831
- spa: this.random(32),
832
- spd: this.random(32),
833
- spe: this.random(32),
834
- };
835
-
836
- // Random nature
837
- let nature = '';
838
- if (doNaturesExist && (naturePool.length > 0)) {
839
- nature = this.sample(naturePool).name;
840
- }
841
-
842
- // Level balance
843
- const mbstmin = 1307;
844
- const stats = species.baseStats;
845
- let mbst = (stats['hp'] * 2 + 31 + 21 + 100) + 10;
846
- mbst += (stats['atk'] * 2 + 31 + 21 + 100) + 5;
847
- mbst += (stats['def'] * 2 + 31 + 21 + 100) + 5;
848
- mbst += (stats['spa'] * 2 + 31 + 21 + 100) + 5;
849
- mbst += (stats['spd'] * 2 + 31 + 21 + 100) + 5;
850
- mbst += (stats['spe'] * 2 + 31 + 21 + 100) + 5;
851
-
852
- let level;
853
- if (this.adjustLevel) {
854
- level = this.adjustLevel;
855
- } else {
856
- level = Math.floor(100 * mbstmin / mbst);
857
- while (level < 100) {
858
- mbst = Math.floor((stats['hp'] * 2 + 31 + 21 + 100) * level / 100 + 10);
859
- mbst += Math.floor(((stats['atk'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
860
- mbst += Math.floor((stats['def'] * 2 + 31 + 21 + 100) * level / 100 + 5);
861
- mbst += Math.floor(((stats['spa'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
862
- mbst += Math.floor((stats['spd'] * 2 + 31 + 21 + 100) * level / 100 + 5);
863
- mbst += Math.floor((stats['spe'] * 2 + 31 + 21 + 100) * level / 100 + 5);
864
- if (mbst >= mbstmin) break;
865
- level++;
866
- }
867
- }
868
-
869
- // Random happiness
870
- const happiness = this.random(256);
871
-
872
- // Random shininess
873
- const shiny = this.randomChance(1, 1024);
874
-
875
- team.push({
876
- name: species.baseSpecies,
877
- species: species.name,
878
- gender: species.gender,
879
- item,
880
- ability,
881
- moves: m,
882
- evs,
883
- ivs,
884
- nature,
885
- level,
886
- happiness,
887
- shiny,
888
- });
889
- }
890
-
891
- return team;
892
- }
893
-
894
- queryMoves(
895
- moves: Set<string> | null,
896
- types: string[],
897
- abilities: Set<string> = new Set(),
898
- movePool: string[] = []
899
- ): MoveCounter {
900
- // This is primarily a helper function for random setbuilder functions.
901
- const counter = new MoveCounter();
902
-
903
- if (!moves?.size) return counter;
904
-
905
- const categories = {Physical: 0, Special: 0, Status: 0};
906
-
907
- // Iterate through all moves we've chosen so far and keep track of what they do:
908
- for (const moveid of moves) {
909
- let move = this.dex.moves.get(moveid);
910
- if (move.id === 'naturepower') {
911
- if (this.gen === 5) move = this.dex.moves.get('earthquake');
912
- }
913
-
914
- let moveType = move.type;
915
- if (['judgment', 'multiattack', 'revelationdance'].includes(moveid)) moveType = types[0];
916
- if (move.damage || move.damageCallback) {
917
- // Moves that do a set amount of damage:
918
- counter.add('damage');
919
- counter.damagingMoves.add(move);
920
- } else {
921
- // Are Physical/Special/Status moves:
922
- categories[move.category]++;
923
- }
924
- // Moves that have a low base power:
925
- if (moveid === 'lowkick' || (move.basePower && move.basePower <= 60 && moveid !== 'rapidspin')) {
926
- counter.add('technician');
927
- }
928
- // Moves that hit up to 5 times:
929
- if (move.multihit && Array.isArray(move.multihit) && move.multihit[1] === 5) counter.add('skilllink');
930
- if (move.recoil || move.hasCrashDamage) counter.add('recoil');
931
- if (move.drain) counter.add('drain');
932
- // Moves which have a base power, but aren't super-weak like Rapid Spin:
933
- if (move.basePower > 30 || move.multihit || move.basePowerCallback || moveid === 'infestation') {
934
- counter.add(moveType);
935
- if (types.includes(moveType)) {
936
- // STAB:
937
- // Certain moves aren't acceptable as a Pokemon's only STAB attack
938
- if (!this.noStab.includes(moveid) && (!moveid.startsWith('hiddenpower') || types.length === 1)) {
939
- counter.add('stab');
940
- // Ties between Physical and Special setup should broken in favor of STABs
941
- categories[move.category] += 0.1;
942
- }
943
- } else if (
944
- // Less obvious forms of STAB
945
- (moveType === 'Normal' && (['Aerilate', 'Galvanize', 'Pixilate', 'Refrigerate'].some(abil => abilities.has(abil)))) ||
946
- (move.priority === 0 && (abilities.has('Libero') || abilities.has('Protean')) && !this.noStab.includes(moveid)) ||
947
- (moveType === 'Steel' && abilities.has('Steelworker'))
948
- ) {
949
- counter.add('stab');
950
- }
951
-
952
- if (move.flags['bite']) counter.add('strongjaw');
953
- if (move.flags['punch']) counter.add('ironfist');
954
- if (move.flags['sound']) counter.add('sound');
955
- if (move.priority !== 0 || (moveid === 'grassyglide' && abilities.has('Grassy Surge'))) {
956
- counter.add('priority');
957
- }
958
- counter.damagingMoves.add(move);
959
- }
960
- // Moves with secondary effects:
961
- if (move.secondary) {
962
- counter.add('sheerforce');
963
- if (sereneGraceBenefits(move)) {
964
- counter.add('serenegrace');
965
- }
966
- }
967
- // Moves with low accuracy:
968
- if (move.accuracy && move.accuracy !== true && move.accuracy < 90) counter.add('inaccurate');
969
-
970
- // Moves that change stats:
971
- if (RecoveryMove.includes(moveid)) counter.add('recovery');
972
- if (ContraryMoves.includes(moveid)) counter.add('contrary');
973
- if (PhysicalSetup.includes(moveid)) {
974
- counter.add('physicalsetup');
975
- counter.setupType = 'Physical';
976
- } else if (SpecialSetup.includes(moveid)) {
977
- counter.add('specialsetup');
978
- counter.setupType = 'Special';
979
- }
980
-
981
- if (MixedSetup.includes(moveid)) counter.add('mixedsetup');
982
- if (SpeedSetup.includes(moveid)) counter.add('speedsetup');
983
- if (Hazards.includes(moveid)) counter.add('hazards');
984
- }
985
-
986
- // Keep track of the available moves
987
- for (const moveid of movePool) {
988
- const move = this.dex.moves.get(moveid);
989
- if (move.damageCallback) continue;
990
- if (move.category === 'Physical') counter.add('physicalpool');
991
- if (move.category === 'Special') counter.add('specialpool');
992
- }
993
-
994
- // Choose a setup type:
995
- if (counter.get('mixedsetup')) {
996
- counter.setupType = 'Mixed';
997
- } else if (counter.get('physicalsetup') && counter.get('specialsetup')) {
998
- const pool = {
999
- Physical: categories['Physical'] + counter.get('physicalpool'),
1000
- Special: categories['Special'] + counter.get('specialpool'),
1001
- };
1002
- if (pool.Physical === pool.Special) {
1003
- if (categories['Physical'] > categories['Special']) counter.setupType = 'Physical';
1004
- if (categories['Special'] > categories['Physical']) counter.setupType = 'Special';
1005
- } else {
1006
- counter.setupType = pool.Physical > pool.Special ? 'Physical' : 'Special';
1007
- }
1008
- } else if (counter.setupType === 'Physical') {
1009
- if (
1010
- (categories['Physical'] < 2 && (!counter.get('stab') || !counter.get('physicalpool'))) &&
1011
- !(moves.has('rest') && moves.has('sleeptalk'))
1012
- ) {
1013
- counter.setupType = '';
1014
- }
1015
- } else if (counter.setupType === 'Special') {
1016
- if (
1017
- (categories['Special'] < 2 && (!counter.get('stab') || !counter.get('specialpool'))) &&
1018
- !(moves.has('rest') && moves.has('sleeptalk')) &&
1019
- !(moves.has('wish') && moves.has('protect'))
1020
- ) {
1021
- counter.setupType = '';
1022
- }
1023
- }
1024
-
1025
- counter.set('Physical', Math.floor(categories['Physical']));
1026
- counter.set('Special', Math.floor(categories['Special']));
1027
- counter.set('Status', categories['Status']);
1028
-
1029
- return counter;
1030
- }
1031
-
1032
- shouldCullMove(
1033
- move: Move,
1034
- types: Set<string>,
1035
- moves: Set<string>,
1036
- abilities: Set<string>,
1037
- counter: MoveCounter,
1038
- movePool: string[],
1039
- teamDetails: RandomTeamsTypes.TeamDetails,
1040
- species: Species,
1041
- isLead: boolean,
1042
- isDoubles: boolean,
1043
- isNoDynamax: boolean,
1044
- ): {cull: boolean, isSetup?: boolean} {
1045
- if (isDoubles && species.baseStats.def >= 140 && movePool.includes('bodypress')) {
1046
- // In Doubles, Pokémon with Defense stats >= 140 should always have body press
1047
- return {cull: true};
1048
- }
1049
- if (
1050
- (species.id === 'doublade' && movePool.includes('swordsdance')) ||
1051
- (species.id === 'entei' && movePool.includes('extremespeed')) ||
1052
- (species.id === 'genesectdouse' && movePool.includes('technoblast')) ||
1053
- (species.id === 'golisopod' && movePool.includes('leechlife') && movePool.includes('firstimpression'))
1054
- ) {
1055
- // Entei should always have Extreme Speed, and Genesect-Douse should always have Techno Blast
1056
- // Golisopod should always have one of its bug moves (Leech Life or First Impression)
1057
- return {cull: true};
1058
- }
1059
-
1060
- const hasRestTalk = moves.has('rest') && moves.has('sleeptalk');
1061
-
1062
- // Reject moves that need support
1063
- switch (move.id) {
1064
- case 'acrobatics': case 'junglehealing':
1065
- // Special case to prevent lead Acrobatics Rillaboom
1066
- return {cull: (species.id.startsWith('rillaboom') && isLead) || (!isDoubles && !counter.setupType)};
1067
- case 'dualwingbeat': case 'fly':
1068
- return {cull: !types.has(move.type) && !counter.setupType && !!counter.get('Status')};
1069
- case 'healbell':
1070
- return {cull: movePool.includes('protect') || movePool.includes('wish')};
1071
- case 'fireblast':
1072
- // Special case for Togekiss, which always wants Aura Sphere
1073
- return {cull: abilities.has('Serene Grace') && (!moves.has('trick') || counter.get('Status') > 1)};
1074
- case 'firepunch':
1075
- // Special case for Darmanitan-Zen-Galar, which doesn't always want Fire Punch
1076
- return {cull: movePool.includes('bellydrum') || (moves.has('earthquake') && movePool.includes('substitute'))};
1077
- case 'flamecharge':
1078
- return {cull: movePool.includes('swordsdance')};
1079
- case 'hypervoice':
1080
- // Special case for Heliolisk, which always wants Thunderbolt
1081
- return {cull: types.has('Electric') && movePool.includes('thunderbolt')};
1082
- case 'payback': case 'psychocut':
1083
- // Special case for Type: Null and Malamar, which don't want these + RestTalk
1084
- return {cull: !counter.get('Status') || hasRestTalk};
1085
- case 'rest':
1086
- const bulkySetup = !moves.has('sleeptalk') && ['bulkup', 'calmmind', 'coil', 'curse'].some(m => movePool.includes(m));
1087
- // Registeel would otherwise get Curse sets without Rest, which are very bad generally
1088
- return {cull: species.id !== 'registeel' && (movePool.includes('sleeptalk') || bulkySetup)};
1089
- case 'sleeptalk':
1090
- if (!moves.has('rest')) return {cull: true};
1091
- if (movePool.length > 1 && !abilities.has('Contrary')) {
1092
- const rest = movePool.indexOf('rest');
1093
- if (rest >= 0) this.fastPop(movePool, rest);
1094
- }
1095
- break;
1096
- case 'storedpower':
1097
- return {cull: !counter.setupType};
1098
- case 'switcheroo': case 'trick':
1099
- return {cull: counter.get('Physical') + counter.get('Special') < 3 || moves.has('rapidspin')};
1100
- case 'trickroom':
1101
- const webs = !!teamDetails.stickyWeb;
1102
- return {cull:
1103
- isLead || webs || !!counter.get('speedsetup') ||
1104
- counter.damagingMoves.size < 2 || movePool.includes('nastyplot'),
1105
- };
1106
- case 'zenheadbutt':
1107
- // Special case for Victini, which should prefer Bolt Strike to Zen Headbutt
1108
- return {cull: movePool.includes('boltstrike') || (species.id === 'eiscue' && moves.has('substitute'))};
1109
-
1110
- // Set up once and only if we have the moves for it
1111
- case 'bellydrum': case 'bulkup': case 'coil': case 'curse': case 'dragondance': case 'honeclaws': case 'swordsdance':
1112
- if (counter.setupType !== 'Physical') return {cull: true}; // if we're not setting up physically this is pointless
1113
- if (counter.get('Physical') + counter.get('physicalpool') < 2 && !hasRestTalk) return {cull: true};
1114
-
1115
- // First Impression + setup is undesirable in Doubles
1116
- if (isDoubles && moves.has('firstimpression')) return {cull: true};
1117
- if (move.id === 'swordsdance' && moves.has('dragondance')) return {cull: true}; // Dragon Dance is judged as better
1118
-
1119
- return {cull: false, isSetup: true};
1120
- case 'calmmind': case 'nastyplot':
1121
- if (species.id === 'togekiss') return {cull: false};
1122
- if (counter.setupType !== 'Special') return {cull: true};
1123
- if (
1124
- (counter.get('Special') + counter.get('specialpool')) < 2 &&
1125
- !hasRestTalk &&
1126
- !(moves.has('wish') && moves.has('protect'))
1127
- ) return {cull: true};
1128
- if (moves.has('healpulse') || move.id === 'calmmind' && moves.has('trickroom')) return {cull: true};
1129
- return {cull: false, isSetup: true};
1130
- case 'quiverdance':
1131
- return {cull: false, isSetup: true};
1132
- case 'clangoroussoul': case 'shellsmash': case 'workup':
1133
- if (counter.setupType !== 'Mixed') return {cull: true};
1134
- if (counter.damagingMoves.size + counter.get('physicalpool') + counter.get('specialpool') < 2) return {cull: true};
1135
- return {cull: false, isSetup: true};
1136
- case 'agility': case 'autotomize': case 'rockpolish': case 'shiftgear':
1137
- if (counter.damagingMoves.size < 2 || moves.has('rest')) return {cull: true};
1138
- if (movePool.includes('calmmind') || movePool.includes('nastyplot')) return {cull: true};
1139
- return {cull: false, isSetup: !counter.setupType};
1140
-
1141
- // Bad after setup
1142
- case 'coaching': case 'counter': case 'reversal':
1143
- // Counter: special case for Alakazam, which doesn't want Counter + Nasty Plot
1144
- return {cull: !!counter.setupType};
1145
- case 'bulletpunch': case 'extremespeed': case 'rockblast':
1146
- return {cull: (
1147
- !!counter.get('speedsetup') ||
1148
- (!isDoubles && moves.has('dragondance')) ||
1149
- counter.damagingMoves.size < 2
1150
- )};
1151
- case 'closecombat': case 'flashcannon': case 'pollenpuff':
1152
- const substituteCullCondition = (
1153
- (moves.has('substitute') && !types.has('Fighting')) ||
1154
- (moves.has('toxic') && movePool.includes('substitute'))
1155
- );
1156
- const preferHJKOverCCCullCondition = (
1157
- move.id === 'closecombat' &&
1158
- !counter.setupType &&
1159
- (moves.has('highjumpkick') || movePool.includes('highjumpkick'))
1160
- );
1161
- return {cull: substituteCullCondition || preferHJKOverCCCullCondition};
1162
- case 'defog':
1163
- return {cull: !!counter.setupType || moves.has('healbell') || moves.has('toxicspikes') || !!teamDetails.defog};
1164
- case 'fakeout':
1165
- return {cull: !!counter.setupType || ['protect', 'rapidspin', 'substitute', 'uturn'].some(m => moves.has(m))};
1166
- case 'firstimpression': case 'glare': case 'icywind': case 'tailwind': case 'waterspout':
1167
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('rest')};
1168
- case 'healingwish': case 'memento':
1169
- return {cull: !!counter.setupType || !!counter.get('recovery') || moves.has('substitute') || moves.has('uturn')};
1170
- case 'highjumpkick':
1171
- // Special case for Hitmonlee to prevent non-Unburden Curse
1172
- return {cull: moves.has('curse')};
1173
- case 'partingshot':
1174
- return {cull: !!counter.get('speedsetup') || moves.has('bulkup') || moves.has('uturn')};
1175
- case 'protect':
1176
- if (!isDoubles && ((counter.setupType && !moves.has('wish')) || moves.has('rest'))) return {cull: true};
1177
- if (
1178
- !isDoubles &&
1179
- counter.get('Status') < 2 &&
1180
- ['Hunger Switch', 'Speed Boost', 'Moody'].every(m => !abilities.has(m))
1181
- ) return {cull: true};
1182
- if (movePool.includes('leechseed') || (movePool.includes('toxic') && !moves.has('wish'))) return {cull: true};
1183
- if (isDoubles && (
1184
- ['bellydrum', 'fakeout', 'shellsmash', 'spore'].some(m => movePool.includes(m)) ||
1185
- moves.has('tailwind') || moves.has('waterspout') || counter.get('recovery')
1186
- )) return {cull: true};
1187
- return {cull: false};
1188
- case 'rapidspin':
1189
- const setup = ['curse', 'nastyplot', 'shellsmash'].some(m => moves.has(m));
1190
- return {cull: !!teamDetails.rapidSpin || setup || (!!counter.setupType && counter.get('Fighting') >= 2)};
1191
- case 'shadowsneak':
1192
- const sneakIncompatible = ['substitute', 'trickroom', 'dualwingbeat', 'toxic'].some(m => moves.has(m));
1193
- return {cull: hasRestTalk || sneakIncompatible || counter.setupType === 'Special'};
1194
- case 'spikes':
1195
- return {cull: !!counter.setupType || (!!teamDetails.spikes && teamDetails.spikes > 1)};
1196
- case 'stealthrock':
1197
- return {cull:
1198
- !!counter.setupType ||
1199
- !!counter.get('speedsetup') ||
1200
- !!teamDetails.stealthRock ||
1201
- ['rest', 'substitute', 'trickroom', 'teleport'].some(m => moves.has(m)) ||
1202
- (species.id === 'palossand' && movePool.includes('shoreup')),
1203
- };
1204
- case 'stickyweb':
1205
- return {cull: counter.setupType === 'Special' || !!teamDetails.stickyWeb};
1206
- case 'taunt':
1207
- return {cull: moves.has('encore') || moves.has('nastyplot') || moves.has('swordsdance')};
1208
- case 'thunderwave': case 'voltswitch':
1209
- const cullInDoubles = isDoubles && (moves.has('electroweb') || moves.has('nuzzle'));
1210
- return {cull: (
1211
- !!counter.setupType ||
1212
- !!counter.get('speedsetup') ||
1213
- moves.has('shiftgear') ||
1214
- moves.has('raindance') ||
1215
- cullInDoubles
1216
- )};
1217
- case 'toxic':
1218
- return {cull: !!counter.setupType || ['sludgewave', 'thunderwave', 'willowisp'].some(m => moves.has(m))};
1219
- case 'toxicspikes':
1220
- return {cull: !!counter.setupType || !!teamDetails.toxicSpikes};
1221
- case 'uturn':
1222
- const bugSwordsDanceCase = types.has('Bug') && counter.get('recovery') && moves.has('swordsdance');
1223
- return {cull: (
1224
- !!counter.get('speedsetup') ||
1225
- (counter.setupType && !bugSwordsDanceCase) ||
1226
- (isDoubles && moves.has('leechlife')) ||
1227
- moves.has('shiftgear')
1228
- )};
1229
-
1230
- /**
1231
- * Ineffective to have both moves together
1232
- *
1233
- * These are sorted in order of:
1234
- * Normal>Fire>Water>Electric>Grass>Ice>Fighting>Poison>Ground>Flying>Psychic>Bug>Rock>Ghost>Dragon>Dark>Fairy
1235
- * and then subsorted alphabetically.
1236
- * This type order is arbitrary and referenced from https://pokemondb.net/type.
1237
- */
1238
- case 'explosion':
1239
- // Rock Blast: Special case for Gigalith to prevent Stone Edge-less Choice Band sets
1240
- const otherMoves = ['curse', 'stompingtantrum', 'rockblast', 'painsplit', 'wish'].some(m => moves.has(m));
1241
- return {cull: !!counter.get('speedsetup') || !!counter.get('recovery') || otherMoves};
1242
- case 'facade':
1243
- // Special case for Snorlax
1244
- return {cull: movePool.includes('doubleedge')};
1245
- case 'quickattack':
1246
- // Diggersby wants U-turn on Choiced sets
1247
- const diggersbyCull = counter.get('Physical') > 3 && movePool.includes('uturn');
1248
- return {cull: !!counter.get('speedsetup') || (types.has('Rock') && !!counter.get('Status')) || diggersbyCull};
1249
- case 'blazekick':
1250
- return {cull: species.id === 'genesect' && counter.get('Special') >= 1};
1251
- case 'blueflare':
1252
- return {cull: moves.has('vcreate')};
1253
- case 'firefang': case 'flamethrower':
1254
- // Fire Fang: Special case for Garchomp, which doesn't want Fire Fang w/o Swords Dance
1255
- const otherFireMoves = ['heatwave', 'overheat'].some(m => moves.has(m));
1256
- return {cull: (moves.has('fireblast') && counter.setupType !== 'Physical') || otherFireMoves};
1257
- case 'flareblitz':
1258
- // Special case for Solgaleo to prevent Flame Charge + Flare Blitz
1259
- return {cull: species.id === 'solgaleo' && moves.has('flamecharge')};
1260
- case 'overheat':
1261
- return {cull: moves.has('flareblitz') || (isDoubles && moves.has('calmmind'))};
1262
- case 'aquatail': case 'flipturn':
1263
- return {cull: moves.has('aquajet') || !!counter.get('Status')};
1264
- case 'hydropump':
1265
- return {cull: moves.has('scald') && (
1266
- (counter.get('Special') < 4 && !moves.has('uturn')) ||
1267
- (species.types.length > 1 && counter.get('stab') < 3)
1268
- )};
1269
- case 'muddywater':
1270
- return {cull: moves.has('liquidation')};
1271
- case 'scald':
1272
- // Special case for Clawitzer
1273
- return {cull: moves.has('waterpulse')};
1274
- case 'thunderbolt':
1275
- // Special case for Goodra, which only wants one move to hit Water-types
1276
- return {cull: moves.has('powerwhip')};
1277
- case 'energyball':
1278
- // Special case to prevent Shiinotic with four Grass moves and no Moonblast
1279
- return {cull: species.id === 'shiinotic' && !moves.has('moonblast')};
1280
- case 'gigadrain':
1281
- // Celebi always wants Leaf Storm on its more pivoting-focused non-Nasty Plot sets
1282
- const celebiPreferLeafStorm = species.id === 'celebi' && !counter.setupType && moves.has('uturn');
1283
- return {cull: celebiPreferLeafStorm || (types.has('Poison') && !counter.get('Poison'))};
1284
- case 'leafblade':
1285
- // Special case for Virizion to prevent Leaf Blade on Assault Vest sets
1286
- return {cull: (moves.has('leafstorm') || movePool.includes('leafstorm')) && counter.setupType !== 'Physical'};
1287
- case 'leafstorm':
1288
- const leafBladePossible = movePool.includes('leafblade') || moves.has('leafblade');
1289
- return {cull:
1290
- // Virizion should always prefer Leaf Blade to Leaf Storm on Physical sets
1291
- (counter.setupType === 'Physical' && (species.id === 'virizion' || leafBladePossible)) ||
1292
- (moves.has('gigadrain') && !!counter.get('Status')) ||
1293
- (isDoubles && moves.has('energyball')),
1294
- };
1295
- case 'powerwhip':
1296
- // Special case for Centiskorch, which doesn't want Assault Vest
1297
- return {cull: moves.has('leechlife')};
1298
- case 'woodhammer':
1299
- return {cull: moves.has('hornleech') && counter.get('Physical') < 4};
1300
- case 'freezedry':
1301
- const betterIceMove = (
1302
- (moves.has('blizzard') && !!counter.setupType) ||
1303
- (moves.has('icebeam') && counter.get('Special') < 4)
1304
- );
1305
- const preferThunderWave = movePool.includes('thunderwave') && types.has('Electric');
1306
- return {cull: betterIceMove || preferThunderWave || movePool.includes('bodyslam')};
1307
- case 'bodypress':
1308
- // Turtonator never wants Earthquake + Body Press, and wants EQ+Smash or Press+No Smash
1309
- const turtonatorPressCull = species.id === 'turtonator' && moves.has('earthquake') && movePool.includes('shellsmash');
1310
- const pressIncompatible = ['shellsmash', 'mirrorcoat', 'whirlwind'].some(m => moves.has(m));
1311
- return {cull: turtonatorPressCull || pressIncompatible || counter.setupType === 'Special'};
1312
- case 'circlethrow':
1313
- // Part of a special case for Throh to pick one specific Fighting move depending on its set
1314
- return {cull: moves.has('stormthrow') && !moves.has('rest')};
1315
- case 'drainpunch':
1316
- return {cull: moves.has('closecombat') || (!types.has('Fighting') && movePool.includes('swordsdance'))};
1317
- case 'dynamicpunch': case 'thunderouskick':
1318
- // Dynamic Punch: Special case for Machamp to better split Guts and No Guard sets
1319
- return {cull: moves.has('closecombat') || moves.has('facade')};
1320
- case 'focusblast':
1321
- // Special cases for Blastoise and Regice; Blastoise wants Shell Smash, and Regice wants Thunderbolt
1322
- return {cull: movePool.includes('shellsmash') || hasRestTalk};
1323
- case 'hammerarm':
1324
- // Special case for Kangaskhan, which always wants Sucker Punch
1325
- return {cull: moves.has('fakeout')};
1326
- case 'stormthrow':
1327
- // Part of a special case for Throh to pick one specific Fighting move depending on its set
1328
- return {cull: hasRestTalk};
1329
- case 'superpower':
1330
- return {
1331
- cull: moves.has('hydropump') ||
1332
- (counter.get('Physical') >= 4 && movePool.includes('uturn')) ||
1333
- (moves.has('substitute') && !abilities.has('Contrary')),
1334
- isSetup: abilities.has('Contrary'),
1335
- };
1336
- case 'poisonjab':
1337
- return {cull: !types.has('Poison') && counter.get('Status') >= 2};
1338
- case 'earthquake':
1339
- const doublesCull = moves.has('earthpower') || moves.has('highhorsepower');
1340
- // Turtonator wants Body Press when it doesn't have Shell Smash
1341
- const turtQuakeCull = species.id === 'turtonator' && movePool.includes('bodypress') && movePool.includes('shellsmash');
1342
- const subToxicPossible = moves.has('substitute') && movePool.includes('toxic');
1343
- return {cull: turtQuakeCull || (isDoubles && doublesCull) || subToxicPossible || moves.has('bonemerang')};
1344
- case 'scorchingsands':
1345
- // Special cases for Ninetales and Palossand; prevents status redundancy
1346
- return {cull: (
1347
- moves.has('willowisp') ||
1348
- moves.has('earthpower') ||
1349
- (moves.has('toxic') && movePool.includes('earthpower'))
1350
- )};
1351
- case 'airslash':
1352
- return {cull:
1353
- (species.id === 'naganadel' && moves.has('nastyplot')) ||
1354
- hasRestTalk ||
1355
- (abilities.has('Simple') && !!counter.get('recovery')) ||
1356
- counter.setupType === 'Physical',
1357
- };
1358
- case 'bravebird':
1359
- // Special case for Mew, which only wants Brave Bird with Swords Dance
1360
- return {cull: moves.has('dragondance')};
1361
- case 'hurricane':
1362
- return {cull: counter.setupType === 'Physical'};
1363
- case 'futuresight':
1364
- return {cull: moves.has('psyshock') || moves.has('trick') || movePool.includes('teleport')};
1365
- case 'photongeyser':
1366
- // Special case for Necrozma-DM, which always wants Dragon Dance
1367
- return {cull: moves.has('morningsun')};
1368
- case 'psychic':
1369
- const alcremieCase = species.id === 'alcremiegmax' && counter.get('Status') < 2;
1370
- return {cull: alcremieCase || (moves.has('psyshock') && (!!counter.setupType || isDoubles))};
1371
- case 'psychicfangs':
1372
- // Special case for Morpeko, which doesn't want 4 attacks Leftovers
1373
- return {cull: moves.has('rapidspin')};
1374
- case 'psyshock':
1375
- // Special case for Sylveon which only wants Psyshock if it gets a Choice item
1376
- const sylveonCase = abilities.has('Pixilate') && counter.get('Special') < 4;
1377
- return {cull: moves.has('psychic') || (!counter.setupType && sylveonCase) || (isDoubles && moves.has('psychic'))};
1378
- case 'bugbuzz':
1379
- return {cull: moves.has('uturn') && !counter.setupType};
1380
- case 'leechlife':
1381
- return {cull:
1382
- (isDoubles && moves.has('lunge')) ||
1383
- (moves.has('uturn') && !counter.setupType) ||
1384
- movePool.includes('spikes'),
1385
- };
1386
- case 'stoneedge':
1387
- const gutsCullCondition = abilities.has('Guts') && (!moves.has('dynamicpunch') || moves.has('spikes'));
1388
- const rockSlidePlusStatusPossible = counter.get('Status') && movePool.includes('rockslide');
1389
- const otherRockMove = moves.has('rockblast') || moves.has('rockslide');
1390
- const lucarioCull = species.id === 'lucario' && !!counter.setupType;
1391
- return {cull: gutsCullCondition || (!isDoubles && rockSlidePlusStatusPossible) || otherRockMove || lucarioCull};
1392
- case 'poltergeist':
1393
- // Special case for Dhelmise in Doubles, which doesn't want both
1394
- return {cull: moves.has('knockoff')};
1395
- case 'shadowball':
1396
- return {cull:
1397
- (isDoubles && moves.has('phantomforce')) ||
1398
- // Special case for Sylveon, which never wants Shadow Ball as its only coverage move
1399
- (abilities.has('Pixilate') && (!!counter.setupType || counter.get('Status') > 1)) ||
1400
- (!types.has('Ghost') && movePool.includes('focusblast')),
1401
- };
1402
- case 'shadowclaw':
1403
- return {cull: types.has('Steel') && moves.has('shadowsneak') && counter.get('Physical') < 4};
1404
- case 'dragonpulse': case 'spacialrend':
1405
- return {cull: moves.has('dracometeor') && counter.get('Special') < 4};
1406
- case 'darkpulse':
1407
- const pulseIncompatible = ['foulplay', 'knockoff'].some(m => moves.has(m)) || (
1408
- species.id === 'shiftry' && (moves.has('defog') || moves.has('suckerpunch'))
1409
- );
1410
- // Special clause to prevent bugged Shiftry sets with Sucker Punch + Nasty Plot
1411
- const shiftryCase = movePool.includes('nastyplot') && !moves.has('defog');
1412
- return {cull: pulseIncompatible && !shiftryCase && counter.setupType !== 'Special'};
1413
- case 'suckerpunch':
1414
- return {cull:
1415
- // Shiftry in No Dynamax would otherwise get Choice Scarf Sucker Punch sometimes.
1416
- (isNoDynamax && species.id === 'shiftry' && moves.has('defog')) ||
1417
- moves.has('rest') ||
1418
- counter.damagingMoves.size < 2 ||
1419
- (counter.setupType === 'Special') ||
1420
- (counter.get('Dark') > 1 && !types.has('Dark')),
1421
- };
1422
- case 'dazzlinggleam':
1423
- return {cull: ['fleurcannon', 'moonblast', 'petaldance'].some(m => moves.has(m))};
1424
-
1425
- // Status:
1426
- case 'bodyslam': case 'clearsmog':
1427
- const toxicCullCondition = moves.has('toxic') && !types.has('Normal');
1428
- return {cull: moves.has('sludgebomb') || moves.has('trick') || movePool.includes('recover') || toxicCullCondition};
1429
- case 'haze':
1430
- // Special case for Corsola-Galar, which always wants Will-O-Wisp
1431
- return {cull: !teamDetails.stealthRock && (moves.has('stealthrock') || movePool.includes('stealthrock'))};
1432
- case 'hypnosis':
1433
- // Special case for Xurkitree to properly split Blunder Policy and Choice item sets
1434
- return {cull: moves.has('voltswitch')};
1435
- case 'willowisp': case 'yawn':
1436
- // Swords Dance is a special case for Rapidash
1437
- return {cull: moves.has('thunderwave') || moves.has('toxic') || moves.has('swordsdance')};
1438
- case 'painsplit': case 'recover': case 'synthesis':
1439
- return {cull: moves.has('rest') || moves.has('wish') || (move.id === 'synthesis' && moves.has('gigadrain'))};
1440
- case 'roost':
1441
- return {cull:
1442
- moves.has('throatchop') ||
1443
- // Hawlucha doesn't want Roost + 3 attacks
1444
- (moves.has('stoneedge') && species.id === 'hawlucha') ||
1445
- // Special cases for Salamence, Dynaless Dragonite, and Scizor to help prevent sets with poor coverage or no setup.
1446
- (moves.has('dualwingbeat') && (moves.has('outrage') || species.id === 'scizor')),
1447
- };
1448
- case 'reflect': case 'lightscreen':
1449
- return {cull: !!teamDetails.screens};
1450
- case 'slackoff':
1451
- // Special case to prevent Scaldless Slowking
1452
- return {cull: species.id === 'slowking' && !moves.has('scald')};
1453
- case 'substitute':
1454
- const moveBasedCull = ['bulkup', 'nastyplot', 'painsplit', 'roost', 'swordsdance'].some(m => movePool.includes(m));
1455
- // Smaller formes of Gourgeist in Doubles don't want Poltergeist as their only attack
1456
- const doublesGourgeist = isDoubles && movePool.includes('powerwhip');
1457
- // Calyrex wants Substitute + Leech Seed not Calm Mind + Leech Seed
1458
- const calmMindCullCondition = !counter.get('recovery') && movePool.includes('calmmind') && species.id !== 'calyrex';
1459
- // Eiscue wants to always have Liquidation and Belly Drum
1460
- const eiscue = species.id === 'eiscue' && moves.has('zenheadbutt');
1461
- return {cull: moves.has('rest') || moveBasedCull || doublesGourgeist || calmMindCullCondition || eiscue};
1462
- case 'helpinghand':
1463
- // Special case for Shuckle in Doubles, which doesn't want sets with no method to harm foes
1464
- return {cull: moves.has('acupressure')};
1465
- case 'wideguard':
1466
- return {cull: moves.has('protect')};
1467
- case 'grassknot':
1468
- // Special case for Raichu and Heliolisk
1469
- return {cull: moves.has('surf')};
1470
- case 'icepunch':
1471
- // Special case for Marshadow
1472
- return {cull: moves.has('rocktomb')};
1473
- case 'leechseed':
1474
- // Special case for Calyrex to prevent Leech Seed + Calm Mind
1475
- return {cull: !!counter.setupType};
1476
- }
1477
-
1478
- return {cull: false};
1479
- }
1480
-
1481
- shouldCullAbility(
1482
- ability: string,
1483
- types: Set<string>,
1484
- moves: Set<string>,
1485
- abilities: Set<string>,
1486
- counter: MoveCounter,
1487
- movePool: string[],
1488
- teamDetails: RandomTeamsTypes.TeamDetails,
1489
- species: Species,
1490
- isDoubles: boolean,
1491
- isNoDynamax: boolean
1492
- ): boolean {
1493
- if ([
1494
- 'Flare Boost', 'Hydration', 'Ice Body', 'Immunity', 'Innards Out', 'Insomnia', 'Misty Surge',
1495
- 'Perish Body', 'Quick Feet', 'Rain Dish', 'Snow Cloak', 'Steadfast', 'Steam Engine',
1496
- ].includes(ability)) return true;
1497
-
1498
- switch (ability) {
1499
- // Abilities which are primarily useful for certain moves
1500
- case 'Contrary': case 'Serene Grace': case 'Skill Link': case 'Strong Jaw':
1501
- return !counter.get(toID(ability));
1502
- case 'Analytic':
1503
- return (moves.has('rapidspin') || species.nfe || isDoubles);
1504
- case 'Blaze':
1505
- return (isDoubles && abilities.has('Solar Power')) || (!isDoubles && !isNoDynamax && species.id === 'charizard');
1506
- // case 'Bulletproof': case 'Overcoat':
1507
- // return !!counter.setupType;
1508
- case 'Chlorophyll':
1509
- return (species.baseStats.spe > 100 || !counter.get('Fire') && !moves.has('sunnyday') && !teamDetails.sun);
1510
- case 'Cloud Nine':
1511
- return (!isNoDynamax || species.id !== 'golduck');
1512
- case 'Competitive':
1513
- return (counter.get('Special') < 2 || (moves.has('rest') && moves.has('sleeptalk')));
1514
- case 'Compound Eyes': case 'No Guard':
1515
- return !counter.get('inaccurate');
1516
- case 'Cursed Body':
1517
- return abilities.has('Infiltrator');
1518
- case 'Defiant':
1519
- return !counter.get('Physical');
1520
- case 'Download':
1521
- return (counter.damagingMoves.size < 3 || moves.has('trick'));
1522
- case 'Early Bird':
1523
- return (types.has('Grass') && isDoubles);
1524
- case 'Flash Fire':
1525
- return (this.dex.getEffectiveness('Fire', species) < -1 || abilities.has('Drought'));
1526
- case 'Gluttony':
1527
- return !moves.has('bellydrum');
1528
- case 'Guts':
1529
- return (!moves.has('facade') && !moves.has('sleeptalk') && !species.nfe);
1530
- case 'Harvest':
1531
- return (abilities.has('Frisk') && !isDoubles);
1532
- case 'Hustle': case 'Inner Focus':
1533
- return (counter.get('Physical') < 2 || abilities.has('Iron Fist'));
1534
- case 'Infiltrator':
1535
- return (moves.has('rest') && moves.has('sleeptalk')) || (isDoubles && abilities.has('Clear Body'));
1536
- case 'Intimidate':
1537
- if (species.id === 'salamence' && moves.has('dragondance')) return true;
1538
- return ['bodyslam', 'bounce', 'tripleaxel'].some(m => moves.has(m));
1539
- case 'Iron Fist':
1540
- return (counter.get('ironfist') < 2 || moves.has('dynamicpunch'));
1541
- case 'Justified':
1542
- return (isDoubles && abilities.has('Inner Focus'));
1543
- case 'Lightning Rod':
1544
- return (species.types.includes('Ground') || (!isNoDynamax && counter.setupType === 'Physical'));
1545
- case 'Limber':
1546
- return species.types.includes('Electric') || moves.has('facade');
1547
- case 'Liquid Voice':
1548
- return !moves.has('hypervoice');
1549
- case 'Magic Guard':
1550
- // For Sigilyph
1551
- return (abilities.has('Tinted Lens') && !counter.get('Status') && !isDoubles);
1552
- case 'Mold Breaker':
1553
- return (
1554
- abilities.has('Adaptability') || abilities.has('Scrappy') || (abilities.has('Unburden') && !!counter.setupType) ||
1555
- (abilities.has('Sheer Force') && !!counter.get('sheerforce'))
1556
- );
1557
- case 'Moxie':
1558
- return (counter.get('Physical') < 2 || moves.has('stealthrock') || moves.has('defog'));
1559
- case 'Overgrow':
1560
- return !counter.get('Grass');
1561
- case 'Own Tempo':
1562
- return !moves.has('petaldance');
1563
- case 'Power Construct':
1564
- return (species.forme === '10%' && !isDoubles);
1565
- case 'Prankster':
1566
- return !counter.get('Status');
1567
- case 'Pressure':
1568
- return (!!counter.setupType || counter.get('Status') < 2 || isDoubles);
1569
- case 'Refrigerate':
1570
- return !counter.get('Normal');
1571
- case 'Regenerator':
1572
- // For Reuniclus
1573
- return abilities.has('Magic Guard');
1574
- case 'Reckless':
1575
- return !counter.get('recoil') || moves.has('curse');
1576
- case 'Rock Head':
1577
- return !counter.get('recoil');
1578
- case 'Sand Force': case 'Sand Veil':
1579
- return !teamDetails.sand;
1580
- case 'Sand Rush':
1581
- return (!teamDetails.sand && (isNoDynamax || !counter.setupType || !counter.get('Rock') || moves.has('rapidspin')));
1582
- case 'Sap Sipper':
1583
- // For Drampa, which wants Berserk with Roost
1584
- return moves.has('roost');
1585
- case 'Scrappy':
1586
- return (moves.has('earthquake') && species.id === 'miltank');
1587
- case 'Screen Cleaner':
1588
- return !!teamDetails.screens;
1589
- case 'Shed Skin':
1590
- // For Scrafty
1591
- return moves.has('dragondance');
1592
- case 'Sheer Force':
1593
- return (!counter.get('sheerforce') || abilities.has('Guts') || (species.id === 'druddigon' && !isDoubles));
1594
- case 'Shell Armor':
1595
- return (species.id === 'omastar' && (moves.has('spikes') || moves.has('stealthrock')));
1596
- case 'Slush Rush':
1597
- return (!teamDetails.hail && !abilities.has('Swift Swim'));
1598
- case 'Sniper':
1599
- // Inteleon wants Torrent unless it is Gmax
1600
- return (species.name === 'Inteleon' || (counter.get('Water') > 1 && !moves.has('focusenergy')));
1601
- case 'Solar Power':
1602
- return (isNoDynamax && !teamDetails.sun);
1603
- case 'Speed Boost':
1604
- return (isNoDynamax && species.id === 'ninjask');
1605
- case 'Steely Spirit':
1606
- return (moves.has('fakeout') && !isDoubles);
1607
- case 'Sturdy':
1608
- return (moves.has('bulkup') || !!counter.get('recoil') || (!isNoDynamax && abilities.has('Solid Rock')));
1609
- case 'Swarm':
1610
- return (!counter.get('Bug') || !!counter.get('recovery'));
1611
- case 'Sweet Veil':
1612
- return types.has('Grass');
1613
- case 'Swift Swim':
1614
- if (isNoDynamax) {
1615
- const neverWantsSwim = !moves.has('raindance') && [
1616
- 'Intimidate', 'Rock Head', 'Water Absorb',
1617
- ].some(m => abilities.has(m));
1618
- const noSwimIfNoRain = !moves.has('raindance') && [
1619
- 'Cloud Nine', 'Lightning Rod', 'Intimidate', 'Rock Head', 'Sturdy', 'Water Absorb', 'Weak Armor',
1620
- ].some(m => abilities.has(m));
1621
- return teamDetails.rain ? neverWantsSwim : noSwimIfNoRain;
1622
- }
1623
- return (!moves.has('raindance') && (
1624
- ['Intimidate', 'Rock Head', 'Slush Rush', 'Water Absorb'].some(abil => abilities.has(abil)) ||
1625
- (abilities.has('Lightning Rod') && !counter.setupType)
1626
- ));
1627
- case 'Synchronize':
1628
- return counter.get('Status') < 3;
1629
- case 'Technician':
1630
- return (
1631
- !counter.get('technician') ||
1632
- moves.has('tailslap') ||
1633
- abilities.has('Punk Rock') ||
1634
- // For Doubles Alolan Persian
1635
- movePool.includes('snarl')
1636
- );
1637
- case 'Tinted Lens':
1638
- return (
1639
- // For Sigilyph
1640
- moves.has('defog') ||
1641
- // For Butterfree
1642
- (moves.has('hurricane') && abilities.has('Compound Eyes')) ||
1643
- (counter.get('Status') > 2 && !counter.setupType)
1644
- );
1645
- case 'Torrent':
1646
- // For Inteleon-Gmax and Primarina
1647
- return (moves.has('focusenergy') || moves.has('hypervoice'));
1648
- case 'Tough Claws':
1649
- // For Perrserker
1650
- return (types.has('Steel') && !moves.has('fakeout'));
1651
- case 'Unaware':
1652
- // For Swoobat and Clefable
1653
- return (!!counter.setupType || moves.has('fireblast'));
1654
- case 'Unburden':
1655
- return (abilities.has('Prankster') || !counter.setupType && !isDoubles);
1656
- case 'Volt Absorb':
1657
- return (this.dex.getEffectiveness('Electric', species) < -1);
1658
- case 'Water Absorb':
1659
- return (
1660
- moves.has('raindance') ||
1661
- ['Drizzle', 'Strong Jaw', 'Unaware', 'Volt Absorb'].some(abil => abilities.has(abil))
1662
- );
1663
- case 'Weak Armor':
1664
- // The Speed less than 50 case is intended for Cursola, but could apply to any slow Pokémon.
1665
- return (
1666
- (!isNoDynamax && species.baseStats.spe > 50) ||
1667
- species.id === 'skarmory' ||
1668
- moves.has('shellsmash') || moves.has('rapidspin')
1669
- );
1670
- }
1671
-
1672
- return false;
1673
- }
1674
-
1675
- getHighPriorityItem(
1676
- ability: string,
1677
- types: Set<string>,
1678
- moves: Set<string>,
1679
- counter: MoveCounter,
1680
- teamDetails: RandomTeamsTypes.TeamDetails,
1681
- species: Species,
1682
- isLead: boolean,
1683
- isDoubles: boolean
1684
- ) {
1685
- // not undefined — we want "no item" not "go find a different item"
1686
- if (moves.has('acrobatics') && ability !== 'Ripen') return ability === 'Grassy Surge' ? 'Grassy Seed' : '';
1687
- if (moves.has('geomancy') || moves.has('meteorbeam')) return 'Power Herb';
1688
- if (moves.has('shellsmash')) {
1689
- if (ability === 'Sturdy' && !isLead && !isDoubles) return 'Heavy-Duty Boots';
1690
- // Shell Smash + Solid Rock is intended for Carracosta, but I think
1691
- // any Pokémon which can take a SE hit via Solid Rock deserves to have
1692
- // its Shell Smash considered a good enough speed setup move for WP.
1693
- if (ability === 'Solid Rock') return 'Weakness Policy';
1694
- return 'White Herb';
1695
- }
1696
- // Techno Blast should always be Water-type
1697
- if (moves.has('technoblast')) return 'Douse Drive';
1698
- // Species-specific logic
1699
- if (
1700
- ['Corsola', 'Garchomp', 'Tangrowth'].includes(species.name) &&
1701
- counter.get('Status') &&
1702
- !counter.setupType &&
1703
- !isDoubles
1704
- ) return 'Rocky Helmet';
1705
-
1706
- if (species.name === 'Eternatus' && counter.get('Status') < 2) return 'Metronome';
1707
- if (species.name === 'Farfetch\u2019d') return 'Leek';
1708
- if (species.name === 'Froslass' && !isDoubles) return 'Wide Lens';
1709
- if (species.name === 'Latios' && counter.get('Special') === 2 && !isDoubles) return 'Soul Dew';
1710
- if (species.name === 'Lopunny') return isDoubles ? 'Iron Ball' : 'Toxic Orb';
1711
- if (species.baseSpecies === 'Marowak') return 'Thick Club';
1712
- if (species.baseSpecies === 'Pikachu') return 'Light Ball';
1713
- if (species.name === 'Regieleki' && !isDoubles) return 'Magnet';
1714
- if (species.name === 'Shedinja') {
1715
- const noSash = !teamDetails.defog && !teamDetails.rapidSpin && !isDoubles;
1716
- return noSash ? 'Heavy-Duty Boots' : 'Focus Sash';
1717
- }
1718
- if (species.name === 'Shuckle' && moves.has('stickyweb')) return 'Mental Herb';
1719
- if (species.name === 'Unfezant' || moves.has('focusenergy')) return 'Scope Lens';
1720
- if (species.name === 'Pincurchin') return 'Shuca Berry';
1721
- if (species.name === 'Wobbuffet' && moves.has('destinybond')) return 'Custap Berry';
1722
- if (species.name === 'Scyther' && counter.damagingMoves.size > 3) return 'Choice Band';
1723
- if (species.name === 'Cinccino' && !moves.has('uturn')) return 'Life Orb';
1724
- if (moves.has('bellydrum') && moves.has('substitute')) return 'Salac Berry';
1725
-
1726
- // Misc item generation logic
1727
- const HDBBetterThanEviolite = (
1728
- !isLead &&
1729
- this.dex.getEffectiveness('Rock', species) >= 2 &&
1730
- !isDoubles
1731
- );
1732
- if (species.nfe && !HDBBetterThanEviolite) return 'Eviolite';
1733
-
1734
- // Ability based logic and miscellaneous logic
1735
- if (species.name === 'Wobbuffet' || ['Cheek Pouch', 'Harvest', 'Ripen'].includes(ability)) return 'Sitrus Berry';
1736
- if (ability === 'Gluttony') return this.sample(['Aguav', 'Figy', 'Iapapa', 'Mago', 'Wiki']) + ' Berry';
1737
- if (
1738
- ability === 'Imposter' ||
1739
- (ability === 'Magnet Pull' && moves.has('bodypress') && !isDoubles)
1740
- ) return 'Choice Scarf';
1741
- if (
1742
- ability === 'Guts' &&
1743
- (counter.get('Physical') > 2 || isDoubles)
1744
- ) {
1745
- return types.has('Fire') ? 'Toxic Orb' : 'Flame Orb';
1746
- }
1747
- if (ability === 'Magic Guard' && counter.damagingMoves.size > 1) {
1748
- return moves.has('counter') ? 'Focus Sash' : 'Life Orb';
1749
- }
1750
- if (ability === 'Sheer Force' && counter.get('sheerforce')) return 'Life Orb';
1751
- if (ability === 'Unburden') return (moves.has('closecombat') || moves.has('curse')) ? 'White Herb' : 'Sitrus Berry';
1752
-
1753
- if (moves.has('trick') || (moves.has('switcheroo') && !isDoubles) || ability === 'Gorilla Tactics') {
1754
- if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && !counter.get('priority') && ability !== 'Triage') {
1755
- return 'Choice Scarf';
1756
- } else {
1757
- return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
1758
- }
1759
- }
1760
- if (moves.has('auroraveil') || moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
1761
- if (moves.has('rest') && !moves.has('sleeptalk') && ability !== 'Shed Skin') return 'Chesto Berry';
1762
- if (moves.has('hypnosis') && ability === 'Beast Boost') return 'Blunder Policy';
1763
- if (moves.has('bellydrum')) return 'Sitrus Berry';
1764
-
1765
- if (this.dex.getEffectiveness('Rock', species) >= 2 && !isDoubles) {
1766
- return 'Heavy-Duty Boots';
1767
- }
1768
- }
1769
-
1770
- /** Item generation specific to Random Doubles */
1771
- getDoublesItem(
1772
- ability: string,
1773
- types: Set<string>,
1774
- moves: Set<string>,
1775
- abilities: Set<string>,
1776
- counter: MoveCounter,
1777
- teamDetails: RandomTeamsTypes.TeamDetails,
1778
- species: Species,
1779
- ) {
1780
- const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
1781
-
1782
- if (
1783
- (['dragonenergy', 'eruption', 'waterspout'].some(m => moves.has(m))) &&
1784
- counter.damagingMoves.size >= 4
1785
- ) return 'Choice Scarf';
1786
- if (moves.has('blizzard') && ability !== 'Snow Warning' && !teamDetails.hail) return 'Blunder Policy';
1787
- if (this.dex.getEffectiveness('Rock', species) >= 2 && !types.has('Flying')) return 'Heavy-Duty Boots';
1788
- if (counter.get('Physical') >= 4 && ['fakeout', 'feint', 'rapidspin', 'suckerpunch'].every(m => !moves.has(m)) && (
1789
- types.has('Dragon') || types.has('Fighting') || types.has('Rock') ||
1790
- moves.has('flipturn') || moves.has('uturn')
1791
- )) {
1792
- return (
1793
- !counter.get('priority') && !abilities.has('Speed Boost') &&
1794
- species.baseStats.spe >= 60 && species.baseStats.spe <= 100 &&
1795
- this.randomChance(1, 2)
1796
- ) ? 'Choice Scarf' : 'Choice Band';
1797
- }
1798
- if (
1799
- (
1800
- counter.get('Special') >= 4 &&
1801
- (types.has('Dragon') || types.has('Fighting') || types.has('Rock') || moves.has('voltswitch'))
1802
- ) || (
1803
- (counter.get('Special') >= 3 && (moves.has('flipturn') || moves.has('uturn'))) &&
1804
- !moves.has('acidspray') && !moves.has('electroweb')
1805
- )
1806
- ) {
1807
- return (
1808
- species.baseStats.spe >= 60 && species.baseStats.spe <= 100 && this.randomChance(1, 2)
1809
- ) ? 'Choice Scarf' : 'Choice Specs';
1810
- }
1811
- // This one is intentionally below the Choice item checks.
1812
- if ((defensiveStatTotal < 250 && ability === 'Regenerator') || species.name === 'Pheromosa') return 'Life Orb';
1813
- if (counter.damagingMoves.size >= 4 && defensiveStatTotal >= 275) return 'Assault Vest';
1814
- if (
1815
- counter.damagingMoves.size >= 3 &&
1816
- species.baseStats.spe >= 60 &&
1817
- ability !== 'Multiscale' && ability !== 'Sturdy' &&
1818
- [
1819
- 'acidspray', 'clearsmog', 'electroweb', 'fakeout', 'feint', 'icywind',
1820
- 'incinerate', 'naturesmadness', 'rapidspin', 'snarl', 'uturn',
1821
- ].every(m => !moves.has(m))
1822
- ) return (ability === 'Defeatist' || defensiveStatTotal >= 275) ? 'Sitrus Berry' : 'Life Orb';
1823
- }
1824
-
1825
- getMediumPriorityItem(
1826
- ability: string,
1827
- moves: Set<string>,
1828
- counter: MoveCounter,
1829
- species: Species,
1830
- isLead: boolean,
1831
- isDoubles: boolean,
1832
- isNoDynamax: boolean
1833
- ): string | undefined {
1834
- const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
1835
-
1836
- // Choice items
1837
- if (
1838
- !isDoubles && counter.get('Physical') >= 4 && ability !== 'Serene Grace' &&
1839
- ['fakeout', 'flamecharge', 'rapidspin'].every(m => !moves.has(m))
1840
- ) {
1841
- const scarfReqs = (
1842
- (species.baseStats.atk >= 100 || ability === 'Huge Power') &&
1843
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
1844
- ability !== 'Speed Boost' && !counter.get('priority') &&
1845
- (isNoDynamax || ['bounce', 'dualwingbeat'].every(m => !moves.has(m)))
1846
- );
1847
- return (scarfReqs && this.randomChance(2, 3)) ? 'Choice Scarf' : 'Choice Band';
1848
- }
1849
- if (!isDoubles && (
1850
- (counter.get('Special') >= 4 && !moves.has('futuresight')) ||
1851
- (counter.get('Special') >= 3 && ['flipturn', 'partingshot', 'uturn'].some(m => moves.has(m)))
1852
- )) {
1853
- const scarfReqs = (
1854
- species.baseStats.spa >= 100 &&
1855
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
1856
- ability !== 'Tinted Lens' && !counter.get('Physical')
1857
- );
1858
- return (scarfReqs && this.randomChance(2, 3)) ? 'Choice Scarf' : 'Choice Specs';
1859
- }
1860
- if (
1861
- !isDoubles &&
1862
- counter.get('Physical') >= 3 &&
1863
- !moves.has('rapidspin') &&
1864
- ['copycat', 'memento', 'partingshot'].some(m => moves.has(m))
1865
- ) return 'Choice Band';
1866
- if (
1867
- !isDoubles &&
1868
- ((counter.get('Physical') >= 3 && moves.has('defog')) || (counter.get('Special') >= 3 && moves.has('healingwish'))) &&
1869
- !counter.get('priority') && !moves.has('uturn')
1870
- ) return 'Choice Scarf';
1871
-
1872
- // Palkia sometimes wants Choice items instead
1873
- if (species.name === 'Palkia') return 'Lustrous Orb';
1874
-
1875
- // Other items
1876
- if (
1877
- moves.has('raindance') || moves.has('sunnyday') ||
1878
- (ability === 'Speed Boost' && !counter.get('hazards')) ||
1879
- (ability === 'Stance Change' && counter.damagingMoves.size >= 3)
1880
- ) return 'Life Orb';
1881
- if (
1882
- !isDoubles &&
1883
- this.dex.getEffectiveness('Rock', species) >= 1 && (
1884
- ['Defeatist', 'Emergency Exit', 'Multiscale'].includes(ability) ||
1885
- ['courtchange', 'defog', 'rapidspin'].some(m => moves.has(m))
1886
- )
1887
- ) return 'Heavy-Duty Boots';
1888
- if (species.name === 'Necrozma-Dusk-Mane' || (
1889
- this.dex.getEffectiveness('Ground', species) < 2 &&
1890
- counter.get('speedsetup') &&
1891
- counter.damagingMoves.size >= 3 &&
1892
- defensiveStatTotal >= 300
1893
- )) return 'Weakness Policy';
1894
- if (counter.damagingMoves.size >= 4 && defensiveStatTotal >= 235) return 'Assault Vest';
1895
- if (
1896
- ['clearsmog', 'curse', 'haze', 'healbell', 'protect', 'sleeptalk', 'strangesteam'].some(m => moves.has(m)) &&
1897
- (ability === 'Moody' || !isDoubles)
1898
- ) return 'Leftovers';
1899
- }
1900
-
1901
- getLowPriorityItem(
1902
- ability: string,
1903
- types: Set<string>,
1904
- moves: Set<string>,
1905
- abilities: Set<string>,
1906
- counter: MoveCounter,
1907
- teamDetails: RandomTeamsTypes.TeamDetails,
1908
- species: Species,
1909
- isLead: boolean,
1910
- isDoubles: boolean,
1911
- isNoDynamax: boolean
1912
- ): string | undefined {
1913
- const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
1914
-
1915
- if (
1916
- isLead && !isDoubles &&
1917
- !['Disguise', 'Sturdy'].includes(ability) && !moves.has('substitute') &&
1918
- !counter.get('drain') && !counter.get('recoil') && !counter.get('recovery') &&
1919
- ((defensiveStatTotal <= 250 && counter.get('hazards')) || defensiveStatTotal <= 210)
1920
- ) return 'Focus Sash';
1921
- if (
1922
- moves.has('clangoroussoul') ||
1923
- // We manually check for speed-boosting moves, rather than using `counter.get('speedsetup')`,
1924
- // because we want to check for ANY speed boosting move.
1925
- // In particular, Shift Gear + Boomburst Toxtricity should get Throat Spray.
1926
- (moves.has('boomburst') && Array.from(moves).some(m => this.dex.moves.get(m).boosts?.spe))
1927
- ) return 'Throat Spray';
1928
-
1929
- const rockWeaknessCase = (
1930
- this.dex.getEffectiveness('Rock', species) >= 1 &&
1931
- (!teamDetails.defog || ability === 'Intimidate' || moves.has('uturn') || moves.has('voltswitch'))
1932
- );
1933
- const spinnerCase = (moves.has('rapidspin') && (ability === 'Regenerator' || !!counter.get('recovery')));
1934
- // Glalie prefers Leftovers
1935
- if (!isDoubles && (rockWeaknessCase || spinnerCase) && species.id !== 'glalie') return 'Heavy-Duty Boots';
1936
-
1937
- if (
1938
- !isDoubles && this.dex.getEffectiveness('Ground', species) >= 2 && !types.has('Poison') &&
1939
- ability !== 'Levitate' && !abilities.has('Iron Barbs')
1940
- ) return 'Air Balloon';
1941
- if (
1942
- !isDoubles &&
1943
- counter.damagingMoves.size >= 3 &&
1944
- !counter.get('damage') &&
1945
- ability !== 'Sturdy' &&
1946
- (species.baseStats.spe >= 90 || !moves.has('voltswitch')) &&
1947
- ['foulplay', 'rapidspin', 'substitute', 'uturn'].every(m => !moves.has(m)) && (
1948
- counter.get('speedsetup') ||
1949
- // No Dynamax Buzzwole doesn't want Life Orb with Bulk Up + 3 attacks
1950
- (counter.get('drain') && (!isNoDynamax || species.id !== 'buzzwole' || moves.has('roost'))) ||
1951
- moves.has('trickroom') || moves.has('psystrike') ||
1952
- (species.baseStats.spe > 40 && defensiveStatTotal < 275)
1953
- )
1954
- ) return 'Life Orb';
1955
- if (
1956
- !isDoubles &&
1957
- counter.damagingMoves.size >= 4 &&
1958
- !counter.get('Dragon') &&
1959
- !counter.get('Normal')
1960
- ) {
1961
- return 'Expert Belt';
1962
- }
1963
- if (
1964
- !isDoubles &&
1965
- !moves.has('substitute') &&
1966
- (moves.has('dragondance') || moves.has('swordsdance')) &&
1967
- (moves.has('outrage') || (
1968
- ['Bug', 'Fire', 'Ground', 'Normal', 'Poison'].every(type => !types.has(type)) &&
1969
- !['Pastel Veil', 'Storm Drain'].includes(ability)
1970
- ))
1971
- ) return 'Lum Berry';
1972
- }
1973
-
1974
- randomSet(
1975
- species: string | Species,
1976
- teamDetails: RandomTeamsTypes.TeamDetails = {},
1977
- isLead = false,
1978
- isDoubles = false,
1979
- isNoDynamax = false
1980
- ): RandomTeamsTypes.RandomSet {
1981
- species = this.dex.species.get(species);
1982
- let forme = species.name;
1983
- let gmax = false;
1984
-
1985
- if (typeof species.battleOnly === 'string') {
1986
- // Only change the forme. The species has custom moves, and may have different typing and requirements.
1987
- forme = species.battleOnly;
1988
- }
1989
- if (species.cosmeticFormes) {
1990
- forme = this.sample([species.name].concat(species.cosmeticFormes));
1991
- }
1992
- if (species.name.endsWith('-Gmax')) {
1993
- forme = species.name.slice(0, -5);
1994
- gmax = true;
1995
- }
1996
-
1997
- const randMoves =
1998
- (isDoubles && species.randomDoubleBattleMoves) ||
1999
- (isNoDynamax && species.randomBattleNoDynamaxMoves) ||
2000
- species.randomBattleMoves;
2001
- const movePool = (randMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
2002
- if (this.format.gameType === 'multi' || this.format.gameType === 'freeforall') {
2003
- // Random Multi Battle uses doubles move pools, but Ally Switch fails in multi battles
2004
- // Random Free-For-All also uses doubles move pools, for now
2005
- const allySwitch = movePool.indexOf('allyswitch');
2006
- if (allySwitch > -1) {
2007
- if (movePool.length > this.maxMoveCount) {
2008
- this.fastPop(movePool, allySwitch);
2009
- } else {
2010
- // Ideally, we'll never get here, but better to have a move that usually does nothing than one that always does
2011
- movePool[allySwitch] = 'sleeptalk';
2012
- }
2013
- }
2014
- }
2015
- const rejectedPool = [];
2016
- let ability = '';
2017
- let item = undefined;
2018
-
2019
- const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
2020
- const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
2021
-
2022
- const types = new Set(species.types);
2023
- const abilities = new Set(Object.values(species.abilities));
2024
- if (species.unreleasedHidden) abilities.delete(species.abilities.H);
2025
-
2026
- const moves = new Set<string>();
2027
- let counter: MoveCounter;
2028
- // This is just for BDSP Unown;
2029
- // it can be removed from this file if BDSP gets its own random-teams file in the future.
2030
- let hasHiddenPower = false;
2031
-
2032
- do {
2033
- // Choose next 4 moves from learnset/viable moves and add them to moves list:
2034
- const pool = (movePool.length ? movePool : rejectedPool);
2035
- while (moves.size < this.maxMoveCount && pool.length) {
2036
- const moveid = this.sampleNoReplace(pool);
2037
- if (moveid.startsWith('hiddenpower')) {
2038
- if (hasHiddenPower) continue;
2039
- hasHiddenPower = true;
2040
- }
2041
- moves.add(moveid);
2042
- }
2043
-
2044
- counter = this.queryMoves(moves, species.types, abilities, movePool);
2045
- const runEnforcementChecker = (checkerName: string) => {
2046
- if (!this.moveEnforcementCheckers[checkerName]) return false;
2047
- return this.moveEnforcementCheckers[checkerName](
2048
- movePool, moves, abilities, types, counter, species as Species, teamDetails
2049
- );
2050
- };
2051
-
2052
- // Iterate through the moves again, this time to cull them:
2053
- for (const moveid of moves) {
2054
- const move = this.dex.moves.get(moveid);
2055
- let {cull, isSetup} = this.shouldCullMove(
2056
- move, types, moves, abilities, counter,
2057
- movePool, teamDetails, species, isLead, isDoubles, isNoDynamax
2058
- );
2059
-
2060
- if (move.id !== 'photongeyser' && (
2061
- (move.category === 'Physical' && counter.setupType === 'Special') ||
2062
- (move.category === 'Special' && counter.setupType === 'Physical')
2063
- )) {
2064
- // Reject STABs last in case the setup type changes later on
2065
- const stabs = counter.get(species.types[0]) + (species.types[1] ? counter.get(species.types[1]) : 0);
2066
- if (!types.has(move.type) || stabs > 1 || counter.get(move.category) < 2) cull = true;
2067
- }
2068
-
2069
- // Pokemon should have moves that benefit their types, stats, or ability
2070
- const isLowBP = move.basePower && move.basePower < 50;
2071
-
2072
- // Genesect-Douse should never reject Techno Blast
2073
- const moveIsRejectable = (
2074
- !(species.id === 'genesectdouse' && move.id === 'technoblast') &&
2075
- !(species.id === 'togekiss' && move.id === 'nastyplot') && (
2076
- move.category === 'Status' ||
2077
- (!types.has(move.type) && move.id !== 'judgment') ||
2078
- (isLowBP && !move.multihit && !abilities.has('Technician'))
2079
- )
2080
- );
2081
- // Setup-supported moves should only be rejected under specific circumstances
2082
- const notImportantSetup = (
2083
- !counter.setupType ||
2084
- counter.setupType === 'Mixed' ||
2085
- (counter.get(counter.setupType) + counter.get('Status') > 3 && !counter.get('hazards')) ||
2086
- (move.category !== counter.setupType && move.category !== 'Status')
2087
- );
2088
-
2089
- if (moveIsRejectable && (
2090
- !cull && !isSetup && !move.weather && !move.stallingMove && notImportantSetup && !move.damage &&
2091
- (isDoubles ? this.unrejectableMovesInDoubles(move) : this.unrejectableMovesInSingles(move))
2092
- )) {
2093
- // There may be more important moves that this Pokemon needs
2094
- if (
2095
- // Pokemon should have at least one STAB move
2096
- (!counter.get('stab') && counter.get('physicalpool') + counter.get('specialpool') > 0 && move.id !== 'stickyweb') ||
2097
- // Swords Dance Mew should have Brave Bird
2098
- (moves.has('swordsdance') && species.id === 'mew' && runEnforcementChecker('Flying')) ||
2099
- // Dhelmise should have Anchor Shot
2100
- (abilities.has('Steelworker') && runEnforcementChecker('Steel')) ||
2101
- // Check for miscellaneous important moves
2102
- (!isDoubles && runEnforcementChecker('recovery') && move.id !== 'stickyweb') ||
2103
- runEnforcementChecker('screens') ||
2104
- runEnforcementChecker('misc') ||
2105
- (isLead && runEnforcementChecker('lead')) ||
2106
- (moves.has('leechseed') && runEnforcementChecker('leechseed'))
2107
- ) {
2108
- cull = true;
2109
- // Pokemon should have moves that benefit their typing
2110
- // Don't cull Sticky Web in type-based enforcement, and make sure Azumarill always has Aqua Jet
2111
- } else if (move.id !== 'stickyweb' && !(species.id === 'azumarill' && move.id === 'aquajet')) {
2112
- for (const type of types) {
2113
- if (runEnforcementChecker(type)) {
2114
- cull = true;
2115
- }
2116
- }
2117
- }
2118
- }
2119
-
2120
- // Sleep Talk shouldn't be selected without Rest
2121
- if (move.id === 'rest' && cull) {
2122
- const sleeptalk = movePool.indexOf('sleeptalk');
2123
- if (sleeptalk >= 0) {
2124
- if (movePool.length < 2) {
2125
- cull = false;
2126
- } else {
2127
- this.fastPop(movePool, sleeptalk);
2128
- }
2129
- }
2130
- }
2131
-
2132
- // Remove rejected moves from the move list
2133
- if (cull && movePool.length) {
2134
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
2135
- if (move.category !== 'Status' && !move.damage) rejectedPool.push(moveid);
2136
- moves.delete(moveid);
2137
- break;
2138
- }
2139
- if (cull && rejectedPool.length) {
2140
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
2141
- moves.delete(moveid);
2142
- break;
2143
- }
2144
- }
2145
- } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
2146
-
2147
- // for BD/SP only
2148
- if (hasHiddenPower) {
2149
- let hpType;
2150
- for (const move of moves) {
2151
- if (move.startsWith('hiddenpower')) hpType = move.substr(11);
2152
- }
2153
- if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
2154
- const HPivs = this.dex.types.get(hpType).HPivs;
2155
- let iv: StatID;
2156
- for (iv in HPivs) {
2157
- ivs[iv] = HPivs[iv]!;
2158
- }
2159
- }
2160
-
2161
- const abilityData = Array.from(abilities).map(a => this.dex.abilities.get(a));
2162
- Utils.sortBy(abilityData, abil => -abil.rating);
2163
-
2164
- if (abilityData[1]) {
2165
- // Sort abilities by rating with an element of randomness
2166
- if (abilityData[2] && abilityData[1].rating <= abilityData[2].rating && this.randomChance(1, 2)) {
2167
- [abilityData[1], abilityData[2]] = [abilityData[2], abilityData[1]];
2168
- }
2169
- if (abilityData[0].rating <= abilityData[1].rating) {
2170
- if (this.randomChance(1, 2)) [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
2171
- } else if (abilityData[0].rating - 0.6 <= abilityData[1].rating) {
2172
- if (this.randomChance(2, 3)) [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
2173
- }
2174
-
2175
- // Start with the first abiility and work our way through, culling as we go
2176
- ability = abilityData[0].name;
2177
- let rejectAbility = false;
2178
- do {
2179
- rejectAbility = this.shouldCullAbility(
2180
- ability, types, moves, abilities, counter, movePool, teamDetails, species, isDoubles, isNoDynamax
2181
- );
2182
-
2183
- if (rejectAbility) {
2184
- // Lopunny, and other Facade users, don't want Limber, even if other abilities are poorly rated,
2185
- // since paralysis would arguably be good for them.
2186
- const limberFacade = moves.has('facade') && ability === 'Limber';
2187
-
2188
- if (ability === abilityData[0].name && (abilityData[1].rating >= 1 || limberFacade)) {
2189
- ability = abilityData[1].name;
2190
- } else if (ability === abilityData[1].name && abilityData[2] && (abilityData[2].rating >= 1 || limberFacade)) {
2191
- ability = abilityData[2].name;
2192
- } else {
2193
- // Default to the highest rated ability if all are rejected
2194
- ability = abilityData[0].name;
2195
- rejectAbility = false;
2196
- }
2197
- }
2198
- } while (rejectAbility);
2199
-
2200
- // Hardcoded abilities for certain contexts
2201
- if (forme === 'Copperajah' && gmax) {
2202
- ability = 'Heavy Metal';
2203
- } else if (abilities.has('Guts') && (
2204
- species.id === 'gurdurr' || species.id === 'throh' ||
2205
- moves.has('facade') || (moves.has('rest') && moves.has('sleeptalk'))
2206
- )) {
2207
- ability = 'Guts';
2208
- } else if (abilities.has('Moxie') && (counter.get('Physical') > 3 || moves.has('bounce')) && !isDoubles) {
2209
- ability = 'Moxie';
2210
- } else if (isDoubles) {
2211
- if (abilities.has('Competitive') && ability !== 'Shadow Tag' && ability !== 'Strong Jaw') ability = 'Competitive';
2212
- if (abilities.has('Friend Guard')) ability = 'Friend Guard';
2213
- if (abilities.has('Gluttony') && moves.has('recycle')) ability = 'Gluttony';
2214
- if (abilities.has('Guts')) ability = 'Guts';
2215
- if (abilities.has('Harvest')) ability = 'Harvest';
2216
- if (abilities.has('Healer') && (
2217
- abilities.has('Natural Cure') ||
2218
- (abilities.has('Aroma Veil') && this.randomChance(1, 2))
2219
- )) ability = 'Healer';
2220
- if (abilities.has('Intimidate')) ability = 'Intimidate';
2221
- if (abilities.has('Klutz') && ability === 'Limber') ability = 'Klutz';
2222
- if (abilities.has('Magic Guard') && ability !== 'Friend Guard' && ability !== 'Unaware') ability = 'Magic Guard';
2223
- if (abilities.has('Ripen')) ability = 'Ripen';
2224
- if (abilities.has('Stalwart')) ability = 'Stalwart';
2225
- if (abilities.has('Storm Drain')) ability = 'Storm Drain';
2226
- if (abilities.has('Telepathy') && (ability === 'Pressure' || abilities.has('Analytic'))) ability = 'Telepathy';
2227
- }
2228
- } else {
2229
- ability = abilityData[0].name;
2230
- }
2231
-
2232
- // item = !isDoubles ? 'Leftovers' : 'Sitrus Berry';
2233
- if (species.requiredItems) {
2234
- item = this.sample(species.requiredItems);
2235
- // First, the extra high-priority items
2236
- } else {
2237
- item = this.getHighPriorityItem(ability, types, moves, counter, teamDetails, species, isLead, isDoubles);
2238
- if (item === undefined && isDoubles) {
2239
- item = this.getDoublesItem(ability, types, moves, abilities, counter, teamDetails, species);
2240
- }
2241
- if (item === undefined) {
2242
- item = this.getMediumPriorityItem(ability, moves, counter, species, isLead, isDoubles, isNoDynamax);
2243
- }
2244
- if (item === undefined) {
2245
- item = this.getLowPriorityItem(
2246
- ability, types, moves, abilities, counter, teamDetails, species, isLead, isDoubles, isNoDynamax
2247
- );
2248
- }
2249
-
2250
- // fallback
2251
- if (item === undefined) item = isDoubles ? 'Sitrus Berry' : 'Leftovers';
2252
- }
2253
-
2254
- // For Trick / Switcheroo
2255
- if (item === 'Leftovers' && types.has('Poison')) {
2256
- item = 'Black Sludge';
2257
- }
2258
- if (species.baseSpecies === 'Pikachu' && !gmax && this.dex.currentMod !== 'gen8bdsp') {
2259
- forme = 'Pikachu' + this.sample(['', '-Original', '-Hoenn', '-Sinnoh', '-Unova', '-Kalos', '-Alola', '-Partner', '-World']);
2260
- }
2261
-
2262
- let level: number;
2263
- if (this.adjustLevel) {
2264
- level = this.adjustLevel;
2265
- // doubles levelling
2266
- } else if (isDoubles && species.randomDoubleBattleLevel) {
2267
- level = species.randomDoubleBattleLevel;
2268
- // No Dmax levelling
2269
- } else if (isNoDynamax) {
2270
- const tier = species.name.endsWith('-Gmax') ? this.dex.species.get(species.changesFrom).tier : species.tier;
2271
- const tierScale: Partial<Record<Species['tier'], number>> = {
2272
- Uber: 76,
2273
- OU: 80,
2274
- UUBL: 81,
2275
- UU: 82,
2276
- RUBL: 83,
2277
- RU: 84,
2278
- NUBL: 85,
2279
- NU: 86,
2280
- PUBL: 87,
2281
- PU: 88, "(PU)": 88, NFE: 88,
2282
- };
2283
- const customScale: {[k: string]: number} = {
2284
- // These Pokemon are too strong and need a lower level
2285
- zaciancrowned: 65, calyrexshadow: 68, xerneas: 70, necrozmaduskmane: 72, zacian: 72, kyogre: 73, eternatus: 73,
2286
- zekrom: 74, marshadow: 75, glalie: 78, urshifurapidstrike: 79, haxorus: 80, inteleon: 80,
2287
- cresselia: 83, octillery: 84, jolteon: 84, swoobat: 84, dugtrio: 84, slurpuff: 84, polteageist: 84,
2288
- wobbuffet: 86, scrafty: 86,
2289
- // These Pokemon are too weak and need a higher level
2290
- delibird: 100, vespiquen: 96, pikachu: 92, shedinja: 92, solrock: 90, arctozolt: 88, reuniclus: 87,
2291
- decidueye: 87, noivern: 85, magnezone: 82, slowking: 81,
2292
- };
2293
- level = customScale[species.id] || tierScale[tier] || 80;
2294
- // BDSP tier levelling
2295
- } else if (this.dex.currentMod === 'gen8bdsp') {
2296
- const tierScale: Partial<Record<Species['tier'], number>> = {
2297
- Uber: 76, Unreleased: 76,
2298
- OU: 80,
2299
- UUBL: 81,
2300
- UU: 82,
2301
- RUBL: 83,
2302
- RU: 84,
2303
- NUBL: 85,
2304
- NU: 86,
2305
- PUBL: 87,
2306
- PU: 88, "(PU)": 88, NFE: 88,
2307
- };
2308
- const customScale: {[k: string]: number} = {delibird: 100, glalie: 76, luvdisc: 100, spinda: 100, unown: 100};
2309
-
2310
- level = customScale[species.id] || tierScale[species.tier] || 80;
2311
- // Arbitrary levelling base on data files (typically winrate-influenced)
2312
- } else if (species.randomBattleLevel) {
2313
- level = species.randomBattleLevel;
2314
- // Default to level 80
2315
- } else {
2316
- level = 80;
2317
- }
2318
-
2319
- // Prepare optimal HP
2320
- const srImmunity = ability === 'Magic Guard' || item === 'Heavy-Duty Boots';
2321
- const srWeakness = srImmunity ? 0 : this.dex.getEffectiveness('Rock', species);
2322
- while (evs.hp > 1) {
2323
- const hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
2324
- const multipleOfFourNecessary = (moves.has('substitute') && !['Leftovers', 'Black Sludge'].includes(item) && (
2325
- item === 'Sitrus Berry' ||
2326
- item === 'Salac Berry' ||
2327
- ability === 'Power Construct'
2328
- ));
2329
- if (multipleOfFourNecessary) {
2330
- // Two Substitutes should activate Sitrus Berry
2331
- if (hp % 4 === 0) break;
2332
- } else if (moves.has('bellydrum') && (item === 'Sitrus Berry' || ability === 'Gluttony')) {
2333
- // Belly Drum should activate Sitrus Berry
2334
- if (hp % 2 === 0) break;
2335
- } else if (moves.has('substitute') && moves.has('reversal')) {
2336
- // Reversal users should be able to use four Substitutes
2337
- if (hp % 4 > 0) break;
2338
- } else {
2339
- // Maximize number of Stealth Rock switch-ins
2340
- if (srWeakness <= 0 || hp % (4 / srWeakness) > 0) break;
2341
- }
2342
- evs.hp -= 4;
2343
- }
2344
-
2345
- if (moves.has('shellsidearm') && item === 'Choice Specs') evs.atk -= 8;
2346
-
2347
- // Minimize confusion damage
2348
- const noAttackStatMoves = [...moves].every(m => {
2349
- const move = this.dex.moves.get(m);
2350
- if (move.damageCallback || move.damage) return true;
2351
- return move.category !== 'Physical' || move.id === 'bodypress';
2352
- });
2353
- if (noAttackStatMoves && !moves.has('transform') && (!moves.has('shellsidearm') || !counter.get('Status'))) {
2354
- evs.atk = 0;
2355
- ivs.atk = 0;
2356
- }
2357
-
2358
- // Ensure Nihilego's Beast Boost gives it Special Attack boosts instead of Special Defense
2359
- if (forme === 'Nihilego') evs.spd -= 32;
2360
-
2361
- if (moves.has('gyroball') || moves.has('trickroom')) {
2362
- evs.spe = 0;
2363
- ivs.spe = 0;
2364
- }
2365
-
2366
- return {
2367
- name: species.baseSpecies,
2368
- species: forme,
2369
- gender: species.gender,
2370
- shiny: this.randomChance(1, 1024),
2371
- gigantamax: gmax,
2372
- level,
2373
- moves: Array.from(moves),
2374
- ability,
2375
- evs,
2376
- ivs,
2377
- item,
2378
- };
2379
- }
2380
-
2381
- getPokemonPool(
2382
- type: string,
2383
- pokemonToExclude: RandomTeamsTypes.RandomSet[] = [],
2384
- isMonotype = false,
2385
- ) {
2386
- const exclude = pokemonToExclude.map(p => toID(p.species));
2387
- const pokemonPool = [];
2388
- for (let species of this.dex.species.all()) {
2389
- if (species.gen > this.gen || exclude.includes(species.id)) continue;
2390
- if (this.dex.currentMod === 'gen8bdsp' && species.gen > 4) continue;
2391
- if (isMonotype) {
2392
- if (!species.types.includes(type)) continue;
2393
- if (typeof species.battleOnly === 'string') {
2394
- species = this.dex.species.get(species.battleOnly);
2395
- if (!species.types.includes(type)) continue;
2396
- }
2397
- }
2398
- pokemonPool.push(species.id);
2399
- }
2400
- return pokemonPool;
2401
- }
2402
-
2403
- randomTeam() {
2404
- this.enforceNoDirectCustomBanlistChanges();
2405
-
2406
- const seed = this.prng.seed;
2407
- const ruleTable = this.dex.formats.getRuleTable(this.format);
2408
- const pokemon: RandomTeamsTypes.RandomSet[] = [];
2409
-
2410
- // For Monotype
2411
- const isMonotype = !!this.forceMonotype || ruleTable.has('sametypeclause');
2412
- const typePool = this.dex.types.names();
2413
- const type = this.forceMonotype || this.sample(typePool);
2414
-
2415
- // PotD stuff
2416
- const usePotD = global.Config && Config.potd && ruleTable.has('potd');
2417
- const potd = usePotD ? this.dex.species.get(Config.potd) : null;
2418
-
2419
- const baseFormes: {[k: string]: number} = {};
2420
-
2421
- const tierCount: {[k: string]: number} = {};
2422
- const typeCount: {[k: string]: number} = {};
2423
- const typeComboCount: {[k: string]: number} = {};
2424
- const teamDetails: RandomTeamsTypes.TeamDetails = {};
2425
-
2426
- const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype);
2427
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
2428
- let species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
2429
- if (!species.exists) continue;
2430
-
2431
- // Check if the forme has moves for random battle
2432
- if (this.format.gameType === 'singles') {
2433
- if (!species.randomBattleMoves?.length) continue;
2434
- } else {
2435
- if (!species.randomDoubleBattleMoves?.length) continue;
2436
- }
2437
-
2438
- // Limit to one of each species (Species Clause)
2439
- if (baseFormes[species.baseSpecies]) continue;
2440
-
2441
- // Adjust rate for species with multiple sets
2442
- // TODO: investigate automating this by searching for Pokémon with multiple sets
2443
- switch (species.baseSpecies) {
2444
- case 'Arceus': case 'Silvally':
2445
- if (this.randomChance(8, 9) && !isMonotype) continue;
2446
- break;
2447
- case 'Aegislash': case 'Basculin': case 'Gourgeist': case 'Meloetta': case 'Rotom':
2448
- if (this.randomChance(1, 2)) continue;
2449
- break;
2450
- case 'Greninja':
2451
- if (this.gen >= 7 && this.randomChance(1, 2)) continue;
2452
- break;
2453
- case 'Darmanitan':
2454
- if (species.gen === 8 && this.randomChance(1, 2)) continue;
2455
- break;
2456
- case 'Necrozma': case 'Calyrex':
2457
- if (this.randomChance(2, 3)) continue;
2458
- break;
2459
- case 'Magearna': case 'Toxtricity': case 'Zacian': case 'Zamazenta': case 'Zarude':
2460
- case 'Appletun': case 'Blastoise': case 'Butterfree': case 'Copperajah': case 'Grimmsnarl':
2461
- case 'Inteleon': case 'Rillaboom': case 'Snorlax': case 'Urshifu': case 'Giratina': case 'Genesect':
2462
- case 'Cinderace':
2463
- if (this.gen >= 8 && this.randomChance(1, 2)) continue;
2464
- break;
2465
- }
2466
-
2467
- // Illusion shouldn't be on the last slot
2468
- if (species.name === 'Zoroark' && pokemon.length >= (this.maxTeamSize - 1)) continue;
2469
- // The sixth slot should not be Zacian/Zamazenta/Eternatus if a Zoroark is present
2470
- if (
2471
- pokemon.some(pkmn => pkmn.species === 'Zoroark') &&
2472
- ['Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned', 'Eternatus'].includes(species.name)
2473
- ) {
2474
- continue;
2475
- }
2476
-
2477
- const tier = species.tier;
2478
- const types = species.types;
2479
- const typeCombo = types.slice().sort().join();
2480
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
2481
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
2482
-
2483
- // Limit one Pokemon per tier, two for Monotype
2484
- // This limitation is not applied to BD/SP team generation, because tiering for BD/SP is not yet complete,
2485
- // meaning that most Pokémon are in OU.
2486
- if (
2487
- this.dex.currentMod !== 'gen8bdsp' &&
2488
- (tierCount[tier] >= (this.forceMonotype || isMonotype ? 2 : 1) * limitFactor) &&
2489
- !this.randomChance(1, Math.pow(5, tierCount[tier]))
2490
- ) {
2491
- continue;
2492
- }
2493
-
2494
- if (!isMonotype && !this.forceMonotype) {
2495
- // TODO: fix type weaknesses
2496
- let skip = false;
2497
-
2498
- for (const typeName of types) {
2499
- if (typeCount[typeName] >= 2 * limitFactor) {
2500
- skip = true;
2501
- break;
2502
- }
2503
- }
2504
- if (skip) continue;
2505
- }
2506
-
2507
- // Limit one of any type combination, two in Monotype
2508
- if (!this.forceMonotype && typeComboCount[typeCombo] >= (isMonotype ? 2 : 1) * limitFactor) continue;
2509
-
2510
- // The Pokemon of the Day
2511
- if (potd?.exists && (pokemon.length === 1 || this.maxTeamSize === 1)) species = potd;
2512
-
2513
- const set = this.randomSet(species, teamDetails, pokemon.length === 0,
2514
- this.format.gameType !== 'singles', this.dex.formats.getRuleTable(this.format).has('dynamaxclause'));
2515
-
2516
- // Okay, the set passes, add it to our team
2517
- pokemon.push(set);
2518
- if (pokemon.length === this.maxTeamSize) {
2519
- // Set Zoroark's level to be the same as the last Pokemon
2520
- const illusion = teamDetails.illusion;
2521
- if (illusion) pokemon[illusion - 1].level = pokemon[this.maxTeamSize - 1].level;
2522
-
2523
- // Don't bother tracking details for the last Pokemon
2524
- break;
2525
- }
2526
-
2527
- // Now that our Pokemon has passed all checks, we can increment our counters
2528
- baseFormes[species.baseSpecies] = 1;
2529
-
2530
- // Increment tier counter
2531
- if (tierCount[tier]) {
2532
- tierCount[tier]++;
2533
- } else {
2534
- tierCount[tier] = 1;
2535
- }
2536
-
2537
- // Increment type counters
2538
- for (const typeName of types) {
2539
- if (typeName in typeCount) {
2540
- typeCount[typeName]++;
2541
- } else {
2542
- typeCount[typeName] = 1;
2543
- }
2544
- }
2545
- if (typeCombo in typeComboCount) {
2546
- typeComboCount[typeCombo]++;
2547
- } else {
2548
- typeComboCount[typeCombo] = 1;
2549
- }
2550
-
2551
- // Track what the team has
2552
- if (set.ability === 'Drizzle' || set.moves.includes('raindance')) teamDetails.rain = 1;
2553
- if (set.ability === 'Drought' || set.moves.includes('sunnyday')) teamDetails.sun = 1;
2554
- if (set.ability === 'Sand Stream') teamDetails.sand = 1;
2555
- if (set.ability === 'Snow Warning') teamDetails.hail = 1;
2556
- if (set.moves.includes('spikes')) teamDetails.spikes = (teamDetails.spikes || 0) + 1;
2557
- if (set.moves.includes('stealthrock')) teamDetails.stealthRock = 1;
2558
- if (set.moves.includes('stickyweb')) teamDetails.stickyWeb = 1;
2559
- if (set.moves.includes('toxicspikes')) teamDetails.toxicSpikes = 1;
2560
- if (set.moves.includes('defog')) teamDetails.defog = 1;
2561
- if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1;
2562
- if (set.moves.includes('auroraveil') || (set.moves.includes('reflect') && set.moves.includes('lightscreen'))) {
2563
- teamDetails.screens = 1;
2564
- }
2565
-
2566
- // For setting Zoroark's level
2567
- if (set.ability === 'Illusion') teamDetails.illusion = pokemon.length;
2568
- }
2569
- if (pokemon.length < this.maxTeamSize && pokemon.length < 12) { // large teams sometimes cannot be built
2570
- throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
2571
- }
2572
-
2573
- return pokemon;
2574
- }
2575
-
2576
- randomCAP1v1Sets: AnyObject = {};
2577
-
2578
- randomCAP1v1Team() {
2579
- this.enforceNoDirectCustomBanlistChanges();
2580
-
2581
- const pokemon = [];
2582
- const pokemonPool = Object.keys(this.randomCAP1v1Sets);
2583
-
2584
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
2585
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
2586
- if (!species.exists) throw new Error(`Invalid Pokemon "${species}" in ${this.format}`);
2587
- if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
2588
-
2589
- const setData: AnyObject = this.sample(this.randomCAP1v1Sets[species.name]);
2590
- const set = {
2591
- name: species.baseSpecies,
2592
- species: species.name,
2593
- gender: species.gender,
2594
- item: this.sampleIfArray(setData.item) || '',
2595
- ability: (this.sampleIfArray(setData.ability)),
2596
- shiny: this.randomChance(1, 1024),
2597
- level: this.adjustLevel || 100,
2598
- evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.evs},
2599
- nature: setData.nature,
2600
- ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.ivs || {}},
2601
- moves: setData.moves.map((move: any) => this.sampleIfArray(move)),
2602
- };
2603
- if (this.adjustLevel) set.level = this.adjustLevel;
2604
- pokemon.push(set);
2605
- }
2606
- return pokemon;
2607
- }
2608
-
2609
- randomFactorySets: {[format: string]: {[species: string]: BattleFactorySpecies}} = {};
2610
-
2611
- randomFactorySet(
2612
- species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails, tier: string
2613
- ): RandomTeamsTypes.RandomFactorySet | null {
2614
- const id = toID(species.name);
2615
- const setList = this.randomFactorySets[tier][id].sets;
2616
-
2617
- const itemsMax: {[k: string]: number} = {
2618
- choicespecs: 1,
2619
- choiceband: 1,
2620
- choicescarf: 1,
2621
- };
2622
- const movesMax: {[k: string]: number} = {
2623
- rapidspin: 1,
2624
- batonpass: 1,
2625
- stealthrock: 1,
2626
- defog: 1,
2627
- spikes: 1,
2628
- toxicspikes: 1,
2629
- };
2630
- const requiredMoves: {[k: string]: string} = {
2631
- stealthrock: 'hazardSet',
2632
- rapidspin: 'hazardClear',
2633
- defog: 'hazardClear',
2634
- };
2635
- const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream'];
2636
-
2637
- // Build a pool of eligible sets, given the team partners
2638
- // Also keep track of sets with moves the team requires
2639
- let effectivePool: {set: AnyObject, moveVariants?: number[]}[] = [];
2640
- const priorityPool = [];
2641
- for (const curSet of setList) {
2642
- // if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
2643
-
2644
- const item = this.dex.items.get(curSet.item);
2645
- if (itemsMax[item.id] && teamData.has[item.id] >= itemsMax[item.id]) continue;
2646
-
2647
- const ability = this.dex.abilities.get(curSet.ability);
2648
- if (teamData.weather && weatherAbilities.includes(ability.id)) continue; // reject 2+ weather setters
2649
-
2650
- let reject = false;
2651
- let hasRequiredMove = false;
2652
- const curSetVariants = [];
2653
- for (const move of curSet.moves) {
2654
- const variantIndex = this.random(move.length);
2655
- const moveId = toID(move[variantIndex]);
2656
- if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
2657
- reject = true;
2658
- break;
2659
- }
2660
- if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
2661
- hasRequiredMove = true;
2662
- }
2663
- curSetVariants.push(variantIndex);
2664
- }
2665
- if (reject) continue;
2666
- effectivePool.push({set: curSet, moveVariants: curSetVariants});
2667
- if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants});
2668
- }
2669
- if (priorityPool.length) effectivePool = priorityPool;
2670
-
2671
- if (!effectivePool.length) {
2672
- if (!teamData.forceResult) return null;
2673
- for (const curSet of setList) {
2674
- effectivePool.push({set: curSet});
2675
- }
2676
- }
2677
-
2678
- const setData = this.sample(effectivePool);
2679
- const moves = [];
2680
- for (const [i, moveSlot] of setData.set.moves.entries()) {
2681
- moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
2682
- }
2683
-
2684
-
2685
- const item = this.sampleIfArray(setData.set.item);
2686
- const ability = this.sampleIfArray(setData.set.ability);
2687
- const nature = this.sampleIfArray(setData.set.nature);
2688
- const level = this.adjustLevel || setData.set.level || (tier === "LC" ? 5 : 100);
2689
-
2690
- return {
2691
- name: setData.set.name || species.baseSpecies,
2692
- species: setData.set.species,
2693
- gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
2694
- item: item || '',
2695
- ability: ability || species.abilities['0'],
2696
- shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
2697
- level,
2698
- happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
2699
- evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
2700
- ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
2701
- nature: nature || 'Serious',
2702
- moves,
2703
- };
2704
- }
2705
-
2706
- randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
2707
- this.enforceNoDirectCustomBanlistChanges();
2708
-
2709
- const forceResult = (depth >= 12);
2710
- // Leaving Monotype code in comments in case it's used in the future
2711
- // const isMonotype = !!this.forceMonotype || this.dex.formats.getRuleTable(this.format).has('sametypeclause');
2712
-
2713
- // The teams generated depend on the tier choice in such a way that
2714
- // no exploitable information is leaked from rolling the tier in getTeam(p1).
2715
- if (!this.factoryTier) {
2716
- // this.factoryTier = isMonotype ? 'Mono' : this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
2717
- this.factoryTier = this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
2718
- }
2719
- /*
2720
- } else if (isMonotype && this.factoryTier !== 'Mono') {
2721
- // I don't think this can ever happen?
2722
- throw new Error(`Can't generate a Monotype Battle Factory set in a battle with factory tier ${this.factoryTier}`);
2723
- }
2724
- */
2725
-
2726
- const tierValues: {[k: string]: number} = {
2727
- Uber: 5,
2728
- OU: 4, UUBL: 4,
2729
- UU: 3, RUBL: 3,
2730
- RU: 2, NUBL: 2,
2731
- NU: 1, PUBL: 1,
2732
- PU: 0,
2733
- };
2734
-
2735
- const pokemon = [];
2736
- const pokemonPool = Object.keys(this.randomFactorySets[this.factoryTier]);
2737
-
2738
- // const typePool = this.dex.types.names();
2739
- // const type = this.sample(typePool);
2740
-
2741
- const teamData: TeamData = {
2742
- typeCount: {}, typeComboCount: {}, baseFormes: {},
2743
- has: {}, forceResult: forceResult, weaknesses: {}, resistances: {},
2744
- };
2745
- const requiredMoveFamilies = ['hazardSet', 'hazardClear'];
2746
- const requiredMoves: {[k: string]: string} = {
2747
- stealthrock: 'hazardSet',
2748
- rapidspin: 'hazardClear',
2749
- defog: 'hazardClear',
2750
- };
2751
- const weatherAbilitiesSet: {[k: string]: string} = {
2752
- drizzle: 'raindance',
2753
- drought: 'sunnyday',
2754
- snowwarning: 'hail',
2755
- sandstream: 'sandstorm',
2756
- };
2757
- const resistanceAbilities: {[k: string]: string[]} = {
2758
- dryskin: ['Water'], waterabsorb: ['Water'], stormdrain: ['Water'],
2759
- flashfire: ['Fire'], heatproof: ['Fire'],
2760
- lightningrod: ['Electric'], motordrive: ['Electric'], voltabsorb: ['Electric'],
2761
- sapsipper: ['Grass'],
2762
- thickfat: ['Ice', 'Fire'],
2763
- levitate: ['Ground'],
2764
- };
2765
-
2766
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
2767
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
2768
- if (!species.exists) continue;
2769
-
2770
- // Lessen the need of deleting sets of Pokemon after tier shifts
2771
- if (
2772
- this.factoryTier in tierValues && species.tier in tierValues &&
2773
- tierValues[species.tier] > tierValues[this.factoryTier]
2774
- ) continue;
2775
-
2776
- // const speciesFlags = this.randomFactorySets[this.factoryTier][species.id].flags;
2777
-
2778
- // Limit to one of each species (Species Clause)
2779
- if (teamData.baseFormes[species.baseSpecies]) continue;
2780
-
2781
- const set = this.randomFactorySet(species, teamData, this.factoryTier);
2782
- if (!set) continue;
2783
-
2784
- const itemData = this.dex.items.get(set.item);
2785
-
2786
- const types = species.types;
2787
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
2788
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
2789
- /*
2790
- // Enforce Monotype
2791
- if (isMonotype) {
2792
- // Prevents Mega Evolutions from breaking the type limits
2793
- if (itemData.megaStone) {
2794
- const megaSpecies = this.dex.species.get(itemData.megaStone);
2795
- if (types.length > megaSpecies.types.length) types = [species.types[0]];
2796
- // Only check the second type because a Mega Evolution should always share the first type with its base forme.
2797
- if (megaSpecies.types[1] && types[1] && megaSpecies.types[1] !== types[1]) {
2798
- types = [megaSpecies.types[0]];
2799
- }
2800
- }
2801
- if (!types.includes(type)) continue;
2802
- } else
2803
- */
2804
- {
2805
- // If not Monotype, limit to two of each type
2806
- let skip = false;
2807
- for (const typeName of types) {
2808
- if (teamData.typeCount[typeName] >= 2 * limitFactor && this.randomChance(4, 5)) {
2809
- skip = true;
2810
- break;
2811
- }
2812
- }
2813
- if (skip) continue;
2814
-
2815
- // Limit 1 of any type combination
2816
- let typeCombo = types.slice().sort().join();
2817
- if (set.ability + '' === 'Drought' || set.ability + '' === 'Drizzle') {
2818
- // Drought and Drizzle don't count towards the type combo limit
2819
- typeCombo = set.ability + '';
2820
- }
2821
- if (teamData.typeComboCount[typeCombo] >= 1 * limitFactor) continue;
2822
- }
2823
-
2824
- // Okay, the set passes, add it to our team
2825
- pokemon.push(set);
2826
- const typeCombo = types.slice().sort().join();
2827
- // Now that our Pokemon has passed all checks, we can update team data:
2828
- for (const typeName of types) {
2829
- if (typeName in teamData.typeCount) {
2830
- teamData.typeCount[typeName]++;
2831
- } else {
2832
- teamData.typeCount[typeName] = 1;
2833
- }
2834
- }
2835
- teamData.typeComboCount[typeCombo] = (teamData.typeComboCount[typeCombo] + 1) || 1;
2836
-
2837
- teamData.baseFormes[species.baseSpecies] = 1;
2838
-
2839
- if (itemData.id in teamData.has) {
2840
- teamData.has[itemData.id]++;
2841
- } else {
2842
- teamData.has[itemData.id] = 1;
2843
- }
2844
-
2845
- const abilityState = this.dex.abilities.get(set.ability);
2846
- if (abilityState.id in weatherAbilitiesSet) {
2847
- teamData.weather = weatherAbilitiesSet[abilityState.id];
2848
- }
2849
-
2850
- for (const move of set.moves) {
2851
- const moveId = toID(move);
2852
- if (moveId in teamData.has) {
2853
- teamData.has[moveId]++;
2854
- } else {
2855
- teamData.has[moveId] = 1;
2856
- }
2857
- if (moveId in requiredMoves) {
2858
- teamData.has[requiredMoves[moveId]] = 1;
2859
- }
2860
- }
2861
-
2862
- for (const typeName of this.dex.types.names()) {
2863
- // Cover any major weakness (3+) with at least one resistance
2864
- if (teamData.resistances[typeName] >= 1) continue;
2865
- if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
2866
- // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
2867
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
2868
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
2869
- continue;
2870
- }
2871
- const typeMod = this.dex.getEffectiveness(typeName, types);
2872
- if (typeMod < 0) {
2873
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
2874
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
2875
- } else if (typeMod > 0) {
2876
- teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
2877
- }
2878
- }
2879
- }
2880
- if (pokemon.length < this.maxTeamSize) return this.randomFactoryTeam(side, ++depth);
2881
-
2882
- // Quality control
2883
- if (!teamData.forceResult) {
2884
- for (const requiredFamily of requiredMoveFamilies) {
2885
- if (!teamData.has[requiredFamily]) return this.randomFactoryTeam(side, ++depth);
2886
- }
2887
- for (const typeName in teamData.weaknesses) {
2888
- if (teamData.weaknesses[typeName] >= 3) return this.randomFactoryTeam(side, ++depth);
2889
- }
2890
- }
2891
-
2892
- return pokemon;
2893
- }
2894
-
2895
- randomBSSFactorySets: AnyObject = {};
2896
-
2897
- randomBSSFactorySet(
2898
- species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails
2899
- ): RandomTeamsTypes.RandomFactorySet | null {
2900
- const id = toID(species.name);
2901
- const setList = this.randomBSSFactorySets[id].sets;
2902
-
2903
- const movesMax: {[k: string]: number} = {
2904
- batonpass: 1,
2905
- stealthrock: 1,
2906
- toxicspikes: 1,
2907
- trickroom: 1,
2908
- auroraveil: 1,
2909
- };
2910
-
2911
- const requiredMoves: {[k: string]: number} = {};
2912
-
2913
- // Build a pool of eligible sets, given the team partners
2914
- // Also keep track of sets with moves the team requires
2915
- let effectivePool: {set: AnyObject, moveVariants?: number[], itemVariants?: number, abilityVariants?: number}[] = [];
2916
- const priorityPool = [];
2917
- for (const curSet of setList) {
2918
- let reject = false;
2919
- let hasRequiredMove = false;
2920
- const curSetMoveVariants = [];
2921
- for (const move of curSet.moves) {
2922
- const variantIndex = this.random(move.length);
2923
- const moveId = toID(move[variantIndex]);
2924
- if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
2925
- reject = true;
2926
- break;
2927
- }
2928
- if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
2929
- hasRequiredMove = true;
2930
- }
2931
- curSetMoveVariants.push(variantIndex);
2932
- }
2933
- if (reject) continue;
2934
- const set = {set: curSet, moveVariants: curSetMoveVariants};
2935
- effectivePool.push(set);
2936
- if (hasRequiredMove) priorityPool.push(set);
2937
- }
2938
- if (priorityPool.length) effectivePool = priorityPool;
2939
-
2940
- if (!effectivePool.length) {
2941
- if (!teamData.forceResult) return null;
2942
- for (const curSet of setList) {
2943
- effectivePool.push({set: curSet});
2944
- }
2945
- }
2946
-
2947
- const setData = this.sample(effectivePool);
2948
- const moves = [];
2949
- for (const [i, moveSlot] of setData.set.moves.entries()) {
2950
- moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
2951
- }
2952
-
2953
- const setDataAbility = this.sampleIfArray(setData.set.ability);
2954
- return {
2955
- name: setData.set.nickname || setData.set.name || species.baseSpecies,
2956
- species: setData.set.species,
2957
- gigantamax: setData.set.gigantamax,
2958
- gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
2959
- item: this.sampleIfArray(setData.set.item) || '',
2960
- ability: setDataAbility || species.abilities['0'],
2961
- shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
2962
- level: setData.set.level || 50,
2963
- happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
2964
- evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
2965
- ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
2966
- nature: setData.set.nature || 'Serious',
2967
- moves,
2968
- };
2969
- }
2970
-
2971
- randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
2972
- this.enforceNoDirectCustomBanlistChanges();
2973
-
2974
- const forceResult = (depth >= 4);
2975
-
2976
- const pokemon = [];
2977
-
2978
- const pokemonPool = Object.keys(this.randomBSSFactorySets);
2979
-
2980
- const teamData: TeamData = {
2981
- typeCount: {}, typeComboCount: {}, baseFormes: {}, has: {}, forceResult: forceResult,
2982
- weaknesses: {}, resistances: {},
2983
- };
2984
- const requiredMoveFamilies: string[] = [];
2985
- const requiredMoves: {[k: string]: string} = {};
2986
- const weatherAbilitiesSet: {[k: string]: string} = {
2987
- drizzle: 'raindance',
2988
- drought: 'sunnyday',
2989
- snowwarning: 'hail',
2990
- sandstream: 'sandstorm',
2991
- };
2992
- const resistanceAbilities: {[k: string]: string[]} = {
2993
- waterabsorb: ['Water'],
2994
- flashfire: ['Fire'],
2995
- lightningrod: ['Electric'], voltabsorb: ['Electric'],
2996
- thickfat: ['Ice', 'Fire'],
2997
- levitate: ['Ground'],
2998
- };
2999
-
3000
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
3001
- // Weighted random sampling
3002
- let maxUsage = 0;
3003
- const sets: {[k: string]: number} = {};
3004
- for (const specie of pokemonPool) {
3005
- if (teamData.baseFormes[this.dex.species.get(specie).baseSpecies]) continue; // Species Clause
3006
- const usage: number = this.randomBSSFactorySets[specie].usage;
3007
- sets[specie] = usage + maxUsage;
3008
- maxUsage += usage;
3009
- }
3010
-
3011
- const usage = this.random(1, maxUsage);
3012
- let last = 0;
3013
- let specie;
3014
- for (const key of Object.keys(sets)) {
3015
- if (usage > last && usage <= sets[key]) {
3016
- specie = key;
3017
- break;
3018
- }
3019
- last = sets[key];
3020
- }
3021
-
3022
- const species = this.dex.species.get(specie);
3023
- if (!species.exists) continue;
3024
- if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
3025
-
3026
- // Limit to one of each species (Species Clause)
3027
- if (teamData.baseFormes[species.baseSpecies]) continue;
3028
-
3029
- // Limit 2 of any type (most of the time)
3030
- const types = species.types;
3031
- let skip = false;
3032
- for (const type of types) {
3033
- if (teamData.typeCount[type] > 1 && this.randomChance(4, 5)) {
3034
- skip = true;
3035
- break;
3036
- }
3037
- }
3038
- if (skip) continue;
3039
-
3040
- const set = this.randomBSSFactorySet(species, teamData);
3041
- if (!set) continue;
3042
-
3043
- // Limit 1 of any type combination
3044
- let typeCombo = types.slice().sort().join();
3045
- if (set.ability === 'Drought' || set.ability === 'Drizzle') {
3046
- // Drought and Drizzle don't count towards the type combo limit
3047
- typeCombo = set.ability;
3048
- }
3049
- if (typeCombo in teamData.typeComboCount) continue;
3050
-
3051
- const itemData = this.dex.items.get(set.item);
3052
- if (teamData.has[itemData.id]) continue; // Item Clause
3053
-
3054
- // Okay, the set passes, add it to our team
3055
- pokemon.push(set);
3056
-
3057
- // Now that our Pokemon has passed all checks, we can update team data:
3058
- for (const type of types) {
3059
- if (type in teamData.typeCount) {
3060
- teamData.typeCount[type]++;
3061
- } else {
3062
- teamData.typeCount[type] = 1;
3063
- }
3064
- }
3065
- teamData.typeComboCount[typeCombo] = 1;
3066
-
3067
- teamData.baseFormes[species.baseSpecies] = 1;
3068
-
3069
- teamData.has[itemData.id] = 1;
3070
-
3071
- const abilityState = this.dex.abilities.get(set.ability);
3072
- if (abilityState.id in weatherAbilitiesSet) {
3073
- teamData.weather = weatherAbilitiesSet[abilityState.id];
3074
- }
3075
-
3076
- for (const move of set.moves) {
3077
- const moveId = toID(move);
3078
- if (moveId in teamData.has) {
3079
- teamData.has[moveId]++;
3080
- } else {
3081
- teamData.has[moveId] = 1;
3082
- }
3083
- if (moveId in requiredMoves) {
3084
- teamData.has[requiredMoves[moveId]] = 1;
3085
- }
3086
- }
3087
-
3088
- for (const typeName of this.dex.types.names()) {
3089
- // Cover any major weakness (3+) with at least one resistance
3090
- if (teamData.resistances[typeName] >= 1) continue;
3091
- if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
3092
- // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
3093
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
3094
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
3095
- continue;
3096
- }
3097
- const typeMod = this.dex.getEffectiveness(typeName, types);
3098
- if (typeMod < 0) {
3099
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
3100
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
3101
- } else if (typeMod > 0) {
3102
- teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
3103
- }
3104
- }
3105
- }
3106
- if (pokemon.length < this.maxTeamSize) return this.randomBSSFactoryTeam(side, ++depth);
3107
-
3108
- // Quality control
3109
- if (!teamData.forceResult) {
3110
- for (const requiredFamily of requiredMoveFamilies) {
3111
- if (!teamData.has[requiredFamily]) return this.randomBSSFactoryTeam(side, ++depth);
3112
- }
3113
- for (const type in teamData.weaknesses) {
3114
- if (teamData.weaknesses[type] >= 3) return this.randomBSSFactoryTeam(side, ++depth);
3115
- }
3116
- }
3117
-
3118
- return pokemon;
3119
- }
3120
- }
3121
-
3122
- export default RandomTeams;