diff --git a/src/data/move.ts b/src/data/move.ts index 9127a7614f2..2fdf8360ffb 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -289,10 +289,9 @@ export default class Move implements Localizable { } /** - * Getter function that returns if the move targets itself or an ally + * Getter function that returns if the move targets the user or its ally * @returns boolean */ - isAllyTarget(): boolean { switch (this.moveTarget) { case MoveTarget.USER: @@ -306,6 +305,10 @@ export default class Move implements Localizable { return false; } + isChargingMove(): this is ChargingMove { + return false; + } + /** * Checks if the move is immune to certain types. * Currently looks at cases of Grass types with powder moves and Dark types with moves affected by Prankster. @@ -884,7 +887,7 @@ export class SelfStatusMove extends Move { type SubMove = new (...args: any[]) => Move; function ChargeMove(Base: TBase) { - return class ChargingMove extends Base { + return class extends Base { /** The animation to play during the move's charging phase */ public readonly chargeAnim: ChargeAnim = ChargeAnim[`${Moves[this.id]}_CHARGING`]; /** The message to show during the move's charging phase */ @@ -893,6 +896,10 @@ function ChargeMove(Base: TBase) { /** Move attributes that apply during the move's charging phase */ public chargeAttrs: MoveAttr[] = []; + override isChargingMove(): this is ChargingMove { + return true; + } + /** * Sets the text to be displayed during this move's charging phase. * References to the user Pokemon should be written as "{USER}", and diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index c043e43a762..b1d0b68aa4f 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -1,7 +1,7 @@ import BattleScene from "#app/battle-scene"; import { BattlerIndex } from "#app/battle"; import { MoveChargeAnim } from "#app/data/battle-anims"; -import { ChargingAttackMove, ChargingSelfStatusMove, applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move"; +import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move"; import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { BooleanHolder } from "#app/utils"; import { MovePhase } from "#app/phases/move-phase"; @@ -31,7 +31,7 @@ export class MoveChargePhase extends PokemonPhase { } const move = this.move.getMove(); - if (!(move instanceof ChargingAttackMove || move instanceof ChargingSelfStatusMove)) { + if (!(move.isChargingMove())) { return this.end(); } @@ -39,11 +39,6 @@ export class MoveChargePhase extends PokemonPhase { move.showChargeText(user, target); user.getMoveQueue().push({ move: move.id, targets: [ target?.getBattlerIndex() ]}); - if (move instanceof ChargingSelfStatusMove) { - user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); - return this.end(); - } - applyMoveChargeAttrs(MoveEffectAttr, user, target, move).then(() => { user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); this.end(); @@ -51,11 +46,12 @@ export class MoveChargePhase extends PokemonPhase { }); } + /** Checks the move's instant charge conditions, then ends this phase. */ end() { const user = this.getUserPokemon(); const move = this.move.getMove(); - if (move instanceof ChargingAttackMove || move instanceof ChargingSelfStatusMove) { + if (move.isChargingMove()) { const instantCharge = new BooleanHolder(false); applyMoveChargeAttrs(InstantChargeAttr, user, null, move, instantCharge); @@ -69,11 +65,11 @@ export class MoveChargePhase extends PokemonPhase { super.end(); } - getUserPokemon(): Pokemon { + protected getUserPokemon(): Pokemon { return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; } - getTargetPokemon(): Pokemon | undefined { + protected getTargetPokemon(): Pokemon | undefined { return this.scene.getField(true).find((p) => this.targetIndex === p.getBattlerIndex()); } } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index b2d429a4313..f8cb7354fd5 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -239,15 +239,13 @@ export class MoveEffectPhase extends PokemonPhase { user, target, move).then(() => { // All other effects require the move to not have failed or have been cancelled to trigger if (hitResult !== HitResult.FAIL) { - /** Are the move's effects tied to the first turn of a charge move? */ - const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move)); /** * If the invoked move's effects are meant to trigger during the move's "charge turn," * ignore all effects after this point. * Otherwise, apply all self-targeted POST_APPLY effects. */ - Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY - && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY + && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move).then(() => { // All effects past this point require the move to have hit the target if (hitResult !== HitResult.NO_EFFECT) { // Apply all non-self-targeted POST_APPLY effects @@ -265,7 +263,7 @@ export class MoveEffectPhase extends PokemonPhase { } } // If the move was not protected against, apply all HIT effects - Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT + Utils.executeIf(!isProtected, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 6272358aa85..97ad5101d94 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -22,6 +22,7 @@ import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import * as Utils from "#app/utils"; import i18next from "i18next"; +import { MoveChargePhase } from "./move-charge-phase"; export class MovePhase extends BattlePhase { protected _pokemon: Pokemon; @@ -132,6 +133,8 @@ export class MovePhase extends BattlePhase { if (this.cancelled || this.failed) { this.handlePreMoveFailures(); + } else if (this.move.getMove().isChargingMove() && !this.pokemon.getTag(BattlerTagType.CHARGING)) { + this.chargeMove(); } else { this.useMove(); } @@ -295,6 +298,11 @@ export class MovePhase extends BattlePhase { } } + /** Queues a {@linkcode MoveChargePhase} for this phase's invoked move. */ + protected chargeMove() { + this.scene.unshiftPhase(new MoveChargePhase(this.scene, this.pokemon.getBattlerIndex(), this.targets[0], this.move)); + } + /** * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * then ends the phase.