|
|
@ -1,5 +1,5 @@
|
|
|
|
import BattleScene from "#app/battle-scene";
|
|
|
|
|
|
|
|
import { BattlerIndex } from "#app/battle";
|
|
|
|
import { BattlerIndex } from "#app/battle";
|
|
|
|
|
|
|
|
import BattleScene from "#app/battle-scene";
|
|
|
|
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability";
|
|
|
|
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability";
|
|
|
|
import { CommonAnim } from "#app/data/battle-anims";
|
|
|
|
import { CommonAnim } from "#app/data/battle-anims";
|
|
|
|
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
|
|
|
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
|
|
@ -15,23 +15,51 @@ import { StatusEffect } from "#app/enums/status-effect";
|
|
|
|
import { MoveUsedEvent } from "#app/events/battle-scene";
|
|
|
|
import { MoveUsedEvent } from "#app/events/battle-scene";
|
|
|
|
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon";
|
|
|
|
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon";
|
|
|
|
import { getPokemonNameWithAffix } from "#app/messages";
|
|
|
|
import { getPokemonNameWithAffix } from "#app/messages";
|
|
|
|
|
|
|
|
import { BattlePhase } from "#app/phases/battle-phase";
|
|
|
|
|
|
|
|
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
|
|
|
|
|
|
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
|
|
|
|
|
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
|
|
|
|
|
|
|
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
|
|
|
import * as Utils from "#app/utils";
|
|
|
|
import * as Utils from "#app/utils";
|
|
|
|
import i18next from "i18next";
|
|
|
|
import i18next from "i18next";
|
|
|
|
import { BattlePhase } from "./battle-phase";
|
|
|
|
|
|
|
|
import { CommonAnimPhase } from "./common-anim-phase";
|
|
|
|
|
|
|
|
import { MoveEffectPhase } from "./move-effect-phase";
|
|
|
|
|
|
|
|
import { MoveEndPhase } from "./move-end-phase";
|
|
|
|
|
|
|
|
import { ShowAbilityPhase } from "./show-ability-phase";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class MovePhase extends BattlePhase {
|
|
|
|
export class MovePhase extends BattlePhase {
|
|
|
|
public pokemon: Pokemon;
|
|
|
|
protected _pokemon: Pokemon;
|
|
|
|
public move: PokemonMove;
|
|
|
|
protected _move: PokemonMove;
|
|
|
|
public targets: BattlerIndex[];
|
|
|
|
protected _targets: BattlerIndex[];
|
|
|
|
protected followUp: boolean;
|
|
|
|
protected followUp: boolean;
|
|
|
|
protected ignorePp: boolean;
|
|
|
|
protected ignorePp: boolean;
|
|
|
|
protected failed: boolean;
|
|
|
|
protected failed: boolean = false;
|
|
|
|
protected cancelled: boolean;
|
|
|
|
protected cancelled: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public get pokemon(): Pokemon {
|
|
|
|
|
|
|
|
return this._pokemon;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected set pokemon(pokemon: Pokemon) {
|
|
|
|
|
|
|
|
this._pokemon = pokemon;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public get move(): PokemonMove {
|
|
|
|
|
|
|
|
return this._move;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected set move(move: PokemonMove) {
|
|
|
|
|
|
|
|
this._move = move;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public get targets(): BattlerIndex[] {
|
|
|
|
|
|
|
|
return this._targets;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected set targets(targets: BattlerIndex[]) {
|
|
|
|
|
|
|
|
this._targets = targets;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param followUp Indicates that the move being uses is a "follow-up" - for example, a move being used by Metronome or Dancer.
|
|
|
|
|
|
|
|
* Follow-ups bypass a few failure conditions, including flinches, sleep/paralysis/freeze and volatile status checks, etc.
|
|
|
|
|
|
|
|
*/
|
|
|
|
constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) {
|
|
|
|
constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) {
|
|
|
|
super(scene);
|
|
|
|
super(scene);
|
|
|
|
|
|
|
|
|
|
|
@ -40,10 +68,13 @@ export class MovePhase extends BattlePhase {
|
|
|
|
this.move = move;
|
|
|
|
this.move = move;
|
|
|
|
this.followUp = followUp ?? false;
|
|
|
|
this.followUp = followUp ?? false;
|
|
|
|
this.ignorePp = ignorePp ?? false;
|
|
|
|
this.ignorePp = ignorePp ?? false;
|
|
|
|
this.failed = false;
|
|
|
|
|
|
|
|
this.cancelled = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Checks if the pokemon is active, if the move is usable, and that the move is targetting something.
|
|
|
|
|
|
|
|
* @param ignoreDisableTags `true` to not check if the move is disabled
|
|
|
|
|
|
|
|
* @returns `true` if all the checks pass
|
|
|
|
|
|
|
|
*/
|
|
|
|
canMove(ignoreDisableTags?: boolean): boolean {
|
|
|
|
canMove(ignoreDisableTags?: boolean): boolean {
|
|
|
|
return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp, ignoreDisableTags) && !!this.targets.length;
|
|
|
|
return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp, ignoreDisableTags) && !!this.targets.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -63,8 +94,9 @@ export class MovePhase extends BattlePhase {
|
|
|
|
|
|
|
|
|
|
|
|
console.log(Moves[this.move.moveId]);
|
|
|
|
console.log(Moves[this.move.moveId]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check if move is unusable (e.g. because it's out of PP due to a mid-turn Spite).
|
|
|
|
if (!this.canMove(true)) {
|
|
|
|
if (!this.canMove(true)) {
|
|
|
|
if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails
|
|
|
|
if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) {
|
|
|
|
this.fail();
|
|
|
|
this.fail();
|
|
|
|
this.showMoveText();
|
|
|
|
this.showMoveText();
|
|
|
|
this.showFailedText();
|
|
|
|
this.showFailedText();
|
|
|
@ -72,179 +104,57 @@ export class MovePhase extends BattlePhase {
|
|
|
|
return this.end();
|
|
|
|
return this.end();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.turnData.acted = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats)
|
|
|
|
|
|
|
|
if (this.followUp) {
|
|
|
|
|
|
|
|
this.pokemon.turnData.hitsLeft = -1;
|
|
|
|
|
|
|
|
this.pokemon.turnData.hitCount = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check move to see if arena.ignoreAbilities should be true.
|
|
|
|
if (!this.followUp) {
|
|
|
|
if (!this.followUp) {
|
|
|
|
if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) {
|
|
|
|
if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) {
|
|
|
|
this.scene.arena.setIgnoreAbilities();
|
|
|
|
this.scene.arena.setIgnoreAbilities();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.resolveRedirectTarget();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.resolveCounterAttackTarget();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.resolvePreMoveStatusEffects();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.lapsePreMoveAndMoveTags();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.resolveFinalPreMoveCancellationChecks();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.cancelled || this.failed) {
|
|
|
|
|
|
|
|
this.handlePreMoveFailures();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct?
|
|
|
|
this.useMove();
|
|
|
|
this.pokemon.turnData.hitCount = 0; // TODO: is `0` correct?
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Move redirection abilities (ie. Storm Drain) only support single target moves
|
|
|
|
this.end();
|
|
|
|
const moveTarget = this.targets.length === 1
|
|
|
|
}
|
|
|
|
? new Utils.IntegerHolder(this.targets[0])
|
|
|
|
|
|
|
|
: null;
|
|
|
|
/** Check for cancellation edge cases - no targets remaining, or Moves.NONE is on the queue (TODO: when does this happen?) */
|
|
|
|
if (moveTarget) {
|
|
|
|
resolveFinalPreMoveCancellationChecks() {
|
|
|
|
const oldTarget = moveTarget.value;
|
|
|
|
const targets = this.getActiveTargetPokemon();
|
|
|
|
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, moveTarget));
|
|
|
|
const moveQueue = this.pokemon.getMoveQueue();
|
|
|
|
this.pokemon.getOpponents().forEach(p => {
|
|
|
|
|
|
|
|
const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag;
|
|
|
|
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
|
|
|
|
if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) {
|
|
|
|
this.showFailedText();
|
|
|
|
moveTarget.value = p.getBattlerIndex();
|
|
|
|
this.cancelled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
//Check if this move is immune to being redirected, and restore its target to the intended target if it is.
|
|
|
|
|
|
|
|
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) {
|
|
|
|
|
|
|
|
//If an ability prevented this move from being redirected, display its ability pop up.
|
|
|
|
|
|
|
|
if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) {
|
|
|
|
|
|
|
|
this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
moveTarget.value = oldTarget;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.targets[0] = moveTarget.value;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for counterattack moves to switch target
|
|
|
|
getActiveTargetPokemon() {
|
|
|
|
if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) {
|
|
|
|
return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex()));
|
|
|
|
if (this.pokemon.turnData.attacksReceived.length) {
|
|
|
|
}
|
|
|
|
const attack = this.pokemon.turnData.attacksReceived[0];
|
|
|
|
|
|
|
|
this.targets[0] = attack.sourceBattlerIndex;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// account for metal burst and comeuppance hitting remaining targets in double battles
|
|
|
|
|
|
|
|
// counterattack will redirect to remaining ally if original attacker faints
|
|
|
|
|
|
|
|
if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) {
|
|
|
|
|
|
|
|
if (this.scene.getField()[this.targets[0]].hp === 0) {
|
|
|
|
|
|
|
|
const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
|
|
|
|
|
|
|
|
//@ts-ignore
|
|
|
|
|
|
|
|
this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex(); //TODO: fix ts-ignore
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.targets[0] === BattlerIndex.ATTACKER) {
|
|
|
|
|
|
|
|
this.fail(); // Marks the move as failed for later in doMove
|
|
|
|
|
|
|
|
this.showMoveText();
|
|
|
|
|
|
|
|
this.showFailedText();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const targets = this.scene.getField(true).filter(p => {
|
|
|
|
|
|
|
|
if (this.targets.indexOf(p.getBattlerIndex()) > -1) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const doMove = () => {
|
|
|
|
|
|
|
|
this.pokemon.turnData.acted = true; // Record that the move was attempted, even if it fails
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let ppUsed = 1;
|
|
|
|
|
|
|
|
// Filter all opponents to include only those this move is targeting
|
|
|
|
|
|
|
|
const targetedOpponents = this.pokemon.getOpponents().filter(o => this.targets.includes(o.getBattlerIndex()));
|
|
|
|
|
|
|
|
for (const opponent of targetedOpponents) {
|
|
|
|
|
|
|
|
if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) { // If we're already at max PP usage, stop checking
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) { // Accounting for abilities like Pressure
|
|
|
|
|
|
|
|
ppUsed++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.followUp && this.canMove() && !this.cancelled) {
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const moveQueue = this.pokemon.getMoveQueue();
|
|
|
|
|
|
|
|
if (this.cancelled || this.failed) {
|
|
|
|
|
|
|
|
if (this.failed) {
|
|
|
|
|
|
|
|
this.move.usePp(ppUsed); // Only use PP if the move failed
|
|
|
|
|
|
|
|
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Record a failed move so Abilities like Truant don't trigger next turn and soft-lock
|
|
|
|
|
|
|
|
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc.
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
|
|
|
|
|
|
|
moveQueue.shift(); // Remove the second turn of charge moves
|
|
|
|
|
|
|
|
return this.end();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.move.moveId) {
|
|
|
|
|
|
|
|
this.showMoveText();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This should only happen when there are no valid targets left on the field
|
|
|
|
|
|
|
|
if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) {
|
|
|
|
|
|
|
|
this.showFailedText();
|
|
|
|
|
|
|
|
this.cancel();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Record a failed move so Abilities like Truant don't trigger next turn and soft-lock
|
|
|
|
|
|
|
|
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc.
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
moveQueue.shift();
|
|
|
|
|
|
|
|
return this.end();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((!moveQueue.length || !moveQueue.shift()?.ignorePP) && !this.ignorePp) { // using .shift here clears out two turn moves once they've been used
|
|
|
|
|
|
|
|
this.move.usePp(ppUsed);
|
|
|
|
|
|
|
|
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
|
|
|
|
|
|
|
|
this.scene.currentBattle.lastMove = this.move.moveId;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Assume conditions affecting targets only apply to moves with a single target
|
|
|
|
|
|
|
|
let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove());
|
|
|
|
|
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
|
|
|
|
|
|
let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled);
|
|
|
|
|
|
|
|
if (success && this.scene.arena.isMoveWeatherCancelled(this.pokemon, this.move.getMove())) {
|
|
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
} else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) {
|
|
|
|
|
|
|
|
success = false;
|
|
|
|
|
|
|
|
if (failedText === null) {
|
|
|
|
|
|
|
|
failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain?.terrainType!); // TODO: is this bang correct?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Trigger pokemon type change before playing the move animation
|
|
|
|
|
|
|
|
* Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse,
|
|
|
|
|
|
|
|
* regardless of whether the move successfully executes or not.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) {
|
|
|
|
|
|
|
|
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
|
|
this.scene.unshiftPhase(this.getEffectPhase());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
|
|
|
|
|
|
|
|
if (!cancelled.value) {
|
|
|
|
|
|
|
|
this.showFailedText(failedText);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if Dancer ability is triggered
|
|
|
|
|
|
|
|
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) {
|
|
|
|
|
|
|
|
// Pokemon with Dancer can be on either side of the battle so we check in both cases
|
|
|
|
|
|
|
|
this.scene.getPlayerField().forEach(pokemon => {
|
|
|
|
|
|
|
|
applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
this.scene.getEnemyField().forEach(pokemon => {
|
|
|
|
|
|
|
|
applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.end();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Handles Sleep/Paralysis/Freeze rolls and side effects, along with lapsing volatile statuses. */
|
|
|
|
|
|
|
|
resolvePreMoveStatusEffects() {
|
|
|
|
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
|
|
|
|
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
|
|
|
|
this.pokemon.status.incrementTurn();
|
|
|
|
this.pokemon.status.incrementTurn();
|
|
|
|
let activated = false;
|
|
|
|
let activated = false;
|
|
|
@ -273,25 +183,260 @@ export class MovePhase extends BattlePhase {
|
|
|
|
if (activated) {
|
|
|
|
if (activated) {
|
|
|
|
this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)));
|
|
|
|
this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)));
|
|
|
|
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1)));
|
|
|
|
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1)));
|
|
|
|
doMove();
|
|
|
|
} else if (healed) {
|
|
|
|
} else {
|
|
|
|
this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)));
|
|
|
|
if (healed) {
|
|
|
|
this.pokemon.resetStatus();
|
|
|
|
this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon)));
|
|
|
|
this.pokemon.updateInfo();
|
|
|
|
this.pokemon.resetStatus();
|
|
|
|
|
|
|
|
this.pokemon.updateInfo();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
doMove();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
doMove();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getEffectPhase(): MoveEffectPhase {
|
|
|
|
/**
|
|
|
|
return new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move);
|
|
|
|
* Lapse `PRE_MOVE` tags that trigger before a move is used, regardless of whether or not it failed.
|
|
|
|
|
|
|
|
* Also lapse `MOVE` tags if the move should be successful.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
lapsePreMoveAndMoveTags() {
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked?
|
|
|
|
|
|
|
|
if (!this.followUp && this.canMove() && !this.cancelled) {
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useMove() {
|
|
|
|
|
|
|
|
const targets = this.getActiveTargetPokemon();
|
|
|
|
|
|
|
|
const moveQueue = this.pokemon.getMoveQueue();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// form changes happen even before we know that the move wll execute.
|
|
|
|
|
|
|
|
this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.showMoveText();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Clean up implementation of two-turn moves.
|
|
|
|
|
|
|
|
if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used
|
|
|
|
|
|
|
|
this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// "commit" to using the move, deducting PP.
|
|
|
|
|
|
|
|
if (!this.ignorePp) {
|
|
|
|
|
|
|
|
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.move.usePp(ppUsed);
|
|
|
|
|
|
|
|
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
|
|
|
|
|
|
|
|
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
|
|
|
|
|
|
|
|
this.scene.currentBattle.lastMove = this.move.moveId;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Determine if the move is successful (meaning that its damage/effects can be attempted)
|
|
|
|
|
|
|
|
* by checking that all of the following are true:
|
|
|
|
|
|
|
|
* - Conditional attributes of the move are all met
|
|
|
|
|
|
|
|
* - The target's `ForceSwitchOutImmunityAbAttr` is not triggered (see {@linkcode Move.prototype.applyConditions})
|
|
|
|
|
|
|
|
* - Weather does not block the move
|
|
|
|
|
|
|
|
* - Terrain does not block the move
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* TODO: These steps are straightforward, but the implementation below is extremely convoluted.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const move = this.move.getMove();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Move conditions assume the move has a single target
|
|
|
|
|
|
|
|
* TODO: is this sustainable?
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
const passesConditions = move.applyConditions(this.pokemon, targets[0], move);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let failedDueToWeather: boolean = false;
|
|
|
|
|
|
|
|
let failedDueToTerrain: boolean = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!passesConditions) {
|
|
|
|
|
|
|
|
failedDueToWeather = this.scene.arena.isMoveWeatherCancelled(this.pokemon, move);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!failedDueToWeather) {
|
|
|
|
|
|
|
|
failedDueToTerrain = this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const success = passesConditions && !failedDueToWeather && !failedDueToTerrain;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* If the move has not failed, trigger ability-based user type changes and then execute it.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* Notably, Roar, Whirlwind, Trick-or-Treat, and Forest's Curse will trigger these type changes even
|
|
|
|
|
|
|
|
* if the move fails.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* TODO: Should these exceptions have a move flag?
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
|
|
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
|
|
|
|
|
|
|
this.scene.unshiftPhase(new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if ([Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) {
|
|
|
|
|
|
|
|
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let failedText: string | undefined;
|
|
|
|
|
|
|
|
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (failureMessage) {
|
|
|
|
|
|
|
|
failedText = failureMessage;
|
|
|
|
|
|
|
|
} else if (failedDueToTerrain) {
|
|
|
|
|
|
|
|
failedText = getTerrainBlockMessage(this.pokemon, this.scene.arena.getTerrainType());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.showFailedText(failedText);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle Dance, which triggers immediately after a move is used (rather than waiting on this.end()).
|
|
|
|
|
|
|
|
// Note that the !this.followUp check here prevents an infinite Dancer loop.
|
|
|
|
|
|
|
|
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) {
|
|
|
|
|
|
|
|
this.scene.getField(true).forEach(pokemon => {
|
|
|
|
|
|
|
|
applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end() {
|
|
|
|
|
|
|
|
if (!this.followUp && this.canMove()) {
|
|
|
|
|
|
|
|
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
super.end();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Applies PP increasing abilities (so, Pressure) if they exist on the target pokemon.
|
|
|
|
|
|
|
|
* Note that targets must include only active pokemon.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
getPpIncreaseFromPressure(targets: Pokemon[]) {
|
|
|
|
|
|
|
|
const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr));
|
|
|
|
|
|
|
|
return foesWithPressure.length;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Modifies this.targets in place, based upon:
|
|
|
|
|
|
|
|
* - Move redirection abilities, effects, etc.
|
|
|
|
|
|
|
|
* - Counterattacks, which pass a special value into the `targets` constructor param (`[BattlerIndex.ATTACKER]`).
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
resolveRedirectTarget() {
|
|
|
|
|
|
|
|
if (this.targets.length === 1) {
|
|
|
|
|
|
|
|
const currentTarget = this.targets[0];
|
|
|
|
|
|
|
|
const redirectTarget = new Utils.NumberHolder(currentTarget);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check move redirection abilities of every pokemon *except* the user.
|
|
|
|
|
|
|
|
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check for center-of-attention tags (note that this will override redirect abilities)
|
|
|
|
|
|
|
|
this.pokemon.getOpponents().forEach(p => {
|
|
|
|
|
|
|
|
const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: don't hardcode this interaction.
|
|
|
|
|
|
|
|
// Handle interaction between the rage powder center-of-attention tag and moves used by grass types/overcoat-havers (which are immune to RP's redirect)
|
|
|
|
|
|
|
|
if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) {
|
|
|
|
|
|
|
|
redirectTarget.value = p.getBattlerIndex();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentTarget !== redirectTarget.value) {
|
|
|
|
|
|
|
|
if (this.move.getMove().hasAttr(BypassRedirectAttr)) {
|
|
|
|
|
|
|
|
redirectTarget.value = currentTarget;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) {
|
|
|
|
|
|
|
|
redirectTarget.value = currentTarget;
|
|
|
|
|
|
|
|
this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.targets[0] = redirectTarget.value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Counter-attacking moves pass in `[BattlerIndex.ATTACKER]` into the constructor's `targets` param.
|
|
|
|
|
|
|
|
* This function modifies `this.targets` to reflect the actual battler index of the user's last
|
|
|
|
|
|
|
|
* attacker.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* If there is no last attacker, or they are no longer on the field, a message is displayed and the
|
|
|
|
|
|
|
|
* move is marked for failure.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
resolveCounterAttackTarget() {
|
|
|
|
|
|
|
|
if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) {
|
|
|
|
|
|
|
|
if (this.pokemon.turnData.attacksReceived.length) {
|
|
|
|
|
|
|
|
const attacker = this.pokemon.scene.getPokemonById(this.pokemon.turnData.attacksReceived[0].sourceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (attacker?.isActive(true)) {
|
|
|
|
|
|
|
|
this.targets[0] = attacker.getBattlerIndex();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// account for metal burst and comeuppance hitting remaining targets in double battles
|
|
|
|
|
|
|
|
// counterattack will redirect to remaining ally if original attacker faints
|
|
|
|
|
|
|
|
if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) {
|
|
|
|
|
|
|
|
if (this.scene.getField()[this.targets[0]].hp === 0) {
|
|
|
|
|
|
|
|
const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
|
|
|
|
|
|
|
|
this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex() ?? BattlerIndex.ATTACKER;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.targets[0] === BattlerIndex.ATTACKER) {
|
|
|
|
|
|
|
|
this.fail();
|
|
|
|
|
|
|
|
this.showMoveText();
|
|
|
|
|
|
|
|
this.showFailedText();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Handles the case where the move was cancelled or failed:
|
|
|
|
|
|
|
|
* - Uses PP if the move failed (not cancelled) and should use PP (failed moves are not affected by Pressure)
|
|
|
|
|
|
|
|
* - Records a cancelled OR failed move in move history, so Abilities like Truant don't trigger on the
|
|
|
|
|
|
|
|
* next turn and soft-lock.
|
|
|
|
|
|
|
|
* - Lapses `MOVE_EFFECT` tags:
|
|
|
|
|
|
|
|
* - Semi-invulnerable battler tags (Fly/Dive/etc.) are intended to lapse on move effects, but also need
|
|
|
|
|
|
|
|
* to lapse on move failure/cancellation.
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* TODO: ...this seems weird.
|
|
|
|
|
|
|
|
* - Lapses `AFTER_MOVE` tags:
|
|
|
|
|
|
|
|
* - This handles the effects of {@linkcode Moves.SUBSTITUTE}
|
|
|
|
|
|
|
|
* - Removes the second turn of charge moves
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* TODO: handle charge moves more gracefully
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
handlePreMoveFailures() {
|
|
|
|
|
|
|
|
if (this.cancelled || this.failed) {
|
|
|
|
|
|
|
|
if (this.failed) {
|
|
|
|
|
|
|
|
const ppUsed = this.ignorePp ? 0 : 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ppUsed) {
|
|
|
|
|
|
|
|
this.move.usePp();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
|
|
|
|
|
|
|
this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.pokemon.getMoveQueue().shift();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
showMoveText(): void {
|
|
|
|
showMoveText(): void {
|
|
|
|
|
|
|
|
if (this.move.moveId === Moves.NONE) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.move.getMove().hasAttr(ChargeAttr)) {
|
|
|
|
if (this.move.getMove().hasAttr(ChargeAttr)) {
|
|
|
|
const lastMove = this.pokemon.getLastXMoves() as TurnMove[];
|
|
|
|
const lastMove = this.pokemon.getLastXMoves() as TurnMove[];
|
|
|
|
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) {
|
|
|
|
if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) {
|
|
|
@ -311,18 +456,10 @@ export class MovePhase extends BattlePhase {
|
|
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
|
|
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
|
|
|
|
moveName: this.move.getName()
|
|
|
|
moveName: this.move.getName()
|
|
|
|
}), 500);
|
|
|
|
}), 500);
|
|
|
|
applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents().find(() => true)!, this.move.getMove()); //TODO: is the bang correct here?
|
|
|
|
applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents()[0], this.move.getMove());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
showFailedText(failedText: string | null = null): void {
|
|
|
|
showFailedText(failedText?: string): void {
|
|
|
|
this.scene.queueMessage(failedText || i18next.t("battle:attackFailed"));
|
|
|
|
this.scene.queueMessage(failedText ?? i18next.t("battle:attackFailed"));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end() {
|
|
|
|
|
|
|
|
if (!this.followUp && this.canMove()) {
|
|
|
|
|
|
|
|
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
super.end();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|