"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 pokemon_exports = {}; __export(pokemon_exports, { Pokemon: () => Pokemon, RESTORATIVE_BERRIES: () => RESTORATIVE_BERRIES }); module.exports = __toCommonJS(pokemon_exports); var import_state = require("./state"); var import_dex = require("./dex"); /** * Simulator Pokemon * Pokemon Showdown - http://pokemonshowdown.com/ * * @license MIT license */ const RESTORATIVE_BERRIES = /* @__PURE__ */ new Set([ "leppaberry", "aguavberry", "enigmaberry", "figyberry", "iapapaberry", "magoberry", "sitrusberry", "wikiberry", "oranberry" ]); class Pokemon { constructor(set, side) { this.getFullDetails = () => { const health = this.getHealth(); let details = this.details; if (this.illusion) { details = this.illusion.getUpdatedDetails( this.battle.ruleTable.has("illusionlevelmod") ? this.illusion.level : this.level ); } if (this.terastallized) details += `, tera:${this.terastallized}`; return { side: health.side, secret: `${details}|${health.secret}`, shared: `${details}|${health.shared}` }; }; this.getHealth = () => { if (!this.hp) return { side: this.side.id, secret: "0 fnt", shared: "0 fnt" }; let secret = `${this.hp}/${this.maxhp}`; let shared; const ratio = this.hp / this.maxhp; if (this.battle.reportExactHP) { shared = secret; } else if (this.battle.reportPercentages || this.battle.gen >= 8) { let percentage = Math.ceil(ratio * 100); if (percentage === 100 && ratio < 1) { percentage = 99; } shared = `${percentage}/100`; } else { const pixels = Math.floor(ratio * 48) || 1; shared = `${pixels}/48`; if (pixels === 9 && ratio > 0.2) { shared += "y"; } else if (pixels === 24 && ratio > 0.5) { shared += "g"; } } if (this.status) { secret += ` ${this.status}`; shared += ` ${this.status}`; } return { side: this.side.id, secret, shared }; }; this.side = side; this.battle = side.battle; this.m = {}; const pokemonScripts = this.battle.format.pokemon || this.battle.dex.data.Scripts.pokemon; if (pokemonScripts) Object.assign(this, pokemonScripts); if (typeof set === "string") set = { name: set }; this.baseSpecies = this.battle.dex.species.get(set.species || set.name); if (!this.baseSpecies.exists) { throw new Error(`Unidentified species: ${this.baseSpecies.name}`); } this.set = set; this.species = this.baseSpecies; if (set.name === set.species || !set.name) { set.name = this.baseSpecies.baseSpecies; } this.speciesState = this.battle.initEffectState({ id: this.species.id }); this.name = set.name.substr(0, 20); this.fullname = `${this.side.id}: ${this.name}`; set.level = this.battle.clampIntRange(set.adjustLevel || set.level || 100, 1, 9999); this.level = set.level; const genders = { M: "M", F: "F", N: "N" }; this.gender = genders[set.gender] || this.species.gender || (this.battle.random(2) ? "F" : "M"); if (this.gender === "N") this.gender = ""; this.happiness = typeof set.happiness === "number" ? this.battle.clampIntRange(set.happiness, 0, 255) : 255; this.pokeball = (0, import_dex.toID)(this.set.pokeball) || "pokeball"; this.dynamaxLevel = typeof set.dynamaxLevel === "number" ? this.battle.clampIntRange(set.dynamaxLevel, 0, 10) : 10; this.gigantamax = this.set.gigantamax || false; this.baseMoveSlots = []; this.moveSlots = []; if (!this.set.moves?.length) { throw new Error(`Set ${this.name} has no moves`); } for (const moveid of this.set.moves) { let move = this.battle.dex.moves.get(moveid); if (!move.id) continue; if (move.id === "hiddenpower" && move.type !== "Normal") { if (!set.hpType) set.hpType = move.type; move = this.battle.dex.moves.get("hiddenpower"); } let basepp = move.noPPBoosts ? move.pp : move.pp * 8 / 5; if (this.battle.gen < 3) basepp = Math.min(61, basepp); this.baseMoveSlots.push({ move: move.name, id: move.id, pp: basepp, maxpp: basepp, target: move.target, disabled: false, disabledSource: "", used: false }); } this.position = 0; this.details = this.getUpdatedDetails(); this.status = ""; this.statusState = this.battle.initEffectState({}); this.volatiles = {}; this.showCure = void 0; if (!this.set.evs) { this.set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 }; } if (!this.set.ivs) { this.set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; } const stats = { hp: 31, atk: 31, def: 31, spe: 31, spa: 31, spd: 31 }; let stat; for (stat in stats) { if (!this.set.evs[stat]) this.set.evs[stat] = 0; if (!this.set.ivs[stat] && this.set.ivs[stat] !== 0) this.set.ivs[stat] = 31; } for (stat in this.set.evs) { this.set.evs[stat] = this.battle.clampIntRange(this.set.evs[stat], 0, 255); } for (stat in this.set.ivs) { this.set.ivs[stat] = this.battle.clampIntRange(this.set.ivs[stat], 0, 31); } if (this.battle.gen && this.battle.gen <= 2) { for (stat in this.set.ivs) { this.set.ivs[stat] &= 30; } } const hpData = this.battle.dex.getHiddenPower(this.set.ivs); this.hpType = set.hpType || hpData.type; this.hpPower = hpData.power; this.baseHpType = this.hpType; this.baseHpPower = this.hpPower; this.baseStoredStats = null; this.storedStats = { atk: 0, def: 0, spa: 0, spd: 0, spe: 0 }; this.boosts = { atk: 0, def: 0, spa: 0, spd: 0, spe: 0, accuracy: 0, evasion: 0 }; this.baseAbility = (0, import_dex.toID)(set.ability); this.ability = this.baseAbility; this.abilityState = this.battle.initEffectState({ id: this.ability, target: this }); this.item = (0, import_dex.toID)(set.item); this.itemState = this.battle.initEffectState({ id: this.item, target: this }); this.lastItem = ""; this.usedItemThisTurn = false; this.ateBerry = false; this.trapped = false; this.maybeTrapped = false; this.maybeDisabled = false; this.illusion = null; this.transformed = false; this.fainted = false; this.faintQueued = false; this.subFainted = null; this.regressionForme = false; this.types = this.baseSpecies.types; this.baseTypes = this.types; this.addedType = ""; this.knownType = true; this.apparentType = this.baseSpecies.types.join("/"); this.teraType = this.set.teraType || this.types[0]; this.switchFlag = false; this.forceSwitchFlag = false; this.skipBeforeSwitchOutEventFlag = false; this.draggedIn = null; this.newlySwitched = false; this.beingCalledBack = false; this.lastMove = null; if (this.battle.gen === 2) this.lastMoveEncore = null; this.lastMoveUsed = null; this.moveThisTurn = ""; this.statsRaisedThisTurn = false; this.statsLoweredThisTurn = false; this.hurtThisTurn = null; this.lastDamage = 0; this.attackedBy = []; this.timesAttacked = 0; this.isActive = false; this.activeTurns = 0; this.activeMoveActions = 0; this.previouslySwitchedIn = 0; this.truantTurn = false; this.swordBoost = false; this.shieldBoost = false; this.syrupTriggered = false; this.stellarBoostedTypes = []; this.isStarted = false; this.duringMove = false; this.weighthg = 1; this.speed = 0; this.canMegaEvo = this.battle.actions.canMegaEvo(this); this.canMegaEvoX = this.battle.actions.canMegaEvoX?.(this); this.canMegaEvoY = this.battle.actions.canMegaEvoY?.(this); this.canUltraBurst = this.battle.actions.canUltraBurst(this); this.canGigantamax = this.baseSpecies.canGigantamax || null; this.canTerastallize = this.battle.actions.canTerastallize(this); if (this.battle.gen === 1) this.modifiedStats = { atk: 0, def: 0, spa: 0, spd: 0, spe: 0 }; this.maxhp = 0; this.baseMaxhp = 0; this.hp = 0; this.clearVolatile(); this.hp = this.maxhp; } toJSON() { return import_state.State.serializePokemon(this); } get moves() { return this.moveSlots.map((moveSlot) => moveSlot.id); } get baseMoves() { return this.baseMoveSlots.map((moveSlot) => moveSlot.id); } getSlot() { const positionOffset = Math.floor(this.side.n / 2) * this.side.active.length; const positionLetter = "abcdef".charAt(this.position + positionOffset); return this.side.id + positionLetter; } toString() { const fullname = this.illusion ? this.illusion.fullname : this.fullname; return this.isActive ? this.getSlot() + fullname.slice(2) : fullname; } getUpdatedDetails(level) { let name = this.species.name; if (["Greninja-Bond", "Rockruff-Dusk"].includes(name)) name = this.species.baseSpecies; if (!level) level = this.level; return name + (level === 100 ? "" : `, L${level}`) + (this.gender === "" ? "" : `, ${this.gender}`) + (this.set.shiny ? ", shiny" : ""); } updateSpeed() { this.speed = this.getActionSpeed(); } calculateStat(statName, boost, modifier, statUser) { statName = (0, import_dex.toID)(statName); if (statName === "hp") throw new Error("Please read `maxhp` directly"); let stat = this.storedStats[statName]; if ("wonderroom" in this.battle.field.pseudoWeather) { if (statName === "def") { stat = this.storedStats["spd"]; } else if (statName === "spd") { stat = this.storedStats["def"]; } } let boosts = {}; const boostName = statName; boosts[boostName] = boost; boosts = this.battle.runEvent("ModifyBoost", statUser || this, null, null, boosts); boost = boosts[boostName]; const boostTable = [1, 1.5, 2, 2.5, 3, 3.5, 4]; if (boost > 6) boost = 6; if (boost < -6) boost = -6; if (boost >= 0) { stat = Math.floor(stat * boostTable[boost]); } else { stat = Math.floor(stat / boostTable[-boost]); } return this.battle.modify(stat, modifier || 1); } getStat(statName, unboosted, unmodified) { statName = (0, import_dex.toID)(statName); if (statName === "hp") throw new Error("Please read `maxhp` directly"); let stat = this.storedStats[statName]; if (unmodified && "wonderroom" in this.battle.field.pseudoWeather) { if (statName === "def") { statName = "spd"; } else if (statName === "spd") { statName = "def"; } } if (!unboosted) { const boosts = this.battle.runEvent("ModifyBoost", this, null, null, { ...this.boosts }); let boost = boosts[statName]; const boostTable = [1, 1.5, 2, 2.5, 3, 3.5, 4]; if (boost > 6) boost = 6; if (boost < -6) boost = -6; if (boost >= 0) { stat = Math.floor(stat * boostTable[boost]); } else { stat = Math.floor(stat / boostTable[-boost]); } } if (!unmodified) { const statTable = { atk: "Atk", def: "Def", spa: "SpA", spd: "SpD", spe: "Spe" }; stat = this.battle.runEvent("Modify" + statTable[statName], this, null, null, stat); } if (statName === "spe" && stat > 1e4 && !this.battle.format.battle?.trunc) stat = 1e4; return stat; } getActionSpeed() { let speed = this.getStat("spe", false, false); const trickRoomCheck = this.battle.ruleTable.has("twisteddimensionmod") ? !this.battle.field.getPseudoWeather("trickroom") : this.battle.field.getPseudoWeather("trickroom"); if (trickRoomCheck) { speed = 1e4 - speed; } return this.battle.trunc(speed, 13); } /** * Gets the Pokemon's best stat. * Moved to its own method due to frequent use of the same code. * Used by Beast Boost, Quark Drive, and Protosynthesis. */ getBestStat(unboosted, unmodified) { let statName = "atk"; let bestStat = 0; const stats = ["atk", "def", "spa", "spd", "spe"]; for (const i of stats) { if (this.getStat(i, unboosted, unmodified) > bestStat) { statName = i; bestStat = this.getStat(i, unboosted, unmodified); } } return statName; } /* Commented out for now until a use for Combat Power is found in Let's Go getCombatPower() { let statSum = 0; let awakeningSum = 0; for (const stat in this.stats) { statSum += this.calculateStat(stat, this.boosts[stat as BoostName]); awakeningSum += this.calculateStat( stat, this.boosts[stat as BoostName]) + this.set.evs[stat]; } const combatPower = Math.floor(Math.floor(statSum * this.level * 6 / 100) + (Math.floor(awakeningSum) * Math.floor((this.level * 4) / 100 + 2))); return this.battle.clampIntRange(combatPower, 0, 10000); } */ getWeight() { const weighthg = this.battle.runEvent("ModifyWeight", this, null, null, this.weighthg); return Math.max(1, weighthg); } getMoveData(move) { move = this.battle.dex.moves.get(move); for (const moveSlot of this.moveSlots) { if (moveSlot.id === move.id) { return moveSlot; } } return null; } getMoveHitData(move) { if (!move.moveHitData) move.moveHitData = {}; const slot = this.getSlot(); return move.moveHitData[slot] || (move.moveHitData[slot] = { crit: false, typeMod: 0, zBrokeProtect: false }); } alliesAndSelf() { return this.side.allies(); } allies() { return this.side.allies().filter((ally) => ally !== this); } adjacentAllies() { return this.side.allies().filter((ally) => this.isAdjacent(ally)); } foes(all) { return this.side.foes(all); } adjacentFoes() { if (this.battle.activePerHalf <= 2) return this.side.foes(); return this.side.foes().filter((foe) => this.isAdjacent(foe)); } isAlly(pokemon) { return !!pokemon && (this.side === pokemon.side || this.side.allySide === pokemon.side); } isAdjacent(pokemon2) { if (this.fainted || pokemon2.fainted) return false; if (this.battle.activePerHalf <= 2) return this !== pokemon2; if (this.side === pokemon2.side) return Math.abs(this.position - pokemon2.position) === 1; return Math.abs(this.position + pokemon2.position + 1 - this.side.active.length) <= 1; } getUndynamaxedHP(amount) { const hp = amount || this.hp; if (this.volatiles["dynamax"]) { return Math.ceil(hp * this.baseMaxhp / this.maxhp); } return hp; } /** Get targets for Dragon Darts */ getSmartTargets(target, move) { const target2 = target.adjacentAllies()[0]; if (!target2 || target2 === this || !target2.hp) { move.smartTarget = false; return [target]; } if (!target.hp) { move.smartTarget = false; return [target2]; } return [target, target2]; } getAtLoc(targetLoc) { let side = this.battle.sides[targetLoc < 0 ? this.side.n % 2 : (this.side.n + 1) % 2]; targetLoc = Math.abs(targetLoc); if (targetLoc > side.active.length) { targetLoc -= side.active.length; side = this.battle.sides[side.n + 2]; } return side.active[targetLoc - 1]; } /** * Returns a relative location: 1-3, positive for foe, and negative for ally. * Use `getAtLoc` to reverse. */ getLocOf(target) { const positionOffset = Math.floor(target.side.n / 2) * target.side.active.length; const position = target.position + positionOffset + 1; const sameHalf = this.side.n % 2 === target.side.n % 2; return sameHalf ? -position : position; } getMoveTargets(move, target) { let targets = []; switch (move.target) { case "all": case "foeSide": case "allySide": case "allyTeam": if (!move.target.startsWith("foe")) { targets.push(...this.alliesAndSelf()); } if (!move.target.startsWith("ally")) { targets.push(...this.foes(true)); } if (targets.length && !targets.includes(target)) { this.battle.retargetLastMove(targets[targets.length - 1]); } break; case "allAdjacent": targets.push(...this.adjacentAllies()); case "allAdjacentFoes": targets.push(...this.adjacentFoes()); if (targets.length && !targets.includes(target)) { this.battle.retargetLastMove(targets[targets.length - 1]); } break; case "allies": targets = this.alliesAndSelf(); break; default: const selectedTarget = target; if (!target || target.fainted && !target.isAlly(this) && this.battle.gameType !== "freeforall") { const possibleTarget = this.battle.getRandomTarget(this, move); if (!possibleTarget) return { targets: [], pressureTargets: [] }; target = possibleTarget; } if (this.battle.activePerHalf > 1 && !move.tracksTarget) { const isCharging = move.flags["charge"] && !this.volatiles["twoturnmove"] && !(move.id.startsWith("solarb") && ["sunnyday", "desolateland"].includes(this.effectiveWeather())) && !(move.id === "electroshot" && ["raindance", "primordialsea"].includes(this.effectiveWeather())) && !(this.hasItem("powerherb") && move.id !== "skydrop"); if (!isCharging) { target = this.battle.priorityEvent("RedirectTarget", this, this, move, target); } } if (move.smartTarget) { targets = this.getSmartTargets(target, move); target = targets[0]; } else { targets.push(target); } if (target.fainted && !move.flags["futuremove"]) { return { targets: [], pressureTargets: [] }; } if (selectedTarget !== target) { this.battle.retargetLastMove(target); } } let pressureTargets = targets; if (move.target === "foeSide") { pressureTargets = []; } if (move.flags["mustpressure"]) { pressureTargets = this.foes(); } return { targets, pressureTargets }; } ignoringAbility() { if (this.battle.gen >= 5 && !this.isActive) return true; if (this.getAbility().flags["notransform"] && this.transformed) return true; if (this.getAbility().flags["cantsuppress"]) return false; if (this.volatiles["gastroacid"]) return true; if (this.hasItem("Ability Shield") || this.ability === "neutralizinggas") return false; for (const pokemon of this.battle.getAllActive()) { if (pokemon.ability === "neutralizinggas" && !pokemon.volatiles["gastroacid"] && !pokemon.transformed && !pokemon.abilityState.ending && !this.volatiles["commanding"]) { return true; } } return false; } ignoringItem() { return !this.getItem().isPrimalOrb && !!(this.itemState.knockedOff || this.battle.gen >= 5 && !this.isActive || !this.getItem().ignoreKlutz && this.hasAbility("klutz") || this.volatiles["embargo"] || this.battle.field.pseudoWeather["magicroom"]); } deductPP(move, amount, target) { const gen = this.battle.gen; move = this.battle.dex.moves.get(move); const ppData = this.getMoveData(move); if (!ppData) return 0; ppData.used = true; if (!ppData.pp && gen > 1) return 0; if (!amount) amount = 1; ppData.pp -= amount; if (ppData.pp < 0 && gen > 1) { amount += ppData.pp; ppData.pp = 0; } return amount; } moveUsed(move, targetLoc) { this.lastMove = move; if (this.battle.gen === 2) this.lastMoveEncore = move; this.lastMoveTargetLoc = targetLoc; this.moveThisTurn = move.id; } gotAttacked(move, damage, source) { const damageNumber = typeof damage === "number" ? damage : 0; move = this.battle.dex.moves.get(move); this.attackedBy.push({ source, damage: damageNumber, move: move.id, thisTurn: true, slot: source.getSlot(), damageValue: damage }); } getLastAttackedBy() { if (this.attackedBy.length === 0) return void 0; return this.attackedBy[this.attackedBy.length - 1]; } getLastDamagedBy(filterOutSameSide) { const damagedBy = this.attackedBy.filter((attacker) => typeof attacker.damageValue === "number" && (filterOutSameSide === void 0 || !this.isAlly(attacker.source))); if (damagedBy.length === 0) return void 0; return damagedBy[damagedBy.length - 1]; } /** * This refers to multi-turn moves like SolarBeam and Outrage and * Sky Drop, which remove all choice (no dynamax, switching, etc). * Don't use it for "soft locks" like Choice Band. */ getLockedMove() { const lockedMove = this.battle.runEvent("LockMove", this); return lockedMove === true ? null : lockedMove; } getMoves(lockedMove, restrictData) { if (lockedMove) { lockedMove = (0, import_dex.toID)(lockedMove); this.trapped = true; if (lockedMove === "recharge") { return [{ move: "Recharge", id: "recharge" }]; } for (const moveSlot of this.moveSlots) { if (moveSlot.id !== lockedMove) continue; return [{ move: moveSlot.move, id: moveSlot.id }]; } return [{ move: this.battle.dex.moves.get(lockedMove).name, id: lockedMove }]; } const moves = []; let hasValidMove = false; for (const moveSlot of this.moveSlots) { let moveName = moveSlot.move; if (moveSlot.id === "hiddenpower") { moveName = `Hidden Power ${this.hpType}`; if (this.battle.gen < 6) moveName += ` ${this.hpPower}`; } else if (moveSlot.id === "return" || moveSlot.id === "frustration") { const basePowerCallback = this.battle.dex.moves.get(moveSlot.id).basePowerCallback; moveName += ` ${basePowerCallback(this)}`; } let target = moveSlot.target; switch (moveSlot.id) { case "curse": if (!this.hasType("Ghost")) { target = this.battle.dex.moves.get("curse").nonGhostTarget; } break; case "pollenpuff": if (this.volatiles["healblock"]) { target = "adjacentFoe"; } break; case "terastarstorm": if (this.species.name === "Terapagos-Stellar") { target = "allAdjacentFoes"; } break; } let disabled = moveSlot.disabled; if (this.volatiles["dynamax"]) { const canCauseStruggle = ["Encore", "Disable", "Taunt", "Assault Vest", "Belch", "Stuff Cheeks"]; disabled = this.maxMoveDisabled(moveSlot.id) || disabled && canCauseStruggle.includes(moveSlot.disabledSource); } else if (moveSlot.pp <= 0 && !this.volatiles["partialtrappinglock"] || disabled && this.side.active.length >= 2 && this.battle.actions.targetTypeChoices(target)) { disabled = true; } if (!disabled) { hasValidMove = true; } else if (disabled === "hidden" && restrictData) { disabled = false; } moves.push({ move: moveName, id: moveSlot.id, pp: moveSlot.pp, maxpp: moveSlot.maxpp, target, disabled }); } return hasValidMove ? moves : []; } /** This should be passed the base move and not the corresponding max move so we can check how much PP is left. */ maxMoveDisabled(baseMove) { baseMove = this.battle.dex.moves.get(baseMove); if (!this.getMoveData(baseMove.id)?.pp) return true; return !!(baseMove.category === "Status" && (this.hasItem("assaultvest") || this.volatiles["taunt"])); } getDynamaxRequest(skipChecks) { if (!skipChecks) { if (!this.side.canDynamaxNow()) return; if (this.species.isMega || this.species.isPrimal || this.species.forme === "Ultra" || this.getItem().zMove || this.canMegaEvo) { return; } if (this.species.cannotDynamax || this.illusion?.species.cannotDynamax) return; } const result = { maxMoves: [] }; let atLeastOne = false; for (const moveSlot of this.moveSlots) { const move = this.battle.dex.moves.get(moveSlot.id); const maxMove = this.battle.actions.getMaxMove(move, this); if (maxMove) { if (this.maxMoveDisabled(move)) { result.maxMoves.push({ move: maxMove.id, target: maxMove.target, disabled: true }); } else { result.maxMoves.push({ move: maxMove.id, target: maxMove.target }); atLeastOne = true; } } } if (!atLeastOne) return; if (this.canGigantamax) result.gigantamax = this.canGigantamax; return result; } getMoveRequestData() { let lockedMove = this.getLockedMove(); const isLastActive = this.isLastActive(); const canSwitchIn = this.battle.canSwitch(this.side) > 0; let moves = this.getMoves(lockedMove, isLastActive); if (!moves.length) { moves = [{ move: "Struggle", id: "struggle", target: "randomNormal", disabled: false }]; lockedMove = "struggle"; } const data = { moves }; if (isLastActive) { if (this.maybeDisabled) { data.maybeDisabled = true; } if (canSwitchIn) { if (this.trapped === true) { data.trapped = true; } else if (this.maybeTrapped) { data.maybeTrapped = true; } } } else if (canSwitchIn) { if (this.trapped) data.trapped = true; } if (!lockedMove) { if (this.canMegaEvo) data.canMegaEvo = true; if (this.canMegaEvoX) data.canMegaEvoX = true; if (this.canMegaEvoY) data.canMegaEvoY = true; if (this.canUltraBurst) data.canUltraBurst = true; const canZMove = this.battle.actions.canZMove(this); if (canZMove) data.canZMove = canZMove; if (this.getDynamaxRequest()) data.canDynamax = true; if (data.canDynamax || this.volatiles["dynamax"]) data.maxMoves = this.getDynamaxRequest(true); if (this.canTerastallize) data.canTerastallize = this.canTerastallize; } return data; } getSwitchRequestData(forAlly) { const entry = { ident: this.fullname, details: this.details, condition: this.getHealth().secret, active: this.position < this.side.active.length, stats: { atk: this.baseStoredStats["atk"], def: this.baseStoredStats["def"], spa: this.baseStoredStats["spa"], spd: this.baseStoredStats["spd"], spe: this.baseStoredStats["spe"] }, moves: this[forAlly ? "baseMoves" : "moves"].map((move) => { if (move === "hiddenpower") { return `${move}${(0, import_dex.toID)(this.hpType)}${this.battle.gen < 6 ? "" : this.hpPower}`; } if (move === "frustration" || move === "return") { const basePowerCallback = this.battle.dex.moves.get(move).basePowerCallback; return `${move}${basePowerCallback(this)}`; } return move; }), baseAbility: this.baseAbility, item: this.item, pokeball: this.pokeball }; if (this.battle.gen > 6) entry.ability = this.ability; if (this.battle.gen >= 9) { entry.commanding = !!this.volatiles["commanding"] && !this.fainted; entry.reviving = this.isActive && !!this.side.slotConditions[this.position]["revivalblessing"]; } if (this.battle.gen === 9) { entry.teraType = this.teraType; entry.terastallized = this.terastallized || ""; } return entry; } isLastActive() { if (!this.isActive) return false; const allyActive = this.side.active; for (let i = this.position + 1; i < allyActive.length; i++) { if (allyActive[i] && !allyActive[i].fainted) return false; } return true; } positiveBoosts() { let boosts = 0; let boost; for (boost in this.boosts) { if (this.boosts[boost] > 0) boosts += this.boosts[boost]; } return boosts; } getCappedBoost(boosts) { const cappedBoost = {}; let boostName; for (boostName in boosts) { const boost = boosts[boostName]; if (!boost) continue; cappedBoost[boostName] = this.battle.clampIntRange(this.boosts[boostName] + boost, -6, 6) - this.boosts[boostName]; } return cappedBoost; } boostBy(boosts) { boosts = this.getCappedBoost(boosts); let delta = 0; let boostName; for (boostName in boosts) { delta = boosts[boostName]; this.boosts[boostName] += delta; } return delta; } clearBoosts() { let boostName; for (boostName in this.boosts) { this.boosts[boostName] = 0; } } setBoost(boosts) { let boostName; for (boostName in boosts) { this.boosts[boostName] = boosts[boostName]; } } copyVolatileFrom(pokemon, switchCause) { this.clearVolatile(); if (switchCause !== "shedtail") this.boosts = pokemon.boosts; for (const i in pokemon.volatiles) { if (switchCause === "shedtail" && i !== "substitute") continue; if (this.battle.dex.conditions.getByID(i).noCopy) continue; this.volatiles[i] = this.battle.initEffectState({ ...pokemon.volatiles[i] }); if (this.volatiles[i].linkedPokemon) { delete pokemon.volatiles[i].linkedPokemon; delete pokemon.volatiles[i].linkedStatus; for (const linkedPoke of this.volatiles[i].linkedPokemon) { const linkedPokeLinks = linkedPoke.volatiles[this.volatiles[i].linkedStatus].linkedPokemon; linkedPokeLinks[linkedPokeLinks.indexOf(pokemon)] = this; } } } pokemon.clearVolatile(); for (const i in this.volatiles) { const volatile = this.getVolatile(i); this.battle.singleEvent("Copy", volatile, this.volatiles[i], this); } } transformInto(pokemon, effect) { const species = pokemon.species; if (pokemon.fainted || this.illusion || pokemon.illusion || pokemon.volatiles["substitute"] && this.battle.gen >= 5 || pokemon.transformed && this.battle.gen >= 2 || this.transformed && this.battle.gen >= 5 || species.name === "Eternatus-Eternamax" || ["Ogerpon", "Terapagos"].includes(species.baseSpecies) && (this.terastallized || pokemon.terastallized) || this.terastallized === "Stellar") { return false; } if (this.battle.dex.currentMod === "gen1stadium" && (species.name === "Ditto" || this.species.name === "Ditto" && pokemon.moves.includes("transform"))) { return false; } if (!this.setSpecies(species, effect, true)) return false; this.transformed = true; this.weighthg = pokemon.weighthg; const types = pokemon.getTypes(true, true); this.setType(pokemon.volatiles["roost"] ? pokemon.volatiles["roost"].typeWas : types, true); this.addedType = pokemon.addedType; this.knownType = this.isAlly(pokemon) && pokemon.knownType; this.apparentType = pokemon.apparentType; let statName; for (statName in this.storedStats) { this.storedStats[statName] = pokemon.storedStats[statName]; if (this.modifiedStats) this.modifiedStats[statName] = pokemon.modifiedStats[statName]; } this.moveSlots = []; this.hpType = this.battle.gen >= 5 ? this.hpType : pokemon.hpType; this.hpPower = this.battle.gen >= 5 ? this.hpPower : pokemon.hpPower; this.timesAttacked = pokemon.timesAttacked; for (const moveSlot of pokemon.moveSlots) { let moveName = moveSlot.move; if (moveSlot.id === "hiddenpower") { moveName = "Hidden Power " + this.hpType; } this.moveSlots.push({ move: moveName, id: moveSlot.id, pp: moveSlot.maxpp === 1 ? 1 : 5, maxpp: this.battle.gen >= 5 ? moveSlot.maxpp === 1 ? 1 : 5 : moveSlot.maxpp, target: moveSlot.target, disabled: false, used: false, virtual: true }); } let boostName; for (boostName in pokemon.boosts) { this.boosts[boostName] = pokemon.boosts[boostName]; } if (this.battle.gen >= 6) { const volatilesToCopy = ["dragoncheer", "focusenergy", "gmaxchistrike", "laserfocus"]; for (const volatile of volatilesToCopy) this.removeVolatile(volatile); for (const volatile of volatilesToCopy) { if (pokemon.volatiles[volatile]) { this.addVolatile(volatile); if (volatile === "gmaxchistrike") this.volatiles[volatile].layers = pokemon.volatiles[volatile].layers; if (volatile === "dragoncheer") this.volatiles[volatile].hasDragonType = pokemon.volatiles[volatile].hasDragonType; } } } if (effect) { this.battle.add("-transform", this, pokemon, "[from] " + effect.fullname); } else { this.battle.add("-transform", this, pokemon); } if (this.terastallized) { this.knownType = true; this.apparentType = this.terastallized; } if (this.battle.gen > 2) this.setAbility(pokemon.ability, this, true, true); if (this.battle.gen === 4) { if (this.species.num === 487) { if (this.species.name === "Giratina" && this.item === "griseousorb") { this.formeChange("Giratina-Origin"); } else if (this.species.name === "Giratina-Origin" && this.item !== "griseousorb") { this.formeChange("Giratina"); } } if (this.species.num === 493) { const item = this.getItem(); const targetForme = item?.onPlate ? "Arceus-" + item.onPlate : "Arceus"; if (this.species.name !== targetForme) { this.formeChange(targetForme); } } } if (["Ogerpon", "Terapagos"].includes(this.species.baseSpecies) && this.canTerastallize) this.canTerastallize = false; return true; } /** * Changes this Pokemon's species to the given speciesId (or species). * This function only handles changes to stats and type. * Use formeChange to handle changes to ability and sending client messages. */ setSpecies(rawSpecies, source = this.battle.effect, isTransform = false) { const species = this.battle.runEvent("ModifySpecies", this, null, source, rawSpecies); if (!species) return null; this.species = species; this.setType(species.types, true); this.apparentType = rawSpecies.types.join("/"); this.addedType = species.addedType || ""; this.knownType = true; this.weighthg = species.weighthg; const stats = this.battle.spreadModify(this.species.baseStats, this.set); if (this.species.maxHP) stats.hp = this.species.maxHP; if (!this.maxhp) { this.baseMaxhp = stats.hp; this.maxhp = stats.hp; this.hp = stats.hp; } if (!isTransform) this.baseStoredStats = stats; let statName; for (statName in this.storedStats) { this.storedStats[statName] = stats[statName]; if (this.modifiedStats) this.modifiedStats[statName] = stats[statName]; } if (this.battle.gen <= 1) { if (this.status === "par") this.modifyStat("spe", 0.25); if (this.status === "brn") this.modifyStat("atk", 0.5); } this.speed = this.storedStats.spe; return species; } /** * Changes this Pokemon's forme to match the given speciesId (or species). * This function handles all changes to stats, ability, type, species, etc. * as well as sending all relevant messages sent to the client. */ formeChange(speciesId, source = this.battle.effect, isPermanent, abilitySlot = "0", message) { const rawSpecies = this.battle.dex.species.get(speciesId); const species = this.setSpecies(rawSpecies, source); if (!species) return false; if (this.battle.gen <= 2) return true; const apparentSpecies = this.illusion ? this.illusion.species.name : species.baseSpecies; if (isPermanent) { if (!this.transformed) this.regressionForme = true; this.baseSpecies = rawSpecies; this.details = this.getUpdatedDetails(); let details = (this.illusion || this).details; if (this.terastallized) details += `, tera:${this.terastallized}`; this.battle.add("detailschange", this, details); if (!source) { } else if (source.effectType === "Item") { this.canTerastallize = null; if (source.zMove) { this.battle.add("-burst", this, apparentSpecies, species.requiredItem); this.moveThisTurnResult = true; } else if (source.isPrimalOrb) { if (this.illusion) { this.ability = ""; this.battle.add("-primal", this.illusion, species.requiredItem); } else { this.battle.add("-primal", this, species.requiredItem); } } else { this.battle.add("-mega", this, apparentSpecies, species.requiredItem); this.moveThisTurnResult = true; } } else if (source.effectType === "Status") { this.battle.add("-formechange", this, species.name, message); } } else { if (source?.effectType === "Ability") { this.battle.add("-formechange", this, species.name, message, `[from] ability: ${source.name}`); } else { this.battle.add("-formechange", this, this.illusion ? this.illusion.species.name : species.name, message); } } if (isPermanent && (!source || !["disguise", "iceface"].includes(source.id))) { if (this.illusion) { this.ability = ""; } const ability = species.abilities[abilitySlot] || species.abilities["0"]; if (source || !this.getAbility().flags["cantsuppress"]) this.setAbility(ability, null, true); this.baseAbility = (0, import_dex.toID)(ability); } if (this.terastallized) { this.knownType = true; this.apparentType = this.terastallized; } return true; } clearVolatile(includeSwitchFlags = true) { this.boosts = { atk: 0, def: 0, spa: 0, spd: 0, spe: 0, accuracy: 0, evasion: 0 }; if (this.battle.gen === 1 && this.baseMoves.includes("mimic") && !this.transformed) { const moveslot = this.baseMoves.indexOf("mimic"); const mimicPP = this.moveSlots[moveslot] ? this.moveSlots[moveslot].pp : 16; this.moveSlots = this.baseMoveSlots.slice(); this.moveSlots[moveslot].pp = mimicPP; } else { this.moveSlots = this.baseMoveSlots.slice(); } this.transformed = false; this.ability = this.baseAbility; this.hpType = this.baseHpType; this.hpPower = this.baseHpPower; if (this.canTerastallize === false) this.canTerastallize = this.teraType; for (const i in this.volatiles) { if (this.volatiles[i].linkedStatus) { this.removeLinkedVolatiles(this.volatiles[i].linkedStatus, this.volatiles[i].linkedPokemon); } } if (this.species.name === "Eternatus-Eternamax" && this.volatiles["dynamax"]) { this.volatiles = { dynamax: this.volatiles["dynamax"] }; } else { this.volatiles = {}; } if (includeSwitchFlags) { this.switchFlag = false; this.forceSwitchFlag = false; } this.lastMove = null; if (this.battle.gen === 2) this.lastMoveEncore = null; this.lastMoveUsed = null; this.moveThisTurn = ""; this.moveLastTurnResult = void 0; this.moveThisTurnResult = void 0; this.lastDamage = 0; this.attackedBy = []; this.hurtThisTurn = null; this.newlySwitched = true; this.beingCalledBack = false; this.volatileStaleness = void 0; delete this.abilityState.started; delete this.itemState.started; this.setSpecies(this.baseSpecies); } hasType(type) { const thisTypes = this.getTypes(); if (typeof type === "string") { return thisTypes.includes(type); } for (const typeName of type) { if (thisTypes.includes(typeName)) return true; } return false; } /** * This function only puts the pokemon in the faint queue; * actually setting of this.fainted comes later when the * faint queue is resolved. * * Returns the amount of damage actually dealt */ faint(source = null, effect = null) { if (this.fainted || this.faintQueued) return 0; const d = this.hp; this.hp = 0; this.switchFlag = false; this.faintQueued = true; this.battle.faintQueue.push({ target: this, source, effect }); return d; } damage(d, source = null, effect = null) { if (!this.hp || isNaN(d) || d <= 0) return 0; if (d < 1 && d > 0) d = 1; d = this.battle.trunc(d); this.hp -= d; if (this.hp <= 0) { d += this.hp; this.faint(source, effect); } return d; } tryTrap(isHidden = false) { if (!this.runStatusImmunity("trapped")) return false; if (this.trapped && isHidden) return true; this.trapped = isHidden ? "hidden" : true; return true; } hasMove(moveid) { moveid = (0, import_dex.toID)(moveid); if (moveid.substr(0, 11) === "hiddenpower") moveid = "hiddenpower"; for (const moveSlot of this.moveSlots) { if (moveid === moveSlot.id) { return moveid; } } return false; } disableMove(moveid, isHidden, sourceEffect) { if (!sourceEffect && this.battle.event) { sourceEffect = this.battle.effect; } moveid = (0, import_dex.toID)(moveid); for (const moveSlot of this.moveSlots) { if (moveSlot.id === moveid && moveSlot.disabled !== true) { moveSlot.disabled = isHidden || true; moveSlot.disabledSource = sourceEffect?.name || moveSlot.move; } } } /** Returns the amount of damage actually healed */ heal(d, source = null, effect = null) { if (!this.hp) return false; d = this.battle.trunc(d); if (isNaN(d)) return false; if (d <= 0) return false; if (this.hp >= this.maxhp) return false; this.hp += d; if (this.hp > this.maxhp) { d -= this.hp - this.maxhp; this.hp = this.maxhp; } return d; } /** Sets HP, returns delta */ sethp(d) { if (!this.hp) return 0; d = this.battle.trunc(d); if (isNaN(d)) return; if (d < 1) d = 1; d -= this.hp; this.hp += d; if (this.hp > this.maxhp) { d -= this.hp - this.maxhp; this.hp = this.maxhp; } return d; } trySetStatus(status, source = null, sourceEffect = null) { return this.setStatus(this.status || status, source, sourceEffect); } /** Unlike clearStatus, gives cure message */ cureStatus(silent = false) { if (!this.hp || !this.status) return false; this.battle.add("-curestatus", this, this.status, silent ? "[silent]" : "[msg]"); if (this.status === "slp" && this.removeVolatile("nightmare")) { this.battle.add("-end", this, "Nightmare", "[silent]"); } this.setStatus(""); return true; } setStatus(status, source = null, sourceEffect = null, ignoreImmunities = false) { if (!this.hp) return false; status = this.battle.dex.conditions.get(status); if (this.battle.event) { if (!source) source = this.battle.event.source; if (!sourceEffect) sourceEffect = this.battle.effect; } if (!source) source = this; if (this.status === status.id) { if (sourceEffect?.status === this.status) { this.battle.add("-fail", this, this.status); } else if (sourceEffect?.status) { this.battle.add("-fail", source); this.battle.attrLastMove("[still]"); } return false; } if (!ignoreImmunities && status.id && !(source?.hasAbility("corrosion") && ["tox", "psn"].includes(status.id))) { if (!this.runStatusImmunity(status.id === "tox" ? "psn" : status.id)) { this.battle.debug("immune to status"); if (sourceEffect?.status) { this.battle.add("-immune", this); } return false; } } const prevStatus = this.status; const prevStatusState = this.statusState; if (status.id) { const result = this.battle.runEvent("SetStatus", this, source, sourceEffect, status); if (!result) { this.battle.debug("set status [" + status.id + "] interrupted"); return result; } } this.status = status.id; this.statusState = this.battle.initEffectState({ id: status.id, target: this }); if (source) this.statusState.source = source; if (status.duration) this.statusState.duration = status.duration; if (status.durationCallback) { this.statusState.duration = status.durationCallback.call(this.battle, this, source, sourceEffect); } if (status.id && !this.battle.singleEvent("Start", status, this.statusState, this, source, sourceEffect)) { this.battle.debug("status start [" + status.id + "] interrupted"); this.status = prevStatus; this.statusState = prevStatusState; return false; } if (status.id && !this.battle.runEvent("AfterSetStatus", this, source, sourceEffect, status)) { return false; } return true; } /** * Unlike cureStatus, does not give cure message */ clearStatus() { if (!this.hp || !this.status) return false; if (this.status === "slp" && this.removeVolatile("nightmare")) { this.battle.add("-end", this, "Nightmare", "[silent]"); } this.setStatus(""); return true; } getStatus() { return this.battle.dex.conditions.getByID(this.status); } eatItem(force, source, sourceEffect) { if (!this.item || this.itemState.knockedOff) return false; if (!this.hp && this.item !== "jabocaberry" && this.item !== "rowapberry" || !this.isActive) return false; if (!sourceEffect && this.battle.effect) sourceEffect = this.battle.effect; if (!source && this.battle.event?.target) source = this.battle.event.target; const item = this.getItem(); if (sourceEffect?.effectType === "Item" && this.item !== sourceEffect.id && source === this) { return false; } if (this.battle.runEvent("UseItem", this, null, null, item) && (force || this.battle.runEvent("TryEatItem", this, null, null, item))) { this.battle.add("-enditem", this, item, "[eat]"); this.battle.singleEvent("Eat", item, this.itemState, this, source, sourceEffect); this.battle.runEvent("EatItem", this, null, null, item); if (RESTORATIVE_BERRIES.has(item.id)) { switch (this.pendingStaleness) { case "internal": if (this.staleness !== "external") this.staleness = "internal"; break; case "external": this.staleness = "external"; break; } this.pendingStaleness = void 0; } this.lastItem = this.item; this.item = ""; this.battle.clearEffectState(this.itemState); this.usedItemThisTurn = true; this.ateBerry = true; this.battle.runEvent("AfterUseItem", this, null, null, item); return true; } return false; } useItem(source, sourceEffect) { if (!this.hp && !this.getItem().isGem || !this.isActive) return false; if (!this.item || this.itemState.knockedOff) return false; if (!sourceEffect && this.battle.effect) sourceEffect = this.battle.effect; if (!source && this.battle.event?.target) source = this.battle.event.target; const item = this.getItem(); if (sourceEffect?.effectType === "Item" && this.item !== sourceEffect.id && source === this) { return false; } if (this.battle.runEvent("UseItem", this, null, null, item)) { switch (item.id) { case "redcard": this.battle.add("-enditem", this, item, `[of] ${source}`); break; default: if (item.isGem) { this.battle.add("-enditem", this, item, "[from] gem"); } else { this.battle.add("-enditem", this, item); } break; } if (item.boosts) { this.battle.boost(item.boosts, this, source, item); } this.battle.singleEvent("Use", item, this.itemState, this, source, sourceEffect); this.lastItem = this.item; this.item = ""; this.battle.clearEffectState(this.itemState); this.usedItemThisTurn = true; this.battle.runEvent("AfterUseItem", this, null, null, item); return true; } return false; } takeItem(source) { if (!this.isActive) return false; if (!this.item || this.itemState.knockedOff) return false; if (!source) source = this; if (this.battle.gen === 4) { if ((0, import_dex.toID)(this.ability) === "multitype") return false; if ((0, import_dex.toID)(source.ability) === "multitype") return false; } const item = this.getItem(); if (this.battle.runEvent("TakeItem", this, source, null, item)) { this.item = ""; const oldItemState = this.itemState; this.battle.clearEffectState(this.itemState); this.pendingStaleness = void 0; this.battle.singleEvent("End", item, oldItemState, this); this.battle.runEvent("AfterTakeItem", this, null, null, item); return item; } return false; } setItem(item, source, effect) { if (!this.hp || !this.isActive) return false; if (this.itemState.knockedOff) return false; if (typeof item === "string") item = this.battle.dex.items.get(item); const effectid = this.battle.effect ? this.battle.effect.id : ""; if (RESTORATIVE_BERRIES.has("leppaberry")) { const inflicted = ["trick", "switcheroo"].includes(effectid); const external = inflicted && source && !source.isAlly(this); this.pendingStaleness = external ? "external" : "internal"; } else { this.pendingStaleness = void 0; } const oldItem = this.getItem(); const oldItemState = this.itemState; this.item = item.id; this.itemState = this.battle.initEffectState({ id: item.id, target: this }); if (oldItem.exists) this.battle.singleEvent("End", oldItem, oldItemState, this); if (item.id) { this.battle.singleEvent("Start", item, this.itemState, this, source, effect); } return true; } getItem() { return this.battle.dex.items.getByID(this.item); } hasItem(item) { if (Array.isArray(item)) { if (!item.map(import_dex.toID).includes(this.item)) return false; } else { if ((0, import_dex.toID)(item) !== this.item) return false; } return !this.ignoringItem(); } clearItem() { return this.setItem(""); } setAbility(ability, source, isFromFormeChange = false, isTransform = false) { if (!this.hp) return false; if (typeof ability === "string") ability = this.battle.dex.abilities.get(ability); const oldAbility = this.ability; if (!isFromFormeChange) { if (ability.flags["cantsuppress"] || this.getAbility().flags["cantsuppress"]) return false; } if (!isFromFormeChange && !isTransform) { const setAbilityEvent = this.battle.runEvent("SetAbility", this, source, this.battle.effect, ability); if (!setAbilityEvent) return setAbilityEvent; } this.battle.singleEvent("End", this.battle.dex.abilities.get(oldAbility), this.abilityState, this, source); if (this.battle.effect && this.battle.effect.effectType === "Move" && !isFromFormeChange) { this.battle.add( "-endability", this, this.battle.dex.abilities.get(oldAbility), `[from] move: ${this.battle.dex.moves.get(this.battle.effect.id)}` ); } this.ability = ability.id; this.abilityState = this.battle.initEffectState({ id: ability.id, target: this }); if (ability.id && this.battle.gen > 3 && (!isTransform || oldAbility !== ability.id || this.battle.gen <= 4)) { this.battle.singleEvent("Start", ability, this.abilityState, this, source); } return oldAbility; } getAbility() { return this.battle.dex.abilities.getByID(this.ability); } hasAbility(ability) { if (Array.isArray(ability)) { if (!ability.map(import_dex.toID).includes(this.ability)) return false; } else { if ((0, import_dex.toID)(ability) !== this.ability) return false; } return !this.ignoringAbility(); } clearAbility() { return this.setAbility(""); } getNature() { return this.battle.dex.natures.get(this.set.nature); } addVolatile(status, source = null, sourceEffect = null, linkedStatus = null) { let result; status = this.battle.dex.conditions.get(status); if (!this.hp && !status.affectsFainted) return false; if (linkedStatus && source && !source.hp) return false; if (this.battle.event) { if (!source) source = this.battle.event.source; if (!sourceEffect) sourceEffect = this.battle.effect; } if (!source) source = this; if (this.volatiles[status.id]) { if (!status.onRestart) return false; return this.battle.singleEvent("Restart", status, this.volatiles[status.id], this, source, sourceEffect); } if (!this.runStatusImmunity(status.id)) { this.battle.debug("immune to volatile status"); if (sourceEffect?.status) { this.battle.add("-immune", this); } return false; } result = this.battle.runEvent("TryAddVolatile", this, source, sourceEffect, status); if (!result) { this.battle.debug("add volatile [" + status.id + "] interrupted"); return result; } this.volatiles[status.id] = this.battle.initEffectState({ id: status.id, name: status.name, target: this }); if (source) { this.volatiles[status.id].source = source; this.volatiles[status.id].sourceSlot = source.getSlot(); } if (sourceEffect) this.volatiles[status.id].sourceEffect = sourceEffect; if (status.duration) this.volatiles[status.id].duration = status.duration; if (status.durationCallback) { this.volatiles[status.id].duration = status.durationCallback.call(this.battle, this, source, sourceEffect); } result = this.battle.singleEvent("Start", status, this.volatiles[status.id], this, source, sourceEffect); if (!result) { delete this.volatiles[status.id]; return result; } if (linkedStatus && source) { if (!source.volatiles[linkedStatus.toString()]) { source.addVolatile(linkedStatus, this, sourceEffect); source.volatiles[linkedStatus.toString()].linkedPokemon = [this]; source.volatiles[linkedStatus.toString()].linkedStatus = status; } else { source.volatiles[linkedStatus.toString()].linkedPokemon.push(this); } this.volatiles[status.toString()].linkedPokemon = [source]; this.volatiles[status.toString()].linkedStatus = linkedStatus; } return true; } getVolatile(status) { status = this.battle.dex.conditions.get(status); if (!this.volatiles[status.id]) return null; return status; } removeVolatile(status) { if (!this.hp) return false; status = this.battle.dex.conditions.get(status); if (!this.volatiles[status.id]) return false; const linkedPokemon = this.volatiles[status.id].linkedPokemon; const linkedStatus = this.volatiles[status.id].linkedStatus; this.battle.singleEvent("End", status, this.volatiles[status.id], this); delete this.volatiles[status.id]; if (linkedPokemon) { this.removeLinkedVolatiles(linkedStatus, linkedPokemon); } return true; } removeLinkedVolatiles(linkedStatus, linkedPokemon) { linkedStatus = linkedStatus.toString(); for (const linkedPoke of linkedPokemon) { const volatileData = linkedPoke.volatiles[linkedStatus]; if (!volatileData) continue; volatileData.linkedPokemon.splice(volatileData.linkedPokemon.indexOf(this), 1); if (volatileData.linkedPokemon.length === 0) { linkedPoke.removeVolatile(linkedStatus); } } } /** * Sets a type (except on Arceus, who resists type changes) */ setType(newType, enforce = false) { if (!enforce) { if (typeof newType === "string" ? newType === "Stellar" : newType.includes("Stellar")) return false; if (this.battle.gen >= 5 && (this.species.num === 493 || this.species.num === 773) || this.battle.gen === 4 && this.hasAbility("multitype")) { return false; } if (this.terastallized) return false; } if (!newType) throw new Error("Must pass type to setType"); this.types = typeof newType === "string" ? [newType] : newType; this.addedType = ""; this.knownType = true; this.apparentType = this.types.join("/"); return true; } /** Removes any types added previously and adds another one. */ addType(newType) { if (this.terastallized) return false; this.addedType = newType; return true; } getTypes(excludeAdded, preterastallized) { if (!preterastallized && this.terastallized && this.terastallized !== "Stellar") { return [this.terastallized]; } const types = this.battle.runEvent("Type", this, null, null, this.types); if (!types.length) types.push(this.battle.gen >= 5 ? "Normal" : "???"); if (!excludeAdded && this.addedType) return types.concat(this.addedType); return types; } isGrounded(negateImmunity = false) { if ("gravity" in this.battle.field.pseudoWeather) return true; if ("ingrain" in this.volatiles && this.battle.gen >= 4) return true; if ("smackdown" in this.volatiles) return true; const item = this.ignoringItem() ? "" : this.item; if (item === "ironball") return true; if (!negateImmunity && this.hasType("Flying") && !(this.hasType("???") && "roost" in this.volatiles)) return false; if (this.hasAbility("levitate") && !this.battle.suppressingAbility(this)) return null; if ("magnetrise" in this.volatiles) return false; if ("telekinesis" in this.volatiles) return false; return item !== "airballoon"; } isSemiInvulnerable() { return this.volatiles["fly"] || this.volatiles["bounce"] || this.volatiles["dive"] || this.volatiles["dig"] || this.volatiles["phantomforce"] || this.volatiles["shadowforce"] || this.isSkyDropped(); } isSkyDropped() { if (this.volatiles["skydrop"]) return true; for (const foeActive of this.side.foe.active) { if (foeActive.volatiles["skydrop"] && foeActive.volatiles["skydrop"].source === this) { return true; } } return false; } /** Specifically: is protected against a single-target damaging move */ isProtected() { return !!(this.volatiles["protect"] || this.volatiles["detect"] || this.volatiles["maxguard"] || this.volatiles["kingsshield"] || this.volatiles["spikyshield"] || this.volatiles["banefulbunker"] || this.volatiles["obstruct"] || this.volatiles["silktrap"] || this.volatiles["burningbulwark"]); } /** * Like Field.effectiveWeather(), but ignores sun and rain if * the Utility Umbrella is active for the Pokemon. */ effectiveWeather() { const weather = this.battle.field.effectiveWeather(); switch (weather) { case "sunnyday": case "raindance": case "desolateland": case "primordialsea": if (this.hasItem("utilityumbrella")) return ""; } return weather; } runEffectiveness(move) { let totalTypeMod = 0; if (this.terastallized && move.type === "Stellar") { totalTypeMod = 1; } else { for (const type of this.getTypes()) { let typeMod = this.battle.dex.getEffectiveness(move, type); typeMod = this.battle.singleEvent("Effectiveness", move, null, this, type, move, typeMod); totalTypeMod += this.battle.runEvent("Effectiveness", this, type, move, typeMod); } } if (this.species.name === "Terapagos-Terastal" && this.hasAbility("Tera Shell") && !this.battle.suppressingAbility(this)) { if (this.abilityState.resisted) return -1; if (move.category === "Status" || move.id === "struggle" || !this.runImmunity(move.type) || totalTypeMod < 0 || this.hp < this.maxhp) { return totalTypeMod; } this.battle.add("-activate", this, "ability: Tera Shell"); this.abilityState.resisted = true; return -1; } return totalTypeMod; } /** false = immune, true = not immune */ runImmunity(type, message) { if (!type || type === "???") return true; if (!this.battle.dex.types.isName(type)) { throw new Error("Use runStatusImmunity for " + type); } if (this.fainted) return false; const negateImmunity = !this.battle.runEvent("NegateImmunity", this, type); const notImmune = type === "Ground" ? this.isGrounded(negateImmunity) : negateImmunity || this.battle.dex.getImmunity(type, this); if (notImmune) return true; if (!message) return false; if (notImmune === null) { this.battle.add("-immune", this, "[from] ability: Levitate"); } else { this.battle.add("-immune", this); } return false; } runStatusImmunity(type, message) { if (this.fainted) return false; if (!type) return true; if (!this.battle.dex.getImmunity(type, this)) { this.battle.debug("natural status immunity"); if (message) { this.battle.add("-immune", this); } return false; } const immunity = this.battle.runEvent("Immunity", this, null, null, type); if (!immunity) { this.battle.debug("artificial status immunity"); if (message && immunity !== null) { this.battle.add("-immune", this); } return false; } return true; } destroy() { this.battle = null; this.side = null; } } //# sourceMappingURL=pokemon.js.map