@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/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 +4 -0
- package/build/gen5.js.map +1 -1
- package/build/gen6.js +1 -0
- package/build/gen6.js.map +1 -1
- package/build/gen7.js +3 -0
- package/build/gen7.js.map +1 -1
- package/build/gen8.d.ts +19 -2
- package/build/gen8.js +289 -40
- 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 +5 -0
- package/src/gen6.ts +2 -0
- package/src/gen7.ts +6 -0
- package/src/gen8.ts +272 -36
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
|
511
|
+
for (const species of speciesPool) {
|
|
409
512
|
if (!(species.num in hasDexNumber))
|
|
410
513
|
continue;
|
|
411
|
-
if (species.gen
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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 (
|
|
440
|
-
|
|
441
|
-
|
|
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 (
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
459
|
-
|
|
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
|
-
|
|
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);
|