From de53bebeb8aee837cb6e9024c63e9fa50883c346 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 5 Sep 2026 12:18:02 -0500 Subject: [PATCH] Remove the main menu scene now that it's just a persistent cutscene The main menu no longer needs its own scene lifecycle - it's started directly by the initial cutscene's CUTSCENE item chaining into main_menu.jsonc. Fold scenemainmenu.c's logic into uimainmenu.c (its only real caller) and drop SCENE_TYPE_MAIN_MENU entirely. Also arm the cutscene's onComplete callback unconditionally in uiMainMenuStartGame so the save-picker still opens on a fresh boot, not just on restart. Co-Authored-By: Claude Sonnet 5 --- .../loader/cutscene/assetcutsceneloader.c | 3 +- src/dusk/scene/CMakeLists.txt | 1 - src/dusk/scene/mainmenu/CMakeLists.txt | 9 -- src/dusk/scene/mainmenu/scenemainmenu.c | 99 ------------------- src/dusk/scene/mainmenu/scenemainmenu.h | 73 -------------- src/dusk/scene/scenetype.c | 7 -- src/dusk/scene/scenetype.h | 3 - src/dusk/ui/screen/mainmenu/uimainmenu.c | 70 ++++++++++++- src/dusk/ui/screen/mainmenu/uimainmenu.h | 45 +++++++++ 9 files changed, 114 insertions(+), 196 deletions(-) delete mode 100644 src/dusk/scene/mainmenu/CMakeLists.txt delete mode 100644 src/dusk/scene/mainmenu/scenemainmenu.c delete mode 100644 src/dusk/scene/mainmenu/scenemainmenu.h diff --git a/src/dusk/asset/loader/cutscene/assetcutsceneloader.c b/src/dusk/asset/loader/cutscene/assetcutsceneloader.c index a7697f44..d4be2600 100644 --- a/src/dusk/asset/loader/cutscene/assetcutsceneloader.c +++ b/src/dusk/asset/loader/cutscene/assetcutsceneloader.c @@ -158,7 +158,6 @@ static const assetcutsceneenumentry_t ASSET_CUTSCENE_BATTLE_STATE_TABLE[] = { static const assetcutsceneenumentry_t ASSET_CUTSCENE_SCENE_TYPE_TABLE[] = { { "NULL", SCENE_TYPE_NULL }, { "INITIAL", SCENE_TYPE_INITIAL }, - { "MAIN_MENU", SCENE_TYPE_MAIN_MENU }, { "OVERWORLD", SCENE_TYPE_OVERWORLD }, { "BATTLE", SCENE_TYPE_BATTLE }, }; @@ -411,7 +410,7 @@ errorret_t assetCutsceneParseItem( // within this sync load (see its ASSET_ENTRY_STATE_LOADING_SYNC case), // and the entry is locked forever (never unlocked) so the pointer // stays valid for the process lifetime, same convention as - // scene/mainmenu/scenemainmenu.c's MAIN_MENU_CUTSCENE_ENTRY. + // ui/screen/mainmenu/uimainmenu.c's MAIN_MENU_CUTSCENE_ENTRY. char_t path[ASSET_FILE_NAME_MAX]; int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name); if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) { diff --git a/src/dusk/scene/CMakeLists.txt b/src/dusk/scene/CMakeLists.txt index ea994c4a..8273e118 100644 --- a/src/dusk/scene/CMakeLists.txt +++ b/src/dusk/scene/CMakeLists.txt @@ -11,6 +11,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} # Subdirs add_subdirectory(initial) -add_subdirectory(mainmenu) add_subdirectory(overworld) add_subdirectory(battle) \ No newline at end of file diff --git a/src/dusk/scene/mainmenu/CMakeLists.txt b/src/dusk/scene/mainmenu/CMakeLists.txt deleted file mode 100644 index 3f71bfdc..00000000 --- a/src/dusk/scene/mainmenu/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - scenemainmenu.c -) diff --git a/src/dusk/scene/mainmenu/scenemainmenu.c b/src/dusk/scene/mainmenu/scenemainmenu.c deleted file mode 100644 index 8ca8cbb3..00000000 --- a/src/dusk/scene/mainmenu/scenemainmenu.c +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scenemainmenu.h" -#include "ui/screen/mainmenu/uimainmenu.h" -#include "ui/dialog/save/uiselectsave.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "scene/scene.h" -#include "asset/asset.h" -#include "assert/assert.h" - -// Loaded lazily on first activation and kept resident for the rest of the -// process - it's tiny and reused every time, so there's no benefit to -// unlocking/reloading it between attempts (same lifetime convention as -// e.g. LOCALE.entry). -static assetentry_t *MAIN_MENU_CUTSCENE_ENTRY = NULL; - -void sceneMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) { - if(slotIndex == UI_SELECT_SAVE_RESULT_NONE) { - uiMainMenuOpen(); - return; - } - - // TODO: load/start the game using the chosen save slot. - sceneSet(SCENE_TYPE_OVERWORLD); -} - -void sceneMainMenuOpenSelectSave(void *userData) { - uiSelectSaveOpen( - UI_SELECT_SAVE_TYPE_LOAD, sceneMainMenuSelectSaveResult, NULL - ); -} - -// Lazily locks/loads the main menu cutscene (runtime-loaded from -// assets/cutscenes/main_menu.jsonc, parsed directly at load time rather -// than compiled in, since it's player-facing flow rather than core engine -// wiring) and asserts it's ready to run. Shared by both the initial -// scene-entry start and the New-Game fallback restart below. -cutscene_t *sceneMainMenuLoadCutscene(void) { - if(MAIN_MENU_CUTSCENE_ENTRY == NULL) { - MAIN_MENU_CUTSCENE_ENTRY = assetLock( - "cutscenes/main_menu.jsonc", ASSET_LOADER_TYPE_CUTSCENE, NULL - ); - } - errorret_t result = assetRequireLoaded(MAIN_MENU_CUTSCENE_ENTRY); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - assertTrue(false, "Failed to load main menu cutscene asset"); - } - - return &MAIN_MENU_CUTSCENE_ENTRY->data.cutscene.cutscene; -} - -// Jumps the main menu cutscene (already running and idling since -// sceneMainMenuInit) forward to its NEW_GAME marker, which checks for a -// save device, loads every slot (checking/retry-on-error shape mirroring -// the initial scene's device lookup, see scene/initial/sceneinitial.c), -// and ends at the LOADED marker with no further action - -// sceneMainMenuOpenSelectSave is armed as the completion callback since a -// file can't store a native function pointer. -void sceneMainMenuStartGame(void) { - if(CUTSCENE_SYSTEM.scene == NULL) { - // The menu cutscene already ran to completion once (e.g. the player - // backed out of the save picker back to this menu) - restart it - // landing straight on NEW_GAME rather than replaying the idle/BGM - // setup from the top. - cutsceneSystemStartCutsceneAndGoToMarker( - sceneMainMenuLoadCutscene(), "NEW_GAME" - ); - cutsceneSystemSetOnComplete(sceneMainMenuOpenSelectSave); - } else { - cutsceneGoTo("NEW_GAME"); - } -} - -errorret_t sceneMainMenuInit(scenedata_t *sceneData) { - // Opening the main menu panel itself is now the cutscene's job (its - // first item is a UI_SHOW), not this function's. - cutsceneSystemStartCutscene(sceneMainMenuLoadCutscene()); - cutsceneSystemSetOnComplete(sceneMainMenuOpenSelectSave); - - errorOk(); -} - -errorret_t sceneMainMenuUpdate(scenedata_t *sceneData) { - errorOk(); -} - -errorret_t sceneMainMenuRender(scenedata_t *sceneData) { - errorOk(); -} - -errorret_t sceneMainMenuDispose(scenedata_t *sceneData) { - errorOk(); -} diff --git a/src/dusk/scene/mainmenu/scenemainmenu.h b/src/dusk/scene/mainmenu/scenemainmenu.h deleted file mode 100644 index 2e4c5419..00000000 --- a/src/dusk/scene/mainmenu/scenemainmenu.h +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scenebase.h" - -typedef struct cutscene_s cutscene_t; - -// Empty - the menu itself lives in ui/screen/mainmenu/uimainmenu.c. A byte -// placeholder keeps the struct non-empty for portability. -typedef struct { - uint8_t reserved; -} scenemainmenu_t; - -/** - * Initializes the main menu scene by starting the main menu cutscene, - * whose own first item opens the main menu panel (see UI_SHOW, - * rpg/cutscene/item/ui/cutsceneuishow.h) before it idles until - * sceneMainMenuStartGame jumps it to its NEW_GAME marker. - * - * @param sceneData The scene data used for this scene. - * @return An error if the init failed, or errorOk() if it succeeded. - */ -errorret_t sceneMainMenuInit(scenedata_t *sceneData); - -/** - * Lazily locks/loads the main menu cutscene asset (assets/cutscenes/ - * main_menu.cts) and returns it, asserting on failure. Kept resident for - * the rest of the process once loaded. - * - * @return The main menu cutscene. - */ -cutscene_t *sceneMainMenuLoadCutscene(void); - -/** - * Starts the "Start Game" flow: jumps the already-running main menu - * cutscene to its NEW_GAME marker (or restarts it there if it already ran - * to completion) to load every save slot - retrying on error, prompting - * if no save device is found - and open the load-game picker once it - * succeeds. Called by ui/screen/mainmenu/uimainmenu.c when the player - * selects Start Game. - */ -void sceneMainMenuStartGame(void); - -/** - * Updates the main menu scene. Currently a no-op - the menu drives - * itself via the global UI element pipeline. - * - * @param sceneData The scene data used for this scene. - * @return An error if the update failed, or errorOk() if it succeeded. - */ -errorret_t sceneMainMenuUpdate(scenedata_t *sceneData); - -/** - * Renders the main menu scene. Currently a no-op - the menu draws - * itself via the global UI element pipeline. - * - * @param sceneData The scene data used for this scene. - * @return An error if the render failed, or errorOk() if it succeeded. - */ -errorret_t sceneMainMenuRender(scenedata_t *sceneData); - -/** - * Disposes the main menu scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the dispose failed, or errorOk() if it succeeded. - */ -errorret_t sceneMainMenuDispose(scenedata_t *sceneData); diff --git a/src/dusk/scene/scenetype.c b/src/dusk/scene/scenetype.c index 15c4543e..3dad6627 100644 --- a/src/dusk/scene/scenetype.c +++ b/src/dusk/scene/scenetype.c @@ -17,13 +17,6 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = { .dispose = sceneInitialDispose }, - [SCENE_TYPE_MAIN_MENU] = { - .init = sceneMainMenuInit, - .update = sceneMainMenuUpdate, - .render = sceneMainMenuRender, - .dispose = sceneMainMenuDispose - }, - [SCENE_TYPE_OVERWORLD] = { .init = sceneOverworldInit, .update = sceneOverworldUpdate, diff --git a/src/dusk/scene/scenetype.h b/src/dusk/scene/scenetype.h index eb3dfc2b..677959ee 100644 --- a/src/dusk/scene/scenetype.h +++ b/src/dusk/scene/scenetype.h @@ -8,13 +8,11 @@ #pragma once #include "scene/scenebase.h" #include "scene/initial/sceneinitial.h" -#include "scene/mainmenu/scenemainmenu.h" #include "scene/overworld/sceneoverworld.h" #include "scene/battle/scenebattle.h" typedef union scenedata_u { sceneinitial_t initial; - scenemainmenu_t mainMenu; sceneoverworld_t overworld; scenebattle_t battle; } scenedata_t; @@ -32,7 +30,6 @@ typedef enum { SCENE_TYPE_NULL, SCENE_TYPE_INITIAL, - SCENE_TYPE_MAIN_MENU, SCENE_TYPE_OVERWORLD, SCENE_TYPE_BATTLE, diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.c b/src/dusk/ui/screen/mainmenu/uimainmenu.c index 4dd78f3d..ca05dee7 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.c +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.c @@ -16,7 +16,10 @@ #include "locale/localemanager.h" #include "asset/loader/locale/assetlocaleloader.h" #include "ui/dialog/uiconfirm.h" -#include "scene/mainmenu/scenemainmenu.h" +#include "ui/dialog/save/uiselectsave.h" +#include "rpg/cutscene/cutscenesystem.h" +#include "scene/scene.h" +#include "asset/asset.h" #define UI_MAIN_MENU_INDEX_START_GAME 0 #define UI_MAIN_MENU_INDEX_OPTIONS 1 @@ -24,10 +27,73 @@ uimainmenu_t UI_MAIN_MENU; +// Loaded lazily on first activation and kept resident for the rest of the +// process - it's tiny and reused every time, so there's no benefit to +// unlocking/reloading it between attempts (same lifetime convention as +// e.g. LOCALE.entry). The persistent cutscene is normally already running +// by the time this screen is reachable at all (chained straight from the +// initial cutscene's own CUTSCENE item, see assets/cutscenes/initial.jsonc +// and CUTSCENE_ITEM_TYPE_CUTSCENE in asset/loader/cutscene/ +// assetcutsceneloader.c) - this lock just gives uiMainMenuStartGame a +// stable pointer to restart it with if that run already completed. +static assetentry_t *MAIN_MENU_CUTSCENE_ENTRY = NULL; + void uiMainMenuQuitConfirmed(const bool_t result, void *user) { if(result) ENGINE.running = false; } +cutscene_t *uiMainMenuLoadCutscene(void) { + if(MAIN_MENU_CUTSCENE_ENTRY == NULL) { + MAIN_MENU_CUTSCENE_ENTRY = assetLock( + "cutscenes/main_menu.jsonc", ASSET_LOADER_TYPE_CUTSCENE, NULL + ); + } + errorret_t result = assetRequireLoaded(MAIN_MENU_CUTSCENE_ENTRY); + if(errorIsNotOk(result)) { + errorCatch(errorPrint(result)); + assertTrue(false, "Failed to load main menu cutscene asset"); + } + + return &MAIN_MENU_CUTSCENE_ENTRY->data.cutscene.cutscene; +} + +void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) { + if(slotIndex == UI_SELECT_SAVE_RESULT_NONE) { + uiMainMenuOpen(); + return; + } + + // TODO: load/start the game using the chosen save slot. + sceneSet(SCENE_TYPE_OVERWORLD); +} + +void uiMainMenuOpenSelectSave(void *userData) { + uiSelectSaveOpen( + UI_SELECT_SAVE_TYPE_LOAD, uiMainMenuSelectSaveResult, NULL + ); +} + +void uiMainMenuStartGame(void) { + if(CUTSCENE_SYSTEM.scene == NULL) { + // The menu cutscene already ran to completion once (e.g. the player + // backed out of the save picker back to this menu) - restart it + // landing straight on NEW_GAME rather than replaying the idle/BGM + // setup from the top. + cutsceneSystemStartCutsceneAndGoToMarker( + uiMainMenuLoadCutscene(), "NEW_GAME" + ); + } else { + cutsceneGoTo("NEW_GAME"); + } + + // Armed unconditionally, whether this just (re)started the cutscene or + // jumped one already running since boot (chained straight from the + // initial cutscene - see assets/cutscenes/initial.jsonc) - that first + // run is never otherwise armed by anything, so setting this only in the + // restart branch above would silently drop it on a fresh boot. + cutsceneSystemSetOnComplete(uiMainMenuOpenSelectSave); +} + void uiMainMenuSelected( const uimenu_t *menu, const uint8_t index, @@ -37,7 +103,7 @@ void uiMainMenuSelected( switch(index) { case UI_MAIN_MENU_INDEX_START_GAME: uiMenuClose(&UI_MAIN_MENU.menu); - sceneMainMenuStartGame(); + uiMainMenuStartGame(); break; case UI_MAIN_MENU_INDEX_OPTIONS: diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.h b/src/dusk/ui/screen/mainmenu/uimainmenu.h index 3a53c9ee..5e0a0436 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.h +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.h @@ -9,6 +9,8 @@ #include "error/error.h" #include "ui/widget/uimenu.h" +typedef struct cutscene_s cutscene_t; + #define UI_MAIN_MENU_ITEM_COUNT 3 #define UI_MAIN_MENU_WIDTH 200.0f #define UI_MAIN_MENU_HEIGHT 160.0f @@ -36,6 +38,49 @@ errorret_t uiMainMenuInit(void); */ void uiMainMenuOpen(void); +/** + * Lazily locks/loads the main menu cutscene asset (assets/cutscenes/ + * main_menu.jsonc, parsed directly at load time rather than compiled in) + * and returns it, asserting on failure. Kept resident for the rest of the + * process once loaded - the persistent cutscene is what actually drives + * this whole screen (opening this panel is its first item's job, see + * UI_SHOW/rpg/cutscene/item/ui/cutsceneuishow.h), this is just the shared + * accessor for the "Start Game" flow below to jump/restart it. + * + * @return The main menu cutscene. + */ +cutscene_t *uiMainMenuLoadCutscene(void); + +/** + * Starts the "Start Game" flow: jumps the already-running main menu + * cutscene to its NEW_GAME marker (or restarts it there if it already ran + * to completion, e.g. the player backed out of the save picker back to + * this menu) to load every save slot - retrying on error, prompting if no + * save device is found - then arms uiMainMenuOpenSelectSave to open the + * load-game picker once that succeeds. Called when the player selects + * Start Game. + */ +void uiMainMenuStartGame(void); + +/** + * Cutscene completion callback armed by uiMainMenuStartGame: opens the + * load-game picker once the NEW_GAME flow has finished loading every save + * slot. + * + * @param userData Unused - required by cutscenecallback_t's signature. + */ +void uiMainMenuOpenSelectSave(void *userData); + +/** + * Result callback for the load-game picker opened by + * uiMainMenuOpenSelectSave: re-opens this panel if backed out of without + * choosing a slot, otherwise starts the game with the chosen slot. + * + * @param slotIndex The chosen slot, or UI_SELECT_SAVE_RESULT_NONE. + * @param user Unused - required by uiselectsaveresultcallback_t's signature. + */ +void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user); + /** * Draws the main menu panel, centered on screen. No-op when not active. *