@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/gen6.ts DELETED
@@ -1,1357 +0,0 @@
1
- import {BattleFactorySpecies, RandomGen7Teams} from './gen7';
2
- import {MoveCounter, TeamData} from './gen8';
3
- import {Utils} from './utils';
4
- import {
5
- AnyObject,
6
- Format,
7
- ModdedDex,
8
- Move,
9
- PRNG,
10
- PRNGSeed,
11
- PlayerOptions,
12
- RandomTeamsTypes,
13
- Species,
14
- StatID,
15
- toID,
16
- } from '@pkmn/sim';
17
-
18
- export class RandomGen6Teams extends RandomGen7Teams {
19
- constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
20
- super(dex, format, prng);
21
- this.noStab = [...this.noStab, 'aquajet', 'fakeout', 'iceshard', 'machpunch', 'quickattack', 'vacuumwave'];
22
-
23
- this.moveEnforcementCheckers = {
24
- Bug: movePool => movePool.includes('megahorn') || movePool.includes('pinmissile'),
25
- Dark: (movePool, moves, abilities, types, counter, species) => (
26
- (!counter.get('Dark') && !abilities.has('Protean'))
27
- ),
28
- Dragon: (movePool, moves, abilities, types, counter) => (
29
- !counter.get('Dragon') &&
30
- !abilities.has('Aerilate') &&
31
- !abilities.has('Pixilate') &&
32
- !moves.has('rest') &&
33
- !moves.has('sleeptalk')
34
- ),
35
- Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric'),
36
- Fairy: (movePool, moves, abilities, types, counter) => (
37
- !counter.get('Fairy') && !abilities.has('Pixilate') && (!!counter.setupType || !counter.get('Status'))
38
- ),
39
- Fighting: (movePool, moves, abilities, types, counter, species) => (
40
- !counter.get('Fighting') && (
41
- species.baseStats.atk >= 110 ||
42
- abilities.has('Justified') || abilities.has('Unburden') ||
43
- !!counter.setupType || !counter.get('Status')
44
- )
45
- ),
46
- Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire') || movePool.includes('quiverdance'),
47
- Flying: (movePool, moves, abilities, types, counter) => (
48
- !counter.get('Flying') && (
49
- abilities.has('Gale Wings') ||
50
- abilities.has('Serene Grace') ||
51
- (types.has('Normal') && movePool.includes('bravebird'))
52
- )
53
- ),
54
- Ghost: (movePool, moves, abilities, types, counter) => !types.has('Dark') && !counter.get('Ghost'),
55
- Grass: (movePool, moves, abilities, types, counter) => (
56
- !counter.get('Grass') && !types.has('Fairy') && !types.has('Poison') && !types.has('Steel')
57
- ),
58
- Ground: (movePool, moves, abilities, types, counter) => (
59
- !counter.get('Ground') && !moves.has('rest') && !moves.has('sleeptalk')
60
- ),
61
- Ice: (movePool, moves, abilities, types, counter) => !counter.get('Ice') && !abilities.has('Refrigerate'),
62
- Normal: movePool => movePool.includes('facade'),
63
- Psychic: (movePool, moves, abilities, types, counter, species) => (
64
- !!counter.get('Psychic') &&
65
- !types.has('Flying') &&
66
- !abilities.has('Pixilate') &&
67
- counter.get('stab') < species.types.length
68
- ),
69
- Rock: (movePool, moves, abilities, types, counter) => (
70
- !counter.get('Rock') &&
71
- !types.has('Fairy') &&
72
- (abilities.has('Rock Head') || counter.setupType === 'Physical')
73
- ),
74
- Steel: (movePool, moves, abilities, types, counter) => (
75
- !counter.get('Steel') && (abilities.has('Technician') || movePool.includes('meteormash'))
76
- ),
77
- Water: (movePool, moves, abilities, types, counter) => (
78
- (!counter.get('Water') || !counter.get('stab')) &&
79
- !abilities.has('Protean')
80
- ),
81
- Adaptability: (movePool, moves, abilities, types, counter, species) => (
82
- !counter.setupType &&
83
- species.types.length > 1 &&
84
- (!counter.get(species.types[0]) || !counter.get(species.types[1]))
85
- ),
86
- Aerilate: (movePool, moves, abilities, types, counter) => !counter.get('Normal'),
87
- Pixilate: (movePool, moves, abilities, types, counter) => !counter.get('Normal'),
88
- Refrigerate: (movePool, moves, abilities, types, counter) => !moves.has('blizzard') && !counter.get('Normal'),
89
- Contrary: (movePool, moves, abilities, types, counter, species) => (
90
- !counter.get('contrary') && species.name !== 'Shuckle'
91
- ),
92
- 'Bad Dreams': movePool => movePool.includes('darkvoid'),
93
- 'Slow Start': movePool => movePool.includes('substitute'),
94
- };
95
- }
96
-
97
- shouldCullMove(
98
- move: Move,
99
- types: Set<string>,
100
- moves: Set<string>,
101
- abilities: Set<string>,
102
- counter: MoveCounter,
103
- movePool: string[],
104
- teamDetails: RandomTeamsTypes.TeamDetails,
105
- species: Species,
106
- isLead: boolean,
107
- ): {cull: boolean, isSetup?: boolean} {
108
- const restTalk = moves.has('rest') && moves.has('sleeptalk');
109
-
110
- if (move.priority > 0 && counter.get('speedsetup')) return {cull: true};
111
-
112
- switch (move.id) {
113
- // Not very useful without their supporting moves
114
- case 'cottonguard': case 'defendorder':
115
- return {cull: !counter.get('recovery') && !moves.has('rest')};
116
- case 'focuspunch':
117
- return {cull: !moves.has('substitute') || counter.damagingMoves.size < 2};
118
- case 'perishsong':
119
- return {cull: !moves.has('protect')};
120
- case 'reflect':
121
- if (movePool.length > 1) {
122
- const screen = movePool.indexOf('lightscreen');
123
- if (screen >= 0) this.fastPop(movePool, screen);
124
- }
125
- return {cull: !moves.has('calmmind') && !moves.has('lightscreen')};
126
- case 'rest':
127
- return {cull: movePool.includes('sleeptalk')};
128
- case 'sleeptalk':
129
- if (movePool.length > 1) {
130
- const rest = movePool.indexOf('rest');
131
- if (rest >= 0) this.fastPop(movePool, rest);
132
- }
133
- return {cull: !moves.has('rest')};
134
- case 'storedpower':
135
- return {cull: !counter.setupType};
136
- case 'switcheroo': case 'trick':
137
- return {cull: counter.get('Physical') + counter.get('Special') < 3 || !!counter.get('priority')};
138
-
139
- // Set up once and only if we have the moves for it
140
- case 'bellydrum': case 'bulkup': case 'coil': case 'curse': case 'dragondance': case 'honeclaws': case 'swordsdance':
141
- return {cull: (
142
- (move.id === 'bellydrum' && !abilities.has('Unburden') && !counter.get('priority')) ||
143
- (counter.get('Physical') + counter.get('physicalpool') < 2 && (!moves.has('rest') || !moves.has('sleeptalk'))) || (
144
- (counter.setupType !== 'Physical' || counter.get('physicalsetup') > 1) &&
145
- (!moves.has('growth') || moves.has('sunnyday'))
146
- )
147
- ), isSetup: true};
148
- case 'calmmind': case 'geomancy': case 'nastyplot': case 'quiverdance': case 'tailglow':
149
- if (types.has('Dark') && moves.has('darkpulse')) {
150
- counter.setupType = 'Special';
151
- return {cull: false, isSetup: true};
152
- }
153
- return {cull: (
154
- counter.setupType !== 'Special' ||
155
- counter.get('specialsetup') > 1 ||
156
- (counter.get('Special') + counter.get('specialpool') < 2 && (!moves.has('rest') || !moves.has('sleeptalk')))
157
- ), isSetup: true};
158
- case 'growth': case 'shellsmash': case 'workup':
159
- return {cull: (
160
- counter.setupType !== 'Mixed' ||
161
- counter.get('mixedsetup') > 1 ||
162
- counter.damagingMoves.size + counter.get('physicalpool') + counter.get('specialpool') < 2 ||
163
- (move.id === 'growth' && !moves.has('sunnyday'))
164
- ), isSetup: true};
165
- case 'agility': case 'autotomize': case 'rockpolish': case 'shiftgear':
166
- return {cull: counter.damagingMoves.size < 2 || restTalk, isSetup: !counter.setupType};
167
- case 'flamecharge':
168
- return {cull: (
169
- moves.has('dracometeor') ||
170
- moves.has('overheat') ||
171
- (counter.damagingMoves.size < 3 && !counter.setupType)
172
- )};
173
-
174
- // Bad after setup
175
- case 'circlethrow': case 'dragontail':
176
- return {cull: (
177
- (!!counter.setupType && ((!moves.has('rest') && !moves.has('sleeptalk')) || moves.has('stormthrow'))) ||
178
- (!!counter.get('speedsetup') || ['encore', 'raindance', 'roar', 'trickroom', 'whirlwind'].some(m => moves.has(m))) ||
179
- (counter.get(move.type) > 1 && counter.get('Status') > 1) ||
180
- (abilities.has('Sheer Force') && !!counter.get('sheerforce'))
181
- )};
182
- case 'defog':
183
- return {cull: (
184
- !!counter.setupType ||
185
- moves.has('spikes') || moves.has('stealthrock') ||
186
- restTalk ||
187
- !!teamDetails.defog
188
- )};
189
- case 'fakeout': case 'tailwind':
190
- return {cull: !!counter.setupType || ['substitute', 'switcheroo', 'trick'].some(m => moves.has(m))};
191
- case 'foulplay':
192
- return {cull: (
193
- !!counter.setupType ||
194
- !!counter.get('speedsetup') ||
195
- counter.get('Dark') > 2 ||
196
- moves.has('clearsmog') ||
197
- restTalk ||
198
- counter.damagingMoves.size - 1 === counter.get('priority')
199
- )};
200
- case 'haze': case 'spikes':
201
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('trickroom')};
202
- case 'healbell': case 'technoblast':
203
- return {cull: !!counter.get('speedsetup')};
204
- case 'healingwish': case 'memento':
205
- return {cull: !!counter.setupType || !!counter.get('recovery') || moves.has('substitute')};
206
- case 'leechseed': case 'roar': case 'whirlwind':
207
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('dragontail')};
208
- case 'nightshade': case 'seismictoss': case 'superfang':
209
- return {cull: counter.damagingMoves.size > 1 || !!counter.setupType};
210
- case 'protect':
211
- const screens = moves.has('lightscreen') && moves.has('reflect');
212
- return {cull: moves.has('rest') || screens || (!!counter.setupType && !moves.has('wish'))};
213
- case 'pursuit':
214
- return {cull: (
215
- moves.has('nightslash') ||
216
- !!counter.setupType ||
217
- counter.get('Status') > 1 ||
218
- counter.get('Dark') > 2 ||
219
- (moves.has('knockoff') && !types.has('Dark'))
220
- )};
221
- case 'rapidspin':
222
- return {cull: !!counter.setupType || !!teamDetails.rapidSpin};
223
- case 'stealthrock':
224
- return {cull: (
225
- !!counter.setupType ||
226
- !!counter.get('speedsetup') ||
227
- ['rest', 'substitute', 'trickroom'].some(m => moves.has(m)) ||
228
- !!teamDetails.stealthRock
229
- )};
230
- case 'stickyweb':
231
- return {cull: !!teamDetails.stickyWeb};
232
- case 'toxicspikes':
233
- return {cull: !!counter.setupType || !!teamDetails.toxicSpikes};
234
- case 'trickroom':
235
- return {cull: (
236
- moves.has('lightscreen') || moves.has('reflect') ||
237
- !!counter.setupType ||
238
- !!counter.get('speedsetup') ||
239
- counter.damagingMoves.size < 2
240
- )};
241
- case 'uturn':
242
- return {cull: (
243
- !!counter.setupType || !!counter.get('speedsetup') ||
244
- (abilities.has('Speed Boost') && moves.has('protect')) ||
245
- (abilities.has('Protean') && counter.get('Status') > 2) || (
246
- types.has('Bug') &&
247
- counter.get('stab') < 2 &&
248
- counter.damagingMoves.size > 2 &&
249
- !abilities.has('Adaptability') && !abilities.has('Download')
250
- )
251
- )};
252
- case 'voltswitch':
253
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('raindance') || moves.has('uturn')};
254
-
255
- // Bit redundant to have both
256
- // Attacks:
257
- case 'bugbite': case 'bugbuzz': case 'signalbeam':
258
- return {cull: moves.has('uturn') && !counter.setupType && !abilities.has('Tinted Lens')};
259
- case 'darkpulse':
260
- return {cull: ['crunch', 'knockoff', 'hyperspacefury'].some(m => moves.has(m)) && counter.setupType !== 'Special'};
261
- case 'suckerpunch':
262
- return {cull: (
263
- counter.damagingMoves.size < 2 ||
264
- (counter.get('Dark') > 1 && !types.has('Dark')) ||
265
- moves.has('glare') ||
266
- restTalk
267
- )};
268
- case 'dragonclaw':
269
- return {cull: moves.has('dragontail') || moves.has('outrage')};
270
- case 'dracometeor':
271
- return {cull: moves.has('swordsdance') || counter.setupType === 'Physical' && counter.get('Dragon') > 1};
272
- case 'dragonpulse': case 'spacialrend':
273
- return {cull: moves.has('dracometeor') || moves.has('outrage') || (moves.has('dragontail') && !counter.setupType)};
274
- case 'outrage':
275
- return {cull: moves.has('dracometeor') && counter.damagingMoves.size < 3};
276
- case 'thunderbolt':
277
- return {cull: moves.has('discharge') || (moves.has('voltswitch') && moves.has('wildcharge'))};
278
- case 'dazzlinggleam':
279
- return {cull: moves.has('playrough') && counter.setupType !== 'Special'};
280
- case 'aurasphere': case 'focusblast':
281
- return {cull: restTalk || ((moves.has('closecombat') || moves.has('superpower')) && counter.setupType !== 'Special')};
282
- case 'drainpunch':
283
- return {cull: (
284
- (!moves.has('bulkup') && (moves.has('closecombat') || moves.has('highjumpkick'))) ||
285
- ((moves.has('focusblast') || moves.has('superpower')) && counter.setupType !== 'Physical')
286
- )};
287
- case 'closecombat': case 'highjumpkick':
288
- return {cull: (
289
- (moves.has('bulkup') && moves.has('drainpunch')) || (
290
- counter.setupType === 'Special' &&
291
- (moves.has('aurasphere') || moves.has('focusblast') || movePool.includes('aurasphere'))
292
- )
293
- )};
294
- case 'machpunch':
295
- return {cull: types.has('Fighting') && counter.get('stab') < 2 && !abilities.has('Technician')};
296
- case 'stormthrow':
297
- return {cull: moves.has('circlethrow') && restTalk};
298
- case 'superpower':
299
- const isSetup = abilities.has('Contrary');
300
- return {cull: (counter.get('Fighting') > 1 && !!counter.setupType) || (restTalk && !isSetup), isSetup};
301
- case 'vacuumwave':
302
- return {cull: (moves.has('closecombat') || moves.has('machpunch')) && counter.setupType !== 'Special'};
303
- case 'fierydance': case 'firefang': case 'flamethrower':
304
- return {cull: (
305
- (move.id === 'flamethrower' && moves.has('drainpunch') && counter.setupType !== 'Special') ||
306
- moves.has('blazekick') ||
307
- moves.has('overheat') ||
308
- ((moves.has('fireblast') || moves.has('lavaplume')) && counter.setupType !== 'Physical')
309
- )};
310
- case 'fireblast':
311
- return {cull: (
312
- (moves.has('flareblitz') && counter.setupType !== 'Special') ||
313
- (moves.has('lavaplume') && !counter.setupType && !counter.get('speedsetup'))
314
- )};
315
- case 'lavaplume':
316
- return {cull: moves.has('firepunch') || moves.has('fireblast') && (!!counter.setupType || !!counter.get('speedsetup'))};
317
- case 'airslash': case 'hurricane':
318
- return {cull: (
319
- [(move.id === 'hurricane' ? 'airslash' : 'hurricane'), 'acrobatics', 'bravebird'].some(m => moves.has(m))
320
- )};
321
- case 'shadowball':
322
- return {cull: moves.has('darkpulse') || (moves.has('hex') && moves.has('willowisp'))};
323
- case 'shadowclaw':
324
- return {cull: (
325
- moves.has('shadowforce') ||
326
- moves.has('shadowsneak') ||
327
- (moves.has('shadowball') && counter.setupType !== 'Physical')
328
- )};
329
- case 'shadowsneak':
330
- return {cull: restTalk || (types.has('Ghost') && species.types.length > 1 && counter.get('stab') < 2)};
331
- case 'hex':
332
- return {cull: moves.has('shadowball') && !moves.has('willowisp')};
333
- case 'gigadrain': case 'powerwhip':
334
- return {cull: (
335
- moves.has('seedbomb') ||
336
- moves.has('petaldance') ||
337
- (moves.has('sunnyday') && moves.has('solarbeam')) ||
338
- (counter.get('Special') < 4 && !counter.setupType && moves.has('leafstorm'))
339
- )};
340
- case 'leafblade': case 'woodhammer':
341
- return {cull: (
342
- (moves.has('hornleech') && counter.get('Physical') < 4) ||
343
- (moves.has('gigadrain') && counter.setupType !== 'Physical')
344
- )};
345
- case 'leafstorm':
346
- return {cull: counter.get('Grass') > 1 && !!counter.setupType};
347
- case 'solarbeam':
348
- return {cull: (
349
- (!abilities.has('Drought') && !moves.has('sunnyday')) ||
350
- moves.has('gigadrain') ||
351
- moves.has('leafstorm')
352
- )};
353
- case 'bonemerang': case 'earthpower': case 'precipiceblades':
354
- return {cull: moves.has('earthquake')};
355
- case 'freezedry':
356
- return {cull: moves.has('icebeam') || moves.has('icywind') || counter.get('stab') < 2};
357
- case 'bodyslam': case 'return':
358
- return {cull: (
359
- moves.has('doubleedge') ||
360
- (moves.has('glare') && moves.has('headbutt')) ||
361
- (move.id === 'return' && moves.has('bodyslam'))
362
- )};
363
- case 'endeavor':
364
- return {cull: !isLead && !abilities.has('Defeatist')};
365
- case 'explosion':
366
- return {cull: (
367
- !!counter.setupType ||
368
- (abilities.has('Refrigerate') && (moves.has('freezedry') || movePool.includes('return'))) ||
369
- moves.has('wish')
370
- )};
371
- case 'extremespeed':
372
- return {cull: counter.setupType !== 'Physical' && moves.has('vacuumwave')};
373
- case 'hiddenpower':
374
- return {cull: moves.has('rest') || !counter.get('stab') && counter.damagingMoves.size < 2};
375
- case 'hypervoice':
376
- return {cull: moves.has('blizzard') || moves.has('return')};
377
- case 'judgment':
378
- return {cull: counter.setupType !== 'Special' && counter.get('stab') > 1};
379
- case 'quickattack':
380
- return {cull: (
381
- (types.has('Normal') && (!counter.get('stab') || counter.get('Normal') > 2)) ||
382
- (types.has('Rock') && !!counter.get('Status'))
383
- )};
384
- case 'weatherball':
385
- return {cull: !moves.has('raindance') && !moves.has('sunnyday')};
386
- case 'poisonjab':
387
- return {cull: moves.has('gunkshot')};
388
- case 'acidspray': case 'sludgewave':
389
- return {cull: moves.has('poisonjab') || moves.has('sludgebomb')};
390
- case 'psychic':
391
- return {cull: moves.has('psyshock')};
392
- case 'psychocut': case 'zenheadbutt':
393
- return {cull: (moves.has('psychic') || moves.has('psyshock')) && counter.setupType !== 'Physical'};
394
- case 'psyshock':
395
- const psychic = movePool.indexOf('psychic');
396
- if (psychic >= 0) this.fastPop(movePool, psychic);
397
- return {cull: false};
398
- case 'headsmash':
399
- return {cull: moves.has('stoneedge')};
400
- case 'rockblast': case 'rockslide':
401
- return {cull: moves.has('headsmash') || moves.has('stoneedge')};
402
- case 'bulletpunch':
403
- return {cull: moves.has('substitute')};
404
- case 'hydropump':
405
- return {cull: (
406
- moves.has('razorshell') ||
407
- moves.has('waterfall') ||
408
- (moves.has('scald') && (counter.get('Special') < 4 || species.types.length > 1 && counter.get('stab') < 3)) ||
409
- restTalk
410
- )};
411
- case 'originpulse': case 'surf':
412
- return {cull: moves.has('hydropump') || moves.has('scald')};
413
- case 'scald':
414
- return {cull: (
415
- moves.has('waterfall') ||
416
- moves.has('waterpulse') ||
417
- (species.id === 'quagsire' && movePool.includes('recover'))
418
- )};
419
-
420
- // Status:
421
- case 'glare': case 'headbutt':
422
- return {cull: moves.has('bodyslam') || !moves.has('glare')};
423
- case 'stunspore': case 'thunderwave':
424
- const otherStatus = ['discharge', 'spore', 'toxic', 'trickroom', 'yawn'].some(m => moves.has(m));
425
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || restTalk || otherStatus};
426
- case 'toxic':
427
- return {cull: (
428
- !!counter.setupType ||
429
- ['hypnosis', 'sleeppowder', 'toxicspikes', 'willowisp', 'yawn', 'raindance', 'flamecharge'].some(m => moves.has(m))
430
- )};
431
- case 'willowisp':
432
- return {cull: moves.has('scald')};
433
- case 'raindance':
434
- return {cull: (
435
- counter.get('Physical') + counter.get('Special') < 2 ||
436
- (!types.has('Water') && !counter.get('Water')) ||
437
- restTalk
438
- )};
439
- case 'sunnyday':
440
- const cull = (
441
- counter.get('Physical') + counter.get('Special') < 2 ||
442
- (!abilities.has('Chlorophyll') && !abilities.has('Flower Gift') && !moves.has('solarbeam')) ||
443
- restTalk
444
- );
445
-
446
- if (cull && movePool.length > 1) {
447
- const solarbeam = movePool.indexOf('solarbeam');
448
- if (solarbeam >= 0) this.fastPop(movePool, solarbeam);
449
- if (movePool.length > 1) {
450
- const weatherball = movePool.indexOf('weatherball');
451
- if (weatherball >= 0) this.fastPop(movePool, weatherball);
452
- }
453
- }
454
-
455
- return {cull};
456
- case 'milkdrink': case 'moonlight': case 'painsplit': case 'recover': case 'roost': case 'synthesis':
457
- return {cull: ['leechseed', 'rest', 'wish'].some(m => moves.has(m))};
458
- case 'safeguard':
459
- return {cull: moves.has('destinybond')};
460
- case 'substitute':
461
- return {cull: (
462
- ['dracometeor', 'pursuit', 'rest', 'taunt', 'uturn', 'voltswitch', 'whirlwind'].some(m => moves.has(m)) ||
463
- (moves.has('leafstorm') && !abilities.has('Contrary')) ||
464
- movePool.includes('copycat')
465
- )};
466
- }
467
-
468
- return {cull: false};
469
- }
470
-
471
- shouldCullAbility(
472
- ability: string,
473
- types: Set<string>,
474
- moves: Set<string>,
475
- abilities: Set<string>,
476
- counter: MoveCounter,
477
- movePool: string[],
478
- teamDetails: RandomTeamsTypes.TeamDetails,
479
- species: Species
480
- ): boolean {
481
- switch (ability) {
482
- case 'Flare Boost': case 'Gluttony': case 'Moody': case 'Snow Cloak': case 'Steadfast':
483
- return true;
484
- case 'Contrary': case 'Iron Fist': case 'Skill Link': case 'Strong Jaw':
485
- return !counter.get(toID(ability));
486
- case 'Aerilate': case 'Pixilate': case 'Refrigerate':
487
- return !counter.get('Normal');
488
- case 'Analytic': case 'Download': case 'Hyper Cutter':
489
- return species.nfe;
490
- case 'Battle Armor': case 'Sturdy':
491
- return (!!counter.get('recoil') && !counter.get('recovery'));
492
- case 'Chlorophyll': case 'Leaf Guard':
493
- return (
494
- species.baseStats.spe > 100 ||
495
- abilities.has('Harvest') ||
496
- (!moves.has('sunnyday') && !teamDetails.sun)
497
- );
498
- case 'Competitive':
499
- return (!counter.get('Special') || moves.has('rest') && moves.has('sleeptalk'));
500
- case 'Compound Eyes': case 'No Guard':
501
- return !counter.get('inaccurate');
502
- case 'Defiant': case 'Moxie':
503
- return (!counter.get('Physical') || moves.has('dragontail'));
504
- case 'Flash Fire':
505
- return abilities.has('Drought');
506
- case 'Harvest':
507
- return abilities.has('Frisk');
508
- case 'Hustle':
509
- return counter.get('Physical') < 2;
510
- case 'Hydration': case 'Rain Dish': case 'Swift Swim':
511
- return (species.baseStats.spe > 100 || !moves.has('raindance') && !teamDetails.rain);
512
- case 'Ice Body':
513
- return !teamDetails.hail;
514
- case 'Immunity': case 'Snow Warning':
515
- return (moves.has('facade') || moves.has('hypervoice'));
516
- case 'Intimidate':
517
- return (moves.has('bodyslam') || moves.has('rest') || abilities.has('Reckless') && counter.get('recoil') > 1);
518
- case 'Lightning Rod':
519
- return species.types.includes('Ground');
520
- case 'Limber':
521
- return species.types.includes('Electric');
522
- case 'Magnet Pull':
523
- return (!types.has('Electric') && !moves.has('earthpower'));
524
- case 'Mold Breaker':
525
- return (
526
- moves.has('acrobatics') ||
527
- abilities.has('Adaptability') ||
528
- (abilities.has('Sheer Force') && !!counter.get('sheerforce'))
529
- );
530
- case 'Overgrow':
531
- return !counter.get('Grass');
532
- case 'Poison Heal':
533
- return (abilities.has('Technician') && !!counter.get('technician'));
534
- case 'Prankster':
535
- return !counter.get('Status');
536
- case 'Pressure': case 'Synchronize':
537
- return (counter.get('Status') < 2 || !!counter.get('recoil') || !!species.isMega);
538
- case 'Reckless': case 'Rock Head':
539
- return (!counter.get('recoil') || !!species.isMega);
540
- case 'Regenerator':
541
- return abilities.has('Magic Guard');
542
- case 'Sand Force': case 'Sand Rush': case 'Sand Veil':
543
- return !teamDetails.sand;
544
- case 'Scrappy':
545
- return !species.types.includes('Normal');
546
- case 'Serene Grace':
547
- return (!counter.get('serenegrace') || species.name === 'Blissey');
548
- case 'Sheer Force':
549
- return (!counter.get('sheerforce') || moves.has('doubleedge') || abilities.has('Guts') || !!species.isMega);
550
- case 'Simple':
551
- return (!counter.setupType && !moves.has('flamecharge'));
552
- case 'Solar Power':
553
- return (!counter.get('Special') || !teamDetails.sun || !!species.isMega);
554
- case 'Speed Boost':
555
- return moves.has('uturn');
556
- case 'Swarm':
557
- return (!counter.get('Bug') || !!species.isMega);
558
- case 'Sweet Veil':
559
- return types.has('Grass');
560
- case 'Technician':
561
- return (!counter.get('technician') || moves.has('tailslap') || !!species.isMega);
562
- case 'Tinted Lens':
563
- return (
564
- moves.has('protect') ||
565
- abilities.has('Prankster') ||
566
- counter.get('damage') >= counter.damagingMoves.size ||
567
- (counter.get('Status') > 2 && !counter.setupType)
568
- );
569
- case 'Torrent':
570
- return (!counter.get('Water') || !!species.isMega);
571
- case 'Unaware':
572
- return (!!counter.setupType || moves.has('stealthrock'));
573
- case 'Unburden':
574
- return (!!species.isMega || abilities.has('Prankster') || !counter.setupType && !moves.has('acrobatics'));
575
- case 'Water Absorb':
576
- return (moves.has('raindance') || ['Drizzle', 'Unaware', 'Volt Absorb'].some(a => abilities.has(a)));
577
- case 'Weak Armor':
578
- return counter.setupType !== 'Physical';
579
- }
580
-
581
- return false;
582
- }
583
-
584
- getHighPriorityItem(
585
- ability: string,
586
- types: Set<string>,
587
- moves: Set<string>,
588
- counter: MoveCounter,
589
- teamDetails: RandomTeamsTypes.TeamDetails,
590
- species: Species,
591
- isLead: boolean
592
- ): string | undefined {
593
- if (species.requiredItem) return species.requiredItem;
594
- if (species.requiredItems) return this.sample(species.requiredItems);
595
-
596
- // First, the extra high-priority items
597
- if (species.name === 'Marowak') return 'Thick Club';
598
- if (species.name === 'Dedenne') return 'Petaya Berry';
599
- if (species.name === 'Deoxys-Attack') return (isLead && moves.has('stealthrock')) ? 'Focus Sash' : 'Life Orb';
600
- if (species.name === 'Farfetch\u2019d') return 'Stick';
601
- if (species.name === 'Genesect' && moves.has('technoblast')) return 'Douse Drive';
602
- if (species.baseSpecies === 'Pikachu') return 'Light Ball';
603
- if (species.name === 'Shedinja' || species.name === 'Smeargle') return 'Focus Sash';
604
- if (species.name === 'Unfezant' && counter.get('Physical') >= 2) return 'Scope Lens';
605
- if (species.name === 'Unown') return 'Choice Specs';
606
- if (species.name === 'Wobbuffet') {
607
- return moves.has('destinybond') ? 'Custap Berry' : this.sample(['Leftovers', 'Sitrus Berry']);
608
- }
609
- if (ability === 'Harvest') return 'Sitrus Berry';
610
- if (ability === 'Imposter') return 'Choice Scarf';
611
- if (moves.has('switcheroo') || moves.has('trick')) {
612
- if (ability === 'Klutz') {
613
- return 'Assault Vest';
614
- } else if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108) {
615
- return 'Choice Scarf';
616
- } else {
617
- return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
618
- }
619
- }
620
- if (species.nfe) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
621
- if (moves.has('copycat') && counter.get('Physical') >= 3) return 'Choice Band';
622
- if (moves.has('bellydrum')) return 'Sitrus Berry';
623
- if (
624
- moves.has('geomancy') ||
625
- (moves.has('solarbeam') && ability !== 'Drought' && !moves.has('sunnyday') && !teamDetails.sun)
626
- ) {
627
- return 'Power Herb';
628
- }
629
- if (moves.has('shellsmash')) {
630
- return (ability === 'Solid Rock' && !!counter.get('priority')) ? 'Weakness Policy' : 'White Herb';
631
- }
632
- if ((ability === 'Guts' || moves.has('facade') || moves.has('psychoshift')) && !moves.has('sleeptalk')) {
633
- return moves.has('drainpunch') ? 'Flame Orb' : 'Toxic Orb';
634
- }
635
- if (
636
- (ability === 'Magic Guard' && counter.damagingMoves.size > 1) ||
637
- (ability === 'Sheer Force' && !!counter.get('sheerforce'))
638
- ) {
639
- return 'Life Orb';
640
- }
641
- if (ability === 'Poison Heal') return 'Toxic Orb';
642
- if (ability === 'Unburden') {
643
- if (moves.has('fakeout')) {
644
- return 'Normal Gem';
645
- } else if (['dracometeor', 'leafstorm', 'overheat'].some(m => moves.has(m))) {
646
- return 'White Herb';
647
- } else if (moves.has('substitute') || counter.setupType) {
648
- return 'Sitrus Berry';
649
- } else {
650
- return 'Red Card';
651
- }
652
- }
653
- if (moves.has('acrobatics')) return ''; // not undefined - we want "no item"
654
- if (moves.has('raindance')) return (ability === 'Forecast') ? 'Damp Rock' : 'Life Orb';
655
- if (moves.has('sunnyday')) return (ability === 'Forecast') ? 'Heat Rock' : 'Life Orb';
656
- if (moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
657
- if (moves.has('rest') && !moves.has('sleeptalk') && ability !== 'Natural Cure' && ability !== 'Shed Skin') {
658
- return 'Chesto Berry';
659
- }
660
- }
661
-
662
- getMediumPriorityItem(
663
- ability: string,
664
- moves: Set<string>,
665
- counter: MoveCounter,
666
- species: Species,
667
- isDoubles: boolean,
668
- isLead: boolean
669
- ): string | undefined {
670
- const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
671
- const scarfReqs = species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && !counter.get('priority');
672
-
673
- if (
674
- (ability === 'Speed Boost' || ability === 'Stance Change') &&
675
- counter.get('Physical') + counter.get('Special') > 2
676
- ) {
677
- return 'Life Orb';
678
- }
679
- if (
680
- counter.get('Physical') >= 4 &&
681
- ['bodyslam', 'dragontail', 'fakeout', 'flamecharge', 'rapidspin', 'suckerpunch'].every(m => !moves.has(m))
682
- ) {
683
- return (
684
- (species.baseStats.atk >= 100 || ability === 'Huge Power') &&
685
- scarfReqs &&
686
- this.randomChance(2, 3)
687
- ) ? 'Choice Scarf' : 'Choice Band';
688
- }
689
- if (
690
- counter.get('Special') >= 4 &&
691
- !moves.has('acidspray') && !moves.has('clearsmog') && !moves.has('fierydance')
692
- ) {
693
- return (
694
- species.baseStats.spa >= 100 &&
695
- scarfReqs &&
696
- this.randomChance(2, 3)
697
- ) ? 'Choice Scarf' : 'Choice Specs';
698
- }
699
- if (
700
- counter.get('Physical') >= 3 &&
701
- moves.has('defog') &&
702
- scarfReqs &&
703
- !moves.has('foulplay')
704
- ) {
705
- return 'Choice Scarf';
706
- }
707
-
708
- if (counter.get('Special') >= 3 && moves.has('uturn') && !moves.has('acidspray')) return 'Choice Specs';
709
- if (
710
- ability === 'Slow Start' ||
711
- ['bite', 'clearsmog', 'curse', 'protect', 'sleeptalk'].some(m => moves.has(m)) ||
712
- species.name.includes('Rotom-')
713
- ) {
714
- return 'Leftovers';
715
- }
716
-
717
- if (['endeavor', 'flail', 'reversal'].some(m => moves.has(m)) && ability !== 'Sturdy') {
718
- return (ability === 'Defeatist') ? 'Expert Belt' : 'Focus Sash';
719
- }
720
- if (moves.has('outrage') && counter.setupType) return 'Lum Berry';
721
- if (moves.has('substitute')) return counter.damagingMoves.size > 2 && !!counter.get('drain') ? 'Life Orb' : 'Leftovers';
722
- if (this.dex.getEffectiveness('Ground', species) >= 2 && ability !== 'Levitate' && !moves.has('magnetrise')) {
723
- return 'Air Balloon';
724
- }
725
- if ((ability === 'Iron Barbs' || ability === 'Rough Skin') && this.randomChance(1, 2)) return 'Rocky Helmet';
726
- if (
727
- counter.get('Physical') + counter.get('Special') >= 4 &&
728
- species.baseStats.spd >= 50 &&
729
- defensiveStatTotal >= 235
730
- ) {
731
- return 'Assault Vest';
732
- }
733
- if (species.name === 'Palkia' && (moves.has('dracometeor') || moves.has('spacialrend')) && moves.has('hydropump')) {
734
- return 'Lustrous Orb';
735
- }
736
- if (counter.damagingMoves.size >= 4) {
737
- return (!!counter.get('Dragon') || !!counter.get('Dark') || !!counter.get('Normal')) ? 'Life Orb' : 'Expert Belt';
738
- }
739
- if (counter.damagingMoves.size >= 3 && counter.get('speedsetup') && defensiveStatTotal >= 300) return 'Weakness Policy';
740
- if (
741
- isLead &&
742
- ability !== 'Regenerator' && ability !== 'Sturdy' &&
743
- !counter.get('recoil') && !counter.get('recovery') &&
744
- defensiveStatTotal <= 275
745
- ) {
746
- return 'Focus Sash';
747
- }
748
- }
749
-
750
- getLowPriorityItem(
751
- ability: string,
752
- types: Set<string>,
753
- moves: Set<string>,
754
- abilities: Set<string>,
755
- counter: MoveCounter,
756
- teamDetails: RandomTeamsTypes.TeamDetails,
757
- species: Species,
758
- isLead: boolean
759
- ): string | undefined {
760
- const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
761
-
762
- if (ability === 'Gale Wings' && moves.has('bravebird')) return 'Sharp Beak';
763
- if (moves.has('stickyweb') && ability === 'Sturdy') return 'Mental Herb';
764
- if (ability === 'Serene Grace' && moves.has('airslash') && species.baseStats.spe > 100) return 'Metronome';
765
- if (ability === 'Sturdy' && moves.has('explosion') && !counter.get('speedsetup')) return 'Custap Berry';
766
- if (ability === 'Super Luck') return 'Scope Lens';
767
- if (
768
- counter.damagingMoves.size >= 3 && ability !== 'Sturdy' &&
769
- ['acidspray', 'dragontail', 'foulplay', 'rapidspin', 'superfang', 'uturn'].every(m => !moves.has(m))
770
- ) {
771
- return (
772
- counter.get('speedsetup') ||
773
- moves.has('trickroom') ||
774
- (species.baseStats.spe > 40 && defensiveStatTotal <= 275)
775
- ) ? 'Life Orb' : 'Leftovers';
776
- }
777
- }
778
-
779
- randomSet(
780
- species: string | Species,
781
- teamDetails: RandomTeamsTypes.TeamDetails = {},
782
- isLead = false
783
- ): RandomTeamsTypes.RandomSet {
784
- species = this.dex.species.get(species);
785
- let forme = species.name;
786
-
787
- if (typeof species.battleOnly === 'string') {
788
- // Only change the forme. The species has custom moves, and may have different typing and requirements.
789
- forme = species.battleOnly;
790
- }
791
- if (species.cosmeticFormes) {
792
- forme = this.sample([species.name].concat(species.cosmeticFormes));
793
- }
794
-
795
- const movePool = (species.randomBattleMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
796
- const rejectedPool = [];
797
- let ability = '';
798
-
799
- const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
800
- const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
801
-
802
- const types = new Set(species.types);
803
- let abilities = new Set(Object.values(species.abilities));
804
- if (species.unreleasedHidden) abilities.delete(species.abilities.H);
805
- let availableHP = 0;
806
- for (const setMoveid of movePool) {
807
- if (setMoveid.startsWith('hiddenpower')) availableHP++;
808
- }
809
-
810
- // These moves can be used even if we aren't setting up to use them:
811
- const SetupException = ['closecombat', 'diamondstorm', 'extremespeed', 'suckerpunch', 'superpower', 'dracometeor'];
812
-
813
- const moves = new Set<string>();
814
- let counter: MoveCounter;
815
- // We use a special variable to track Hidden Power
816
- // so that we can check for all Hidden Powers at once
817
- let hasHiddenPower = false;
818
-
819
- do {
820
- // Choose next 4 moves from learnset/viable moves and add them to moves list:
821
- while (moves.size < this.maxMoveCount && movePool.length) {
822
- const moveid = this.sampleNoReplace(movePool);
823
- if (moveid.startsWith('hiddenpower')) {
824
- availableHP--;
825
- if (hasHiddenPower) continue;
826
- hasHiddenPower = true;
827
- }
828
- moves.add(moveid);
829
- }
830
-
831
- while (moves.size < this.maxMoveCount && rejectedPool.length) {
832
- const moveid = this.sampleNoReplace(rejectedPool);
833
- if (moveid.startsWith('hiddenpower')) {
834
- if (hasHiddenPower) continue;
835
- hasHiddenPower = true;
836
- }
837
- moves.add(moveid);
838
- }
839
-
840
- counter = this.queryMoves(moves, species.types, abilities, movePool);
841
-
842
- // Iterate through the moves again, this time to cull them:
843
- for (const moveid of moves) {
844
- const move = this.dex.moves.get(moveid);
845
-
846
- let {cull, isSetup} = this.shouldCullMove(
847
- move, types, moves, abilities, counter, movePool,
848
- teamDetails, species, isLead
849
- );
850
-
851
- // This move doesn't satisfy our setup requirements:
852
- if (
853
- (move.category === 'Physical' && counter.setupType === 'Special') ||
854
- (move.category === 'Special' && counter.setupType === 'Physical')
855
- ) {
856
- // Reject STABs last in case the setup type changes later on
857
- const stabs = counter.get(species.types[0]) + counter.get(species.types[1]);
858
- if (
859
- !SetupException.includes(moveid) &&
860
- (!types.has(move.type) || stabs > 1 || counter.get(move.category) < 2)
861
- ) cull = true;
862
- }
863
- if (
864
- counter.setupType && !isSetup && counter.setupType !== 'Mixed' && move.category !== counter.setupType &&
865
- counter.get(counter.setupType) < 2 && (move.category !== 'Status' || !move.flags.heal) &&
866
- moveid !== 'sleeptalk' && !types.has('Dark') && !moves.has('darkpulse') && (
867
- move.category !== 'Status' || (
868
- counter.get(counter.setupType) + counter.get('Status') > 3 &&
869
- counter.get('physicalsetup') + counter.get('specialsetup') < 2
870
- )
871
- )
872
- ) {
873
- // Mono-attacking with setup and RestTalk is allowed
874
- // Reject Status moves only if there is nothing else to reject
875
- cull = true;
876
- }
877
-
878
- if (
879
- counter.setupType === 'Special' &&
880
- moveid === 'hiddenpower' &&
881
- species.types.length > 1 &&
882
- counter.get('Special') <= 2 &&
883
- !types.has(move.type) &&
884
- !counter.get('Physical') &&
885
- counter.get('specialpool')
886
- ) {
887
- // Hidden Power isn't good enough
888
- cull = true;
889
- }
890
-
891
- const runEnforcementChecker = (checkerName: string) => {
892
- if (!this.moveEnforcementCheckers[checkerName]) return false;
893
- return this.moveEnforcementCheckers[checkerName](
894
- movePool, moves, abilities, types, counter, species as Species, teamDetails
895
- );
896
- };
897
-
898
- // Pokemon should have moves that benefit their Type/Ability/Weather, as well as moves required by its forme
899
- if (
900
- !cull && !isSetup && !move.weather && !move.damage &&
901
- (move.category !== 'Status' || !move.flags.heal) &&
902
- !['judgment', 'sleeptalk', 'toxic'].includes(moveid) &&
903
- (counter.get('physicalsetup') + counter.get('specialsetup') < 2 && (
904
- !counter.setupType || counter.setupType === 'Mixed' ||
905
- (move.category !== counter.setupType && move.category !== 'Status') ||
906
- counter.get(counter.setupType) + counter.get('Status') > 3
907
- )) && (
908
- move.category === 'Status' ||
909
- !types.has(move.type) ||
910
- (move.basePower && move.basePower < 40 && !move.multihit)
911
- )
912
- ) {
913
- if (
914
- (!counter.get('stab') && !moves.has('nightshade') && !moves.has('seismictoss') && (
915
- species.types.length > 1 ||
916
- (species.types[0] !== 'Normal' && species.types[0] !== 'Psychic') ||
917
- !moves.has('icebeam') ||
918
- species.baseStats.spa >= species.baseStats.spd)
919
- ) ||
920
- (!counter.get('recovery') && !counter.setupType && !moves.has('healingwish') && (
921
- movePool.includes('recover') || movePool.includes('roost') || movePool.includes('softboiled')
922
- ) && (counter.get('Status') > 1 || (species.nfe && !!counter.get('Status')))) ||
923
- (movePool.includes('stickyweb') && !counter.setupType && !teamDetails.stickyWeb) ||
924
- (species.requiredMove && movePool.includes(toID(species.requiredMove))) ||
925
- (moves.has('suckerpunch') && counter.get('stab') < species.types.length)
926
- ) {
927
- cull = true;
928
- } else {
929
- for (const type of types) {
930
- if (runEnforcementChecker(type)) {
931
- cull = true;
932
- }
933
- }
934
- for (const abil of abilities) {
935
- if (runEnforcementChecker(abil)) {
936
- cull = true;
937
- }
938
- }
939
- }
940
- }
941
-
942
- // Sleep Talk shouldn't be selected without Rest
943
- if (moveid === 'rest' && cull) {
944
- const sleeptalk = movePool.indexOf('sleeptalk');
945
- if (sleeptalk >= 0) {
946
- if (movePool.length < 2) {
947
- cull = false;
948
- } else {
949
- this.fastPop(movePool, sleeptalk);
950
- }
951
- }
952
- }
953
-
954
- // Remove cull moves from the move list
955
- if (cull && (
956
- movePool.length - availableHP ||
957
- (availableHP && (moveid.startsWith('hiddenpower') || !hasHiddenPower))
958
- )) {
959
- if (
960
- move.category !== 'Status' &&
961
- !move.damage && !move.flags.charge &&
962
- (moveid !== 'hiddenpower' || !availableHP)
963
- ) rejectedPool.push(moveid);
964
- moves.delete(moveid);
965
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
966
- break;
967
- }
968
-
969
- if (cull && rejectedPool.length) {
970
- moves.delete(moveid);
971
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
972
- break;
973
- }
974
- }
975
- } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
976
-
977
- if (hasHiddenPower) {
978
- let hpType;
979
- for (const move of moves) {
980
- if (move.startsWith('hiddenpower')) {
981
- hpType = move.substr(11);
982
- break;
983
- }
984
- }
985
- if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
986
- const HPivs = this.dex.types.get(hpType).HPivs;
987
- let iv: StatID;
988
- for (iv in HPivs) {
989
- ivs[iv] = HPivs[iv]!;
990
- }
991
- }
992
-
993
- // Moveset modifications
994
- if (moves.has('autotomize') && moves.has('heavyslam')) {
995
- moves.delete('autotomize');
996
- moves.add('rockpolish');
997
- }
998
- if (moves.has('raindance') && moves.has('thunderbolt')) {
999
- moves.delete('thunderbolt');
1000
- moves.add('thunder');
1001
- }
1002
-
1003
- if (species.battleOnly && !species.requiredAbility) {
1004
- abilities = new Set(Object.values(this.dex.species.get(species.battleOnly as string).abilities));
1005
- }
1006
- const abilityData = [...abilities].map(a => this.dex.abilities.get(a));
1007
- Utils.sortBy(abilityData, abil => -abil.rating);
1008
-
1009
- if (abilityData.length > 1) {
1010
- // Sort abilities by rating with an element of randomness
1011
- if (abilityData[2] && abilityData[1].rating <= abilityData[2].rating && this.randomChance(1, 2)) {
1012
- [abilityData[1], abilityData[2]] = [abilityData[2], abilityData[1]];
1013
- }
1014
- if (abilityData[0].rating <= abilityData[1].rating && this.randomChance(1, 2)) {
1015
- [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
1016
- } else if (abilityData[0].rating - 0.6 <= abilityData[1].rating && this.randomChance(2, 3)) {
1017
- [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
1018
- }
1019
-
1020
- // Start with the first abiility and work our way through, culling as we go
1021
- ability = abilityData[0].name;
1022
-
1023
- while (this.shouldCullAbility(ability, types, moves, abilities, counter, movePool, teamDetails, species)) {
1024
- if (ability === abilityData[0].name && abilityData[1].rating >= 1) {
1025
- ability = abilityData[1].name;
1026
- } else if (ability === abilityData[1].name && abilityData[2] && abilityData[2].rating >= 1) {
1027
- ability = abilityData[2].name;
1028
- } else {
1029
- // Default to the highest rated ability if all are rejected
1030
- ability = abilityData[0].name;
1031
- break;
1032
- }
1033
- }
1034
-
1035
- if (
1036
- abilities.has('Guts') &&
1037
- ability !== 'Quick Feet' &&
1038
- (moves.has('facade') || moves.has('protect') || (moves.has('rest') && moves.has('sleeptalk')))
1039
- ) {
1040
- ability = 'Guts';
1041
- } else if (abilities.has('Moxie') && counter.get('Physical') > 3) {
1042
- ability = 'Moxie';
1043
- }
1044
- if (species.name === 'Ambipom' && !counter.get('technician')) {
1045
- // If it doesn't qualify for Technician, Skill Link is useless on it
1046
- ability = 'Pickup';
1047
- } else if (species.name === 'Lopunny' && moves.has('switcheroo') && this.randomChance(2, 3)) {
1048
- ability = 'Klutz';
1049
- }
1050
- } else {
1051
- ability = abilityData[0].name;
1052
- }
1053
-
1054
- let item = this.getHighPriorityItem(ability, types, moves, counter, teamDetails, species, isLead);
1055
- if (item === undefined) item = this.getMediumPriorityItem(ability, moves, counter, species, false, isLead);
1056
- if (item === undefined) {
1057
- item = this.getLowPriorityItem(ability, types, moves, abilities, counter, teamDetails, species, isLead);
1058
- }
1059
- if (item === undefined) item = 'Leftovers';
1060
-
1061
- // For Trick / Switcheroo
1062
- if (item === 'Leftovers' && types.has('Poison')) {
1063
- item = 'Black Sludge';
1064
- }
1065
-
1066
- const levelScale: {[k: string]: number} = {
1067
- uber: 76, ou: 80, uu: 82, ru: 84, nu: 86, pu: 88,
1068
- };
1069
- const customScale: {[k: string]: number} = {
1070
- // Banned Ability
1071
- Dugtrio: 82, Gothitelle: 82, Ninetales: 84, Politoed: 84, Wobbuffet: 82,
1072
- // Holistic judgement
1073
- Castform: 100, Delibird: 100, 'Genesect-Douse': 80, Luvdisc: 100, Spinda: 100, Unown: 100,
1074
- };
1075
- const tier = toID(species.tier).replace('bl', '');
1076
- const level = this.adjustLevel || customScale[species.name] || levelScale[tier] || (species.nfe ? 90 : 80);
1077
-
1078
- // Prepare optimal HP
1079
- const srWeakness = this.dex.getEffectiveness('Rock', species);
1080
- while (evs.hp > 1) {
1081
- const hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
1082
- if (moves.has('substitute') && moves.has('reversal')) {
1083
- // Reversal users should be able to use four Substitutes
1084
- if (hp % 4 > 0) break;
1085
- } else if (moves.has('substitute') && (item === 'Petaya Berry' || item === 'Sitrus Berry')) {
1086
- // Three Substitutes should activate Petaya Berry for Dedenne
1087
- // Two Substitutes should activate Sitrus Berry
1088
- if (hp % 4 === 0) break;
1089
- } else if (moves.has('bellydrum') && item === 'Sitrus Berry') {
1090
- // Belly Drum should activate Sitrus Berry
1091
- if (hp % 2 === 0) break;
1092
- } else {
1093
- // Maximize number of Stealth Rock switch-ins
1094
- if (srWeakness <= 0 || hp % (4 / srWeakness) > 0) break;
1095
- }
1096
- evs.hp -= 4;
1097
- }
1098
-
1099
- // Minimize confusion damage
1100
- if (!counter.get('Physical') && !moves.has('copycat') && !moves.has('transform')) {
1101
- evs.atk = 0;
1102
- ivs.atk = hasHiddenPower ? ivs.atk - 30 : 0;
1103
- }
1104
-
1105
- if (['gyroball', 'metalburst', 'trickroom'].some(m => moves.has(m))) {
1106
- evs.spe = 0;
1107
- ivs.spe = hasHiddenPower ? ivs.spe - 30 : 0;
1108
- }
1109
-
1110
- return {
1111
- name: species.baseSpecies,
1112
- species: forme,
1113
- gender: species.gender,
1114
- moves: Array.from(moves),
1115
- ability: ability,
1116
- evs: evs,
1117
- ivs: ivs,
1118
- item: item,
1119
- level,
1120
- shiny: this.randomChance(1, 1024),
1121
- };
1122
- }
1123
-
1124
- randomFactorySets: {[format: string]: {[species: string]: BattleFactorySpecies}} = {};
1125
-
1126
- randomFactorySet(
1127
- species: Species,
1128
- teamData: RandomTeamsTypes.FactoryTeamDetails,
1129
- tier: string
1130
- ): RandomTeamsTypes.RandomFactorySet | null {
1131
- const id = toID(species.name);
1132
- // const flags = this.randomFactorySets[tier][id].flags;
1133
- const setList = this.randomFactorySets[tier][id].sets;
1134
-
1135
- const itemsMax: {[k: string]: number} = {choicespecs: 1, choiceband: 1, choicescarf: 1};
1136
- const movesMax: {[k: string]: number} = {
1137
- rapidspin: 1, batonpass: 1, stealthrock: 1, defog: 1, spikes: 1, toxicspikes: 1,
1138
- };
1139
- const requiredMoves: {[k: string]: string} = {stealthrock: 'hazardSet', rapidspin: 'hazardClear', defog: 'hazardClear'};
1140
- const weatherAbilitiesRequire: {[k: string]: string} = {
1141
- hydration: 'raindance', swiftswim: 'raindance',
1142
- leafguard: 'sunnyday', solarpower: 'sunnyday', chlorophyll: 'sunnyday',
1143
- sandforce: 'sandstorm', sandrush: 'sandstorm', sandveil: 'sandstorm',
1144
- snowcloak: 'hail',
1145
- };
1146
- const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream'];
1147
-
1148
- // Build a pool of eligible sets, given the team partners
1149
- // Also keep track of sets with moves the team requires
1150
- let effectivePool: {set: AnyObject, moveVariants?: number[], itemVariants?: number, abilityVariants?: number}[] = [];
1151
- const priorityPool = [];
1152
- for (const curSet of setList) {
1153
- if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
1154
-
1155
- const itemData = this.dex.items.get(curSet.item);
1156
- if (teamData.megaCount && teamData.megaCount > 0 && itemData.megaStone) continue; // reject 2+ mega stones
1157
- if (itemsMax[itemData.id] && teamData.has[itemData.id] >= itemsMax[itemData.id]) continue;
1158
-
1159
- const abilityState = this.dex.abilities.get(curSet.ability);
1160
- if (weatherAbilitiesRequire[abilityState.id] && teamData.weather !== weatherAbilitiesRequire[abilityState.id]) continue;
1161
- if (teamData.weather && weatherAbilities.includes(abilityState.id)) continue; // reject 2+ weather setters
1162
-
1163
- let reject = false;
1164
- let hasRequiredMove = false;
1165
- const curSetVariants = [];
1166
- for (const move of curSet.moves) {
1167
- const variantIndex = this.random(move.length);
1168
- const moveId = toID(move[variantIndex]);
1169
- if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
1170
- reject = true;
1171
- break;
1172
- }
1173
- if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
1174
- hasRequiredMove = true;
1175
- }
1176
- curSetVariants.push(variantIndex);
1177
- }
1178
- if (reject) continue;
1179
- effectivePool.push({set: curSet, moveVariants: curSetVariants});
1180
- if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants});
1181
- }
1182
- if (priorityPool.length) effectivePool = priorityPool;
1183
-
1184
- if (!effectivePool.length) {
1185
- if (!teamData.forceResult) return null;
1186
- for (const curSet of setList) {
1187
- effectivePool.push({set: curSet});
1188
- }
1189
- }
1190
-
1191
- const setData = this.sample(effectivePool);
1192
- const moves = [];
1193
- for (const [i, moveSlot] of setData.set.moves.entries()) {
1194
- moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
1195
- }
1196
-
1197
- return {
1198
- name: setData.set.name || species.baseSpecies,
1199
- species: setData.set.species,
1200
- gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
1201
- item: setData.set.item || '',
1202
- ability: setData.set.ability || species.abilities['0'],
1203
- shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
1204
- level: this.adjustLevel || 100,
1205
- happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
1206
- evs: setData.set.evs || {hp: 84, atk: 84, def: 84, spa: 84, spd: 84, spe: 84},
1207
- ivs: setData.set.ivs || {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31},
1208
- nature: setData.set.nature || 'Serious',
1209
- moves: moves,
1210
- };
1211
- }
1212
-
1213
- randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
1214
- this.enforceNoDirectCustomBanlistChanges();
1215
-
1216
- const forceResult = (depth >= 4);
1217
-
1218
- // The teams generated depend on the tier choice in such a way that
1219
- // no exploitable information is leaked from rolling the tier in getTeam(p1).
1220
- if (!this.factoryTier) this.factoryTier = this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU']);
1221
- const chosenTier = this.factoryTier;
1222
-
1223
- const pokemon = [];
1224
-
1225
- const pokemonPool = Object.keys(this.randomFactorySets[chosenTier]);
1226
-
1227
- const teamData: TeamData = {
1228
- typeCount: {}, typeComboCount: {}, baseFormes: {}, megaCount: 0, has: {}, forceResult,
1229
- weaknesses: {}, resistances: {},
1230
- };
1231
- const requiredMoveFamilies = ['hazardSet', 'hazardClear'];
1232
- const requiredMoves: {[k: string]: string} = {stealthrock: 'hazardSet', rapidspin: 'hazardClear', defog: 'hazardClear'};
1233
- const weatherAbilitiesSet: {[k: string]: string} = {
1234
- drizzle: 'raindance', drought: 'sunnyday', snowwarning: 'hail', sandstream: 'sandstorm',
1235
- };
1236
- const resistanceAbilities: {[k: string]: string[]} = {
1237
- dryskin: ['Water'], waterabsorb: ['Water'], stormdrain: ['Water'],
1238
- flashfire: ['Fire'], heatproof: ['Fire'],
1239
- lightningrod: ['Electric'], motordrive: ['Electric'], voltabsorb: ['Electric'],
1240
- sapsipper: ['Grass'],
1241
- thickfat: ['Ice', 'Fire'],
1242
- levitate: ['Ground'],
1243
- };
1244
-
1245
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
1246
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
1247
- if (!species.exists) continue;
1248
-
1249
- const speciesFlags = this.randomFactorySets[chosenTier][species.id].flags;
1250
-
1251
- // Limit to one of each species (Species Clause)
1252
- if (teamData.baseFormes[species.baseSpecies]) continue;
1253
-
1254
- // Limit the number of Megas to one
1255
- if (!teamData.megaCount) teamData.megaCount = 0;
1256
- if (teamData.megaCount >= 1 && speciesFlags.megaOnly) continue;
1257
-
1258
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
1259
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
1260
-
1261
- // Limit 2 of any type
1262
- const types = species.types;
1263
- let skip = false;
1264
- for (const type of types) {
1265
- if (teamData.typeCount[type] >= 2 * limitFactor && this.randomChance(4, 5)) {
1266
- skip = true;
1267
- break;
1268
- }
1269
- }
1270
- if (skip) continue;
1271
-
1272
- const set = this.randomFactorySet(species, teamData, chosenTier);
1273
- if (!set) continue;
1274
-
1275
- // Limit 1 of any type combination
1276
- let typeCombo = types.slice().sort().join();
1277
- if (set.ability === 'Drought' || set.ability === 'Drizzle') {
1278
- // Drought and Drizzle don't count towards the type combo limit
1279
- typeCombo = set.ability;
1280
- }
1281
- if (teamData.typeComboCount[typeCombo] >= 1 * limitFactor) continue;
1282
-
1283
- // Okay, the set passes, add it to our team
1284
- pokemon.push(set);
1285
-
1286
- // Now that our Pokemon has passed all checks, we can update team data:
1287
- for (const type of types) {
1288
- if (type in teamData.typeCount) {
1289
- teamData.typeCount[type]++;
1290
- } else {
1291
- teamData.typeCount[type] = 1;
1292
- }
1293
- }
1294
- teamData.typeComboCount[typeCombo] = (teamData.typeComboCount[typeCombo] + 1) || 1;
1295
-
1296
- teamData.baseFormes[species.baseSpecies] = 1;
1297
-
1298
- const itemData = this.dex.items.get(set.item);
1299
- if (itemData.megaStone) teamData.megaCount++;
1300
- if (itemData.id in teamData.has) {
1301
- teamData.has[itemData.id]++;
1302
- } else {
1303
- teamData.has[itemData.id] = 1;
1304
- }
1305
-
1306
- const abilityState = this.dex.abilities.get(set.ability);
1307
- if (abilityState.id in weatherAbilitiesSet) {
1308
- teamData.weather = weatherAbilitiesSet[abilityState.id];
1309
- }
1310
-
1311
- for (const move of set.moves) {
1312
- const moveId = toID(move);
1313
- if (moveId in teamData.has) {
1314
- teamData.has[moveId]++;
1315
- } else {
1316
- teamData.has[moveId] = 1;
1317
- }
1318
- if (moveId in requiredMoves) {
1319
- teamData.has[requiredMoves[moveId]] = 1;
1320
- }
1321
- }
1322
-
1323
- for (const typeName of this.dex.types.names()) {
1324
- // Cover any major weakness (3+) with at least one resistance
1325
- if (teamData.resistances[typeName] >= 1) continue;
1326
- if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
1327
- // Heuristic: assume that Pokemon with these abilities don't have (too) negative typing.
1328
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
1329
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
1330
- continue;
1331
- }
1332
- const typeMod = this.dex.getEffectiveness(typeName, types);
1333
- if (typeMod < 0) {
1334
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
1335
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
1336
- } else if (typeMod > 0) {
1337
- teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
1338
- }
1339
- }
1340
- }
1341
- if (pokemon.length < this.maxTeamSize) return this.randomFactoryTeam(side, ++depth);
1342
-
1343
- // Quality control
1344
- if (!teamData.forceResult) {
1345
- for (const requiredFamily of requiredMoveFamilies) {
1346
- if (!teamData.has[requiredFamily]) return this.randomFactoryTeam(side, ++depth);
1347
- }
1348
- for (const type in teamData.weaknesses) {
1349
- if (teamData.weaknesses[type] >= 3) return this.randomFactoryTeam(side, ++depth);
1350
- }
1351
- }
1352
-
1353
- return pokemon;
1354
- }
1355
- }
1356
-
1357
- export default RandomGen6Teams;