gdddddddd

This commit is contained in:
Bertie690 2025-09-04 22:28:00 -04:00
parent 375736bfec
commit 8c301c184a
6 changed files with 60 additions and 58 deletions

View File

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

View File

@ -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",

View File

@ -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`

View File

@ -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 },

View File

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

View File

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