@pkmn/randoms 0.6.4 → 0.7.1

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