@pkmn/randoms 0.5.4 → 0.5.5

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/gen8.js CHANGED
@@ -210,6 +210,8 @@ class RandomTeams {
210
210
  */
211
211
  sampleNoReplace(list) {
212
212
  const length = list.length;
213
+ if (length === 0)
214
+ return null;
213
215
  const index = this.random(length);
214
216
  return this.fastPop(list, index);
215
217
  }
@@ -225,6 +227,49 @@ class RandomTeams {
225
227
  }
226
228
  return samples;
227
229
  }
230
+ /**
231
+ * Check if user has directly tried to ban/unban/restrict things in a custom battle.
232
+ * Doesn't count bans nested inside other formats/rules.
233
+ */
234
+ hasDirectCustomBanlistChanges() {
235
+ if (!this.format.customRules)
236
+ return false;
237
+ for (const rule of this.format.customRules) {
238
+ for (const banlistOperator of ['-', '+', '*']) {
239
+ if (rule.startsWith(banlistOperator))
240
+ return true;
241
+ }
242
+ }
243
+ return false;
244
+ }
245
+ /**
246
+ * Inform user when custom bans are unsupported in a team generator.
247
+ */
248
+ enforceNoDirectCustomBanlistChanges() {
249
+ if (this.hasDirectCustomBanlistChanges()) {
250
+ throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
251
+ }
252
+ }
253
+ /**
254
+ * Inform user when complex bans are unsupported in a team generator.
255
+ */
256
+ enforceNoDirectComplexBans() {
257
+ if (!this.format.customRules)
258
+ return false;
259
+ for (const rule of this.format.customRules) {
260
+ if (rule.includes('+') && !rule.startsWith('+')) {
261
+ throw new Error(`Complex bans are not currently supported in ${this.format.name}.`);
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Validate set element pool size is sufficient to support size requirements after simple bans.
267
+ */
268
+ enforceCustomPoolSizeNoComplexBans(effectTypeName, basicEffectPool, requiredCount, requiredCountExplanation) {
269
+ if (basicEffectPool.length >= requiredCount)
270
+ return;
271
+ throw new Error(`Legal ${effectTypeName} count is insufficient to support ${requiredCountExplanation} (${basicEffectPool.length} / ${requiredCount}).`);
272
+ }
228
273
  unrejectableMovesInSingles(move) {
229
274
  // These moves cannot be rejected in favor of a forced move in singles
230
275
  return (move.category !== 'Status' || !move.flags.heal) && ![
@@ -237,6 +282,7 @@ class RandomTeams {
237
282
  return move.id !== 'bodypress';
238
283
  }
239
284
  randomCCTeam() {
285
+ this.enforceNoDirectCustomBanlistChanges();
240
286
  const dex = this.dex;
241
287
  const team = [];
242
288
  const natures = this.dex.natures.all();
@@ -374,7 +420,7 @@ class RandomTeams {
374
420
  }
375
421
  return team;
376
422
  }
377
- randomNPokemon(n, requiredType, minSourceGen) {
423
+ randomNPokemon(n, requiredType, minSourceGen, ruleTable) {
378
424
  // Picks `n` random pokemon--no repeats, even among formes
379
425
  // Also need to either normalize for formes or select formes at random
380
426
  // Unreleased are okay but no CAP
@@ -384,20 +430,77 @@ class RandomTeams {
384
430
  if (requiredType && !this.dex.types.get(requiredType).exists) {
385
431
  throw new Error(`"${requiredType}" is not a valid type.`);
386
432
  }
433
+ const isNotCustom = !ruleTable;
387
434
  const pool = [];
388
- for (const species of this.dex.species.all()) {
389
- if (species.isNonstandard && species.isNonstandard !== 'Unobtainable')
390
- continue;
391
- if (requiredType && !species.types.includes(requiredType))
392
- continue;
393
- if (minSourceGen && species.gen < minSourceGen)
394
- continue;
395
- const num = species.num;
396
- if (num <= 0 || pool.includes(num))
397
- continue;
398
- if (num > last)
399
- break;
400
- pool.push(num);
435
+ let speciesPool = [];
436
+ if (isNotCustom) {
437
+ speciesPool = [...this.dex.species.all()];
438
+ for (const species of speciesPool) {
439
+ if (species.isNonstandard && species.isNonstandard !== 'Unobtainable')
440
+ continue;
441
+ if (requiredType && !species.types.includes(requiredType))
442
+ continue;
443
+ if (minSourceGen && species.gen < minSourceGen)
444
+ continue;
445
+ const num = species.num;
446
+ if (num <= 0 || pool.includes(num))
447
+ continue;
448
+ if (num > last)
449
+ break;
450
+ pool.push(num);
451
+ }
452
+ }
453
+ else {
454
+ const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
455
+ const nonexistentBanReason = ruleTable.check('nonexistent');
456
+ // Assume tierSpecies does not differ from species here (mega formes can be used without their stone, etc)
457
+ for (const species of this.dex.species.all()) {
458
+ if (requiredType && !species.types.includes(requiredType))
459
+ continue;
460
+ let banReason = ruleTable.check('pokemon:' + species.id);
461
+ if (banReason)
462
+ continue;
463
+ if (banReason !== '') {
464
+ if (species.isMega && ruleTable.check('pokemontag:mega'))
465
+ continue;
466
+ banReason = ruleTable.check('basepokemon:' + (0, sim_1.toID)(species.baseSpecies));
467
+ if (banReason)
468
+ continue;
469
+ if (banReason !== '' || this.dex.species.get(species.baseSpecies).isNonstandard === species.isNonstandard) {
470
+ const nonexistentCheck = sim_1.Tags.nonexistent.genericFilter(species) && nonexistentBanReason;
471
+ let tagWhitelisted = false;
472
+ let tagBlacklisted = false;
473
+ for (const ruleid of ruleTable.tagRules) {
474
+ if (ruleid.startsWith('*'))
475
+ continue;
476
+ const tagid = ruleid.slice(12);
477
+ const tag = sim_1.Tags[tagid];
478
+ if ((tag.speciesFilter || tag.genericFilter)(species)) {
479
+ const existenceTag = EXISTENCE_TAG.includes(tagid);
480
+ if (ruleid.startsWith('+')) {
481
+ if (!existenceTag && nonexistentCheck)
482
+ continue;
483
+ tagWhitelisted = true;
484
+ break;
485
+ }
486
+ tagBlacklisted = true;
487
+ break;
488
+ }
489
+ }
490
+ if (tagBlacklisted)
491
+ continue;
492
+ if (!tagWhitelisted) {
493
+ if (ruleTable.check('pokemontag:allpokemon'))
494
+ continue;
495
+ }
496
+ }
497
+ }
498
+ const num = species.num;
499
+ if (pool.includes(num))
500
+ continue;
501
+ pool.push(num);
502
+ speciesPool.push(species);
503
+ }
401
504
  }
402
505
  const hasDexNumber = {};
403
506
  for (let i = 0; i < n; i++) {
@@ -405,14 +508,18 @@ class RandomTeams {
405
508
  hasDexNumber[num] = i;
406
509
  }
407
510
  const formes = [];
408
- for (const species of this.dex.species.all()) {
511
+ for (const species of speciesPool) {
409
512
  if (!(species.num in hasDexNumber))
410
513
  continue;
411
- if (species.gen <= this.gen && (!species.isNonstandard || species.isNonstandard === 'Unobtainable')) {
412
- if (!formes[hasDexNumber[species.num]])
413
- formes[hasDexNumber[species.num]] = [];
414
- formes[hasDexNumber[species.num]].push(species.name);
415
- }
514
+ if (isNotCustom && (species.gen > this.gen ||
515
+ (species.isNonstandard && species.isNonstandard !== 'Unobtainable')))
516
+ continue;
517
+ if (!formes[hasDexNumber[species.num]])
518
+ formes[hasDexNumber[species.num]] = [];
519
+ formes[hasDexNumber[species.num]].push(species.name);
520
+ }
521
+ if (formes.length < n) {
522
+ throw new Error(`Legal Pokemon forme count insufficient to support Max Team Size: (${formes.length} / ${n}).`);
416
523
  }
417
524
  const nPokemon = [];
418
525
  for (let i = 0; i < n; i++) {
@@ -424,41 +531,177 @@ class RandomTeams {
424
531
  return nPokemon;
425
532
  }
426
533
  randomHCTeam() {
534
+ const hasCustomBans = this.hasDirectCustomBanlistChanges();
535
+ const ruleTable = this.dex.formats.getRuleTable(this.format);
536
+ const hasNonexistentBan = hasCustomBans && ruleTable.check('nonexistent');
537
+ const hasNonexistentWhitelist = hasCustomBans && (hasNonexistentBan === '');
538
+ if (hasCustomBans) {
539
+ this.enforceNoDirectComplexBans();
540
+ }
541
+ // Item Pool
542
+ const doItemsExist = this.gen > 1;
543
+ let itemPool = [];
544
+ if (doItemsExist) {
545
+ if (!hasCustomBans) {
546
+ itemPool = [...this.dex.items.all()].filter(item => (item.gen <= this.gen && !item.isNonstandard));
547
+ }
548
+ else {
549
+ const hasAllItemsBan = ruleTable.check('pokemontag:allitems');
550
+ for (const item of this.dex.items.all()) {
551
+ let banReason = ruleTable.check('item:' + item.id);
552
+ if (banReason)
553
+ continue;
554
+ if (banReason !== '' && item.id) {
555
+ if (hasAllItemsBan)
556
+ continue;
557
+ if (item.isNonstandard) {
558
+ banReason = ruleTable.check('pokemontag:' + (0, sim_1.toID)(item.isNonstandard));
559
+ if (banReason)
560
+ continue;
561
+ if (banReason !== '' && item.isNonstandard !== 'Unobtainable') {
562
+ if (hasNonexistentBan)
563
+ continue;
564
+ if (!hasNonexistentWhitelist)
565
+ continue;
566
+ }
567
+ }
568
+ }
569
+ itemPool.push(item);
570
+ }
571
+ if (ruleTable.check('item:noitem')) {
572
+ this.enforceCustomPoolSizeNoComplexBans('item', itemPool, this.maxTeamSize, 'Max Team Size');
573
+ }
574
+ }
575
+ }
576
+ // Ability Pool
577
+ const doAbilitiesExist = (this.gen > 2) && (this.dex.currentMod !== 'gen7letsgo');
578
+ let abilityPool = [];
579
+ if (doAbilitiesExist) {
580
+ if (!hasCustomBans) {
581
+ abilityPool = [...this.dex.abilities.all()].filter(ability => (ability.gen <= this.gen && !ability.isNonstandard));
582
+ }
583
+ else {
584
+ const hasAllAbilitiesBan = ruleTable.check('pokemontag:allabilities');
585
+ for (const ability of this.dex.abilities.all()) {
586
+ let banReason = ruleTable.check('ability:' + ability.id);
587
+ if (banReason)
588
+ continue;
589
+ if (banReason !== '') {
590
+ if (hasAllAbilitiesBan)
591
+ continue;
592
+ if (ability.isNonstandard) {
593
+ banReason = ruleTable.check('pokemontag:' + (0, sim_1.toID)(ability.isNonstandard));
594
+ if (banReason)
595
+ continue;
596
+ if (banReason !== '') {
597
+ if (hasNonexistentBan)
598
+ continue;
599
+ if (!hasNonexistentWhitelist)
600
+ continue;
601
+ }
602
+ }
603
+ }
604
+ abilityPool.push(ability);
605
+ }
606
+ if (ruleTable.check('ability:noability')) {
607
+ this.enforceCustomPoolSizeNoComplexBans('ability', abilityPool, this.maxTeamSize, 'Max Team Size');
608
+ }
609
+ }
610
+ }
611
+ // Move Pool
612
+ const setMoveCount = ruleTable.maxMoveCount;
613
+ let movePool = [];
614
+ if (!hasCustomBans) {
615
+ movePool = [...this.dex.moves.all()].filter(move => (move.gen <= this.gen && !move.isNonstandard && !move.name.startsWith('Hidden Power ')));
616
+ }
617
+ else {
618
+ const hasAllMovesBan = ruleTable.check('pokemontag:allmoves');
619
+ for (const move of this.dex.moves.all()) {
620
+ // Legality of specific HP types can't be altered in built formats anyway
621
+ if (move.name.startsWith('Hidden Power '))
622
+ continue;
623
+ let banReason = ruleTable.check('move:' + move.id);
624
+ if (banReason)
625
+ continue;
626
+ if (banReason !== '') {
627
+ if (hasAllMovesBan)
628
+ continue;
629
+ if (move.isNonstandard) {
630
+ banReason = ruleTable.check('pokemontag:' + (0, sim_1.toID)(move.isNonstandard));
631
+ if (banReason)
632
+ continue;
633
+ if (banReason !== '' && move.isNonstandard !== 'Unobtainable') {
634
+ if (hasNonexistentBan)
635
+ continue;
636
+ if (!hasNonexistentWhitelist)
637
+ continue;
638
+ }
639
+ }
640
+ }
641
+ movePool.push(move);
642
+ }
643
+ this.enforceCustomPoolSizeNoComplexBans('move', movePool, this.maxTeamSize * setMoveCount, 'Max Team Size * Max Move Count');
644
+ }
645
+ // Nature Pool
646
+ const doNaturesExist = this.gen > 2;
647
+ let naturePool = [];
648
+ if (doNaturesExist) {
649
+ if (!hasCustomBans) {
650
+ if (!hasCustomBans) {
651
+ naturePool = [...this.dex.natures.all()];
652
+ }
653
+ else {
654
+ const hasAllNaturesBan = ruleTable.check('pokemontag:allnatures');
655
+ for (const nature of this.dex.natures.all()) {
656
+ let banReason = ruleTable.check('nature:' + nature.id);
657
+ if (banReason)
658
+ continue;
659
+ if (banReason !== '' && nature.id) {
660
+ if (hasAllNaturesBan)
661
+ continue;
662
+ if (nature.isNonstandard) {
663
+ banReason = ruleTable.check('pokemontag:' + (0, sim_1.toID)(nature.isNonstandard));
664
+ if (banReason)
665
+ continue;
666
+ if (banReason !== '' && nature.isNonstandard !== 'Unobtainable') {
667
+ if (hasNonexistentBan)
668
+ continue;
669
+ if (!hasNonexistentWhitelist)
670
+ continue;
671
+ }
672
+ }
673
+ }
674
+ naturePool.push(nature);
675
+ }
676
+ // There is no 'nature:nonature' rule so do not constrain pool size
677
+ }
678
+ }
679
+ }
680
+ const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, undefined, hasCustomBans ? ruleTable : undefined);
427
681
  const team = [];
428
- const itemPool = [...this.dex.items.all()];
429
- const abilityPool = [...this.dex.abilities.all()];
430
- const movePool = [...this.dex.moves.all()];
431
- const naturePool = this.dex.natures.all();
432
- const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype);
433
682
  for (const forme of randomN) {
434
683
  // Choose forme
435
684
  const species = this.dex.species.get(forme);
436
685
  // Random unique item
437
686
  let item = '';
438
687
  let itemData;
439
- if (this.gen >= 2) {
440
- do {
441
- itemData = this.sampleNoReplace(itemPool);
442
- item = itemData.name;
443
- } while (itemData.gen > this.gen || itemData.isNonstandard);
688
+ if (doItemsExist) {
689
+ itemData = this.sampleNoReplace(itemPool);
690
+ item = itemData === null || itemData === void 0 ? void 0 : itemData.name;
444
691
  }
445
692
  // Random unique ability
446
693
  let ability = 'No Ability';
447
694
  let abilityData;
448
- if (this.gen >= 3) {
449
- do {
450
- abilityData = this.sampleNoReplace(abilityPool);
451
- ability = abilityData.name;
452
- } while (abilityData.gen > this.gen || abilityData.isNonstandard);
695
+ if (doAbilitiesExist) {
696
+ abilityData = this.sampleNoReplace(abilityPool);
697
+ ability = abilityData === null || abilityData === void 0 ? void 0 : abilityData.name;
453
698
  }
454
699
  // Random unique moves
455
700
  const m = [];
456
701
  do {
457
702
  const move = this.sampleNoReplace(movePool);
458
- if (move.gen <= this.gen && !move.isNonstandard && !move.name.startsWith('Hidden Power ')) {
459
- m.push(move.id);
460
- }
461
- } while (m.length < 4);
703
+ m.push(move.id);
704
+ } while (m.length < setMoveCount);
462
705
  // Random EVs
463
706
  const evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
464
707
  if (this.gen === 6) {
@@ -485,7 +728,10 @@ class RandomTeams {
485
728
  spe: this.random(32),
486
729
  };
487
730
  // Random nature
488
- const nature = this.sample(naturePool).name;
731
+ let nature = '';
732
+ if (doNaturesExist && (naturePool.length > 0)) {
733
+ nature = this.sample(naturePool).name;
734
+ }
489
735
  // Level balance
490
736
  const mbstmin = 1307;
491
737
  const stats = species.baseStats;
@@ -1935,6 +2181,7 @@ class RandomTeams {
1935
2181
  }
1936
2182
  randomTeam() {
1937
2183
  var _a, _b;
2184
+ this.enforceNoDirectCustomBanlistChanges();
1938
2185
  const seed = this.prng.seed;
1939
2186
  const ruleTable = this.dex.formats.getRuleTable(this.format);
1940
2187
  const pokemon = [];
@@ -2124,6 +2371,7 @@ class RandomTeams {
2124
2371
  return pokemon;
2125
2372
  }
2126
2373
  randomCAP1v1Team() {
2374
+ this.enforceNoDirectCustomBanlistChanges();
2127
2375
  const pokemon = [];
2128
2376
  const pokemonPool = Object.keys(this.randomCAP1v1Sets);
2129
2377
  while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
@@ -2220,6 +2468,7 @@ class RandomTeams {
2220
2468
  }
2221
2469
  randomBSSFactoryTeam(side, depth = 0) {
2222
2470
  var _a;
2471
+ this.enforceNoDirectCustomBanlistChanges();
2223
2472
  const forceResult = (depth >= 4);
2224
2473
  const pokemon = [];
2225
2474
  const pokemonPool = Object.keys(this.randomBSSFactorySets);