From bcec819fa91d96ef81cfffd7c716940052f3d395 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Sun, 29 Sep 2024 18:50:11 -0700 Subject: [PATCH] [Move] Fully Implement Syrup Bomb (#4441) * Syrup Bomb + Tests * Fix typo on import * Documentation * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Removed unnecessary overlap check * Removed obsolete comment * learned how forceHit works * added custom lapse message --------- Co-authored-by: frutescens Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 39 +++++++++++++++ src/data/move.ts | 5 +- src/enums/battler-tag-type.ts | 1 + src/locales/en/battler-tags.json | 4 +- src/test/moves/syrup_bomb.test.ts | 82 +++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 src/test/moves/syrup_bomb.test.ts diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 4e688251147..6758dc1fada 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2589,6 +2589,43 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { } } +/** + * Battler Tag that applies the effects of Syrup Bomb to the target Pokemon. + * For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1. + * The tag can also expire by taking the target Pokemon off the field. + */ +export class SyrupBombTag extends BattlerTag { + constructor() { + super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB); + } + + /** + * Adds the Syrup Bomb battler tag to the target Pokemon. + * @param {Pokemon} pokemon the target Pokemon + */ + override onAdd(pokemon: Pokemon) { + super.onAdd(pokemon); + pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + /** + * Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count + * @param {Pokemon} pokemon the target Pokemon + * @param {BattlerTagLapseType} _lapseType + * @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field + */ + override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { + if (!pokemon.isActive(true)) { + return false; + } + pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline + pokemon.scene.unshiftPhase(new StatStageChangePhase( + pokemon.scene, pokemon.getBattlerIndex(), true, + [Stat.SPD], -1, true, false, true + )); + return --this.turnCount > 0; + } +} /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. @@ -2763,6 +2800,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new TauntTag(); case BattlerTagType.IMPRISON: return new ImprisonTag(sourceId); + case BattlerTagType.SYRUP_BOMB: + return new SyrupBombTag(); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index 75799bdf2cd..59db7495754 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -9622,9 +9622,8 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES) .triageMove(), new AttackMove(Moves.SYRUP_BOMB, Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) //Temporary - .ballBombMove() - .partial(), + .attr(AddBattlerTagAttr, BattlerTagType.SYRUP_BOMB, false, false, 3) + .ballBombMove(), new AttackMove(Moves.IVY_CUDGEL, Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9) .attr(IvyCudgelTypeAttr) .attr(HighCritAttr) diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 9ed3b629746..209d36316f9 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -85,4 +85,5 @@ export enum BattlerTagType { TORMENT = "TORMENT", TAUNT = "TAUNT", IMPRISON = "IMPRISON", + SYRUP_BOMB = "SYRUP_BOMB", } diff --git a/src/locales/en/battler-tags.json b/src/locales/en/battler-tags.json index 520ac2a6202..bb4b708b25f 100644 --- a/src/locales/en/battler-tags.json +++ b/src/locales/en/battler-tags.json @@ -78,5 +78,7 @@ "tormentOnAdd": "{{pokemonNameWithAffix}} was subjected to torment!", "tauntOnAdd": "{{pokemonNameWithAffix}} fell for the taunt!", "imprisonOnAdd": "{{pokemonNameWithAffix}} sealed the opponents move(s)!", - "autotomizeOnAdd": "{{pokemonNameWithAffix}} became nimble!" + "autotomizeOnAdd": "{{pokemonNameWithAffix}} became nimble!", + "syrupBombOnAdd": "{{pokemonNameWithAffix}} got covered in sticky, candy syrup!", + "syrupBombLapse": "The sticky syrup slowed down {{pokemonNameWithAffix}}!" } diff --git a/src/test/moves/syrup_bomb.test.ts b/src/test/moves/syrup_bomb.test.ts new file mode 100644 index 00000000000..20cd590e457 --- /dev/null +++ b/src/test/moves/syrup_bomb.test.ts @@ -0,0 +1,82 @@ +import { allMoves } from "#app/data/move"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { BattlerIndex } from "#app/battle"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - SYRUP BOMB", () => { + 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 + .starterSpecies(Species.MAGIKARP) + .enemySpecies(Species.SNORLAX) + .startingLevel(30) + .enemyLevel(100) + .moveset([Moves.SYRUP_BOMB, Moves.SPLASH]) + .enemyMoveset(Moves.SPLASH); + vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100); + }); + + //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move) + + it("decreases the target Pokemon's speed stat once per turn for 3 turns", + async () => { + await game.startBattle([Species.MAGIKARP]); + + const targetPokemon = game.scene.getEnemyPokemon()!; + expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); + + game.move.select(Moves.SYRUP_BOMB); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.move.forceHit(); + await game.toNextTurn(); + expect(targetPokemon.getTag(BattlerTagType.SYRUP_BOMB)).toBeDefined(); + expect(targetPokemon.getStatStage(Stat.SPD)).toBe(-1); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + expect(targetPokemon.getTag(BattlerTagType.SYRUP_BOMB)).toBeDefined(); + expect(targetPokemon.getStatStage(Stat.SPD)).toBe(-2); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + expect(targetPokemon.getTag(BattlerTagType.SYRUP_BOMB)).toBeUndefined(); + expect(targetPokemon.getStatStage(Stat.SPD)).toBe(-3); + } + ); + + it("does not affect Pokemon with the ability Bulletproof", + async () => { + game.override.enemyAbility(Abilities.BULLETPROOF); + await game.startBattle([Species.MAGIKARP]); + + const targetPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SYRUP_BOMB); + await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.move.forceHit(); + await game.toNextTurn(); + expect(targetPokemon.isFullHp()).toBe(true); + expect(targetPokemon.getTag(BattlerTagType.SYRUP_BOMB)).toBeUndefined(); + expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); + } + ); +});