@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/build/gen1.js +4 -18
- package/build/gen1.js.map +1 -1
- package/build/gen8.d.ts +23 -1
- package/build/gen8.js +270 -0
- package/build/gen8.js.map +1 -1
- package/package.json +2 -2
- package/src/gen1.ts +4 -19
- package/src/gen8.ts +299 -0
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
|
|
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 ||
|
|
157
|
+
if (nuCount >= 4 * limitFactor || this.randomChance(1, 3)) continue;
|
|
170
158
|
break;
|
|
171
159
|
case 'Uber':
|
|
172
|
-
//
|
|
173
|
-
if (uberCount >= 1 * limitFactor
|
|
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(
|