Jofthomas's picture
Jofthomas HF staff
Upload 4781 files
5c2ed06 verified
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var teams_exports = {};
__export(teams_exports, {
MoveCounter: () => MoveCounter,
RandomTeams: () => RandomTeams,
default: () => teams_default
});
module.exports = __toCommonJS(teams_exports);
var import_dex = require("../../../sim/dex");
var import_lib = require("../../../lib");
var import_prng = require("../../../sim/prng");
var import_tags = require("./../../tags");
var import_teams = require("../../../sim/teams");
class MoveCounter extends import_lib.Utils.Multiset {
constructor() {
super();
this.damagingMoves = /* @__PURE__ */ new Set();
}
}
const RECOVERY_MOVES = [
"healorder",
"milkdrink",
"moonlight",
"morningsun",
"recover",
"roost",
"shoreup",
"slackoff",
"softboiled",
"strengthsap",
"synthesis"
];
const CONTRARY_MOVES = [
"armorcannon",
"closecombat",
"leafstorm",
"makeitrain",
"overheat",
"spinout",
"superpower",
"vcreate"
];
const PHYSICAL_SETUP = [
"bellydrum",
"bulkup",
"coil",
"curse",
"dragondance",
"honeclaws",
"howl",
"meditate",
"poweruppunch",
"swordsdance",
"tidyup",
"victorydance"
];
const SPECIAL_SETUP = [
"calmmind",
"chargebeam",
"geomancy",
"nastyplot",
"quiverdance",
"tailglow",
"takeheart",
"torchsong"
];
const MIXED_SETUP = [
"clangoroussoul",
"growth",
"happyhour",
"holdhands",
"noretreat",
"shellsmash",
"workup"
];
const SPEED_SETUP = [
"agility",
"autotomize",
"flamecharge",
"rockpolish",
"snowscape",
"trailblaze"
];
const SETUP = [
"acidarmor",
"agility",
"autotomize",
"bellydrum",
"bulkup",
"calmmind",
"clangoroussoul",
"coil",
"cosmicpower",
"curse",
"dragondance",
"flamecharge",
"growth",
"honeclaws",
"howl",
"irondefense",
"meditate",
"nastyplot",
"noretreat",
"poweruppunch",
"quiverdance",
"rockpolish",
"shellsmash",
"shiftgear",
"swordsdance",
"tailglow",
"takeheart",
"tidyup",
"trailblaze",
"workup",
"victorydance"
];
const SPEED_CONTROL = [
"electroweb",
"glare",
"icywind",
"lowsweep",
"nuzzle",
"quash",
"tailwind",
"thunderwave",
"trickroom"
];
const NO_STAB = [
"accelerock",
"aquajet",
"bounce",
"breakingswipe",
"bulletpunch",
"chatter",
"chloroblast",
"circlethrow",
"clearsmog",
"covet",
"dragontail",
"doomdesire",
"electroweb",
"eruption",
"explosion",
"fakeout",
"feint",
"flamecharge",
"flipturn",
"futuresight",
"grassyglide",
"iceshard",
"icywind",
"incinerate",
"infestation",
"machpunch",
"meteorbeam",
"mortalspin",
"nuzzle",
"pluck",
"pursuit",
"quickattack",
"rapidspin",
"reversal",
"selfdestruct",
"shadowsneak",
"skydrop",
"snarl",
"strugglebug",
"suckerpunch",
"uturn",
"vacuumwave",
"voltswitch",
"watershuriken",
"waterspout"
];
const HAZARDS = [
"spikes",
"stealthrock",
"stickyweb",
"toxicspikes"
];
const PROTECT_MOVES = [
"banefulbunker",
"burningbulwark",
"protect",
"silktrap",
"spikyshield"
];
const PIVOT_MOVES = [
"chillyreception",
"flipturn",
"partingshot",
"shedtail",
"teleport",
"uturn",
"voltswitch"
];
const MOVE_PAIRS = [
["lightscreen", "reflect"],
["sleeptalk", "rest"],
["protect", "wish"],
["leechseed", "protect"],
["leechseed", "substitute"]
];
const PRIORITY_POKEMON = [
"breloom",
"brutebonnet",
"cacturne",
"honchkrow",
"mimikyu",
"ragingbolt",
"scizor"
];
const NO_LEAD_POKEMON = [
"Zacian",
"Zamazenta"
];
const DOUBLES_NO_LEAD_POKEMON = [
"Basculegion",
"Houndstone",
"Iron Bundle",
"Roaring Moon",
"Zacian",
"Zamazenta"
];
const DEFENSIVE_TERA_BLAST_USERS = [
"alcremie",
"bellossom",
"comfey",
"fezandipiti",
"florges",
"raikou"
];
function sereneGraceBenefits(move) {
return move.secondary?.chance && move.secondary.chance > 20 && move.secondary.chance < 100;
}
class RandomTeams {
constructor(format, prng) {
this.randomSets = require("./sets.json");
this.randomDoublesSets = require("./doubles-sets.json");
this.randomFactorySets = require("./factory-sets.json");
this.randomBSSFactorySets = require("./bss-factory-sets.json");
this.randomDraftFactoryMatchups = require("./draft-factory-matchups.json").matchups;
this.rdfMatchupIndex = -1;
this.rdfMatchupSide = -1;
format = import_dex.Dex.formats.get(format);
this.dex = import_dex.Dex.forFormat(format);
this.gen = this.dex.gen;
this.noStab = NO_STAB;
const ruleTable = import_dex.Dex.formats.getRuleTable(format);
this.maxTeamSize = ruleTable.maxTeamSize;
this.adjustLevel = ruleTable.adjustLevel;
this.maxMoveCount = ruleTable.maxMoveCount;
const forceMonotype = ruleTable.valueRules.get("forcemonotype");
this.forceMonotype = forceMonotype && this.dex.types.get(forceMonotype).exists ? this.dex.types.get(forceMonotype).name : void 0;
const forceTeraType = ruleTable.valueRules.get("forceteratype");
this.forceTeraType = forceTeraType && this.dex.types.get(forceTeraType).exists ? this.dex.types.get(forceTeraType).name : void 0;
this.factoryTier = "";
this.format = format;
this.prng = import_prng.PRNG.get(prng);
this.moveEnforcementCheckers = {
Bug: (movePool, moves, abilities, types, counter) => movePool.includes("megahorn") || movePool.includes("xscissor") || !counter.get("Bug") && (types.includes("Electric") || types.includes("Psychic")),
Dark: (movePool, moves, abilities, types, counter, species, teamDetails, isLead, isDoubles, teraType, role) => {
if (counter.get("Dark") < 2 && PRIORITY_POKEMON.includes(species.id) && role === "Wallbreaker")
return true;
return !counter.get("Dark");
},
Dragon: (movePool, moves, abilities, types, counter) => !counter.get("Dragon"),
Electric: (movePool, moves, abilities, types, counter) => !counter.get("Electric"),
Fairy: (movePool, moves, abilities, types, counter) => !counter.get("Fairy"),
Fighting: (movePool, moves, abilities, types, counter) => !counter.get("Fighting"),
Fire: (movePool, moves, abilities, types, counter, species) => !counter.get("Fire"),
Flying: (movePool, moves, abilities, types, counter) => !counter.get("Flying"),
Ghost: (movePool, moves, abilities, types, counter) => !counter.get("Ghost"),
Grass: (movePool, moves, abilities, types, counter, species) => !counter.get("Grass") && (movePool.includes("leafstorm") || species.baseStats.atk >= 100 || types.includes("Electric") || abilities.includes("Seed Sower")),
Ground: (movePool, moves, abilities, types, counter) => !counter.get("Ground"),
Ice: (movePool, moves, abilities, types, counter) => movePool.includes("freezedry") || movePool.includes("blizzard") || !counter.get("Ice"),
Normal: (movePool, moves, types, counter) => movePool.includes("boomburst") || movePool.includes("hypervoice"),
Poison: (movePool, moves, abilities, types, counter) => {
if (types.includes("Ground"))
return false;
return !counter.get("Poison");
},
Psychic: (movePool, moves, abilities, types, counter, species, teamDetails, isLead, isDoubles) => {
if ((isDoubles || species.id === "bruxish") && movePool.includes("psychicfangs"))
return true;
if (species.id === "hoopaunbound" && movePool.includes("psychic"))
return true;
if (["Dark", "Steel", "Water"].some((m) => types.includes(m)))
return false;
return !counter.get("Psychic");
},
Rock: (movePool, moves, abilities, types, counter, species) => !counter.get("Rock") && species.baseStats.atk >= 80,
Steel: (movePool, moves, abilities, types, counter, species, teamDetails, isLead, isDoubles) => !counter.get("Steel") && (isDoubles || species.baseStats.atk >= 90 || movePool.includes("gigatonhammer") || movePool.includes("makeitrain")),
Water: (movePool, moves, abilities, types, counter) => !counter.get("Water") && !types.includes("Ground")
};
this.poolsCacheKey = void 0;
this.cachedPool = void 0;
this.cachedSpeciesPool = void 0;
this.cachedStatusMoves = this.dex.moves.all().filter((move) => move.category === "Status").map((move) => move.id);
}
setSeed(prng) {
this.prng = import_prng.PRNG.get(prng);
}
getTeam(options = null) {
const generatorName = typeof this.format.team === "string" && this.format.team.startsWith("random") ? this.format.team + "Team" : "";
return this[generatorName || "randomTeam"](options);
}
randomChance(numerator, denominator) {
return this.prng.randomChance(numerator, denominator);
}
sample(items) {
return this.prng.sample(items);
}
sampleIfArray(item) {
if (Array.isArray(item)) {
return this.sample(item);
}
return item;
}
random(m, n) {
return this.prng.random(m, n);
}
/**
* Remove an element from an unsorted array significantly faster
* than .splice
*/
fastPop(list, index) {
const length = list.length;
if (index < 0 || index >= list.length) {
throw new Error(`Index ${index} out of bounds for given array`);
}
const element = list[index];
list[index] = list[length - 1];
list.pop();
return element;
}
/**
* Remove a random element from an unsorted array and return it.
* Uses the battle's RNG if in a battle.
*/
sampleNoReplace(list) {
const length = list.length;
if (length === 0)
return null;
const index = this.random(length);
return this.fastPop(list, index);
}
/**
* Removes n random elements from an unsorted array and returns them.
* If n is less than the array's length, randomly removes and returns all the elements
* in the array (so the returned array could have length < n).
*/
multipleSamplesNoReplace(list, n) {
const samples = [];
while (samples.length < n && list.length) {
samples.push(this.sampleNoReplace(list));
}
return samples;
}
/**
* Check if user has directly tried to ban/unban/restrict things in a custom battle.
* Doesn't count bans nested inside other formats/rules.
*/
hasDirectCustomBanlistChanges() {
if (this.format.ruleTable?.has("+pokemontag:cap"))
return false;
if (this.format.banlist.length || this.format.restricted.length || this.format.unbanlist.length)
return true;
if (!this.format.customRules)
return false;
for (const rule of this.format.customRules) {
for (const banlistOperator of ["-", "+", "*"]) {
if (rule.startsWith(banlistOperator))
return true;
}
}
return false;
}
/**
* Inform user when custom bans are unsupported in a team generator.
*/
enforceNoDirectCustomBanlistChanges() {
if (this.hasDirectCustomBanlistChanges()) {
throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
}
}
/**
* Inform user when complex bans are unsupported in a team generator.
*/
enforceNoDirectComplexBans() {
if (!this.format.customRules)
return false;
for (const rule of this.format.customRules) {
if (rule.includes("+") && !rule.startsWith("+")) {
throw new Error(`Complex bans are not currently supported in ${this.format.name}.`);
}
}
}
/**
* Validate set element pool size is sufficient to support size requirements after simple bans.
*/
enforceCustomPoolSizeNoComplexBans(effectTypeName, basicEffectPool, requiredCount, requiredCountExplanation) {
if (basicEffectPool.length >= requiredCount)
return;
throw new Error(`Legal ${effectTypeName} count is insufficient to support ${requiredCountExplanation} (${basicEffectPool.length} / ${requiredCount}).`);
}
queryMoves(moves, species, teraType, abilities) {
const counter = new MoveCounter();
const types = species.types;
if (!moves?.size)
return counter;
const categories = { Physical: 0, Special: 0, Status: 0 };
for (const moveid of moves) {
const move = this.dex.moves.get(moveid);
const moveType = this.getMoveType(move, species, abilities, teraType);
if (move.damage || move.damageCallback) {
counter.add("damage");
counter.damagingMoves.add(move);
} else {
categories[move.category]++;
}
if (moveid === "lowkick" || move.basePower && move.basePower <= 60 && moveid !== "rapidspin") {
counter.add("technician");
}
if (move.multihit && Array.isArray(move.multihit) && move.multihit[1] === 5)
counter.add("skilllink");
if (move.recoil || move.hasCrashDamage)
counter.add("recoil");
if (move.drain)
counter.add("drain");
if (move.basePower || move.basePowerCallback) {
if (!this.noStab.includes(moveid) || PRIORITY_POKEMON.includes(species.id) && move.priority > 0) {
counter.add(moveType);
if (types.includes(moveType))
counter.add("stab");
if (teraType === moveType)
counter.add("stabtera");
counter.damagingMoves.add(move);
}
if (move.flags["bite"])
counter.add("strongjaw");
if (move.flags["punch"])
counter.add("ironfist");
if (move.flags["sound"])
counter.add("sound");
if (move.priority > 0 || moveid === "grassyglide" && abilities.includes("Grassy Surge")) {
counter.add("priority");
}
}
if (move.secondary || move.hasSheerForce) {
counter.add("sheerforce");
if (sereneGraceBenefits(move)) {
counter.add("serenegrace");
}
}
if (move.accuracy && move.accuracy !== true && move.accuracy < 90)
counter.add("inaccurate");
if (RECOVERY_MOVES.includes(moveid))
counter.add("recovery");
if (CONTRARY_MOVES.includes(moveid))
counter.add("contrary");
if (PHYSICAL_SETUP.includes(moveid))
counter.add("physicalsetup");
if (SPECIAL_SETUP.includes(moveid))
counter.add("specialsetup");
if (MIXED_SETUP.includes(moveid))
counter.add("mixedsetup");
if (SPEED_SETUP.includes(moveid))
counter.add("speedsetup");
if (SETUP.includes(moveid))
counter.add("setup");
if (HAZARDS.includes(moveid))
counter.add("hazards");
}
counter.set("Physical", Math.floor(categories["Physical"]));
counter.set("Special", Math.floor(categories["Special"]));
counter.set("Status", categories["Status"]);
return counter;
}
cullMovePool(types, moves, abilities, counter, movePool, teamDetails, species, isLead, isDoubles, teraType, role) {
if (moves.size + movePool.length <= this.maxMoveCount)
return;
if (moves.size === this.maxMoveCount - 2) {
const unpairedMoves = [...movePool];
for (const pair of MOVE_PAIRS) {
if (movePool.includes(pair[0]) && movePool.includes(pair[1])) {
this.fastPop(unpairedMoves, unpairedMoves.indexOf(pair[0]));
this.fastPop(unpairedMoves, unpairedMoves.indexOf(pair[1]));
}
}
if (unpairedMoves.length === 1) {
this.fastPop(movePool, movePool.indexOf(unpairedMoves[0]));
}
}
if (moves.size === this.maxMoveCount - 1) {
for (const pair of MOVE_PAIRS) {
if (movePool.includes(pair[0]) && movePool.includes(pair[1])) {
this.fastPop(movePool, movePool.indexOf(pair[0]));
this.fastPop(movePool, movePool.indexOf(pair[1]));
}
}
}
const statusMoves = this.cachedStatusMoves;
if (teamDetails.screens) {
if (movePool.includes("auroraveil"))
this.fastPop(movePool, movePool.indexOf("auroraveil"));
if (movePool.length >= this.maxMoveCount + 2) {
if (movePool.includes("reflect"))
this.fastPop(movePool, movePool.indexOf("reflect"));
if (movePool.includes("lightscreen"))
this.fastPop(movePool, movePool.indexOf("lightscreen"));
}
}
if (teamDetails.stickyWeb) {
if (movePool.includes("stickyweb"))
this.fastPop(movePool, movePool.indexOf("stickyweb"));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
if (teamDetails.stealthRock) {
if (movePool.includes("stealthrock"))
this.fastPop(movePool, movePool.indexOf("stealthrock"));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
if (teamDetails.defog || teamDetails.rapidSpin) {
if (movePool.includes("defog"))
this.fastPop(movePool, movePool.indexOf("defog"));
if (movePool.includes("rapidspin"))
this.fastPop(movePool, movePool.indexOf("rapidspin"));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
if (teamDetails.toxicSpikes) {
if (movePool.includes("toxicspikes"))
this.fastPop(movePool, movePool.indexOf("toxicspikes"));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
if (teamDetails.spikes && teamDetails.spikes >= 2) {
if (movePool.includes("spikes"))
this.fastPop(movePool, movePool.indexOf("spikes"));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
if (teamDetails.statusCure) {
if (movePool.includes("healbell"))
this.fastPop(movePool, movePool.indexOf("healbell"));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
if (isDoubles) {
const doublesIncompatiblePairs = [
// In order of decreasing generalizability
[SPEED_CONTROL, SPEED_CONTROL],
[HAZARDS, HAZARDS],
["rockslide", "stoneedge"],
[SETUP, ["fakeout", "helpinghand"]],
[PROTECT_MOVES, "wideguard"],
[["fierydance", "fireblast"], "heatwave"],
["dazzlinggleam", ["fleurcannon", "moonblast"]],
["poisongas", ["toxicspikes", "willowisp"]],
[RECOVERY_MOVES, "healpulse"],
["lifedew", "healpulse"],
["haze", "icywind"],
[["hydropump", "muddywater"], ["muddywater", "scald"]],
["disable", "encore"],
["freezedry", "icebeam"],
["energyball", "leafstorm"],
["wildcharge", "thunderbolt"],
["earthpower", "sandsearstorm"],
["coaching", ["helpinghand", "howl"]]
];
for (const pair of doublesIncompatiblePairs)
this.incompatibleMoves(moves, movePool, pair[0], pair[1]);
if (role !== "Offensive Protect")
this.incompatibleMoves(moves, movePool, PROTECT_MOVES, ["flipturn", "uturn"]);
}
const incompatiblePairs = [
// These moves don't mesh well with other aspects of the set
[statusMoves, ["healingwish", "switcheroo", "trick"]],
[SETUP, PIVOT_MOVES],
[SETUP, HAZARDS],
[SETUP, ["defog", "nuzzle", "toxic", "yawn", "haze"]],
[PHYSICAL_SETUP, PHYSICAL_SETUP],
[SPECIAL_SETUP, "thunderwave"],
["substitute", PIVOT_MOVES],
[SPEED_SETUP, ["aquajet", "rest", "trickroom"]],
["curse", ["irondefense", "rapidspin"]],
["dragondance", "dracometeor"],
["yawn", "roar"],
// These attacks are redundant with each other
[["psychic", "psychicnoise"], ["psyshock", "psychicnoise"]],
["surf", "hydropump"],
["liquidation", "wavecrash"],
["aquajet", "flipturn"],
["gigadrain", "leafstorm"],
["powerwhip", "hornleech"],
[["airslash", "bravebird", "hurricane"], ["airslash", "bravebird", "hurricane"]],
["knockoff", "foulplay"],
["throatchop", ["crunch", "lashout"]],
["doubleedge", ["bodyslam", "headbutt"]],
["fireblast", ["fierydance", "flamethrower"]],
["lavaplume", "magmastorm"],
["thunderpunch", "wildcharge"],
["thunderbolt", "discharge"],
["gunkshot", ["direclaw", "poisonjab", "sludgebomb"]],
["aurasphere", "focusblast"],
["closecombat", "drainpunch"],
["bugbite", "pounce"],
[["dragonpulse", "spacialrend"], "dracometeor"],
["heavyslam", "flashcannon"],
["alluringvoice", "dazzlinggleam"],
// These status moves are redundant with each other
["taunt", "disable"],
[["thunderwave", "toxic"], ["thunderwave", "willowisp"]],
[["thunderwave", "toxic", "willowisp"], "toxicspikes"],
// This space reserved for assorted hardcodes that otherwise make little sense out of context
// Landorus and Thundurus
["nastyplot", ["rockslide", "knockoff"]],
// Persian
["switcheroo", "fakeout"],
// Amoonguss, though this can work well as a general rule later
["toxic", "clearsmog"],
// Chansey and Blissey
["healbell", "stealthrock"],
// Azelf and Zoroarks
["trick", "uturn"],
// Araquanid
["mirrorcoat", "hydropump"]
];
for (const pair of incompatiblePairs)
this.incompatibleMoves(moves, movePool, pair[0], pair[1]);
if (!types.includes("Ice"))
this.incompatibleMoves(moves, movePool, "icebeam", "icywind");
if (!isDoubles)
this.incompatibleMoves(moves, movePool, "taunt", "encore");
if (!types.includes("Dark") && teraType !== "Dark")
this.incompatibleMoves(moves, movePool, "knockoff", "suckerpunch");
if (!abilities.includes("Prankster"))
this.incompatibleMoves(moves, movePool, "thunderwave", "yawn");
if (species.id === "barraskewda") {
this.incompatibleMoves(moves, movePool, ["psychicfangs", "throatchop"], ["poisonjab", "throatchop"]);
}
if (species.id === "cyclizar")
this.incompatibleMoves(moves, movePool, "taunt", "knockoff");
if (species.id === "camerupt")
this.incompatibleMoves(moves, movePool, "roar", "willowisp");
if (species.id === "coalossal")
this.incompatibleMoves(moves, movePool, "flamethrower", "overheat");
}
// Checks for and removes incompatible moves, starting with the first move in movesA.
incompatibleMoves(moves, movePool, movesA, movesB) {
const moveArrayA = Array.isArray(movesA) ? movesA : [movesA];
const moveArrayB = Array.isArray(movesB) ? movesB : [movesB];
if (moves.size + movePool.length <= this.maxMoveCount)
return;
for (const moveid1 of moves) {
if (moveArrayB.includes(moveid1)) {
for (const moveid2 of moveArrayA) {
if (moveid1 !== moveid2 && movePool.includes(moveid2)) {
this.fastPop(movePool, movePool.indexOf(moveid2));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
}
}
if (moveArrayA.includes(moveid1)) {
for (const moveid2 of moveArrayB) {
if (moveid1 !== moveid2 && movePool.includes(moveid2)) {
this.fastPop(movePool, movePool.indexOf(moveid2));
if (moves.size + movePool.length <= this.maxMoveCount)
return;
}
}
}
}
}
// Adds a move to the moveset, returns the MoveCounter
addMove(move, moves, types, abilities, teamDetails, species, isLead, isDoubles, movePool, teraType, role) {
moves.add(move);
this.fastPop(movePool, movePool.indexOf(move));
const counter = this.queryMoves(moves, species, teraType, abilities);
this.cullMovePool(types, moves, abilities, counter, movePool, teamDetails, species, isLead, isDoubles, teraType, role);
return counter;
}
// Returns the type of a given move for STAB/coverage enforcement purposes
getMoveType(move, species, abilities, teraType) {
if (move.id === "terablast")
return teraType;
if (["judgment", "revelationdance"].includes(move.id))
return species.types[0];
if (move.name === "Raging Bull" && species.name.startsWith("Tauros-Paldea")) {
if (species.name.endsWith("Combat"))
return "Fighting";
if (species.name.endsWith("Blaze"))
return "Fire";
if (species.name.endsWith("Aqua"))
return "Water";
}
if (move.name === "Ivy Cudgel" && species.name.startsWith("Ogerpon")) {
if (species.name.endsWith("Wellspring"))
return "Water";
if (species.name.endsWith("Hearthflame"))
return "Fire";
if (species.name.endsWith("Cornerstone"))
return "Rock";
}
const moveType = move.type;
if (moveType === "Normal") {
if (abilities.includes("Aerilate"))
return "Flying";
if (abilities.includes("Galvanize"))
return "Electric";
if (abilities.includes("Pixilate"))
return "Fairy";
if (abilities.includes("Refrigerate"))
return "Ice";
}
return moveType;
}
// Generate random moveset for a given species, role, tera type.
randomMoveset(types, abilities, teamDetails, species, isLead, isDoubles, movePool, teraType, role) {
const moves = /* @__PURE__ */ new Set();
let counter = this.queryMoves(moves, species, teraType, abilities);
this.cullMovePool(types, moves, abilities, counter, movePool, teamDetails, species, isLead, isDoubles, teraType, role);
if (movePool.length <= this.maxMoveCount) {
for (const moveid of movePool) {
moves.add(moveid);
}
return moves;
}
const runEnforcementChecker = (checkerName) => {
if (!this.moveEnforcementCheckers[checkerName])
return false;
return this.moveEnforcementCheckers[checkerName](
movePool,
moves,
abilities,
types,
counter,
species,
teamDetails,
isLead,
isDoubles,
teraType,
role
);
};
if (role === "Tera Blast user") {
counter = this.addMove(
"terablast",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (species.requiredMove) {
const move = this.dex.moves.get(species.requiredMove).id;
counter = this.addMove(
move,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (movePool.includes("facade") && abilities.includes("Guts")) {
counter = this.addMove(
"facade",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
for (const moveid of ["nightshade", "revelationdance", "revivalblessing", "stickyweb"]) {
if (movePool.includes(moveid)) {
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (movePool.includes("trickroom") && role === "Doubles Wallbreaker") {
counter = this.addMove(
"trickroom",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (role === "Bulky Support" && !teamDetails.defog && !teamDetails.rapidSpin) {
if (movePool.includes("rapidspin")) {
counter = this.addMove(
"rapidspin",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (movePool.includes("defog")) {
counter = this.addMove(
"defog",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (!teamDetails.screens && movePool.includes("auroraveil")) {
counter = this.addMove(
"auroraveil",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (!isDoubles && types.length === 1 && (types.includes("Normal") || types.includes("Fighting"))) {
if (movePool.includes("knockoff")) {
counter = this.addMove(
"knockoff",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (species.id === "smeargle") {
if (movePool.includes("spore")) {
counter = this.addMove(
"spore",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (isDoubles) {
const doublesEnforcedMoves = ["mortalspin", "spore"];
for (const moveid of doublesEnforcedMoves) {
if (movePool.includes(moveid)) {
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (movePool.includes("fakeout") && species.baseStats.spe <= 50) {
counter = this.addMove(
"fakeout",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (movePool.includes("tailwind") && (abilities.includes("Prankster") || abilities.includes("Gale Wings"))) {
counter = this.addMove(
"tailwind",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (movePool.includes("thunderwave") && abilities.includes("Prankster")) {
counter = this.addMove(
"thunderwave",
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (["Bulky Attacker", "Bulky Setup", "Wallbreaker", "Doubles Wallbreaker"].includes(role) || PRIORITY_POKEMON.includes(species.id)) {
const priorityMoves = [];
for (const moveid of movePool) {
const move = this.dex.moves.get(moveid);
const moveType = this.getMoveType(move, species, abilities, teraType);
if (types.includes(moveType) && (move.priority > 0 || moveid === "grassyglide" && abilities.includes("Grassy Surge")) && (move.basePower || move.basePowerCallback)) {
priorityMoves.push(moveid);
}
}
if (priorityMoves.length) {
const moveid = this.sample(priorityMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
for (const type of types) {
const stabMoves = [];
for (const moveid of movePool) {
const move = this.dex.moves.get(moveid);
const moveType = this.getMoveType(move, species, abilities, teraType);
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && type === moveType) {
stabMoves.push(moveid);
}
}
while (runEnforcementChecker(type)) {
if (!stabMoves.length)
break;
const moveid = this.sampleNoReplace(stabMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (!counter.get("stabtera") && !["Bulky Support", "Doubles Support"].includes(role)) {
const stabMoves = [];
for (const moveid of movePool) {
const move = this.dex.moves.get(moveid);
const moveType = this.getMoveType(move, species, abilities, teraType);
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && teraType === moveType) {
stabMoves.push(moveid);
}
}
if (stabMoves.length) {
const moveid = this.sample(stabMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (!counter.get("stab")) {
const stabMoves = [];
for (const moveid of movePool) {
const move = this.dex.moves.get(moveid);
const moveType = this.getMoveType(move, species, abilities, teraType);
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback) && types.includes(moveType)) {
stabMoves.push(moveid);
}
}
if (stabMoves.length) {
const moveid = this.sample(stabMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (["Bulky Support", "Bulky Attacker", "Bulky Setup"].includes(role)) {
const recoveryMoves = movePool.filter((moveid) => RECOVERY_MOVES.includes(moveid));
if (recoveryMoves.length) {
const moveid = this.sample(recoveryMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (role === "AV Pivot") {
const pivotMoves = movePool.filter((moveid) => ["uturn", "voltswitch"].includes(moveid));
if (pivotMoves.length) {
const moveid = this.sample(pivotMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (role.includes("Setup") || role === "Tera Blast user") {
const nonSpeedSetupMoves = movePool.filter((moveid) => SETUP.includes(moveid) && !SPEED_SETUP.includes(moveid));
if (nonSpeedSetupMoves.length) {
const moveid = this.sample(nonSpeedSetupMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
} else {
const setupMoves = movePool.filter((moveid) => SETUP.includes(moveid));
if (setupMoves.length) {
const moveid = this.sample(setupMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
}
if (role === "Doubles Support") {
for (const moveid of ["fakeout", "followme", "ragepowder"]) {
if (movePool.includes(moveid)) {
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
const speedControl = movePool.filter((moveid) => SPEED_CONTROL.includes(moveid));
if (speedControl.length) {
const moveid = this.sample(speedControl);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (role.includes("Protect")) {
const protectMoves = movePool.filter((moveid) => PROTECT_MOVES.includes(moveid));
if (protectMoves.length) {
const moveid = this.sample(protectMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (!counter.damagingMoves.size) {
const attackingMoves = [];
for (const moveid of movePool) {
const move = this.dex.moves.get(moveid);
if (!this.noStab.includes(moveid) && move.category !== "Status")
attackingMoves.push(moveid);
}
if (attackingMoves.length) {
const moveid = this.sample(attackingMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
if (!["AV Pivot", "Fast Support", "Bulky Support", "Bulky Protect", "Doubles Support"].includes(role)) {
if (counter.damagingMoves.size === 1) {
const currentAttackType = counter.damagingMoves.values().next().value.type;
const coverageMoves = [];
for (const moveid of movePool) {
const move = this.dex.moves.get(moveid);
const moveType = this.getMoveType(move, species, abilities, teraType);
if (!this.noStab.includes(moveid) && (move.basePower || move.basePowerCallback)) {
if (currentAttackType !== moveType)
coverageMoves.push(moveid);
}
}
if (coverageMoves.length) {
const moveid = this.sample(coverageMoves);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
}
while (moves.size < this.maxMoveCount && movePool.length) {
if (moves.size + movePool.length <= this.maxMoveCount) {
for (const moveid2 of movePool) {
moves.add(moveid2);
}
break;
}
const moveid = this.sample(movePool);
counter = this.addMove(
moveid,
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
for (const pair of MOVE_PAIRS) {
if (moveid === pair[0] && movePool.includes(pair[1])) {
counter = this.addMove(
pair[1],
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
if (moveid === pair[1] && movePool.includes(pair[0])) {
counter = this.addMove(
pair[0],
moves,
types,
abilities,
teamDetails,
species,
isLead,
isDoubles,
movePool,
teraType,
role
);
}
}
}
return moves;
}
shouldCullAbility(ability, types, moves, abilities, counter, teamDetails, species, isLead, isDoubles, teraType, role) {
switch (ability) {
case "Chlorophyll":
case "Solar Power":
return !teamDetails.sun;
case "Defiant":
return species.id === "thundurus" && !!counter.get("Status");
case "Hydration":
case "Swift Swim":
return !teamDetails.rain;
case "Iron Fist":
case "Skill Link":
return !counter.get((0, import_dex.toID)(ability));
case "Overgrow":
return !counter.get("Grass");
case "Prankster":
return !counter.get("Status");
case "Sand Force":
case "Sand Rush":
return !teamDetails.sand;
case "Slush Rush":
return !teamDetails.snow;
case "Swarm":
return !counter.get("Bug");
case "Torrent":
return !counter.get("Water") && !moves.has("flipturn");
}
return false;
}
getAbility(types, moves, abilities, counter, teamDetails, species, isLead, isDoubles, teraType, role) {
if (this.format.gameType === "freeforall") {
if (species.id === "bellossom")
return "Chlorophyll";
if (species.id === "sinistcha")
return "Heatproof";
if (abilities.length === 1 && abilities[0] === "Telepathy") {
return species.id === "oranguru" ? "Inner Focus" : "Pressure";
}
if (species.id === "duraludon")
return "Light Metal";
if (species.id === "clefairy")
return "Magic Guard";
if (species.id === "blissey")
return "Natural Cure";
if (species.id === "barraskewda")
return "Swift Swim";
}
if (abilities.length <= 1)
return abilities[0];
if (species.id === "drifblim")
return moves.has("defog") ? "Aftermath" : "Unburden";
if (abilities.includes("Flash Fire") && this.dex.getEffectiveness("Fire", teraType) >= 1)
return "Flash Fire";
if (species.id === "hitmonchan" && counter.get("ironfist"))
return "Iron Fist";
if ((species.id === "thundurus" || species.id === "tornadus") && !counter.get("Physical"))
return "Prankster";
if (species.id === "swampert" && (counter.get("Water") || moves.has("flipturn")))
return "Torrent";
if (species.id === "toucannon" && counter.get("skilllink"))
return "Skill Link";
if (abilities.includes("Slush Rush") && moves.has("snowscape"))
return "Slush Rush";
if (species.id === "golduck" && teamDetails.rain)
return "Swift Swim";
const abilityAllowed = [];
for (const ability of abilities) {
if (!this.shouldCullAbility(
ability,
types,
moves,
abilities,
counter,
teamDetails,
species,
isLead,
isDoubles,
teraType,
role
)) {
abilityAllowed.push(ability);
}
}
if (abilityAllowed.length >= 1)
return this.sample(abilityAllowed);
if (!abilityAllowed.length) {
const weatherAbilities = abilities.filter(
(a) => ["Chlorophyll", "Hydration", "Sand Force", "Sand Rush", "Slush Rush", "Solar Power", "Swift Swim"].includes(a)
);
if (weatherAbilities.length)
return this.sample(weatherAbilities);
}
return this.sample(abilities);
}
getPriorityItem(ability, types, moves, counter, teamDetails, species, isLead, isDoubles, teraType, role) {
if (!isDoubles) {
if (role === "Fast Bulky Setup" && (ability === "Quark Drive" || ability === "Protosynthesis")) {
return "Booster Energy";
}
if (species.id === "lokix") {
return role === "Fast Attacker" ? "Silver Powder" : "Life Orb";
}
}
if (species.requiredItems) {
if (species.baseSpecies === "Arceus") {
return species.requiredItems[0];
}
return this.sample(species.requiredItems);
}
if (role === "AV Pivot")
return "Assault Vest";
if (species.id === "pikachu")
return "Light Ball";
if (species.id === "regieleki")
return "Magnet";
if (types.includes("Normal") && moves.has("doubleedge") && moves.has("fakeout"))
return "Silk Scarf";
if (species.id === "froslass" || moves.has("populationbomb") || ability === "Hustle" && counter.get("setup") && !isDoubles && this.randomChance(1, 2))
return "Wide Lens";
if (species.id === "smeargle" && !isDoubles)
return "Focus Sash";
if (moves.has("clangoroussoul") || species.id === "toxtricity" && moves.has("shiftgear"))
return "Throat Spray";
if (species.baseSpecies === "Magearna" && role === "Tera Blast user" || species.id === "necrozmaduskmane" || species.id === "calyrexice" && isDoubles)
return "Weakness Policy";
if (["dragonenergy", "lastrespects", "waterspout"].some((m) => moves.has(m)))
return "Choice Scarf";
if (!isDoubles && (ability === "Imposter" || species.id === "magnezone" && role === "Fast Attacker"))
return "Choice Scarf";
if (species.id === "rampardos" && (role === "Fast Attacker" || isDoubles))
return "Choice Scarf";
if (species.id === "palkia" && counter.get("Special") < 4)
return "Lustrous Orb";
if (moves.has("courtchange") || !isDoubles && (species.id === "luvdisc" || species.id === "terapagos" && !moves.has("rest")))
return "Heavy-Duty Boots";
if (moves.has("bellydrum") && moves.has("substitute"))
return "Salac Berry";
if (["Cheek Pouch", "Cud Chew", "Harvest", "Ripen"].some((m) => ability === m) || moves.has("bellydrum") || moves.has("filletaway")) {
return "Sitrus Berry";
}
if (["healingwish", "switcheroo", "trick"].some((m) => moves.has(m))) {
if (species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && role !== "Wallbreaker" && role !== "Doubles Wallbreaker" && !counter.get("priority")) {
return "Choice Scarf";
} else {
return counter.get("Physical") > counter.get("Special") ? "Choice Band" : "Choice Specs";
}
}
if (counter.get("Status") && (species.name === "Latias" || species.name === "Latios"))
return "Soul Dew";
if (species.id === "scyther" && !isDoubles)
return isLead && !moves.has("uturn") ? "Eviolite" : "Heavy-Duty Boots";
if (ability === "Poison Heal" || ability === "Quick Feet")
return "Toxic Orb";
if (species.nfe)
return "Eviolite";
if ((ability === "Guts" || moves.has("facade")) && !moves.has("sleeptalk")) {
return types.includes("Fire") || ability === "Toxic Boost" ? "Toxic Orb" : "Flame Orb";
}
if (ability === "Magic Guard" || ability === "Sheer Force" && counter.get("sheerforce"))
return "Life Orb";
if (ability === "Anger Shell")
return this.sample(["Rindo Berry", "Passho Berry", "Scope Lens", "Sitrus Berry"]);
if (moves.has("dragondance") && isDoubles)
return "Clear Amulet";
if (counter.get("skilllink") && ability !== "Skill Link" && species.id !== "breloom")
return "Loaded Dice";
if (ability === "Unburden") {
return moves.has("closecombat") || moves.has("leafstorm") ? "White Herb" : "Sitrus Berry";
}
if (moves.has("shellsmash") && ability !== "Weak Armor")
return "White Herb";
if (moves.has("meteorbeam") || moves.has("electroshot") && !teamDetails.rain)
return "Power Herb";
if (moves.has("acrobatics") && ability !== "Protosynthesis")
return "";
if (moves.has("auroraveil") || moves.has("lightscreen") && moves.has("reflect"))
return "Light Clay";
if (ability === "Gluttony")
return `${this.sample(["Aguav", "Figy", "Iapapa", "Mago", "Wiki"])} Berry`;
if (moves.has("rest") && !moves.has("sleeptalk") && ability !== "Natural Cure" && ability !== "Shed Skin") {
return "Chesto Berry";
}
if (species.id !== "yanmega" && this.dex.getEffectiveness("Rock", species) >= 2 && (!types.includes("Flying") || !isDoubles))
return "Heavy-Duty Boots";
}
/** Item generation specific to Random Doubles */
getDoublesItem(ability, types, moves, counter, teamDetails, species, isLead, teraType, role) {
const scarfReqs = !counter.get("priority") && ability !== "Speed Boost" && role !== "Doubles Wallbreaker" && species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && this.randomChance(1, 2);
const offensiveRole = ["Doubles Fast Attacker", "Doubles Wallbreaker", "Doubles Setup Sweeper", "Offensive Protect"].some((m) => role === m);
const doublesLeftoversHardcodes = moves.has("acidarmor") || species.id === "eternatus" || species.id === "regigigas" || moves.has("wish");
if (species.id === "ursalunabloodmoon" && moves.has("protect"))
return "Silk Scarf";
if (moves.has("flipturn") && moves.has("protect") && (moves.has("aquajet") || moves.has("jetpunch")))
return "Mystic Water";
if (counter.get("speedsetup") && role === "Doubles Bulky Setup")
return "Weakness Policy";
if (species.id === "toxapex")
return "Binding Band";
if (moves.has("blizzard") && ability !== "Snow Warning" && !teamDetails.snow)
return "Blunder Policy";
if (role === "Choice Item user") {
if (scarfReqs || counter.get("Physical") < 4 && counter.get("Special") < 3 && !moves.has("memento")) {
return "Choice Scarf";
}
return counter.get("Physical") >= 3 ? "Choice Band" : "Choice Specs";
}
if (counter.get("Physical") >= 4 && ["fakeout", "feint", "firstimpression", "rapidspin", "suckerpunch"].every((m) => !moves.has(m)) && (moves.has("flipturn") || moves.has("uturn") || role === "Doubles Wallbreaker")) {
return scarfReqs ? "Choice Scarf" : "Choice Band";
}
if ((counter.get("Special") >= 4 && (moves.has("voltswitch") || role === "Doubles Wallbreaker") || counter.get("Special") >= 3 && (moves.has("uturn") || moves.has("flipturn"))) && !moves.has("electroweb")) {
return scarfReqs ? "Choice Scarf" : "Choice Specs";
}
if (role === "Bulky Protect" && counter.get("setup") || moves.has("substitute") || moves.has("irondefense") || moves.has("coil") || doublesLeftoversHardcodes)
return "Leftovers";
if (species.id === "sylveon")
return "Pixie Plate";
if (ability === "Intimidate" && this.dex.getEffectiveness("Rock", species) >= 1)
return "Heavy-Duty Boots";
if ((offensiveRole || role === "Tera Blast user" && (species.baseStats.spe >= 80 || moves.has("trickroom"))) && (!moves.has("fakeout") || species.id === "ambipom") && !moves.has("incinerate") && (!moves.has("uturn") || types.includes("Bug") || ability === "Libero") && (!moves.has("icywind") && !moves.has("electroweb") || species.id === "ironbundle")) {
return (ability === "Quark Drive" || ability === "Protosynthesis") && !isLead && species.id !== "ironvaliant" && ["dracometeor", "firstimpression", "uturn", "voltswitch"].every((m) => !moves.has(m)) ? "Booster Energy" : "Life Orb";
}
if (isLead && (species.id === "glimmora" || ["Doubles Fast Attacker", "Doubles Wallbreaker", "Offensive Protect"].includes(role) && species.baseStats.hp + species.baseStats.def + species.baseStats.spd <= 230))
return "Focus Sash";
if (["Doubles Fast Attacker", "Doubles Wallbreaker", "Offensive Protect"].includes(role) && moves.has("fakeout") || moves.has("incinerate")) {
return this.dex.getEffectiveness("Rock", species) >= 1 ? "Heavy-Duty Boots" : "Clear Amulet";
}
if (!counter.get("Status"))
return "Assault Vest";
return "Sitrus Berry";
}
getItem(ability, types, moves, counter, teamDetails, species, isLead, teraType, role) {
if (species.id !== "jirachi" && counter.get("Physical") >= 4 && ["dragontail", "fakeout", "firstimpression", "flamecharge", "rapidspin"].every((m) => !moves.has(m))) {
const scarfReqs = role !== "Wallbreaker" && (species.baseStats.atk >= 100 || ability === "Huge Power" || ability === "Pure Power") && species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && ability !== "Speed Boost" && !counter.get("priority") && !moves.has("aquastep");
return scarfReqs && this.randomChance(1, 2) ? "Choice Scarf" : "Choice Band";
}
if (counter.get("Special") >= 4 || counter.get("Special") >= 3 && ["flipturn", "uturn"].some((m) => moves.has(m))) {
const scarfReqs = role !== "Wallbreaker" && species.baseStats.spa >= 100 && species.baseStats.spe >= 60 && species.baseStats.spe <= 108 && ability !== "Speed Boost" && ability !== "Tinted Lens" && !moves.has("uturn") && !counter.get("priority");
return scarfReqs && this.randomChance(1, 2) ? "Choice Scarf" : "Choice Specs";
}
if (counter.get("speedsetup") && role === "Bulky Setup")
return "Weakness Policy";
if (!counter.get("Status") && !["Fast Attacker", "Wallbreaker", "Tera Blast user"].includes(role)) {
return "Assault Vest";
}
if (species.id === "golem")
return counter.get("speedsetup") ? "Weakness Policy" : "Custap Berry";
if (moves.has("substitute"))
return "Leftovers";
if (moves.has("stickyweb") && isLead && species.baseStats.hp + species.baseStats.def + species.baseStats.spd <= 235)
return "Focus Sash";
if (this.dex.getEffectiveness("Rock", species) >= 1)
return "Heavy-Duty Boots";
if (moves.has("chillyreception") || role === "Fast Support" && [...PIVOT_MOVES, "defog", "mortalspin", "rapidspin"].some((m) => moves.has(m)) && !types.includes("Flying") && ability !== "Levitate")
return "Heavy-Duty Boots";
if (ability === "Rough Skin" || ability === "Regenerator" && (role === "Bulky Support" || role === "Bulky Attacker") && species.baseStats.hp + species.baseStats.def >= 180 && this.randomChance(1, 2) || ability !== "Regenerator" && !counter.get("setup") && counter.get("recovery") && this.dex.getEffectiveness("Fighting", species) < 1 && species.baseStats.hp + species.baseStats.def > 200 && this.randomChance(1, 2))
return "Rocky Helmet";
if (moves.has("outrage") && counter.get("setup"))
return "Lum Berry";
if (moves.has("protect") && ability !== "Speed Boost")
return "Leftovers";
if (role === "Fast Support" && isLead && !counter.get("recovery") && !counter.get("recoil") && (counter.get("hazards") || counter.get("setup")) && species.baseStats.hp + species.baseStats.def + species.baseStats.spd < 258)
return "Focus Sash";
if (!counter.get("setup") && ability !== "Levitate" && this.dex.getEffectiveness("Ground", species) >= 2)
return "Air Balloon";
if (["Bulky Attacker", "Bulky Support", "Bulky Setup"].some((m) => role === m))
return "Leftovers";
if (species.id === "pawmot" && moves.has("nuzzle"))
return "Leppa Berry";
if (role === "Fast Support" || role === "Fast Bulky Setup") {
return counter.get("Physical") + counter.get("Special") >= 3 && !moves.has("nuzzle") ? "Life Orb" : "Leftovers";
}
if (role === "Tera Blast user" && DEFENSIVE_TERA_BLAST_USERS.includes(species.id))
return "Leftovers";
if (["flamecharge", "rapidspin", "trailblaze"].every((m) => !moves.has(m)) && ["Fast Attacker", "Setup Sweeper", "Tera Blast user", "Wallbreaker"].some((m) => role === m))
return "Life Orb";
return "Leftovers";
}
getLevel(species, isDoubles) {
if (this.adjustLevel)
return this.adjustLevel;
if (isDoubles && this.randomDoublesSets[species.id]["level"])
return this.randomDoublesSets[species.id]["level"];
if (!isDoubles && this.randomSets[species.id]["level"])
return this.randomSets[species.id]["level"];
const tier = species.tier;
const tierScale = {
Uber: 76,
OU: 80,
UUBL: 81,
UU: 82,
RUBL: 83,
RU: 84,
NUBL: 85,
NU: 86,
PUBL: 87,
PU: 88,
"(PU)": 88,
NFE: 88
};
return tierScale[tier] || 80;
}
getForme(species) {
if (typeof species.battleOnly === "string") {
return species.battleOnly;
}
if (species.cosmeticFormes)
return this.sample([species.name].concat(species.cosmeticFormes));
if (["Dudunsparce", "Magearna", "Maushold", "Polteageist", "Sinistcha", "Zarude"].includes(species.baseSpecies)) {
return this.sample([species.name].concat(species.otherFormes));
}
if (species.baseSpecies === "Basculin")
return "Basculin" + this.sample(["", "-Blue-Striped"]);
if (species.baseSpecies === "Pikachu") {
return "Pikachu" + this.sample(
["", "-Original", "-Hoenn", "-Sinnoh", "-Unova", "-Kalos", "-Alola", "-Partner", "-World"]
);
}
return species.name;
}
randomSet(s, teamDetails = {}, isLead = false, isDoubles = false) {
const species = this.dex.species.get(s);
const forme = this.getForme(species);
const sets = this[`random${isDoubles ? "Doubles" : ""}Sets`][species.id]["sets"];
const possibleSets = [];
const ruleTable = this.dex.formats.getRuleTable(this.format);
for (const set2 of sets) {
const abilities2 = set2.abilities;
if (isLead && (abilities2.includes("Protosynthesis") || abilities2.includes("Quark Drive")) && set2.role === "Fast Bulky Setup")
continue;
if ((teamDetails.teraBlast || ruleTable.has("terastalclause")) && set2.role === "Tera Blast user") {
continue;
}
possibleSets.push(set2);
}
const set = this.sampleIfArray(possibleSets);
const role = set.role;
const movePool = [];
for (const movename of set.movepool) {
movePool.push(this.dex.moves.get(movename).id);
}
const teraTypes = set.teraTypes;
let teraType = this.sampleIfArray(teraTypes);
let ability = "";
let item = void 0;
const evs = { hp: 85, atk: 85, def: 85, spa: 85, spd: 85, spe: 85 };
const ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 };
const types = species.types;
const abilities = set.abilities;
const moves = this.randomMoveset(types, abilities, teamDetails, species, isLead, isDoubles, movePool, teraType, role);
const counter = this.queryMoves(moves, species, teraType, abilities);
ability = this.getAbility(types, moves, abilities, counter, teamDetails, species, isLead, isDoubles, teraType, role);
item = this.getPriorityItem(ability, types, moves, counter, teamDetails, species, isLead, isDoubles, teraType, role);
if (item === void 0) {
if (isDoubles) {
item = this.getDoublesItem(ability, types, moves, counter, teamDetails, species, isLead, teraType, role);
} else {
item = this.getItem(ability, types, moves, counter, teamDetails, species, isLead, teraType, role);
}
}
const level = this.getLevel(species, isDoubles);
const srImmunity = ability === "Magic Guard" || item === "Heavy-Duty Boots";
let srWeakness = srImmunity ? 0 : this.dex.getEffectiveness("Rock", species);
if (["axekick", "highjumpkick", "jumpkick", "supercellslam"].some((m) => moves.has(m)))
srWeakness = 2;
while (evs.hp > 1) {
const hp = Math.floor(Math.floor(2 * species.baseStats.hp + ivs.hp + Math.floor(evs.hp / 4) + 100) * level / 100 + 10);
if (moves.has("substitute") && ["Sitrus Berry", "Salac Berry"].includes(item) || species.id === "minior") {
if (hp % 4 === 0)
break;
} else if ((moves.has("bellydrum") || moves.has("filletaway") || moves.has("shedtail")) && (item === "Sitrus Berry" || ability === "Gluttony")) {
if (hp % 2 === 0)
break;
} else if (moves.has("substitute") && moves.has("endeavor")) {
if (hp % 4 > 0)
break;
} else {
if (isDoubles)
break;
if (srWeakness <= 0 || ability === "Regenerator" || ["Leftovers", "Life Orb"].includes(item))
break;
if (item !== "Sitrus Berry" && hp % (4 / srWeakness) > 0)
break;
if (item === "Sitrus Berry" && hp % (4 / srWeakness) === 0)
break;
}
evs.hp -= 4;
}
const noAttackStatMoves = [...moves].every((m) => {
const move = this.dex.moves.get(m);
if (move.damageCallback || move.damage)
return true;
if (move.id === "shellsidearm")
return false;
if (move.id === "terablast" && (species.id === "porygon2" || ["Contrary", "Defiant"].includes(ability) || moves.has("shiftgear") || species.baseStats.atk > species.baseStats.spa))
return false;
return move.category !== "Physical" || move.id === "bodypress" || move.id === "foulplay";
});
if (noAttackStatMoves && !moves.has("transform") && this.format.mod !== "partnersincrime") {
evs.atk = 0;
ivs.atk = 0;
}
if (moves.has("gyroball") || moves.has("trickroom")) {
evs.spe = 0;
ivs.spe = 0;
}
if (this.forceTeraType)
teraType = this.forceTeraType;
const shuffledMoves = Array.from(moves);
this.prng.shuffle(shuffledMoves);
return {
name: species.baseSpecies,
species: forme,
gender: species.baseSpecies === "Greninja" ? "M" : species.gender,
shiny: this.randomChance(1, 1024),
level,
moves: shuffledMoves,
ability,
evs,
ivs,
item,
teraType,
role
};
}
getPokemonPool(type, pokemonToExclude = [], isMonotype = false, pokemonList) {
const exclude = pokemonToExclude.map((p) => (0, import_dex.toID)(p.species));
const pokemonPool = {};
const baseSpeciesPool = [];
for (const pokemon of pokemonList) {
let species = this.dex.species.get(pokemon);
if (exclude.includes(species.id))
continue;
if (isMonotype) {
if (!species.types.includes(type))
continue;
if (typeof species.battleOnly === "string") {
species = this.dex.species.get(species.battleOnly);
if (!species.types.includes(type))
continue;
}
}
if (species.baseSpecies in pokemonPool) {
pokemonPool[species.baseSpecies].push(pokemon);
} else {
pokemonPool[species.baseSpecies] = [pokemon];
}
}
for (const baseSpecies of Object.keys(pokemonPool)) {
const weight = baseSpecies === "Squawkabilly" ? 1 : Math.min(Math.ceil(pokemonPool[baseSpecies].length / 3), 3);
for (let i = 0; i < weight; i++)
baseSpeciesPool.push(baseSpecies);
}
return [pokemonPool, baseSpeciesPool];
}
randomTeam() {
this.enforceNoDirectCustomBanlistChanges();
const seed = this.prng.getSeed();
const ruleTable = this.dex.formats.getRuleTable(this.format);
const pokemon = [];
const isMonotype = !!this.forceMonotype || ruleTable.has("sametypeclause");
const isDoubles = this.format.gameType !== "singles";
const typePool = this.dex.types.names().filter((name) => name !== "Stellar");
const type = this.forceMonotype || this.sample(typePool);
const usePotD = global.Config && Config.potd && ruleTable.has("potd");
const potd = usePotD ? this.dex.species.get(Config.potd) : null;
const baseFormes = {};
const typeCount = {};
const typeComboCount = {};
const typeWeaknesses = {};
const typeDoubleWeaknesses = {};
const teamDetails = {};
let numMaxLevelPokemon = 0;
const pokemonList = isDoubles ? Object.keys(this.randomDoublesSets) : Object.keys(this.randomSets);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
let leadsRemaining = this.format.gameType === "doubles" ? 2 : 1;
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
let species = this.dex.species.get(this.sample(pokemonPool[baseSpecies]));
if (!species.exists)
continue;
if (baseFormes[species.baseSpecies])
continue;
if (["ogerpon", "ogerponhearthflame", "terapagos"].includes(species.id) && teamDetails.teraBlast)
continue;
if (species.baseSpecies === "Zoroark" && pokemon.length >= this.maxTeamSize - 1)
continue;
const types = species.types;
const typeCombo = types.slice().sort().join();
const weakToFreezeDry = this.dex.getEffectiveness("Ice", species) > 0 || this.dex.getEffectiveness("Ice", species) > -2 && types.includes("Water");
const limitFactor = Math.round(this.maxTeamSize / 6) || 1;
if (!isMonotype && !this.forceMonotype) {
let skip = false;
for (const typeName of types) {
if (typeCount[typeName] >= 2 * limitFactor) {
skip = true;
break;
}
}
if (skip)
continue;
for (const typeName of this.dex.types.names()) {
if (this.dex.getEffectiveness(typeName, species) > 0) {
if (!typeWeaknesses[typeName])
typeWeaknesses[typeName] = 0;
if (typeWeaknesses[typeName] >= 3 * limitFactor) {
skip = true;
break;
}
}
if (this.dex.getEffectiveness(typeName, species) > 1) {
if (!typeDoubleWeaknesses[typeName])
typeDoubleWeaknesses[typeName] = 0;
if (typeDoubleWeaknesses[typeName] >= limitFactor) {
skip = true;
break;
}
}
}
if (skip)
continue;
if (this.dex.getEffectiveness("Fire", species) === 0 && Object.values(species.abilities).filter((a) => ["Dry Skin", "Fluffy"].includes(a)).length) {
if (!typeWeaknesses["Fire"])
typeWeaknesses["Fire"] = 0;
if (typeWeaknesses["Fire"] >= 3 * limitFactor)
continue;
}
if (weakToFreezeDry) {
if (!typeWeaknesses["Freeze-Dry"])
typeWeaknesses["Freeze-Dry"] = 0;
if (typeWeaknesses["Freeze-Dry"] >= 4 * limitFactor)
continue;
}
if (!this.adjustLevel && this.getLevel(species, isDoubles) === 100 && numMaxLevelPokemon >= limitFactor) {
continue;
}
}
if (!this.forceMonotype && isMonotype && typeComboCount[typeCombo] >= 3 * limitFactor)
continue;
if (potd?.exists && (pokemon.length === 1 || this.maxTeamSize === 1))
species = potd;
let set;
if (leadsRemaining) {
if (isDoubles && DOUBLES_NO_LEAD_POKEMON.includes(species.baseSpecies) || !isDoubles && NO_LEAD_POKEMON.includes(species.baseSpecies)) {
if (pokemon.length + leadsRemaining === this.maxTeamSize)
continue;
set = this.randomSet(species, teamDetails, false, isDoubles);
pokemon.push(set);
} else {
set = this.randomSet(species, teamDetails, true, isDoubles);
pokemon.unshift(set);
leadsRemaining--;
}
} else {
set = this.randomSet(species, teamDetails, false, isDoubles);
pokemon.push(set);
}
if (pokemon.length === this.maxTeamSize)
break;
baseFormes[species.baseSpecies] = 1;
for (const typeName of types) {
if (typeName in typeCount) {
typeCount[typeName]++;
} else {
typeCount[typeName] = 1;
}
}
if (typeCombo in typeComboCount) {
typeComboCount[typeCombo]++;
} else {
typeComboCount[typeCombo] = 1;
}
for (const typeName of this.dex.types.names()) {
if (this.dex.getEffectiveness(typeName, species) > 0) {
typeWeaknesses[typeName]++;
}
if (this.dex.getEffectiveness(typeName, species) > 1) {
typeDoubleWeaknesses[typeName]++;
}
}
if (["Dry Skin", "Fluffy"].includes(set.ability) && this.dex.getEffectiveness("Fire", species) === 0) {
typeWeaknesses["Fire"]++;
}
if (weakToFreezeDry)
typeWeaknesses["Freeze-Dry"]++;
if (set.level === 100)
numMaxLevelPokemon++;
if (set.ability === "Drizzle" || set.moves.includes("raindance"))
teamDetails.rain = 1;
if (set.ability === "Drought" || set.ability === "Orichalcum Pulse" || set.moves.includes("sunnyday")) {
teamDetails.sun = 1;
}
if (set.ability === "Sand Stream")
teamDetails.sand = 1;
if (set.ability === "Snow Warning" || set.moves.includes("snowscape") || set.moves.includes("chillyreception")) {
teamDetails.snow = 1;
}
if (set.moves.includes("healbell"))
teamDetails.statusCure = 1;
if (set.moves.includes("spikes") || set.moves.includes("ceaselessedge")) {
teamDetails.spikes = (teamDetails.spikes || 0) + 1;
}
if (set.moves.includes("toxicspikes") || set.ability === "Toxic Debris")
teamDetails.toxicSpikes = 1;
if (set.moves.includes("stealthrock") || set.moves.includes("stoneaxe"))
teamDetails.stealthRock = 1;
if (set.moves.includes("stickyweb"))
teamDetails.stickyWeb = 1;
if (set.moves.includes("defog"))
teamDetails.defog = 1;
if (set.moves.includes("rapidspin") || set.moves.includes("mortalspin"))
teamDetails.rapidSpin = 1;
if (set.moves.includes("auroraveil") || set.moves.includes("reflect") && set.moves.includes("lightscreen")) {
teamDetails.screens = 1;
}
if (set.role === "Tera Blast user" || ["ogerpon", "ogerponhearthflame", "terapagos"].includes(species.id)) {
teamDetails.teraBlast = 1;
}
}
if (pokemon.length < this.maxTeamSize && pokemon.length < 12) {
throw new Error(`Could not build a random team for ${this.format} (seed=${seed})`);
}
return pokemon;
}
randomCCTeam() {
this.enforceNoDirectCustomBanlistChanges();
const dex = this.dex;
const team = [];
const natures = this.dex.natures.all();
const items = this.dex.items.all();
const randomN = this.randomNPokemon(this.maxTeamSize, this.forceMonotype, void 0, void 0, true);
for (let forme of randomN) {
let species = dex.species.get(forme);
if (species.isNonstandard)
species = dex.species.get(species.baseSpecies);
let item = "";
let isIllegalItem;
let isBadItem;
if (this.gen >= 2) {
do {
item = this.sample(items).name;
isIllegalItem = this.dex.items.get(item).gen > this.gen || this.dex.items.get(item).isNonstandard;
isBadItem = item.startsWith("TR") || this.dex.items.get(item).isPokeball;
} while (isIllegalItem || isBadItem && this.randomChance(19, 20));
}
if (species.battleOnly) {
if (typeof species.battleOnly === "string") {
species = dex.species.get(species.battleOnly);
} else {
species = dex.species.get(this.sample(species.battleOnly));
}
forme = species.name;
} else if (species.requiredItems && !species.requiredItems.some((req) => (0, import_dex.toID)(req) === item)) {
if (!species.changesFrom)
throw new Error(`${species.name} needs a changesFrom value`);
species = dex.species.get(species.changesFrom);
forme = species.name;
}
let itemData = this.dex.items.get(item);
if (itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies) {
do {
itemData = this.sample(items);
item = itemData.name;
} while (itemData.gen > this.gen || itemData.isNonstandard || itemData.forcedForme && forme === this.dex.species.get(itemData.forcedForme).baseSpecies);
}
const abilities = Object.values(species.abilities).filter((a) => this.dex.abilities.get(a).gen <= this.gen);
const ability = this.gen <= 2 ? "No Ability" : this.sample(abilities);
let pool = ["struggle"];
if (forme === "Smeargle") {
pool = this.dex.moves.all().filter((move) => !(move.isNonstandard || move.isZ || move.isMax || move.realMove)).map((m) => m.id);
} else {
pool = [...this.dex.species.getMovePool(species.id)];
}
const moves = this.multipleSamplesNoReplace(pool, this.maxMoveCount);
const evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
const s = ["hp", "atk", "def", "spa", "spd", "spe"];
let evpool = 510;
do {
const x = this.sample(s);
const y = this.random(Math.min(256 - evs[x], evpool + 1));
evs[x] += y;
evpool -= y;
} while (evpool > 0);
const ivs = {
hp: this.random(32),
atk: this.random(32),
def: this.random(32),
spa: this.random(32),
spd: this.random(32),
spe: this.random(32)
};
const nature = this.sample(natures).name;
const mbstmin = 1307;
let stats = species.baseStats;
if (species.baseSpecies === "Wishiwashi")
stats = import_dex.Dex.species.get("wishiwashischool").baseStats;
if (species.baseSpecies === "Terapagos")
stats = import_dex.Dex.species.get("terapagosterastal").baseStats;
let mbst = stats["hp"] * 2 + 31 + 21 + 100 + 10;
mbst += stats["atk"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["def"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["spa"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["spd"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["spe"] * 2 + 31 + 21 + 100 + 5;
let level;
if (this.adjustLevel) {
level = this.adjustLevel;
} else {
level = Math.floor(100 * mbstmin / mbst);
while (level < 100) {
mbst = Math.floor((stats["hp"] * 2 + 31 + 21 + 100) * level / 100 + 10);
mbst += Math.floor(((stats["atk"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
mbst += Math.floor((stats["def"] * 2 + 31 + 21 + 100) * level / 100 + 5);
mbst += Math.floor(((stats["spa"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
mbst += Math.floor((stats["spd"] * 2 + 31 + 21 + 100) * level / 100 + 5);
mbst += Math.floor((stats["spe"] * 2 + 31 + 21 + 100) * level / 100 + 5);
if (mbst >= mbstmin)
break;
level++;
}
}
const happiness = this.random(256);
const shiny = this.randomChance(1, 1024);
const set = {
name: species.baseSpecies,
species: species.name,
gender: species.gender,
item,
ability,
moves,
evs,
ivs,
nature,
level,
happiness,
shiny
};
if (this.gen === 9) {
if (this.forceTeraType) {
set.teraType = this.forceTeraType;
} else {
set.teraType = this.sample(this.dex.types.names());
}
}
team.push(set);
}
return team;
}
getPools(requiredType, minSourceGen, ruleTable, requireMoves = false) {
const isNotCustom = !ruleTable;
let pool = [];
let speciesPool = [];
const ck = this.poolsCacheKey;
if (ck && this.cachedPool && this.cachedSpeciesPool && ck[0] === requiredType && ck[1] === minSourceGen && ck[2] === ruleTable && ck[3] === requireMoves) {
speciesPool = this.cachedSpeciesPool.slice();
pool = this.cachedPool.slice();
} else if (isNotCustom) {
speciesPool = [...this.dex.species.all()];
for (const species of speciesPool) {
if (species.isNonstandard && species.isNonstandard !== "Unobtainable")
continue;
if (requireMoves) {
const hasMovesInCurrentGen = this.dex.species.getMovePool(species.id).size;
if (!hasMovesInCurrentGen)
continue;
}
if (requiredType && !species.types.includes(requiredType))
continue;
if (minSourceGen && species.gen < minSourceGen)
continue;
const num = species.num;
if (num <= 0 || pool.includes(num))
continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
} else {
const EXISTENCE_TAG = ["past", "future", "lgpe", "unobtainable", "cap", "custom", "nonexistent"];
const nonexistentBanReason = ruleTable.check("nonexistent");
for (const species of this.dex.species.all()) {
if (requiredType && !species.types.includes(requiredType))
continue;
let banReason = ruleTable.check("pokemon:" + species.id);
if (banReason)
continue;
if (banReason !== "") {
if (species.isMega && ruleTable.check("pokemontag:mega"))
continue;
banReason = ruleTable.check("basepokemon:" + (0, import_dex.toID)(species.baseSpecies));
if (banReason)
continue;
if (banReason !== "" || this.dex.species.get(species.baseSpecies).isNonstandard !== species.isNonstandard) {
const nonexistentCheck = import_tags.Tags.nonexistent.genericFilter(species) && nonexistentBanReason;
let tagWhitelisted = false;
let tagBlacklisted = false;
for (const ruleid of ruleTable.tagRules) {
if (ruleid.startsWith("*"))
continue;
const tagid = ruleid.slice(12);
const tag = import_tags.Tags[tagid];
if ((tag.speciesFilter || tag.genericFilter)(species)) {
const existenceTag = EXISTENCE_TAG.includes(tagid);
if (ruleid.startsWith("+")) {
if (!existenceTag && nonexistentCheck)
continue;
tagWhitelisted = true;
break;
}
tagBlacklisted = true;
break;
}
}
if (tagBlacklisted)
continue;
if (!tagWhitelisted) {
if (ruleTable.check("pokemontag:allpokemon"))
continue;
}
}
}
speciesPool.push(species);
const num = species.num;
if (pool.includes(num))
continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
}
return { pool, speciesPool };
}
randomNPokemon(n, requiredType, minSourceGen, ruleTable, requireMoves = false) {
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}
const { pool, speciesPool } = this.getPools(requiredType, minSourceGen, ruleTable, requireMoves);
const isNotCustom = !ruleTable;
const hasDexNumber = {};
for (let i = 0; i < n; i++) {
const num = this.sampleNoReplace(pool);
hasDexNumber[num] = i;
}
const formes = [];
for (const species of speciesPool) {
if (!(species.num in hasDexNumber))
continue;
if (isNotCustom && (species.gen > this.gen || species.isNonstandard && species.isNonstandard !== "Unobtainable"))
continue;
if (requiredType && !species.types.includes(requiredType))
continue;
if (!formes[hasDexNumber[species.num]])
formes[hasDexNumber[species.num]] = [];
formes[hasDexNumber[species.num]].push(species.name);
}
if (formes.length < n) {
throw new Error(`Legal Pokemon forme count insufficient to support Max Team Size: (${formes.length} / ${n}).`);
}
const nPokemon = [];
for (let i = 0; i < n; i++) {
if (!formes[i].length) {
throw new Error(`Invalid pokemon gen ${this.gen}: ${JSON.stringify(formes)} numbers ${JSON.stringify(hasDexNumber)}`);
}
nPokemon.push(this.sample(formes[i]));
}
return nPokemon;
}
randomHCTeam() {
const hasCustomBans = this.hasDirectCustomBanlistChanges();
const ruleTable = this.dex.formats.getRuleTable(this.format);
const hasNonexistentBan = hasCustomBans && ruleTable.check("nonexistent");
const hasNonexistentWhitelist = hasCustomBans && hasNonexistentBan === "";
if (hasCustomBans) {
this.enforceNoDirectComplexBans();
}
const doItemsExist = this.gen > 1;
let itemPool = [];
if (doItemsExist) {
if (!hasCustomBans) {
itemPool = [...this.dex.items.all()].filter((item) => item.gen <= this.gen && !item.isNonstandard);
} else {
const hasAllItemsBan = ruleTable.check("pokemontag:allitems");
for (const item of this.dex.items.all()) {
let banReason = ruleTable.check("item:" + item.id);
if (banReason)
continue;
if (banReason !== "" && item.id) {
if (hasAllItemsBan)
continue;
if (item.isNonstandard) {
banReason = ruleTable.check("pokemontag:" + (0, import_dex.toID)(item.isNonstandard));
if (banReason)
continue;
if (banReason !== "" && item.isNonstandard !== "Unobtainable") {
if (hasNonexistentBan)
continue;
if (!hasNonexistentWhitelist)
continue;
}
}
}
itemPool.push(item);
}
if (ruleTable.check("item:noitem")) {
this.enforceCustomPoolSizeNoComplexBans("item", itemPool, this.maxTeamSize, "Max Team Size");
}
}
}
const doAbilitiesExist = this.gen > 2 && this.dex.currentMod !== "gen7letsgo";
let abilityPool = [];
if (doAbilitiesExist) {
if (!hasCustomBans) {
abilityPool = [...this.dex.abilities.all()].filter((ability) => ability.gen <= this.gen && !ability.isNonstandard);
} else {
const hasAllAbilitiesBan = ruleTable.check("pokemontag:allabilities");
for (const ability of this.dex.abilities.all()) {
let banReason = ruleTable.check("ability:" + ability.id);
if (banReason)
continue;
if (banReason !== "") {
if (hasAllAbilitiesBan)
continue;
if (ability.isNonstandard) {
banReason = ruleTable.check("pokemontag:" + (0, import_dex.toID)(ability.isNonstandard));
if (banReason)
continue;
if (banReason !== "") {
if (hasNonexistentBan)
continue;
if (!hasNonexistentWhitelist)
continue;
}
}
}
abilityPool.push(ability);
}
if (ruleTable.check("ability:noability")) {
this.enforceCustomPoolSizeNoComplexBans("ability", abilityPool, this.maxTeamSize, "Max Team Size");
}
}
}
const setMoveCount = ruleTable.maxMoveCount;
let movePool = [];
if (!hasCustomBans) {
movePool = [...this.dex.moves.all()].filter((move) => move.gen <= this.gen && !move.isNonstandard);
} else {
const hasAllMovesBan = ruleTable.check("pokemontag:allmoves");
for (const move of this.dex.moves.all()) {
let banReason = ruleTable.check("move:" + move.id);
if (banReason)
continue;
if (banReason !== "") {
if (hasAllMovesBan)
continue;
if (move.isNonstandard) {
banReason = ruleTable.check("pokemontag:" + (0, import_dex.toID)(move.isNonstandard));
if (banReason)
continue;
if (banReason !== "" && move.isNonstandard !== "Unobtainable") {
if (hasNonexistentBan)
continue;
if (!hasNonexistentWhitelist)
continue;
}
}
}
movePool.push(move);
}
this.enforceCustomPoolSizeNoComplexBans("move", movePool, this.maxTeamSize * setMoveCount, "Max Team Size * Max Move Count");
}
const doNaturesExist = this.gen > 2;
let naturePool = [];
if (doNaturesExist) {
if (!hasCustomBans) {
naturePool = [...this.dex.natures.all()];
} else {
const hasAllNaturesBan = ruleTable.check("pokemontag:allnatures");
for (const nature of this.dex.natures.all()) {
let banReason = ruleTable.check("nature:" + nature.id);
if (banReason)
continue;
if (banReason !== "" && nature.id) {
if (hasAllNaturesBan)
continue;
if (nature.isNonstandard) {
banReason = ruleTable.check("pokemontag:" + (0, import_dex.toID)(nature.isNonstandard));
if (banReason)
continue;
if (banReason !== "" && nature.isNonstandard !== "Unobtainable") {
if (hasNonexistentBan)
continue;
if (!hasNonexistentWhitelist)
continue;
}
}
}
naturePool.push(nature);
}
}
}
const randomN = this.randomNPokemon(
this.maxTeamSize,
this.forceMonotype,
void 0,
hasCustomBans ? ruleTable : void 0
);
const team = [];
for (const forme of randomN) {
const species = this.dex.species.get(forme);
let item = "";
let itemData;
let isBadItem;
if (doItemsExist) {
do {
itemData = this.sampleNoReplace(itemPool);
item = itemData?.name;
isBadItem = item.startsWith("TR") || itemData.isPokeball;
} while (isBadItem && this.randomChance(19, 20) && itemPool.length > this.maxTeamSize);
}
let ability = "No Ability";
let abilityData;
if (doAbilitiesExist) {
abilityData = this.sampleNoReplace(abilityPool);
ability = abilityData?.name;
}
const m = [];
do {
const move = this.sampleNoReplace(movePool);
m.push(move.id);
} while (m.length < setMoveCount);
const evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 };
if (this.gen === 6) {
let evpool = 510;
do {
const x = this.sample(import_dex.Dex.stats.ids());
const y = this.random(Math.min(256 - evs[x], evpool + 1));
evs[x] += y;
evpool -= y;
} while (evpool > 0);
} else {
for (const x of import_dex.Dex.stats.ids()) {
evs[x] = this.random(256);
}
}
const ivs = {
hp: this.random(32),
atk: this.random(32),
def: this.random(32),
spa: this.random(32),
spd: this.random(32),
spe: this.random(32)
};
let nature = "";
if (doNaturesExist && naturePool.length > 0) {
nature = this.sample(naturePool).name;
}
const mbstmin = 1307;
const stats = species.baseStats;
let mbst = stats["hp"] * 2 + 31 + 21 + 100 + 10;
mbst += stats["atk"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["def"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["spa"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["spd"] * 2 + 31 + 21 + 100 + 5;
mbst += stats["spe"] * 2 + 31 + 21 + 100 + 5;
let level;
if (this.adjustLevel) {
level = this.adjustLevel;
} else {
level = Math.floor(100 * mbstmin / mbst);
while (level < 100) {
mbst = Math.floor((stats["hp"] * 2 + 31 + 21 + 100) * level / 100 + 10);
mbst += Math.floor(((stats["atk"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
mbst += Math.floor((stats["def"] * 2 + 31 + 21 + 100) * level / 100 + 5);
mbst += Math.floor(((stats["spa"] * 2 + 31 + 21 + 100) * level / 100 + 5) * level / 100);
mbst += Math.floor((stats["spd"] * 2 + 31 + 21 + 100) * level / 100 + 5);
mbst += Math.floor((stats["spe"] * 2 + 31 + 21 + 100) * level / 100 + 5);
if (mbst >= mbstmin)
break;
level++;
}
}
const happiness = this.random(256);
const shiny = this.randomChance(1, 1024);
const set = {
name: species.baseSpecies,
species: species.name,
gender: species.gender,
item,
ability,
moves: m,
evs,
ivs,
nature,
level,
happiness,
shiny
};
if (this.gen === 9) {
if (this.forceTeraType) {
set.teraType = this.forceTeraType;
} else {
set.teraType = this.sample(this.dex.types.names());
}
}
team.push(set);
}
return team;
}
randomFactorySet(species, teamData, tier) {
const id = (0, import_dex.toID)(species.name);
const setList = this.randomFactorySets[tier][id].sets;
const itemsLimited = ["choicespecs", "choiceband", "choicescarf"];
const movesLimited = {
stealthrock: "stealthRock",
stoneaxe: "stealthRock",
spikes: "spikes",
ceaselessedge: "spikes",
toxicspikes: "toxicSpikes",
rapidspin: "hazardClear",
defog: "hazardClear"
};
const abilitiesLimited = {
toxicdebris: "toxicSpikes"
};
const effectivePool = [];
for (const set of setList) {
let reject = false;
if (set.wantsTera && teamData.wantsTeraCount) {
continue;
}
const allowedItems = [];
for (const itemString of set.item) {
const itemId = (0, import_dex.toID)(itemString);
if (itemsLimited.includes(itemId) && teamData.has[itemId])
continue;
allowedItems.push(itemString);
}
if (!allowedItems.length)
continue;
const item2 = this.sample(allowedItems);
const abilityId = (0, import_dex.toID)(this.sample(set.ability));
if (abilitiesLimited[abilityId] && teamData.has[abilitiesLimited[abilityId]])
continue;
const moves2 = [];
for (const move of set.moves) {
const allowedMoves = [];
for (const m of move) {
const moveId = (0, import_dex.toID)(m);
if (movesLimited[moveId] && teamData.has[movesLimited[moveId]])
continue;
allowedMoves.push(m);
}
if (!allowedMoves.length) {
reject = true;
break;
}
moves2.push(this.sample(allowedMoves));
}
if (reject)
continue;
effectivePool.push({ set, moves: moves2, item: item2 });
}
if (!effectivePool.length) {
if (!teamData.forceResult)
return null;
for (const set of setList) {
effectivePool.push({ set });
}
}
let setData = this.sample(effectivePool);
const total = effectivePool.reduce((a, b) => a + b.set.weight, 0);
const setRand = this.random(total);
let cur = 0;
for (const set of effectivePool) {
cur += set.set.weight;
if (cur > setRand) {
setData = set;
break;
}
}
const moves = [];
for (const [i, moveSlot] of setData.set.moves.entries()) {
moves.push(setData.moves ? setData.moves[i] : this.sample(moveSlot));
}
const item = setData.item || this.sample(setData.set.item);
return {
name: species.baseSpecies,
species: typeof species.battleOnly === "string" ? species.battleOnly : species.name,
teraType: this.sample(setData.set.teraType),
gender: setData.set.gender || species.gender || (tier === "OU" ? "F" : ""),
// F for Cute Charm Enamorus
item,
ability: this.sample(setData.set.ability),
shiny: setData.set.shiny || this.randomChance(1, 1024),
level: this.adjustLevel || (tier === "LC" ? 5 : 100),
happiness: 255,
evs: { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs },
ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs },
nature: this.sample(setData.set.nature) || "Serious",
moves,
wantsTera: setData.set.wantsTera
};
}
randomFactoryTeam(side, depth = 0) {
this.enforceNoDirectCustomBanlistChanges();
const forceResult = depth >= 12;
if (!this.factoryTier) {
this.factoryTier = this.sample(["Uber", "OU", "UU", "RU", "NU", "PU"]);
}
const tierValues = {
Uber: 5,
OU: 4,
UUBL: 4,
UU: 3,
RUBL: 3,
RU: 2,
NUBL: 2,
NU: 1,
PUBL: 1,
PU: 0
};
const pokemon = [];
const pokemonPool = Object.keys(this.randomFactorySets[this.factoryTier]);
const teamData = {
typeCount: {},
typeComboCount: {},
baseFormes: {},
has: {},
wantsTeraCount: 0,
forceResult,
weaknesses: {},
resistances: {}
};
const resistanceAbilities = {
dryskin: ["Water"],
waterabsorb: ["Water"],
stormdrain: ["Water"],
flashfire: ["Fire"],
heatproof: ["Fire"],
waterbubble: ["Fire"],
wellbakedbody: ["Fire"],
lightningrod: ["Electric"],
motordrive: ["Electric"],
voltabsorb: ["Electric"],
sapsipper: ["Grass"],
thickfat: ["Ice", "Fire"],
eartheater: ["Ground"],
levitate: ["Ground"]
};
const movesLimited = {
stealthrock: "stealthRock",
stoneaxe: "stealthRock",
spikes: "spikes",
ceaselessedge: "spikes",
toxicspikes: "toxicSpikes",
rapidspin: "hazardClear",
defog: "hazardClear"
};
const abilitiesLimited = {
toxicdebris: "toxicSpikes"
};
const limitFactor = Math.ceil(this.maxTeamSize / 6);
const shuffledSpecies = [];
for (const speciesName of pokemonPool) {
const sortObject = {
speciesName,
score: this.prng.random() ** (1 / this.randomFactorySets[this.factoryTier][speciesName].weight)
};
shuffledSpecies.push(sortObject);
}
shuffledSpecies.sort((a, b) => a.score - b.score);
while (shuffledSpecies.length && pokemon.length < this.maxTeamSize) {
const species = this.dex.species.get(shuffledSpecies.pop().speciesName);
if (!species.exists)
continue;
if (this.factoryTier in tierValues && species.tier in tierValues && tierValues[species.tier] > tierValues[this.factoryTier])
continue;
if (this.forceMonotype && !species.types.includes(this.forceMonotype))
continue;
if (teamData.baseFormes[species.baseSpecies])
continue;
const types = species.types;
let skip = false;
if (!this.forceMonotype) {
for (const type of types) {
if (teamData.typeCount[type] >= 2 * limitFactor && this.randomChance(4, 5)) {
skip = true;
break;
}
}
}
if (skip)
continue;
if (!teamData.forceResult && !this.forceMonotype) {
for (const typeName of this.dex.types.names()) {
if (this.dex.getEffectiveness(typeName, species) > 0 && this.dex.getImmunity(typeName, types)) {
if (teamData.weaknesses[typeName] >= 3 * limitFactor) {
skip = true;
break;
}
}
}
}
if (skip)
continue;
const set = this.randomFactorySet(species, teamData, this.factoryTier);
if (!set)
continue;
let typeCombo = types.slice().sort().join();
if (set.ability === "Drought" || set.ability === "Drizzle") {
typeCombo = set.ability;
}
if (!this.forceMonotype && teamData.typeComboCount[typeCombo] >= limitFactor)
continue;
pokemon.push(set);
for (const type of types) {
if (type in teamData.typeCount) {
teamData.typeCount[type]++;
} else {
teamData.typeCount[type] = 1;
}
}
if (typeCombo in teamData.typeComboCount) {
teamData.typeComboCount[typeCombo]++;
} else {
teamData.typeComboCount[typeCombo] = 1;
}
teamData.baseFormes[species.baseSpecies] = 1;
teamData.has[(0, import_dex.toID)(set.item)] = 1;
if (set.wantsTera) {
if (!teamData.wantsTeraCount)
teamData.wantsTeraCount = 0;
teamData.wantsTeraCount++;
}
for (const move of set.moves) {
const moveId = (0, import_dex.toID)(move);
if (movesLimited[moveId]) {
teamData.has[movesLimited[moveId]] = 1;
}
}
const ability = this.dex.abilities.get(set.ability);
if (abilitiesLimited[ability.id]) {
teamData.has[abilitiesLimited[ability.id]] = 1;
}
for (const typeName of this.dex.types.names()) {
const typeMod = this.dex.getEffectiveness(typeName, types);
if (typeMod < 0 || resistanceAbilities[ability.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
teamData.resistances[typeName] = 1;
} else if (typeMod > 0) {
teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
}
}
}
if (!teamData.forceResult && pokemon.length < this.maxTeamSize)
return this.randomFactoryTeam(side, ++depth);
if (!teamData.forceResult && !this.forceMonotype) {
for (const type in teamData.weaknesses) {
if (teamData.resistances[type])
continue;
if (teamData.weaknesses[type] >= 3 * limitFactor)
return this.randomFactoryTeam(side, ++depth);
}
if (!teamData.has["stealthRock"] && this.factoryTier !== "Uber")
return this.randomFactoryTeam(side, ++depth);
}
return pokemon;
}
randomBSSFactorySet(species, teamData) {
const id = (0, import_dex.toID)(species.name);
const setList = this.randomBSSFactorySets[id].sets;
const movesMax = {
batonpass: 1,
stealthrock: 1,
toxicspikes: 1,
trickroom: 1,
auroraveil: 1
};
const weatherAbilities = ["drizzle", "drought", "snowwarning", "sandstream"];
const terrainAbilities = {
electricsurge: "electric",
psychicsurge: "psychic",
grassysurge: "grassy",
seedsower: "grassy",
mistysurge: "misty"
};
const terrainItemsRequire = {
electricseed: "electric",
psychicseed: "psychic",
grassyseed: "grassy",
mistyseed: "misty"
};
const maxWantsTera = 2;
const effectivePool = [];
for (const curSet of setList) {
let reject = false;
if (curSet.wantsTera && teamData.wantsTeraCount && teamData.wantsTeraCount >= maxWantsTera) {
continue;
}
if (teamData.weather && weatherAbilities.includes(curSet.ability)) {
continue;
}
if (terrainAbilities[curSet.ability]) {
if (!teamData.terrain)
teamData.terrain = [];
teamData.terrain.push(terrainAbilities[curSet.ability]);
}
for (const item of curSet.item) {
if (terrainItemsRequire[item] && !teamData.terrain?.includes(terrainItemsRequire[item])) {
reject = true;
break;
}
}
const curSetMoveVariants = [];
for (const move of curSet.moves) {
const variantIndex = this.random(move.length);
const moveId = (0, import_dex.toID)(move[variantIndex]);
if (movesMax[moveId] && teamData.has[moveId] >= movesMax[moveId]) {
reject = true;
break;
}
curSetMoveVariants.push(variantIndex);
}
if (reject)
continue;
const set = { set: curSet, moveVariants: curSetMoveVariants };
effectivePool.push(set);
}
if (!effectivePool.length) {
if (!teamData.forceResult)
return null;
for (const curSet of setList) {
effectivePool.push({ set: curSet });
}
}
let setData = this.sample(effectivePool);
const total = effectivePool.reduce((a, b) => a + b.set.weight, 0);
const setRand = this.random(total);
let cur = 0;
for (const set of effectivePool) {
cur += set.set.weight;
if (cur > setRand) {
setData = set;
break;
}
}
const moves = [];
for (const [i, moveSlot] of setData.set.moves.entries()) {
moves.push(setData.moveVariants ? moveSlot[setData.moveVariants[i]] : this.sample(moveSlot));
}
return {
name: setData.set.species || species.baseSpecies,
species: setData.set.species,
teraType: this.sampleIfArray(setData.set.teraType),
gender: setData.set.gender || species.gender || (this.randomChance(1, 2) ? "M" : "F"),
item: this.sampleIfArray(setData.set.item) || "",
ability: this.sampleIfArray(setData.set.ability),
shiny: this.randomChance(1, 1024),
level: 50,
happiness: 255,
evs: { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0, ...setData.set.evs },
ivs: { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31, ...setData.set.ivs },
nature: setData.set.nature || "Serious",
moves,
wantsTera: setData.set.wantsTera
};
}
randomBSSFactoryTeam(side, depth = 0) {
this.enforceNoDirectCustomBanlistChanges();
const forceResult = depth >= 4;
const pokemon = [];
const pokemonPool = Object.keys(this.randomBSSFactorySets);
const teamData = {
typeCount: {},
typeComboCount: {},
baseFormes: {},
has: {},
wantsTeraCount: 0,
forceResult,
weaknesses: {},
resistances: {}
};
const weatherAbilitiesSet = {
drizzle: "raindance",
drought: "sunnyday",
snowwarning: "hail",
sandstream: "sandstorm"
};
const resistanceAbilities = {
waterabsorb: ["Water"],
flashfire: ["Fire"],
lightningrod: ["Electric"],
voltabsorb: ["Electric"],
thickfat: ["Ice", "Fire"],
levitate: ["Ground"]
};
const limitFactor = Math.ceil(this.maxTeamSize / 6);
const shuffledSpecies = [];
for (const speciesName of pokemonPool) {
const sortObject = {
speciesName,
score: this.prng.random() ** (1 / this.randomBSSFactorySets[speciesName].weight)
};
shuffledSpecies.push(sortObject);
}
shuffledSpecies.sort((a, b) => a.score - b.score);
while (shuffledSpecies.length && pokemon.length < this.maxTeamSize) {
const species = this.dex.species.get(shuffledSpecies.pop().speciesName);
if (!species.exists)
continue;
if (this.forceMonotype && !species.types.includes(this.forceMonotype))
continue;
if (teamData.baseFormes[species.baseSpecies])
continue;
const types = species.types;
let skip = false;
if (!this.forceMonotype) {
for (const type of types) {
if (teamData.typeCount[type] >= 2 * limitFactor && this.randomChance(4, 5)) {
skip = true;
break;
}
}
}
if (skip)
continue;
const set = this.randomBSSFactorySet(species, teamData);
if (!set)
continue;
let typeCombo = types.slice().sort().join();
if (set.ability === "Drought" || set.ability === "Drizzle") {
typeCombo = set.ability;
}
if (!this.forceMonotype && teamData.typeComboCount[typeCombo] >= limitFactor)
continue;
const itemData = this.dex.items.get(set.item);
if (teamData.has[itemData.id])
continue;
pokemon.push(set);
for (const type of types) {
if (type in teamData.typeCount) {
teamData.typeCount[type]++;
} else {
teamData.typeCount[type] = 1;
}
}
if (typeCombo in teamData.typeComboCount) {
teamData.typeComboCount[typeCombo]++;
} else {
teamData.typeComboCount[typeCombo] = 1;
}
teamData.baseFormes[species.baseSpecies] = 1;
teamData.has[itemData.id] = 1;
if (set.wantsTera) {
if (!teamData.wantsTeraCount)
teamData.wantsTeraCount = 0;
teamData.wantsTeraCount++;
}
const abilityState = this.dex.abilities.get(set.ability);
if (abilityState.id in weatherAbilitiesSet) {
teamData.weather = weatherAbilitiesSet[abilityState.id];
}
for (const move of set.moves) {
const moveId = (0, import_dex.toID)(move);
if (moveId in teamData.has) {
teamData.has[moveId]++;
} else {
teamData.has[moveId] = 1;
}
}
for (const typeName of this.dex.types.names()) {
if (teamData.resistances[typeName] >= 1)
continue;
if (resistanceAbilities[abilityState.id]?.includes(typeName) || !this.dex.getImmunity(typeName, types)) {
teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
if (teamData.resistances[typeName] >= 1)
teamData.weaknesses[typeName] = 0;
continue;
}
const typeMod = this.dex.getEffectiveness(typeName, types);
if (typeMod < 0) {
teamData.resistances[typeName] = (teamData.resistances[typeName] || 0) + 1;
if (teamData.resistances[typeName] >= 1)
teamData.weaknesses[typeName] = 0;
} else if (typeMod > 0) {
teamData.weaknesses[typeName] = (teamData.weaknesses[typeName] || 0) + 1;
}
}
}
if (!teamData.forceResult && pokemon.length < this.maxTeamSize)
return this.randomBSSFactoryTeam(side, ++depth);
if (!teamData.forceResult && !this.forceMonotype) {
for (const type in teamData.weaknesses) {
if (teamData.weaknesses[type] >= 3 * limitFactor)
return this.randomBSSFactoryTeam(side, ++depth);
}
}
return pokemon;
}
randomDraftFactoryTeam(side) {
this.enforceNoDirectCustomBanlistChanges();
if (this.rdfMatchupIndex === -1)
this.rdfMatchupIndex = this.random(0, this.randomDraftFactoryMatchups.length);
if (this.rdfMatchupSide === -1)
this.rdfMatchupSide = this.random(0, 2);
const matchup = this.randomDraftFactoryMatchups[this.rdfMatchupIndex];
const team = import_teams.Teams.unpack(matchup[this.rdfMatchupSide]);
if (!team)
throw new Error(`Invalid team for draft factory matchup ${this.rdfMatchupIndex}`);
this.rdfMatchupSide = 1 - this.rdfMatchupSide;
return team.map((set) => {
let species = this.dex.species.get(set.species);
if (species.battleOnly) {
if (typeof species.battleOnly !== "string") {
throw new Error(`Invalid species ${species.name} for draft factory matchup ${this.rdfMatchupIndex} team ${this.rdfMatchupSide}`);
}
species = this.dex.species.get(species.battleOnly);
}
return {
name: species.baseSpecies,
species: species.name,
gender: set.gender,
moves: set.moves,
ability: set.ability,
evs: set.evs,
ivs: set.ivs,
item: set.item,
level: this.adjustLevel || set.level,
shiny: !!set.shiny,
nature: set.nature,
teraType: set.teraType,
teraCaptain: set.name === "Tera Captain"
};
});
}
}
var teams_default = RandomTeams;
//# sourceMappingURL=teams.js.map