@pkmn/randoms 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gen4.ts DELETED
@@ -1,858 +0,0 @@
1
- import {MoveCounter} from './gen8';
2
- import {RandomGen5Teams} from './gen5';
3
- import {Utils} from './utils';
4
- import {
5
- Format,
6
- ModdedDex,
7
- Move,
8
- PRNG,
9
- PRNGSeed,
10
- RandomTeamsTypes,
11
- SparseStatsTable,
12
- Species,
13
- StatID,
14
- toID,
15
- } from '@pkmn/sim';
16
-
17
-
18
- // These moves can be used even if we aren't setting up to use them:
19
- const SetupException = ['dracometeor', 'overheat'];
20
-
21
- // Give recovery moves priority over certain other defensive status moves
22
- const recoveryMoves = [
23
- 'healorder', 'milkdrink', 'moonlight', 'morningsun', 'painsplit', 'recover', 'rest', 'roost',
24
- 'slackoff', 'softboiled', 'synthesis', 'wish',
25
- ];
26
- const defensiveStatusMoves = ['aromatherapy', 'haze', 'healbell', 'roar', 'whirlwind', 'willowisp', 'yawn'];
27
- export class RandomGen4Teams extends RandomGen5Teams {
28
- constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
29
- super(dex, format, prng);
30
- this.moveEnforcementCheckers = {
31
- Bug: (movePool, moves, abilities, types, counter) => (
32
- !counter.get('Bug') && (movePool.includes('bugbuzz') || movePool.includes('megahorn'))
33
- ),
34
- Dark: (movePool, moves, abilities, types, counter) => (
35
- (!counter.get('Dark') || (counter.get('Dark') === 1 && moves.has('pursuit'))) &&
36
- movePool.includes('suckerpunch') &&
37
- counter.setupType !== 'Special'
38
- ),
39
- Dragon: (movePool, moves, abilities, types, counter) => !counter.get('Dragon'),
40
- Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric'),
41
- Fighting: (movePool, moves, abilities, types, counter) => (
42
- !counter.get('Fighting') &&
43
- (!!counter.setupType || !counter.get('Status') || movePool.includes('closecombat') || movePool.includes('highjumpkick'))
44
- ),
45
- Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'),
46
- Flying: (movePool, moves, abilities, types, counter) => !counter.get('Flying') && (
47
- (counter.setupType !== 'Special' && movePool.includes('bravebird')) ||
48
- (abilities.has('Serene Grace') && movePool.includes('airslash'))
49
- ),
50
- Grass: (movePool, moves, abilities, types, counter) => (
51
- !counter.get('Grass') &&
52
- ['leafblade', 'leafstorm', 'seedflare', 'woodhammer'].some(m => movePool.includes(m))
53
- ),
54
- Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'),
55
- Ice: (movePool, moves, abilities, types, counter) => (
56
- !counter.get('Ice') && (!types.has('Water') || !counter.get('Water'))
57
- ),
58
- Rock: (movePool, moves, abilities, types, counter) => (
59
- !counter.get('Rock') && (movePool.includes('headsmash') || movePool.includes('stoneedge'))
60
- ),
61
- Steel: (movePool, moves, abilities, types, counter) => !counter.get('Steel') && movePool.includes('meteormash'),
62
- Water: (movePool, moves, abilities, types, counter) => (
63
- !counter.get('Water') && (moves.has('raindance') || !types.has('Ice') || !counter.get('Ice'))
64
- ),
65
- Adaptability: (movePool, moves, abilities, types, counter, species) => (
66
- !counter.setupType &&
67
- species.types.length > 1 &&
68
- (!counter.get(species.types[0]) || !counter.get(species.types[1]))
69
- ),
70
- Guts: (movePool, moves, abilities, types) => types.has('Normal') && movePool.includes('facade'),
71
- 'Slow Start': movePool => movePool.includes('substitute'),
72
- };
73
- }
74
- shouldCullMove(
75
- move: Move,
76
- types: Set<string>,
77
- moves: Set<string>,
78
- abilities: Set<string>,
79
- counter: MoveCounter,
80
- movePool: string[],
81
- teamDetails: RandomTeamsTypes.TeamDetails,
82
- species: Species,
83
- isLead: boolean,
84
- ): {cull: boolean, isSetup?: boolean} {
85
- const restTalk = moves.has('rest') && moves.has('sleeptalk');
86
-
87
- switch (move.id) {
88
- // Not very useful without their supporting moves
89
- case 'batonpass':
90
- return {cull: !counter.setupType && !counter.get('speedsetup') && !moves.has('substitute')};
91
- case 'eruption': case 'waterspout':
92
- return {cull: counter.get('Physical') + counter.get('Special') < 4};
93
- case 'focuspunch':
94
- return {cull: !moves.has('substitute') || counter.damagingMoves.size < 2 || moves.has('hammerarm')};
95
- case 'raindance':
96
- return {cull: abilities.has('Hydration') ? !moves.has('rest') : counter.get('Physical') + counter.get('Special') < 2};
97
- case 'refresh':
98
- return {cull: !(moves.has('calmmind') && (moves.has('recover') || moves.has('roost')))};
99
- case 'rest':
100
- return {cull: movePool.includes('sleeptalk') || (abilities.has('Hydration') && !moves.has('raindance'))};
101
- case 'sleeptalk':
102
- if (movePool.length > 1) {
103
- const rest = movePool.indexOf('rest');
104
- if (rest >= 0) this.fastPop(movePool, rest);
105
- }
106
- return {cull: !moves.has('rest')};
107
- case 'sunnyday':
108
- return {cull: !moves.has('solarbeam')};
109
- case 'weatherball':
110
- return {cull: !moves.has('raindance') && !moves.has('sunnyday')};
111
-
112
- // Set up once and only if we have the moves for it
113
- case 'bellydrum': case 'bulkup': case 'curse': case 'dragondance': case 'swordsdance':
114
- const notEnoughPhysicalMoves = (
115
- counter.get('Physical') + counter.get('physicalpool') < 2 &&
116
- !moves.has('batonpass') &&
117
- (!moves.has('rest') || !moves.has('sleeptalk'))
118
- );
119
- const badPhysicalMoveset = counter.setupType !== 'Physical' || counter.get('physicalsetup') > 1;
120
- return {cull: moves.has('sunnyday') || notEnoughPhysicalMoves || badPhysicalMoveset, isSetup: true};
121
- case 'calmmind': case 'nastyplot': case 'tailglow':
122
- const notEnoughSpecialMoves = (
123
- counter.get('Special') + counter.get('specialpool') < 2 &&
124
- !moves.has('batonpass') &&
125
- (!moves.has('rest') || !moves.has('sleeptalk'))
126
- );
127
- const badSpecialMoveset = counter.setupType !== 'Special' || counter.get('specialsetup') > 1;
128
- return {cull: notEnoughSpecialMoves || badSpecialMoveset, isSetup: true};
129
- case 'agility': case 'rockpolish':
130
- return {cull: restTalk || (counter.damagingMoves.size < 2 && !moves.has('batonpass')), isSetup: !counter.setupType};
131
-
132
- // Bad after setup
133
- case 'destinybond':
134
- return {cull: !!counter.setupType || moves.has('explosion')};
135
- case 'explosion': case 'selfdestruct':
136
- return {cull: (
137
- counter.setupType === 'Special' ||
138
- Array.from(moves).some(id => recoveryMoves.includes(id) || defensiveStatusMoves.includes(id)) ||
139
- ['batonpass', 'protect', 'substitute'].some(m => moves.has(m))
140
- )};
141
- case 'foresight': case 'roar': case 'whirlwind':
142
- return {cull: !!counter.setupType && !abilities.has('Speed Boost')};
143
- case 'healingwish': case 'lunardance':
144
- return {cull: !!counter.setupType || moves.has('rest') || moves.has('substitute')};
145
- case 'protect':
146
- return {cull: (
147
- !['Guts', 'Quick Feet', 'Speed Boost'].some(abil => abilities.has(abil)) ||
148
- ['rest', 'softboiled', 'toxic', 'wish'].some(m => moves.has(m))
149
- )};
150
- case 'wish':
151
- return {cull: (
152
- !['batonpass', 'protect', 'uturn'].some(m => moves.has(m)) ||
153
- moves.has('rest') ||
154
- !!counter.get('speedsetup')
155
- )};
156
- case 'rapidspin':
157
- return {cull: !!teamDetails.rapidSpin || (!!counter.setupType && counter.get('Physical') + counter.get('Special') < 2)};
158
- case 'reflect': case 'lightscreen': case 'fakeout':
159
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('substitute')};
160
- case 'spikes':
161
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('substitute')};
162
- case 'stealthrock':
163
- return {cull: (
164
- !!counter.setupType ||
165
- !!counter.get('speedsetup') ||
166
- moves.has('rest') || moves.has('substitute') ||
167
- !!teamDetails.stealthRock
168
- )};
169
- case 'switcheroo': case 'trick':
170
- return {cull: (
171
- counter.get('Physical') + counter.get('Special') < 3 ||
172
- !!counter.setupType ||
173
- ['fakeout', 'lightscreen', 'reflect', 'suckerpunch', 'trickroom'].some(m => moves.has(m))
174
- )};
175
- case 'toxic': case 'toxicspikes':
176
- return {cull: (
177
- !!counter.setupType || !!counter.get('speedsetup') || !!teamDetails.toxicSpikes || moves.has('willowisp')
178
- )};
179
- case 'trickroom':
180
- return {cull: (
181
- !!counter.setupType ||
182
- !!counter.get('speedsetup') ||
183
- counter.damagingMoves.size < 2 ||
184
- moves.has('lightscreen') || moves.has('reflect') ||
185
- restTalk
186
- )};
187
- case 'uturn':
188
- return {cull: (
189
- (types.has('Bug') && counter.get('stab') < 2 && counter.damagingMoves.size > 1) ||
190
- (abilities.has('Speed Boost') && moves.has('protect')) ||
191
- !!counter.setupType ||
192
- !!counter.get('speedsetup') ||
193
- moves.has('batonpass') || moves.has('substitute')
194
- )};
195
-
196
- // Bit redundant to have both
197
- // Attacks:
198
- case 'bodyslam': case 'slash':
199
- return {cull: moves.has('facade') || moves.has('return')};
200
- case 'doubleedge':
201
- return {cull: ['bodyslam', 'facade', 'return'].some(m => moves.has(m))};
202
- case 'endeavor':
203
- return {cull: !isLead};
204
- case 'headbutt':
205
- return {cull: !moves.has('bodyslam') && !moves.has('thunderwave')};
206
- case 'judgment': case 'swift':
207
- return {cull: counter.setupType !== 'Special' && counter.get('stab') > 1};
208
- case 'quickattack':
209
- return {cull: moves.has('thunderwave')};
210
- case 'firepunch': case 'flamethrower':
211
- return {cull: moves.has('fireblast') || moves.has('overheat') && !counter.setupType};
212
- case 'lavaplume': case 'fireblast':
213
- if (move.id === 'fireblast' && moves.has('lavaplume') && !counter.get('speedsetup')) return {cull: true};
214
- if (move.id === 'lavaplume' && moves.has('fireblast') && counter.get('speedsetup')) return {cull: true};
215
- if (moves.has('flareblitz') && counter.setupType !== 'Special') return {cull: true};
216
- break;
217
- case 'overheat':
218
- return {cull: counter.setupType === 'Special' || ['batonpass', 'fireblast', 'flareblitz'].some(m => moves.has(m))};
219
- case 'aquajet':
220
- return {cull: moves.has('dragondance') || (moves.has('waterfall') && counter.get('Physical') < 3)};
221
- case 'hydropump':
222
- return {cull: moves.has('surf')};
223
- case 'waterfall':
224
- return {cull: (
225
- moves.has('aquatail') ||
226
- (counter.setupType !== 'Physical' && (moves.has('hydropump') || moves.has('surf')))
227
- )};
228
- case 'chargebeam':
229
- return {cull: moves.has('thunderbolt') && counter.get('Special') < 3};
230
- case 'discharge':
231
- return {cull: moves.has('thunderbolt')};
232
- case 'energyball':
233
- return {cull: (
234
- moves.has('leafblade') ||
235
- moves.has('woodhammer') ||
236
- (moves.has('sunnyday') && moves.has('solarbeam')) ||
237
- (moves.has('leafstorm') && counter.get('Physical') + counter.get('Special') < 4)
238
- )};
239
- case 'grassknot': case 'leafblade': case 'seedbomb':
240
- return {cull: moves.has('woodhammer') || (moves.has('sunnyday') && moves.has('solarbeam'))};
241
- case 'leafstorm':
242
- return {cull: (
243
- !!counter.setupType ||
244
- moves.has('batonpass') ||
245
- moves.has('powerwhip') ||
246
- (moves.has('sunnyday') && moves.has('solarbeam'))
247
- )};
248
- case 'solarbeam':
249
- return {cull: counter.setupType === 'Physical' || !moves.has('sunnyday')};
250
- case 'icepunch':
251
- return {cull: !counter.setupType && moves.has('icebeam')};
252
- case 'aurasphere': case 'drainpunch': case 'focusblast':
253
- return {cull: moves.has('closecombat') && counter.setupType !== 'Special'};
254
- case 'brickbreak': case 'closecombat': case 'crosschop': case 'lowkick':
255
- return {cull: moves.has('substitute') && moves.has('focuspunch')};
256
- case 'machpunch':
257
- return {cull: (
258
- counter.damagingMoves.size <= counter.get('Fighting') ||
259
- (types.has('Fighting') && counter.get('stab') < 2 && !abilities.has('Technician'))
260
- )};
261
- case 'seismictoss':
262
- return {cull: moves.has('nightshade') || counter.get('Physical') + counter.get('Special') >= 1};
263
- case 'superpower':
264
- return {cull: moves.has('dragondance') || !!counter.get('speedsetup')};
265
- case 'gunkshot':
266
- return {cull: moves.has('poisonjab')};
267
- case 'earthpower':
268
- return {cull: moves.has('earthquake')};
269
- case 'airslash':
270
- return {cull: !counter.setupType && moves.has('bravebird')};
271
- case 'zenheadbutt':
272
- return {cull: moves.has('psychocut')};
273
- case 'rockblast': case 'rockslide':
274
- return {cull: moves.has('stoneedge')};
275
- case 'shadowclaw':
276
- return {cull: moves.has('shadowforce') || moves.has('suckerpunch') && !types.has('Ghost')};
277
- case 'dracometeor':
278
- return {cull: moves.has('calmmind') || restTalk || (!!counter.setupType && counter.get('stab') < 2)};
279
- case 'dragonclaw':
280
- return {cull: moves.has('outrage')};
281
- case 'dragonpulse':
282
- return {cull: moves.has('dracometeor') && moves.has('outrage')};
283
- case 'crunch': case 'nightslash':
284
- return {cull: moves.has('suckerpunch')};
285
- case 'pursuit':
286
- return {cull: !!counter.setupType || moves.has('payback')};
287
- case 'flashcannon':
288
- return {cull: (moves.has('ironhead') || movePool.includes('ironhead')) && counter.setupType !== 'Special'};
289
-
290
- // Status:
291
- case 'encore':
292
- return {cull: ['roar', 'taunt', 'whirlwind'].some(m => moves.has(m)) || restTalk};
293
- case 'haze': case 'taunt':
294
- return {cull: restTalk};
295
- case 'leechseed': case 'painsplit':
296
- return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('rest')};
297
- case 'recover': case 'slackoff':
298
- return {cull: restTalk};
299
- case 'stunspore':
300
- return {cull: (
301
- !!counter.setupType ||
302
- moves.has('toxic') ||
303
- movePool.includes('sleeppowder') ||
304
- movePool.includes('spore')
305
- )};
306
- case 'substitute':
307
- return {cull: ['pursuit', 'rapidspin', 'rest', 'taunt'].some(m => moves.has(m))};
308
- case 'thunderwave':
309
- return {cull: (
310
- !!counter.setupType ||
311
- moves.has('toxic') ||
312
- moves.has('trickroom') ||
313
- (moves.has('bodyslam') && abilities.has('Serene Grace'))
314
- )};
315
- case 'yawn':
316
- return {cull: moves.has('thunderwave') || moves.has('toxic')};
317
- }
318
- return {cull: false};
319
- }
320
-
321
- shouldCullAbility(
322
- ability: string,
323
- types: Set<string>,
324
- moves: Set<string>,
325
- abilities: Set<string>,
326
- counter: MoveCounter,
327
- movePool: string[],
328
- teamDetails: RandomTeamsTypes.TeamDetails,
329
- species: Species,
330
- ) {
331
- switch (ability) {
332
- case 'Anger Point': case 'Ice Body': case 'Steadfast':
333
- return true;
334
- case 'Blaze':
335
- return !counter.get('Fire');
336
- case 'Chlorophyll':
337
- return !moves.has('sunnyday') && !teamDetails.sun;
338
- case 'Compound Eyes': case 'No Guard':
339
- return !counter.get('inaccurate');
340
- case 'Early Bird':
341
- return !moves.has('rest');
342
- case 'Gluttony':
343
- return !moves.has('bellydrum');
344
- case 'Hustle':
345
- return counter.get('Physical') < 2;
346
- case 'Mold Breaker':
347
- return !moves.has('earthquake');
348
- case 'Overgrow':
349
- return !counter.get('Grass');
350
- case 'Reckless': case 'Rock Head':
351
- return !counter.get('recoil');
352
- case 'Sand Veil':
353
- return !teamDetails.sand;
354
- case 'Serene Grace':
355
- return !counter.get('serenegrace') || species.id === 'blissey';
356
- case 'Simple':
357
- return !counter.setupType && !moves.has('cosmicpower');
358
- case 'Skill Link':
359
- return !counter.get('skilllink');
360
- case 'Snow Cloak':
361
- return !teamDetails.hail;
362
- case 'Solar Power':
363
- return !counter.get('Special') || !moves.has('sunnyday') && !teamDetails.sun;
364
- case 'Speed Boost':
365
- return moves.has('uturn');
366
- case 'Swift Swim':
367
- return !moves.has('raindance') && !teamDetails.rain;
368
- case 'Swarm':
369
- return !counter.get('Bug');
370
- case 'Synchronize':
371
- return counter.get('Status') < 2;
372
- case 'Technician':
373
- return !counter.get('technician') || moves.has('toxic');
374
- case 'Tinted Lens':
375
- return counter.get('damage') >= counter.damagingMoves.size;
376
- case 'Torrent':
377
- return !counter.get('Water');
378
- }
379
-
380
- return false;
381
- }
382
-
383
- getHighPriorityItem(
384
- ability: string,
385
- types: Set<string>,
386
- moves: Set<string>,
387
- counter: MoveCounter,
388
- teamDetails: RandomTeamsTypes.TeamDetails,
389
- species: Species,
390
- isLead: boolean,
391
- ): string | undefined {
392
- if (species.requiredItem) return species.requiredItem;
393
- if (species.requiredItems) return this.sample(species.requiredItems);
394
- if (species.name === 'Farfetch\u2019d') return 'Stick';
395
- if (species.name === 'Marowak') return 'Thick Club';
396
- if (species.name === 'Shedinja' || species.name === 'Smeargle') return 'Focus Sash';
397
- if (species.name === 'Unown') return 'Choice Specs';
398
- if (species.name === 'Wobbuffet') {
399
- return moves.has('destinybond') ? 'Custap Berry' : this.sample(['Leftovers', 'Sitrus Berry']);
400
- }
401
-
402
- if (moves.has('switcheroo') || moves.has('trick')) {
403
- if (
404
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
405
- !counter.get('priority') &&
406
- this.randomChance(2, 3)
407
- ) {
408
- return 'Choice Scarf';
409
- } else {
410
- return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
411
- }
412
- }
413
- if (moves.has('bellydrum')) return 'Sitrus Berry';
414
- if (ability === 'Magic Guard' || ability === 'Speed Boost' && counter.get('Status') < 2) return 'Life Orb';
415
- if (ability === 'Poison Heal' || ability === 'Toxic Boost') return 'Toxic Orb';
416
-
417
- if (moves.has('rest') && !moves.has('sleeptalk') && ability !== 'Natural Cure' && ability !== 'Shed Skin') {
418
- return (moves.has('raindance') && ability === 'Hydration') ? 'Damp Rock' : 'Chesto Berry';
419
- }
420
- if (moves.has('raindance') && ability === 'Swift Swim' && counter.get('Status') < 2) return 'Life Orb';
421
- if (moves.has('sunnyday')) return (ability === 'Chlorophyll' && counter.get('Status') < 2) ? 'Life Orb' : 'Heat Rock';
422
- if (moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
423
- if ((ability === 'Guts' || ability === 'Quick Feet') && moves.has('facade')) return 'Toxic Orb';
424
- if (ability === 'Unburden') return 'Sitrus Berry';
425
- if (species.baseStats.hp + species.baseStats.def + species.baseStats.spd <= 150) {
426
- return isLead ? 'Focus Sash' : 'Life Orb';
427
- }
428
- if (moves.has('endeavor')) return 'Focus Sash';
429
- }
430
-
431
- getMediumPriorityItem(
432
- ability: string,
433
- moves: Set<string>,
434
- counter: MoveCounter,
435
- species: Species,
436
- isDoubles: boolean,
437
- isLead: boolean
438
- ): string | undefined {
439
- if (
440
- ability === 'Slow Start' ||
441
- ['curse', 'leechseed', 'protect', 'roar', 'sleeptalk', 'whirlwind'].some(m => moves.has(m)) ||
442
- (ability === 'Serene Grace' && ['bodyslam', 'headbutt', 'ironhead'].some(m => moves.has(m)))
443
- ) {
444
- return 'Leftovers';
445
- }
446
-
447
- if (counter.get('Physical') >= 4 && !moves.has('fakeout') && !moves.has('rapidspin') && !moves.has('suckerpunch')) {
448
- return (
449
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
450
- !counter.get('priority') && !moves.has('bodyslam') && this.randomChance(2, 3)
451
- ) ? 'Choice Scarf' : 'Choice Band';
452
- }
453
-
454
- if (
455
- (counter.get('Special') >= 4 || (
456
- counter.get('Special') >= 3 &&
457
- ['batonpass', 'uturn', 'waterspout', 'selfdestruct'].some(m => moves.has(m))
458
- )) &&
459
- !moves.has('chargebeam')
460
- ) {
461
- return (
462
- species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
463
- ability !== 'Speed Boost' && !counter.get('priority') && this.randomChance(2, 3)
464
- ) ? 'Choice Scarf' : 'Choice Specs';
465
- }
466
-
467
- if (moves.has('outrage') && counter.setupType) return 'Lum Berry';
468
- if (moves.has('substitute')) {
469
- return (counter.damagingMoves.size < 2 ||
470
- !counter.get('drain') &&
471
- (counter.damagingMoves.size < 3 || species.baseStats.hp >= 60 || species.baseStats.def + species.baseStats.spd >= 180)
472
- ) ? 'Leftovers' : 'Life Orb';
473
- }
474
- if (ability === 'Guts') return 'Toxic Orb';
475
- if (
476
- isLead &&
477
- !counter.get('recoil') &&
478
- !Array.from(moves).some(id => !!recoveryMoves.includes(id)) &&
479
- species.baseStats.hp + species.baseStats.def + species.baseStats.spd < 225
480
- ) {
481
- return 'Focus Sash';
482
- }
483
- if (counter.damagingMoves.size >= 4) {
484
- return (
485
- counter.get('Normal') || counter.get('Dragon') > 1 || moves.has('chargebeam') || moves.has('suckerpunch')
486
- ) ? 'Life Orb' : 'Expert Belt';
487
- }
488
- if (counter.damagingMoves.size >= 3 && !moves.has('superfang') && !moves.has('metalburst')) {
489
- const totalBulk = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
490
- return (
491
- counter.get('speedsetup') || counter.get('priority') ||
492
- moves.has('dragondance') || moves.has('trickroom') ||
493
- totalBulk < 235 ||
494
- (species.baseStats.spe >= 70 && (totalBulk < 260 || (!!counter.get('recovery') && totalBulk < 285)))
495
- ) ? 'Life Orb' : 'Leftovers';
496
- }
497
- }
498
-
499
- getLowPriorityItem(
500
- ability: string,
501
- types: Set<string>,
502
- moves: Set<string>,
503
- abilities: Set<string>,
504
- counter: MoveCounter,
505
- teamDetails: RandomTeamsTypes.TeamDetails,
506
- species: Species,
507
- ) {
508
- if (types.has('Poison')) return 'Black Sludge';
509
- if (this.dex.getEffectiveness('Rock', species) >= 1 || moves.has('roar')) return 'Leftovers';
510
- if (counter.get('Status') <= 1 && ['metalburst', 'rapidspin', 'superfang'].every(m => !moves.has(m))) return 'Life Orb';
511
- return 'Leftovers';
512
- }
513
-
514
- randomSet(
515
- species: string | Species,
516
- teamDetails: RandomTeamsTypes.TeamDetails = {},
517
- isLead = false
518
- ): RandomTeamsTypes.RandomSet {
519
- species = this.dex.species.get(species);
520
- let forme = species.name;
521
-
522
- if (typeof species.battleOnly === 'string') forme = species.battleOnly;
523
-
524
- if (species.cosmeticFormes) {
525
- forme = this.sample([species.name].concat(species.cosmeticFormes));
526
- }
527
-
528
- const movePool = (species.randomBattleMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
529
- const rejectedPool: string[] = [];
530
- const moves = new Set<string>();
531
- let ability = '';
532
- let item: string | undefined;
533
- const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
534
- const ivs: SparseStatsTable = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
535
-
536
- const types = new Set(species.types);
537
-
538
- const abilities = new Set(Object.values(species.abilities));
539
-
540
- let availableHP = 0;
541
- for (const setMoveid of movePool) {
542
- if (setMoveid.startsWith('hiddenpower')) availableHP++;
543
- }
544
-
545
- let counter: MoveCounter;
546
- let hasHiddenPower = false;
547
-
548
- do {
549
- // Choose next 4 moves from learnset/viable moves and add them to moves list:
550
- while (moves.size < this.maxMoveCount && movePool.length) {
551
- const moveid = this.sampleNoReplace(movePool);
552
- if (moveid.startsWith('hiddenpower')) {
553
- availableHP--;
554
- if (hasHiddenPower) continue;
555
- hasHiddenPower = true;
556
- }
557
- moves.add(moveid);
558
- }
559
-
560
- while (moves.size < this.maxMoveCount && rejectedPool.length) {
561
- const moveid = this.sampleNoReplace(rejectedPool);
562
- if (moveid.startsWith('hiddenpower')) {
563
- if (hasHiddenPower) continue;
564
- hasHiddenPower = true;
565
- }
566
- moves.add(moveid);
567
- }
568
-
569
- counter = this.queryMoves(moves, species.types, abilities, movePool);
570
- if (types.has('Dark') && moves.has('suckerpunch') && species.types.length === 1) {
571
- counter.add('stab');
572
- }
573
-
574
- // Iterate through the moves again, this time to cull them:
575
- for (const moveid of moves) {
576
- const move = this.dex.moves.get(moveid);
577
-
578
- let {cull, isSetup} = this.shouldCullMove(
579
- move, types, moves, abilities, counter,
580
- movePool, teamDetails, species, isLead
581
- );
582
-
583
- // Increased/decreased priority moves are unneeded with moves that boost only speed
584
- if (move.priority !== 0 && !!counter.get('speedsetup')) cull = true;
585
-
586
- // This move doesn't satisfy our setup requirements:
587
- if (
588
- (move.category === 'Physical' && counter.setupType === 'Special') ||
589
- (move.category === 'Special' && counter.setupType === 'Physical')
590
- ) {
591
- // Reject STABs last in case the setup type changes later on
592
- if (
593
- !SetupException.includes(moveid) &&
594
- (!types.has(move.type) || counter.get('stab') > 1 || counter.get(move.category) < 2)
595
- ) {
596
- cull = true;
597
- }
598
- }
599
- if (
600
- counter.setupType && !isSetup && move.category !== counter.setupType &&
601
- counter.get(counter.setupType) < 2 && !moves.has('batonpass')
602
- ) {
603
- // Mono-attacking with setup and RestTalk or recovery + status healing is allowed
604
- if (
605
- moveid !== 'rest' && moveid !== 'sleeptalk' &&
606
- !(recoveryMoves.includes(moveid) && (moves.has('healbell') || moves.has('refresh'))) &&
607
- !((moveid === 'healbell' || moveid === 'refresh') && Array.from(moves).some(id => recoveryMoves.includes(id))) && (
608
- // Reject Status moves only if there is nothing else to reject
609
- move.category !== 'Status' || (
610
- counter.get(counter.setupType) + counter.get('Status') > 3 &&
611
- counter.get('physicalsetup') + counter.get('specialsetup') < 2
612
- )
613
- )
614
- ) {
615
- cull = true;
616
- }
617
- }
618
- if (
619
- moveid === 'hiddenpower' &&
620
- counter.setupType === 'Special' &&
621
- species.types.length > 1 &&
622
- counter.get('Special') <= 2 &&
623
- !types.has(move.type) &&
624
- !counter.get('Physical') &&
625
- counter.get('specialpool') &&
626
- (!(types.has('Ghost') && move.type === 'Fighting' || types.has('Electric') && move.type === 'Ice'))
627
- ) {
628
- // Hidden Power isn't good enough
629
- cull = true;
630
- }
631
-
632
- // Reject defensive status moves if a reliable recovery move is available but not selected.
633
- // Toxic is only defensive if used with another status move other than Protect (Toxic + 3 attacks and Toxic + Protect are ok).
634
- if (
635
- !Array.from(moves).some(id => recoveryMoves.includes(id)) &&
636
- movePool.some(id => recoveryMoves.includes(id)) && (
637
- defensiveStatusMoves.includes(moveid) ||
638
- (moveid === 'toxic' && ((counter.get('Status') > 1 && !moves.has('protect')) || counter.get('Status') > 2))
639
- )
640
- ) {
641
- cull = true;
642
- }
643
-
644
- const runEnforcementChecker = (checkerName: string) => {
645
- if (!this.moveEnforcementCheckers[checkerName]) return false;
646
- return this.moveEnforcementCheckers[checkerName](
647
- movePool, moves, abilities, types, counter, species as Species, teamDetails
648
- );
649
- };
650
-
651
- const moveIsRejectable = (
652
- !move.weather &&
653
- !move.damage &&
654
- (move.category !== 'Status' || !move.flags.heal) &&
655
- (move.category === 'Status' || !types.has(move.type) || (move.basePower && move.basePower < 40 && !move.multihit)) &&
656
- // These moves cannot be rejected in favor of a forced move
657
- !['judgment', 'sleeptalk'].includes(moveid) &&
658
- // Setup-supported moves should only be rejected under specific circumstances
659
- (counter.get('physicalsetup') + counter.get('specialsetup') < 2 && (
660
- !counter.setupType || counter.setupType === 'Mixed' ||
661
- (move.category !== counter.setupType && move.category !== 'Status') ||
662
- counter.get(counter.setupType) + counter.get('Status') > 3
663
- ))
664
- );
665
-
666
- if (!cull && !isSetup && moveIsRejectable) {
667
- // There may be more important moves that this Pokemon needs
668
- const canRollForcedMoves = (
669
- // These moves should always be rolled
670
- movePool.includes('spore') || (!Array.from(moves).some(id => recoveryMoves.includes(id)) && (
671
- movePool.includes('softboiled') ||
672
- (species.baseSpecies === 'Arceus' && movePool.includes('recover'))
673
- ))
674
- );
675
- // Pokemon should usually have at least one STAB move
676
- const requiresStab = (
677
- !counter.get('stab') && !counter.get('damage') && (
678
- species.types.length > 1 ||
679
- (species.types[0] !== 'Normal' && species.types[0] !== 'Psychic') ||
680
- !moves.has('icebeam') ||
681
- species.baseStats.spa >= species.baseStats.spd
682
- )
683
- );
684
- if (
685
- canRollForcedMoves ||
686
- requiresStab ||
687
- (species.requiredMove && movePool.includes(toID(species.requiredMove))) ||
688
- (counter.get('defensesetup') && !counter.get('recovery') && !moves.has('rest'))
689
- ) {
690
- cull = true;
691
- } else {
692
- // Pokemon should have moves that benefit their typing or ability
693
- for (const type of types) {
694
- if (runEnforcementChecker(type)) cull = true;
695
- }
696
- for (const abil of abilities) {
697
- if (runEnforcementChecker(abil)) cull = true;
698
- }
699
- }
700
- }
701
-
702
- // Sleep Talk shouldn't be selected without Rest
703
- if (moveid === 'rest' && cull) {
704
- const sleeptalk = movePool.indexOf('sleeptalk');
705
- if (sleeptalk >= 0) {
706
- if (movePool.length < 2) {
707
- cull = false;
708
- } else {
709
- this.fastPop(movePool, sleeptalk);
710
- }
711
- }
712
- }
713
-
714
- // Remove rejected moves from the move list
715
- if (cull && (
716
- movePool.length - availableHP || availableHP && (moveid.startsWith('hiddenpower') || !hasHiddenPower)
717
- )) {
718
- if (move.category !== 'Status' && (!moveid.startsWith('hiddenpower') || !availableHP)) rejectedPool.push(moveid);
719
- moves.delete(moveid);
720
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
721
- break;
722
- }
723
- if (cull && rejectedPool.length) {
724
- moves.delete(moveid);
725
- if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
726
- break;
727
- }
728
- }
729
- } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
730
-
731
- if (hasHiddenPower) {
732
- let hpType;
733
- for (const move of moves) {
734
- if (move.startsWith('hiddenpower')) {
735
- hpType = move.substr(11);
736
- break;
737
- }
738
- }
739
- if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
740
- const HPivs = this.dex.types.get(hpType).HPivs;
741
- let iv: StatID;
742
- for (iv in HPivs) {
743
- ivs[iv] = HPivs[iv]!;
744
- }
745
- }
746
-
747
- const abilityData = Array.from(abilities).map(a => this.dex.abilities.get(a));
748
- Utils.sortBy(abilityData, abil => -abil.rating);
749
-
750
- let ability0 = abilityData[0];
751
- let ability1 = abilityData[1];
752
- if (abilityData[1]) {
753
- if (ability0.rating <= ability1.rating && this.randomChance(1, 2)) {
754
- [ability0, ability1] = [ability1, ability0];
755
- } else if (ability0.rating - 0.6 <= ability1.rating && this.randomChance(2, 3)) {
756
- [ability0, ability1] = [ability1, ability0];
757
- }
758
- ability = ability0.name;
759
-
760
- while (this.shouldCullAbility(ability, types, moves, abilities, counter, movePool, teamDetails, species)) {
761
- if (ability === ability0.name && ability1.rating >= 1) {
762
- ability = ability1.name;
763
- } else {
764
- // Default to the highest rated ability if all are rejected
765
- ability = abilityData[0].name;
766
- break;
767
- }
768
- }
769
-
770
- if (abilities.has('Hydration') && moves.has('raindance') && moves.has('rest')) {
771
- ability = 'Hydration';
772
- } else if (abilities.has('Swift Swim') && moves.has('raindance')) {
773
- ability = 'Swift Swim';
774
- } else if (abilities.has('Technician') && moves.has('machpunch') && types.has('Fighting') && counter.get('stab') < 2) {
775
- ability = 'Technician';
776
- }
777
- } else {
778
- ability = ability0.name;
779
- }
780
-
781
- item = this.getHighPriorityItem(ability, types, moves, counter, teamDetails, species, isLead);
782
- if (item === undefined) item = this.getMediumPriorityItem(ability, moves, counter, species, false, isLead);
783
- if (item === undefined) {
784
- item = this.getLowPriorityItem(ability, types, moves, abilities, counter, teamDetails, species);
785
- }
786
-
787
- // For Trick / Switcheroo
788
- if (item === 'Leftovers' && types.has('Poison')) {
789
- item = 'Black Sludge';
790
- }
791
-
792
- const levelScale: {[k: string]: number} = {
793
- AG: 74,
794
- Uber: 76,
795
- OU: 80,
796
- '(OU)': 82,
797
- UUBL: 82,
798
- UU: 84,
799
- NUBL: 86,
800
- NU: 88,
801
- };
802
- const customScale: {[k: string]: number} = {
803
- Delibird: 100, Ditto: 100, 'Farfetch\u2019d': 100, Unown: 100, Castform: 100,
804
- };
805
- const level = this.adjustLevel || customScale[species.name] || levelScale[species.tier] || (species.nfe ? 90 : 80);
806
-
807
- // Prepare optimal HP
808
- let hp = Math.floor(
809
- Math.floor(
810
- 2 * species.baseStats.hp + (ivs.hp || 31) + Math.floor(evs.hp / 4) + 100
811
- ) * level / 100 + 10
812
- );
813
- if (moves.has('substitute') && item === 'Sitrus Berry') {
814
- // Two Substitutes should activate Sitrus Berry
815
- while (hp % 4 > 0) {
816
- evs.hp -= 4;
817
- hp = Math.floor(
818
- Math.floor(
819
- 2 * species.baseStats.hp + (ivs.hp || 31) + Math.floor(evs.hp / 4) + 100
820
- ) * level / 100 + 10
821
- );
822
- }
823
- } else if (moves.has('bellydrum') && item === 'Sitrus Berry') {
824
- // Belly Drum should activate Sitrus Berry
825
- if (hp % 2 > 0) evs.hp -= 4;
826
- } else {
827
- // Maximize number of Stealth Rock switch-ins
828
- const srWeakness = this.dex.getEffectiveness('Rock', species);
829
- if (srWeakness > 0 && hp % (4 / srWeakness) === 0) evs.hp -= 4;
830
- }
831
-
832
- // Minimize confusion damage
833
- if (!counter.get('Physical') && !moves.has('transform')) {
834
- evs.atk = 0;
835
- ivs.atk = hasHiddenPower ? (ivs.atk || 31) - 28 : 0;
836
- }
837
-
838
- if (['gyroball', 'metalburst', 'trickroom'].some(m => moves.has(m))) {
839
- evs.spe = 0;
840
- ivs.spe = hasHiddenPower ? (ivs.spe || 31) - 28 : 0;
841
- }
842
-
843
- return {
844
- name: species.baseSpecies,
845
- species: forme,
846
- gender: species.gender,
847
- shiny: this.randomChance(1, 1024),
848
- moves: Array.from(moves),
849
- ability,
850
- evs,
851
- ivs,
852
- item,
853
- level,
854
- };
855
- }
856
- }
857
-
858
- export default RandomGen4Teams;