[Bug] Fix: Gravity tag removes flying type during damage calculation (#3670)

* add `hasTag` to arena

* fix flying type damage calculation for grounded states

Before the grounded state would make e.g. electric moves no more very-effective. This is invalid

Co-authored-by: Tristan D Gant <tgant2017@outlook.com>

* add tests for gravity v. ground v. electric

* Update src/test/arena/arena_gravity.test.ts

fix typo

Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com>

* use `arena` instead of this.scene.arena

* use `const arena = this.scene.arean` instead of destructoring

* Apply suggestions from code review

Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>

* fix es-lint

---------

Co-authored-by: Tristan D Gant <tgant2017@outlook.com>
Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com>
Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
This commit is contained in:
flx-sta 2024-08-21 15:40:55 -07:00 committed by GitHub
parent 6b21a777a1
commit 3a167610cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 93 additions and 20 deletions

View File

@ -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<ArenaTag>, 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))

View File

@ -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"));

View File

@ -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);
});
});
});