diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index b0dae769..7c379dee 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -100,13 +100,14 @@ typedef struct cutscene_s { // Shows a modal with option buttons and immediately continues on, same // as CUTSCENE_MODAL - it does not block waiting for a selection. -// CALLBACK fires with the selected option index (or UI_MODAL_RESULT_NONE -// if backed out of) once the dialog closes. Option labels are passed as -// trailing arguments, e.g. CUTSCENE_MODAL_OPTIONS(title, message, -// callback, "Retry", "Cancel") - their strings are not copied by this -// item, so they must stay valid until the modal opens (string literals -// are fine). Like TITLE and MESSAGE, each option is translated if it -// matches a locale message ID. +// Back/cancel input is disabled while it's open (see +// uiMenuSetDisableBack), so it must be dismissed by picking one; CALLBACK +// then fires with the selected option index once the dialog closes. +// Option labels are passed as trailing arguments, e.g. +// CUTSCENE_MODAL_OPTIONS(title, message, callback, "Retry", "Cancel") - +// their strings are not copied by this item, so they must stay valid +// until the modal opens (string literals are fine). Like TITLE and +// MESSAGE, each option is translated if it matches a locale message ID. #define CUTSCENE_MODAL_OPTIONS(TITLE, MESSAGE, CALLBACK, ...) \ { \ .type = CUTSCENE_ITEM_TYPE_MODAL, \ @@ -121,10 +122,9 @@ typedef struct cutscene_s { } // Same as CUTSCENE_MODAL_OPTIONS, but fixed to exactly one option and -// MARKER1 to jump straight to via cutsceneGoTo once it's selected (or -// the dialog is backed out of) - no callback function to write. Does -// not fall through to whatever follows this item, so MARKER1 must be -// scripted elsewhere in the same cutscene. +// MARKER1 to jump straight to via cutsceneGoTo once it's selected - no +// callback function to write. Does not fall through to whatever follows +// this item, so MARKER1 must be scripted elsewhere in the same cutscene. #define CUTSCENE_MODAL_OPTIONS_ONE(TITLE, MESSAGE, OPTION1, MARKER1) \ { \ .type = CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, \ @@ -139,10 +139,8 @@ typedef struct cutscene_s { // Same as CUTSCENE_MODAL_OPTIONS, but fixed to exactly two options, // jumping straight to OPTION1_MARKER or OPTION2_MARKER via cutsceneGoTo // once the corresponding option is selected - no callback function to -// write. Backing out without selecting (UI_MODAL_RESULT_NONE) is -// treated the same as selecting option 2. Does not fall through to -// whatever follows this item, so both markers must be scripted -// elsewhere in the same cutscene. +// write. Does not fall through to whatever follows this item, so both +// markers must be scripted elsewhere in the same cutscene. #define CUTSCENE_MODAL_OPTIONS_TWO( \ TITLE, MESSAGE, OPTION1, OPTION1_MARKER, OPTION2, OPTION2_MARKER \ ) \ diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c index 35438df9..800cce4a 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ b/src/dusk/rpg/cutscene/cutscenesystem.c @@ -17,11 +17,7 @@ void cutsceneSystemInit() { memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); } -void cutsceneSystemStartCutscene(const cutscene_t *cutscene) { - cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL); -} - -void cutsceneSystemStartCutsceneWith( +void cutsceneSystemPrepare( const cutscene_t *cutscene, entity_t *interact, entity_t *interacted @@ -40,9 +36,29 @@ void cutsceneSystemStartCutsceneWith( CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED; CUTSCENE_SYSTEM.textMiniLastCreated = CUTSCENE_TEXT_MINI_LAST_CREATED; CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0. +} + +void cutsceneSystemStartCutscene(const cutscene_t *cutscene) { + cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL); +} + +void cutsceneSystemStartCutsceneWith( + const cutscene_t *cutscene, + entity_t *interact, + entity_t *interacted +) { + cutsceneSystemPrepare(cutscene, interact, interacted); cutsceneSystemNext(); } +void cutsceneSystemStartCutsceneAndGoToMarker( + const cutscene_t *cutscene, + const char_t *marker +) { + cutsceneSystemPrepare(cutscene, NULL, NULL); + cutsceneGoTo(marker); +} + void cutsceneRestart(void) { assertNotNull( CUTSCENE_SYSTEM.scene, "cutsceneRestart called with no cutscene running" diff --git a/src/dusk/rpg/cutscene/cutscenesystem.h b/src/dusk/rpg/cutscene/cutscenesystem.h index 75d75d07..2e04c1e9 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.h +++ b/src/dusk/rpg/cutscene/cutscenesystem.h @@ -67,6 +67,21 @@ void cutsceneSystemStartCutsceneWith( entity_t *interacted ); +/** + * Starts a cutscene with no bound entities, jumping straight to the + * CUTSCENE_MARKER item with the given name instead of running from the + * first item - as if cutsceneGoTo(marker) had been called immediately + * after cutsceneSystemStartCutscene. Asserts if no marker with that + * name exists in the cutscene. + * + * @param cutscene Pointer to the cutscene to start. + * @param marker Marker name to jump to, matched with stringEquals. + */ +void cutsceneSystemStartCutsceneAndGoToMarker( + const cutscene_t *cutscene, + const char_t *marker +); + /** * Restarts the currently running cutscene from its first item, * preserving whatever interact/interacted entities triggered it. diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h index 7d843e41..fd7207ef 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h @@ -17,10 +17,10 @@ typedef union cutsceneitemdata_u cutsceneitemdata_t; /** * Callback invoked with the selected option once a cutscene modal - * item's dialog closes. + * item's dialog closes. Back/cancel input is disabled while it's open + * (see uiMenuSetDisableBack), so it must be dismissed by picking one. * - * @param optionIndex Index into the options array that was selected, - * or UI_MODAL_RESULT_NONE if backed out of without selecting one. + * @param optionIndex Index into the options array that was selected. * @param userData CUTSCENE_SYSTEM.userData for the running cutscene. */ typedef void (*cutscenemodaloptioncallback_t)( diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c index db31da53..0611a15c 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c @@ -16,10 +16,10 @@ void cutsceneModalOptionsMarkersCallback( const cutscenemodaloptionsmarkers_t *options = &cutsceneSystemGetCurrentItem()->modalOptionsMarkers; - // A backed-out-of dialog (UI_MODAL_RESULT_NONE) falls back to the - // last option's marker, same as a hand-written two-option callback - // (e.g. sceneInitialSaveDeviceRetryCallback) treating "backed out" as - // the safe/continue choice rather than the destructive one. + // uiModalOpen disables back/cancel for a dialog with options, so + // UI_MODAL_RESULT_NONE can't reach here via input - this fallback to + // the last option's marker only matters if something ever force-closes + // the modal directly (uiModalClose) before an option is picked. const uint8_t index = optionIndex < options->optionCount ? optionIndex : options->optionCount - 1; cutsceneGoTo(options->markers[index]); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h index f096f4ea..15c60178 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h @@ -42,9 +42,10 @@ void cutsceneModalOptionsMarkersStart( /** * Updates a modal-options-markers item. Never completes on its own - - * the cutscene blocks here indefinitely. Once an option is selected (or - * the dialog is backed out of, which is treated as selecting the last - * option), it jumps straight to that option's marker via cutsceneGoTo. + * the cutscene blocks here indefinitely. Back/cancel input is disabled + * while it's open (see uiMenuSetDisableBack), so it must be dismissed + * by picking an option, which jumps straight to that option's marker + * via cutsceneGoTo. * * @param item The cutscene item. * @param data Runtime data storage. diff --git a/src/dusk/scene/initial/sceneinitial.c b/src/dusk/scene/initial/sceneinitial.c index 9b01a53e..3b619615 100644 --- a/src/dusk/scene/initial/sceneinitial.c +++ b/src/dusk/scene/initial/sceneinitial.c @@ -19,7 +19,7 @@ CUTSCENE(INITIAL, 0, DEFAULT, CUTSCENE_MODAL( "initial.checking_save.title", "initial.checking_save.message" ), - CUTSCENE_WAIT(1.0f), + CUTSCENE_WAIT(0.2f), CUTSCENE_SAVE_DEVICE_CHECK("CONTINUE", "NO_DEVICE"), CUTSCENE_MARKER("NO_DEVICE"), diff --git a/src/dusk/ui/dialog/uiconfirm.h b/src/dusk/ui/dialog/uiconfirm.h index 398aff14..3b76ac59 100644 --- a/src/dusk/ui/dialog/uiconfirm.h +++ b/src/dusk/ui/dialog/uiconfirm.h @@ -9,10 +9,12 @@ #include "error/error.h" /** - * Callback invoked once a confirm dialog is dismissed. + * Callback invoked once a confirm dialog is dismissed. Back/cancel + * input is disabled while it's open, so the dialog can only be + * dismissed by picking Confirm or Cancel. * * @param result True if Confirm was selected, false if Cancel was - * selected or the dialog was backed out of. + * selected. * @param user Arbitrary pointer passed to uiConfirmOpen. */ typedef void (*uiconfirmcallback_t)(const bool_t result, void *user); @@ -48,8 +50,7 @@ bool_t uiConfirmGetResult(void); /** * Opens a confirm dialog, backed by the shared uimodal_t, with the given * question text and Confirm/Cancel buttons. callback is invoked exactly - * once with the result, whether the dialog was dismissed by selecting a - * button or by pressing cancel/back. + * once with the result once a button is selected. * * @param question Display text, or a locale message ID; copied * internally by uimodal, safe to be transient. diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c index 6ca946dc..31e9f4c0 100644 --- a/src/dusk/ui/focus/uifocus.c +++ b/src/dusk/ui/focus/uifocus.c @@ -153,7 +153,7 @@ void uiFocusUpdate(void) { } if(inputPressed(INPUT_ACTION_CANCEL)) { - uiFocusPop(); + if(!item->disableBack) uiFocusPop(); return; } diff --git a/src/dusk/ui/focus/uifocusitem.h b/src/dusk/ui/focus/uifocusitem.h index 413e89ee..081de0df 100644 --- a/src/dusk/ui/focus/uifocusitem.h +++ b/src/dusk/ui/focus/uifocusitem.h @@ -54,4 +54,9 @@ struct uifocusitem_s { uifocusitemcallback_t closed; uifocusitemdirectioncallback_t direction; void *user; + + // When true, INPUT_ACTION_CANCEL is ignored while this item is the + // topmost focus item - it does not pop. Does not affect a + // programmatic uiFocusPop()/uiFocusPopItem() call. + bool_t disableBack; }; diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.c b/src/dusk/ui/screen/mainmenu/uimainmenu.c index 6b4771af..4a74c033 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.c +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.c @@ -36,7 +36,7 @@ CUTSCENE(MAIN_MENU_START_GAME, 0, DEFAULT, "main_menu.checking_save.title", "main_menu.checking_save.message" ), - CUTSCENE_WAIT(1.0f), + CUTSCENE_WAIT(0.2f), CUTSCENE_SAVE_DEVICE_CHECK("CONTINUE", "NO_DEVICE"), CUTSCENE_MARKER("NO_DEVICE"), @@ -90,15 +90,6 @@ void uiMainMenuOpenSelectSave(void *userData) { ); } -void uiMainMenuClosed(const uimenu_t *menu) { - if(UI_MAIN_MENU.suppressClosedSceneChange) { - UI_MAIN_MENU.suppressClosedSceneChange = false; - return; - } - - sceneSet(SCENE_TYPE_INITIAL); -} - void uiMainMenuSelected( const uimenu_t *menu, const uint8_t index, @@ -107,7 +98,6 @@ void uiMainMenuSelected( switch(index) { case UI_MAIN_MENU_INDEX_START_GAME: - UI_MAIN_MENU.suppressClosedSceneChange = true; uiMenuClose(&UI_MAIN_MENU.menu); cutsceneSystemStartCutscene(&CUTSCENE_MAIN_MENU_START_GAME); break; @@ -151,14 +141,17 @@ errorret_t uiMainMenuInit(void) { )); MENU_BEGIN( - &UI_MAIN_MENU.menu, UI_MAIN_MENU.items, uiMainMenuSelected, - uiMainMenuClosed, NULL + &UI_MAIN_MENU.menu, UI_MAIN_MENU.items, uiMainMenuSelected, NULL, NULL ); MENU_BUTTON(UI_MAIN_MENU.startGameLabel); MENU_BUTTON(UI_MAIN_MENU.optionsLabel); MENU_BUTTON(UI_MAIN_MENU.quitLabel); MENU_END(UI_MAIN_MENU.items, 1); + // The main menu is the root screen once past the initial boot check - + // there's nothing to back out to, so back/cancel does nothing here. + uiMenuSetDisableBack(&UI_MAIN_MENU.menu, true); + errorOk(); } diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.h b/src/dusk/ui/screen/mainmenu/uimainmenu.h index ce06ae3f..3a53c9ee 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.h +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.h @@ -20,12 +20,6 @@ typedef struct { char_t startGameLabel[UI_MAIN_MENU_LABEL_MAX]; char_t optionsLabel[UI_MAIN_MENU_LABEL_MAX]; char_t quitLabel[UI_MAIN_MENU_LABEL_MAX]; - - // Set right before a uiMenuClose() call that hands off to something - // else (e.g. starting the start-game cutscene) rather than backing - // all the way out, so the menu's closed handler knows to skip - // returning to the initial scene for that one close. - bool_t suppressClosedSceneChange; } uimainmenu_t; extern uimainmenu_t UI_MAIN_MENU; diff --git a/src/dusk/ui/widget/uimenu.c b/src/dusk/ui/widget/uimenu.c index 56ec65d3..8c736f2f 100644 --- a/src/dusk/ui/widget/uimenu.c +++ b/src/dusk/ui/widget/uimenu.c @@ -67,6 +67,7 @@ void uiMenuOpen(uimenu_t *menu) { uiMenuFocusDirection, menu ); + menu->focusItem->disableBack = menu->disableBack; } void uiMenuClose(uimenu_t *menu) { @@ -76,6 +77,12 @@ void uiMenuClose(uimenu_t *menu) { menu->focusItem = NULL; } +void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack) { + assertNotNull(menu, "Menu cannot be NULL"); + menu->disableBack = disableBack; + if(menu->focusItem != NULL) menu->focusItem->disableBack = disableBack; +} + bool_t uiMenuIsActive(const uimenu_t *menu) { assertNotNull(menu, "Menu cannot be NULL"); return menu->focusItem != NULL; diff --git a/src/dusk/ui/widget/uimenu.h b/src/dusk/ui/widget/uimenu.h index 870ed130..8fbb7ec3 100644 --- a/src/dusk/ui/widget/uimenu.h +++ b/src/dusk/ui/widget/uimenu.h @@ -66,6 +66,9 @@ typedef struct uimenu_s { uimenuchangedcallback_t changed; void *user; + + // See uiMenuSetDisableBack. + bool_t disableBack; } uimenu_t; /** @@ -119,11 +122,22 @@ void uiMenuOpen(uimenu_t *menu); /** * Pops a menu from the UI focus stack, removing it from the active menu. - * + * * @param menu The menu to pop. */ void uiMenuClose(uimenu_t *menu); +/** + * Sets whether pressing back/cancel is ignored while this menu is the + * topmost focus item - it simply does nothing, rather than closing the + * menu. Does not affect a deliberate uiMenuClose() call. Safe to call + * before or after uiMenuOpen(). + * + * @param menu The menu to update. + * @param disableBack True to ignore back/cancel input for this menu. + */ +void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack); + /** * Returns whether the menu is currently active (on the UI focus stack). * diff --git a/src/dusk/ui/widget/uimodal.c b/src/dusk/ui/widget/uimodal.c index 97a1c62c..f248db0c 100644 --- a/src/dusk/ui/widget/uimodal.c +++ b/src/dusk/ui/widget/uimodal.c @@ -202,6 +202,9 @@ void uiModalOpen( } MENU_END(UI_MODAL.options, menuIndex); + // A dialog with options must be dismissed by picking one, not by + // backing out of it - see uiMenuSetDisableBack. + uiMenuSetDisableBack(&UI_MODAL.menu, true); uiMenuOpen(&UI_MODAL.menu); } else { uiMenuInit(&UI_MODAL.menu, uiModalSelected, uiModalClosed, NULL); diff --git a/src/dusk/ui/widget/uimodal.h b/src/dusk/ui/widget/uimodal.h index 0434ac86..66ebe222 100644 --- a/src/dusk/ui/widget/uimodal.h +++ b/src/dusk/ui/widget/uimodal.h @@ -27,8 +27,10 @@ * Callback invoked once a modal is dismissed. * * @param optionIndex Index into the options array passed to uiModalOpen - * that was selected, or UI_MODAL_RESULT_NONE if backed out of without - * selecting an option. + * that was selected. A dialog with options ignores back/cancel input + * (see uiMenuSetDisableBack) and must be dismissed by picking one, so + * UI_MODAL_RESULT_NONE only reaches here via a direct uiModalClose() + * call made before any option was picked. * @param user Arbitrary pointer passed to uiModalOpen. */ typedef void (*uimodaloptioncallback_t)(const uint8_t optionIndex, void *user); @@ -103,8 +105,9 @@ uint8_t uiModalGetResult(void); /** * Opens the modal dialog with the given title, message, and options. - * callback is invoked exactly once with the result, whether the dialog - * was dismissed by selecting an option or by pressing cancel/back. + * callback is invoked exactly once with the selected option. A dialog + * with options > 0 disables back/cancel (see uiMenuSetDisableBack), so + * it must be dismissed by picking one. * * title, message, and each option are treated as locale message IDs * first: if one matches an entry in the active locale file its