[Ability] Fully implement Synchronize (#4785)
Co-authored-by: frutescens <info@laptop> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
4f733796c5
commit
6b7efb444b
|
@ -5433,8 +5433,7 @@ export function initAbilities() {
|
||||||
.attr(EffectSporeAbAttr),
|
.attr(EffectSporeAbAttr),
|
||||||
new Ability(Abilities.SYNCHRONIZE, 3)
|
new Ability(Abilities.SYNCHRONIZE, 3)
|
||||||
.attr(SyncEncounterNatureAbAttr)
|
.attr(SyncEncounterNatureAbAttr)
|
||||||
.attr(SynchronizeStatusAbAttr)
|
.attr(SynchronizeStatusAbAttr),
|
||||||
.partial(), // interaction with psycho shift needs work, keeping to old Gen interaction for now
|
|
||||||
new Ability(Abilities.CLEAR_BODY, 3)
|
new Ability(Abilities.CLEAR_BODY, 3)
|
||||||
.attr(ProtectStatAbAttr)
|
.attr(ProtectStatAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
|
@ -6036,7 +6035,7 @@ export function initAbilities() {
|
||||||
.bypassFaint(),
|
.bypassFaint(),
|
||||||
new Ability(Abilities.CORROSION, 7)
|
new Ability(Abilities.CORROSION, 7)
|
||||||
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ])
|
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ])
|
||||||
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet)
|
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented) + fling with toxic orb (not implemented yet)
|
||||||
new Ability(Abilities.COMATOSE, 7)
|
new Ability(Abilities.COMATOSE, 7)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(UnswappableAbilityAbAttr)
|
.attr(UnswappableAbilityAbAttr)
|
||||||
|
|
|
@ -18,7 +18,7 @@ import Move, {
|
||||||
StatusCategoryOnAllyAttr
|
StatusCategoryOnAllyAttr
|
||||||
} from "#app/data/move";
|
} from "#app/data/move";
|
||||||
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { getStatusEffectHealText, StatusEffect } from "#app/data/status-effect";
|
||||||
import { TerrainType } from "#app/data/terrain";
|
import { TerrainType } from "#app/data/terrain";
|
||||||
import { Type } from "#app/data/type";
|
import { Type } from "#app/data/type";
|
||||||
import { WeatherType } from "#app/data/weather";
|
import { WeatherType } from "#app/data/weather";
|
||||||
|
@ -2866,6 +2866,28 @@ export class GrudgeTag extends BattlerTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon
|
||||||
|
*/
|
||||||
|
export class PsychoShiftTag extends BattlerTag {
|
||||||
|
constructor() {
|
||||||
|
super(BattlerTagType.PSYCHO_SHIFT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.PSYCHO_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heals Psycho Shift's user of its status effect after it uses a move
|
||||||
|
* @returns `false` to expire the tag immediately
|
||||||
|
*/
|
||||||
|
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||||
|
if (pokemon.status && pokemon.isActive(true)) {
|
||||||
|
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
|
||||||
|
pokemon.resetStatus();
|
||||||
|
pokemon.updateInfo();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
|
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
|
||||||
* @param sourceId - The ID of the pokemon adding the tag
|
* @param sourceId - The ID of the pokemon adding the tag
|
||||||
|
@ -3049,6 +3071,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||||
return new PowerTrickTag(sourceMove, sourceId);
|
return new PowerTrickTag(sourceMove, sourceId);
|
||||||
case BattlerTagType.GRUDGE:
|
case BattlerTagType.GRUDGE:
|
||||||
return new GrudgeTag();
|
return new GrudgeTag();
|
||||||
|
case BattlerTagType.PSYCHO_SHIFT:
|
||||||
|
return new PsychoShiftTag();
|
||||||
case BattlerTagType.NONE:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
|
|
@ -2270,24 +2270,26 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
/**
|
||||||
|
* Applies the effect of Psycho Shift to its target
|
||||||
|
* Psycho Shift takes the user's status effect and passes it onto the target. The user is then healed after the move has been successfully executed.
|
||||||
|
* @returns `true` if Psycho Shift's effect is able to be applied to the target
|
||||||
|
*/
|
||||||
|
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||||
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||||
|
|
||||||
if (target.status) {
|
if (target.status) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||||
|
const trySetStatus = canSetStatus ? target.trySetStatus(statusToApply, true, user) : false;
|
||||||
|
|
||||||
if (canSetStatus) {
|
if (trySetStatus && user.status) {
|
||||||
if (user.status) {
|
// PsychoShiftTag is added to the user if move succeeds so that the user is healed of its status effect after its move
|
||||||
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
user.addTag(BattlerTagType.PSYCHO_SHIFT);
|
||||||
}
|
|
||||||
user.resetStatus();
|
|
||||||
user.updateInfo();
|
|
||||||
target.trySetStatus(statusToApply, true, user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return canSetStatus;
|
return trySetStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,5 +90,6 @@ export enum BattlerTagType {
|
||||||
ELECTRIFIED = "ELECTRIFIED",
|
ELECTRIFIED = "ELECTRIFIED",
|
||||||
TELEKINESIS = "TELEKINESIS",
|
TELEKINESIS = "TELEKINESIS",
|
||||||
COMMANDED = "COMMANDED",
|
COMMANDED = "COMMANDED",
|
||||||
GRUDGE = "GRUDGE"
|
GRUDGE = "GRUDGE",
|
||||||
|
PSYCHO_SHIFT = "PSYCHO_SHIFT",
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Corrosion", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.SPLASH ])
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.GRIMER)
|
||||||
|
.enemyAbility(Abilities.CORROSION)
|
||||||
|
.enemyMoveset(Moves.TOXIC);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("If a Poison- or Steel-type Pokémon with this Ability poisons a target with Synchronize, Synchronize does not gain the ability to poison Poison- or Steel-type Pokémon.", async () => {
|
||||||
|
game.override.ability(Abilities.SYNCHRONIZE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon();
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
expect(playerPokemon!.status).toBeUndefined();
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
expect(playerPokemon!.status).toBeDefined();
|
||||||
|
expect(enemyPokemon!.status).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
|
@ -94,16 +94,4 @@ describe("Abilities - Synchronize", () => {
|
||||||
expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS);
|
expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should activate with Psycho Shift after the move clears the status", async () => {
|
|
||||||
game.override.statusEffect(StatusEffect.PARALYSIS);
|
|
||||||
await game.classicMode.startBattle();
|
|
||||||
|
|
||||||
game.move.select(Moves.PSYCHO_SHIFT);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
|
||||||
|
|
||||||
expect(game.scene.getPlayerPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS); // keeping old gen < V impl for now since it's buggy otherwise
|
|
||||||
expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS);
|
|
||||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { StatusEffect } from "#app/enums/status-effect";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Psycho Shift", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.PSYCHO_SHIFT ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.statusEffect(StatusEffect.POISON)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyLevel(20)
|
||||||
|
.enemyAbility(Abilities.SYNCHRONIZE)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("If Psycho Shift is used on a Pokémon with Synchronize, the user of Psycho Shift will already be afflicted with a status condition when Synchronize activates", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon();
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
expect(enemyPokemon?.status).toBeUndefined();
|
||||||
|
|
||||||
|
game.move.select(Moves.PSYCHO_SHIFT);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(playerPokemon?.status).toBeNull();
|
||||||
|
expect(enemyPokemon?.status).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue