diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index b1c1b46487c..81ba4b17005 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -633,6 +633,11 @@ export class GravityTag extends ArenaTag { onAdd(arena: Arena): void { arena.scene.queueMessage("Gravity intensified!"); + arena.scene.getField(true).forEach((pokemon) => { + if (pokemon !== null) { + pokemon.removeTag(BattlerTagType.MAGNET_RISEN); + } + }); } onRemove(arena: Arena): void { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 977dceb8d30..7f041e54f52 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1187,8 +1187,11 @@ export class HideSpriteTag extends BattlerTag { export class TypeImmuneTag extends BattlerTag { public immuneType: Type; - constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number) { - super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove); + + constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) { + super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove); + + this.immuneType = immuneType; } /** @@ -1205,6 +1208,18 @@ export class MagnetRisenTag extends TypeImmuneTag { constructor(tagType: BattlerTagType, sourceMove: Moves) { super(tagType, sourceMove, Type.GROUND, 5); } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + pokemon.scene.queueMessage(getPokemonMessage(pokemon, " levitated with electromagnetism!")); + } + + onRemove(pokemon: Pokemon): void { + super.onRemove(pokemon); + + pokemon.scene.queueMessage(getPokemonMessage(pokemon, " stopped levitating!")); + } } export class TypeBoostTag extends BattlerTag { diff --git a/src/data/move.ts b/src/data/move.ts index 83909c7f3c5..715ac533ae7 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -6585,10 +6585,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true), new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4) .attr(AddBattlerTagAttr, BattlerTagType.MAGNET_RISEN, true, true) - .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && - !user.getTag(BattlerTagType.IGNORE_FLYING) && !user.getTag(BattlerTagType.INGRAIN) && - !user.getTag(BattlerTagType.MAGNET_RISEN)) - .unimplemented(), + .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [BattlerTagType.MAGNET_RISEN, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN].every((tag) => !user.getTag(tag))), new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4) .attr(RecoilAttr, false, 0.33) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) @@ -6821,7 +6818,7 @@ export function initMoves() { new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 5) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING]) + .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) .attr(HitsTagAttr, BattlerTagType.FLYING, false) .makesContact(false), new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) @@ -7205,7 +7202,7 @@ export function initMoves() { .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(HitsTagAttr, BattlerTagType.FLYING, false) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING]) + .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN]) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 396f3f3e539..408065cab8b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -18,7 +18,7 @@ import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEv import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases"; import { BattleStat } from "../data/battle-stat"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, TypeImmuneTag, getBattlerTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; import { TempBattleStat } from "../data/temp-battle-stat"; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag"; @@ -1096,7 +1096,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isGrounded(): boolean { - return !this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE); + return !this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN); } /** @@ -1134,6 +1134,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!cancelled.value && !ignoreAbility) { applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelled, typeMultiplier, true); } + return (!cancelled.value ? typeMultiplier.value : 0) as TypeDamageMultiplier; } @@ -1161,6 +1162,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreStrongWinds && this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && multiplier >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { multiplier /= 2; } + + if (!!this.summonData?.tags.find((tag) => tag instanceof TypeImmuneTag && tag.immuneType === moveType)) { + multiplier = 0; + } + return multiplier; } @@ -1728,6 +1734,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { typeMultiplier.value = 0; } + // Apply arena tags for conditional protection if (!move.checkFlag(MoveFlags.IGNORE_PROTECT, source, this) && !move.isAllyTarget()) { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; diff --git a/src/test/moves/magnet_rise.test.ts b/src/test/moves/magnet_rise.test.ts new file mode 100644 index 00000000000..7ed5b84654c --- /dev/null +++ b/src/test/moves/magnet_rise.test.ts @@ -0,0 +1,61 @@ +import {beforeAll, afterEach, beforeEach, describe, vi, it, expect} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import {Moves} from "#enums/moves"; +import {Species} from "#enums/species"; +import {CommandPhase, TurnEndPhase} from "#app/phases.js"; + +describe("Moves - Magnet Rise", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + const moveToUse = Moves.MAGNET_RISE; + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGNEZONE); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DRILL_RUN, Moves.DRILL_RUN, Moves.DRILL_RUN, Moves.DRILL_RUN]); + vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(1); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse, Moves.SPLASH, Moves.GRAVITY, Moves.BATON_PASS]); + }); + + it("MAGNET RISE", async () => { + await game.startBattle(); + + const startingHp = game.scene.getParty()[0].hp; + game.doAttack(0); + await game.phaseInterceptor.to(TurnEndPhase); + const finalHp = game.scene.getParty()[0].hp; + const hpLost = finalHp - startingHp; + expect(hpLost).toBe(0); + }, 20000); + + it("MAGNET RISE - Gravity", async () => { + await game.startBattle(); + + const startingHp = game.scene.getParty()[0].hp; + game.doAttack(0); + await game.phaseInterceptor.to(CommandPhase); + let finalHp = game.scene.getParty()[0].hp; + let hpLost = finalHp - startingHp; + expect(hpLost).toBe(0); + game.doAttack(2); + await game.phaseInterceptor.to(TurnEndPhase); + finalHp = game.scene.getParty()[0].hp; + hpLost = finalHp - startingHp; + expect(hpLost).not.toBe(0); + }, 20000); +});