From 0b5741ea1b13b688d3e43ef01fd610449ed6f19f Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 21 Aug 2024 04:26:04 -0700 Subject: [PATCH] Implement Lash Out --- src/data/move.ts | 10 ++--- src/field/pokemon.ts | 3 +- src/phases/stat-change-phase.ts | 12 ++++-- src/test/moves/burning_jealousy.test.ts | 57 +++++++++++++++++++++++-- src/test/moves/lash_out.test.ts | 53 +++++++++++++++++++++++ 5 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 src/test/moves/lash_out.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 0c7e48d7cd8..eab0cd33df9 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3,7 +3,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat"; import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; -import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect"; +import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects } from "./status-effect"; import { getTypeResistances, Type } from "./type"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; @@ -5891,7 +5891,6 @@ export class DestinyBondAttr extends MoveEffectAttr { /** * Attribute to apply a battler tag to the target if they have had their stats boosted this turn. - * * @extends AddBattlerTagAttr */ export class AddBattlerTagIfBoostedAttr extends AddBattlerTagAttr { @@ -5907,7 +5906,7 @@ export class AddBattlerTagIfBoostedAttr extends AddBattlerTagAttr { * @returns true */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (target.turnData.battleStatsChange.find(v => v > 0)) { + if (target.turnData.battleStatsIncrease) { super.apply(user, target, move, args); } return true; @@ -5916,7 +5915,6 @@ export class AddBattlerTagIfBoostedAttr extends AddBattlerTagAttr { /** * Attribute to apply a status effect to the target if they have had their stats boosted this turn. - * * @extends MoveEffectAttr */ export class StatusIfBoostedAttr extends MoveEffectAttr { @@ -5935,7 +5933,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr { * @returns true */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (target.turnData.battleStatsChange.find(v => v > 0)) { + if (target.turnData.battleStatsIncrease) { target.trySetStatus(this.effect, true, user); } return true; @@ -8573,7 +8571,7 @@ export function initMoves() { .attr(StatusIfBoostedAttr, StatusEffect.BURN) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) - .partial(), + .attr(MovePowerMultiplierAttr, (user, target, move) => user.turnData.battleStatsDecrease ? 2 : 1), new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8) .attr(AttackedByItemAttr) .makesContact(false), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a57b9d7cd90..b21e3408c60 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4278,7 +4278,8 @@ export class PokemonTurnData { public damageTaken: number = 0; public attacksReceived: AttackMoveResult[] = []; public order: number; - public battleStatsChange: number[] = [0, 0, 0, 0, 0, 0, 0]; + public battleStatsIncrease: boolean = false; + public battleStatsDecrease: boolean = false; } export enum AiType { diff --git a/src/phases/stat-change-phase.ts b/src/phases/stat-change-phase.ts index ffe5f11e28d..423349c0908 100644 --- a/src/phases/stat-change-phase.ts +++ b/src/phases/stat-change-phase.ts @@ -72,7 +72,7 @@ export class StatChangePhase extends PokemonPhase { } const battleStats = this.getPokemon().summonData.battleStats; - const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]); + const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats[stat] + levels.value, 6) : Math.max(battleStats[stat] + levels.value, -6)) - battleStats[stat]); this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels); @@ -85,12 +85,18 @@ export class StatChangePhase extends PokemonPhase { } for (const stat of filteredStats) { - if (levels.value > 0 && pokemon.summonData.battleStats[stat] + levels.value <= 6) { + if (levels.value > 0 && pokemon.summonData.battleStats[stat] < 6) { if (!pokemon.turnData) { // Temporary fix for missing turn data struct on turn 1 pokemon.resetTurnData(); } - pokemon.turnData.battleStatsChange[stat] += levels.value; + pokemon.turnData.battleStatsIncrease = true; + } else if (levels.value < 0 && pokemon.summonData.battleStats[stat] > -6) { + if (!pokemon.turnData) { + // Temporary fix for missing turn data struct on turn 1 + pokemon.resetTurnData(); + } + pokemon.turnData.battleStatsDecrease = true; } pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); diff --git a/src/test/moves/burning_jealousy.test.ts b/src/test/moves/burning_jealousy.test.ts index c97abe3feda..84189ee846a 100644 --- a/src/test/moves/burning_jealousy.test.ts +++ b/src/test/moves/burning_jealousy.test.ts @@ -1,13 +1,14 @@ import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { StatusEffect } from "#app/enums/status-effect"; -import { BerryPhase } from "#app/phases/berry-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { SPLASH_ONLY } from "../utils/testUtils"; const TIMEOUT = 20 * 1000; @@ -37,7 +38,7 @@ describe("Moves - Burning Jealousy", () => { .enemyLevel(10) .starterSpecies(Species.FEEBAS) .ability(Abilities.BALL_FETCH) - .moveset([Moves.BURNING_JEALOUSY]); + .moveset([Moves.BURNING_JEALOUSY, Moves.GROWL]); }); @@ -48,8 +49,56 @@ describe("Moves - Burning Jealousy", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.BURNING_JEALOUSY)); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to(BerryPhase); + await game.phaseInterceptor.to("BerryPhase"); expect(enemy.status?.effect).toBe(StatusEffect.BURN); }, TIMEOUT); + + it("should still burn the opponent if their stats were both raised and lowered in the same turn", async () => { + game.override + .starterSpecies(0) + .battleType("double"); + await game.startBattle([Species.FEEBAS, Species.ABRA]); + + const enemy = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.BURNING_JEALOUSY)); + game.doAttack(getMovePosition(game.scene, 1, Moves.GROWL)); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.status?.effect).toBe(StatusEffect.BURN); + }, TIMEOUT); + + it("should ignore stats raised by imposter", async () => { + game.override + .enemySpecies(Species.DITTO) + .enemyAbility(Abilities.IMPOSTER) + .enemyMoveset(SPLASH_ONLY); + await game.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.BURNING_JEALOUSY)); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.status?.effect).toBeUndefined(); + }, TIMEOUT); + + it.skip("should ignore weakness policy", async () => { // TODO: Make this test if WP is implemented + await game.startBattle(); + }, TIMEOUT); + + it("should be boosted by Sheer Force even if opponent didn't raise stats", async () => { + game.override + .ability(Abilities.SHEER_FORCE) + .enemyMoveset(SPLASH_ONLY); + vi.spyOn(allMoves[Moves.BURNING_JEALOUSY], "calculateBattlePower"); + await game.startBattle(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.BURNING_JEALOUSY)); + await game.phaseInterceptor.to("BerryPhase"); + + expect(allMoves[Moves.BURNING_JEALOUSY].calculateBattlePower).toHaveReturnedWith(allMoves[Moves.BURNING_JEALOUSY].power * 5461 / 4096); + }, TIMEOUT); }); diff --git a/src/test/moves/lash_out.test.ts b/src/test/moves/lash_out.test.ts new file mode 100644 index 00000000000..674a1f0a094 --- /dev/null +++ b/src/test/moves/lash_out.test.ts @@ -0,0 +1,53 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#app/enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +const TIMEOUT = 20 * 1000; + +describe("Moves - Burning Jealousy", () => { + 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") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.FUR_COAT) + .enemyMoveset(Array(4).fill(Moves.GROWL)) + .startingLevel(10) + .enemyLevel(10) + .starterSpecies(Species.FEEBAS) + .ability(Abilities.BALL_FETCH) + .moveset([Moves.LASH_OUT]); + + }); + + it("should deal double damage if the user's stats were lowered this turn", async () => { + vi.spyOn(allMoves[Moves.LASH_OUT], "calculateBattlePower"); + await game.startBattle(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.LASH_OUT)); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(allMoves[Moves.LASH_OUT].calculateBattlePower).toHaveReturnedWith(150); + }, TIMEOUT); +});