|
"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"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
const RESTORATIVE_BERRIES = 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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
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]; |
|
} |
|
|
|
|
|
|
|
|
|
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]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 : []; |
|
} |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
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); |
|
} |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
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); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
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"]); |
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
|