Initial changes for Synchronize ability
This commit is contained in:
parent
f4c8f0080a
commit
7067187532
|
@ -1623,6 +1623,68 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for defining all {@linkcode Ability} Attributes after a status effect has been set.
|
||||
* @see {@linkcode applyPostSetStatus()}.
|
||||
*/
|
||||
export class PostSetStatusAbAttr extends AbAttr {
|
||||
/**
|
||||
* Does nothing after a status condition is set.
|
||||
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
||||
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon.
|
||||
* @param passive Whether this ability is a passive.
|
||||
* @param effect {@linkcode StatusEffect} that was set.
|
||||
* @param args Set of unique arguments needed by this attribute.
|
||||
* @returns true if application of the ability succeeds.
|
||||
*/
|
||||
applyPostSetStatus(
|
||||
pokemon: Pokemon,
|
||||
sourcePokemon: Pokemon = null,
|
||||
passive: boolean,
|
||||
effect: StatusEffect,
|
||||
args: any[]) : boolean | Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If another Pokemon burns, paralyzes, poisons, or badly poisons this Pokemon,
|
||||
* that Pokemon receives the same non-volatile status condition as part of this
|
||||
* ability attribute. For Synchronize ability.
|
||||
*/
|
||||
export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
|
||||
/**
|
||||
* If the StatusEffect that was set is Burn, Paralysis, Poison, or Toxic, and the status
|
||||
* was set by a source Pokemon, set the source Pokemon's status to the same StatusEffect.
|
||||
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
||||
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon.
|
||||
* @param passive Whether this ability is a passive.
|
||||
* @param effect {@linkcode StatusEffect} that was set.
|
||||
* @param args Set of unique arguments needed by this attribute.
|
||||
* @returns true if application of the ability succeeds.
|
||||
*/
|
||||
applyPostSetStatus(
|
||||
pokemon: Pokemon,
|
||||
sourcePokemon: Pokemon = null,
|
||||
passive: boolean,
|
||||
effect: StatusEffect,
|
||||
args: any[]): boolean {
|
||||
// Synchronizable statuses
|
||||
const syncStatuses = new Set<StatusEffect>([
|
||||
StatusEffect.BURN,
|
||||
StatusEffect.PARALYSIS,
|
||||
StatusEffect.POISON,
|
||||
StatusEffect.TOXIC
|
||||
]);
|
||||
|
||||
if (sourcePokemon && syncStatuses.has(effect)) {
|
||||
return sourcePokemon.trySetStatus(effect, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostVictoryAbAttr extends AbAttr {
|
||||
applyPostVictory(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
return false;
|
||||
|
@ -4012,6 +4074,11 @@ export function applyPostMoveUsedAbAttrs(attrType: Constructor<PostMoveUsedAbAtt
|
|||
return applyAbAttrsInternal<PostMoveUsedAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, args), args);
|
||||
}
|
||||
|
||||
export function applyPostSetStatusAbAttrs(attrType: Constructor<PostSetStatusAbAttr>,
|
||||
pokemon: Pokemon, effect: StatusEffect, sourcePokemon?: Pokemon, ...args: any[]): Promise<void> {
|
||||
return applyAbAttrsInternal<PostSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, args), args);
|
||||
}
|
||||
|
||||
export function applyBattleStatMultiplierAbAttrs(attrType: Constructor<BattleStatMultiplierAbAttr>,
|
||||
pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, ...args: any[]): Promise<void> {
|
||||
return applyAbAttrsInternal<BattleStatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, battleStat, statValue, args), args);
|
||||
|
@ -4239,7 +4306,8 @@ export function initAbilities() {
|
|||
.attr(EffectSporeAbAttr),
|
||||
new Ability(Abilities.SYNCHRONIZE, 3)
|
||||
.attr(SyncEncounterNatureAbAttr)
|
||||
.unimplemented(),
|
||||
.attr(SynchronizeStatusAbAttr)
|
||||
.partial(), // interaction with psycho shift needs work
|
||||
new Ability(Abilities.CLEAR_BODY, 3)
|
||||
.attr(ProtectStatAbAttr)
|
||||
.ignorable(),
|
||||
|
|
|
@ -23,7 +23,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HelpingHandTag
|
|||
import { WeatherType } from "../data/weather";
|
||||
import { TempBattleStat } from "../data/temp-battle-stat";
|
||||
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
|
||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AllyMoveCategoryPowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AddSecondStrikeAbAttr } from "../data/ability";
|
||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AllyMoveCategoryPowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AddSecondStrikeAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability";
|
||||
import PokemonData from "../system/pokemon-data";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Mode } from "../ui/ui";
|
||||
|
@ -2586,6 +2586,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
|
||||
if (effect !== StatusEffect.FAINT) {
|
||||
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
||||
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import * as overrides from "#app/overrides";
|
||||
import {
|
||||
MoveEffectPhase,
|
||||
TurnEndPhase,
|
||||
} from "#app/phases";
|
||||
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { StatusEffect } from "#app/data/status-effect.js";
|
||||
|
||||
describe("Abilities - Synchronize", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
|
||||
vi.spyOn(overrides, "STATUS_OVERRIDE", "get").mockReturnValue(StatusEffect.NONE);
|
||||
vi.spyOn(overrides, "OPP_STATUS_OVERRIDE", "get").mockReturnValue(StatusEffect.NONE);
|
||||
// Opponent mocks
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ALAKAZAM);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PSYCHIC, Moves.CALM_MIND, Moves.FOCUS_BLAST, Moves.RECOVER]);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SYNCHRONIZE);
|
||||
}, 20000);
|
||||
|
||||
it("does not trigger when no status is applied by opponent Pokemon", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.HEADBUTT;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ZIGZAGOON);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.PICKUP);
|
||||
|
||||
// Act
|
||||
await game.startBattle();
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status).toBe(undefined);
|
||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
|
||||
it("sets the status of the source pokemon to Paralysis when paralyzed by it", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.THUNDER_WAVE;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.TOGEKISS);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SERENE_GRACE);
|
||||
|
||||
// Act
|
||||
await game.startBattle();
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
|
||||
it("sets the status of the source pokemon to Burned when burn is applied by it", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.WILL_O_WISP;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.EEVEE);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.GUTS);
|
||||
|
||||
// Act
|
||||
await game.startBattle();
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.BURN);
|
||||
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.BURN);
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
|
||||
it("sets the status of the source pokemon to Poisoned when poison is applied by it", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.POISON_POWDER;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BRELOOM);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.TECHNICIAN);
|
||||
|
||||
// Act
|
||||
await game.startBattle();
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.POISON);
|
||||
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.POISON);
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
|
||||
it("sets the status of the source pokemon to Toxic when toxic is applied by it", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.TOXIC;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.QUAGSIRE);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DAMP);
|
||||
|
||||
// Act
|
||||
await game.startBattle();
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.TOXIC);
|
||||
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.TOXIC);
|
||||
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
|
||||
it("does not trigger when Pokemon is statused to non Burn, Paralysis, Poison, or Toxic", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.SPORE;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BRELOOM);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.POISON_HEAL);
|
||||
|
||||
// Act
|
||||
await game.startBattle();
|
||||
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status?.effect).toBe(undefined);
|
||||
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.SLEEP);
|
||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
|
||||
it("does not trigger when Pokemon is statused by Toxic Spikes", async () => {
|
||||
// Arrange
|
||||
const moveToUse = Moves.SPLASH;
|
||||
|
||||
// Starter mocks
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SYNCHRONIZE);
|
||||
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.BRELOOM);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TOXIC_SPIKES]);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.TECHNICIAN);
|
||||
|
||||
// Act
|
||||
// Turn 1 - Opponent uses spikes, trainer uses splash
|
||||
// Turn 2 - Opponent uses splash, trainer sends out Alakazam. Alakazam is toxic-ed but Synchronize should not proc
|
||||
await game.startBattle([Species.MAGIKARP, Species.ALAKAZAM]);
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); // use splash
|
||||
|
||||
await game.toNextTurn();
|
||||
game.doSwitchPokemon(1);
|
||||
game.doAttack(0);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
// Assert
|
||||
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.POISON);
|
||||
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(undefined);
|
||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||
}, 20000);
|
||||
});
|
Loading…
Reference in New Issue