mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-25 08:16:04 +00:00
[Ability] Implement Protean and Libero abilities (#1309)
* Add generic to util holders to reduce manual type casting
* implement protean and libero abilities
* remove use only once per turn trigger
* Revert Attack Attribute Conditions back to requiring unused vars
* Remove conditional before invoking type change ability
* update protean to properly trigger and skip certain moves
* remove some dangerous typecasts
* revert autoformatting changes
* not all autoformatting changes were reverted
* Revert "Add generic to util holders to reduce manual type casting"
This reverts commit 3ee7f1d5ff
.
* change some variable names
* remove incorrect comment
* update abilities so they use gen 9 logic
* fix typescript error from missing Terrain type
* update gameManager switchPokemon to match other menu utilities
* add test cases for protean and libero
This commit is contained in:
parent
048993b2c2
commit
819fe9b4a1
@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags";
|
||||
import { BattlerTagType } from "./enums/battler-tag-type";
|
||||
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
||||
import { Gender } from "./gender";
|
||||
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move";
|
||||
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr } from "./move";
|
||||
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
|
||||
import { ArenaTagType } from "./enums/arena-tag-type";
|
||||
import { Stat, getStatName } from "./pokemon-stat";
|
||||
@ -1085,21 +1085,20 @@ export class FieldMultiplyBattleStatAbAttr extends AbAttr {
|
||||
}
|
||||
|
||||
export class MoveTypeChangeAttr extends PreAttackAbAttr {
|
||||
private newType: Type;
|
||||
private powerMultiplier: number;
|
||||
private condition: PokemonAttackCondition;
|
||||
|
||||
constructor(newType: Type, powerMultiplier: number, condition: PokemonAttackCondition) {
|
||||
constructor(
|
||||
private newType: Type,
|
||||
private powerMultiplier: number,
|
||||
private condition?: PokemonAttackCondition
|
||||
) {
|
||||
super(true);
|
||||
this.newType = newType;
|
||||
this.powerMultiplier = powerMultiplier;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (this.condition(pokemon, defender, move)) {
|
||||
if (this.condition && this.condition(pokemon, defender, move)) {
|
||||
move.type = this.newType;
|
||||
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
|
||||
if (args[0] && args[0] instanceof Utils.NumberHolder) {
|
||||
args[0].value *= this.powerMultiplier;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1107,6 +1106,58 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/** Ability attribute for changing a pokemon's type before using a move */
|
||||
export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
private moveType: Type;
|
||||
|
||||
constructor() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (
|
||||
!pokemon.isTerastallized() &&
|
||||
move.id !== Moves.STRUGGLE &&
|
||||
/**
|
||||
* Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute
|
||||
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves}
|
||||
*/
|
||||
!move.findAttr((attr) =>
|
||||
attr instanceof RandomMovesetMoveAttr ||
|
||||
attr instanceof RandomMoveAttr ||
|
||||
attr instanceof NaturePowerAttr ||
|
||||
attr instanceof CopyMoveAttr
|
||||
)
|
||||
) {
|
||||
// TODO remove this copy when phase order is changed so that damage, type, category, etc.
|
||||
// TODO are all calculated prior to playing the move animation.
|
||||
const moveCopy = new Move(move.id, move.type, move.category, move.moveTarget, move.power, move.accuracy, move.pp, move.chance, move.priority, move.generation);
|
||||
moveCopy.attrs = move.attrs;
|
||||
|
||||
// Moves like Weather Ball ignore effects of abilities like Normalize and Refrigerate
|
||||
if (move.findAttr(attr => attr instanceof VariableMoveTypeAttr)) {
|
||||
applyMoveAttrs(VariableMoveTypeAttr, pokemon, null, moveCopy);
|
||||
} else {
|
||||
applyPreAttackAbAttrs(MoveTypeChangeAttr, pokemon, null, moveCopy);
|
||||
}
|
||||
|
||||
if (pokemon.getTypes().some((t) => t !== moveCopy.type)) {
|
||||
this.moveType = moveCopy.type;
|
||||
pokemon.summonData.types = [moveCopy.type];
|
||||
pokemon.updateInfo();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||
return getPokemonMessage(pokemon, ` transformed into the ${Type[this.moveType]} type!`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for abilities that boost the damage of moves
|
||||
* For abilities that boost the base power of moves, see VariableMovePowerAbAttr
|
||||
@ -3557,6 +3608,9 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
|
||||
}
|
||||
pokemon.scene.setPhaseQueueSplice();
|
||||
const onApplySuccess = () => {
|
||||
if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) {
|
||||
pokemon.summonData.abilitiesApplied.push(ability.id);
|
||||
}
|
||||
if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) {
|
||||
pokemon.battleData.abilitiesApplied.push(ability.id);
|
||||
}
|
||||
@ -4039,8 +4093,9 @@ export function initAbilities() {
|
||||
.conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
|
||||
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5),
|
||||
new Ability(Abilities.NORMALIZE, 4)
|
||||
.attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => move.id !== Moves.HIDDEN_POWER && move.id !== Moves.WEATHER_BALL &&
|
||||
move.id !== Moves.NATURAL_GIFT && move.id !== Moves.JUDGMENT && move.id !== Moves.TECHNO_BLAST),
|
||||
.attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => {
|
||||
return ![Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST].includes(move.id);
|
||||
}),
|
||||
new Ability(Abilities.SNIPER, 4)
|
||||
.attr(MultCritAbAttr, 1.5),
|
||||
new Ability(Abilities.MAGIC_GUARD, 4)
|
||||
@ -4259,7 +4314,8 @@ export function initAbilities() {
|
||||
.attr(HealFromBerryUseAbAttr, 1/3)
|
||||
.partial(), // Healing not blocked by Heal Block
|
||||
new Ability(Abilities.PROTEAN, 6)
|
||||
.unimplemented(),
|
||||
.attr(PokemonTypeChangeAbAttr)
|
||||
.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)),
|
||||
new Ability(Abilities.FUR_COAT, 6)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, 0.5)
|
||||
.ignorable(),
|
||||
@ -4498,7 +4554,8 @@ export function initAbilities() {
|
||||
.attr(PostSummonStatChangeAbAttr, BattleStat.DEF, 1, true)
|
||||
.condition(getOncePerBattleCondition(Abilities.DAUNTLESS_SHIELD)),
|
||||
new Ability(Abilities.LIBERO, 8)
|
||||
.unimplemented(),
|
||||
.attr(PokemonTypeChangeAbAttr)
|
||||
.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.LIBERO)),
|
||||
new Ability(Abilities.BALL_FETCH, 8)
|
||||
.attr(FetchBallAbAttr)
|
||||
.condition(getOncePerBattleCondition(Abilities.BALL_FETCH)),
|
||||
|
@ -3811,6 +3811,7 @@ export class PokemonSummonData {
|
||||
public disabledTurns: integer = 0;
|
||||
public tags: BattlerTag[] = [];
|
||||
public abilitySuppressed: boolean = false;
|
||||
public abilitiesApplied: Abilities[] = [];
|
||||
|
||||
public speciesForm: PokemonSpeciesForm;
|
||||
public fusionSpeciesForm: PokemonSpeciesForm;
|
||||
@ -3819,7 +3820,8 @@ export class PokemonSummonData {
|
||||
public fusionGender: Gender;
|
||||
public stats: integer[];
|
||||
public moveset: PokemonMove[];
|
||||
public types: Type[];
|
||||
// If not initialized this value will not be populated from save data.
|
||||
public types: Type[] = null;
|
||||
}
|
||||
|
||||
export class PokemonBattleData {
|
||||
@ -3831,7 +3833,9 @@ export class PokemonBattleData {
|
||||
}
|
||||
|
||||
export class PokemonBattleSummonData {
|
||||
/** The number of turns the pokemon has passed since entering the battle */
|
||||
public turnCount: integer = 1;
|
||||
/** The list of moves the pokemon has used since entering the battle */
|
||||
public moveHistory: TurnMove[] = [];
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get
|
||||
import { TempBattleStat } from "./data/temp-battle-stat";
|
||||
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
|
||||
import { ArenaTagType } from "./data/enums/arena-tag-type";
|
||||
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr } from "./data/ability";
|
||||
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr } from "./data/ability";
|
||||
import { Unlockables, getUnlockableName } from "./system/unlockables";
|
||||
import { getBiomeKey } from "./field/arena";
|
||||
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
|
||||
@ -2692,6 +2692,16 @@ export class MovePhase extends BattlePhase {
|
||||
failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain.terrainType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger pokemon type change before playing the move animation
|
||||
* Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse,
|
||||
* regardless of whether the move successfully executes or not.
|
||||
*/
|
||||
if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) {
|
||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
||||
}
|
||||
|
||||
if (success) {
|
||||
this.scene.unshiftPhase(this.getEffectPhase());
|
||||
} else {
|
||||
|
@ -2,16 +2,19 @@ import { Arena } from "../field/arena";
|
||||
import { ArenaTag } from "../data/arena-tag";
|
||||
import { Biome } from "../data/enums/biome";
|
||||
import { Weather } from "../data/weather";
|
||||
import { Terrain } from "#app/data/terrain.js";
|
||||
|
||||
export default class ArenaData {
|
||||
public biome: Biome;
|
||||
public weather: Weather;
|
||||
public terrain: Terrain;
|
||||
public tags: ArenaTag[];
|
||||
|
||||
constructor(source: Arena | any) {
|
||||
const sourceArena = source instanceof Arena ? source as Arena : null;
|
||||
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
||||
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : undefined;
|
||||
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : undefined;
|
||||
this.tags = sourceArena ? sourceArena.tags : [];
|
||||
}
|
||||
}
|
||||
|
@ -829,7 +829,7 @@ export class GameData {
|
||||
loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData): Promise<boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const initSessionFromData = async sessionData => {
|
||||
const initSessionFromData = async (sessionData: SessionSaveData) => {
|
||||
console.debug(sessionData);
|
||||
|
||||
scene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC);
|
||||
|
@ -54,7 +54,7 @@ export default class PokemonData {
|
||||
public summonData: PokemonSummonData;
|
||||
|
||||
constructor(source: Pokemon | any, forHistory: boolean = false) {
|
||||
const sourcePokemon = source instanceof Pokemon ? source as Pokemon : null;
|
||||
const sourcePokemon = source instanceof Pokemon ? source : null;
|
||||
this.id = source.id;
|
||||
this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player;
|
||||
this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species;
|
||||
@ -121,6 +121,7 @@ export default class PokemonData {
|
||||
this.summonData.disabledMove = source.summonData.disabledMove;
|
||||
this.summonData.disabledTurns = source.summonData.disabledTurns;
|
||||
this.summonData.abilitySuppressed = source.summonData.abilitySuppressed;
|
||||
this.summonData.abilitiesApplied = source.summonData.abilitiesApplied;
|
||||
|
||||
this.summonData.ability = source.summonData.ability;
|
||||
this.summonData.moveset = source.summonData.moveset?.map(m => PokemonMove.loadMove(m));
|
||||
|
364
src/test/abilities/libero.test.ts
Normal file
364
src/test/abilities/libero.test.ts
Normal file
@ -0,0 +1,364 @@
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import GameManager from "../utils/gameManager";
|
||||
import * as Overrides from "#app/overrides";
|
||||
import { Species } from "#app/data/enums/species.js";
|
||||
import { Abilities } from "#app/data/enums/abilities.js";
|
||||
import { Moves } from "#app/data/enums/moves.js";
|
||||
import { getMovePosition } from "../utils/gameManagerUtils";
|
||||
import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js";
|
||||
import { allMoves } from "#app/data/move.js";
|
||||
import { BattlerTagType } from "#app/data/enums/battler-tag-type.js";
|
||||
import { Weather, WeatherType } from "#app/data/weather.js";
|
||||
import { Type } from "#app/data/type.js";
|
||||
import { Biome } from "#app/data/enums/biome.js";
|
||||
import { PlayerPokemon } from "#app/field/pokemon.js";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Abilities - Protean", () => {
|
||||
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.LIBERO);
|
||||
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.ENDURE, Moves.ENDURE, Moves.ENDURE, Moves.ENDURE]);
|
||||
});
|
||||
|
||||
test(
|
||||
"ability applies and changes a pokemon's type",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies only once per switch in",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.AGILITY]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]);
|
||||
|
||||
let leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.AGILITY));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied.filter((a) => a === Abilities.LIBERO)).toHaveLength(1);
|
||||
const leadPokemonType = Type[leadPokemon.getTypes()[0]];
|
||||
const moveType = Type[allMoves[Moves.AGILITY].defaultType];
|
||||
expect(leadPokemonType).not.toBe(moveType);
|
||||
|
||||
await game.toNextTurn();
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
|
||||
leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move has a variable type",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WEATHER_BALL]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.scene.arena.weather = new Weather(WeatherType.SUNNY);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.WEATHER_BALL));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.LIBERO);
|
||||
expect(leadPokemon.getTypes()).toHaveLength(1);
|
||||
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
|
||||
moveType = Type[Type.FIRE];
|
||||
expect(leadPokemonType).toBe(moveType);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the type has changed by another ability",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.REFRIGERATE);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.LIBERO);
|
||||
expect(leadPokemon.getTypes()).toHaveLength(1);
|
||||
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
|
||||
moveType = Type[Type.ICE];
|
||||
expect(leadPokemonType).toBe(moveType);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move calls another move",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.NATURE_POWER]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.scene.arena.biomeType = Biome.MOUNTAIN;
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.NATURE_POWER));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.AIR_SLASH);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move is delayed / charging",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DIG]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.DIG));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.DIG);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move misses",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move is protected against",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move fails because of type immunity",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if pokemon's type is the same as the move's type",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType];
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if pokemon is terastallized",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if pokemon uses struggle",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.STRUGGLE]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if the pokemon's move fails",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.BURN_UP]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's Trick-or-Treat fails",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TRICK_OR_TREAT]);
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TRICK_OR_TREAT);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly and the pokemon curses itself",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.CURSE]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.CURSE);
|
||||
expect(leadPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
});
|
||||
|
||||
function testPokemonTypeMatchesDefaultMoveType(pokemon: PlayerPokemon, move: Moves) {
|
||||
expect(pokemon.summonData.abilitiesApplied).toContain(Abilities.LIBERO);
|
||||
expect(pokemon.getTypes()).toHaveLength(1);
|
||||
const pokemonType = Type[pokemon.getTypes()[0]],
|
||||
moveType = Type[allMoves[move].defaultType];
|
||||
expect(pokemonType).toBe(moveType);
|
||||
}
|
364
src/test/abilities/protean.test.ts
Normal file
364
src/test/abilities/protean.test.ts
Normal file
@ -0,0 +1,364 @@
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import GameManager from "../utils/gameManager";
|
||||
import * as Overrides from "#app/overrides";
|
||||
import { Species } from "#app/data/enums/species.js";
|
||||
import { Abilities } from "#app/data/enums/abilities.js";
|
||||
import { Moves } from "#app/data/enums/moves.js";
|
||||
import { getMovePosition } from "../utils/gameManagerUtils";
|
||||
import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js";
|
||||
import { allMoves } from "#app/data/move.js";
|
||||
import { BattlerTagType } from "#app/data/enums/battler-tag-type.js";
|
||||
import { Weather, WeatherType } from "#app/data/weather.js";
|
||||
import { Type } from "#app/data/type.js";
|
||||
import { Biome } from "#app/data/enums/biome.js";
|
||||
import { PlayerPokemon } from "#app/field/pokemon.js";
|
||||
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
describe("Abilities - Protean", () => {
|
||||
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, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.PROTEAN);
|
||||
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.ENDURE, Moves.ENDURE, Moves.ENDURE, Moves.ENDURE]);
|
||||
});
|
||||
|
||||
test(
|
||||
"ability applies and changes a pokemon's type",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies only once per switch in",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.AGILITY]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]);
|
||||
|
||||
let leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.AGILITY));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied.filter((a) => a === Abilities.PROTEAN)).toHaveLength(1);
|
||||
const leadPokemonType = Type[leadPokemon.getTypes()[0]];
|
||||
const moveType = Type[allMoves[Moves.AGILITY].defaultType];
|
||||
expect(leadPokemonType).not.toBe(moveType);
|
||||
|
||||
await game.toNextTurn();
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
game.doSwitchPokemon(1);
|
||||
await game.toNextTurn();
|
||||
|
||||
leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move has a variable type",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WEATHER_BALL]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.scene.arena.weather = new Weather(WeatherType.SUNNY);
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.WEATHER_BALL));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.PROTEAN);
|
||||
expect(leadPokemon.getTypes()).toHaveLength(1);
|
||||
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
|
||||
moveType = Type[Type.FIRE];
|
||||
expect(leadPokemonType).toBe(moveType);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the type has changed by another ability",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.REFRIGERATE);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.PROTEAN);
|
||||
expect(leadPokemon.getTypes()).toHaveLength(1);
|
||||
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
|
||||
moveType = Type[Type.ICE];
|
||||
expect(leadPokemonType).toBe(moveType);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move calls another move",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.NATURE_POWER]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.scene.arena.biomeType = Biome.MOUNTAIN;
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.NATURE_POWER));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.AIR_SLASH);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move is delayed / charging",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DIG]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.DIG));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.DIG);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move misses",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move is protected against",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's move fails because of type immunity",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if pokemon's type is the same as the move's type",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType];
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if pokemon is terastallized",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if pokemon uses struggle",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.STRUGGLE]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability is not applied if the pokemon's move fails",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.BURN_UP]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly even if the pokemon's Trick-or-Treat fails",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TRICK_OR_TREAT]);
|
||||
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TRICK_OR_TREAT);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
test(
|
||||
"ability applies correctly and the pokemon curses itself",
|
||||
async () => {
|
||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.CURSE]);
|
||||
|
||||
await game.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const leadPokemon = game.scene.getPlayerPokemon();
|
||||
expect(leadPokemon).not.toBe(undefined);
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.CURSE);
|
||||
expect(leadPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined);
|
||||
},
|
||||
TIMEOUT,
|
||||
);
|
||||
});
|
||||
|
||||
function testPokemonTypeMatchesDefaultMoveType(pokemon: PlayerPokemon, move: Moves) {
|
||||
expect(pokemon.summonData.abilitiesApplied).toContain(Abilities.PROTEAN);
|
||||
expect(pokemon.getTypes()).toHaveLength(1);
|
||||
const pokemonType = Type[pokemon.getTypes()[0]],
|
||||
moveType = Type[allMoves[move].defaultType];
|
||||
expect(pokemonType).toBe(moveType);
|
||||
}
|
Loading…
Reference in New Issue
Block a user