Compare commits
2 Commits
aa0180571e
...
1ddc298a74
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ddc298a74 | |||
| e2a9442aa6 |
@@ -53,7 +53,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
consolePrint("Assertions real");
|
||||
#endif
|
||||
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
sceneSet(SCENE_TYPE_INITIAL);
|
||||
|
||||
|
||||
errorOk();
|
||||
|
||||
+12
-18
@@ -16,18 +16,14 @@
|
||||
#include "time/time.h"
|
||||
#include "rpgcamera.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
#include "save/save.h"
|
||||
#include "error/error.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
#include "ui/rpg/uiemoji.h"
|
||||
#include "rpg/story/storyflag.h"
|
||||
|
||||
static void rpgTestSaveComplete(errorret_t result, void *user) {
|
||||
if(errorIsNotOk(result)) errorCatch(errorPrint(result));
|
||||
}
|
||||
|
||||
errorret_t rpgInit(void) {
|
||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||
@@ -71,13 +67,6 @@ errorret_t rpgInit(void) {
|
||||
backpackAdd(ITEM_ID_POTATO, 3);
|
||||
backpackAdd(ITEM_ID_APPLE, 8);
|
||||
|
||||
// TEST: Verify the save system round-trips real game data, not just the
|
||||
// header/version. Remove once there's an actual name-entry flow. On PSP
|
||||
// this shows the real native save dialog every boot - expected while
|
||||
// testing that path, not something to ship as-is.
|
||||
stringCopy(saveSlot->playerName, "Dusk", SAVE_PLAYER_NAME_MAX);
|
||||
saveWriteSlot(SAVE_ACTIVE_SLOT, rpgTestSaveComplete, NULL);
|
||||
|
||||
// All Good!
|
||||
errorOk();
|
||||
}
|
||||
@@ -92,12 +81,17 @@ errorret_t rpgUpdate(void) {
|
||||
// TODO: Do not update if the scene is not the map scene?
|
||||
errorChain(mapUpdate());
|
||||
|
||||
// Update overworld ents.
|
||||
entity_t *ent = &ENTITIES[0];
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
entityUpdate(ent);
|
||||
} while(++ent < &ENTITIES[ENTITY_COUNT]);
|
||||
// Update overworld ents - only while actually in the overworld. Entities
|
||||
// (the player among them) keep existing across scene changes, but their
|
||||
// input/movement/animation logic doesn't make sense to run mid-battle or
|
||||
// before the initial scene has handed off to the overworld.
|
||||
if(SCENE.current == SCENE_TYPE_OVERWORLD) {
|
||||
entity_t *ent = &ENTITIES[0];
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
entityUpdate(ent);
|
||||
} while(++ent < &ENTITIES[ENTITY_COUNT]);
|
||||
}
|
||||
|
||||
cutsceneSystemUpdate();
|
||||
errorChain(rpgCameraUpdate());
|
||||
|
||||
@@ -51,7 +51,15 @@ errorret_t saveInit(void) {
|
||||
}
|
||||
|
||||
bool_t saveIsAvailable(void) {
|
||||
return SAVE.available;
|
||||
return SAVE.available && !SAVE.temporary;
|
||||
}
|
||||
|
||||
void saveMarkTemporary(void) {
|
||||
SAVE.temporary = true;
|
||||
}
|
||||
|
||||
bool_t saveIsTemporary(void) {
|
||||
return SAVE.temporary;
|
||||
}
|
||||
|
||||
errorret_t saveDispose(void) {
|
||||
|
||||
+34
-4
@@ -26,6 +26,15 @@ typedef struct {
|
||||
* conditions here, not fatal errors - see saveIsAvailable().
|
||||
*/
|
||||
bool_t available;
|
||||
/**
|
||||
* True once the player has explicitly acknowledged there's no save
|
||||
* device and chosen to continue anyway (see the initial scene's "no
|
||||
* card" prompt) - sticky for the rest of the session, folded into
|
||||
* saveIsAvailable() so every existing/future call site that already
|
||||
* gates on that automatically refuses to save/load from here on. See
|
||||
* saveMarkTemporary()/saveIsTemporary().
|
||||
*/
|
||||
bool_t temporary;
|
||||
/**
|
||||
* Scratch error state used by platforms whose save/load completes
|
||||
* asynchronously (see saveIsBusy()) to construct a result to hand to a
|
||||
@@ -60,14 +69,35 @@ errorret_t saveInit(void);
|
||||
|
||||
/**
|
||||
* Checks whether the save medium was reachable as of the last load/write
|
||||
* attempt (or saveInit(), if none has been attempted yet). Intended for UI
|
||||
* to decide whether to offer saving/loading at all, or to explain why it
|
||||
* isn't available right now - e.g. "No memory card inserted".
|
||||
* attempt (or saveInit(), if none has been attempted yet), AND the
|
||||
* session hasn't been marked temporary (see saveMarkTemporary()).
|
||||
* Intended for UI to decide whether to offer saving/loading at all, or to
|
||||
* explain why it isn't available right now - e.g. "No memory card
|
||||
* inserted".
|
||||
*
|
||||
* @return true if the save medium was available last time it was checked.
|
||||
* @return true if saving/loading can be attempted right now.
|
||||
*/
|
||||
bool_t saveIsAvailable(void);
|
||||
|
||||
/**
|
||||
* Marks this session as temporary - the player explicitly acknowledged
|
||||
* there's no save device and chose to continue anyway. One-way: nothing
|
||||
* currently re-probes the save medium mid-session (see saveInit()'s doc
|
||||
* comment), so there's no legitimate way to un-stick this once set short
|
||||
* of restarting the game.
|
||||
*/
|
||||
void saveMarkTemporary(void);
|
||||
|
||||
/**
|
||||
* True once saveMarkTemporary() has been called this session. Distinct
|
||||
* from saveIsAvailable() so UI can give a more specific message (e.g.
|
||||
* "this session is temporary" rather than the generic "no save device
|
||||
* found") once the player has already made that choice.
|
||||
*
|
||||
* @return true if this session has been marked temporary.
|
||||
*/
|
||||
bool_t saveIsTemporary(void);
|
||||
|
||||
/**
|
||||
* Disposes of the save system.
|
||||
*
|
||||
|
||||
@@ -10,5 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(initial)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(battle)
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
sceneinitial.c
|
||||
)
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "scene/scene.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "error/error.h"
|
||||
#include "save/save.h"
|
||||
#include "ui/frame/initial/uiinitialnocard.h"
|
||||
#include "ui/frame/initial/uiinitialcreatesave.h"
|
||||
|
||||
static void sceneInitialCheckSave(void);
|
||||
|
||||
static void sceneInitialCreateSaveWriteComplete(errorret_t result, void *user) {
|
||||
if(errorIsNotOk(result)) errorCatch(errorPrint(result));
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
}
|
||||
|
||||
static void sceneInitialCreateSaveResult(const bool_t create, void *user) {
|
||||
if(!create) {
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
return;
|
||||
}
|
||||
|
||||
saveWriteSlot(SAVE_ACTIVE_SLOT, sceneInitialCreateSaveWriteComplete, NULL);
|
||||
}
|
||||
|
||||
static void sceneInitialNoCardResult(const bool_t retry, void *user) {
|
||||
if(retry) {
|
||||
sceneInitialCheckSave();
|
||||
return;
|
||||
}
|
||||
|
||||
// The player explicitly acknowledged there's no save device and chose
|
||||
// to proceed anyway - stick with that for the rest of the session (see
|
||||
// saveMarkTemporary()'s doc comment for why this can't be un-set later).
|
||||
saveMarkTemporary();
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
}
|
||||
|
||||
static void sceneInitialLoadComplete(errorret_t result, void *user) {
|
||||
if(errorIsNotOk(result) || !saveIsAvailable()) {
|
||||
errorCatch(result);
|
||||
uiInitialNoCardOpen(sceneInitialNoCardResult, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if(saveSlotExists(SAVE_ACTIVE_SLOT)) {
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
return;
|
||||
}
|
||||
|
||||
uiInitialCreateSaveOpen(sceneInitialCreateSaveResult, NULL);
|
||||
}
|
||||
|
||||
static void sceneInitialCheckSave(void) {
|
||||
if(!saveIsAvailable()) {
|
||||
uiInitialNoCardOpen(sceneInitialNoCardResult, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if(saveIsBusy()) return;
|
||||
|
||||
saveLoadSlot(SAVE_ACTIVE_SLOT, sceneInitialLoadComplete, NULL);
|
||||
}
|
||||
|
||||
errorret_t sceneInitialInit(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
memoryZero(&sceneData->initial, sizeof(sceneinitial_t));
|
||||
|
||||
sceneInitialCheckSave();
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneInitialUpdate(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneInitialRender(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneInitialDispose(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "scene/scenebase.h"
|
||||
|
||||
// No per-scene state needed - the save globals and the two modal UI
|
||||
// elements (see ui/frame/initial/) carry everything this scene cares
|
||||
// about. A byte placeholder keeps the struct non-empty for portability.
|
||||
typedef struct {
|
||||
uint8_t reserved;
|
||||
} sceneinitial_t;
|
||||
|
||||
/**
|
||||
* Initialises the initial (boot) scene - kicks off the save
|
||||
* availability/existence check that decides which prompt, if any, to
|
||||
* show before proceeding to the overworld.
|
||||
*
|
||||
* @param sceneData The scene data used for this scene.
|
||||
* @return An error if the init failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneInitialInit(scenedata_t *sceneData);
|
||||
|
||||
/**
|
||||
* Updates the initial scene. Currently a no-op - all the work happens in
|
||||
* save callbacks and the two modal UI elements' own button handling.
|
||||
*
|
||||
* @param sceneData The scene data used for this scene.
|
||||
* @return An error if the update failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneInitialUpdate(scenedata_t *sceneData);
|
||||
|
||||
/**
|
||||
* Renders the initial scene. Currently a no-op - the modals draw
|
||||
* themselves 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 sceneInitialRender(scenedata_t *sceneData);
|
||||
|
||||
/**
|
||||
* Disposes the initial scene.
|
||||
*
|
||||
* @param sceneData The scene data used for this scene.
|
||||
* @return An error if the dispose failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t sceneInitialDispose(scenedata_t *sceneData);
|
||||
@@ -10,6 +10,13 @@
|
||||
scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = {
|
||||
[SCENE_TYPE_NULL] = { 0 },
|
||||
|
||||
[SCENE_TYPE_INITIAL] = {
|
||||
.init = sceneInitialInit,
|
||||
.update = sceneInitialUpdate,
|
||||
.render = sceneInitialRender,
|
||||
.dispose = sceneInitialDispose
|
||||
},
|
||||
|
||||
[SCENE_TYPE_OVERWORLD] = {
|
||||
.init = sceneOverworldInit,
|
||||
.update = sceneOverworldUpdate,
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
|
||||
#pragma once
|
||||
#include "scene/scenebase.h"
|
||||
#include "scene/initial/sceneinitial.h"
|
||||
#include "scene/overworld/sceneoverworld.h"
|
||||
#include "scene/battle/scenebattle.h"
|
||||
|
||||
typedef union scenedata_u {
|
||||
sceneinitial_t initial;
|
||||
sceneoverworld_t overworld;
|
||||
scenebattle_t battle;
|
||||
} scenedata_t;
|
||||
@@ -27,6 +29,7 @@ typedef struct {
|
||||
typedef enum {
|
||||
SCENE_TYPE_NULL,
|
||||
|
||||
SCENE_TYPE_INITIAL,
|
||||
SCENE_TYPE_OVERWORLD,
|
||||
SCENE_TYPE_BATTLE,
|
||||
|
||||
|
||||
@@ -13,3 +13,4 @@ add_subdirectory(game)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(battle)
|
||||
add_subdirectory(backpack)
|
||||
add_subdirectory(initial)
|
||||
|
||||
@@ -81,6 +81,13 @@ static void uiGameMenuSaveCheckComplete(errorret_t result, void *user) {
|
||||
}
|
||||
|
||||
static void uiGameMenuSave(void) {
|
||||
if(saveIsTemporary()) {
|
||||
uiTextboxMainSetText(
|
||||
"This session is temporary - no save device was found, so saving "
|
||||
"is disabled."
|
||||
);
|
||||
return;
|
||||
}
|
||||
if(!saveIsAvailable()) {
|
||||
uiTextboxMainSetText("Can't save - no save device found.");
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
uiinitialnocard.c
|
||||
uiinitialcreatesave.c
|
||||
)
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uiinitialcreatesave.h"
|
||||
#include "ui/frame/uiframe.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/color.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
#define UI_INITIAL_CREATE_SAVE_BACKDROP_COLOR color4b(0, 0, 0, 160)
|
||||
#define UI_INITIAL_CREATE_SAVE_TEXT "No save data found. Create a new save?"
|
||||
|
||||
uiinitialcreatesave_t UI_INITIAL_CREATE_SAVE;
|
||||
|
||||
static void uiInitialCreateSaveSelected(
|
||||
const uimenu_t *menu,
|
||||
const uint8_t index,
|
||||
const uimenuitem_t *item
|
||||
) {
|
||||
UI_INITIAL_CREATE_SAVE.create = index == UI_INITIAL_CREATE_SAVE_INDEX_YES;
|
||||
uiInitialCreateSaveClose();
|
||||
}
|
||||
|
||||
static void uiInitialCreateSaveClosed(const uimenu_t *menu) {
|
||||
if(UI_INITIAL_CREATE_SAVE.callback != NULL) {
|
||||
UI_INITIAL_CREATE_SAVE.callback(
|
||||
UI_INITIAL_CREATE_SAVE.create, UI_INITIAL_CREATE_SAVE.user
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t uiInitialCreateSaveInit(void) {
|
||||
memoryZero(&UI_INITIAL_CREATE_SAVE, sizeof(uiinitialcreatesave_t));
|
||||
|
||||
MENU_BEGIN(
|
||||
&UI_INITIAL_CREATE_SAVE.menu, UI_INITIAL_CREATE_SAVE.items,
|
||||
uiInitialCreateSaveSelected, uiInitialCreateSaveClosed, NULL
|
||||
);
|
||||
MENU_BUTTON("Yes");
|
||||
MENU_BUTTON("No");
|
||||
|
||||
MENU_END(UI_INITIAL_CREATE_SAVE.items, menuIndex);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiInitialCreateSaveDraw(void) {
|
||||
if(!uiMenuIsActive(&UI_INITIAL_CREATE_SAVE.menu)) errorOk();
|
||||
|
||||
spritebatchsprite_t backdropSprite = {
|
||||
.min = { 0.0f, 0.0f, 0.0f },
|
||||
.max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f },
|
||||
.uvMin = { 0.0f, 0.0f },
|
||||
.uvMax = { 1.0f, 1.0f }
|
||||
};
|
||||
shadermaterial_t backdropMaterial = {
|
||||
.unlit = {
|
||||
.color = UI_INITIAL_CREATE_SAVE_BACKDROP_COLOR,
|
||||
.texture = &TEXTURE_WHITE
|
||||
}
|
||||
};
|
||||
errorChain(
|
||||
spriteBatchBuffer(&backdropSprite, 1, &SHADER_UNLIT, backdropMaterial)
|
||||
);
|
||||
errorChain(spriteBatchFlush());
|
||||
|
||||
int32_t textW, textH;
|
||||
textMeasure(UI_INITIAL_CREATE_SAVE_TEXT, &FONT_DEFAULT, &textW, &textH);
|
||||
|
||||
float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
float_t width = mathMax(
|
||||
(float_t)textW + (UI_FRAME_START_X * 2), UI_INITIAL_CREATE_SAVE_MIN_WIDTH
|
||||
);
|
||||
float_t height = (UI_FRAME_START_Y * 2) + rowHeight + UI_FRAME_PADDING_Y +
|
||||
rowHeight;
|
||||
float_t x = (float_t)SCREEN.scanX + ((float_t)SCREEN.scanWidth - width) * 0.5f;
|
||||
float_t y = (float_t)SCREEN.scanY + ((float_t)SCREEN.scanHeight - height) * 0.5f;
|
||||
|
||||
errorChain(uiFrameDraw(x, y, width, height));
|
||||
|
||||
float_t contentX = x + UI_FRAME_START_X;
|
||||
float_t contentY = y + UI_FRAME_START_Y;
|
||||
float_t contentWidth = width - (UI_FRAME_START_X * 2);
|
||||
|
||||
errorChain(textDraw(
|
||||
contentX, contentY, UI_INITIAL_CREATE_SAVE_TEXT, COLOR_WHITE, &FONT_DEFAULT
|
||||
));
|
||||
|
||||
float_t buttonsY = contentY + rowHeight + UI_FRAME_PADDING_Y;
|
||||
errorChain(uiMenuDraw(
|
||||
&UI_INITIAL_CREATE_SAVE.menu, contentX, buttonsY, contentWidth, rowHeight
|
||||
));
|
||||
|
||||
errorChain(spriteBatchFlush());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t uiInitialCreateSaveIsOpen(void) {
|
||||
return uiMenuIsActive(&UI_INITIAL_CREATE_SAVE.menu);
|
||||
}
|
||||
|
||||
void uiInitialCreateSaveOpen(
|
||||
uiinitialcreatesavecallback_t callback, void *user
|
||||
) {
|
||||
UI_INITIAL_CREATE_SAVE.callback = callback;
|
||||
UI_INITIAL_CREATE_SAVE.user = user;
|
||||
UI_INITIAL_CREATE_SAVE.create = false;
|
||||
uiMenuOpen(&UI_INITIAL_CREATE_SAVE.menu);
|
||||
}
|
||||
|
||||
void uiInitialCreateSaveClose(void) {
|
||||
uiMenuClose(&UI_INITIAL_CREATE_SAVE.menu);
|
||||
}
|
||||
|
||||
errorret_t uiInitialCreateSaveDispose(void) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "ui/widget/uimenu.h"
|
||||
|
||||
#define UI_INITIAL_CREATE_SAVE_INDEX_YES 0
|
||||
#define UI_INITIAL_CREATE_SAVE_INDEX_NO 1
|
||||
#define UI_INITIAL_CREATE_SAVE_ITEM_COUNT 2
|
||||
#define UI_INITIAL_CREATE_SAVE_MIN_WIDTH 160.0f
|
||||
|
||||
/**
|
||||
* Callback invoked once the create-save modal is dismissed.
|
||||
*
|
||||
* @param create True if Yes was selected, false if No was selected or
|
||||
* the dialog was backed out of.
|
||||
* @param user Arbitrary pointer passed to uiInitialCreateSaveOpen.
|
||||
*/
|
||||
typedef void (*uiinitialcreatesavecallback_t)(const bool_t create, void *user);
|
||||
|
||||
typedef struct {
|
||||
uimenu_t menu;
|
||||
uimenuitem_t items[UI_INITIAL_CREATE_SAVE_ITEM_COUNT];
|
||||
uiinitialcreatesavecallback_t callback;
|
||||
void *user;
|
||||
bool_t create;
|
||||
} uiinitialcreatesave_t;
|
||||
|
||||
extern uiinitialcreatesave_t UI_INITIAL_CREATE_SAVE;
|
||||
|
||||
/**
|
||||
* Initializes the create-save modal.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiInitialCreateSaveInit(void);
|
||||
|
||||
/**
|
||||
* Draws the create-save modal: a semi-transparent black backdrop covering
|
||||
* the whole screen, then its own centered frame with a fixed message and
|
||||
* Yes/No buttons. No-op when not open.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiInitialCreateSaveDraw(void);
|
||||
|
||||
/**
|
||||
* Returns true when the modal is currently open.
|
||||
*
|
||||
* @return True if open.
|
||||
*/
|
||||
bool_t uiInitialCreateSaveIsOpen(void);
|
||||
|
||||
/**
|
||||
* Opens the create-save modal. callback is invoked exactly once with the
|
||||
* result, whether dismissed by selecting a button or backing out.
|
||||
*
|
||||
* @param callback Called with the result once the modal closes. May be
|
||||
* NULL.
|
||||
* @param user Arbitrary pointer passed through to callback.
|
||||
*/
|
||||
void uiInitialCreateSaveOpen(uiinitialcreatesavecallback_t callback, void *user);
|
||||
|
||||
/**
|
||||
* Closes the modal. No-op when already closed.
|
||||
*/
|
||||
void uiInitialCreateSaveClose(void);
|
||||
|
||||
/**
|
||||
* Disposes of the modal.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiInitialCreateSaveDispose(void);
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uiinitialnocard.h"
|
||||
#include "ui/frame/uiframe.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/color.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
#define UI_INITIAL_NO_CARD_BACKDROP_COLOR color4b(0, 0, 0, 160)
|
||||
#define UI_INITIAL_NO_CARD_TEXT \
|
||||
"No save device found. You can continue, but\nprogress will not be saved."
|
||||
|
||||
uiinitialnocard_t UI_INITIAL_NO_CARD;
|
||||
|
||||
static void uiInitialNoCardSelected(
|
||||
const uimenu_t *menu,
|
||||
const uint8_t index,
|
||||
const uimenuitem_t *item
|
||||
) {
|
||||
UI_INITIAL_NO_CARD.retry = index == UI_INITIAL_NO_CARD_INDEX_RETRY;
|
||||
uiInitialNoCardClose();
|
||||
}
|
||||
|
||||
static void uiInitialNoCardClosed(const uimenu_t *menu) {
|
||||
if(UI_INITIAL_NO_CARD.callback != NULL) {
|
||||
UI_INITIAL_NO_CARD.callback(UI_INITIAL_NO_CARD.retry, UI_INITIAL_NO_CARD.user);
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t uiInitialNoCardInit(void) {
|
||||
memoryZero(&UI_INITIAL_NO_CARD, sizeof(uiinitialnocard_t));
|
||||
|
||||
MENU_BEGIN(
|
||||
&UI_INITIAL_NO_CARD.menu, UI_INITIAL_NO_CARD.items,
|
||||
uiInitialNoCardSelected, uiInitialNoCardClosed, NULL
|
||||
);
|
||||
MENU_BUTTON("Retry");
|
||||
MENU_BUTTON("Continue Anyway");
|
||||
|
||||
MENU_END(UI_INITIAL_NO_CARD.items, menuIndex);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiInitialNoCardDraw(void) {
|
||||
if(!uiMenuIsActive(&UI_INITIAL_NO_CARD.menu)) errorOk();
|
||||
|
||||
spritebatchsprite_t backdropSprite = {
|
||||
.min = { 0.0f, 0.0f, 0.0f },
|
||||
.max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f },
|
||||
.uvMin = { 0.0f, 0.0f },
|
||||
.uvMax = { 1.0f, 1.0f }
|
||||
};
|
||||
shadermaterial_t backdropMaterial = {
|
||||
.unlit = {
|
||||
.color = UI_INITIAL_NO_CARD_BACKDROP_COLOR,
|
||||
.texture = &TEXTURE_WHITE
|
||||
}
|
||||
};
|
||||
errorChain(
|
||||
spriteBatchBuffer(&backdropSprite, 1, &SHADER_UNLIT, backdropMaterial)
|
||||
);
|
||||
errorChain(spriteBatchFlush());
|
||||
|
||||
int32_t textW, textH;
|
||||
textMeasure(UI_INITIAL_NO_CARD_TEXT, &FONT_DEFAULT, &textW, &textH);
|
||||
|
||||
float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
||||
float_t width = mathMax(
|
||||
(float_t)textW + (UI_FRAME_START_X * 2), UI_INITIAL_NO_CARD_MIN_WIDTH
|
||||
);
|
||||
float_t height = (UI_FRAME_START_Y * 2) + (float_t)textH +
|
||||
UI_FRAME_PADDING_Y + rowHeight;
|
||||
float_t x = (float_t)SCREEN.scanX + ((float_t)SCREEN.scanWidth - width) * 0.5f;
|
||||
float_t y = (float_t)SCREEN.scanY + ((float_t)SCREEN.scanHeight - height) * 0.5f;
|
||||
|
||||
errorChain(uiFrameDraw(x, y, width, height));
|
||||
|
||||
float_t contentX = x + UI_FRAME_START_X;
|
||||
float_t contentY = y + UI_FRAME_START_Y;
|
||||
float_t contentWidth = width - (UI_FRAME_START_X * 2);
|
||||
|
||||
errorChain(textDraw(
|
||||
contentX, contentY, UI_INITIAL_NO_CARD_TEXT, COLOR_WHITE, &FONT_DEFAULT
|
||||
));
|
||||
|
||||
float_t buttonsY = contentY + (float_t)textH + UI_FRAME_PADDING_Y;
|
||||
errorChain(uiMenuDraw(
|
||||
&UI_INITIAL_NO_CARD.menu, contentX, buttonsY, contentWidth, rowHeight
|
||||
));
|
||||
|
||||
errorChain(spriteBatchFlush());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t uiInitialNoCardIsOpen(void) {
|
||||
return uiMenuIsActive(&UI_INITIAL_NO_CARD.menu);
|
||||
}
|
||||
|
||||
void uiInitialNoCardOpen(uiinitialnocardcallback_t callback, void *user) {
|
||||
UI_INITIAL_NO_CARD.callback = callback;
|
||||
UI_INITIAL_NO_CARD.user = user;
|
||||
UI_INITIAL_NO_CARD.retry = false;
|
||||
uiMenuOpen(&UI_INITIAL_NO_CARD.menu);
|
||||
}
|
||||
|
||||
void uiInitialNoCardClose(void) {
|
||||
uiMenuClose(&UI_INITIAL_NO_CARD.menu);
|
||||
}
|
||||
|
||||
errorret_t uiInitialNoCardDispose(void) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "ui/widget/uimenu.h"
|
||||
|
||||
#define UI_INITIAL_NO_CARD_INDEX_RETRY 0
|
||||
#define UI_INITIAL_NO_CARD_INDEX_CONTINUE 1
|
||||
#define UI_INITIAL_NO_CARD_ITEM_COUNT 2
|
||||
#define UI_INITIAL_NO_CARD_MIN_WIDTH 220.0f
|
||||
|
||||
/**
|
||||
* Callback invoked once the no-save-device modal is dismissed.
|
||||
*
|
||||
* @param retry True if Retry was selected, false if Continue Anyway was.
|
||||
* @param user Arbitrary pointer passed to uiInitialNoCardOpen.
|
||||
*/
|
||||
typedef void (*uiinitialnocardcallback_t)(const bool_t retry, void *user);
|
||||
|
||||
typedef struct {
|
||||
uimenu_t menu;
|
||||
uimenuitem_t items[UI_INITIAL_NO_CARD_ITEM_COUNT];
|
||||
uiinitialnocardcallback_t callback;
|
||||
void *user;
|
||||
bool_t retry;
|
||||
} uiinitialnocard_t;
|
||||
|
||||
extern uiinitialnocard_t UI_INITIAL_NO_CARD;
|
||||
|
||||
/**
|
||||
* Initializes the no-save-device modal.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiInitialNoCardInit(void);
|
||||
|
||||
/**
|
||||
* Draws the no-save-device modal: a semi-transparent black backdrop
|
||||
* covering the whole screen, then its own centered frame with a fixed
|
||||
* message and Retry/Continue Anyway buttons. No-op when not open.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiInitialNoCardDraw(void);
|
||||
|
||||
/**
|
||||
* Returns true when the modal is currently open.
|
||||
*
|
||||
* @return True if open.
|
||||
*/
|
||||
bool_t uiInitialNoCardIsOpen(void);
|
||||
|
||||
/**
|
||||
* Opens the no-save-device modal. callback is invoked exactly once with
|
||||
* the result, whether dismissed by selecting a button or backing out.
|
||||
*
|
||||
* @param callback Called with the result once the modal closes. May be
|
||||
* NULL.
|
||||
* @param user Arbitrary pointer passed through to callback.
|
||||
*/
|
||||
void uiInitialNoCardOpen(uiinitialnocardcallback_t callback, void *user);
|
||||
|
||||
/**
|
||||
* Closes the modal. No-op when already closed.
|
||||
*/
|
||||
void uiInitialNoCardClose(void);
|
||||
|
||||
/**
|
||||
* Disposes of the modal.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiInitialNoCardDispose(void);
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "ui/frame/battle/uibattlemenu.h"
|
||||
#include "ui/frame/backpack/uibackpack.h"
|
||||
#include "ui/frame/uiconfirm.h"
|
||||
#include "ui/frame/initial/uiinitialnocard.h"
|
||||
#include "ui/frame/initial/uiinitialcreatesave.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||
#include "ui/rpg/uiemoji.h"
|
||||
@@ -79,6 +81,18 @@ uielement_t UI_ELEMENTS[] = {
|
||||
.dispose = uiConfirmDispose
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiInitialNoCardInit,
|
||||
.draw = uiInitialNoCardDraw,
|
||||
.dispose = uiInitialNoCardDispose
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiInitialCreateSaveInit,
|
||||
.draw = uiInitialCreateSaveDraw,
|
||||
.dispose = uiInitialCreateSaveDispose
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiTextboxMainInit,
|
||||
.update = uiTextboxMainUpdate,
|
||||
|
||||
Reference in New Issue
Block a user