diff --git a/src/field/arena.ts b/src/field/arena.ts index eb3770d61d5..2ef6ce7dab3 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -584,6 +584,10 @@ export class Arena { return this.getTagOnSide(tagType, ArenaTagSide.BOTH); } + hasTag(tagType: ArenaTagType) : boolean { + return !!this.getTag(tagType); + } + getTagOnSide(tagType: ArenaTagType | Constructor, side: ArenaTagSide): ArenaTag | undefined { return typeof(tagType) === "string" ? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6a445a83b4e..b3a96a5f1fc 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -978,12 +978,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // this.scene potentially can be undefined for a fainted pokemon in doubles // use optional chaining to avoid runtime errors - if (forDefend && (this.getTag(GroundedTag) || this.scene?.arena.getTag(ArenaTagType.GRAVITY))) { - const flyingIndex = types.indexOf(Type.FLYING); - if (flyingIndex > -1) { - types.splice(flyingIndex, 1); - } - } if (!types.length) { // become UNKNOWN if no types are present types.push(Type.UNKNOWN); @@ -1272,6 +1266,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.isTerastallized() ? 2 : 1; } const types = this.getTypes(true, true); + const arena = this.scene.arena; + + // Handle flying v ground type immunity without removing flying type so effective types are still effective + // Related to https://github.com/pagefaultgames/pokerogue/issues/524 + if (moveType === Type.GROUND && (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY))) { + const flyingIndex = types.indexOf(Type.FLYING); + if (flyingIndex > -1) { + types.splice(flyingIndex, 1); + } + } let multiplier = types.map(defType => { if (source) { @@ -1293,7 +1297,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; // Handle strong winds lowering effectiveness of types super effective against pure flying - if (!ignoreStrongWinds && this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { + if (!ignoreStrongWinds && arena.weather?.weatherType === WeatherType.STRONG_WINDS && !arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { multiplier /= 2; if (!simulated) { this.scene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); diff --git a/src/test/arena/arena_gravity.test.ts b/src/test/arena/arena_gravity.test.ts index 68c31258454..8fad4dde83d 100644 --- a/src/test/arena/arena_gravity.test.ts +++ b/src/test/arena/arena_gravity.test.ts @@ -1,14 +1,15 @@ -import { allMoves } from "#app/data/move.js"; -import { Abilities } from "#app/enums/abilities.js"; -import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import GameManager from "#test/utils/gameManager"; -import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#app/enums/abilities"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; -import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { SPLASH_ONLY } from "../utils/testUtils"; describe("Arena - Gravity", () => { let phaserGame: Phaser.Game; @@ -26,14 +27,17 @@ describe("Arena - Gravity", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleType("single"); - game.override.moveset([Moves.TACKLE, Moves.GRAVITY, Moves.FISSURE]); - game.override.ability(Abilities.UNNERVE); - game.override.enemyAbility(Abilities.BALL_FETCH); - game.override.enemySpecies(Species.SHUCKLE); - game.override.enemyMoveset(new Array(4).fill(Moves.SPLASH)); + game.override + .battleType("single") + .moveset([Moves.TACKLE, Moves.GRAVITY, Moves.FISSURE]) + .ability(Abilities.UNNERVE) + .enemyAbility(Abilities.BALL_FETCH) + .enemySpecies(Species.SHUCKLE) + .enemyMoveset(SPLASH_ONLY); }); + // Reference: https://bulbapedia.bulbagarden.net/wiki/Gravity_(move) + it("non-OHKO move accuracy is multiplied by 1.67", async () => { const moveToCheck = allMoves[Moves.TACKLE]; @@ -77,4 +81,65 @@ describe("Arena - Gravity", () => { expect(moveToCheck.calculateBattleAccuracy).toHaveReturnedWith(30); }); + + describe("Against flying types", () => { + it("can be hit by ground-type moves now", async () => { + game.override + .startingLevel(5) + .enemyLevel(5) + .enemySpecies(Species.PIDGEOT) + .moveset([Moves.GRAVITY, Moves.EARTHQUAKE]); + + await game.startBattle([Species.PIKACHU]); + + const pidgeot = game.scene.getEnemyPokemon()!; + vi.spyOn(pidgeot, "getAttackTypeEffectiveness"); + + // Try earthquake on 1st turn (fails!); + game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(0); + + // Setup Gravity on 2nd turn + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.GRAVITY)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); + + // Use ground move on 3rd turn + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(1); + }); + + it("keeps super-effective moves super-effective after using gravity", async () => { + game.override + .startingLevel(5) + .enemyLevel(5) + .enemySpecies(Species.PIDGEOT) + .moveset([Moves.GRAVITY, Moves.THUNDERBOLT]); + + await game.startBattle([Species.PIKACHU]); + + const pidgeot = game.scene.getEnemyPokemon()!; + vi.spyOn(pidgeot, "getAttackTypeEffectiveness"); + + // Setup Gravity on 1st turn + game.doAttack(getMovePosition(game.scene, 0, Moves.GRAVITY)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(game.scene.arena.getTag(ArenaTagType.GRAVITY)).toBeDefined(); + + // Use electric move on 2nd turn + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT)); + await game.phaseInterceptor.to(TurnEndPhase); + + expect(pidgeot.getAttackTypeEffectiveness).toHaveReturnedWith(2); + }); + }); });