diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 35a33907850..863b0f41d2c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1427,22 +1427,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } let multiplier = types.map(defType => { + const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType)); + applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier); if (source) { const ignoreImmunity = new Utils.BooleanHolder(false); if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType); } if (ignoreImmunity.value) { - return 1; + if (multiplier.value === 0) { + return 1; + } } const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[]; if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) { - return 1; + if (multiplier.value === 0) { + return 1; + } } } - const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType)); - applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier); return multiplier.value; }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; diff --git a/src/test/battle/inverse_battle.test.ts b/src/test/battle/inverse_battle.test.ts index be8b04155eb..2a561a09e5e 100644 --- a/src/test/battle/inverse_battle.test.ts +++ b/src/test/battle/inverse_battle.test.ts @@ -1,8 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/move"; import { Type } from "#app/data/type"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Challenges } from "#enums/challenges"; @@ -11,7 +8,8 @@ import { Species } from "#enums/species"; import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const TIMEOUT = 20 * 1000; @@ -39,43 +37,63 @@ describe("Inverse Battle", () => { .starterSpecies(Species.FEEBAS) .ability(Abilities.BALL_FETCH) .enemySpecies(Species.MAGIKARP) - .enemyAbility(Abilities.BALL_FETCH); + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(SPLASH_ONLY); }); - it("1. immune types are 2x effective - Thunderbolt against Ground Type", async () => { - game.override.enemySpecies(Species.SANDSHREW); + it("Immune types are 2x effective - Thunderbolt against Ground Type", async () => { + game.override + .moveset([Moves.THUNDERBOLT]) + .enemySpecies(Species.SANDSHREW); + await game.challengeMode.startBattle(); - const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); - expect(enemy.getMoveEffectiveness(player, allMoves[Moves.THUNDERBOLT])).toBe(2); + game.move.select(Moves.THUNDERBOLT); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); }, TIMEOUT); - it("2. 2x effective types are 0.5x effective - Thunderbolt against Flying Type", async () => { - game.override.enemySpecies(Species.PIDGEY); + it("2x effective types are 0.5x effective - Thunderbolt against Flying Type", async () => { + game.override + .moveset([Moves.THUNDERBOLT]) + .enemySpecies(Species.PIDGEY); await game.challengeMode.startBattle(); - const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); - expect(enemy.getMoveEffectiveness(player, allMoves[Moves.THUNDERBOLT])).toBe(0.5); + game.move.select(Moves.THUNDERBOLT); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(0.5); }, TIMEOUT); - it("3. 0.5x effective types are 2x effective - Thunderbolt against Electric Type", async () => { - game.override.enemySpecies(Species.CHIKORITA); + it("0.5x effective types are 2x effective - Thunderbolt against Electric Type", async () => { + game.override + .moveset([Moves.THUNDERBOLT]) + .enemySpecies(Species.CHIKORITA); await game.challengeMode.startBattle(); - const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); - expect(enemy.getMoveEffectiveness(player, allMoves[Moves.THUNDERBOLT])).toBe(2); + game.move.select(Moves.THUNDERBOLT); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); }, TIMEOUT); - it("4. Stealth Rock follows the inverse matchups - Stealth Rock against Charizard deals 1/32 of max HP", async () => { + it("Stealth Rock follows the inverse matchups - Stealth Rock against Charizard deals 1/32 of max HP", async () => { game.scene.arena.addTag(ArenaTagType.STEALTH_ROCK, 1, Moves.STEALTH_ROCK, 0); game.override .enemySpecies(Species.CHARIZARD) @@ -95,18 +113,24 @@ describe("Inverse Battle", () => { expect(currentHp).toBeGreaterThan(maxHp * 31 / 32 - 1); }, TIMEOUT); - it("5. Freeze Dry is 2x effective against Water Type like other Ice type Move - Freeze Dry against Squirtle", async () => { - game.override.enemySpecies(Species.SQUIRTLE); + it("Freeze Dry is 2x effective against Water Type like other Ice type Move - Freeze Dry against Squirtle", async () => { + game.override + .moveset([Moves.FREEZE_DRY]) + .enemySpecies(Species.SQUIRTLE); await game.challengeMode.startBattle(); - const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); - expect(enemy.getMoveEffectiveness(player, allMoves[Moves.FREEZE_DRY])).toBe(2); + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); }, TIMEOUT); - it("6. Water Absorb should heal against water moves - Water Absorb against Water gun", async () => { + it("Water Absorb should heal against water moves - Water Absorb against Water gun", async () => { game.override .moveset([Moves.WATER_GUN]) .enemyAbility(Abilities.WATER_ABSORB); @@ -117,13 +141,12 @@ describe("Inverse Battle", () => { enemy.hp = enemy.getMaxHp() - 1; game.move.select(Moves.WATER_GUN); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - - await game.phaseInterceptor.to(MoveEndPhase); + await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.hp).toBe(enemy.getMaxHp()); }, TIMEOUT); - it("7. Fire type does not get burned - Will-O-Wisp against Charmander", async () => { + it("Fire type does not get burned - Will-O-Wisp against Charmander", async () => { game.override .moveset([Moves.WILL_O_WISP]) .enemySpecies(Species.CHARMANDER); @@ -135,13 +158,12 @@ describe("Inverse Battle", () => { game.move.select(Moves.WILL_O_WISP); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.move.forceHit(); - - await game.phaseInterceptor.to(MoveEndPhase); + await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.status?.effect).not.toBe(StatusEffect.BURN); }, TIMEOUT); - it("8. Electric type does not get paralyzed - Nuzzle against Pikachu", async () => { + it("Electric type does not get paralyzed - Nuzzle against Pikachu", async () => { game.override .moveset([Moves.NUZZLE]) .enemySpecies(Species.PIKACHU) @@ -153,14 +175,30 @@ describe("Inverse Battle", () => { game.move.select(Moves.NUZZLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - - await game.phaseInterceptor.to(MoveEndPhase); + await game.phaseInterceptor.to("MoveEndPhase"); expect(enemy.status?.effect).not.toBe(StatusEffect.PARALYSIS); }, TIMEOUT); + it("Ground type is not immune to Thunder Wave - Thunder Wave against Sandshrew", async () => { + game.override + .moveset([Moves.THUNDER_WAVE]) + .enemySpecies(Species.SANDSHREW); - it("10. Anticipation should trigger on 2x effective moves - Anticipation against Thunderbolt", async () => { + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.THUNDER_WAVE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.move.forceHit(); + await game.phaseInterceptor.to("MoveEndPhase"); + + expect(enemy.status?.effect).toBe(StatusEffect.PARALYSIS); + }, TIMEOUT); + + + it("Anticipation should trigger on 2x effective moves - Anticipation against Thunderbolt", async () => { game.override .moveset([Moves.THUNDERBOLT]) .enemySpecies(Species.SANDSHREW) @@ -171,7 +209,7 @@ describe("Inverse Battle", () => { expect(game.scene.getEnemyPokemon()?.summonData.abilitiesApplied[0]).toBe(Abilities.ANTICIPATION); }, TIMEOUT); - it("11. Conversion 2 should change the type to the resistive type - Conversion 2 against Dragonite", async () => { + it("Conversion 2 should change the type to the resistive type - Conversion 2 against Dragonite", async () => { game.override .moveset([Moves.CONVERSION_2]) .enemyMoveset([Moves.DRAGON_CLAW, Moves.DRAGON_CLAW, Moves.DRAGON_CLAW, Moves.DRAGON_CLAW]); @@ -183,21 +221,64 @@ describe("Inverse Battle", () => { game.move.select(Moves.CONVERSION_2); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(player.getTypes()[0]).toBe(Type.DRAGON); }, TIMEOUT); - it("12. Flying Press should be 0.25x effective against Grass + Dark Type - Flying Press against Meowscarada", async () => { + it("Flying Press should be 0.25x effective against Grass + Dark Type - Flying Press against Meowscarada", async () => { game.override .moveset([Moves.FLYING_PRESS]) .enemySpecies(Species.MEOWSCARADA); await game.challengeMode.startBattle(); - const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); - expect(enemy.getMoveEffectiveness(player, allMoves[Moves.FLYING_PRESS])).toBe(0.25); + game.move.select(Moves.FLYING_PRESS); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(0.25); + }, TIMEOUT); + + it("Scrappy ability has no effect - Tackle against Ghost Type still 2x effective with Scrappy", async () => { + game.override + .moveset([Moves.TACKLE]) + .ability(Abilities.SCRAPPY) + .enemySpecies(Species.GASTLY); + + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.TACKLE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); + }, TIMEOUT); + + it("FORESIGHT has no effect - Tackle against Ghost Type still 2x effective with Foresight", async () => { + game.override + .moveset([Moves.FORESIGHT, Moves.TACKLE]) + .enemySpecies(Species.GASTLY); + + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FORESIGHT); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("TurnEndPhase"); + + game.move.select(Moves.TACKLE); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); }, TIMEOUT); });