@pkmn/randoms 0.5.3 → 0.5.7
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/gen1.js +7 -1
- package/build/gen1.js.map +1 -1
- package/build/gen3.js +10 -2
- package/build/gen3.js.map +1 -1
- package/build/gen5.js +5 -1
- package/build/gen5.js.map +1 -1
- package/build/gen6.js +2 -1
- package/build/gen6.js.map +1 -1
- package/build/gen7.js +7 -2
- package/build/gen7.js.map +1 -1
- package/build/gen8.d.ts +19 -2
- package/build/gen8.js +301 -53
- package/build/gen8.js.map +1 -1
- package/package.json +2 -2
- package/src/gen1.ts +10 -1
- package/src/gen3.ts +9 -2
- package/src/gen5.ts +6 -1
- package/src/gen6.ts +3 -1
- package/src/gen7.ts +12 -2
- package/src/gen8.ts +284 -50
package/src/gen3.ts
CHANGED
|
@@ -28,7 +28,10 @@ export class RandomGen3Teams extends RandomGen4Teams {
|
|
|
28
28
|
Fighting: (movePool, moves, abilities, types, counter) => !counter.get('Fighting'),
|
|
29
29
|
Fire: (movePool, moves, abilities, types, counter) => !counter.get('Fire'),
|
|
30
30
|
Ground: (movePool, moves, abilities, types, counter) => !counter.get('Ground'),
|
|
31
|
-
Normal: (movePool, moves, abilities, types, counter) =>
|
|
31
|
+
Normal: (movePool, moves, abilities, types, counter, species) => {
|
|
32
|
+
if (species.id === 'blissey' && movePool.includes('softboiled')) return true;
|
|
33
|
+
return !counter.get('Normal') && counter.setupType === 'Physical';
|
|
34
|
+
},
|
|
32
35
|
Psychic: (movePool, moves, abilities, types, counter, species) => (
|
|
33
36
|
types.has('Psychic') &&
|
|
34
37
|
(movePool.includes('psychic') || movePool.includes('psychoboost')) &&
|
|
@@ -41,6 +44,7 @@ export class RandomGen3Teams extends RandomGen4Teams {
|
|
|
41
44
|
// If the Pokémon has this move, the other move will be forced
|
|
42
45
|
protect: movePool => movePool.includes('wish'),
|
|
43
46
|
sunnyday: movePool => movePool.includes('solarbeam'),
|
|
47
|
+
sleeptalk: movePool => movePool.includes('rest'),
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -82,11 +86,12 @@ export class RandomGen3Teams extends RandomGen4Teams {
|
|
|
82
86
|
|
|
83
87
|
// Not very useful without their supporting moves
|
|
84
88
|
case 'amnesia': case 'sleeptalk':
|
|
89
|
+
if (!moves.has('rest')) return {cull: true};
|
|
85
90
|
if (movePool.length > 1) {
|
|
86
91
|
const rest = movePool.indexOf('rest');
|
|
87
92
|
if (rest >= 0) this.fastPop(movePool, rest);
|
|
88
93
|
}
|
|
89
|
-
|
|
94
|
+
break;
|
|
90
95
|
case 'barrier':
|
|
91
96
|
return {cull: !moves.has('calmmind') && !moves.has('batonpass') && !moves.has('mirrorcoat')};
|
|
92
97
|
case 'batonpass':
|
|
@@ -575,6 +580,8 @@ export class RandomGen3Teams extends RandomGen4Teams {
|
|
|
575
580
|
}
|
|
576
581
|
|
|
577
582
|
randomTeam() {
|
|
583
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
584
|
+
|
|
578
585
|
const seed = this.prng.seed;
|
|
579
586
|
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
580
587
|
const pokemon: RandomTeamsTypes.RandomSet[] = [];
|
package/src/gen5.ts
CHANGED
|
@@ -346,7 +346,7 @@ export class RandomGen5Teams extends RandomGen6Teams {
|
|
|
346
346
|
return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
|
-
if (species.
|
|
349
|
+
if (species.nfe) return 'Eviolite';
|
|
350
350
|
if (moves.has('shellsmash')) return 'White Herb';
|
|
351
351
|
if (ability === 'Harvest' || moves.has('bellydrum')) return 'Sitrus Berry';
|
|
352
352
|
if ((ability === 'Magic Guard' || ability === 'Sheer Force') && counter.damagingMoves.size > 1) return 'Life Orb';
|
|
@@ -729,6 +729,9 @@ export class RandomGen5Teams extends RandomGen6Teams {
|
|
|
729
729
|
NUBL: 86,
|
|
730
730
|
NU: 86,
|
|
731
731
|
'(NU)': 88,
|
|
732
|
+
PUBL: 88,
|
|
733
|
+
PU: 88,
|
|
734
|
+
'(PU)': 90,
|
|
732
735
|
};
|
|
733
736
|
const customScale: {[forme: string]: number} = {
|
|
734
737
|
Delibird: 100, 'Farfetch\u2019d': 100, Luvdisc: 100, Unown: 100,
|
|
@@ -780,6 +783,8 @@ export class RandomGen5Teams extends RandomGen6Teams {
|
|
|
780
783
|
}
|
|
781
784
|
|
|
782
785
|
randomTeam() {
|
|
786
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
787
|
+
|
|
783
788
|
const seed = this.prng.seed;
|
|
784
789
|
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
785
790
|
const pokemon: RandomTeamsTypes.RandomSet[] = [];
|
package/src/gen6.ts
CHANGED
|
@@ -611,7 +611,7 @@ export class RandomGen6Teams extends RandomGen7Teams {
|
|
|
611
611
|
return (counter.get('Physical') > counter.get('Special')) ? 'Choice Band' : 'Choice Specs';
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
|
-
if (species.
|
|
614
|
+
if (species.nfe) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
|
|
615
615
|
if (moves.has('copycat') && counter.get('Physical') >= 3) return 'Choice Band';
|
|
616
616
|
if (moves.has('bellydrum')) return 'Sitrus Berry';
|
|
617
617
|
if (
|
|
@@ -1207,6 +1207,8 @@ export class RandomGen6Teams extends RandomGen7Teams {
|
|
|
1207
1207
|
}
|
|
1208
1208
|
|
|
1209
1209
|
randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
|
|
1210
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
1211
|
+
|
|
1210
1212
|
const forceResult = (depth >= 4);
|
|
1211
1213
|
|
|
1212
1214
|
// The teams generated depend on the tier choice in such a way that
|
package/src/gen7.ts
CHANGED
|
@@ -448,7 +448,11 @@ export class RandomGen7Teams extends RandomTeams {
|
|
|
448
448
|
case 'endeavor':
|
|
449
449
|
return {cull: !isLead && !abilities.has('Defeatist')};
|
|
450
450
|
case 'explosion':
|
|
451
|
-
return {cull:
|
|
451
|
+
return {cull: (
|
|
452
|
+
!!counter.setupType ||
|
|
453
|
+
moves.has('wish') ||
|
|
454
|
+
(abilities.has('Refrigerate') && (moves.has('freezedry') || movePool.includes('return')))
|
|
455
|
+
)};
|
|
452
456
|
case 'extremespeed': case 'skyattack':
|
|
453
457
|
return {cull: moves.has('substitute') || counter.setupType !== 'Physical' && moves.has('vacuumwave')};
|
|
454
458
|
case 'facade':
|
|
@@ -732,7 +736,7 @@ export class RandomGen7Teams extends RandomTeams {
|
|
|
732
736
|
if (ability === 'Harvest' || ability === 'Emergency Exit' && !!counter.get('Status')) return 'Sitrus Berry';
|
|
733
737
|
if (ability === 'Imposter') return 'Choice Scarf';
|
|
734
738
|
if (ability === 'Poison Heal') return 'Toxic Orb';
|
|
735
|
-
if (species.
|
|
739
|
+
if (species.nfe) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
|
|
736
740
|
if (moves.has('switcheroo') || moves.has('trick')) {
|
|
737
741
|
if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108) {
|
|
738
742
|
return 'Choice Scarf';
|
|
@@ -1467,6 +1471,8 @@ export class RandomGen7Teams extends RandomTeams {
|
|
|
1467
1471
|
}
|
|
1468
1472
|
|
|
1469
1473
|
randomTeam() {
|
|
1474
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
1475
|
+
|
|
1470
1476
|
const seed = this.prng.seed;
|
|
1471
1477
|
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
1472
1478
|
const pokemon = [];
|
|
@@ -1739,6 +1745,8 @@ export class RandomGen7Teams extends RandomTeams {
|
|
|
1739
1745
|
}
|
|
1740
1746
|
|
|
1741
1747
|
randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
|
|
1748
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
1749
|
+
|
|
1742
1750
|
const forceResult = (depth >= 4);
|
|
1743
1751
|
const isMonotype = !!this.forceMonotype || this.dex.formats.getRuleTable(this.format).has('sametypeclause');
|
|
1744
1752
|
|
|
@@ -2026,6 +2034,8 @@ export class RandomGen7Teams extends RandomTeams {
|
|
|
2026
2034
|
}
|
|
2027
2035
|
|
|
2028
2036
|
randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
|
|
2037
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
2038
|
+
|
|
2029
2039
|
const forceResult = (depth >= 4);
|
|
2030
2040
|
|
|
2031
2041
|
const pokemon = [];
|
package/src/gen8.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {Utils} from './utils';
|
|
2
2
|
import {
|
|
3
|
+
Ability,
|
|
3
4
|
AnyObject,
|
|
5
|
+
BasicEffect,
|
|
6
|
+
Dex,
|
|
4
7
|
Format,
|
|
5
|
-
|
|
8
|
+
Item,
|
|
6
9
|
ModdedDex,
|
|
7
10
|
Move,
|
|
11
|
+
Nature,
|
|
8
12
|
PRNG,
|
|
9
13
|
PRNGSeed,
|
|
10
14
|
PlayerOptions,
|
|
@@ -13,6 +17,7 @@ import {
|
|
|
13
17
|
Species,
|
|
14
18
|
StatID,
|
|
15
19
|
StatsTable,
|
|
20
|
+
Tags,
|
|
16
21
|
toID,
|
|
17
22
|
} from '@pkmn/sim';
|
|
18
23
|
|
|
@@ -86,6 +91,10 @@ const Hazards = [
|
|
|
86
91
|
'spikes', 'stealthrock', 'stickyweb', 'toxicspikes',
|
|
87
92
|
];
|
|
88
93
|
|
|
94
|
+
function sereneGraceBenefits(move: Move) {
|
|
95
|
+
return move.secondary?.chance && move.secondary.chance >= 20 && move.secondary.chance < 100;
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
export class RandomTeams {
|
|
90
99
|
dex: ModdedDex;
|
|
91
100
|
gen: number;
|
|
@@ -272,6 +281,7 @@ export class RandomTeams {
|
|
|
272
281
|
*/
|
|
273
282
|
sampleNoReplace(list: any[]) {
|
|
274
283
|
const length = list.length;
|
|
284
|
+
if (length === 0) return null;
|
|
275
285
|
const index = this.random(length);
|
|
276
286
|
return this.fastPop(list, index);
|
|
277
287
|
}
|
|
@@ -290,6 +300,54 @@ export class RandomTeams {
|
|
|
290
300
|
return samples;
|
|
291
301
|
}
|
|
292
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Check if user has directly tried to ban/unban/restrict things in a custom battle.
|
|
305
|
+
* Doesn't count bans nested inside other formats/rules.
|
|
306
|
+
*/
|
|
307
|
+
private hasDirectCustomBanlistChanges() {
|
|
308
|
+
if (!this.format.customRules) return false;
|
|
309
|
+
for (const rule of this.format.customRules) {
|
|
310
|
+
for (const banlistOperator of ['-', '+', '*']) {
|
|
311
|
+
if (rule.startsWith(banlistOperator)) return true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Inform user when custom bans are unsupported in a team generator.
|
|
319
|
+
*/
|
|
320
|
+
protected enforceNoDirectCustomBanlistChanges() {
|
|
321
|
+
if (this.hasDirectCustomBanlistChanges()) {
|
|
322
|
+
throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Inform user when complex bans are unsupported in a team generator.
|
|
328
|
+
*/
|
|
329
|
+
protected enforceNoDirectComplexBans() {
|
|
330
|
+
if (!this.format.customRules) return false;
|
|
331
|
+
for (const rule of this.format.customRules) {
|
|
332
|
+
if (rule.includes('+') && !rule.startsWith('+')) {
|
|
333
|
+
throw new Error(`Complex bans are not currently supported in ${this.format.name}.`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Validate set element pool size is sufficient to support size requirements after simple bans.
|
|
340
|
+
*/
|
|
341
|
+
private enforceCustomPoolSizeNoComplexBans(
|
|
342
|
+
effectTypeName: string,
|
|
343
|
+
basicEffectPool: BasicEffect[],
|
|
344
|
+
requiredCount: number,
|
|
345
|
+
requiredCountExplanation: string
|
|
346
|
+
) {
|
|
347
|
+
if (basicEffectPool.length >= requiredCount) return;
|
|
348
|
+
throw new Error(`Legal ${effectTypeName} count is insufficient to support ${requiredCountExplanation} (${basicEffectPool.length} / ${requiredCount}).`);
|
|
349
|
+
}
|
|
350
|
+
|
|
293
351
|
unrejectableMovesInSingles(move: Move) {
|
|
294
352
|
// These moves cannot be rejected in favor of a forced move in singles
|
|
295
353
|
return (move.category !== 'Status' || !move.flags.heal) && ![
|
|
@@ -304,6 +362,8 @@ export class RandomTeams {
|
|
|
304
362
|
}
|
|
305
363
|
|
|
306
364
|
randomCCTeam(): RandomTeamsTypes.RandomSet[] {
|
|
365
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
366
|
+
|
|
307
367
|
const dex = this.dex;
|
|
308
368
|
const team = [];
|
|
309
369
|
|
|
@@ -463,7 +523,7 @@ export class RandomTeams {
|
|
|
463
523
|
return team;
|
|
464
524
|
}
|
|
465
525
|
|
|
466
|
-
randomNPokemon(n: number, requiredType?: string, minSourceGen?: number) {
|
|
526
|
+
randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: Dex.RuleTable) {
|
|
467
527
|
// Picks `n` random pokemon--no repeats, even among formes
|
|
468
528
|
// Also need to either normalize for formes or select formes at random
|
|
469
529
|
// Unreleased are okay but no CAP
|
|
@@ -474,15 +534,65 @@ export class RandomTeams {
|
|
|
474
534
|
throw new Error(`"${requiredType}" is not a valid type.`);
|
|
475
535
|
}
|
|
476
536
|
|
|
537
|
+
const isNotCustom = !ruleTable;
|
|
538
|
+
|
|
477
539
|
const pool: number[] = [];
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
540
|
+
let speciesPool: Species[] = [];
|
|
541
|
+
if (isNotCustom) {
|
|
542
|
+
speciesPool = [...this.dex.species.all()];
|
|
543
|
+
for (const species of speciesPool) {
|
|
544
|
+
if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
|
|
545
|
+
if (requiredType && !species.types.includes(requiredType)) continue;
|
|
546
|
+
if (minSourceGen && species.gen < minSourceGen) continue;
|
|
547
|
+
const num = species.num;
|
|
548
|
+
if (num <= 0 || pool.includes(num)) continue;
|
|
549
|
+
if (num > last) break;
|
|
550
|
+
pool.push(num);
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
|
|
554
|
+
const nonexistentBanReason = ruleTable.check('nonexistent');
|
|
555
|
+
// Assume tierSpecies does not differ from species here (mega formes can be used without their stone, etc)
|
|
556
|
+
for (const species of this.dex.species.all()) {
|
|
557
|
+
if (requiredType && !species.types.includes(requiredType)) continue;
|
|
558
|
+
|
|
559
|
+
let banReason = ruleTable.check('pokemon:' + species.id);
|
|
560
|
+
if (banReason) continue;
|
|
561
|
+
if (banReason !== '') {
|
|
562
|
+
if (species.isMega && ruleTable.check('pokemontag:mega')) continue;
|
|
563
|
+
|
|
564
|
+
banReason = ruleTable.check('basepokemon:' + toID(species.baseSpecies));
|
|
565
|
+
if (banReason) continue;
|
|
566
|
+
if (banReason !== '' || this.dex.species.get(species.baseSpecies).isNonstandard === species.isNonstandard) {
|
|
567
|
+
const nonexistentCheck = Tags.nonexistent.genericFilter!(species) && nonexistentBanReason;
|
|
568
|
+
let tagWhitelisted = false;
|
|
569
|
+
let tagBlacklisted = false;
|
|
570
|
+
for (const ruleid of ruleTable.tagRules) {
|
|
571
|
+
if (ruleid.startsWith('*')) continue;
|
|
572
|
+
const tagid = ruleid.slice(12);
|
|
573
|
+
const tag = Tags[tagid];
|
|
574
|
+
if ((tag.speciesFilter || tag.genericFilter)!(species)) {
|
|
575
|
+
const existenceTag = EXISTENCE_TAG.includes(tagid);
|
|
576
|
+
if (ruleid.startsWith('+')) {
|
|
577
|
+
if (!existenceTag && nonexistentCheck) continue;
|
|
578
|
+
tagWhitelisted = true;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
tagBlacklisted = true;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (tagBlacklisted) continue;
|
|
586
|
+
if (!tagWhitelisted) {
|
|
587
|
+
if (ruleTable.check('pokemontag:allpokemon')) continue;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const num = species.num;
|
|
592
|
+
if (pool.includes(num)) continue;
|
|
593
|
+
pool.push(num);
|
|
594
|
+
speciesPool.push(species);
|
|
595
|
+
}
|
|
486
596
|
}
|
|
487
597
|
|
|
488
598
|
const hasDexNumber: {[k: string]: number} = {};
|
|
@@ -492,12 +602,16 @@ export class RandomTeams {
|
|
|
492
602
|
}
|
|
493
603
|
|
|
494
604
|
const formes: string[][] = [];
|
|
495
|
-
for (const species of
|
|
605
|
+
for (const species of speciesPool) {
|
|
496
606
|
if (!(species.num in hasDexNumber)) continue;
|
|
497
|
-
if (species.gen
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
607
|
+
if (isNotCustom && (species.gen > this.gen ||
|
|
608
|
+
(species.isNonstandard && species.isNonstandard !== 'Unobtainable'))) continue;
|
|
609
|
+
if (!formes[hasDexNumber[species.num]]) formes[hasDexNumber[species.num]] = [];
|
|
610
|
+
formes[hasDexNumber[species.num]].push(species.name);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (formes.length < n) {
|
|
614
|
+
throw new Error(`Legal Pokemon forme count insufficient to support Max Team Size: (${formes.length} / ${n}).`);
|
|
501
615
|
}
|
|
502
616
|
|
|
503
617
|
const nPokemon = [];
|
|
@@ -511,15 +625,138 @@ export class RandomTeams {
|
|
|
511
625
|
}
|
|
512
626
|
|
|
513
627
|
randomHCTeam(): PokemonSet[] {
|
|
514
|
-
const
|
|
628
|
+
const hasCustomBans = this.hasDirectCustomBanlistChanges();
|
|
629
|
+
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
630
|
+
const hasNonexistentBan = hasCustomBans && ruleTable.check('nonexistent');
|
|
631
|
+
const hasNonexistentWhitelist = hasCustomBans && (hasNonexistentBan === '');
|
|
515
632
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const naturePool = this.dex.natures.all();
|
|
633
|
+
if (hasCustomBans) {
|
|
634
|
+
this.enforceNoDirectComplexBans();
|
|
635
|
+
}
|
|
520
636
|
|
|
521
|
-
|
|
637
|
+
// Item Pool
|
|
638
|
+
const doItemsExist = this.gen > 1;
|
|
639
|
+
let itemPool: Item[] = [];
|
|
640
|
+
if (doItemsExist) {
|
|
641
|
+
if (!hasCustomBans) {
|
|
642
|
+
itemPool = [...this.dex.items.all()].filter(item => (item.gen <= this.gen && !item.isNonstandard));
|
|
643
|
+
} else {
|
|
644
|
+
const hasAllItemsBan = ruleTable.check('pokemontag:allitems');
|
|
645
|
+
for (const item of this.dex.items.all()) {
|
|
646
|
+
let banReason = ruleTable.check('item:' + item.id);
|
|
647
|
+
if (banReason) continue;
|
|
648
|
+
if (banReason !== '' && item.id) {
|
|
649
|
+
if (hasAllItemsBan) continue;
|
|
650
|
+
if (item.isNonstandard) {
|
|
651
|
+
banReason = ruleTable.check('pokemontag:' + toID(item.isNonstandard));
|
|
652
|
+
if (banReason) continue;
|
|
653
|
+
if (banReason !== '' && item.isNonstandard !== 'Unobtainable') {
|
|
654
|
+
if (hasNonexistentBan) continue;
|
|
655
|
+
if (!hasNonexistentWhitelist) continue;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
itemPool.push(item);
|
|
660
|
+
}
|
|
661
|
+
if (ruleTable.check('item:noitem')) {
|
|
662
|
+
this.enforceCustomPoolSizeNoComplexBans('item', itemPool, this.maxTeamSize, 'Max Team Size');
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Ability Pool
|
|
668
|
+
const doAbilitiesExist = (this.gen > 2) && (this.dex.currentMod !== 'gen7letsgo');
|
|
669
|
+
let abilityPool: Ability[] = [];
|
|
670
|
+
if (doAbilitiesExist) {
|
|
671
|
+
if (!hasCustomBans) {
|
|
672
|
+
abilityPool = [...this.dex.abilities.all()].filter(ability => (ability.gen <= this.gen && !ability.isNonstandard));
|
|
673
|
+
} else {
|
|
674
|
+
const hasAllAbilitiesBan = ruleTable.check('pokemontag:allabilities');
|
|
675
|
+
for (const ability of this.dex.abilities.all()) {
|
|
676
|
+
let banReason = ruleTable.check('ability:' + ability.id);
|
|
677
|
+
if (banReason) continue;
|
|
678
|
+
if (banReason !== '') {
|
|
679
|
+
if (hasAllAbilitiesBan) continue;
|
|
680
|
+
if (ability.isNonstandard) {
|
|
681
|
+
banReason = ruleTable.check('pokemontag:' + toID(ability.isNonstandard));
|
|
682
|
+
if (banReason) continue;
|
|
683
|
+
if (banReason !== '') {
|
|
684
|
+
if (hasNonexistentBan) continue;
|
|
685
|
+
if (!hasNonexistentWhitelist) continue;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
abilityPool.push(ability);
|
|
690
|
+
}
|
|
691
|
+
if (ruleTable.check('ability:noability')) {
|
|
692
|
+
this.enforceCustomPoolSizeNoComplexBans('ability', abilityPool, this.maxTeamSize, 'Max Team Size');
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
522
696
|
|
|
697
|
+
// Move Pool
|
|
698
|
+
const setMoveCount = ruleTable.maxMoveCount;
|
|
699
|
+
let movePool: Move[] = [];
|
|
700
|
+
if (!hasCustomBans) {
|
|
701
|
+
movePool = [...this.dex.moves.all()].filter(move =>
|
|
702
|
+
(move.gen <= this.gen && !move.isNonstandard && !move.name.startsWith('Hidden Power ')));
|
|
703
|
+
} else {
|
|
704
|
+
const hasAllMovesBan = ruleTable.check('pokemontag:allmoves');
|
|
705
|
+
for (const move of this.dex.moves.all()) {
|
|
706
|
+
// Legality of specific HP types can't be altered in built formats anyway
|
|
707
|
+
if (move.name.startsWith('Hidden Power ')) continue;
|
|
708
|
+
let banReason = ruleTable.check('move:' + move.id);
|
|
709
|
+
if (banReason) continue;
|
|
710
|
+
if (banReason !== '') {
|
|
711
|
+
if (hasAllMovesBan) continue;
|
|
712
|
+
if (move.isNonstandard) {
|
|
713
|
+
banReason = ruleTable.check('pokemontag:' + toID(move.isNonstandard));
|
|
714
|
+
if (banReason) continue;
|
|
715
|
+
if (banReason !== '' && move.isNonstandard !== 'Unobtainable') {
|
|
716
|
+
if (hasNonexistentBan) continue;
|
|
717
|
+
if (!hasNonexistentWhitelist) continue;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
movePool.push(move);
|
|
722
|
+
}
|
|
723
|
+
this.enforceCustomPoolSizeNoComplexBans('move', movePool, this.maxTeamSize * setMoveCount, 'Max Team Size * Max Move Count');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Nature Pool
|
|
727
|
+
const doNaturesExist = this.gen > 2;
|
|
728
|
+
let naturePool: Nature[] = [];
|
|
729
|
+
if (doNaturesExist) {
|
|
730
|
+
if (!hasCustomBans) {
|
|
731
|
+
if (!hasCustomBans) {
|
|
732
|
+
naturePool = [...this.dex.natures.all()];
|
|
733
|
+
} else {
|
|
734
|
+
const hasAllNaturesBan = ruleTable.check('pokemontag:allnatures');
|
|
735
|
+
for (const nature of this.dex.natures.all()) {
|
|
736
|
+
let banReason = ruleTable.check('nature:' + nature.id);
|
|
737
|
+
if (banReason) continue;
|
|
738
|
+
if (banReason !== '' && nature.id) {
|
|
739
|
+
if (hasAllNaturesBan) continue;
|
|
740
|
+
if (nature.isNonstandard) {
|
|
741
|
+
banReason = ruleTable.check('pokemontag:' + toID(nature.isNonstandard));
|
|
742
|
+
if (banReason) continue;
|
|
743
|
+
if (banReason !== '' && nature.isNonstandard !== 'Unobtainable') {
|
|
744
|
+
if (hasNonexistentBan) continue;
|
|
745
|
+
if (!hasNonexistentWhitelist) continue;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
naturePool.push(nature);
|
|
750
|
+
}
|
|
751
|
+
// There is no 'nature:nonature' rule so do not constrain pool size
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, undefined,
|
|
757
|
+
hasCustomBans ? ruleTable : undefined);
|
|
758
|
+
|
|
759
|
+
const team = [];
|
|
523
760
|
for (const forme of randomN) {
|
|
524
761
|
// Choose forme
|
|
525
762
|
const species = this.dex.species.get(forme);
|
|
@@ -527,31 +764,25 @@ export class RandomTeams {
|
|
|
527
764
|
// Random unique item
|
|
528
765
|
let item = '';
|
|
529
766
|
let itemData;
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
item = itemData.name;
|
|
534
|
-
} while (itemData.gen > this.gen || itemData.isNonstandard);
|
|
767
|
+
if (doItemsExist) {
|
|
768
|
+
itemData = this.sampleNoReplace(itemPool);
|
|
769
|
+
item = itemData?.name;
|
|
535
770
|
}
|
|
536
771
|
|
|
537
772
|
// Random unique ability
|
|
538
773
|
let ability = 'No Ability';
|
|
539
774
|
let abilityData;
|
|
540
|
-
if (
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
ability = abilityData.name;
|
|
544
|
-
} while (abilityData.gen > this.gen || abilityData.isNonstandard);
|
|
775
|
+
if (doAbilitiesExist) {
|
|
776
|
+
abilityData = this.sampleNoReplace(abilityPool);
|
|
777
|
+
ability = abilityData?.name;
|
|
545
778
|
}
|
|
546
779
|
|
|
547
780
|
// Random unique moves
|
|
548
781
|
const m = [];
|
|
549
782
|
do {
|
|
550
783
|
const move = this.sampleNoReplace(movePool);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
} while (m.length < 4);
|
|
784
|
+
m.push(move.id);
|
|
785
|
+
} while (m.length < setMoveCount);
|
|
555
786
|
|
|
556
787
|
// Random EVs
|
|
557
788
|
const evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
|
@@ -580,7 +811,10 @@ export class RandomTeams {
|
|
|
580
811
|
};
|
|
581
812
|
|
|
582
813
|
// Random nature
|
|
583
|
-
|
|
814
|
+
let nature = '';
|
|
815
|
+
if (doNaturesExist && (naturePool.length > 0)) {
|
|
816
|
+
nature = this.sample(naturePool).name;
|
|
817
|
+
}
|
|
584
818
|
|
|
585
819
|
// Level balance
|
|
586
820
|
const mbstmin = 1307;
|
|
@@ -697,7 +931,7 @@ export class RandomTeams {
|
|
|
697
931
|
// Moves with secondary effects:
|
|
698
932
|
if (move.secondary) {
|
|
699
933
|
counter.add('sheerforce');
|
|
700
|
-
if (move
|
|
934
|
+
if (sereneGraceBenefits(move)) {
|
|
701
935
|
counter.add('serenegrace');
|
|
702
936
|
}
|
|
703
937
|
}
|
|
@@ -1086,10 +1320,6 @@ export class RandomTeams {
|
|
|
1086
1320
|
case 'airslash':
|
|
1087
1321
|
return {cull:
|
|
1088
1322
|
(species.id === 'naganadel' && moves.has('nastyplot')) ||
|
|
1089
|
-
// I'm told that Nasty Plot Noctowl wants Air Slash (presumably for consistent damage),
|
|
1090
|
-
// and Defog Noctowl wants Hurricane—presumably for a high-risk, high-reward damaging move
|
|
1091
|
-
// after its main job of removing hazards is done.
|
|
1092
|
-
(species.id === 'noctowl' && !counter.setupType) ||
|
|
1093
1323
|
hasRestTalk ||
|
|
1094
1324
|
(abilities.has('Simple') && !!counter.get('recovery')) ||
|
|
1095
1325
|
counter.setupType === 'Physical',
|
|
@@ -1098,9 +1328,7 @@ export class RandomTeams {
|
|
|
1098
1328
|
// Special case for Mew, which only wants Brave Bird with Swords Dance
|
|
1099
1329
|
return {cull: moves.has('dragondance')};
|
|
1100
1330
|
case 'hurricane':
|
|
1101
|
-
|
|
1102
|
-
const noctowlCase = (!isNoDynamax && !isDoubles && species.id === 'noctowl' && !!counter.setupType);
|
|
1103
|
-
return {cull: counter.setupType === 'Physical' || noctowlCase};
|
|
1331
|
+
return {cull: counter.setupType === 'Physical'};
|
|
1104
1332
|
case 'futuresight':
|
|
1105
1333
|
return {cull: moves.has('psyshock') || moves.has('trick') || movePool.includes('teleport')};
|
|
1106
1334
|
case 'photongeyser':
|
|
@@ -1469,7 +1697,7 @@ export class RandomTeams {
|
|
|
1469
1697
|
this.dex.getEffectiveness('Rock', species) >= 2 &&
|
|
1470
1698
|
!isDoubles
|
|
1471
1699
|
);
|
|
1472
|
-
if (species.
|
|
1700
|
+
if (species.nfe && !HDBBetterThanEviolite) return 'Eviolite';
|
|
1473
1701
|
|
|
1474
1702
|
// Ability based logic and miscellaneous logic
|
|
1475
1703
|
if (species.name === 'Wobbuffet' || ['Cheek Pouch', 'Harvest', 'Ripen'].includes(ability)) return 'Sitrus Berry';
|
|
@@ -1785,7 +2013,6 @@ export class RandomTeams {
|
|
|
1785
2013
|
// Iterate through the moves again, this time to cull them:
|
|
1786
2014
|
for (const moveid of moves) {
|
|
1787
2015
|
const move = this.dex.moves.get(moveid);
|
|
1788
|
-
|
|
1789
2016
|
let {cull, isSetup} = this.shouldCullMove(
|
|
1790
2017
|
move, types, moves, abilities, counter,
|
|
1791
2018
|
movePool, teamDetails, species, isLead, isDoubles, isNoDynamax
|
|
@@ -1806,7 +2033,7 @@ export class RandomTeams {
|
|
|
1806
2033
|
// Genesect-Douse should never reject Techno Blast
|
|
1807
2034
|
const moveIsRejectable = !(species.id === 'genesectdouse' && move.id === 'technoblast') && (
|
|
1808
2035
|
move.category === 'Status' ||
|
|
1809
|
-
!types.has(move.type) ||
|
|
2036
|
+
(!types.has(move.type) && move.id !== 'judgment') ||
|
|
1810
2037
|
(isLowBP && !move.multihit && !abilities.has('Technician'))
|
|
1811
2038
|
);
|
|
1812
2039
|
// Setup-supported moves should only be rejected under specific circumstances
|
|
@@ -1838,7 +2065,8 @@ export class RandomTeams {
|
|
|
1838
2065
|
) {
|
|
1839
2066
|
cull = true;
|
|
1840
2067
|
// Pokemon should have moves that benefit their typing
|
|
1841
|
-
|
|
2068
|
+
// Don't cull Sticky Web in type-based enforcement, and make sure Azumarill always has Aqua Jet
|
|
2069
|
+
} else if (move.id !== 'stickyweb' && !(species.id === 'azumarill' && move.id === 'aquajet')) {
|
|
1842
2070
|
for (const type of types) {
|
|
1843
2071
|
if (runEnforcementChecker(type)) {
|
|
1844
2072
|
cull = true;
|
|
@@ -2033,7 +2261,7 @@ export class RandomTeams {
|
|
|
2033
2261
|
PUBL: 87,
|
|
2034
2262
|
PU: 88, "(PU)": 88, NFE: 88,
|
|
2035
2263
|
};
|
|
2036
|
-
const customScale: {[k: string]: number} = {};
|
|
2264
|
+
const customScale: {[k: string]: number} = {delibird: 100, luvdisc: 100, spinda: 100, unown: 100};
|
|
2037
2265
|
|
|
2038
2266
|
level = customScale[species.id] || tierScale[species.tier] || 80;
|
|
2039
2267
|
// Arbitrary levelling base on data files (typically winrate-influenced)
|
|
@@ -2075,7 +2303,7 @@ export class RandomTeams {
|
|
|
2075
2303
|
// Minimize confusion damage
|
|
2076
2304
|
const noAttackStatMoves = [...moves].every(m => {
|
|
2077
2305
|
const move = this.dex.moves.get(m);
|
|
2078
|
-
if (move.damageCallback || move.damage) return
|
|
2306
|
+
if (move.damageCallback || move.damage) return true;
|
|
2079
2307
|
return move.category !== 'Physical' || move.id === 'bodypress';
|
|
2080
2308
|
});
|
|
2081
2309
|
if (noAttackStatMoves && !moves.has('transform') && (!moves.has('shellsidearm') || !counter.get('Status'))) {
|
|
@@ -2129,6 +2357,8 @@ export class RandomTeams {
|
|
|
2129
2357
|
}
|
|
2130
2358
|
|
|
2131
2359
|
randomTeam() {
|
|
2360
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
2361
|
+
|
|
2132
2362
|
const seed = this.prng.seed;
|
|
2133
2363
|
const ruleTable = this.dex.formats.getRuleTable(this.format);
|
|
2134
2364
|
const pokemon: RandomTeamsTypes.RandomSet[] = [];
|
|
@@ -2301,6 +2531,8 @@ export class RandomTeams {
|
|
|
2301
2531
|
randomCAP1v1Sets: AnyObject = {};
|
|
2302
2532
|
|
|
2303
2533
|
randomCAP1v1Team() {
|
|
2534
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
2535
|
+
|
|
2304
2536
|
const pokemon = [];
|
|
2305
2537
|
const pokemonPool = Object.keys(this.randomCAP1v1Sets);
|
|
2306
2538
|
|
|
@@ -2404,6 +2636,8 @@ export class RandomTeams {
|
|
|
2404
2636
|
}
|
|
2405
2637
|
|
|
2406
2638
|
randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
|
|
2639
|
+
this.enforceNoDirectCustomBanlistChanges();
|
|
2640
|
+
|
|
2407
2641
|
const forceResult = (depth >= 4);
|
|
2408
2642
|
|
|
2409
2643
|
const pokemon = [];
|