pokemon-showdown/mods/linked/scripts.js
2018-04-04 20:10:14 -04:00

751 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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;
},
},
};