From 75cb8b22b99b4ce06d8d55cd6bb00e299a7c73b5 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Mon, 2 Jun 2025 16:31:53 +0100 Subject: [PATCH 01/12] 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. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Inês Simões --- src/system/game-data.ts | 42 +++++++ src/ui/run-info-ui-handler.ts | 4 + src/ui/save-slot-select-ui-handler.ts | 158 ++++++++++++++++++++++---- 3 files changed, 182 insertions(+), 22 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 4f251b212b1..f4b8318cbbb 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -126,6 +126,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, @@ -978,6 +979,47 @@ export class GameData { }); } + renameSession(slotId: number, newName: string): Promise { + return new Promise(async resolve => { + if (slotId < 0) { + return resolve(false); + } + + const sessionData: SessionSaveData | null = await this.getSession(slotId); + + if (!sessionData) return resolve(false); + + sessionData.runNameText = newName; + + const updatedDataStr = JSON.stringify(sessionData); + + if (bypassLogin) { + localStorage.setItem( + `sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, + encrypt(updatedDataStr, bypassLogin), + ); + resolve(true); + } else { + const encrypted = encrypt(updatedDataStr, bypassLogin); + const secretId = this.secretId; + const trainerId = this.trainerId; + + const error = await pokerogueApi.savedata.session.update( + { slot: slotId, trainerId, secretId, clientSessionId }, + encrypted, + ); + + if (!error) { + localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted); // Keep local in sync + resolve(true); + } else { + console.error("Failed to update session name:", error); + resolve(false); + } + } + }); + } + loadSession(slotId: number, sessionData?: SessionSaveData): Promise { // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this return new Promise(async (resolve, reject) => { diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 29f95c4e4c8..9284dc5d92c 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -206,6 +206,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); } /** diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index bcbe60265cd..a631332a44d 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -1,6 +1,7 @@ import { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; +import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import { UiMode } from "#enums/ui-mode"; // biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts` import * as Modifier from "#modifiers/modifier"; @@ -14,7 +15,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, @@ -32,6 +33,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { private uiMode: SaveSlotUiMode; private saveSlotSelectCallback: SaveSlotSelectCallback | null; + protected manageDataConfig: OptionSelectConfig; private scrollCursor = 0; @@ -100,6 +102,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { processInput(button: Button): boolean { const ui = this.getUi(); + const manageDataOptions: any[] = []; let success = false; let error = false; @@ -113,9 +116,107 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { } else { switch (this.uiMode) { case SaveSlotUiMode.LOAD: - this.saveSlotSelectCallback = null; - originalCallback?.(cursor); + 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_POKEMON, + { + 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; @@ -160,6 +261,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { } } else { this.saveSlotSelectCallback = null; + ui.showText("", 0); // Clear any UI text if needed originalCallback?.(-1); success = true; } @@ -266,33 +368,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 valueHeight = this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 76 : 76; //nocas + const cursorIncrement = cursorPosition * 76; if (this.sessionSlots[cursorPosition] && this.cursorObj) { const hasData = this.sessionSlots[cursorPosition].hasData; // 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); @@ -310,7 +413,8 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { revertSessionSlot(slotIndex: number): void { const sessionSlot = this.sessionSlots[slotIndex]; if (sessionSlot) { - sessionSlot.setPosition(0, slotIndex * 56); + const valueHeight = sessionSlot.saveData?.runNameText ? 76 : 76; + sessionSlot.setPosition(0, slotIndex * valueHeight); } } @@ -339,7 +443,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", }); @@ -373,12 +477,12 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { class SessionSlot extends Phaser.GameObjects.Container { public slotId: number; public hasData: 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; @@ -386,32 +490,42 @@ 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); } 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); + } const gameModeLabel = addTextObject( 8, - 5, + hasName ? 19 : 12, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${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, + hasName ? 33 : 26, + new Date(data.timestamp).toLocaleString(), + TextStyle.WINDOW, + ); this.add(timestampLabel); - const playTimeLabel = addTextObject(8, 33, getPlayTimeString(data.playTime), TextStyle.WINDOW); + const playTimeLabel = addTextObject(8, hasName ? 47 : 40, getPlayTimeString(data.playTime), TextStyle.WINDOW); this.add(playTimeLabel); - const pokemonIconsContainer = globalScene.add.container(144, 4); + const pokemonIconsContainer = globalScene.add.container(144, hasName ? 16 : 9); data.party.forEach((p: PokemonData, i: number) => { const iconContainer = globalScene.add.container(26 * i, 0); iconContainer.setScale(0.75); @@ -440,7 +554,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) { From 034de5d307e3d662c22135dda30db8bd91631643 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Wed, 4 Jun 2025 15:25:19 +0100 Subject: [PATCH 02/12] 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. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created a UiHandler RenameRunFormUiHandler (rename-run-ui-handler.ts), creating a frontend input overlay for the Name Run Feature. Signed-off-by: Matheus Alves Co-authored-by: Inês Simões --- src/enums/ui-mode.ts | 1 + src/system/game-data.ts | 5 +- src/ui/rename-run-ui-handler.ts | 49 ++++++++++++++++ src/ui/save-slot-select-ui-handler.ts | 4 +- src/ui/ui.ts | 3 + test/system/rename_run.test.ts | 82 +++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 src/ui/rename-run-ui-handler.ts create mode 100644 test/system/rename_run.test.ts diff --git a/src/enums/ui-mode.ts b/src/enums/ui-mode.ts index dcf6bd2a238..ac6480098d8 100644 --- a/src/enums/ui-mode.ts +++ b/src/enums/ui-mode.ts @@ -38,6 +38,7 @@ export enum UiMode { UNAVAILABLE, CHALLENGE_SELECT, RENAME_POKEMON, + RENAME_RUN, RUN_HISTORY, RUN_INFO, TEST_DIALOGUE, diff --git a/src/system/game-data.ts b/src/system/game-data.ts index f4b8318cbbb..3cb74aacc01 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -979,7 +979,7 @@ export class GameData { }); } - renameSession(slotId: number, newName: string): Promise { + async renameSession(slotId: number, newName: string): Promise { return new Promise(async resolve => { if (slotId < 0) { return resolve(false); @@ -1010,7 +1010,8 @@ export class GameData { ); if (!error) { - localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted); // Keep local in sync + localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted); + await updateUserInfo(); resolve(true); } else { console.error("Failed to update session name:", error); diff --git a/src/ui/rename-run-ui-handler.ts b/src/ui/rename-run-ui-handler.ts new file mode 100644 index 00000000000..0ba27f760be --- /dev/null +++ b/src/ui/rename-run-ui-handler.ts @@ -0,0 +1,49 @@ +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; +import i18next from "i18next"; + +export default 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)) { + 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; + } + return false; + } +} diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index a631332a44d..19dc90f7d22 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -131,11 +131,11 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { handler: () => { globalScene.ui.revertMode(); ui.setOverlayMode( - UiMode.RENAME_POKEMON, + UiMode.RENAME_RUN, { buttonActions: [ (sanitizedName: string) => { - const name = decodeURIComponent((atob(sanitizedName))); + const name = decodeURIComponent(atob(sanitizedName)); globalScene.gameData.renameSession(cursor, name).then(response => { if (response[0] === false) { globalScene.reset(true); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 394409bcb9d..79e3c7a0cef 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -58,6 +58,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, @@ -96,6 +97,7 @@ const noTransitionModes = [ UiMode.SESSION_RELOAD, UiMode.UNAVAILABLE, UiMode.RENAME_POKEMON, + UiMode.RENAME_RUN, UiMode.TEST_DIALOGUE, UiMode.AUTO_COMPLETE, UiMode.ADMIN, @@ -165,6 +167,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), diff --git a/test/system/rename_run.test.ts b/test/system/rename_run.test.ts new file mode 100644 index 00000000000..2b728525a7c --- /dev/null +++ b/test/system/rename_run.test.ts @@ -0,0 +1,82 @@ +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 { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import * as account from "#app/account"; + +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([Moves.SPLASH]) + .battleStyle("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.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(); + }); + }); +}); From e5cd77016f5271c29c9e5e09f9d44813243e7844 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Thu, 5 Jun 2025 17:31:52 +0100 Subject: [PATCH 03/12] Fixed formating and best practices issues: Rewrote renameSession to be more inline with other API call funtions, removed debugging comments and whitespaces. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus Alves Co-authored-by: Inês Simões --- src/system/game-data.ts | 43 +++++++++++++-------------- src/ui/save-slot-select-ui-handler.ts | 4 +-- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 3cb74aacc01..70d1d7ba755 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -984,39 +984,38 @@ export class GameData { if (slotId < 0) { return resolve(false); } - const sessionData: SessionSaveData | null = await this.getSession(slotId); - if (!sessionData) return resolve(false); + if (!sessionData) { + return resolve(false); + } sessionData.runNameText = newName; - const updatedDataStr = JSON.stringify(sessionData); + const encrypted = encrypt(updatedDataStr, bypassLogin); + const secretId = this.secretId; + const trainerId = this.trainerId; - if (bypassLogin) { + if (!bypassLogin) { + 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); + } + }); + } else { localStorage.setItem( `sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypt(updatedDataStr, bypassLogin), ); resolve(true); - } else { - const encrypted = encrypt(updatedDataStr, bypassLogin); - const secretId = this.secretId; - const trainerId = this.trainerId; - - const error = await pokerogueApi.savedata.session.update( - { slot: slotId, trainerId, secretId, clientSessionId }, - encrypted, - ); - - if (!error) { - localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted); - await updateUserInfo(); - resolve(true); - } else { - console.error("Failed to update session name:", error); - resolve(false); - } } }); } diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 19dc90f7d22..689669cbfe1 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -261,7 +261,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { } } else { this.saveSlotSelectCallback = null; - ui.showText("", 0); // Clear any UI text if needed + ui.showText("", 0); originalCallback?.(-1); success = true; } @@ -385,7 +385,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { this.sessionSlotsContainer.add(this.cursorObj); } const cursorPosition = cursor + this.scrollCursor; - const valueHeight = this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 76 : 76; //nocas + const valueHeight = this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 76 : 76; const cursorIncrement = cursorPosition * 76; if (this.sessionSlots[cursorPosition] && this.cursorObj) { const hasData = this.sessionSlots[cursorPosition].hasData; From 8dc3591c9e5448965e2d897bfaee0fe24b539fae Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Thu, 12 Jun 2025 12:14:04 +0100 Subject: [PATCH 04/12] Minor Sanitization for aesthetics Deleting the input when closing the overlay for aesthetics purpose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus Alves Co-authored-by: Inês Simões --- src/ui/rename-run-ui-handler.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ui/rename-run-ui-handler.ts b/src/ui/rename-run-ui-handler.ts index 0ba27f760be..0033079de63 100644 --- a/src/ui/rename-run-ui-handler.ts +++ b/src/ui/rename-run-ui-handler.ts @@ -35,6 +35,11 @@ export default class RenameRunFormUiHandler extends FormModalUiHandler { show(args: any[]): boolean { if (super.show(args)) { + if (this.inputs?.length) { + this.inputs.forEach(input => { + input.text = ""; + }); + } const config = args[0] as ModalConfig; this.submitAction = _ => { this.sanitizeInputs(); From b25b8bde6af0c02599113e9bbb6acccd7a129a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Sim=C3=B5es?= Date: Thu, 12 Jun 2025 22:23:15 +0100 Subject: [PATCH 05/12] Fixed minor rebase alterations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- test/system/rename_run.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/system/rename_run.test.ts b/test/system/rename_run.test.ts index 2b728525a7c..38906129e15 100644 --- a/test/system/rename_run.test.ts +++ b/test/system/rename_run.test.ts @@ -1,12 +1,12 @@ 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 { Abilities } from "#enums/abilities"; -import { Moves } from "#enums/moves"; +import { MoveId } from "#enums/move-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as account from "#app/account"; +import { AbilityId } from "#enums/ability-id"; describe("System - Rename Run", () => { let phaserGame: Phaser.Game; @@ -21,10 +21,10 @@ describe("System - Rename Run", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([Moves.SPLASH]) + .moveset([MoveId.SPLASH]) .battleStyle("single") - .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset(Moves.SPLASH); + .enemyAbility(AbilityId.BALL_FETCH) + .enemyMoveset(MoveId.SPLASH); }); afterEach(() => { From 116daa4baac9ae3d4484c8ef355aa5d430d44d23 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Fri, 20 Jun 2025 14:08:28 +0100 Subject: [PATCH 06/12] 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. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/system/game-data.ts | 33 ++++++++++++-------- src/ui/save-slot-select-ui-handler.ts | 44 +++++++++++++++++++-------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 70d1d7ba755..1cea0d9805c 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -990,6 +990,10 @@ export class GameData { return resolve(false); } + if (newName === "") { + return resolve(true); + } + sessionData.runNameText = newName; const updatedDataStr = JSON.stringify(sessionData); const encrypted = encrypt(updatedDataStr, bypassLogin); @@ -997,19 +1001,22 @@ export class GameData { const trainerId = this.trainerId; if (!bypassLogin) { - 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); - } - }); + 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); + } + }); } else { localStorage.setItem( `sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 689669cbfe1..c0593009895 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -3,7 +3,6 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; 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"; @@ -13,6 +12,7 @@ import { addTextObject, TextStyle } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; +import { GameModes } from "#enums/game-modes"; const SESSION_SLOTS_COUNT = 5; const SLOTS_ON_SCREEN = 2; @@ -385,7 +385,6 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { this.sessionSlotsContainer.add(this.cursorObj); } const cursorPosition = cursor + this.scrollCursor; - const valueHeight = this.sessionSlots[prevSlotIndex ?? 0]?.saveData?.runNameText ? 76 : 76; const cursorIncrement = cursorPosition * 76; if (this.sessionSlots[cursorPosition] && this.cursorObj) { const hasData = this.sessionSlots[cursorPosition].hasData; @@ -413,7 +412,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { revertSessionSlot(slotIndex: number): void { const sessionSlot = this.sessionSlots[slotIndex]; if (sessionSlot) { - const valueHeight = sessionSlot.saveData?.runNameText ? 76 : 76; + const valueHeight = 76; sessionSlot.setPosition(0, slotIndex * valueHeight); } } @@ -498,34 +497,55 @@ class SessionSlot extends Phaser.GameObjects.Container { this.add(this.loadingLabel); } + decideFallback(data: SessionSaveData) { + let fallbackName; + switch (data.gameMode) { + case GameModes.CLASSIC: + fallbackName = `${GameMode.getModeName(data.gameMode)} (${globalScene.gameData.gameStats.classicSessionsPlayed + 1})`; + break; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + fallbackName = `${GameMode.getModeName(data.gameMode)} (${globalScene.gameData.gameStats.endlessSessionsPlayed + 1})`; + break; + case GameModes.DAILY: + const runDay = new Date(data.timestamp).toLocaleDateString(); + fallbackName = `${GameMode.getModeName(data.gameMode)} (${runDay})`; + break; + case GameModes.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, - hasName ? 19 : 12, + 19, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW, ); this.add(gameModeLabel); - const timestampLabel = addTextObject( - 8, - hasName ? 33 : 26, - 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, hasName ? 47 : 40, getPlayTimeString(data.playTime), TextStyle.WINDOW); + const playTimeLabel = addTextObject(8, 47, getPlayTimeString(data.playTime), TextStyle.WINDOW); this.add(playTimeLabel); - const pokemonIconsContainer = globalScene.add.container(144, hasName ? 16 : 9); + 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); From 428c175cb5f43c0b385041b5854f287fa476d71c Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Sun, 29 Jun 2025 13:17:14 +0100 Subject: [PATCH 07/12] Replace fallback name logic: use first active challenge instead of game mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Inês Simões --- src/ui/save-slot-select-ui-handler.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index c0593009895..9c6ca432320 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -507,12 +507,16 @@ class SessionSlot extends Phaser.GameObjects.Container { case GameModes.SPLICED_ENDLESS: fallbackName = `${GameMode.getModeName(data.gameMode)} (${globalScene.gameData.gameStats.endlessSessionsPlayed + 1})`; break; - case GameModes.DAILY: + case GameModes.DAILY: { const runDay = new Date(data.timestamp).toLocaleDateString(); fallbackName = `${GameMode.getModeName(data.gameMode)} (${runDay})`; break; + } case GameModes.CHALLENGE: - fallbackName = `${GameMode.getModeName(data.gameMode)}`; + fallbackName = data.challenges + .find(c => c.value !== 0) + ?.toChallenge() + .getName(); break; } return fallbackName; From 8594614e649572f70d2daae54d54e815e5ff40e5 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Tue, 22 Jul 2025 22:47:11 +0100 Subject: [PATCH 08/12] Rebasing and conflict resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus Alves Co-authored-by: Inês Simões --- src/system/game-data.ts | 36 ++++++++++++++++----------------- src/ui/rename-run-ui-handler.ts | 32 ++++++++++++++--------------- test/system/rename_run.test.ts | 6 +++--- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 1cea0d9805c..9204186bc5e 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1000,30 +1000,30 @@ export class GameData { const secretId = this.secretId; const trainerId = this.trainerId; - if (!bypassLogin) { - 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); - } - }); - } else { + 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); + } + }); }); } diff --git a/src/ui/rename-run-ui-handler.ts b/src/ui/rename-run-ui-handler.ts index 0033079de63..113f432693a 100644 --- a/src/ui/rename-run-ui-handler.ts +++ b/src/ui/rename-run-ui-handler.ts @@ -1,7 +1,7 @@ +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"; -import i18next from "i18next"; export default class RenameRunFormUiHandler extends FormModalUiHandler { getModalTitle(_config?: ModalConfig): string { @@ -34,21 +34,21 @@ export default class RenameRunFormUiHandler extends FormModalUiHandler { } show(args: any[]): boolean { - if (super.show(args)) { - 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; + if (!super.show(args)) { + return false; } - 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; } } diff --git a/test/system/rename_run.test.ts b/test/system/rename_run.test.ts index 38906129e15..b6e002086a0 100644 --- a/test/system/rename_run.test.ts +++ b/test/system/rename_run.test.ts @@ -1,12 +1,12 @@ +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/testUtils/gameManager"; +import { GameManager } from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import * as account from "#app/account"; -import { AbilityId } from "#enums/ability-id"; describe("System - Rename Run", () => { let phaserGame: Phaser.Game; From f5b44036e516d9760284ac8a63c047f3526cde35 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Tue, 22 Jul 2025 23:04:42 +0100 Subject: [PATCH 09/12] Lint fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus Alves Co-authored-by: Inês Simões --- src/ui/save-slot-select-ui-handler.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 9c6ca432320..ab2d1f2e010 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -1,8 +1,8 @@ import { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; -import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; 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"; @@ -12,7 +12,6 @@ import { addTextObject, TextStyle } from "#ui/text"; import { addWindow } from "#ui/ui-theme"; import { fixedInt, formatLargeNumber, getPlayTimeString, isNullOrUndefined } from "#utils/common"; import i18next from "i18next"; -import { GameModes } from "#enums/game-modes"; const SESSION_SLOTS_COUNT = 5; const SLOTS_ON_SCREEN = 2; @@ -583,7 +582,7 @@ class SessionSlot extends Phaser.GameObjects.Container { let visibleModifierIndex = 0; for (const m of data.modifiers) { const modifier = m.toModifier(Modifier[m.className]); - if (modifier instanceof Modifier.PokemonHeldItemModifier) { + if (modifier instanceof PokemonHeldItemModifier) { continue; } const icon = modifier?.getIcon(false); From b74849d3e21c04e1d9e088e29bfd30f05d1e360c Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Tue, 22 Jul 2025 23:11:13 +0100 Subject: [PATCH 10/12] Minor compile fix --- src/ui/save-slot-select-ui-handler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index ab2d1f2e010..4acca93c353 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -1,6 +1,8 @@ import { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; +import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import { Button } from "#enums/buttons"; +import { GameModes } from "#enums/game-modes"; import { UiMode } from "#enums/ui-mode"; // biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts` import * as Modifier from "#modifiers/modifier"; @@ -582,7 +584,7 @@ class SessionSlot extends Phaser.GameObjects.Container { let visibleModifierIndex = 0; for (const m of data.modifiers) { const modifier = m.toModifier(Modifier[m.className]); - if (modifier instanceof PokemonHeldItemModifier) { + if (modifier instanceof Modifier.PokemonHeldItemModifier) { continue; } const icon = modifier?.getIcon(false); From a6007cef1011f175eaf65c163c6ed85ef874442b Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Tue, 5 Aug 2025 10:46:15 +0100 Subject: [PATCH 11/12] Dependency resolved --- test/system/rename_run.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/system/rename_run.test.ts b/test/system/rename_run.test.ts index b6e002086a0..5031d84245f 100644 --- a/test/system/rename_run.test.ts +++ b/test/system/rename_run.test.ts @@ -4,7 +4,7 @@ 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/testUtils/gameManager"; +import { GameManager } from "#test/test-utils/game-manager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; From 8d1e5f120050a1fe6c00983bd95433e9dd185e42 Mon Sep 17 00:00:00 2001 From: Matheus Alves Date: Tue, 5 Aug 2025 10:59:20 +0100 Subject: [PATCH 12/12] Format name respected --- test/system/{rename_run.test.ts => rename-run.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/system/{rename_run.test.ts => rename-run.test.ts} (100%) diff --git a/test/system/rename_run.test.ts b/test/system/rename-run.test.ts similarity index 100% rename from test/system/rename_run.test.ts rename to test/system/rename-run.test.ts