From 05fcf581394f7c1f446b14815d335504e0dccbae Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 25 Oct 2024 04:22:07 -0400 Subject: [PATCH] squash --- src/data/ability.ts | 1 + src/data/move.ts | 1 + src/field/pokemon.ts | 2 + src/phases/faint-phase.ts | 5 +- src/phases/post-turn-status-effect-phase.ts | 3 + src/phases/weather-effect-phase.ts | 3 + src/test/moves/fell_stinger.test.ts | 121 ++++++++++++++++++++ 7 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/fell_stinger.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index ebdd5105bb4..64dd4b6b976 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3343,6 +3343,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; scene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER); + pokemon.turnData.lastDmgSrc = WeatherType.HARSH_SUN; } return true; diff --git a/src/data/move.ts b/src/data/move.ts index ec25844909e..bcfc7b6e381 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1352,6 +1352,7 @@ export class RecoilAttr extends MoveEffectAttr { user.damageAndUpdate(recoilDamage, HitResult.OTHER, false, true, true); user.scene.queueMessage(i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) })); user.turnData.damageTaken += recoilDamage; + user.turnData.lastDmgSrc = user.id; return true; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a3d7429ed9b..e46bddddda5 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2796,6 +2796,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.battleData.hitCount++; const attackResult = { move: move.id, result: result as DamageResult, damage: damage, critical: isCritical, sourceId: source.id, sourceBattlerIndex: source.getBattlerIndex() }; this.turnData.attacksReceived.unshift(attackResult); + this.turnData.lastDmgSrc = source; if (source.isPlayer() && !this.isPlayer()) { this.scene.applyModifiers(DamageMoneyRewardModifier, true, source, new Utils.NumberHolder(damage)); } @@ -5125,6 +5126,7 @@ export class PokemonTurnData { public statStagesDecreased: boolean = false; public moveEffectiveness: TypeDamageMultiplier | null = null; public combiningPledge?: Moves; + public lastDmgSrc: Pokemon | WeatherType | StatusEffect; } export enum AiType { diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index eee1fd52938..fb19ca7179f 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -96,8 +96,9 @@ export class FaintPhase extends PokemonPhase { const alivePlayField = this.scene.getField(true); alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); if (pokemon.turnData?.attacksReceived?.length) { - const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); - if (defeatSource?.isOnField()) { + const defeatSource = pokemon.turnData.lastDmgSrc; + + if (defeatSource instanceof Pokemon && defeatSource?.isOnField()) { applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 2efd992a2b5..efbe019e3de 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -28,13 +28,16 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { switch (pokemon.status.effect) { case StatusEffect.POISON: damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); + pokemon.turnData.lastDmgSrc = StatusEffect.POISON; break; case StatusEffect.TOXIC: damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.toxicTurnCount), 1); + pokemon.turnData.lastDmgSrc = StatusEffect.TOXIC; break; case StatusEffect.BURN: damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); + pokemon.turnData.lastDmgSrc = StatusEffect.BURN; break; } if (damage.value) { diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index b48ee342780..1a5865faf55 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -54,6 +54,9 @@ export class WeatherEffectPhase extends CommonAnimPhase { const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; if (!immune) { inflictDamage(pokemon); + if (this.weather?.weatherType) { + pokemon.turnData.lastDmgSrc = this.weather.weatherType; + } } }); } diff --git a/src/test/moves/fell_stinger.test.ts b/src/test/moves/fell_stinger.test.ts new file mode 100644 index 00000000000..596a4aa8db0 --- /dev/null +++ b/src/test/moves/fell_stinger.test.ts @@ -0,0 +1,121 @@ +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import GameManager from "../utils/gameManager"; +import { Species } from "#enums/species"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#app/enums/status-effect"; +import { WeatherType } from "#app/enums/weather-type"; + + +describe("Moves - Fell Stinger", () => { + 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") + .moveset([ Moves.FELL_STINGER ]) + .startingLevel(50); + + game.override.enemyAbility(Abilities.STURDY) + .enemySpecies(Species.HYPNO) + .enemyLevel(5) + .enemyHeldItems([]); + + game.override.weather(WeatherType.NONE); + }); + + it("should not grant stat boost when opponent gets KO'd by recoil", async () => { + game.override.enemyMoveset([ Moves.DOUBLE_EDGE ]); + await game.classicMode.startBattle([ Species.LEAVANNY ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK) === 0); // Attack stage should still be at 0 + }); + + it("should not grant stat boost when enemy is KO'd by status effect", async () => { + game.override + .enemyMoveset(Moves.SPLASH) + .enemyStatusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([ Species.LEAVANNY ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK) === 0); // Attack stage should still be at 0 + }); + + it("should not grant stat boost when enemy is KO'd by damaging weather", async () => { + game.override.weather(WeatherType.HAIL); + await game.classicMode.startBattle([ Species.LEAVANNY ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK) === 0); // Attack stage should still be at 0 + }); + + it("should not grant stat boost when enemy is KO'd by Dry Skin + Harsh Sunlight", async () => { + game.override + .enemyPassiveAbility(Abilities.STURDY) + .enemyAbility(Abilities.DRY_SKIN) + .weather(WeatherType.HARSH_SUN); + await game.challengeMode.startBattle([ Species.LEAVANNY ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK) === 0); // Attack stage should still be at 0 + }); + + it("should not grant stat boost if enemy is saved by Reviver Seed", async () => { + game.override + .enemyAbility(Abilities.KLUTZ) + .enemyHeldItems([{ name: "REVIVER_SEED" }]); + + await game.classicMode.startBattle([ Species.LEAVANNY ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(leadPokemon.getStatStage(Stat.ATK) === 0); // Attack stage should still be at 0 + }); + + it("should grant stat boost when enemy dies directly to hit", async () => { + game.override.enemyAbility(Abilities.KLUTZ); + await game.challengeMode.startBattle([ Species.LEAVANNY ]); + + const leadPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.FELL_STINGER); + + await game.phaseInterceptor.to("VictoryPhase"); + + expect(leadPokemon.getStatStage(Stat.ATK) === 3); // Attack stage should have risen to 3 + }); +});