[Move][Ability] Fully Implement Forest's Curse / Trick Or Treat / Mimicry (#4682)
* addedType variable * basic mimicry implementation * eslint * rage * quick change * made files * added mimicry activation message * test for moves done * hahahhaha * done? for now? * laklhaflhasd * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * time to start... ughhh * reflect type * Added new message * Update src/field/pokemon.ts Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * added overrides * some checks * removed comments * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: frutescens <info@laptop> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
This commit is contained in:
parent
38a6bf07e3
commit
fb2d3e45d6
|
@ -4703,6 +4703,84 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This applies a terrain-based type change to the Pokemon.
|
||||||
|
* Used by Mimicry.
|
||||||
|
*/
|
||||||
|
export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
|
||||||
|
constructor() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean {
|
||||||
|
if (pokemon.isTerastallized()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const currentTerrain = pokemon.scene.arena.getTerrainType();
|
||||||
|
const typeChange: Type[] = this.determineTypeChange(pokemon, currentTerrain);
|
||||||
|
if (typeChange.length !== 0) {
|
||||||
|
if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) {
|
||||||
|
pokemon.summonData.addedType = null;
|
||||||
|
}
|
||||||
|
pokemon.summonData.types = typeChange;
|
||||||
|
pokemon.updateInfo();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the type(s) the Pokemon should change to in response to a terrain
|
||||||
|
* @param pokemon
|
||||||
|
* @param currentTerrain {@linkcode TerrainType}
|
||||||
|
* @returns a list of type(s)
|
||||||
|
*/
|
||||||
|
private determineTypeChange(pokemon: Pokemon, currentTerrain: TerrainType): Type[] {
|
||||||
|
const typeChange: Type[] = [];
|
||||||
|
switch (currentTerrain) {
|
||||||
|
case TerrainType.ELECTRIC:
|
||||||
|
typeChange.push(Type.ELECTRIC);
|
||||||
|
break;
|
||||||
|
case TerrainType.MISTY:
|
||||||
|
typeChange.push(Type.FAIRY);
|
||||||
|
break;
|
||||||
|
case TerrainType.GRASSY:
|
||||||
|
typeChange.push(Type.GRASS);
|
||||||
|
break;
|
||||||
|
case TerrainType.PSYCHIC:
|
||||||
|
typeChange.push(Type.PSYCHIC);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pokemon.getTypes(false, false, true).forEach(t => {
|
||||||
|
typeChange.push(t);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return typeChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Pokemon should change types if summoned into an active terrain
|
||||||
|
* @returns `true` if there is an active terrain requiring a type change | `false` if not
|
||||||
|
*/
|
||||||
|
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
|
if (pokemon.scene.arena.getTerrainType() !== TerrainType.NONE) {
|
||||||
|
return this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) {
|
||||||
|
const currentTerrain = pokemon.scene.arena.getTerrainType();
|
||||||
|
const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon);
|
||||||
|
if (currentTerrain === TerrainType.NONE) {
|
||||||
|
return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix });
|
||||||
|
} else {
|
||||||
|
const moveType = i18next.t(`pokemonInfo:Type.${Type[this.determineTypeChange(pokemon, currentTerrain)[0]]}`);
|
||||||
|
return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
||||||
attrType: Constructor<TAttr>,
|
attrType: Constructor<TAttr>,
|
||||||
pokemon: Pokemon | null,
|
pokemon: Pokemon | null,
|
||||||
|
@ -5767,7 +5845,7 @@ export function initAbilities() {
|
||||||
new Ability(Abilities.POWER_SPOT, 8)
|
new Ability(Abilities.POWER_SPOT, 8)
|
||||||
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ], 1.3),
|
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ], 1.3),
|
||||||
new Ability(Abilities.MIMICRY, 8)
|
new Ability(Abilities.MIMICRY, 8)
|
||||||
.unimplemented(),
|
.attr(TerrainEventTypeChangeAbAttr),
|
||||||
new Ability(Abilities.SCREEN_CLEANER, 8)
|
new Ability(Abilities.SCREEN_CLEANER, 8)
|
||||||
.attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]),
|
.attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]),
|
||||||
new Ability(Abilities.STEELY_SPIRIT, 8)
|
new Ability(Abilities.STEELY_SPIRIT, 8)
|
||||||
|
|
|
@ -5858,6 +5858,9 @@ export class RemoveTypeAttr extends MoveEffectAttr {
|
||||||
|
|
||||||
const userTypes = user.getTypes(true);
|
const userTypes = user.getTypes(true);
|
||||||
const modifiedTypes = userTypes.filter(type => type !== this.removedType);
|
const modifiedTypes = userTypes.filter(type => type !== this.removedType);
|
||||||
|
if (modifiedTypes.length === 0) {
|
||||||
|
modifiedTypes.push(Type.UNKNOWN);
|
||||||
|
}
|
||||||
user.summonData.types = modifiedTypes;
|
user.summonData.types = modifiedTypes;
|
||||||
user.updateInfo();
|
user.updateInfo();
|
||||||
|
|
||||||
|
@ -5880,7 +5883,11 @@ export class CopyTypeAttr extends MoveEffectAttr {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.summonData.types = target.getTypes(true);
|
const targetTypes = target.getTypes(true);
|
||||||
|
if (targetTypes.includes(Type.UNKNOWN) && targetTypes.indexOf(Type.UNKNOWN) > -1) {
|
||||||
|
targetTypes[targetTypes.indexOf(Type.UNKNOWN)] = Type.NORMAL;
|
||||||
|
}
|
||||||
|
user.summonData.types = targetTypes;
|
||||||
user.updateInfo();
|
user.updateInfo();
|
||||||
|
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) }));
|
user.scene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) }));
|
||||||
|
@ -5889,7 +5896,7 @@ export class CopyTypeAttr extends MoveEffectAttr {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN;
|
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN || target.summonData.addedType !== null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5947,11 +5954,7 @@ export class AddTypeAttr extends MoveEffectAttr {
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied
|
target.summonData.addedType = this.type;
|
||||||
if (this.type !== Type.UNKNOWN) {
|
|
||||||
types.push(this.type);
|
|
||||||
}
|
|
||||||
target.summonData.types = types;
|
|
||||||
target.updateInfo();
|
target.updateInfo();
|
||||||
|
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) }));
|
user.scene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) }));
|
||||||
|
@ -8983,8 +8986,7 @@ export function initMoves() {
|
||||||
.ignoresProtect()
|
.ignoresProtect()
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
|
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
|
||||||
.attr(AddTypeAttr, Type.GHOST)
|
.attr(AddTypeAttr, Type.GHOST),
|
||||||
.edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up
|
|
||||||
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
|
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1)
|
||||||
.soundBased(),
|
.soundBased(),
|
||||||
|
@ -8996,8 +8998,7 @@ export function initMoves() {
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||||
.triageMove(),
|
.triageMove(),
|
||||||
new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6)
|
new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6)
|
||||||
.attr(AddTypeAttr, Type.GRASS)
|
.attr(AddTypeAttr, Type.GRASS),
|
||||||
.edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up
|
|
||||||
new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6)
|
new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6)
|
||||||
.windMove()
|
.windMove()
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
|
|
|
@ -10,7 +10,14 @@ import Move from "#app/data/move";
|
||||||
import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
|
import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { Terrain, TerrainType } from "#app/data/terrain";
|
import { Terrain, TerrainType } from "#app/data/terrain";
|
||||||
import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "#app/data/ability";
|
import {
|
||||||
|
applyAbAttrs,
|
||||||
|
applyPostTerrainChangeAbAttrs,
|
||||||
|
applyPostWeatherChangeAbAttrs,
|
||||||
|
PostTerrainChangeAbAttr,
|
||||||
|
PostWeatherChangeAbAttr,
|
||||||
|
TerrainEventTypeChangeAbAttr
|
||||||
|
} from "#app/data/ability";
|
||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
|
@ -387,6 +394,7 @@ export class Arena {
|
||||||
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
|
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
|
||||||
pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain));
|
pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain));
|
||||||
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain);
|
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain);
|
||||||
|
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1258,6 +1258,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the type added to Pokemon from moves like Forest's Curse or Trick Or Treat
|
||||||
|
if (!ignoreOverride && this.summonData && this.summonData.addedType && !types.includes(this.summonData.addedType)) {
|
||||||
|
types.push(this.summonData.addedType);
|
||||||
|
}
|
||||||
|
|
||||||
// If both types are the same (can happen in weird custom typing scenarios), reduce to single type
|
// If both types are the same (can happen in weird custom typing scenarios), reduce to single type
|
||||||
if (types.length > 1 && types[0] === types[1]) {
|
if (types.length > 1 && types[0] === types[1]) {
|
||||||
types.splice(0, 1);
|
types.splice(0, 1);
|
||||||
|
@ -5100,6 +5105,7 @@ export class PokemonSummonData {
|
||||||
public moveset: (PokemonMove | null)[];
|
public moveset: (PokemonMove | null)[];
|
||||||
// If not initialized this value will not be populated from save data.
|
// If not initialized this value will not be populated from save data.
|
||||||
public types: Type[] = [];
|
public types: Type[] = [];
|
||||||
|
public addedType: Type | null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonBattleData {
|
export class PokemonBattleData {
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Mimicry", () => {
|
||||||
|
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 ])
|
||||||
|
.ability(Abilities.MIMICRY)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Mimicry activates after the Pokémon with Mimicry is switched in while terrain is present, or whenever there is a change in terrain", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.MISTY_SURGE);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS, Species.ABRA ]);
|
||||||
|
|
||||||
|
const [ playerPokemon1, playerPokemon2 ] = game.scene.getParty();
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon1.getTypes().includes(Type.FAIRY)).toBe(true);
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(playerPokemon2.getTypes().includes(Type.FAIRY)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Pokemon should revert back to its original, root type once terrain ends", async () => {
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.SPLASH, Moves.TRANSFORM ])
|
||||||
|
.enemyAbility(Abilities.MIMICRY)
|
||||||
|
.enemyMoveset([ Moves.SPLASH, Moves.PSYCHIC_TERRAIN ]);
|
||||||
|
await game.classicMode.startBattle([ Species.REGIELEKI ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon();
|
||||||
|
game.move.select(Moves.TRANSFORM);
|
||||||
|
await game.forceEnemyMove(Moves.PSYCHIC_TERRAIN);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon?.getTypes().includes(Type.PSYCHIC)).toBe(true);
|
||||||
|
|
||||||
|
if (game.scene.arena.terrain) {
|
||||||
|
game.scene.arena.terrain.turnsLeft = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon?.getTypes().includes(Type.ELECTRIC)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("If the Pokemon is under the effect of a type-adding move and an equivalent terrain activates, the move's effect disappears", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyMoveset([ Moves.FORESTS_CURSE, Moves.GRASSY_TERRAIN ]);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon();
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.FORESTS_CURSE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(playerPokemon?.summonData.addedType).toBe(Type.GRASS);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.GRASSY_TERRAIN);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(playerPokemon?.summonData.addedType).toBeNull();
|
||||||
|
expect(playerPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Forest's Curse", () => {
|
||||||
|
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.FORESTS_CURSE, Moves.TRICK_OR_TREAT ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will replace the added type from Trick Or Treat", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
game.move.select(Moves.TRICK_OR_TREAT);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(enemyPokemon!.summonData.addedType).toBe(Type.GHOST);
|
||||||
|
|
||||||
|
game.move.select(Moves.FORESTS_CURSE);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(enemyPokemon?.summonData.addedType).toBe(Type.GRASS);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Reflect Type", () => {
|
||||||
|
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
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => {
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.FORESTS_CURSE, Moves.REFLECT_TYPE ])
|
||||||
|
.startingLevel(60)
|
||||||
|
.enemySpecies(Species.CHARMANDER)
|
||||||
|
.enemyMoveset([ Moves.BURN_UP, Moves.SPLASH ]);
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon();
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.forceEnemyMove(Moves.BURN_UP);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.FORESTS_CURSE);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(enemyPokemon?.getTypes().includes(Type.UNKNOWN)).toBe(true);
|
||||||
|
expect(enemyPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
|
||||||
|
|
||||||
|
game.move.select(Moves.REFLECT_TYPE);
|
||||||
|
await game.forceEnemyMove(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(playerPokemon?.getTypes()[0]).toBe(Type.NORMAL);
|
||||||
|
expect(playerPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Trick Or Treat", () => {
|
||||||
|
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.FORESTS_CURSE, Moves.TRICK_OR_TREAT ])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will replace added type from Forest's Curse", async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.FEEBAS ]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||||
|
game.move.select(Moves.FORESTS_CURSE);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(enemyPokemon!.summonData.addedType).toBe(Type.GRASS);
|
||||||
|
|
||||||
|
game.move.select(Moves.TRICK_OR_TREAT);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
expect(enemyPokemon?.summonData.addedType).toBe(Type.GHOST);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue