From e3c0ac823837043f82fef1fb9c3b5a9e909bf2ef Mon Sep 17 00:00:00 2001 From: Christopher Schmidt Date: Sun, 2 Mar 2025 22:19:39 -0500 Subject: [PATCH] Adds guard for critical hits before checking for screen multipliers, adjusts tests for reflect and lightscreen --- src/field/pokemon.ts | 6 +++++- test/moves/light_screen.test.ts | 26 ++++++++++++++++++++------ test/moves/reflect.test.ts | 23 ++++++++++++++++++----- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 53d4b6c54d2..d8e22898d42 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2886,7 +2886,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ const screenMultiplier = new Utils.NumberHolder(1); - globalScene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier); + + // Critical hits should bypass screens + if (!isCritical) { + globalScene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier); + } /** * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: diff --git a/test/moves/light_screen.test.ts b/test/moves/light_screen.test.ts index 8eee58c8e17..9e3c03c4dda 100644 --- a/test/moves/light_screen.test.ts +++ b/test/moves/light_screen.test.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/move"; -import { allMoves } from "#app/data/move"; +import { allMoves, CritOnlyAttr } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; @@ -11,7 +11,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; let globalScene: BattleScene; @@ -45,7 +45,7 @@ describe("Moves - Light Screen", () => { it("reduces damage of special attacks by half in a single battle", async () => { const moveToUse = Moves.ABSORB; - await game.startBattle([ Species.SHUCKLE ]); + await game.classicMode.startBattle([ Species.SHUCKLE ]); game.move.select(moveToUse); @@ -60,7 +60,7 @@ describe("Moves - Light Screen", () => { game.override.battleType("double"); const moveToUse = Moves.DAZZLING_GLEAM; - await game.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); game.move.select(moveToUse); game.move.select(moveToUse, 1); @@ -73,7 +73,7 @@ describe("Moves - Light Screen", () => { it("does not affect physical attacks", async () => { const moveToUse = Moves.TACKLE; - await game.startBattle([ Species.SHUCKLE ]); + await game.classicMode.startBattle([ Species.SHUCKLE ]); game.move.select(moveToUse); @@ -82,6 +82,18 @@ describe("Moves - Light Screen", () => { expect(mockedDmg).toBe(allMoves[moveToUse].power); }); + + it("does not affect critical hits", async () => { + const moveToUse = Moves.FROST_BREATH; + vi.spyOn(allMoves[Moves.FROST_BREATH], "accuracy", "get").mockReturnValue(100); + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + game.move.select(moveToUse); + await game.phaseInterceptor.to(TurnEndPhase); + + const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]); + expect(mockedDmg).toBe(allMoves[moveToUse].power); + }); }); /** @@ -98,7 +110,9 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { - globalScene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder); + if (move.getAttrs(CritOnlyAttr).length === 0) { + globalScene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder); + } } return move.power * multiplierHolder.value; diff --git a/test/moves/reflect.test.ts b/test/moves/reflect.test.ts index edc3f1ab8aa..ea57a02923d 100644 --- a/test/moves/reflect.test.ts +++ b/test/moves/reflect.test.ts @@ -1,7 +1,7 @@ import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; import type Move from "#app/data/move"; -import { allMoves } from "#app/data/move"; +import { allMoves, CritOnlyAttr } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; @@ -45,7 +45,7 @@ describe("Moves - Reflect", () => { it("reduces damage of physical attacks by half in a single battle", async () => { const moveToUse = Moves.TACKLE; - await game.startBattle([ Species.SHUCKLE ]); + await game.classicMode.startBattle([ Species.SHUCKLE ]); game.move.select(moveToUse); @@ -59,7 +59,7 @@ describe("Moves - Reflect", () => { game.override.battleType("double"); const moveToUse = Moves.ROCK_SLIDE; - await game.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); game.move.select(moveToUse); game.move.select(moveToUse, 1); @@ -72,7 +72,7 @@ describe("Moves - Reflect", () => { it("does not affect special attacks", async () => { const moveToUse = Moves.ABSORB; - await game.startBattle([ Species.SHUCKLE ]); + await game.classicMode.startBattle([ Species.SHUCKLE ]); game.move.select(moveToUse); @@ -82,6 +82,17 @@ describe("Moves - Reflect", () => { expect(mockedDmg).toBe(allMoves[moveToUse].power); }); + + it("does not affect critical hits", async () => { + const moveToUse = Moves.WICKED_BLOW; + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + game.move.select(moveToUse); + await game.phaseInterceptor.to(TurnEndPhase); + + const mockedDmg = getMockedMoveDamage(game.scene.getEnemyPokemon()!, game.scene.getPlayerPokemon()!, allMoves[moveToUse]); + expect(mockedDmg).toBe(allMoves[moveToUse].power); + }); }); /** @@ -98,7 +109,9 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { - globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); + if (move.getAttrs(CritOnlyAttr).length === 0) { + globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); + } } return move.power * multiplierHolder.value;