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:
@@ -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) {
|
||||
|
||||
@@ -11,6 +11,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(initial)
|
||||
add_subdirectory(mainmenu)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(battle)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user