From 1d39f8d6387ae40d930169f47c5db2a5d6e193c5 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Tue, 23 Jul 2024 07:19:48 -0700 Subject: [PATCH] [Bug] Fix Thousand Arrows not hitting targets under the effects of Magnet Rise (#3100) * Fix Thousand Arrows not hitting through Magnet Rise * Add integration test for Thousand Arrows vs. Magnet Rise * ESLint * Remove unnecessary checks in integration tests --- src/data/move.ts | 1 + src/field/pokemon.ts | 27 ++++++++++---- src/test/moves/thousand_arrows.test.ts | 51 +++++++++++++++----------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 93d3fc2e2eb..5e2fbb5e207 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7531,6 +7531,7 @@ export function initMoves() { .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(HitsTagAttr, BattlerTagType.FLYING, false) + .attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) .makesContact(false) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a2d00792faf..0685f9173ab 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1170,7 +1170,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false): TypeDamageMultiplier { const move = pokemonMove.getMove(); const typeless = move.hasAttr(TypelessAttr); - const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move.type, source)); + const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move, source)); const cancelled = new Utils.BooleanHolder(false); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); if (!typeless && !ignoreAbility) { @@ -1185,13 +1185,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Calculates the type effectiveness multiplier for an attack type - * @param moveType Type of the move + * @param moveOrType The move being used, or a type if the move is unknown * @param source the Pokemon using the move * @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) * @param simulated tag to only apply the strong winds effect message when the move is used * @returns a multiplier for the type effectiveness */ - getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier { + getAttackTypeEffectiveness(moveOrType: Move | Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier { + const move = (moveOrType instanceof Move) + ? moveOrType + : undefined; + const moveType = (moveOrType instanceof Move) + ? move.type + : moveOrType; + if (moveType === Type.STELLAR) { return this.isTerastallized() ? 2 : 1; } @@ -1219,9 +1226,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - if (!!this.summonData?.tags.find((tag) => tag instanceof TypeImmuneTag && tag.immuneType === moveType)) { - multiplier = 0; - } + const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); + immuneTags.forEach(tag => { + if (move !== undefined) { + const hitsTagAttrs = move.getAttrs(HitsTagAttr).filter(attr => attr.tagType === tag.tagType); + if (hitsTagAttrs.length === 0) { + multiplier = 0; + } + } + }); return multiplier; } @@ -1806,7 +1819,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const cancelled = new Utils.BooleanHolder(false); const typeless = move.hasAttr(TypelessAttr); const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) - ? this.getAttackTypeEffectiveness(move.type, source, false, false) + ? this.getAttackTypeEffectiveness(move, source, false, false) : 1); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); if (typeless) { diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index b46bef9626f..f729d0c82cd 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -1,10 +1,10 @@ -import {afterEach, beforeAll, beforeEach, describe, expect, test, vi} from "vitest"; +import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest"; import Phaser from "phaser"; import GameManager from "#app/test/utils/gameManager"; import overrides from "#app/overrides"; import { - MoveEffectPhase, - TurnEndPhase + BerryPhase, + MoveEffectPhase } from "#app/phases"; import {getMovePosition} from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -38,18 +38,12 @@ describe("Moves - Thousand Arrows", () => { vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH,Moves.SPLASH,Moves.SPLASH,Moves.SPLASH]); }); - test( + it( "move should hit and ground Flying-type targets", async () => { await game.startBattle([ Species.ILLUMISE ]); - const leadPokemon = game.scene.getPlayerPokemon(); - expect(leadPokemon).toBeDefined(); - const enemyPokemon = game.scene.getEnemyPokemon(); - expect(enemyPokemon).toBeDefined(); - - const enemyStartingHp = enemyPokemon.hp; game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS)); @@ -57,14 +51,14 @@ describe("Moves - Thousand Arrows", () => { // Enemy should not be grounded before move effect is applied expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined(); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); - expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); }, TIMEOUT ); - test( + it( "move should hit and ground targets with Levitate", async () => { vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX); @@ -72,13 +66,7 @@ describe("Moves - Thousand Arrows", () => { await game.startBattle([ Species.ILLUMISE ]); - const leadPokemon = game.scene.getPlayerPokemon(); - expect(leadPokemon).toBeDefined(); - const enemyPokemon = game.scene.getEnemyPokemon(); - expect(enemyPokemon).toBeDefined(); - - const enemyStartingHp = enemyPokemon.hp; game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS)); @@ -86,10 +74,31 @@ describe("Moves - Thousand Arrows", () => { // Enemy should not be grounded before move effect is applied expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined(); - await game.phaseInterceptor.to(TurnEndPhase, false); + await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); - expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); }, TIMEOUT ); + + it( + "move should hit and ground targets under the effects of Magnet Rise", + async () => { + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX); + + await game.startBattle([ Species.ILLUMISE ]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + + enemyPokemon.addTag(BattlerTagType.MAGNET_RISEN, null, Moves.MAGNET_RISE); + + game.doAttack(getMovePosition(game.scene, 0, Moves.THOUSAND_ARROWS)); + + await game.phaseInterceptor.to(BerryPhase, false); + + expect(enemyPokemon.getTag(BattlerTagType.MAGNET_RISEN)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + } + ); });