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 <[email protected]>
This commit is contained in:
2026-09-05 12:18:02 -05:00
co-authored by Claude Sonnet 5
parent 6ccaedd48f
commit de53bebeb8
9 changed files with 114 additions and 196 deletions
@@ -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) {
-1
View File
@@ -11,6 +11,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Subdirs
add_subdirectory(initial)
add_subdirectory(mainmenu)
add_subdirectory(overworld)
add_subdirectory(battle)
-9
View File
@@ -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
)
-99
View File
@@ -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();
}
-73
View File
@@ -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);
-7
View File
@@ -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,
-3
View File
@@ -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,
+68 -2
View File
@@ -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:
+45
View File
@@ -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.
*