@pkmn/randoms 0.5.25 → 0.5.27

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/gen1.ts CHANGED
@@ -127,7 +127,6 @@ export class RandomGen1Teams extends RandomGen2Teams {
127
127
 
128
128
  /** Pokémon that are not wholly incompatible with the team, but still pretty bad */
129
129
  const rejectedButNotInvalidPool: string[] = [];
130
- const handicapMons = ['magikarp', 'weedle', 'kakuna', 'caterpie', 'metapod'];
131
130
  const nuTiers = ['UU', 'UUBL', 'NFE', 'LC', 'NU'];
132
131
  const uuTiers = ['NFE', 'UU', 'UUBL', 'NU'];
133
132
 
@@ -136,7 +135,6 @@ export class RandomGen1Teams extends RandomGen2Teams {
136
135
  const weaknessCount: {[k: string]: number} = {Electric: 0, Psychic: 0, Water: 0, Ice: 0, Ground: 0};
137
136
  let uberCount = 0;
138
137
  let nuCount = 0;
139
- let hasShitmon = false;
140
138
 
141
139
  const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype);
142
140
  while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
@@ -147,16 +145,6 @@ export class RandomGen1Teams extends RandomGen2Teams {
147
145
  // to face each other.
148
146
  if (species.id === 'ditto' && this.battleHasDitto) continue;
149
147
 
150
- // Really bad Pokémon shouldn't be leads.
151
- if (pokemon.length === 0 && handicapMons.includes(species.id)) continue;
152
-
153
- // Bias the tiers so you get less shitmons and only one of the two Ubers.
154
- // If you have a shitmon, don't get another
155
- if (handicapMons.includes(species.id) && hasShitmon) {
156
- rejectedButNotInvalidPool.push(species.id);
157
- continue;
158
- }
159
-
160
148
  // Dynamically scale limits for different team sizes. The default and minimum value is 1.
161
149
  const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
162
150
 
@@ -164,13 +152,13 @@ export class RandomGen1Teams extends RandomGen2Teams {
164
152
  switch (tier) {
165
153
  case 'LC':
166
154
  case 'NFE':
167
- // Don't add pre-evo mon if already 4 or more non-OUs, or if already 3 or more non-OUs with one being a shitmon
155
+ // Don't add pre-evo mon if already 4 or more non-OUs
168
156
  // Regardless, pre-evo mons are slightly less common.
169
- if (nuCount >= 4 * limitFactor || (hasShitmon && nuCount >= 4 * limitFactor - 1) || this.randomChance(1, 3)) continue;
157
+ if (nuCount >= 4 * limitFactor || this.randomChance(1, 3)) continue;
170
158
  break;
171
159
  case 'Uber':
172
- // If you have one of the worst mons we allow luck to give you all Ubers.
173
- if (uberCount >= 1 * limitFactor && !hasShitmon) continue;
160
+ // Only allow a single Uber.
161
+ if (uberCount >= 1 * limitFactor) continue;
174
162
  break;
175
163
  default:
176
164
  // OUs are fine. Otherwise 50% chance to skip mon if already 4 or more non-OUs.
@@ -241,9 +229,6 @@ export class RandomGen1Teams extends RandomGen2Teams {
241
229
  nuCount++;
242
230
  }
243
231
 
244
- // Is it Magikarp or one of the useless bugs?
245
- if (handicapMons.includes(species.id)) hasShitmon = true;
246
-
247
232
  // Ditto check
248
233
  if (species.id === 'ditto') this.battleHasDitto = true;
249
234
  }
package/src/gen8.ts CHANGED
@@ -35,6 +35,19 @@ export interface TeamData {
35
35
  eeveeLimCount?: number;
36
36
  gigantamax?: boolean;
37
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
+ }
38
51
  export class MoveCounter extends Utils.Multiset<string> {
39
52
  damagingMoves: Set<Move>;
40
53
  setupType: string;
@@ -2588,6 +2601,292 @@ export class RandomTeams {
2588
2601
  return pokemon;
2589
2602
  }
2590
2603
 
2604
+ randomFactorySets: {[format: string]: {[species: string]: BattleFactorySpecies}} = {};
2605
+
2606
+ randomFactorySet(
2607
+ species: Species, teamData: RandomTeamsTypes.FactoryTeamDetails, tier: string
2608
+ ): RandomTeamsTypes.RandomFactorySet | null {
2609
+ const id = toID(species.name);
2610
+ const setList = this.randomFactorySets[tier][id].sets;
2611
+
2612
+ const itemsMax: {[k: string]: number} = {
2613
+ choicespecs: 1,
2614
+ choiceband: 1,
2615
+ choicescarf: 1,
2616
+ };
2617
+ const movesMax: {[k: string]: number} = {
2618
+ rapidspin: 1,
2619
+ batonpass: 1,
2620
+ stealthrock: 1,
2621
+ defog: 1,
2622
+ spikes: 1,
2623
+ toxicspikes: 1,
2624
+ };
2625
+ const requiredMoves: {[k: string]: string} = {
2626
+ stealthrock: 'hazardSet',
2627
+ rapidspin: 'hazardClear',
2628
+ defog: 'hazardClear',
2629
+ };
2630
+ const weatherAbilities = ['drizzle', 'drought', 'snowwarning', 'sandstream'];
2631
+
2632
+ // Build a pool of eligible sets, given the team partners
2633
+ // Also keep track of sets with moves the team requires
2634
+ let effectivePool: {set: AnyObject, moveVariants?: number[]}[] = [];
2635
+ const priorityPool = [];
2636
+ for (const curSet of setList) {
2637
+ // if (this.forceMonotype && !species.types.includes(this.forceMonotype)) continue;
2638
+
2639
+ const item = this.dex.items.get(curSet.item);
2640
+ if (itemsMax[item.id] && teamData.has[item.id] >= itemsMax[item.id]) continue;
2641
+
2642
+ const ability = this.dex.abilities.get(curSet.ability);
2643
+ if (teamData.weather && weatherAbilities.includes(ability.id)) continue; // reject 2+ weather setters
2644
+
2645
+ let reject = false;
2646
+ let hasRequiredMove = false;
2647
+ const curSetVariants = [];
2648
+ for (const move of curSet.moves) {
2649
+ const variantIndex = this.random(move.length);
2650
+ const moveId = toID(move[variantIndex]);
2651
+ if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
2652
+ reject = true;
2653
+ break;
2654
+ }
2655
+ if (requiredMoves[moveId] && !teamData.has[requiredMoves[moveId]]) {
2656
+ hasRequiredMove = true;
2657
+ }
2658
+ curSetVariants.push(variantIndex);
2659
+ }
2660
+ if (reject) continue;
2661
+ effectivePool.push({set: curSet, moveVariants: curSetVariants});
2662
+ if (hasRequiredMove) priorityPool.push({set: curSet, moveVariants: curSetVariants});
2663
+ }
2664
+ if (priorityPool.length) effectivePool = priorityPool;
2665
+
2666
+ if (!effectivePool.length) {
2667
+ if (!teamData.forceResult) return null;
2668
+ for (const curSet of setList) {
2669
+ effectivePool.push({set: curSet});
2670
+ }
2671
+ }
2672
+
2673
+ const setData = this.sample(effectivePool);
2674
+ const moves = [];
2675
+ for (const [i, moveSlot] of setData.set.moves.entries()) {
2676
+ moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
2677
+ }
2678
+
2679
+
2680
+ const item = this.sampleIfArray(setData.set.item);
2681
+ const ability = this.sampleIfArray(setData.set.ability);
2682
+ const nature = this.sampleIfArray(setData.set.nature);
2683
+ const level = this.adjustLevel || setData.set.level || (tier === "LC" ? 5 : 100);
2684
+
2685
+ return {
2686
+ name: setData.set.name || species.baseSpecies,
2687
+ species: setData.set.species,
2688
+ gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? 'M' : 'F'),
2689
+ item: item || '',
2690
+ ability: ability || species.abilities['0'],
2691
+ shiny: typeof setData.set.shiny === 'undefined' ? this.randomChance(1, 1024) : setData.set.shiny,
2692
+ level,
2693
+ happiness: typeof setData.set.happiness === 'undefined' ? 255 : setData.set.happiness,
2694
+ evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs},
2695
+ ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs},
2696
+ nature: nature || 'Serious',
2697
+ moves,
2698
+ };
2699
+ }
2700
+
2701
+ randomFactoryTeam(side: PlayerOptions, depth = 0): RandomTeamsTypes.RandomFactorySet[] {
2702
+ this.enforceNoDirectCustomBanlistChanges();
2703
+
2704
+ const forceResult = (depth >= 4);
2705
+ // Leaving Monotype code in comments in case it's used in the future
2706
+ // const isMonotype = !!this.forceMonotype || this.dex.formats.getRuleTable(this.format).has('sametypeclause');
2707
+
2708
+ // The teams generated depend on the tier choice in such a way that
2709
+ // no exploitable information is leaked from rolling the tier in getTeam(p1).
2710
+ if (!this.factoryTier) {
2711
+ // this.factoryTier = isMonotype ? 'Mono' : this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
2712
+ this.factoryTier = this.sample(['Uber', 'OU', 'UU', 'RU', 'NU', 'PU', 'LC']);
2713
+ }
2714
+ /*
2715
+ } else if (isMonotype && this.factoryTier !== 'Mono') {
2716
+ // I don't think this can ever happen?
2717
+ throw new Error(`Can't generate a Monotype Battle Factory set in a battle with factory tier ${this.factoryTier}`);
2718
+ }
2719
+ */
2720
+
2721
+ const tierValues: {[k: string]: number} = {
2722
+ Uber: 5,
2723
+ OU: 4, UUBL: 4,
2724
+ UU: 3, RUBL: 3,
2725
+ RU: 2, NUBL: 2,
2726
+ NU: 1, PUBL: 1,
2727
+ PU: 0,
2728
+ };
2729
+
2730
+ const pokemon = [];
2731
+ const pokemonPool = Object.keys(this.randomFactorySets[this.factoryTier]);
2732
+
2733
+ // const typePool = this.dex.types.names();
2734
+ // const type = this.sample(typePool);
2735
+
2736
+ const teamData: TeamData = {
2737
+ typeCount: {}, typeComboCount: {}, baseFormes: {},
2738
+ has: {}, forceResult: forceResult, weaknesses: {}, resistances: {},
2739
+ };
2740
+ const requiredMoveFamilies = ['hazardSet', 'hazardClear'];
2741
+ const requiredMoves: {[k: string]: string} = {
2742
+ stealthrock: 'hazardSet',
2743
+ rapidspin: 'hazardClear',
2744
+ defog: 'hazardClear',
2745
+ };
2746
+ const weatherAbilitiesSet: {[k: string]: string} = {
2747
+ drizzle: 'raindance',
2748
+ drought: 'sunnyday',
2749
+ snowwarning: 'hail',
2750
+ sandstream: 'sandstorm',
2751
+ };
2752
+ const resistanceAbilities: {[k: string]: string[]} = {
2753
+ dryskin: ['Water'], waterabsorb: ['Water'], stormdrain: ['Water'],
2754
+ flashfire: ['Fire'], heatproof: ['Fire'],
2755
+ lightningrod: ['Electric'], motordrive: ['Electric'], voltabsorb: ['Electric'],
2756
+ sapsipper: ['Grass'],
2757
+ thickfat: ['Ice', 'Fire'],
2758
+ levitate: ['Ground'],
2759
+ };
2760
+
2761
+ while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
2762
+ const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
2763
+ if (!species.exists) continue;
2764
+
2765
+ // Lessen the need of deleting sets of Pokemon after tier shifts
2766
+ if (
2767
+ this.factoryTier in tierValues && species.tier in tierValues &&
2768
+ tierValues[species.tier] > tierValues[this.factoryTier]
2769
+ ) continue;
2770
+
2771
+ // const speciesFlags = this.randomFactorySets[this.factoryTier][species.id].flags;
2772
+
2773
+ // Limit to one of each species (Species Clause)
2774
+ if (teamData.baseFormes[species.baseSpecies]) continue;
2775
+
2776
+ const set = this.randomFactorySet(species, teamData, this.factoryTier);
2777
+ if (!set) continue;
2778
+
2779
+ const itemData = this.dex.items.get(set.item);
2780
+
2781
+ const types = species.types;
2782
+ // Dynamically scale limits for different team sizes. The default and minimum value is 1.
2783
+ const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
2784
+ /*
2785
+ // Enforce Monotype
2786
+ if (isMonotype) {
2787
+ // Prevents Mega Evolutions from breaking the type limits
2788
+ if (itemData.megaStone) {
2789
+ const megaSpecies = this.dex.species.get(itemData.megaStone);
2790
+ if (types.length > megaSpecies.types.length) types = [species.types[0]];
2791
+ // Only check the second type because a Mega Evolution should always share the first type with its base forme.
2792
+ if (megaSpecies.types[1] && types[1] && megaSpecies.types[1] !== types[1]) {
2793
+ types = [megaSpecies.types[0]];
2794
+ }
2795
+ }
2796
+ if (!types.includes(type)) continue;
2797
+ } else
2798
+ */
2799
+ {
2800
+ // If not Monotype, limit to two of each type
2801
+ let skip = false;
2802
+ for (const typeName of types) {
2803
+ if (teamData.typeCount[typeName] >= 2 * limitFactor && this.randomChance(4, 5)) {
2804
+ skip = true;
2805
+ break;
2806
+ }
2807
+ }
2808
+ if (skip) continue;
2809
+
2810
+ // Limit 1 of any type combination
2811
+ let typeCombo = types.slice().sort().join();
2812
+ if (set.ability + '' === 'Drought' || set.ability + '' === 'Drizzle') {
2813
+ // Drought and Drizzle don't count towards the type combo limit
2814
+ typeCombo = set.ability + '';
2815
+ }
2816
+ if (teamData.typeComboCount[typeCombo] >= 1 * limitFactor) continue;
2817
+ }
2818
+
2819
+ // Okay, the set passes, add it to our team
2820
+ pokemon.push(set);
2821
+ const typeCombo = types.slice().sort().join();
2822
+ // Now that our Pokemon has passed all checks, we can update team data:
2823
+ for (const typeName of types) {
2824
+ if (typeName in teamData.typeCount) {
2825
+ teamData.typeCount[typeName]++;
2826
+ } else {
2827
+ teamData.typeCount[typeName] = 1;
2828
+ }
2829
+ }
2830
+ teamData.typeComboCount[typeCombo] = (teamData.typeComboCount[typeCombo] + 1) || 1;
2831
+
2832
+ teamData.baseFormes[species.baseSpecies] = 1;
2833
+
2834
+ if (itemData.id in teamData.has) {
2835
+ teamData.has[itemData.id]++;
2836
+ } else {
2837
+ teamData.has[itemData.id] = 1;
2838
+ }
2839
+
2840
+ const abilityState = this.dex.abilities.get(set.ability);
2841
+ if (abilityState.id in weatherAbilitiesSet) {
2842
+ teamData.weather = weatherAbilitiesSet[abilityState.id];
2843
+ }
2844
+
2845
+ for (const move of set.moves) {
2846
+ const moveId = toID(move);
2847
+ if (moveId in teamData.has) {
2848
+ teamData.has[moveId]++;
2849
+ } else {
2850
+ teamData.has[moveId] = 1;
2851
+ }
2852
+ if (moveId in requiredMoves) {
2853
+ teamData.has[requiredMoves[moveId]] = 1;
2854
+ }
2855
+ }
2856
+
2857
+ for (const typeName of this.dex.types.names()) {
2858
+ // Cover any major weakness (3+) with at least one resistance
2859
+ if (teamData.resistances[typeName] >= 1) continue;
2860
+ if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
2861
+ // Heuristic: assume that Pokémon with these abilities don't have (too) negative typing.
2862
+ teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
2863
+ if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
2864
+ continue;
2865
+ }
2866
+ const typeMod = this.dex.getEffectiveness(typeName, types);
2867
+ if (typeMod < 0) {
2868
+ teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
2869
+ if (teamData.resistances[typeName] >= 1) teamData.weaknesses[typeName] = 0;
2870
+ } else if (typeMod > 0) {
2871
+ teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
2872
+ }
2873
+ }
2874
+ }
2875
+ if (pokemon.length < this.maxTeamSize) return this.randomFactoryTeam(side, ++depth);
2876
+
2877
+ // Quality control
2878
+ if (!teamData.forceResult) {
2879
+ for (const requiredFamily of requiredMoveFamilies) {
2880
+ if (!teamData.has[requiredFamily]) return this.randomFactoryTeam(side, ++depth);
2881
+ }
2882
+ for (const typeName in teamData.weaknesses) {
2883
+ if (teamData.weaknesses[typeName] >= 3) return this.randomFactoryTeam(side, ++depth);
2884
+ }
2885
+ }
2886
+
2887
+ return pokemon;
2888
+ }
2889
+
2591
2890
  randomBSSFactorySets: AnyObject = {};
2592
2891
 
2593
2892
  randomBSSFactorySet(