@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/build/index.d.ts +5 -3
- package/build/index.js +8036 -33
- package/build/index.js.map +1 -1
- package/build/index.mjs +8039 -0
- package/build/index.mjs.map +1 -0
- package/package.json +8 -9
- package/build/gen1.d.ts +0 -43
- package/build/gen1.js +0 -447
- package/build/gen1.js.map +0 -1
- package/build/gen2.d.ts +0 -13
- package/build/gen2.js +0 -289
- package/build/gen2.js.map +0 -1
- package/build/gen3.d.ts +0 -17
- package/build/gen3.js +0 -621
- package/build/gen3.js.map +0 -1
- package/build/gen4.d.ts +0 -16
- package/build/gen4.js +0 -724
- package/build/gen4.js.map +0 -1
- package/build/gen5.d.ts +0 -16
- package/build/gen5.js +0 -842
- package/build/gen5.js.map +0 -1
- package/build/gen6.d.ts +0 -23
- package/build/gen6.js +0 -1216
- package/build/gen6.js.map +0 -1
- package/build/gen7.d.ts +0 -43
- package/build/gen7.js +0 -2022
- package/build/gen7.js.map +0 -1
- package/build/gen8.d.ts +0 -154
- package/build/gen8.js +0 -2921
- package/build/gen8.js.map +0 -1
- package/build/utils.d.ts +0 -43
- package/build/utils.js +0 -70
- package/build/utils.js.map +0 -1
- package/src/gen1.ts +0 -493
- package/src/gen2.ts +0 -323
- package/src/gen3.ts +0 -688
- package/src/gen4.ts +0 -858
- package/src/gen5.ts +0 -916
- package/src/gen6.ts +0 -1357
- package/src/gen7.ts +0 -2194
- package/src/gen8.ts +0 -3122
- package/src/global.d.ts +0 -6
- package/src/index.ts +0 -46
- package/src/utils.ts +0 -78
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;
|