From 8c301c184afd734310da13429079d2a78df07f7b Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Thu, 4 Sep 2025 22:28:00 -0400 Subject: [PATCH] gdddddddd --- src/data/moves/move.ts | 25 ++++---- src/enums/battler-tag-type.ts | 1 + src/field/pokemon.ts | 8 +-- test/arena/terrain.test.ts | 2 +- test/moves/smack-down-thousand-arrows.test.ts | 25 ++++---- test/moves/telekinesis.test.ts | 57 ++++++++++--------- 6 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 23ea033d4f0..1946f27786a 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -5803,26 +5803,17 @@ export class LeechSeedAttr extends AddBattlerTagAttr { } /** - * Attribute to add the {@linkcode BattlerTagType.IGNORE_FLYING | IGNORE_FLYING} battler tag to the target + * Attribute to add the {@linkcode BattlerTagType.IGNORE_FLYING | IGNORE_FLYING} BattlerTag to the target * and remove any prior sources of ungroundedness. * - * Does nothing if the target was not already ungrounded. + * Used by {@linkcode MoveId.SMACK_DOWN} and {@linkcode MoveId.THOUSAND_ARROWS}, + * and does nothing if the target was not already ungrounded. */ export class FallDownAttr extends AddBattlerTagAttr { constructor() { super(BattlerTagType.IGNORE_FLYING, false, false, 0, 0, true); } - /** - * Add `GroundedTag` to the target, remove all prior sources of ungroundedness - * and display a message. - * @param user - The {@linkcode Pokemon} using the move - * @param target - The {@linkcode Pokemon} targeted by the move - * @param move - The {@linkcode Move} invoking this effect - * @param args n/a - * @returns Whether the target was successfully brought down to earth. - * - */ apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { // Smack down and similar only add their tag if the target is already ungrounded, // barring any prior semi-invulnerability. @@ -5830,9 +5821,13 @@ export class FallDownAttr extends AddBattlerTagAttr { return false; } + if (!super.apply(user, target, move, _args)) { + return false; + } + // Remove the target's prior sources of ungroundedness. - // NB: These effects cannot simply be part of the tag's `onAdd` effect as Ingrain also adds the tag - // but does not remove Telekinesis' accuracy boost + // NB: These effects cannot simply be part of the tag's `onAdd` effect as Ingrain + // also forcibly grounds the user without removing Telekinesis' accuracy boost target.removeTag(BattlerTagType.FLOATING); target.removeTag(BattlerTagType.TELEKINESIS); if (target.getTag(BattlerTagType.FLYING)) { @@ -5842,7 +5837,7 @@ export class FallDownAttr extends AddBattlerTagAttr { } globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) })); - return super.apply(user, target, move, _args); + return true; } } diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 6d9d2dd4a92..fbe68f0cb84 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -48,6 +48,7 @@ export enum BattlerTagType { ALWAYS_CRIT = "ALWAYS_CRIT", IGNORE_ACCURACY = "IGNORE_ACCURACY", BYPASS_SLEEP = "BYPASS_SLEEP", + // TODO: Rename this to "Smacked Down" or "Forcibly Grounded" IGNORE_FLYING = "IGNORE_FLYING", SALT_CURED = "SALT_CURED", CURSED = "CURSED", diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 97735567b40..3f31ab3dc74 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2284,13 +2284,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { /** * Return whether this Pokemon is currently on the ground. * - * To be considered grounded, a Pokemon must either: - * * Be {@linkcode GroundedTag | forcibly grounded} from an effect like Smack Down or Ingrain + * To be considered grounded, a Pokemon must fulfill any of the following criteria: + * * Be {@linkcode BattlerTagType.IGNORE_FLYING | forcibly grounded} from an effect like Smack Down or Ingrain * * Be under the effects of {@linkcode ArenaTagType.GRAVITY | harsh gravity} - * * **Not** be all of the following things: + * * **Not** be any of the following things: * * {@linkcode PokemonType.FLYING | Flying-type} * * {@linkcode AbilityId.LEVITATE | Levitating} - * * {@linkcode BattlerTagType.FLOATING | Floating} from Magnet Rise or Telekinesis. + * * {@linkcode BattlerTagType.FLOATING | Floating} from Magnet Rise or Telekinesis * * {@linkcode SemiInvulnerableTag | Semi-invulnerable} with `ignoreSemiInvulnerable` set to `false` * @param ignoreSemiInvulnerable - Whether to ignore the target's semi-invulnerable state when determining groundedness; default `false` diff --git a/test/arena/terrain.test.ts b/test/arena/terrain.test.ts index 28b137d4f09..630c116c77d 100644 --- a/test/arena/terrain.test.ts +++ b/test/arena/terrain.test.ts @@ -43,7 +43,7 @@ describe("Terrain -", () => { .passiveAbility(AbilityId.NO_GUARD); }); - // TODO: Terrain boosts currently apply to damage dealt, not base power + // TODO: Terrain boosts currently apply directly to damage dealt, not base power describe.todo.each<{ name: string; type: PokemonType; terrain: TerrainType; move: MoveId }>([ { name: "Electric", type: PokemonType.ELECTRIC, terrain: TerrainType.ELECTRIC, move: MoveId.THUNDERBOLT }, { name: "Psychic", type: PokemonType.PSYCHIC, terrain: TerrainType.PSYCHIC, move: MoveId.PSYCHIC }, diff --git a/test/moves/smack-down-thousand-arrows.test.ts b/test/moves/smack-down-thousand-arrows.test.ts index 4e8cdabf8f3..5226a7a6a03 100644 --- a/test/moves/smack-down-thousand-arrows.test.ts +++ b/test/moves/smack-down-thousand-arrows.test.ts @@ -40,17 +40,17 @@ describe("Moves - Smack Down and Thousand Arrows", () => { { name: "Smack Down", move: MoveId.SMACK_DOWN }, { name: "Thousand Arrows", move: MoveId.THOUSAND_ARROWS }, ])("$name should hit and ground ungrounded targets", async ({ move }) => { - game.override.enemySpecies(SpeciesId.TORNADUS); + game.override.enemySpecies(SpeciesId.ROOKIDEE); await game.classicMode.startBattle([SpeciesId.ILLUMISE]); - const enemy = game.field.getEnemyPokemon(); - expect(enemy.isGrounded()).toBe(false); + const rookidee = game.field.getEnemyPokemon(); + expect(rookidee.isGrounded()).toBe(false); game.move.use(move); await game.toEndOfTurn(); - expect(enemy).toHaveBattlerTag(BattlerTagType.IGNORE_FLYING); - expect(enemy.isGrounded()).toBe(true); + expect(rookidee).toHaveBattlerTag(BattlerTagType.IGNORE_FLYING); + expect(rookidee.isGrounded()).toBe(true); }); it("should affect targets with Levitate", async () => { @@ -86,17 +86,22 @@ describe("Moves - Smack Down and Thousand Arrows", () => { // NB: This test might sound useless, but semi-invulnerable pokemon are technically considered "ungrounded" // by most things - it("should not ground semi-invulnerable targets hit via No Guard unless otherwise ungrounded", async () => { + it("should not consider semi-invulnerability as a valid source of ungroundedness", async () => { game.override.ability(AbilityId.NO_GUARD); await game.classicMode.startBattle([SpeciesId.ILLUMISE]); game.move.use(MoveId.THOUSAND_ARROWS); await game.move.forceEnemyMove(MoveId.DIG); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("MoveEndPhase"); + + // Eelektross should be grounded solely due to using Dig, + const eelektross = game.field.getEnemyPokemon(); + expect(eelektross.isGrounded()).toBe(false); + expect(eelektross.isGrounded(true)).toBe(true); await game.toEndOfTurn(); // Eelektross took damage but was not forcibly grounded - const eelektross = game.field.getEnemyPokemon(); expect(eelektross.isGrounded()).toBe(true); expect(eelektross).not.toHaveBattlerTag(BattlerTagType.IGNORE_FLYING); expect(eelektross).not.toHaveFullHp(); @@ -112,23 +117,23 @@ describe("Moves - Smack Down and Thousand Arrows", () => { hitSpy = vi.spyOn(MoveEffectPhase.prototype, "hitCheck"); }); - it("should deal a fixed 1x damage when hitting ungrounded Flying-types", async () => { + it("should have 1x type effectiveness when hitting ungrounded Flying-types", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); const archeops = game.field.getEnemyPokemon(); + // first turn: 1x game.move.use(MoveId.THOUSAND_ARROWS); await game.toEndOfTurn(); - // first turn: 1x expect(archeops).not.toHaveFullHp(); expect(archeops.isGrounded()).toBe(true); expect(hitSpy).toHaveLastReturnedWith([expect.anything(), 1]); + // 2nd turn: 2x game.move.use(MoveId.THOUSAND_ARROWS); await game.toEndOfTurn(); - // 2nd turn: 2x expect(hitSpy).toHaveLastReturnedWith([expect.anything(), 2]); }); diff --git a/test/moves/telekinesis.test.ts b/test/moves/telekinesis.test.ts index fe0fc101232..18034f39845 100644 --- a/test/moves/telekinesis.test.ts +++ b/test/moves/telekinesis.test.ts @@ -71,39 +71,41 @@ describe("Move - Telekinesis", () => { species: s, name: getEnumStr(SpeciesId, s, { casing: "Title" }), })); - it.each(invalidSpecies)("should fail if used on a $name, but can be baton passable onto one", async ({ species }) => { - await game.classicMode.startBattle([species, SpeciesId.FEEBAS]); + it.each(invalidSpecies)( + "should fail if used on a $name, but can still be baton passed onto one", + async ({ species }) => { + await game.classicMode.startBattle([species, SpeciesId.FEEBAS]); - const invalidMon = game.field.getPlayerPokemon(); - expect(invalidMon.species.speciesId).toBe(species); + const [invalidMon, feebas] = game.scene.getPlayerParty(); + expect(invalidMon.species.speciesId).toBe(species); - game.move.use(MoveId.TELEPORT); - game.doSelectPartyPokemon(1); - await game.move.forceEnemyMove(MoveId.TELEKINESIS); - await game.toNextTurn(); + game.move.use(MoveId.TELEPORT); + game.doSelectPartyPokemon(1); + await game.move.forceEnemyMove(MoveId.TELEKINESIS); + await game.toNextTurn(); - // Adding tags directly did not work - const enemy = game.field.getEnemyPokemon(); - expect(enemy).toHaveUsedMove({ move: MoveId.TELEKINESIS, result: MoveResult.FAIL }); + // Adding tags directly did not work + const enemy = game.field.getEnemyPokemon(); + expect(enemy).toHaveUsedMove({ move: MoveId.TELEKINESIS, result: MoveResult.FAIL }); - expect(invalidMon.isOnField()).toBe(false); + expect(invalidMon.isOnField()).toBe(false); - game.move.use(MoveId.BATON_PASS); - game.doSelectPartyPokemon(1); - await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("MoveEndPhase"); + game.move.use(MoveId.BATON_PASS); + game.doSelectPartyPokemon(1); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("MoveEndPhase"); - const feebas = game.field.getPlayerPokemon(); - expect(feebas).toHaveBattlerTag(BattlerTagType.TELEKINESIS); - expect(feebas).toHaveBattlerTag(BattlerTagType.FLOATING); + expect(feebas).toHaveBattlerTag(BattlerTagType.TELEKINESIS); + expect(feebas).toHaveBattlerTag(BattlerTagType.FLOATING); - await game.toEndOfTurn(); + await game.toEndOfTurn(); - // Should have recieved tags successfully - expect(invalidMon.isOnField()).toBe(true); - expect(invalidMon).toHaveBattlerTag(BattlerTagType.TELEKINESIS); - expect(invalidMon).toHaveBattlerTag(BattlerTagType.FLOATING); - }); + // Should have received tags successfully + expect(invalidMon.isOnField()).toBe(true); + expect(invalidMon).toHaveBattlerTag(BattlerTagType.TELEKINESIS); + expect(invalidMon).toHaveBattlerTag(BattlerTagType.FLOATING); + }, + ); it("should still affect enemies transformed into invalid Pokemon", async () => { game.override.enemyAbility(AbilityId.IMPOSTER); @@ -151,7 +153,7 @@ describe("Move - Telekinesis", () => { game.override.starterForms({ [SpeciesId.GENGAR]: 1 }); await game.classicMode.startBattle([SpeciesId.GENGAR, SpeciesId.FEEBAS]); - const gengar = game.field.getPlayerPokemon(); + const [gengar, feebas] = game.scene.getPlayerParty(); game.move.use(MoveId.TELEPORT); game.doSelectPartyPokemon(1); @@ -169,13 +171,12 @@ describe("Move - Telekinesis", () => { await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MoveEndPhase"); - const feebas = game.field.getPlayerPokemon(); expect(feebas).toHaveBattlerTag(BattlerTagType.TELEKINESIS); expect(feebas).toHaveBattlerTag(BattlerTagType.FLOATING); await game.toEndOfTurn(); - // Should have recieved tags successfully + // Should have not received tags expect(gengar.isOnField()).toBe(true); expect(gengar).not.toHaveBattlerTag(BattlerTagType.TELEKINESIS); expect(gengar).not.toHaveBattlerTag(BattlerTagType.FLOATING);