mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-24 00:36:27 -05:00
751 lines
26 KiB
JavaScript
751 lines
26 KiB
JavaScript
'use strict';
|
||
|
||
exports.BattleScripts = {
|
||
runMove: function (move, pokemon, targetLoc, sourceEffect, zMove, externalMove) {
|
||
let target = this.getTarget(pokemon, zMove || move, targetLoc);
|
||
if (!sourceEffect && toId(move) !== 'struggle' && !zMove) {
|
||
let changedMove = this.runEvent('OverrideAction', pokemon, target, move);
|
||
if (changedMove && changedMove !== true) {
|
||
move = changedMove;
|
||
target = null;
|
||
}
|
||
}
|
||
let baseMove = this.getMove(move);
|
||
move = zMove ? this.getZMoveCopy(baseMove, pokemon) : baseMove;
|
||
if (!target && target !== false) target = this.resolveTarget(pokemon, move);
|
||
|
||
// copy the priority for Quick Guard
|
||
if (zMove) move.priority = baseMove.priority;
|
||
move.isExternal = externalMove;
|
||
|
||
this.setActiveMove(move, pokemon, target);
|
||
|
||
/* if (pokemon.moveThisTurn) {
|
||
// THIS IS PURELY A SANITY CHECK
|
||
// DO NOT TAKE ADVANTAGE OF THIS TO PREVENT A POKEMON FROM MOVING;
|
||
// USE this.cancelMove INSTEAD
|
||
this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn);
|
||
this.clearActiveMove(true);
|
||
return;
|
||
} */
|
||
let willTryMove = this.runEvent('BeforeMove', pokemon, target, move);
|
||
if (!willTryMove) {
|
||
if (pokemon.volatiles['twoturnmove'] && pokemon.volatiles['twoturnmove'].move === move.id) { // Linked mod
|
||
pokemon.removeVolatile('twoturnmove');
|
||
}
|
||
this.runEvent('MoveAborted', pokemon, target, move);
|
||
this.clearActiveMove(true);
|
||
// The event 'BeforeMove' could have returned false or null
|
||
// false indicates that this counts as a move failing for the purpose of calculating Stomping Tantrum's base power
|
||
// null indicates the opposite, as the Pokemon didn't have an option to choose anything
|
||
pokemon.moveThisTurnResult = willTryMove;
|
||
return;
|
||
}
|
||
if (move.beforeMoveCallback) {
|
||
if (move.beforeMoveCallback.call(this, pokemon, target, move)) {
|
||
this.clearActiveMove(true);
|
||
pokemon.moveThisTurnResult = false;
|
||
return;
|
||
}
|
||
}
|
||
pokemon.lastDamage = 0;
|
||
let lockedMove;
|
||
if (!externalMove) {
|
||
lockedMove = this.runEvent('LockMove', pokemon);
|
||
if (lockedMove === true) lockedMove = false;
|
||
if (!lockedMove) {
|
||
if (!pokemon.deductPP(baseMove, null, target) && (move.id !== 'struggle')) {
|
||
this.add('cant', pokemon, 'nopp', move);
|
||
let gameConsole = [null, 'Game Boy', 'Game Boy', 'Game Boy Advance', 'DS', 'DS'][this.gen] || '3DS';
|
||
this.add('-hint', "This is not a bug, this is really how it works on the " + gameConsole + "; try it yourself if you don't believe us.");
|
||
this.clearActiveMove(true);
|
||
pokemon.moveThisTurnResult = false;
|
||
return;
|
||
}
|
||
} else {
|
||
sourceEffect = this.getEffect('lockedmove');
|
||
}
|
||
pokemon.moveUsed(move, targetLoc);
|
||
}
|
||
|
||
// Dancer Petal Dance hack
|
||
// TODO: implement properly
|
||
let noLock = externalMove && !pokemon.volatiles.lockedmove;
|
||
|
||
if (zMove) {
|
||
if (pokemon.illusion) {
|
||
this.singleEvent('End', this.getAbility('Illusion'), pokemon.abilityData, pokemon);
|
||
}
|
||
this.add('-zpower', pokemon);
|
||
pokemon.side.zMoveUsed = true;
|
||
}
|
||
let moveDidSomething = this.useMove(baseMove, pokemon, target, sourceEffect, zMove);
|
||
this.singleEvent('AfterMove', move, null, pokemon, target, move);
|
||
this.runEvent('AfterMove', pokemon, target, move);
|
||
|
||
// Dancer's activation order is completely different from any other event, so it's handled separately
|
||
if (move.flags['dance'] && moveDidSomething && !move.isExternal) {
|
||
let dancers = [];
|
||
for (const side of this.sides) {
|
||
for (const currentPoke of side.active) {
|
||
if (!currentPoke || !currentPoke.hp || pokemon === currentPoke) continue;
|
||
if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) {
|
||
dancers.push(currentPoke);
|
||
}
|
||
}
|
||
}
|
||
// Dancer activates in order of lowest speed stat to highest
|
||
// Ties go to whichever Pokemon has had the ability for the least amount of time
|
||
dancers.sort(function (a, b) { return -(b.stats['spe'] - a.stats['spe']) || b.abilityOrder - a.abilityOrder; });
|
||
for (const dancer of dancers) {
|
||
if (this.faintMessages()) break;
|
||
this.add('-activate', dancer, 'ability: Dancer');
|
||
this.runMove(baseMove.id, dancer, 0, this.getAbility('dancer'), undefined, true);
|
||
}
|
||
}
|
||
if (noLock && pokemon.volatiles.lockedmove) delete pokemon.volatiles.lockedmove;
|
||
},
|
||
tryMoveHit: function (target, pokemon, move) {
|
||
this.setActiveMove(move, pokemon, target);
|
||
move.zBrokeProtect = false;
|
||
let hitResult = true;
|
||
hitResult = this.singleEvent('PrepareHit', move, {}, target, pokemon, move);
|
||
if (!hitResult) {
|
||
if (hitResult === false) this.add('-fail', target);
|
||
return false;
|
||
}
|
||
this.runEvent('PrepareHit', pokemon, target, move);
|
||
if (!this.singleEvent('Try', move, null, pokemon, target, move)) return false;
|
||
if (['all', 'foeSide', 'allySide', 'allyTeam'].includes(move.target)) {
|
||
if (move.target === 'all') {
|
||
hitResult = this.runEvent('TryHitField', target, pokemon, move);
|
||
} else {
|
||
hitResult = this.runEvent('TryHitSide', target, pokemon, move);
|
||
}
|
||
if (!hitResult) {
|
||
if (hitResult === false) this.add('-fail', target);
|
||
return false;
|
||
}
|
||
return this.moveHit(target, pokemon, move);
|
||
}
|
||
if (move.ignoreImmunity === undefined) move.ignoreImmunity = (move.category === 'Status');
|
||
if (this.gen < 7 && (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) && !target.runImmunity(move.type, true)) return false;
|
||
hitResult = this.runEvent('TryHit', target, pokemon, move);
|
||
if (!hitResult) {
|
||
if (hitResult === false) this.add('-fail', target);
|
||
return false;
|
||
}
|
||
if (this.gen >= 7 && (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) && !target.runImmunity(move.type, true)) return false;
|
||
if (move.flags['powder'] && target !== pokemon && !this.getImmunity('powder', target)) {
|
||
this.debug('natural powder immunity');
|
||
this.add('-immune', target, '[msg]');
|
||
return false;
|
||
}
|
||
if (this.gen >= 7 && pokemon.hasAbility('prankster') && target.side !== pokemon.side && !this.getImmunity('prankster', target)) {
|
||
let linkedMoves = pokemon.getLinkedMoves();
|
||
this.debug('natural prankster immunity');
|
||
if (!target.illusion) this.add('-hint', "In gen 7, Dark is immune to Prankster moves.");
|
||
if (this.getMove(linkedMoves[0]).pranksterBoosted) this.add('-immune', target, '[msg]');
|
||
if (this.getMove(linkedMoves[1]).pranksterBoosted) this.add('-immune', target, '[msg]');
|
||
if (move.pranksterBoosted) this.add('-immune', target, '[msg]');
|
||
return false;
|
||
}
|
||
|
||
let boostTable = [1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3];
|
||
|
||
// calculate true accuracy
|
||
let accuracy = move.accuracy;
|
||
let boosts, boost;
|
||
if (accuracy !== true) {
|
||
if (!move.ignoreAccuracy) {
|
||
boosts = this.runEvent('ModifyBoost', pokemon, null, null, Object.assign({}, pokemon.boosts));
|
||
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
|
||
if (boost > 0) {
|
||
accuracy *= boostTable[boost];
|
||
} else {
|
||
accuracy /= boostTable[-boost];
|
||
}
|
||
}
|
||
if (!move.ignoreEvasion) {
|
||
boosts = this.runEvent('ModifyBoost', target, null, null, Object.assign({}, target.boosts));
|
||
boost = this.clampIntRange(boosts['evasion'], -6, 6);
|
||
if (boost > 0) {
|
||
accuracy /= boostTable[boost];
|
||
} else if (boost < 0) {
|
||
accuracy *= boostTable[-boost];
|
||
}
|
||
}
|
||
}
|
||
if (move.ohko) { // bypasses accuracy modifiers
|
||
if (!target.isSemiInvulnerable()) {
|
||
accuracy = 30;
|
||
if (move.ohko === 'Ice' && this.gen >= 7 && !pokemon.hasType('Ice')) {
|
||
accuracy = 20;
|
||
}
|
||
if (pokemon.level >= target.level && (move.ohko === true || !target.hasType(move.ohko))) {
|
||
accuracy += (pokemon.level - target.level);
|
||
} else {
|
||
this.add('-immune', target, '[ohko]');
|
||
return false;
|
||
}
|
||
}
|
||
} else {
|
||
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||
}
|
||
if (move.alwaysHit || (move.id === 'toxic' && this.gen >= 6 && pokemon.hasType('Poison'))) {
|
||
accuracy = true; // bypasses ohko accuracy modifiers
|
||
} else {
|
||
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||
}
|
||
// @ts-ignore
|
||
if (accuracy !== true && !this.randomChance(accuracy, 100)) {
|
||
if (!move.spreadHit) this.attrLastMove('[miss]');
|
||
this.add('-miss', pokemon, target);
|
||
return false;
|
||
}
|
||
|
||
if (move.breaksProtect) {
|
||
let broke = false;
|
||
for (const effectid of ['banefulbunker', 'kingsshield', 'protect', 'spikyshield']) {
|
||
if (target.removeVolatile(effectid)) broke = true;
|
||
}
|
||
if (this.gen >= 6 || target.side !== pokemon.side) {
|
||
for (const effectid of ['craftyshield', 'matblock', 'quickguard', 'wideguard']) {
|
||
if (target.side.removeSideCondition(effectid)) broke = true;
|
||
}
|
||
}
|
||
if (broke) {
|
||
if (move.id === 'feint') {
|
||
this.add('-activate', target, 'move: Feint');
|
||
} else {
|
||
this.add('-activate', target, 'move: ' + move.name, '[broken]');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (move.stealsBoosts) {
|
||
let boosts = {};
|
||
let stolen = false;
|
||
for (let statName in target.boosts) {
|
||
let stage = target.boosts[statName];
|
||
if (stage > 0) {
|
||
boosts[statName] = stage;
|
||
stolen = true;
|
||
}
|
||
}
|
||
if (stolen) {
|
||
this.attrLastMove('[still]');
|
||
this.add('-clearpositiveboost', target, pokemon, 'move: ' + move.name);
|
||
this.boost(boosts, pokemon, pokemon);
|
||
|
||
for (let statName in boosts) {
|
||
boosts[statName] = 0;
|
||
}
|
||
target.setBoost(boosts);
|
||
this.add('-anim', pokemon, "Spectral Thief", target);
|
||
}
|
||
}
|
||
|
||
move.totalDamage = 0;
|
||
/**@type {number | false} */
|
||
let damage = 0;
|
||
pokemon.lastDamage = 0;
|
||
if (move.multihit) {
|
||
let hits = move.multihit;
|
||
if (Array.isArray(hits)) {
|
||
// yes, it's hardcoded... meh
|
||
if (hits[0] === 2 && hits[1] === 5) {
|
||
if (this.gen >= 5) {
|
||
hits = this.sample([2, 2, 3, 3, 4, 5]);
|
||
} else {
|
||
hits = this.sample([2, 2, 2, 3, 3, 3, 4, 5]);
|
||
}
|
||
} else {
|
||
hits = this.random(hits[0], hits[1] + 1);
|
||
}
|
||
}
|
||
hits = Math.floor(hits);
|
||
let nullDamage = true;
|
||
/**@type {number | false} */
|
||
let moveDamage;
|
||
// There is no need to recursively check the ´sleepUsable´ flag as Sleep Talk can only be used while asleep.
|
||
let isSleepUsable = move.sleepUsable || this.getMove(move.sourceEffect).sleepUsable;
|
||
let i;
|
||
for (i = 0; i < hits && target.hp && pokemon.hp; i++) {
|
||
if (pokemon.status === 'slp' && !isSleepUsable) break;
|
||
|
||
if (move.multiaccuracy && i > 0) {
|
||
accuracy = move.accuracy;
|
||
if (accuracy !== true) {
|
||
if (!move.ignoreAccuracy) {
|
||
boosts = this.runEvent('ModifyBoost', pokemon, null, null, Object.assign({}, pokemon.boosts));
|
||
boost = this.clampIntRange(boosts['accuracy'], -6, 6);
|
||
if (boost > 0) {
|
||
accuracy *= boostTable[boost];
|
||
} else {
|
||
accuracy /= boostTable[-boost];
|
||
}
|
||
}
|
||
if (!move.ignoreEvasion) {
|
||
boosts = this.runEvent('ModifyBoost', target, null, null, Object.assign({}, target.boosts));
|
||
boost = this.clampIntRange(boosts['evasion'], -6, 6);
|
||
if (boost > 0) {
|
||
accuracy /= boostTable[boost];
|
||
} else if (boost < 0) {
|
||
accuracy *= boostTable[-boost];
|
||
}
|
||
}
|
||
}
|
||
accuracy = this.runEvent('ModifyAccuracy', target, pokemon, move, accuracy);
|
||
if (!move.alwaysHit) {
|
||
accuracy = this.runEvent('Accuracy', target, pokemon, move, accuracy);
|
||
// @ts-ignore
|
||
if (accuracy !== true && !this.randomChance(accuracy, 100)) break;
|
||
}
|
||
}
|
||
|
||
moveDamage = this.moveHit(target, pokemon, move);
|
||
if (moveDamage === false) break;
|
||
if (nullDamage && (moveDamage || moveDamage === 0 || moveDamage === undefined)) nullDamage = false;
|
||
// Damage from each hit is individually counted for the
|
||
// purposes of Counter, Metal Burst, and Mirror Coat.
|
||
damage = (moveDamage || 0);
|
||
// Total damage dealt is accumulated for the purposes of recoil (Parental Bond).
|
||
move.totalDamage += damage;
|
||
if (move.mindBlownRecoil && i === 0) {
|
||
this.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.getEffect('Mind Blown'), true);
|
||
}
|
||
this.eachEvent('Update');
|
||
}
|
||
if (i === 0) return false;
|
||
if (nullDamage) damage = false;
|
||
this.add('-hitcount', target, i);
|
||
} else {
|
||
damage = this.moveHit(target, pokemon, move);
|
||
move.totalDamage = damage;
|
||
}
|
||
|
||
if (move.recoil && move.totalDamage) {
|
||
this.damage(this.calcRecoilDamage(move.totalDamage, move), pokemon, pokemon, 'recoil');
|
||
}
|
||
|
||
if (move.struggleRecoil) {
|
||
// @ts-ignore
|
||
this.directDamage(this.clampIntRange(Math.round(pokemon.maxhp / 4), 1), pokemon, pokemon, {id: 'strugglerecoil'});
|
||
}
|
||
|
||
if (target && pokemon !== target) target.gotAttacked(move, damage, pokemon);
|
||
|
||
if (move.ohko) this.add('-ohko');
|
||
|
||
if (!damage && damage !== 0) return damage;
|
||
|
||
this.eachEvent('Update');
|
||
|
||
if (target && !move.negateSecondary && !(move.hasSheerForce && pokemon.hasAbility('sheerforce'))) {
|
||
this.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move);
|
||
this.runEvent('AfterMoveSecondary', target, pokemon, move);
|
||
}
|
||
|
||
return damage;
|
||
},
|
||
resolveAction: function (action, midTurn = false) {
|
||
if (!action) throw new Error(`Action not passed to resolveAction`);
|
||
|
||
if (!action.side && action.pokemon) action.side = action.pokemon.side;
|
||
if (!action.move && action.moveid) action.move = this.getMoveCopy(action.moveid);
|
||
if (!action.choice && action.move) action.choice = 'move';
|
||
if (!action.priority && action.priority !== 0) {
|
||
let priorities = {
|
||
'beforeTurn': 100,
|
||
'beforeTurnMove': 99,
|
||
'switch': 7,
|
||
'runUnnerve': 7.3,
|
||
'runSwitch': 7.2,
|
||
'runPrimal': 7.1,
|
||
'instaswitch': 101,
|
||
'megaEvo': 6.9,
|
||
'residual': -100,
|
||
'team': 102,
|
||
'start': 101,
|
||
};
|
||
if (action.choice in priorities) {
|
||
action.priority = priorities[action.choice];
|
||
}
|
||
}
|
||
if (!midTurn) {
|
||
if (action.choice === 'move') {
|
||
if (!action.zmove && action.move.beforeTurnCallback) {
|
||
this.addToQueue({choice: 'beforeTurnMove', pokemon: action.pokemon, move: action.move, targetLoc: action.targetLoc});
|
||
}
|
||
if (action.mega) {
|
||
// TODO: Check that the Pokémon is not affected by Sky Drop.
|
||
// (This is currently being done in `runMegaEvo`).
|
||
this.addToQueue({
|
||
choice: 'megaEvo',
|
||
pokemon: action.pokemon,
|
||
});
|
||
}
|
||
|
||
let linkedMoves = action.pokemon.getLinkedMoves();
|
||
if (linkedMoves.length && !linkedMoves.disabled && !action.pokemon.getItem().isChoice && !action.zmove) {
|
||
let decisionMove = toId(action.move);
|
||
if (linkedMoves.includes(decisionMove)) {
|
||
// flag the move as linked here
|
||
action.linked = linkedMoves;
|
||
if (this.getMove(linkedMoves[1 - linkedMoves.indexOf(decisionMove)]).beforeTurnCallback) {
|
||
this.addToQueue({choice: 'beforeTurnMove', pokemon: action.pokemon, move: linkedMoves[1 - linkedMoves.indexOf(decisionMove)], targetLoc: action.targetLoc});
|
||
}
|
||
}
|
||
}
|
||
} else if (action.choice === 'switch' || action.choice === 'instaswitch') {
|
||
if (typeof action.pokemon.switchFlag === 'string') {
|
||
action.sourceEffect = this.getEffect(action.pokemon.switchFlag);
|
||
}
|
||
action.pokemon.switchFlag = false;
|
||
if (!action.speed) action.speed = action.pokemon.getActionSpeed();
|
||
}
|
||
}
|
||
|
||
let deferPriority = this.gen >= 7 && action.mega && action.mega !== 'done';
|
||
if (action.move) {
|
||
let target = null;
|
||
action.move = this.getMoveCopy(action.move);
|
||
|
||
if (!action.targetLoc) {
|
||
target = this.resolveTarget(action.pokemon, action.move);
|
||
action.targetLoc = this.getTargetLoc(target, action.pokemon);
|
||
}
|
||
|
||
if (!action.priority && !deferPriority) {
|
||
let move = action.move;
|
||
if (action.zmove) {
|
||
// @ts-ignore
|
||
let zMoveName = this.getZMove(action.move, action.pokemon, true);
|
||
let zMove = this.getMove(zMoveName);
|
||
if (zMove.exists) {
|
||
move = zMove;
|
||
}
|
||
}
|
||
let priority = this.runEvent('ModifyPriority', action.pokemon, target, move, move.priority);
|
||
|
||
let linkedMoves = action.pokemon.getLinkedMoves();
|
||
if (linkedMoves.length && !linkedMoves.disabled && !move.isZ) {
|
||
let actionMove = toId(action.move);
|
||
let index = linkedMoves.indexOf(actionMove);
|
||
if (index !== -1) {
|
||
let altMove = this.getMoveCopy(linkedMoves[1 - index]);
|
||
let altPriority = this.runEvent('ModifyPriority', action.pokemon, target, altMove, altMove.priority);
|
||
priority = Math.min(priority, altPriority);
|
||
}
|
||
}
|
||
|
||
action.priority = priority;
|
||
// In Gen 6, Quick Guard blocks moves with artificially enhanced priority.
|
||
if (this.gen > 5) action.move.priority = priority;
|
||
}
|
||
}
|
||
if (!action.speed) {
|
||
if ((action.choice === 'switch' || action.choice === 'instaswitch') && action.target) {
|
||
action.speed = action.target.getActionSpeed();
|
||
} else if (!action.pokemon) {
|
||
action.speed = 1;
|
||
} else if (!deferPriority) {
|
||
action.speed = action.pokemon.getActionSpeed();
|
||
}
|
||
}
|
||
return /** @type {any} */ (action);
|
||
},
|
||
|
||
runAction: function (action) {
|
||
// returns whether or not we ended in a callback
|
||
switch (action.choice) {
|
||
case 'start': {
|
||
// I GIVE UP, WILL WRESTLE WITH EVENT SYSTEM LATER
|
||
let format = this.getFormat();
|
||
|
||
// Remove Pokémon duplicates remaining after `team` decisions.
|
||
this.p1.pokemon = this.p1.pokemon.slice(0, this.p1.pokemonLeft);
|
||
this.p2.pokemon = this.p2.pokemon.slice(0, this.p2.pokemonLeft);
|
||
|
||
if (format.teamLength && format.teamLength.battle) {
|
||
// Trim the team: not all of the Pokémon brought to Preview will battle.
|
||
this.p1.pokemon = this.p1.pokemon.slice(0, format.teamLength.battle);
|
||
this.p1.pokemonLeft = this.p1.pokemon.length;
|
||
this.p2.pokemon = this.p2.pokemon.slice(0, format.teamLength.battle);
|
||
this.p2.pokemonLeft = this.p2.pokemon.length;
|
||
}
|
||
|
||
this.add('start');
|
||
for (let pos = 0; pos < this.p1.active.length; pos++) {
|
||
this.switchIn(this.p1.pokemon[pos], pos);
|
||
}
|
||
for (let pos = 0; pos < this.p2.active.length; pos++) {
|
||
this.switchIn(this.p2.pokemon[pos], pos);
|
||
}
|
||
for (const pokemon of this.p1.pokemon) {
|
||
this.singleEvent('Start', this.getEffect(pokemon.species), pokemon.speciesData, pokemon);
|
||
}
|
||
for (const pokemon of this.p2.pokemon) {
|
||
this.singleEvent('Start', this.getEffect(pokemon.species), pokemon.speciesData, pokemon);
|
||
}
|
||
this.midTurn = true;
|
||
break;
|
||
}
|
||
|
||
case 'move':
|
||
if (!action.pokemon.isActive) return false;
|
||
if (action.pokemon.fainted) return false;
|
||
// This is where moves are linked
|
||
if (action.linked) {
|
||
let linkedMoves = action.linked;
|
||
for (let i = linkedMoves.length - 1; i >= 0; i--) {
|
||
let pseudoAction = {choice: 'move', move: linkedMoves[i], targetLoc: action.targetLoc, pokemon: action.pokemon, targetPosition: action.targetPosition, targetSide: action.targetSide};
|
||
this.queue.unshift(pseudoAction);
|
||
}
|
||
return;
|
||
}
|
||
// @ts-ignore
|
||
this.runMove(action.move, action.pokemon, action.targetLoc, action.sourceEffect, action.zmove);
|
||
break;
|
||
case 'megaEvo':
|
||
// @ts-ignore
|
||
this.runMegaEvo(action.pokemon);
|
||
break;
|
||
case 'beforeTurnMove': {
|
||
if (!action.pokemon.isActive) return false;
|
||
if (action.pokemon.fainted) return false;
|
||
this.debug('before turn callback: ' + action.move.id);
|
||
let target = this.getTarget(action.pokemon, action.move, action.targetLoc);
|
||
if (!target) return false;
|
||
if (!action.move.beforeTurnCallback) throw new Error(`beforeTurnMove has no beforeTurnCallback`);
|
||
action.move.beforeTurnCallback.call(this, action.pokemon, target);
|
||
break;
|
||
}
|
||
|
||
case 'event':
|
||
// @ts-ignore Easier than defining a custom event attribute tbh
|
||
this.runEvent(action.event, action.pokemon);
|
||
break;
|
||
case 'team': {
|
||
action.pokemon.side.pokemon.splice(action.index, 0, action.pokemon);
|
||
action.pokemon.position = action.index;
|
||
// we return here because the update event would crash since there are no active pokemon yet
|
||
return;
|
||
}
|
||
|
||
case 'pass':
|
||
return;
|
||
case 'instaswitch':
|
||
case 'switch':
|
||
if (action.choice === 'switch' && action.pokemon.status && this.data.Abilities.naturalcure) {
|
||
this.singleEvent('CheckShow', this.getAbility('naturalcure'), null, action.pokemon);
|
||
}
|
||
if (action.pokemon.hp) {
|
||
action.pokemon.beingCalledBack = true;
|
||
const sourceEffect = action.sourceEffect;
|
||
// @ts-ignore
|
||
if (sourceEffect && sourceEffect.selfSwitch === 'copyvolatile') {
|
||
action.pokemon.switchCopyFlag = true;
|
||
}
|
||
if (!action.pokemon.switchCopyFlag) {
|
||
this.runEvent('BeforeSwitchOut', action.pokemon);
|
||
if (this.gen >= 5) {
|
||
this.eachEvent('Update');
|
||
}
|
||
}
|
||
if (!this.runEvent('SwitchOut', action.pokemon)) {
|
||
// Warning: DO NOT interrupt a switch-out
|
||
// if you just want to trap a pokemon.
|
||
// To trap a pokemon and prevent it from switching out,
|
||
// (e.g. Mean Look, Magnet Pull) use the 'trapped' flag
|
||
// instead.
|
||
|
||
// Note: Nothing in BW or earlier interrupts
|
||
// a switch-out.
|
||
break;
|
||
}
|
||
}
|
||
action.pokemon.illusion = null;
|
||
this.singleEvent('End', this.getAbility(action.pokemon.ability), action.pokemon.abilityData, action.pokemon);
|
||
if (!action.pokemon.hp && !action.pokemon.fainted) {
|
||
// a pokemon fainted from Pursuit before it could switch
|
||
if (this.gen <= 4) {
|
||
// in gen 2-4, the switch still happens
|
||
action.priority = -101;
|
||
this.queue.unshift(action);
|
||
this.add('-hint', 'Pursuit target fainted, switch continues in gen 2-4');
|
||
break;
|
||
}
|
||
// in gen 5+, the switch is cancelled
|
||
this.debug('A Pokemon can\'t switch between when it runs out of HP and when it faints');
|
||
break;
|
||
}
|
||
if (action.target.isActive) {
|
||
this.add('-hint', 'Switch failed; switch target is already active');
|
||
break;
|
||
}
|
||
if (action.choice === 'switch' && action.pokemon.activeTurns === 1) {
|
||
for (const foeActive of action.pokemon.side.foe.active) {
|
||
if (foeActive.isStale >= 2) {
|
||
action.pokemon.isStaleCon++;
|
||
action.pokemon.isStaleSource = 'switch';
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
this.switchIn(action.target, action.pokemon.position, action.sourceEffect);
|
||
break;
|
||
case 'runUnnerve':
|
||
this.singleEvent('PreStart', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon);
|
||
break;
|
||
case 'runSwitch':
|
||
this.runEvent('SwitchIn', action.pokemon);
|
||
if (this.gen <= 2 && !action.pokemon.side.faintedThisTurn && action.pokemon.draggedIn !== this.turn) this.runEvent('AfterSwitchInSelf', action.pokemon);
|
||
if (!action.pokemon.hp) break;
|
||
action.pokemon.isStarted = true;
|
||
if (!action.pokemon.fainted) {
|
||
this.singleEvent('Start', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon);
|
||
action.pokemon.abilityOrder = this.abilityOrder++;
|
||
this.singleEvent('Start', action.pokemon.getItem(), action.pokemon.itemData, action.pokemon);
|
||
}
|
||
delete action.pokemon.draggedIn;
|
||
break;
|
||
case 'runPrimal':
|
||
if (!action.pokemon.transformed) this.singleEvent('Primal', action.pokemon.getItem(), action.pokemon.itemData, action.pokemon);
|
||
break;
|
||
case 'shift': {
|
||
if (!action.pokemon.isActive) return false;
|
||
if (action.pokemon.fainted) return false;
|
||
action.pokemon.activeTurns--;
|
||
this.swapPosition(action.pokemon, 1);
|
||
for (const foeActive of action.pokemon.side.foe.active) {
|
||
if (foeActive.isStale >= 2) {
|
||
action.pokemon.isStaleCon++;
|
||
action.pokemon.isStaleSource = 'switch';
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 'beforeTurn':
|
||
this.eachEvent('BeforeTurn');
|
||
break;
|
||
case 'residual':
|
||
this.add('');
|
||
this.clearActiveMove(true);
|
||
this.updateSpeed();
|
||
this.residualEvent('Residual');
|
||
this.add('upkeep');
|
||
break;
|
||
}
|
||
|
||
// phazing (Roar, etc)
|
||
for (const pokemon of this.p1.active) {
|
||
if (pokemon.forceSwitchFlag) {
|
||
if (pokemon.hp) this.dragIn(pokemon.side, pokemon.position);
|
||
pokemon.forceSwitchFlag = false;
|
||
}
|
||
}
|
||
for (const pokemon of this.p2.active) {
|
||
if (pokemon.forceSwitchFlag) {
|
||
if (pokemon.hp) this.dragIn(pokemon.side, pokemon.position);
|
||
pokemon.forceSwitchFlag = false;
|
||
}
|
||
}
|
||
|
||
this.clearActiveMove();
|
||
|
||
// fainting
|
||
|
||
this.faintMessages();
|
||
if (this.ended) return true;
|
||
|
||
// switching (fainted pokemon, U-turn, Baton Pass, etc)
|
||
|
||
if (!this.queue.length || (this.gen <= 3 && ['move', 'residual'].includes(this.queue[0].choice))) {
|
||
// in gen 3 or earlier, switching in fainted pokemon is done after
|
||
// every move, rather than only at the end of the turn.
|
||
this.checkFainted();
|
||
} else if (action.choice === 'megaEvo' && this.gen >= 7) {
|
||
this.eachEvent('Update');
|
||
// In Gen 7, the action order is recalculated for a Pokémon that mega evolves.
|
||
const moveIndex = this.queue.findIndex(queuedAction => queuedAction.pokemon === action.pokemon && queuedAction.choice === 'move');
|
||
if (moveIndex >= 0) {
|
||
const moveAction = /** @type {MoveAction} */ (this.queue.splice(moveIndex, 1)[0]);
|
||
moveAction.mega = 'done';
|
||
this.insertQueue(moveAction, true);
|
||
}
|
||
return false;
|
||
} else if (this.queue.length && this.queue[0].choice === 'instaswitch') {
|
||
return false;
|
||
}
|
||
|
||
let p1switch = this.p1.active.some(mon => mon && !!mon.switchFlag);
|
||
let p2switch = this.p2.active.some(mon => mon && !!mon.switchFlag);
|
||
|
||
if (p1switch && !this.canSwitch(this.p1)) {
|
||
for (const pokemon of this.p1.active) {
|
||
pokemon.switchFlag = false;
|
||
}
|
||
p1switch = false;
|
||
}
|
||
if (p2switch && !this.canSwitch(this.p2)) {
|
||
for (const pokemon of this.p2.active) {
|
||
pokemon.switchFlag = false;
|
||
}
|
||
p2switch = false;
|
||
}
|
||
|
||
if (p1switch || p2switch) {
|
||
if (this.gen >= 5) {
|
||
this.eachEvent('Update');
|
||
}
|
||
this.makeRequest('switch');
|
||
return true;
|
||
}
|
||
|
||
this.eachEvent('Update');
|
||
|
||
return false;
|
||
},
|
||
|
||
pokemon: {
|
||
moveUsed: function (move, targetLoc) {
|
||
let lastMove = this.moveThisTurn ? [toId(this.moveThisTurn), move] : move;
|
||
this.lastMove = lastMove;
|
||
this.moveThisTurn = lastMove.id;
|
||
this.lastMoveTargetLoc = targetLoc;
|
||
},
|
||
getLastMoveAbsolute: function () { // used
|
||
if (Array.isArray(this.lastMove)) return this.lastMove[1];
|
||
return this.lastMove;
|
||
},
|
||
checkMoveThisTurn: function (move) {
|
||
if (Array.isArray(this.moveThisTurn)) return this.moveThisTurn.includes(toId(move));
|
||
return this.moveThisTurn === toId(move);
|
||
},
|
||
getLinkedMoves: function () {
|
||
let linkedMoves = this.moveSlots.slice(0, 2);
|
||
if (linkedMoves.length !== 2 || linkedMoves[0].pp <= 0 || linkedMoves[1].pp <= 0) return [];
|
||
let ret = [linkedMoves[0].id, linkedMoves[1].id];
|
||
|
||
// Disabling effects which won't abort execution of moves already added to battle event loop.
|
||
if (!this.ateBerry && ret.includes('belch')) {
|
||
ret.disabled = true;
|
||
} else if (this.hasItem('assaultvest') && (this.battle.getMove(ret[0]).category === 'Status' || this.battle.getMove(ret[1]).category === 'Status')) {
|
||
ret.disabled = true;
|
||
}
|
||
return ret;
|
||
},
|
||
hasLinkedMove: function (move) {
|
||
move = toId(move);
|
||
let linkedMoves = this.getLinkedMoves();
|
||
if (!linkedMoves.length) return;
|
||
return linkedMoves[0] === move || linkedMoves[1] === move;
|
||
},
|
||
},
|
||
};
|