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": {
|
||||
"hero": {
|
||||
"attack": 10, "defense": 5, "magic": 0, "speed": 10, "luck": 0,
|
||||
"healthMax": 30, "mpMax": 10
|
||||
"attack": 10,
|
||||
"defense": 5,
|
||||
"magic": 0,
|
||||
"speed": 10,
|
||||
"luck": 0,
|
||||
"healthMax": 30,
|
||||
"mpMax": 10
|
||||
},
|
||||
"companion": {
|
||||
"attack": 8, "defense": 6, "magic": 4, "speed": 8, "luck": 1,
|
||||
"healthMax": 25, "mpMax": 15
|
||||
"attack": 8,
|
||||
"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 "assert/assert.h"
|
||||
#include "ui/overlay/uifatalerror.h"
|
||||
#include "save/slot/saveslot.h"
|
||||
|
||||
cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
|
||||
@@ -84,6 +85,8 @@ void cutsceneSystemStartCutsceneWith(
|
||||
);
|
||||
|
||||
cutsceneSystemPrepare(cutscene, interact, interacted);
|
||||
saveSlotSetCurrentCutscene(CUTSCENE_SYSTEM.loadedFile);
|
||||
|
||||
cutsceneSystemNext();
|
||||
}
|
||||
|
||||
@@ -160,8 +163,14 @@ void cutsceneSystemLoad(const char_t *file) {
|
||||
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);
|
||||
cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene);
|
||||
}
|
||||
|
||||
void cutsceneRestart(void) {
|
||||
|
||||
@@ -27,6 +27,12 @@ errorret_t saveSlotInit(saveslot_t *slot) {
|
||||
// Set the default party.
|
||||
errorChain(partyInit(&slot->party));
|
||||
|
||||
// Set the default current cutscene.
|
||||
stringCopy(
|
||||
slot->currentCutscene, SAVE_SLOT_CUTSCENE_DEFAULT,
|
||||
sizeof(slot->currentCutscene) - 1
|
||||
);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -41,6 +47,15 @@ errorret_t saveSlotNewGame(void) {
|
||||
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) {
|
||||
assertNotNull(slot, "Slot cannot be null");
|
||||
return slot->name[0] != '\0';
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "rpg/battle/party.h"
|
||||
#include "rpg/story/story.h"
|
||||
#include "time/timeepoch.h"
|
||||
#include "asset/assetfile.h"
|
||||
#include "yyjson.h"
|
||||
|
||||
#define SAVE_SLOT_NAME_LENGTH 8
|
||||
@@ -19,6 +20,8 @@
|
||||
#endif
|
||||
|
||||
#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 {
|
||||
char_t name[SAVE_SLOT_NAME_LENGTH + 1];// 8 characters + null terminator
|
||||
@@ -34,6 +37,10 @@ typedef struct saveslot_s {
|
||||
saveslotcache_t cachedData;
|
||||
party_t party;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -59,6 +66,16 @@ errorret_t saveSlotInit(saveslot_t *slot);
|
||||
*/
|
||||
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
|
||||
* player name is set or not.
|
||||
|
||||
@@ -37,6 +37,10 @@ errorret_t saveSlotCurrentWriteJSON(
|
||||
|
||||
memoryCopy(ver1.storyFlags, slot->storyFlags, sizeof(ver1.storyFlags));
|
||||
|
||||
stringCopy(
|
||||
ver1.currentCutscene, slot->currentCutscene, sizeof(ver1.currentCutscene)
|
||||
);
|
||||
|
||||
errorChain(saveSlotVer1WriteJSON(&ver1, doc, object));
|
||||
errorOk();
|
||||
}
|
||||
@@ -66,5 +70,10 @@ errorret_t saveSlotCurrentReadJSON(saveslot_t *slot, yyjson_val *object) {
|
||||
|
||||
memoryCopy(slot->storyFlags, ver1.storyFlags, sizeof(slot->storyFlags));
|
||||
|
||||
stringCopy(
|
||||
slot->currentCutscene, ver1.currentCutscene,
|
||||
sizeof(slot->currentCutscene)
|
||||
);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ errorret_t saveSlotVer1WriteJSON(
|
||||
);
|
||||
}
|
||||
|
||||
writeString("currentCutscene", slot->currentCutscene);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
// bump instead.
|
||||
#define SAVE_SLOT_VER1_NAME_LENGTH 8
|
||||
#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
|
||||
// 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];
|
||||
|
||||
storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX];
|
||||
|
||||
char_t currentCutscene[SAVE_SLOT_VER1_CUTSCENE_NAME_MAX];
|
||||
} saveslotver1_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,6 +63,10 @@ void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) {
|
||||
}
|
||||
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
|
||||
if(SAVE.slot.currentCutscene[0] != '\0') {
|
||||
cutsceneSystemLoad(SAVE.slot.currentCutscene);
|
||||
}
|
||||
}
|
||||
|
||||
void uiMainMenuOpenSelectSave(void *userData) {
|
||||
|
||||
Reference in New Issue
Block a user