Implemented safeguard and tests

This commit is contained in:
Joshua Keegan 2024-08-08 13:53:55 -07:00 committed by NightKev
parent b794662776
commit 0692ef4fe7
4 changed files with 164 additions and 3 deletions

View File

@ -844,7 +844,30 @@ class HappyHourTag extends ArenaTag {
}
}
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
class SafegaurdTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
}
onAdd(arena: Arena): void {
if (this.side === ArenaTagSide.PLAYER) {
arena.scene.queueMessage("Your team cloaked itself in a mystical veil!");
} else {
arena.scene.queueMessage("Enemy team cloaked itself in a mystical veil!");
}
}
onRemove(arena: Arena): void {
if (this.side === ArenaTagSide.PLAYER) {
arena.scene.queueMessage("Your team is not longer protected by the mystical veil!");
} else {
arena.scene.queueMessage("Enemy team is not longer protected by the mystical veil!");
}
}
}
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag {
switch (tagType) {
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
@ -889,6 +912,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side);
case ArenaTagType.SAFEGUARD:
return new SafegaurdTag(turnCount, sourceId, side);
default:
return null;
}

View File

@ -4,7 +4,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat";
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, StockpilingTag, 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";
@ -1918,6 +1918,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
const targetSide = target instanceof EnemyPokemon ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER;
if (statusCheck) {
const pokemon = this.selfTarget ? user : target;
if (pokemon.status) {
@ -1927,6 +1928,16 @@ export class StatusEffectAttr extends MoveEffectAttr {
return false;
}
}
if (user.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, targetSide)) {
if (move.category === MoveCategory.STATUS) {
user.scene.pushPhase(
new MessagePhase(user.scene,
`${target.name} is protected by Safeguard!`,
0, false, 0), false);
}
return false;
}
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, this.effect);
@ -6740,7 +6751,7 @@ export function initMoves() {
.attr(FriendshipPowerAttr, true),
new StatusMove(Moves.SAFEGUARD, Type.NORMAL, -1, 25, -1, 0, 2)
.target(MoveTarget.USER_SIDE)
.unimplemented(),
.attr(AddArenaTagAttr, ArenaTagType.SAFEGUARD, 5, true, true),
new StatusMove(Moves.PAIN_SPLIT, Type.NORMAL, -1, 20, -1, 0, 2)
.attr(HpSplitAttr)
.condition(failOnBossCondition),

View File

@ -22,5 +22,6 @@ export enum ArenaTagType {
CRAFTY_SHIELD = "CRAFTY_SHIELD",
TAILWIND = "TAILWIND",
HAPPY_HOUR = "HAPPY_HOUR",
SAFEGUARD = "SAFEGUARD",
NO_CRIT = "NO_CRIT"
}

View File

@ -0,0 +1,124 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {
CommandPhase,
SelectTargetPhase,
TurnEndPhase,
} from "#app/phases";
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattlerIndex } from "#app/battle.js";
import { Stat } from "#app/data/pokemon-stat";
import { ArenaTagType } from "#app/enums/arena-tag-type.js";
import { ArenaTagSide } from "#app/data/arena-tag.js";
const TIMEOUT = 20 * 1000;
const SAFEGUARD = Moves.SAFEGUARD;
describe("Moves - Safeguard", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.AMOONGUSS);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.DEOXYS);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPORE, Moves.SPORE, Moves.SPORE, Moves.SPORE]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SAFEGUARD, Moves.SAFEGUARD, Moves.SAFEGUARD, Moves.SAFEGUARD]);
});
it("protects from nuzzle status",
async () => {
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SENTRET);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.NUZZLE,
Moves.NUZZLE,
Moves.NUZZLE,
Moves.NUZZLE]);
await game.startBattle([Species.DEOXYS]);
const enemyPokemon = game.scene.getEnemyField();
const playerPokemon = game.scene.getPlayerField();
game.doAttack(getMovePosition(game.scene, 0, SAFEGUARD));
expect(enemyPokemon[0].status).toBe(undefined);
expect(playerPokemon[0].status).toBe(undefined);
}, TIMEOUT
);
it("protects from spore",
async () => {
await game.startBattle([Species.DEOXYS]);
const enemyPokemon = game.scene.getEnemyField();
const playerPokemon = game.scene.getPlayerField();
game.doAttack(getMovePosition(game.scene, 0, Moves.SAFEGUARD));
expect(enemyPokemon[0].status).toBe(undefined);
expect(playerPokemon[0].status).toBe(undefined);
}, TIMEOUT
);
it("protects ally from status",
async () => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.DEOXYS);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.AMOONGUSS);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SAFEGUARD, Moves.SAFEGUARD, Moves.SAFEGUARD, Moves.SAFEGUARD]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPORE, Moves.NUZZLE, Moves.SPORE, Moves.SPORE]);
await game.startBattle([Species.AMOONGUSS, Species.FURRET]);
game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 1;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPORE));
await game.phaseInterceptor.to(SelectTargetPhase, false);
game.doSelectTarget(BattlerIndex.ENEMY_2);
await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 1, Moves.NUZZLE));
await game.phaseInterceptor.to(SelectTargetPhase, false);
game.doSelectTarget(BattlerIndex.ENEMY_2);
await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyField();
const playerPokemon = game.scene.getPlayerField();
expect(enemyPokemon[0].status).toBe(undefined);
expect(enemyPokemon[1].status).toBe(undefined);
expect(playerPokemon[0].status).toBe(undefined);
expect(playerPokemon[1].status).toBe(undefined);
}, TIMEOUT
);
it("applys arena tag for 5 turns",
async () => {
await game.startBattle([Species.DEOXYS]);
for (let i=0;i<5;i++) {
game.doAttack(getMovePosition(game.scene, 0, Moves.SAFEGUARD));
await game.phaseInterceptor.to(CommandPhase);
}
expect(game.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, ArenaTagSide.PLAYER)).toBeUndefined();
}, TIMEOUT
);
});