mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-04-22 01:24:13 +01:00
[Feature] Fully implement Octolock (#2985)
* implement octolock * Add tests
This commit is contained in:
parent
985c24e7bd
commit
0aa5e0d49d
@ -634,6 +634,32 @@ export class IngrainTag extends TrappedTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Octolock traps the target pokemon and reduces its DEF and SPDEF by one stage at the
|
||||||
|
* end of each turn.
|
||||||
|
*/
|
||||||
|
export class OctolockTag extends TrappedTag {
|
||||||
|
constructor(sourceId: number) {
|
||||||
|
super(BattlerTagType.OCTOLOCK, BattlerTagLapseType.TURN_END, 1, Moves.OCTOLOCK, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
|
const isOctolocked = pokemon.getTag(BattlerTagType.OCTOLOCK);
|
||||||
|
return !isOctolocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
|
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
|
if (shouldLapse) {
|
||||||
|
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], -1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class AquaRingTag extends BattlerTag {
|
export class AquaRingTag extends BattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined);
|
super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined);
|
||||||
@ -1662,6 +1688,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
|
|||||||
return new DestinyBondTag(sourceMove, sourceId);
|
return new DestinyBondTag(sourceMove, sourceId);
|
||||||
case BattlerTagType.ICE_FACE:
|
case BattlerTagType.ICE_FACE:
|
||||||
return new IceFaceTag(sourceMove);
|
return new IceFaceTag(sourceMove);
|
||||||
|
case BattlerTagType.OCTOLOCK:
|
||||||
|
return new OctolockTag(sourceId);
|
||||||
case BattlerTagType.NONE:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
@ -7796,8 +7796,7 @@ export function initMoves() {
|
|||||||
.attr(EatBerryAttr)
|
.attr(EatBerryAttr)
|
||||||
.target(MoveTarget.ALL),
|
.target(MoveTarget.ALL),
|
||||||
new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8)
|
new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1)
|
.attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1),
|
||||||
.partial(),
|
|
||||||
new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
||||||
.attr(FirstAttackDoublePowerAttr),
|
.attr(FirstAttackDoublePowerAttr),
|
||||||
new AttackMove(Moves.FISHIOUS_REND, Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
new AttackMove(Moves.FISHIOUS_REND, Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
||||||
|
@ -13,6 +13,7 @@ export enum BattlerTagType {
|
|||||||
ENCORE = "ENCORE",
|
ENCORE = "ENCORE",
|
||||||
HELPING_HAND = "HELPING_HAND",
|
HELPING_HAND = "HELPING_HAND",
|
||||||
INGRAIN = "INGRAIN",
|
INGRAIN = "INGRAIN",
|
||||||
|
OCTOLOCK = "OCTOLOCK",
|
||||||
AQUA_RING = "AQUA_RING",
|
AQUA_RING = "AQUA_RING",
|
||||||
DROWSY = "DROWSY",
|
DROWSY = "DROWSY",
|
||||||
TRAPPED = "TRAPPED",
|
TRAPPED = "TRAPPED",
|
||||||
|
62
src/test/battlerTags/octolock.test.ts
Normal file
62
src/test/battlerTags/octolock.test.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import Pokemon from "#app/field/pokemon.js";
|
||||||
|
import BattleScene from "#app/battle-scene.js";
|
||||||
|
import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags.js";
|
||||||
|
import { StatChangePhase } from "#app/phases.js";
|
||||||
|
import { BattleStat } from "#app/data/battle-stat.js";
|
||||||
|
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
||||||
|
|
||||||
|
jest.mock("#app/battle-scene.js");
|
||||||
|
|
||||||
|
describe("BattlerTag - OctolockTag", () => {
|
||||||
|
describe("lapse behavior", () => {
|
||||||
|
it("unshifts a StatChangePhase with expected stat changes", { timeout: 10000 }, async () => {
|
||||||
|
const mockPokemon = {
|
||||||
|
scene: new BattleScene(),
|
||||||
|
getBattlerIndex: () => 0,
|
||||||
|
} as Pokemon;
|
||||||
|
|
||||||
|
const subject = new OctolockTag(1);
|
||||||
|
|
||||||
|
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
|
||||||
|
expect(phase).toBeInstanceOf(StatChangePhase);
|
||||||
|
expect((phase as StatChangePhase)["levels"]).toEqual(-1);
|
||||||
|
expect((phase as StatChangePhase)["stats"]).toEqual([BattleStat.DEF, BattleStat.SPDEF]);
|
||||||
|
});
|
||||||
|
|
||||||
|
subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END);
|
||||||
|
|
||||||
|
expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it ("traps its target (extends TrappedTag)", { timeout: 2000 }, async () => {
|
||||||
|
expect(new OctolockTag(1)).toBeInstanceOf(TrappedTag);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can be added to pokemon who are not octolocked", { timeout: 2000 }, async => {
|
||||||
|
const mockPokemon = {
|
||||||
|
getTag: vi.fn().mockReturnValue(undefined) as Pokemon["getTag"],
|
||||||
|
} as Pokemon;
|
||||||
|
|
||||||
|
const subject = new OctolockTag(1);
|
||||||
|
|
||||||
|
expect(subject.canAdd(mockPokemon)).toBeTruthy();
|
||||||
|
|
||||||
|
expect(mockPokemon.getTag).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cannot be added to pokemon who are octolocked", { timeout: 2000 }, async => {
|
||||||
|
const mockPokemon = {
|
||||||
|
getTag: vi.fn().mockReturnValue(new BattlerTag(null, null, null, null)) as Pokemon["getTag"],
|
||||||
|
} as Pokemon;
|
||||||
|
|
||||||
|
const subject = new OctolockTag(1);
|
||||||
|
|
||||||
|
expect(subject.canAdd(mockPokemon)).toBeFalsy();
|
||||||
|
|
||||||
|
expect(mockPokemon.getTag).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK);
|
||||||
|
});
|
||||||
|
});
|
78
src/test/moves/octolock.test.ts
Normal file
78
src/test/moves/octolock.test.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
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, MoveEndPhase, TurnInitPhase } from "#app/phases";
|
||||||
|
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
|
||||||
|
import {BattleStat} from "#app/data/battle-stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { TrappedTag } from "#app/data/battler-tags.js";
|
||||||
|
|
||||||
|
describe("Moves - Octolock", () => {
|
||||||
|
describe("integration tests", () => {
|
||||||
|
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.RATTATA);
|
||||||
|
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
|
||||||
|
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
|
||||||
|
|
||||||
|
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(2000);
|
||||||
|
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.OCTOLOCK, Moves.SPLASH]);
|
||||||
|
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Reduces DEf and SPDEF by 1 each turn", { timeout: 10000 }, async () => {
|
||||||
|
await game.startBattle([Species.GRAPPLOCT]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
// use Octolock and advance to init phase of next turn to check for stat changes
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
|
||||||
|
await game.phaseInterceptor.to(TurnInitPhase);
|
||||||
|
|
||||||
|
expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-1);
|
||||||
|
expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-1);
|
||||||
|
|
||||||
|
// take a second turn to make sure stat changes occur again
|
||||||
|
await game.phaseInterceptor.to(CommandPhase);
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnInitPhase);
|
||||||
|
expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-2);
|
||||||
|
expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Traps the target pokemon", { timeout: 10000 }, async () => {
|
||||||
|
await game.startBattle([Species.GRAPPLOCT]);
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
// before Octolock - enemy should not be trapped
|
||||||
|
expect(enemyPokemon[0].findTag(t => t instanceof TrappedTag)).toBeUndefined();
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
|
||||||
|
|
||||||
|
// after Octolock - enemy should be trapped
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
expect(enemyPokemon[0].findTag(t => t instanceof TrappedTag)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user