@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/gen8.ts
DELETED
|
@@ -1,3122 +0,0 @@
|
|
|
1
|
-
import {Utils} from './utils';
|
|
2
|
-
import {
|
|
3
|
-
Ability,
|
|
4
|
-
AnyObject,
|
|
5
|
-
BasicEffect,
|
|
6
|
-
Dex,
|
|
7
|
-
Format,
|
|
8
|
-
Item,
|
|
9
|
-
ModdedDex,
|
|
10
|
-
Move,
|
|
11
|
-
Nature,
|
|
12
|
-
PRNG,
|
|
13
|
-
PRNGSeed,
|
|
14
|
-
PlayerOptions,
|
|
15
|
-
PokemonSet,
|
|
16
|
-
RandomTeamsTypes,
|
|
17
|
-
Species,
|
|
18
|
-
StatID,
|
|
19
|
-
StatsTable,
|
|
20
|
-
Tags,
|
|
21
|
-
toID,
|
|
22
|
-
} from '@pkmn/sim';
|
|
23
|
-
|
|
24
|
-
export interface TeamData {
|
|
25
|
-
typeCount: {[k: string]: number};
|
|
26
|
-
typeComboCount: {[k: string]: number};
|
|
27
|
-
baseFormes: {[k: string]: number};
|
|
28
|
-
megaCount?: number;
|
|
29
|
-
zCount?: number;
|
|
30
|
-
has: {[k: string]: number};
|
|
31
|
-
forceResult: boolean;
|
|
32
|
-
weaknesses: {[k: string]: number};
|
|
33
|
-
resistances: {[k: string]: number};
|
|
34
|
-
weather?: string;
|
|
35
|
-
eeveeLimCount?: number;
|
|
36
|
-
gigantamax?: boolean;
|
|
37
|
-
}
|
|
38
|
-
export interface BattleFactorySpecies {
|
|
39
|
-
flags: {limEevee?: 1};
|
|
40
|
-
sets: BattleFactorySet[];
|
|
41
|
-
}
|
|
42
|
-
interface BattleFactorySet {
|
|
43
|
-
species: string;
|
|
44
|
-
item: string;
|
|
45
|
-
ability: string;
|
|
46
|
-
nature: string;
|
|
47
|
-
moves: string[];
|
|
48
|
-
evs?: Partial<StatsTable>;
|
|
49
|
-
ivs?: Partial<StatsTable>;
|
|
50
|
-
}
|
|
51
|
-
export class MoveCounter extends Utils.Multiset<string> {
|
|
52
|
-
damagingMoves: Set<Move>;
|
|
53
|
-
setupType: string;
|
|
54
|
-
|
|
55
|
-
constructor() {
|
|
56
|
-
super();
|
|
57
|
-
this.damagingMoves = new Set();
|
|
58
|
-
this.setupType = '';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get(key: string): number {
|
|
62
|
-
return super.get(key) || 0;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
type MoveEnforcementChecker = (
|
|
67
|
-
movePool: string[], moves: Set<string>, abilities: Set<string>, types: Set<string>,
|
|
68
|
-
counter: MoveCounter, species: Species, teamDetails: RandomTeamsTypes.TeamDetails
|
|
69
|
-
) => boolean;
|
|
70
|
-
|
|
71
|
-
// Moves that restore HP:
|
|
72
|
-
const RecoveryMove = [
|
|
73
|
-
'healorder', 'milkdrink', 'moonlight', 'morningsun', 'recover', 'roost', 'shoreup', 'slackoff', 'softboiled', 'strengthsap', 'synthesis',
|
|
74
|
-
];
|
|
75
|
-
// Moves that drop stats:
|
|
76
|
-
const ContraryMoves = [
|
|
77
|
-
'closecombat', 'leafstorm', 'overheat', 'superpower', 'vcreate',
|
|
78
|
-
];
|
|
79
|
-
// Moves that boost Attack:
|
|
80
|
-
const PhysicalSetup = [
|
|
81
|
-
'bellydrum', 'bulkup', 'coil', 'curse', 'dragondance', 'honeclaws', 'howl', 'meditate', 'poweruppunch', 'swordsdance',
|
|
82
|
-
];
|
|
83
|
-
// Moves which boost Special Attack:
|
|
84
|
-
const SpecialSetup = [
|
|
85
|
-
'calmmind', 'chargebeam', 'geomancy', 'nastyplot', 'quiverdance', 'tailglow',
|
|
86
|
-
];
|
|
87
|
-
// Moves that boost Attack AND Special Attack:
|
|
88
|
-
const MixedSetup = [
|
|
89
|
-
'clangoroussoul', 'growth', 'happyhour', 'holdhands', 'noretreat', 'shellsmash', 'workup',
|
|
90
|
-
];
|
|
91
|
-
// Some moves that only boost Speed:
|
|
92
|
-
const SpeedSetup = [
|
|
93
|
-
'agility', 'autotomize', 'flamecharge', 'rockpolish',
|
|
94
|
-
];
|
|
95
|
-
// Moves that shouldn't be the only STAB moves:
|
|
96
|
-
const NoStab = [
|
|
97
|
-
'accelerock', 'aquajet', 'beakblast', 'bounce', 'breakingswipe', 'chatter', 'clearsmog', 'dragontail', 'eruption', 'explosion',
|
|
98
|
-
'fakeout', 'firstimpression', 'flamecharge', 'flipturn', 'iceshard', 'icywind', 'incinerate', 'machpunch',
|
|
99
|
-
'meteorbeam', 'pluck', 'pursuit', 'quickattack', 'reversal', 'selfdestruct', 'skydrop', 'snarl', 'suckerpunch', 'uturn', 'watershuriken',
|
|
100
|
-
'vacuumwave', 'voltswitch', 'waterspout',
|
|
101
|
-
];
|
|
102
|
-
// Hazard-setting moves
|
|
103
|
-
const Hazards = [
|
|
104
|
-
'spikes', 'stealthrock', 'stickyweb', 'toxicspikes',
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
function sereneGraceBenefits(move: Move) {
|
|
108
|
-
return move.secondary?.chance && move.secondary.chance >= 20 && move.secondary.chance < 100;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export class RandomTeams {
|
|
112
|
-
dex: ModdedDex;
|
|
113
|
-
gen: number;
|
|
114
|
-
factoryTier: string;
|
|
115
|
-
format: Format;
|
|
116
|
-
prng: PRNG;
|
|
117
|
-
noStab: string[];
|
|
118
|
-
readonly maxTeamSize: number;
|
|
119
|
-
readonly adjustLevel: number | null;
|
|
120
|
-
readonly maxMoveCount: number;
|
|
121
|
-
readonly forceMonotype: string | undefined;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Checkers for move enforcement based on a Pokémon's types or other factors
|
|
125
|
-
*
|
|
126
|
-
* returns true to reject one of its other moves to try to roll the forced move, false otherwise.
|
|
127
|
-
*/
|
|
128
|
-
moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker};
|
|
129
|
-
|
|
130
|
-
constructor(dex: ModdedDex, format: Format, prng: PRNG | PRNGSeed | null) {
|
|
131
|
-
this.dex = dex;
|
|
132
|
-
this.gen = this.dex.gen;
|
|
133
|
-
this.noStab = NoStab;
|
|
134
|
-
|
|
135
|
-
const ruleTable = this.dex.formats.getRuleTable(format);
|
|
136
|
-
this.maxTeamSize = ruleTable.maxTeamSize;
|
|
137
|
-
this.adjustLevel = ruleTable.adjustLevel;
|
|
138
|
-
this.maxMoveCount = ruleTable.maxMoveCount;
|
|
139
|
-
const forceMonotype = ruleTable.valueRules.get('forcemonotype');
|
|
140
|
-
this.forceMonotype = forceMonotype && this.dex.types.get(forceMonotype).exists ?
|
|
141
|
-
this.dex.types.get(forceMonotype).name : undefined;
|
|
142
|
-
|
|
143
|
-
this.factoryTier = '';
|
|
144
|
-
this.format = format;
|
|
145
|
-
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
|
|
146
|
-
|
|
147
|
-
this.moveEnforcementCheckers = {
|
|
148
|
-
screens: (movePool, moves, abilities, types, counter, species, teamDetails) => {
|
|
149
|
-
if (teamDetails.screens) return false;
|
|
150
|
-
return (
|
|
151
|
-
(moves.has('lightscreen') && movePool.includes('reflect')) ||
|
|
152
|
-
(moves.has('reflect') && movePool.includes('lightscreen'))
|
|
153
|
-
);
|
|
154
|
-
},
|
|
155
|
-
recovery: (movePool, moves, abilities, types, counter, species, teamDetails) => (
|
|
156
|
-
!!counter.get('Status') &&
|
|
157
|
-
!counter.setupType &&
|
|
158
|
-
['morningsun', 'recover', 'roost', 'slackoff', 'softboiled'].some(moveid => movePool.includes(moveid)) &&
|
|
159
|
-
['healingwish', 'switcheroo', 'trick', 'trickroom'].every(moveid => !moves.has(moveid))
|
|
160
|
-
),
|
|
161
|
-
misc: (movePool, moves, abilities, types, counter, species, teamDetails) => {
|
|
162
|
-
if (movePool.includes('milkdrink') || movePool.includes('quiverdance')) return true;
|
|
163
|
-
return movePool.includes('stickyweb') && !counter.setupType && !teamDetails.stickyWeb;
|
|
164
|
-
},
|
|
165
|
-
lead: (movePool, moves, abilities, types, counter) => (
|
|
166
|
-
movePool.includes('stealthrock') &&
|
|
167
|
-
!!counter.get('Status') &&
|
|
168
|
-
!counter.setupType &&
|
|
169
|
-
!counter.get('speedsetup') &&
|
|
170
|
-
!moves.has('substitute')
|
|
171
|
-
),
|
|
172
|
-
leechseed: (movePool, moves) => (
|
|
173
|
-
!moves.has('calmmind') &&
|
|
174
|
-
['protect', 'substitute', 'spikyshield'].some(m => movePool.includes(m))
|
|
175
|
-
),
|
|
176
|
-
Bug: (movePool) => movePool.includes('megahorn'),
|
|
177
|
-
Dark: (movePool, moves, abilities, types, counter) => {
|
|
178
|
-
if (!counter.get('Dark')) return true;
|
|
179
|
-
return moves.has('suckerpunch') && (movePool.includes('knockoff') || movePool.includes('wickedblow'));
|
|
180
|
-
},
|
|
181
|
-
Dragon: (movePool, moves, abilities, types, counter) => (
|
|
182
|
-
!counter.get('Dragon') &&
|
|
183
|
-
!moves.has('dragonascent') &&
|
|
184
|
-
!moves.has('substitute') &&
|
|
185
|
-
!(moves.has('rest') && moves.has('sleeptalk'))
|
|
186
|
-
),
|
|
187
|
-
Electric: (movePool, moves, abilities, types, counter) => !counter.get('Electric') || movePool.includes('thunder'),
|
|
188
|
-
Fairy: (movePool, moves, abilities, types, counter) => (
|
|
189
|
-
!counter.get('Fairy') &&
|
|
190
|
-
['dazzlinggleam', 'moonblast', 'fleurcannon', 'playrough', 'strangesteam'].some(moveid => movePool.includes(moveid))
|
|
191
|
-
),
|
|
192
|
-
Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting') || !counter.get('stab'),
|
|
193
|
-
Fire: (movePool, moves, abilities, types, counter, species) => {
|
|
194
|
-
// Entei should never reject Extreme Speed even if Flare Blitz could be rolled instead
|
|
195
|
-
const enteiException = moves.has('extremespeed') && species.id === 'entei';
|
|
196
|
-
return !moves.has('bellydrum') && (!counter.get('Fire') || (!enteiException && movePool.includes('flareblitz')));
|
|
197
|
-
},
|
|
198
|
-
Flying: (movePool, moves, abilities, types, counter) => (
|
|
199
|
-
!counter.get('Flying') && !types.has('Dragon') && [
|
|
200
|
-
'airslash', 'bravebird', 'dualwingbeat', 'oblivionwing',
|
|
201
|
-
].some(moveid => movePool.includes(moveid))
|
|
202
|
-
),
|
|
203
|
-
Ghost: (movePool, moves, abilities, types, counter) => {
|
|
204
|
-
if (moves.has('nightshade')) return false;
|
|
205
|
-
if (!counter.get('Ghost') && !types.has('Dark')) return true;
|
|
206
|
-
if (movePool.includes('poltergeist')) return true;
|
|
207
|
-
return movePool.includes('spectralthief') && !counter.get('Dark');
|
|
208
|
-
},
|
|
209
|
-
Grass: (movePool, moves, abilities, types, counter, species) => {
|
|
210
|
-
if (movePool.includes('leafstorm') || movePool.includes('grassyglide')) return true;
|
|
211
|
-
return !counter.get('Grass') && species.baseStats.atk >= 100;
|
|
212
|
-
},
|
|
213
|
-
Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'),
|
|
214
|
-
Ice: (movePool, moves, abilities, types, counter) => {
|
|
215
|
-
if (!counter.get('Ice')) return true;
|
|
216
|
-
if (movePool.includes('iciclecrash')) return true;
|
|
217
|
-
return abilities.has('Snow Warning') && movePool.includes('blizzard');
|
|
218
|
-
},
|
|
219
|
-
Normal: (movePool, moves, abilities, types, counter) => (
|
|
220
|
-
(abilities.has('Guts') && movePool.includes('facade')) || (abilities.has('Pixilate') && !counter.get('Normal'))
|
|
221
|
-
),
|
|
222
|
-
Poison: (movePool, moves, abilities, types, counter) => {
|
|
223
|
-
if (counter.get('Poison')) return false;
|
|
224
|
-
return types.has('Ground') || types.has('Psychic') || types.has('Grass') || !!counter.setupType || movePool.includes('gunkshot');
|
|
225
|
-
},
|
|
226
|
-
Psychic: (movePool, moves, abilities, types, counter) => {
|
|
227
|
-
if (counter.get('Psychic')) return false;
|
|
228
|
-
if (types.has('Ghost') || types.has('Steel')) return false;
|
|
229
|
-
return abilities.has('Psychic Surge') || !!counter.setupType || movePool.includes('psychicfangs');
|
|
230
|
-
},
|
|
231
|
-
Rock: (movePool, moves, abilities, types, counter, species) => !counter.get('Rock') && species.baseStats.atk >= 80,
|
|
232
|
-
Steel: (movePool, moves, abilities, types, counter, species) => {
|
|
233
|
-
if (species.baseStats.atk < 95) return false;
|
|
234
|
-
if (movePool.includes('meteormash')) return true;
|
|
235
|
-
return !counter.get('Steel');
|
|
236
|
-
},
|
|
237
|
-
Water: (movePool, moves, abilities, types, counter, species) => {
|
|
238
|
-
if (!counter.get('Water') && !moves.has('hypervoice')) return true;
|
|
239
|
-
if (['hypervoice', 'liquidation', 'surgingstrikes'].some(m => movePool.includes(m))) return true;
|
|
240
|
-
return abilities.has('Huge Power') && movePool.includes('aquajet');
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
setSeed(prng?: PRNG | PRNGSeed) {
|
|
246
|
-
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
getTeam(options?: PlayerOptions | null): PokemonSet[] {
|
|
250
|
-
const generatorName = (
|
|
251
|
-
typeof this.format.team === 'string' && this.format.team.startsWith('random')
|
|
252
|
-
) ? this.format.team + 'Team' : '';
|
|
253
|
-
// @ts-ignore
|
|
254
|
-
return this[generatorName || 'randomTeam'](options);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
randomChance(numerator: number, denominator: number) {
|
|
258
|
-
return this.prng.randomChance(numerator, denominator);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
sample<T>(items: readonly T[]): T {
|
|
262
|
-
return this.prng.sample(items);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
sampleIfArray<T>(item: T | T[]): T {
|
|
266
|
-
if (Array.isArray(item)) {
|
|
267
|
-
return this.sample(item);
|
|
268
|
-
}
|
|
269
|
-
return item;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
random(m?: number, n?: number) {
|
|
273
|
-
return this.prng.next(m, n);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Remove an element from an unsorted array significantly faster
|
|
278
|
-
* than .splice
|
|
279
|
-
*/
|
|
280
|
-
fastPop(list: any[], index: number) {
|
|
281
|
-
// If an array doesn't need to be in order, replacing the
|
|
282
|
-
// element at the given index with the removed element
|
|
283
|
-
// is much, much faster than using list.splice(index, 1).
|
|
284
|
-
const length = list.length;
|
|
285
|
-
if (index < 0 || index >= list.length) {
|
|
286
|
-
// sanity check
|
|
287
|
-
throw new Error(`Index ${index} out of bounds for given array`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const element = list[index];
|
|
291
|
-
list[index] = list[length - 1];
|
|
292
|
-
list.pop();
|
|
293
|
-
return element;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Remove a random element from an unsorted array and return it.
|
|
298
|
-
* Uses the battle's RNG if in a battle.
|
|
299
|
-
*/
|
|
300
|
-
sampleNoReplace(list: any[]) {
|
|
301
|
-
const length = list.length;
|
|
302
|
-
if (length === 0) return null;
|
|
303
|
-
const index = this.random(length);
|
|
304
|
-
return this.fastPop(list, index);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Removes n random elements from an unsorted array and returns them.
|
|
309
|
-
* If n is less than the array's length, randomly removes and returns all the elements
|
|
310
|
-
* in the array (so the returned array could have length < n).
|
|
311
|
-
*/
|
|
312
|
-
multipleSamplesNoReplace<T>(list: T[], n: number): T[] {
|
|
313
|
-
const samples = [];
|
|
314
|
-
while (samples.length < n && list.length) {
|
|
315
|
-
samples.push(this.sampleNoReplace(list));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return samples;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Check if user has directly tried to ban/unban/restrict things in a custom battle.
|
|
323
|
-
* Doesn't count bans nested inside other formats/rules.
|
|
324
|
-
*/
|
|
325
|
-
private hasDirectCustomBanlistChanges() {
|
|
326
|
-
if (!this.format.customRules) return false;
|
|
327
|
-
for (const rule of this.format.customRules) {
|
|
328
|
-
for (const banlistOperator of ['-', '+', '*']) {
|
|
329
|
-
if (rule.startsWith(banlistOperator)) return true;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Inform user when custom bans are unsupported in a team generator.
|
|
337
|
-
*/
|
|
338
|
-
protected enforceNoDirectCustomBanlistChanges() {
|
|
339
|
-
if (this.hasDirectCustomBanlistChanges()) {
|
|
340
|
-
throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Inform user when complex bans are unsupported in a team generator.
|
|
346
|
-
*/
|
|
347
|
-
protected enforceNoDirectComplexBans() {
|
|
348
|
-
if (!this.format.customRules) return false;
|
|
349
|
-
for (const rule of this.format.customRules) {
|
|
350
|
-
if (rule.includes('+') && !rule.startsWith('+')) {
|
|
351
|
-
throw new Error(`Complex bans are not currently supported in ${this.format.name}.`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Validate set element pool size is sufficient to support size requirements after simple bans.
|
|
358
|
-
*/
|
|
359
|
-
private enforceCustomPoolSizeNoComplexBans(
|
|
360
|
-
effectTypeName: string,
|
|
361
|
-
basicEffectPool: BasicEffect[],
|
|
362
|
-
requiredCount: number,
|
|
363
|
-
requiredCountExplanation: string
|
|
364
|
-
) {
|
|
365
|
-
if (basicEffectPool.length >= requiredCount) return;
|
|
366
|
-
throw new Error(`Legal ${effectTypeName} count is insufficient to support ${requiredCountExplanation} (${basicEffectPool.length} / ${requiredCount}).`);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
unrejectableMovesInSingles(move: Move) {
|
|
370
|
-
// These moves cannot be rejected in favor of a forced move in singles
|
|
371
|
-
return (move.category !== 'Status' || !move.flags.heal) && ![
|
|
372
|
-
'facade', 'leechseed', 'lightscreen', 'reflect', 'sleeptalk', 'spore', 'substitute', 'switcheroo',
|
|
373
|
-
'teleport', 'toxic', 'trick',
|
|
374
|
-
].includes(move.id);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
unrejectableMovesInDoubles(move: Move) {
|
|
378
|
-
// These moves cannot be rejected in favor of a forced move in doubles
|
|
379
|
-
return move.id !== 'bodypress';
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
randomCCTeam(): RandomTeamsTypes.RandomSet[] {
|
|
383
|
-
this.enforceNoDirectCustomBanlistChanges();
|
|
384
|
-
|
|
385
|
-
const dex = this.dex;
|
|
386
|
-
const team = [];
|
|
387
|
-
|
|
388
|
-
const natures = this.dex.natures.all();
|
|
389
|
-
const items = this.dex.items.all();
|
|
390
|
-
|
|
391
|
-
const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype);
|
|
392
|
-
|
|
393
|
-
for (let forme of randomN) {
|
|
394
|
-
let species = dex.species.get(forme);
|
|
395
|
-
if (species.isNonstandard) species = dex.species.get(species.baseSpecies);
|
|
396
|
-
|
|
397
|
-
// Random legal item
|
|
398
|
-
let item = '';
|
|
399
|
-
if (this.gen >= 2) {
|
|
400
|
-
do {
|
|
401
|
-
item = this.sample(items).name;
|
|
402
|
-
} while (this.dex.items.get(item).gen > this.gen || this.dex.items.get(item).isNonstandard);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Make sure forme is legal
|
|
406
|
-
if (species.battleOnly) {
|
|
407
|
-
if (typeof species.battleOnly === 'string') {
|
|
408
|
-
species = dex.species.get(species.battleOnly);
|
|
409
|
-
} else {
|
|
410
|
-
species = dex.species.get(this.sample(species.battleOnly));
|
|
411
|
-
}
|
|
412
|
-
forme = species.name;
|
|
413
|
-
} else if (species.requiredItems && !species.requiredItems.some(req => toID(req) === item)) {
|
|
414
|
-
if (!species.changesFrom) throw new Error(`${species.name} needs a changesFrom value`);
|
|
415
|
-
species = dex.species.get(species.changesFrom);
|
|
416
|
-
forme = species.name;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Make sure that a base forme does not hold any forme-modifier items.
|
|
420
|
-
let itemData = this.dex.items.get(item);
|
|
421
|
-
if (itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies) {
|
|
422
|
-
do {
|
|
423
|
-
itemData = this.sample(items);
|
|
424
|
-
item = itemData.name;
|
|
425
|
-
} while (
|
|
426
|
-
itemData.gen > this.gen ||
|
|
427
|
-
itemData.isNonstandard ||
|
|
428
|
-
(itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies)
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Random legal ability
|
|
433
|
-
const abilities = Object.values(species.abilities).filter(a => this.dex.abilities.get(a).gen <= this.gen);
|
|
434
|
-
const ability: string = this.gen <= 2 ? 'No Ability' : this.sample(abilities);
|
|
435
|
-
|
|
436
|
-
// Four random unique moves from the movepool
|
|
437
|
-
let pool = ['struggle'];
|
|
438
|
-
if (forme === 'Smeargle') {
|
|
439
|
-
pool = this.dex.moves
|
|
440
|
-
.all()
|
|
441
|
-
.filter(move => !(move.isNonstandard || move.isZ || move.isMax || move.realMove))
|
|
442
|
-
.map(m => m.id);
|
|
443
|
-
} else {
|
|
444
|
-
const formes = ['gastrodoneast', 'pumpkaboosuper', 'zygarde10'];
|
|
445
|
-
let learnset = this.dex.species.getLearnset(species.id);
|
|
446
|
-
if (formes.includes(species.id) || !learnset) {
|
|
447
|
-
learnset = this.dex.species.getLearnset(this.dex.species.get(species.baseSpecies).id);
|
|
448
|
-
}
|
|
449
|
-
if (learnset) {
|
|
450
|
-
pool = Object.keys(learnset).filter(
|
|
451
|
-
moveid => learnset![moveid].find(learned => learned.startsWith(String(this.gen)))
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
if (species.changesFrom) {
|
|
455
|
-
learnset = this.dex.species.getLearnset(toID(species.changesFrom));
|
|
456
|
-
const basePool = Object.keys(learnset!).filter(
|
|
457
|
-
moveid => learnset![moveid].find(learned => learned.startsWith(String(this.gen)))
|
|
458
|
-
);
|
|
459
|
-
pool = [...new Set(pool.concat(basePool))];
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const moves = this.multipleSamplesNoReplace(pool, this.maxMoveCount);
|
|
464
|
-
|
|
465
|
-
// Random EVs
|
|
466
|
-
const evs: StatsTable = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
|
467
|
-
const s: StatID[] = ["hp", "atk", "def", "spa", "spd", "spe"];
|
|
468
|
-
let evpool = 510;
|
|
469
|
-
do {
|
|
470
|
-
const x = this.sample(s);
|
|
471
|
-
const y = this.random(Math.min(256 - evs[x], evpool + 1));
|
|
472
|
-
evs[x] += y;
|
|
473
|
-
evpool -= y;
|
|
474
|
-
} while (evpool > 0);
|
|
475
|
-
|
|
476
|
-
// Random IVs
|
|
477
|
-
const ivs = {
|
|
478
|
-
hp: this.random(32),
|
|
479
|
-
atk: this.random(32),
|
|
480
|
-
def: this.random(32),
|
|
481
|
-
spa: this.random(32),
|
|
482
|
-
spd: this.random(32),
|
|
483
|
-
spe: this.random(32),
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
// Random nature
|
|
487
|
-
const nature = this.sample(natures).name;
|
|
488
|
-
|
|
489
|
-
// Level balance--calculate directly from stats rather than using some silly lookup table
|
|
490
|
-
const mbstmin = 1307; // Sunkern has the lowest modified base stat total, and that total is 807
|
|
491
|
-
|
|
492
|
-
let stats = species.baseStats;
|
|
493
|
-
// If Wishiwashi, use the school-forme's much higher stats
|
|
494
|
-
if (species.baseSpecies === 'Wishiwashi') stats = this.dex.species.get('wishiwashischool').baseStats;
|
|
495
|
-
|
|
496
|
-
// Modified base stat total assumes 31 IVs, 85 EVs in every stat
|
|
497
|
-
let mbst = (stats["hp"] * 2 + 31 + 21 + 100) + 10;
|
|
498
|
-
mbst += (stats["atk"] * 2 + 31 + 21 + 100) + 5;
|
|
499
|
-
mbst += (stats["def"] * 2 + 31 + 21 + 100) + 5;
|
|
500
|
-
mbst += (stats["spa"] * 2 + 31 + 21 + 100) + 5;
|
|
501
|
-
mbst += (stats["spd"] * 2 + 31 + 21 + 100) + 5;
|
|
502
|
-
mbst += (stats["spe"] * 2 + 31 + 21 + 100) + 5;
|
|
503
|
-
|
|
504
|
-
let level;
|
|
505
|
-
if (this.adjustLevel) {
|
|
506
|
-
level = this.adjustLevel;
|
|
507
|
-
} else {
|
|
508
|
-
level = Math.floor(100 * mbstmin / mbst); // Initial level guess will underestimate
|
|
509
|
-
|
|
510
|
-
while (level < 100) {
|
|
511
|
-
mbst = Math.floor((stats["hp"] * 2 + 31 + 21 + 100) * level / 100 + 10);
|
|
512
|
-
// Since damage is roughly proportional to level
|
|
513
|
-
mbst += Math.floor(((stats["atk"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
|
|
514
|
-
mbst += Math.floor((stats["def"] * 2 + 31 + 21 + 100) * level / 100 + 5);
|
|
515
|
-
mbst += Math.floor(((stats["spa"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
|
|
516
|
-
mbst += Math.floor((stats["spd"] * 2 + 31 + 21 + 100) * level / 100 + 5);
|
|
517
|
-
mbst += Math.floor((stats["spe"] * 2 + 31 + 21 + 100) * level / 100 + 5);
|
|
518
|
-
|
|
519
|
-
if (mbst >= mbstmin) break;
|
|
520
|
-
level++;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Random happiness
|
|
525
|
-
const happiness = this.random(256);
|
|
526
|
-
|
|
527
|
-
// Random shininess
|
|
528
|
-
const shiny = this.randomChance(1, 1024);
|
|
529
|
-
|
|
530
|
-
team.push({
|
|
531
|
-
name: species.baseSpecies,
|
|
532
|
-
species: species.name,
|
|
533
|
-
gender: species.gender,
|
|
534
|
-
item,
|
|
535
|
-
ability,
|
|
536
|
-
moves,
|
|
537
|
-
evs,
|
|
538
|
-
ivs,
|
|
539
|
-
nature,
|
|
540
|
-
level,
|
|
541
|
-
happiness,
|
|
542
|
-
shiny,
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
return team;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: Dex.RuleTable) {
|
|
550
|
-
// Picks `n` random pokemon--no repeats, even among formes
|
|
551
|
-
// Also need to either normalize for formes or select formes at random
|
|
552
|
-
// Unreleased are okay but no CAP
|
|
553
|
-
const last = [0, 151, 251, 386, 493, 649, 721, 807, 890][this.gen];
|
|
554
|
-
|
|
555
|
-
if (n <= 0 || n > last) throw new Error(`n must be a number between 1 and ${last} (got ${n})`);
|
|
556
|
-
if (requiredType && !this.dex.types.get(requiredType).exists) {
|
|
557
|
-
throw new Error(`"${requiredType}" is not a valid type.`);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const isNotCustom = !ruleTable;
|
|
561
|
-
|
|
562
|
-
const pool: number[] = [];
|
|
563
|
-
let speciesPool: Species[] = [];
|
|
564
|
-
if (isNotCustom) {
|
|
565
|
-
speciesPool = [...this.dex.species.all()];
|
|
566
|
-
for (const species of speciesPool) {
|
|
567
|
-
if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
|
|
568
|
-
if (requiredType && !species.types.includes(requiredType)) continue;
|
|
569
|
-
if (minSourceGen && species.gen < minSourceGen) continue;
|
|
570
|
-
const num = species.num;
|
|
571
|
-
if (num <= 0 || pool.includes(num)) continue;
|
|
572
|
-
if (num > last) break;
|
|
573
|
-
pool.push(num);
|
|
574
|
-
}
|
|
575
|
-
} else {
|
|
576
|
-
const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
|
|
577
|
-
const nonexistentBanReason = ruleTable.check('nonexistent');
|
|
578
|
-
// Assume tierSpecies does not differ from species here (mega formes can be used without their stone, etc)
|
|
579
|
-
for (const species of this.dex.species.all()) {
|
|
580
|
-
if (requiredType && !species.types.includes(requiredType)) continue;
|
|
581
|
-
|
|
582
|
-
let banReason = ruleTable.check('pokemon:' + species.id);
|
|
583
|
-
if (banReason) continue;
|
|
584
|
-
if (banReason !== '') {
|
|
585
|
-
if (species.isMega && ruleTable.check('pokemontag:mega')) continue;
|
|
586
|
-
|
|
587
|
-
banReason = ruleTable.check('basepokemon:' + toID(species.baseSpecies));
|
|
588
|
-
if (banReason) continue;
|
|
589
|
-
if (banReason !== '' || this.dex.species.get(species.baseSpecies).isNonstandard !== species.isNonstandard) {
|
|
590
|
-
const nonexistentCheck = Tags.nonexistent.genericFilter!(species) && nonexistentBanReason;
|
|
591
|
-
let tagWhitelisted = false;
|
|
592
|
-
let tagBlacklisted = false;
|
|
593
|
-
for (const ruleid of ruleTable.tagRules) {
|
|
594
|
-
if (ruleid.startsWith('*')) continue;
|
|
595
|
-
const tagid = ruleid.slice(12);
|
|
596
|
-
const tag = Tags[tagid];
|
|
597
|
-
if ((tag.speciesFilter || tag.genericFilter)!(species)) {
|
|
598
|
-
const existenceTag = EXISTENCE_TAG.includes(tagid);
|
|
599
|
-
if (ruleid.startsWith('+')) {
|
|
600
|
-
if (!existenceTag && nonexistentCheck) continue;
|
|
601
|
-
tagWhitelisted = true;
|
|
602
|
-
break;
|
|
603
|
-
}
|
|
604
|
-
tagBlacklisted = true;
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
if (tagBlacklisted) continue;
|
|
609
|
-
if (!tagWhitelisted) {
|
|
610
|
-
if (ruleTable.check('pokemontag:allpokemon')) continue;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
speciesPool.push(species);
|
|
615
|
-
const num = species.num;
|
|
616
|
-
if (pool.includes(num)) continue;
|
|
617
|
-
pool.push(num);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const hasDexNumber: {[k: string]: number} = {};
|
|
622
|
-
for (let i = 0; i < n; i++) {
|
|
623
|
-
const num = this.sampleNoReplace(pool);
|
|
624
|
-
hasDexNumber[num] = i;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const formes: string[][] = [];
|
|
628
|
-
for (const species of speciesPool) {
|
|
629
|
-
if (!(species.num in hasDexNumber)) continue;
|
|
630
|
-
if (isNotCustom && (species.gen > this.gen ||
|
|
631
|
-
(species.isNonstandard && species.isNonstandard !== 'Unobtainable'))) continue;
|
|
632
|
-
if (!formes[hasDexNumber[species.num]]) formes[hasDexNumber[species.num]] = [];
|
|
633
|
-
formes[hasDexNumber[species.num]].push(species.name);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (formes.length < n) {
|
|
637
|
-
throw new Error(`Legal Pokemon forme count insufficient to support Max Team Size: (${formes.length} / ${n}).`);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const nPokemon = [];
|
|
641
|
-
for (let i = 0; i < n; i++) {
|
|
642
|
-
if (!formes[i].length) {
|
|
643
|
-
throw new Error(`Invalid pokemon gen ${this.gen}: ${JSON.stringify(formes)} numbers ${JSON.stringify(hasDexNumber)}`);
|
|
644
|
-
}
|
|
645
|
-
nPokemon.push(this.sample(formes[i]));
|
|
646
|
-
}
|
|
647
|
-
return nPokemon;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
randomHCTeam(): PokemonSet[] {
|
|
651
|
-
const hasCustomBans = this.hasDirectCustomBanlistChanges();
|
|
652
|
-
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
653
|
-
const hasNonexistentBan = hasCustomBans && ruleTable.check('nonexistent');
|
|
654
|
-
const hasNonexistentWhitelist = hasCustomBans && (hasNonexistentBan === '');
|
|
655
|
-
|
|
656
|
-
if (hasCustomBans) {
|
|
657
|
-
this.enforceNoDirectComplexBans();
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Item Pool
|
|
661
|
-
const doItemsExist = this.gen > 1;
|
|
662
|
-
let itemPool: Item[] = [];
|
|
663
|
-
if (doItemsExist) {
|
|
664
|
-
if (!hasCustomBans) {
|
|
665
|
-
itemPool = [...this.dex.items.all()].filter(item => (item.gen <= this.gen && !item.isNonstandard));
|
|
666
|
-
} else {
|
|
667
|
-
const hasAllItemsBan = ruleTable.check('pokemontag:allitems');
|
|
668
|
-
for (const item of this.dex.items.all()) {
|
|
669
|
-
let banReason = ruleTable.check('item:' + item.id);
|
|
670
|
-
if (banReason) continue;
|
|
671
|
-
if (banReason !== '' && item.id) {
|
|
672
|
-
if (hasAllItemsBan) continue;
|
|
673
|
-
if (item.isNonstandard) {
|
|
674
|
-
banReason = ruleTable.check('pokemontag:' + toID(item.isNonstandard));
|
|
675
|
-
if (banReason) continue;
|
|
676
|
-
if (banReason !== '' && item.isNonstandard !== 'Unobtainable') {
|
|
677
|
-
if (hasNonexistentBan) continue;
|
|
678
|
-
if (!hasNonexistentWhitelist) continue;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
itemPool.push(item);
|
|
683
|
-
}
|
|
684
|
-
if (ruleTable.check('item:noitem')) {
|
|
685
|
-
this.enforceCustomPoolSizeNoComplexBans('item', itemPool, this.maxTeamSize, 'Max Team Size');
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Ability Pool
|
|
691
|
-
const doAbilitiesExist = (this.gen > 2) && (this.dex.currentMod !== 'gen7letsgo');
|
|
692
|
-
let abilityPool: Ability[] = [];
|
|
693
|
-
if (doAbilitiesExist) {
|
|
694
|
-
if (!hasCustomBans) {
|
|
695
|
-
abilityPool = [...this.dex.abilities.all()].filter(ability => (ability.gen <= this.gen && !ability.isNonstandard));
|
|
696
|
-
} else {
|
|
697
|
-
const hasAllAbilitiesBan = ruleTable.check('pokemontag:allabilities');
|
|
698
|
-
for (const ability of this.dex.abilities.all()) {
|
|
699
|
-
let banReason = ruleTable.check('ability:' + ability.id);
|
|
700
|
-
if (banReason) continue;
|
|
701
|
-
if (banReason !== '') {
|
|
702
|
-
if (hasAllAbilitiesBan) continue;
|
|
703
|
-
if (ability.isNonstandard) {
|
|
704
|
-
banReason = ruleTable.check('pokemontag:' + toID(ability.isNonstandard));
|
|
705
|
-
if (banReason) continue;
|
|
706
|
-
if (banReason !== '') {
|
|
707
|
-
if (hasNonexistentBan) continue;
|
|
708
|
-
if (!hasNonexistentWhitelist) continue;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
abilityPool.push(ability);
|
|
713
|
-
}
|
|
714
|
-
if (ruleTable.check('ability:noability')) {
|
|
715
|
-
this.enforceCustomPoolSizeNoComplexBans('ability', abilityPool, this.maxTeamSize, 'Max Team Size');
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Move Pool
|
|
721
|
-
const setMoveCount = ruleTable.maxMoveCount;
|
|
722
|
-
let movePool: Move[] = [];
|
|
723
|
-
if (!hasCustomBans) {
|
|
724
|
-
movePool = [...this.dex.moves.all()].filter(move =>
|
|
725
|
-
(move.gen <= this.gen && !move.isNonstandard && !move.name.startsWith('Hidden Power ')));
|
|
726
|
-
} else {
|
|
727
|
-
const hasAllMovesBan = ruleTable.check('pokemontag:allmoves');
|
|
728
|
-
for (const move of this.dex.moves.all()) {
|
|
729
|
-
// Legality of specific HP types can't be altered in built formats anyway
|
|
730
|
-
if (move.name.startsWith('Hidden Power ')) continue;
|
|
731
|
-
let banReason = ruleTable.check('move:' + move.id);
|
|
732
|
-
if (banReason) continue;
|
|
733
|
-
if (banReason !== '') {
|
|
734
|
-
if (hasAllMovesBan) continue;
|
|
735
|
-
if (move.isNonstandard) {
|
|
736
|
-
banReason = ruleTable.check('pokemontag:' + toID(move.isNonstandard));
|
|
737
|
-
if (banReason) continue;
|
|
738
|
-
if (banReason !== '' && move.isNonstandard !== 'Unobtainable') {
|
|
739
|
-
if (hasNonexistentBan) continue;
|
|
740
|
-
if (!hasNonexistentWhitelist) continue;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
movePool.push(move);
|
|
745
|
-
}
|
|
746
|
-
this.enforceCustomPoolSizeNoComplexBans('move', movePool, this.maxTeamSize * setMoveCount, 'Max Team Size * Max Move Count');
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// Nature Pool
|
|
750
|
-
const doNaturesExist = this.gen > 2;
|
|
751
|
-
let naturePool: Nature[] = [];
|
|
752
|
-
if (doNaturesExist) {
|
|
753
|
-
if (!hasCustomBans) {
|
|
754
|
-
if (!hasCustomBans) {
|
|
755
|
-
naturePool = [...this.dex.natures.all()];
|
|
756
|
-
} else {
|
|
757
|
-
const hasAllNaturesBan = ruleTable.check('pokemontag:allnatures');
|
|
758
|
-
for (const nature of this.dex.natures.all()) {
|
|
759
|
-
let banReason = ruleTable.check('nature:' + nature.id);
|
|
760
|
-
if (banReason) continue;
|
|
761
|
-
if (banReason !== '' && nature.id) {
|
|
762
|
-
if (hasAllNaturesBan) continue;
|
|
763
|
-
if (nature.isNonstandard) {
|
|
764
|
-
banReason = ruleTable.check('pokemontag:' + toID(nature.isNonstandard));
|
|
765
|
-
if (banReason) continue;
|
|
766
|
-
if (banReason !== '' && nature.isNonstandard !== 'Unobtainable') {
|
|
767
|
-
if (hasNonexistentBan) continue;
|
|
768
|
-
if (!hasNonexistentWhitelist) continue;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
naturePool.push(nature);
|
|
773
|
-
}
|
|
774
|
-
// There is no 'nature:nonature' rule so do not constrain pool size
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, undefined,
|
|
780
|
-
hasCustomBans ? ruleTable : undefined);
|
|
781
|
-
|
|
782
|
-
const team = [];
|
|
783
|
-
for (const forme of randomN) {
|
|
784
|
-
// Choose forme
|
|
785
|
-
const species = this.dex.species.get(forme);
|
|
786
|
-
|
|
787
|
-
// Random unique item
|
|
788
|
-
let item = '';
|
|
789
|
-
let itemData;
|
|
790
|
-
if (doItemsExist) {
|
|
791
|
-
itemData = this.sampleNoReplace(itemPool);
|
|
792
|
-
item = itemData?.name;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Random unique ability
|
|
796
|
-
let ability = 'No Ability';
|
|
797
|
-
let abilityData;
|
|
798
|
-
if (doAbilitiesExist) {
|
|
799
|
-
abilityData = this.sampleNoReplace(abilityPool);
|
|
800
|
-
ability = abilityData?.name;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// Random unique moves
|
|
804
|
-
const m = [];
|
|
805
|
-
do {
|
|
806
|
-
const move = this.sampleNoReplace(movePool);
|
|
807
|
-
m.push(move.id);
|
|
808
|
-
} while (m.length < setMoveCount);
|
|
809
|
-
|
|
810
|
-
// Random EVs
|
|
811
|
-
const evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
|
812
|
-
if (this.gen === 6) {
|
|
813
|
-
let evpool = 510;
|
|
814
|
-
do {
|
|
815
|
-
const x = this.sample(this.dex.stats.ids());
|
|
816
|
-
const y = this.random(Math.min(256 - evs[x], evpool + 1));
|
|
817
|
-
evs[x] += y;
|
|
818
|
-
evpool -= y;
|
|
819
|
-
} while (evpool > 0);
|
|
820
|
-
} else {
|
|
821
|
-
for (const x of this.dex.stats.ids()) {
|
|
822
|
-
evs[x] = this.random(256);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Random IVs
|
|
827
|
-
const ivs: StatsTable = {
|
|
828
|
-
hp: this.random(32),
|
|
829
|
-
atk: this.random(32),
|
|
830
|
-
def: this.random(32),
|
|
831
|
-
spa: this.random(32),
|
|
832
|
-
spd: this.random(32),
|
|
833
|
-
spe: this.random(32),
|
|
834
|
-
};
|
|
835
|
-
|
|
836
|
-
// Random nature
|
|
837
|
-
let nature = '';
|
|
838
|
-
if (doNaturesExist && (naturePool.length > 0)) {
|
|
839
|
-
nature = this.sample(naturePool).name;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Level balance
|
|
843
|
-
const mbstmin = 1307;
|
|
844
|
-
const stats = species.baseStats;
|
|
845
|
-
let mbst = (stats['hp'] * 2 + 31 + 21 + 100) + 10;
|
|
846
|
-
mbst += (stats['atk'] * 2 + 31 + 21 + 100) + 5;
|
|
847
|
-
mbst += (stats['def'] * 2 + 31 + 21 + 100) + 5;
|
|
848
|
-
mbst += (stats['spa'] * 2 + 31 + 21 + 100) + 5;
|
|
849
|
-
mbst += (stats['spd'] * 2 + 31 + 21 + 100) + 5;
|
|
850
|
-
mbst += (stats['spe'] * 2 + 31 + 21 + 100) + 5;
|
|
851
|
-
|
|
852
|
-
let level;
|
|
853
|
-
if (this.adjustLevel) {
|
|
854
|
-
level = this.adjustLevel;
|
|
855
|
-
} else {
|
|
856
|
-
level = Math.floor(100 * mbstmin / mbst);
|
|
857
|
-
while (level < 100) {
|
|
858
|
-
mbst = Math.floor((stats['hp'] * 2 + 31 + 21 + 100) * level / 100 + 10);
|
|
859
|
-
mbst += Math.floor(((stats['atk'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
|
|
860
|
-
mbst += Math.floor((stats['def'] * 2 + 31 + 21 + 100) * level / 100 + 5);
|
|
861
|
-
mbst += Math.floor(((stats['spa'] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
|
|
862
|
-
mbst += Math.floor((stats['spd'] * 2 + 31 + 21 + 100) * level / 100 + 5);
|
|
863
|
-
mbst += Math.floor((stats['spe'] * 2 + 31 + 21 + 100) * level / 100 + 5);
|
|
864
|
-
if (mbst >= mbstmin) break;
|
|
865
|
-
level++;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Random happiness
|
|
870
|
-
const happiness = this.random(256);
|
|
871
|
-
|
|
872
|
-
// Random shininess
|
|
873
|
-
const shiny = this.randomChance(1, 1024);
|
|
874
|
-
|
|
875
|
-
team.push({
|
|
876
|
-
name: species.baseSpecies,
|
|
877
|
-
species: species.name,
|
|
878
|
-
gender: species.gender,
|
|
879
|
-
item,
|
|
880
|
-
ability,
|
|
881
|
-
moves: m,
|
|
882
|
-
evs,
|
|
883
|
-
ivs,
|
|
884
|
-
nature,
|
|
885
|
-
level,
|
|
886
|
-
happiness,
|
|
887
|
-
shiny,
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
return team;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
queryMoves(
|
|
895
|
-
moves: Set<string> | null,
|
|
896
|
-
types: string[],
|
|
897
|
-
abilities: Set<string> = new Set(),
|
|
898
|
-
movePool: string[] = []
|
|
899
|
-
): MoveCounter {
|
|
900
|
-
// This is primarily a helper function for random setbuilder functions.
|
|
901
|
-
const counter = new MoveCounter();
|
|
902
|
-
|
|
903
|
-
if (!moves?.size) return counter;
|
|
904
|
-
|
|
905
|
-
const categories = {Physical: 0, Special: 0, Status: 0};
|
|
906
|
-
|
|
907
|
-
// Iterate through all moves we've chosen so far and keep track of what they do:
|
|
908
|
-
for (const moveid of moves) {
|
|
909
|
-
let move = this.dex.moves.get(moveid);
|
|
910
|
-
if (move.id === 'naturepower') {
|
|
911
|
-
if (this.gen === 5) move = this.dex.moves.get('earthquake');
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
let moveType = move.type;
|
|
915
|
-
if (['judgment', 'multiattack', 'revelationdance'].includes(moveid)) moveType = types[0];
|
|
916
|
-
if (move.damage || move.damageCallback) {
|
|
917
|
-
// Moves that do a set amount of damage:
|
|
918
|
-
counter.add('damage');
|
|
919
|
-
counter.damagingMoves.add(move);
|
|
920
|
-
} else {
|
|
921
|
-
// Are Physical/Special/Status moves:
|
|
922
|
-
categories[move.category]++;
|
|
923
|
-
}
|
|
924
|
-
// Moves that have a low base power:
|
|
925
|
-
if (moveid === 'lowkick' || (move.basePower && move.basePower <= 60 && moveid !== 'rapidspin')) {
|
|
926
|
-
counter.add('technician');
|
|
927
|
-
}
|
|
928
|
-
// Moves that hit up to 5 times:
|
|
929
|
-
if (move.multihit && Array.isArray(move.multihit) && move.multihit[1] === 5) counter.add('skilllink');
|
|
930
|
-
if (move.recoil || move.hasCrashDamage) counter.add('recoil');
|
|
931
|
-
if (move.drain) counter.add('drain');
|
|
932
|
-
// Moves which have a base power, but aren't super-weak like Rapid Spin:
|
|
933
|
-
if (move.basePower > 30 || move.multihit || move.basePowerCallback || moveid === 'infestation') {
|
|
934
|
-
counter.add(moveType);
|
|
935
|
-
if (types.includes(moveType)) {
|
|
936
|
-
// STAB:
|
|
937
|
-
// Certain moves aren't acceptable as a Pokemon's only STAB attack
|
|
938
|
-
if (!this.noStab.includes(moveid) && (!moveid.startsWith('hiddenpower') || types.length === 1)) {
|
|
939
|
-
counter.add('stab');
|
|
940
|
-
// Ties between Physical and Special setup should broken in favor of STABs
|
|
941
|
-
categories[move.category] += 0.1;
|
|
942
|
-
}
|
|
943
|
-
} else if (
|
|
944
|
-
// Less obvious forms of STAB
|
|
945
|
-
(moveType === 'Normal' && (['Aerilate', 'Galvanize', 'Pixilate', 'Refrigerate'].some(abil => abilities.has(abil)))) ||
|
|
946
|
-
(move.priority === 0 && (abilities.has('Libero') || abilities.has('Protean')) && !this.noStab.includes(moveid)) ||
|
|
947
|
-
(moveType === 'Steel' && abilities.has('Steelworker'))
|
|
948
|
-
) {
|
|
949
|
-
counter.add('stab');
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
if (move.flags['bite']) counter.add('strongjaw');
|
|
953
|
-
if (move.flags['punch']) counter.add('ironfist');
|
|
954
|
-
if (move.flags['sound']) counter.add('sound');
|
|
955
|
-
if (move.priority !== 0 || (moveid === 'grassyglide' && abilities.has('Grassy Surge'))) {
|
|
956
|
-
counter.add('priority');
|
|
957
|
-
}
|
|
958
|
-
counter.damagingMoves.add(move);
|
|
959
|
-
}
|
|
960
|
-
// Moves with secondary effects:
|
|
961
|
-
if (move.secondary) {
|
|
962
|
-
counter.add('sheerforce');
|
|
963
|
-
if (sereneGraceBenefits(move)) {
|
|
964
|
-
counter.add('serenegrace');
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
// Moves with low accuracy:
|
|
968
|
-
if (move.accuracy && move.accuracy !== true && move.accuracy < 90) counter.add('inaccurate');
|
|
969
|
-
|
|
970
|
-
// Moves that change stats:
|
|
971
|
-
if (RecoveryMove.includes(moveid)) counter.add('recovery');
|
|
972
|
-
if (ContraryMoves.includes(moveid)) counter.add('contrary');
|
|
973
|
-
if (PhysicalSetup.includes(moveid)) {
|
|
974
|
-
counter.add('physicalsetup');
|
|
975
|
-
counter.setupType = 'Physical';
|
|
976
|
-
} else if (SpecialSetup.includes(moveid)) {
|
|
977
|
-
counter.add('specialsetup');
|
|
978
|
-
counter.setupType = 'Special';
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
if (MixedSetup.includes(moveid)) counter.add('mixedsetup');
|
|
982
|
-
if (SpeedSetup.includes(moveid)) counter.add('speedsetup');
|
|
983
|
-
if (Hazards.includes(moveid)) counter.add('hazards');
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
// Keep track of the available moves
|
|
987
|
-
for (const moveid of movePool) {
|
|
988
|
-
const move = this.dex.moves.get(moveid);
|
|
989
|
-
if (move.damageCallback) continue;
|
|
990
|
-
if (move.category === 'Physical') counter.add('physicalpool');
|
|
991
|
-
if (move.category === 'Special') counter.add('specialpool');
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
// Choose a setup type:
|
|
995
|
-
if (counter.get('mixedsetup')) {
|
|
996
|
-
counter.setupType = 'Mixed';
|
|
997
|
-
} else if (counter.get('physicalsetup') && counter.get('specialsetup')) {
|
|
998
|
-
const pool = {
|
|
999
|
-
Physical: categories['Physical'] + counter.get('physicalpool'),
|
|
1000
|
-
Special: categories['Special'] + counter.get('specialpool'),
|
|
1001
|
-
};
|
|
1002
|
-
if (pool.Physical === pool.Special) {
|
|
1003
|
-
if (categories['Physical'] > categories['Special']) counter.setupType = 'Physical';
|
|
1004
|
-
if (categories['Special'] > categories['Physical']) counter.setupType = 'Special';
|
|
1005
|
-
} else {
|
|
1006
|
-
counter.setupType = pool.Physical > pool.Special ? 'Physical' : 'Special';
|
|
1007
|
-
}
|
|
1008
|
-
} else if (counter.setupType === 'Physical') {
|
|
1009
|
-
if (
|
|
1010
|
-
(categories['Physical'] < 2 && (!counter.get('stab') || !counter.get('physicalpool'))) &&
|
|
1011
|
-
!(moves.has('rest') && moves.has('sleeptalk'))
|
|
1012
|
-
) {
|
|
1013
|
-
counter.setupType = '';
|
|
1014
|
-
}
|
|
1015
|
-
} else if (counter.setupType === 'Special') {
|
|
1016
|
-
if (
|
|
1017
|
-
(categories['Special'] < 2 && (!counter.get('stab') || !counter.get('specialpool'))) &&
|
|
1018
|
-
!(moves.has('rest') && moves.has('sleeptalk')) &&
|
|
1019
|
-
!(moves.has('wish') && moves.has('protect'))
|
|
1020
|
-
) {
|
|
1021
|
-
counter.setupType = '';
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
counter.set('Physical', Math.floor(categories['Physical']));
|
|
1026
|
-
counter.set('Special', Math.floor(categories['Special']));
|
|
1027
|
-
counter.set('Status', categories['Status']);
|
|
1028
|
-
|
|
1029
|
-
return counter;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
shouldCullMove(
|
|
1033
|
-
move: Move,
|
|
1034
|
-
types: Set<string>,
|
|
1035
|
-
moves: Set<string>,
|
|
1036
|
-
abilities: Set<string>,
|
|
1037
|
-
counter: MoveCounter,
|
|
1038
|
-
movePool: string[],
|
|
1039
|
-
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
1040
|
-
species: Species,
|
|
1041
|
-
isLead: boolean,
|
|
1042
|
-
isDoubles: boolean,
|
|
1043
|
-
isNoDynamax: boolean,
|
|
1044
|
-
): {cull: boolean, isSetup?: boolean} {
|
|
1045
|
-
if (isDoubles && species.baseStats.def >= 140 && movePool.includes('bodypress')) {
|
|
1046
|
-
// In Doubles, Pokémon with Defense stats >= 140 should always have body press
|
|
1047
|
-
return {cull: true};
|
|
1048
|
-
}
|
|
1049
|
-
if (
|
|
1050
|
-
(species.id === 'doublade' && movePool.includes('swordsdance')) ||
|
|
1051
|
-
(species.id === 'entei' && movePool.includes('extremespeed')) ||
|
|
1052
|
-
(species.id === 'genesectdouse' && movePool.includes('technoblast')) ||
|
|
1053
|
-
(species.id === 'golisopod' && movePool.includes('leechlife') && movePool.includes('firstimpression'))
|
|
1054
|
-
) {
|
|
1055
|
-
// Entei should always have Extreme Speed, and Genesect-Douse should always have Techno Blast
|
|
1056
|
-
// Golisopod should always have one of its bug moves (Leech Life or First Impression)
|
|
1057
|
-
return {cull: true};
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const hasRestTalk = moves.has('rest') && moves.has('sleeptalk');
|
|
1061
|
-
|
|
1062
|
-
// Reject moves that need support
|
|
1063
|
-
switch (move.id) {
|
|
1064
|
-
case 'acrobatics': case 'junglehealing':
|
|
1065
|
-
// Special case to prevent lead Acrobatics Rillaboom
|
|
1066
|
-
return {cull: (species.id.startsWith('rillaboom') && isLead) || (!isDoubles && !counter.setupType)};
|
|
1067
|
-
case 'dualwingbeat': case 'fly':
|
|
1068
|
-
return {cull: !types.has(move.type) && !counter.setupType && !!counter.get('Status')};
|
|
1069
|
-
case 'healbell':
|
|
1070
|
-
return {cull: movePool.includes('protect') || movePool.includes('wish')};
|
|
1071
|
-
case 'fireblast':
|
|
1072
|
-
// Special case for Togekiss, which always wants Aura Sphere
|
|
1073
|
-
return {cull: abilities.has('Serene Grace') && (!moves.has('trick') || counter.get('Status') > 1)};
|
|
1074
|
-
case 'firepunch':
|
|
1075
|
-
// Special case for Darmanitan-Zen-Galar, which doesn't always want Fire Punch
|
|
1076
|
-
return {cull: movePool.includes('bellydrum') || (moves.has('earthquake') && movePool.includes('substitute'))};
|
|
1077
|
-
case 'flamecharge':
|
|
1078
|
-
return {cull: movePool.includes('swordsdance')};
|
|
1079
|
-
case 'hypervoice':
|
|
1080
|
-
// Special case for Heliolisk, which always wants Thunderbolt
|
|
1081
|
-
return {cull: types.has('Electric') && movePool.includes('thunderbolt')};
|
|
1082
|
-
case 'payback': case 'psychocut':
|
|
1083
|
-
// Special case for Type: Null and Malamar, which don't want these + RestTalk
|
|
1084
|
-
return {cull: !counter.get('Status') || hasRestTalk};
|
|
1085
|
-
case 'rest':
|
|
1086
|
-
const bulkySetup = !moves.has('sleeptalk') && ['bulkup', 'calmmind', 'coil', 'curse'].some(m => movePool.includes(m));
|
|
1087
|
-
// Registeel would otherwise get Curse sets without Rest, which are very bad generally
|
|
1088
|
-
return {cull: species.id !== 'registeel' && (movePool.includes('sleeptalk') || bulkySetup)};
|
|
1089
|
-
case 'sleeptalk':
|
|
1090
|
-
if (!moves.has('rest')) return {cull: true};
|
|
1091
|
-
if (movePool.length > 1 && !abilities.has('Contrary')) {
|
|
1092
|
-
const rest = movePool.indexOf('rest');
|
|
1093
|
-
if (rest >= 0) this.fastPop(movePool, rest);
|
|
1094
|
-
}
|
|
1095
|
-
break;
|
|
1096
|
-
case 'storedpower':
|
|
1097
|
-
return {cull: !counter.setupType};
|
|
1098
|
-
case 'switcheroo': case 'trick':
|
|
1099
|
-
return {cull: counter.get('Physical') + counter.get('Special') < 3 || moves.has('rapidspin')};
|
|
1100
|
-
case 'trickroom':
|
|
1101
|
-
const webs = !!teamDetails.stickyWeb;
|
|
1102
|
-
return {cull:
|
|
1103
|
-
isLead || webs || !!counter.get('speedsetup') ||
|
|
1104
|
-
counter.damagingMoves.size < 2 || movePool.includes('nastyplot'),
|
|
1105
|
-
};
|
|
1106
|
-
case 'zenheadbutt':
|
|
1107
|
-
// Special case for Victini, which should prefer Bolt Strike to Zen Headbutt
|
|
1108
|
-
return {cull: movePool.includes('boltstrike') || (species.id === 'eiscue' && moves.has('substitute'))};
|
|
1109
|
-
|
|
1110
|
-
// Set up once and only if we have the moves for it
|
|
1111
|
-
case 'bellydrum': case 'bulkup': case 'coil': case 'curse': case 'dragondance': case 'honeclaws': case 'swordsdance':
|
|
1112
|
-
if (counter.setupType !== 'Physical') return {cull: true}; // if we're not setting up physically this is pointless
|
|
1113
|
-
if (counter.get('Physical') + counter.get('physicalpool') < 2 && !hasRestTalk) return {cull: true};
|
|
1114
|
-
|
|
1115
|
-
// First Impression + setup is undesirable in Doubles
|
|
1116
|
-
if (isDoubles && moves.has('firstimpression')) return {cull: true};
|
|
1117
|
-
if (move.id === 'swordsdance' && moves.has('dragondance')) return {cull: true}; // Dragon Dance is judged as better
|
|
1118
|
-
|
|
1119
|
-
return {cull: false, isSetup: true};
|
|
1120
|
-
case 'calmmind': case 'nastyplot':
|
|
1121
|
-
if (species.id === 'togekiss') return {cull: false};
|
|
1122
|
-
if (counter.setupType !== 'Special') return {cull: true};
|
|
1123
|
-
if (
|
|
1124
|
-
(counter.get('Special') + counter.get('specialpool')) < 2 &&
|
|
1125
|
-
!hasRestTalk &&
|
|
1126
|
-
!(moves.has('wish') && moves.has('protect'))
|
|
1127
|
-
) return {cull: true};
|
|
1128
|
-
if (moves.has('healpulse') || move.id === 'calmmind' && moves.has('trickroom')) return {cull: true};
|
|
1129
|
-
return {cull: false, isSetup: true};
|
|
1130
|
-
case 'quiverdance':
|
|
1131
|
-
return {cull: false, isSetup: true};
|
|
1132
|
-
case 'clangoroussoul': case 'shellsmash': case 'workup':
|
|
1133
|
-
if (counter.setupType !== 'Mixed') return {cull: true};
|
|
1134
|
-
if (counter.damagingMoves.size + counter.get('physicalpool') + counter.get('specialpool') < 2) return {cull: true};
|
|
1135
|
-
return {cull: false, isSetup: true};
|
|
1136
|
-
case 'agility': case 'autotomize': case 'rockpolish': case 'shiftgear':
|
|
1137
|
-
if (counter.damagingMoves.size < 2 || moves.has('rest')) return {cull: true};
|
|
1138
|
-
if (movePool.includes('calmmind') || movePool.includes('nastyplot')) return {cull: true};
|
|
1139
|
-
return {cull: false, isSetup: !counter.setupType};
|
|
1140
|
-
|
|
1141
|
-
// Bad after setup
|
|
1142
|
-
case 'coaching': case 'counter': case 'reversal':
|
|
1143
|
-
// Counter: special case for Alakazam, which doesn't want Counter + Nasty Plot
|
|
1144
|
-
return {cull: !!counter.setupType};
|
|
1145
|
-
case 'bulletpunch': case 'extremespeed': case 'rockblast':
|
|
1146
|
-
return {cull: (
|
|
1147
|
-
!!counter.get('speedsetup') ||
|
|
1148
|
-
(!isDoubles && moves.has('dragondance')) ||
|
|
1149
|
-
counter.damagingMoves.size < 2
|
|
1150
|
-
)};
|
|
1151
|
-
case 'closecombat': case 'flashcannon': case 'pollenpuff':
|
|
1152
|
-
const substituteCullCondition = (
|
|
1153
|
-
(moves.has('substitute') && !types.has('Fighting')) ||
|
|
1154
|
-
(moves.has('toxic') && movePool.includes('substitute'))
|
|
1155
|
-
);
|
|
1156
|
-
const preferHJKOverCCCullCondition = (
|
|
1157
|
-
move.id === 'closecombat' &&
|
|
1158
|
-
!counter.setupType &&
|
|
1159
|
-
(moves.has('highjumpkick') || movePool.includes('highjumpkick'))
|
|
1160
|
-
);
|
|
1161
|
-
return {cull: substituteCullCondition || preferHJKOverCCCullCondition};
|
|
1162
|
-
case 'defog':
|
|
1163
|
-
return {cull: !!counter.setupType || moves.has('healbell') || moves.has('toxicspikes') || !!teamDetails.defog};
|
|
1164
|
-
case 'fakeout':
|
|
1165
|
-
return {cull: !!counter.setupType || ['protect', 'rapidspin', 'substitute', 'uturn'].some(m => moves.has(m))};
|
|
1166
|
-
case 'firstimpression': case 'glare': case 'icywind': case 'tailwind': case 'waterspout':
|
|
1167
|
-
return {cull: !!counter.setupType || !!counter.get('speedsetup') || moves.has('rest')};
|
|
1168
|
-
case 'healingwish': case 'memento':
|
|
1169
|
-
return {cull: !!counter.setupType || !!counter.get('recovery') || moves.has('substitute') || moves.has('uturn')};
|
|
1170
|
-
case 'highjumpkick':
|
|
1171
|
-
// Special case for Hitmonlee to prevent non-Unburden Curse
|
|
1172
|
-
return {cull: moves.has('curse')};
|
|
1173
|
-
case 'partingshot':
|
|
1174
|
-
return {cull: !!counter.get('speedsetup') || moves.has('bulkup') || moves.has('uturn')};
|
|
1175
|
-
case 'protect':
|
|
1176
|
-
if (!isDoubles && ((counter.setupType && !moves.has('wish')) || moves.has('rest'))) return {cull: true};
|
|
1177
|
-
if (
|
|
1178
|
-
!isDoubles &&
|
|
1179
|
-
counter.get('Status') < 2 &&
|
|
1180
|
-
['Hunger Switch', 'Speed Boost', 'Moody'].every(m => !abilities.has(m))
|
|
1181
|
-
) return {cull: true};
|
|
1182
|
-
if (movePool.includes('leechseed') || (movePool.includes('toxic') && !moves.has('wish'))) return {cull: true};
|
|
1183
|
-
if (isDoubles && (
|
|
1184
|
-
['bellydrum', 'fakeout', 'shellsmash', 'spore'].some(m => movePool.includes(m)) ||
|
|
1185
|
-
moves.has('tailwind') || moves.has('waterspout') || counter.get('recovery')
|
|
1186
|
-
)) return {cull: true};
|
|
1187
|
-
return {cull: false};
|
|
1188
|
-
case 'rapidspin':
|
|
1189
|
-
const setup = ['curse', 'nastyplot', 'shellsmash'].some(m => moves.has(m));
|
|
1190
|
-
return {cull: !!teamDetails.rapidSpin || setup || (!!counter.setupType && counter.get('Fighting') >= 2)};
|
|
1191
|
-
case 'shadowsneak':
|
|
1192
|
-
const sneakIncompatible = ['substitute', 'trickroom', 'dualwingbeat', 'toxic'].some(m => moves.has(m));
|
|
1193
|
-
return {cull: hasRestTalk || sneakIncompatible || counter.setupType === 'Special'};
|
|
1194
|
-
case 'spikes':
|
|
1195
|
-
return {cull: !!counter.setupType || (!!teamDetails.spikes && teamDetails.spikes > 1)};
|
|
1196
|
-
case 'stealthrock':
|
|
1197
|
-
return {cull:
|
|
1198
|
-
!!counter.setupType ||
|
|
1199
|
-
!!counter.get('speedsetup') ||
|
|
1200
|
-
!!teamDetails.stealthRock ||
|
|
1201
|
-
['rest', 'substitute', 'trickroom', 'teleport'].some(m => moves.has(m)) ||
|
|
1202
|
-
(species.id === 'palossand' && movePool.includes('shoreup')),
|
|
1203
|
-
};
|
|
1204
|
-
case 'stickyweb':
|
|
1205
|
-
return {cull: counter.setupType === 'Special' || !!teamDetails.stickyWeb};
|
|
1206
|
-
case 'taunt':
|
|
1207
|
-
return {cull: moves.has('encore') || moves.has('nastyplot') || moves.has('swordsdance')};
|
|
1208
|
-
case 'thunderwave': case 'voltswitch':
|
|
1209
|
-
const cullInDoubles = isDoubles && (moves.has('electroweb') || moves.has('nuzzle'));
|
|
1210
|
-
return {cull: (
|
|
1211
|
-
!!counter.setupType ||
|
|
1212
|
-
!!counter.get('speedsetup') ||
|
|
1213
|
-
moves.has('shiftgear') ||
|
|
1214
|
-
moves.has('raindance') ||
|
|
1215
|
-
cullInDoubles
|
|
1216
|
-
)};
|
|
1217
|
-
case 'toxic':
|
|
1218
|
-
return {cull: !!counter.setupType || ['sludgewave', 'thunderwave', 'willowisp'].some(m => moves.has(m))};
|
|
1219
|
-
case 'toxicspikes':
|
|
1220
|
-
return {cull: !!counter.setupType || !!teamDetails.toxicSpikes};
|
|
1221
|
-
case 'uturn':
|
|
1222
|
-
const bugSwordsDanceCase = types.has('Bug') && counter.get('recovery') && moves.has('swordsdance');
|
|
1223
|
-
return {cull: (
|
|
1224
|
-
!!counter.get('speedsetup') ||
|
|
1225
|
-
(counter.setupType && !bugSwordsDanceCase) ||
|
|
1226
|
-
(isDoubles && moves.has('leechlife')) ||
|
|
1227
|
-
moves.has('shiftgear')
|
|
1228
|
-
)};
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Ineffective to have both moves together
|
|
1232
|
-
*
|
|
1233
|
-
* These are sorted in order of:
|
|
1234
|
-
* Normal>Fire>Water>Electric>Grass>Ice>Fighting>Poison>Ground>Flying>Psychic>Bug>Rock>Ghost>Dragon>Dark>Fairy
|
|
1235
|
-
* and then subsorted alphabetically.
|
|
1236
|
-
* This type order is arbitrary and referenced from https://pokemondb.net/type.
|
|
1237
|
-
*/
|
|
1238
|
-
case 'explosion':
|
|
1239
|
-
// Rock Blast: Special case for Gigalith to prevent Stone Edge-less Choice Band sets
|
|
1240
|
-
const otherMoves = ['curse', 'stompingtantrum', 'rockblast', 'painsplit', 'wish'].some(m => moves.has(m));
|
|
1241
|
-
return {cull: !!counter.get('speedsetup') || !!counter.get('recovery') || otherMoves};
|
|
1242
|
-
case 'facade':
|
|
1243
|
-
// Special case for Snorlax
|
|
1244
|
-
return {cull: movePool.includes('doubleedge')};
|
|
1245
|
-
case 'quickattack':
|
|
1246
|
-
// Diggersby wants U-turn on Choiced sets
|
|
1247
|
-
const diggersbyCull = counter.get('Physical') > 3 && movePool.includes('uturn');
|
|
1248
|
-
return {cull: !!counter.get('speedsetup') || (types.has('Rock') && !!counter.get('Status')) || diggersbyCull};
|
|
1249
|
-
case 'blazekick':
|
|
1250
|
-
return {cull: species.id === 'genesect' && counter.get('Special') >= 1};
|
|
1251
|
-
case 'blueflare':
|
|
1252
|
-
return {cull: moves.has('vcreate')};
|
|
1253
|
-
case 'firefang': case 'flamethrower':
|
|
1254
|
-
// Fire Fang: Special case for Garchomp, which doesn't want Fire Fang w/o Swords Dance
|
|
1255
|
-
const otherFireMoves = ['heatwave', 'overheat'].some(m => moves.has(m));
|
|
1256
|
-
return {cull: (moves.has('fireblast') && counter.setupType !== 'Physical') || otherFireMoves};
|
|
1257
|
-
case 'flareblitz':
|
|
1258
|
-
// Special case for Solgaleo to prevent Flame Charge + Flare Blitz
|
|
1259
|
-
return {cull: species.id === 'solgaleo' && moves.has('flamecharge')};
|
|
1260
|
-
case 'overheat':
|
|
1261
|
-
return {cull: moves.has('flareblitz') || (isDoubles && moves.has('calmmind'))};
|
|
1262
|
-
case 'aquatail': case 'flipturn':
|
|
1263
|
-
return {cull: moves.has('aquajet') || !!counter.get('Status')};
|
|
1264
|
-
case 'hydropump':
|
|
1265
|
-
return {cull: moves.has('scald') && (
|
|
1266
|
-
(counter.get('Special') < 4 && !moves.has('uturn')) ||
|
|
1267
|
-
(species.types.length > 1 && counter.get('stab') < 3)
|
|
1268
|
-
)};
|
|
1269
|
-
case 'muddywater':
|
|
1270
|
-
return {cull: moves.has('liquidation')};
|
|
1271
|
-
case 'scald':
|
|
1272
|
-
// Special case for Clawitzer
|
|
1273
|
-
return {cull: moves.has('waterpulse')};
|
|
1274
|
-
case 'thunderbolt':
|
|
1275
|
-
// Special case for Goodra, which only wants one move to hit Water-types
|
|
1276
|
-
return {cull: moves.has('powerwhip')};
|
|
1277
|
-
case 'energyball':
|
|
1278
|
-
// Special case to prevent Shiinotic with four Grass moves and no Moonblast
|
|
1279
|
-
return {cull: species.id === 'shiinotic' && !moves.has('moonblast')};
|
|
1280
|
-
case 'gigadrain':
|
|
1281
|
-
// Celebi always wants Leaf Storm on its more pivoting-focused non-Nasty Plot sets
|
|
1282
|
-
const celebiPreferLeafStorm = species.id === 'celebi' && !counter.setupType && moves.has('uturn');
|
|
1283
|
-
return {cull: celebiPreferLeafStorm || (types.has('Poison') && !counter.get('Poison'))};
|
|
1284
|
-
case 'leafblade':
|
|
1285
|
-
// Special case for Virizion to prevent Leaf Blade on Assault Vest sets
|
|
1286
|
-
return {cull: (moves.has('leafstorm') || movePool.includes('leafstorm')) && counter.setupType !== 'Physical'};
|
|
1287
|
-
case 'leafstorm':
|
|
1288
|
-
const leafBladePossible = movePool.includes('leafblade') || moves.has('leafblade');
|
|
1289
|
-
return {cull:
|
|
1290
|
-
// Virizion should always prefer Leaf Blade to Leaf Storm on Physical sets
|
|
1291
|
-
(counter.setupType === 'Physical' && (species.id === 'virizion' || leafBladePossible)) ||
|
|
1292
|
-
(moves.has('gigadrain') && !!counter.get('Status')) ||
|
|
1293
|
-
(isDoubles && moves.has('energyball')),
|
|
1294
|
-
};
|
|
1295
|
-
case 'powerwhip':
|
|
1296
|
-
// Special case for Centiskorch, which doesn't want Assault Vest
|
|
1297
|
-
return {cull: moves.has('leechlife')};
|
|
1298
|
-
case 'woodhammer':
|
|
1299
|
-
return {cull: moves.has('hornleech') && counter.get('Physical') < 4};
|
|
1300
|
-
case 'freezedry':
|
|
1301
|
-
const betterIceMove = (
|
|
1302
|
-
(moves.has('blizzard') && !!counter.setupType) ||
|
|
1303
|
-
(moves.has('icebeam') && counter.get('Special') < 4)
|
|
1304
|
-
);
|
|
1305
|
-
const preferThunderWave = movePool.includes('thunderwave') && types.has('Electric');
|
|
1306
|
-
return {cull: betterIceMove || preferThunderWave || movePool.includes('bodyslam')};
|
|
1307
|
-
case 'bodypress':
|
|
1308
|
-
// Turtonator never wants Earthquake + Body Press, and wants EQ+Smash or Press+No Smash
|
|
1309
|
-
const turtonatorPressCull = species.id === 'turtonator' && moves.has('earthquake') && movePool.includes('shellsmash');
|
|
1310
|
-
const pressIncompatible = ['shellsmash', 'mirrorcoat', 'whirlwind'].some(m => moves.has(m));
|
|
1311
|
-
return {cull: turtonatorPressCull || pressIncompatible || counter.setupType === 'Special'};
|
|
1312
|
-
case 'circlethrow':
|
|
1313
|
-
// Part of a special case for Throh to pick one specific Fighting move depending on its set
|
|
1314
|
-
return {cull: moves.has('stormthrow') && !moves.has('rest')};
|
|
1315
|
-
case 'drainpunch':
|
|
1316
|
-
return {cull: moves.has('closecombat') || (!types.has('Fighting') && movePool.includes('swordsdance'))};
|
|
1317
|
-
case 'dynamicpunch': case 'thunderouskick':
|
|
1318
|
-
// Dynamic Punch: Special case for Machamp to better split Guts and No Guard sets
|
|
1319
|
-
return {cull: moves.has('closecombat') || moves.has('facade')};
|
|
1320
|
-
case 'focusblast':
|
|
1321
|
-
// Special cases for Blastoise and Regice; Blastoise wants Shell Smash, and Regice wants Thunderbolt
|
|
1322
|
-
return {cull: movePool.includes('shellsmash') || hasRestTalk};
|
|
1323
|
-
case 'hammerarm':
|
|
1324
|
-
// Special case for Kangaskhan, which always wants Sucker Punch
|
|
1325
|
-
return {cull: moves.has('fakeout')};
|
|
1326
|
-
case 'stormthrow':
|
|
1327
|
-
// Part of a special case for Throh to pick one specific Fighting move depending on its set
|
|
1328
|
-
return {cull: hasRestTalk};
|
|
1329
|
-
case 'superpower':
|
|
1330
|
-
return {
|
|
1331
|
-
cull: moves.has('hydropump') ||
|
|
1332
|
-
(counter.get('Physical') >= 4 && movePool.includes('uturn')) ||
|
|
1333
|
-
(moves.has('substitute') && !abilities.has('Contrary')),
|
|
1334
|
-
isSetup: abilities.has('Contrary'),
|
|
1335
|
-
};
|
|
1336
|
-
case 'poisonjab':
|
|
1337
|
-
return {cull: !types.has('Poison') && counter.get('Status') >= 2};
|
|
1338
|
-
case 'earthquake':
|
|
1339
|
-
const doublesCull = moves.has('earthpower') || moves.has('highhorsepower');
|
|
1340
|
-
// Turtonator wants Body Press when it doesn't have Shell Smash
|
|
1341
|
-
const turtQuakeCull = species.id === 'turtonator' && movePool.includes('bodypress') && movePool.includes('shellsmash');
|
|
1342
|
-
const subToxicPossible = moves.has('substitute') && movePool.includes('toxic');
|
|
1343
|
-
return {cull: turtQuakeCull || (isDoubles && doublesCull) || subToxicPossible || moves.has('bonemerang')};
|
|
1344
|
-
case 'scorchingsands':
|
|
1345
|
-
// Special cases for Ninetales and Palossand; prevents status redundancy
|
|
1346
|
-
return {cull: (
|
|
1347
|
-
moves.has('willowisp') ||
|
|
1348
|
-
moves.has('earthpower') ||
|
|
1349
|
-
(moves.has('toxic') && movePool.includes('earthpower'))
|
|
1350
|
-
)};
|
|
1351
|
-
case 'airslash':
|
|
1352
|
-
return {cull:
|
|
1353
|
-
(species.id === 'naganadel' && moves.has('nastyplot')) ||
|
|
1354
|
-
hasRestTalk ||
|
|
1355
|
-
(abilities.has('Simple') && !!counter.get('recovery')) ||
|
|
1356
|
-
counter.setupType === 'Physical',
|
|
1357
|
-
};
|
|
1358
|
-
case 'bravebird':
|
|
1359
|
-
// Special case for Mew, which only wants Brave Bird with Swords Dance
|
|
1360
|
-
return {cull: moves.has('dragondance')};
|
|
1361
|
-
case 'hurricane':
|
|
1362
|
-
return {cull: counter.setupType === 'Physical'};
|
|
1363
|
-
case 'futuresight':
|
|
1364
|
-
return {cull: moves.has('psyshock') || moves.has('trick') || movePool.includes('teleport')};
|
|
1365
|
-
case 'photongeyser':
|
|
1366
|
-
// Special case for Necrozma-DM, which always wants Dragon Dance
|
|
1367
|
-
return {cull: moves.has('morningsun')};
|
|
1368
|
-
case 'psychic':
|
|
1369
|
-
const alcremieCase = species.id === 'alcremiegmax' && counter.get('Status') < 2;
|
|
1370
|
-
return {cull: alcremieCase || (moves.has('psyshock') && (!!counter.setupType || isDoubles))};
|
|
1371
|
-
case 'psychicfangs':
|
|
1372
|
-
// Special case for Morpeko, which doesn't want 4 attacks Leftovers
|
|
1373
|
-
return {cull: moves.has('rapidspin')};
|
|
1374
|
-
case 'psyshock':
|
|
1375
|
-
// Special case for Sylveon which only wants Psyshock if it gets a Choice item
|
|
1376
|
-
const sylveonCase = abilities.has('Pixilate') && counter.get('Special') < 4;
|
|
1377
|
-
return {cull: moves.has('psychic') || (!counter.setupType && sylveonCase) || (isDoubles && moves.has('psychic'))};
|
|
1378
|
-
case 'bugbuzz':
|
|
1379
|
-
return {cull: moves.has('uturn') && !counter.setupType};
|
|
1380
|
-
case 'leechlife':
|
|
1381
|
-
return {cull:
|
|
1382
|
-
(isDoubles && moves.has('lunge')) ||
|
|
1383
|
-
(moves.has('uturn') && !counter.setupType) ||
|
|
1384
|
-
movePool.includes('spikes'),
|
|
1385
|
-
};
|
|
1386
|
-
case 'stoneedge':
|
|
1387
|
-
const gutsCullCondition = abilities.has('Guts') && (!moves.has('dynamicpunch') || moves.has('spikes'));
|
|
1388
|
-
const rockSlidePlusStatusPossible = counter.get('Status') && movePool.includes('rockslide');
|
|
1389
|
-
const otherRockMove = moves.has('rockblast') || moves.has('rockslide');
|
|
1390
|
-
const lucarioCull = species.id === 'lucario' && !!counter.setupType;
|
|
1391
|
-
return {cull: gutsCullCondition || (!isDoubles && rockSlidePlusStatusPossible) || otherRockMove || lucarioCull};
|
|
1392
|
-
case 'poltergeist':
|
|
1393
|
-
// Special case for Dhelmise in Doubles, which doesn't want both
|
|
1394
|
-
return {cull: moves.has('knockoff')};
|
|
1395
|
-
case 'shadowball':
|
|
1396
|
-
return {cull:
|
|
1397
|
-
(isDoubles && moves.has('phantomforce')) ||
|
|
1398
|
-
// Special case for Sylveon, which never wants Shadow Ball as its only coverage move
|
|
1399
|
-
(abilities.has('Pixilate') && (!!counter.setupType || counter.get('Status') > 1)) ||
|
|
1400
|
-
(!types.has('Ghost') && movePool.includes('focusblast')),
|
|
1401
|
-
};
|
|
1402
|
-
case 'shadowclaw':
|
|
1403
|
-
return {cull: types.has('Steel') && moves.has('shadowsneak') && counter.get('Physical') < 4};
|
|
1404
|
-
case 'dragonpulse': case 'spacialrend':
|
|
1405
|
-
return {cull: moves.has('dracometeor') && counter.get('Special') < 4};
|
|
1406
|
-
case 'darkpulse':
|
|
1407
|
-
const pulseIncompatible = ['foulplay', 'knockoff'].some(m => moves.has(m)) || (
|
|
1408
|
-
species.id === 'shiftry' && (moves.has('defog') || moves.has('suckerpunch'))
|
|
1409
|
-
);
|
|
1410
|
-
// Special clause to prevent bugged Shiftry sets with Sucker Punch + Nasty Plot
|
|
1411
|
-
const shiftryCase = movePool.includes('nastyplot') && !moves.has('defog');
|
|
1412
|
-
return {cull: pulseIncompatible && !shiftryCase && counter.setupType !== 'Special'};
|
|
1413
|
-
case 'suckerpunch':
|
|
1414
|
-
return {cull:
|
|
1415
|
-
// Shiftry in No Dynamax would otherwise get Choice Scarf Sucker Punch sometimes.
|
|
1416
|
-
(isNoDynamax && species.id === 'shiftry' && moves.has('defog')) ||
|
|
1417
|
-
moves.has('rest') ||
|
|
1418
|
-
counter.damagingMoves.size < 2 ||
|
|
1419
|
-
(counter.setupType === 'Special') ||
|
|
1420
|
-
(counter.get('Dark') > 1 && !types.has('Dark')),
|
|
1421
|
-
};
|
|
1422
|
-
case 'dazzlinggleam':
|
|
1423
|
-
return {cull: ['fleurcannon', 'moonblast', 'petaldance'].some(m => moves.has(m))};
|
|
1424
|
-
|
|
1425
|
-
// Status:
|
|
1426
|
-
case 'bodyslam': case 'clearsmog':
|
|
1427
|
-
const toxicCullCondition = moves.has('toxic') && !types.has('Normal');
|
|
1428
|
-
return {cull: moves.has('sludgebomb') || moves.has('trick') || movePool.includes('recover') || toxicCullCondition};
|
|
1429
|
-
case 'haze':
|
|
1430
|
-
// Special case for Corsola-Galar, which always wants Will-O-Wisp
|
|
1431
|
-
return {cull: !teamDetails.stealthRock && (moves.has('stealthrock') || movePool.includes('stealthrock'))};
|
|
1432
|
-
case 'hypnosis':
|
|
1433
|
-
// Special case for Xurkitree to properly split Blunder Policy and Choice item sets
|
|
1434
|
-
return {cull: moves.has('voltswitch')};
|
|
1435
|
-
case 'willowisp': case 'yawn':
|
|
1436
|
-
// Swords Dance is a special case for Rapidash
|
|
1437
|
-
return {cull: moves.has('thunderwave') || moves.has('toxic') || moves.has('swordsdance')};
|
|
1438
|
-
case 'painsplit': case 'recover': case 'synthesis':
|
|
1439
|
-
return {cull: moves.has('rest') || moves.has('wish') || (move.id === 'synthesis' && moves.has('gigadrain'))};
|
|
1440
|
-
case 'roost':
|
|
1441
|
-
return {cull:
|
|
1442
|
-
moves.has('throatchop') ||
|
|
1443
|
-
// Hawlucha doesn't want Roost + 3 attacks
|
|
1444
|
-
(moves.has('stoneedge') && species.id === 'hawlucha') ||
|
|
1445
|
-
// Special cases for Salamence, Dynaless Dragonite, and Scizor to help prevent sets with poor coverage or no setup.
|
|
1446
|
-
(moves.has('dualwingbeat') && (moves.has('outrage') || species.id === 'scizor')),
|
|
1447
|
-
};
|
|
1448
|
-
case 'reflect': case 'lightscreen':
|
|
1449
|
-
return {cull: !!teamDetails.screens};
|
|
1450
|
-
case 'slackoff':
|
|
1451
|
-
// Special case to prevent Scaldless Slowking
|
|
1452
|
-
return {cull: species.id === 'slowking' && !moves.has('scald')};
|
|
1453
|
-
case 'substitute':
|
|
1454
|
-
const moveBasedCull = ['bulkup', 'nastyplot', 'painsplit', 'roost', 'swordsdance'].some(m => movePool.includes(m));
|
|
1455
|
-
// Smaller formes of Gourgeist in Doubles don't want Poltergeist as their only attack
|
|
1456
|
-
const doublesGourgeist = isDoubles && movePool.includes('powerwhip');
|
|
1457
|
-
// Calyrex wants Substitute + Leech Seed not Calm Mind + Leech Seed
|
|
1458
|
-
const calmMindCullCondition = !counter.get('recovery') && movePool.includes('calmmind') && species.id !== 'calyrex';
|
|
1459
|
-
// Eiscue wants to always have Liquidation and Belly Drum
|
|
1460
|
-
const eiscue = species.id === 'eiscue' && moves.has('zenheadbutt');
|
|
1461
|
-
return {cull: moves.has('rest') || moveBasedCull || doublesGourgeist || calmMindCullCondition || eiscue};
|
|
1462
|
-
case 'helpinghand':
|
|
1463
|
-
// Special case for Shuckle in Doubles, which doesn't want sets with no method to harm foes
|
|
1464
|
-
return {cull: moves.has('acupressure')};
|
|
1465
|
-
case 'wideguard':
|
|
1466
|
-
return {cull: moves.has('protect')};
|
|
1467
|
-
case 'grassknot':
|
|
1468
|
-
// Special case for Raichu and Heliolisk
|
|
1469
|
-
return {cull: moves.has('surf')};
|
|
1470
|
-
case 'icepunch':
|
|
1471
|
-
// Special case for Marshadow
|
|
1472
|
-
return {cull: moves.has('rocktomb')};
|
|
1473
|
-
case 'leechseed':
|
|
1474
|
-
// Special case for Calyrex to prevent Leech Seed + Calm Mind
|
|
1475
|
-
return {cull: !!counter.setupType};
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
return {cull: false};
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
shouldCullAbility(
|
|
1482
|
-
ability: string,
|
|
1483
|
-
types: Set<string>,
|
|
1484
|
-
moves: Set<string>,
|
|
1485
|
-
abilities: Set<string>,
|
|
1486
|
-
counter: MoveCounter,
|
|
1487
|
-
movePool: string[],
|
|
1488
|
-
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
1489
|
-
species: Species,
|
|
1490
|
-
isDoubles: boolean,
|
|
1491
|
-
isNoDynamax: boolean
|
|
1492
|
-
): boolean {
|
|
1493
|
-
if ([
|
|
1494
|
-
'Flare Boost', 'Hydration', 'Ice Body', 'Immunity', 'Innards Out', 'Insomnia', 'Misty Surge',
|
|
1495
|
-
'Perish Body', 'Quick Feet', 'Rain Dish', 'Snow Cloak', 'Steadfast', 'Steam Engine',
|
|
1496
|
-
].includes(ability)) return true;
|
|
1497
|
-
|
|
1498
|
-
switch (ability) {
|
|
1499
|
-
// Abilities which are primarily useful for certain moves
|
|
1500
|
-
case 'Contrary': case 'Serene Grace': case 'Skill Link': case 'Strong Jaw':
|
|
1501
|
-
return !counter.get(toID(ability));
|
|
1502
|
-
case 'Analytic':
|
|
1503
|
-
return (moves.has('rapidspin') || species.nfe || isDoubles);
|
|
1504
|
-
case 'Blaze':
|
|
1505
|
-
return (isDoubles && abilities.has('Solar Power')) || (!isDoubles && !isNoDynamax && species.id === 'charizard');
|
|
1506
|
-
// case 'Bulletproof': case 'Overcoat':
|
|
1507
|
-
// return !!counter.setupType;
|
|
1508
|
-
case 'Chlorophyll':
|
|
1509
|
-
return (species.baseStats.spe > 100 || !counter.get('Fire') && !moves.has('sunnyday') && !teamDetails.sun);
|
|
1510
|
-
case 'Cloud Nine':
|
|
1511
|
-
return (!isNoDynamax || species.id !== 'golduck');
|
|
1512
|
-
case 'Competitive':
|
|
1513
|
-
return (counter.get('Special') < 2 || (moves.has('rest') && moves.has('sleeptalk')));
|
|
1514
|
-
case 'Compound Eyes': case 'No Guard':
|
|
1515
|
-
return !counter.get('inaccurate');
|
|
1516
|
-
case 'Cursed Body':
|
|
1517
|
-
return abilities.has('Infiltrator');
|
|
1518
|
-
case 'Defiant':
|
|
1519
|
-
return !counter.get('Physical');
|
|
1520
|
-
case 'Download':
|
|
1521
|
-
return (counter.damagingMoves.size < 3 || moves.has('trick'));
|
|
1522
|
-
case 'Early Bird':
|
|
1523
|
-
return (types.has('Grass') && isDoubles);
|
|
1524
|
-
case 'Flash Fire':
|
|
1525
|
-
return (this.dex.getEffectiveness('Fire', species) < -1 || abilities.has('Drought'));
|
|
1526
|
-
case 'Gluttony':
|
|
1527
|
-
return !moves.has('bellydrum');
|
|
1528
|
-
case 'Guts':
|
|
1529
|
-
return (!moves.has('facade') && !moves.has('sleeptalk') && !species.nfe);
|
|
1530
|
-
case 'Harvest':
|
|
1531
|
-
return (abilities.has('Frisk') && !isDoubles);
|
|
1532
|
-
case 'Hustle': case 'Inner Focus':
|
|
1533
|
-
return (counter.get('Physical') < 2 || abilities.has('Iron Fist'));
|
|
1534
|
-
case 'Infiltrator':
|
|
1535
|
-
return (moves.has('rest') && moves.has('sleeptalk')) || (isDoubles && abilities.has('Clear Body'));
|
|
1536
|
-
case 'Intimidate':
|
|
1537
|
-
if (species.id === 'salamence' && moves.has('dragondance')) return true;
|
|
1538
|
-
return ['bodyslam', 'bounce', 'tripleaxel'].some(m => moves.has(m));
|
|
1539
|
-
case 'Iron Fist':
|
|
1540
|
-
return (counter.get('ironfist') < 2 || moves.has('dynamicpunch'));
|
|
1541
|
-
case 'Justified':
|
|
1542
|
-
return (isDoubles && abilities.has('Inner Focus'));
|
|
1543
|
-
case 'Lightning Rod':
|
|
1544
|
-
return (species.types.includes('Ground') || (!isNoDynamax && counter.setupType === 'Physical'));
|
|
1545
|
-
case 'Limber':
|
|
1546
|
-
return species.types.includes('Electric') || moves.has('facade');
|
|
1547
|
-
case 'Liquid Voice':
|
|
1548
|
-
return !moves.has('hypervoice');
|
|
1549
|
-
case 'Magic Guard':
|
|
1550
|
-
// For Sigilyph
|
|
1551
|
-
return (abilities.has('Tinted Lens') && !counter.get('Status') && !isDoubles);
|
|
1552
|
-
case 'Mold Breaker':
|
|
1553
|
-
return (
|
|
1554
|
-
abilities.has('Adaptability') || abilities.has('Scrappy') || (abilities.has('Unburden') && !!counter.setupType) ||
|
|
1555
|
-
(abilities.has('Sheer Force') && !!counter.get('sheerforce'))
|
|
1556
|
-
);
|
|
1557
|
-
case 'Moxie':
|
|
1558
|
-
return (counter.get('Physical') < 2 || moves.has('stealthrock') || moves.has('defog'));
|
|
1559
|
-
case 'Overgrow':
|
|
1560
|
-
return !counter.get('Grass');
|
|
1561
|
-
case 'Own Tempo':
|
|
1562
|
-
return !moves.has('petaldance');
|
|
1563
|
-
case 'Power Construct':
|
|
1564
|
-
return (species.forme === '10%' && !isDoubles);
|
|
1565
|
-
case 'Prankster':
|
|
1566
|
-
return !counter.get('Status');
|
|
1567
|
-
case 'Pressure':
|
|
1568
|
-
return (!!counter.setupType || counter.get('Status') < 2 || isDoubles);
|
|
1569
|
-
case 'Refrigerate':
|
|
1570
|
-
return !counter.get('Normal');
|
|
1571
|
-
case 'Regenerator':
|
|
1572
|
-
// For Reuniclus
|
|
1573
|
-
return abilities.has('Magic Guard');
|
|
1574
|
-
case 'Reckless':
|
|
1575
|
-
return !counter.get('recoil') || moves.has('curse');
|
|
1576
|
-
case 'Rock Head':
|
|
1577
|
-
return !counter.get('recoil');
|
|
1578
|
-
case 'Sand Force': case 'Sand Veil':
|
|
1579
|
-
return !teamDetails.sand;
|
|
1580
|
-
case 'Sand Rush':
|
|
1581
|
-
return (!teamDetails.sand && (isNoDynamax || !counter.setupType || !counter.get('Rock') || moves.has('rapidspin')));
|
|
1582
|
-
case 'Sap Sipper':
|
|
1583
|
-
// For Drampa, which wants Berserk with Roost
|
|
1584
|
-
return moves.has('roost');
|
|
1585
|
-
case 'Scrappy':
|
|
1586
|
-
return (moves.has('earthquake') && species.id === 'miltank');
|
|
1587
|
-
case 'Screen Cleaner':
|
|
1588
|
-
return !!teamDetails.screens;
|
|
1589
|
-
case 'Shed Skin':
|
|
1590
|
-
// For Scrafty
|
|
1591
|
-
return moves.has('dragondance');
|
|
1592
|
-
case 'Sheer Force':
|
|
1593
|
-
return (!counter.get('sheerforce') || abilities.has('Guts') || (species.id === 'druddigon' && !isDoubles));
|
|
1594
|
-
case 'Shell Armor':
|
|
1595
|
-
return (species.id === 'omastar' && (moves.has('spikes') || moves.has('stealthrock')));
|
|
1596
|
-
case 'Slush Rush':
|
|
1597
|
-
return (!teamDetails.hail && !abilities.has('Swift Swim'));
|
|
1598
|
-
case 'Sniper':
|
|
1599
|
-
// Inteleon wants Torrent unless it is Gmax
|
|
1600
|
-
return (species.name === 'Inteleon' || (counter.get('Water') > 1 && !moves.has('focusenergy')));
|
|
1601
|
-
case 'Solar Power':
|
|
1602
|
-
return (isNoDynamax && !teamDetails.sun);
|
|
1603
|
-
case 'Speed Boost':
|
|
1604
|
-
return (isNoDynamax && species.id === 'ninjask');
|
|
1605
|
-
case 'Steely Spirit':
|
|
1606
|
-
return (moves.has('fakeout') && !isDoubles);
|
|
1607
|
-
case 'Sturdy':
|
|
1608
|
-
return (moves.has('bulkup') || !!counter.get('recoil') || (!isNoDynamax && abilities.has('Solid Rock')));
|
|
1609
|
-
case 'Swarm':
|
|
1610
|
-
return (!counter.get('Bug') || !!counter.get('recovery'));
|
|
1611
|
-
case 'Sweet Veil':
|
|
1612
|
-
return types.has('Grass');
|
|
1613
|
-
case 'Swift Swim':
|
|
1614
|
-
if (isNoDynamax) {
|
|
1615
|
-
const neverWantsSwim = !moves.has('raindance') && [
|
|
1616
|
-
'Intimidate', 'Rock Head', 'Water Absorb',
|
|
1617
|
-
].some(m => abilities.has(m));
|
|
1618
|
-
const noSwimIfNoRain = !moves.has('raindance') && [
|
|
1619
|
-
'Cloud Nine', 'Lightning Rod', 'Intimidate', 'Rock Head', 'Sturdy', 'Water Absorb', 'Weak Armor',
|
|
1620
|
-
].some(m => abilities.has(m));
|
|
1621
|
-
return teamDetails.rain ? neverWantsSwim : noSwimIfNoRain;
|
|
1622
|
-
}
|
|
1623
|
-
return (!moves.has('raindance') && (
|
|
1624
|
-
['Intimidate', 'Rock Head', 'Slush Rush', 'Water Absorb'].some(abil => abilities.has(abil)) ||
|
|
1625
|
-
(abilities.has('Lightning Rod') && !counter.setupType)
|
|
1626
|
-
));
|
|
1627
|
-
case 'Synchronize':
|
|
1628
|
-
return counter.get('Status') < 3;
|
|
1629
|
-
case 'Technician':
|
|
1630
|
-
return (
|
|
1631
|
-
!counter.get('technician') ||
|
|
1632
|
-
moves.has('tailslap') ||
|
|
1633
|
-
abilities.has('Punk Rock') ||
|
|
1634
|
-
// For Doubles Alolan Persian
|
|
1635
|
-
movePool.includes('snarl')
|
|
1636
|
-
);
|
|
1637
|
-
case 'Tinted Lens':
|
|
1638
|
-
return (
|
|
1639
|
-
// For Sigilyph
|
|
1640
|
-
moves.has('defog') ||
|
|
1641
|
-
// For Butterfree
|
|
1642
|
-
(moves.has('hurricane') && abilities.has('Compound Eyes')) ||
|
|
1643
|
-
(counter.get('Status') > 2 && !counter.setupType)
|
|
1644
|
-
);
|
|
1645
|
-
case 'Torrent':
|
|
1646
|
-
// For Inteleon-Gmax and Primarina
|
|
1647
|
-
return (moves.has('focusenergy') || moves.has('hypervoice'));
|
|
1648
|
-
case 'Tough Claws':
|
|
1649
|
-
// For Perrserker
|
|
1650
|
-
return (types.has('Steel') && !moves.has('fakeout'));
|
|
1651
|
-
case 'Unaware':
|
|
1652
|
-
// For Swoobat and Clefable
|
|
1653
|
-
return (!!counter.setupType || moves.has('fireblast'));
|
|
1654
|
-
case 'Unburden':
|
|
1655
|
-
return (abilities.has('Prankster') || !counter.setupType && !isDoubles);
|
|
1656
|
-
case 'Volt Absorb':
|
|
1657
|
-
return (this.dex.getEffectiveness('Electric', species) < -1);
|
|
1658
|
-
case 'Water Absorb':
|
|
1659
|
-
return (
|
|
1660
|
-
moves.has('raindance') ||
|
|
1661
|
-
['Drizzle', 'Strong Jaw', 'Unaware', 'Volt Absorb'].some(abil => abilities.has(abil))
|
|
1662
|
-
);
|
|
1663
|
-
case 'Weak Armor':
|
|
1664
|
-
// The Speed less than 50 case is intended for Cursola, but could apply to any slow Pokémon.
|
|
1665
|
-
return (
|
|
1666
|
-
(!isNoDynamax && species.baseStats.spe > 50) ||
|
|
1667
|
-
species.id === 'skarmory' ||
|
|
1668
|
-
moves.has('shellsmash') || moves.has('rapidspin')
|
|
1669
|
-
);
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
return false;
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
getHighPriorityItem(
|
|
1676
|
-
ability: string,
|
|
1677
|
-
types: Set<string>,
|
|
1678
|
-
moves: Set<string>,
|
|
1679
|
-
counter: MoveCounter,
|
|
1680
|
-
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
1681
|
-
species: Species,
|
|
1682
|
-
isLead: boolean,
|
|
1683
|
-
isDoubles: boolean
|
|
1684
|
-
) {
|
|
1685
|
-
// not undefined — we want "no item" not "go find a different item"
|
|
1686
|
-
if (moves.has('acrobatics') && ability !== 'Ripen') return ability === 'Grassy Surge' ? 'Grassy Seed' : '';
|
|
1687
|
-
if (moves.has('geomancy') || moves.has('meteorbeam')) return 'Power Herb';
|
|
1688
|
-
if (moves.has('shellsmash')) {
|
|
1689
|
-
if (ability === 'Sturdy' && !isLead && !isDoubles) return 'Heavy-Duty Boots';
|
|
1690
|
-
// Shell Smash + Solid Rock is intended for Carracosta, but I think
|
|
1691
|
-
// any Pokémon which can take a SE hit via Solid Rock deserves to have
|
|
1692
|
-
// its Shell Smash considered a good enough speed setup move for WP.
|
|
1693
|
-
if (ability === 'Solid Rock') return 'Weakness Policy';
|
|
1694
|
-
return 'White Herb';
|
|
1695
|
-
}
|
|
1696
|
-
// Techno Blast should always be Water-type
|
|
1697
|
-
if (moves.has('technoblast')) return 'Douse Drive';
|
|
1698
|
-
// Species-specific logic
|
|
1699
|
-
if (
|
|
1700
|
-
['Corsola', 'Garchomp', 'Tangrowth'].includes(species.name) &&
|
|
1701
|
-
counter.get('Status') &&
|
|
1702
|
-
!counter.setupType &&
|
|
1703
|
-
!isDoubles
|
|
1704
|
-
) return 'Rocky Helmet';
|
|
1705
|
-
|
|
1706
|
-
if (species.name === 'Eternatus' && counter.get('Status') < 2) return 'Metronome';
|
|
1707
|
-
if (species.name === 'Farfetch\u2019d') return 'Leek';
|
|
1708
|
-
if (species.name === 'Froslass' && !isDoubles) return 'Wide Lens';
|
|
1709
|
-
if (species.name === 'Latios' && counter.get('Special') === 2 && !isDoubles) return 'Soul Dew';
|
|
1710
|
-
if (species.name === 'Lopunny') return isDoubles ? 'Iron Ball' : 'Toxic Orb';
|
|
1711
|
-
if (species.baseSpecies === 'Marowak') return 'Thick Club';
|
|
1712
|
-
if (species.baseSpecies === 'Pikachu') return 'Light Ball';
|
|
1713
|
-
if (species.name === 'Regieleki' && !isDoubles) return 'Magnet';
|
|
1714
|
-
if (species.name === 'Shedinja') {
|
|
1715
|
-
const noSash = !teamDetails.defog && !teamDetails.rapidSpin && !isDoubles;
|
|
1716
|
-
return noSash ? 'Heavy-Duty Boots' : 'Focus Sash';
|
|
1717
|
-
}
|
|
1718
|
-
if (species.name === 'Shuckle' && moves.has('stickyweb')) return 'Mental Herb';
|
|
1719
|
-
if (species.name === 'Unfezant' || moves.has('focusenergy')) return 'Scope Lens';
|
|
1720
|
-
if (species.name === 'Pincurchin') return 'Shuca Berry';
|
|
1721
|
-
if (species.name === 'Wobbuffet' && moves.has('destinybond')) return 'Custap Berry';
|
|
1722
|
-
if (species.name === 'Scyther' && counter.damagingMoves.size > 3) return 'Choice Band';
|
|
1723
|
-
if (species.name === 'Cinccino' && !moves.has('uturn')) return 'Life Orb';
|
|
1724
|
-
if (moves.has('bellydrum') && moves.has('substitute')) return 'Salac Berry';
|
|
1725
|
-
|
|
1726
|
-
// Misc item generation logic
|
|
1727
|
-
const HDBBetterThanEviolite = (
|
|
1728
|
-
!isLead &&
|
|
1729
|
-
this.dex.getEffectiveness('Rock', species) >= 2 &&
|
|
1730
|
-
!isDoubles
|
|
1731
|
-
);
|
|
1732
|
-
if (species.nfe && !HDBBetterThanEviolite) return 'Eviolite';
|
|
1733
|
-
|
|
1734
|
-
// Ability based logic and miscellaneous logic
|
|
1735
|
-
if (species.name === 'Wobbuffet' || ['Cheek Pouch', 'Harvest', 'Ripen'].includes(ability)) return 'Sitrus Berry';
|
|
1736
|
-
if (ability === 'Gluttony') return this.sample(['Aguav', 'Figy', 'Iapapa', 'Mago', 'Wiki']) + ' Berry';
|
|
1737
|
-
if (
|
|
1738
|
-
ability === 'Imposter' ||
|
|
1739
|
-
(ability === 'Magnet Pull' && moves.has('bodypress') && !isDoubles)
|
|
1740
|
-
) return 'Choice Scarf';
|
|
1741
|
-
if (
|
|
1742
|
-
ability === 'Guts' &&
|
|
1743
|
-
(counter.get('Physical') > 2 || isDoubles)
|
|
1744
|
-
) {
|
|
1745
|
-
return types.has('Fire') ? 'Toxic Orb' : 'Flame Orb';
|
|
1746
|
-
}
|
|
1747
|
-
if (ability === 'Magic Guard' && counter.damagingMoves.size > 1) {
|
|
1748
|
-
return moves.has('counter') ? 'Focus Sash' : 'Life Orb';
|
|
1749
|
-
}
|
|
1750
|
-
if (ability === 'Sheer Force' && counter.get('sheerforce')) return 'Life Orb';
|
|
1751
|
-
if (ability === 'Unburden') return (moves.has('closecombat') || moves.has('curse')) ? 'White Herb' : 'Sitrus Berry';
|
|
1752
|
-
|
|
1753
|
-
if (moves.has('trick') || (moves.has('switcheroo') && !isDoubles) || ability === 'Gorilla Tactics') {
|
|
1754
|
-
if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && !counter.get('priority') && ability !== 'Triage') {
|
|
1755
|
-
return 'Choice Scarf';
|
|
1756
|
-
} else {
|
|
1757
|
-
return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
if (moves.has('auroraveil') || moves.has('lightscreen') && moves.has('reflect')) return 'Light Clay';
|
|
1761
|
-
if (moves.has('rest') && !moves.has('sleeptalk') && ability !== 'Shed Skin') return 'Chesto Berry';
|
|
1762
|
-
if (moves.has('hypnosis') && ability === 'Beast Boost') return 'Blunder Policy';
|
|
1763
|
-
if (moves.has('bellydrum')) return 'Sitrus Berry';
|
|
1764
|
-
|
|
1765
|
-
if (this.dex.getEffectiveness('Rock', species) >= 2 && !isDoubles) {
|
|
1766
|
-
return 'Heavy-Duty Boots';
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
/** Item generation specific to Random Doubles */
|
|
1771
|
-
getDoublesItem(
|
|
1772
|
-
ability: string,
|
|
1773
|
-
types: Set<string>,
|
|
1774
|
-
moves: Set<string>,
|
|
1775
|
-
abilities: Set<string>,
|
|
1776
|
-
counter: MoveCounter,
|
|
1777
|
-
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
1778
|
-
species: Species,
|
|
1779
|
-
) {
|
|
1780
|
-
const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
|
|
1781
|
-
|
|
1782
|
-
if (
|
|
1783
|
-
(['dragonenergy', 'eruption', 'waterspout'].some(m => moves.has(m))) &&
|
|
1784
|
-
counter.damagingMoves.size >= 4
|
|
1785
|
-
) return 'Choice Scarf';
|
|
1786
|
-
if (moves.has('blizzard') && ability !== 'Snow Warning' && !teamDetails.hail) return 'Blunder Policy';
|
|
1787
|
-
if (this.dex.getEffectiveness('Rock', species) >= 2 && !types.has('Flying')) return 'Heavy-Duty Boots';
|
|
1788
|
-
if (counter.get('Physical') >= 4 && ['fakeout', 'feint', 'rapidspin', 'suckerpunch'].every(m => !moves.has(m)) && (
|
|
1789
|
-
types.has('Dragon') || types.has('Fighting') || types.has('Rock') ||
|
|
1790
|
-
moves.has('flipturn') || moves.has('uturn')
|
|
1791
|
-
)) {
|
|
1792
|
-
return (
|
|
1793
|
-
!counter.get('priority') && !abilities.has('Speed Boost') &&
|
|
1794
|
-
species.baseStats.spe >= 60 && species.baseStats.spe <= 100 &&
|
|
1795
|
-
this.randomChance(1, 2)
|
|
1796
|
-
) ? 'Choice Scarf' : 'Choice Band';
|
|
1797
|
-
}
|
|
1798
|
-
if (
|
|
1799
|
-
(
|
|
1800
|
-
counter.get('Special') >= 4 &&
|
|
1801
|
-
(types.has('Dragon') || types.has('Fighting') || types.has('Rock') || moves.has('voltswitch'))
|
|
1802
|
-
) || (
|
|
1803
|
-
(counter.get('Special') >= 3 && (moves.has('flipturn') || moves.has('uturn'))) &&
|
|
1804
|
-
!moves.has('acidspray') && !moves.has('electroweb')
|
|
1805
|
-
)
|
|
1806
|
-
) {
|
|
1807
|
-
return (
|
|
1808
|
-
species.baseStats.spe >= 60 && species.baseStats.spe <= 100 && this.randomChance(1, 2)
|
|
1809
|
-
) ? 'Choice Scarf' : 'Choice Specs';
|
|
1810
|
-
}
|
|
1811
|
-
// This one is intentionally below the Choice item checks.
|
|
1812
|
-
if ((defensiveStatTotal < 250 && ability === 'Regenerator') || species.name === 'Pheromosa') return 'Life Orb';
|
|
1813
|
-
if (counter.damagingMoves.size >= 4 && defensiveStatTotal >= 275) return 'Assault Vest';
|
|
1814
|
-
if (
|
|
1815
|
-
counter.damagingMoves.size >= 3 &&
|
|
1816
|
-
species.baseStats.spe >= 60 &&
|
|
1817
|
-
ability !== 'Multiscale' && ability !== 'Sturdy' &&
|
|
1818
|
-
[
|
|
1819
|
-
'acidspray', 'clearsmog', 'electroweb', 'fakeout', 'feint', 'icywind',
|
|
1820
|
-
'incinerate', 'naturesmadness', 'rapidspin', 'snarl', 'uturn',
|
|
1821
|
-
].every(m => !moves.has(m))
|
|
1822
|
-
) return (ability === 'Defeatist' || defensiveStatTotal >= 275) ? 'Sitrus Berry' : 'Life Orb';
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
getMediumPriorityItem(
|
|
1826
|
-
ability: string,
|
|
1827
|
-
moves: Set<string>,
|
|
1828
|
-
counter: MoveCounter,
|
|
1829
|
-
species: Species,
|
|
1830
|
-
isLead: boolean,
|
|
1831
|
-
isDoubles: boolean,
|
|
1832
|
-
isNoDynamax: boolean
|
|
1833
|
-
): string | undefined {
|
|
1834
|
-
const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
|
|
1835
|
-
|
|
1836
|
-
// Choice items
|
|
1837
|
-
if (
|
|
1838
|
-
!isDoubles && counter.get('Physical') >= 4 && ability !== 'Serene Grace' &&
|
|
1839
|
-
['fakeout', 'flamecharge', 'rapidspin'].every(m => !moves.has(m))
|
|
1840
|
-
) {
|
|
1841
|
-
const scarfReqs = (
|
|
1842
|
-
(species.baseStats.atk >= 100 || ability === 'Huge Power') &&
|
|
1843
|
-
species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
|
|
1844
|
-
ability !== 'Speed Boost' && !counter.get('priority') &&
|
|
1845
|
-
(isNoDynamax || ['bounce', 'dualwingbeat'].every(m => !moves.has(m)))
|
|
1846
|
-
);
|
|
1847
|
-
return (scarfReqs && this.randomChance(2, 3)) ? 'Choice Scarf' : 'Choice Band';
|
|
1848
|
-
}
|
|
1849
|
-
if (!isDoubles && (
|
|
1850
|
-
(counter.get('Special') >= 4 && !moves.has('futuresight')) ||
|
|
1851
|
-
(counter.get('Special') >= 3 && ['flipturn', 'partingshot', 'uturn'].some(m => moves.has(m)))
|
|
1852
|
-
)) {
|
|
1853
|
-
const scarfReqs = (
|
|
1854
|
-
species.baseStats.spa >= 100 &&
|
|
1855
|
-
species.baseStats.spe >= 60 && species.baseStats.spe <= 108 &&
|
|
1856
|
-
ability !== 'Tinted Lens' && !counter.get('Physical')
|
|
1857
|
-
);
|
|
1858
|
-
return (scarfReqs && this.randomChance(2, 3)) ? 'Choice Scarf' : 'Choice Specs';
|
|
1859
|
-
}
|
|
1860
|
-
if (
|
|
1861
|
-
!isDoubles &&
|
|
1862
|
-
counter.get('Physical') >= 3 &&
|
|
1863
|
-
!moves.has('rapidspin') &&
|
|
1864
|
-
['copycat', 'memento', 'partingshot'].some(m => moves.has(m))
|
|
1865
|
-
) return 'Choice Band';
|
|
1866
|
-
if (
|
|
1867
|
-
!isDoubles &&
|
|
1868
|
-
((counter.get('Physical') >= 3 && moves.has('defog')) || (counter.get('Special') >= 3 && moves.has('healingwish'))) &&
|
|
1869
|
-
!counter.get('priority') && !moves.has('uturn')
|
|
1870
|
-
) return 'Choice Scarf';
|
|
1871
|
-
|
|
1872
|
-
// Palkia sometimes wants Choice items instead
|
|
1873
|
-
if (species.name === 'Palkia') return 'Lustrous Orb';
|
|
1874
|
-
|
|
1875
|
-
// Other items
|
|
1876
|
-
if (
|
|
1877
|
-
moves.has('raindance') || moves.has('sunnyday') ||
|
|
1878
|
-
(ability === 'Speed Boost' && !counter.get('hazards')) ||
|
|
1879
|
-
(ability === 'Stance Change' && counter.damagingMoves.size >= 3)
|
|
1880
|
-
) return 'Life Orb';
|
|
1881
|
-
if (
|
|
1882
|
-
!isDoubles &&
|
|
1883
|
-
this.dex.getEffectiveness('Rock', species) >= 1 && (
|
|
1884
|
-
['Defeatist', 'Emergency Exit', 'Multiscale'].includes(ability) ||
|
|
1885
|
-
['courtchange', 'defog', 'rapidspin'].some(m => moves.has(m))
|
|
1886
|
-
)
|
|
1887
|
-
) return 'Heavy-Duty Boots';
|
|
1888
|
-
if (species.name === 'Necrozma-Dusk-Mane' || (
|
|
1889
|
-
this.dex.getEffectiveness('Ground', species) < 2 &&
|
|
1890
|
-
counter.get('speedsetup') &&
|
|
1891
|
-
counter.damagingMoves.size >= 3 &&
|
|
1892
|
-
defensiveStatTotal >= 300
|
|
1893
|
-
)) return 'Weakness Policy';
|
|
1894
|
-
if (counter.damagingMoves.size >= 4 && defensiveStatTotal >= 235) return 'Assault Vest';
|
|
1895
|
-
if (
|
|
1896
|
-
['clearsmog', 'curse', 'haze', 'healbell', 'protect', 'sleeptalk', 'strangesteam'].some(m => moves.has(m)) &&
|
|
1897
|
-
(ability === 'Moody' || !isDoubles)
|
|
1898
|
-
) return 'Leftovers';
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
getLowPriorityItem(
|
|
1902
|
-
ability: string,
|
|
1903
|
-
types: Set<string>,
|
|
1904
|
-
moves: Set<string>,
|
|
1905
|
-
abilities: Set<string>,
|
|
1906
|
-
counter: MoveCounter,
|
|
1907
|
-
teamDetails: RandomTeamsTypes.TeamDetails,
|
|
1908
|
-
species: Species,
|
|
1909
|
-
isLead: boolean,
|
|
1910
|
-
isDoubles: boolean,
|
|
1911
|
-
isNoDynamax: boolean
|
|
1912
|
-
): string | undefined {
|
|
1913
|
-
const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
|
|
1914
|
-
|
|
1915
|
-
if (
|
|
1916
|
-
isLead && !isDoubles &&
|
|
1917
|
-
!['Disguise', 'Sturdy'].includes(ability) && !moves.has('substitute') &&
|
|
1918
|
-
!counter.get('drain') && !counter.get('recoil') && !counter.get('recovery') &&
|
|
1919
|
-
((defensiveStatTotal <= 250 && counter.get('hazards')) || defensiveStatTotal <= 210)
|
|
1920
|
-
) return 'Focus Sash';
|
|
1921
|
-
if (
|
|
1922
|
-
moves.has('clangoroussoul') ||
|
|
1923
|
-
// We manually check for speed-boosting moves, rather than using `counter.get('speedsetup')`,
|
|
1924
|
-
// because we want to check for ANY speed boosting move.
|
|
1925
|
-
// In particular, Shift Gear + Boomburst Toxtricity should get Throat Spray.
|
|
1926
|
-
(moves.has('boomburst') && Array.from(moves).some(m => this.dex.moves.get(m).boosts?.spe))
|
|
1927
|
-
) return 'Throat Spray';
|
|
1928
|
-
|
|
1929
|
-
const rockWeaknessCase = (
|
|
1930
|
-
this.dex.getEffectiveness('Rock', species) >= 1 &&
|
|
1931
|
-
(!teamDetails.defog || ability === 'Intimidate' || moves.has('uturn') || moves.has('voltswitch'))
|
|
1932
|
-
);
|
|
1933
|
-
const spinnerCase = (moves.has('rapidspin') && (ability === 'Regenerator' || !!counter.get('recovery')));
|
|
1934
|
-
// Glalie prefers Leftovers
|
|
1935
|
-
if (!isDoubles && (rockWeaknessCase || spinnerCase) && species.id !== 'glalie') return 'Heavy-Duty Boots';
|
|
1936
|
-
|
|
1937
|
-
if (
|
|
1938
|
-
!isDoubles && this.dex.getEffectiveness('Ground', species) >= 2 && !types.has('Poison') &&
|
|
1939
|
-
ability !== 'Levitate' && !abilities.has('Iron Barbs')
|
|
1940
|
-
) return 'Air Balloon';
|
|
1941
|
-
if (
|
|
1942
|
-
!isDoubles &&
|
|
1943
|
-
counter.damagingMoves.size >= 3 &&
|
|
1944
|
-
!counter.get('damage') &&
|
|
1945
|
-
ability !== 'Sturdy' &&
|
|
1946
|
-
(species.baseStats.spe >= 90 || !moves.has('voltswitch')) &&
|
|
1947
|
-
['foulplay', 'rapidspin', 'substitute', 'uturn'].every(m => !moves.has(m)) && (
|
|
1948
|
-
counter.get('speedsetup') ||
|
|
1949
|
-
// No Dynamax Buzzwole doesn't want Life Orb with Bulk Up + 3 attacks
|
|
1950
|
-
(counter.get('drain') && (!isNoDynamax || species.id !== 'buzzwole' || moves.has('roost'))) ||
|
|
1951
|
-
moves.has('trickroom') || moves.has('psystrike') ||
|
|
1952
|
-
(species.baseStats.spe > 40 && defensiveStatTotal < 275)
|
|
1953
|
-
)
|
|
1954
|
-
) return 'Life Orb';
|
|
1955
|
-
if (
|
|
1956
|
-
!isDoubles &&
|
|
1957
|
-
counter.damagingMoves.size >= 4 &&
|
|
1958
|
-
!counter.get('Dragon') &&
|
|
1959
|
-
!counter.get('Normal')
|
|
1960
|
-
) {
|
|
1961
|
-
return 'Expert Belt';
|
|
1962
|
-
}
|
|
1963
|
-
if (
|
|
1964
|
-
!isDoubles &&
|
|
1965
|
-
!moves.has('substitute') &&
|
|
1966
|
-
(moves.has('dragondance') || moves.has('swordsdance')) &&
|
|
1967
|
-
(moves.has('outrage') || (
|
|
1968
|
-
['Bug', 'Fire', 'Ground', 'Normal', 'Poison'].every(type => !types.has(type)) &&
|
|
1969
|
-
!['Pastel Veil', 'Storm Drain'].includes(ability)
|
|
1970
|
-
))
|
|
1971
|
-
) return 'Lum Berry';
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
randomSet(
|
|
1975
|
-
species: string | Species,
|
|
1976
|
-
teamDetails: RandomTeamsTypes.TeamDetails = {},
|
|
1977
|
-
isLead = false,
|
|
1978
|
-
isDoubles = false,
|
|
1979
|
-
isNoDynamax = false
|
|
1980
|
-
): RandomTeamsTypes.RandomSet {
|
|
1981
|
-
species = this.dex.species.get(species);
|
|
1982
|
-
let forme = species.name;
|
|
1983
|
-
let gmax = false;
|
|
1984
|
-
|
|
1985
|
-
if (typeof species.battleOnly === 'string') {
|
|
1986
|
-
// Only change the forme. The species has custom moves, and may have different typing and requirements.
|
|
1987
|
-
forme = species.battleOnly;
|
|
1988
|
-
}
|
|
1989
|
-
if (species.cosmeticFormes) {
|
|
1990
|
-
forme = this.sample([species.name].concat(species.cosmeticFormes));
|
|
1991
|
-
}
|
|
1992
|
-
if (species.name.endsWith('-Gmax')) {
|
|
1993
|
-
forme = species.name.slice(0, -5);
|
|
1994
|
-
gmax = true;
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
const randMoves =
|
|
1998
|
-
(isDoubles && species.randomDoubleBattleMoves) ||
|
|
1999
|
-
(isNoDynamax && species.randomBattleNoDynamaxMoves) ||
|
|
2000
|
-
species.randomBattleMoves;
|
|
2001
|
-
const movePool = (randMoves || Object.keys(this.dex.species.getLearnset(species.id)!)).slice();
|
|
2002
|
-
if (this.format.gameType === 'multi' || this.format.gameType === 'freeforall') {
|
|
2003
|
-
// Random Multi Battle uses doubles move pools, but Ally Switch fails in multi battles
|
|
2004
|
-
// Random Free-For-All also uses doubles move pools, for now
|
|
2005
|
-
const allySwitch = movePool.indexOf('allyswitch');
|
|
2006
|
-
if (allySwitch > -1) {
|
|
2007
|
-
if (movePool.length > this.maxMoveCount) {
|
|
2008
|
-
this.fastPop(movePool, allySwitch);
|
|
2009
|
-
} else {
|
|
2010
|
-
// Ideally, we'll never get here, but better to have a move that usually does nothing than one that always does
|
|
2011
|
-
movePool[allySwitch] = 'sleeptalk';
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
const rejectedPool = [];
|
|
2016
|
-
let ability = '';
|
|
2017
|
-
let item = undefined;
|
|
2018
|
-
|
|
2019
|
-
const evs = {hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85};
|
|
2020
|
-
const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
|
2021
|
-
|
|
2022
|
-
const types = new Set(species.types);
|
|
2023
|
-
const abilities = new Set(Object.values(species.abilities));
|
|
2024
|
-
if (species.unreleasedHidden) abilities.delete(species.abilities.H);
|
|
2025
|
-
|
|
2026
|
-
const moves = new Set<string>();
|
|
2027
|
-
let counter: MoveCounter;
|
|
2028
|
-
// This is just for BDSP Unown;
|
|
2029
|
-
// it can be removed from this file if BDSP gets its own random-teams file in the future.
|
|
2030
|
-
let hasHiddenPower = false;
|
|
2031
|
-
|
|
2032
|
-
do {
|
|
2033
|
-
// Choose next 4 moves from learnset/viable moves and add them to moves list:
|
|
2034
|
-
const pool = (movePool.length ? movePool : rejectedPool);
|
|
2035
|
-
while (moves.size < this.maxMoveCount && pool.length) {
|
|
2036
|
-
const moveid = this.sampleNoReplace(pool);
|
|
2037
|
-
if (moveid.startsWith('hiddenpower')) {
|
|
2038
|
-
if (hasHiddenPower) continue;
|
|
2039
|
-
hasHiddenPower = true;
|
|
2040
|
-
}
|
|
2041
|
-
moves.add(moveid);
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
counter = this.queryMoves(moves, species.types, abilities, movePool);
|
|
2045
|
-
const runEnforcementChecker = (checkerName: string) => {
|
|
2046
|
-
if (!this.moveEnforcementCheckers[checkerName]) return false;
|
|
2047
|
-
return this.moveEnforcementCheckers[checkerName](
|
|
2048
|
-
movePool, moves, abilities, types, counter, species as Species, teamDetails
|
|
2049
|
-
);
|
|
2050
|
-
};
|
|
2051
|
-
|
|
2052
|
-
// Iterate through the moves again, this time to cull them:
|
|
2053
|
-
for (const moveid of moves) {
|
|
2054
|
-
const move = this.dex.moves.get(moveid);
|
|
2055
|
-
let {cull, isSetup} = this.shouldCullMove(
|
|
2056
|
-
move, types, moves, abilities, counter,
|
|
2057
|
-
movePool, teamDetails, species, isLead, isDoubles, isNoDynamax
|
|
2058
|
-
);
|
|
2059
|
-
|
|
2060
|
-
if (move.id !== 'photongeyser' && (
|
|
2061
|
-
(move.category === 'Physical' && counter.setupType === 'Special') ||
|
|
2062
|
-
(move.category === 'Special' && counter.setupType === 'Physical')
|
|
2063
|
-
)) {
|
|
2064
|
-
// Reject STABs last in case the setup type changes later on
|
|
2065
|
-
const stabs = counter.get(species.types[0]) + (species.types[1] ? counter.get(species.types[1]) : 0);
|
|
2066
|
-
if (!types.has(move.type) || stabs > 1 || counter.get(move.category) < 2) cull = true;
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
// Pokemon should have moves that benefit their types, stats, or ability
|
|
2070
|
-
const isLowBP = move.basePower && move.basePower < 50;
|
|
2071
|
-
|
|
2072
|
-
// Genesect-Douse should never reject Techno Blast
|
|
2073
|
-
const moveIsRejectable = (
|
|
2074
|
-
!(species.id === 'genesectdouse' && move.id === 'technoblast') &&
|
|
2075
|
-
!(species.id === 'togekiss' && move.id === 'nastyplot') && (
|
|
2076
|
-
move.category === 'Status' ||
|
|
2077
|
-
(!types.has(move.type) && move.id !== 'judgment') ||
|
|
2078
|
-
(isLowBP && !move.multihit && !abilities.has('Technician'))
|
|
2079
|
-
)
|
|
2080
|
-
);
|
|
2081
|
-
// Setup-supported moves should only be rejected under specific circumstances
|
|
2082
|
-
const notImportantSetup = (
|
|
2083
|
-
!counter.setupType ||
|
|
2084
|
-
counter.setupType === 'Mixed' ||
|
|
2085
|
-
(counter.get(counter.setupType) + counter.get('Status') > 3 && !counter.get('hazards')) ||
|
|
2086
|
-
(move.category !== counter.setupType && move.category !== 'Status')
|
|
2087
|
-
);
|
|
2088
|
-
|
|
2089
|
-
if (moveIsRejectable && (
|
|
2090
|
-
!cull && !isSetup && !move.weather && !move.stallingMove && notImportantSetup && !move.damage &&
|
|
2091
|
-
(isDoubles ? this.unrejectableMovesInDoubles(move) : this.unrejectableMovesInSingles(move))
|
|
2092
|
-
)) {
|
|
2093
|
-
// There may be more important moves that this Pokemon needs
|
|
2094
|
-
if (
|
|
2095
|
-
// Pokemon should have at least one STAB move
|
|
2096
|
-
(!counter.get('stab') && counter.get('physicalpool') + counter.get('specialpool') > 0 && move.id !== 'stickyweb') ||
|
|
2097
|
-
// Swords Dance Mew should have Brave Bird
|
|
2098
|
-
(moves.has('swordsdance') && species.id === 'mew' && runEnforcementChecker('Flying')) ||
|
|
2099
|
-
// Dhelmise should have Anchor Shot
|
|
2100
|
-
(abilities.has('Steelworker') && runEnforcementChecker('Steel')) ||
|
|
2101
|
-
// Check for miscellaneous important moves
|
|
2102
|
-
(!isDoubles && runEnforcementChecker('recovery') && move.id !== 'stickyweb') ||
|
|
2103
|
-
runEnforcementChecker('screens') ||
|
|
2104
|
-
runEnforcementChecker('misc') ||
|
|
2105
|
-
(isLead && runEnforcementChecker('lead')) ||
|
|
2106
|
-
(moves.has('leechseed') && runEnforcementChecker('leechseed'))
|
|
2107
|
-
) {
|
|
2108
|
-
cull = true;
|
|
2109
|
-
// Pokemon should have moves that benefit their typing
|
|
2110
|
-
// Don't cull Sticky Web in type-based enforcement, and make sure Azumarill always has Aqua Jet
|
|
2111
|
-
} else if (move.id !== 'stickyweb' && !(species.id === 'azumarill' && move.id === 'aquajet')) {
|
|
2112
|
-
for (const type of types) {
|
|
2113
|
-
if (runEnforcementChecker(type)) {
|
|
2114
|
-
cull = true;
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
// Sleep Talk shouldn't be selected without Rest
|
|
2121
|
-
if (move.id === 'rest' && cull) {
|
|
2122
|
-
const sleeptalk = movePool.indexOf('sleeptalk');
|
|
2123
|
-
if (sleeptalk >= 0) {
|
|
2124
|
-
if (movePool.length < 2) {
|
|
2125
|
-
cull = false;
|
|
2126
|
-
} else {
|
|
2127
|
-
this.fastPop(movePool, sleeptalk);
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
// Remove rejected moves from the move list
|
|
2133
|
-
if (cull && movePool.length) {
|
|
2134
|
-
if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
|
|
2135
|
-
if (move.category !== 'Status' && !move.damage) rejectedPool.push(moveid);
|
|
2136
|
-
moves.delete(moveid);
|
|
2137
|
-
break;
|
|
2138
|
-
}
|
|
2139
|
-
if (cull && rejectedPool.length) {
|
|
2140
|
-
if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
|
|
2141
|
-
moves.delete(moveid);
|
|
2142
|
-
break;
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
} while (moves.size < this.maxMoveCount && (movePool.length || rejectedPool.length));
|
|
2146
|
-
|
|
2147
|
-
// for BD/SP only
|
|
2148
|
-
if (hasHiddenPower) {
|
|
2149
|
-
let hpType;
|
|
2150
|
-
for (const move of moves) {
|
|
2151
|
-
if (move.startsWith('hiddenpower')) hpType = move.substr(11);
|
|
2152
|
-
}
|
|
2153
|
-
if (!hpType) throw new Error(`hasHiddenPower is true, but no Hidden Power move was found.`);
|
|
2154
|
-
const HPivs = this.dex.types.get(hpType).HPivs;
|
|
2155
|
-
let iv: StatID;
|
|
2156
|
-
for (iv in HPivs) {
|
|
2157
|
-
ivs[iv] = HPivs[iv]!;
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
const abilityData = Array.from(abilities).map(a => this.dex.abilities.get(a));
|
|
2162
|
-
Utils.sortBy(abilityData, abil => -abil.rating);
|
|
2163
|
-
|
|
2164
|
-
if (abilityData[1]) {
|
|
2165
|
-
// Sort abilities by rating with an element of randomness
|
|
2166
|
-
if (abilityData[2] && abilityData[1].rating <= abilityData[2].rating && this.randomChance(1, 2)) {
|
|
2167
|
-
[abilityData[1], abilityData[2]] = [abilityData[2], abilityData[1]];
|
|
2168
|
-
}
|
|
2169
|
-
if (abilityData[0].rating <= abilityData[1].rating) {
|
|
2170
|
-
if (this.randomChance(1, 2)) [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
|
|
2171
|
-
} else if (abilityData[0].rating - 0.6 <= abilityData[1].rating) {
|
|
2172
|
-
if (this.randomChance(2, 3)) [abilityData[0], abilityData[1]] = [abilityData[1], abilityData[0]];
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
// Start with the first abiility and work our way through, culling as we go
|
|
2176
|
-
ability = abilityData[0].name;
|
|
2177
|
-
let rejectAbility = false;
|
|
2178
|
-
do {
|
|
2179
|
-
rejectAbility = this.shouldCullAbility(
|
|
2180
|
-
ability, types, moves, abilities, counter, movePool, teamDetails, species, isDoubles, isNoDynamax
|
|
2181
|
-
);
|
|
2182
|
-
|
|
2183
|
-
if (rejectAbility) {
|
|
2184
|
-
// Lopunny, and other Facade users, don't want Limber, even if other abilities are poorly rated,
|
|
2185
|
-
// since paralysis would arguably be good for them.
|
|
2186
|
-
const limberFacade = moves.has('facade') && ability === 'Limber';
|
|
2187
|
-
|
|
2188
|
-
if (ability === abilityData[0].name && (abilityData[1].rating >= 1 || limberFacade)) {
|
|
2189
|
-
ability = abilityData[1].name;
|
|
2190
|
-
} else if (ability === abilityData[1].name && abilityData[2] && (abilityData[2].rating >= 1 || limberFacade)) {
|
|
2191
|
-
ability = abilityData[2].name;
|
|
2192
|
-
} else {
|
|
2193
|
-
// Default to the highest rated ability if all are rejected
|
|
2194
|
-
ability = abilityData[0].name;
|
|
2195
|
-
rejectAbility = false;
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
} while (rejectAbility);
|
|
2199
|
-
|
|
2200
|
-
// Hardcoded abilities for certain contexts
|
|
2201
|
-
if (forme === 'Copperajah' && gmax) {
|
|
2202
|
-
ability = 'Heavy Metal';
|
|
2203
|
-
} else if (abilities.has('Guts') && (
|
|
2204
|
-
species.id === 'gurdurr' || species.id === 'throh' ||
|
|
2205
|
-
moves.has('facade') || (moves.has('rest') && moves.has('sleeptalk'))
|
|
2206
|
-
)) {
|
|
2207
|
-
ability = 'Guts';
|
|
2208
|
-
} else if (abilities.has('Moxie') && (counter.get('Physical') > 3 || moves.has('bounce')) && !isDoubles) {
|
|
2209
|
-
ability = 'Moxie';
|
|
2210
|
-
} else if (isDoubles) {
|
|
2211
|
-
if (abilities.has('Competitive') && ability !== 'Shadow Tag' && ability !== 'Strong Jaw') ability = 'Competitive';
|
|
2212
|
-
if (abilities.has('Friend Guard')) ability = 'Friend Guard';
|
|
2213
|
-
if (abilities.has('Gluttony') && moves.has('recycle')) ability = 'Gluttony';
|
|
2214
|
-
if (abilities.has('Guts')) ability = 'Guts';
|
|
2215
|
-
if (abilities.has('Harvest')) ability = 'Harvest';
|
|
2216
|
-
if (abilities.has('Healer') && (
|
|
2217
|
-
abilities.has('Natural Cure') ||
|
|
2218
|
-
(abilities.has('Aroma Veil') && this.randomChance(1, 2))
|
|
2219
|
-
)) ability = 'Healer';
|
|
2220
|
-
if (abilities.has('Intimidate')) ability = 'Intimidate';
|
|
2221
|
-
if (abilities.has('Klutz') && ability === 'Limber') ability = 'Klutz';
|
|
2222
|
-
if (abilities.has('Magic Guard') && ability !== 'Friend Guard' && ability !== 'Unaware') ability = 'Magic Guard';
|
|
2223
|
-
if (abilities.has('Ripen')) ability = 'Ripen';
|
|
2224
|
-
if (abilities.has('Stalwart')) ability = 'Stalwart';
|
|
2225
|
-
if (abilities.has('Storm Drain')) ability = 'Storm Drain';
|
|
2226
|
-
if (abilities.has('Telepathy') && (ability === 'Pressure' || abilities.has('Analytic'))) ability = 'Telepathy';
|
|
2227
|
-
}
|
|
2228
|
-
} else {
|
|
2229
|
-
ability = abilityData[0].name;
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
// item = !isDoubles ? 'Leftovers' : 'Sitrus Berry';
|
|
2233
|
-
if (species.requiredItems) {
|
|
2234
|
-
item = this.sample(species.requiredItems);
|
|
2235
|
-
// First, the extra high-priority items
|
|
2236
|
-
} else {
|
|
2237
|
-
item = this.getHighPriorityItem(ability, types, moves, counter, teamDetails, species, isLead, isDoubles);
|
|
2238
|
-
if (item === undefined && isDoubles) {
|
|
2239
|
-
item = this.getDoublesItem(ability, types, moves, abilities, counter, teamDetails, species);
|
|
2240
|
-
}
|
|
2241
|
-
if (item === undefined) {
|
|
2242
|
-
item = this.getMediumPriorityItem(ability, moves, counter, species, isLead, isDoubles, isNoDynamax);
|
|
2243
|
-
}
|
|
2244
|
-
if (item === undefined) {
|
|
2245
|
-
item = this.getLowPriorityItem(
|
|
2246
|
-
ability, types, moves, abilities, counter, teamDetails, species, isLead, isDoubles, isNoDynamax
|
|
2247
|
-
);
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
// fallback
|
|
2251
|
-
if (item === undefined) item = isDoubles ? 'Sitrus Berry' : 'Leftovers';
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
// For Trick / Switcheroo
|
|
2255
|
-
if (item === 'Leftovers' && types.has('Poison')) {
|
|
2256
|
-
item = 'Black Sludge';
|
|
2257
|
-
}
|
|
2258
|
-
if (species.baseSpecies === 'Pikachu' && !gmax && this.dex.currentMod !== 'gen8bdsp') {
|
|
2259
|
-
forme = 'Pikachu' + this.sample(['', '-Original', '-Hoenn', '-Sinnoh', '-Unova', '-Kalos', '-Alola', '-Partner', '-World']);
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
let level: number;
|
|
2263
|
-
if (this.adjustLevel) {
|
|
2264
|
-
level = this.adjustLevel;
|
|
2265
|
-
// doubles levelling
|
|
2266
|
-
} else if (isDoubles && species.randomDoubleBattleLevel) {
|
|
2267
|
-
level = species.randomDoubleBattleLevel;
|
|
2268
|
-
// No Dmax levelling
|
|
2269
|
-
} else if (isNoDynamax) {
|
|
2270
|
-
const tier = species.name.endsWith('-Gmax') ? this.dex.species.get(species.changesFrom).tier : species.tier;
|
|
2271
|
-
const tierScale: Partial<Record<Species['tier'], number>> = {
|
|
2272
|
-
Uber: 76,
|
|
2273
|
-
OU: 80,
|
|
2274
|
-
UUBL: 81,
|
|
2275
|
-
UU: 82,
|
|
2276
|
-
RUBL: 83,
|
|
2277
|
-
RU: 84,
|
|
2278
|
-
NUBL: 85,
|
|
2279
|
-
NU: 86,
|
|
2280
|
-
PUBL: 87,
|
|
2281
|
-
PU: 88, "(PU)": 88, NFE: 88,
|
|
2282
|
-
};
|
|
2283
|
-
const customScale: {[k: string]: number} = {
|
|
2284
|
-
// These Pokemon are too strong and need a lower level
|
|
2285
|
-
zaciancrowned: 65, calyrexshadow: 68, xerneas: 70, necrozmaduskmane: 72, zacian: 72, kyogre: 73, eternatus: 73,
|
|
2286
|
-
zekrom: 74, marshadow: 75, glalie: 78, urshifurapidstrike: 79, haxorus: 80, inteleon: 80,
|
|
2287
|
-
cresselia: 83, octillery: 84, jolteon: 84, swoobat: 84, dugtrio: 84, slurpuff: 84, polteageist: 84,
|
|
2288
|
-
wobbuffet: 86, scrafty: 86,
|
|
2289
|
-
// These Pokemon are too weak and need a higher level
|
|
2290
|
-
delibird: 100, vespiquen: 96, pikachu: 92, shedinja: 92, solrock: 90, arctozolt: 88, reuniclus: 87,
|
|
2291
|
-
decidueye: 87, noivern: 85, magnezone: 82, slowking: 81,
|
|
2292
|
-
};
|
|
2293
|
-
level = customScale[species.id] || tierScale[tier] || 80;
|
|
2294
|
-
// BDSP tier levelling
|
|
2295
|
-
} else if (this.dex.currentMod === 'gen8bdsp') {
|
|
2296
|
-
const tierScale: Partial<Record<Species['tier'], number>> = {
|
|
2297
|
-
Uber: 76, Unreleased: 76,
|
|
2298
|
-
OU: 80,
|
|
2299
|
-
UUBL: 81,
|
|
2300
|
-
UU: 82,
|
|
2301
|
-
RUBL: 83,
|
|
2302
|
-
RU: 84,
|
|
2303
|
-
NUBL: 85,
|
|
2304
|
-
NU: 86,
|
|
2305
|
-
PUBL: 87,
|
|
2306
|
-
PU: 88, "(PU)": 88, NFE: 88,
|
|
2307
|
-
};
|
|
2308
|
-
const customScale: {[k: string]: number} = {delibird: 100, glalie: 76, luvdisc: 100, spinda: 100, unown: 100};
|
|
2309
|
-
|
|
2310
|
-
level = customScale[species.id] || tierScale[species.tier] || 80;
|
|
2311
|
-
// Arbitrary levelling base on data files (typically winrate-influenced)
|
|
2312
|
-
} else if (species.randomBattleLevel) {
|
|
2313
|
-
level = species.randomBattleLevel;
|
|
2314
|
-
// Default to level 80
|
|
2315
|
-
} else {
|
|
2316
|
-
level = 80;
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
|
-
// Prepare optimal HP
|
|
2320
|
-
const srImmunity = ability === 'Magic Guard' || item === 'Heavy-Duty Boots';
|
|
2321
|
-
const srWeakness = srImmunity ? 0 : this.dex.getEffectiveness('Rock', species);
|
|
2322
|
-
while (evs.hp > 1) {
|
|
2323
|
-
const hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
|
|
2324
|
-
const multipleOfFourNecessary = (moves.has('substitute') && !['Leftovers', 'Black Sludge'].includes(item) && (
|
|
2325
|
-
item === 'Sitrus Berry' ||
|
|
2326
|
-
item === 'Salac Berry' ||
|
|
2327
|
-
ability === 'Power Construct'
|
|
2328
|
-
));
|
|
2329
|
-
if (multipleOfFourNecessary) {
|
|
2330
|
-
// Two Substitutes should activate Sitrus Berry
|
|
2331
|
-
if (hp % 4 === 0) break;
|
|
2332
|
-
} else if (moves.has('bellydrum') && (item === 'Sitrus Berry' || ability === 'Gluttony')) {
|
|
2333
|
-
// Belly Drum should activate Sitrus Berry
|
|
2334
|
-
if (hp % 2 === 0) break;
|
|
2335
|
-
} else if (moves.has('substitute') && moves.has('reversal')) {
|
|
2336
|
-
// Reversal users should be able to use four Substitutes
|
|
2337
|
-
if (hp % 4 > 0) break;
|
|
2338
|
-
} else {
|
|
2339
|
-
// Maximize number of Stealth Rock switch-ins
|
|
2340
|
-
if (srWeakness <= 0 || hp % (4 / srWeakness) > 0) break;
|
|
2341
|
-
}
|
|
2342
|
-
evs.hp -= 4;
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
if (moves.has('shellsidearm') && item === 'Choice Specs') evs.atk -= 8;
|
|
2346
|
-
|
|
2347
|
-
// Minimize confusion damage
|
|
2348
|
-
const noAttackStatMoves = [...moves].every(m => {
|
|
2349
|
-
const move = this.dex.moves.get(m);
|
|
2350
|
-
if (move.damageCallback || move.damage) return true;
|
|
2351
|
-
return move.category !== 'Physical' || move.id === 'bodypress';
|
|
2352
|
-
});
|
|
2353
|
-
if (noAttackStatMoves && !moves.has('transform') && (!moves.has('shellsidearm') || !counter.get('Status'))) {
|
|
2354
|
-
evs.atk = 0;
|
|
2355
|
-
ivs.atk = 0;
|
|
2356
|
-
}
|
|
2357
|
-
|
|
2358
|
-
// Ensure Nihilego's Beast Boost gives it Special Attack boosts instead of Special Defense
|
|
2359
|
-
if (forme === 'Nihilego') evs.spd -= 32;
|
|
2360
|
-
|
|
2361
|
-
if (moves.has('gyroball') || moves.has('trickroom')) {
|
|
2362
|
-
evs.spe = 0;
|
|
2363
|
-
ivs.spe = 0;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
return {
|
|
2367
|
-
name: species.baseSpecies,
|
|
2368
|
-
species: forme,
|
|
2369
|
-
gender: species.gender,
|
|
2370
|
-
shiny: this.randomChance(1, 1024),
|
|
2371
|
-
gigantamax: gmax,
|
|
2372
|
-
level,
|
|
2373
|
-
moves: Array.from(moves),
|
|
2374
|
-
ability,
|
|
2375
|
-
evs,
|
|
2376
|
-
ivs,
|
|
2377
|
-
item,
|
|
2378
|
-
};
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
getPokemonPool(
|
|
2382
|
-
type: string,
|
|
2383
|
-
pokemonToExclude: RandomTeamsTypes.RandomSet[] = [],
|
|
2384
|
-
isMonotype = false,
|
|
2385
|
-
) {
|
|
2386
|
-
const exclude = pokemonToExclude.map(p => toID(p.species));
|
|
2387
|
-
const pokemonPool = [];
|
|
2388
|
-
for (let species of this.dex.species.all()) {
|
|
2389
|
-
if (species.gen > this.gen || exclude.includes(species.id)) continue;
|
|
2390
|
-
if (this.dex.currentMod === 'gen8bdsp' && species.gen > 4) continue;
|
|
2391
|
-
if (isMonotype) {
|
|
2392
|
-
if (!species.types.includes(type)) continue;
|
|
2393
|
-
if (typeof species.battleOnly === 'string') {
|
|
2394
|
-
species = this.dex.species.get(species.battleOnly);
|
|
2395
|
-
if (!species.types.includes(type)) continue;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
pokemonPool.push(species.id);
|
|
2399
|
-
}
|
|
2400
|
-
return pokemonPool;
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
randomTeam() {
|
|
2404
|
-
this.enforceNoDirectCustomBanlistChanges();
|
|
2405
|
-
|
|
2406
|
-
const seed = this.prng.seed;
|
|
2407
|
-
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
2408
|
-
const pokemon: RandomTeamsTypes.RandomSet[] = [];
|
|
2409
|
-
|
|
2410
|
-
// For Monotype
|
|
2411
|
-
const isMonotype = !!this.forceMonotype || ruleTable.has('sametypeclause');
|
|
2412
|
-
const typePool = this.dex.types.names();
|
|
2413
|
-
const type = this.forceMonotype || this.sample(typePool);
|
|
2414
|
-
|
|
2415
|
-
// PotD stuff
|
|
2416
|
-
const usePotD = global.Config && Config.potd && ruleTable.has('potd');
|
|
2417
|
-
const potd = usePotD ? this.dex.species.get(Config.potd) : null;
|
|
2418
|
-
|
|
2419
|
-
const baseFormes: {[k: string]: number} = {};
|
|
2420
|
-
|
|
2421
|
-
const tierCount: {[k: string]: number} = {};
|
|
2422
|
-
const typeCount: {[k: string]: number} = {};
|
|
2423
|
-
const typeComboCount: {[k: string]: number} = {};
|
|
2424
|
-
const teamDetails: RandomTeamsTypes.TeamDetails = {};
|
|
2425
|
-
|
|
2426
|
-
const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype);
|
|
2427
|
-
while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
|
|
2428
|
-
let species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
|
|
2429
|
-
if (!species.exists) continue;
|
|
2430
|
-
|
|
2431
|
-
// Check if the forme has moves for random battle
|
|
2432
|
-
if (this.format.gameType === 'singles') {
|
|
2433
|
-
if (!species.randomBattleMoves?.length) continue;
|
|
2434
|
-
} else {
|
|
2435
|
-
if (!species.randomDoubleBattleMoves?.length) continue;
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
// Limit to one of each species (Species Clause)
|
|
2439
|
-
if (baseFormes[species.baseSpecies]) continue;
|
|
2440
|
-
|
|
2441
|
-
// Adjust rate for species with multiple sets
|
|
2442
|
-
// TODO: investigate automating this by searching for Pokémon with multiple sets
|
|
2443
|
-
switch (species.baseSpecies) {
|
|
2444
|
-
case 'Arceus': case 'Silvally':
|
|
2445
|
-
if (this.randomChance(8, 9) && !isMonotype) continue;
|
|
2446
|
-
break;
|
|
2447
|
-
case 'Aegislash': case 'Basculin': case 'Gourgeist': case 'Meloetta': case 'Rotom':
|
|
2448
|
-
if (this.randomChance(1, 2)) continue;
|
|
2449
|
-
break;
|
|
2450
|
-
case 'Greninja':
|
|
2451
|
-
if (this.gen >= 7 && this.randomChance(1, 2)) continue;
|
|
2452
|
-
break;
|
|
2453
|
-
case 'Darmanitan':
|
|
2454
|
-
if (species.gen === 8 && this.randomChance(1, 2)) continue;
|
|
2455
|
-
break;
|
|
2456
|
-
case 'Necrozma': case 'Calyrex':
|
|
2457
|
-
if (this.randomChance(2, 3)) continue;
|
|
2458
|
-
break;
|
|
2459
|
-
case 'Magearna': case 'Toxtricity': case 'Zacian': case 'Zamazenta': case 'Zarude':
|
|
2460
|
-
case 'Appletun': case 'Blastoise': case 'Butterfree': case 'Copperajah': case 'Grimmsnarl':
|
|
2461
|
-
case 'Inteleon': case 'Rillaboom': case 'Snorlax': case 'Urshifu': case 'Giratina': case 'Genesect':
|
|
2462
|
-
case 'Cinderace':
|
|
2463
|
-
if (this.gen >= 8 && this.randomChance(1, 2)) continue;
|
|
2464
|
-
break;
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
// Illusion shouldn't be on the last slot
|
|
2468
|
-
if (species.name === 'Zoroark' && pokemon.length >= (this.maxTeamSize - 1)) continue;
|
|
2469
|
-
// The sixth slot should not be Zacian/Zamazenta/Eternatus if a Zoroark is present
|
|
2470
|
-
if (
|
|
2471
|
-
pokemon.some(pkmn => pkmn.species === 'Zoroark') &&
|
|
2472
|
-
['Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned', 'Eternatus'].includes(species.name)
|
|
2473
|
-
) {
|
|
2474
|
-
continue;
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
const tier = species.tier;
|
|
2478
|
-
const types = species.types;
|
|
2479
|
-
const typeCombo = types.slice().sort().join();
|
|
2480
|
-
// Dynamically scale limits for different team sizes. The default and minimum value is 1.
|
|
2481
|
-
const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
|
|
2482
|
-
|
|
2483
|
-
// Limit one Pokemon per tier, two for Monotype
|
|
2484
|
-
// This limitation is not applied to BD/SP team generation, because tiering for BD/SP is not yet complete,
|
|
2485
|
-
// meaning that most Pokémon are in OU.
|
|
2486
|
-
if (
|
|
2487
|
-
this.dex.currentMod !== 'gen8bdsp' &&
|
|
2488
|
-
(tierCount[tier] >= (this.forceMonotype || isMonotype ? 2 : 1) * limitFactor) &&
|
|
2489
|
-
!this.randomChance(1, Math.pow(5, tierCount[tier]))
|
|
2490
|
-
) {
|
|
2491
|
-
continue;
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
if (!isMonotype && !this.forceMonotype) {
|
|
2495
|
-
// TODO: fix type weaknesses
|
|
2496
|
-
let skip = false;
|
|
2497
|
-
|
|
2498
|
-
for (const typeName of types) {
|
|
2499
|
-
if (typeCount[typeName] >= 2 * limitFactor) {
|
|
2500
|
-
skip = true;
|
|
2501
|
-
break;
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
|
-
if (skip) continue;
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
// Limit one of any type combination, two in Monotype
|
|
2508
|
-
if (!this.forceMonotype && typeComboCount[typeCombo] >= (isMonotype ? 2 : 1) * limitFactor) continue;
|
|
2509
|
-
|
|
2510
|
-
// The Pokemon of the Day
|
|
2511
|
-
if (potd?.exists && (pokemon.length === 1 || this.maxTeamSize === 1)) species = potd;
|
|
2512
|
-
|
|
2513
|
-
const set = this.randomSet(species, teamDetails, pokemon.length === 0,
|
|
2514
|
-
this.format.gameType !== 'singles', this.dex.formats.getRuleTable(this.format).has('dynamaxclause'));
|
|
2515
|
-
|
|
2516
|
-
// Okay, the set passes, add it to our team
|
|
2517
|
-
pokemon.push(set);
|
|
2518
|
-
if (pokemon.length === this.maxTeamSize) {
|
|
2519
|
-
// Set Zoroark's level to be the same as the last Pokemon
|
|
2520
|
-
const illusion = teamDetails.illusion;
|
|
2521
|
-
if (illusion) pokemon[illusion - 1].level = pokemon[this.maxTeamSize - 1].level;
|
|
2522
|
-
|
|
2523
|
-
// Don't bother tracking details for the last Pokemon
|
|
2524
|
-
break;
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
// Now that our Pokemon has passed all checks, we can increment our counters
|
|
2528
|
-
baseFormes[species.baseSpecies] = 1;
|
|
2529
|
-
|
|
2530
|
-
// Increment tier counter
|
|
2531
|
-
if (tierCount[tier]) {
|
|
2532
|
-
tierCount[tier]++;
|
|
2533
|
-
} else {
|
|
2534
|
-
tierCount[tier] = 1;
|
|
2535
|
-
}
|
|
2536
|
-
|
|
2537
|
-
// Increment type counters
|
|
2538
|
-
for (const typeName of types) {
|
|
2539
|
-
if (typeName in typeCount) {
|
|
2540
|
-
typeCount[typeName]++;
|
|
2541
|
-
} else {
|
|
2542
|
-
typeCount[typeName] = 1;
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
if (typeCombo in typeComboCount) {
|
|
2546
|
-
typeComboCount[typeCombo]++;
|
|
2547
|
-
} else {
|
|
2548
|
-
typeComboCount[typeCombo] = 1;
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
|
-
// Track what the team has
|
|
2552
|
-
if (set.ability === 'Drizzle' || set.moves.includes('raindance')) teamDetails.rain = 1;
|
|
2553
|
-
if (set.ability === 'Drought' || set.moves.includes('sunnyday')) teamDetails.sun = 1;
|
|
2554
|
-
if (set.ability === 'Sand Stream') teamDetails.sand = 1;
|
|
2555
|
-
if (set.ability === 'Snow Warning') teamDetails.hail = 1;
|
|
2556
|
-
if (set.moves.includes('spikes')) teamDetails.spikes = (teamDetails.spikes || 0) + 1;
|
|
2557
|
-
if (set.moves.includes('stealthrock')) teamDetails.stealthRock = 1;
|
|
2558
|
-
if (set.moves.includes('stickyweb')) teamDetails.stickyWeb = 1;
|
|
2559
|
-
if (set.moves.includes('toxicspikes')) teamDetails.toxicSpikes = 1;
|
|
2560
|
-
if (set.moves.includes('defog')) teamDetails.defog = 1;
|
|
2561
|
-
if (set.moves.includes('rapidspin')) teamDetails.rapidSpin = 1;
|
|
2562
|
-
if (set.moves.includes('auroraveil') || (set.moves.includes('reflect') && set.moves.includes('lightscreen'))) {
|
|
2563
|
-
teamDetails.screens = 1;
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
// For setting Zoroark's level
|
|
2567
|
-
if (set.ability === 'Illusion') teamDetails.illusion = pokemon.length;
|
|
2568
|
-
}
|
|
2569
|
-
if (pokemon.length < this.maxTeamSize && pokemon.length < 12) { // large teams sometimes cannot be built
|
|
2570
|
-
throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
return pokemon;
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
randomCAP1v1Sets: AnyObject = {};
|
|
2577
|
-
|
|
2578
|
-
randomCAP1v1Team() {
|
|
2579
|
-
this.enforceNoDirectCustomBanlistChanges();
|
|
2580
|
-
|
|
2581
|
-
const pokemon = [];
|
|
2582
|
-
const pokemonPool = Object.keys(this.randomCAP1v1Sets);
|
|
2583
|
-
|
|
2584
|
-
while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
|
|
2585
|
-
const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
|
|
2586
|
-
if (!species.exists) throw new Error(`Invalid Pokemon "${species}" in ${this.format}`);
|
|
2587
|
-
if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
|
|
2588
|
-
|
|
2589
|
-
const setData: AnyObject = this.sample(this.randomCAP1v1Sets[species.name]);
|
|
2590
|
-
const set = {
|
|
2591
|
-
name: species.baseSpecies,
|
|
2592
|
-
species: species.name,
|
|
2593
|
-
gender: species.gender,
|
|
2594
|
-
item: this.sampleIfArray(setData.item) || '',
|
|
2595
|
-
ability: (this.sampleIfArray(setData.ability)),
|
|
2596
|
-
shiny: this.randomChance(1, 1024),
|
|
2597
|
-
level: this.adjustLevel || 100,
|
|
2598
|
-
evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.evs},
|
|
2599
|
-
nature: setData.nature,
|
|
2600
|
-
ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.ivs || {}},
|
|
2601
|
-
moves: setData.moves.map((move: any) => this.sampleIfArray(move)),
|
|
2602
|
-
};
|
|
2603
|
-
if (this.adjustLevel) set.level = this.adjustLevel;
|
|
2604
|
-
pokemon.push(set);
|
|
2605
|
-
}
|
|
2606
|
-
return pokemon;
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
randomFactorySets: {[format: string]: {[species: string]: BattleFactorySpecies}} = {};
|
|
2610
|
-
|
|
2611
|
-
randomFactorySet(
|
|
2612
|
-
species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails, tier: string
|
|
2613
|
-
): RandomTeamsTypes.RandomFactorySet | null {
|
|
2614
|
-
const id = toID(species.name);
|
|
2615
|
-
const setList = this.randomFactorySets[tier][id].sets;
|
|
2616
|
-
|
|
2617
|
-
const itemsMax: {[k: string]: number} = {
|
|
2618
|
-
choicespecs: 1,
|
|
2619
|
-
choiceband: 1,
|
|
2620
|
-
choicescarf: 1,
|
|
2621
|
-
};
|
|
2622
|
-
const movesMax: {[k: string]: number} = {
|
|
2623
|
-
rapidspin: 1,
|
|
2624
|
-
batonpass: 1,
|
|
2625
|
-
stealthrock: 1,
|
|
2626
|
-
defog: 1,
|
|
2627
|
-
spikes: 1,
|
|
2628
|
-
toxicspikes: 1,
|
|
2629
|
-
};
|
|
2630
|
-
const requiredMoves: {[k: string]: string} = {
|
|
2631
|
-
stealthrock: 'hazardSet',
|
|
2632
|
-
rapidspin: 'hazardClear',
|
|
2633
|
-
defog: 'hazardClear',
|
|
2634
|
-
};
|
|
2635
|
-
const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream'];
|
|
2636
|
-
|
|
2637
|
-
// Build a pool of eligible sets, given the team partners
|
|
2638
|
-
// Also keep track of sets with moves the team requires
|
|
2639
|
-
let effectivePool: {set: AnyObject, moveVariants?: number[]}[] = [];
|
|
2640
|
-
const priorityPool = [];
|
|
2641
|
-
for (const curSet of setList) {
|
|
2642
|
-
// if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
|
|
2643
|
-
|
|
2644
|
-
const item = this.dex.items.get(curSet.item);
|
|
2645
|
-
if (itemsMax[item.id] && teamData.has[item.id] >= itemsMax[item.id]) continue;
|
|
2646
|
-
|
|
2647
|
-
const ability = this.dex.abilities.get(curSet.ability);
|
|
2648
|
-
if (teamData.weather && weatherAbilities.includes(ability.id)) continue; // reject 2+ weather setters
|
|
2649
|
-
|
|
2650
|
-
let reject = false;
|
|
2651
|
-
let hasRequiredMove = false;
|
|
2652
|
-
const curSetVariants = [];
|
|
2653
|
-
for (const move of curSet.moves) {
|
|
2654
|
-
const variantIndex = this.random(move.length);
|
|
2655
|
-
const moveId = toID(move[variantIndex]);
|
|
2656
|
-
if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
|
|
2657
|
-
reject = true;
|
|
2658
|
-
break;
|
|
2659
|
-
}
|
|
2660
|
-
if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
|
|
2661
|
-
hasRequiredMove = true;
|
|
2662
|
-
}
|
|
2663
|
-
curSetVariants.push(variantIndex);
|
|
2664
|
-
}
|
|
2665
|
-
if (reject) continue;
|
|
2666
|
-
effectivePool.push({set: curSet, moveVariants: curSetVariants});
|
|
2667
|
-
if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants});
|
|
2668
|
-
}
|
|
2669
|
-
if (priorityPool.length) effectivePool = priorityPool;
|
|
2670
|
-
|
|
2671
|
-
if (!effectivePool.length) {
|
|
2672
|
-
if (!teamData.forceResult) return null;
|
|
2673
|
-
for (const curSet of setList) {
|
|
2674
|
-
effectivePool.push({set: curSet});
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
const setData = this.sample(effectivePool);
|
|
2679
|
-
const moves = [];
|
|
2680
|
-
for (const [i, moveSlot] of setData.set.moves.entries()) {
|
|
2681
|
-
moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
const item = this.sampleIfArray(setData.set.item);
|
|
2686
|
-
const ability = this.sampleIfArray(setData.set.ability);
|
|
2687
|
-
const nature = this.sampleIfArray(setData.set.nature);
|
|
2688
|
-
const level = this.adjustLevel || setData.set.level || (tier === "LC" ? 5 : 100);
|
|
2689
|
-
|
|
2690
|
-
return {
|
|
2691
|
-
name: setData.set.name || species.baseSpecies,
|
|
2692
|
-
species: setData.set.species,
|
|
2693
|
-
gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
|
|
2694
|
-
item: item || '',
|
|
2695
|
-
ability: ability || species.abilities['0'],
|
|
2696
|
-
shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
|
|
2697
|
-
level,
|
|
2698
|
-
happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
|
|
2699
|
-
evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
|
|
2700
|
-
ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
|
|
2701
|
-
nature: nature || 'Serious',
|
|
2702
|
-
moves,
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
|
|
2707
|
-
this.enforceNoDirectCustomBanlistChanges();
|
|
2708
|
-
|
|
2709
|
-
const forceResult = (depth >= 12);
|
|
2710
|
-
// Leaving Monotype code in comments in case it's used in the future
|
|
2711
|
-
// const isMonotype = !!this.forceMonotype || this.dex.formats.getRuleTable(this.format).has('sametypeclause');
|
|
2712
|
-
|
|
2713
|
-
// The teams generated depend on the tier choice in such a way that
|
|
2714
|
-
// no exploitable information is leaked from rolling the tier in getTeam(p1).
|
|
2715
|
-
if (!this.factoryTier) {
|
|
2716
|
-
// this.factoryTier = isMonotype ? 'Mono' : this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
|
|
2717
|
-
this.factoryTier = this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
|
|
2718
|
-
}
|
|
2719
|
-
/*
|
|
2720
|
-
} else if (isMonotype && this.factoryTier !== 'Mono') {
|
|
2721
|
-
// I don't think this can ever happen?
|
|
2722
|
-
throw new Error(`Can't generate a Monotype Battle Factory set in a battle with factory tier ${this.factoryTier}`);
|
|
2723
|
-
}
|
|
2724
|
-
*/
|
|
2725
|
-
|
|
2726
|
-
const tierValues: {[k: string]: number} = {
|
|
2727
|
-
Uber: 5,
|
|
2728
|
-
OU: 4, UUBL: 4,
|
|
2729
|
-
UU: 3, RUBL: 3,
|
|
2730
|
-
RU: 2, NUBL: 2,
|
|
2731
|
-
NU: 1, PUBL: 1,
|
|
2732
|
-
PU: 0,
|
|
2733
|
-
};
|
|
2734
|
-
|
|
2735
|
-
const pokemon = [];
|
|
2736
|
-
const pokemonPool = Object.keys(this.randomFactorySets[this.factoryTier]);
|
|
2737
|
-
|
|
2738
|
-
// const typePool = this.dex.types.names();
|
|
2739
|
-
// const type = this.sample(typePool);
|
|
2740
|
-
|
|
2741
|
-
const teamData: TeamData = {
|
|
2742
|
-
typeCount: {}, typeComboCount: {}, baseFormes: {},
|
|
2743
|
-
has: {}, forceResult: forceResult, weaknesses: {}, resistances: {},
|
|
2744
|
-
};
|
|
2745
|
-
const requiredMoveFamilies = ['hazardSet', 'hazardClear'];
|
|
2746
|
-
const requiredMoves: {[k: string]: string} = {
|
|
2747
|
-
stealthrock: 'hazardSet',
|
|
2748
|
-
rapidspin: 'hazardClear',
|
|
2749
|
-
defog: 'hazardClear',
|
|
2750
|
-
};
|
|
2751
|
-
const weatherAbilitiesSet: {[k: string]: string} = {
|
|
2752
|
-
drizzle: 'raindance',
|
|
2753
|
-
drought: 'sunnyday',
|
|
2754
|
-
snowwarning: 'hail',
|
|
2755
|
-
sandstream: 'sandstorm',
|
|
2756
|
-
};
|
|
2757
|
-
const resistanceAbilities: {[k: string]: string[]} = {
|
|
2758
|
-
dryskin: ['Water'], waterabsorb: ['Water'], stormdrain: ['Water'],
|
|
2759
|
-
flashfire: ['Fire'], heatproof: ['Fire'],
|
|
2760
|
-
lightningrod: ['Electric'], motordrive: ['Electric'], voltabsorb: ['Electric'],
|
|
2761
|
-
sapsipper: ['Grass'],
|
|
2762
|
-
thickfat: ['Ice', 'Fire'],
|
|
2763
|
-
levitate: ['Ground'],
|
|
2764
|
-
};
|
|
2765
|
-
|
|
2766
|
-
while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
|
|
2767
|
-
const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
|
|
2768
|
-
if (!species.exists) continue;
|
|
2769
|
-
|
|
2770
|
-
// Lessen the need of deleting sets of Pokemon after tier shifts
|
|
2771
|
-
if (
|
|
2772
|
-
this.factoryTier in tierValues && species.tier in tierValues &&
|
|
2773
|
-
tierValues[species.tier] > tierValues[this.factoryTier]
|
|
2774
|
-
) continue;
|
|
2775
|
-
|
|
2776
|
-
// const speciesFlags = this.randomFactorySets[this.factoryTier][species.id].flags;
|
|
2777
|
-
|
|
2778
|
-
// Limit to one of each species (Species Clause)
|
|
2779
|
-
if (teamData.baseFormes[species.baseSpecies]) continue;
|
|
2780
|
-
|
|
2781
|
-
const set = this.randomFactorySet(species, teamData, this.factoryTier);
|
|
2782
|
-
if (!set) continue;
|
|
2783
|
-
|
|
2784
|
-
const itemData = this.dex.items.get(set.item);
|
|
2785
|
-
|
|
2786
|
-
const types = species.types;
|
|
2787
|
-
// Dynamically scale limits for different team sizes. The default and minimum value is 1.
|
|
2788
|
-
const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
|
|
2789
|
-
/*
|
|
2790
|
-
// Enforce Monotype
|
|
2791
|
-
if (isMonotype) {
|
|
2792
|
-
// Prevents Mega Evolutions from breaking the type limits
|
|
2793
|
-
if (itemData.megaStone) {
|
|
2794
|
-
const megaSpecies = this.dex.species.get(itemData.megaStone);
|
|
2795
|
-
if (types.length > megaSpecies.types.length) types = [species.types[0]];
|
|
2796
|
-
// Only check the second type because a Mega Evolution should always share the first type with its base forme.
|
|
2797
|
-
if (megaSpecies.types[1] && types[1] && megaSpecies.types[1] !== types[1]) {
|
|
2798
|
-
types = [megaSpecies.types[0]];
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
if (!types.includes(type)) continue;
|
|
2802
|
-
} else
|
|
2803
|
-
*/
|
|
2804
|
-
{
|
|
2805
|
-
// If not Monotype, limit to two of each type
|
|
2806
|
-
let skip = false;
|
|
2807
|
-
for (const typeName of types) {
|
|
2808
|
-
if (teamData.typeCount[typeName] >= 2 * limitFactor && this.randomChance(4, 5)) {
|
|
2809
|
-
skip = true;
|
|
2810
|
-
break;
|
|
2811
|
-
}
|
|
2812
|
-
}
|
|
2813
|
-
if (skip) continue;
|
|
2814
|
-
|
|
2815
|
-
// Limit 1 of any type combination
|
|
2816
|
-
let typeCombo = types.slice().sort().join();
|
|
2817
|
-
if (set.ability + '' === 'Drought' || set.ability + '' === 'Drizzle') {
|
|
2818
|
-
// Drought and Drizzle don't count towards the type combo limit
|
|
2819
|
-
typeCombo = set.ability + '';
|
|
2820
|
-
}
|
|
2821
|
-
if (teamData.typeComboCount[typeCombo] >= 1 * limitFactor) continue;
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
// Okay, the set passes, add it to our team
|
|
2825
|
-
pokemon.push(set);
|
|
2826
|
-
const typeCombo = types.slice().sort().join();
|
|
2827
|
-
// Now that our Pokemon has passed all checks, we can update team data:
|
|
2828
|
-
for (const typeName of types) {
|
|
2829
|
-
if (typeName in teamData.typeCount) {
|
|
2830
|
-
teamData.typeCount[typeName]++;
|
|
2831
|
-
} else {
|
|
2832
|
-
teamData.typeCount[typeName] = 1;
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
teamData.typeComboCount[typeCombo] = (teamData.typeComboCount[typeCombo] + 1) || 1;
|
|
2836
|
-
|
|
2837
|
-
teamData.baseFormes[species.baseSpecies] = 1;
|
|
2838
|
-
|
|
2839
|
-
if (itemData.id in teamData.has) {
|
|
2840
|
-
teamData.has[itemData.id]++;
|
|
2841
|
-
} else {
|
|
2842
|
-
teamData.has[itemData.id] = 1;
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
const abilityState = this.dex.abilities.get(set.ability);
|
|
2846
|
-
if (abilityState.id in weatherAbilitiesSet) {
|
|
2847
|
-
teamData.weather = weatherAbilitiesSet[abilityState.id];
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
for (const move of set.moves) {
|
|
2851
|
-
const moveId = toID(move);
|
|
2852
|
-
if (moveId in teamData.has) {
|
|
2853
|
-
teamData.has[moveId]++;
|
|
2854
|
-
} else {
|
|
2855
|
-
teamData.has[moveId] = 1;
|
|
2856
|
-
}
|
|
2857
|
-
if (moveId in requiredMoves) {
|
|
2858
|
-
teamData.has[requiredMoves[moveId]] = 1;
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
for (const typeName of this.dex.types.names()) {
|
|
2863
|
-
// Cover any major weakness (3+) with at least one resistance
|
|
2864
|
-
if (teamData.resistances[typeName] >= 1) continue;
|
|
2865
|
-
if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
|
|
2866
|
-
// Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
|
|
2867
|
-
teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
|
|
2868
|
-
if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
|
|
2869
|
-
continue;
|
|
2870
|
-
}
|
|
2871
|
-
const typeMod = this.dex.getEffectiveness(typeName, types);
|
|
2872
|
-
if (typeMod < 0) {
|
|
2873
|
-
teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
|
|
2874
|
-
if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
|
|
2875
|
-
} else if (typeMod > 0) {
|
|
2876
|
-
teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
|
|
2877
|
-
}
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
if (pokemon.length < this.maxTeamSize) return this.randomFactoryTeam(side, ++depth);
|
|
2881
|
-
|
|
2882
|
-
// Quality control
|
|
2883
|
-
if (!teamData.forceResult) {
|
|
2884
|
-
for (const requiredFamily of requiredMoveFamilies) {
|
|
2885
|
-
if (!teamData.has[requiredFamily]) return this.randomFactoryTeam(side, ++depth);
|
|
2886
|
-
}
|
|
2887
|
-
for (const typeName in teamData.weaknesses) {
|
|
2888
|
-
if (teamData.weaknesses[typeName] >= 3) return this.randomFactoryTeam(side, ++depth);
|
|
2889
|
-
}
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
return pokemon;
|
|
2893
|
-
}
|
|
2894
|
-
|
|
2895
|
-
randomBSSFactorySets: AnyObject = {};
|
|
2896
|
-
|
|
2897
|
-
randomBSSFactorySet(
|
|
2898
|
-
species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails
|
|
2899
|
-
): RandomTeamsTypes.RandomFactorySet | null {
|
|
2900
|
-
const id = toID(species.name);
|
|
2901
|
-
const setList = this.randomBSSFactorySets[id].sets;
|
|
2902
|
-
|
|
2903
|
-
const movesMax: {[k: string]: number} = {
|
|
2904
|
-
batonpass: 1,
|
|
2905
|
-
stealthrock: 1,
|
|
2906
|
-
toxicspikes: 1,
|
|
2907
|
-
trickroom: 1,
|
|
2908
|
-
auroraveil: 1,
|
|
2909
|
-
};
|
|
2910
|
-
|
|
2911
|
-
const requiredMoves: {[k: string]: number} = {};
|
|
2912
|
-
|
|
2913
|
-
// Build a pool of eligible sets, given the team partners
|
|
2914
|
-
// Also keep track of sets with moves the team requires
|
|
2915
|
-
let effectivePool: {set: AnyObject, moveVariants?: number[], itemVariants?: number, abilityVariants?: number}[] = [];
|
|
2916
|
-
const priorityPool = [];
|
|
2917
|
-
for (const curSet of setList) {
|
|
2918
|
-
let reject = false;
|
|
2919
|
-
let hasRequiredMove = false;
|
|
2920
|
-
const curSetMoveVariants = [];
|
|
2921
|
-
for (const move of curSet.moves) {
|
|
2922
|
-
const variantIndex = this.random(move.length);
|
|
2923
|
-
const moveId = toID(move[variantIndex]);
|
|
2924
|
-
if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
|
|
2925
|
-
reject = true;
|
|
2926
|
-
break;
|
|
2927
|
-
}
|
|
2928
|
-
if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
|
|
2929
|
-
hasRequiredMove = true;
|
|
2930
|
-
}
|
|
2931
|
-
curSetMoveVariants.push(variantIndex);
|
|
2932
|
-
}
|
|
2933
|
-
if (reject) continue;
|
|
2934
|
-
const set = {set: curSet, moveVariants: curSetMoveVariants};
|
|
2935
|
-
effectivePool.push(set);
|
|
2936
|
-
if (hasRequiredMove) priorityPool.push(set);
|
|
2937
|
-
}
|
|
2938
|
-
if (priorityPool.length) effectivePool = priorityPool;
|
|
2939
|
-
|
|
2940
|
-
if (!effectivePool.length) {
|
|
2941
|
-
if (!teamData.forceResult) return null;
|
|
2942
|
-
for (const curSet of setList) {
|
|
2943
|
-
effectivePool.push({set: curSet});
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
|
|
2947
|
-
const setData = this.sample(effectivePool);
|
|
2948
|
-
const moves = [];
|
|
2949
|
-
for (const [i, moveSlot] of setData.set.moves.entries()) {
|
|
2950
|
-
moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
const setDataAbility = this.sampleIfArray(setData.set.ability);
|
|
2954
|
-
return {
|
|
2955
|
-
name: setData.set.nickname || setData.set.name || species.baseSpecies,
|
|
2956
|
-
species: setData.set.species,
|
|
2957
|
-
gigantamax: setData.set.gigantamax,
|
|
2958
|
-
gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
|
|
2959
|
-
item: this.sampleIfArray(setData.set.item) || '',
|
|
2960
|
-
ability: setDataAbility || species.abilities['0'],
|
|
2961
|
-
shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
|
|
2962
|
-
level: setData.set.level || 50,
|
|
2963
|
-
happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
|
|
2964
|
-
evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
|
|
2965
|
-
ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
|
|
2966
|
-
nature: setData.set.nature || 'Serious',
|
|
2967
|
-
moves,
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
|
|
2971
|
-
randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
|
|
2972
|
-
this.enforceNoDirectCustomBanlistChanges();
|
|
2973
|
-
|
|
2974
|
-
const forceResult = (depth >= 4);
|
|
2975
|
-
|
|
2976
|
-
const pokemon = [];
|
|
2977
|
-
|
|
2978
|
-
const pokemonPool = Object.keys(this.randomBSSFactorySets);
|
|
2979
|
-
|
|
2980
|
-
const teamData: TeamData = {
|
|
2981
|
-
typeCount: {}, typeComboCount: {}, baseFormes: {}, has: {}, forceResult: forceResult,
|
|
2982
|
-
weaknesses: {}, resistances: {},
|
|
2983
|
-
};
|
|
2984
|
-
const requiredMoveFamilies: string[] = [];
|
|
2985
|
-
const requiredMoves: {[k: string]: string} = {};
|
|
2986
|
-
const weatherAbilitiesSet: {[k: string]: string} = {
|
|
2987
|
-
drizzle: 'raindance',
|
|
2988
|
-
drought: 'sunnyday',
|
|
2989
|
-
snowwarning: 'hail',
|
|
2990
|
-
sandstream: 'sandstorm',
|
|
2991
|
-
};
|
|
2992
|
-
const resistanceAbilities: {[k: string]: string[]} = {
|
|
2993
|
-
waterabsorb: ['Water'],
|
|
2994
|
-
flashfire: ['Fire'],
|
|
2995
|
-
lightningrod: ['Electric'], voltabsorb: ['Electric'],
|
|
2996
|
-
thickfat: ['Ice', 'Fire'],
|
|
2997
|
-
levitate: ['Ground'],
|
|
2998
|
-
};
|
|
2999
|
-
|
|
3000
|
-
while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
|
|
3001
|
-
// Weighted random sampling
|
|
3002
|
-
let maxUsage = 0;
|
|
3003
|
-
const sets: {[k: string]: number} = {};
|
|
3004
|
-
for (const specie of pokemonPool) {
|
|
3005
|
-
if (teamData.baseFormes[this.dex.species.get(specie).baseSpecies]) continue; // Species Clause
|
|
3006
|
-
const usage: number = this.randomBSSFactorySets[specie].usage;
|
|
3007
|
-
sets[specie] = usage + maxUsage;
|
|
3008
|
-
maxUsage += usage;
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
const usage = this.random(1, maxUsage);
|
|
3012
|
-
let last = 0;
|
|
3013
|
-
let specie;
|
|
3014
|
-
for (const key of Object.keys(sets)) {
|
|
3015
|
-
if (usage > last && usage <= sets[key]) {
|
|
3016
|
-
specie = key;
|
|
3017
|
-
break;
|
|
3018
|
-
}
|
|
3019
|
-
last = sets[key];
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3022
|
-
const species = this.dex.species.get(specie);
|
|
3023
|
-
if (!species.exists) continue;
|
|
3024
|
-
if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
|
|
3025
|
-
|
|
3026
|
-
// Limit to one of each species (Species Clause)
|
|
3027
|
-
if (teamData.baseFormes[species.baseSpecies]) continue;
|
|
3028
|
-
|
|
3029
|
-
// Limit 2 of any type (most of the time)
|
|
3030
|
-
const types = species.types;
|
|
3031
|
-
let skip = false;
|
|
3032
|
-
for (const type of types) {
|
|
3033
|
-
if (teamData.typeCount[type] > 1 && this.randomChance(4, 5)) {
|
|
3034
|
-
skip = true;
|
|
3035
|
-
break;
|
|
3036
|
-
}
|
|
3037
|
-
}
|
|
3038
|
-
if (skip) continue;
|
|
3039
|
-
|
|
3040
|
-
const set = this.randomBSSFactorySet(species, teamData);
|
|
3041
|
-
if (!set) continue;
|
|
3042
|
-
|
|
3043
|
-
// Limit 1 of any type combination
|
|
3044
|
-
let typeCombo = types.slice().sort().join();
|
|
3045
|
-
if (set.ability === 'Drought' || set.ability === 'Drizzle') {
|
|
3046
|
-
// Drought and Drizzle don't count towards the type combo limit
|
|
3047
|
-
typeCombo = set.ability;
|
|
3048
|
-
}
|
|
3049
|
-
if (typeCombo in teamData.typeComboCount) continue;
|
|
3050
|
-
|
|
3051
|
-
const itemData = this.dex.items.get(set.item);
|
|
3052
|
-
if (teamData.has[itemData.id]) continue; // Item Clause
|
|
3053
|
-
|
|
3054
|
-
// Okay, the set passes, add it to our team
|
|
3055
|
-
pokemon.push(set);
|
|
3056
|
-
|
|
3057
|
-
// Now that our Pokemon has passed all checks, we can update team data:
|
|
3058
|
-
for (const type of types) {
|
|
3059
|
-
if (type in teamData.typeCount) {
|
|
3060
|
-
teamData.typeCount[type]++;
|
|
3061
|
-
} else {
|
|
3062
|
-
teamData.typeCount[type] = 1;
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
teamData.typeComboCount[typeCombo] = 1;
|
|
3066
|
-
|
|
3067
|
-
teamData.baseFormes[species.baseSpecies] = 1;
|
|
3068
|
-
|
|
3069
|
-
teamData.has[itemData.id] = 1;
|
|
3070
|
-
|
|
3071
|
-
const abilityState = this.dex.abilities.get(set.ability);
|
|
3072
|
-
if (abilityState.id in weatherAbilitiesSet) {
|
|
3073
|
-
teamData.weather = weatherAbilitiesSet[abilityState.id];
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
for (const move of set.moves) {
|
|
3077
|
-
const moveId = toID(move);
|
|
3078
|
-
if (moveId in teamData.has) {
|
|
3079
|
-
teamData.has[moveId]++;
|
|
3080
|
-
} else {
|
|
3081
|
-
teamData.has[moveId] = 1;
|
|
3082
|
-
}
|
|
3083
|
-
if (moveId in requiredMoves) {
|
|
3084
|
-
teamData.has[requiredMoves[moveId]] = 1;
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
|
|
3088
|
-
for (const typeName of this.dex.types.names()) {
|
|
3089
|
-
// Cover any major weakness (3+) with at least one resistance
|
|
3090
|
-
if (teamData.resistances[typeName] >= 1) continue;
|
|
3091
|
-
if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
|
|
3092
|
-
// Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
|
|
3093
|
-
teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
|
|
3094
|
-
if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
|
|
3095
|
-
continue;
|
|
3096
|
-
}
|
|
3097
|
-
const typeMod = this.dex.getEffectiveness(typeName, types);
|
|
3098
|
-
if (typeMod < 0) {
|
|
3099
|
-
teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
|
|
3100
|
-
if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
|
|
3101
|
-
} else if (typeMod > 0) {
|
|
3102
|
-
teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
|
|
3103
|
-
}
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
if (pokemon.length < this.maxTeamSize) return this.randomBSSFactoryTeam(side, ++depth);
|
|
3107
|
-
|
|
3108
|
-
// Quality control
|
|
3109
|
-
if (!teamData.forceResult) {
|
|
3110
|
-
for (const requiredFamily of requiredMoveFamilies) {
|
|
3111
|
-
if (!teamData.has[requiredFamily]) return this.randomBSSFactoryTeam(side, ++depth);
|
|
3112
|
-
}
|
|
3113
|
-
for (const type in teamData.weaknesses) {
|
|
3114
|
-
if (teamData.weaknesses[type] >= 3) return this.randomBSSFactoryTeam(side, ++depth);
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
return pokemon;
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
export default RandomTeams;
|