Spaces:
Running
Running
; | |
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 | |