@pkmn/randoms 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gen5.ts DELETED
@@ -1,916 +0,0 @@
1
- import {MoveCounter} from './gen8';
2
- import {RandomGen6Teams} from './gen6';
3
- import {Utils} from './utils';
4
- import {
5
- Format,
6
- ModdedDex,
7
- Move,
8
- PRNG,
9
- PRNGSeed,
10
- RandomTeamsTypes,
11
- SparseStatsTable,
12
- Species,
13
- StatID,
14
- toID,
15
- } from '@pkmn/sim';
16
-
17
- export class RandomGen5Teams extends RandomGen6Teams {
18
- constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
19
- super(dex, format, prng);
20
- this.moveEnforcementCheckers = {
21
- lead: (movePool, moves, abilities, types, counter) => (
22
- movePool.includes('stealthrock') &&
23
- !!counter.get('Status') &&
24
- !counter.setupType &&
25
- !counter.get('speedsetup') &&
26
- !moves.has('substitute')
27
- ),
28
- Dark: (movePool, moves, abilities, types, counter) => !counter.get('Dark'),
29
- Dragon: (movePool, moves, abilities, types, counter) => !counter.get('Dragon'),
30
- Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric') || movePool.includes('thunder'),
31
- Fighting: (movePool, moves, abilities, types, counter, species) => (
32
- !counter.get('Fighting') &&
33
- (species.baseStats.atk >= 90 || abilities.has('Pure Power') || !!counter.setupType || !counter.get('Status'))
34
- ),
35
- Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'),
36
- Flying: (movePool, moves, abilities, types, counter) => (
37
- !counter.get('Flying') && (types.has('Normal') || abilities.has('Serene Grace'))
38
- ),
39
- Ghost: (movePool, moves, abilities, types, counter) => !types.has('Dark') && !counter.get('Ghost'),
40
- Grass: movePool => movePool.includes('hornleech') || movePool.includes('seedflare'),
41
- Ground: (movePool, moves, abilities, types, counter) => (
42
- !counter.get('Ground') && !moves.has('rest') && !moves.has('sleeptalk')
43
- ),
44
- Ice: (movePool, moves, abilities, types, counter) => !counter.get('Ice'),
45
- Normal: (movePool, moves, abilities, types, counter, species) => (
46
- movePool.includes('return') && species.baseStats.atk > 80
47
- ),
48
- Rock: (movePool, moves, abilities, types, counter, species) => !counter.get('Rock') && species.baseStats.atk >= 80,
49
- Steel: (movePool, moves, abilities, types, counter) => !counter.get('Steel') && abilities.has('Technician'),
50
- Water: (movePool, moves, abilities, types, counter) => (
51
- !counter.get('Water') || (abilities.has('Adaptability') && movePool.includes('waterfall'))
52
- ),
53
- Contrary: (movePool, moves, abilities, types, counter, species, teamDetails) => (
54
- !counter.get('contrary') && species.name !== 'Shuckle'
55
- ),
56
- Guts: (movePool, moves, abilities, types) => types.has('Normal') && movePool.includes('facade'),
57
- 'Slow Start': movePool => movePool.includes('substitute'),
58
- };
59
- }
60
-
61
- shouldCullMove(
62
- move: Move,
63
- types: Set<string>,
64
- moves: Set<string>,
65
- abilities: Set<string>,
66
- counter: MoveCounter,
67
- movePool: string[],
68
- teamDetails: RandomTeamsTypes.TeamDetails,
69
- species: Species,
70
- isLead: boolean,
71
- ): {cull: boolean, isSetup?: boolean} {
72
- const hasRestTalk = moves.has('rest') && moves.has('sleeptalk');
73
- switch (move.id) {
74
- // Not very useful without their supporting moves
75
- case 'batonpass':
76
- return {cull: !counter.setupType && !counter.get('speedsetup') && !moves.has('substitute') && !moves.has('wish')};
77
- case 'endeavor':
78
- return {cull: !isLead};
79
- case 'focuspunch':
80
- return {cull: !moves.has('substitute') || counter.damagingMoves.size < 2 || moves.has('swordsdance')};
81
- case 'rest':
82
- return {cull: movePool.includes('sleeptalk')};
83
- case 'sleeptalk':
84
- if (movePool.length > 1) {
85
- const rest = movePool.indexOf('rest');
86
- if (rest >= 0) this.fastPop(movePool, rest);
87
- }
88
- return {cull: !moves.has('rest')};
89
- case 'storedpower':
90
- return {cull: !counter.setupType && !moves.has('cosmicpower')};
91
- case 'weatherball':
92
- return {cull: !moves.has('sunnyday')};
93
-
94
- // Set up once and only if we have the moves for it
95
- case 'bellydrum': case 'bulkup': case 'coil': case 'curse': case 'dragondance': case 'honeclaws': case 'swordsdance':
96
- return {cull: (counter.setupType !== 'Physical' || counter.get('physicalsetup') > 1 || (
97
- counter.get('Physical') + counter.get('physicalpool') < 2 &&
98
- !moves.has('batonpass') &&
99
- !hasRestTalk
100
- )), isSetup: true};
101
- case 'calmmind': case 'nastyplot': case 'tailglow':
102
- return {cull: (counter.setupType !== 'Special' || counter.get('specialsetup') > 1 || (
103
- counter.get('Special') + counter.get('specialpool') < 2 &&
104
- !moves.has('batonpass') &&
105
- !hasRestTalk
106
- )), isSetup: true};
107
- case 'growth': case 'shellsmash': case 'workup':
108
- const moveTotal = counter.damagingMoves.size + counter.get('physicalpool') + counter.get('specialpool');
109
- return {
110
- cull: (
111
- counter.setupType !== 'Mixed' ||
112
- counter.get('mixedsetup') > 1 ||
113
- moveTotal < 2 ||
114
- (move.id === 'growth' && !moves.has('sunnyday'))
115
- ),
116
- isSetup: true,
117
- };
118
- case 'agility': case 'autotomize': case 'rockpolish':
119
- return {
120
- cull: (
121
- (counter.damagingMoves.size < 2 && !counter.setupType && !moves.has('batonpass')) ||
122
- hasRestTalk
123
- ),
124
- isSetup: !counter.setupType,
125
- };
126
-
127
- // Bad after setup
128
- case 'bulletpunch':
129
- return {cull: !!counter.get('speedsetup')};
130
- case 'circlethrow': case 'dragontail':
131
- return {cull: moves.has('substitute') || (!!counter.setupType && !moves.has('rest') && !moves.has('sleeptalk'))};
132
- case 'fakeout': case 'healingwish':
133
- return {cull: !!counter.setupType || !!counter.get('recovery') || moves.has('substitute')};
134
- case 'haze': case 'magiccoat': case 'pursuit': case 'spikes':
135
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('rest') || moves.has('trickroom')};
136
- case 'leechseed': case 'roar': case 'whirlwind':
137
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('dragontail')};
138
- case 'nightshade': case 'seismictoss': case 'superfang':
139
- return {cull: !!counter.setupType || counter.damagingMoves.size > 1};
140
- case 'protect':
141
- return {cull: (
142
- moves.has('rest') ||
143
- (counter.setupType && !abilities.has('Speed Boost') && !moves.has('wish')) ||
144
- moves.has('lightscreen') && moves.has('reflect')
145
- )};
146
- case 'rapidspin':
147
- return {cull: moves.has('shellsmash') || (!!counter.setupType && counter.get('Status') >= 2)};
148
- case 'stealthrock':
149
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('rest') || !!teamDetails.stealthRock};
150
- case 'switcheroo': case 'trick':
151
- return {cull: (
152
- counter.get('Physical') + counter.get('Special') < 3 ||
153
- !!counter.get('priority') ||
154
- moves.has('rapidspin')
155
- )};
156
- case 'toxic':
157
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('trickroom')};
158
- case 'toxicspikes':
159
- return {cull: !!counter.setupType || !!teamDetails.toxicSpikes};
160
- case 'trickroom':
161
- return {cull: (
162
- !!counter.setupType ||
163
- !!counter.get('speedsetup') ||
164
- counter.damagingMoves.size < 2 ||
165
- moves.has('lightscreen') || moves.has('reflect')
166
- )};
167
- case 'uturn':
168
- // Infernape doesn't want mixed sets with U-turn
169
- const infernapeCase = species.id === 'infernape' && !!counter.get('Special');
170
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('batonpass') || infernapeCase};
171
- case 'voltswitch':
172
- return {cull: (
173
- !!counter.setupType ||
174
- !!counter.get('speedsetup') ||
175
- ['batonpass', 'magnetrise', 'uturn'].some(m => moves.has(m))
176
- )};
177
-
178
- // Ineffective having both
179
- // Attacks:
180
- case 'bugbite':
181
- return {cull: moves.has('uturn')};
182
- case 'crunch':
183
- return {cull: !types.has('Dark') && moves.has('suckerpunch')};
184
- case 'dragonpulse': case 'spacialrend':
185
- return {cull: moves.has('dracometeor') || moves.has('outrage')};
186
- case 'thunderbolt':
187
- return {cull: moves.has('wildcharge')};
188
- case 'drainpunch': case 'focusblast':
189
- return {cull: moves.has('closecombat') || moves.has('lowkick')};
190
- case 'blueflare': case 'flareblitz': case 'fierydance': case 'flamethrower': case 'lavaplume':
191
- return {cull: ['fireblast', 'overheat', 'vcreate'].some(m => moves.has(m))};
192
- case 'bravebird': case 'pluck':
193
- return {cull: moves.has('acrobatics') || moves.has('hurricane')};
194
- case 'gigadrain':
195
- return {cull: (!counter.setupType && moves.has('leafstorm')) || moves.has('petaldance') || moves.has('powerwhip')};
196
- case 'solarbeam':
197
- return {cull: (!abilities.has('Drought') && !moves.has('sunnyday')) || moves.has('gigadrain')};
198
- case 'leafstorm':
199
- return {cull: !!counter.setupType && moves.has('gigadrain')};
200
- case 'bonemerang': case 'earthpower':
201
- return {cull: moves.has('earthquake')};
202
- case 'extremespeed': case 'headsmash':
203
- return {cull: moves.has('roost')};
204
- case 'facade':
205
- return {cull: moves.has('suckerpunch') && !types.has('Normal')};
206
- case 'judgment':
207
- return {cull: counter.setupType !== 'Special' && counter.get('stab') > 1};
208
- case 'return':
209
- return {cull: moves.has('doubleedge')};
210
- case 'poisonjab':
211
- return {cull: moves.has('gunkshot')};
212
- case 'psychic':
213
- return {cull: moves.has('psyshock')};
214
- case 'scald': case 'surf':
215
- return {cull: moves.has('hydropump') || moves.has('waterfall')};
216
- case 'waterspout':
217
- return {cull: !!counter.get('Status')};
218
-
219
- // Status:
220
- case 'encore': case 'icepunch': case 'raindance': case 'suckerpunch':
221
- return {cull: moves.has('thunderwave') || hasRestTalk};
222
- case 'glare': case 'headbutt':
223
- return {cull: moves.has('bodyslam')};
224
- case 'healbell':
225
- return {cull: !!counter.get('speedsetup') || moves.has('magiccoat')};
226
- case 'moonlight': case 'painsplit': case 'recover': case 'roost': case 'softboiled': case 'synthesis':
227
- return {cull: ['leechseed', 'rest', 'wish'].some(m => moves.has(m))};
228
- case 'substitute':
229
- return {cull: (
230
- (moves.has('doubleedge') && !abilities.has('rockhead')) ||
231
- ['pursuit', 'rest', 'superpower', 'uturn', 'voltswitch'].some(m => moves.has(m))
232
- )};
233
- case 'thunderwave':
234
- return {cull: (
235
- !!counter.setupType ||
236
- !!counter.get('speedsetup') ||
237
- hasRestTalk ||
238
- moves.has('discharge') || moves.has('trickroom')
239
- )};
240
- case 'willowisp':
241
- return {cull: moves.has('lavaplume') || moves.has('scald') && !types.has('Ghost')};
242
- }
243
-
244
- return {cull: false};
245
- }
246
-
247
- shouldCullAbility(
248
- ability: string,
249
- types: Set<string>,
250
- moves: Set<string>,
251
- abilities: Set<string>,
252
- counter: MoveCounter,
253
- movePool: string[],
254
- teamDetails: RandomTeamsTypes.TeamDetails,
255
- species: Species
256
- ): boolean {
257
- switch (ability) {
258
- case 'Anger Point': case 'Gluttony': case 'Keen Eye': case 'Moody':
259
- case 'Sand Veil': case 'Snow Cloak': case 'Steadfast': case 'Weak Armor':
260
- return true;
261
- case 'Analytic': case 'Download': case 'Hyper Cutter':
262
- return species.nfe;
263
- case 'Chlorophyll': case 'Solar Power':
264
- return (!moves.has('sunnyday') && !teamDetails.sun);
265
- case 'Compound Eyes': case 'No Guard':
266
- return !counter.get('inaccurate');
267
- case 'Contrary': case 'Iron Fist': case 'Skill Link':
268
- return !counter.get(toID(ability));
269
- case 'Defiant': case 'Moxie':
270
- return (!counter.get('Physical') && !moves.has('batonpass'));
271
- case 'Flash Fire':
272
- return abilities.has('Drought');
273
- case 'Hydration': case 'Rain Dish': case 'Swift Swim':
274
- return (!moves.has('raindance') && !teamDetails.rain);
275
- case 'Hustle':
276
- return counter.get('Physical') < 2;
277
- case 'Ice Body':
278
- return !teamDetails.hail;
279
- case 'Immunity':
280
- return abilities.has('Toxic Boost');
281
- case 'Intimidate':
282
- return moves.has('rest') || species.id === 'staraptor';
283
- case 'Lightning Rod':
284
- return species.types.includes('Ground');
285
- case 'Limber':
286
- return species.types.includes('Electric');
287
- case 'Mold Breaker':
288
- return (abilities.has('Adaptability') || moves.has('rest') && moves.has('sleeptalk'));
289
- case 'Overgrow':
290
- return !counter.get('Grass');
291
- case 'Poison Heal':
292
- return (abilities.has('Technician') && !!counter.get('technician'));
293
- case 'Prankster':
294
- return !counter.get('Status');
295
- case 'Pressure': case 'Synchronize':
296
- return (counter.get('Status') < 2 || abilities.has('Trace'));
297
- case 'Reckless': case 'Rock Head':
298
- return (!counter.get('recoil') || abilities.has('Sap Sipper'));
299
- case 'Regenerator':
300
- return abilities.has('Magic Guard');
301
- case 'Sand Force': case 'Sand Rush':
302
- return !teamDetails.sand;
303
- case 'Serene Grace':
304
- return (!counter.get('serenegrace') || species.id === 'blissey');
305
- case 'Sheer Force':
306
- return (!counter.get('sheerforce') || abilities.has('Guts'));
307
- case 'Sturdy':
308
- return (!!counter.get('recoil') && !counter.get('recovery'));
309
- case 'Swarm':
310
- return !counter.get('Bug');
311
- case 'Technician':
312
- return (!counter.get('technician') || moves.has('tailslap'));
313
- case 'Tinted Lens':
314
- return (abilities.has('Insomnia') || abilities.has('Magic Guard') || moves.has('protect'));
315
- case 'Unaware':
316
- return (!!counter.setupType || abilities.has('Magic Guard'));
317
- case 'Unburden':
318
- return species.baseStats.spe > 100 && !moves.has('acrobatics');
319
- case 'Water Absorb':
320
- return (abilities.has('Drizzle') || abilities.has('Unaware') || abilities.has('Volt Absorb'));
321
- }
322
-
323
- return false;
324
- }
325
-
326
- getHighPriorityItem(
327
- ability: string,
328
- types: Set<string>,
329
- moves: Set<string>,
330
- counter: MoveCounter,
331
- teamDetails: RandomTeamsTypes.TeamDetails,
332
- species: Species,
333
- isLead: boolean
334
- ): string | undefined {
335
- if (species.requiredItem) return species.requiredItem;
336
- if (species.requiredItems) return this.sample(species.requiredItems);
337
-
338
- if (species.name === 'Marowak') return 'Thick Club';
339
- if (species.name === 'Farfetch\u2019d') return 'Stick';
340
- if (species.name === 'Pikachu') return 'Light Ball';
341
- if (species.name === 'Shedinja' || species.name === 'Smeargle') return 'Focus Sash';
342
- if (species.name === 'Unown') return 'Choice Specs';
343
- if (species.name === 'Wobbuffet' && moves.has('destinybond') && this.randomChance(1, 2)) return 'Custap Berry';
344
- if (ability === 'Imposter') return 'Choice Scarf';
345
- if (moves.has('switcheroo') || moves.has('trick')) {
346
- if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && !counter.get('priority')) {
347
- return 'Choice Scarf';
348
- } else {
349
- return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
350
- }
351
- }
352
- if (species.nfe) return 'Eviolite';
353
- if (moves.has('shellsmash')) return 'White Herb';
354
- if (ability === 'Harvest' || moves.has('bellydrum')) return 'Sitrus Berry';
355
- if ((ability === 'Magic Guard' || ability === 'Sheer Force') && counter.damagingMoves.size > 1) return 'Life Orb';
356
- if (
357
- ability === 'Poison Heal' ||
358
- ability === 'Toxic Boost' ||
359
- (ability === 'Quick Feet' && moves.has('facade')) ||
360
- moves.has('psychoshift')
361
- ) {
362
- return 'Toxic Orb';
363
- }
364
- if (moves.has('rest') && !moves.has('sleeptalk') && ability !== 'Natural Cure' && ability !== 'Shed Skin') {
365
- return 'Chesto Berry';
366
- }
367
- if (ability === 'Guts' && moves.has('facade')) {
368
- return (types.has('Fire') || moves.has('uturn') || moves.has('voltswitch')) ? 'Toxic Orb' : 'Flame Orb';
369
- }
370
- if (moves.has('raindance')) return (ability === 'Forecast') ? 'Damp Rock' : 'Life Orb';
371
- if (moves.has('sunnyday')) return (ability === 'Forecast' || ability === 'Flower Gift') ? 'Heat Rock' : 'Life Orb';
372
- if (moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
373
- if (moves.has('acrobatics')) return 'Flying Gem';
374
- if (ability === 'Unburden') return moves.has('fakeout') ? 'Normal Gem' : `${species.types[0]} Gem`;
375
- }
376
-
377
- getLowPriorityItem(
378
- ability: string,
379
- types: Set<string>,
380
- moves: Set<string>,
381
- abilities: Set<string>,
382
- counter: MoveCounter,
383
- teamDetails: RandomTeamsTypes.TeamDetails,
384
- species: Species,
385
- isLead: boolean,
386
- ): string | undefined {
387
- if (
388
- ability === 'Speed Boost' &&
389
- !moves.has('substitute') &&
390
- counter.get('Physical') + counter.get('Special') > 2
391
- ) {
392
- return 'Life Orb';
393
- }
394
- if (
395
- counter.get('Physical') >= 4 &&
396
- ['dragontail', 'fakeout', 'flamecharge'].every(m => !moves.has(m)) &&
397
- !moves.has('suckerpunch') &&
398
- (!moves.has('rapidspin') || this.dex.getEffectiveness('Rock', species) < 1)
399
- ) {
400
- return (
401
- (species.baseStats.atk >= 100 || abilities.has('Huge Power')) &&
402
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
403
- !counter.get('priority') &&
404
- this.randomChance(2, 3)
405
- ) ? 'Choice Scarf' : 'Choice Band';
406
- }
407
- if (counter.get('Special') >= 4 || (counter.get('Special') >= 3 && moves.has('uturn'))) {
408
- return (
409
- species.baseStats.spa >= 100 &&
410
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
411
- !moves.has('uturn') &&
412
- (ability === 'Download' || moves.has('eruption') || moves.has('waterspout') || this.randomChance(2, 3))
413
- ) ? 'Choice Scarf' : 'Choice Specs';
414
- }
415
-
416
- if (counter.setupType && moves.has('outrage')) return 'Lum Berry';
417
- if (this.dex.getEffectiveness('Ground', species) >= 2 && ability !== 'Levitate') return 'Air Balloon';
418
- if (
419
- types.has('Poison') ||
420
- ['bodyslam', 'dragontail', 'protect', 'scald', 'sleeptalk', 'substitute'].some(m => moves.has(m))
421
- ) {
422
- return 'Leftovers';
423
- }
424
- if (species.name === 'Palkia' && (moves.has('dracometeor') || moves.has('spacialrend')) && moves.has('hydropump')) {
425
- return 'Lustrous Orb';
426
- }
427
- if (counter.damagingMoves.size >= 4 && ability !== 'Sturdy') {
428
- return moves.has('uturn') ? 'Expert Belt' : 'Life Orb';
429
- }
430
- if (
431
- isLead &&
432
- counter.get('hazards') &&
433
- !counter.get('recovery') &&
434
- ability !== 'Regenerator' &&
435
- species.baseStats.hp + species.baseStats.def + species.baseStats.spd <= 275
436
- ) {
437
- return ability === 'Sturdy' ? 'Custap Berry' : 'Focus Sash';
438
- }
439
- if (moves.has('voltswitch') && species.baseStats.spe <= 90) {
440
- return 'Leftovers';
441
- }
442
- if (
443
- counter.damagingMoves.size >= 3 &&
444
- species.baseStats.spe >= 40 &&
445
- species.baseStats.hp + species.baseStats.def + species.baseStats.spd <= 275 &&
446
- ability !== 'Sturdy' &&
447
- !moves.has('rapidspin') && !moves.has('uturn')
448
- ) {
449
- return 'Life Orb';
450
- }
451
- }
452
-
453
- randomSet(
454
- species: string | Species,
455
- teamDetails: RandomTeamsTypes.TeamDetails = {},
456
- isLead = false
457
- ): RandomTeamsTypes.RandomSet {
458
- species = this.dex.species.get(species);
459
- let forme = species.name;
460
-
461
- if (typeof species.battleOnly === 'string') {
462
- // Only change the forme. The species has custom moves, and may have different typing and requirements.
463
- forme = species.battleOnly;
464
- }
465
- if (species.cosmeticFormes) {
466
- forme = this.sample([species.name].concat(species.cosmeticFormes));
467
- }
468
-
469
- const movePool = (species.randomBattleMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
470
- const rejectedPool = [];
471
- const moves = new Set<string>();
472
- let ability = '';
473
-
474
- const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
475
- const ivs: SparseStatsTable = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
476
- const types = new Set(species.types);
477
- const abilities = new Set(Object.values(species.abilities));
478
- if (species.unreleasedHidden) abilities.delete(species.abilities.H);
479
-
480
- let availableHP = 0;
481
- for (const setMoveid of movePool) {
482
- if (setMoveid.startsWith('hiddenpower')) availableHP++;
483
- }
484
-
485
- let counter: MoveCounter;
486
- // We use a special variable to track Hidden Power
487
- // so that we can check for all Hidden Powers at once
488
- let hasHiddenPower = false;
489
-
490
- do {
491
- // Choose next 4 moves from learnset/viable moves and add them to moves list:
492
- while (moves.size < this.maxMoveCount && movePool.length) {
493
- const moveid = this.sampleNoReplace(movePool);
494
- if (moveid.startsWith('hiddenpower')) {
495
- availableHP--;
496
- if (hasHiddenPower) continue;
497
- hasHiddenPower = true;
498
- }
499
- moves.add(moveid);
500
- }
501
-
502
- while (moves.size < this.maxMoveCount && rejectedPool.length) {
503
- const moveid = this.sampleNoReplace(rejectedPool);
504
- if (moveid.startsWith('hiddenpower')) {
505
- if (hasHiddenPower) {
506
- continue;
507
- }
508
- hasHiddenPower = true;
509
- }
510
- moves.add(moveid);
511
- }
512
-
513
- counter = this.queryMoves(moves, species.types, abilities, movePool);
514
-
515
- // Iterate through the moves again, this time to cull them:
516
- for (const moveid of moves) {
517
- const move = this.dex.moves.get(moveid);
518
-
519
- let {cull, isSetup} = this.shouldCullMove(
520
- move, types, moves, abilities, counter, movePool,
521
- teamDetails, species, isLead
522
- );
523
-
524
- // This move doesn't satisfy our setup requirements:
525
- if (
526
- (move.category === 'Physical' && counter.setupType === 'Special') ||
527
- (move.category === 'Special' && counter.setupType === 'Physical')
528
- ) {
529
- // Reject STABs last in case the setup type changes later on
530
- const stabs = counter.get(species.types[0]) + (counter.get(species.types[1]) || 0);
531
- if (!types.has(move.type) || stabs > 1 || counter.get(move.category) < 2) cull = true;
532
- }
533
- if (
534
- !isSetup &&
535
- counter.setupType &&
536
- counter.setupType !== 'Mixed' &&
537
- move.category !== counter.setupType &&
538
- counter.get(counter.setupType) < 2 &&
539
- !moves.has('batonpass') &&
540
- (move.category !== 'Status' || !move.flags.heal) &&
541
- moveid !== 'sleeptalk' && (
542
- move.category !== 'Status' || (
543
- counter.get(counter.setupType) + counter.get('Status') > 3 &&
544
- counter.get('physicalsetup') + counter.get('specialsetup') < 2
545
- )
546
- )
547
- ) {
548
- // Mono-attacking with setup and RestTalk is allowed
549
- // Reject Status moves only if there is nothing else to reject
550
- cull = true;
551
- }
552
-
553
- if (
554
- counter.setupType === 'Special' &&
555
- moveid === 'hiddenpower' &&
556
- species.types.length > 1 &&
557
- counter.get('Special') <= 2 &&
558
- !types.has(move.type) &&
559
- !counter.get('Physical') &&
560
- counter.get('specialpool')
561
- ) {
562
- // Hidden Power isn't good enough
563
- cull = true;
564
- }
565
-
566
- const runEnforcementChecker = (checkerName: string) => {
567
- if (!this.moveEnforcementCheckers[checkerName]) return false;
568
- return this.moveEnforcementCheckers[checkerName](
569
- movePool, moves, abilities, types, counter, species as Species, teamDetails
570
- );
571
- };
572
- // Pokemon should have moves that benefit their Type/Ability/Weather, as well as moves required by its forme
573
- if (
574
- !cull &&
575
- !['judgment', 'quiverdance', 'sleeptalk'].includes(moveid) &&
576
- !isSetup && !move.weather && !move.damage && (move.category !== 'Status' || !move.flags.heal) && (
577
- move.category === 'Status' ||
578
- !types.has(move.type) ||
579
- move.basePower && move.basePower < 40 && !move.multihit
580
- ) && (counter.get('physicalsetup') + counter.get('specialsetup') < 2 && (
581
- !counter.setupType ||
582
- counter.setupType === 'Mixed' ||
583
- (move.category !== counter.setupType && move.category !== 'Status') ||
584
- counter.get(counter.setupType) + counter.get('Status') > 3
585
- ))
586
- ) {
587
- if (
588
- (
589
- !counter.get('stab') &&
590
- !counter.get('damage') && (
591
- species.types.length > 1 ||
592
- (species.types[0] !== 'Normal' && species.types[0] !== 'Psychic') ||
593
- !moves.has('icebeam') ||
594
- species.baseStats.spa >= species.baseStats.spd
595
- )
596
- ) || (
597
- !counter.get('recovery') &&
598
- !counter.setupType &&
599
- !moves.has('healingwish') &&
600
- (counter.get('Status') > 1 || (species.nfe && !!counter.get('Status'))) &&
601
- (movePool.includes('recover') || movePool.includes('roost') || movePool.includes('softboiled'))
602
- ) || (
603
- movePool.includes('darkvoid') ||
604
- movePool.includes('quiverdance') ||
605
- (species.requiredMove && movePool.includes(toID(species.requiredMove)))
606
- ) || (
607
- isLead && runEnforcementChecker('lead')
608
- )
609
- ) {
610
- cull = true;
611
- } else {
612
- for (const type of types) {
613
- if (runEnforcementChecker(type)) {
614
- cull = true;
615
- }
616
- }
617
- for (const abil of abilities) {
618
- if (runEnforcementChecker(abil)) {
619
- cull = true;
620
- }
621
- }
622
- }
623
- }
624
-
625
- // Sleep Talk shouldn't be selected without Rest
626
- if (moveid === 'rest' && cull) {
627
- const sleeptalk = movePool.indexOf('sleeptalk');
628
- if (sleeptalk >= 0) {
629
- if (movePool.length < 2) {
630
- cull = false;
631
- } else {
632
- this.fastPop(movePool, sleeptalk);
633
- }
634
- }
635
- }
636
-
637
- // Remove rejected moves from the move list
638
- const isHP = moveid.startsWith('hiddenpower');
639
- if (
640
- cull &&
641
- (movePool.length - availableHP || availableHP && (isHP || !hasHiddenPower))
642
- ) {
643
- if (
644
- move.category !== 'Status' && !move.damage && !move.flags.charge &&
645
- (!isHP || !availableHP)
646
- ) rejectedPool.push(moveid);
647
- moves.delete(moveid);
648
- if (isHP) hasHiddenPower = false;
649
- break;
650
- }
651
- if (cull && rejectedPool.length) {
652
- moves.delete(moveid);
653
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
654
- break;
655
- }
656
- }
657
- } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
658
-
659
- if (hasHiddenPower) {
660
- let hpType;
661
- for (const move of moves) {
662
- if (move.startsWith('hiddenpower')) hpType = move.substr(11);
663
- }
664
- if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
665
- const HPivs = this.dex.types.get(hpType).HPivs;
666
- let iv: StatID;
667
- for (iv in HPivs) {
668
- ivs[iv] = HPivs[iv];
669
- }
670
- }
671
-
672
-
673
- const abilityData = Array.from(abilities).map(a => this.dex.abilities.get(a));
674
- Utils.sortBy(abilityData, abil => -abil.rating);
675
-
676
- if (abilityData.length > 1) {
677
- // Sort abilities by rating with an element of randomness
678
- if (abilityData[2] && abilityData[1].rating <= abilityData[2].rating && this.randomChance(1, 2)) {
679
- [abilityData[1], abilityData[2]] = [abilityData[2], abilityData[1]];
680
- }
681
- if (abilityData[0].rating <= abilityData[1].rating) {
682
- if (this.randomChance(1, 2)) [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
683
- } else if (abilityData[0].rating - 0.6 <= abilityData[1].rating) {
684
- if (this.randomChance(2, 3)) [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
685
- }
686
-
687
- // Start with the first abiility and work our way through, culling as we go
688
- ability = abilityData[0].name;
689
-
690
-
691
- while (this.shouldCullAbility(ability, types, moves, abilities, counter, movePool, teamDetails, species)) {
692
- if (ability === abilityData[0].name && abilityData[1].rating >= 1) {
693
- ability = abilityData[1].name;
694
- } else if (ability === abilityData[1].name && abilityData[2] && abilityData[2].rating >= 1) {
695
- ability = abilityData[2].name;
696
- } else {
697
- ability = abilityData[0].name;
698
- break;
699
- }
700
- }
701
-
702
- if (abilities.has('Guts') && moves.has('facade') && (ability !== 'Quick Feet' || !counter.setupType)) {
703
- ability = 'Guts';
704
- } else if (abilities.has('Prankster') && counter.get('Status') > 1) {
705
- ability = 'Prankster';
706
- } else if (abilities.has('Quick Feet') && moves.has('facade')) {
707
- ability = 'Quick Feet';
708
- } else if (abilities.has('Swift Swim') && moves.has('raindance')) {
709
- ability = 'Swift Swim';
710
- }
711
- } else {
712
- ability = abilityData[0].name;
713
- }
714
-
715
- let item = this.getHighPriorityItem(ability, types, moves, counter, teamDetails, species, isLead);
716
- if (item === undefined) {
717
- item = this.getLowPriorityItem(ability, types, moves, abilities, counter, teamDetails, species, isLead);
718
- }
719
- if (item === undefined) item = 'Leftovers';
720
- if (item === 'Leftovers' && types.has('Poison')) {
721
- item = 'Black Sludge';
722
- }
723
-
724
- const levelScale: {[tier: string]: number} = {
725
- Uber: 76,
726
- OU: 80,
727
- '(OU)': 82,
728
- UUBL: 82,
729
- UU: 82,
730
- RUBL: 84,
731
- RU: 84,
732
- NUBL: 86,
733
- NU: 86,
734
- '(NU)': 88,
735
- PUBL: 88,
736
- PU: 88,
737
- '(PU)': 90,
738
- };
739
- const customScale: {[forme: string]: number} = {
740
- Delibird: 100, 'Farfetch\u2019d': 100, Luvdisc: 100, Unown: 100,
741
- };
742
- const level = this.adjustLevel || customScale[species.name] || levelScale[species.tier] || (species.nfe ? 90 : 80);
743
-
744
- // Prepare optimal HP
745
- const srWeakness = this.dex.getEffectiveness('Rock', species);
746
- while (evs.hp > 1) {
747
- const hp = Math.floor(
748
- Math.floor(
749
- 2 * species.baseStats.hp + (ivs.hp || 31) + Math.floor(evs.hp / 4) + 100
750
- ) * level / 100 + 10
751
- );
752
- if (moves.has('bellydrum') && item === 'Sitrus Berry') {
753
- // Belly Drum should activate Sitrus Berry
754
- if (hp % 2 === 0) break;
755
- } else {
756
- // Maximize number of Stealth Rock switch-ins
757
- if (srWeakness <= 0 || hp % (4 / srWeakness) > 0) break;
758
- }
759
- evs.hp -= 4;
760
- }
761
-
762
- // Minimize confusion damage
763
- if (!counter.get('Physical') && !moves.has('transform')) {
764
- evs.atk = 0;
765
- ivs.atk = hasHiddenPower ? (ivs.atk || 31) - 28 : 0;
766
- }
767
-
768
- if (['gyroball', 'metalburst', 'trickroom'].some(m => moves.has(m))) {
769
- evs.spe = 0;
770
- ivs.spe = hasHiddenPower ? (ivs.spe || 31) - 28 : 0;
771
- }
772
-
773
- return {
774
- name: species.baseSpecies,
775
- species: forme,
776
- gender: species.gender,
777
- shiny: this.randomChance(1, 1024),
778
- moves: Array.from(moves),
779
- ability,
780
- evs,
781
- ivs,
782
- item,
783
- level,
784
- };
785
- }
786
-
787
- randomTeam() {
788
- this.enforceNoDirectCustomBanlistChanges();
789
-
790
- const seed = this.prng.seed;
791
- const ruleTable = this.dex.formats.getRuleTable(this.format);
792
- const pokemon: RandomTeamsTypes.RandomSet[] = [];
793
-
794
- // For Monotype
795
- const isMonotype = !!this.forceMonotype || ruleTable.has('sametypeclause');
796
- const typePool = this.dex.types.names();
797
- const type = this.forceMonotype || this.sample(typePool);
798
-
799
- const baseFormes: {[k: string]: number} = {};
800
- const tierCount: {[k: string]: number} = {};
801
- const typeCount: {[k: string]: number} = {};
802
- const typeComboCount: {[k: string]: number} = {};
803
- const teamDetails: RandomTeamsTypes.TeamDetails = {};
804
-
805
- const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype);
806
-
807
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
808
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
809
- if (!species.exists || !species.randomBattleMoves) continue;
810
-
811
- // Limit to one of each species (Species Clause)
812
- if (baseFormes[species.baseSpecies]) continue;
813
-
814
- // Adjust rate for species with multiple sets
815
- switch (species.baseSpecies) {
816
- case 'Arceus':
817
- if (this.randomChance(16, 17) && !isMonotype) continue;
818
- break;
819
- case 'Rotom':
820
- if (this.gen < 5 && this.randomChance(5, 6) && !isMonotype) continue;
821
- break;
822
- case 'Basculin': case 'Castform': case 'Cherrim': case 'Meloetta':
823
- if (this.randomChance(1, 2)) continue;
824
- break;
825
- }
826
-
827
- // Illusion shouldn't be in the last slot
828
- if (species.name === 'Zoroark' && pokemon.length > 4) continue;
829
-
830
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
831
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
832
- const tier = species.tier;
833
-
834
- // Limit two Pokemon per tier
835
- if (this.gen === 5 && !isMonotype && !this.forceMonotype && tierCount[tier] >= 2 * limitFactor) continue;
836
-
837
- const set = this.randomSet(species, teamDetails, pokemon.length === 0);
838
-
839
- const types = species.types;
840
-
841
- if (!isMonotype && !this.forceMonotype) {
842
- // Limit two of any type
843
- let skip = false;
844
- for (const typeName of types) {
845
- if (typeCount[typeName] >= 2 * limitFactor) {
846
- skip = true;
847
- break;
848
- }
849
- }
850
- if (skip) continue;
851
- }
852
-
853
- // Limit one of any type combination, two in Monotype
854
- let typeCombo = types.slice().sort().join();
855
- if (set.ability === 'Drought' || set.ability === 'Drizzle' || set.ability === 'Sand Stream') {
856
- // Drought, Drizzle and Sand Stream don't count towards the type combo limit
857
- typeCombo = set.ability;
858
- if (typeCombo in typeComboCount) continue;
859
- } else if (!this.forceMonotype) {
860
- if (typeComboCount[typeCombo] >= (isMonotype ? 2 : 1) * limitFactor) continue;
861
- }
862
-
863
- // Okay, the set passes, add it to our team
864
- pokemon.push(set);
865
-
866
- if (pokemon.length === this.maxTeamSize) {
867
- // Set Zoroark's level to be the same as the last Pokemon
868
- const illusion = teamDetails.illusion;
869
- if (illusion) pokemon[illusion - 1].level = pokemon[this.maxTeamSize - 1].level;
870
- break;
871
- }
872
-
873
- // Now that our Pokemon has passed all checks, we can increment our counters
874
- baseFormes[species.baseSpecies] = 1;
875
-
876
- // Increment tier counter
877
- if (tierCount[tier]) {
878
- tierCount[tier]++;
879
- } else {
880
- tierCount[tier] = 1;
881
- }
882
-
883
- // Increment type counters
884
- for (const typeName of types) {
885
- if (typeName in typeCount) {
886
- typeCount[typeName]++;
887
- } else {
888
- typeCount[typeName] = 1;
889
- }
890
- }
891
- if (typeCombo in typeComboCount) {
892
- typeComboCount[typeCombo]++;
893
- } else {
894
- typeComboCount[typeCombo] = 1;
895
- }
896
-
897
- // Team details
898
- if (set.ability === 'Snow Warning' || set.moves.includes('hail')) teamDetails.hail = 1;
899
- if (set.ability === 'Drizzle' || set.moves.includes('raindance')) teamDetails.rain = 1;
900
- if (set.ability === 'Sand Stream') teamDetails.sand = 1;
901
- if (set.moves.includes('stealthrock')) teamDetails.stealthRock = 1;
902
- if (set.moves.includes('toxicspikes')) teamDetails.toxicSpikes = 1;
903
- if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1;
904
-
905
- // For setting Zoroark's level
906
- if (set.ability === 'Illusion') teamDetails.illusion = pokemon.length;
907
- }
908
- if (pokemon.length < this.maxTeamSize && pokemon.length < 12) {
909
- throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
910
- }
911
-
912
- return pokemon;
913
- }
914
- }
915
-
916
- export default RandomGen5Teams;