From b5f948ffec4a03f76eb71d2d04894c21d076ee8f Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:44:46 -0700 Subject: [PATCH] Implement Safeguard for non-volatile statuses --- src/data/ability.ts | 2 +- src/data/move.ts | 5 +- src/field/pokemon.ts | 5 ++ src/test/moves/safeguard.test.ts | 78 ++++++++++++++++++++++++++++++-- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 950538af9f6..5c66e2409df 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -845,7 +845,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { } export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { - private chance: integer; + public chance: integer; private effects: StatusEffect[]; constructor(chance: integer, ...effects: StatusEffect[]) { diff --git a/src/data/move.ts b/src/data/move.ts index 97d8463f058..a17bad1cdc5 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1929,7 +1929,7 @@ export class StatusEffectAttr extends MoveEffectAttr { } } - if (user.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, targetSide)) { + if (user !== target && user.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, targetSide)) { if (move.category === MoveCategory.STATUS) { user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target)})); } @@ -6748,7 +6748,8 @@ export function initMoves() { .attr(FriendshipPowerAttr, true), new StatusMove(Moves.SAFEGUARD, Type.NORMAL, -1, 25, -1, 0, 2) .target(MoveTarget.USER_SIDE) - .attr(AddArenaTagAttr, ArenaTagType.SAFEGUARD, 5, true, true), + .attr(AddArenaTagAttr, ArenaTagType.SAFEGUARD, 5, true, true) + .partial(), new StatusMove(Moves.PAIN_SPLIT, Type.NORMAL, -1, 20, -1, 0, 2) .attr(HpSplitAttr) .condition(failOnBossCondition), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index bf9cc6db42a..fe6d3b8ad39 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2686,6 +2686,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const types = this.getTypes(true, true); + const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { + return false; + } + switch (effect) { case StatusEffect.POISON: case StatusEffect.TOXIC: diff --git a/src/test/moves/safeguard.test.ts b/src/test/moves/safeguard.test.ts index 3d899968e27..cab53032a64 100644 --- a/src/test/moves/safeguard.test.ts +++ b/src/test/moves/safeguard.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import Phaser from "phaser"; import GameManager from "#app/test/utils/gameManager"; import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; @@ -8,6 +8,8 @@ import { Species } from "#enums/species"; import { BattlerIndex } from "#app/battle.js"; import { Abilities } from "#app/enums/abilities.js"; import { mockTurnOrder } from "../utils/testUtils"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { allAbilities, PostDefendContactApplyStatusEffectAbAttr } from "#app/data/ability.js"; const TIMEOUT = 20 * 1000; @@ -34,10 +36,10 @@ describe("Moves - Safeguard", () => { .enemyAbility(Abilities.BALL_FETCH) .enemyLevel(5) .starterSpecies(Species.DRATINI) - .moveset([Moves.NUZZLE, Moves.SPORE]) + .moveset([Moves.NUZZLE, Moves.SPORE, Moves.YAWN, Moves.SPLASH]) .ability(Abilities.BALL_FETCH); }); - it("protects from nuzzle status", + it("protects from damaging moves with additional effects", async () => { await game.startBattle(); const enemy = game.scene.getEnemyPokemon()!; @@ -49,9 +51,8 @@ describe("Moves - Safeguard", () => { expect(enemy.status).toBeUndefined(); }, TIMEOUT ); - it("protects from spore", + it("protects from status moves", async () => { - await game.startBattle(); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -88,4 +89,71 @@ describe("Moves - Safeguard", () => { expect(enemyPokemon[1].status).toBeUndefined(); }, TIMEOUT ); + + it.skip("protects from new volatile status", // not yet + async () => { + await game.startBattle(); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.YAWN)); + await mockTurnOrder(game, [BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toNextTurn(); + + expect(enemyPokemon.summonData.tags).toEqual([]); + }, TIMEOUT + ); + + it.skip("doesn't protect from already existing volatile status", // not yet + async () => { + await game.startBattle(); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.YAWN)); + await mockTurnOrder(game, [BattlerIndex.PLAYER, BattlerIndex.ENEMY]); + await game.toNextTurn(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.toNextTurn(); + + expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP); + }, TIMEOUT + ); + + it("doesn't protect from self-inflicted via Rest or Flame Orb", + async () => { + game.override.enemyHeldItems([{name: "FLAME_ORB"}]); + await game.startBattle(); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await mockTurnOrder(game, [BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toNextTurn(); + + expect(enemyPokemon.status?.effect).toEqual(StatusEffect.BURN); + + game.override.enemyMoveset(Array(4).fill(Moves.REST)); + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.toNextTurn(); + + expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP); + }, TIMEOUT + ); + + it("protects from ability-inflicted status", + async () => { + game.override.ability(Abilities.STATIC); + vi.spyOn(allAbilities[Abilities.STATIC].getAttrs(PostDefendContactApplyStatusEffectAbAttr)[0], "chance", "get").mockReturnValue(100); + await game.startBattle(); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await mockTurnOrder(game, [BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toNextTurn(); + game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)); + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.toNextTurn(); + + expect(enemyPokemon.status).toBeUndefined(); + }, TIMEOUT + ); });