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