Compare commits

...

16 Commits

Author SHA1 Message Date
Bertie690
6ec30e2451
Merge ab968f1ac6 into f42237d415 2025-08-14 13:28:35 -04:00
Bertie690
f42237d415
[Refactor] Removed map(x => x) (#6256)
* Enforced a few usages of `toCamelCase`

* Removed `map(x => x)`

* Removed more maps and sufff

* Update test/mystery-encounter/encounters/weird-dream-encounter.test.ts

* Update game-data.ts types to work
2025-08-14 10:25:44 -07:00
fabske0
b44f0a4176
[Refactor] Remove bgm param from arena constructor (#6254) 2025-08-14 16:52:56 +00:00
Sirz Benjie
076ef81691
[Bug] [UI/UX] [Beta] Fix icons not showing in save slot selection (#6262)
Fix icons not showing in save slot selection
2025-08-13 20:49:46 -05:00
fabske0
23271901cf
[Docs] Add locale key naming info to localization.md (#6260) 2025-08-14 01:12:00 +00:00
Inês Simões
1517e0512e
[UI/UX] [Feature] Save Management Tool (Rename/Delete Saves) (#5978)
* Implement Name Run Feat
Modified load session ui component, adding a submenu when selecting a 3
slot. This menu has 4 options:
Load Game -> Behaves as before, allowing the player to continue
progress from the last saved state in the slot.

Rename Run -> Overlays a rename form, allowing the player to type a
name for the run, checking for string validity, with the option to
cancel or confirm (Rename).

Delete Run -> Prompts user confirmation to delete save data, removing
the current save slot from the users save data.

Cancel -> Hides menu overlay.

Modified game data to implement a function to accept and store
runNameText to the users data.

Modified run info ui component, to display the chosen name when
viewing run information.

Example: When loading the game, the user can choose the Load Game
menu option, then select a save slot, prompting the menu, then choose
"Rename Run" and type the name "Monotype Water Run" then confirm,
thus being able to better organize their save files.

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Implement Rename Input Design and Tests for Name Run Feat
Created a test to verify Name Run Feature behaviour in the
backend (rename_run.test.ts), checking possible errors and
 expected behaviours.

Created a UiHandler RenameRunFormUiHandler
(rename-run-ui-handler.ts), creating a frontend input
overlay for the Name Run Feature.

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Fixed formating and best practices issues:
Rewrote renameSession to be more inline with other
API call funtions, removed debugging comments and
whitespaces.

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Minor Sanitization for aesthetics
Deleting the input when closing the overlay for
aesthetics purpose

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Fixed minor rebase alterations.

Signed-off-by: Matheus Alves matheus.r.noya.alves@tecnico.ulisboa.pt
Co-authored-by: Inês Simões ines.p.simoes@tecnico.ulisboa.pt

* Implemented Default Name Logic
Altered logic in save-slot-select-ui-handler.ts to
support default naming of runs based on the run
game mode with decideFallback function.

In game-data.ts, to prevent inconsistent naming,
added check for unfilled input, ignoring empty
rename requests.

Signed-off-by: Matheus Alves matheus.r.noya.alves@tecnico.ulisboa.pt
Co-authored-by: Inês Simões ines.p.simoes@tecnico.ulisboa.pt

* Replace fallback name logic: use first active challenge instead
of game mode

Previously used game mode as the fallback name, updated to use the
first active challenge instead (e.g. Monogen or Mono Type), which
better reflects the run's theme.
Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Rebasing and conflict resolution

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Lint fix

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Inês Simões <ines.p.simoes@tecnico.ulisboa.pt>

* Minor compile fix

* Dependency resolved

* Format name respected

* Add all active challenges to default challenge session name if possible

If more than 3 challenges are active, only the first 3 are added
to the name (to prevent the text going off-screen)
and then "..." is appended to the end to indicate
there were more challenges active than the ones listed

* Allow deleting malformed sessions

---------

Signed-off-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Signed-off-by: Matheus Alves matheus.r.noya.alves@tecnico.ulisboa.pt
Co-authored-by: Matheus Alves <matheus.r.noya.alves@tecnico.ulisboa.pt>
Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
2025-08-13 20:08:12 -05:00
Bertie690
ab968f1ac6
Merge branch 'beta' into test-cleanup 2025-08-12 10:37:22 -04:00
Bertie690
64be310b9a Merge remote-tracking branch 'upstream/beta' into test-cleanup 2025-08-03 14:54:35 -04:00
Bertie690
bf855d5acf Fixed remaining error 2025-08-03 14:54:22 -04:00
Bertie690
bad73f0ce2 Fixed remaining issues; removed direct assignment to Pokemon.moveset 2025-08-01 11:15:43 -04:00
Bertie690
034c56473c Fiexd syntax errors 2025-07-30 22:22:55 -04:00
Bertie690
3db1cb7486 Deleted duplicate sturdy test case 2025-07-30 22:18:54 -04:00
Bertie690
a9a3b0760a Replaced game.scene.getXXXParty()[0] with game.field.getEnemyPokemon 2025-07-30 18:21:35 -04:00
Bertie690
c55f105d31 More array destructuring!!! 2025-07-30 18:15:23 -04:00
Bertie690
6013bc8c00 Replaced instances of consecutive game.scene.getPlayerParty with destructuring 2025-07-30 18:14:05 -04:00
Bertie690
5d4e93b009 Removed bangs from getEnemyParty and getPlayerParty 2025-07-30 18:08:14 -04:00
50 changed files with 644 additions and 309 deletions

View File

@ -90,9 +90,13 @@ If this feature requires new text, the text should be integrated into the code w
- For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text.
[Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice.
- You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response.
3. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
4. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
5. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
3. Your locales should use the following format:
- File names should be in `kebab-case`. Example: `trainer-names.json`
- Key names should be in `camelCase`. Example: `aceTrainer`
- If you make use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context), you need to use `snake_case` for the context key. Example: `aceTrainer_male`
4. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes).
5. The Translation Team will approve the locale PR (after corrections, if necessary), then merge it into `pokerogue-locales`.
6. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment.
[^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates).
If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle.

View File

@ -104,6 +104,7 @@ import {
getLuckString,
getLuckTextTint,
getPartyLuckValue,
type ModifierType,
PokemonHeldItemModifierType,
} from "#modifiers/modifier-type";
import { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
@ -1203,7 +1204,9 @@ export class BattleScene extends SceneBase {
this.updateScoreText();
this.scoreText.setVisible(false);
[this.luckLabelText, this.luckText].map(t => t.setVisible(false));
[this.luckLabelText, this.luckText].forEach(t => {
t.setVisible(false);
});
this.newArena(Overrides.STARTING_BIOME_OVERRIDE || BiomeId.TOWN);
@ -1237,8 +1240,7 @@ export class BattleScene extends SceneBase {
Object.values(mp)
.flat()
.map(mt => mt.modifierType)
.filter(mt => "localize" in mt)
.map(lpb => lpb as unknown as Localizable),
.filter((mt): mt is ModifierType & Localizable => "localize" in mt && typeof mt.localize === "function"),
),
];
for (const item of localizable) {
@ -1513,8 +1515,8 @@ export class BattleScene extends SceneBase {
return this.currentBattle;
}
newArena(biome: BiomeId, playerFaints?: number): Arena {
this.arena = new Arena(biome, BiomeId[biome].toLowerCase(), playerFaints);
newArena(biome: BiomeId, playerFaints = 0): Arena {
this.arena = new Arena(biome, playerFaints);
this.eventTarget.dispatchEvent(new NewArenaEvent());
this.arenaBg.pipelineData = {
@ -2711,7 +2713,9 @@ export class BattleScene extends SceneBase {
}
}
this.party.map(p => p.updateInfo(instant));
this.party.forEach(p => {
p.updateInfo(instant);
});
} else {
const args = [this];
if (modifier.shouldApply(...args)) {

View File

@ -74,6 +74,7 @@ import {
randSeedItem,
toDmgValue,
} from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
export class Ability implements Localizable {
@ -109,13 +110,9 @@ export class Ability implements Localizable {
}
localize(): void {
const i18nKey = AbilityId[this.id]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as string;
const i18nKey = toCamelCase(AbilityId[this.id]);
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`)}${this.nameAppend}` : "";
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
}

View File

@ -1866,17 +1866,16 @@ interface PokemonPrevolutions {
export const pokemonPrevolutions: PokemonPrevolutions = {};
export function initPokemonPrevolutions(): void {
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ].map(sfk => sfk as string);
const prevolutionKeys = Object.keys(pokemonEvolutions);
prevolutionKeys.forEach(pk => {
const evolutions = pokemonEvolutions[pk];
// TODO: Why do we have empty strings in our array?
const megaFormKeys = [ SpeciesFormKey.MEGA, "", SpeciesFormKey.MEGA_X, "", SpeciesFormKey.MEGA_Y ];
for (const [pk, evolutions] of Object.entries(pokemonEvolutions)) {
for (const ev of evolutions) {
if (ev.evoFormKey && megaFormKeys.indexOf(ev.evoFormKey) > -1) {
continue;
}
pokemonPrevolutions[ev.speciesId] = Number.parseInt(pk) as SpeciesId;
}
});
}
}

View File

@ -90,7 +90,7 @@ import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindS
import type { TurnMove } from "#types/turn-move";
import { BooleanHolder, type Constructor, isNullOrUndefined, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common";
import { getEnumValues } from "#utils/enums";
import { toTitleCase } from "#utils/strings";
import { toCamelCase, toTitleCase } from "#utils/strings";
import i18next from "i18next";
import { applyChallenges } from "#utils/challenge-utils";
@ -162,10 +162,16 @@ export abstract class Move implements Localizable {
}
localize(): void {
const i18nKey = MoveId[this.id].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
const i18nKey = toCamelCase(MoveId[this.id])
this.name = this.id ? `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}` : "";
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
if (this.id === MoveId.NONE) {
this.name = "";
this.effect = ""
return;
}
this.name = `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}`;
this.effect = `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}`;
}
/**
@ -5926,8 +5932,8 @@ export class ProtectAttr extends AddBattlerTagAttr {
for (const turnMove of user.getLastXMoves(-1).slice()) {
if (
// Quick & Wide guard increment the Protect counter without using it for fail chance
!(allMoves[turnMove.move].hasAttr("ProtectAttr") ||
[MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) ||
!(allMoves[turnMove.move].hasAttr("ProtectAttr") ||
[MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) ||
turnMove.result !== MoveResult.SUCCESS
) {
break;

View File

@ -12,6 +12,7 @@ import { WeatherType } from "#enums/weather-type";
import type { Pokemon } from "#field/pokemon";
import type { PokemonFormChangeItemModifier } from "#modifiers/modifier";
import { type Constructor, coerceArray } from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
export abstract class SpeciesFormChangeTrigger {
@ -143,11 +144,7 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge
super();
this.move = move;
this.known = known;
const moveKey = MoveId[this.move]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as unknown as string;
const moveKey = toCamelCase(MoveId[this.move]);
this.description = known
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
move: i18next.t(`move:${moveKey}.name`),

View File

@ -38,6 +38,7 @@ export enum UiMode {
UNAVAILABLE,
CHALLENGE_SELECT,
RENAME_POKEMON,
RENAME_RUN,
RUN_HISTORY,
RUN_INFO,
TEST_DIALOGUE,

View File

@ -54,7 +54,7 @@ export class Arena {
public bgm: string;
public ignoreAbilities: boolean;
public ignoringEffectSource: BattlerIndex | null;
public playerTerasUsed: number;
public playerTerasUsed = 0;
/**
* Saves the number of times a party pokemon faints during a arena encounter.
* {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave).
@ -68,12 +68,11 @@ export class Arena {
public readonly eventTarget: EventTarget = new EventTarget();
constructor(biome: BiomeId, bgm: string, playerFaints = 0) {
constructor(biome: BiomeId, playerFaints = 0) {
this.biomeType = biome;
this.bgm = bgm;
this.bgm = BiomeId[biome].toLowerCase();
this.trainerPool = biomeTrainerPools[biome];
this.updatePoolsForTimeOfDay();
this.playerTerasUsed = 0;
this.playerFaints = playerFaints;
}

View File

@ -447,7 +447,9 @@ export class LoadingScene extends SceneBase {
);
if (!mobile) {
loadingGraphics.map(g => g.setVisible(false));
loadingGraphics.forEach(g => {
g.setVisible(false);
});
}
const intro = this.add.video(0, 0);

View File

@ -121,8 +121,8 @@ export class ModifierBar extends Phaser.GameObjects.Container {
}
updateModifierOverflowVisibility(ignoreLimit: boolean) {
const modifierIcons = this.getAll().reverse();
for (const modifier of modifierIcons.map(m => m as Phaser.GameObjects.Container).slice(iconOverflowIndex)) {
const modifierIcons = this.getAll().reverse() as Phaser.GameObjects.Container[];
for (const modifier of modifierIcons.slice(iconOverflowIndex)) {
modifier.setVisible(ignoreLimit);
}
}

View File

@ -127,6 +127,7 @@ export interface SessionSaveData {
battleType: BattleType;
trainer: TrainerData;
gameVersion: string;
runNameText: string;
timestamp: number;
challenges: ChallengeData[];
mysteryEncounterType: MysteryEncounterType | -1; // Only defined when current wave is ME,
@ -206,10 +207,12 @@ export interface StarterData {
[key: number]: StarterDataEntry;
}
export interface TutorialFlags {
[key: string]: boolean;
}
// TODO: Rework into a bitmask
export type TutorialFlags = {
[key in Tutorial]: boolean;
};
// TODO: Rework into a bitmask
export interface SeenDialogues {
[key: string]: boolean;
}
@ -822,52 +825,51 @@ export class GameData {
return true; // TODO: is `true` the correct return value?
}
private loadGamepadSettings(): boolean {
Object.values(SettingGamepad)
.map(setting => setting as SettingGamepad)
.forEach(setting => setSettingGamepad(setting, settingGamepadDefaults[setting]));
private loadGamepadSettings(): void {
Object.values(SettingGamepad).forEach(setting => {
setSettingGamepad(setting, settingGamepadDefaults[setting]);
});
if (!localStorage.hasOwnProperty("settingsGamepad")) {
return false;
return;
}
const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")!); // TODO: is this bang correct?
for (const setting of Object.keys(settingsGamepad)) {
setSettingGamepad(setting as SettingGamepad, settingsGamepad[setting]);
}
return true; // TODO: is `true` the correct return value?
}
public saveTutorialFlag(tutorial: Tutorial, flag: boolean): boolean {
const key = getDataTypeKey(GameDataType.TUTORIALS);
let tutorials: object = {};
if (localStorage.hasOwnProperty(key)) {
tutorials = JSON.parse(localStorage.getItem(key)!); // TODO: is this bang correct?
/**
* Save the specified tutorial as having the specified completion status.
* @param tutorial - The {@linkcode Tutorial} whose completion status is being saved
* @param status - The completion status to set
*/
public saveTutorialFlag(tutorial: Tutorial, status: boolean): void {
// Grab the prior save data tutorial
const saveDataKey = getDataTypeKey(GameDataType.TUTORIALS);
const tutorials: TutorialFlags = localStorage.hasOwnProperty(saveDataKey)
? JSON.parse(localStorage.getItem(saveDataKey)!)
: {};
// TODO: We shouldn't be storing this like that
for (const key of Object.values(Tutorial)) {
if (key === tutorial) {
tutorials[key] = status;
} else {
tutorials[key] ??= false;
}
}
Object.keys(Tutorial)
.map(t => t as Tutorial)
.forEach(t => {
const key = Tutorial[t];
if (key === tutorial) {
tutorials[key] = flag;
} else {
tutorials[key] ??= false;
}
});
localStorage.setItem(key, JSON.stringify(tutorials));
return true;
localStorage.setItem(saveDataKey, JSON.stringify(tutorials));
}
public getTutorialFlags(): TutorialFlags {
const key = getDataTypeKey(GameDataType.TUTORIALS);
const ret: TutorialFlags = {};
Object.values(Tutorial)
.map(tutorial => tutorial as Tutorial)
.forEach(tutorial => (ret[Tutorial[tutorial]] = false));
const ret: TutorialFlags = Object.values(Tutorial).reduce((acc, tutorial) => {
acc[Tutorial[tutorial]] = false;
return acc;
}, {} as TutorialFlags);
if (!localStorage.hasOwnProperty(key)) {
return ret;
@ -979,6 +981,54 @@ export class GameData {
});
}
async renameSession(slotId: number, newName: string): Promise<boolean> {
return new Promise(async resolve => {
if (slotId < 0) {
return resolve(false);
}
const sessionData: SessionSaveData | null = await this.getSession(slotId);
if (!sessionData) {
return resolve(false);
}
if (newName === "") {
return resolve(true);
}
sessionData.runNameText = newName;
const updatedDataStr = JSON.stringify(sessionData);
const encrypted = encrypt(updatedDataStr, bypassLogin);
const secretId = this.secretId;
const trainerId = this.trainerId;
if (bypassLogin) {
localStorage.setItem(
`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`,
encrypt(updatedDataStr, bypassLogin),
);
resolve(true);
return;
}
pokerogueApi.savedata.session
.update({ slot: slotId, trainerId, secretId, clientSessionId }, encrypted)
.then(error => {
if (error) {
console.error("Failed to update session name:", error);
resolve(false);
} else {
localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted);
updateUserInfo().then(success => {
if (success !== null && !success) {
return resolve(false);
}
});
resolve(true);
}
});
});
}
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this
return new Promise(async (resolve, reject) => {

View File

@ -0,0 +1,54 @@
import i18next from "i18next";
import type { InputFieldConfig } from "./form-modal-ui-handler";
import { FormModalUiHandler } from "./form-modal-ui-handler";
import type { ModalConfig } from "./modal-ui-handler";
export class RenameRunFormUiHandler extends FormModalUiHandler {
getModalTitle(_config?: ModalConfig): string {
return i18next.t("menu:renamerun");
}
getWidth(_config?: ModalConfig): number {
return 160;
}
getMargin(_config?: ModalConfig): [number, number, number, number] {
return [0, 0, 48, 0];
}
getButtonLabels(_config?: ModalConfig): string[] {
return [i18next.t("menu:rename"), i18next.t("menu:cancel")];
}
getReadableErrorMessage(error: string): string {
const colonIndex = error?.indexOf(":");
if (colonIndex > 0) {
error = error.slice(0, colonIndex);
}
return super.getReadableErrorMessage(error);
}
override getInputFieldConfigs(): InputFieldConfig[] {
return [{ label: i18next.t("menu:runName") }];
}
show(args: any[]): boolean {
if (!super.show(args)) {
return false;
}
if (this.inputs?.length) {
this.inputs.forEach(input => {
input.text = "";
});
}
const config = args[0] as ModalConfig;
this.submitAction = _ => {
this.sanitizeInputs();
const sanitizedName = btoa(encodeURIComponent(this.inputs[0].text));
config.buttonActions[0](sanitizedName);
return true;
};
return true;
}
}

View File

@ -26,6 +26,7 @@ import { addBBCodeTextObject, addTextObject, getTextColor } from "#ui/text";
import { UiHandler } from "#ui/ui-handler";
import { addWindow } from "#ui/ui-theme";
import { formatFancyLargeNumber, formatLargeNumber, formatMoney, getPlayTimeString } from "#utils/common";
import { toCamelCase } from "#utils/strings";
import i18next from "i18next";
import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle";
@ -207,6 +208,10 @@ export class RunInfoUiHandler extends UiHandler {
headerText.setOrigin(0, 0);
headerText.setPositionRelative(headerBg, 8, 4);
this.runContainer.add(headerText);
const runName = addTextObject(0, 0, this.runInfo.runNameText, TextStyle.WINDOW);
runName.setOrigin(0, 0);
runName.setPositionRelative(headerBg, 60, 4);
this.runContainer.add(runName);
}
/**
@ -702,10 +707,7 @@ export class RunInfoUiHandler extends UiHandler {
rules.push(i18next.t("challenges:inverseBattle.shortName"));
break;
default: {
const localizationKey = Challenges[this.runInfo.challenges[i].id]
.split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("");
const localizationKey = toCamelCase(Challenges[this.runInfo.challenges[i].id]);
rules.push(i18next.t(`challenges:${localizationKey}.name`));
break;
}

View File

@ -1,12 +1,14 @@
import { GameMode } from "#app/game-mode";
import { globalScene } from "#app/global-scene";
import { Button } from "#enums/buttons";
import { GameModes } from "#enums/game-modes";
import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
import * as Modifier from "#modifiers/modifier";
import type { SessionSaveData } from "#system/game-data";
import type { PokemonData } from "#system/pokemon-data";
import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler";
import { MessageUiHandler } from "#ui/message-ui-handler";
import { RunDisplayMode } from "#ui/run-info-ui-handler";
import { addTextObject } from "#ui/text";
@ -15,7 +17,7 @@ import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } fro
import i18next from "i18next";
const SESSION_SLOTS_COUNT = 5;
const SLOTS_ON_SCREEN = 3;
const SLOTS_ON_SCREEN = 2;
export enum SaveSlotUiMode {
LOAD,
@ -33,6 +35,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
private uiMode: SaveSlotUiMode;
private saveSlotSelectCallback: SaveSlotSelectCallback | null;
protected manageDataConfig: OptionSelectConfig;
private scrollCursor = 0;
@ -101,6 +104,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
processInput(button: Button): boolean {
const ui = this.getUi();
const manageDataOptions: any[] = [];
let success = false;
let error = false;
@ -109,14 +113,115 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
const originalCallback = this.saveSlotSelectCallback;
if (button === Button.ACTION) {
const cursor = this.cursor + this.scrollCursor;
if (this.uiMode === SaveSlotUiMode.LOAD && !this.sessionSlots[cursor].hasData) {
const sessionSlot = this.sessionSlots[cursor];
if (this.uiMode === SaveSlotUiMode.LOAD && !sessionSlot.hasData) {
error = true;
} else {
switch (this.uiMode) {
case SaveSlotUiMode.LOAD:
this.saveSlotSelectCallback = null;
originalCallback?.(cursor);
if (!sessionSlot.malformed) {
manageDataOptions.push({
label: i18next.t("menu:loadGame"),
handler: () => {
globalScene.ui.revertMode();
originalCallback?.(cursor);
return true;
},
keepOpen: false,
});
manageDataOptions.push({
label: i18next.t("saveSlotSelectUiHandler:renameRun"),
handler: () => {
globalScene.ui.revertMode();
ui.setOverlayMode(
UiMode.RENAME_RUN,
{
buttonActions: [
(sanitizedName: string) => {
const name = decodeURIComponent(atob(sanitizedName));
globalScene.gameData.renameSession(cursor, name).then(response => {
if (response[0] === false) {
globalScene.reset(true);
} else {
this.clearSessionSlots();
this.cursorObj = null;
this.populateSessionSlots();
this.setScrollCursor(0);
this.setCursor(0);
ui.revertMode();
ui.showText("", 0);
}
});
},
() => {
ui.revertMode();
},
],
},
"",
);
return true;
},
});
}
this.manageDataConfig = {
xOffset: 0,
yOffset: 48,
options: manageDataOptions,
maxOptions: 4,
};
manageDataOptions.push({
label: i18next.t("saveSlotSelectUiHandler:deleteRun"),
handler: () => {
globalScene.ui.revertMode();
ui.showText(i18next.t("saveSlotSelectUiHandler:deleteData"), null, () => {
ui.setOverlayMode(
UiMode.CONFIRM,
() => {
globalScene.gameData.tryClearSession(cursor).then(response => {
if (response[0] === false) {
globalScene.reset(true);
} else {
this.clearSessionSlots();
this.cursorObj = null;
this.populateSessionSlots();
this.setScrollCursor(0);
this.setCursor(0);
ui.revertMode();
ui.showText("", 0);
}
});
},
() => {
ui.revertMode();
ui.showText("", 0);
},
false,
0,
19,
import.meta.env.DEV ? 300 : 2000,
);
});
return true;
},
keepOpen: false,
});
manageDataOptions.push({
label: i18next.t("menuUiHandler:cancel"),
handler: () => {
globalScene.ui.revertMode();
return true;
},
keepOpen: true,
});
ui.setOverlayMode(UiMode.MENU_OPTION_SELECT, this.manageDataConfig);
break;
case SaveSlotUiMode.SAVE: {
const saveAndCallback = () => {
const originalCallback = this.saveSlotSelectCallback;
@ -161,6 +266,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
}
} else {
this.saveSlotSelectCallback = null;
ui.showText("", 0);
originalCallback?.(-1);
success = true;
}
@ -267,33 +373,34 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
this.cursorObj = globalScene.add.container(0, 0);
const cursorBox = globalScene.add.nineslice(
0,
0,
15,
"select_cursor_highlight_thick",
undefined,
296,
44,
294,
this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 50 : 60,
6,
6,
6,
6,
);
const rightArrow = globalScene.add.image(0, 0, "cursor");
rightArrow.setPosition(160, 0);
rightArrow.setPosition(160, 15);
rightArrow.setName("rightArrow");
this.cursorObj.add([cursorBox, rightArrow]);
this.sessionSlotsContainer.add(this.cursorObj);
}
const cursorPosition = cursor + this.scrollCursor;
const cursorIncrement = cursorPosition * 56;
const cursorIncrement = cursorPosition * 76;
if (this.sessionSlots[cursorPosition] && this.cursorObj) {
const hasData = this.sessionSlots[cursorPosition].hasData;
const session = this.sessionSlots[cursorPosition];
const hasData = session.hasData && !session.malformed;
// If the session slot lacks session data, it does not move from its default, central position.
// Only session slots with session data will move leftwards and have a visible arrow.
if (!hasData) {
this.cursorObj.setPosition(151, 26 + cursorIncrement);
this.cursorObj.setPosition(151, 20 + cursorIncrement);
this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement);
} else {
this.cursorObj.setPosition(145, 26 + cursorIncrement);
this.cursorObj.setPosition(145, 20 + cursorIncrement);
this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement);
}
this.setArrowVisibility(hasData);
@ -311,7 +418,8 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
revertSessionSlot(slotIndex: number): void {
const sessionSlot = this.sessionSlots[slotIndex];
if (sessionSlot) {
sessionSlot.setPosition(0, slotIndex * 56);
const valueHeight = 76;
sessionSlot.setPosition(0, slotIndex * valueHeight);
}
}
@ -340,7 +448,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
this.setCursor(this.cursor, prevSlotIndex);
globalScene.tweens.add({
targets: this.sessionSlotsContainer,
y: this.sessionSlotsContainerInitialY - 56 * scrollCursor,
y: this.sessionSlotsContainerInitialY - 76 * scrollCursor,
duration: fixedInt(325),
ease: "Sine.easeInOut",
});
@ -374,12 +482,14 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
class SessionSlot extends Phaser.GameObjects.Container {
public slotId: number;
public hasData: boolean;
/** Indicates the save slot ran into an error while being loaded */
public malformed: boolean;
private slotWindow: Phaser.GameObjects.NineSlice;
private loadingLabel: Phaser.GameObjects.Text;
public saveData: SessionSaveData;
constructor(slotId: number) {
super(globalScene, 0, slotId * 56);
super(globalScene, 0, slotId * 76);
this.slotId = slotId;
@ -387,32 +497,89 @@ class SessionSlot extends Phaser.GameObjects.Container {
}
setup() {
const slotWindow = addWindow(0, 0, 304, 52);
this.add(slotWindow);
this.slotWindow = addWindow(0, 0, 304, 70);
this.add(this.slotWindow);
this.loadingLabel = addTextObject(152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW);
this.loadingLabel = addTextObject(152, 33, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW);
this.loadingLabel.setOrigin(0.5, 0.5);
this.add(this.loadingLabel);
}
/**
* Generates a name for sessions that don't have a name yet.
* @param data - The {@linkcode SessionSaveData} being checked
* @returns The default name for the given data.
*/
decideFallback(data: SessionSaveData): string {
let fallbackName = `${GameMode.getModeName(data.gameMode)}`;
switch (data.gameMode) {
case GameModes.CLASSIC:
fallbackName += ` (${globalScene.gameData.gameStats.classicSessionsPlayed + 1})`;
break;
case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS:
fallbackName += ` (${globalScene.gameData.gameStats.endlessSessionsPlayed + 1})`;
break;
case GameModes.DAILY: {
const runDay = new Date(data.timestamp).toLocaleDateString();
fallbackName += ` (${runDay})`;
break;
}
case GameModes.CHALLENGE: {
const activeChallenges = data.challenges.filter(c => c.value !== 0);
if (activeChallenges.length === 0) {
break;
}
fallbackName = "";
for (const challenge of activeChallenges.slice(0, 3)) {
if (fallbackName !== "") {
fallbackName += ", ";
}
fallbackName += challenge.toChallenge().getName();
}
if (activeChallenges.length > 3) {
fallbackName += ", ...";
} else if (fallbackName === "") {
// Something went wrong when retrieving the names of the active challenges,
// so fall back to just naming the run "Challenge"
fallbackName = `${GameMode.getModeName(data.gameMode)}`;
}
break;
}
}
return fallbackName;
}
async setupWithData(data: SessionSaveData) {
const hasName = data?.runNameText;
this.remove(this.loadingLabel, true);
if (hasName) {
const nameLabel = addTextObject(8, 5, data.runNameText, TextStyle.WINDOW);
this.add(nameLabel);
} else {
const fallbackName = this.decideFallback(data);
await globalScene.gameData.renameSession(this.slotId, fallbackName);
const nameLabel = addTextObject(8, 5, fallbackName, TextStyle.WINDOW);
this.add(nameLabel);
}
const gameModeLabel = addTextObject(
8,
5,
19,
`${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unknown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`,
TextStyle.WINDOW,
);
this.add(gameModeLabel);
const timestampLabel = addTextObject(8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);
const timestampLabel = addTextObject(8, 33, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW);
this.add(timestampLabel);
const playTimeLabel = addTextObject(8, 33, getPlayTimeString(data.playTime), TextStyle.WINDOW);
const playTimeLabel = addTextObject(8, 47, getPlayTimeString(data.playTime), TextStyle.WINDOW);
this.add(playTimeLabel);
const pokemonIconsContainer = globalScene.add.container(144, 4);
const pokemonIconsContainer = globalScene.add.container(144, 16);
data.party.forEach((p: PokemonData, i: number) => {
const iconContainer = globalScene.add.container(26 * i, 0);
iconContainer.setScale(0.75);
@ -427,13 +594,9 @@ class SessionSlot extends Phaser.GameObjects.Container {
TextStyle.PARTY,
{ fontSize: "54px", color: "#f8f8f8" },
);
text.setShadow(0, 0, undefined);
text.setStroke("#424242", 14);
text.setOrigin(1, 0);
iconContainer.add(icon);
iconContainer.add(text);
text.setShadow(0, 0, undefined).setStroke("#424242", 14).setOrigin(1, 0);
iconContainer.add([icon, text]);
pokemonIconsContainer.add(iconContainer);
pokemon.destroy();
@ -441,7 +604,7 @@ class SessionSlot extends Phaser.GameObjects.Container {
this.add(pokemonIconsContainer);
const modifierIconsContainer = globalScene.add.container(148, 30);
const modifierIconsContainer = globalScene.add.container(148, 38);
modifierIconsContainer.setScale(0.5);
let visibleModifierIndex = 0;
for (const m of data.modifiers) {
@ -464,22 +627,33 @@ class SessionSlot extends Phaser.GameObjects.Container {
load(): Promise<boolean> {
return new Promise<boolean>(resolve => {
globalScene.gameData.getSession(this.slotId).then(async sessionData => {
// Ignore the results if the view was exited
if (!this.active) {
return;
}
if (!sessionData) {
this.hasData = false;
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
resolve(false);
return;
}
this.hasData = true;
this.saveData = sessionData;
await this.setupWithData(sessionData);
resolve(true);
});
globalScene.gameData
.getSession(this.slotId)
.then(async sessionData => {
// Ignore the results if the view was exited
if (!this.active) {
return;
}
this.hasData = !!sessionData;
if (!sessionData) {
this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty"));
resolve(false);
return;
}
this.saveData = sessionData;
this.setupWithData(sessionData);
resolve(true);
})
.catch(e => {
if (!this.active) {
return;
}
console.warn(`Failed to load session slot #${this.slotId}:`, e);
this.loadingLabel.setText(i18next.t("menu:failedToLoadSession"));
this.hasData = true;
this.malformed = true;
resolve(true);
});
});
}
}

View File

@ -31,7 +31,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler {
// we check for null or undefined here as per above - the typeof is still an object but the value is null so we need to exit out of this and pass the null key
// Return in the format expected by i18next
return middleKey ? `${topKey}:${middleKey.map(m => m).join(".")}.${t}` : `${topKey}:${t}`;
return middleKey ? `${topKey}:${middleKey.join(".")}.${t}` : `${topKey}:${t}`;
}
})
.filter(t => t);

View File

@ -60,6 +60,7 @@ import { addWindow } from "#ui/ui-theme";
import { UnavailableModalUiHandler } from "#ui/unavailable-modal-ui-handler";
import { executeIf } from "#utils/common";
import i18next from "i18next";
import { RenameRunFormUiHandler } from "./rename-run-ui-handler";
const transitionModes = [
UiMode.SAVE_SLOT,
@ -98,6 +99,7 @@ const noTransitionModes = [
UiMode.SESSION_RELOAD,
UiMode.UNAVAILABLE,
UiMode.RENAME_POKEMON,
UiMode.RENAME_RUN,
UiMode.TEST_DIALOGUE,
UiMode.AUTO_COMPLETE,
UiMode.ADMIN,
@ -168,6 +170,7 @@ export class UI extends Phaser.GameObjects.Container {
new UnavailableModalUiHandler(),
new GameChallengesUiHandler(),
new RenameFormUiHandler(),
new RenameRunFormUiHandler(),
new RunHistoryUiHandler(),
new RunInfoUiHandler(),
new TestDialogueUiHandler(UiMode.TEST_DIALOGUE),

View File

@ -134,7 +134,7 @@ describe("Abilities - Disguise", () => {
});
await game.classicMode.startBattle([SpeciesId.FURRET, SpeciesId.MIMIKYU]);
const mimikyu = game.scene.getPlayerParty()[1]!;
const mimikyu = game.scene.getPlayerParty()[1];
expect(mimikyu.formIndex).toBe(bustedForm);
game.move.select(MoveId.SPLASH);

View File

@ -121,8 +121,8 @@ describe("Abilities - Sheer Force", () => {
await game.classicMode.startBattle([SpeciesId.PIDGEOT]);
const pidgeot = game.scene.getPlayerParty()[0];
const onix = game.scene.getEnemyParty()[0];
const pidgeot = game.field.getPlayerPokemon();
const onix = game.field.getEnemyPokemon();
pidgeot.stats[Stat.DEF] = 10000;
onix.stats[Stat.DEF] = 10000;

View File

@ -1,7 +1,6 @@
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import type { EnemyPokemon } from "#field/pokemon";
import { DamageAnimPhase } from "#phases/damage-anim-phase";
import { MoveEndPhase } from "#phases/move-end-phase";
import { GameManager } from "#test/test-utils/game-manager";
@ -38,13 +37,13 @@ describe("Abilities - Sturdy", () => {
await game.classicMode.startBattle();
game.move.select(MoveId.CLOSE_COMBAT);
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.getEnemyParty()[0].hp).toBe(1);
expect(game.field.getEnemyPokemon().hp).toBe(1);
});
test("Sturdy doesn't activate when user is not at full HP", async () => {
await game.classicMode.startBattle();
const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0];
const enemyPokemon = game.field.getEnemyPokemon();
enemyPokemon.hp = enemyPokemon.getMaxHp() - 1;
game.move.select(MoveId.CLOSE_COMBAT);
@ -59,19 +58,7 @@ describe("Abilities - Sturdy", () => {
game.move.select(MoveId.FISSURE);
await game.phaseInterceptor.to(MoveEndPhase);
const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0];
const enemyPokemon = game.field.getEnemyPokemon();
expect(enemyPokemon.isFullHp()).toBe(true);
});
test("Sturdy is ignored by pokemon with `AbilityId.MOLD_BREAKER`", async () => {
game.override.ability(AbilityId.MOLD_BREAKER);
await game.classicMode.startBattle();
game.move.select(MoveId.CLOSE_COMBAT);
await game.phaseInterceptor.to(DamageAnimPhase);
const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0];
expect(enemyPokemon.hp).toBe(0);
expect(enemyPokemon.isFainted()).toBe(true);
});
});

View File

@ -336,7 +336,7 @@ describe("Abilities - Wimp Out", () => {
game.move.select(MoveId.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(game.scene.getPlayerParty()[0].getHpRatio()).toEqual(0.51);
expect(game.field.getPlayerPokemon().getHpRatio()).toEqual(0.51);
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.WIMPOD);
});
@ -344,8 +344,7 @@ describe("Abilities - Wimp Out", () => {
it("Wimp Out activating should not cancel a double battle", async () => {
game.override.battleStyle("double").enemyAbility(AbilityId.WIMP_OUT).enemyMoveset([MoveId.SPLASH]).enemyLevel(1);
await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]);
const enemyLeadPokemon = game.scene.getEnemyParty()[0];
const enemySecPokemon = game.scene.getEnemyParty()[1];
const [enemyLeadPokemon, enemySecPokemon] = game.scene.getEnemyParty();
game.move.select(MoveId.FALSE_SWIPE, 0, BattlerIndex.ENEMY);
game.move.select(MoveId.SPLASH, 1);

View File

@ -40,8 +40,7 @@ describe("Abilities - ZERO TO HERO", () => {
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.PALAFIN, SpeciesId.PALAFIN]);
const palafin1 = game.scene.getPlayerParty()[1];
const palafin2 = game.scene.getPlayerParty()[2];
const [, palafin1, palafin2] = game.scene.getPlayerParty();
expect(palafin1.formIndex).toBe(heroForm);
expect(palafin2.formIndex).toBe(heroForm);
palafin2.hp = 0;

View File

@ -64,11 +64,9 @@ describe("Boss Pokemon / Shields", () => {
it("should reduce the number of shields if we are in a double battle", async () => {
game.override.battleStyle("double").startingWave(150); // Floor 150 > 2 shields / 3 health segments
await game.classicMode.startBattle([SpeciesId.MEWTWO]);
const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
const [boss1, boss2] = game.scene.getEnemyParty();
expect(boss1.isBoss()).toBe(true);
expect(boss1.bossSegments).toBe(2);
expect(boss2.isBoss()).toBe(true);
@ -112,7 +110,7 @@ describe("Boss Pokemon / Shields", () => {
// In this test we want to break through 3 shields at once
const brokenShields = 3;
const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
const boss1 = game.field.getEnemyPokemon();
const boss1SegmentHp = boss1.getMaxHp() / boss1.bossSegments;
const requiredDamageBoss1 = boss1SegmentHp * (1 + Math.pow(2, brokenShields));
expect(boss1.isBoss()).toBe(true);
@ -124,7 +122,7 @@ describe("Boss Pokemon / Shields", () => {
expect(boss1.bossSegmentIndex).toBe(1);
expect(boss1.hp).toBe(boss1.getMaxHp() - toDmgValue(boss1SegmentHp * 3));
const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
const boss2 = game.scene.getEnemyParty()[1];
const boss2SegmentHp = boss2.getMaxHp() / boss2.bossSegments;
const requiredDamageBoss2 = boss2SegmentHp * (1 + Math.pow(2, brokenShields));
@ -144,7 +142,7 @@ describe("Boss Pokemon / Shields", () => {
await game.classicMode.startBattle([SpeciesId.MEWTWO]);
const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
const boss1 = game.field.getEnemyPokemon();
const boss1SegmentHp = boss1.getMaxHp() / boss1.bossSegments;
const singleShieldDamage = Math.ceil(boss1SegmentHp);
expect(boss1.isBoss()).toBe(true);
@ -167,7 +165,7 @@ describe("Boss Pokemon / Shields", () => {
expect(getTotalStatStageBoosts(boss1)).toBe(totalStatStages);
}
const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
const boss2 = game.scene.getEnemyParty()[1];
const boss2SegmentHp = boss2.getMaxHp() / boss2.bossSegments;
const requiredDamage = boss2SegmentHp * (1 + Math.pow(2, shieldsToBreak - 1));

View File

@ -34,8 +34,7 @@ describe("Evolution", () => {
it("should keep hidden ability after evolving", async () => {
await game.classicMode.runToSummon([SpeciesId.EEVEE, SpeciesId.TRAPINCH]);
const eevee = game.scene.getPlayerParty()[0];
const trapinch = game.scene.getPlayerParty()[1];
const [eevee, trapinch] = game.scene.getPlayerParty();
eevee.abilityIndex = 2;
trapinch.abilityIndex = 2;
@ -49,8 +48,7 @@ describe("Evolution", () => {
it("should keep same ability slot after evolving", async () => {
await game.classicMode.runToSummon([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
const bulbasaur = game.scene.getPlayerParty()[0];
const charmander = game.scene.getPlayerParty()[1];
const [bulbasaur, charmander] = game.scene.getPlayerParty();
bulbasaur.abilityIndex = 0;
charmander.abilityIndex = 1;
@ -80,8 +78,7 @@ describe("Evolution", () => {
nincada.gender = 1;
await nincada.evolve(pokemonEvolutions[SpeciesId.NINCADA][0], nincada.getSpeciesForm());
const ninjask = game.scene.getPlayerParty()[0];
const shedinja = game.scene.getPlayerParty()[1];
const [ninjask, shedinja] = game.scene.getPlayerParty();
expect(ninjask.abilityIndex).toBe(2);
expect(shedinja.abilityIndex).toBe(1);
expect(ninjask.gender).toBe(1);

View File

@ -85,17 +85,14 @@ describe("Spec - Pokemon", () => {
});
describe("Get correct fusion type", () => {
let scene: BattleScene;
beforeEach(async () => {
game.override.enemySpecies(SpeciesId.ZUBAT).starterSpecies(SpeciesId.ABRA).enableStarterFusion();
scene = game.scene;
});
it("Fusing two mons with a single type", async () => {
game.override.starterFusionSpecies(SpeciesId.CHARMANDER);
await game.classicMode.startBattle();
const pokemon = scene.getPlayerParty()[0];
const pokemon = game.field.getPlayerPokemon();
let types = pokemon.getTypes();
expect(types[0]).toBe(PokemonType.PSYCHIC);
@ -136,7 +133,7 @@ describe("Spec - Pokemon", () => {
it("Fusing two mons with same single type", async () => {
game.override.starterFusionSpecies(SpeciesId.DROWZEE);
await game.classicMode.startBattle();
const pokemon = scene.getPlayerParty()[0];
const pokemon = game.field.getPlayerPokemon();
const types = pokemon.getTypes();
expect(types[0]).toBe(PokemonType.PSYCHIC);
@ -146,7 +143,7 @@ describe("Spec - Pokemon", () => {
it("Fusing mons with one and two types", async () => {
game.override.starterSpecies(SpeciesId.CHARMANDER).starterFusionSpecies(SpeciesId.HOUNDOUR);
await game.classicMode.startBattle();
const pokemon = scene.getPlayerParty()[0];
const pokemon = game.field.getPlayerPokemon();
const types = pokemon.getTypes();
expect(types[0]).toBe(PokemonType.FIRE);
@ -156,7 +153,7 @@ describe("Spec - Pokemon", () => {
it("Fusing mons with two and one types", async () => {
game.override.starterSpecies(SpeciesId.NUMEL).starterFusionSpecies(SpeciesId.CHARMANDER);
await game.classicMode.startBattle();
const pokemon = scene.getPlayerParty()[0];
const pokemon = game.field.getPlayerPokemon();
const types = pokemon.getTypes();
expect(types[0]).toBe(PokemonType.FIRE);
@ -166,7 +163,7 @@ describe("Spec - Pokemon", () => {
it("Fusing two mons with two types", async () => {
game.override.starterSpecies(SpeciesId.NATU).starterFusionSpecies(SpeciesId.HOUNDOUR);
await game.classicMode.startBattle();
const pokemon = scene.getPlayerParty()[0];
const pokemon = game.field.getPlayerPokemon();
let types = pokemon.getTypes();
expect(types[0]).toBe(PokemonType.PSYCHIC);

View File

@ -33,7 +33,7 @@ describe("Items - Light Ball", () => {
const consoleSpy = vi.spyOn(console, "log");
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
// Checking console log to make sure Light Ball is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
@ -84,7 +84,7 @@ describe("Items - Light Ball", () => {
it("LIGHT_BALL held by PIKACHU", async () => {
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const atkStat = partyMember.getStat(Stat.ATK);
const spAtkStat = partyMember.getStat(Stat.SPATK);
@ -113,8 +113,7 @@ describe("Items - Light Ball", () => {
it("LIGHT_BALL held by fused PIKACHU (base)", async () => {
await game.classicMode.startBattle([SpeciesId.PIKACHU, SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -152,8 +151,7 @@ describe("Items - Light Ball", () => {
it("LIGHT_BALL held by fused PIKACHU (part)", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK, SpeciesId.PIKACHU]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -191,7 +189,7 @@ describe("Items - Light Ball", () => {
it("LIGHT_BALL not held by PIKACHU", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const atkStat = partyMember.getStat(Stat.ATK);
const spAtkStat = partyMember.getStat(Stat.SPATK);

View File

@ -33,7 +33,7 @@ describe("Items - Metal Powder", () => {
const consoleSpy = vi.spyOn(console, "log");
await game.classicMode.startBattle([SpeciesId.DITTO]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
// Checking console log to make sure Metal Powder is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
@ -84,7 +84,7 @@ describe("Items - Metal Powder", () => {
it("METAL_POWDER held by DITTO", async () => {
await game.classicMode.startBattle([SpeciesId.DITTO]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const defStat = partyMember.getStat(Stat.DEF);
@ -107,8 +107,7 @@ describe("Items - Metal Powder", () => {
it("METAL_POWDER held by fused DITTO (base)", async () => {
await game.classicMode.startBattle([SpeciesId.DITTO, SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -140,8 +139,7 @@ describe("Items - Metal Powder", () => {
it("METAL_POWDER held by fused DITTO (part)", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK, SpeciesId.DITTO]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -173,7 +171,7 @@ describe("Items - Metal Powder", () => {
it("METAL_POWDER not held by DITTO", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const defStat = partyMember.getStat(Stat.DEF);

View File

@ -33,7 +33,7 @@ describe("Items - Quick Powder", () => {
const consoleSpy = vi.spyOn(console, "log");
await game.classicMode.startBattle([SpeciesId.DITTO]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
// Checking console log to make sure Quick Powder is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
@ -84,7 +84,7 @@ describe("Items - Quick Powder", () => {
it("QUICK_POWDER held by DITTO", async () => {
await game.classicMode.startBattle([SpeciesId.DITTO]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const spdStat = partyMember.getStat(Stat.SPD);
@ -107,8 +107,7 @@ describe("Items - Quick Powder", () => {
it("QUICK_POWDER held by fused DITTO (base)", async () => {
await game.classicMode.startBattle([SpeciesId.DITTO, SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -140,8 +139,7 @@ describe("Items - Quick Powder", () => {
it("QUICK_POWDER held by fused DITTO (part)", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK, SpeciesId.DITTO]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -173,7 +171,7 @@ describe("Items - Quick Powder", () => {
it("QUICK_POWDER not held by DITTO", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const spdStat = partyMember.getStat(Stat.SPD);

View File

@ -33,7 +33,7 @@ describe("Items - Thick Club", () => {
const consoleSpy = vi.spyOn(console, "log");
await game.classicMode.startBattle([SpeciesId.CUBONE]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
// Checking console log to make sure Thick Club is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
@ -84,7 +84,7 @@ describe("Items - Thick Club", () => {
it("THICK_CLUB held by CUBONE", async () => {
await game.classicMode.startBattle([SpeciesId.CUBONE]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const atkStat = partyMember.getStat(Stat.ATK);
@ -107,7 +107,7 @@ describe("Items - Thick Club", () => {
it("THICK_CLUB held by MAROWAK", async () => {
await game.classicMode.startBattle([SpeciesId.MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const atkStat = partyMember.getStat(Stat.ATK);
@ -130,7 +130,7 @@ describe("Items - Thick Club", () => {
it("THICK_CLUB held by ALOLA_MAROWAK", async () => {
await game.classicMode.startBattle([SpeciesId.ALOLA_MAROWAK]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const atkStat = partyMember.getStat(Stat.ATK);
@ -157,8 +157,7 @@ describe("Items - Thick Club", () => {
await game.classicMode.startBattle([species[randSpecies], SpeciesId.PIKACHU]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -194,8 +193,7 @@ describe("Items - Thick Club", () => {
await game.classicMode.startBattle([SpeciesId.PIKACHU, species[randSpecies]]);
const partyMember = game.scene.getPlayerParty()[0];
const ally = game.scene.getPlayerParty()[1];
const [partyMember, ally] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@ -227,7 +225,7 @@ describe("Items - Thick Club", () => {
it("THICK_CLUB not held by CUBONE", async () => {
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
const partyMember = game.scene.getPlayerParty()[0];
const partyMember = game.field.getPlayerPokemon();
const atkStat = partyMember.getStat(Stat.ATK);

View File

@ -46,7 +46,7 @@ describe("Moves - Dragon Rage", () => {
await game.classicMode.startBattle();
partyPokemon = game.scene.getPlayerParty()[0];
partyPokemon = game.field.getPlayerPokemon();
enemyPokemon = game.field.getEnemyPokemon();
});

View File

@ -76,10 +76,9 @@ describe("Moves - Dragon Tail", () => {
game.override.battleStyle("double").enemyMoveset(MoveId.SPLASH).enemyAbility(AbilityId.ROUGH_SKIN);
await game.classicMode.startBattle([SpeciesId.DRATINI, SpeciesId.DRATINI, SpeciesId.WAILORD, SpeciesId.WAILORD]);
const leadPokemon = game.scene.getPlayerParty()[0]!;
const leadPokemon = game.field.getPlayerPokemon();
const enemyLeadPokemon = game.scene.getEnemyParty()[0]!;
const enemySecPokemon = game.scene.getEnemyParty()[1]!;
const [enemyLeadPokemon, enemySecPokemon] = game.scene.getEnemyParty();
game.move.select(MoveId.DRAGON_TAIL, 0, BattlerIndex.ENEMY);
game.move.select(MoveId.SPLASH, 1);
@ -105,11 +104,9 @@ describe("Moves - Dragon Tail", () => {
game.override.battleStyle("double").enemyMoveset(MoveId.SPLASH).enemyAbility(AbilityId.ROUGH_SKIN);
await game.classicMode.startBattle([SpeciesId.DRATINI, SpeciesId.DRATINI, SpeciesId.WAILORD, SpeciesId.WAILORD]);
const leadPokemon = game.scene.getPlayerParty()[0]!;
const secPokemon = game.scene.getPlayerParty()[1]!;
const [leadPokemon, secPokemon] = game.scene.getPlayerParty();
const enemyLeadPokemon = game.scene.getEnemyParty()[0]!;
const enemySecPokemon = game.scene.getEnemyParty()[1]!;
const [enemyLeadPokemon, enemySecPokemon] = game.scene.getEnemyParty();
game.move.select(MoveId.DRAGON_TAIL, 0, BattlerIndex.ENEMY);
// target the same pokemon, second move should be redirected after first flees

View File

@ -42,7 +42,7 @@ describe("Moves - Fissure", () => {
await game.classicMode.startBattle();
partyPokemon = game.scene.getPlayerParty()[0];
partyPokemon = game.field.getPlayerPokemon();
enemyPokemon = game.field.getEnemyPokemon();
});

View File

@ -80,19 +80,19 @@ describe("Moves - Parting Shot", () => {
// use Memento 3 times to debuff enemy
game.move.select(MoveId.MEMENTO);
await game.phaseInterceptor.to(FaintPhase);
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
expect(game.field.getPlayerPokemon().isFainted()).toBe(true);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to(TurnInitPhase, false);
game.move.select(MoveId.MEMENTO);
await game.phaseInterceptor.to(FaintPhase);
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
expect(game.field.getPlayerPokemon().isFainted()).toBe(true);
game.doSelectPartyPokemon(2);
await game.phaseInterceptor.to(TurnInitPhase, false);
game.move.select(MoveId.MEMENTO);
await game.phaseInterceptor.to(FaintPhase);
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
expect(game.field.getPlayerPokemon().isFainted()).toBe(true);
game.doSelectPartyPokemon(3);
// set up done
@ -177,8 +177,8 @@ describe("Moves - Parting Shot", () => {
game.move.select(MoveId.SPLASH);
// intentionally kill party pokemon, switch to second slot (now 1 party mon is fainted)
await game.killPokemon(game.scene.getPlayerParty()[0]);
expect(game.scene.getPlayerParty()[0].isFainted()).toBe(true);
await game.killPokemon(game.field.getPlayerPokemon());
expect(game.field.getPlayerPokemon().isFainted()).toBe(true);
await game.phaseInterceptor.run(MessagePhase);
game.doSelectPartyPokemon(1);

View File

@ -133,6 +133,6 @@ describe("Moves - Revival Blessing", () => {
await game.toNextTurn();
// If there are incorrectly two switch phases into this slot, the fainted pokemon will end up in slot 3
// Make sure it's still in slot 1
expect(game.scene.getEnemyParty()[0]).toBe(enemyFainting);
expect(game.field.getEnemyPokemon()).toBe(enemyFainting);
});
});

View File

@ -44,10 +44,10 @@ describe("Moves - Rollout", () => {
await game.classicMode.startBattle();
const playerPkm = game.scene.getPlayerParty()[0];
const playerPkm = game.field.getPlayerPokemon();
vi.spyOn(playerPkm, "stats", "get").mockReturnValue([500000, 1, 1, 1, 1, 1]); // HP, ATK, DEF, SPATK, SPDEF, SPD
const enemyPkm = game.scene.getEnemyParty()[0];
const enemyPkm = game.field.getEnemyPokemon();
vi.spyOn(enemyPkm, "stats", "get").mockReturnValue([500000, 1, 1, 1, 1, 1]); // HP, ATK, DEF, SPATK, SPDEF, SPD
vi.spyOn(enemyPkm, "getHeldItems").mockReturnValue([]); //no berries

View File

@ -6,7 +6,6 @@ import { MoveResult } from "#enums/move-result";
import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect";
import { RandomMoveAttr } from "#moves/move";
import { PokemonMove } from "#moves/pokemon-move";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@ -40,7 +39,7 @@ describe("Moves - Sketch", () => {
await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
const playerPokemon = game.field.getPlayerPokemon();
// can't use normal moveset override because we need to check moveset changes
playerPokemon.moveset = [new PokemonMove(MoveId.SKETCH), new PokemonMove(MoveId.SKETCH)];
game.move.changeMoveset(playerPokemon, [MoveId.SKETCH, MoveId.SKETCH]);
game.move.select(MoveId.SKETCH);
await game.phaseInterceptor.to("TurnEndPhase");
@ -62,7 +61,7 @@ describe("Moves - Sketch", () => {
await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
const playerPokemon = game.field.getPlayerPokemon();
const enemyPokemon = game.field.getEnemyPokemon();
playerPokemon.moveset = [new PokemonMove(MoveId.SKETCH), new PokemonMove(MoveId.GROWL)];
game.move.changeMoveset(playerPokemon, [MoveId.SKETCH, MoveId.GROWL]);
game.move.select(MoveId.GROWL);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
@ -88,8 +87,9 @@ describe("Moves - Sketch", () => {
game.override.enemyMoveset([MoveId.METRONOME]);
await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
const playerPokemon = game.field.getPlayerPokemon();
playerPokemon.moveset = [new PokemonMove(MoveId.SKETCH)];
game.move.changeMoveset(playerPokemon, MoveId.SKETCH);
// Opponent uses Metronome -> False Swipe, then player uses Sketch, which should sketch Metronome
game.move.select(MoveId.SKETCH);

View File

@ -48,7 +48,7 @@ describe("Moves - Spikes", () => {
game.doSwitchPokemon(1);
await game.toNextTurn();
const player = game.scene.getPlayerParty()[0];
const player = game.field.getPlayerPokemon();
expect(player.hp).toBe(player.getMaxHp());
});
@ -62,7 +62,7 @@ describe("Moves - Spikes", () => {
game.move.select(MoveId.ROAR);
await game.toNextTurn();
const enemy = game.scene.getEnemyParty()[0];
const enemy = game.field.getEnemyPokemon();
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
});
@ -77,7 +77,7 @@ describe("Moves - Spikes", () => {
game.forceEnemyToSwitch();
await game.toNextTurn();
const enemy = game.scene.getEnemyParty()[0];
const enemy = game.field.getEnemyPokemon();
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
});

View File

@ -50,7 +50,7 @@ describe("Moves - Tackle", () => {
const moveToUse = MoveId.TACKLE;
await game.classicMode.startBattle([SpeciesId.MIGHTYENA]);
game.scene.currentBattle.enemyParty[0].stats[Stat.DEF] = 50;
game.scene.getPlayerParty()[0].stats[Stat.ATK] = 50;
game.field.getPlayerPokemon().stats[Stat.ATK] = 50;
const hpOpponent = game.scene.currentBattle.enemyParty[0].hp;

View File

@ -72,7 +72,7 @@ describe("Moves - Tera Starstorm", () => {
it("targets both opponents in a double battle when used by Terapagos immediately after terastallizing", async () => {
await game.classicMode.startBattle([SpeciesId.TERAPAGOS]);
const terapagos = game.scene.getPlayerParty()[0];
const terapagos = game.field.getPlayerPokemon();
terapagos.isTerastallized = false;
game.move.selectWithTera(MoveId.TERA_STARSTORM, 0);
@ -89,7 +89,7 @@ describe("Moves - Tera Starstorm", () => {
it("targets only one opponent in a double battle when used by Terapagos without terastallizing", async () => {
await game.classicMode.startBattle([SpeciesId.TERAPAGOS]);
const terapagos = game.scene.getPlayerParty()[0];
const terapagos = game.field.getPlayerPokemon();
terapagos.isTerastallized = false;
game.move.select(MoveId.TERA_STARSTORM, 0, BattlerIndex.ENEMY);
@ -106,8 +106,7 @@ describe("Moves - Tera Starstorm", () => {
it("applies the effects when Terapagos in Stellar Form is fused with another Pokemon", async () => {
await game.classicMode.startBattle([SpeciesId.TERAPAGOS, SpeciesId.CHARMANDER, SpeciesId.MAGIKARP]);
const fusionedMon = game.scene.getPlayerParty()[0];
const magikarp = game.scene.getPlayerParty()[2];
const [fusionedMon, , magikarp] = game.scene.getPlayerParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
fusionedMon.fusionSpecies = magikarp.species;

View File

@ -7,7 +7,6 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { ShinyRateBoosterModifier } from "#modifiers/modifier";
import { PokemonMove } from "#moves/pokemon-move";
import { AnOfferYouCantRefuseEncounter } from "#mystery-encounters/an-offer-you-cant-refuse-encounter";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
@ -207,9 +206,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => {
game.override.ability(AbilityId.SYNCHRONIZE); // Not an extortion ability, so we can test extortion move
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, [SpeciesId.ABRA]);
const party = scene.getPlayerParty();
const abra = party.find(pkm => pkm.species.speciesId === SpeciesId.ABRA)!;
abra.moveset = [new PokemonMove(MoveId.BEAT_UP)];
const abra = game.field.getPlayerPokemon();
game.move.changeMoveset(abra, MoveId.BEAT_UP);
const expBefore = abra.exp;
await runMysteryEncounterToEnd(game, 2);

View File

@ -230,7 +230,7 @@ describe("Clowning Around - Mystery Encounter", () => {
// Stop next battle before it runs
await game.phaseInterceptor.to(NewBattlePhase, false);
const leadPokemon = scene.getPlayerParty()[0];
const leadPokemon = game.field.getPlayerPokemon();
expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain);
});
});
@ -263,30 +263,30 @@ describe("Clowning Around - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
// Set some moves on party for attack type booster generation
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.TACKLE), new PokemonMove(MoveId.THIEF)];
game.move.changeMoveset(game.field.getPlayerPokemon(), [MoveId.TACKLE, MoveId.THIEF]);
// 2 Sitrus Berries on lead
scene.modifiers = [];
let itemType = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 2, itemType);
// 2 Ganlon Berries on lead
itemType = generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 2, itemType);
// 5 Golden Punch on lead (ultra)
itemType = generateModifierType(modifierTypes.GOLDEN_PUNCH) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 5, itemType);
// 5 Lucky Egg on lead (ultra)
itemType = generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 5, itemType);
// 3 Soothe Bell on lead (great tier, but counted as ultra by this ME)
itemType = generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 3, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 3, itemType);
// 5 Soul Dew on lead (rogue)
itemType = generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 5, itemType);
// 2 Golden Egg on lead (rogue)
itemType = generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType);
await addItemToPokemon(scene, game.field.getPlayerPokemon(), 2, itemType);
// 5 Soul Dew on second party pokemon (these should not change)
itemType = generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType;
@ -294,7 +294,7 @@ describe("Clowning Around - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
const leadItemsAfter = scene.getPlayerParty()[0].getHeldItems();
const leadItemsAfter = game.field.getPlayerPokemon().getHeldItems();
const ultraCountAfter = leadItemsAfter
.filter(m => m.type.tier === ModifierTier.ULTRA)
.reduce((a, b) => a + b.stackCount, 0);
@ -348,14 +348,14 @@ describe("Clowning Around - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
// Same type moves on lead
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.ICE_BEAM), new PokemonMove(MoveId.SURF)];
game.move.changeMoveset(game.field.getPlayerPokemon(), [MoveId.ICE_BEAM, MoveId.SURF]);
// Different type moves on second
scene.getPlayerParty()[1].moveset = [new PokemonMove(MoveId.GRASS_KNOT), new PokemonMove(MoveId.ELECTRO_BALL)];
game.move.changeMoveset(scene.getPlayerParty()[1], [MoveId.GRASS_KNOT, MoveId.ELECTRO_BALL]);
// No moves on third
scene.getPlayerParty()[2].moveset = [];
await runMysteryEncounterToEnd(game, 3);
const leadTypesAfter = scene.getPlayerParty()[0].getTypes();
const leadTypesAfter = game.field.getPlayerPokemon().getTypes();
const secondaryTypesAfter = scene.getPlayerParty()[1].getTypes();
const thirdTypesAfter = scene.getPlayerParty()[2].getTypes();

View File

@ -6,7 +6,6 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode";
import { PokemonMove } from "#moves/pokemon-move";
import { DancingLessonsEncounter } from "#mystery-encounters/dancing-lessons-encounter";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
@ -100,7 +99,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
// Make party lead's level arbitrarily high to not get KOed by move
const partyLead = scene.getPlayerParty()[0];
const partyLead = game.field.getPlayerPokemon();
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true);
@ -121,7 +120,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
it("should have a Baton in the rewards after battle", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
// Make party lead's level arbitrarily high to not get KOed by move
const partyLead = scene.getPlayerParty()[0];
const partyLead = game.field.getPlayerPokemon();
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true);
@ -159,7 +158,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene.phaseManager, "unshiftPhase");
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
scene.getPlayerParty()[0].moveset = [];
game.field.getPlayerPokemon().moveset = [];
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof LearnMovePhase).map(p => p[0]);
@ -171,7 +170,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
scene.getPlayerParty()[0].moveset = [];
game.field.getPlayerPokemon().moveset = [];
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
@ -199,7 +198,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
it("should add Oricorio to the party", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
const partyCountBefore = scene.getPlayerParty().length;
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.DRAGON_DANCE)];
game.move.changeMoveset(game.field.getPlayerPokemon(), MoveId.DRAGON_DANCE);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
const partyCountAfter = scene.getPlayerParty().length;
@ -238,7 +237,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.DRAGON_DANCE)];
game.move.changeMoveset(game.field.getPlayerPokemon(), MoveId.DRAGON_DANCE);
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1 });
expect(leaveEncounterWithoutBattleSpy).toBeCalled();

View File

@ -201,7 +201,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 2 Sitrus berries on party lead
scene.modifiers = [];
const sitrus = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS])!;
const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
const sitrusMod = sitrus.newModifier(game.field.getPlayerPokemon()) as BerryModifier;
sitrusMod.stackCount = 2;
scene.addModifier(sitrusMod, true, false, false, true);
await scene.updateModifiers(true);
@ -222,7 +222,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead
scene.modifiers = [];
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier;
const modifier = revSeed.newModifier(game.field.getPlayerPokemon()) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -248,7 +248,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const sitrus = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS])!;
// Sitrus berries on party
const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
const sitrusMod = sitrus.newModifier(game.field.getPlayerPokemon()) as BerryModifier;
sitrusMod.stackCount = 2;
scene.addModifier(sitrusMod, true, false, false, true);
await scene.updateModifiers(true);
@ -277,7 +277,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier;
const modifier = revSeed.newModifier(game.field.getPlayerPokemon()) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -301,7 +301,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]);
const modifier = soulDew.newModifier(game.field.getPlayerPokemon());
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -329,7 +329,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier;
const modifier = revSeed.newModifier(game.field.getPlayerPokemon()) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -363,7 +363,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 2 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
const modifier = soulDew.newModifier(game.field.getPlayerPokemon()) as PokemonNatureWeightModifier;
modifier.stackCount = 2;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -384,7 +384,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
const modifier = soulDew.newModifier(game.field.getPlayerPokemon()) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -410,7 +410,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
const modifier = soulDew.newModifier(game.field.getPlayerPokemon()) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -434,7 +434,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead
scene.modifiers = [];
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!;
const modifier = revSeed.newModifier(scene.getPlayerParty()[0]);
const modifier = revSeed.newModifier(game.field.getPlayerPokemon());
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -463,7 +463,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
const modifier = soulDew.newModifier(game.field.getPlayerPokemon()) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);

View File

@ -6,7 +6,6 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode";
import { PokemonMove } from "#moves/pokemon-move";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import { FightOrFlightEncounter } from "#mystery-encounters/fight-or-flight-encounter";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
@ -178,7 +177,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
// Mock moveset
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.KNOCK_OFF)];
game.move.changeMoveset(game.field.getPlayerPokemon(), MoveId.KNOCK_OFF);
const item = game.scene.currentBattle.mysteryEncounter!.misc;
await runMysteryEncounterToEnd(game, 2);

View File

@ -105,14 +105,14 @@ describe("Global Trade System - Mystery Encounter", () => {
it("Should trade a Pokemon from the player's party for the first of 3 Pokemon options", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
const speciesBefore = scene.getPlayerParty()[0].species.speciesId;
const speciesBefore = game.field.getPlayerPokemon().species.speciesId;
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1, optionNo: 1 });
const speciesAfter = scene.getPlayerParty().at(-1)?.species.speciesId;
expect(speciesAfter).toBeDefined();
expect(speciesBefore).not.toBe(speciesAfter);
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
expect(defaultParty).not.toContain(speciesAfter);
});
it("Should trade a Pokemon from the player's party for the second of 3 Pokemon options", async () => {
@ -220,7 +220,7 @@ describe("Global Trade System - Mystery Encounter", () => {
// Set 2 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
const modifier = soulDew.newModifier(game.field.getPlayerPokemon()) as PokemonNatureWeightModifier;
modifier.stackCount = 2;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -247,7 +247,7 @@ describe("Global Trade System - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!;
const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier;
const modifier = soulDew.newModifier(game.field.getPlayerPokemon()) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);

View File

@ -5,7 +5,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { PokemonMove } from "#moves/pokemon-move";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#mystery-encounters/mystery-encounters";
@ -110,7 +109,7 @@ describe("Part-Timer - Mystery Encounter", () => {
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(1), true, false);
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getPlayerParty()[0].moveset;
const moves = game.field.getPlayerPokemon().moveset;
for (const move of moves) {
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
}
@ -233,7 +232,9 @@ describe("Part-Timer - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
// Mock movesets
scene.getPlayerParty().forEach(p => (p.moveset = []));
scene.getPlayerParty().forEach(p => {
p.moveset = [];
});
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
@ -257,14 +258,14 @@ describe("Part-Timer - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.PART_TIMER, defaultParty);
// Mock moveset
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.ATTRACT)];
game.move.changeMoveset(game.field.getPlayerPokemon(), MoveId.ATTRACT);
await runMysteryEncounterToEnd(game, 3);
expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(2.5), true, false);
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getPlayerParty()[0].moveset;
const moves = game.field.getPlayerPokemon().moveset;
for (const move of moves) {
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
expect(move.getMovePp() - move.ppUsed).toBe(2);
}
});

View File

@ -11,7 +11,6 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import type { BerryModifier } from "#modifiers/modifier";
import { PokemonMove } from "#moves/pokemon-move";
import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils";
import { generateModifierType } from "#mystery-encounters/encounter-phase-utils";
import * as MysteryEncounters from "#mystery-encounters/mystery-encounters";
@ -214,11 +213,11 @@ describe("Uncommon Breed - Mystery Encounter", () => {
// Berries on party lead
const sitrus = generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS])!;
const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
const sitrusMod = sitrus.newModifier(game.field.getPlayerPokemon()) as BerryModifier;
sitrusMod.stackCount = 2;
scene.addModifier(sitrusMod, true, false, false, true);
const ganlon = generateModifierType(modifierTypes.BERRY, [BerryType.GANLON])!;
const ganlonMod = ganlon.newModifier(scene.getPlayerParty()[0]) as BerryModifier;
const ganlonMod = ganlon.newModifier(game.field.getPlayerPokemon()) as BerryModifier;
ganlonMod.stackCount = 3;
scene.addModifier(ganlonMod, true, false, false, true);
await scene.updateModifiers(true);
@ -270,7 +269,7 @@ describe("Uncommon Breed - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
// Mock moveset
scene.getPlayerParty()[0].moveset = [new PokemonMove(MoveId.CHARM)];
game.move.changeMoveset(game.field.getPlayerPokemon(), MoveId.CHARM);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();

View File

@ -112,7 +112,7 @@ describe("Weird Dream - Mystery Encounter", () => {
it("should transform the new party into new species, 2 at +90/+110, the rest at +40/50 BST", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
const pokemonPrior = scene.getPlayerParty().map(pokemon => pokemon);
const pokemonPrior = scene.getPlayerParty().slice();
const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal());
await runMysteryEncounterToEnd(game, 1);

View File

@ -38,7 +38,7 @@ describe("Form Change Phase", () => {
await game.classicMode.startBattle([SpeciesId.ZACIAN]);
// Before the form change: Should be Hero form
const zacian = game.scene.getPlayerParty()[0];
const zacian = game.field.getPlayerPokemon();
expect(zacian.getFormKey()).toBe("hero-of-many-battles");
expect(zacian.getTypes()).toStrictEqual([PokemonType.FAIRY]);
expect(zacian.calculateBaseStats()).toStrictEqual([92, 120, 115, 80, 115, 138]);

View File

@ -0,0 +1,82 @@
import * as account from "#app/account";
import * as bypassLoginModule from "#app/global-vars/bypass-login";
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import type { SessionSaveData } from "#app/system/game-data";
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { GameManager } from "#test/test-utils/game-manager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("System - Rename Run", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([MoveId.SPLASH])
.battleStyle("single")
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
describe("renameSession", () => {
beforeEach(() => {
vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(false);
vi.spyOn(account, "updateUserInfo").mockImplementation(async () => [true, 1]);
});
it("should return false if slotId < 0", async () => {
const result = await game.scene.gameData.renameSession(-1, "Named Run");
expect(result).toEqual(false);
});
it("should return false if getSession returns null", async () => {
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue(null as unknown as SessionSaveData);
const result = await game.scene.gameData.renameSession(-1, "Named Run");
expect(result).toEqual(false);
});
it("should return true if bypassLogin is true", async () => {
vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(true);
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
const result = await game.scene.gameData.renameSession(0, "Named Run");
expect(result).toEqual(true);
});
it("should return false if api returns error", async () => {
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("Unknown Error!");
const result = await game.scene.gameData.renameSession(0, "Named Run");
expect(result).toEqual(false);
});
it("should return true if api is succesfull", async () => {
vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData);
vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("");
const result = await game.scene.gameData.renameSession(0, "Named Run");
expect(result).toEqual(true);
expect(account.updateUserInfo).toHaveBeenCalled();
});
});
});

View File

@ -90,10 +90,10 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
expect(game.scene.getPlayerParty()[0].variant).toBe(2);
expect(game.scene.getPlayerParty()[0].gender).toBe(Gender.MALE);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(true);
expect(game.field.getPlayerPokemon().variant).toBe(2);
expect(game.field.getPlayerPokemon().gender).toBe(Gender.MALE);
});
it("Bulbasaur - shiny - variant 2 female hardy overgrow", async () => {
@ -151,11 +151,11 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
expect(game.scene.getPlayerParty()[0].variant).toBe(2);
expect(game.scene.getPlayerParty()[0].nature).toBe(Nature.HARDY);
expect(game.scene.getPlayerParty()[0].getAbility().id).toBe(AbilityId.OVERGROW);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(true);
expect(game.field.getPlayerPokemon().variant).toBe(2);
expect(game.field.getPlayerPokemon().nature).toBe(Nature.HARDY);
expect(game.field.getPlayerPokemon().getAbility().id).toBe(AbilityId.OVERGROW);
});
it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async () => {
@ -215,12 +215,12 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
expect(game.scene.getPlayerParty()[0].variant).toBe(2);
expect(game.scene.getPlayerParty()[0].gender).toBe(Gender.FEMALE);
expect(game.scene.getPlayerParty()[0].nature).toBe(Nature.LONELY);
expect(game.scene.getPlayerParty()[0].getAbility().id).toBe(AbilityId.CHLOROPHYLL);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(true);
expect(game.field.getPlayerPokemon().variant).toBe(2);
expect(game.field.getPlayerPokemon().gender).toBe(Gender.FEMALE);
expect(game.field.getPlayerPokemon().nature).toBe(Nature.LONELY);
expect(game.field.getPlayerPokemon().getAbility().id).toBe(AbilityId.CHLOROPHYLL);
});
it("Bulbasaur - shiny - variant 2 female", async () => {
@ -278,10 +278,10 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
expect(game.scene.getPlayerParty()[0].variant).toBe(2);
expect(game.scene.getPlayerParty()[0].gender).toBe(Gender.FEMALE);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(true);
expect(game.field.getPlayerPokemon().variant).toBe(2);
expect(game.field.getPlayerPokemon().gender).toBe(Gender.FEMALE);
});
it("Bulbasaur - not shiny", async () => {
@ -339,9 +339,9 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(false);
expect(game.scene.getPlayerParty()[0].variant).toBe(0);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(false);
expect(game.field.getPlayerPokemon().variant).toBe(0);
});
it("Bulbasaur - shiny - variant 1", async () => {
@ -401,9 +401,9 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
expect(game.scene.getPlayerParty()[0].variant).toBe(1);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(true);
expect(game.field.getPlayerPokemon().variant).toBe(1);
});
it("Bulbasaur - shiny - variant 0", async () => {
@ -462,9 +462,9 @@ describe("UI - Starter select", () => {
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.scene.getPlayerParty()[0].shiny).toBe(true);
expect(game.scene.getPlayerParty()[0].variant).toBe(0);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.BULBASAUR);
expect(game.field.getPlayerPokemon().shiny).toBe(true);
expect(game.field.getPlayerPokemon().variant).toBe(0);
});
it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column", async () => {
@ -528,7 +528,7 @@ describe("UI - Starter select", () => {
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.CATERPIE);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.CATERPIE);
});
it("Check if first pokemon in party is nidoran_m from gen 1 and 2nd row, 4th column (cursor (9+4)-1)", async () => {
@ -594,6 +594,6 @@ describe("UI - Starter select", () => {
saveSlotSelectUiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.whenAboutToRun(EncounterPhase);
expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(SpeciesId.NIDORAN_M);
expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.NIDORAN_M);
});
});