Merge branch 'beta' into refactor-starter-handling

This commit is contained in:
Sirz Benjie 2025-09-05 22:48:21 -05:00 committed by GitHub
commit 6e11111c3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 765 additions and 389 deletions

View File

@ -23,7 +23,9 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
api-dir: ./ docs-dir: ./pokerogue_docs
# Only push docs when running on pushes to main/beta
DRY_RUN: ${{github.event_name != 'push' || (github.ref_name != 'beta' && github.ref_name != 'main')}}
strategy: strategy:
fail-fast: false fail-fast: false
@ -45,7 +47,7 @@ jobs:
with: with:
version: 10 version: 10
- name: Setup Node 22.14.1 - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: "pokerogue_docs/.nvmrc" node-version-file: "pokerogue_docs/.nvmrc"
@ -58,26 +60,26 @@ jobs:
ref: gh-pages ref: gh-pages
- name: Install Node.js dependencies - name: Install Node.js dependencies
working-directory: ${{env.api-dir}} working-directory: ${{env.docs-dir}}
run: | run: pnpm i
cd pokerogue_docs
pnpm i
- name: Generate Typedoc docs - name: Generate Typedoc docs
working-directory: ${{env.api-dir}} working-directory: ${{env.docs-dir}}
run: | env:
cd pokerogue_docs REF_NAME: ${{github.ref_name}}
pnpm exec typedoc --out /tmp/docs --githubPages false --entryPoints ./src/ DRY_RUN: ${{env.DRY_RUN}}
run: pnpm typedoc
- name: Commit & Push docs - name: Commit & Push docs
if: github.event_name == 'push' && (github.ref_name == 'beta' || github.ref_name == 'main') # env vars are stored as strings instead of booleans (hence why an explicit check is required)
if: ${{ env.DRY_RUN == 'false'}}
run: | run: |
cd pokerogue_gh cd pokerogue_gh
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
mkdir -p $GITHUB_REF_NAME mkdir -p $GITHUB_REF_NAME
rm -rf $GITHUB_REF_NAME/* rm -rf $GITHUB_REF_NAME/*
cp -r /tmp/docs/. $GITHUB_REF_NAME cp -r /tmp/docs $GITHUB_REF_NAME
git add $GITHUB_REF_NAME git add $GITHUB_REF_NAME
git commit --allow-empty -m "[skip ci] Deploy docs" git commit -m "[skip ci] Deploy docs"
git push git push

View File

@ -1,5 +1,10 @@
<picture><img src="./public/images/logo.png" width="300" alt="PokéRogue"></picture> <picture><img src="./public/images/logo.png" width="300" alt="PokéRogue"></picture>
![Discord Static Badge](https://img.shields.io/badge/Community_Discord-blurple?style=flat&logo=discord&logoSize=auto&labelColor=white&color=5865F2&link=https://discord.gg/pokerogue)
[![Docs Coverage Static Badge](https://pagefaultgames.github.io/pokerogue/beta/coverage.svg)](https://pagefaultgames.github.io/pokerogue/beta)
[![Testing Badge](https://github.com/pagefaultgames/pokerogue/actions/workflows/tests.yml/badge.svg)](https://github.com/pagefaultgames/pokerogue/actions/workflows/tests.yml)
[![License: GNU AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more! PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more!
# Contributing # Contributing
@ -7,7 +12,7 @@ PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite
See [CONTRIBUTING.md](./CONTRIBUTING.md), this includes instructions on how to set up the game locally. See [CONTRIBUTING.md](./CONTRIBUTING.md), this includes instructions on how to set up the game locally.
# 📝 Credits # 📝 Credits
>
> If this project contains assets you have produced and you do not see your name, **please** reach out, either [here on GitHub](https://github.com/pagefaultgames/pokerogue/issues/new) or via [Discord](https://discord.gg/pokerogue). > If this project contains assets you have produced and you do not see your name, **please** reach out, either [here on GitHub](https://github.com/pagefaultgames/pokerogue/issues/new) or via [Discord](https://discord.gg/pokerogue).
Thank you to all the wonderful people that have contributed to the PokéRogue project! You can find the credits [here](./CREDITS.md). Thank you to all the wonderful people that have contributed to the PokéRogue project! You can find the credits [here](./CREDITS.md).

11
global.d.ts vendored
View File

@ -18,3 +18,14 @@ declare global {
call<T extends AnyFn>(this: T, thisArg: ThisParameterType<T>, ...argArray: Parameters<T>): ReturnType<T>; call<T extends AnyFn>(this: T, thisArg: ThisParameterType<T>, ...argArray: Parameters<T>): ReturnType<T>;
} }
} }
// Global augments for `typedoc` to prevent TS from erroring when editing the config JS file
declare module "typedoc" {
export interface TypeDocOptionMap {
coverageLabel: string;
coverageColor: string;
coverageOutputPath: string;
coverageOutputType: "svg" | "json" | "all";
coverageSvgWidth: number;
}
}

View File

@ -19,7 +19,7 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error", "biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"docs": "typedoc", "typedoc": "typedoc",
"depcruise": "depcruise src test", "depcruise": "depcruise src test",
"postinstall": "lefthook install; git submodule update --init --recursive", "postinstall": "lefthook install; git submodule update --init --recursive",
"update-version:patch": "pnpm version patch --force --no-git-tag-version", "update-version:patch": "pnpm version patch --force --no-git-tag-version",
@ -42,6 +42,9 @@
"msw": "^2.10.4", "msw": "^2.10.4",
"phaser3spectorjs": "^0.0.8", "phaser3spectorjs": "^0.0.8",
"typedoc": "^0.28.8", "typedoc": "^0.28.8",
"typedoc-github-theme": "^0.3.1",
"typedoc-plugin-coverage": "^4.0.1",
"typedoc-plugin-mdn-links": "^5.0.9",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.6", "vite": "^7.0.6",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",

View File

@ -87,6 +87,15 @@ importers:
typedoc: typedoc:
specifier: ^0.28.8 specifier: ^0.28.8
version: 0.28.8(typescript@5.8.3) version: 0.28.8(typescript@5.8.3)
typedoc-github-theme:
specifier: ^0.3.1
version: 0.3.1(typedoc@0.28.8(typescript@5.8.3))
typedoc-plugin-coverage:
specifier: ^4.0.1
version: 4.0.1(typedoc@0.28.8(typescript@5.8.3))
typedoc-plugin-mdn-links:
specifier: ^5.0.9
version: 5.0.9(typedoc@0.28.8(typescript@5.8.3))
typescript: typescript:
specifier: ^5.8.3 specifier: ^5.8.3
version: 5.8.3 version: 5.8.3
@ -1789,6 +1798,23 @@ packages:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'} engines: {node: '>=16'}
typedoc-github-theme@0.3.1:
resolution: {integrity: sha512-j6PmkAGmf/MGCzYjQcUH6jS9djPsNl/IoTXooxC+MoeMkBhbmPyKJlpR6Lw12BLoe2OYpYA2J1KMktUJXp/8Sw==}
engines: {node: '>=18.0.0'}
peerDependencies:
typedoc: ~0.28.0
typedoc-plugin-coverage@4.0.1:
resolution: {integrity: sha512-P1QBR5GJSfW3fDrpz4Vkd8z8lzWaBYjaHebRLk0u2Uga0oSFlPaqrCyiHzItBXxZX28aMlNlZwrUnsLgUgqA7g==}
engines: {node: '>= 18'}
peerDependencies:
typedoc: 0.28.x
typedoc-plugin-mdn-links@5.0.9:
resolution: {integrity: sha512-kXssRKBhUd0JeHzFmxWVsGWVFR9WXafe70Y8Ed+MYH2Nu2647cqfGQN1OBKgvXpmAT8MTpACmUIQ7GnQnh1/iw==}
peerDependencies:
typedoc: 0.27.x || 0.28.x
typedoc@0.28.8: typedoc@0.28.8:
resolution: {integrity: sha512-16GfLopc8icHfdvqZDqdGBoS2AieIRP2rpf9mU+MgN+gGLyEQvAO0QgOa6NJ5QNmQi0LFrDY9in4F2fUNKgJKA==} resolution: {integrity: sha512-16GfLopc8icHfdvqZDqdGBoS2AieIRP2rpf9mU+MgN+gGLyEQvAO0QgOa6NJ5QNmQi0LFrDY9in4F2fUNKgJKA==}
engines: {node: '>= 18', pnpm: '>= 10'} engines: {node: '>= 18', pnpm: '>= 10'}
@ -3634,6 +3660,18 @@ snapshots:
type-fest@4.41.0: {} type-fest@4.41.0: {}
typedoc-github-theme@0.3.1(typedoc@0.28.8(typescript@5.8.3)):
dependencies:
typedoc: 0.28.8(typescript@5.8.3)
typedoc-plugin-coverage@4.0.1(typedoc@0.28.8(typescript@5.8.3)):
dependencies:
typedoc: 0.28.8(typescript@5.8.3)
typedoc-plugin-mdn-links@5.0.9(typedoc@0.28.8(typescript@5.8.3)):
dependencies:
typedoc: 0.28.8(typescript@5.8.3)
typedoc@0.28.8(typescript@5.8.3): typedoc@0.28.8(typescript@5.8.3):
dependencies: dependencies:
'@gerrit0/mini-shiki': 3.8.1 '@gerrit0/mini-shiki': 3.8.1

View File

@ -2,6 +2,7 @@ import type { ArenaTagTypeMap } from "#data/arena-tag";
import type { ArenaTagType } from "#enums/arena-tag-type"; import type { ArenaTagType } from "#enums/arena-tag-type";
// biome-ignore lint/correctness/noUnusedImports: TSDocs // biome-ignore lint/correctness/noUnusedImports: TSDocs
import type { SessionSaveData } from "#system/game-data"; import type { SessionSaveData } from "#system/game-data";
import type { ObjectValues } from "#types/type-helpers";
/** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */ /** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */
export type EntryHazardTagType = export type EntryHazardTagType =
@ -24,22 +25,32 @@ export type TurnProtectArenaTagType =
/** Subset of {@linkcode ArenaTagType}s that create Trick Room-like effects which are removed upon overlap. */ /** Subset of {@linkcode ArenaTagType}s that create Trick Room-like effects which are removed upon overlap. */
export type RoomArenaTagType = ArenaTagType.TRICK_ROOM; export type RoomArenaTagType = ArenaTagType.TRICK_ROOM;
/** Subset of {@linkcode ArenaTagType}s that cannot persist across turns, and thus should not be serialized in {@linkcode SessionSaveData}. */ /** Subset of {@linkcode ArenaTagType}s that are **not** able to persist across turns, and should therefore not be serialized in {@linkcode SessionSaveData}. */
export type NonSerializableArenaTagType = ArenaTagType.NONE | TurnProtectArenaTagType | ArenaTagType.ION_DELUGE; export type NonSerializableArenaTagType = ArenaTagType.NONE | TurnProtectArenaTagType | ArenaTagType.ION_DELUGE;
/** Subset of {@linkcode ArenaTagType}s that may persist across turns, and thus must be serialized in {@linkcode SessionSaveData}. */ /** Subset of {@linkcode ArenaTagType}s that may persist across turns, and thus must be serialized in {@linkcode SessionSaveData}. */
export type SerializableArenaTagType = Exclude<ArenaTagType, NonSerializableArenaTagType>; export type SerializableArenaTagType = Exclude<ArenaTagType, NonSerializableArenaTagType>;
/** /**
* Type-safe representation of an arbitrary, serialized Arena Tag * Utility type containing all entries of {@linkcode ArenaTagTypeMap} corresponding to serializable tags.
*/ */
export type ArenaTagTypeData = Parameters< type SerializableArenaTagTypeMap = Pick<ArenaTagTypeMap, SerializableArenaTagType>;
ArenaTagTypeMap[keyof {
[K in keyof ArenaTagTypeMap as K extends SerializableArenaTagType ? K : never]: ArenaTagTypeMap[K];
}]["loadTag"]
>[0];
/** Dummy, typescript-only declaration to ensure that /**
* Type mapping all `ArenaTag`s to type-safe representations of their serialized forms.
* @interface
*/
export type ArenaTagDataMap = {
[k in keyof SerializableArenaTagTypeMap]: Parameters<SerializableArenaTagTypeMap[k]["loadTag"]>[0];
};
/**
* Type-safe representation of an arbitrary, serialized `ArenaTag`.
*/
export type ArenaTagData = ObjectValues<ArenaTagDataMap>;
/**
* Dummy, typescript-only declaration to ensure that
* {@linkcode ArenaTagTypeMap} has a map for all ArenaTagTypes. * {@linkcode ArenaTagTypeMap} has a map for all ArenaTagTypes.
* *
* If an arena tag is missing from the map, typescript will throw an error on this statement. * If an arena tag is missing from the map, typescript will throw an error on this statement.

View File

@ -1,8 +1,11 @@
// biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment // biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment
import type { AbilityBattlerTag, BattlerTagTypeMap, SerializableBattlerTag, TypeBoostTag } from "#data/battler-tags"; import type { AbilityBattlerTag, BattlerTagTypeMap, SerializableBattlerTag, TypeBoostTag } from "#data/battler-tags";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
// biome-ignore-end lint/correctness/noUnusedImports: end import type { SessionSaveData } from "#system/game-data";
// biome-ignore-end lint/correctness/noUnusedImports: Used in a TSDoc comment
import type { BattlerTagType } from "#enums/battler-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type";
import type { InferKeys, ObjectValues } from "#types/type-helpers";
/** /**
* Subset of {@linkcode BattlerTagType}s that restrict the use of moves. * Subset of {@linkcode BattlerTagType}s that restrict the use of moves.
@ -103,28 +106,35 @@ export type RemovedTypeTagType = BattlerTagType.DOUBLE_SHOCKED | BattlerTagType.
export type HighestStatBoostTagType = export type HighestStatBoostTagType =
| BattlerTagType.QUARK_DRIVE // formatting | BattlerTagType.QUARK_DRIVE // formatting
| BattlerTagType.PROTOSYNTHESIS; | BattlerTagType.PROTOSYNTHESIS;
/**
* Subset of {@linkcode BattlerTagType}s that are able to persist between turns and should therefore be serialized
*/
export type SerializableBattlerTagType = keyof {
[K in keyof BattlerTagTypeMap as BattlerTagTypeMap[K] extends SerializableBattlerTag
? K
: never]: BattlerTagTypeMap[K];
};
/** /**
* Subset of {@linkcode BattlerTagType}s that are not able to persist across waves and should therefore not be serialized * Subset of {@linkcode BattlerTagType}s that are able to persist between turns, and should therefore be serialized.
*/
export type SerializableBattlerTagType = InferKeys<BattlerTagTypeMap, SerializableBattlerTag>;
/**
* Subset of {@linkcode BattlerTagType}s that are **not** able to persist between turns,
* and should therefore not be serialized in {@linkcode SessionSaveData}.
*/ */
export type NonSerializableBattlerTagType = Exclude<BattlerTagType, SerializableBattlerTagType>; export type NonSerializableBattlerTagType = Exclude<BattlerTagType, SerializableBattlerTagType>;
/** /**
* Type-safe representation of an arbitrary, serialized Battler Tag * Utility type containing all entries of {@linkcode BattlerTagTypeMap} corresponding to serializable tags.
*/ */
export type BattlerTagTypeData = Parameters< type SerializableBattlerTagTypeMap = Pick<BattlerTagTypeMap, SerializableBattlerTagType>;
BattlerTagTypeMap[keyof {
[K in keyof BattlerTagTypeMap as K extends SerializableBattlerTagType ? K : never]: BattlerTagTypeMap[K]; /**
}]["loadTag"] * Type mapping all `BattlerTag`s to type-safe representations of their serialized forms.
>[0]; * @interface
*/
export type BattlerTagDataMap = {
[k in keyof SerializableBattlerTagTypeMap]: Parameters<SerializableBattlerTagTypeMap[k]["loadTag"]>[0];
};
/**
* Type-safe representation of an arbitrary, serialized `BattlerTag`.
*/
export type BattlerTagData = ObjectValues<BattlerTagDataMap>;
/** /**
* Dummy, typescript-only declaration to ensure that * Dummy, typescript-only declaration to ensure that

View File

@ -36,15 +36,18 @@ export type Mutable<T> = {
/** /**
* Type helper to obtain the keys associated with a given value inside an object. * Type helper to obtain the keys associated with a given value inside an object.
* Acts similar to {@linkcode Pick}, except checking the object's values instead of its keys.
* @typeParam O - The type of the object * @typeParam O - The type of the object
* @typeParam V - The type of one of O's values * @typeParam V - The type of one of O's values.
*/ */
export type InferKeys<O extends object, V extends ObjectValues<O>> = { export type InferKeys<O extends object, V> = V extends ObjectValues<O>
[K in keyof O]: O[K] extends V ? K : never; ? {
}[keyof O]; [K in keyof O]: O[K] extends V ? K : never;
}[keyof O]
: never;
/** /**
* Utility type to obtain the values of a given object. \ * Utility type to obtain a union of the values of a given object. \
* Functions similar to `keyof E`, except producing the values instead of the keys. * Functions similar to `keyof E`, except producing the values instead of the keys.
* @remarks * @remarks
* This can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members. * This can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members.

View File

@ -1561,9 +1561,9 @@ export class BattleScene extends SceneBase {
return 0; return 0;
} }
const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes( const isEggPhase =
this.phaseManager.getCurrentPhase()?.phaseName ?? "", this.phaseManager.getCurrentPhase().is("EggLapsePhase") ||
); this.phaseManager.getCurrentPhase().is("EggHatchPhase");
if ( if (
// Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros. // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros.

View File

@ -23,7 +23,7 @@ import type { Arena } from "#field/arena";
import type { Pokemon } from "#field/pokemon"; import type { Pokemon } from "#field/pokemon";
import type { import type {
ArenaScreenTagType, ArenaScreenTagType,
ArenaTagTypeData, ArenaTagData,
EntryHazardTagType, EntryHazardTagType,
RoomArenaTagType, RoomArenaTagType,
SerializableArenaTagType, SerializableArenaTagType,
@ -471,7 +471,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => {
const move = allMoves[moveId]; const move = allMoves[moveId];
const effectPhase = globalScene.phaseManager.getCurrentPhase(); const effectPhase = globalScene.phaseManager.getCurrentPhase();
if (effectPhase?.is("MoveEffectPhase")) { if (effectPhase.is("MoveEffectPhase")) {
const attacker = effectPhase.getUserPokemon(); const attacker = effectPhase.getUserPokemon();
if (attacker) { if (attacker) {
return move.getPriority(attacker) > 0; return move.getPriority(attacker) > 0;
@ -1663,7 +1663,7 @@ export function getArenaTag(
* @param source - An arena tag * @param source - An arena tag
* @returns The valid arena tag * @returns The valid arena tag
*/ */
export function loadArenaTag(source: ArenaTag | ArenaTagTypeData | { tagType: ArenaTagType.NONE }): ArenaTag { export function loadArenaTag(source: ArenaTag | ArenaTagData | { tagType: ArenaTagType.NONE }): ArenaTag {
if (source.tagType === ArenaTagType.NONE) { if (source.tagType === ArenaTagType.NONE) {
return new NoneTag(); return new NoneTag();
} }

View File

@ -34,7 +34,7 @@ import type { StatStageChangeCallback } from "#phases/stat-stage-change-phase";
import i18next from "#plugins/i18n"; import i18next from "#plugins/i18n";
import type { import type {
AbilityBattlerTagType, AbilityBattlerTagType,
BattlerTagTypeData, BattlerTagData,
ContactSetStatusProtectedTagType, ContactSetStatusProtectedTagType,
ContactStatStageChangeProtectedTagType, ContactStatStageChangeProtectedTagType,
CritStageBoostTagType, CritStageBoostTagType,
@ -228,26 +228,27 @@ interface GenericSerializableBattlerTag<T extends BattlerTagType> extends Serial
* Descendants can override {@linkcode isMoveRestricted} to restrict moves that * Descendants can override {@linkcode isMoveRestricted} to restrict moves that
* match a condition. A restricted move gets cancelled before it is used. * match a condition. A restricted move gets cancelled before it is used.
* Players and enemies should not be allowed to select restricted moves. * Players and enemies should not be allowed to select restricted moves.
* @todo Require descendant subclasses to inherit a `PRE_MOVE` lapse type
*/ */
export abstract class MoveRestrictionBattlerTag extends SerializableBattlerTag { export abstract class MoveRestrictionBattlerTag extends SerializableBattlerTag {
public declare readonly tagType: MoveRestrictionBattlerTagType; public declare readonly tagType: MoveRestrictionBattlerTagType;
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) { if (lapseType !== BattlerTagLapseType.PRE_MOVE) {
// Cancel the affected pokemon's selected move return super.lapse(pokemon, lapseType);
const phase = globalScene.phaseManager.getCurrentPhase() as MovePhase;
const move = phase.move;
if (this.isMoveRestricted(move.moveId, pokemon)) {
if (this.interruptedText(pokemon, move.moveId)) {
globalScene.phaseManager.queueMessage(this.interruptedText(pokemon, move.moveId));
}
phase.cancel();
}
return true;
} }
return super.lapse(pokemon, lapseType); // Cancel the affected pokemon's selected move
const phase = globalScene.phaseManager.getCurrentPhase() as MovePhase;
const move = phase.move;
if (this.isMoveRestricted(move.moveId, pokemon)) {
if (this.interruptedText(pokemon, move.moveId)) {
globalScene.phaseManager.queueMessage(this.interruptedText(pokemon, move.moveId));
}
phase.cancel();
}
return true;
} }
/** /**
@ -3842,7 +3843,7 @@ export function getBattlerTag(
* @param source - An object containing the data necessary to reconstruct the BattlerTag. * @param source - An object containing the data necessary to reconstruct the BattlerTag.
* @returns The valid battler tag * @returns The valid battler tag
*/ */
export function loadBattlerTag(source: BattlerTag | BattlerTagTypeData): BattlerTag { export function loadBattlerTag(source: BattlerTag | BattlerTagData): BattlerTag {
// TODO: Remove this bang by fixing the signature of `getBattlerTag` // TODO: Remove this bang by fixing the signature of `getBattlerTag`
// to allow undefined sourceIds and sourceMoves (with appropriate fallback for tags that require it) // to allow undefined sourceIds and sourceMoves (with appropriate fallback for tags that require it)
const tag = getBattlerTag(source.tagType, source.turnCount, source.sourceMove!, source.sourceId!); const tag = getBattlerTag(source.tagType, source.turnCount, source.sourceMove!, source.sourceId!);

View File

@ -8903,7 +8903,9 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, true) .attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, true)
.target(MoveTarget.USER_SIDE), .target(MoveTarget.USER_SIDE),
new SelfStatusMove(MoveId.FOCUS_ENERGY, PokemonType.NORMAL, -1, 30, -1, 0, 1) new SelfStatusMove(MoveId.FOCUS_ENERGY, PokemonType.NORMAL, -1, 30, -1, 0, 1)
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, true), .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, true)
// TODO: Remove once dragon cheer & focus energy are merged into 1 tag
.condition((_user, target) => !target.getTag(BattlerTagType.DRAGON_CHEER)),
new AttackMove(MoveId.BIDE, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, 1, 1) new AttackMove(MoveId.BIDE, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, 1, 1)
.target(MoveTarget.USER) .target(MoveTarget.USER)
.unimplemented(), .unimplemented(),
@ -9430,7 +9432,9 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND) .attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND)
.ignoresSubstitute() .ignoresSubstitute()
.target(MoveTarget.NEAR_ALLY) .target(MoveTarget.NEAR_ALLY)
.condition(failIfSingleBattle), .condition(failIfSingleBattle)
// should stack multiplicatively if used multiple times in 1 turn
.edgeCase(),
new StatusMove(MoveId.TRICK, PokemonType.PSYCHIC, 100, 10, -1, 0, 3) new StatusMove(MoveId.TRICK, PokemonType.PSYCHIC, 100, 10, -1, 0, 3)
.unimplemented(), .unimplemented(),
new StatusMove(MoveId.ROLE_PLAY, PokemonType.PSYCHIC, -1, 10, -1, 0, 3) new StatusMove(MoveId.ROLE_PLAY, PokemonType.PSYCHIC, -1, 10, -1, 0, 3)
@ -11599,6 +11603,8 @@ export function initMoves() {
.attr(OpponentHighHpPowerAttr, 100), .attr(OpponentHighHpPowerAttr, 100),
new StatusMove(MoveId.DRAGON_CHEER, PokemonType.DRAGON, -1, 15, -1, 0, 9) new StatusMove(MoveId.DRAGON_CHEER, PokemonType.DRAGON, -1, 15, -1, 0, 9)
.attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true) .attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
// TODO: Remove once dragon cheer & focus energy are merged into 1 tag
.condition((_user, target) => !target.getTag(BattlerTagType.CRIT_BOOST))
.target(MoveTarget.NEAR_ALLY), .target(MoveTarget.NEAR_ALLY),
new AttackMove(MoveId.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) new AttackMove(MoveId.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
.attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED) .attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)

View File

@ -1252,7 +1252,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
} }
// During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus"
const currentPhase = globalScene.phaseManager.getCurrentPhase(); const currentPhase = globalScene.phaseManager.getCurrentPhase();
return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this); return !(currentPhase.is("MoveEffectPhase") && currentPhase.getPokemon() === this);
} }
/** If this Pokemon has a Substitute on the field, removes its sprite from the field. */ /** If this Pokemon has a Substitute on the field, removes its sprite from the field. */
@ -4969,7 +4969,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
const currentPhase = globalScene.phaseManager.getCurrentPhase(); const currentPhase = globalScene.phaseManager.getCurrentPhase();
if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) { if (currentPhase.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) {
this.turnData.hitCount = 1; this.turnData.hitCount = 1;
this.turnData.hitsLeft = 1; this.turnData.hitsLeft = 1;
} }

View File

@ -236,7 +236,7 @@ export class PhaseManager {
/** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */ /** Parallel array to {@linkcode dynamicPhaseQueues} - matches phase types to their queues */
private dynamicPhaseTypes: Constructor<Phase>[]; private dynamicPhaseTypes: Constructor<Phase>[];
private currentPhase: Phase | null = null; private currentPhase: Phase;
private standbyPhase: Phase | null = null; private standbyPhase: Phase | null = null;
constructor() { constructor() {
@ -260,7 +260,9 @@ export class PhaseManager {
} }
/* Phase Functions */ /* Phase Functions */
getCurrentPhase(): Phase | null {
/** @returns The currently running {@linkcode Phase}. */
getCurrentPhase(): Phase {
return this.currentPhase; return this.currentPhase;
} }
@ -370,20 +372,28 @@ export class PhaseManager {
unactivatedConditionalPhases.push([condition, phase]); unactivatedConditionalPhases.push([condition, phase]);
} }
} }
this.conditionalQueue = unactivatedConditionalPhases; this.conditionalQueue = unactivatedConditionalPhases;
// If no phases are left, unshift phases to start a new turn.
if (!this.phaseQueue.length) { if (!this.phaseQueue.length) {
this.populatePhaseQueue(); this.populatePhaseQueue();
// Clear the conditionalQueue if there are no phases left in the phaseQueue // Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = []; this.conditionalQueue = [];
} }
this.currentPhase = this.phaseQueue.shift() ?? null; // Bang is justified as `populatePhaseQueue` ensures we always have _something_ in the queue at all times
this.currentPhase = this.phaseQueue.shift()!;
if (this.currentPhase) { this.startCurrentPhase();
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); }
this.currentPhase.start();
} /**
* Helper method to start and log the current phase.
*/
private startCurrentPhase(): void {
console.log(`%cStart Phase ${this.currentPhase.phaseName}`, "color:green;");
this.currentPhase.start();
} }
overridePhase(phase: Phase): boolean { overridePhase(phase: Phase): boolean {
@ -393,8 +403,7 @@ export class PhaseManager {
this.standbyPhase = this.currentPhase; this.standbyPhase = this.currentPhase;
this.currentPhase = phase; this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;"); this.startCurrentPhase();
phase.start();
return true; return true;
} }

View File

@ -5,14 +5,14 @@ import { Terrain } from "#data/terrain";
import { Weather } from "#data/weather"; import { Weather } from "#data/weather";
import type { BiomeId } from "#enums/biome-id"; import type { BiomeId } from "#enums/biome-id";
import { Arena } from "#field/arena"; import { Arena } from "#field/arena";
import type { ArenaTagTypeData } from "#types/arena-tags"; import type { ArenaTagData } from "#types/arena-tags";
import type { NonFunctionProperties } from "#types/type-helpers"; import type { NonFunctionProperties } from "#types/type-helpers";
export interface SerializedArenaData { export interface SerializedArenaData {
biome: BiomeId; biome: BiomeId;
weather: NonFunctionProperties<Weather> | null; weather: NonFunctionProperties<Weather> | null;
terrain: NonFunctionProperties<Terrain> | null; terrain: NonFunctionProperties<Terrain> | null;
tags?: ArenaTagTypeData[]; tags?: ArenaTagData[];
positionalTags: SerializedPositionalTag[]; positionalTags: SerializedPositionalTag[];
playerTerasUsed?: number; playerTerasUsed?: number;
} }
@ -31,7 +31,7 @@ export class ArenaData {
// is not yet an instance of `ArenaTag` // is not yet an instance of `ArenaTag`
this.tags = this.tags =
source.tags source.tags
?.map((t: ArenaTag | ArenaTagTypeData) => loadArenaTag(t)) ?.map((t: ArenaTag | ArenaTagData) => loadArenaTag(t))
?.filter((tag): tag is SerializableArenaTag => tag instanceof SerializableArenaTag) ?? []; ?.filter((tag): tag is SerializableArenaTag => tag instanceof SerializableArenaTag) ?? [];
this.playerTerasUsed = source.playerTerasUsed ?? 0; this.playerTerasUsed = source.playerTerasUsed ?? 0;

View File

@ -383,14 +383,14 @@ export class GameChallengesUiHandler extends UiHandler {
this.updateChallengeArrows(this.startCursor.visible); this.updateChallengeArrows(this.startCursor.visible);
} else { } else {
globalScene.phaseManager.toTitleScreen(); globalScene.phaseManager.toTitleScreen();
globalScene.phaseManager.getCurrentPhase()?.end(); globalScene.phaseManager.getCurrentPhase().end();
} }
success = true; success = true;
} else if (button === Button.SUBMIT || button === Button.ACTION) { } else if (button === Button.SUBMIT || button === Button.ACTION) {
if (this.hasSelectedChallenge) { if (this.hasSelectedChallenge) {
if (this.startCursor.visible) { if (this.startCursor.visible) {
globalScene.phaseManager.unshiftNew("SelectStarterPhase"); globalScene.phaseManager.unshiftNew("SelectStarterPhase");
globalScene.phaseManager.getCurrentPhase()?.end(); globalScene.phaseManager.getCurrentPhase().end();
} else { } else {
this.startCursor.setVisible(true); this.startCursor.setVisible(true);
this.cursorObj?.setVisible(false); this.cursorObj?.setVisible(false);

View File

@ -45,7 +45,7 @@ export class EggHatchSceneHandler extends UiHandler {
processInput(button: Button): boolean { processInput(button: Button): boolean {
if (button === Button.ACTION || button === Button.CANCEL) { if (button === Button.ACTION || button === Button.CANCEL) {
const phase = globalScene.phaseManager.getCurrentPhase(); const phase = globalScene.phaseManager.getCurrentPhase();
if (phase?.is("EggHatchPhase") && phase.trySkip()) { if (phase.is("EggHatchPhase") && phase.trySkip()) {
return true; return true;
} }
} }

View File

@ -222,7 +222,7 @@ export class EggSummaryUiHandler extends MessageUiHandler {
if (button === Button.CANCEL) { if (button === Button.CANCEL) {
if (!this.blockExit) { if (!this.blockExit) {
const phase = globalScene.phaseManager.getCurrentPhase(); const phase = globalScene.phaseManager.getCurrentPhase();
if (phase?.is("EggSummaryPhase")) { if (phase.is("EggSummaryPhase")) {
phase.end(); phase.end();
} }
success = true; success = true;

View File

@ -126,7 +126,7 @@ export class MenuUiHandler extends MessageUiHandler {
const ui = this.getUi(); const ui = this.getUi();
this.excludedMenus = () => [ this.excludedMenus = () => [
{ {
condition: !!globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase"), condition: globalScene.phaseManager.getCurrentPhase().is("SelectModifierPhase"),
options: [MenuOptions.EGG_GACHA], options: [MenuOptions.EGG_GACHA],
}, },
{ condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, { condition: bypassLogin, options: [MenuOptions.LOG_OUT] },

View File

@ -862,7 +862,7 @@ export class PartyUiHandler extends MessageUiHandler {
// TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check? // TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check?
if ( if (
option >= PartyOption.FORM_CHANGE_ITEM && option >= PartyOption.FORM_CHANGE_ITEM &&
globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase") && globalScene.phaseManager.getCurrentPhase().is("SelectModifierPhase") &&
this.partyUiMode === PartyUiMode.CHECK this.partyUiMode === PartyUiMode.CHECK
) { ) {
const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon);
@ -1556,7 +1556,7 @@ export class PartyUiHandler extends MessageUiHandler {
break; break;
case PartyUiMode.CHECK: case PartyUiMode.CHECK:
this.addCommonOptions(pokemon); this.addCommonOptions(pokemon);
if (globalScene.phaseManager.getCurrentPhase()?.is("SelectModifierPhase")) { if (globalScene.phaseManager.getCurrentPhase().is("SelectModifierPhase")) {
const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon);
for (let i = 0; i < formChangeItemModifiers.length; i++) { for (let i = 0; i < formChangeItemModifiers.length; i++) {
this.options.push(PartyOption.FORM_CHANGE_ITEM + i); this.options.push(PartyOption.FORM_CHANGE_ITEM + i);

View File

@ -710,7 +710,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
show(args: any[]): boolean { show(args: any[]): boolean {
// Allow the use of candies if we are in one of the whitelisted phases // Allow the use of candies if we are in one of the whitelisted phases
this.canUseCandies = ["TitlePhase", "SelectStarterPhase", "CommandPhase"].includes( this.canUseCandies = ["TitlePhase", "SelectStarterPhase", "CommandPhase"].includes(
globalScene.phaseManager.getCurrentPhase()?.phaseName ?? "", globalScene.phaseManager.getCurrentPhase().phaseName,
); );
if (args.length >= 1 && args[0] === "refresh") { if (args.length >= 1 && args[0] === "refresh") {

View File

@ -328,7 +328,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
private teraLabel: Phaser.GameObjects.Text; private teraLabel: Phaser.GameObjects.Text;
private goFilterLabel: Phaser.GameObjects.Text; private goFilterLabel: Phaser.GameObjects.Text;
/** Group holding the UI elements appearing in the instructionsContainer */ /** Group holding the UI elements appearing in the instructionsContainer */
/* TODO: Uncomment this once our testing infra supports mocks of `Phaser.GameObject.Group` /* TODO: Uncomment this once our testing infra supports mocks of `Phaser.GameObject.Group`
private instructionElemGroup: Phaser.GameObjects.Group; private instructionElemGroup: Phaser.GameObjects.Group;
*/ */
@ -4419,7 +4419,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
globalScene.phaseManager.pushNew("EncounterPhase"); globalScene.phaseManager.pushNew("EncounterPhase");
} }
this.clearText(); this.clearText();
globalScene.phaseManager.getCurrentPhase()?.end(); globalScene.phaseManager.getCurrentPhase().end();
}, },
cancel, cancel,
null, null,

View File

@ -1,8 +1,10 @@
import "vitest"; import "vitest";
import type { TerrainType } from "#app/data/terrain"; import type { Phase } from "#app/phase";
import type Overrides from "#app/overrides"; import type Overrides from "#app/overrides";
import type { ArenaTag } from "#data/arena-tag"; import type { ArenaTag } from "#data/arena-tag";
import type { TerrainType } from "#data/terrain";
import type { BattlerTag } from "#data/battler-tags";
import type { PositionalTag } from "#data/positional-tags/positional-tag"; import type { PositionalTag } from "#data/positional-tags/positional-tag";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
import type { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTagSide } from "#enums/arena-tag-side";
@ -22,10 +24,12 @@ import type { toHaveEffectiveStatOptions } from "#test/test-utils/matchers/to-ha
import type { toHavePositionalTagOptions } from "#test/test-utils/matchers/to-have-positional-tag"; import type { toHavePositionalTagOptions } from "#test/test-utils/matchers/to-have-positional-tag";
import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect"; import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect";
import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types"; import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types";
import type { PhaseString } from "#types/phase-types";
import type { TurnMove } from "#types/turn-move"; import type { TurnMove } from "#types/turn-move";
import type { AtLeastOne } from "#types/type-helpers"; import type { AtLeastOne } from "#types/type-helpers";
import type { toDmgValue } from "utils/common"; import type { toDmgValue } from "utils/common";
import type { expect } from "vitest"; import type { expect } from "vitest";
import { toHaveBattlerTagOptions } from "#test/test-utils/matchers/to-have-battler-tag";
declare module "vitest" { declare module "vitest" {
interface Assertion<T> { interface Assertion<T> {
@ -40,6 +44,12 @@ declare module "vitest" {
*/ */
toEqualArrayUnsorted(expected: T[]): void; toEqualArrayUnsorted(expected: T[]): void;
/**
* Check if the currently-running {@linkcode Phase} is of the given type.
* @param expectedPhase - The expected {@linkcode PhaseString}
*/
toBeAtPhase(expectedPhase: PhaseString): void;
// #region Arena Matchers // #region Arena Matchers
/** /**
@ -125,10 +135,15 @@ declare module "vitest" {
toHaveStatStage(stat: BattleStat, expectedStage: number): void; toHaveStatStage(stat: BattleStat, expectedStage: number): void;
/** /**
* Check whether a {@linkcode Pokemon} has a specific {@linkcode BattlerTagType}. * Check whether a {@linkcode Pokemon} has the given {@linkcode BattlerTag}.
* @param expectedBattlerTagType - The expected {@linkcode BattlerTagType} * @param expectedTag - A partially-filled {@linkcode BattlerTag} containing the desired properties
*/ */
toHaveBattlerTag(expectedBattlerTagType: BattlerTagType): void; toHaveBattlerTag<B extends BattlerTagType>(expectedTag: toHaveBattlerTagOptions<B>): void;
/**
* Check whether a {@linkcode Pokemon} has the given {@linkcode BattlerTag}.
* @param expectedType - The expected {@linkcode BattlerTagType}
*/
toHaveBattlerTag(expectedType: BattlerTagType): void;
/** /**
* Check whether a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}. * Check whether a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}.

View File

@ -196,7 +196,7 @@ describe("Abilities - Disguise", () => {
game.move.select(MoveId.SHADOW_SNEAK); game.move.select(MoveId.SHADOW_SNEAK);
await game.toNextWave(); await game.toNextWave();
expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
expect(game.scene.currentBattle.waveIndex).toBe(2); expect(game.scene.currentBattle.waveIndex).toBe(2);
}); });

View File

@ -3,7 +3,6 @@ import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { TurnEndPhase } from "#phases/turn-end-phase";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -24,48 +23,35 @@ describe("Abilities - Pastel Veil", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override.battleStyle("double").enemyAbility(AbilityId.BALL_FETCH).enemySpecies(SpeciesId.TOXAPEX);
.battleStyle("double")
.moveset([MoveId.TOXIC_THREAD, MoveId.SPLASH])
.enemyAbility(AbilityId.BALL_FETCH)
.enemySpecies(SpeciesId.SUNKERN)
.enemyMoveset(MoveId.SPLASH);
}); });
it("prevents the user and its allies from being afflicted by poison", async () => { it("should prevent the user and its allies from being poisoned", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.GALAR_PONYTA]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.GALAR_PONYTA]);
const ponyta = game.scene.getPlayerField()[1]; const [magikarp, ponyta] = game.scene.getPlayerField();
const magikarp = game.scene.getPlayerField()[0]; game.field.mockAbility(ponyta, AbilityId.PASTEL_VEIL);
ponyta.abilityIndex = 1;
expect(ponyta.hasAbility(AbilityId.PASTEL_VEIL)).toBe(true); game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.move.forceEnemyMove(MoveId.TOXIC, BattlerIndex.PLAYER);
await game.move.forceEnemyMove(MoveId.TOXIC, BattlerIndex.PLAYER_2);
await game.toEndOfTurn();
game.move.select(MoveId.SPLASH); expect(magikarp).toHaveStatusEffect(StatusEffect.NONE);
game.move.select(MoveId.TOXIC_THREAD, 1, BattlerIndex.PLAYER); expect(ponyta).toHaveStatusEffect(StatusEffect.NONE);
await game.phaseInterceptor.to(TurnEndPhase);
expect(magikarp.status?.effect).toBeUndefined();
}); });
it("it heals the poisoned status condition of allies if user is sent out into battle", async () => { it("should cure allies' poison if user is sent out into battle", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS, SpeciesId.GALAR_PONYTA]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS, SpeciesId.GALAR_PONYTA]);
const ponyta = game.scene.getPlayerParty()[2]; const [magikarp, , ponyta] = game.scene.getPlayerParty();
const magikarp = game.scene.getPlayerField()[0]; game.field.mockAbility(ponyta, AbilityId.PASTEL_VEIL);
ponyta.abilityIndex = 1;
expect(ponyta.hasAbility(AbilityId.PASTEL_VEIL)).toBe(true); magikarp.doSetStatus(StatusEffect.POISON);
game.move.select(MoveId.SPLASH); game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.select(MoveId.TOXIC_THREAD, 1, BattlerIndex.PLAYER);
await game.phaseInterceptor.to(TurnEndPhase);
expect(magikarp.status?.effect).toBe(StatusEffect.POISON);
game.move.select(MoveId.SPLASH);
game.doSwitchPokemon(2); game.doSwitchPokemon(2);
await game.phaseInterceptor.to(TurnEndPhase); await game.toEndOfTurn();
expect(magikarp.status?.effect).toBeUndefined(); expect(magikarp).toHaveStatusEffect(StatusEffect.NONE);
}); });
}); });

View File

@ -2,8 +2,9 @@ import { AbilityId } from "#enums/ability-id";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MoveResult } from "#enums/move-result";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TurnEndPhase } from "#phases/turn-end-phase"; import { StatusEffect } from "#enums/status-effect";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -26,62 +27,108 @@ describe("Abilities - Sweet Veil", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("double") .battleStyle("double")
.moveset([MoveId.SPLASH, MoveId.REST, MoveId.YAWN]) .ability(AbilityId.BALL_FETCH)
.enemySpecies(SpeciesId.MAGIKARP) .enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.POWDER); .enemyMoveset(MoveId.SPLASH);
}); });
it("prevents the user and its allies from falling asleep", async () => { function expectNoStatus() {
game.scene.getPlayerField().forEach(p => {
expect.soft(p).toHaveStatusEffect(StatusEffect.NONE);
});
}
it("should prevent the user and its allies from falling asleep", async () => {
await game.classicMode.startBattle([SpeciesId.SWIRLIX, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.SWIRLIX, SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPLASH); game.field.mockAbility(game.field.getPlayerPokemon(), AbilityId.SWEET_VEIL);
game.move.select(MoveId.SPLASH, 1); game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.move.forceEnemyMove(MoveId.SPORE, BattlerIndex.PLAYER);
await game.move.forceEnemyMove(MoveId.SPORE, BattlerIndex.PLAYER_2);
await game.toEndOfTurn();
await game.phaseInterceptor.to(TurnEndPhase); expectNoStatus();
expect(game.scene.getPlayerField().every(p => p.status?.effect)).toBe(false);
}); });
it("causes Rest to fail when used by the user or its allies", async () => { it("should cause Rest to fail when used by the user or its allies", async () => {
game.override.enemyMoveset(MoveId.SPLASH);
await game.classicMode.startBattle([SpeciesId.SWIRLIX, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.SWIRLIX, SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPLASH); const [swirlix, magikarp] = game.scene.getPlayerField();
game.move.select(MoveId.REST, 1); game.field.mockAbility(swirlix, AbilityId.SWEET_VEIL);
swirlix.hp = 1;
magikarp.hp = 1;
await game.phaseInterceptor.to(TurnEndPhase); game.move.use(MoveId.REST, BattlerIndex.PLAYER);
game.move.use(MoveId.REST, BattlerIndex.PLAYER_2);
await game.toEndOfTurn();
expect(game.scene.getPlayerField().every(p => p.status?.effect)).toBe(false); expectNoStatus();
expect(swirlix).toHaveUsedMove({ move: MoveId.REST, result: MoveResult.FAIL });
expect(magikarp).toHaveUsedMove({ move: MoveId.REST, result: MoveResult.FAIL });
}); });
it("causes Yawn to fail if used on the user or its allies", async () => { it("should cause Yawn to fail if used on the user or its allies", async () => {
game.override.enemyMoveset(MoveId.YAWN);
await game.classicMode.startBattle([SpeciesId.SWIRLIX, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.SWIRLIX, SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPLASH); const [shuckle, swirlix] = game.scene.getPlayerField();
game.move.select(MoveId.SPLASH, 1); game.field.mockAbility(swirlix, AbilityId.SWEET_VEIL);
await game.phaseInterceptor.to(TurnEndPhase); game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.move.forceEnemyMove(MoveId.YAWN, BattlerIndex.PLAYER);
await game.move.forceEnemyMove(MoveId.YAWN, BattlerIndex.PLAYER_2);
await game.toEndOfTurn();
expect(game.scene.getPlayerField().every(p => !!p.getTag(BattlerTagType.DROWSY))).toBe(false); expect(shuckle).not.toHaveBattlerTag(BattlerTagType.DROWSY);
expect(swirlix).not.toHaveBattlerTag(BattlerTagType.DROWSY);
// TODO: This dooesn't work ATM
/*
const [karp1, karp2] = game.scene.getEnemyField();
expect(karp1).toHaveUsedMove({move: MoveId.YAWN, result: MoveResult.FAIL});
expect(karp2).toHaveUsedMove({move: MoveId.YAWN, result: MoveResult.FAIL});
*/
}); });
it("prevents the user and its allies already drowsy due to Yawn from falling asleep.", async () => { it("should NOT cure allies' sleep status if user is sent out into battle", async () => {
game.override.enemySpecies(SpeciesId.PIKACHU).enemyLevel(5).startingLevel(5).enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS, SpeciesId.SWIRLIX]);
await game.classicMode.startBattle([SpeciesId.SHUCKLE, SpeciesId.SHUCKLE, SpeciesId.SWIRLIX]); const [magikarp, , swirlix] = game.scene.getPlayerParty();
game.field.mockAbility(swirlix, AbilityId.PASTEL_VEIL);
game.move.select(MoveId.SPLASH); game.move.use(MoveId.SPLASH);
game.move.select(MoveId.YAWN, 1, BattlerIndex.PLAYER); game.move.use(MoveId.SPORE, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER);
await game.toNextTurn();
await game.phaseInterceptor.to("BerryPhase"); expect(magikarp).toHaveStatusEffect(StatusEffect.SLEEP);
expect(game.scene.getPlayerField().some(p => !!p.getTag(BattlerTagType.DROWSY))).toBe(true); game.move.use(MoveId.SPLASH);
game.move.select(MoveId.SPLASH);
game.doSwitchPokemon(2); game.doSwitchPokemon(2);
await game.toEndOfTurn();
expect(game.scene.getPlayerField().every(p => p.status?.effect)).toBe(false); expect(magikarp).toHaveStatusEffect(StatusEffect.SLEEP);
});
it("should prevent an already-drowsy user or ally from falling asleep", async () => {
await game.classicMode.startBattle([SpeciesId.SHUCKLE, SpeciesId.SWIRLIX]);
// Add yawn before granting ability
const [shuckle, swirlix] = game.scene.getPlayerField();
shuckle.addTag(BattlerTagType.DROWSY, 1);
swirlix.addTag(BattlerTagType.DROWSY, 1);
game.field.mockAbility(shuckle, AbilityId.SWEET_VEIL);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.toNextTurn();
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.toNextTurn();
expect(shuckle).not.toHaveBattlerTag(BattlerTagType.DROWSY);
expect(swirlix).not.toHaveBattlerTag(BattlerTagType.DROWSY);
expectNoStatus();
}); });
}); });

View File

@ -36,62 +36,62 @@ describe("Test Battle Phase", () => {
game.override.battleStyle("single").startingWave(10); game.override.battleStyle("single").startingWave(10);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 2vs2 boss", async () => { it("startBattle 2vs2 boss", async () => {
game.override.battleStyle("double").startingWave(10); game.override.battleStyle("double").startingWave(10);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 2vs2 trainer", async () => { it("startBattle 2vs2 trainer", async () => {
game.override.battleStyle("double").startingWave(5); game.override.battleStyle("double").startingWave(5);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 2vs1 trainer", async () => { it("startBattle 2vs1 trainer", async () => {
game.override.battleStyle("single").startingWave(5); game.override.battleStyle("single").startingWave(5);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 2vs1 rival", async () => { it("startBattle 2vs1 rival", async () => {
game.override.battleStyle("single").startingWave(8); game.override.battleStyle("single").startingWave(8);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 2vs2 rival", async () => { it("startBattle 2vs2 rival", async () => {
game.override.battleStyle("double").startingWave(8); game.override.battleStyle("double").startingWave(8);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 1vs1 trainer", async () => { it("startBattle 1vs1 trainer", async () => {
game.override.battleStyle("single").startingWave(5); game.override.battleStyle("single").startingWave(5);
await game.classicMode.startBattle([SpeciesId.BLASTOISE]); await game.classicMode.startBattle([SpeciesId.BLASTOISE]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 2vs2 trainer", async () => { it("startBattle 2vs2 trainer", async () => {
game.override.battleStyle("double").startingWave(5); game.override.battleStyle("double").startingWave(5);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
it("startBattle 4vs2 trainer", async () => { it("startBattle 4vs2 trainer", async () => {
game.override.battleStyle("double").startingWave(5); game.override.battleStyle("double").startingWave(5);
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD, SpeciesId.DARKRAI, SpeciesId.GABITE]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD, SpeciesId.DARKRAI, SpeciesId.GABITE]);
expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND);
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
}); });
}); });

View File

@ -1,3 +1,4 @@
import { toBeAtPhase } from "#test/test-utils/matchers/to-be-at-phase";
import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted"; import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted";
import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied"; import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied";
import { toHaveArenaTag } from "#test/test-utils/matchers/to-have-arena-tag"; import { toHaveArenaTag } from "#test/test-utils/matchers/to-have-arena-tag";
@ -24,6 +25,7 @@ import { expect } from "vitest";
expect.extend({ expect.extend({
toEqualArrayUnsorted, toEqualArrayUnsorted,
toBeAtPhase,
toHaveWeather, toHaveWeather,
toHaveTerrain, toHaveTerrain,
toHaveArenaTag, toHaveArenaTag,

View File

@ -1,15 +1,18 @@
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MoveResult } from "#enums/move-result";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Dragon Cheer", () => { describe("Move - Dragon Cheer", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
@ -24,75 +27,81 @@ describe("Moves - Dragon Cheer", () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.battleStyle("double") .battleStyle("double")
.ability(AbilityId.BALL_FETCH)
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH) .enemyMoveset(MoveId.SPLASH)
.enemyLevel(20) .enemyLevel(20);
.moveset([MoveId.DRAGON_CHEER, MoveId.TACKLE, MoveId.SPLASH]);
}); });
it("increases the user's allies' critical hit ratio by one stage", async () => { it("should increase non-Dragon type allies' crit ratios by 1 stage", async () => {
await game.classicMode.startBattle([SpeciesId.DRAGONAIR, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.DRAGONAIR, SpeciesId.MAGIKARP]);
const enemy = game.scene.getEnemyField()[0]; const enemy = game.field.getEnemyPokemon();
vi.spyOn(enemy, "getCritStage"); vi.spyOn(enemy, "getCritStage");
game.move.select(MoveId.DRAGON_CHEER, 0); game.move.use(MoveId.DRAGON_CHEER, BattlerIndex.PLAYER);
game.move.select(MoveId.TACKLE, 1, BattlerIndex.ENEMY); game.move.use(MoveId.TACKLE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.toEndOfTurn();
// After Tackle const [dragonair, magikarp] = game.scene.getPlayerField();
await game.phaseInterceptor.to("TurnEndPhase"); expect(dragonair).not.toHaveBattlerTag(BattlerTagType.DRAGON_CHEER);
expect(magikarp).toHaveBattlerTag({ tagType: BattlerTagType.DRAGON_CHEER, critStages: 1 });
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
}); });
it("increases the user's Dragon-type allies' critical hit ratio by two stages", async () => { it("should increase Dragon-type allies' crit ratios by 2 stages", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.DRAGONAIR]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.DRAGONAIR]);
const enemy = game.scene.getEnemyField()[0]; const enemy = game.field.getEnemyPokemon();
vi.spyOn(enemy, "getCritStage"); vi.spyOn(enemy, "getCritStage");
game.move.select(MoveId.DRAGON_CHEER, 0); game.move.use(MoveId.DRAGON_CHEER, BattlerIndex.PLAYER);
game.move.select(MoveId.TACKLE, 1, BattlerIndex.ENEMY); game.move.use(MoveId.TACKLE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.toEndOfTurn();
// After Tackle const [magikarp, dragonair] = game.scene.getPlayerField();
await game.phaseInterceptor.to("TurnEndPhase"); expect(magikarp).not.toHaveBattlerTag(BattlerTagType.DRAGON_CHEER);
expect(dragonair).toHaveBattlerTag({ tagType: BattlerTagType.DRAGON_CHEER, critStages: 2 });
expect(enemy.getCritStage).toHaveReturnedWith(2); // getCritStage is called on defender expect(enemy.getCritStage).toHaveReturnedWith(2); // getCritStage is called on defender
}); });
it("applies the effect based on the allies' type upon use of the move, and do not change if the allies' type changes later in battle", async () => { it("should maintain crit boost amount even if user's type is changed", async () => {
await game.classicMode.startBattle([SpeciesId.DRAGONAIR, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.DRAGONAIR, SpeciesId.MAGIKARP]);
const magikarp = game.scene.getPlayerField()[1]; // Use Reflect Type to become Dragon-type mid-turn
const enemy = game.scene.getEnemyField()[0]; game.move.use(MoveId.DRAGON_CHEER, BattlerIndex.PLAYER);
game.move.use(MoveId.REFLECT_TYPE, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER);
vi.spyOn(enemy, "getCritStage");
game.move.select(MoveId.DRAGON_CHEER, 0);
game.move.select(MoveId.TACKLE, 1, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
// After Tackle
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
await game.toNextTurn();
// Change Magikarp's type to Dragon
vi.spyOn(magikarp, "getTypes").mockReturnValue([PokemonType.DRAGON]);
expect(magikarp.getTypes()).toEqual([PokemonType.DRAGON]);
game.move.select(MoveId.SPLASH, 0);
game.move.select(MoveId.TACKLE, 1, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to("MoveEndPhase"); await game.phaseInterceptor.to("MoveEndPhase");
expect(enemy.getCritStage).toHaveReturnedWith(1); // getCritStage is called on defender
// Dragon cheer added +1 stages
const magikarp = game.scene.getPlayerField()[1];
expect(magikarp).toHaveBattlerTag({ tagType: BattlerTagType.DRAGON_CHEER, critStages: 1 });
expect(magikarp).toHaveTypes([PokemonType.WATER]);
await game.toEndOfTurn();
// Should be dragon type, but still with a +1 stage boost
expect(magikarp).toHaveTypes([PokemonType.DRAGON]);
expect(magikarp).toHaveBattlerTag({ tagType: BattlerTagType.DRAGON_CHEER, critStages: 1 });
});
it.each([
{ name: "Focus Energy", tagType: BattlerTagType.CRIT_BOOST },
{ name: "Dragon Cheer", tagType: BattlerTagType.DRAGON_CHEER },
])("should fail if $name is already present", async ({ tagType }) => {
await game.classicMode.startBattle([SpeciesId.DRAGONAIR, SpeciesId.MAGIKARP]);
const [dragonair, magikarp] = game.scene.getPlayerField();
magikarp.addTag(tagType);
game.move.use(MoveId.DRAGON_CHEER, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.toEndOfTurn();
expect(dragonair).toHaveUsedMove({ move: MoveId.DRAGON_CHEER, result: MoveResult.FAIL });
expect(magikarp).toHaveBattlerTag(tagType);
}); });
}); });

View File

@ -0,0 +1,69 @@
import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id";
import { MoveResult } from "#enums/move-result";
import { SpeciesId } from "#enums/species-id";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Move - Focus Energy", () => {
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
.ability(AbilityId.BALL_FETCH)
.battleStyle("single")
.criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH)
.startingLevel(100)
.enemyLevel(100);
});
it("should increase the user's crit ratio by 2 stages", async () => {
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
game.move.use(MoveId.FOCUS_ENERGY);
await game.toNextTurn();
const feebas = game.field.getPlayerPokemon();
expect(feebas).toHaveBattlerTag({ tagType: BattlerTagType.CRIT_BOOST, critStages: 2 });
const enemy = game.field.getEnemyPokemon();
vi.spyOn(enemy, "getCritStage");
game.move.use(MoveId.TACKLE);
await game.toEndOfTurn();
expect(enemy.getCritStage).toHaveReturnedWith(2);
});
it.each([
{ name: "Focus Energy", tagType: BattlerTagType.CRIT_BOOST },
{ name: "Dragon Cheer", tagType: BattlerTagType.DRAGON_CHEER },
])("should fail if $name is already present", async ({ tagType }) => {
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
const feebas = game.field.getPlayerPokemon();
feebas.addTag(tagType);
game.move.use(MoveId.FOCUS_ENERGY);
await game.toEndOfTurn();
expect(feebas).toHaveUsedMove({ move: MoveId.FOCUS_ENERGY, result: MoveResult.FAIL });
});
});

View File

@ -212,7 +212,7 @@ describe("Transforming Effects", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextWave(); await game.toNextWave();
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
expect(game.scene.currentBattle.waveIndex).toBe(2); expect(game.scene.currentBattle.waveIndex).toBe(2);
await game.reload.reloadSession(); await game.reload.reloadSession();
@ -242,7 +242,7 @@ describe("Transforming Effects", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextWave(); await game.toNextWave();
expect(game.scene.phaseManager.getCurrentPhase()?.phaseName).toBe("CommandPhase"); expect(game).toBeAtPhase("CommandPhase");
expect(game.scene.currentBattle.waveIndex).toBe(2); expect(game.scene.currentBattle.waveIndex).toBe(2);
expect(player.getSpeciesForm().speciesId).toBe(enemy.getSpeciesForm().speciesId); expect(player.getSpeciesForm().speciesId).toBe(enemy.getSpeciesForm().speciesId);

View File

@ -9,7 +9,6 @@ import { ATrainersTestEncounter } from "#mystery-encounters/a-trainers-test-enco
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { PartyHealPhase } from "#phases/party-heal-phase"; import { PartyHealPhase } from "#phases/party-heal-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
@ -106,7 +105,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect( expect(
@ -131,7 +130,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
const eggsAfter = scene.gameData.eggs; const eggsAfter = scene.gameData.eggs;
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();
@ -179,7 +178,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
const eggsAfter = scene.gameData.eggs; const eggsAfter = scene.gameData.eggs;
expect(eggsAfter).toBeDefined(); expect(eggsAfter).toBeDefined();

View File

@ -10,7 +10,6 @@ import { BerryModifier, PokemonHeldItemModifier } from "#modifiers/modifier";
import { AbsoluteAvariceEncounter } from "#mystery-encounters/absolute-avarice-encounter"; import { AbsoluteAvariceEncounter } from "#mystery-encounters/absolute-avarice-encounter";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
@ -132,7 +131,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(SpeciesId.GREEDENT); expect(enemyField[0].species.speciesId).toBe(SpeciesId.GREEDENT);
const moveset = enemyField[0].moveset.map(m => m.moveId); const moveset = enemyField[0].moveset.map(m => m.moveId);
@ -148,7 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
for (const partyPokemon of scene.getPlayerParty()) { for (const partyPokemon of scene.getPlayerParty()) {
const pokemonId = partyPokemon.id; const pokemonId = partyPokemon.id;

View File

@ -11,8 +11,6 @@ import { BerriesAboundEncounter } from "#mystery-encounters/berries-abound-encou
import * as EncounterDialogueUtils from "#mystery-encounters/encounter-dialogue-utils"; import * as EncounterDialogueUtils from "#mystery-encounters/encounter-dialogue-utils";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -114,7 +112,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
}); });
@ -135,7 +133,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
const berriesAfterCount = berriesAfter.reduce((a, b) => a + b.stackCount, 0); const berriesAfterCount = berriesAfter.reduce((a, b) => a + b.stackCount, 0);
@ -186,7 +184,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
@ -210,7 +208,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
@ -230,8 +228,6 @@ describe("Berries Abound - Mystery Encounter", () => {
}); });
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -12,9 +12,7 @@ import { PokemonMove } from "#moves/pokemon-move";
import { BugTypeSuperfanEncounter } from "#mystery-encounters/bug-type-superfan-encounter"; import { BugTypeSuperfanEncounter } from "#mystery-encounters/bug-type-superfan-encounter";
import * as encounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as encounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
runSelectMysteryEncounterOption, runSelectMysteryEncounterOption,
@ -231,7 +229,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(2); expect(enemyParty.length).toBe(2);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -244,7 +242,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(3); expect(enemyParty.length).toBe(3);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -258,7 +256,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(4); expect(enemyParty.length).toBe(4);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -273,7 +271,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(5); expect(enemyParty.length).toBe(5);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -289,7 +287,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(5); expect(enemyParty.length).toBe(5);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -307,7 +305,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(5); expect(enemyParty.length).toBe(5);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -325,7 +323,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(5); expect(enemyParty.length).toBe(5);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -343,7 +341,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyParty = scene.getEnemyParty(); const enemyParty = scene.getEnemyParty();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyParty.length).toBe(5); expect(enemyParty.length).toBe(5);
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN); expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.BUG_TYPE_SUPERFAN);
expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL); expect(enemyParty[0].species.speciesId).toBe(SpeciesId.BEEDRILL);
@ -365,7 +363,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game, false); await skipBattleRunMysteryEncounterRewardsPhase(game, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterRewardsPhase.name); expect(game).toBeAtPhase("MysteryEncounterRewardsPhase");
game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers
game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => { game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => {
game.endPhase(); game.endPhase();
@ -406,7 +404,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -416,7 +414,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.BUG_TYPE_SUPERFAN, defaultParty);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -435,7 +433,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
]); ]);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -457,7 +455,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
]); ]);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -481,7 +479,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
]); ]);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -542,7 +540,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -557,7 +555,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -22,10 +22,7 @@ import { ClowningAroundEncounter } from "#mystery-encounters/clowning-around-enc
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import { generateModifierType } from "#mystery-encounters/encounter-phase-utils"; import { generateModifierType } from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { PostMysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -171,7 +168,7 @@ describe("Clowning Around - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(2); expect(enemyField.length).toBe(2);
expect(enemyField[0].species.speciesId).toBe(SpeciesId.MR_MIME); expect(enemyField[0].species.speciesId).toBe(SpeciesId.MR_MIME);
expect(enemyField[0].moveset).toEqual([ expect(enemyField[0].moveset).toEqual([
@ -199,9 +196,6 @@ describe("Clowning Around - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.to("SelectModifierPhase");
const abilityToTrain = scene.currentBattle.mysteryEncounter?.misc.ability; const abilityToTrain = scene.currentBattle.mysteryEncounter?.misc.ability;
game.onNextPrompt("PostMysteryEncounterPhase", UiMode.MESSAGE, () => { game.onNextPrompt("PostMysteryEncounterPhase", UiMode.MESSAGE, () => {
@ -215,7 +209,7 @@ describe("Clowning Around - Mystery Encounter", () => {
vi.spyOn(partyUiHandler, "show"); vi.spyOn(partyUiHandler, "show");
game.endPhase(); game.endPhase();
await game.phaseInterceptor.to("PostMysteryEncounterPhase"); await game.phaseInterceptor.to("PostMysteryEncounterPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(PostMysteryEncounterPhase.name); expect(game).toBeAtPhase("PostMysteryEncounterPhase");
// Wait for Yes/No confirmation to appear // Wait for Yes/No confirmation to appear
await vi.waitFor(() => expect(optionSelectUiHandler.show).toHaveBeenCalled()); await vi.waitFor(() => expect(optionSelectUiHandler.show).toHaveBeenCalled());

View File

@ -9,11 +9,9 @@ import { UiMode } from "#enums/ui-mode";
import { DancingLessonsEncounter } from "#mystery-encounters/dancing-lessons-encounter"; import { DancingLessonsEncounter } from "#mystery-encounters/dancing-lessons-encounter";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { LearnMovePhase } from "#phases/learn-move-phase"; import { LearnMovePhase } from "#phases/learn-move-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
runSelectMysteryEncounterOption, runSelectMysteryEncounterOption,
@ -105,7 +103,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(SpeciesId.ORICORIO); expect(enemyField[0].species.speciesId).toBe(SpeciesId.ORICORIO);
expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 0, 0, 0]); expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 0, 0, 0]);
@ -126,7 +124,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -226,7 +224,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);
const partyCountAfter = scene.getPlayerParty().length; const partyCountAfter = scene.getPlayerParty().length;
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -161,7 +161,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -316,7 +316,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -449,7 +449,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -9,7 +9,6 @@ import { DepartmentStoreSaleEncounter } from "#mystery-encounters/department-sto
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
@ -93,7 +92,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only TMs", async () => { it("should have shop with only TMs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -130,7 +129,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Vitamins", async () => { it("should have shop with only Vitamins", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -170,7 +169,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only X Items", async () => { it("should have shop with only X Items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 3); await runMysteryEncounterToEnd(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -210,7 +209,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Pokeballs", async () => { it("should have shop with only Pokeballs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 4); await runMysteryEncounterToEnd(game, 4);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -16,7 +16,6 @@ import { AttackTypeBoosterModifier, PokemonHeldItemModifier } from "#modifiers/m
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import { FieryFalloutEncounter } from "#mystery-encounters/fiery-fallout-encounter"; import { FieryFalloutEncounter } from "#mystery-encounters/fiery-fallout-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { SelectModifierPhase } from "#phases/select-modifier-phase";
@ -161,7 +160,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(2); expect(enemyField.length).toBe(2);
expect(enemyField[0].species.speciesId).toBe(SpeciesId.VOLCARONA); expect(enemyField[0].species.speciesId).toBe(SpeciesId.VOLCARONA);
expect(enemyField[1].species.speciesId).toBe(SpeciesId.VOLCARONA); expect(enemyField[1].species.speciesId).toBe(SpeciesId.VOLCARONA);
@ -177,7 +176,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
const leadPokemonId = scene.getPlayerParty()?.[0].id; const leadPokemonId = scene.getPlayerParty()?.[0].id;
const leadPokemonItems = scene.findModifiers( const leadPokemonItems = scene.findModifiers(
@ -266,7 +265,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runMysteryEncounterToEnd(game, 3); await runMysteryEncounterToEnd(game, 3);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
const leadPokemonItems = scene.getPlayerParty()[0].getHeldItems() as PokemonHeldItemModifier[]; const leadPokemonItems = scene.getPlayerParty()[0].getHeldItems() as PokemonHeldItemModifier[];
const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier);
@ -292,7 +291,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(continueEncounterSpy).not.toHaveBeenCalled(); expect(continueEncounterSpy).not.toHaveBeenCalled();
}); });
}); });

View File

@ -9,9 +9,7 @@ import { UiMode } from "#enums/ui-mode";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import { FightOrFlightEncounter } from "#mystery-encounters/fight-or-flight-encounter"; import { FightOrFlightEncounter } from "#mystery-encounters/fight-or-flight-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
runSelectMysteryEncounterOption, runSelectMysteryEncounterOption,
@ -109,7 +107,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
}); });
@ -122,8 +120,9 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find( const modifierSelectHandler = scene.ui.handlers.find(
@ -165,7 +164,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -182,7 +181,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -14,9 +14,8 @@ import { FunAndGamesEncounter } from "#mystery-encounters/fun-and-games-encounte
import { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase"; import type { CommandPhase } from "#phases/command-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
runSelectMysteryEncounterOption, runSelectMysteryEncounterOption,
@ -131,7 +130,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -143,7 +142,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(game.field.getEnemyPokemon().species.speciesId).toBe(SpeciesId.WOBBUFFET); expect(game.field.getEnemyPokemon().species.speciesId).toBe(SpeciesId.WOBBUFFET);
expect(game.field.getEnemyPokemon().ivs).toEqual([0, 0, 0, 0, 0, 0]); expect(game.field.getEnemyPokemon().ivs).toEqual([0, 0, 0, 0, 0, 0]);
expect(game.field.getEnemyPokemon().nature).toBe(Nature.MILD); expect(game.field.getEnemyPokemon().nature).toBe(Nature.MILD);
@ -165,7 +164,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards // Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
}); });
it("should have no items in rewards if Wubboffet doesn't take enough damage", async () => { it("should have no items in rewards if Wubboffet doesn't take enough damage", async () => {
@ -173,7 +172,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => {
game.endPhase(); game.endPhase();
}); });
@ -184,7 +183,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards // Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -200,7 +199,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => {
game.endPhase(); game.endPhase();
}); });
@ -213,7 +212,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards // Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -230,7 +229,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => {
game.endPhase(); game.endPhase();
}); });
@ -243,7 +242,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards // Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -260,7 +259,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => { game.onNextPrompt("MessagePhase", UiMode.MESSAGE, () => {
game.endPhase(); game.endPhase();
}); });
@ -273,7 +272,7 @@ describe("Fun And Games! - Mystery Encounter", () => {
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
// Rewards // Rewards
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -13,7 +13,6 @@ import { generateModifierType } from "#mystery-encounters/encounter-phase-utils"
import { GlobalTradeSystemEncounter } from "#mystery-encounters/global-trade-system-encounter"; import { GlobalTradeSystemEncounter } from "#mystery-encounters/global-trade-system-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
@ -226,7 +225,7 @@ describe("Global Trade System - Mystery Encounter", () => {
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 }); await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -147,7 +147,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -212,7 +212,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -12,8 +12,6 @@ import { MysteriousChallengersEncounter } from "#mystery-encounters/mysterious-c
import { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { CommandPhase } from "#phases/command-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -152,7 +150,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
}); });
@ -162,7 +160,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -196,7 +194,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
}); });
@ -206,7 +204,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -253,7 +251,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 3, undefined, true); await runMysteryEncounterToEnd(game, 3, undefined, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
}); });
@ -262,8 +260,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 3, undefined, true); await runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); expect(game).toBeAtPhase("SelectModifierPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -247,7 +247,7 @@ describe("Part-Timer - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -119,7 +119,7 @@ describe("Safari Zone - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -8,9 +8,7 @@ import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { TeleportingHijinksEncounter } from "#mystery-encounters/teleporting-hijinks-encounter"; import { TeleportingHijinksEncounter } from "#mystery-encounters/teleporting-hijinks-encounter";
import { CommandPhase } from "#phases/command-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
runSelectMysteryEncounterOption, runSelectMysteryEncounterOption,
@ -157,7 +155,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -167,7 +165,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
}); });
it("should transport to a new area", async () => { it("should transport to a new area", async () => {
@ -229,7 +227,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -239,7 +237,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [SpeciesId.METAGROSS]); await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [SpeciesId.METAGROSS]);
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
}); });
it("should transport to a new area", async () => { it("should transport to a new area", async () => {
@ -300,7 +298,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3, undefined, true); await runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -12,8 +12,6 @@ import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { TheExpertPokemonBreederEncounter } from "#mystery-encounters/the-expert-pokemon-breeder-encounter"; import { TheExpertPokemonBreederEncounter } from "#mystery-encounters/the-expert-pokemon-breeder-encounter";
import { CommandPhase } from "#phases/command-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -157,7 +155,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(successfullyLoaded).toBe(true); expect(successfullyLoaded).toBe(true);
// Check usual battle stuff // Check usual battle stuff
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
expect(scene.getPlayerParty().length).toBe(1); expect(scene.getPlayerParty().length).toBe(1);
@ -175,8 +173,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); expect(game).toBeAtPhase("SelectModifierPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.to("SelectModifierPhase");
const eggsAfter = scene.gameData.eggs; const eggsAfter = scene.gameData.eggs;
const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon1CommonEggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon1CommonEggs;
@ -242,7 +240,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(successfullyLoaded).toBe(true); expect(successfullyLoaded).toBe(true);
// Check usual battle stuff // Check usual battle stuff
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
expect(scene.getPlayerParty().length).toBe(1); expect(scene.getPlayerParty().length).toBe(1);
@ -260,8 +258,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); expect(game).toBeAtPhase("SelectModifierPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.to("SelectModifierPhase");
const eggsAfter = scene.gameData.eggs; const eggsAfter = scene.gameData.eggs;
const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon2CommonEggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon2CommonEggs;
@ -324,7 +322,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(successfullyLoaded).toBe(true); expect(successfullyLoaded).toBe(true);
// Check usual battle stuff // Check usual battle stuff
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
expect(scene.getPlayerParty().length).toBe(1); expect(scene.getPlayerParty().length).toBe(1);
@ -342,8 +340,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3, undefined, true); await runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); expect(game).toBeAtPhase("SelectModifierPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.to("SelectModifierPhase");
const eggsAfter = scene.gameData.eggs; const eggsAfter = scene.gameData.eggs;
const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon3CommonEggs; const commonEggs = scene.currentBattle.mysteryEncounter!.misc.pokemon3CommonEggs;

View File

@ -182,7 +182,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -17,9 +17,7 @@ import { PokemonMove } from "#moves/pokemon-move";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { TheStrongStuffEncounter } from "#mystery-encounters/the-strong-stuff-encounter"; import { TheStrongStuffEncounter } from "#mystery-encounters/the-strong-stuff-encounter";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -192,7 +190,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(SpeciesId.SHUCKLE); expect(enemyField[0].species.speciesId).toBe(SpeciesId.SHUCKLE);
expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 0, 0, 0]); expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 0, 0, 0]);
@ -230,7 +228,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -15,9 +15,7 @@ import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters";
import { TheWinstrateChallengeEncounter } from "#mystery-encounters/the-winstrate-challenge-encounter"; import { TheWinstrateChallengeEncounter } from "#mystery-encounters/the-winstrate-challenge-encounter";
import { CommandPhase } from "#phases/command-phase";
import { PartyHealPhase } from "#phases/party-heal-phase"; import { PartyHealPhase } from "#phases/party-heal-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { VictoryPhase } from "#phases/victory-phase"; import { VictoryPhase } from "#phases/victory-phase";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
@ -262,7 +260,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VICTOR); expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VICTOR);
expect(scene.currentBattle.mysteryEncounter?.enemyPartyConfigs.length).toBe(4); expect(scene.currentBattle.mysteryEncounter?.enemyPartyConfigs.length).toBe(4);
@ -295,7 +293,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
// Should have Macho Brace in the rewards // Should have Macho Brace in the rewards
await skipBattleToNextBattle(game, true); await skipBattleToNextBattle(game, true);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -337,7 +335,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
it("should have a Rarer Candy in the rewards", async () => { it("should have a Rarer Candy in the rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -20,9 +20,7 @@ import {
} from "#mystery-encounters/encounter-phase-utils"; } from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { TrashToTreasureEncounter } from "#mystery-encounters/trash-to-treasure-encounter"; import { TrashToTreasureEncounter } from "#mystery-encounters/trash-to-treasure-encounter";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -172,8 +170,8 @@ describe("Trash to Treasure - Mystery Encounter", () => {
it("should give 1 Leftovers, 1 Shell Bell, and Black Sludge", async () => { it("should give 1 Leftovers, 1 Shell Bell, and Black Sludge", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to("SelectModifierPhase", false); expect(game).toBeAtPhase("SelectModifierPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.to("SelectModifierPhase");
const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier; const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier;
expect(leftovers).toBeDefined(); expect(leftovers).toBeDefined();
@ -221,7 +219,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(SpeciesId.GARBODOR); expect(enemyField[0].species.speciesId).toBe(SpeciesId.GARBODOR);
expect(enemyField[0].moveset).toEqual([ expect(enemyField[0].moveset).toEqual([
@ -243,7 +241,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -15,7 +15,6 @@ import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"
import { generateModifierType } from "#mystery-encounters/encounter-phase-utils"; import { generateModifierType } from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { UncommonBreedEncounter } from "#mystery-encounters/uncommon-breed-encounter"; import { UncommonBreedEncounter } from "#mystery-encounters/uncommon-breed-encounter";
import { CommandPhase } from "#phases/command-phase";
import { MovePhase } from "#phases/move-phase"; import { MovePhase } from "#phases/move-phase";
import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases";
import { StatStageChangePhase } from "#phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#phases/stat-stage-change-phase";
@ -120,7 +119,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
@ -147,7 +146,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
@ -199,7 +198,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -259,7 +258,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -10,8 +10,6 @@ import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"
import * as EncounterTransformationSequence from "#mystery-encounters/encounter-transformation-sequence"; import * as EncounterTransformationSequence from "#mystery-encounters/encounter-transformation-sequence";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { WeirdDreamEncounter } from "#mystery-encounters/weird-dream-encounter"; import { WeirdDreamEncounter } from "#mystery-encounters/weird-dream-encounter";
import { CommandPhase } from "#phases/command-phase";
import { SelectModifierPhase } from "#phases/select-modifier-phase";
import { import {
runMysteryEncounterToEnd, runMysteryEncounterToEnd,
skipBattleRunMysteryEncounterRewardsPhase, skipBattleRunMysteryEncounterRewardsPhase,
@ -116,8 +114,8 @@ describe("Weird Dream - Mystery Encounter", () => {
const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal()); const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal());
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to("SelectModifierPhase", false); expect(game).toBeAtPhase("SelectModifierPhase");
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); await game.phaseInterceptor.to("SelectModifierPhase");
const pokemonAfter = scene.getPlayerParty(); const pokemonAfter = scene.getPlayerParty();
const bstsAfter = pokemonAfter.map(pokemon => pokemon.getSpeciesForm().getBaseStatTotal()); const bstsAfter = pokemonAfter.map(pokemon => pokemon.getSpeciesForm().getBaseStatTotal());
@ -140,7 +138,7 @@ describe("Weird Dream - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
@ -187,7 +185,7 @@ describe("Weird Dream - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(game).toBeAtPhase("CommandPhase");
expect(enemyField.length).toBe(1); expect(enemyField.length).toBe(1);
expect(scene.getEnemyParty().length).toBe(scene.getPlayerParty().length); expect(scene.getEnemyParty().length).toBe(scene.getPlayerParty().length);
}); });
@ -197,7 +195,7 @@ describe("Weird Dream - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to("SelectModifierPhase", false); await game.phaseInterceptor.to("SelectModifierPhase", false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); expect(game).toBeAtPhase("SelectModifierPhase");
await game.phaseInterceptor.to("SelectModifierPhase"); await game.phaseInterceptor.to("SelectModifierPhase");
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);

View File

@ -34,7 +34,7 @@ describe("Mystery Encounters", () => {
]); ]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false); await game.phaseInterceptor.to(MysteryEncounterPhase, false);
expect(game.scene.phaseManager.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
}); });
it("Encounters should not run on X1 waves", async () => { it("Encounters should not run on X1 waves", async () => {

View File

@ -3,7 +3,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { MysteryEncounterOptionSelectedPhase } from "#phases/mystery-encounter-phases";
import { GameManager } from "#test/test-utils/game-manager"; import { GameManager } from "#test/test-utils/game-manager";
import type { MessageUiHandler } from "#ui/message-ui-handler"; import type { MessageUiHandler } from "#ui/message-ui-handler";
import type { MysteryEncounterUiHandler } from "#ui/mystery-encounter-ui-handler"; import type { MysteryEncounterUiHandler } from "#ui/mystery-encounter-ui-handler";
@ -38,7 +38,7 @@ describe("Mystery Encounter Phases", () => {
]); ]);
await game.phaseInterceptor.to("MysteryEncounterPhase", false); await game.phaseInterceptor.to("MysteryEncounterPhase", false);
expect(game.scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); expect(game).toBeAtPhase("MysteryEncounterPhase");
}); });
it("Runs MysteryEncounterPhase", async () => { it("Runs MysteryEncounterPhase", async () => {

View File

@ -44,6 +44,7 @@ import type { InputsHandler } from "#test/test-utils/inputs-handler";
import { MockFetch } from "#test/test-utils/mocks/mock-fetch"; import { MockFetch } from "#test/test-utils/mocks/mock-fetch";
import { PhaseInterceptor } from "#test/test-utils/phase-interceptor"; import { PhaseInterceptor } from "#test/test-utils/phase-interceptor";
import { TextInterceptor } from "#test/test-utils/text-interceptor"; import { TextInterceptor } from "#test/test-utils/text-interceptor";
import type { PhaseClass, PhaseString } from "#types/phase-types";
import type { BallUiHandler } from "#ui/ball-ui-handler"; import type { BallUiHandler } from "#ui/ball-ui-handler";
import type { BattleMessageUiHandler } from "#ui/battle-message-ui-handler"; import type { BattleMessageUiHandler } from "#ui/battle-message-ui-handler";
import type { CommandUiHandler } from "#ui/command-ui-handler"; import type { CommandUiHandler } from "#ui/command-ui-handler";
@ -160,7 +161,7 @@ export class GameManager {
* End the currently running phase immediately. * End the currently running phase immediately.
*/ */
endPhase() { endPhase() {
this.scene.phaseManager.getCurrentPhase()?.end(); this.scene.phaseManager.getCurrentPhase().end();
} }
/** /**
@ -412,10 +413,11 @@ export class GameManager {
* Checks if the current phase matches the target phase. * Checks if the current phase matches the target phase.
* @param phaseTarget - The target phase. * @param phaseTarget - The target phase.
* @returns Whether the current phase matches the target phase * @returns Whether the current phase matches the target phase
* @todo Remove `phaseClass` from signature
*/ */
isCurrentPhase(phaseTarget) { isCurrentPhase(phaseTarget: PhaseClass | PhaseString) {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
return this.scene.phaseManager.getCurrentPhase()?.constructor.name === targetName; return this.scene.phaseManager.getCurrentPhase().phaseName === targetName;
} }
/** /**

View File

@ -0,0 +1,45 @@
/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */
import type { Phase } from "#app/phase";
import type { GameManager } from "#test/test-utils/game-manager";
// biome-ignore-end lint/correctness/noUnusedImports: TSDoc
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
import type { PhaseString } from "#types/phase-types";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/**
* Matcher that checks if the current {@linkcode Phase} is of the given type.
* @param received - The object to check. Should be the current {@linkcode GameManager}
* @param expectedPhase - The expected {@linkcode PhaseString}
* @returns The result of the matching
*/
export function toBeAtPhase(this: MatcherState, received: unknown, expectedPhase: PhaseString): SyncExpectationResult {
if (!isGameManagerInstance(received)) {
return {
pass: this.isNot,
message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
};
}
if (!received.scene?.phaseManager) {
return {
pass: this.isNot,
message: () => `Expected GameManager.${received.scene ? "scene.phaseManager" : "scene"} to be defined!`,
};
}
const currPhase = received.scene.phaseManager.getCurrentPhase();
const pass = currPhase.is(expectedPhase);
const actual = currPhase.phaseName;
return {
pass,
message: () =>
pass
? `Expected the current phase to NOT be ${expectedPhase}, but it was!`
: `Expected the current phase to be ${expectedPhase}, but got ${actual} instead!`,
expected: expectedPhase,
actual,
};
}

View File

@ -6,11 +6,21 @@ import type { OneOther } from "#test/@types/test-helpers";
import type { GameManager } from "#test/test-utils/game-manager"; import type { GameManager } from "#test/test-utils/game-manager";
import { getOnelineDiffStr } from "#test/test-utils/string-utils"; import { getOnelineDiffStr } from "#test/test-utils/string-utils";
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
import type { ArenaTagDataMap, SerializableArenaTagType } from "#types/arena-tags";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
// intersection required to preserve T for inferences /**
export type toHaveArenaTagOptions<T extends ArenaTagType> = OneOther<ArenaTagTypeMap[T], "tagType" | "side"> & { * Options type for {@linkcode toHaveArenaTag}.
tagType: T; * @typeParam A - The {@linkcode ArenaTagType} being checked
* @remarks
* If A corresponds to a serializable `ArenaTag`, only properties allowed to be serialized
* (i.e. can change across instances) will be present and able to be checked.
*/
export type toHaveArenaTagOptions<A extends ArenaTagType> = OneOther<
A extends SerializableArenaTagType ? ArenaTagDataMap[A] : ArenaTagTypeMap[A],
"tagType" | "side"
> & {
tagType: A;
}; };
/** /**
@ -22,10 +32,10 @@ export type toHaveArenaTagOptions<T extends ArenaTagType> = OneOther<ArenaTagTyp
* {@linkcode ArenaTagSide.BOTH} to check both sides * {@linkcode ArenaTagSide.BOTH} to check both sides
* @returns The result of the matching * @returns The result of the matching
*/ */
export function toHaveArenaTag<T extends ArenaTagType>( export function toHaveArenaTag<A extends ArenaTagType>(
this: MatcherState, this: MatcherState,
received: unknown, received: unknown,
expectedTag: T | toHaveArenaTagOptions<T>, expectedTag: A | toHaveArenaTagOptions<A>,
side: ArenaTagSide = ArenaTagSide.BOTH, side: ArenaTagSide = ArenaTagSide.BOTH,
): SyncExpectationResult { ): SyncExpectationResult {
if (!isGameManagerInstance(received)) { if (!isGameManagerInstance(received)) {

View File

@ -3,21 +3,39 @@ import type { Pokemon } from "#field/pokemon";
/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ /* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type { BattlerTagTypeMap } from "#data/battler-tags";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { getEnumStr } from "#test/test-utils/string-utils"; import type { OneOther } from "#test/@types/test-helpers";
import { getEnumStr, getOnelineDiffStr } from "#test/test-utils/string-utils";
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
import type { BattlerTagDataMap, SerializableBattlerTagType } from "#types/battler-tags";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
// intersection required to preserve T for inferences
/** /**
* Matcher that checks if a {@linkcode Pokemon} has a specific {@linkcode BattlerTagType}. * Options type for {@linkcode toHaveBattlerTag}.
* @typeParam B - The {@linkcode BattlerTagType} being checked
* @remarks
* If B corresponds to a serializable `BattlerTag`, only properties allowed to be serialized
* (i.e. can change across instances) will be present and able to be checked.
*/
export type toHaveBattlerTagOptions<B extends BattlerTagType> = (B extends SerializableBattlerTagType
? OneOther<BattlerTagDataMap[B], "tagType">
: OneOther<BattlerTagTypeMap[B], "tagType">) & {
tagType: B;
};
/**
* Matcher that checks if a {@linkcode Pokemon} has a specific {@linkcode BattlerTag}.
* @param received - The object to check. Should be a {@linkcode Pokemon} * @param received - The object to check. Should be a {@linkcode Pokemon}
* @param expectedBattlerTagType - The {@linkcode BattlerTagType} to check for * @param expectedTag - The `BattlerTagType` of the desired tag, or a partially-filled object
* containing the desired properties
* @returns Whether the matcher passed * @returns Whether the matcher passed
*/ */
export function toHaveBattlerTag( export function toHaveBattlerTag<B extends BattlerTagType>(
this: MatcherState, this: MatcherState,
received: unknown, received: unknown,
expectedBattlerTagType: BattlerTagType, expectedTag: B | toHaveBattlerTagOptions<B>,
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
@ -26,18 +44,44 @@ export function toHaveBattlerTag(
}; };
} }
const pass = !!received.getTag(expectedBattlerTagType);
const pkmName = getPokemonNameWithAffix(received); const pkmName = getPokemonNameWithAffix(received);
// "BattlerTagType.SEEDED (=1)"
const expectedTagStr = getEnumStr(BattlerTagType, expectedBattlerTagType, { prefix: "BattlerTagType." });
// Coerce lone `tagType`s into objects
const etag = typeof expectedTag === "object" ? expectedTag : { tagType: expectedTag };
const gotTag = received.getTag(etag.tagType);
// If checking exclusively tag type OR no tags were found, break out early.
if (typeof expectedTag !== "object" || !gotTag) {
const pass = !!gotTag;
// "BattlerTagType.SEEDED (=1)"
const expectedTagStr = getEnumStr(BattlerTagType, etag.tagType, { prefix: "BattlerTagType." });
return {
pass,
message: () =>
pass
? `Expected ${pkmName} to NOT have a tag of type ${expectedTagStr}, but it did!`
: `Expected ${pkmName} to have a tag of type ${expectedTagStr}, but it didn't!`,
expected: expectedTag,
actual: received.summonData.tags.map(t => t.tagType),
};
}
// Check for equality with the provided tag
const pass = this.equals(gotTag, etag, [
...this.customTesters,
this.utils.subsetEquality,
this.utils.iterableEquality,
]);
const expectedStr = getOnelineDiffStr.call(this, expectedTag);
return { return {
pass, pass,
message: () => message: () =>
pass pass
? `Expected ${pkmName} to NOT have ${expectedTagStr}, but it did!` ? `Expected ${pkmName} to NOT have a tag matching ${expectedStr}, but it did!`
: `Expected ${pkmName} to have ${expectedTagStr}, but it didn't!`, : `Expected ${pkmName} to have a tag matching ${expectedStr}, but it didn't!`,
expected: expectedBattlerTagType, expected: expectedTag,
actual: received.summonData.tags.map(t => t.tagType), actual: gotTag,
}; };
} }

View File

@ -384,7 +384,7 @@ export class PhaseInterceptor {
const actionForNextPrompt = this.prompts[0]; const actionForNextPrompt = this.prompts[0];
const expireFn = actionForNextPrompt.expireFn?.(); const expireFn = actionForNextPrompt.expireFn?.();
const currentMode = this.scene.ui.getMode(); const currentMode = this.scene.ui.getMode();
const currentPhase = this.scene.phaseManager.getCurrentPhase()?.constructor.name; const currentPhase = this.scene.phaseManager.getCurrentPhase().phaseName;
const currentHandler = this.scene.ui.getHandler(); const currentHandler = this.scene.ui.getHandler();
if (expireFn) { if (expireFn) {
this.prompts.shift(); this.prompts.shift();

View File

@ -183,5 +183,5 @@ export function getOnelineDiffStr(this: MatcherState, obj: unknown): string {
return this.utils return this.utils
.stringify(obj, undefined, { maxLength: 35, indent: 0, printBasicPrototype: false }) .stringify(obj, undefined, { maxLength: 35, indent: 0, printBasicPrototype: false })
.replace(/\n/g, " ") // Replace newlines with spaces .replace(/\n/g, " ") // Replace newlines with spaces
.replace(/,(\s*)}$/g, "$1}"); // Trim trailing commas .replace(/,(\s*)\}$/g, "$1}"); // Trim trailing commas
} }

View File

@ -0,0 +1,34 @@
// @ts-check
import { PageKind, Renderer } from "typedoc";
/**
* @module
* Typedoc plugin to run post-processing on the `index.html` file and replace the coverage SVG
* for Beta with the newly generated file for the current branch.
*/
/**
* @param {import('typedoc').Application} app
*/
export function load(app) {
// Don't do anything if no REF_NAME was specified (likely indicating a local docs run)
if (!process.env.REF_NAME) {
return;
}
app.renderer.on(Renderer.EVENT_END_PAGE, page => {
if (page.pageKind === PageKind.Index && page.contents) {
page.contents = page.contents
// Replace links to the beta documentation site with the current ref name
.replace(
/href="(.*pagefaultgames.github.io\/pokerogue\/).*?"/, // formatting
`href="$1/${process.env.REF_NAME}"`,
)
// Replace the link to Beta's coverage SVG with the SVG file for the branch in question.
.replace(
/img src=".*?coverage.svg/, // formatting
`img src="coverage.svg"`,
);
}
});
}

58
typedoc.config.js Normal file
View File

@ -0,0 +1,58 @@
import { globSync } from "node:fs";
const dryRun = !!process.env.DRY_RUN?.match(/true/gi);
/**
* @type {Partial<import("typedoc").TypeDocOptions>}
*/
const config = {
entryPoints: ["./src", "./test/test-utils"],
entryPointStrategy: "expand",
exclude: ["**/*+.test.ts", "src/polyfills.ts", "src/vite.env.d.ts"],
excludeReferences: true, // prevent documenting re-exports
requiredToBeDocumented: [
"Enum",
"EnumMember",
"Variable",
"Function",
"Class",
"Interface",
"Property",
"Method",
"Accessor",
"TypeAlias",
],
highlightLanguages: ["javascript", "json", "jsonc", "json5", "tsx", "typescript", "markdown"],
plugin: [
"typedoc-github-theme",
"typedoc-plugin-coverage",
"typedoc-plugin-mdn-links",
...globSync("./typedoc-plugins/**/*.js").map(plugin => "./" + plugin),
],
// Avoid emitting docs for branches other than main/beta
emit: dryRun ? "none" : "docs",
out: process.env.CI ? "/tmp/docs" : "./typedoc",
name: "PokéRogue",
readme: "./README.md",
coverageLabel: "Documented",
coverageSvgWidth: 120, // Increased from 104 baseline due to adding 2 extra letters
favicon: "./public/images/logo.png",
theme: "typedoc-github-theme",
customFooterHtml: "<p>Copyright <strong>Pagefault Games</strong> 2025</p>",
customFooterHtmlDisableWrapper: true,
navigationLinks: {
GitHub: "https://github.com/pagefaultgames/pokerogue",
},
};
// If generating docs for main/beta, check the ref name and add an appropriate navigation header
if (!dryRun && process.env.REF_NAME) {
const otherRefName = process.env.REF_NAME === "main" ? "beta" : "main";
config.navigationLinks = {
...config.navigationLinks,
// This will be "Switch to Beta" when on main, and vice versa
[`Switch to ${otherRefName.charAt(0).toUpperCase() + otherRefName.slice(1).toLowerCase()}`]: `https://pagefaultgames.github.io/pokerogue/${otherRefName}`,
};
}
export default config;

View File

@ -1,7 +0,0 @@
{
"entryPoints": ["./src"],
"entryPointStrategy": "expand",
"exclude": ["**/*+.test.ts"],
"out": "typedoc",
"highlightLanguages": ["javascript", "json", "jsonc", "json5", "tsx", "typescript", "markdown"]
}