@pkmn/randoms 0.6.4 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gen7.ts DELETED
@@ -1,2194 +0,0 @@
1
- import {MoveCounter, RandomTeams, TeamData} from './gen8';
2
- import {Utils} from './utils';
3
- import {
4
- AnyObject,
5
- Format,
6
- ModdedDex,
7
- Move,
8
- PRNG,
9
- PRNGSeed,
10
- PlayerOptions,
11
- RandomTeamsTypes,
12
- SparseStatsTable,
13
- Species,
14
- StatID,
15
- StatsTable,
16
- toID,
17
- } from '@pkmn/sim';
18
-
19
- export interface BattleFactorySpecies {
20
- flags: {megaOnly?: 1, zmoveOnly?: 1, limEevee?: 1};
21
- sets: BattleFactorySet[];
22
- }
23
- interface BattleFactorySet {
24
- species: string;
25
- item: string;
26
- ability: string;
27
- nature: string;
28
- moves: string[];
29
- evs?: Partial<StatsTable>;
30
- ivs?: Partial<StatsTable>;
31
- }
32
-
33
- const ZeroAttackHPIVs: {[k: string]: SparseStatsTable} = {
34
- grass: {hp: 30, spa: 30},
35
- fire: {spa: 30, spe: 30},
36
- ice: {def: 30},
37
- ground: {spa: 30, spd: 30},
38
- fighting: {def: 30, spa: 30, spd: 30, spe: 30},
39
- electric: {def: 30, spe: 30},
40
- psychic: {spe: 30},
41
- flying: {spa: 30, spd: 30, spe: 30},
42
- rock: {def: 30, spd: 30, spe: 30},
43
- };
44
-
45
- export class RandomGen7Teams extends RandomTeams {
46
- constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
47
- super(dex, format, prng);
48
-
49
- this.noStab = [...this.noStab, 'voltswitch'];
50
-
51
- this.moveEnforcementCheckers = {
52
- Bug: movePool => movePool.includes('megahorn') || movePool.includes('pinmissile'),
53
- Dark: (movePool, moves, abilities, types, counter, species) => (
54
- (!counter.get('Dark') && !abilities.has('Protean')) ||
55
- (moves.has('pursuit') && species.types.length > 1 && counter.get('Dark') === 1)
56
- ),
57
- Dragon: (movePool, moves, abilities, types, counter) => (
58
- !counter.get('Dragon') &&
59
- !abilities.has('Aerilate') && !abilities.has('Pixilate') &&
60
- !moves.has('fly') && !moves.has('rest') && !moves.has('sleeptalk')
61
- ),
62
- Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric') || movePool.includes('thunder'),
63
- Fairy: (movePool, moves, abilities, types, counter) => (
64
- (!counter.get('Fairy') && !types.has('Flying') && !abilities.has('Pixilate'))
65
- ),
66
- Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting') || !counter.get('stab'),
67
- Fire: (movePool, moves, abilities, types, counter) => (
68
- !counter.get('Fire') || ['eruption', 'flareblitz', 'quiverdance'].some(m => movePool.includes(m))
69
- ),
70
- Flying: (movePool, moves, abilities, types, counter, species) => (
71
- !counter.get('Flying') && (
72
- species.id === 'rotomfan' ||
73
- abilities.has('Gale Wings') ||
74
- abilities.has('Serene Grace') || (
75
- types.has('Normal') && (movePool.includes('beakblast') || movePool.includes('bravebird'))
76
- )
77
- )
78
- ),
79
- Ghost: (movePool, moves, abilities, types, counter) => (
80
- (!counter.get('Ghost') || movePool.includes('spectralthief')) &&
81
- !types.has('Dark') &&
82
- !abilities.has('Steelworker')
83
- ),
84
- Grass: (movePool, moves, abilities, types, counter, species) => (
85
- !counter.get('Grass') && (species.baseStats.atk >= 100 || movePool.includes('leafstorm'))
86
- ),
87
- Ground: (movePool, moves, abilities, types, counter) => (
88
- !counter.get('Ground') && !moves.has('rest') && !moves.has('sleeptalk')
89
- ),
90
- Ice: (movePool, moves, abilities, types, counter) => (
91
- !abilities.has('Refrigerate') && (
92
- !counter.get('Ice') ||
93
- movePool.includes('iciclecrash') ||
94
- (abilities.has('Snow Warning') && movePool.includes('blizzard'))
95
- )
96
- ),
97
- Normal: movePool => movePool.includes('facade'),
98
- Poison: (movePool, moves, abilities, types, counter) => (
99
- !counter.get('Poison') &&
100
- (!!counter.setupType || abilities.has('Adaptability') || abilities.has('Sheer Force') || movePool.includes('gunkshot'))
101
- ),
102
- Psychic: (movePool, moves, abilities, types, counter, species) => (
103
- !counter.get('Psychic') && (
104
- abilities.has('Psychic Surge') ||
105
- movePool.includes('psychicfangs') ||
106
- (!types.has('Flying') && !abilities.has('Pixilate') && counter.get('stab') < species.types.length)
107
- )
108
- ),
109
- Rock: (movePool, moves, abilities, types, counter, species) => (
110
- !counter.get('Rock') &&
111
- !types.has('Fairy') &&
112
- (counter.setupType === 'Physical' || species.baseStats.atk >= 105 || abilities.has('Rock Head'))
113
- ),
114
- Steel: (movePool, moves, abilities, types, counter, species) => (
115
- !counter.get('Steel') && (species.baseStats.atk >= 100 || abilities.has('Steelworker'))
116
- ),
117
- Water: (movePool, moves, abilities, types, counter, species) => (
118
- (!counter.get('Water') && !abilities.has('Protean')) ||
119
- !counter.get('stab') ||
120
- movePool.includes('crabhammer') ||
121
- (abilities.has('Huge Power') && movePool.includes('aquajet'))
122
- ),
123
- Adaptability: (movePool, moves, abilities, types, counter, species) => (
124
- !counter.setupType &&
125
- species.types.length > 1 &&
126
- (!counter.get(species.types[0]) || !counter.get(species.types[1]))
127
- ),
128
- Contrary: (movePool, moves, abilities, types, counter, species) => (
129
- !counter.get('contrary') && species.name !== 'Shuckle'
130
- ),
131
- 'Slow Start': movePool => movePool.includes('substitute'),
132
- };
133
- }
134
-
135
- shouldCullMove(
136
- move: Move,
137
- types: Set<string>,
138
- moves: Set<string>,
139
- abilities: Set<string>,
140
- counter: MoveCounter,
141
- movePool: string[],
142
- teamDetails: RandomTeamsTypes.TeamDetails,
143
- species: Species,
144
- isLead: boolean,
145
- isDoubles: boolean
146
- ): {cull: boolean, isSetup?: boolean} {
147
- const hasRestTalk = moves.has('rest') && moves.has('sleeptalk');
148
- switch (move.id) {
149
- // Not very useful without their supporting moves
150
- case 'clangingscales': case 'electricterrain': case 'happyhour': case 'holdhands':
151
- return {
152
- cull: !!teamDetails.zMove || hasRestTalk,
153
- isSetup: move.id === 'happyhour' || move.id === 'holdhands',
154
- };
155
- case 'cottonguard': case 'defendorder':
156
- return {cull: !counter.get('recovery') && !moves.has('rest')};
157
- case 'bounce': case 'dig': case 'fly':
158
- return {cull: !!teamDetails.zMove || counter.setupType !== 'Physical'};
159
- case 'focuspunch':
160
- return {cull: !moves.has('substitute') || counter.damagingMoves.size < 2};
161
- case 'icebeam':
162
- return {cull: abilities.has('Tinted Lens') && !!counter.get('Status')};
163
- case 'perishsong':
164
- return {cull: !moves.has('protect')};
165
- case 'reflect':
166
- if (movePool.length > 1) {
167
- const screen = movePool.indexOf('lightscreen');
168
- if (screen >= 0) this.fastPop(movePool, screen);
169
- }
170
- return {cull: !moves.has('calmmind') && !moves.has('lightscreen')};
171
- case 'rest':
172
- return {cull: movePool.includes('sleeptalk')};
173
- case 'sleeptalk':
174
- if (movePool.length > 1) {
175
- const rest = movePool.indexOf('rest');
176
- if (rest >= 0) this.fastPop(movePool, rest);
177
- }
178
- return {cull: !moves.has('rest')};
179
- case 'storedpower':
180
- return {cull: !counter.setupType};
181
- case 'switcheroo': case 'trick':
182
- return {cull: (
183
- counter.get('Physical') + counter.get('Special') < 3 ||
184
- ['electroweb', 'snarl', 'suckerpunch'].some(m => moves.has(m))
185
- )};
186
-
187
- // Set up once and only if we have the moves for it
188
- case 'bellydrum': case 'bulkup': case 'coil': case 'curse': case 'dragondance': case 'honeclaws': case 'swordsdance':
189
- return {cull: (
190
- counter.setupType !== 'Physical' ||
191
- counter.get('physicalsetup') > 1 ||
192
- (counter.get('Physical') + counter.get('physicalpool') < 2 && !hasRestTalk) ||
193
- (move.id === 'bulkup' && hasRestTalk) ||
194
- (move.id === 'bellydrum' && !abilities.has('Unburden') && !counter.get('priority'))
195
- ), isSetup: true};
196
- case 'calmmind': case 'geomancy': case 'nastyplot': case 'quiverdance': case 'tailglow':
197
- if (types.has('Dark') && moves.has('darkpulse')) {
198
- counter.setupType = 'Special';
199
- return {cull: false, isSetup: true};
200
- }
201
- return {cull: (
202
- counter.setupType !== 'Special' ||
203
- counter.get('specialsetup') > 1 ||
204
- (counter.get('Special') + counter.get('specialpool') < 2 && !hasRestTalk)
205
- ), isSetup: true};
206
- case 'growth': case 'shellsmash': case 'workup':
207
- return {cull: (
208
- counter.setupType !== 'Mixed' ||
209
- counter.get('mixedsetup') > 1 ||
210
- counter.damagingMoves.size + counter.get('physicalpool') + counter.get('specialpool') < 2 ||
211
- (move.id === 'growth' && !moves.has('sunnyday'))
212
- ), isSetup: true};
213
- case 'agility': case 'autotomize': case 'rockpolish': case 'shiftgear':
214
- return {cull: counter.damagingMoves.size < 2 || hasRestTalk, isSetup: !counter.setupType};
215
- case 'flamecharge':
216
- return {cull: (
217
- moves.has('dracometeor') ||
218
- moves.has('overheat') ||
219
- (counter.damagingMoves.size < 3 && !counter.setupType)
220
- )};
221
-
222
- // Bad after setup
223
- case 'circlethrow': case 'dragontail':
224
- return {cull: (
225
- !!counter.get('speedsetup') ||
226
- (isDoubles && moves.has('superpower')) ||
227
- (!!counter.setupType && ((!moves.has('rest') && !moves.has('sleeptalk')) || moves.has('stormthrow'))) ||
228
- ['encore', 'raindance', 'roar', 'trickroom', 'whirlwind'].some(m => moves.has(m)) ||
229
- (counter.get(move.type) > 1 && counter.get('Status') > 1) ||
230
- (abilities.has('Sheer Force') && !!counter.get('sheerforce'))
231
- )};
232
- case 'defog':
233
- return {cull: !!counter.setupType || moves.has('spikes') || moves.has('stealthrock') || !!teamDetails.defog};
234
- case 'fakeout': case 'tailwind':
235
- return {cull: !!counter.setupType || ['substitute', 'switcheroo', 'trick'].some(m => moves.has(m))};
236
- case 'foulplay':
237
- return {cull: (
238
- !!counter.setupType ||
239
- !!counter.get('speedsetup') ||
240
- counter.get('Dark') > 2 ||
241
- moves.has('clearsmog') ||
242
- counter.damagingMoves.size - 1 === counter.get('priority') ||
243
- hasRestTalk
244
- )};
245
- case 'haze': case 'spikes':
246
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('trickroom')};
247
- case 'healbell': case 'technoblast':
248
- return {cull: !!counter.get('speedsetup')};
249
- case 'healingwish': case 'memento':
250
- return {cull: !!counter.setupType || !!counter.get('recovery') || moves.has('substitute')};
251
- case 'helpinghand': case 'yawn':
252
- return {cull: !!counter.setupType};
253
- case 'icywind': case 'stringshot':
254
- return {cull: !!counter.get('speedsetup') || moves.has('trickroom')};
255
- case 'leechseed': case 'roar': case 'whirlwind':
256
- return {cull: (
257
- !!counter.setupType ||
258
- !!counter.get('speedsetup') ||
259
- moves.has('dragontail') ||
260
- (isDoubles && (movePool.includes('protect') || movePool.includes('spikyshield')))
261
- )};
262
- case 'protect':
263
- const doublesCondition = (
264
- moves.has('fakeout') ||
265
- (moves.has('tailwind') && moves.has('roost')) ||
266
- movePool.includes('bellydrum') ||
267
- movePool.includes('shellsmash')
268
- );
269
- const singlesCondition = counter.setupType && !moves.has('wish');
270
- return {cull: (
271
- (isDoubles ? doublesCondition : singlesCondition) ||
272
- !!counter.get('speedsetup') ||
273
- moves.has('rest') || moves.has('roar') || moves.has('whirlwind') ||
274
- (moves.has('lightscreen') && moves.has('reflect'))
275
- )};
276
- case 'pursuit':
277
- return {cull: (
278
- !!counter.setupType ||
279
- counter.get('Status') > 1 ||
280
- counter.get('Dark') > 2 ||
281
- (moves.has('knockoff') && !types.has('Dark'))
282
- )};
283
- case 'rapidspin':
284
- return {cull: !!counter.setupType || !!teamDetails.rapidSpin};
285
- case 'reversal':
286
- return {cull: moves.has('substitute') && !!teamDetails.zMove};
287
- case 'seismictoss': case 'superfang':
288
- return {cull: !abilities.has('Parental Bond') && (counter.damagingMoves.size > 1 || !!counter.setupType)};
289
- case 'stealthrock':
290
- return {cull: (
291
- !!counter.setupType ||
292
- !!counter.get('speedsetup') ||
293
- ['rest', 'substitute', 'trickroom'].some(m => moves.has(m)) ||
294
- !!teamDetails.stealthRock
295
- )};
296
- case 'stickyweb':
297
- return {cull: !!teamDetails.stickyWeb};
298
- case 'toxicspikes':
299
- return {cull: !!counter.setupType || !!teamDetails.toxicSpikes};
300
- case 'trickroom':
301
- return {cull: (
302
- !!counter.setupType ||
303
- !!counter.get('speedsetup') ||
304
- counter.damagingMoves.size < 2 ||
305
- moves.has('lightscreen') ||
306
- moves.has('reflect')
307
- )};
308
- case 'uturn':
309
- return {cull: (
310
- (abilities.has('Speed Boost') && moves.has('protect')) ||
311
- (abilities.has('Protean') && counter.get('Status') > 2) ||
312
- !!counter.setupType ||
313
- !!counter.get('speedsetup') || (
314
- types.has('Bug') &&
315
- counter.get('stab') < 2 &&
316
- counter.damagingMoves.size > 2 &&
317
- !abilities.has('Adaptability') &&
318
- !abilities.has('Download')
319
- )
320
- )};
321
- case 'voltswitch':
322
- return {cull: (
323
- !!counter.setupType ||
324
- !!counter.get('speedsetup') ||
325
- ['electricterrain', 'raindance', 'uturn'].some(m => moves.has(m))
326
- )};
327
-
328
- // Bit redundant to have both
329
- // Attacks:
330
- case 'bugbite': case 'bugbuzz': case 'infestation': case 'signalbeam':
331
- return {cull: moves.has('uturn') && !counter.setupType && !abilities.has('Tinted Lens')};
332
- case 'darkestlariat': case 'nightslash':
333
- return {cull: moves.has('knockoff') || moves.has('pursuit')};
334
- case 'darkpulse':
335
- return {cull: ['crunch', 'knockoff', 'hyperspacefury'].some(m => moves.has(m)) && counter.setupType !== 'Special'};
336
- case 'suckerpunch':
337
- return {cull: counter.damagingMoves.size < 2 || moves.has('glare') || !types.has('Dark') && counter.get('Dark') > 1};
338
- case 'dracometeor':
339
- return {cull: hasRestTalk};
340
- case 'dragonpulse': case 'spacialrend':
341
- return {cull: moves.has('dracometeor') || moves.has('outrage') || (moves.has('dragontail') && !counter.setupType)};
342
- case 'outrage':
343
- return {cull: (
344
- moves.has('dragonclaw') ||
345
- (moves.has('dracometeor') && counter.damagingMoves.size < 3) ||
346
- (moves.has('clangingscales') && !teamDetails.zMove)
347
- )};
348
- case 'thunderbolt':
349
- return {cull: ['discharge', 'voltswitch', 'wildcharge'].some(m => moves.has(m))};
350
- case 'moonblast':
351
- return {cull: isDoubles && moves.has('dazzlinggleam')};
352
- case 'aurasphere': case 'focusblast':
353
- return {cull: (
354
- hasRestTalk ||
355
- ((moves.has('closecombat') || moves.has('superpower')) && counter.setupType !== 'Special')
356
- )};
357
- case 'drainpunch':
358
- return {cull: (
359
- (!moves.has('bulkup') && (moves.has('closecombat') || moves.has('highjumpkick'))) ||
360
- ((moves.has('focusblast') || moves.has('superpower')) && counter.setupType !== 'Physical')
361
- )};
362
- case 'closecombat': case 'highjumpkick':
363
- return {cull: (
364
- (moves.has('bulkup') && moves.has('drainpunch')) ||
365
- (counter.setupType === 'Special' && ['aurasphere', 'focusblast'].some(m => moves.has(m) || movePool.includes(m)))
366
- )};
367
- case 'dynamicpunch': case 'vacuumwave':
368
- return {cull: (moves.has('closecombat') || moves.has('facade')) && counter.setupType !== 'Special'};
369
- case 'stormthrow':
370
- return {cull: moves.has('circlethrow') && hasRestTalk};
371
- case 'superpower':
372
- return {
373
- cull: (counter.get('Fighting') > 1 && !!counter.setupType) || (hasRestTalk && !abilities.has('Contrary')),
374
- isSetup: abilities.has('Contrary'),
375
- };
376
- case 'fierydance': case 'heatwave':
377
- return {cull: moves.has('fireblast') && (!!counter.get('Status') || isDoubles)};
378
- case 'firefang': case 'firepunch': case 'flamethrower':
379
- return {cull: (
380
- ['blazekick', 'heatwave', 'overheat'].some(m => moves.has(m)) ||
381
- ((moves.has('fireblast') || moves.has('lavaplume')) && counter.setupType !== 'Physical')
382
- )};
383
- case 'fireblast': case 'magmastorm':
384
- return {cull: (
385
- (moves.has('flareblitz') && counter.setupType !== 'Special') ||
386
- (moves.has('lavaplume') && !counter.setupType && !counter.get('speedsetup'))
387
- )};
388
- case 'lavaplume':
389
- return {cull: moves.has('firepunch') || moves.has('fireblast') && (!!counter.setupType || !!counter.get('speedsetup'))};
390
- case 'overheat':
391
- return {cull: ['fireblast', 'flareblitz', 'lavaplume'].some(m => moves.has(m))};
392
- case 'hurricane':
393
- return {cull: moves.has('bravebird') || moves.has('airslash') && !!counter.get('Status')};
394
- case 'hex':
395
- return {cull: !moves.has('thunderwave') && !moves.has('willowisp')};
396
- case 'shadowball':
397
- return {cull: moves.has('darkpulse') || (moves.has('hex') && moves.has('willowisp'))};
398
- case 'shadowclaw':
399
- return {cull: (
400
- moves.has('shadowforce') ||
401
- moves.has('shadowsneak') ||
402
- (moves.has('shadowball') && counter.setupType !== 'Physical')
403
- )};
404
- case 'shadowsneak':
405
- return {cull: (
406
- moves.has('trick') ||
407
- hasRestTalk ||
408
- (types.has('Ghost') && species.types.length > 1 && counter.get('stab') < 2)
409
- )};
410
- case 'gigadrain':
411
- return {cull: (
412
- moves.has('petaldance') ||
413
- moves.has('powerwhip') ||
414
- (!isDoubles && moves.has('seedbomb')) ||
415
- (moves.has('leafstorm') && counter.get('Special') < 4 && !counter.setupType && !moves.has('trickroom'))
416
- )};
417
- case 'leafblade': case 'woodhammer':
418
- return {cull: (
419
- (moves.has('gigadrain') && counter.setupType !== 'Physical') ||
420
- (moves.has('hornleech') && !!counter.setupType)
421
- )};
422
- case 'leafstorm':
423
- return {cull: (
424
- moves.has('trickroom') ||
425
- (isDoubles && moves.has('energyball')) ||
426
- (counter.get('Grass') > 1 && !!counter.setupType)
427
- )};
428
- case 'seedbomb':
429
- return {cull: moves.has('leafstorm') || isDoubles && moves.has('gigadrain')};
430
- case 'solarbeam':
431
- return {cull: (
432
- (!abilities.has('Drought') && !moves.has('sunnyday')) ||
433
- moves.has('gigadrain') ||
434
- moves.has('leafstorm')
435
- )};
436
- case 'bonemerang': case 'precipiceblades':
437
- return {cull: moves.has('earthquake')};
438
- case 'earthpower':
439
- return {cull: moves.has('earthquake') && counter.setupType !== 'Special'};
440
- case 'earthquake':
441
- return {cull: isDoubles && moves.has('highhorsepower')};
442
- case 'freezedry':
443
- return {cull: moves.has('icebeam') || moves.has('icywind') || counter.get('stab') < 2};
444
- case 'bodyslam': case 'return':
445
- return {cull: (
446
- moves.has('doubleedge') ||
447
- (moves.has('glare') && moves.has('headbutt')) ||
448
- (move.id === 'return' && moves.has('bodyslam'))
449
- )};
450
- case 'endeavor':
451
- return {cull: !isLead && !abilities.has('Defeatist')};
452
- case 'explosion':
453
- return {cull: (
454
- !!counter.setupType ||
455
- moves.has('wish') ||
456
- (abilities.has('Refrigerate') && (moves.has('freezedry') || movePool.includes('return')))
457
- )};
458
- case 'extremespeed': case 'skyattack':
459
- return {cull: moves.has('substitute') || counter.setupType !== 'Physical' && moves.has('vacuumwave')};
460
- case 'facade':
461
- return {cull: moves.has('bulkup') || hasRestTalk};
462
- case 'hiddenpower':
463
- return {cull: (
464
- moves.has('rest') ||
465
- (!counter.get('stab') && counter.damagingMoves.size < 2) ||
466
- // Force Moonblast on Special-setup Fairies
467
- (counter.setupType === 'Special' && types.has('Fairy') && movePool.includes('moonblast'))
468
- )};
469
- case 'hypervoice':
470
- return {cull: moves.has('blizzard')};
471
- case 'judgment':
472
- return {cull: counter.setupType !== 'Special' && counter.get('stab') > 1};
473
- case 'quickattack':
474
- return {cull: (
475
- !!counter.get('speedsetup') ||
476
- (types.has('Rock') && !!counter.get('Status')) ||
477
- moves.has('feint') ||
478
- (types.has('Normal') && !counter.get('stab'))
479
- )};
480
- case 'weatherball':
481
- return {cull: !moves.has('raindance') && !moves.has('sunnyday')};
482
- case 'poisonjab':
483
- return {cull: moves.has('gunkshot')};
484
- case 'acidspray': case 'sludgewave':
485
- return {cull: moves.has('poisonjab') || moves.has('sludgebomb')};
486
- case 'psychic':
487
- return {cull: moves.has('psyshock')};
488
- case 'psychocut': case 'zenheadbutt':
489
- return {cull: (
490
- ((moves.has('psychic') || moves.has('psyshock')) && counter.setupType !== 'Physical') ||
491
- (abilities.has('Contrary') && !counter.setupType && !!counter.get('physicalpool'))
492
- )};
493
- case 'psyshock':
494
- const psychic = movePool.indexOf('psychic');
495
- if (psychic >= 0) this.fastPop(movePool, psychic);
496
- return {cull: false};
497
- case 'headsmash':
498
- return {cull: moves.has('stoneedge') || isDoubles && moves.has('rockslide')};
499
- case 'rockblast': case 'rockslide':
500
- return {cull: (moves.has('headsmash') || moves.has('stoneedge')) && !isDoubles};
501
- case 'stoneedge':
502
- return {cull: (
503
- moves.has('rockslide') ||
504
- (abilities.has('Guts') && !moves.has('dynamicpunch')) ||
505
- (isDoubles && moves.has('rockslide'))
506
- )};
507
- case 'bulletpunch':
508
- return {cull: types.has('Steel') && counter.get('stab') < 2 && !abilities.has('Technician')};
509
- case 'flashcannon':
510
- return {cull: (moves.has('ironhead') || moves.has('meteormash')) && counter.setupType !== 'Special'};
511
- case 'hydropump':
512
- return {cull: (
513
- moves.has('liquidation') ||
514
- moves.has('waterfall') ||
515
- hasRestTalk || (
516
- moves.has('scald') &&
517
- ((counter.get('Special') < 4 && !moves.has('uturn')) || (species.types.length > 1 && counter.get('stab') < 3))
518
- )
519
- )};
520
- case 'muddywater':
521
- return {cull: isDoubles && (moves.has('scald') || moves.has('hydropump'))};
522
- case 'originpulse': case 'surf':
523
- return {cull: moves.has('hydropump') || moves.has('scald')};
524
- case 'scald':
525
- return {cull: ['liquidation', 'waterfall', 'waterpulse'].some(m => moves.has(m))};
526
-
527
- // Status:
528
- case 'electroweb': case 'stunspore': case 'thunderwave':
529
- return {cull: (
530
- !!counter.setupType ||
531
- !!counter.get('speedsetup') ||
532
- hasRestTalk ||
533
- ['discharge', 'spore', 'toxic', 'trickroom', 'yawn'].some(m => moves.has(m))
534
- )};
535
- case 'glare': case 'headbutt':
536
- return {cull: moves.has('bodyslam') || !moves.has('glare')};
537
- case 'toxic':
538
- const otherStatus = ['hypnosis', 'sleeppowder', 'toxicspikes', 'willowisp', 'yawn'].some(m => moves.has(m));
539
- return {cull: otherStatus || !!counter.setupType || moves.has('flamecharge') || moves.has('raindance')};
540
- case 'willowisp':
541
- return {cull: moves.has('scald')};
542
- case 'raindance':
543
- return {cull: (
544
- counter.get('Physical') + counter.get('Special') < 2 ||
545
- hasRestTalk ||
546
- moves.has('rest') ||
547
- (!types.has('Water') && !counter.get('Water'))
548
- )};
549
- case 'sunnyday':
550
- const cull = (
551
- counter.get('Physical') + counter.get('Special') < 2 ||
552
- (!abilities.has('Chlorophyll') && !abilities.has('Flower Gift') && !moves.has('solarbeam')) ||
553
- hasRestTalk
554
- );
555
-
556
- if (cull && movePool.length > 1) {
557
- const solarbeam = movePool.indexOf('solarbeam');
558
- if (solarbeam >= 0) this.fastPop(movePool, solarbeam);
559
- if (movePool.length > 1) {
560
- const weatherball = movePool.indexOf('weatherball');
561
- if (weatherball >= 0) this.fastPop(movePool, weatherball);
562
- }
563
- }
564
-
565
- return {cull};
566
- case 'painsplit': case 'recover': case 'roost': case 'synthesis':
567
- return {cull: moves.has('leechseed') || moves.has('rest')};
568
- case 'substitute':
569
- return {cull: (
570
- moves.has('dracometeor') ||
571
- (moves.has('leafstorm') && !abilities.has('Contrary')) ||
572
- ['encore', 'pursuit', 'rest', 'taunt', 'uturn', 'voltswitch', 'whirlwind'].some(m => moves.has(m)) ||
573
- movePool.includes('copycat')
574
- )};
575
- case 'powersplit':
576
- return {cull: moves.has('guardsplit')};
577
- case 'wideguard':
578
- return {cull: moves.has('protect')};
579
- case 'bravebird':
580
- // Hurricane > Brave Bird in the rain
581
- return {cull: (moves.has('raindance') || abilities.has('Drizzle')) && movePool.includes('hurricane')};
582
- }
583
- return {cull: false};
584
- }
585
-
586
- shouldCullAbility(
587
- ability: string,
588
- types: Set<string>,
589
- moves: Set<string>,
590
- abilities: Set<string>,
591
- counter: MoveCounter,
592
- movePool: string[],
593
- teamDetails: RandomTeamsTypes.TeamDetails,
594
- species: Species,
595
- isDoubles: boolean
596
- ): boolean {
597
- switch (ability) {
598
- case 'Battle Bond': case 'Dazzling': case 'Flare Boost': case 'Hyper Cutter':
599
- case 'Ice Body': case 'Innards Out': case 'Moody': case 'Steadfast':
600
- return true;
601
- case 'Aerilate': case 'Galvanize': case 'Pixilate': case 'Refrigerate':
602
- return !counter.get('Normal');
603
- case 'Analytic': case 'Download':
604
- return species.nfe;
605
- case 'Battle Armor': case 'Sturdy':
606
- return (!!counter.get('recoil') && !counter.get('recovery'));
607
- case 'Chlorophyll': case 'Leaf Guard':
608
- return (
609
- species.baseStats.spe > 100 ||
610
- abilities.has('Harvest') ||
611
- (!moves.has('sunnyday') && !teamDetails.sun)
612
- );
613
- case 'Competitive':
614
- return (!counter.get('Special') || moves.has('sleeptalk') && moves.has('rest'));
615
- case 'Compound Eyes': case 'No Guard':
616
- return !counter.get('inaccurate');
617
- case 'Contrary': case 'Iron Fist': case 'Skill Link': case 'Strong Jaw':
618
- return !counter.get(toID(ability));
619
- case 'Defiant': case 'Justified': case 'Moxie':
620
- return !counter.get('Physical') || moves.has('dragontail');
621
- case 'Flash Fire':
622
- return abilities.has('Drought');
623
- case 'Gluttony':
624
- return !moves.has('bellydrum');
625
- case 'Harvest':
626
- return abilities.has('Frisk');
627
- case 'Hustle':
628
- return counter.get('Physical') < 2;
629
- case 'Hydration': case 'Rain Dish': case 'Swift Swim':
630
- return (species.baseStats.spe > 100 || !moves.has('raindance') && !teamDetails.rain);
631
- case 'Slush Rush': case 'Snow Cloak':
632
- return !teamDetails.hail;
633
- case 'Immunity': case 'Snow Warning':
634
- return (moves.has('facade') || moves.has('hypervoice'));
635
- case 'Intimidate':
636
- return (moves.has('bodyslam') || moves.has('rest') || abilities.has('Reckless') && counter.get('recoil') > 1);
637
- case 'Lightning Rod':
638
- return species.types.includes('Ground');
639
- case 'Limber':
640
- return species.types.includes('Electric');
641
- case 'Liquid Voice':
642
- return !counter.get('sound');
643
- case 'Magic Guard': case 'Speed Boost':
644
- return (abilities.has('Tinted Lens') && (!counter.get('Status') || moves.has('uturn')));
645
- case 'Magician':
646
- return moves.has('switcheroo');
647
- case 'Magnet Pull':
648
- return (!!counter.get('Normal') || !types.has('Electric') && !moves.has('earthpower'));
649
- case 'Mold Breaker':
650
- return (
651
- moves.has('acrobatics') || moves.has('sleeptalk') ||
652
- abilities.has('Adaptability') || abilities.has('Iron Fist') ||
653
- (abilities.has('Sheer Force') && !!counter.get('sheerforce'))
654
- );
655
- case 'Overgrow':
656
- return !counter.get('Grass');
657
- case 'Poison Heal':
658
- return (abilities.has('Technician') && !!counter.get('technician'));
659
- case 'Power Construct':
660
- return species.forme === '10%';
661
- case 'Prankster':
662
- return !counter.get('Status');
663
- case 'Pressure': case 'Synchronize':
664
- return (counter.get('Status') < 2 || !!counter.get('recoil') || !!species.isMega);
665
- case 'Regenerator':
666
- return abilities.has('Magic Guard');
667
- case 'Quick Feet':
668
- return moves.has('bellydrum');
669
- case 'Reckless': case 'Rock Head':
670
- return (!counter.get('recoil') || !!species.isMega);
671
- case 'Sand Force': case 'Sand Rush': case 'Sand Veil':
672
- return !teamDetails.sand;
673
- case 'Scrappy':
674
- return !species.types.includes('Normal');
675
- case 'Serene Grace':
676
- return (!counter.get('serenegrace') || species.name === 'Blissey');
677
- case 'Sheer Force':
678
- return (!counter.get('sheerforce') || moves.has('doubleedge') || abilities.has('Guts') || !!species.isMega);
679
- case 'Simple':
680
- return (!counter.setupType && !moves.has('flamecharge'));
681
- case 'Solar Power':
682
- return (!counter.get('Special') || !teamDetails.sun || !!species.isMega);
683
- case 'Swarm':
684
- return (!counter.get('Bug') || !!species.isMega);
685
- case 'Sweet Veil':
686
- return types.has('Grass');
687
- case 'Technician':
688
- return (!counter.get('technician') || moves.has('tailslap') || !!species.isMega);
689
- case 'Tinted Lens':
690
- return (
691
- moves.has('protect') || !!counter.get('damage') ||
692
- (counter.get('Status') > 2 && !counter.setupType) ||
693
- abilities.has('Prankster') ||
694
- (abilities.has('Magic Guard') && !!counter.get('Status'))
695
- );
696
- case 'Torrent':
697
- return (!counter.get('Water') || !!species.isMega);
698
- case 'Unaware':
699
- return (!!counter.setupType || abilities.has('Magic Guard'));
700
- case 'Unburden':
701
- return (!!species.isMega || abilities.has('Prankster') || !counter.setupType && !moves.has('acrobatics'));
702
- case 'Water Absorb':
703
- return moves.has('raindance') || ['Drizzle', 'Unaware', 'Volt Absorb'].some(abil => abilities.has(abil));
704
- case 'Weak Armor':
705
- return counter.setupType !== 'Physical';
706
- }
707
-
708
- return false;
709
- }
710
-
711
- getHighPriorityItem(
712
- ability: string,
713
- types: Set<string>,
714
- moves: Set<string>,
715
- counter: MoveCounter,
716
- teamDetails: RandomTeamsTypes.TeamDetails,
717
- species: Species,
718
- isLead: boolean,
719
- isDoubles: boolean
720
- ): string | undefined {
721
- if (species.requiredItems) {
722
- if (
723
- species.baseSpecies === 'Arceus' &&
724
- (moves.has('judgment') || !counter.get(species.types[0]) || teamDetails.zMove)
725
- ) {
726
- // Judgment doesn't change type with Z-Crystals
727
- return species.requiredItems[0];
728
- }
729
- return this.sample(species.requiredItems);
730
- }
731
-
732
- // First, the extra high-priority items
733
- if (species.name === 'Dedenne') return moves.has('substitute') ? 'Petaya Berry' : 'Sitrus Berry';
734
- if (species.name === 'Deoxys-Attack') return (isLead && moves.has('stealthrock')) ? 'Focus Sash' : 'Life Orb';
735
- if (species.name === 'Farfetch\u2019d') return 'Stick';
736
- if (species.name === 'Genesect' && moves.has('technoblast')) return 'Douse Drive';
737
- if (species.baseSpecies === 'Marowak') return 'Thick Club';
738
- if (species.name === 'Pikachu') return 'Light Ball';
739
- if (species.name === 'Shedinja' || species.name === 'Smeargle') return 'Focus Sash';
740
- if (species.name === 'Unfezant' && counter.get('Physical') >= 2) return 'Scope Lens';
741
- if (species.name === 'Unown') return 'Choice Specs';
742
- if (species.name === 'Wobbuffet') return 'Custap Berry';
743
- if (ability === 'Harvest' || ability === 'Emergency Exit' && !!counter.get('Status')) return 'Sitrus Berry';
744
- if (ability === 'Imposter') return 'Choice Scarf';
745
- if (ability === 'Poison Heal') return 'Toxic Orb';
746
- if (species.nfe) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
747
- if (moves.has('switcheroo') || moves.has('trick')) {
748
- if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108) {
749
- return 'Choice Scarf';
750
- } else {
751
- return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
752
- }
753
- }
754
- if (moves.has('bellydrum')) {
755
- if (ability === 'Gluttony') {
756
- return `${this.sample(['Aguav', 'Figy', 'Iapapa', 'Mago', 'Wiki'])} Berry`;
757
- } else if (species.baseStats.spe <= 50 && !teamDetails.zMove && this.randomChance(1, 2)) {
758
- return 'Normalium Z';
759
- } else {
760
- return 'Sitrus Berry';
761
- }
762
- }
763
- if (moves.has('copycat') && counter.get('Physical') >= 3) return 'Choice Band';
764
- if (moves.has('geomancy') || moves.has('skyattack')) return 'Power Herb';
765
- if (moves.has('shellsmash')) {
766
- return (ability === 'Solid Rock' && !!counter.get('priority')) ? 'Weakness Policy' : 'White Herb';
767
- }
768
- if ((ability === 'Guts' || moves.has('facade')) && !moves.has('sleeptalk')) {
769
- return (types.has('Fire') || ability === 'Quick Feet' || ability === 'Toxic Boost') ? 'Toxic Orb' : 'Flame Orb';
770
- }
771
- if (
772
- (ability === 'Magic Guard' && counter.damagingMoves.size > 1) ||
773
- (ability === 'Sheer Force' && counter.get('sheerforce'))
774
- ) {
775
- return 'Life Orb';
776
- }
777
- if (ability === 'Unburden') return moves.has('fakeout') ? 'Normal Gem' : 'Sitrus Berry';
778
- if (moves.has('acrobatics')) return '';
779
- if (moves.has('electricterrain') || ability === 'Electric Surge' && moves.has('thunderbolt')) return 'Electrium Z';
780
- if (
781
- moves.has('happyhour') ||
782
- moves.has('holdhands') ||
783
- (moves.has('encore') && ability === 'Contrary')
784
- ) return 'Normalium Z';
785
- if (moves.has('raindance')) {
786
- if (species.baseSpecies === 'Castform' && !teamDetails.zMove) {
787
- return 'Waterium Z';
788
- } else {
789
- return (ability === 'Forecast') ? 'Damp Rock' : 'Life Orb';
790
- }
791
- }
792
- if (moves.has('sunnyday')) {
793
- if ((species.baseSpecies === 'Castform' || species.baseSpecies === 'Cherrim') && !teamDetails.zMove) {
794
- return 'Firium Z';
795
- } else {
796
- return (ability === 'Forecast') ? 'Heat Rock' : 'Life Orb';
797
- }
798
- }
799
-
800
- if (moves.has('solarbeam') && ability !== 'Drought' && !moves.has('sunnyday') && !teamDetails.sun) {
801
- return !teamDetails.zMove ? 'Grassium Z' : 'Power Herb';
802
- }
803
-
804
- if (moves.has('auroraveil') || moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
805
- if (
806
- moves.has('rest') && !moves.has('sleeptalk') &&
807
- ability !== 'Natural Cure' && ability !== 'Shed Skin' && ability !== 'Shadow Tag'
808
- ) {
809
- return 'Chesto Berry';
810
- }
811
-
812
- // Z-Moves
813
- if (!teamDetails.zMove) {
814
- if (species.name === 'Decidueye' && moves.has('spiritshackle') && counter.setupType) {
815
- return 'Decidium Z';
816
- }
817
- if (species.name === 'Kommo-o') return moves.has('clangingscales') ? 'Kommonium Z' : 'Dragonium Z';
818
- if (species.baseSpecies === 'Lycanroc' && moves.has('stoneedge') && counter.setupType) {
819
- return 'Lycanium Z';
820
- }
821
- if (species.name === 'Marshadow' && moves.has('spectralthief') && counter.setupType) {
822
- return 'Marshadium Z';
823
- }
824
- if (species.name === 'Necrozma-Dusk-Mane' || species.name === 'Necrozma-Dawn-Wings') {
825
- if (moves.has('autotomize') && moves.has('sunsteelstrike')) {
826
- return 'Solganium Z';
827
- } else if (moves.has('trickroom') && moves.has('moongeistbeam')) {
828
- return 'Lunalium Z';
829
- } else {
830
- return 'Ultranecrozium Z';
831
- }
832
- }
833
-
834
- if (species.name === 'Mimikyu' && moves.has('playrough') && counter.setupType) return 'Mimikium Z';
835
- if (species.name === 'Raichu-Alola' && moves.has('thunderbolt') && counter.setupType) return 'Aloraichium Z';
836
- if (moves.has('bugbuzz') && counter.setupType && species.baseStats.spa > 100) return 'Buginium Z';
837
- if (
838
- (moves.has('darkpulse') && ability === 'Fur Coat' && counter.setupType) ||
839
- (moves.has('suckerpunch') && ability === 'Moxie' && counter.get('Dark') < 2)
840
- ) {
841
- return 'Darkinium Z';
842
- }
843
- if (moves.has('outrage') && counter.setupType && !moves.has('fly')) return 'Dragonium Z';
844
- if (moves.has('fleurcannon') && !!counter.get('speedsetup')) return 'Fairium Z';
845
- if (
846
- (moves.has('focusblast') && types.has('Fighting') && counter.setupType) ||
847
- (moves.has('reversal') && moves.has('substitute'))
848
- ) {
849
- return 'Fightinium Z';
850
- }
851
- if (
852
- moves.has('fly') ||
853
- (moves.has('hurricane') && species.baseStats.spa >= 125 && (!!counter.get('Status') || moves.has('superpower'))) ||
854
- ((moves.has('bounce') || moves.has('bravebird')) && counter.setupType)
855
- ) {
856
- return 'Flyinium Z';
857
- }
858
- if (moves.has('shadowball') && counter.setupType && ability === 'Beast Boost') return 'Ghostium Z';
859
- if (
860
- moves.has('sleeppowder') && types.has('Grass') &&
861
- counter.setupType && species.baseStats.spe <= 70
862
- ) {
863
- return 'Grassium Z';
864
- }
865
- if (moves.has('magmastorm')) return 'Firium Z';
866
- if (moves.has('dig')) return 'Groundium Z';
867
- if (moves.has('photongeyser') && counter.setupType) return 'Psychium Z';
868
- if (moves.has('stoneedge') && types.has('Rock') && moves.has('swordsdance')) return 'Rockium Z';
869
- if (moves.has('hydropump') && ability === 'Battle Bond' && moves.has('uturn')) return 'Waterium Z';
870
- if ((moves.has('hail') || (moves.has('blizzard') && ability !== 'Snow Warning'))) return 'Icium Z';
871
- }
872
- }
873
-
874
- getMediumPriorityItem(
875
- ability: string,
876
- moves: Set<string>,
877
- counter: MoveCounter,
878
- species: Species,
879
- isDoubles: boolean,
880
- isLead: boolean
881
- ): string | undefined {
882
- const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
883
-
884
- if (
885
- (ability === 'Speed Boost' || ability === 'Stance Change' || species.name === 'Pheromosa') &&
886
- counter.get('Physical') + counter.get('Special') > 2 &&
887
- !moves.has('uturn')
888
- ) {
889
- return 'Life Orb';
890
- }
891
-
892
- if (isDoubles && moves.has('uturn') && counter.get('Physical') === 4 && !moves.has('fakeout')) {
893
- return (
894
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
895
- !counter.get('priority') && this.randomChance(1, 2)
896
- ) ? 'Choice Scarf' : 'Choice Band';
897
- }
898
- if (isDoubles && counter.get('Special') === 4 && (moves.has('waterspout') || moves.has('eruption'))) {
899
- return 'Choice Scarf';
900
- }
901
-
902
- if (
903
- !isDoubles &&
904
- counter.get('Physical') >= 4 &&
905
- ['bodyslam', 'dragontail', 'fakeout', 'flamecharge', 'rapidspin', 'suckerpunch'].every(m => !moves.has(m))
906
- ) {
907
- return (
908
- (species.baseStats.atk >= 100 || ability === 'Huge Power') &&
909
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
910
- !counter.get('priority') &&
911
- this.randomChance(2, 3)
912
- ) ? 'Choice Scarf' : 'Choice Band';
913
- }
914
- if (
915
- !isDoubles &&
916
- (counter.get('Special') >= 4 || (counter.get('Special') >= 3 && moves.has('uturn'))) &&
917
- !moves.has('acidspray') && !moves.has('clearsmog')
918
- ) {
919
- return (
920
- species.baseStats.spa >= 100 &&
921
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
922
- ability !== 'Tinted Lens' &&
923
- !counter.get('Physical') && !counter.get('priority') &&
924
- this.randomChance(2, 3)
925
- ) ? 'Choice Scarf' : 'Choice Specs';
926
- }
927
- if (
928
- !isDoubles &&
929
- counter.get('Physical') >= 3 &&
930
- moves.has('defog') &&
931
- !moves.has('foulplay') &&
932
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
933
- !counter.get('priority')
934
- ) {
935
- return 'Choice Scarf';
936
- }
937
- if (!isDoubles && (
938
- ability === 'Drizzle' ||
939
- ability === 'Slow Start' ||
940
- species.name.includes('Rotom-') ||
941
- ['aromatherapy', 'bite', 'clearsmog', 'curse', 'protect', 'sleeptalk'].some(m => moves.has(m)))
942
- ) {
943
- return 'Leftovers';
944
- }
945
- if (['endeavor', 'flail', 'reversal'].some(m => moves.has(m)) && ability !== 'Sturdy') {
946
- return (ability === 'Defeatist') ? 'Expert Belt' : 'Focus Sash';
947
- }
948
- if (moves.has('outrage') && counter.setupType) return 'Lum Berry';
949
- if (
950
- isDoubles &&
951
- counter.damagingMoves.size >= 3 &&
952
- species.baseStats.spe >= 70 &&
953
- ability !== 'Multiscale' && ability !== 'Sturdy' && [
954
- 'acidspray', 'electroweb', 'fakeout', 'feint', 'flamecharge', 'icywind',
955
- 'incinerate', 'naturesmadness', 'rapidspin', 'snarl', 'suckerpunch', 'uturn',
956
- ].every(m => !moves.has(m))
957
- ) {
958
- return defensiveStatTotal >= 275 ? 'Sitrus Berry' : 'Life Orb';
959
- }
960
-
961
- if (moves.has('substitute')) return counter.damagingMoves.size > 2 && !!counter.get('drain') ? 'Life Orb' : 'Leftovers';
962
- if (
963
- !isDoubles &&
964
- this.dex.getEffectiveness('Ground', species) >= 2 &&
965
- ability !== 'Levitate' &&
966
- !moves.has('magnetrise')
967
- ) {
968
- return 'Air Balloon';
969
- }
970
- if ((ability === 'Iron Barbs' || ability === 'Rough Skin') && this.randomChance(1, 2)) return 'Rocky Helmet';
971
- if (
972
- counter.get('Physical') + counter.get('Special') >= 4 &&
973
- species.baseStats.spd >= 50 && defensiveStatTotal >= 235
974
- ) {
975
- return 'Assault Vest';
976
- }
977
- if (species.name === 'Palkia' && (moves.has('dracometeor') || moves.has('spacialrend')) && moves.has('hydropump')) {
978
- return 'Lustrous Orb';
979
- }
980
- if (counter.damagingMoves.size >= 4) {
981
- return (counter.get('Dragon') || counter.get('Dark') || counter.get('Normal')) ? 'Life Orb' : 'Expert Belt';
982
- }
983
- if (counter.damagingMoves.size >= 3 && !!counter.get('speedsetup') && defensiveStatTotal >= 300) {
984
- return 'Weakness Policy';
985
- }
986
- if (
987
- !isDoubles && isLead &&
988
- !['Regenerator', 'Sturdy'].includes(ability) &&
989
- !counter.get('recoil') && !counter.get('recovery') &&
990
- defensiveStatTotal < 255
991
- ) {
992
- return 'Focus Sash';
993
- }
994
- }
995
-
996
- getLowPriorityItem(
997
- ability: string,
998
- types: Set<string>,
999
- moves: Set<string>,
1000
- abilities: Set<string>,
1001
- counter: MoveCounter,
1002
- teamDetails: RandomTeamsTypes.TeamDetails,
1003
- species: Species,
1004
- isLead: boolean,
1005
- isDoubles: boolean
1006
- ): string | undefined {
1007
- // This is the "REALLY can't think of a good item" cutoff
1008
- if (moves.has('stickyweb') && ability === 'Sturdy') return 'Mental Herb';
1009
- if (ability === 'Serene Grace' && moves.has('airslash') && species.baseStats.spe > 100) return 'Metronome';
1010
- if (ability === 'Sturdy' && moves.has('explosion') && !counter.get('speedsetup')) return 'Custap Berry';
1011
- if (ability === 'Super Luck') return 'Scope Lens';
1012
- if (
1013
- !isDoubles &&
1014
- counter.damagingMoves.size >= 3 &&
1015
- ability !== 'Sturdy' &&
1016
- ['acidspray', 'dragontail', 'foulplay', 'rapidspin', 'superfang', 'uturn'].every(m => !moves.has(m)) && (
1017
- counter.get('speedsetup') ||
1018
- moves.has('trickroom') ||
1019
- (species.baseStats.spe > 40 && species.baseStats.hp + species.baseStats.def + species.baseStats.spd < 275)
1020
- )
1021
- ) {
1022
- return 'Life Orb';
1023
- }
1024
- }
1025
-
1026
- randomSet(
1027
- species: string | Species,
1028
- teamDetails: RandomTeamsTypes.TeamDetails = {},
1029
- isLead = false,
1030
- isDoubles = false
1031
- ): RandomTeamsTypes.RandomSet {
1032
- species = this.dex.species.get(species);
1033
- let forme = species.name;
1034
-
1035
- if (typeof species.battleOnly === 'string') {
1036
- // Only change the forme. The species has custom moves, and may have different typing and requirements.
1037
- forme = species.battleOnly;
1038
- }
1039
- if (species.cosmeticFormes) {
1040
- forme = this.sample([species.name].concat(species.cosmeticFormes));
1041
- }
1042
-
1043
- const randMoves = isDoubles ?
1044
- (species.randomDoubleBattleMoves || species.randomBattleMoves) :
1045
- species.randomBattleMoves;
1046
- const movePool = (randMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
1047
- if (this.format.gameType === 'multi') {
1048
- // Random Multi Battle uses doubles move pools, but Ally Switch fails in multi battles
1049
- const allySwitch = movePool.indexOf('allyswitch');
1050
- if (allySwitch > -1) {
1051
- if (movePool.length > this.maxMoveCount) {
1052
- this.fastPop(movePool, allySwitch);
1053
- } else {
1054
- // Ideally, we'll never get here, but better to have a move that usually does nothing than one that always does
1055
- movePool[allySwitch] = 'sleeptalk';
1056
- }
1057
- }
1058
- }
1059
- const rejectedPool = [];
1060
- const moves = new Set<string>();
1061
- let ability = '';
1062
-
1063
- const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
1064
- const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
1065
-
1066
- const types = new Set(species.types);
1067
- const abilities = new Set<string>();
1068
- for (const abilityName of Object.values(species.abilities)) {
1069
- if (abilityName === species.abilities.S || (species.unreleasedHidden && abilityName === species.abilities.H)) continue;
1070
- abilities.add(abilityName);
1071
- }
1072
-
1073
- let availableHP = 0;
1074
- for (const moveid of movePool) {
1075
- if (moveid.startsWith('hiddenpower')) availableHP++;
1076
- }
1077
-
1078
- // These moves can be used even if we aren't setting up to use them:
1079
- const SetupException = ['closecombat', 'diamondstorm', 'extremespeed', 'superpower', 'clangingscales'];
1080
-
1081
- let counter: MoveCounter;
1082
- // We use a special variable to track Hidden Power
1083
- // so that we can check for all Hidden Powers at once
1084
- let hasHiddenPower = false;
1085
-
1086
- do {
1087
- // Choose next 4 moves from learnset/viable moves and add them to moves list:
1088
- while (moves.size < this.maxMoveCount && movePool.length) {
1089
- const moveid = this.sampleNoReplace(movePool);
1090
- if (moveid.startsWith('hiddenpower')) {
1091
- availableHP--;
1092
- if (hasHiddenPower) continue;
1093
- hasHiddenPower = true;
1094
- }
1095
- moves.add(moveid);
1096
- }
1097
- while (moves.size < this.maxMoveCount && rejectedPool.length) {
1098
- const moveid = this.sampleNoReplace(rejectedPool);
1099
- if (moveid.startsWith('hiddenpower')) {
1100
- if (hasHiddenPower) continue;
1101
- hasHiddenPower = true;
1102
- }
1103
- moves.add(moveid);
1104
- }
1105
-
1106
- counter = this.queryMoves(moves, species.types, abilities, movePool);
1107
- const runEnforcementChecker = (checkerName: string) => {
1108
- if (!this.moveEnforcementCheckers[checkerName]) return false;
1109
- return this.moveEnforcementCheckers[checkerName](
1110
- movePool, moves, abilities, types, counter, species as Species, teamDetails
1111
- );
1112
- };
1113
-
1114
- // Iterate through the moves again, this time to cull them:
1115
- for (const moveid of moves) {
1116
- const move = this.dex.moves.get(moveid);
1117
-
1118
- let {cull, isSetup} = this.shouldCullMove(
1119
- move, types, moves, abilities, counter, movePool, teamDetails,
1120
- species, isLead, isDoubles
1121
- );
1122
-
1123
- // This move doesn't satisfy our setup requirements:
1124
- if (
1125
- (move.category === 'Physical' && counter.setupType === 'Special') ||
1126
- (move.category === 'Special' && counter.setupType === 'Physical')
1127
- ) {
1128
- // Reject STABs last in case the setup type changes later on
1129
- const stabs = counter.get(species.types[0]) + (counter.get(species.types[1]) || 0);
1130
- if (
1131
- !SetupException.includes(moveid) &&
1132
- (!types.has(move.type) || stabs > 1 || counter.get(move.category) < 2)
1133
- ) cull = true;
1134
- }
1135
- // Hidden Power isn't good enough
1136
- if (
1137
- counter.setupType === 'Special' &&
1138
- moveid === 'hiddenpower' &&
1139
- species.types.length > 1 &&
1140
- counter.get('Special') <= 2 &&
1141
- !types.has(move.type) &&
1142
- !counter.get('Physical') &&
1143
- counter.get('specialpool')
1144
- ) {
1145
- cull = true;
1146
- }
1147
-
1148
- const singlesEnforcement = (
1149
- !['judgment', 'lightscreen', 'reflect', 'sleeptalk', 'toxic'].includes(moveid) && (
1150
- move.category !== 'Status' ||
1151
- // should allow Meganium to cull a recovery move for the sake of STAB
1152
- !(move.flags.heal && species.id !== 'meganium')
1153
- )
1154
- );
1155
- // Pokemon should have moves that benefit their Type/Ability/Weather, as well as moves required by its forme
1156
- if (
1157
- !cull &&
1158
- !move.damage &&
1159
- !isSetup &&
1160
- !move.weather &&
1161
- !move.stallingMove &&
1162
- (isDoubles || singlesEnforcement) && (
1163
- !counter.setupType || counter.setupType === 'Mixed' ||
1164
- (move.category !== counter.setupType && move.category !== 'Status') ||
1165
- (counter.get(counter.setupType) + counter.get('Status') > 3 && !counter.get('hazards'))
1166
- ) && (
1167
- move.category === 'Status' ||
1168
- !types.has(move.type) ||
1169
- (move.basePower && move.basePower < 40 && !move.multihit)
1170
- )
1171
- ) {
1172
- if (
1173
- (!counter.get('stab') && !moves.has('nightshade') && !moves.has('seismictoss') && (
1174
- species.types.length > 1 ||
1175
- (species.types[0] !== 'Normal' && species.types[0] !== 'Psychic') ||
1176
- !moves.has('icebeam') ||
1177
- species.baseStats.spa >= species.baseStats.spd
1178
- )) || (
1179
- moves.has('suckerpunch') && !abilities.has('Contrary') && counter.get('stab') < species.types.length
1180
- ) || (
1181
- (['recover', 'roost', 'slackoff', 'softboiled'].some(m => movePool.includes(m))) &&
1182
- counter.get('Status') &&
1183
- !counter.setupType &&
1184
- ['healingwish', 'trick', 'trickroom'].every(m => !moves.has(m))
1185
- ) || (
1186
- movePool.includes('milkdrink') ||
1187
- movePool.includes('shoreup') ||
1188
- (movePool.includes('stickyweb') && !counter.setupType && !teamDetails.stickyWeb)
1189
- ) || (
1190
- isLead &&
1191
- movePool.includes('stealthrock') &&
1192
- counter.get('Status') && !counter.setupType &&
1193
- !counter.get('speedsetup') && !moves.has('substitute')
1194
- ) || (
1195
- species.requiredMove && movePool.includes(toID(species.requiredMove))
1196
- ) || (
1197
- !counter.get('Normal') &&
1198
- (abilities.has('Aerilate') || abilities.has('Pixilate') || (abilities.has('Refrigerate') && !moves.has('blizzard')))
1199
- )
1200
- ) {
1201
- cull = true;
1202
- } else {
1203
- for (const type of types) {
1204
- if (runEnforcementChecker(type)) {
1205
- cull = true;
1206
- }
1207
- }
1208
- for (const abil of abilities) {
1209
- if (runEnforcementChecker(abil)) {
1210
- cull = true;
1211
- }
1212
- }
1213
- }
1214
- }
1215
-
1216
- // Sleep Talk shouldn't be selected without Rest
1217
- if (moveid === 'rest' && cull) {
1218
- const sleeptalk = movePool.indexOf('sleeptalk');
1219
- if (sleeptalk >= 0) {
1220
- if (movePool.length < 2) {
1221
- cull = false;
1222
- } else {
1223
- this.fastPop(movePool, sleeptalk);
1224
- }
1225
- }
1226
- }
1227
-
1228
- // Remove rejected moves from the move list
1229
- const moveIsHP = moveid.startsWith('hiddenpower');
1230
- if (cull && (
1231
- movePool.length - availableHP ||
1232
- (availableHP && (moveIsHP || !hasHiddenPower))
1233
- )) {
1234
- if (
1235
- move.category !== 'Status' &&
1236
- !move.damage &&
1237
- !move.flags.charge &&
1238
- (!moveIsHP || !availableHP)
1239
- ) {
1240
- rejectedPool.push(moveid);
1241
- }
1242
- if (moveIsHP) hasHiddenPower = false;
1243
- moves.delete(moveid);
1244
- break;
1245
- }
1246
-
1247
- if (cull && rejectedPool.length) {
1248
- if (moveIsHP) hasHiddenPower = false;
1249
- moves.delete(moveid);
1250
- break;
1251
- }
1252
- }
1253
- } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
1254
-
1255
- // Moveset modifications
1256
- if (moves.has('autotomize') && moves.has('heavyslam')) {
1257
- if (species.id === 'celesteela') {
1258
- moves.delete('heavyslam');
1259
- moves.add('flashcannon');
1260
- } else {
1261
- moves.delete('autotomize');
1262
- moves.add('rockpolish');
1263
- }
1264
- }
1265
- if (moves.has('raindance') && moves.has('thunderbolt') && !isDoubles) {
1266
- moves.delete('thunderbolt');
1267
- moves.add('thunder');
1268
- }
1269
- if (moves.has('workup') && !counter.get('Special') && species.id === 'zeraora') {
1270
- moves.delete('workup');
1271
- moves.add('bulkup');
1272
- }
1273
-
1274
- const battleOnly = species.battleOnly && !species.requiredAbility;
1275
- const baseSpecies: Species = battleOnly ? this.dex.species.get(species.battleOnly as string) : species;
1276
-
1277
- const abilityData = Object.values(baseSpecies.abilities).map(a => this.dex.abilities.get(a));
1278
- Utils.sortBy(abilityData, abil => -abil.rating);
1279
-
1280
- if (abilityData[1]) {
1281
- // Sort abilities by rating with an element of randomness
1282
- if (abilityData[2] && abilityData[1].rating <= abilityData[2].rating && this.randomChance(1, 2)) {
1283
- [abilityData[1], abilityData[2]] = [abilityData[2], abilityData[1]];
1284
- }
1285
- if (abilityData[0].rating <= abilityData[1].rating && this.randomChance(1, 2)) {
1286
- [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
1287
- } else if (abilityData[0].rating - 0.6 <= abilityData[1].rating && this.randomChance(2, 3)) {
1288
- [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
1289
- }
1290
- ability = abilityData[0].name;
1291
-
1292
- while (this.shouldCullAbility(
1293
- ability, types, moves, abilities, counter, movePool, teamDetails, species, isDoubles
1294
- )) {
1295
- if (ability === abilityData[0].name && abilityData[1].rating >= 1) {
1296
- ability = abilityData[1].name;
1297
- } else if (ability === abilityData[1].name && abilityData[2] && abilityData[2].rating >= 1) {
1298
- ability = abilityData[2].name;
1299
- } else {
1300
- // Default to the highest rated ability if all are rejected
1301
- ability = abilityData[0].name;
1302
- break;
1303
- }
1304
- }
1305
-
1306
- if (
1307
- abilities.has('Guts') &&
1308
- ability !== 'Quick Feet' &&
1309
- (moves.has('facade') || (moves.has('protect') && !isDoubles) || (moves.has('sleeptalk') && moves.has('rest')))
1310
- ) {
1311
- ability = 'Guts';
1312
- } else if (abilities.has('Moxie') && (counter.get('Physical') > 3 || moves.has('bounce')) && !isDoubles) {
1313
- ability = 'Moxie';
1314
- } else if (isDoubles) {
1315
- if (abilities.has('Intimidate') && !battleOnly) ability = 'Intimidate';
1316
- if (abilities.has('Guts') && ability !== 'Intimidate') ability = 'Guts';
1317
- if (abilities.has('Storm Drain')) ability = 'Storm Drain';
1318
- if (abilities.has('Harvest')) ability = 'Harvest';
1319
- if (abilities.has('Unburden') && ability !== 'Prankster' && !species.isMega) ability = 'Unburden';
1320
- }
1321
- if (species.name === 'Ambipom' && !counter.get('technician')) {
1322
- // If it doesn't qualify for Technician, Skill Link is useless on it
1323
- ability = 'Pickup';
1324
- }
1325
- if (species.name === 'Raticate-Alola') ability = 'Hustle';
1326
- if (species.name === 'Altaria') ability = 'Natural Cure';
1327
- } else {
1328
- ability = abilityData[0].name;
1329
- }
1330
-
1331
- if (species.name === 'Genesect' && moves.has('technoblast')) forme = 'Genesect-Douse';
1332
- if (!isDoubles && species.id === 'pikachu') {
1333
- const pikachuForme = this.sample(['', '-Original', '-Hoenn', '-Sinnoh', '-Unova', '-Kalos', '-Alola', '-Partner']);
1334
- forme = `Pikachu${pikachuForme}`;
1335
- if (forme !== 'Pikachu') ability = 'Static';
1336
- }
1337
-
1338
- if (
1339
- !moves.has('photongeyser') &&
1340
- !teamDetails.zMove &&
1341
- (species.name === 'Necrozma-Dusk-Mane' || species.name === 'Necrozma-Dawn-Wings')
1342
- ) {
1343
- for (const moveid of moves) {
1344
- const move = this.dex.moves.get(moveid);
1345
- if (move.category === 'Status' || types.has(move.type)) continue;
1346
- moves.delete(moveid);
1347
- moves.add('photongeyser');
1348
- break;
1349
- }
1350
- }
1351
-
1352
- let item = this.getHighPriorityItem(ability, types, moves, counter, teamDetails, species, isLead, isDoubles);
1353
- if (item === undefined) item = this.getMediumPriorityItem(ability, moves, counter, species, isDoubles, isLead);
1354
- if (item === undefined) {
1355
- item = this.getLowPriorityItem(ability, types, moves, abilities, counter, teamDetails, species, isLead, isDoubles);
1356
- }
1357
-
1358
- // fallback
1359
- if (item === undefined) item = isDoubles ? 'Sitrus Berry' : 'Leftovers';
1360
- // For Trick / Switcheroo
1361
- if (item === 'Leftovers' && types.has('Poison')) {
1362
- item = 'Black Sludge';
1363
- }
1364
-
1365
- let level: number;
1366
- if (this.adjustLevel) {
1367
- level = this.adjustLevel;
1368
- } else if (!isDoubles) {
1369
- const levelScale: {[k: string]: number} = {uber: 76, ou: 80, uu: 82, ru: 84, nu: 86, pu: 88};
1370
- const customScale: {[k: string]: number} = {
1371
- // Banned Ability
1372
- Dugtrio: 82, Gothitelle: 82, Pelipper: 84, Politoed: 84, Torkoal: 84, Wobbuffet: 82,
1373
- // Holistic judgement
1374
- 'Castform-Rainy': 100, 'Castform-Snowy': 100, 'Castform-Sunny': 100, Delibird: 100, Luvdisc: 100, Spinda: 100, Unown: 100,
1375
- };
1376
- const tier = toID(species.tier).replace('bl', '');
1377
- level = levelScale[tier] || (species.nfe ? 90 : 80);
1378
- if (customScale[species.name]) level = customScale[species.name];
1379
- } else {
1380
- // We choose level based on BST. Min level is 70, max level is 99. 600+ BST is 70, less than 300 is 99. Calculate with those values.
1381
- // Every 10.34 BST adds a level from 70 up to 99. Results are floored. Uses the Mega's stats if holding a Mega Stone
1382
- const baseStats = species.baseStats;
1383
-
1384
- let bst = species.bst;
1385
- // If Wishiwashi, use the school-forme's much higher stats
1386
- if (species.baseSpecies === 'Wishiwashi') bst = this.dex.species.get('wishiwashischool').bst;
1387
- // Adjust levels of mons based on abilities (Pure Power, Sheer Force, etc.) and also Eviolite
1388
- // For the stat boosted, treat the Pokemon's base stat as if it were multiplied by the boost. (Actual effective base stats are higher.)
1389
- const speciesAbility = (baseSpecies === species ? ability : species.abilities[0]);
1390
- if (speciesAbility === 'Huge Power' || speciesAbility === 'Pure Power') {
1391
- bst += baseStats.atk;
1392
- } else if (speciesAbility === 'Parental Bond') {
1393
- bst += 0.25 * (counter.get('Physical') > counter.get('Special') ? baseStats.atk : baseStats.spa);
1394
- } else if (speciesAbility === 'Protean') {
1395
- bst += 0.3 * (counter.get('Physical') > counter.get('Special') ? baseStats.atk : baseStats.spa);
1396
- } else if (speciesAbility === 'Fur Coat') {
1397
- bst += baseStats.def;
1398
- } else if (speciesAbility === 'Slow Start') {
1399
- bst -= baseStats.atk / 2 + baseStats.spe / 2;
1400
- } else if (speciesAbility === 'Truant') {
1401
- bst *= 2 / 3;
1402
- }
1403
- if (item === 'Eviolite') {
1404
- bst += 0.5 * (baseStats.def + baseStats.spd);
1405
- } else if (item === 'Light Ball') {
1406
- bst += baseStats.atk + baseStats.spa;
1407
- }
1408
- level = 70 + Math.floor(((600 - Utils.clampIntRange(bst, 300, 600)) / 10.34));
1409
- }
1410
-
1411
- // Prepare optimal HP
1412
- const srWeakness = this.dex.getEffectiveness('Rock', species);
1413
- while (evs.hp > 1) {
1414
- const hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
1415
- if (moves.has('substitute') && moves.has('reversal')) {
1416
- // Reversal users should be able to use four Substitutes
1417
- if (hp % 4 > 0) break;
1418
- } else if (moves.has('substitute') && (
1419
- item === 'Petaya Berry' || item === 'Sitrus Berry' ||
1420
- (ability === 'Power Construct' && item !== 'Leftovers')
1421
- )) {
1422
- // Three Substitutes should activate Petaya Berry for Dedenne
1423
- // Two Substitutes should activate Sitrus Berry or Power Construct
1424
- if (hp % 4 === 0) break;
1425
- } else if (moves.has('bellydrum') && (item === 'Sitrus Berry' || ability === 'Gluttony')) {
1426
- // Belly Drum should activate Sitrus Berry
1427
- if (hp % 2 === 0) break;
1428
- } else {
1429
- // Maximize number of Stealth Rock switch-ins
1430
- if (srWeakness <= 0 || hp % (4 / srWeakness) > 0) break;
1431
- }
1432
- evs.hp -= 4;
1433
- }
1434
-
1435
- // Minimize confusion damage
1436
- if (!counter.get('Physical') && !moves.has('copycat') && !moves.has('transform')) {
1437
- evs.atk = 0;
1438
- ivs.atk = 0;
1439
- }
1440
-
1441
- // Ensure Nihilego's Beast Boost gives it Special Attack boosts instead of Special Defense
1442
- if (forme === 'Nihilego') evs.spd -= 32;
1443
-
1444
- if (ability === 'Beast Boost' && counter.get('Special') < 1) {
1445
- evs.spa = 0;
1446
- ivs.spa = 0;
1447
- }
1448
-
1449
- if (['gyroball', 'metalburst', 'trickroom'].some(m => moves.has(m))) {
1450
- evs.spe = 0;
1451
- ivs.spe = 0;
1452
- }
1453
-
1454
- // Fix IVs for non-Bottle Cap-able sets
1455
- if (hasHiddenPower && level < 100) {
1456
- let hpType;
1457
- for (const move of moves) {
1458
- if (move.startsWith('hiddenpower')) hpType = move.substr(11);
1459
- }
1460
- if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
1461
- const HPivs = ivs.atk === 0 ? ZeroAttackHPIVs[hpType] : this.dex.types.get(hpType).HPivs;
1462
- let iv: StatID;
1463
- for (iv in HPivs) {
1464
- ivs[iv] = HPivs[iv]!;
1465
- }
1466
- }
1467
-
1468
- return {
1469
- name: species.baseSpecies,
1470
- species: forme,
1471
- gender: species.gender,
1472
- shiny: this.randomChance(1, 1024),
1473
- moves: Array.from(moves),
1474
- ability,
1475
- evs,
1476
- ivs,
1477
- item,
1478
- level,
1479
- };
1480
- }
1481
-
1482
- randomTeam() {
1483
- this.enforceNoDirectCustomBanlistChanges();
1484
-
1485
- const seed = this.prng.seed;
1486
- const ruleTable = this.dex.formats.getRuleTable(this.format);
1487
- const pokemon = [];
1488
-
1489
- // For Monotype
1490
- const isMonotype = !!this.forceMonotype || ruleTable.has('sametypeclause');
1491
- const typePool = this.dex.types.names();
1492
- const type = this.forceMonotype || this.sample(typePool);
1493
-
1494
- const baseFormes: {[k: string]: number} = {};
1495
- let hasMega = false;
1496
-
1497
- const tierCount: {[k: string]: number} = {};
1498
- const typeCount: {[k: string]: number} = {};
1499
- const typeComboCount: {[k: string]: number} = {};
1500
- const teamDetails: RandomTeamsTypes.TeamDetails = {};
1501
-
1502
- // We make at most two passes through the potential Pokemon pool when creating a team - if the first pass doesn't
1503
- // result in a team of six Pokemon we perform a second iteration relaxing as many restrictions as possible.
1504
- for (const restrict of [true, false]) {
1505
- if (pokemon.length >= this.maxTeamSize) break;
1506
- const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype);
1507
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
1508
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
1509
-
1510
- // Check if the forme has moves for random battle
1511
- if (this.format.gameType === 'singles') {
1512
- if (!species.randomBattleMoves) continue;
1513
- } else {
1514
- if (!species.randomDoubleBattleMoves) continue;
1515
- }
1516
- if (!species.exists) continue;
1517
-
1518
- // Limit to one of each species (Species Clause)
1519
- if (baseFormes[species.baseSpecies]) continue;
1520
-
1521
- // Limit one Mega per team
1522
- if (hasMega && species.isMega) continue;
1523
-
1524
- // Adjust rate for species with multiple sets
1525
- switch (species.baseSpecies) {
1526
- case 'Arceus': case 'Silvally':
1527
- if (this.randomChance(8, 9) && !isMonotype) continue;
1528
- break;
1529
- case 'Oricorio':
1530
- if (this.randomChance(3, 4)) continue;
1531
- break;
1532
- case 'Castform': case 'Floette':
1533
- if (this.randomChance(2, 3)) continue;
1534
- break;
1535
- case 'Aegislash': case 'Basculin': case 'Cherrim': case 'Gourgeist': case 'Groudon': case 'Kyogre': case 'Meloetta':
1536
- if (this.randomChance(1, 2)) continue;
1537
- break;
1538
- case 'Greninja':
1539
- if (this.gen >= 7 && this.randomChance(1, 2)) continue;
1540
- break;
1541
- }
1542
- if (species.otherFormes && !hasMega && (
1543
- species.otherFormes.includes(species.name + '-Mega') ||
1544
- species.otherFormes.includes(species.name + '-Mega-X')
1545
- )) {
1546
- continue;
1547
- }
1548
-
1549
- const tier = species.tier;
1550
- const types = species.types;
1551
- const typeCombo = types.slice().sort().join();
1552
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
1553
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
1554
-
1555
- if (restrict && !species.isMega) {
1556
- // Limit one Pokemon per tier, two for Monotype
1557
- if (
1558
- (tierCount[tier] >= (isMonotype || this.forceMonotype ? 2 : 1) * limitFactor) &&
1559
- !this.randomChance(1, Math.pow(5, tierCount[tier]))
1560
- ) {
1561
- continue;
1562
- }
1563
-
1564
- if (!isMonotype && !this.forceMonotype) {
1565
- // Limit two of any type
1566
- let skip = false;
1567
- for (const typeName of types) {
1568
- if (typeCount[typeName] >= 2 * limitFactor) {
1569
- skip = true;
1570
- break;
1571
- }
1572
- }
1573
- if (skip) continue;
1574
- }
1575
-
1576
- // Limit one of any type combination, two in Monotype
1577
- if (!this.forceMonotype && typeComboCount[typeCombo] >= (isMonotype ? 2 : 1) * limitFactor) continue;
1578
- }
1579
-
1580
- const set = this.randomSet(
1581
- species,
1582
- teamDetails,
1583
- pokemon.length === this.maxTeamSize - 1,
1584
- this.format.gameType !== 'singles'
1585
- );
1586
-
1587
- const item = this.dex.items.get(set.item);
1588
-
1589
- // Limit one Z-Move per team
1590
- if (item.zMove && teamDetails.zMove) continue;
1591
-
1592
- // Zoroark copies the last Pokemon
1593
- if (set.ability === 'Illusion') {
1594
- if (pokemon.length < 1) continue;
1595
- set.level = pokemon[pokemon.length - 1].level;
1596
- }
1597
-
1598
- // Okay, the set passes, add it to our team
1599
- pokemon.unshift(set);
1600
-
1601
- // Don't bother tracking details for the last Pokemon
1602
- if (pokemon.length === this.maxTeamSize) break;
1603
-
1604
- // Now that our Pokemon has passed all checks, we can increment our counters
1605
- baseFormes[species.baseSpecies] = 1;
1606
-
1607
- // Increment tier counter
1608
- if (tierCount[tier]) {
1609
- tierCount[tier]++;
1610
- } else {
1611
- tierCount[tier] = 1;
1612
- }
1613
-
1614
- // Increment type counters
1615
- for (const typeName of types) {
1616
- if (typeName in typeCount) {
1617
- typeCount[typeName]++;
1618
- } else {
1619
- typeCount[typeName] = 1;
1620
- }
1621
- }
1622
- if (typeCombo in typeComboCount) {
1623
- typeComboCount[typeCombo]++;
1624
- } else {
1625
- typeComboCount[typeCombo] = 1;
1626
- }
1627
-
1628
- // Track what the team has
1629
- if (item.megaStone) hasMega = true;
1630
- if (item.zMove) teamDetails.zMove = 1;
1631
- if (set.ability === 'Snow Warning' || set.moves.includes('hail')) teamDetails.hail = 1;
1632
- if (set.moves.includes('raindance') || set.ability === 'Drizzle' && !item.onPrimal) teamDetails.rain = 1;
1633
- if (set.ability === 'Sand Stream') teamDetails.sand = 1;
1634
- if (set.moves.includes('sunnyday') || set.ability === 'Drought' && !item.onPrimal) teamDetails.sun = 1;
1635
- if (set.moves.includes('spikes')) teamDetails.spikes = (teamDetails.spikes || 0) + 1;
1636
- if (set.moves.includes('stealthrock')) teamDetails.stealthRock = 1;
1637
- if (set.moves.includes('stickyweb')) teamDetails.stickyWeb = 1;
1638
- if (set.moves.includes('toxicspikes')) teamDetails.toxicSpikes = 1;
1639
- if (set.moves.includes('defog')) teamDetails.defog = 1;
1640
- if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1;
1641
- }
1642
- }
1643
- if (pokemon.length < this.maxTeamSize && pokemon.length < 12) {
1644
- throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
1645
- }
1646
-
1647
- return pokemon;
1648
- }
1649
-
1650
- randomFactorySets: {[format: string]: {[species: string]: BattleFactorySpecies}} = {};
1651
-
1652
- randomFactorySet(
1653
- species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails, tier: string
1654
- ): RandomTeamsTypes.RandomFactorySet | null {
1655
- const id = toID(species.name);
1656
- const setList = this.randomFactorySets[tier][id].sets;
1657
-
1658
- const itemsMax: {[k: string]: number} = {
1659
- choicespecs: 1,
1660
- choiceband: 1,
1661
- choicescarf: 1,
1662
- };
1663
- const movesMax: {[k: string]: number} = {
1664
- rapidspin: 1,
1665
- batonpass: 1,
1666
- stealthrock: 1,
1667
- defog: 1,
1668
- spikes: 1,
1669
- toxicspikes: 1,
1670
- };
1671
- const requiredMoves: {[k: string]: string} = {
1672
- stealthrock: 'hazardSet',
1673
- rapidspin: 'hazardClear',
1674
- defog: 'hazardClear',
1675
- };
1676
- const weatherAbilitiesRequire: {[k: string]: string} = {
1677
- hydration: 'raindance', swiftswim: 'raindance',
1678
- leafguard: 'sunnyday', solarpower: 'sunnyday', chlorophyll: 'sunnyday',
1679
- sandforce: 'sandstorm', sandrush: 'sandstorm', sandveil: 'sandstorm',
1680
- slushrush: 'hail', snowcloak: 'hail',
1681
- };
1682
- const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream'];
1683
-
1684
- // Build a pool of eligible sets, given the team partners
1685
- // Also keep track of sets with moves the team requires
1686
- let effectivePool: {set: AnyObject, moveVariants?: number[]}[] = [];
1687
- const priorityPool = [];
1688
- for (const curSet of setList) {
1689
- if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
1690
-
1691
- const item = this.dex.items.get(curSet.item);
1692
- if (teamData.megaCount && teamData.megaCount > 0 && item.megaStone) continue; // reject 2+ mega stones
1693
- if (teamData.zCount && teamData.zCount > 0 && item.zMove) continue; // reject 2+ Z stones
1694
- if (itemsMax[item.id] && teamData.has[item.id] >= itemsMax[item.id]) continue;
1695
-
1696
- const ability = this.dex.abilities.get(curSet.ability);
1697
- if (weatherAbilitiesRequire[ability.id] && teamData.weather !== weatherAbilitiesRequire[ability.id]) continue;
1698
- if (teamData.weather && weatherAbilities.includes(ability.id)) continue; // reject 2+ weather setters
1699
-
1700
- let reject = false;
1701
- let hasRequiredMove = false;
1702
- const curSetVariants = [];
1703
- for (const move of curSet.moves) {
1704
- const variantIndex = this.random(move.length);
1705
- const moveId = toID(move[variantIndex]);
1706
- if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
1707
- reject = true;
1708
- break;
1709
- }
1710
- if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
1711
- hasRequiredMove = true;
1712
- }
1713
- curSetVariants.push(variantIndex);
1714
- }
1715
- if (reject) continue;
1716
- effectivePool.push({set: curSet, moveVariants: curSetVariants});
1717
- if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants});
1718
- }
1719
- if (priorityPool.length) effectivePool = priorityPool;
1720
-
1721
- if (!effectivePool.length) {
1722
- if (!teamData.forceResult) return null;
1723
- for (const curSet of setList) {
1724
- effectivePool.push({set: curSet});
1725
- }
1726
- }
1727
-
1728
- const setData = this.sample(effectivePool);
1729
- const moves = [];
1730
- for (const [i, moveSlot] of setData.set.moves.entries()) {
1731
- moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
1732
- }
1733
-
1734
-
1735
- const item = this.sampleIfArray(setData.set.item);
1736
- const ability = this.sampleIfArray(setData.set.ability);
1737
- const nature = this.sampleIfArray(setData.set.nature);
1738
- const level = this.adjustLevel || setData.set.level || (tier === "LC" ? 5 : 100);
1739
-
1740
- return {
1741
- name: setData.set.name || species.baseSpecies,
1742
- species: setData.set.species,
1743
- gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
1744
- item: item || '',
1745
- ability: ability || species.abilities['0'],
1746
- shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
1747
- level,
1748
- happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
1749
- evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
1750
- ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
1751
- nature: nature || 'Serious',
1752
- moves,
1753
- };
1754
- }
1755
-
1756
- randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
1757
- this.enforceNoDirectCustomBanlistChanges();
1758
-
1759
- const forceResult = (depth >= 12);
1760
- const isMonotype = !!this.forceMonotype || this.dex.formats.getRuleTable(this.format).has('sametypeclause');
1761
-
1762
- // The teams generated depend on the tier choice in such a way that
1763
- // no exploitable information is leaked from rolling the tier in getTeam(p1).
1764
- if (!this.factoryTier) {
1765
- this.factoryTier = isMonotype ? 'Mono' : this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
1766
- } else if (isMonotype && this.factoryTier !== 'Mono') {
1767
- // I don't think this can ever happen?
1768
- throw new Error(`Can't generate a Monotype Battle Factory set in a battle with factory tier ${this.factoryTier}`);
1769
- }
1770
-
1771
- const tierValues: {[k: string]: number} = {
1772
- Uber: 5,
1773
- OU: 4, UUBL: 4,
1774
- UU: 3, RUBL: 3,
1775
- RU: 2, NUBL: 2,
1776
- NU: 1, PUBL: 1,
1777
- PU: 0,
1778
- };
1779
-
1780
- const pokemon = [];
1781
- const pokemonPool = Object.keys(this.randomFactorySets[this.factoryTier]);
1782
-
1783
- const typePool = this.dex.types.names();
1784
- const type = this.sample(typePool);
1785
-
1786
- const teamData: TeamData = {
1787
- typeCount: {}, typeComboCount: {}, baseFormes: {}, megaCount: 0, zCount: 0,
1788
- has: {}, forceResult: forceResult, weaknesses: {}, resistances: {},
1789
- };
1790
- const requiredMoveFamilies = ['hazardSet', 'hazardClear'];
1791
- const requiredMoves: {[k: string]: string} = {
1792
- stealthrock: 'hazardSet',
1793
- rapidspin: 'hazardClear',
1794
- defog: 'hazardClear',
1795
- };
1796
- const weatherAbilitiesSet: {[k: string]: string} = {
1797
- drizzle: 'raindance',
1798
- drought: 'sunnyday',
1799
- snowwarning: 'hail',
1800
- sandstream: 'sandstorm',
1801
- };
1802
- const resistanceAbilities: {[k: string]: string[]} = {
1803
- dryskin: ['Water'], waterabsorb: ['Water'], stormdrain: ['Water'],
1804
- flashfire: ['Fire'], heatproof: ['Fire'],
1805
- lightningrod: ['Electric'], motordrive: ['Electric'], voltabsorb: ['Electric'],
1806
- sapsipper: ['Grass'],
1807
- thickfat: ['Ice', 'Fire'],
1808
- levitate: ['Ground'],
1809
- };
1810
-
1811
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
1812
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
1813
- if (!species.exists) continue;
1814
-
1815
- // Lessen the need of deleting sets of Pokemon after tier shifts
1816
- if (
1817
- this.factoryTier in tierValues && species.tier in tierValues &&
1818
- tierValues[species.tier] > tierValues[this.factoryTier]
1819
- ) continue;
1820
-
1821
- const speciesFlags = this.randomFactorySets[this.factoryTier][species.id].flags;
1822
-
1823
- // Limit to one of each species (Species Clause)
1824
- if (teamData.baseFormes[species.baseSpecies]) continue;
1825
-
1826
- // Limit the number of Megas to one
1827
- if (!teamData.megaCount) teamData.megaCount = 0;
1828
- if (teamData.megaCount >= 1 && speciesFlags.megaOnly) continue;
1829
-
1830
- const set = this.randomFactorySet(species, teamData, this.factoryTier);
1831
- if (!set) continue;
1832
-
1833
- const itemData = this.dex.items.get(set.item);
1834
-
1835
- // Actually limit the number of Megas to one
1836
- if (teamData.megaCount >= 1 && itemData.megaStone) continue;
1837
-
1838
- // Limit the number of Z moves to one
1839
- if (teamData.zCount && teamData.zCount >= 1 && itemData.zMove) continue;
1840
-
1841
- let types = species.types;
1842
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
1843
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
1844
-
1845
- // Enforce Monotype
1846
- if (isMonotype) {
1847
- // Prevents Mega Evolutions from breaking the type limits
1848
- if (itemData.megaStone) {
1849
- const megaSpecies = this.dex.species.get(itemData.megaStone);
1850
- if (types.length > megaSpecies.types.length) types = [species.types[0]];
1851
- // Only check the second type because a Mega Evolution should always share the first type with its base forme.
1852
- if (megaSpecies.types[1] && types[1] && megaSpecies.types[1] !== types[1]) {
1853
- types = [megaSpecies.types[0]];
1854
- }
1855
- }
1856
- if (!types.includes(type)) continue;
1857
- } else {
1858
- // If not Monotype, limit to two of each type
1859
- let skip = false;
1860
- for (const typeName of types) {
1861
- if (teamData.typeCount[typeName] >= 2 * limitFactor && this.randomChance(4, 5)) {
1862
- skip = true;
1863
- break;
1864
- }
1865
- }
1866
- if (skip) continue;
1867
-
1868
- // Limit 1 of any type combination
1869
- let typeCombo = types.slice().sort().join();
1870
- if (set.ability + '' === 'Drought' || set.ability + '' === 'Drizzle') {
1871
- // Drought and Drizzle don't count towards the type combo limit
1872
- typeCombo = set.ability + '';
1873
- }
1874
- if (teamData.typeComboCount[typeCombo] >= 1 * limitFactor) continue;
1875
- }
1876
-
1877
- // Okay, the set passes, add it to our team
1878
- pokemon.push(set);
1879
- const typeCombo = types.slice().sort().join();
1880
- // Now that our Pokemon has passed all checks, we can update team data:
1881
- for (const typeName of types) {
1882
- if (typeName in teamData.typeCount) {
1883
- teamData.typeCount[typeName]++;
1884
- } else {
1885
- teamData.typeCount[typeName] = 1;
1886
- }
1887
- }
1888
- teamData.typeComboCount[typeCombo] = (teamData.typeComboCount[typeCombo] + 1) || 1;
1889
-
1890
- teamData.baseFormes[species.baseSpecies] = 1;
1891
-
1892
- if (itemData.megaStone) teamData.megaCount++;
1893
- if (itemData.zMove) {
1894
- if (!teamData.zCount) teamData.zCount = 0;
1895
- teamData.zCount++;
1896
- }
1897
- if (itemData.id in teamData.has) {
1898
- teamData.has[itemData.id]++;
1899
- } else {
1900
- teamData.has[itemData.id] = 1;
1901
- }
1902
-
1903
- const abilityState = this.dex.abilities.get(set.ability);
1904
- if (abilityState.id in weatherAbilitiesSet) {
1905
- teamData.weather = weatherAbilitiesSet[abilityState.id];
1906
- }
1907
-
1908
- for (const move of set.moves) {
1909
- const moveId = toID(move);
1910
- if (moveId in teamData.has) {
1911
- teamData.has[moveId]++;
1912
- } else {
1913
- teamData.has[moveId] = 1;
1914
- }
1915
- if (moveId in requiredMoves) {
1916
- teamData.has[requiredMoves[moveId]] = 1;
1917
- }
1918
- }
1919
-
1920
- for (const typeName of this.dex.types.names()) {
1921
- // Cover any major weakness (3+) with at least one resistance
1922
- if (teamData.resistances[typeName] >= 1) continue;
1923
- if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
1924
- // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
1925
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
1926
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
1927
- continue;
1928
- }
1929
- const typeMod = this.dex.getEffectiveness(typeName, types);
1930
- if (typeMod < 0) {
1931
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
1932
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
1933
- } else if (typeMod > 0) {
1934
- teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
1935
- }
1936
- }
1937
- }
1938
- if (pokemon.length < this.maxTeamSize) return this.randomFactoryTeam(side, ++depth);
1939
-
1940
- // Quality control
1941
- if (!teamData.forceResult) {
1942
- for (const requiredFamily of requiredMoveFamilies) {
1943
- if (!teamData.has[requiredFamily]) return this.randomFactoryTeam(side, ++depth);
1944
- }
1945
- for (const typeName in teamData.weaknesses) {
1946
- if (teamData.weaknesses[typeName] >= 3) return this.randomFactoryTeam(side, ++depth);
1947
- }
1948
- }
1949
-
1950
- return pokemon;
1951
- }
1952
-
1953
- randomBSSFactorySets: AnyObject = {};
1954
-
1955
- randomBSSFactorySet(
1956
- species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails
1957
- ): RandomTeamsTypes.RandomFactorySet | null {
1958
- const id = toID(species.name);
1959
- // const flags = this.randomBSSFactorySets[tier][id].flags;
1960
- const setList = this.randomBSSFactorySets[id].sets;
1961
-
1962
- const movesMax: {[k: string]: number} = {
1963
- batonpass: 1,
1964
- stealthrock: 1,
1965
- spikes: 1,
1966
- toxicspikes: 1,
1967
- doubleedge: 1,
1968
- trickroom: 1,
1969
- };
1970
- const requiredMoves: {[k: string]: number} = {};
1971
- const weatherAbilitiesRequire: {[k: string]: string} = {
1972
- swiftswim: 'raindance',
1973
- sandrush: 'sandstorm', sandveil: 'sandstorm',
1974
- };
1975
- const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream'];
1976
-
1977
- // Build a pool of eligible sets, given the team partners
1978
- // Also keep track of sets with moves the team requires
1979
- let effectivePool: {set: AnyObject, moveVariants?: number[], itemVariants?: number, abilityVariants?: number}[] = [];
1980
- const priorityPool = [];
1981
- for (const curSet of setList) {
1982
- if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
1983
-
1984
- const item = this.dex.items.get(curSet.item);
1985
- if (teamData.megaCount && teamData.megaCount > 1 && item.megaStone) continue; // reject 3+ mega stones
1986
- if (teamData.zCount && teamData.zCount > 1 && item.zMove) continue; // reject 3+ Z stones
1987
- if (teamData.has[item.id]) continue; // Item clause
1988
-
1989
- const ability = this.dex.abilities.get(curSet.ability);
1990
- if (weatherAbilitiesRequire[ability.id] && teamData.weather !== weatherAbilitiesRequire[ability.id]) continue;
1991
- if (teamData.weather && weatherAbilities.includes(ability.id)) continue; // reject 2+ weather setters
1992
-
1993
- if (curSet.species === 'Aron' && teamData.weather !== 'sandstorm') continue; // reject Aron without a Sand Stream user
1994
-
1995
- let reject = false;
1996
- let hasRequiredMove = false;
1997
- const curSetVariants = [];
1998
- for (const move of curSet.moves) {
1999
- const variantIndex = this.random(move.length);
2000
- const moveId = toID(move[variantIndex]);
2001
- if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
2002
- reject = true;
2003
- break;
2004
- }
2005
- if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
2006
- hasRequiredMove = true;
2007
- }
2008
- curSetVariants.push(variantIndex);
2009
- }
2010
- if (reject) continue;
2011
- effectivePool.push({set: curSet, moveVariants: curSetVariants});
2012
- if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants});
2013
- }
2014
- if (priorityPool.length) effectivePool = priorityPool;
2015
-
2016
- if (!effectivePool.length) {
2017
- if (!teamData.forceResult) return null;
2018
- for (const curSet of setList) {
2019
- effectivePool.push({set: curSet});
2020
- }
2021
- }
2022
-
2023
- const setData = this.sample(effectivePool);
2024
- const moves = [];
2025
- for (const [i, moveSlot] of setData.set.moves.entries()) {
2026
- moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
2027
- }
2028
-
2029
- return {
2030
- name: setData.set.nickname || setData.set.name || species.baseSpecies,
2031
- species: setData.set.species,
2032
- gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
2033
- item: this.sampleIfArray(setData.set.item) || '',
2034
- ability: setData.set.ability || species.abilities['0'],
2035
- shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
2036
- level: setData.set.level || 50,
2037
- happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
2038
- evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
2039
- ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
2040
- nature: setData.set.nature || 'Serious',
2041
- moves,
2042
- };
2043
- }
2044
-
2045
- randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
2046
- this.enforceNoDirectCustomBanlistChanges();
2047
-
2048
- const forceResult = (depth >= 4);
2049
-
2050
- const pokemon = [];
2051
-
2052
- const pokemonPool = Object.keys(this.randomBSSFactorySets);
2053
-
2054
- const teamData: TeamData = {
2055
- typeCount: {}, typeComboCount: {}, baseFormes: {}, megaCount: 0, zCount: 0,
2056
- eeveeLimCount: 0, has: {}, forceResult, weaknesses: {}, resistances: {},
2057
- };
2058
- const requiredMoveFamilies: string[] = [];
2059
- const requiredMoves: {[k: string]: string} = {};
2060
- const weatherAbilitiesSet: {[k: string]: string} = {
2061
- drizzle: 'raindance',
2062
- drought: 'sunnyday',
2063
- snowwarning: 'hail',
2064
- sandstream: 'sandstorm',
2065
- };
2066
- const resistanceAbilities: {[k: string]: string[]} = {
2067
- waterabsorb: ['Water'],
2068
- flashfire: ['Fire'],
2069
- lightningrod: ['Electric'], voltabsorb: ['Electric'],
2070
- thickfat: ['Ice', 'Fire'],
2071
- levitate: ['Ground'],
2072
- };
2073
-
2074
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
2075
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
2076
- if (!species.exists) continue;
2077
-
2078
- const speciesFlags = this.randomBSSFactorySets[species.id].flags;
2079
- if (!teamData.megaCount) teamData.megaCount = 0;
2080
-
2081
- // Limit to one of each species (Species Clause)
2082
- if (teamData.baseFormes[species.baseSpecies]) continue;
2083
-
2084
- // Limit the number of Megas + Z-moves to 3
2085
- if (teamData.megaCount + (teamData.zCount ? teamData.zCount : 0) >= 3 && speciesFlags.megaOnly) continue;
2086
-
2087
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
2088
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
2089
-
2090
- // Limit 2 of any type
2091
- const types = species.types;
2092
- let skip = false;
2093
- for (const type of types) {
2094
- if (teamData.typeCount[type] >= 2 * limitFactor && this.randomChance(4, 5)) {
2095
- skip = true;
2096
- break;
2097
- }
2098
- }
2099
- if (skip) continue;
2100
-
2101
- // Restrict Eevee with certain Pokemon
2102
- if (speciesFlags.limEevee) {
2103
- if (!teamData.eeveeLimCount) teamData.eeveeLimCount = 0;
2104
- teamData.eeveeLimCount++;
2105
- }
2106
- if (teamData.eeveeLimCount && teamData.eeveeLimCount >= 1 && speciesFlags.limEevee) continue;
2107
-
2108
- const set = this.randomBSSFactorySet(species, teamData);
2109
- if (!set) continue;
2110
-
2111
- // Limit 1 of any type combination
2112
- let typeCombo = types.slice().sort().join();
2113
- if (set.ability === 'Drought' || set.ability === 'Drizzle') {
2114
- // Drought and Drizzle don't count towards the type combo limit
2115
- typeCombo = set.ability;
2116
- }
2117
- if (teamData.typeComboCount[typeCombo] >= 1 * limitFactor) continue;
2118
-
2119
- // Okay, the set passes, add it to our team
2120
- pokemon.push(set);
2121
-
2122
- // Now that our Pokemon has passed all checks, we can update team data:
2123
- for (const type of types) {
2124
- if (type in teamData.typeCount) {
2125
- teamData.typeCount[type]++;
2126
- } else {
2127
- teamData.typeCount[type] = 1;
2128
- }
2129
- }
2130
- teamData.typeComboCount[typeCombo] = (teamData.typeComboCount[typeCombo] + 1) || 1;
2131
-
2132
- teamData.baseFormes[species.baseSpecies] = 1;
2133
-
2134
- // Limit Mega and Z-move
2135
- const itemData = this.dex.items.get(set.item);
2136
- if (itemData.megaStone) teamData.megaCount++;
2137
- if (itemData.zMove) {
2138
- if (!teamData.zCount) teamData.zCount = 0;
2139
- teamData.zCount++;
2140
- }
2141
- teamData.has[itemData.id] = 1;
2142
-
2143
- const abilityState = this.dex.abilities.get(set.ability);
2144
- if (abilityState.id in weatherAbilitiesSet) {
2145
- teamData.weather = weatherAbilitiesSet[abilityState.id];
2146
- }
2147
-
2148
- for (const move of set.moves) {
2149
- const moveId = toID(move);
2150
- if (moveId in teamData.has) {
2151
- teamData.has[moveId]++;
2152
- } else {
2153
- teamData.has[moveId] = 1;
2154
- }
2155
- if (moveId in requiredMoves) {
2156
- teamData.has[requiredMoves[moveId]] = 1;
2157
- }
2158
- }
2159
-
2160
- for (const typeName of this.dex.types.names()) {
2161
- // Cover any major weakness (3+) with at least one resistance
2162
- if (teamData.resistances[typeName] >= 1) continue;
2163
- if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
2164
- // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
2165
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
2166
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
2167
- continue;
2168
- }
2169
- const typeMod = this.dex.getEffectiveness(typeName, types);
2170
- if (typeMod < 0) {
2171
- teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
2172
- if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
2173
- } else if (typeMod > 0) {
2174
- teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
2175
- }
2176
- }
2177
- }
2178
- if (pokemon.length < this.maxTeamSize) return this.randomBSSFactoryTeam(side, ++depth);
2179
-
2180
- // Quality control
2181
- if (!teamData.forceResult) {
2182
- for (const requiredFamily of requiredMoveFamilies) {
2183
- if (!teamData.has[requiredFamily]) return this.randomBSSFactoryTeam(side, ++depth);
2184
- }
2185
- for (const type in teamData.weaknesses) {
2186
- if (teamData.weaknesses[type] >= 3) return this.randomBSSFactoryTeam(side, ++depth);
2187
- }
2188
- }
2189
-
2190
- return pokemon;
2191
- }
2192
- }
2193
-
2194
- export default RandomGen7Teams;