From 13a4b99072dd8b2d8f304468dc341812036d50a5 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Sat, 9 Aug 2025 13:44:28 -0400 Subject: [PATCH] Added `overrideGameWithChallenges` --- src/game-mode.ts | 17 +- test/abilities/tera-shell.test.ts | 15 + test/battle/inverse-battle.test.ts | 31 +- test/moves/flying-press.test.ts | 38 +- test/moves/freeze-dry.test.ts | 398 +++++------------- test/moves/synchronoise.test.ts | 21 +- .../helpers/challenge-mode-helper.ts | 27 +- test/test-utils/helpers/daily-mode-helper.ts | 2 +- 8 files changed, 198 insertions(+), 351 deletions(-) diff --git a/src/game-mode.ts b/src/game-mode.ts index 82f7b4fa77f..f634a6418eb 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -61,15 +61,24 @@ export class GameMode implements GameModeConfig { /** * Enables challenges if they are disabled and sets the specified challenge's value - * @param challenge The challenge to set - * @param value The value to give the challenge. Impact depends on the specific challenge + * @param challenge - The challenge to set + * @param value - The value to give the challenge. Impact depends on the specific challenge + * @param severity - If provided, will override the given severity amount. Unused if `challenge` does not use severity + * @todo Add severity support to daily mode challenge setting */ - setChallengeValue(challenge: Challenges, value: number) { + setChallengeValue(challenge: Challenges, value: number, severity?: number) { if (!this.isChallenge) { this.isChallenge = true; this.challenges = allChallenges.map(c => copyChallenge(c)); } - this.challenges.filter((chal: Challenge) => chal.id === challenge).map((chal: Challenge) => (chal.value = value)); + this.challenges + .filter((chal: Challenge) => chal.id === challenge) + .forEach(chal => { + chal.value = value; + if (chal.hasSeverity()) { + chal.severity = severity ?? chal.severity; + } + }); } /** diff --git a/test/abilities/tera-shell.test.ts b/test/abilities/tera-shell.test.ts index 4183cd4d0a6..0fe532cdb4f 100644 --- a/test/abilities/tera-shell.test.ts +++ b/test/abilities/tera-shell.test.ts @@ -1,6 +1,7 @@ import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; +import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; import { GameManager } from "#test/test-utils/game-manager"; import Phaser from "phaser"; @@ -113,4 +114,18 @@ describe("Abilities - Tera Shell", () => { } expect(spy).toHaveReturnedTimes(2); }); + + it("should overwrite Freeze-Dry", async () => { + await game.classicMode.startBattle([SpeciesId.TERAPAGOS]); + + const terapagos = game.field.getPlayerPokemon(); + terapagos.summonData.types = [PokemonType.WATER]; + const spy = vi.spyOn(terapagos, "getMoveEffectiveness"); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.FREEZE_DRY); + await game.toEndOfTurn(); + + expect(spy).toHaveLastReturnedWith(0.5); + }); }); diff --git a/test/battle/inverse-battle.test.ts b/test/battle/inverse-battle.test.ts index 66a21e80009..0b16063886b 100644 --- a/test/battle/inverse-battle.test.ts +++ b/test/battle/inverse-battle.test.ts @@ -106,21 +106,6 @@ describe("Inverse Battle", () => { expect(currentHp).toBeGreaterThan((maxHp * 31) / 32 - 1); }); - it("Freeze Dry is 2x effective against Water Type like other Ice type Move - Freeze Dry against Squirtle", async () => { - game.override.moveset([MoveId.FREEZE_DRY]).enemySpecies(SpeciesId.SQUIRTLE); - - await game.challengeMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }); - it("Water Absorb should heal against water moves - Water Absorb against Water gun", async () => { game.override.moveset([MoveId.WATER_GUN]).enemyAbility(AbilityId.WATER_ABSORB); @@ -164,6 +149,7 @@ describe("Inverse Battle", () => { expect(enemy.status?.effect).not.toBe(StatusEffect.PARALYSIS); }); + // TODO: These should belong to their respective moves' test files, not the inverse battle mechanic itself it("Ground type is not immune to Thunder Wave - Thunder Wave against Sandshrew", async () => { game.override.moveset([MoveId.THUNDER_WAVE]).enemySpecies(SpeciesId.SANDSHREW); @@ -202,21 +188,6 @@ describe("Inverse Battle", () => { expect(player.getTypes()[0]).toBe(PokemonType.DRAGON); }); - it("Flying Press should be 0.25x effective against Grass + Dark Type - Flying Press against Meowscarada", async () => { - game.override.moveset([MoveId.FLYING_PRESS]).enemySpecies(SpeciesId.MEOWSCARADA); - - await game.challengeMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FLYING_PRESS); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(0.25); - }); - it("Scrappy ability has no effect - Tackle against Ghost Type still 2x effective with Scrappy", async () => { game.override.moveset([MoveId.TACKLE]).ability(AbilityId.SCRAPPY).enemySpecies(SpeciesId.GASTLY); diff --git a/test/moves/flying-press.test.ts b/test/moves/flying-press.test.ts index 36f572b7a94..96da0a7c051 100644 --- a/test/moves/flying-press.test.ts +++ b/test/moves/flying-press.test.ts @@ -5,7 +5,7 @@ import { Challenges } from "#enums/challenges"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; -import type { PlayerPokemon } from "#field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#field/pokemon"; import { GameManager } from "#test/test-utils/game-manager"; import { getEnumValues } from "#utils/enums"; import { toTitleCase } from "#utils/strings"; @@ -16,6 +16,7 @@ describe.sequential("Move - Flying Press", () => { let phaserGame: Phaser.Game; let game: GameManager; let hawlucha: PlayerPokemon; + let enemy: EnemyPokemon; beforeAll(async () => { phaserGame = new Phaser.Game({ @@ -26,32 +27,29 @@ describe.sequential("Move - Flying Press", () => { game.override .ability(AbilityId.BALL_FETCH) .battleStyle("single") - .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) - .enemyMoveset(MoveId.SPLASH) - .startingLevel(100) - .enemyLevel(100); + .enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.HAWLUCHA]); + hawlucha = game.field.getPlayerPokemon(); + enemy = game.field.getEnemyPokemon(); }); afterAll(() => { game.phaseInterceptor.restoreOg(); }); - // Reset temporary summon data overrides to reset effects + // Reset temp data after each test afterEach(() => { hawlucha.resetSummonData(); - expect(hawlucha).not.toHaveBattlerTag(BattlerTagType.ELECTRIFIED); - expect(hawlucha.hasAbility(AbilityId.NORMALIZE)).toBe(false); + enemy.resetSummonData(); }); const pokemonTypes = getEnumValues(PokemonType); function checkEffForAllTypes(primaryType: PokemonType) { - const enemy = game.field.getEnemyPokemon(); for (const type of pokemonTypes) { enemy.summonData.types = [type]; const primaryEff = enemy.getAttackTypeEffectiveness(primaryType, { source: hawlucha }); @@ -71,7 +69,7 @@ describe.sequential("Move - Flying Press", () => { } } - describe("Normal", () => { + describe("Normal -", () => { it("should deal damage as a Fighting/Flying type move by default", async () => { checkEffForAllTypes(PokemonType.FIGHTING); }); @@ -85,12 +83,25 @@ describe.sequential("Move - Flying Press", () => { hawlucha.setTempAbility(allAbilities[AbilityId.NORMALIZE]); checkEffForAllTypes(PokemonType.NORMAL); }); + + it("should deal 8x damage against a Normal/Ice type with Grass added", () => { + enemy.summonData.types = [PokemonType.NORMAL, PokemonType.ICE]; + enemy.summonData.addedType = PokemonType.GRASS; + + const moveType = hawlucha.getMoveType(allMoves[MoveId.FLYING_PRESS]); + const flyingPressEff = enemy.getAttackTypeEffectiveness(moveType, { + source: hawlucha, + move: allMoves[MoveId.FLYING_PRESS], + }); + expect(flyingPressEff).toBe(8); + }); }); - describe("Inverse", () => { + describe("Inverse Battle -", () => { beforeAll(() => { - game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); + game.challengeMode.overrideGameWithChallenges(Challenges.INVERSE_BATTLE, 1, 1); }); + it("should deal damage as a Fighting/Flying type move by default", async () => { checkEffForAllTypes(PokemonType.FIGHTING); }); @@ -107,10 +118,7 @@ describe.sequential("Move - Flying Press", () => { }); it("should deal 2x to Wonder Guard Shedinja under Electrify", () => { - const enemy = game.field.getEnemyPokemon(); game.field.mockAbility(enemy, AbilityId.WONDER_GUARD); - enemy.resetSummonData(); - hawlucha.addTag(BattlerTagType.ELECTRIFIED); const flyingPressEff = enemy.getAttackTypeEffectiveness(hawlucha.getMoveType(allMoves[MoveId.FLYING_PRESS]), { diff --git a/test/moves/freeze-dry.test.ts b/test/moves/freeze-dry.test.ts index 0b22d4f0997..87ef0c5d210 100644 --- a/test/moves/freeze-dry.test.ts +++ b/test/moves/freeze-dry.test.ts @@ -1,330 +1,140 @@ +import { allMoves } from "#data/data-lists"; +import type { TypeDamageMultiplier } from "#data/type"; import { AbilityId } from "#enums/ability-id"; -import { BattlerIndex } from "#enums/battler-index"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { Challenges } from "#enums/challenges"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; +import type { EnemyPokemon, PlayerPokemon } from "#field/pokemon"; import { GameManager } from "#test/test-utils/game-manager"; +import { stringifyEnumArray } from "#test/test-utils/string-utils"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; -describe("Moves - Freeze-Dry", () => { +type typesArray = [PokemonType] | [PokemonType, PokemonType] | [PokemonType, PokemonType, PokemonType]; + +describe.sequential("Move - Freeze-Dry", () => { let phaserGame: Phaser.Game; let game: GameManager; - beforeAll(() => { + let feebas: PlayerPokemon; + let enemy: EnemyPokemon; + + beforeAll(async () => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, }); - }); - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); - - beforeEach(() => { game = new GameManager(phaserGame); game.override .battleStyle("single") .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH) - .starterSpecies(SpeciesId.FEEBAS) - .ability(AbilityId.BALL_FETCH) - .moveset([MoveId.FREEZE_DRY, MoveId.FORESTS_CURSE, MoveId.SOAK]); + .ability(AbilityId.BALL_FETCH); + + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + feebas = game.field.getPlayerPokemon(); + enemy = game.field.getEnemyPokemon(); }); - it("should deal 2x damage to pure water types", async () => { - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); + // Reset temp data after each test + afterEach(() => { + feebas.resetSummonData(); + enemy.resetSummonData(); + enemy.isTerastallized = false; }); - it("should deal 4x damage to water/flying types", async () => { - game.override.enemySpecies(SpeciesId.WINGULL); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); - }); - - it("should deal 1x damage to water/fire types", async () => { - game.override.enemySpecies(SpeciesId.VOLCANION); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1); + afterAll(() => { + game.phaseInterceptor.restoreOg(); }); /** - * Freeze drys forced super effectiveness should overwrite wonder guard + * Check that Freeze-Dry is the given effectiveness against the given type. + * @param types - The base {@linkcode PokemonType}s to set; will populate `addedType` if above 3 + * @param multi - The expected {@linkcode TypeDamageMultiplier} */ - it("should deal 2x dmg against soaked wonder guard target", async () => { - game.override - .enemySpecies(SpeciesId.SHEDINJA) - .enemyMoveset(MoveId.SPLASH) - .starterSpecies(SpeciesId.MAGIKARP) - .moveset([MoveId.SOAK, MoveId.FREEZE_DRY]); - await game.classicMode.startBattle(); + function expectEffectiveness(types: typesArray, multi: TypeDamageMultiplier): void { + enemy.summonData.types = types.slice(0, 2); + if (types[2] !== undefined) { + enemy.summonData.addedType = types[2]; + } - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); + const moveType = feebas.getMoveType(allMoves[MoveId.FREEZE_DRY]); + const eff = enemy.getAttackTypeEffectiveness(moveType, { source: feebas, move: allMoves[MoveId.FREEZE_DRY] }); + expect( + eff, + `Freeze-dry effectiveness against ${stringifyEnumArray(PokemonType, types)} was ${eff} instead of ${multi}!`, + ).toBe(multi); + } - game.move.select(MoveId.SOAK); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.toNextTurn(); + describe("Normal -", () => { + it.each<{ name: string; types: typesArray; eff: TypeDamageMultiplier }>([ + { name: "Pure Water", types: [PokemonType.WATER], eff: 2 }, + { name: "Water/Ground", types: [PokemonType.WATER, PokemonType.GROUND], eff: 4 }, + { name: "Water/Flying/Grass", types: [PokemonType.WATER, PokemonType.FLYING, PokemonType.GRASS], eff: 8 }, + { name: "Water/Fire", types: [PokemonType.WATER, PokemonType.FIRE], eff: 1 }, + ])("should be $effx effective against a $name-type opponent", ({ types, eff }) => { + expectEffectiveness(types, eff); + }); - game.move.select(MoveId.FREEZE_DRY); - await game.phaseInterceptor.to("MoveEffectPhase"); + it("should deal 2x dmg against soaked wonder guard target", async () => { + game.field.mockAbility(enemy, AbilityId.WONDER_GUARD); - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expectEffectiveness([PokemonType.WATER], 2); + }); + + it("should consider the target's Tera Type", async () => { + // Steel type terastallized into Water; 2x + enemy.teraType = PokemonType.WATER; + enemy.isTerastallized = true; + + expectEffectiveness([PokemonType.STEEL], 2); + + // Water type terastallized into steel; 0.5x + enemy.teraType = PokemonType.STEEL; + expectEffectiveness([PokemonType.WATER], 2); + }); + + it.each<{ name: string; types: typesArray; eff: TypeDamageMultiplier }>([ + { name: "Pure Water", types: [PokemonType.WATER], eff: 2 }, + { name: "Water/Ghost", types: [PokemonType.WATER, PokemonType.GHOST], eff: 0 }, + ])("should be $effx effective against a $name-type opponent with Normalize", ({ types, eff }) => { + game.field.mockAbility(feebas, AbilityId.NORMALIZE); + expectEffectiveness(types, eff); + }); + + it("should not stack with Electrify", async () => { + feebas.addTag(BattlerTagType.ELECTRIFIED); + expect(feebas.getMoveType(allMoves[MoveId.FREEZE_DRY])).toBe(PokemonType.ELECTRIC); + + expectEffectiveness([PokemonType.WATER], 2); + }); }); - it("should deal 8x damage to water/ground/grass type under Forest's Curse", async () => { - game.override.enemySpecies(SpeciesId.QUAGSIRE); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FORESTS_CURSE); - await game.toNextTurn(); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(8); - }); - - it("should deal 2x damage to steel type terastallized into water", async () => { - game.override.enemySpecies(SpeciesId.SKARMORY); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - enemy.teraType = PokemonType.WATER; - enemy.isTerastallized = true; - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }); - - it("should deal 0.5x damage to water type terastallized into fire", async () => { - game.override.enemySpecies(SpeciesId.PELIPPER); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - enemy.teraType = PokemonType.FIRE; - enemy.isTerastallized = true; - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.5); - }); - - it("should deal 0.5x damage to water type Terapagos with Tera Shell", async () => { - game.override.enemySpecies(SpeciesId.TERAPAGOS).enemyAbility(AbilityId.TERA_SHELL); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.SOAK); - await game.toNextTurn(); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.5); - }); - - it("should deal 2x damage to water type under Normalize", async () => { - game.override.ability(AbilityId.NORMALIZE); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }); - - it("should deal 0.25x damage to rock/steel type under Normalize", async () => { - game.override.ability(AbilityId.NORMALIZE).enemySpecies(SpeciesId.SHIELDON); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.25); - }); - - it("should deal 0x damage to water/ghost type under Normalize", async () => { - game.override.ability(AbilityId.NORMALIZE).enemySpecies(SpeciesId.JELLICENT); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0); - }); - - it("should deal 2x damage to water type under Electrify", async () => { - game.override.enemyMoveset([MoveId.ELECTRIFY]); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); - }); - - it("should deal 4x damage to water/flying type under Electrify", async () => { - game.override.enemyMoveset([MoveId.ELECTRIFY]).enemySpecies(SpeciesId.GYARADOS); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); - }); - - it("should deal 0x damage to water/ground type under Electrify", async () => { - game.override.enemyMoveset([MoveId.ELECTRIFY]).enemySpecies(SpeciesId.BARBOACH); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0); - }); - - it("should deal 0.25x damage to Grass/Dragon type under Electrify", async () => { - game.override.enemyMoveset([MoveId.ELECTRIFY]).enemySpecies(SpeciesId.FLAPPLE); - await game.classicMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.25); - }); - - it("should deal 2x damage to Water type during inverse battle", async () => { - game.override.moveset([MoveId.FREEZE_DRY]).enemySpecies(SpeciesId.MAGIKARP); - game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); - - await game.challengeMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }); - - it("should deal 2x damage to Water type during inverse battle under Normalize", async () => { - game.override.moveset([MoveId.FREEZE_DRY]).ability(AbilityId.NORMALIZE).enemySpecies(SpeciesId.MAGIKARP); - game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); - - await game.challengeMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }); - - it("should deal 2x damage to Water type during inverse battle under Electrify", async () => { - game.override.moveset([MoveId.FREEZE_DRY]).enemySpecies(SpeciesId.MAGIKARP).enemyMoveset([MoveId.ELECTRIFY]); - game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); - - await game.challengeMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("MoveEffectPhase"); - - expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); - }); - - it("should deal 1x damage to water/flying type during inverse battle under Electrify", async () => { - game.override.enemyMoveset([MoveId.ELECTRIFY]).enemySpecies(SpeciesId.GYARADOS); - - game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); - - await game.challengeMode.startBattle(); - - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(enemy, "getMoveEffectiveness"); - - game.move.select(MoveId.FREEZE_DRY); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase"); - - expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1); + describe("Inverse Battle -", () => { + beforeAll(() => { + game.challengeMode.overrideGameWithChallenges(Challenges.INVERSE_BATTLE, 1, 1); + }); + + it("should deal 2x damage to Water type", async () => { + expectEffectiveness([PokemonType.WATER], 2); + }); + + it("should deal 2x damage to Water type under Normalize", async () => { + game.field.mockAbility(feebas, AbilityId.NORMALIZE); + expectEffectiveness([PokemonType.WATER], 2); + }); + + it("should still deal 2x damage to Water type under Electrify", async () => { + feebas.addTag(BattlerTagType.ELECTRIFIED); + expectEffectiveness([PokemonType.WATER], 2); + }); + + it("should deal 1x damage to Water/Flying type under Electrify", async () => { + feebas.addTag(BattlerTagType.ELECTRIFIED); + expectEffectiveness([PokemonType.WATER, PokemonType.FLYING], 1); + }); }); }); diff --git a/test/moves/synchronoise.test.ts b/test/moves/synchronoise.test.ts index 98178b66d00..4fb1a7ca161 100644 --- a/test/moves/synchronoise.test.ts +++ b/test/moves/synchronoise.test.ts @@ -32,16 +32,25 @@ describe("Moves - Synchronoise", () => { .enemyMoveset(MoveId.SPLASH); }); - it("should consider the user's tera type if it is terastallized", async () => { + // TODO: Write test + it.todo("should affect all opponents that share a type with the user"); + + it("should consider the user's Tera Type if it is Terastallized", async () => { await game.classicMode.startBattle([SpeciesId.BIDOOF]); + const playerPokemon = game.field.getPlayerPokemon(); const enemyPokemon = game.field.getEnemyPokemon(); - // force the player to be terastallized playerPokemon.teraType = PokemonType.WATER; - playerPokemon.isTerastallized = true; - game.move.select(MoveId.SYNCHRONOISE); - await game.phaseInterceptor.to("BerryPhase"); - expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + game.move.selectWithTera(MoveId.SYNCHRONOISE); + await game.toEndOfTurn(); + + expect(enemyPokemon).not.toHaveFullHp(); }); + + // TODO: Write test + it.todo("should fail if no opponents share a type with the user"); + + // TODO: Write test + it.todo("should fail if the user is typeless"); }); diff --git a/test/test-utils/helpers/challenge-mode-helper.ts b/test/test-utils/helpers/challenge-mode-helper.ts index 3952685a560..16b08c1389a 100644 --- a/test/test-utils/helpers/challenge-mode-helper.ts +++ b/test/test-utils/helpers/challenge-mode-helper.ts @@ -12,6 +12,8 @@ import { generateStarter } from "#test/test-utils/game-manager-utils"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { copyChallenge } from "data/challenge"; +type challengeStub = { id: Challenges; value: number; severity: number }; + /** * Helper to handle Challenge mode specifics */ @@ -33,8 +35,9 @@ export class ChallengeModeHelper extends GameManagerHelper { * Runs the Challenge game to the summon phase. * @param gameMode - Optional game mode to set. * @returns A promise that resolves when the summon phase is reached. + * @todo this duplicates nearly all its code with the classic mode variant... */ - async runToSummon(species?: SpeciesId[]) { + private async runToSummon(species?: SpeciesId[]) { await this.game.runToTitle(); if (this.game.override.disableShinies) { @@ -88,4 +91,26 @@ export class ChallengeModeHelper extends GameManagerHelper { await this.game.phaseInterceptor.to(CommandPhase); console.log("==================[New Turn]=================="); } + + /** + * Override an already-started game with the given challenges. + * @param id - The challenge id + * @param value - The challenge value + * @param severity - The challenge severity + * @todo Make severity optional for challenges that do not require it + */ + public overrideGameWithChallenges(id: Challenges, value: number, severity: number): void; + /** + * Override an already-started game with the given challenges. + * @param challenges - One or more challenges to set. + */ + public overrideGameWithChallenges(challenges: challengeStub[]): void; + public overrideGameWithChallenges(challenges: challengeStub[] | Challenges, value?: number, severity?: number): void { + if (typeof challenges !== "object") { + challenges = [{ id: challenges, value: value!, severity: severity! }]; + } + for (const challenge of challenges) { + this.game.scene.gameMode.setChallengeValue(challenge.id, challenge.value, challenge.severity); + } + } } diff --git a/test/test-utils/helpers/daily-mode-helper.ts b/test/test-utils/helpers/daily-mode-helper.ts index 7aa1e699118..a288b52e24f 100644 --- a/test/test-utils/helpers/daily-mode-helper.ts +++ b/test/test-utils/helpers/daily-mode-helper.ts @@ -18,7 +18,7 @@ export class DailyModeHelper extends GameManagerHelper { * @returns A promise that resolves when the summon phase is reached. * @remarks Please do not use for starting normal battles - use {@linkcode startBattle} instead */ - async runToSummon(): Promise { + private async runToSummon(): Promise { await this.game.runToTitle(); if (this.game.override.disableShinies) {