From e1662b82513931b0d5190e275f95057497327eee Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:07:32 -0400 Subject: [PATCH] [Bug] Adjust how counter attacks target to account for uturn/voltswitch and double battles (#2462) * Adjust how counter attacks target to account for uturn/voltswitch * Creates move flag for metal burst/comeuppance to redirect in some cases * Remove debug printing * Bit shifts the redirect counter flag * Removes extraneous class from prior testing * Remove vitest timestamp file that was accidentally added --- src/data/move.ts | 17 +++++++++++++++++ src/field/pokemon.ts | 3 ++- src/phases.ts | 13 ++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 64aed985d57..4e0841fcbd8 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -92,6 +92,10 @@ export enum MoveFlags { * Enables all hits of a multi-hit move to be accuracy checked individually */ CHECK_ALL_HITS = 1 << 17, + /** + * Indicates a move is able to be redirected to allies in a double battle if the attacker faints + */ + REDIRECT_COUNTER = 1 << 18, } type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; @@ -549,6 +553,17 @@ export default class Move implements Localizable { return this; } + /** + * Sets the {@linkcode MoveFlags.REDIRECT_COUNTER} flag for the calling Move + * @param redirectCounter The value (boolean) to set the flag to + * example: @see {@linkcode Moves.METAL_BURST} + * @returns The {@linkcode Move} that called this function + */ + redirectCounter(redirectCounter?: boolean): this { + this.setFlag(MoveFlags.REDIRECT_COUNTER, redirectCounter); + return this; + } + /** * Checks if the move flag applies to the pokemon(s) using/receiving the move * @param flag {@linkcode MoveFlags} MoveFlag to check on user and/or target @@ -6907,6 +6922,7 @@ export function initMoves() { .target(MoveTarget.USER_OR_NEAR_ALLY), new AttackMove(Moves.METAL_BURST, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 4) .attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5) + .redirectCounter() .makesContact(false) .target(MoveTarget.ATTACKER), new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4) @@ -8587,6 +8603,7 @@ export function initMoves() { }), // TODO Add Instruct/Encore interaction new AttackMove(Moves.COMEUPPANCE, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9) .attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5) + .redirectCounter() .target(MoveTarget.ATTACKER), new AttackMove(Moves.AQUA_CUTTER, Type.WATER, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 9) .attr(HighCritAttr) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 2ae7d6d97ff..a0200e7c455 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2075,7 +2075,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { source.turnData.damageDealt += damage.value; source.turnData.currDamageDealt = damage.value; this.battleData.hitCount++; - const attackResult = { move: move.id, result: result as DamageResult, damage: damage.value, critical: isCritical, sourceId: source.id }; + const attackResult = { move: move.id, result: result as DamageResult, damage: damage.value, critical: isCritical, sourceId: source.id, attackingPosition: source.getBattlerIndex() }; this.turnData.attacksReceived.unshift(attackResult); if (source.isPlayer() && !this.isPlayer()) { this.scene.applyModifiers(DamageMoneyRewardModifier, true, source, damage); @@ -3990,6 +3990,7 @@ export interface AttackMoveResult { damage: integer; critical: boolean; sourceId: integer; + attackingPosition: BattlerIndex; } export class PokemonSummonData { diff --git a/src/phases.ts b/src/phases.ts index 8dc3d8661a3..65ec43f1eae 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2661,11 +2661,18 @@ export class MovePhase extends BattlePhase { this.targets[0] = moveTarget.value; } + // Check for counterattack moves to switch target if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.pokemon.turnData.attacksReceived.length) { - const attacker = this.pokemon.turnData.attacksReceived.length ? this.pokemon.scene.getPokemonById(this.pokemon.turnData.attacksReceived[0].sourceId) : null; - if (attacker?.isActive(true)) { - this.targets[0] = attacker.getBattlerIndex(); + const attack = this.pokemon.turnData.attacksReceived[0]; + this.targets[0] = attack.attackingPosition; + + // 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.getEnemyField()[this.targets[0]]) { + this.targets[0] = this.scene.getEnemyField().find(p => p.isActive(true)).getBattlerIndex(); + } } } if (this.targets[0] === BattlerIndex.ATTACKER) {