Persist and resume in-progress cutscene via save slot; add species stub
Save slots now track the currently-active cutscene file (defaulting to a new placeholder new_game.jsonc intro) so loading a save resumes it; cutsceneSystemStartCutsceneWith persists this on every cutscene start, and a cutsceneSystemLoad ordering bug (loadedFile stamped after starting) is fixed so the persisted path is always correct. Also adds a battlefighterspecies_t/battlespeciesid_t stub for upcoming species data. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
// Placeholder for the new-game intro flow - just shows a modal for now.
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "MODAL",
|
||||||
|
"title": "New Game",
|
||||||
|
"message": "This is a placeholder for the new game intro.",
|
||||||
|
"blocking": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+14
-8
@@ -1,16 +1,22 @@
|
|||||||
{
|
{
|
||||||
// Starting party roster, loaded by partyInit into a fresh save slot.
|
|
||||||
// Each key is a stable identifier cutscene content can reference
|
|
||||||
// directly (see PARTY_ADD_MEMBER/PARTY_REMOVE_MEMBER/PARTY_HEAL_MEMBER's
|
|
||||||
// "key" field) instead of a fragile positional index.
|
|
||||||
"members": {
|
"members": {
|
||||||
"hero": {
|
"hero": {
|
||||||
"attack": 10, "defense": 5, "magic": 0, "speed": 10, "luck": 0,
|
"attack": 10,
|
||||||
"healthMax": 30, "mpMax": 10
|
"defense": 5,
|
||||||
|
"magic": 0,
|
||||||
|
"speed": 10,
|
||||||
|
"luck": 0,
|
||||||
|
"healthMax": 30,
|
||||||
|
"mpMax": 10
|
||||||
},
|
},
|
||||||
"companion": {
|
"companion": {
|
||||||
"attack": 8, "defense": 6, "magic": 4, "speed": 8, "luck": 1,
|
"attack": 8,
|
||||||
"healthMax": 25, "mpMax": 15
|
"defense": 6,
|
||||||
|
"magic": 4,
|
||||||
|
"speed": 8,
|
||||||
|
"luck": 1,
|
||||||
|
"healthMax": 25,
|
||||||
|
"mpMax": 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
#include "rpg/battle/fighter/battlefighterstats.h"
|
||||||
|
|
||||||
|
#define BATTLE_FIGHTER_SPECIES_NAME_MAX 16
|
||||||
|
#define BATTLE_FIGHTER_SPECIES_COUNT_MAX 250
|
||||||
|
|
||||||
|
typedef uint8_t battlespeciesid_t;
|
||||||
|
|
||||||
|
#define BATTLE_SPECIES_ID_NULL ((battlespeciesid_t)0)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char_t name[BATTLE_FIGHTER_SPECIES_NAME_MAX];
|
||||||
|
battlefighterstats_t baseStats;
|
||||||
|
} battlefighterspecies_t;
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "ui/overlay/uifatalerror.h"
|
#include "ui/overlay/uifatalerror.h"
|
||||||
|
#include "save/slot/saveslot.h"
|
||||||
|
|
||||||
cutscenesystem_t CUTSCENE_SYSTEM;
|
cutscenesystem_t CUTSCENE_SYSTEM;
|
||||||
|
|
||||||
@@ -84,6 +85,8 @@ void cutsceneSystemStartCutsceneWith(
|
|||||||
);
|
);
|
||||||
|
|
||||||
cutsceneSystemPrepare(cutscene, interact, interacted);
|
cutsceneSystemPrepare(cutscene, interact, interacted);
|
||||||
|
saveSlotSetCurrentCutscene(CUTSCENE_SYSTEM.loadedFile);
|
||||||
|
|
||||||
cutsceneSystemNext();
|
cutsceneSystemNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +163,14 @@ void cutsceneSystemLoad(const char_t *file) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene);
|
// Stamped before starting (not after) - cutsceneSystemStartCutsceneWith
|
||||||
|
// reads loadedFile to persist the in-progress cutscene to the save (see
|
||||||
|
// saveSlotSetCurrentCutscene), so it must already be correct by then.
|
||||||
|
// Safe: cutsceneSystemPrepare (called from within Start) only clears
|
||||||
|
// loadedFile when switching away from loadedScene entirely, which this
|
||||||
|
// isn't.
|
||||||
stringCopy(CUTSCENE_SYSTEM.loadedFile, fileCopy, ASSET_FILE_NAME_MAX - 1);
|
stringCopy(CUTSCENE_SYSTEM.loadedFile, fileCopy, ASSET_FILE_NAME_MAX - 1);
|
||||||
|
cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneRestart(void) {
|
void cutsceneRestart(void) {
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ errorret_t saveSlotInit(saveslot_t *slot) {
|
|||||||
// Set the default party.
|
// Set the default party.
|
||||||
errorChain(partyInit(&slot->party));
|
errorChain(partyInit(&slot->party));
|
||||||
|
|
||||||
|
// Set the default current cutscene.
|
||||||
|
stringCopy(
|
||||||
|
slot->currentCutscene, SAVE_SLOT_CUTSCENE_DEFAULT,
|
||||||
|
sizeof(slot->currentCutscene) - 1
|
||||||
|
);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +47,15 @@ errorret_t saveSlotNewGame(void) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void saveSlotSetCurrentCutscene(const char_t *file) {
|
||||||
|
assertNotNull(file, "File cannot be null");
|
||||||
|
|
||||||
|
stringCopy(
|
||||||
|
SAVE.slot.currentCutscene, file, sizeof(SAVE.slot.currentCutscene) - 1
|
||||||
|
);
|
||||||
|
SAVE.slotDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
bool_t saveSlotInUse(saveslotcache_t *slot) {
|
bool_t saveSlotInUse(saveslotcache_t *slot) {
|
||||||
assertNotNull(slot, "Slot cannot be null");
|
assertNotNull(slot, "Slot cannot be null");
|
||||||
return slot->name[0] != '\0';
|
return slot->name[0] != '\0';
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "rpg/battle/party.h"
|
#include "rpg/battle/party.h"
|
||||||
#include "rpg/story/story.h"
|
#include "rpg/story/story.h"
|
||||||
#include "time/timeepoch.h"
|
#include "time/timeepoch.h"
|
||||||
|
#include "asset/assetfile.h"
|
||||||
#include "yyjson.h"
|
#include "yyjson.h"
|
||||||
|
|
||||||
#define SAVE_SLOT_NAME_LENGTH 8
|
#define SAVE_SLOT_NAME_LENGTH 8
|
||||||
@@ -19,6 +20,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define SAVE_SLOT_MAP_NAME_DEFAULT "overworld"
|
#define SAVE_SLOT_MAP_NAME_DEFAULT "overworld"
|
||||||
|
#define SAVE_SLOT_CUTSCENE_NAME_MAX ASSET_FILE_NAME_MAX
|
||||||
|
#define SAVE_SLOT_CUTSCENE_DEFAULT "cutscenes/new_game.jsonc"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char_t name[SAVE_SLOT_NAME_LENGTH + 1];// 8 characters + null terminator
|
char_t name[SAVE_SLOT_NAME_LENGTH + 1];// 8 characters + null terminator
|
||||||
@@ -34,6 +37,10 @@ typedef struct saveslot_s {
|
|||||||
saveslotcache_t cachedData;
|
saveslotcache_t cachedData;
|
||||||
party_t party;
|
party_t party;
|
||||||
storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX];
|
storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX];
|
||||||
|
// Asset path of the cutscene currently in progress for this slot (e.g.
|
||||||
|
// resumed on load) - defaults to SAVE_SLOT_CUTSCENE_DEFAULT on a fresh
|
||||||
|
// slot (see saveSlotInit).
|
||||||
|
char_t currentCutscene[SAVE_SLOT_CUTSCENE_NAME_MAX];
|
||||||
} saveslot_t;
|
} saveslot_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,6 +66,16 @@ errorret_t saveSlotInit(saveslot_t *slot);
|
|||||||
*/
|
*/
|
||||||
errorret_t saveSlotNewGame(void);
|
errorret_t saveSlotNewGame(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current save slot's (SAVE.slot) in-progress cutscene and
|
||||||
|
* dirties the save (SAVE.slotDirty), same as storySetFlagValue - so the
|
||||||
|
* next save write persists it. Use this (not writing SAVE.slot.
|
||||||
|
* currentCutscene directly) anywhere gameplay needs to change it.
|
||||||
|
*
|
||||||
|
* @param file Asset path of the cutscene now in progress, or "" for none.
|
||||||
|
*/
|
||||||
|
void saveSlotSetCurrentCutscene(const char_t *file);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the save slot is in use, this is determined by checking if the
|
* Checks if the save slot is in use, this is determined by checking if the
|
||||||
* player name is set or not.
|
* player name is set or not.
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ errorret_t saveSlotCurrentWriteJSON(
|
|||||||
|
|
||||||
memoryCopy(ver1.storyFlags, slot->storyFlags, sizeof(ver1.storyFlags));
|
memoryCopy(ver1.storyFlags, slot->storyFlags, sizeof(ver1.storyFlags));
|
||||||
|
|
||||||
|
stringCopy(
|
||||||
|
ver1.currentCutscene, slot->currentCutscene, sizeof(ver1.currentCutscene)
|
||||||
|
);
|
||||||
|
|
||||||
errorChain(saveSlotVer1WriteJSON(&ver1, doc, object));
|
errorChain(saveSlotVer1WriteJSON(&ver1, doc, object));
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -66,5 +70,10 @@ errorret_t saveSlotCurrentReadJSON(saveslot_t *slot, yyjson_val *object) {
|
|||||||
|
|
||||||
memoryCopy(slot->storyFlags, ver1.storyFlags, sizeof(slot->storyFlags));
|
memoryCopy(slot->storyFlags, ver1.storyFlags, sizeof(slot->storyFlags));
|
||||||
|
|
||||||
|
stringCopy(
|
||||||
|
slot->currentCutscene, ver1.currentCutscene,
|
||||||
|
sizeof(slot->currentCutscene)
|
||||||
|
);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ errorret_t saveSlotVer1WriteJSON(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeString("currentCutscene", slot->currentCutscene);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,5 +96,10 @@ errorret_t saveSlotVer1ReadJSON(saveslotver1_t *slot, yyjson_val *object) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readString(
|
||||||
|
"currentCutscene", slot->currentCutscene, SAVE_SLOT_VER1_CUTSCENE_DEFAULT,
|
||||||
|
SAVE_SLOT_VER1_CUTSCENE_NAME_MAX - 1
|
||||||
|
);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,10 @@
|
|||||||
// bump instead.
|
// bump instead.
|
||||||
#define SAVE_SLOT_VER1_NAME_LENGTH 8
|
#define SAVE_SLOT_VER1_NAME_LENGTH 8
|
||||||
#define SAVE_SLOT_VER1_MAP_NAME_MAX 10
|
#define SAVE_SLOT_VER1_MAP_NAME_MAX 10
|
||||||
|
#define SAVE_SLOT_VER1_CUTSCENE_NAME_MAX 128
|
||||||
|
// Own frozen copy of save/slot/saveslot.h's SAVE_SLOT_CUTSCENE_DEFAULT, for
|
||||||
|
// the same reason as the sizes above - never reference the live constant.
|
||||||
|
#define SAVE_SLOT_VER1_CUTSCENE_DEFAULT "cutscenes/new_game.jsonc"
|
||||||
|
|
||||||
// Independent from saveslotcache_t (save/slot/saveslot.h) by design - this
|
// Independent from saveslotcache_t (save/slot/saveslot.h) by design - this
|
||||||
// is what version 1 actually put on disk, frozen forever. saveSlotCurrent
|
// is what version 1 actually put on disk, frozen forever. saveSlotCurrent
|
||||||
@@ -52,6 +56,8 @@ typedef struct saveslotver1_s {
|
|||||||
uint8_t partyOrder[PARTY_ACTIVE_SIZE_MAX];
|
uint8_t partyOrder[PARTY_ACTIVE_SIZE_MAX];
|
||||||
|
|
||||||
storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX];
|
storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX];
|
||||||
|
|
||||||
|
char_t currentCutscene[SAVE_SLOT_VER1_CUTSCENE_NAME_MAX];
|
||||||
} saveslotver1_t;
|
} saveslotver1_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||||
|
|
||||||
|
if(SAVE.slot.currentCutscene[0] != '\0') {
|
||||||
|
cutsceneSystemLoad(SAVE.slot.currentCutscene);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiMainMenuOpenSelectSave(void *userData) {
|
void uiMainMenuOpenSelectSave(void *userData) {
|
||||||
|
|||||||
Reference in New Issue
Block a user