From 4251d5c919d7436bd8ce441fd229e4f2f4160f8d Mon Sep 17 00:00:00 2001 From: PrabbyDD Date: Tue, 22 Oct 2024 14:53:11 -0700 Subject: [PATCH] fulling implementing forests curse and trick or treat with edge cases --- src/data/move.ts | 29 +++++++--- src/test/moves/forests_curse.test.ts | 87 ++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 src/test/moves/forests_curse.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 309a2d3a7eb..ac9bf67a439 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -5873,16 +5873,29 @@ export class AddTypeAttr extends MoveEffectAttr { constructor(type: Type) { super(false, MoveEffectTrigger.HIT); - this.type = type; } 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 - if (this.type !== Type.UNKNOWN) { - types.push(this.type); + let currentTypes = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN) as Type[]; + const baseTypes = target.getTypes(false, false, true); + const forestsCurseApplied: boolean = currentTypes.includes(Type.GRASS) && !baseTypes.includes(Type.GRASS); + const trickOrTreatApplied: boolean = currentTypes.includes(Type.GHOST) && !baseTypes.includes(Type.GHOST); + + if (move.id === Moves.TRICK_OR_TREAT && forestsCurseApplied) { + // Has extra grass type, remove that and append ghost type + currentTypes = currentTypes.filter(type => type !== Type.GRASS); + currentTypes.push(this.type); + } else if (move.id === Moves.FORESTS_CURSE && trickOrTreatApplied) { + // Has extra ghost type, remove that and append grass type + currentTypes = currentTypes.filter(type => type !== Type.GHOST); + currentTypes.push(this.type); + } else if (this.type !== Type.UNKNOWN) { + currentTypes = currentTypes; + currentTypes.push(this.type); } - target.summonData.types = types; + + target.summonData.types = currentTypes; target.updateInfo(); user.scene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) })); @@ -8877,8 +8890,7 @@ export function initMoves() { .ignoresProtect() .ignoresVirtual(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) - .attr(AddTypeAttr, Type.GHOST) - .edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up + .attr(AddTypeAttr, Type.GHOST), new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1) .soundBased(), @@ -8890,8 +8902,7 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_OTHERS) .triageMove(), new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6) - .attr(AddTypeAttr, Type.GRASS) - .edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up + .attr(AddTypeAttr, Type.GRASS), new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6) .windMove() .makesContact(false) diff --git a/src/test/moves/forests_curse.test.ts b/src/test/moves/forests_curse.test.ts new file mode 100644 index 00000000000..53f8552642a --- /dev/null +++ b/src/test/moves/forests_curse.test.ts @@ -0,0 +1,87 @@ +import { BattlerIndex } from "#app/battle"; +import { Type } from "#app/data/type"; +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, test } 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.battleType("single"); + game.override.enemySpecies(Species.RELICANTH); + game.override.startingLevel(5); + game.override.enemyLevel(100); + game.override.enemyMoveset(Moves.FORESTS_CURSE); + game.override.moveset([ Moves.SPLASH, Moves.BURN_UP, Moves.DOUBLE_SHOCK ]); + }); + test( + "a mono type afflicted with forest's curse should be its type + grass (2 types)", + async () => { + await game.classicMode.startBattle([ Species.RATTATA ]); + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(TurnEndPhase); + + // Should be normal/grass + const playerPokemonTypes = playerPokemon.getTypes(); + expect(playerPokemonTypes.filter(type => type === Type.NORMAL)).toHaveLength(1); + expect(playerPokemonTypes.filter(type => type === Type.GRASS)).toHaveLength(1); + expect(playerPokemonTypes.length === 2).toBeTruthy(); + } + ); + + test( + "a dual type afflicted with forest's curse should be its dual type + grass (3 types)", + async () => { + await game.classicMode.startBattle([ Species.MOLTRES ]); + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(TurnEndPhase); + + // Should be flying/fire/grass + const playerPokemonTypes = playerPokemon.getTypes(); + expect(playerPokemonTypes.filter(type => type === Type.FLYING)).toHaveLength(1); + expect(playerPokemonTypes.filter(type => type === Type.FIRE)).toHaveLength(1); + expect(playerPokemonTypes.filter(type => type === Type.GRASS)).toHaveLength(1); + expect(playerPokemonTypes.length === 3).toBeTruthy(); + + } + ); + + test( + "A fire/flying type that uses burn up, then has forest's curse applied should be flying/grass", + async () => { + await game.classicMode.startBattle([ Species.MOLTRES ]); + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.BURN_UP); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(TurnEndPhase); + + // Should be flying/grass + const playerPokemonTypes = playerPokemon.getTypes(); + expect(playerPokemonTypes.filter(type => type === Type.FLYING)).toHaveLength(1); + expect(playerPokemonTypes.filter(type => type === Type.FIRE)).toHaveLength(0); + expect(playerPokemonTypes.filter(type => type === Type.GRASS)).toHaveLength(1); + expect(playerPokemonTypes.length === 2).toBeTruthy(); + } + ); +});