@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/gen3.ts DELETED
@@ -1,688 +0,0 @@
1
- import {MoveCounter} from './gen8';
2
- import {RandomGen4Teams} from './gen4';
3
- import {Utils} from './utils';
4
- import {
5
- Format,
6
- ModdedDex,
7
- Move,
8
- PRNG,
9
- PRNGSeed,
10
- RandomTeamsTypes,
11
- Species,
12
- StatID,
13
- } from '@pkmn/sim';
14
-
15
- export class RandomGen3Teams extends RandomGen4Teams {
16
- battleHasDitto: boolean;
17
- battleHasWobbuffet: boolean;
18
-
19
- constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
20
- super(dex, format, prng);
21
- this.battleHasDitto = false;
22
- this.battleHasWobbuffet = false;
23
- this.moveEnforcementCheckers = {
24
- Bug: (movePool, moves, abilities, types, counter, species) => (
25
- movePool.includes('megahorn') || (!species.types[1] && movePool.includes('hiddenpowerbug'))
26
- ),
27
- Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric'),
28
- Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting'),
29
- Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'),
30
- Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'),
31
- Normal: (movePool, moves, abilities, types, counter, species) => {
32
- if (species.id === 'blissey' && movePool.includes('softboiled')) return true;
33
- return !counter.get('Normal') && counter.setupType === 'Physical';
34
- },
35
- Psychic: (movePool, moves, abilities, types, counter, species) => (
36
- types.has('Psychic') &&
37
- (movePool.includes('psychic') || movePool.includes('psychoboost')) &&
38
- species.baseStats.spa >= 100
39
- ),
40
- Rock: (movePool, moves, abilities, types, counter, species) => !counter.get('Rock') && species.baseStats.atk >= 100,
41
- Water: (movePool, moves, abilities, types, counter, species) => (
42
- !counter.get('Water') && counter.setupType !== 'Physical' && species.baseStats.spa >= 60
43
- ),
44
- // If the Pokémon has this move, the other move will be forced
45
- protect: movePool => movePool.includes('wish'),
46
- sunnyday: movePool => movePool.includes('solarbeam'),
47
- sleeptalk: movePool => movePool.includes('rest'),
48
- };
49
- }
50
-
51
- shouldCullMove(
52
- move: Move,
53
- types: Set<string>,
54
- moves: Set<string>,
55
- abilities: Set<string>,
56
- counter: MoveCounter,
57
- movePool: string[],
58
- teamDetails: RandomTeamsTypes.TeamDetails,
59
- species: Species,
60
- ): {cull: boolean, isSetup?: boolean} {
61
- const restTalk = moves.has('rest') && moves.has('sleeptalk');
62
-
63
- switch (move.id) {
64
- // Set up once and only if we have the moves for it
65
- case 'bulkup': case 'curse': case 'dragondance': case 'swordsdance':
66
- return {
67
- cull: (
68
- (counter.setupType !== 'Physical' || counter.get('physicalsetup') > 1) ||
69
- (counter.get('Physical') + counter.get('physicalpool') < 2 && !moves.has('batonpass') && !restTalk)
70
- ),
71
- isSetup: true,
72
- };
73
- case 'calmmind':
74
- return {
75
- cull: (
76
- counter.setupType !== 'Special' ||
77
- (counter.get('Special') + counter.get('specialpool') < 2 && !moves.has('batonpass') && !restTalk)
78
- ),
79
- isSetup: true,
80
- };
81
- case 'agility':
82
- return {
83
- cull: (counter.damagingMoves.size < 2 && !moves.has('batonpass')) || moves.has('substitute') || restTalk,
84
- isSetup: !counter.setupType,
85
- };
86
-
87
- // Not very useful without their supporting moves
88
- case 'amnesia': case 'sleeptalk':
89
- if (moves.has('roar')) return {cull: true};
90
- if (!moves.has('rest')) return {cull: true};
91
- if (movePool.length > 1) {
92
- const rest = movePool.indexOf('rest');
93
- if (rest >= 0) this.fastPop(movePool, rest);
94
- }
95
- break;
96
- case 'barrier':
97
- return {cull: !moves.has('calmmind') && !moves.has('batonpass') && !moves.has('mirrorcoat')};
98
- case 'batonpass':
99
- return {cull: (
100
- (!counter.setupType && !counter.get('speedsetup')) &&
101
- ['meanlook', 'spiderweb', 'substitute', 'wish'].every(m => !moves.has(m))
102
- )};
103
- case 'endeavor': case 'flail': case 'reversal':
104
- return {cull: restTalk || (!moves.has('endure') && !moves.has('substitute'))};
105
- case 'endure':
106
- return {cull: movePool.includes('destinybond')};
107
- case 'extremespeed': case 'raindance': case 'sunnyday':
108
- return {cull: counter.damagingMoves.size < 2 || moves.has('rest')};
109
- case 'focuspunch':
110
- return {cull: (
111
- (counter.damagingMoves.size < 2 || moves.has('rest') || counter.setupType && !moves.has('spore')) ||
112
- (!moves.has('substitute') && (counter.get('Physical') < 4 || moves.has('fakeout'))) ||
113
- // Breloom likes to have coverage
114
- (species.id === 'breloom' && (moves.has('machpunch') || moves.has('skyuppercut')))
115
- )};
116
- case 'moonlight':
117
- return {cull: moves.has('wish') || moves.has('protect')};
118
- case 'perishsong':
119
- return {cull: !moves.has('meanlook') && !moves.has('spiderweb')};
120
- case 'protect':
121
- return {cull: !abilities.has('Speed Boost') && ['perishsong', 'toxic', 'wish'].every(m => !moves.has(m))};
122
- case 'refresh':
123
- return {cull: !counter.setupType};
124
- case 'rest':
125
- return {cull: (
126
- movePool.includes('sleeptalk') ||
127
- (!moves.has('sleeptalk') && (!!counter.get('recovery') || movePool.includes('curse')))
128
- )};
129
- case 'solarbeam':
130
- if (movePool.length > 1) {
131
- const sunnyday = movePool.indexOf('sunnyday');
132
- if (sunnyday >= 0) this.fastPop(movePool, sunnyday);
133
- }
134
- return {cull: !moves.has('sunnyday')};
135
-
136
- // Bad after setup
137
- case 'aromatherapy': case 'healbell':
138
- return {cull: moves.has('rest') || !!teamDetails.statusCure};
139
- case 'confuseray':
140
- return {cull: !!counter.setupType || restTalk};
141
- case 'counter': case 'mirrorcoat':
142
- return {cull: !!counter.setupType || ['rest', 'substitute', 'toxic'].some(m => moves.has(m))};
143
- case 'destinybond':
144
- return {cull: !!counter.setupType || moves.has('explosion') || moves.has('selfdestruct')};
145
- case 'doubleedge': case 'facade': case 'fakeout': case 'waterspout':
146
- return {cull: (
147
- (!types.has(move.type) && counter.get('Status') >= 1) ||
148
- (move.id === 'doubleedge' && moves.has('return'))
149
- )};
150
- case 'encore': case 'painsplit': case 'recover': case 'yawn':
151
- return {cull: restTalk};
152
- case 'explosion': case 'machpunch': case 'selfdestruct':
153
- // Snorlax doesn't want to roll selfdestruct as its only STAB move
154
- const snorlaxCase = species.id === 'snorlax' && !moves.has('return') && !moves.has('bodyslam');
155
- return {cull: snorlaxCase || moves.has('rest') || moves.has('substitute') || !!counter.get('recovery')};
156
- case 'haze':
157
- return {cull: !!counter.setupType || moves.has('raindance') || restTalk};
158
- case 'icywind': case 'pursuit': case 'superpower': case 'transform':
159
- return {cull: !!counter.setupType || moves.has('rest')};
160
- case 'leechseed':
161
- return {cull: !!counter.setupType || moves.has('explosion')};
162
- case 'stunspore':
163
- return {cull: moves.has('sunnyday') || moves.has('toxic')};
164
- case 'lightscreen':
165
- return {cull: !!counter.setupType || !!counter.get('speedsetup')};
166
- case 'meanlook': case 'spiderweb':
167
- return {cull: !!counter.get('speedsetup') || (!moves.has('batonpass') && !moves.has('perishsong'))};
168
- case 'morningsun':
169
- return {cull: counter.get('speedsetup') >= 1};
170
- case 'quickattack':
171
- return {cull: (
172
- !!counter.get('speedsetup') ||
173
- moves.has('substitute') ||
174
- (!types.has('Normal') && !!counter.get('Status'))
175
- )};
176
- case 'rapidspin':
177
- return {cull: !!counter.setupType || moves.has('rest') || !!teamDetails.rapidSpin};
178
- case 'reflect':
179
- return {cull: !!counter.setupType || !!counter.get('speedsetup')};
180
- case 'roar':
181
- return {cull: moves.has('sleeptalk') || moves.has('rest')};
182
- case 'seismictoss':
183
- return {cull: !!counter.setupType || moves.has('thunderbolt')};
184
- case 'spikes':
185
- return {cull: !!counter.setupType || moves.has('substitute') || restTalk || !!teamDetails.spikes};
186
- case 'substitute':
187
- const restOrDD = moves.has('rest') || (moves.has('dragondance') && !moves.has('bellydrum'));
188
- // This cull condition otherwise causes mono-solarbeam Entei
189
- return {cull: restOrDD || (species.id !== 'entei' && !moves.has('batonpass') && movePool.includes('calmmind'))};
190
- case 'thunderwave':
191
- return {cull: !!counter.setupType || moves.has('bodyslam') || moves.has('substitute') || restTalk};
192
- case 'toxic':
193
- return {cull: (
194
- !!counter.setupType ||
195
- !!counter.get('speedsetup') ||
196
- ['endure', 'focuspunch', 'raindance', 'yawn', 'hypnosis'].some(m => moves.has(m))
197
- )};
198
- case 'trick':
199
- return {cull: counter.get('Status') > 1};
200
- case 'willowisp':
201
- return {cull: !!counter.setupType || moves.has('hypnosis') || moves.has('toxic')};
202
-
203
- // Bit redundant to have both
204
- case 'bodyslam':
205
- return {cull: moves.has('return') && !!counter.get('Status')};
206
- case 'headbutt':
207
- return {cull: !moves.has('bodyslam') && !moves.has('thunderwave')};
208
- case 'return':
209
- return {cull: (
210
- moves.has('endure') ||
211
- (moves.has('substitute') && moves.has('flail')) ||
212
- (moves.has('bodyslam') && !counter.get('Status'))
213
- )};
214
- case 'fireblast':
215
- return {cull: moves.has('flamethrower') && !!counter.get('Status')};
216
- case 'flamethrower':
217
- return {cull: moves.has('fireblast') && !counter.get('Status')};
218
- case 'overheat':
219
- return {cull: moves.has('flamethrower') || moves.has('substitute')};
220
- case 'hydropump':
221
- return {cull: moves.has('surf') && !!counter.get('Status')};
222
- case 'surf':
223
- return {cull: moves.has('hydropump') && !counter.get('Status')};
224
- case 'gigadrain':
225
- return {cull: moves.has('morningsun') || moves.has('toxic')};
226
- case 'hiddenpower':
227
- const stabCondition = types.has(move.type) && counter.get(move.type) > 1 && (
228
- (moves.has('substitute') && !counter.setupType && !moves.has('toxic')) ||
229
- // This otherwise causes STABless meganium
230
- (species.id !== 'meganium' && moves.has('toxic') && !moves.has('substitute')) ||
231
- restTalk
232
- );
233
- return {cull: stabCondition || (move.type === 'Grass' && moves.has('sunnyday') && moves.has('solarbeam'))};
234
- case 'brickbreak': case 'crosschop': case 'skyuppercut':
235
- return {cull: moves.has('substitute') && (moves.has('focuspunch') || movePool.includes('focuspunch'))};
236
- case 'earthquake':
237
- return {cull: moves.has('bonemerang')};
238
- }
239
-
240
- return {cull: false};
241
- }
242
-
243
-
244
- getItem(
245
- ability: string,
246
- types: Set<string>,
247
- moves: Set<string>,
248
- counter: MoveCounter,
249
- species: Species
250
- ) {
251
- // First, the high-priority items
252
- if (species.name === 'Ditto') return this.sample(['Metal Powder', 'Quick Claw']);
253
- if (species.name === 'Farfetch\u2019d') return 'Stick';
254
- if (species.name === 'Marowak') return 'Thick Club';
255
- if (species.name === 'Pikachu') return 'Light Ball';
256
- if (species.name === 'Shedinja') return 'Lum Berry';
257
- if (species.name === 'Unown') return 'Twisted Spoon';
258
-
259
- if (moves.has('trick')) return 'Choice Band';
260
- if (moves.has('rest') && !moves.has('sleeptalk') && !['Early Bird', 'Natural Cure', 'Shed Skin'].includes(ability)) {
261
- return 'Chesto Berry';
262
- }
263
-
264
- // Medium priority items
265
- if (moves.has('dragondance') && ability !== 'Natural Cure') return 'Lum Berry';
266
- if ((moves.has('bellydrum') && counter.get('Physical') - counter.get('priority') > 1) || (
267
- ((moves.has('swordsdance') && counter.get('Status') < 2) || (moves.has('bulkup') && moves.has('substitute'))) &&
268
- !counter.get('priority') &&
269
- species.baseStats.spe >= 60 && species.baseStats.spe <= 95
270
- )) {
271
- return 'Salac Berry';
272
- }
273
- if (moves.has('endure') || (
274
- moves.has('substitute') &&
275
- ['bellydrum', 'endeavor', 'flail', 'reversal'].some(m => moves.has(m))
276
- )) {
277
- return (
278
- species.baseStats.spe <= 100 && ability !== 'Speed Boost' && !counter.get('speedsetup') && !moves.has('focuspunch')
279
- ) ? 'Salac Berry' : 'Liechi Berry';
280
- }
281
- if (moves.has('substitute') && counter.get('Physical') >= 3 && species.baseStats.spe >= 120) return 'Liechi Berry';
282
- if ((moves.has('substitute') || moves.has('raindance')) && counter.get('Special') >= 3) return 'Petaya Berry';
283
- if (counter.get('Physical') >= 4 && !moves.has('fakeout')) return 'Choice Band';
284
- if (counter.get('Physical') >= 3 && !moves.has('rapidspin') && (
285
- ['firepunch', 'icebeam', 'overheat'].some(m => moves.has(m)) ||
286
- Array.from(moves).some(m => {
287
- const moveData = this.dex.moves.get(m);
288
- return moveData.category === 'Special' && types.has(moveData.type);
289
- })
290
- )) {
291
- return 'Choice Band';
292
- }
293
-
294
- // Default to Leftovers
295
- return 'Leftovers';
296
- }
297
-
298
- shouldCullAbility(
299
- ability: string,
300
- types: Set<string>,
301
- moves: Set<string>,
302
- abilities: Set<string>,
303
- counter: MoveCounter,
304
- movePool: string[],
305
- teamDetails: RandomTeamsTypes.TeamDetails,
306
- species: Species,
307
- ) {
308
- switch (ability) {
309
- case 'Chlorophyll':
310
- return !moves.has('sunnyday') && !teamDetails['sun'];
311
- case 'Compound Eyes':
312
- return !counter.get('inaccurate');
313
- case 'Hustle':
314
- return counter.get('Physical') < 2;
315
- case 'Lightning Rod':
316
- return species.types.includes('Ground');
317
- case 'Overgrow':
318
- return !counter.get('Grass');
319
- case 'Rock Head':
320
- return !counter.get('recoil');
321
- case 'Sand Veil':
322
- return !teamDetails['sand'];
323
- case 'Serene Grace':
324
- return species.id === 'blissey';
325
- case 'Sturdy':
326
- // Sturdy is bad.
327
- return true;
328
- case 'Swift Swim':
329
- return !moves.has('raindance') && !teamDetails['rain'];
330
- case 'Swarm':
331
- return !counter.get('Bug');
332
- case 'Torrent':
333
- return !counter.get('Water');
334
- case 'Water Absorb':
335
- return abilities.has('Swift Swim');
336
- }
337
-
338
- return false;
339
- }
340
-
341
- randomSet(species: string | Species, teamDetails: RandomTeamsTypes.TeamDetails = {}): RandomTeamsTypes.RandomSet {
342
- species = this.dex.species.get(species);
343
- let forme = species.name;
344
-
345
- if (typeof species.battleOnly === 'string') forme = species.battleOnly;
346
-
347
- const movePool = (species.randomBattleMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
348
- const rejectedPool = [];
349
- const moves = new Set<string>();
350
- let ability = '';
351
- const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
352
- const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
353
- let availableHP = 0;
354
- for (const setMoveid of movePool) {
355
- if (setMoveid.startsWith('hiddenpower')) availableHP++;
356
- }
357
-
358
- const types = new Set(species.types);
359
-
360
- const abilities = new Set(Object.values(species.abilities));
361
-
362
- let counter: MoveCounter;
363
- // We use a special variable to track Hidden Power
364
- // so that we can check for all Hidden Powers at once
365
- let hasHiddenPower = false;
366
-
367
- do {
368
- // Choose next 4 moves from learnset/viable moves and add them to moves list:
369
- while (moves.size < this.maxMoveCount && movePool.length) {
370
- const moveid = this.sampleNoReplace(movePool);
371
- if (moveid.startsWith('hiddenpower')) {
372
- availableHP--;
373
- if (hasHiddenPower) continue;
374
- hasHiddenPower = true;
375
- }
376
- moves.add(moveid);
377
- }
378
-
379
- while (moves.size < this.maxMoveCount && rejectedPool.length) {
380
- const moveid = this.sampleNoReplace(rejectedPool);
381
- if (moveid.startsWith('hiddenpower')) {
382
- if (hasHiddenPower) continue;
383
- hasHiddenPower = true;
384
- }
385
- moves.add(moveid);
386
- }
387
-
388
- counter = this.queryMoves(moves, species.types, abilities, movePool);
389
-
390
- // Iterate through the moves again, this time to cull them:
391
- for (const moveid of moves) {
392
- const move = this.dex.moves.get(moveid);
393
-
394
- let {cull, isSetup} = this.shouldCullMove(move, types, moves, abilities, counter, movePool, teamDetails, species);
395
-
396
- // This move doesn't satisfy our setup requirements:
397
- if (
398
- (counter.setupType === 'Physical' && move.category === 'Special' && !types.has(move.type) && move.type !== 'Fire') ||
399
- (counter.setupType === 'Special' && move.category === 'Physical' && moveid !== 'superpower')
400
- ) {
401
- cull = true;
402
- }
403
- const moveIsRejectable = (
404
- !move.weather &&
405
- (move.category !== 'Status' || !move.flags.heal) &&
406
- (counter.setupType || !move.stallingMove) &&
407
- // These moves cannot be rejected in favor of a forced move
408
- !['batonpass', 'sleeptalk', 'solarbeam', 'substitute', 'sunnyday'].includes(moveid) &&
409
- (move.category === 'Status' || !types.has(move.type) || (move.basePower && move.basePower < 40 && !move.multihit))
410
- );
411
- // Pokemon should usually have at least one STAB move
412
- const requiresStab = (
413
- !counter.get('stab') &&
414
- !moves.has('seismictoss') && !moves.has('nightshade') &&
415
- species.id !== 'castform' &&
416
- // If a Flying-type has Psychic, it doesn't need STAB
417
- !(moves.has('psychic') && types.has('Flying')) &&
418
- !(types.has('Ghost') && species.baseStats.spa > species.baseStats.atk) &&
419
- !(
420
- // With Calm Mind, Lugia and pure Normal-types are fine without STAB
421
- counter.setupType === 'Special' && (
422
- species.id === 'lugia' ||
423
- (types.has('Normal') && species.types.length < 2)
424
- )
425
- ) &&
426
- !(
427
- // With Swords Dance, Dark-types and pure Water-types are fine without STAB
428
- counter.setupType === 'Physical' &&
429
- ((types.has('Water') && species.types.length < 2) || types.has('Dark'))
430
- ) &&
431
- counter.get('physicalpool') + counter.get('specialpool') > 0
432
- );
433
-
434
- const runEnforcementChecker = (checkerName: string) => {
435
- if (!this.moveEnforcementCheckers[checkerName]) return false;
436
- return this.moveEnforcementCheckers[checkerName](
437
- movePool, moves, abilities, types, counter, species as Species, teamDetails
438
- );
439
- };
440
-
441
- if (!cull && !isSetup && moveIsRejectable) {
442
- // There may be more important moves that this Pokemon needs
443
- if (
444
- requiresStab ||
445
- (counter.setupType && counter.get(counter.setupType) < 2) ||
446
- (moves.has('substitute') && movePool.includes('morningsun')) ||
447
- ['meteormash', 'spore', 'recover'].some(m => movePool.includes(m))
448
- ) {
449
- cull = true;
450
- } else {
451
- // Pokemon should have moves that benefit their typing and their other moves
452
- for (const type of types) {
453
- if (runEnforcementChecker(type)) {
454
- cull = true;
455
- }
456
- }
457
- for (const m of moves) {
458
- if (runEnforcementChecker(m)) cull = true;
459
- }
460
- }
461
- }
462
-
463
- // Sleep Talk shouldn't be selected without Rest
464
- if (moveid === 'rest' && cull) {
465
- const sleeptalk = movePool.indexOf('sleeptalk');
466
- if (sleeptalk >= 0) {
467
- if (movePool.length < 2) {
468
- cull = false;
469
- } else {
470
- this.fastPop(movePool, sleeptalk);
471
- }
472
- }
473
- }
474
-
475
- // Remove rejected moves from the move list
476
- const moveIsHP = moveid.startsWith('hiddenpower');
477
- if (
478
- cull &&
479
- (movePool.length - availableHP || availableHP && (moveIsHP || !hasHiddenPower))
480
- ) {
481
- if (move.category !== 'Status' && !move.damage && (!moveIsHP || !availableHP)) {
482
- rejectedPool.push(moveid);
483
- }
484
- if (moveIsHP) hasHiddenPower = false;
485
- moves.delete(moveid);
486
- break;
487
- }
488
- if (cull && rejectedPool.length) {
489
- if (moveIsHP) hasHiddenPower = false;
490
- moves.delete(moveid);
491
- break;
492
- }
493
- }
494
- } while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
495
-
496
- if (hasHiddenPower) {
497
- let hpType;
498
- for (const move of moves) {
499
- if (move.startsWith('hiddenpower')) hpType = move.substr(11);
500
- }
501
- if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
502
- const HPivs = this.dex.types.get(hpType).HPivs;
503
- let iv: StatID;
504
- for (iv in HPivs) {
505
- ivs[iv] = HPivs[iv]!;
506
- }
507
- }
508
-
509
- const abilityData = Array.from(abilities).map(a => this.dex.abilities.get(a)).filter(a => a.gen === 3);
510
- Utils.sortBy(abilityData, abil => -abil.rating);
511
- let ability0 = abilityData[0];
512
- let ability1 = abilityData[1];
513
- if (abilityData[1]) {
514
- if (ability0.rating <= ability1.rating && this.randomChance(1, 2)) {
515
- [ability0, ability1] = [ability1, ability0];
516
- } else if (ability0.rating - 0.6 <= ability1.rating && this.randomChance(2, 3)) {
517
- [ability0, ability1] = [ability1, ability0];
518
- }
519
- ability = ability0.name;
520
-
521
- while (this.shouldCullAbility(ability, types, moves, abilities, counter, movePool, teamDetails, species)) {
522
- if (ability === ability0.name && ability1.rating > 1) {
523
- ability = ability1.name;
524
- } else {
525
- // Default to the highest rated ability if all are rejected
526
- ability = abilityData[0].name;
527
- break;
528
- }
529
- }
530
- } else {
531
- ability = abilityData[0].name;
532
- }
533
-
534
- const item = this.getItem(ability, types, moves, counter, species);
535
- const levelScale: {[k: string]: number} = {
536
- Uber: 76,
537
- OU: 80,
538
- UUBL: 82,
539
- UU: 84,
540
- NUBL: 86,
541
- NU: 88,
542
- NFE: 90,
543
- };
544
- const customScale: {[k: string]: number} = {
545
- Ditto: 99, Unown: 99,
546
- };
547
- const tier = species.tier;
548
- const level = this.adjustLevel || customScale[species.name] || levelScale[tier] || (species.nfe ? 90 : 80);
549
-
550
- // Prepare optimal HP
551
- let hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
552
- if (moves.has('substitute') && ['endeavor', 'flail', 'reversal'].some(m => moves.has(m))) {
553
- // Endeavor/Flail/Reversal users should be able to use four Substitutes
554
- if (hp % 4 === 0) evs.hp -= 4;
555
- } else if (moves.has('substitute') && (item === 'Salac Berry' || item === 'Petaya Berry' || item === 'Liechi Berry')) {
556
- // Other pinch berry holders should have berries activate after three Substitutes
557
- while (hp % 4 > 0) {
558
- evs.hp -= 4;
559
- hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
560
- }
561
- }
562
-
563
- // Minimize confusion damage
564
- if (!counter.get('Physical') && !moves.has('transform')) {
565
- evs.atk = 0;
566
- ivs.atk = hasHiddenPower ? ivs.atk - 28 : 0;
567
- }
568
-
569
- return {
570
- name: species.baseSpecies,
571
- species: forme,
572
- gender: species.gender,
573
- moves: Array.from(moves),
574
- ability: ability,
575
- evs: evs,
576
- ivs: ivs,
577
- item: item,
578
- level,
579
- shiny: this.randomChance(1, 1024),
580
- };
581
- }
582
-
583
- randomTeam() {
584
- this.enforceNoDirectCustomBanlistChanges();
585
-
586
- const seed = this.prng.seed;
587
- const ruleTable = this.dex.formats.getRuleTable(this.format);
588
- const pokemon: RandomTeamsTypes.RandomSet[] = [];
589
-
590
- // For Monotype
591
- const isMonotype = !!this.forceMonotype || ruleTable.has('sametypeclause');
592
- const typePool = this.dex.types.names();
593
- const type = this.forceMonotype || this.sample(typePool);
594
-
595
- const baseFormes: {[k: string]: number} = {};
596
- const tierCount: {[k: string]: number} = {};
597
- const typeCount: {[k: string]: number} = {};
598
- const typeComboCount: {[k: string]: number} = {};
599
- const teamDetails: RandomTeamsTypes.TeamDetails = {};
600
-
601
- const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype);
602
-
603
- while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
604
- const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
605
- if (!species.exists || !species.randomBattleMoves) continue;
606
- // Limit to one of each species (Species Clause)
607
- if (baseFormes[species.baseSpecies]) continue;
608
-
609
- // Limit to one Wobbuffet per battle (not just per team)
610
- if (species.name === 'Wobbuffet' && this.battleHasWobbuffet) continue;
611
- // Limit to one Ditto per battle in Gen 2
612
- if (this.dex.gen < 3 && species.name === 'Ditto' && this.battleHasDitto) continue;
613
-
614
- const tier = species.tier;
615
- const types = species.types;
616
- const typeCombo = types.slice().sort().join();
617
-
618
- if (!isMonotype && !this.forceMonotype) {
619
- // Dynamically scale limits for different team sizes. The default and minimum value is 1.
620
- const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
621
-
622
- // Limit two Pokemon per tier
623
- if (tierCount[tier] >= 2 * limitFactor) continue;
624
-
625
- // Limit two of any type
626
- let skip = false;
627
- for (const typeName of types) {
628
- if (typeCount[typeName] >= 2 * limitFactor) {
629
- skip = true;
630
- break;
631
- }
632
- }
633
- if (skip) continue;
634
-
635
- // Limit one of any type combination
636
- if (!this.forceMonotype && typeComboCount[typeCombo] >= 1 * limitFactor) continue;
637
- }
638
-
639
- // Okay, the set passes, add it to our team
640
- const set = this.randomSet(species, teamDetails);
641
- pokemon.push(set);
642
-
643
- // Now that our Pokemon has passed all checks, we can increment our counters
644
- baseFormes[species.baseSpecies] = 1;
645
-
646
- // Increment tier counter
647
- if (tierCount[tier]) {
648
- tierCount[tier]++;
649
- } else {
650
- tierCount[tier] = 1;
651
- }
652
-
653
- // Increment type counters
654
- for (const typeName of types) {
655
- if (typeName in typeCount) {
656
- typeCount[typeName]++;
657
- } else {
658
- typeCount[typeName] = 1;
659
- }
660
- }
661
- if (typeCombo in typeComboCount) {
662
- typeComboCount[typeCombo]++;
663
- } else {
664
- typeComboCount[typeCombo] = 1;
665
- }
666
-
667
- // Updateeam details
668
- if (set.ability === 'Drizzle' || set.moves.includes('raindance')) teamDetails.rain = 1;
669
- if (set.ability === 'Sand Stream') teamDetails.sand = 1;
670
- if (set.moves.includes('spikes')) teamDetails.spikes = 1;
671
- if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1;
672
- if (set.moves.includes('aromatherapy') || set.moves.includes('healbell')) teamDetails.statusCure = 1;
673
-
674
- // In Gen 3, Shadow Tag users can prevent each other from switching out, possibly causing and endless battle or at least causing a long stall war
675
- // To prevent this, we prevent more than one Wobbuffet in a single battle.
676
- if (set.ability === 'Shadow Tag') this.battleHasWobbuffet = true;
677
- if (species.id === 'ditto') this.battleHasDitto = true;
678
- }
679
-
680
- if (pokemon.length < this.maxTeamSize && !isMonotype && !this.forceMonotype && pokemon.length < 12) {
681
- throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
682
- }
683
-
684
- return pokemon;
685
- }
686
- }
687
-
688
- export default RandomGen3Teams;