Pokemon_server / dist /data /cg-teams.js
Jofthomas's picture
Jofthomas HF staff
Upload 4781 files
5c2ed06 verified
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var cg_teams_exports = {};
__export(cg_teams_exports, {
default: () => TeamGenerator,
levelUpdateInterval: () => levelUpdateInterval
});
module.exports = __toCommonJS(cg_teams_exports);
var import_sim = require("../sim");
var import_cg_team_data = require("./cg-team-data");
const MAX_WEAK_TO_SAME_TYPE = 3;
const TOP_SPEED = 300;
const levelOverride = {};
let levelUpdateInterval = null;
const useBaseSpecies = [
"Pikachu",
"Gastrodon",
"Magearna",
"Dudunsparce",
"Maushold",
"Keldeo",
"Zarude",
"Polteageist",
"Sinistcha",
"Sawsbuck",
"Vivillon",
"Florges",
"Minior",
"Toxtricity",
"Tatsugiri",
"Alcremie"
];
async function updateLevels(database) {
const updateSpecies = await database.prepare(
"UPDATE gen9computergeneratedteams SET wins = 0, losses = 0, level = ? WHERE species_id = ?"
);
const updateHistory = await database.prepare(
`INSERT INTO gen9_historical_levels (level, species_id, timestamp) VALUES (?, ?, ${Date.now()})`
);
const data = await database.all("SELECT species_id, wins, losses, level FROM gen9computergeneratedteams");
for (let { species_id, wins, losses, level } of data) {
const total = wins + losses;
if (total > 10) {
if (wins / total >= 0.55)
level--;
if (wins / total <= 0.45)
level++;
level = Math.max(1, Math.min(100, level));
await updateSpecies?.run([level, species_id]);
await updateHistory?.run([level, species_id]);
}
levelOverride[species_id] = level;
}
}
if (global.Config && Config.usesqlite && Config.usesqliteleveling) {
const database = (0, import_sim.SQL)(module, { file: "./databases/battlestats.db" });
void updateLevels(database);
levelUpdateInterval = setInterval(() => void updateLevels(database), 1e3 * 60 * 60 * 2);
}
class TeamGenerator {
constructor(format, seed) {
this.dex = import_sim.Dex.forFormat(format);
this.format = import_sim.Dex.formats.get(format);
this.teamSize = this.format.ruleTable?.maxTeamSize || 6;
this.prng = import_sim.PRNG.get(seed);
this.itemPool = this.dex.items.all().filter((i) => i.exists && i.isNonstandard !== "Past" && !i.isPokeball);
this.specialItems = {};
for (const i of this.itemPool) {
if (i.itemUser && !i.isNonstandard) {
for (const user of i.itemUser) {
if (import_sim.Dex.species.get(user).requiredItems?.[0] !== i.name)
this.specialItems[user] = i.id;
}
}
}
const rules = import_sim.Dex.formats.getRuleTable(this.format);
if (rules.adjustLevel)
this.forceLevel = rules.adjustLevel;
}
getTeam() {
let speciesPool = this.dex.species.all().filter((s) => {
if (!s.exists)
return false;
if (s.isNonstandard || s.isNonstandard === "Unobtainable")
return false;
if (s.nfe)
return false;
if (s.battleOnly && (!s.requiredItems?.length || s.name.endsWith("-Tera")))
return false;
return true;
});
const teamStats = {
hazardSetters: {},
typeWeaknesses: {},
hazardRemovers: 0
};
const team = [];
while (team.length < this.teamSize && speciesPool.length) {
const species = this.prng.sample(speciesPool);
const haveRoomToReject = speciesPool.length >= this.teamSize - team.length;
const isGoodFit = this.speciesIsGoodFit(species, teamStats);
if (haveRoomToReject && !isGoodFit)
continue;
speciesPool = speciesPool.filter((s) => s.baseSpecies !== species.baseSpecies);
team.push(this.makeSet(species, teamStats));
}
return team;
}
makeSet(species, teamStats) {
const abilityPool = Object.values(species.abilities);
const abilityWeights = abilityPool.map((a) => this.getAbilityWeight(this.dex.abilities.get(a)));
const ability = this.weightedRandomPick(abilityPool, abilityWeights);
const level = this.forceLevel || TeamGenerator.getLevel(species);
const moves = [];
let movesStats = {
setup: { atk: 0, def: 0, spa: 0, spd: 0, spe: 0 },
attackTypes: {},
noSleepTalk: 0,
hazards: 0,
stallingMoves: 0,
healing: 0,
nonStatusMoves: 0
};
let movePool = [...this.dex.species.getMovePool(species.id)];
if (!movePool.length)
throw new Error(`No moves for ${species.id}`);
const numberOfMovesToConsider = Math.min(movePool.length, Math.max(15, Math.trunc(movePool.length * 0.3)));
let movePoolIsTrimmed = false;
let isRound2 = false;
const movePoolCopy = movePool;
let interimMovePool = [];
while (moves.length < 4 && movePool.length) {
let weights;
if (!movePoolIsTrimmed) {
if (!isRound2) {
for (const moveID2 of movePool) {
const move2 = this.dex.moves.get(moveID2);
const weight = this.getMoveWeight(move2, teamStats, species, moves, movesStats, ability, level);
interimMovePool.push({ move: moveID2, weight });
}
interimMovePool.sort((a, b) => b.weight - a.weight);
} else {
const originalWeights = [];
for (const move2 of moves) {
originalWeights.push(interimMovePool.find((m) => m.move === move2.id));
}
interimMovePool = originalWeights;
for (const moveID2 of movePoolCopy) {
const move2 = this.dex.moves.get(moveID2);
if (moves.includes(move2))
continue;
const weight = this.getMoveWeight(move2, teamStats, species, moves, movesStats, ability, level);
interimMovePool.push({ move: moveID2, weight });
}
interimMovePool.sort((a, b) => b.weight - a.weight);
moves.splice(0);
movesStats = {
setup: { atk: 0, def: 0, spa: 0, spd: 0, spe: 0 },
attackTypes: {},
noSleepTalk: 0,
hazards: 0,
stallingMoves: 0,
healing: 0,
nonStatusMoves: 0
};
}
movePool = [];
weights = [];
for (let i = 0; i < numberOfMovesToConsider; i++) {
movePool.push(interimMovePool[i].move);
weights.push(interimMovePool[i].weight);
}
movePoolIsTrimmed = true;
} else {
weights = movePool.map(
(m) => this.getMoveWeight(this.dex.moves.get(m), teamStats, species, moves, movesStats, ability, level)
);
}
const moveID = this.weightedRandomPick(movePool, weights, { remove: true });
const move = this.dex.moves.get(moveID);
moves.push(move);
if (TeamGenerator.moveIsHazard(moves[moves.length - 1])) {
teamStats.hazardSetters[moveID] = (teamStats.hazardSetters[moveID] || 0) + 1;
movesStats.hazards++;
}
if (["defog", "courtchange", "tidyup", "rapidspin", "mortalspin"].includes(moveID))
teamStats.hazardRemovers++;
const boosts = move.boosts || move.self?.boosts || move.selfBoost?.boosts || ability !== "Sheer Force" && move.secondary?.self?.boosts;
if (move.category === "Status") {
if (boosts) {
for (const stat in boosts) {
const chance = Math.min(100, move.secondary?.chance || 100 * (ability === "Serene Grace" ? 2 : 1));
const boost = (boosts[stat] || 0) * chance / 100;
if (boost) {
if (movesStats.setup[stat] < 0 && boost > 0) {
movesStats.setup[stat] = boost;
} else {
movesStats.setup[stat] += boost;
}
if (boost > 1)
movesStats.noSleepTalk++;
}
}
} else {
movesStats.noSleepTalk++;
}
if (move.heal)
movesStats.healing++;
if (move.stallingMove)
movesStats.stallingMoves++;
} else {
movesStats.nonStatusMoves++;
const bp = +move.basePower;
const moveType = TeamGenerator.moveType(move, species);
if (movesStats.attackTypes[moveType] < bp)
movesStats.attackTypes[moveType] = bp;
}
if (!isRound2 && moves.length === 3) {
isRound2 = true;
movePoolIsTrimmed = false;
continue;
}
const pairedMove = import_cg_team_data.MOVE_PAIRINGS[moveID];
const alreadyHavePairedMove = moves.some((m) => m.id === pairedMove);
if (moves.length < 4 && pairedMove && !(pairedMove === "sleeptalk" && movesStats.noSleepTalk) && !alreadyHavePairedMove && // We don't check movePool because sometimes paired moves are bad.
this.dex.species.getLearnsetData(species.id).learnset?.[pairedMove]) {
moves.push(this.dex.moves.get(pairedMove));
const pairedMoveIndex = movePool.indexOf(pairedMove);
if (pairedMoveIndex > -1)
movePool.splice(pairedMoveIndex, 1);
}
}
let item = "";
const nonStatusMoves = moves.filter((m) => this.dex.moves.get(m).category !== "Status");
if (species.requiredItem) {
item = species.requiredItem;
} else if (species.requiredItems) {
item = this.prng.sample(species.requiredItems.filter((i) => !this.dex.items.get(i).isNonstandard));
} else if (this.specialItems[species.name] && nonStatusMoves.length) {
item = this.specialItems[species.name];
} else if (moves.every((m) => m.id !== "acrobatics")) {
const weights = [];
const items = [];
for (const i of this.itemPool) {
const weight = this.getItemWeight(i, teamStats, species, moves, ability, level);
if (weight !== 0) {
weights.push(weight);
items.push(i.name);
}
}
if (!item)
item = this.weightedRandomPick(items, weights);
} else if (["Quark Drive", "Protosynthesis"].includes(ability)) {
item = "Booster Energy";
}
const ivs = {
hp: 31,
atk: moves.some((move) => this.dex.moves.get(move).category === "Physical") ? 31 : 0,
def: 31,
spa: 31,
spd: 31,
spe: 31
};
const hasTeraBlast = moves.some((m) => m.id === "terablast");
const hasRevelationDance = moves.some((m) => m.id === "revelationdance");
let teraType;
if (species.forceTeraType) {
teraType = species.forceTeraType;
} else if (item === "blacksludge" && this.prng.randomChance(2, 3)) {
teraType = "Poison";
} else if (hasTeraBlast && ability === "Contrary" && this.prng.randomChance(2, 3)) {
teraType = "Stellar";
} else {
let types = nonStatusMoves.map((m) => TeamGenerator.moveType(this.dex.moves.get(m), species));
const noStellar = ability === "Adaptability" || new Set(types).size < 3;
if (hasTeraBlast || hasRevelationDance || !nonStatusMoves.length) {
types = [...this.dex.types.names()];
if (noStellar)
types.splice(types.indexOf("Stellar"));
} else {
if (!noStellar)
types.push("Stellar");
}
teraType = this.prng.sample(types);
}
return {
name: species.name,
species: species.name,
item,
ability,
moves: moves.map((m) => m.name),
nature: "Quirky",
gender: species.gender,
evs: { hp: 84, atk: 84, def: 84, spa: 84, spd: 84, spe: 84 },
ivs,
level,
teraType,
shiny: this.prng.randomChance(1, 1024),
happiness: 255
};
}
/**
* @returns true if the Pokémon is a good fit for the team so far, and no otherwise
*/
speciesIsGoodFit(species, stats) {
for (const typeName of this.dex.types.names()) {
const effectiveness = this.dex.getEffectiveness(typeName, species.types);
if (effectiveness === 1) {
if (stats.typeWeaknesses[typeName] === void 0) {
stats.typeWeaknesses[typeName] = 0;
}
if (stats.typeWeaknesses[typeName] >= MAX_WEAK_TO_SAME_TYPE) {
return false;
}
}
}
for (const typeName of this.dex.types.names()) {
const effectiveness = this.dex.getEffectiveness(typeName, species.types);
if (effectiveness === 1) {
stats.typeWeaknesses[typeName]++;
}
}
return true;
}
/**
* @returns A weighting for the Pokémon's ability.
*/
getAbilityWeight(ability) {
return ability.rating + 1;
}
static moveIsHazard(move) {
return !!(move.sideCondition && move.target === "foeSide") || ["stoneaxe", "ceaselessedge"].includes(move.id);
}
/**
* @returns A weight for a given move on a given Pokémon.
*/
getMoveWeight(move, teamStats, species, movesSoFar, movesStats, ability, level) {
if (!move.exists)
return 0;
if (move.target === "adjacentAlly")
return 0;
if (ability === "Tera Shift")
species = this.dex.species.get("Terapagos-Terastal");
const adjustedStats = {
hp: species.baseStats.hp * level / 100 + level,
atk: species.baseStats.atk * level * level / 1e4,
def: species.baseStats.def * level / 100,
spa: species.baseStats.spa * level * level / 1e4,
spd: species.baseStats.spd * level / 100,
spe: species.baseStats.spe * level / 100
};
if (move.category === "Status") {
let weight2 = 2400;
if (move.status)
weight2 *= TeamGenerator.statusWeight(move.status) * 2;
if (TeamGenerator.moveIsHazard(move) && (teamStats.hazardSetters[move.id] || 0) < 1) {
weight2 *= move.id === "spikes" ? 12 : 16;
if (movesStats.hazards)
weight2 *= 2;
}
if (["defog", "courtchange", "tidyup"].includes(move.id) && !teamStats.hazardRemovers) {
weight2 *= 32;
weight2 *= 0.8 ** Object.values(teamStats.hazardSetters).reduce((total, num) => total + num, 0);
}
weight2 *= this.boostWeight(move, movesSoFar, species, ability, level);
weight2 *= this.opponentDebuffWeight(move);
if (move.id === "focusenergy" && ability !== "Super Luck") {
const highCritMoves = movesSoFar.filter((m) => m.critRatio && m.critRatio > 1);
weight2 *= 1 + highCritMoves.length * (ability === "Sniper" ? 2 : 1);
} else if (move.id === "tailwind" && ability === "Wind Rider" && movesSoFar.some((m) => m.category === "Physical")) {
weight2 *= 2.5;
}
if (!movesStats.stallingMoves) {
if (adjustedStats.def >= 80 || adjustedStats.spd >= 80 || adjustedStats.hp >= 80) {
switch (move.volatileStatus) {
case "endure":
weight2 *= 2;
break;
case "protect":
weight2 *= 3;
break;
case "kingsshield":
case "silktrap":
weight2 *= 4;
break;
case "banefulbunker":
case "burningbulwark":
case "spikyshield":
weight2 *= 5;
break;
default:
break;
}
}
}
if (move.id in import_cg_team_data.HARDCODED_MOVE_WEIGHTS)
weight2 *= import_cg_team_data.HARDCODED_MOVE_WEIGHTS[move.id];
const sleepImmunities = [
"Comatose",
"Purifying Salt",
"Shields Down",
"Insomnia",
"Vital Spirit",
"Sweet Veil",
"Misty Surge",
"Electric Surge",
"Hadron Engine"
];
if (["sleeptalk", "rest"].includes(move.id) && sleepImmunities.includes(ability))
return 0;
if (move.id === "sleeptalk") {
if (movesStats.noSleepTalk)
weight2 *= 0.1;
} else if (movesSoFar.some((m) => m.id === "sleeptalk")) {
let sleepTalkSpammable = ["takeheart", "junglehealing", "healbell"].includes(move.id);
if (move.boosts) {
for (const stat in move.boosts) {
if (move.boosts[stat] === 1) {
sleepTalkSpammable = true;
break;
}
}
}
if (!sleepTalkSpammable)
weight2 *= 0.1;
}
const goodAttacker = adjustedStats.atk > 65 || adjustedStats.spa > 65;
if (goodAttacker && movesStats.nonStatusMoves < 2) {
weight2 *= 0.3;
}
if (movesSoFar.length === 3 && movesStats.nonStatusMoves === 0) {
weight2 *= 0.6;
for (const stat in movesStats.setup) {
if (movesStats.setup[stat] > 0) {
weight2 *= 0.6;
}
}
}
if (move.heal && movesStats.healing)
weight2 *= 0.5;
return weight2;
}
let basePower = move.basePower;
if (import_cg_team_data.WEIGHT_BASED_MOVES.includes(move.id) || import_cg_team_data.TARGET_HP_BASED_MOVES.includes(move.id))
basePower = 60;
const slownessRating = Math.max(0, TOP_SPEED - adjustedStats.spe) / TOP_SPEED;
if (move.id === "gyroball")
basePower = 150 * slownessRating * slownessRating;
if (move.id === "electroball")
basePower = 150 * (1 - slownessRating) * (1 - slownessRating);
let baseStat = move.category === "Physical" ? adjustedStats.atk : adjustedStats.spa;
if (move.id === "foulplay")
baseStat = adjustedStats.spe * level / 100;
if (move.id === "bodypress")
baseStat = adjustedStats.def * level / 100;
let accuracy = move.accuracy === true || ability === "No Guard" ? 110 : move.accuracy;
if (accuracy < 100) {
if (ability === "Compound Eyes")
accuracy = Math.min(100, Math.round(accuracy * 1.3));
if (ability === "Victory Star")
accuracy = Math.min(100, Math.round(accuracy * 1.1));
}
accuracy /= 100;
const moveType = TeamGenerator.moveType(move, species);
let powerEstimate = basePower * baseStat * accuracy;
if (species.types.includes(moveType))
powerEstimate *= ability === "Adaptability" ? 2 : 1.5;
if (ability === "Technician" && move.basePower <= 60)
powerEstimate *= 1.5;
if (ability === "Sheer Force" && (move.secondary || move.secondaries))
powerEstimate *= 1.3;
const numberOfHits = Array.isArray(move.multihit) ? ability === "Skill Link" ? move.multihit[1] : (move.multihit[0] + move.multihit[1]) / 2 : move.multihit || 1;
powerEstimate *= numberOfHits;
if (species.requiredItems) {
const item = this.dex.items.get(this.specialItems[species.name]);
if (item.onBasePower && (species.types.includes(moveType) || item.name.endsWith("Mask")))
powerEstimate *= 1.2;
} else if (this.specialItems[species.name]) {
const item = this.dex.items.get(this.specialItems[species.name]);
if (item.onBasePower && species.types.includes(moveType))
powerEstimate *= 1.2;
if (item.id === "lightball")
powerEstimate *= 2;
}
const specialSetup = movesStats.setup.spa;
const physicalSetup = movesStats.setup.atk;
if (move.category === "Physical" && !["bodypress", "foulplay"].includes(move.id)) {
powerEstimate *= Math.max(0.5, 1 + physicalSetup) / Math.max(0.5, 1 + specialSetup);
}
if (move.category === "Special")
powerEstimate *= Math.max(0.5, 1 + specialSetup) / Math.max(0.5, 1 + physicalSetup);
const abilityBonus = (import_cg_team_data.ABILITY_MOVE_BONUSES[this.dex.toID(ability)]?.[move.id] || 1) * (import_cg_team_data.ABILITY_MOVE_TYPE_BONUSES[this.dex.toID(ability)]?.[moveType] || 1);
let weight = powerEstimate * abilityBonus;
if (move.id in import_cg_team_data.HARDCODED_MOVE_WEIGHTS)
weight *= import_cg_team_data.HARDCODED_MOVE_WEIGHTS[move.id];
if (!this.specialItems[species.name] && !species.requiredItem) {
if (move.id === "acrobatics")
weight *= 1.75;
if (move.id === "facade") {
if (!["Comatose", "Purifying Salt", "Shields Down", "Natural Cure", "Misty Surge"].includes(ability))
weight *= 1.5;
}
}
if (move.priority > 0 && move.id !== "upperhand")
weight *= Math.max(105 - adjustedStats.spe, 0) / 105 * 0.5 + 1;
if (move.priority < 0 || move.id === "upperhand")
weight *= Math.min(1 / adjustedStats.spe * 25, 1);
if (move.flags.charge || move.flags.recharge && ability !== "Truant")
weight *= 0.5;
if (move.flags.contact) {
if (ability === "Tough Claws")
weight *= 1.3;
if (ability === "Unseen Fist")
weight *= 1.1;
if (ability === "Poison Touch")
weight *= TeamGenerator.statusWeight("psn", 1 - 0.7 ** numberOfHits);
}
if (move.flags.bite && ability === "Strong Jaw")
weight *= 1.5;
if (move.flags.bypasssub)
weight *= 1.05;
if (move.flags.pulse && ability === "Mega Launcher")
weight *= 1.5;
if (move.flags.punch && ability === "Iron Fist")
weight *= 1.2;
if (!move.flags.protect)
weight *= 1.05;
if (move.flags.slicing && ability === "Sharpness")
weight *= 1.5;
if (move.flags.sound && ability === "Punk Rock")
weight *= 1.3;
weight *= this.boostWeight(move, movesSoFar, species, ability, level);
const secondaryChance = Math.min((move.secondary?.chance || 100) * (ability === "Serene Grace" ? 2 : 1) / 100, 100);
if (move.secondary || move.secondaries) {
if (ability === "Sheer Force") {
weight *= 1.3;
} else {
const secondaries = move.secondaries || [move.secondary];
for (const secondary of secondaries) {
if (secondary.status) {
weight *= TeamGenerator.statusWeight(secondary.status, secondaryChance, slownessRating);
if (ability === "Poison Puppeteer" && ["psn", "tox"].includes(secondary.status)) {
weight *= TeamGenerator.statusWeight("confusion", secondaryChance);
}
}
if (secondary.volatileStatus) {
weight *= TeamGenerator.statusWeight(secondary.volatileStatus, secondaryChance, slownessRating);
}
}
}
}
if (ability === "Toxic Chain")
weight *= TeamGenerator.statusWeight("tox", 1 - 0.7 ** numberOfHits);
if (move.id === "lashout")
weight *= 1 + 0.2 * slownessRating;
if (move.id === "burningjealousy")
weight *= TeamGenerator.statusWeight("brn", 0.2 * slownessRating);
if (move.id === "alluringvoice")
weight *= TeamGenerator.statusWeight("confusion", 0.2 * slownessRating);
if (move.self?.volatileStatus)
weight *= 0.8;
if ((movesStats.attackTypes[moveType] || 0) > 60)
weight *= 0.3;
if (move.selfdestruct)
weight *= 0.3;
if (move.recoil && ability !== "Rock Head" && ability !== "Magic Guard") {
weight *= 1 - move.recoil[0] / move.recoil[1];
if (ability === "Reckless")
weight *= 1.2;
}
if (move.hasCrashDamage && ability !== "Magic Guard") {
weight *= 1 - 0.75 * (1.2 - accuracy);
if (ability === "Reckless")
weight *= 1.2;
}
if (move.mindBlownRecoil)
weight *= 0.25;
if (move.flags["futuremove"])
weight *= 0.3;
let critRate = move.willCrit ? 4 : move.critRatio || 1;
if (ability === "Super Luck")
critRate++;
if (movesSoFar.some((m) => m.id === "focusenergy")) {
critRate += 2;
weight *= 0.9;
}
if (critRate > 4)
critRate = 4;
weight *= 1 + [0, 1 / 24, 1 / 8, 1 / 2, 1][critRate] * (ability === "Sniper" ? 1 : 0.5);
if (["rapidspin", "mortalspin"].includes(move.id)) {
weight *= 1 + 20 * 0.25 ** teamStats.hazardRemovers;
}
if (move.id === "stoneaxe" && teamStats.hazardSetters.stealthrock)
weight /= 4;
if (move.id === "ceaselessedge" && teamStats.hazardSetters.spikes)
weight /= 2;
if (move.drain) {
const drainedFraction = move.drain[0] / move.drain[1];
weight *= 1 + drainedFraction * 0.5;
}
if (move.id === "terablast" && (species.baseSpecies === "Oricorio" || species.forceTeraType))
weight *= 0.5;
return weight;
}
/**
* @returns The effective type of moves with variable types such as Judgment
*/
static moveType(move, species) {
switch (move.id) {
case "ivycudgel":
case "ragingbull":
if (species.types.length > 1)
return species.types[1];
case "judgment":
case "revelationdance":
return species.types[0];
}
return move.type;
}
static moveIsPhysical(move, species) {
if (move.category === "Physical") {
return !(move.damageCallback || move.damage);
} else if (["terablast", "terastarstorm", "photongeyser", "shellsidearm"].includes(move.id)) {
return species.baseStats.atk > species.baseStats.spa;
} else {
return false;
}
}
static moveIsSpecial(move, species) {
if (move.category === "Special") {
return !(move.damageCallback || move.damage);
} else if (["terablast", "terastarstorm", "photongeyser", "shellsidearm"].includes(move.id)) {
return species.baseStats.atk <= species.baseStats.spa;
} else {
return false;
}
}
/**
* @returns A multiplier to a move weighting based on the status it inflicts.
*/
static statusWeight(status, chance = 1, slownessRating) {
if (chance !== 1)
return 1 + (TeamGenerator.statusWeight(status) - 1) * chance;
switch (status) {
case "brn":
return 2;
case "frz":
return 5;
case "par":
return slownessRating && slownessRating > 0.25 ? 2 + slownessRating : 2;
case "psn":
return 1.75;
case "tox":
return 4;
case "slp":
return 4;
case "confusion":
return 1.5;
case "healblock":
return 1.75;
case "flinch":
return slownessRating ? slownessRating * 3 : 1;
case "saltcure":
return 2;
case "sparklingaria":
return 0.95;
case "syrupbomb":
return 1.5;
}
return 1;
}
/**
* @returns A multiplier to a move weighting based on the boosts it produces for the user.
*/
boostWeight(move, movesSoFar, species, ability, level) {
const physicalIsRelevant = TeamGenerator.moveIsPhysical(move, species) || movesSoFar.some(
(m) => TeamGenerator.moveIsPhysical(m, species) && !m.overrideOffensiveStat && !m.overrideOffensivePokemon
);
const specialIsRelevant = TeamGenerator.moveIsSpecial(move, species) || movesSoFar.some((m) => TeamGenerator.moveIsSpecial(m, species));
const adjustedStats = {
hp: species.baseStats.hp * level / 100 + level,
atk: species.baseStats.atk * level * level / 1e4,
def: species.baseStats.def * level / 100,
spa: species.baseStats.spa * level * level / 1e4,
spd: species.baseStats.spd * level / 100,
spe: species.baseStats.spe * level / 100
};
let weight = 0;
const accuracy = move.accuracy === true ? 100 : move.accuracy / 100;
const secondaryChance = move.secondary && ability !== "Sheer Force" ? Math.min((move.secondary.chance || 100) * (ability === "Serene Grace" ? 2 : 1) / 100, 100) * accuracy : 0;
const abilityMod = ability === "Simple" ? 2 : ability === "Contrary" ? -1 : 1;
const bodyPressMod = movesSoFar.some((m) => m.id === "bodyPress") ? 2 : 1;
const electroBallMod = movesSoFar.some((m) => m.id === "electroball") ? 2 : 1;
for (const { chance, boosts } of [
{ chance: 1, boosts: move.boosts },
{ chance: 1, boosts: move.self?.boosts },
{ chance: 1, boosts: move.selfBoost?.boosts },
{
chance: secondaryChance,
boosts: move.secondary?.self?.boosts
}
]) {
if (!boosts || chance === 0)
continue;
const statusMod = move.category === "Status" ? 1 : 0.5;
if (boosts.atk && physicalIsRelevant)
weight += chance * boosts.atk * abilityMod * 2 * statusMod;
if (boosts.spa && specialIsRelevant)
weight += chance * boosts.spa * abilityMod * 2 * statusMod;
if (boosts.def) {
weight += chance * boosts.def * abilityMod * bodyPressMod * (adjustedStats.def > 60 ? 0.5 : 1) * statusMod;
}
if (boosts.spd)
weight += chance * boosts.spd * abilityMod * (adjustedStats.spd > 60 ? 0.5 : 1) * statusMod;
if (boosts.spe) {
weight += chance * boosts.spe * abilityMod * electroBallMod * (adjustedStats.spe > 95 ? 0.5 : 1) * statusMod;
}
}
return weight >= 0 ? 1 + weight : 1 / (1 - weight);
}
/**
* @returns A weight for a move based on how much it will reduce the opponent's stats.
*/
opponentDebuffWeight(move) {
if (!["allAdjacentFoes", "allAdjacent", "foeSide", "normal"].includes(move.target))
return 1;
let averageNumberOfDebuffs = 0;
for (const { chance, boosts } of [
{ chance: 1, boosts: move.boosts },
{
chance: move.secondary ? (move.secondary.chance || 100) / 100 : 0,
boosts: move.secondary?.boosts
}
]) {
if (!boosts || chance === 0)
continue;
const numBoosts = Object.values(boosts).filter((x) => x < 0).length;
averageNumberOfDebuffs += chance * numBoosts;
}
return 1 + 0.5 * averageNumberOfDebuffs;
}
/**
* @returns A weight for an item.
*/
getItemWeight(item, teamStats, species, moves, ability, level) {
const adjustedStats = {
hp: species.baseStats.hp * level / 100 + level,
atk: species.baseStats.atk * level * level / 1e4,
def: species.baseStats.def * level / 100,
spa: species.baseStats.spa * level * level / 1e4,
spd: species.baseStats.spd * level / 100,
spe: species.baseStats.spe * level / 100
};
const statusImmunities = ["Comatose", "Purifying Salt", "Shields Down", "Natural Cure", "Misty Surge"];
let weight;
switch (item.id) {
case "choiceband":
return moves.every((x) => TeamGenerator.moveIsPhysical(x, species)) ? 50 : 0;
case "choicespecs":
return moves.every((x) => TeamGenerator.moveIsSpecial(x, species)) ? 50 : 0;
case "choicescarf":
if (moves.some((x) => x.category === "Status" || x.secondary?.self?.boosts?.spe))
return 0;
if (adjustedStats.spe > 50 && adjustedStats.spe < 120)
return 50;
return 10;
case "lifeorb":
return moves.filter((x) => x.category !== "Status" && !x.damage && !x.damageCallback).length * 8;
case "focussash":
if (ability === "Sturdy")
return 0;
if (adjustedStats.hp < 65 && adjustedStats.def < 65 && adjustedStats.spd < 65)
return 35;
return 10;
case "heavydutyboots":
switch (this.dex.getEffectiveness("Rock", species)) {
case 1:
return 30;
case 0:
return 10;
}
return 5;
case "assaultvest":
if (moves.some((x) => x.category === "Status"))
return 0;
return 30;
case "scopelens":
const attacks = moves.filter((x) => x.category !== "Status" && !x.damage && !x.damageCallback && !x.willCrit);
if (moves.some((m) => m.id === "focusenergy")) {
if (ability === "Super Luck")
return 0;
return attacks.length * (ability === "Sniper" ? 16 : 12);
} else if (attacks.filter((x) => (x.critRatio || 1) > 1).length || ability === "Super Luck") {
return attacks.reduce((total, x) => {
let ratio = ability === "Super Luck" ? 2 : 1;
if ((x.critRatio || 1) > 1)
ratio++;
return total + [0, 3, 6, 12][ratio] * (ability === "Sniper" ? 4 / 3 : 1);
}, 0);
}
return 0;
case "eviolite":
return species.nfe || species.id === "dipplin" ? 100 : 0;
case "flameorb":
if (species.types.includes("Fire"))
return 0;
if (statusImmunities.includes(ability))
return 0;
if (["Thermal Exchange", "Water Bubble", "Water Veil"].includes(ability))
return 0;
weight = ["Guts", "Flare Boost"].includes(ability) ? 30 : 0;
if (moves.some((m) => m.id === "facade")) {
if (!weight && !moves.some((m) => TeamGenerator.moveIsPhysical(m, species) && m.id !== "facade")) {
weight = 30;
} else {
weight *= 2;
}
}
return weight;
case "toxicorb":
if (species.types.includes("Poison") || species.types.includes("Steel"))
return 0;
if (statusImmunities.includes(ability))
return 0;
if (ability === "Immunity")
return 0;
if (!moves.some((m) => TeamGenerator.moveIsPhysical(m, species) && m.id !== "facade") && !species.types.includes("Fire") && ["Thermal Exchange", "Water Bubble", "Water Veil"].includes(ability))
return 0;
weight = 0;
if (["Poison Heal", "Toxic Boost"].includes("ability"))
weight += 25;
if (moves.some((m) => m.id === "facade"))
weight += 25;
return weight;
case "leftovers":
return moves.some((m) => m.stallingMove) ? 40 : 20;
case "blacksludge":
return species.types.includes("Poison") ? moves.some((m) => m.stallingMove) ? 20 : 10 : 0;
case "sitrusberry":
case "magoberry":
return 20;
case "throatspray":
if (moves.some((m) => m.flags.sound) && moves.some((m) => m.category === "Special"))
return 30;
return 0;
default:
return 0;
}
}
/**
* @returns The level a Pokémon should be.
*/
static getLevel(species) {
if (["Zacian", "Zamazenta"].includes(species.name)) {
species = import_sim.Dex.species.get(species.otherFormes[0]);
} else if (species.baseSpecies === "Squawkabilly") {
if (["Yellow", "White"].includes(species.forme)) {
species = import_sim.Dex.species.get("Squawkabilly-Yellow");
} else {
species = import_sim.Dex.species.get("Squawkabilly");
}
} else if (useBaseSpecies.includes(species.baseSpecies)) {
species = import_sim.Dex.species.get(species.baseSpecies);
}
if (levelOverride[species.id])
return levelOverride[species.id];
switch (species.tier) {
case "AG":
return 60;
case "Uber":
return 70;
case "OU":
case "Unreleased":
return 80;
case "UU":
return 90;
case "LC":
case "NFE":
return 100;
}
return 100;
}
/**
* Picks a choice from `choices` based on the weights in `weights`.
* `weights` must be the same length as `choices`.
*/
weightedRandomPick(choices, weights, options) {
if (!choices.length)
throw new Error(`Can't pick from an empty list`);
if (choices.length !== weights.length)
throw new Error(`Choices and weights must be the same length`);
const totalWeight = weights.reduce((a, b) => a + b, 0);
let randomWeight = this.prng.random(0, totalWeight);
for (let i = 0; i < choices.length; i++) {
randomWeight -= weights[i];
if (randomWeight < 0) {
const choice = choices[i];
if (options?.remove)
choices.splice(i, 1);
return choice;
}
}
if (options?.remove && choices.length)
return choices.pop();
return choices[choices.length - 1];
}
setSeed(seed) {
this.prng.setSeed(seed);
}
}
//# sourceMappingURL=cg-teams.js.map