'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; }, 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.map(moveId => this.getMoveCopy(moveId)); let linkedOtherMove = action.linked[1 - linkedMoves.indexOf(decisionMove)]; if (linkedOtherMove.beforeTurnCallback) { this.addToQueue({choice: 'beforeTurnMove', pokemon: action.pokemon, move: linkedOtherMove, 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 = move.priority; let linkedMoves = action.pokemon.getLinkedMoves(); let linkIndex = -1; if (linkedMoves.length && !linkedMoves.disabled && !move.isZ && (linkIndex = linkedMoves.indexOf(toId(action.move))) >= 0) { let linkedActions = action.linked || linkedMoves.map(moveId => this.getMoveCopy(moveId)); let altMove = linkedActions[1 - linkIndex]; let thisPriority = this.runEvent('ModifyPriority', action.pokemon, target, linkedActions[linkIndex], priority); let otherPriority = this.runEvent('ModifyPriority', action.pokemon, target, altMove, altMove.priority); priority = Math.min(thisPriority, otherPriority); action.priority = priority; if (this.gen > 5) { // Gen 6+: Quick Guard blocks moves with artificially enhanced priority. // This also applies to Psychic Terrain. linkedActions[linkIndex].priority = priority; altMove.priority = priority; } } else { priority = this.runEvent('ModifyPriority', action.pokemon, target, move, priority); action.priority = priority; if (this.gen > 5) { // Gen 6+: Quick Guard blocks moves with artificially enhanced priority. // This also applies to Psychic Terrain. 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 ? [this.moveThisTurn, move] : move; this.lastMove = lastMove; this.moveThisTurn = lastMove; this.lastMoveTargetLoc = targetLoc; }, getLastMoveAbsolute: function () { // used if (Array.isArray(this.lastMove)) return this.lastMove[1]; return this.lastMove; }, checkMoveThisTurn: function (move) { const moveId = toId(move); if (Array.isArray(this.moveThisTurn)) return this.moveThisTurn.some(moveUsed => moveUsed.id === moveId); return this.moveThisTurn && this.moveThisTurn.id === moveId; }, 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; }, }, };