1654 lines
64 KiB
JavaScript
1654 lines
64 KiB
JavaScript
"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 battle_actions_exports = {};
|
|
__export(battle_actions_exports, {
|
|
BattleActions: () => BattleActions
|
|
});
|
|
module.exports = __toCommonJS(battle_actions_exports);
|
|
var import_dex = require("./dex");
|
|
const CHOOSABLE_TARGETS = /* @__PURE__ */ new Set(["normal", "any", "adjacentAlly", "adjacentAllyOrSelf", "adjacentFoe"]);
|
|
class BattleActions {
|
|
constructor(battle) {
|
|
this.MAX_MOVES = {
|
|
Flying: "Max Airstream",
|
|
Dark: "Max Darkness",
|
|
Fire: "Max Flare",
|
|
Bug: "Max Flutterby",
|
|
Water: "Max Geyser",
|
|
Status: "Max Guard",
|
|
Ice: "Max Hailstorm",
|
|
Fighting: "Max Knuckle",
|
|
Electric: "Max Lightning",
|
|
Psychic: "Max Mindstorm",
|
|
Poison: "Max Ooze",
|
|
Grass: "Max Overgrowth",
|
|
Ghost: "Max Phantasm",
|
|
Ground: "Max Quake",
|
|
Rock: "Max Rockfall",
|
|
Fairy: "Max Starfall",
|
|
Steel: "Max Steelspike",
|
|
Normal: "Max Strike",
|
|
Dragon: "Max Wyrmwind"
|
|
};
|
|
this.Z_MOVES = {
|
|
Poison: "Acid Downpour",
|
|
Fighting: "All-Out Pummeling",
|
|
Dark: "Black Hole Eclipse",
|
|
Grass: "Bloom Doom",
|
|
Normal: "Breakneck Blitz",
|
|
Rock: "Continental Crush",
|
|
Steel: "Corkscrew Crash",
|
|
Dragon: "Devastating Drake",
|
|
Electric: "Gigavolt Havoc",
|
|
Water: "Hydro Vortex",
|
|
Fire: "Inferno Overdrive",
|
|
Ghost: "Never-Ending Nightmare",
|
|
Bug: "Savage Spin-Out",
|
|
Psychic: "Shattered Psyche",
|
|
Ice: "Subzero Slammer",
|
|
Flying: "Supersonic Skystrike",
|
|
Ground: "Tectonic Rage",
|
|
Fairy: "Twinkle Tackle"
|
|
};
|
|
this.battle = battle;
|
|
this.dex = battle.dex;
|
|
if (this.dex.data.Scripts.actions)
|
|
Object.assign(this, this.dex.data.Scripts.actions);
|
|
if (battle.format.actions)
|
|
Object.assign(this, battle.format.actions);
|
|
}
|
|
// #region SWITCH
|
|
// ==================================================================
|
|
switchIn(pokemon, pos, sourceEffect = null, isDrag) {
|
|
if (!pokemon || pokemon.isActive) {
|
|
this.battle.hint("A switch failed because the Pok\xE9mon trying to switch in is already in.");
|
|
return false;
|
|
}
|
|
const side = pokemon.side;
|
|
if (pos >= side.active.length) {
|
|
throw new Error(`Invalid switch position ${pos} / ${side.active.length}`);
|
|
}
|
|
const oldActive = side.active[pos];
|
|
const unfaintedActive = oldActive?.hp ? oldActive : null;
|
|
if (unfaintedActive) {
|
|
oldActive.beingCalledBack = true;
|
|
let switchCopyFlag = false;
|
|
if (sourceEffect && typeof sourceEffect.selfSwitch === "string") {
|
|
switchCopyFlag = sourceEffect.selfSwitch;
|
|
}
|
|
if (!oldActive.skipBeforeSwitchOutEventFlag && !isDrag) {
|
|
this.battle.runEvent("BeforeSwitchOut", oldActive);
|
|
if (this.battle.gen >= 5) {
|
|
this.battle.eachEvent("Update");
|
|
}
|
|
}
|
|
oldActive.skipBeforeSwitchOutEventFlag = false;
|
|
if (!this.battle.runEvent("SwitchOut", oldActive)) {
|
|
return false;
|
|
}
|
|
if (!oldActive.hp) {
|
|
return "pursuitfaint";
|
|
}
|
|
oldActive.illusion = null;
|
|
this.battle.singleEvent("End", oldActive.getAbility(), oldActive.abilityState, oldActive);
|
|
this.battle.queue.cancelAction(oldActive);
|
|
let newMove = null;
|
|
if (this.battle.gen === 4 && sourceEffect) {
|
|
newMove = oldActive.lastMove;
|
|
}
|
|
if (switchCopyFlag) {
|
|
pokemon.copyVolatileFrom(oldActive, switchCopyFlag);
|
|
}
|
|
if (newMove)
|
|
pokemon.lastMove = newMove;
|
|
oldActive.clearVolatile();
|
|
}
|
|
if (oldActive) {
|
|
oldActive.isActive = false;
|
|
oldActive.isStarted = false;
|
|
oldActive.usedItemThisTurn = false;
|
|
oldActive.statsRaisedThisTurn = false;
|
|
oldActive.statsLoweredThisTurn = false;
|
|
oldActive.position = pokemon.position;
|
|
pokemon.position = pos;
|
|
side.pokemon[pokemon.position] = pokemon;
|
|
side.pokemon[oldActive.position] = oldActive;
|
|
}
|
|
pokemon.isActive = true;
|
|
side.active[pos] = pokemon;
|
|
pokemon.activeTurns = 0;
|
|
pokemon.activeMoveActions = 0;
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
moveSlot.used = false;
|
|
}
|
|
this.battle.runEvent("BeforeSwitchIn", pokemon);
|
|
if (sourceEffect) {
|
|
this.battle.add(isDrag ? "drag" : "switch", pokemon, pokemon.getDetails, "[from] " + sourceEffect);
|
|
} else {
|
|
this.battle.add(isDrag ? "drag" : "switch", pokemon, pokemon.getDetails);
|
|
}
|
|
pokemon.abilityOrder = this.battle.abilityOrder++;
|
|
if (isDrag && this.battle.gen === 2)
|
|
pokemon.draggedIn = this.battle.turn;
|
|
pokemon.previouslySwitchedIn++;
|
|
if (isDrag && this.battle.gen >= 5) {
|
|
this.battle.singleEvent("PreStart", pokemon.getAbility(), pokemon.abilityState, pokemon);
|
|
this.runSwitch(pokemon);
|
|
} else {
|
|
this.battle.queue.insertChoice({ choice: "runUnnerve", pokemon });
|
|
this.battle.queue.insertChoice({ choice: "runSwitch", pokemon });
|
|
}
|
|
return true;
|
|
}
|
|
dragIn(side, pos) {
|
|
const pokemon = this.battle.getRandomSwitchable(side);
|
|
if (!pokemon || pokemon.isActive)
|
|
return false;
|
|
const oldActive = side.active[pos];
|
|
if (!oldActive)
|
|
throw new Error(`nothing to drag out`);
|
|
if (!oldActive.hp)
|
|
return false;
|
|
if (!this.battle.runEvent("DragOut", oldActive)) {
|
|
return false;
|
|
}
|
|
if (!this.switchIn(pokemon, pos, null, true))
|
|
return false;
|
|
return true;
|
|
}
|
|
runSwitch(pokemon) {
|
|
this.battle.runEvent("Swap", pokemon);
|
|
if (this.battle.gen >= 5) {
|
|
this.battle.runEvent("SwitchIn", pokemon);
|
|
}
|
|
this.battle.runEvent("EntryHazard", pokemon);
|
|
if (this.battle.gen <= 4) {
|
|
this.battle.runEvent("SwitchIn", pokemon);
|
|
}
|
|
if (this.battle.gen <= 2) {
|
|
for (const poke of this.battle.getAllActive())
|
|
poke.lastMove = null;
|
|
if (!pokemon.side.faintedThisTurn && pokemon.draggedIn !== this.battle.turn) {
|
|
this.battle.runEvent("AfterSwitchInSelf", pokemon);
|
|
}
|
|
}
|
|
if (!pokemon.hp)
|
|
return false;
|
|
pokemon.isStarted = true;
|
|
if (!pokemon.fainted) {
|
|
this.battle.singleEvent("Start", pokemon.getAbility(), pokemon.abilityState, pokemon);
|
|
this.battle.singleEvent("Start", pokemon.getItem(), pokemon.itemState, pokemon);
|
|
}
|
|
if (this.battle.gen === 4) {
|
|
for (const foeActive of pokemon.foes()) {
|
|
foeActive.removeVolatile("substitutebroken");
|
|
}
|
|
}
|
|
pokemon.draggedIn = null;
|
|
return true;
|
|
}
|
|
// #endregion
|
|
// #region MOVES
|
|
// ==================================================================
|
|
/**
|
|
* runMove is the "outside" move caller. It handles deducting PP,
|
|
* flinching, full paralysis, etc. All the stuff up to and including
|
|
* the "POKEMON used MOVE" message.
|
|
*
|
|
* For details of the difference between runMove and useMove, see
|
|
* useMove's info.
|
|
*
|
|
* externalMove skips LockMove and PP deduction, mostly for use by
|
|
* Dancer.
|
|
*/
|
|
runMove(moveOrMoveName, pokemon, targetLoc, sourceEffect, zMove, externalMove, maxMove, originalTarget) {
|
|
pokemon.activeMoveActions++;
|
|
let target = this.battle.getTarget(pokemon, maxMove || zMove || moveOrMoveName, targetLoc, originalTarget);
|
|
let baseMove = this.dex.getActiveMove(moveOrMoveName);
|
|
const pranksterBoosted = baseMove.pranksterBoosted;
|
|
if (baseMove.id !== "struggle" && !zMove && !maxMove && !externalMove) {
|
|
const changedMove = this.battle.runEvent("OverrideAction", pokemon, target, baseMove);
|
|
if (changedMove && changedMove !== true) {
|
|
baseMove = this.dex.getActiveMove(changedMove);
|
|
if (pranksterBoosted)
|
|
baseMove.pranksterBoosted = pranksterBoosted;
|
|
target = this.battle.getRandomTarget(pokemon, baseMove);
|
|
}
|
|
}
|
|
let move = baseMove;
|
|
if (zMove) {
|
|
move = this.getActiveZMove(baseMove, pokemon);
|
|
} else if (maxMove) {
|
|
move = this.getActiveMaxMove(baseMove, pokemon);
|
|
}
|
|
move.isExternal = externalMove;
|
|
this.battle.setActiveMove(move, pokemon, target);
|
|
const willTryMove = this.battle.runEvent("BeforeMove", pokemon, target, move);
|
|
if (!willTryMove) {
|
|
this.battle.runEvent("MoveAborted", pokemon, target, move);
|
|
this.battle.clearActiveMove(true);
|
|
pokemon.moveThisTurnResult = willTryMove;
|
|
return;
|
|
}
|
|
if (move.beforeMoveCallback) {
|
|
if (move.beforeMoveCallback.call(this.battle, pokemon, target, move)) {
|
|
this.battle.clearActiveMove(true);
|
|
pokemon.moveThisTurnResult = false;
|
|
return;
|
|
}
|
|
}
|
|
pokemon.lastDamage = 0;
|
|
let lockedMove;
|
|
if (!externalMove) {
|
|
lockedMove = this.battle.runEvent("LockMove", pokemon);
|
|
if (lockedMove === true)
|
|
lockedMove = false;
|
|
if (!lockedMove) {
|
|
if (!pokemon.deductPP(baseMove, null, target) && move.id !== "struggle") {
|
|
this.battle.add("cant", pokemon, "nopp", move);
|
|
this.battle.clearActiveMove(true);
|
|
pokemon.moveThisTurnResult = false;
|
|
return;
|
|
}
|
|
} else {
|
|
sourceEffect = this.dex.conditions.get("lockedmove");
|
|
}
|
|
pokemon.moveUsed(move, targetLoc);
|
|
}
|
|
const noLock = externalMove && !pokemon.volatiles["lockedmove"];
|
|
if (zMove) {
|
|
if (pokemon.illusion) {
|
|
this.battle.singleEvent("End", this.dex.abilities.get("Illusion"), pokemon.abilityState, pokemon);
|
|
}
|
|
this.battle.add("-zpower", pokemon);
|
|
pokemon.side.zMoveUsed = true;
|
|
}
|
|
const oldActiveMove = move;
|
|
const moveDidSomething = this.useMove(baseMove, pokemon, target, sourceEffect, zMove, maxMove);
|
|
this.battle.lastSuccessfulMoveThisTurn = moveDidSomething ? this.battle.activeMove && this.battle.activeMove.id : null;
|
|
if (this.battle.activeMove)
|
|
move = this.battle.activeMove;
|
|
this.battle.singleEvent("AfterMove", move, null, pokemon, target, move);
|
|
this.battle.runEvent("AfterMove", pokemon, target, move);
|
|
if (move.flags["dance"] && moveDidSomething && !move.isExternal) {
|
|
const dancers = [];
|
|
for (const currentPoke of this.battle.getAllActive()) {
|
|
if (pokemon === currentPoke)
|
|
continue;
|
|
if (currentPoke.hasAbility("dancer") && !currentPoke.isSemiInvulnerable()) {
|
|
dancers.push(currentPoke);
|
|
}
|
|
}
|
|
dancers.sort(
|
|
(a, b) => -(b.storedStats["spe"] - a.storedStats["spe"]) || b.abilityOrder - a.abilityOrder
|
|
);
|
|
const targetOf1stDance = this.battle.activeTarget;
|
|
for (const dancer of dancers) {
|
|
if (this.battle.faintMessages())
|
|
break;
|
|
if (dancer.fainted)
|
|
continue;
|
|
this.battle.add("-activate", dancer, "ability: Dancer");
|
|
const dancersTarget = !targetOf1stDance.isAlly(dancer) && pokemon.isAlly(dancer) ? targetOf1stDance : pokemon;
|
|
const dancersTargetLoc = dancer.getLocOf(dancersTarget);
|
|
this.runMove(move.id, dancer, dancersTargetLoc, this.dex.abilities.get("dancer"), void 0, true);
|
|
}
|
|
}
|
|
if (noLock && pokemon.volatiles["lockedmove"])
|
|
delete pokemon.volatiles["lockedmove"];
|
|
this.battle.faintMessages();
|
|
this.battle.checkWin();
|
|
if (this.battle.gen <= 4) {
|
|
this.battle.activeMove = oldActiveMove;
|
|
}
|
|
}
|
|
/**
|
|
* useMove is the "inside" move caller. It handles effects of the
|
|
* move itself, but not the idea of using the move.
|
|
*
|
|
* Most caller effects, like Sleep Talk, Nature Power, Magic Bounce,
|
|
* etc use useMove.
|
|
*
|
|
* The only ones that use runMove are Instruct, Pursuit, and
|
|
* Dancer.
|
|
*/
|
|
useMove(move, pokemon, target, sourceEffect, zMove, maxMove) {
|
|
pokemon.moveThisTurnResult = void 0;
|
|
const oldMoveResult = pokemon.moveThisTurnResult;
|
|
const moveResult = this.useMoveInner(move, pokemon, target, sourceEffect, zMove, maxMove);
|
|
if (oldMoveResult === pokemon.moveThisTurnResult)
|
|
pokemon.moveThisTurnResult = moveResult;
|
|
return moveResult;
|
|
}
|
|
useMoveInner(moveOrMoveName, pokemon, target, sourceEffect, zMove, maxMove) {
|
|
if (!sourceEffect && this.battle.effect.id)
|
|
sourceEffect = this.battle.effect;
|
|
if (sourceEffect && ["instruct", "custapberry"].includes(sourceEffect.id))
|
|
sourceEffect = null;
|
|
let move = this.dex.getActiveMove(moveOrMoveName);
|
|
pokemon.lastMoveUsed = move;
|
|
if (move.id === "weatherball" && zMove) {
|
|
this.battle.singleEvent("ModifyType", move, null, pokemon, target, move, move);
|
|
if (move.type !== "Normal")
|
|
sourceEffect = move;
|
|
}
|
|
if (zMove || move.category !== "Status" && sourceEffect && sourceEffect.isZ) {
|
|
move = this.getActiveZMove(move, pokemon);
|
|
}
|
|
if (maxMove && move.category !== "Status") {
|
|
this.battle.singleEvent("ModifyType", move, null, pokemon, target, move, move);
|
|
this.battle.runEvent("ModifyType", pokemon, target, move, move);
|
|
}
|
|
if (maxMove || move.category !== "Status" && sourceEffect && sourceEffect.isMax) {
|
|
move = this.getActiveMaxMove(move, pokemon);
|
|
}
|
|
if (this.battle.activeMove) {
|
|
move.priority = this.battle.activeMove.priority;
|
|
if (!move.hasBounced)
|
|
move.pranksterBoosted = this.battle.activeMove.pranksterBoosted;
|
|
}
|
|
const baseTarget = move.target;
|
|
let targetRelayVar = { target };
|
|
targetRelayVar = this.battle.runEvent("ModifyTarget", pokemon, target, move, targetRelayVar, true);
|
|
if (targetRelayVar.target !== void 0)
|
|
target = targetRelayVar.target;
|
|
if (target === void 0)
|
|
target = this.battle.getRandomTarget(pokemon, move);
|
|
if (move.target === "self" || move.target === "allies") {
|
|
target = pokemon;
|
|
}
|
|
if (sourceEffect) {
|
|
move.sourceEffect = sourceEffect.id;
|
|
move.ignoreAbility = sourceEffect.ignoreAbility;
|
|
}
|
|
let moveResult = false;
|
|
this.battle.setActiveMove(move, pokemon, target);
|
|
this.battle.singleEvent("ModifyType", move, null, pokemon, target, move, move);
|
|
this.battle.singleEvent("ModifyMove", move, null, pokemon, target, move, move);
|
|
if (baseTarget !== move.target) {
|
|
target = this.battle.getRandomTarget(pokemon, move);
|
|
}
|
|
move = this.battle.runEvent("ModifyType", pokemon, target, move, move);
|
|
move = this.battle.runEvent("ModifyMove", pokemon, target, move, move);
|
|
if (baseTarget !== move.target) {
|
|
target = this.battle.getRandomTarget(pokemon, move);
|
|
}
|
|
if (!move || pokemon.fainted) {
|
|
return false;
|
|
}
|
|
let attrs = "";
|
|
let movename = move.name;
|
|
if (move.id === "hiddenpower")
|
|
movename = "Hidden Power";
|
|
if (sourceEffect)
|
|
attrs += `|[from]${sourceEffect.fullname}`;
|
|
if (zMove && move.isZ === true) {
|
|
attrs = "|[anim]" + movename + attrs;
|
|
movename = "Z-" + movename;
|
|
}
|
|
this.battle.addMove("move", pokemon, movename, target + attrs);
|
|
if (zMove)
|
|
this.runZPower(move, pokemon);
|
|
if (!target) {
|
|
this.battle.attrLastMove("[notarget]");
|
|
this.battle.add(this.battle.gen >= 5 ? "-fail" : "-notarget", pokemon);
|
|
return false;
|
|
}
|
|
const { targets, pressureTargets } = pokemon.getMoveTargets(move, target);
|
|
if (targets.length) {
|
|
target = targets[targets.length - 1];
|
|
}
|
|
const callerMoveForPressure = sourceEffect && sourceEffect.pp ? sourceEffect : null;
|
|
if (!sourceEffect || callerMoveForPressure || sourceEffect.id === "pursuit") {
|
|
let extraPP = 0;
|
|
for (const source of pressureTargets) {
|
|
const ppDrop = this.battle.runEvent("DeductPP", source, pokemon, move);
|
|
if (ppDrop !== true) {
|
|
extraPP += ppDrop || 0;
|
|
}
|
|
}
|
|
if (extraPP > 0) {
|
|
pokemon.deductPP(callerMoveForPressure || moveOrMoveName, extraPP);
|
|
}
|
|
}
|
|
if (!this.battle.singleEvent("TryMove", move, null, pokemon, target, move) || !this.battle.runEvent("TryMove", pokemon, target, move)) {
|
|
move.mindBlownRecoil = false;
|
|
return false;
|
|
}
|
|
this.battle.singleEvent("UseMoveMessage", move, null, pokemon, target, move);
|
|
if (move.ignoreImmunity === void 0) {
|
|
move.ignoreImmunity = move.category === "Status";
|
|
}
|
|
if (this.battle.gen !== 4 && move.selfdestruct === "always") {
|
|
this.battle.faint(pokemon, pokemon, move);
|
|
}
|
|
let damage = false;
|
|
if (move.target === "all" || move.target === "foeSide" || move.target === "allySide" || move.target === "allyTeam") {
|
|
damage = this.tryMoveHit(targets, pokemon, move);
|
|
if (damage === this.battle.NOT_FAIL)
|
|
pokemon.moveThisTurnResult = null;
|
|
if (damage || damage === 0 || damage === void 0)
|
|
moveResult = true;
|
|
} else {
|
|
if (!targets.length) {
|
|
this.battle.attrLastMove("[notarget]");
|
|
this.battle.add(this.battle.gen >= 5 ? "-fail" : "-notarget", pokemon);
|
|
return false;
|
|
}
|
|
if (this.battle.gen === 4 && move.selfdestruct === "always") {
|
|
this.battle.faint(pokemon, pokemon, move);
|
|
}
|
|
moveResult = this.trySpreadMoveHit(targets, pokemon, move);
|
|
}
|
|
if (move.selfBoost && moveResult)
|
|
this.moveHit(pokemon, pokemon, move, move.selfBoost, false, true);
|
|
if (!pokemon.hp) {
|
|
this.battle.faint(pokemon, pokemon, move);
|
|
}
|
|
if (!moveResult) {
|
|
this.battle.singleEvent("MoveFail", move, null, target, pokemon, move);
|
|
return false;
|
|
}
|
|
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility("sheerforce")) && !move.flags["futuremove"]) {
|
|
const originalHp = pokemon.hp;
|
|
this.battle.singleEvent("AfterMoveSecondarySelf", move, null, pokemon, target, move);
|
|
this.battle.runEvent("AfterMoveSecondarySelf", pokemon, target, move);
|
|
if (pokemon && pokemon !== target && move.category !== "Status") {
|
|
if (pokemon.hp <= pokemon.maxhp / 2 && originalHp > pokemon.maxhp / 2) {
|
|
this.battle.runEvent("EmergencyExit", pokemon, pokemon);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
/** NOTE: includes single-target moves */
|
|
trySpreadMoveHit(targets, pokemon, move, notActive) {
|
|
if (targets.length > 1 && !move.smartTarget)
|
|
move.spreadHit = true;
|
|
const moveSteps = [
|
|
// 0. check for semi invulnerability
|
|
this.hitStepInvulnerabilityEvent,
|
|
// 1. run the 'TryHit' event (Protect, Magic Bounce, Volt Absorb, etc.) (this is step 2 in gens 5 & 6, and step 4 in gen 4)
|
|
this.hitStepTryHitEvent,
|
|
// 2. check for type immunity (this is step 1 in gens 4-6)
|
|
this.hitStepTypeImmunity,
|
|
// 3. check for various move-specific immunities
|
|
this.hitStepTryImmunity,
|
|
// 4. check accuracy
|
|
this.hitStepAccuracy,
|
|
// 5. break protection effects
|
|
this.hitStepBreakProtect,
|
|
// 6. steal positive boosts (Spectral Thief)
|
|
this.hitStepStealBoosts,
|
|
// 7. loop that processes each hit of the move (has its own steps per iteration)
|
|
this.hitStepMoveHitLoop
|
|
];
|
|
if (this.battle.gen <= 6) {
|
|
[moveSteps[1], moveSteps[2]] = [moveSteps[2], moveSteps[1]];
|
|
}
|
|
if (this.battle.gen === 4) {
|
|
[moveSteps[2], moveSteps[4]] = [moveSteps[4], moveSteps[2]];
|
|
}
|
|
if (notActive)
|
|
this.battle.setActiveMove(move, pokemon, targets[0]);
|
|
const hitResult = this.battle.singleEvent("Try", move, null, pokemon, targets[0], move) && this.battle.singleEvent("PrepareHit", move, {}, targets[0], pokemon, move) && this.battle.runEvent("PrepareHit", pokemon, targets[0], move);
|
|
if (!hitResult) {
|
|
if (hitResult === false) {
|
|
this.battle.add("-fail", pokemon);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
return hitResult === this.battle.NOT_FAIL;
|
|
}
|
|
let atLeastOneFailure = false;
|
|
for (const step of moveSteps) {
|
|
const hitResults = step.call(this, targets, pokemon, move);
|
|
if (!hitResults)
|
|
continue;
|
|
targets = targets.filter((val, i) => hitResults[i] || hitResults[i] === 0);
|
|
atLeastOneFailure = atLeastOneFailure || hitResults.some((val) => val === false);
|
|
if (!targets.length) {
|
|
break;
|
|
}
|
|
}
|
|
const moveResult = !!targets.length;
|
|
if (!moveResult && !atLeastOneFailure)
|
|
pokemon.moveThisTurnResult = null;
|
|
const hitSlot = targets.map((p) => p.getSlot());
|
|
if (move.spreadHit)
|
|
this.battle.attrLastMove("[spread] " + hitSlot.join(","));
|
|
return moveResult;
|
|
}
|
|
hitStepInvulnerabilityEvent(targets, pokemon, move) {
|
|
if (move.id === "helpinghand" || this.battle.gen >= 8 && move.id === "toxic" && pokemon.hasType("Poison")) {
|
|
return new Array(targets.length).fill(true);
|
|
}
|
|
const hitResults = this.battle.runEvent("Invulnerability", targets, pokemon, move);
|
|
for (const [i, target] of targets.entries()) {
|
|
if (hitResults[i] === false) {
|
|
if (move.smartTarget) {
|
|
move.smartTarget = false;
|
|
} else {
|
|
if (!move.spreadHit)
|
|
this.battle.attrLastMove("[miss]");
|
|
this.battle.add("-miss", pokemon, target);
|
|
}
|
|
}
|
|
}
|
|
return hitResults;
|
|
}
|
|
hitStepTryHitEvent(targets, pokemon, move) {
|
|
const hitResults = this.battle.runEvent("TryHit", targets, pokemon, move);
|
|
if (!hitResults.includes(true) && hitResults.includes(false)) {
|
|
this.battle.add("-fail", pokemon);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
for (const i of targets.keys()) {
|
|
if (hitResults[i] !== this.battle.NOT_FAIL)
|
|
hitResults[i] = hitResults[i] || false;
|
|
}
|
|
return hitResults;
|
|
}
|
|
hitStepTypeImmunity(targets, pokemon, move) {
|
|
if (move.ignoreImmunity === void 0) {
|
|
move.ignoreImmunity = move.category === "Status";
|
|
}
|
|
const hitResults = [];
|
|
for (const i of targets.keys()) {
|
|
hitResults[i] = move.ignoreImmunity && (move.ignoreImmunity === true || move.ignoreImmunity[move.type]) || targets[i].runImmunity(move.type, !move.smartTarget);
|
|
if (move.smartTarget && !hitResults[i])
|
|
move.smartTarget = false;
|
|
}
|
|
return hitResults;
|
|
}
|
|
hitStepTryImmunity(targets, pokemon, move) {
|
|
const hitResults = [];
|
|
for (const [i, target] of targets.entries()) {
|
|
if (this.battle.gen >= 6 && move.flags["powder"] && target !== pokemon && !this.dex.getImmunity("powder", target)) {
|
|
this.battle.debug("natural powder immunity");
|
|
this.battle.add("-immune", target);
|
|
hitResults[i] = false;
|
|
} else if (!this.battle.singleEvent("TryImmunity", move, {}, target, pokemon, move)) {
|
|
this.battle.add("-immune", target);
|
|
hitResults[i] = false;
|
|
} else if (this.battle.gen >= 7 && move.pranksterBoosted && pokemon.hasAbility("prankster") && !targets[i].isAlly(pokemon) && !this.dex.getImmunity("prankster", target)) {
|
|
this.battle.debug("natural prankster immunity");
|
|
if (!target.illusion)
|
|
this.battle.hint("Since gen 7, Dark is immune to Prankster moves.");
|
|
this.battle.add("-immune", target);
|
|
hitResults[i] = false;
|
|
} else {
|
|
hitResults[i] = true;
|
|
}
|
|
}
|
|
return hitResults;
|
|
}
|
|
hitStepAccuracy(targets, pokemon, move) {
|
|
const hitResults = [];
|
|
for (const [i, target] of targets.entries()) {
|
|
this.battle.activeTarget = target;
|
|
let accuracy = move.accuracy;
|
|
if (move.ohko) {
|
|
if (!target.isSemiInvulnerable()) {
|
|
accuracy = 30;
|
|
if (move.ohko === "Ice" && this.battle.gen >= 7 && !pokemon.hasType("Ice")) {
|
|
accuracy = 20;
|
|
}
|
|
if (!target.volatiles["dynamax"] && pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) {
|
|
accuracy += pokemon.level - target.level;
|
|
} else {
|
|
this.battle.add("-immune", target, "[ohko]");
|
|
hitResults[i] = false;
|
|
continue;
|
|
}
|
|
}
|
|
} else {
|
|
accuracy = this.battle.runEvent("ModifyAccuracy", target, pokemon, move, accuracy);
|
|
if (accuracy !== true) {
|
|
let boost = 0;
|
|
if (!move.ignoreAccuracy) {
|
|
const boosts = this.battle.runEvent("ModifyBoost", pokemon, null, null, { ...pokemon.boosts });
|
|
boost = this.battle.clampIntRange(boosts["accuracy"], -6, 6);
|
|
}
|
|
if (!move.ignoreEvasion) {
|
|
const boosts = this.battle.runEvent("ModifyBoost", target, null, null, { ...target.boosts });
|
|
boost = this.battle.clampIntRange(boost - boosts["evasion"], -6, 6);
|
|
}
|
|
if (boost > 0) {
|
|
accuracy = this.battle.trunc(accuracy * (3 + boost) / 3);
|
|
} else if (boost < 0) {
|
|
accuracy = this.battle.trunc(accuracy * 3 / (3 - boost));
|
|
}
|
|
}
|
|
}
|
|
if (move.alwaysHit || move.id === "toxic" && this.battle.gen >= 8 && pokemon.hasType("Poison") || move.target === "self" && move.category === "Status" && !target.isSemiInvulnerable()) {
|
|
accuracy = true;
|
|
} else {
|
|
accuracy = this.battle.runEvent("Accuracy", target, pokemon, move, accuracy);
|
|
}
|
|
if (accuracy !== true && !this.battle.randomChance(accuracy, 100)) {
|
|
if (move.smartTarget) {
|
|
move.smartTarget = false;
|
|
} else {
|
|
if (!move.spreadHit)
|
|
this.battle.attrLastMove("[miss]");
|
|
this.battle.add("-miss", pokemon, target);
|
|
}
|
|
if (!move.ohko && pokemon.hasItem("blunderpolicy") && pokemon.useItem()) {
|
|
this.battle.boost({ spe: 2 }, pokemon);
|
|
}
|
|
hitResults[i] = false;
|
|
continue;
|
|
}
|
|
hitResults[i] = true;
|
|
}
|
|
return hitResults;
|
|
}
|
|
hitStepBreakProtect(targets, pokemon, move) {
|
|
if (move.breaksProtect) {
|
|
for (const target of targets) {
|
|
let broke = false;
|
|
for (const effectid of ["banefulbunker", "kingsshield", "obstruct", "protect", "silktrap", "spikyshield"]) {
|
|
if (target.removeVolatile(effectid))
|
|
broke = true;
|
|
}
|
|
if (this.battle.gen >= 6 || !target.isAlly(pokemon)) {
|
|
for (const effectid of ["craftyshield", "matblock", "quickguard", "wideguard"]) {
|
|
if (target.side.removeSideCondition(effectid))
|
|
broke = true;
|
|
}
|
|
}
|
|
if (broke) {
|
|
if (move.id === "feint") {
|
|
this.battle.add("-activate", target, "move: Feint");
|
|
} else {
|
|
this.battle.add("-activate", target, "move: " + move.name, "[broken]");
|
|
}
|
|
if (this.battle.gen >= 6)
|
|
delete target.volatiles["stall"];
|
|
}
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
hitStepStealBoosts(targets, pokemon, move) {
|
|
const target = targets[0];
|
|
if (move.stealsBoosts) {
|
|
const boosts = {};
|
|
let stolen = false;
|
|
let statName;
|
|
for (statName in target.boosts) {
|
|
const stage = target.boosts[statName];
|
|
if (stage > 0) {
|
|
boosts[statName] = stage;
|
|
stolen = true;
|
|
}
|
|
}
|
|
if (stolen) {
|
|
this.battle.attrLastMove("[still]");
|
|
this.battle.add("-clearpositiveboost", target, pokemon, "move: " + move.name);
|
|
this.battle.boost(boosts, pokemon, pokemon);
|
|
let statName2;
|
|
for (statName2 in boosts) {
|
|
boosts[statName2] = 0;
|
|
}
|
|
target.setBoost(boosts);
|
|
this.battle.addMove("-anim", pokemon, "Spectral Thief", target);
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
afterMoveSecondaryEvent(targets, pokemon, move) {
|
|
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility("sheerforce"))) {
|
|
this.battle.singleEvent("AfterMoveSecondary", move, null, targets[0], pokemon, move);
|
|
this.battle.runEvent("AfterMoveSecondary", targets, pokemon, move);
|
|
}
|
|
return void 0;
|
|
}
|
|
/** NOTE: used only for moves that target sides/fields rather than pokemon */
|
|
tryMoveHit(targetOrTargets, pokemon, move) {
|
|
const target = Array.isArray(targetOrTargets) ? targetOrTargets[0] : targetOrTargets;
|
|
const targets = Array.isArray(targetOrTargets) ? targetOrTargets : [target];
|
|
this.battle.setActiveMove(move, pokemon, targets[0]);
|
|
let hitResult = this.battle.singleEvent("Try", move, null, pokemon, target, move) && this.battle.singleEvent("PrepareHit", move, {}, target, pokemon, move) && this.battle.runEvent("PrepareHit", pokemon, target, move);
|
|
if (!hitResult) {
|
|
if (hitResult === false) {
|
|
this.battle.add("-fail", pokemon);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
return false;
|
|
}
|
|
const isFFAHazard = move.target === "foeSide" && this.battle.gameType === "freeforall";
|
|
if (move.target === "all") {
|
|
hitResult = this.battle.runEvent("TryHitField", target, pokemon, move);
|
|
} else if (isFFAHazard) {
|
|
const hitResults = this.battle.runEvent("TryHitSide", targets, pokemon, move);
|
|
if (hitResults.some((result) => !result))
|
|
return false;
|
|
hitResult = true;
|
|
} else {
|
|
hitResult = this.battle.runEvent("TryHitSide", target, pokemon, move);
|
|
}
|
|
if (!hitResult) {
|
|
if (hitResult === false) {
|
|
this.battle.add("-fail", pokemon);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
return false;
|
|
}
|
|
return this.moveHit(isFFAHazard ? targets : target, pokemon, move);
|
|
}
|
|
hitStepMoveHitLoop(targets, pokemon, move) {
|
|
let damage = [];
|
|
for (const i of targets.keys()) {
|
|
damage[i] = 0;
|
|
}
|
|
move.totalDamage = 0;
|
|
pokemon.lastDamage = 0;
|
|
let targetHits = move.multihit || 1;
|
|
if (Array.isArray(targetHits)) {
|
|
if (targetHits[0] === 2 && targetHits[1] === 5) {
|
|
if (this.battle.gen >= 5) {
|
|
targetHits = this.battle.sample([2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5]);
|
|
if (targetHits < 4 && pokemon.hasItem("loadeddice")) {
|
|
targetHits = 5 - this.battle.random(2);
|
|
}
|
|
} else {
|
|
targetHits = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
|
|
}
|
|
} else {
|
|
targetHits = this.battle.random(targetHits[0], targetHits[1] + 1);
|
|
}
|
|
}
|
|
if (targetHits === 10 && pokemon.hasItem("loadeddice"))
|
|
targetHits -= this.battle.random(7);
|
|
targetHits = Math.floor(targetHits);
|
|
let nullDamage = true;
|
|
let moveDamage = [];
|
|
const isSleepUsable = move.sleepUsable || this.dex.moves.get(move.sourceEffect).sleepUsable;
|
|
let targetsCopy = targets.slice(0);
|
|
let hit;
|
|
for (hit = 1; hit <= targetHits; hit++) {
|
|
if (damage.includes(false))
|
|
break;
|
|
if (hit > 1 && pokemon.status === "slp" && (!isSleepUsable || this.battle.gen === 4))
|
|
break;
|
|
if (targets.every((target2) => !target2?.hp))
|
|
break;
|
|
move.hit = hit;
|
|
if (move.smartTarget && targets.length > 1) {
|
|
targetsCopy = [targets[hit - 1]];
|
|
damage = [damage[hit - 1]];
|
|
} else {
|
|
targetsCopy = targets.slice(0);
|
|
}
|
|
const target = targetsCopy[0];
|
|
if (target && typeof move.smartTarget === "boolean") {
|
|
if (hit > 1) {
|
|
this.battle.addMove("-anim", pokemon, move.name, target);
|
|
} else {
|
|
this.battle.retargetLastMove(target);
|
|
}
|
|
}
|
|
if (target && move.multiaccuracy && hit > 1) {
|
|
let accuracy = move.accuracy;
|
|
const boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
|
if (accuracy !== true) {
|
|
if (!move.ignoreAccuracy) {
|
|
const boosts = this.battle.runEvent("ModifyBoost", pokemon, null, null, { ...pokemon.boosts });
|
|
const boost = this.battle.clampIntRange(boosts["accuracy"], -6, 6);
|
|
if (boost > 0) {
|
|
accuracy *= boostTable[boost];
|
|
} else {
|
|
accuracy /= boostTable[-boost];
|
|
}
|
|
}
|
|
if (!move.ignoreEvasion) {
|
|
const boosts = this.battle.runEvent("ModifyBoost", target, null, null, { ...target.boosts });
|
|
const boost = this.battle.clampIntRange(boosts["evasion"], -6, 6);
|
|
if (boost > 0) {
|
|
accuracy /= boostTable[boost];
|
|
} else if (boost < 0) {
|
|
accuracy *= boostTable[-boost];
|
|
}
|
|
}
|
|
}
|
|
accuracy = this.battle.runEvent("ModifyAccuracy", target, pokemon, move, accuracy);
|
|
if (!move.alwaysHit) {
|
|
accuracy = this.battle.runEvent("Accuracy", target, pokemon, move, accuracy);
|
|
if (accuracy !== true && !this.battle.randomChance(accuracy, 100))
|
|
break;
|
|
}
|
|
}
|
|
const moveData = move;
|
|
if (!moveData.flags)
|
|
moveData.flags = {};
|
|
[moveDamage, targetsCopy] = this.spreadMoveHit(targetsCopy, pokemon, move, moveData);
|
|
if (!moveDamage.some((val) => val !== false))
|
|
break;
|
|
nullDamage = false;
|
|
for (const [i, md] of moveDamage.entries()) {
|
|
damage[i] = md === true || !md ? 0 : md;
|
|
move.totalDamage += damage[i];
|
|
}
|
|
if (move.mindBlownRecoil) {
|
|
const hpBeforeRecoil = pokemon.hp;
|
|
this.battle.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get(move.id), true);
|
|
move.mindBlownRecoil = false;
|
|
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
|
this.battle.runEvent("EmergencyExit", pokemon, pokemon);
|
|
}
|
|
}
|
|
this.battle.eachEvent("Update");
|
|
if (!pokemon.hp && targets.length === 1) {
|
|
hit++;
|
|
break;
|
|
}
|
|
}
|
|
if (hit === 1)
|
|
return damage.fill(false);
|
|
if (nullDamage)
|
|
damage.fill(false);
|
|
this.battle.faintMessages(false, false, !pokemon.hp);
|
|
if (move.multihit && typeof move.smartTarget !== "boolean") {
|
|
this.battle.add("-hitcount", targets[0], hit - 1);
|
|
}
|
|
if (move.recoil && move.totalDamage) {
|
|
const hpBeforeRecoil = pokemon.hp;
|
|
this.battle.damage(this.calcRecoilDamage(move.totalDamage, move), pokemon, pokemon, "recoil");
|
|
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
|
this.battle.runEvent("EmergencyExit", pokemon, pokemon);
|
|
}
|
|
}
|
|
if (move.struggleRecoil) {
|
|
const hpBeforeRecoil = pokemon.hp;
|
|
let recoilDamage;
|
|
if (this.dex.gen >= 5) {
|
|
recoilDamage = this.battle.clampIntRange(Math.round(pokemon.baseMaxhp / 4), 1);
|
|
} else {
|
|
recoilDamage = this.battle.clampIntRange(this.battle.trunc(pokemon.maxhp / 4), 1);
|
|
}
|
|
this.battle.directDamage(recoilDamage, pokemon, pokemon, { id: "strugglerecoil" });
|
|
if (pokemon.hp <= pokemon.maxhp / 2 && hpBeforeRecoil > pokemon.maxhp / 2) {
|
|
this.battle.runEvent("EmergencyExit", pokemon, pokemon);
|
|
}
|
|
}
|
|
if (move.smartTarget) {
|
|
if (move.smartTarget && targets.length > 1) {
|
|
targetsCopy = [targets[hit - 1]];
|
|
} else {
|
|
targetsCopy = targets.slice(0);
|
|
}
|
|
}
|
|
for (const [i, target] of targetsCopy.entries()) {
|
|
if (target && pokemon !== target) {
|
|
target.gotAttacked(move, moveDamage[i], pokemon);
|
|
if (typeof moveDamage[i] === "number") {
|
|
target.timesAttacked += hit - 1;
|
|
}
|
|
}
|
|
}
|
|
if (move.ohko && !targets[0].hp)
|
|
this.battle.add("-ohko");
|
|
if (!damage.some((val) => !!val || val === 0))
|
|
return damage;
|
|
this.battle.eachEvent("Update");
|
|
this.afterMoveSecondaryEvent(targetsCopy.filter((val) => !!val), pokemon, move);
|
|
if (!move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility("sheerforce"))) {
|
|
for (const [i, d] of damage.entries()) {
|
|
const curDamage = targets.length === 1 ? move.totalDamage : d;
|
|
if (typeof curDamage === "number" && targets[i].hp) {
|
|
const targetHPBeforeDamage = (targets[i].hurtThisTurn || 0) + curDamage;
|
|
if (targets[i].hp <= targets[i].maxhp / 2 && targetHPBeforeDamage > targets[i].maxhp / 2) {
|
|
this.battle.runEvent("EmergencyExit", targets[i], pokemon);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
spreadMoveHit(targets, pokemon, moveOrMoveName, hitEffect, isSecondary, isSelf) {
|
|
const target = targets[0];
|
|
let damage = [];
|
|
for (const i of targets.keys()) {
|
|
damage[i] = true;
|
|
}
|
|
const move = this.dex.getActiveMove(moveOrMoveName);
|
|
let hitResult = true;
|
|
let moveData = hitEffect;
|
|
if (!moveData)
|
|
moveData = move;
|
|
if (!moveData.flags)
|
|
moveData.flags = {};
|
|
if (move.target === "all" && !isSelf) {
|
|
hitResult = this.battle.singleEvent("TryHitField", moveData, {}, target || null, pokemon, move);
|
|
} else if ((move.target === "foeSide" || move.target === "allySide" || move.target === "allyTeam") && !isSelf) {
|
|
hitResult = this.battle.singleEvent("TryHitSide", moveData, {}, target || null, pokemon, move);
|
|
} else if (target) {
|
|
hitResult = this.battle.singleEvent("TryHit", moveData, {}, target, pokemon, move);
|
|
}
|
|
if (!hitResult) {
|
|
if (hitResult === false) {
|
|
this.battle.add("-fail", pokemon);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
return [[false], targets];
|
|
}
|
|
if (!isSecondary && !isSelf) {
|
|
if (move.target !== "all" && move.target !== "allyTeam" && move.target !== "allySide" && move.target !== "foeSide") {
|
|
damage = this.tryPrimaryHitEvent(damage, targets, pokemon, move, moveData, isSecondary);
|
|
}
|
|
}
|
|
for (const i of targets.keys()) {
|
|
if (damage[i] === this.battle.HIT_SUBSTITUTE) {
|
|
damage[i] = true;
|
|
targets[i] = null;
|
|
}
|
|
if (targets[i] && isSecondary && !moveData.self) {
|
|
damage[i] = true;
|
|
}
|
|
if (!damage[i])
|
|
targets[i] = false;
|
|
}
|
|
damage = this.getSpreadDamage(damage, targets, pokemon, move, moveData, isSecondary, isSelf);
|
|
for (const i of targets.keys()) {
|
|
if (damage[i] === false)
|
|
targets[i] = false;
|
|
}
|
|
damage = this.battle.spreadDamage(damage, targets, pokemon, move);
|
|
for (const i of targets.keys()) {
|
|
if (damage[i] === false)
|
|
targets[i] = false;
|
|
}
|
|
damage = this.runMoveEffects(damage, targets, pokemon, move, moveData, isSecondary, isSelf);
|
|
for (const i of targets.keys()) {
|
|
if (!damage[i] && damage[i] !== 0)
|
|
targets[i] = false;
|
|
}
|
|
const activeTarget = this.battle.activeTarget;
|
|
if (moveData.self && !move.selfDropped)
|
|
this.selfDrops(targets, pokemon, move, moveData, isSecondary);
|
|
if (moveData.secondaries)
|
|
this.secondaries(targets, pokemon, move, moveData, isSelf);
|
|
this.battle.activeTarget = activeTarget;
|
|
if (moveData.forceSwitch)
|
|
damage = this.forceSwitch(damage, targets, pokemon, move);
|
|
for (const i of targets.keys()) {
|
|
if (!damage[i] && damage[i] !== 0)
|
|
targets[i] = false;
|
|
}
|
|
const damagedTargets = [];
|
|
const damagedDamage = [];
|
|
for (const [i, t] of targets.entries()) {
|
|
if (typeof damage[i] === "number" && t) {
|
|
damagedTargets.push(t);
|
|
damagedDamage.push(damage[i]);
|
|
}
|
|
}
|
|
const pokemonOriginalHP = pokemon.hp;
|
|
if (damagedDamage.length && !isSecondary && !isSelf) {
|
|
this.battle.runEvent("DamagingHit", damagedTargets, pokemon, move, damagedDamage);
|
|
if (moveData.onAfterHit) {
|
|
for (const t of damagedTargets) {
|
|
this.battle.singleEvent("AfterHit", moveData, {}, t, pokemon, move);
|
|
}
|
|
}
|
|
if (pokemon.hp && pokemon.hp <= pokemon.maxhp / 2 && pokemonOriginalHP > pokemon.maxhp / 2) {
|
|
this.battle.runEvent("EmergencyExit", pokemon);
|
|
}
|
|
}
|
|
return [damage, targets];
|
|
}
|
|
tryPrimaryHitEvent(damage, targets, pokemon, move, moveData, isSecondary) {
|
|
for (const [i, target] of targets.entries()) {
|
|
if (!target)
|
|
continue;
|
|
damage[i] = this.battle.runEvent("TryPrimaryHit", target, pokemon, moveData);
|
|
}
|
|
return damage;
|
|
}
|
|
getSpreadDamage(damage, targets, source, move, moveData, isSecondary, isSelf) {
|
|
for (const [i, target] of targets.entries()) {
|
|
if (!target)
|
|
continue;
|
|
this.battle.activeTarget = target;
|
|
damage[i] = void 0;
|
|
const curDamage = this.getDamage(source, target, moveData);
|
|
if (curDamage === false || curDamage === null) {
|
|
if (damage[i] === false && !isSecondary && !isSelf) {
|
|
this.battle.add("-fail", source);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
this.battle.debug("damage calculation interrupted");
|
|
damage[i] = false;
|
|
continue;
|
|
}
|
|
damage[i] = curDamage;
|
|
}
|
|
return damage;
|
|
}
|
|
runMoveEffects(damage, targets, source, move, moveData, isSecondary, isSelf) {
|
|
let didAnything = damage.reduce(this.combineResults);
|
|
for (const [i, target] of targets.entries()) {
|
|
if (target === false)
|
|
continue;
|
|
let hitResult;
|
|
let didSomething = void 0;
|
|
if (target) {
|
|
if (moveData.boosts && !target.fainted) {
|
|
hitResult = this.battle.boost(moveData.boosts, target, source, move, isSecondary, isSelf);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.heal && !target.fainted) {
|
|
if (target.hp >= target.maxhp) {
|
|
this.battle.add("-fail", target, "heal");
|
|
this.battle.attrLastMove("[still]");
|
|
damage[i] = this.combineResults(damage[i], false);
|
|
didAnything = this.combineResults(didAnything, null);
|
|
continue;
|
|
}
|
|
const amount = target.baseMaxhp * moveData.heal[0] / moveData.heal[1];
|
|
const d = target.heal((this.battle.gen < 5 ? Math.floor : Math.round)(amount));
|
|
if (!d && d !== 0) {
|
|
this.battle.add("-fail", source);
|
|
this.battle.attrLastMove("[still]");
|
|
this.battle.debug("heal interrupted");
|
|
damage[i] = this.combineResults(damage[i], false);
|
|
didAnything = this.combineResults(didAnything, null);
|
|
continue;
|
|
}
|
|
this.battle.add("-heal", target, target.getHealth);
|
|
didSomething = true;
|
|
}
|
|
if (moveData.status) {
|
|
hitResult = target.trySetStatus(moveData.status, source, moveData.ability ? moveData.ability : move);
|
|
if (!hitResult && move.status) {
|
|
damage[i] = this.combineResults(damage[i], false);
|
|
didAnything = this.combineResults(didAnything, null);
|
|
continue;
|
|
}
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.forceStatus) {
|
|
hitResult = target.setStatus(moveData.forceStatus, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.volatileStatus) {
|
|
hitResult = target.addVolatile(moveData.volatileStatus, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.sideCondition) {
|
|
hitResult = target.side.addSideCondition(moveData.sideCondition, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.slotCondition) {
|
|
hitResult = target.side.addSlotCondition(target, moveData.slotCondition, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.weather) {
|
|
hitResult = this.battle.field.setWeather(moveData.weather, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.terrain) {
|
|
hitResult = this.battle.field.setTerrain(moveData.terrain, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.pseudoWeather) {
|
|
hitResult = this.battle.field.addPseudoWeather(moveData.pseudoWeather, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (moveData.forceSwitch) {
|
|
hitResult = !!this.battle.canSwitch(target.side);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (move.target === "all" && !isSelf) {
|
|
if (moveData.onHitField) {
|
|
hitResult = this.battle.singleEvent("HitField", moveData, {}, target, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
} else if ((move.target === "foeSide" || move.target === "allySide") && !isSelf) {
|
|
if (moveData.onHitSide) {
|
|
hitResult = this.battle.singleEvent("HitSide", moveData, {}, target.side, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
} else {
|
|
if (moveData.onHit) {
|
|
hitResult = this.battle.singleEvent("Hit", moveData, {}, target, source, move);
|
|
didSomething = this.combineResults(didSomething, hitResult);
|
|
}
|
|
if (!isSelf && !isSecondary) {
|
|
this.battle.runEvent("Hit", target, source, move);
|
|
}
|
|
}
|
|
}
|
|
if (moveData.selfdestruct === "ifHit" && damage[i] !== false) {
|
|
this.battle.faint(source, source, move);
|
|
}
|
|
if (moveData.selfSwitch) {
|
|
if (this.battle.canSwitch(source.side)) {
|
|
didSomething = true;
|
|
} else {
|
|
didSomething = this.combineResults(didSomething, false);
|
|
}
|
|
}
|
|
if (didSomething === void 0)
|
|
didSomething = true;
|
|
damage[i] = this.combineResults(damage[i], didSomething === null ? false : didSomething);
|
|
didAnything = this.combineResults(didAnything, didSomething);
|
|
}
|
|
if (!didAnything && didAnything !== 0 && !moveData.self && !moveData.selfdestruct) {
|
|
if (!isSelf && !isSecondary) {
|
|
if (didAnything === false) {
|
|
this.battle.add("-fail", source);
|
|
this.battle.attrLastMove("[still]");
|
|
}
|
|
}
|
|
this.battle.debug("move failed because it did nothing");
|
|
} else if (move.selfSwitch && source.hp) {
|
|
source.switchFlag = move.id;
|
|
}
|
|
return damage;
|
|
}
|
|
selfDrops(targets, source, move, moveData, isSecondary) {
|
|
for (const target of targets) {
|
|
if (target === false)
|
|
continue;
|
|
if (moveData.self && !move.selfDropped) {
|
|
if (!isSecondary && moveData.self.boosts) {
|
|
const secondaryRoll = this.battle.random(100);
|
|
if (typeof moveData.self.chance === "undefined" || secondaryRoll < moveData.self.chance) {
|
|
this.moveHit(source, source, move, moveData.self, isSecondary, true);
|
|
}
|
|
if (!move.multihit)
|
|
move.selfDropped = true;
|
|
} else {
|
|
this.moveHit(source, source, move, moveData.self, isSecondary, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
secondaries(targets, source, move, moveData, isSelf) {
|
|
if (!moveData.secondaries)
|
|
return;
|
|
for (const target of targets) {
|
|
if (target === false)
|
|
continue;
|
|
const secondaries = this.battle.runEvent("ModifySecondaries", target, source, moveData, moveData.secondaries.slice());
|
|
for (const secondary of secondaries) {
|
|
const secondaryRoll = this.battle.random(100);
|
|
const secondaryOverflow = (secondary.boosts || secondary.self) && this.battle.gen <= 8;
|
|
if (typeof secondary.chance === "undefined" || secondaryRoll < (secondaryOverflow ? secondary.chance % 256 : secondary.chance)) {
|
|
this.moveHit(target, source, move, secondary, true, isSelf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
forceSwitch(damage, targets, source, move) {
|
|
for (const [i, target] of targets.entries()) {
|
|
if (target && target.hp > 0 && source.hp > 0 && this.battle.canSwitch(target.side)) {
|
|
const hitResult = this.battle.runEvent("DragOut", target, source, move);
|
|
if (hitResult) {
|
|
target.forceSwitchFlag = true;
|
|
} else if (hitResult === false && move.category === "Status") {
|
|
this.battle.add("-fail", source);
|
|
this.battle.attrLastMove("[still]");
|
|
damage[i] = false;
|
|
}
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
moveHit(targets, pokemon, moveOrMoveName, moveData, isSecondary, isSelf) {
|
|
if (!Array.isArray(targets))
|
|
targets = [targets];
|
|
const retVal = this.spreadMoveHit(targets, pokemon, moveOrMoveName, moveData, isSecondary, isSelf)[0][0];
|
|
return retVal === true ? void 0 : retVal;
|
|
}
|
|
calcRecoilDamage(damageDealt, move) {
|
|
return this.battle.clampIntRange(Math.round(damageDealt * move.recoil[0] / move.recoil[1]), 1);
|
|
}
|
|
getZMove(move, pokemon, skipChecks) {
|
|
const item = pokemon.getItem();
|
|
if (!skipChecks) {
|
|
if (pokemon.side.zMoveUsed)
|
|
return;
|
|
if (!item.zMove)
|
|
return;
|
|
if (item.itemUser && !item.itemUser.includes(pokemon.species.name))
|
|
return;
|
|
const moveData = pokemon.getMoveData(move);
|
|
if (!moveData?.pp)
|
|
return;
|
|
}
|
|
if (item.zMoveFrom) {
|
|
if (move.name === item.zMoveFrom)
|
|
return item.zMove;
|
|
} else if (item.zMove === true) {
|
|
if (move.type === item.zMoveType) {
|
|
if (move.category === "Status") {
|
|
return move.name;
|
|
} else if (move.zMove?.basePower) {
|
|
return this.Z_MOVES[move.type];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
getActiveZMove(move, pokemon) {
|
|
if (pokemon) {
|
|
const item = pokemon.getItem();
|
|
if (move.name === item.zMoveFrom) {
|
|
const zMove2 = this.dex.getActiveMove(item.zMove);
|
|
zMove2.isZOrMaxPowered = true;
|
|
return zMove2;
|
|
}
|
|
}
|
|
if (move.category === "Status") {
|
|
const zMove2 = this.dex.getActiveMove(move);
|
|
zMove2.isZ = true;
|
|
zMove2.isZOrMaxPowered = true;
|
|
return zMove2;
|
|
}
|
|
const zMove = this.dex.getActiveMove(this.Z_MOVES[move.type]);
|
|
zMove.basePower = move.zMove.basePower;
|
|
zMove.category = move.category;
|
|
zMove.priority = move.priority;
|
|
zMove.isZOrMaxPowered = true;
|
|
return zMove;
|
|
}
|
|
canZMove(pokemon) {
|
|
if (pokemon.side.zMoveUsed || pokemon.transformed && (pokemon.species.isMega || pokemon.species.isPrimal || pokemon.species.forme === "Ultra"))
|
|
return;
|
|
const item = pokemon.getItem();
|
|
if (!item.zMove)
|
|
return;
|
|
if (item.itemUser && !item.itemUser.includes(pokemon.species.name))
|
|
return;
|
|
let atLeastOne = false;
|
|
let mustStruggle = true;
|
|
const zMoves = [];
|
|
for (const moveSlot of pokemon.moveSlots) {
|
|
if (moveSlot.pp <= 0) {
|
|
zMoves.push(null);
|
|
continue;
|
|
}
|
|
if (!moveSlot.disabled) {
|
|
mustStruggle = false;
|
|
}
|
|
const move = this.dex.moves.get(moveSlot.move);
|
|
let zMoveName = this.getZMove(move, pokemon, true) || "";
|
|
if (zMoveName) {
|
|
const zMove = this.dex.moves.get(zMoveName);
|
|
if (!zMove.isZ && zMove.category === "Status")
|
|
zMoveName = "Z-" + zMoveName;
|
|
zMoves.push({ move: zMoveName, target: zMove.target });
|
|
} else {
|
|
zMoves.push(null);
|
|
}
|
|
if (zMoveName)
|
|
atLeastOne = true;
|
|
}
|
|
if (atLeastOne && !mustStruggle)
|
|
return zMoves;
|
|
}
|
|
getMaxMove(move, pokemon) {
|
|
if (typeof move === "string")
|
|
move = this.dex.moves.get(move);
|
|
if (move.name === "Struggle")
|
|
return move;
|
|
if (pokemon.gigantamax && pokemon.canGigantamax && move.category !== "Status") {
|
|
const gMaxMove = this.dex.moves.get(pokemon.canGigantamax);
|
|
if (gMaxMove.exists && gMaxMove.type === move.type)
|
|
return gMaxMove;
|
|
}
|
|
const maxMove = this.dex.moves.get(this.MAX_MOVES[move.category === "Status" ? move.category : move.type]);
|
|
if (maxMove.exists)
|
|
return maxMove;
|
|
}
|
|
getActiveMaxMove(move, pokemon) {
|
|
if (typeof move === "string")
|
|
move = this.dex.getActiveMove(move);
|
|
if (move.name === "Struggle")
|
|
return this.dex.getActiveMove(move);
|
|
let maxMove = this.dex.getActiveMove(this.MAX_MOVES[move.category === "Status" ? move.category : move.type]);
|
|
if (move.category !== "Status") {
|
|
if (pokemon.gigantamax && pokemon.canGigantamax) {
|
|
const gMaxMove = this.dex.getActiveMove(pokemon.canGigantamax);
|
|
if (gMaxMove.exists && gMaxMove.type === move.type)
|
|
maxMove = gMaxMove;
|
|
}
|
|
if (!move.maxMove?.basePower)
|
|
throw new Error(`${move.name} doesn't have a maxMove basePower`);
|
|
if (!["gmaxdrumsolo", "gmaxfireball", "gmaxhydrosnipe"].includes(maxMove.id)) {
|
|
maxMove.basePower = move.maxMove.basePower;
|
|
}
|
|
maxMove.category = move.category;
|
|
}
|
|
maxMove.baseMove = move.id;
|
|
maxMove.priority = move.priority;
|
|
maxMove.isZOrMaxPowered = true;
|
|
return maxMove;
|
|
}
|
|
runZPower(move, pokemon) {
|
|
const zPower = this.dex.conditions.get("zpower");
|
|
if (move.category !== "Status") {
|
|
this.battle.attrLastMove("[zeffect]");
|
|
} else if (move.zMove?.boost) {
|
|
this.battle.boost(move.zMove.boost, pokemon, pokemon, zPower);
|
|
} else if (move.zMove?.effect) {
|
|
switch (move.zMove.effect) {
|
|
case "heal":
|
|
this.battle.heal(pokemon.maxhp, pokemon, pokemon, zPower);
|
|
break;
|
|
case "healreplacement":
|
|
pokemon.side.addSlotCondition(pokemon, "healreplacement", pokemon, move);
|
|
break;
|
|
case "clearnegativeboost":
|
|
const boosts = {};
|
|
let i;
|
|
for (i in pokemon.boosts) {
|
|
if (pokemon.boosts[i] < 0) {
|
|
boosts[i] = 0;
|
|
}
|
|
}
|
|
pokemon.setBoost(boosts);
|
|
this.battle.add("-clearnegativeboost", pokemon, "[zeffect]");
|
|
break;
|
|
case "redirect":
|
|
pokemon.addVolatile("followme", pokemon, zPower);
|
|
break;
|
|
case "crit2":
|
|
pokemon.addVolatile("focusenergy", pokemon, zPower);
|
|
break;
|
|
case "curse":
|
|
if (pokemon.hasType("Ghost")) {
|
|
this.battle.heal(pokemon.maxhp, pokemon, pokemon, zPower);
|
|
} else {
|
|
this.battle.boost({ atk: 1 }, pokemon, pokemon, zPower);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
targetTypeChoices(targetType) {
|
|
return CHOOSABLE_TARGETS.has(targetType);
|
|
}
|
|
combineResults(left, right) {
|
|
const NOT_FAILURE = "string";
|
|
const NULL = "object";
|
|
const resultsPriorities = ["undefined", NOT_FAILURE, NULL, "boolean", "number"];
|
|
if (resultsPriorities.indexOf(typeof left) > resultsPriorities.indexOf(typeof right)) {
|
|
return left;
|
|
} else if (left && !right && right !== 0) {
|
|
return left;
|
|
} else if (typeof left === "number" && typeof right === "number") {
|
|
return left + right;
|
|
} else {
|
|
return right;
|
|
}
|
|
}
|
|
/**
|
|
* 0 is a success dealing 0 damage, such as from False Swipe at 1 HP.
|
|
*
|
|
* Normal PS return value rules apply:
|
|
* undefined = success, null = silent failure, false = loud failure
|
|
*/
|
|
getDamage(source, target, move, suppressMessages = false) {
|
|
if (typeof move === "string")
|
|
move = this.dex.getActiveMove(move);
|
|
if (typeof move === "number") {
|
|
const basePower2 = move;
|
|
move = new import_dex.Dex.Move({
|
|
basePower: basePower2,
|
|
type: "???",
|
|
category: "Physical",
|
|
willCrit: false
|
|
});
|
|
move.hit = 0;
|
|
}
|
|
if (!move.ignoreImmunity || move.ignoreImmunity !== true && !move.ignoreImmunity[move.type]) {
|
|
if (!target.runImmunity(move.type, !suppressMessages)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (move.ohko)
|
|
return target.maxhp;
|
|
if (move.damageCallback)
|
|
return move.damageCallback.call(this.battle, source, target);
|
|
if (move.damage === "level") {
|
|
return source.level;
|
|
} else if (move.damage) {
|
|
return move.damage;
|
|
}
|
|
const category = this.battle.getCategory(move);
|
|
let basePower = move.basePower;
|
|
if (move.basePowerCallback) {
|
|
basePower = move.basePowerCallback.call(this.battle, source, target, move);
|
|
}
|
|
if (!basePower)
|
|
return basePower === 0 ? void 0 : basePower;
|
|
basePower = this.battle.clampIntRange(basePower, 1);
|
|
let critMult;
|
|
let critRatio = this.battle.runEvent("ModifyCritRatio", source, target, move, move.critRatio || 0);
|
|
if (this.battle.gen <= 5) {
|
|
critRatio = this.battle.clampIntRange(critRatio, 0, 5);
|
|
critMult = [0, 16, 8, 4, 3, 2];
|
|
} else {
|
|
critRatio = this.battle.clampIntRange(critRatio, 0, 4);
|
|
if (this.battle.gen === 6) {
|
|
critMult = [0, 16, 8, 2, 1];
|
|
} else {
|
|
critMult = [0, 24, 8, 2, 1];
|
|
}
|
|
}
|
|
const moveHit = target.getMoveHitData(move);
|
|
moveHit.crit = move.willCrit || false;
|
|
if (move.willCrit === void 0) {
|
|
if (critRatio) {
|
|
moveHit.crit = this.battle.randomChance(1, critMult[critRatio]);
|
|
}
|
|
}
|
|
if (moveHit.crit) {
|
|
moveHit.crit = this.battle.runEvent("CriticalHit", target, null, move);
|
|
}
|
|
basePower = this.battle.runEvent("BasePower", source, target, move, basePower, true);
|
|
if (!basePower)
|
|
return 0;
|
|
basePower = this.battle.clampIntRange(basePower, 1);
|
|
if (!source.volatiles["dynamax"] && move.isMax || move.isMax && this.dex.moves.get(move.baseMove).isMax) {
|
|
basePower = 0;
|
|
}
|
|
if (basePower < 60 && source.getTypes(true).includes(move.type) && source.terastallized && move.priority <= 0 && // Hard move.basePower check for moves like Dragon Energy that have variable BP
|
|
!move.multihit && !((move.basePower === 0 || move.basePower === 150) && move.basePowerCallback)) {
|
|
basePower = 60;
|
|
}
|
|
const level = source.level;
|
|
const attacker = move.overrideOffensivePokemon === "target" ? target : source;
|
|
const defender = move.overrideDefensivePokemon === "source" ? source : target;
|
|
const isPhysical = move.category === "Physical";
|
|
let attackStat = move.overrideOffensiveStat || (isPhysical ? "atk" : "spa");
|
|
const defenseStat = move.overrideDefensiveStat || (isPhysical ? "def" : "spd");
|
|
const statTable = { atk: "Atk", def: "Def", spa: "SpA", spd: "SpD", spe: "Spe" };
|
|
let atkBoosts = attacker.boosts[attackStat];
|
|
let defBoosts = defender.boosts[defenseStat];
|
|
let ignoreNegativeOffensive = !!move.ignoreNegativeOffensive;
|
|
let ignorePositiveDefensive = !!move.ignorePositiveDefensive;
|
|
if (moveHit.crit) {
|
|
ignoreNegativeOffensive = true;
|
|
ignorePositiveDefensive = true;
|
|
}
|
|
const ignoreOffensive = !!(move.ignoreOffensive || ignoreNegativeOffensive && atkBoosts < 0);
|
|
const ignoreDefensive = !!(move.ignoreDefensive || ignorePositiveDefensive && defBoosts > 0);
|
|
if (ignoreOffensive) {
|
|
this.battle.debug("Negating (sp)atk boost/penalty.");
|
|
atkBoosts = 0;
|
|
}
|
|
if (ignoreDefensive) {
|
|
this.battle.debug("Negating (sp)def boost/penalty.");
|
|
defBoosts = 0;
|
|
}
|
|
let attack = attacker.calculateStat(attackStat, atkBoosts, 1, source);
|
|
let defense = defender.calculateStat(defenseStat, defBoosts, 1, target);
|
|
attackStat = category === "Physical" ? "atk" : "spa";
|
|
attack = this.battle.runEvent("Modify" + statTable[attackStat], source, target, move, attack);
|
|
defense = this.battle.runEvent("Modify" + statTable[defenseStat], target, source, move, defense);
|
|
if (this.battle.gen <= 4 && ["explosion", "selfdestruct"].includes(move.id) && defenseStat === "def") {
|
|
defense = this.battle.clampIntRange(Math.floor(defense / 2), 1);
|
|
}
|
|
const tr = this.battle.trunc;
|
|
const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * attack) / defense) / 50);
|
|
return this.modifyDamage(baseDamage, source, target, move, suppressMessages);
|
|
}
|
|
modifyDamage(baseDamage, pokemon, target, move, suppressMessages = false) {
|
|
const tr = this.battle.trunc;
|
|
if (!move.type)
|
|
move.type = "???";
|
|
const type = move.type;
|
|
baseDamage += 2;
|
|
if (move.spreadHit) {
|
|
const spreadModifier = move.spreadModifier || (this.battle.gameType === "freeforall" ? 0.5 : 0.75);
|
|
this.battle.debug("Spread modifier: " + spreadModifier);
|
|
baseDamage = this.battle.modify(baseDamage, spreadModifier);
|
|
} else if (move.multihitType === "parentalbond" && move.hit > 1) {
|
|
const bondModifier = this.battle.gen > 6 ? 0.25 : 0.5;
|
|
this.battle.debug(`Parental Bond modifier: ${bondModifier}`);
|
|
baseDamage = this.battle.modify(baseDamage, bondModifier);
|
|
}
|
|
baseDamage = this.battle.runEvent("WeatherModifyDamage", pokemon, target, move, baseDamage);
|
|
const isCrit = target.getMoveHitData(move).crit;
|
|
if (isCrit) {
|
|
baseDamage = tr(baseDamage * (move.critModifier || (this.battle.gen >= 6 ? 1.5 : 2)));
|
|
}
|
|
baseDamage = this.battle.randomizer(baseDamage);
|
|
if (move.forceSTAB || type !== "???" && (pokemon.hasType(type) || pokemon.terastallized && pokemon.getTypes(false, true).includes(type))) {
|
|
let stab = move.stab || 1.5;
|
|
if (type === pokemon.terastallized && pokemon.getTypes(false, true).includes(type)) {
|
|
stab = stab === 2 ? 2.25 : 2;
|
|
} else if (pokemon.terastallized && type !== pokemon.terastallized) {
|
|
stab = 1.5;
|
|
}
|
|
baseDamage = this.battle.modify(baseDamage, stab);
|
|
}
|
|
let typeMod = target.runEffectiveness(move);
|
|
typeMod = this.battle.clampIntRange(typeMod, -6, 6);
|
|
target.getMoveHitData(move).typeMod = typeMod;
|
|
if (typeMod > 0) {
|
|
if (!suppressMessages)
|
|
this.battle.add("-supereffective", target);
|
|
for (let i = 0; i < typeMod; i++) {
|
|
baseDamage *= 2;
|
|
}
|
|
}
|
|
if (typeMod < 0) {
|
|
if (!suppressMessages)
|
|
this.battle.add("-resisted", target);
|
|
for (let i = 0; i > typeMod; i--) {
|
|
baseDamage = tr(baseDamage / 2);
|
|
}
|
|
}
|
|
if (isCrit && !suppressMessages)
|
|
this.battle.add("-crit", target);
|
|
if (pokemon.status === "brn" && move.category === "Physical" && !pokemon.hasAbility("guts")) {
|
|
if (this.battle.gen < 6 || move.id !== "facade") {
|
|
baseDamage = this.battle.modify(baseDamage, 0.5);
|
|
}
|
|
}
|
|
if (this.battle.gen === 5 && !baseDamage)
|
|
baseDamage = 1;
|
|
baseDamage = this.battle.runEvent("ModifyDamage", pokemon, target, move, baseDamage);
|
|
if (move.isZOrMaxPowered && target.getMoveHitData(move).zBrokeProtect) {
|
|
baseDamage = this.battle.modify(baseDamage, 0.25);
|
|
this.battle.add("-zbroken", target);
|
|
}
|
|
if (this.battle.gen !== 5 && !baseDamage)
|
|
return 1;
|
|
return tr(baseDamage, 16);
|
|
}
|
|
/**
|
|
* Confusion damage is unique - most typical modifiers that get run when calculating
|
|
* damage (e.g. Huge Power, Life Orb, critical hits) don't apply. It also uses a 16-bit
|
|
* context for its damage, unlike the regular damage formula (though this only comes up
|
|
* for base damage).
|
|
*/
|
|
getConfusionDamage(pokemon, basePower) {
|
|
const tr = this.battle.trunc;
|
|
const attack = pokemon.calculateStat("atk", pokemon.boosts["atk"]);
|
|
const defense = pokemon.calculateStat("def", pokemon.boosts["def"]);
|
|
const level = pokemon.level;
|
|
const baseDamage = tr(tr(tr(tr(2 * level / 5 + 2) * basePower * attack) / defense) / 50) + 2;
|
|
let damage = tr(baseDamage, 16);
|
|
damage = this.battle.randomizer(damage);
|
|
return Math.max(1, damage);
|
|
}
|
|
// #endregion
|
|
// #region MEGA EVOLUTION
|
|
// ==================================================================
|
|
canMegaEvo(pokemon) {
|
|
const species = pokemon.baseSpecies;
|
|
const altForme = species.otherFormes && this.dex.species.get(species.otherFormes[0]);
|
|
const item = pokemon.getItem();
|
|
if ((this.battle.gen <= 7 || this.battle.ruleTable.has("+pokemontag:past")) && altForme?.isMega && altForme?.requiredMove && pokemon.baseMoves.includes((0, import_dex.toID)(altForme.requiredMove)) && !item.zMove) {
|
|
return altForme.name;
|
|
}
|
|
if (item.megaEvolves === species.baseSpecies && item.megaStone !== species.name) {
|
|
return item.megaStone;
|
|
}
|
|
return null;
|
|
}
|
|
canUltraBurst(pokemon) {
|
|
if (["Necrozma-Dawn-Wings", "Necrozma-Dusk-Mane"].includes(pokemon.baseSpecies.name) && pokemon.getItem().id === "ultranecroziumz") {
|
|
return "Necrozma-Ultra";
|
|
}
|
|
return null;
|
|
}
|
|
runMegaEvo(pokemon) {
|
|
const speciesid = pokemon.canMegaEvo || pokemon.canUltraBurst;
|
|
if (!speciesid)
|
|
return false;
|
|
pokemon.formeChange(speciesid, pokemon.getItem(), true);
|
|
const wasMega = pokemon.canMegaEvo;
|
|
for (const ally of pokemon.side.pokemon) {
|
|
if (wasMega) {
|
|
ally.canMegaEvo = null;
|
|
} else {
|
|
ally.canUltraBurst = null;
|
|
}
|
|
}
|
|
this.battle.runEvent("AfterMega", pokemon);
|
|
return true;
|
|
}
|
|
canTerastallize(pokemon) {
|
|
if (pokemon.species.isMega || pokemon.species.isPrimal || pokemon.species.forme === "Ultra" || pokemon.getItem().zMove || pokemon.canMegaEvo || pokemon.side.canDynamaxNow() || this.dex.gen !== 9) {
|
|
return null;
|
|
}
|
|
return pokemon.teraType;
|
|
}
|
|
terastallize(pokemon) {
|
|
const type = pokemon.teraType;
|
|
this.battle.add("-terastallize", pokemon, type);
|
|
pokemon.terastallized = type;
|
|
for (const ally of pokemon.side.pokemon) {
|
|
ally.canTerastallize = null;
|
|
}
|
|
pokemon.addedType = "";
|
|
pokemon.knownType = true;
|
|
pokemon.apparentType = type;
|
|
this.battle.runEvent("AfterTerastallization", pokemon);
|
|
}
|
|
// #endregion
|
|
}
|
|
//# sourceMappingURL=battle-actions.js.map
|