@pkmn/randoms 0.5.2 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gen3.ts 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) => !counter.get('Normal') && counter.setupType === 'Physical',
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
- return {cull: !moves.has('rest')};
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.evos.length) return 'Eviolite';
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.evos.length) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
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
@@ -732,7 +732,7 @@ export class RandomGen7Teams extends RandomTeams {
732
732
  if (ability === 'Harvest' || ability === 'Emergency Exit' && !!counter.get('Status')) return 'Sitrus Berry';
733
733
  if (ability === 'Imposter') return 'Choice Scarf';
734
734
  if (ability === 'Poison Heal') return 'Toxic Orb';
735
- if (species.evos.length) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
735
+ if (species.nfe) return (ability === 'Technician' && counter.get('Physical') >= 4) ? 'Choice Band' : 'Eviolite';
736
736
  if (moves.has('switcheroo') || moves.has('trick')) {
737
737
  if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108) {
738
738
  return 'Choice Scarf';
@@ -1467,6 +1467,8 @@ export class RandomGen7Teams extends RandomTeams {
1467
1467
  }
1468
1468
 
1469
1469
  randomTeam() {
1470
+ this.enforceNoDirectCustomBanlistChanges();
1471
+
1470
1472
  const seed = this.prng.seed;
1471
1473
  const ruleTable = this.dex.formats.getRuleTable(this.format);
1472
1474
  const pokemon = [];
@@ -1739,6 +1741,8 @@ export class RandomGen7Teams extends RandomTeams {
1739
1741
  }
1740
1742
 
1741
1743
  randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
1744
+ this.enforceNoDirectCustomBanlistChanges();
1745
+
1742
1746
  const forceResult = (depth >= 4);
1743
1747
  const isMonotype = !!this.forceMonotype || this.dex.formats.getRuleTable(this.format).has('sametypeclause');
1744
1748
 
@@ -2026,6 +2030,8 @@ export class RandomGen7Teams extends RandomTeams {
2026
2030
  }
2027
2031
 
2028
2032
  randomBSSFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
2033
+ this.enforceNoDirectCustomBanlistChanges();
2034
+
2029
2035
  const forceResult = (depth >= 4);
2030
2036
 
2031
2037
  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
- ID,
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
- for (const species of this.dex.species.all()) {
479
- if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
480
- if (requiredType && !species.types.includes(requiredType)) continue;
481
- if (minSourceGen && species.gen < minSourceGen) continue;
482
- const num = species.num;
483
- if (num <= 0 || pool.includes(num)) continue;
484
- if (num > last) break;
485
- pool.push(num);
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 this.dex.species.all()) {
605
+ for (const species of speciesPool) {
496
606
  if (!(species.num in hasDexNumber)) continue;
497
- if (species.gen <= this.gen && (!species.isNonstandard || species.isNonstandard === 'Unobtainable')) {
498
- if (!formes[hasDexNumber[species.num]]) formes[hasDexNumber[species.num]] = [];
499
- formes[hasDexNumber[species.num]].push(species.name);
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 team = [];
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
- const itemPool = [...this.dex.items.all()];
517
- const abilityPool = [...this.dex.abilities.all()];
518
- const movePool = [...this.dex.moves.all()];
519
- const naturePool = this.dex.natures.all();
633
+ if (hasCustomBans) {
634
+ this.enforceNoDirectComplexBans();
635
+ }
520
636
 
521
- const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype);
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
+ }
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
+ }
522
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 (this.gen >= 2) {
531
- do {
532
- itemData = this.sampleNoReplace(itemPool);
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 (this.gen >= 3) {
541
- do {
542
- abilityData = this.sampleNoReplace(abilityPool);
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
- if (move.gen <= this.gen && !move.isNonstandard && !move.name.startsWith('Hidden Power ')) {
552
- m.push(move.id);
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
- const nature = this.sample(naturePool).name;
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.secondary.chance && move.secondary.chance >= 20 && move.secondary.chance < 100) {
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
- // Special case for Noctowl, which wants Air Slash if Nasty Plot instead
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.evos.length && !HDBBetterThanEviolite) return 'Eviolite';
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
@@ -1816,7 +2043,6 @@ export class RandomTeams {
1816
2043
  (counter.get(counter.setupType) + counter.get('Status') > 3 && !counter.get('hazards')) ||
1817
2044
  (move.category !== counter.setupType && move.category !== 'Status')
1818
2045
  );
1819
-
1820
2046
  if (moveIsRejectable && (
1821
2047
  !cull && !isSetup && !move.weather && !move.stallingMove && notImportantSetup && !move.damage &&
1822
2048
  (isDoubles ? this.unrejectableMovesInDoubles(move) : this.unrejectableMovesInSingles(move))
@@ -1838,7 +2064,8 @@ export class RandomTeams {
1838
2064
  ) {
1839
2065
  cull = true;
1840
2066
  // Pokemon should have moves that benefit their typing
1841
- } else if (move.id !== 'stickyweb') { // Don't cull Sticky Web in type-based enforcement
2067
+ // Don't cull Sticky Web in type-based enforcement, and make sure Azumarill always has Aqua Jet
2068
+ } else if (move.id !== 'stickyweb' && !(species.id === 'azumarill' && move.id === 'aquajet')) {
1842
2069
  for (const type of types) {
1843
2070
  if (runEnforcementChecker(type)) {
1844
2071
  cull = true;
@@ -1859,6 +2086,7 @@ export class RandomTeams {
1859
2086
  }
1860
2087
  }
1861
2088
 
2089
+
1862
2090
  // Remove rejected moves from the move list
1863
2091
  if (cull && movePool.length) {
1864
2092
  if (moveid.startsWith('hiddenpower')) hasHiddenPower = false;
@@ -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 false;
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 = [];