Sunset the dedicated cutscene asset loader and the item pool

Cutscenes now load through the generic ASSET_LOADER_TYPE_JSON loader
and parse straight into fixed static buffers via new cutsceneParseDoc/
cutsceneParseItem/cutsceneCountItems helpers (cutscene.c), removing the
bespoke ASSET_LOADER_TYPE_CUTSCENE pipeline entirely. Only one cutscene
(plus one INSERT target) is ever resident, so each resolve locks,
steals the parsed doc, unlocks, and force-reaps immediately - fixing a
stale-cache-reuse bug where revisiting a cutscene name after a
different one could return data from the shared buffer's previous
occupant.

ENTITY_WALK_TO and MAP_AREA_WAIT now store their waypoints/area ids in
fixed-size arrays inline on their own item struct instead of a shared
bump-allocated pool, so pool/poolOffset are gone from every item type's
Load callback. cutscene_t.dataSize (and the CUTSCENE(...) macro's SIZE
parameter) is removed along with it - it sized entries out of that same
now-deleted pool and had no other reader.

NPC #4's battletest cutscene and the main menu's cutscene now resolve
their target by name on interact instead of eagerly at init, so a
resolved-once pointer is never held past the point its backing buffer
can be reused for something else.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-09-13 15:18:57 -05:00
co-authored by Claude Sonnet 5
parent 109ddc9e1e
commit 2ee97ad4ce
98 changed files with 443 additions and 957 deletions
-1
View File
@@ -17,6 +17,5 @@ add_subdirectory(locale)
add_subdirectory(json)
add_subdirectory(chunk)
add_subdirectory(dmf)
add_subdirectory(cutscene)
add_subdirectory(wav)
add_subdirectory(mp3)
-6
View File
@@ -52,12 +52,6 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
.dispose = assetChunkDispose
},
[ASSET_LOADER_TYPE_CUTSCENE] = {
.loadSync = assetCutsceneLoaderSync,
.loadAsync = assetCutsceneLoaderAsync,
.dispose = assetCutsceneDispose
},
[ASSET_LOADER_TYPE_WAV] = {
.loadSync = assetWavLoaderSync,
.loadAsync = assetWavLoaderAsync,
-5
View File
@@ -13,7 +13,6 @@
#include "asset/loader/locale/assetlocaleloader.h"
#include "asset/loader/json/assetjsonloader.h"
#include "asset/loader/chunk/assetchunkloader.h"
#include "asset/loader/cutscene/assetcutsceneloader.h"
#include "asset/loader/wav/assetwavloader.h"
#include "asset/loader/mp3/assetmp3loader.h"
@@ -27,7 +26,6 @@ typedef enum {
ASSET_LOADER_TYPE_LOCALE,
ASSET_LOADER_TYPE_JSON,
ASSET_LOADER_TYPE_CHUNK,
ASSET_LOADER_TYPE_CUTSCENE,
ASSET_LOADER_TYPE_WAV,
ASSET_LOADER_TYPE_MP3,
@@ -42,7 +40,6 @@ typedef union {
assetlocaleloaderloading_t locale;
assetjsonloaderloading_t json;
assetchunkloaderloading_t chunk;
assetcutsceneloaderloading_t cutscene;
assetwavloaderloading_t wav;
assetmp3loaderloading_t mp3;
} assetloaderloading_t;
@@ -55,7 +52,6 @@ typedef union {
assetlocaleoutput_t locale;
assetjsonoutput_t json;
assetchunkoutput_t chunk;
assetcutsceneoutput_t cutscene;
assetwavoutput_t wav;
assetmp3output_t mp3;
} assetloaderoutput_t;
@@ -66,7 +62,6 @@ typedef union {
assetlocaleloaderinput_t locale;
assetjsonloaderinput_t json;
assetchunkloaderinput_t chunk;
assetcutsceneloaderinput_t cutscene;
assetwavloaderinput_t wav;
assetmp3loaderinput_t mp3;
} assetloaderinput_t;
@@ -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
assetcutsceneloader.c
)
@@ -1,258 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetcutsceneloader.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "asset/loader/assetloading.h"
#include "asset/loader/assetentry.h"
#include "asset/loader/assetloader.h"
#include "asset/asset.h"
#include "rpg/cutscene/item/json/cutscenejsonpauseflags.h"
errorret_t assetCutsceneParseItem(
assetloading_t *loading,
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset,
const uint8_t index
) {
if(!yyjson_is_obj(itemObj)) {
assetLoaderErrorThrow(
loading,
"Cutscene item %u is not an object",
(uint32_t)index
);
}
const char_t *typeName = yyjson_get_str(yyjson_obj_get(itemObj, "type"));
if(!cutsceneItemLookupType(typeName, &item->type)) {
assetLoaderErrorThrow(
loading,
"Cutscene item %u: missing/unsupported item type %s",
(uint32_t)index,
typeName
);
}
cutsceneitemloadcallback_t *load = CUTSCENE_ITEM_CALLBACKS[item->type].load;
if(load == NULL) {
assetLoaderErrorThrow(
loading, "Cutscene item %u: unhandled item type %u", (uint32_t)index,
(uint32_t)item->type
);
}
errorret_t result = load(itemObj, item, pool, poolOffset);
if(errorIsNotOk(result)) {
loading->entry->state = ASSET_ENTRY_STATE_ERROR;
errorChain(result);
}
errorOk();
}
size_t assetCutsceneComputePoolSize(yyjson_val *itemsArr) {
size_t size = 0;
yyjson_arr_iter iter = yyjson_arr_iter_with(itemsArr);
yyjson_val *itemVal;
while((itemVal = yyjson_arr_iter_next(&iter)) != NULL) {
const char_t *typeName = yyjson_get_str(yyjson_obj_get(itemVal, "type"));
cutsceneitemtype_t type;
if(!cutsceneItemLookupType(typeName, &type)) continue;
cutsceneitempoolsizecallback_t *poolSize =
CUTSCENE_ITEM_CALLBACKS[type].poolSize;
if(poolSize == NULL) continue;
size = cutsceneJsonPoolAlign(size) + poolSize(itemVal);
}
return size;
}
// Raw file-read scratch space - fixed-size (ASSET_CUTSCENE_FILE_SIZE_MAX is
// already a hard cap below) rather than malloc'd per load. Safe to share:
// ASSET only ever runs one async load at a time on its single load thread
// (see assetUpdateAsync), and yyjson_read copies everything it needs out
// of this buffer into the doc before this function returns, so it's free
// to be overwritten by the next load.
static uint8_t CUTSCENE_FILE_BUFFER[ASSET_CUTSCENE_FILE_SIZE_MAX];
errorret_t assetCutsceneLoaderAsync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertNotMainThread("Should be called from an async thread.");
if(
loading->loading.cutscene.state != ASSET_CUTSCENE_LOADING_STATE_READ_FILE
) {
errorOk();
}
const assetcutsceneloaderinput_t *input = loading->entry->input != NULL
? &loading->entry->input->cutscene
: NULL;
const bool_t toInsert =
input != NULL && input->dest == ASSET_CUTSCENE_LOAD_DEST_INSERT;
const char_t *filePath = (input != NULL && input->path[0] != '\0')
? input->path
: loading->entry->name;
cutsceneitem_t *targetItems =
toInsert ? CUTSCENE_INSERT_ITEMS : CUTSCENE_LOADED_ITEMS;
const size_t targetItemsMax =
toInsert ? CUTSCENE_INSERT_ITEMS_MAX : CUTSCENE_LOADED_ITEMS_MAX;
uint8_t *targetPool = toInsert ? CUTSCENE_INSERT_POOL : CUTSCENE_LOADED_POOL;
const size_t targetPoolMax =
toInsert ? CUTSCENE_INSERT_POOL_SIZE_MAX : CUTSCENE_LOADED_POOL_SIZE_MAX;
cutscene_t *targetScene = toInsert ? &CUTSCENE_INSERT_SCENE : &CUTSCENE_LOADED_SCENE;
assetfile_t *file = &loading->loading.cutscene.file;
assetLoaderErrorChain(loading,
assetFileInit(file, filePath, NULL, NULL)
);
if(file->size > ASSET_CUTSCENE_FILE_SIZE_MAX) {
assetLoaderErrorThrow(
loading, "Cutscene JSON exceeds maximum allowed size"
);
}
size_t fileSize = (size_t)file->size;
assetLoaderErrorChain(loading, assetFileOpen(file));
assetLoaderErrorChain(
loading, assetFileRead(file, CUTSCENE_FILE_BUFFER, fileSize)
);
assertTrue(
file->lastRead == file->size,
"Failed to read entire cutscene file."
);
assetLoaderErrorChain(loading, assetFileClose(file));
assetLoaderErrorChain(loading, assetFileDispose(file));
// Parsing is pure CPU work with no main-thread dependency (every item
// type's load callback just parses fields - see cutsceneCutsceneLoad's
// doc comment for how CUTSCENE items avoid needing one), so it happens
// right here instead of bouncing to the sync phase.
yyjson_doc *doc = yyjson_read(
(char *)CUTSCENE_FILE_BUFFER, fileSize,
YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS
);
if(doc == NULL) {
assetLoaderErrorThrow(loading, "Failed to parse cutscene JSON");
}
yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *itemsArr = yyjson_obj_get(root, "items");
if(!yyjson_is_arr(itemsArr)) {
yyjson_doc_free(doc);
assetLoaderErrorThrow(loading, "Cutscene JSON is missing an 'items' array");
}
size_t itemCount = yyjson_arr_size(itemsArr);
if(itemCount > targetItemsMax) {
yyjson_doc_free(doc);
assetLoaderErrorThrow(
loading, "Cutscene has more items than its target buffer allows"
);
}
cutscenepause_t pause = CUTSCENE_PAUSE_DEFAULT;
yyjson_val *pauseArr = yyjson_obj_get(root, "pause");
if(yyjson_is_arr(pauseArr)) {
if(!cutsceneJsonParsePauseFlags(pauseArr, &pause)) {
yyjson_doc_free(doc);
assetLoaderErrorThrow(
loading, "Cutscene JSON has an invalid 'pause' flag"
);
}
}
size_t poolSize = assetCutsceneComputePoolSize(itemsArr);
if(poolSize > targetPoolMax) {
yyjson_doc_free(doc);
assetLoaderErrorThrow(
loading, "Cutscene needs more pool space than its target buffer allows"
);
}
// Everything structural has validated by this point, so it's safe to
// start overwriting the target's shared items/pool buffers - a per-item
// parse failure past this point still leaves them partially overwritten,
// but that's fine: any load failure here already sends the game into the
// fatal-error overlay (see cutsceneSystemLoad/cutsceneCutsceneResolve/
// cutsceneInsertResolve), so nothing keeps running against stale data.
memoryZero(targetItems, itemCount * sizeof(cutsceneitem_t));
size_t poolOffset = 0;
yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr);
yyjson_val *itemVal;
uint8_t index = 0;
while((itemVal = yyjson_arr_iter_next(&itemIter)) != NULL) {
errorret_t itemResult = assetCutsceneParseItem(
loading, itemVal, &targetItems[index],
targetPool, &poolOffset, index
);
if(errorIsNotOk(itemResult)) {
yyjson_doc_free(doc);
errorChain(itemResult);
}
index++;
}
// Takes ownership of doc - marker/cutscene-reference name fields point
// directly into it (see cutsceneCutsceneRefLoad/cutsceneMarkerLoad), so
// it must outlive this load, replacing whatever the previously loaded
// cutscene left behind for this same destination.
if(toInsert) cutsceneInsertSetDoc(doc);
else cutsceneLoadedSetDoc(doc);
targetScene->itemCount = (uint8_t)itemCount;
targetScene->pause = pause;
targetScene->dataSize = 0;
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_PARSE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
errorOk();
}
errorret_t assetCutsceneLoaderSync(assetloading_t *loading) {
assertNotNull(loading, "Loading cannot be NULL");
assertTrue(loading->type == ASSET_LOADER_TYPE_CUTSCENE, "Invalid type.");
assertIsMainThread("Must be called from the main thread.");
if(loading->loading.cutscene.state == ASSET_CUTSCENE_LOADING_STATE_INITIAL) {
loading->loading.cutscene.state = ASSET_CUTSCENE_LOADING_STATE_READ_FILE;
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
errorOk();
}
// Everything else already happened in the async phase - see
// assetCutsceneLoaderAsync. This flip still has to happen from here
// rather than the async phase setting LOADED directly, since
// assetUpdate() only fires an entry's onLoaded callback for entries
// that finish via this sync path.
assertTrue(
loading->loading.cutscene.state == ASSET_CUTSCENE_LOADING_STATE_PARSE,
"Unexpected cutscene loading state."
);
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
errorOk();
}
errorret_t assetCutsceneDispose(assetentry_t *entry) {
assertNotNull(entry, "Entry cannot be NULL");
assertTrue(entry->type == ASSET_LOADER_TYPE_CUTSCENE, "Invalid type.");
assertIsMainThread("Must be called from the main thread.");
// Nothing to free here - a loaded cutscene's data lives in the fixed,
// shared CUTSCENE_LOADED_SCENE/ITEMS/POOL globals (see cutscenesystem.h),
// not per-entry storage; this entry only ever drove the read+parse
// pipeline into them.
errorOk();
}
@@ -1,127 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/assetfile.h"
#include "rpg/cutscene/cutscene.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/item/json/cutscenejsonpool.h"
#include "yyjson.h"
#define ASSET_CUTSCENE_FILE_SIZE_MAX (1024 * 64)
typedef struct assetloading_s assetloading_t;
typedef struct assetentry_s assetentry_t;
// Which fixed, shared buffer set (see cutscenesystem.h) a load should
// parse into - MAIN for the one JSON-authored cutscene resident via
// cutsceneSystemLoad/cutsceneCutsceneResolve, INSERT for the one resident
// via cutsceneInsertResolve (see CUTSCENE_ITEM_TYPE_INSERT).
typedef enum {
ASSET_CUTSCENE_LOAD_DEST_MAIN,
ASSET_CUTSCENE_LOAD_DEST_INSERT
} assetcutsceneloaddest_t;
typedef struct {
assetcutsceneloaddest_t dest;
// Optional file path override, same trick as assetjsonloaderinput_t -
// when path[0] is non-zero this is read instead of loading->entry->name,
// so a synthetic cache key (e.g. "insert:cutscenes/foo.jsonc", used to
// avoid a single entry being shared - and its dest silently ignored -
// between two different logical loads of the same file, see
// asset.c/assetGetEntry's name-only dedup) can still point at the real
// file.
char_t path[ASSET_FILE_NAME_MAX];
} assetcutsceneloaderinput_t;
typedef enum {
ASSET_CUTSCENE_LOADING_STATE_INITIAL,
ASSET_CUTSCENE_LOADING_STATE_READ_FILE,
ASSET_CUTSCENE_LOADING_STATE_PARSE
} assetcutsceneloadingstate_t;
typedef struct {
assetfile_t file;
assetcutsceneloadingstate_t state;
} assetcutsceneloaderloading_t;
typedef struct {
// Nothing - a loaded cutscene's data lives entirely in the fixed,
// shared CUTSCENE_LOADED_SCENE/ITEMS/POOL globals (see cutscenesystem.h)
// that assetCutsceneLoaderAsync parses directly into, not per-entry
// storage. This entry only ever exists to drive the read+parse
// pipeline, and is unlocked (see cutsceneSystemLoad/
// cutsceneCutsceneResolve) the instant that finishes.
uint8_t unused;
} assetcutsceneoutput_t;
/**
* Parses one cutscene item object into item, writing any pool-backed
* array data (entityWalkTo positions, mapAreaWait areaIds) into pool at
* *poolOffset and advancing it. Every field/enum name is validated as
* real (untrusted, authored) file content - failures are real errors, not
* asserts. Dispatches to the matching type's Load callback (see
* CUTSCENE_ITEM_CALLBACKS in rpg/cutscene/item/cutsceneitem.h) - the
* per-type parsing logic itself lives there now, colocated with that
* type's runtime behavior.
*
* @param loading Loading information for the asset being loaded.
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Base pointer of the entry's pool allocation (may be NULL if
* assetCutsceneComputePoolSize returned 0).
* @param poolOffset In/out cursor into pool.
* @param index Index of this item within the cutscene, for error messages.
* @return Error code indicating success or failure of the parse.
*/
errorret_t assetCutsceneParseItem(
assetloading_t *loading,
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset,
const uint8_t index
);
/**
* Asynchronous loader for cutscene assets. Reads the raw JSONC file bytes
* and, in the same call, parses them directly into one of the fixed,
* shared buffer sets in cutscenesystem.h - CUTSCENE_LOADED_ITEMS/POOL by
* default, or CUTSCENE_INSERT_ITEMS/POOL when
* loading->entry->input->cutscene.dest is ASSET_CUTSCENE_LOAD_DEST_INSERT -
* no heap allocation at all, since only one cutscene is ever resident per
* destination. None of that work touches the main thread (every item
* type's load callback is pure parsing now; CUTSCENE items defer their
* nested asset resolution to cutsceneCutsceneStart), so doing it all here
* keeps a large cutscene from ever blocking a frame.
*
* @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation.
*/
errorret_t assetCutsceneLoaderAsync(assetloading_t *loading);
/**
* Synchronous loader for cutscene assets. All actual work already
* happened in the async phase - this only flips the entry to LOADED,
* which must happen through this sync callback (rather than the async
* phase setting it directly) so assetUpdate()'s onLoaded dispatch, which
* only fires for entries finishing via the sync path, still runs.
*
* @param loading Loading information for the asset being loaded.
* @return Error code indicating success or failure of the load operation.
*/
errorret_t assetCutsceneLoaderSync(assetloading_t *loading);
/**
* Disposer for cutscene assets.
*
* @param entry Asset entry containing the cutscene data to dispose.
* @return Error code indicating success or failure of the dispose operation.
*/
errorret_t assetCutsceneDispose(assetentry_t *entry);
+4 -5
View File
@@ -12,11 +12,10 @@
// Forward-declared rather than including asset/asset.h - this header only
// ever needs a pointer to it, and pulling in the full asset module here
// creates a real include cycle for anything that reaches this header via
// asset/loader/cutscene/assetcutsceneloader.h (asset.h -> ... ->
// assetloader.h -> assetcutsceneloader.h -> cutscene.h -> cutsceneitem.h
// -> the audio cutscene items -> this header -> asset/asset.h again,
// mid-parse). audiomixerchannel.c itself still includes the full header.
// risks an include cycle for anything that reaches this header via a
// cutscene item (cutscene.h -> cutsceneitem.h -> the audio cutscene items
// -> this header -> asset/asset.h, mid-parse). audiomixerchannel.c itself
// still includes the full header.
typedef struct assetentry_s assetentry_t;
typedef struct audiostream_s audiostream_t;
+1
View File
@@ -6,6 +6,7 @@
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
cutscene.c
cutscenesystem.c
)
+107
View File
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cutscene.h"
#include "item/json/cutscenejsonpauseflags.h"
#include "util/memory.h"
#include "assert/assert.h"
errorret_t cutsceneParseItem(
yyjson_val *itemObj,
cutsceneitem_t *item,
const uint8_t index
) {
assertNotNull(itemObj, "Item object cannot be NULL");
assertNotNull(item, "Item cannot be NULL");
if(!yyjson_is_obj(itemObj)) {
errorThrow("Cutscene item %u is not an object", (uint32_t)index);
}
const char_t *typeName = yyjson_get_str(yyjson_obj_get(itemObj, "type"));
if(!cutsceneItemLookupType(typeName, &item->type)) {
errorThrow(
"Cutscene item %u: missing/unsupported item type %s",
(uint32_t)index, typeName
);
}
cutsceneitemloadcallback_t *load = CUTSCENE_ITEM_CALLBACKS[item->type].load;
if(load == NULL) {
errorThrow(
"Cutscene item %u: unhandled item type %u",
(uint32_t)index, (uint32_t)item->type
);
}
errorret_t result = load(itemObj, item);
if(errorIsNotOk(result)) errorChain(result);
errorOk();
}
size_t cutsceneCountItems(yyjson_doc *doc) {
assertNotNull(doc, "Doc cannot be NULL");
yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *itemsArr = yyjson_obj_get(root, "items");
if(!yyjson_is_arr(itemsArr)) return 0;
return yyjson_arr_size(itemsArr);
}
errorret_t cutsceneParseDoc(
yyjson_doc *doc,
cutscene_t *scene,
cutsceneitem_t *items,
size_t itemsMax
) {
assertNotNull(doc, "Doc cannot be NULL");
assertNotNull(scene, "Scene cannot be NULL");
assertNotNull(items, "Items cannot be NULL");
assertTrue(itemsMax > 0, "Items max cannot be 0");
yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *itemsArr = yyjson_obj_get(root, "items");
if(!yyjson_is_arr(itemsArr)) {
errorThrow("Cutscene JSON is missing an 'items' array");
}
size_t itemCount = yyjson_arr_size(itemsArr);
if(itemCount > itemsMax) {
errorThrow("Cutscene has more items than its target buffer allows");
}
cutscenepause_t pause = CUTSCENE_PAUSE_DEFAULT;
yyjson_val *pauseArr = yyjson_obj_get(root, "pause");
if(yyjson_is_arr(pauseArr)) {
if(!cutsceneJsonParsePauseFlags(pauseArr, &pause)) {
errorThrow("Cutscene JSON has an invalid 'pause' flag");
}
}
// Everything structural has validated by this point, so it's safe to
// start overwriting items - a per-item parse failure past this point
// still leaves it partially overwritten, but that's fine: any load
// failure here already sends the game into the fatal-error overlay
// (see cutsceneSystemLoad/cutsceneCutsceneResolve/cutsceneInsertResolve),
// so nothing keeps running against stale data.
memoryZero(items, itemCount * sizeof(cutsceneitem_t));
yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr);
yyjson_val *itemVal;
uint8_t index = 0;
while((itemVal = yyjson_arr_iter_next(&itemIter)) != NULL) {
errorret_t itemResult = cutsceneParseItem(itemVal, &items[index], index);
if(errorIsNotOk(itemResult)) errorChain(itemResult);
index++;
}
scene->itemCount = (uint8_t)itemCount;
scene->pause = pause;
errorOk();
}
+53 -8
View File
@@ -13,10 +13,6 @@ typedef struct cutscene_s {
const cutsceneitem_t *items;
uint8_t itemCount;
cutscenepause_t pause;
// Size in bytes of this cutscene's custom user data, carved out of
// CUTSCENE_SYSTEM.data while the cutscene is running.
size_t dataSize;
} cutscene_t;
/**
@@ -25,19 +21,16 @@ typedef struct cutscene_s {
*
* @param NAME Suffix for the generated CUTSCENE_##NAME cutscene/items
* statics.
* @param SIZE Bytes of custom user data this cutscene needs, carved out
* of CUTSCENE_SYSTEM.data while it runs.
* @param PAUSE_TYPE Suffix of the CUTSCENE_PAUSE_* flag(s) to apply
* while this cutscene runs (e.g. NONE, ALL).
* @param ... The cutscene's cutsceneitem_t entries, in order.
*/
#define CUTSCENE(NAME, SIZE, PAUSE_TYPE, ...) \
#define CUTSCENE(NAME, PAUSE_TYPE, ...) \
static const cutsceneitem_t CUTSCENE_##NAME##_ITEMS[] = { __VA_ARGS__ }; \
static const cutscene_t CUTSCENE_##NAME = { \
.items = CUTSCENE_##NAME##_ITEMS, \
.itemCount = sizeof(CUTSCENE_##NAME##_ITEMS) / sizeof(cutsceneitem_t), \
.pause = CUTSCENE_PAUSE_##PAUSE_TYPE, \
.dataSize = SIZE \
};
/**
@@ -79,3 +72,55 @@ typedef struct cutscene_s {
.type = CUTSCENE_ITEM_TYPE_INSERT, \
.insert = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \
}
/**
* Parses one cutscene item object into item. Every field/enum name is
* validated as real (untrusted, authored) file content - failures are
* real errors, not asserts. Dispatches to the matching type's Load
* callback (see CUTSCENE_ITEM_CALLBACKS in item/cutsceneitem.h) - the
* per-type parsing logic itself lives there, colocated with that type's
* runtime behavior.
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param index Index of this item within the cutscene, for error messages.
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneParseItem(
yyjson_val *itemObj,
cutsceneitem_t *item,
const uint8_t index
);
/**
* Counts how many entries are in doc's top-level "items" array, without
* parsing any of them - e.g. so a caller that wants to malloc its own
* items array instead of using a fixed one could size it first (we don't
* do that ourselves, but the option stays open).
*
* @param doc Already-parsed JSON document.
* @return Number of entries in "items", or 0 if it's missing/not an array.
*/
size_t cutsceneCountItems(yyjson_doc *doc);
/**
* Parses doc's "items" (and optional "pause") into items, and updates
* scene's itemCount/pause - scene->items is left untouched, since the
* caller already points it at items. Purely a parser: doesn't touch the
* asset system, doesn't lock/load anything, and doesn't take ownership of
* doc - the caller is responsible for both loading doc beforehand and for
* its lifetime afterward (name/string fields written into items point
* directly into it, so it must outlive items' use).
*
* @param doc Already-parsed JSON document to read from.
* @param scene Destination scene - itemCount/pause are overwritten.
* @param items Destination item array, capacity itemsMax.
* @param itemsMax Capacity of items.
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneParseDoc(
yyjson_doc *doc,
cutscene_t *scene,
cutsceneitem_t *items,
size_t itemsMax
);
+32 -17
View File
@@ -11,17 +11,16 @@
#include "util/string.h"
#include "assert/assert.h"
#include "asset/asset.h"
#include "asset/loader/assetloader.h"
#include "ui/overlay/uifatalerror.h"
cutscenesystem_t CUTSCENE_SYSTEM;
cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
uint8_t CUTSCENE_LOADED_POOL[CUTSCENE_LOADED_POOL_SIZE_MAX];
cutscene_t CUTSCENE_LOADED_SCENE = {
.items = CUTSCENE_LOADED_ITEMS,
.itemCount = 0,
.pause = CUTSCENE_PAUSE_NONE,
.dataSize = 0
.pause = CUTSCENE_PAUSE_NONE
};
static yyjson_doc *CUTSCENE_LOADED_DOC = NULL;
@@ -31,12 +30,10 @@ void cutsceneLoadedSetDoc(yyjson_doc *doc) {
}
cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
uint8_t CUTSCENE_INSERT_POOL[CUTSCENE_INSERT_POOL_SIZE_MAX];
cutscene_t CUTSCENE_INSERT_SCENE = {
.items = CUTSCENE_INSERT_ITEMS,
.itemCount = 0,
.pause = CUTSCENE_PAUSE_NONE,
.dataSize = 0
.pause = CUTSCENE_PAUSE_NONE
};
static yyjson_doc *CUTSCENE_INSERT_DOC = NULL;
@@ -54,11 +51,6 @@ void cutsceneSystemPrepare(
entity_t *interact,
entity_t *interacted
) {
assertTrue(
cutscene->dataSize < CUTSCENE_SYSTEM_SIZE_MAX,
"Cutscene data size exceeds CUTSCENE_SYSTEM_SIZE_MAX"
);
CUTSCENE_SYSTEM.scene = cutscene;
CUTSCENE_SYSTEM.pause = cutscene->pause;
CUTSCENE_SYSTEM.entityInteract = interact;
@@ -98,19 +90,42 @@ void cutsceneSystemLoad(const char_t *file) {
char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s", file);
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL);
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_JSON, NULL);
errorret_t result = assetRequireLoaded(entry);
// Released the instant loading finishes - the loader already parsed
// everything we need into CUTSCENE_LOADED_SCENE/ITEMS/POOL, so nothing
// further depends on this entry (see cutscenesystem.h's doc comment on
// CUTSCENE_LOADED_ITEMS_MAX for why only one cutscene stays resident).
assetUnlockEntry(entry);
if(errorIsNotOk(result)) {
assetUnlockEntry(entry);
errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL);
return;
}
// Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_LOADED_ITEMS' name pointers
// the moment this entry gets reaped.
yyjson_doc *doc = entry->data.json;
entry->data.json = NULL;
assetUnlockEntry(entry);
// Force this now-zero-ref entry to actually go away right away, rather
// than leaving it languishing as LOADED until something else happens to
// trigger a reap - assetRequireLoaded treats an already-LOADED entry as
// an instant cache hit with no reparsing, so a stale leftover entry for
// this same path would otherwise make a later load of it wrongly skip
// reloading, still returning CUTSCENE_LOADED_SCENE even after some
// other cutscene has since overwritten it.
assetReapUnused();
errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_LOADED_SCENE, CUTSCENE_LOADED_ITEMS,
CUTSCENE_LOADED_ITEMS_MAX
);
if(errorIsNotOk(parseResult)) {
yyjson_doc_free(doc);
errorCatch(errorPrint(parseResult));
uiFatalErrorOpen(NULL);
return;
}
cutsceneLoadedSetDoc(doc);
cutsceneSystemStartCutscene(&CUTSCENE_LOADED_SCENE);
}
+41 -44
View File
@@ -17,8 +17,8 @@ typedef struct entity_s entity_t;
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
#define CUTSCENE_TEXT_MINI_LAST_CREATED ((uint8_t)0xFA)
// Maximum number of bytes a running cutscene may request via
// cutscene_t.dataSize.
// Size in bytes of cutscenesystem_t.userData, the running cutscene's
// scratch space.
#define CUTSCENE_SYSTEM_SIZE_MAX 8192
// Size of CUTSCENE_SYSTEM.textCache - keep >= UI_KEYBOARD_TEXT_MAX (see
@@ -30,61 +30,59 @@ typedef struct entity_s entity_t;
// CUTSCENE item hands control off completely (see cutsceneCutsceneStart)
// and cutsceneSystemLoad/cutsceneCutsceneResolve release their asset lock
// the instant loading finishes - so there's no benefit to malloc'ing a
// fresh items/pool buffer per load. Instead the asset loader
// (assetCutsceneLoaderAsync) parses directly into these fixed, static
// buffers. Bump these if a real cutscene ever needs more; C-authored
// cutscenes (the CUTSCENE(...) macro) don't use these at all - they're
// compiled directly into their own static const arrays.
// fresh items buffer per load. Instead those functions parse directly
// into this fixed, static buffer via cutsceneParseDoc (see cutscene.h),
// once the raw JSON itself has been read+parsed via the generic
// ASSET_LOADER_TYPE_JSON asset loader. Bump this if a real cutscene ever
// needs more; C-authored cutscenes (the CUTSCENE(...) macro) don't use
// this at all - they're compiled directly into their own static const
// arrays.
#define CUTSCENE_LOADED_ITEMS_MAX 128
#define CUTSCENE_LOADED_POOL_SIZE_MAX 4096
extern cutsceneitem_t CUTSCENE_LOADED_ITEMS[CUTSCENE_LOADED_ITEMS_MAX];
extern uint8_t CUTSCENE_LOADED_POOL[CUTSCENE_LOADED_POOL_SIZE_MAX];
// The cutscene_t for whatever was last parsed into CUTSCENE_LOADED_ITEMS/
// CUTSCENE_LOADED_POOL - .items always points at CUTSCENE_LOADED_ITEMS;
// itemCount/pause/dataSize are overwritten by the loader on each load.
// The cutscene_t for whatever was last parsed into CUTSCENE_LOADED_ITEMS -
// .items always points at CUTSCENE_LOADED_ITEMS; itemCount/pause are
// overwritten by the loader on each load.
extern cutscene_t CUTSCENE_LOADED_SCENE;
/**
* Frees whatever yyjson_doc currently backs CUTSCENE_LOADED_ITEMS' string
* fields (marker/cutscene-reference names point directly into it - see
* cutsceneCutsceneRefLoad/cutsceneMarkerLoad) and takes ownership of doc
* instead. Called by the cutscene asset loader once a fresh parse into
* CUTSCENE_LOADED_ITEMS/CUTSCENE_LOADED_POOL completes successfully, and
* from cutsceneSystemDispose to release the last one at shutdown.
* instead. Called by cutsceneSystemLoad/cutsceneCutsceneResolve once a
* fresh parse into CUTSCENE_LOADED_ITEMS completes successfully, and from
* cutsceneSystemDispose to release the last one at shutdown.
*
* @param doc The new doc to take ownership of (NULL just frees/clears the
* current one).
*/
void cutsceneLoadedSetDoc(yyjson_doc *doc);
// A second, much smaller buffer set dedicated to CUTSCENE_ITEM_TYPE_INSERT
// A second, much smaller buffer dedicated to CUTSCENE_ITEM_TYPE_INSERT
// (see cutsceneSystemInsertCutscene) - kept entirely separate from
// CUTSCENE_LOADED_ITEMS/POOL so that a JSON-authored INSERT item resolving
// its target by name (via cutsceneInsertResolve) never overwrites the
// buffer the currently-running outer cutscene's own items live in. Sized
// small deliberately - an inserted snippet is meant to be a short splice,
// not a full cutscene.
// CUTSCENE_LOADED_ITEMS so that a JSON-authored INSERT item resolving its
// target by name (via cutsceneInsertResolve) never overwrites the buffer
// the currently-running outer cutscene's own items live in. Sized small
// deliberately - an inserted snippet is meant to be a short splice, not a
// full cutscene.
#define CUTSCENE_INSERT_ITEMS_MAX 16
#define CUTSCENE_INSERT_POOL_SIZE_MAX 512
extern cutsceneitem_t CUTSCENE_INSERT_ITEMS[CUTSCENE_INSERT_ITEMS_MAX];
extern uint8_t CUTSCENE_INSERT_POOL[CUTSCENE_INSERT_POOL_SIZE_MAX];
// The cutscene_t for whatever was last parsed into CUTSCENE_INSERT_ITEMS/
// CUTSCENE_INSERT_POOL - .items always points at CUTSCENE_INSERT_ITEMS.
// Only one JSON-authored INSERT target is ever resident at a time, same
// restriction as CUTSCENE_LOADED_SCENE - a JSON-authored INSERT whose
// target itself contains a JSON-authored INSERT would overwrite this out
// from under itself, so that isn't supported (compile-time CUTSCENE_INSERT
// nesting is unaffected, since it never touches asset loading).
// The cutscene_t for whatever was last parsed into CUTSCENE_INSERT_ITEMS -
// .items always points at CUTSCENE_INSERT_ITEMS. Only one JSON-authored
// INSERT target is ever resident at a time, same restriction as
// CUTSCENE_LOADED_SCENE - a JSON-authored INSERT whose target itself
// contains a JSON-authored INSERT would overwrite this out from under
// itself, so that isn't supported (compile-time CUTSCENE_INSERT nesting is
// unaffected, since it never touches asset loading).
extern cutscene_t CUTSCENE_INSERT_SCENE;
/**
* Same as cutsceneLoadedSetDoc, but for whatever yyjson_doc currently
* backs CUTSCENE_INSERT_ITEMS' string fields. Called by the cutscene asset
* loader once a fresh parse into CUTSCENE_INSERT_ITEMS/CUTSCENE_INSERT_POOL
* backs CUTSCENE_INSERT_ITEMS' string fields. Called by
* cutsceneInsertResolve once a fresh parse into CUTSCENE_INSERT_ITEMS
* completes successfully, and from cutsceneSystemDispose to release the
* last one at shutdown.
*
@@ -125,8 +123,7 @@ typedef struct {
// Data (used by the current item).
cutsceneitemdata_t data;
// Custom user data for the running cutscene, sized per-scene by
// cutscene_t.dataSize.
// Custom user data for the running cutscene.
uint8_t userData[CUTSCENE_SYSTEM_SIZE_MAX];
// See cutsceneSystemSetOnComplete.
@@ -192,7 +189,7 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
* "last created" state, the text cache or userData - those all keep
* whatever the outer cutscene set, since this is meant to feel like
* pasting cutscene's items in place rather than starting an independent
* cutscene. Because of this, cutscene's own .pause/.dataSize are ignored.
* cutscene. Because of this, cutscene's own .pause is ignored.
* Asserts if no cutscene is currently running, or if inserts are already
* nested CUTSCENE_INSERT_STACK_MAX deep.
*
@@ -206,15 +203,15 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene);
/**
* Locks, loads and immediately starts a cutscene asset by file name,
* e.g. cutsceneSystemLoad("main_menu.jsonc") loads and starts
* assets/cutscenes/main_menu.jsonc - same lock/assetRequireLoaded/start
* sequence as sceneInitialInit's INITIAL_CUTSCENE_ENTRY. The entry is
* locked forever (never unlocked), same lifetime convention as
* LOCALE.entry - it's fine to call this again later for the same file,
* it'll just resolve to the same already-loaded entry. Opens the fatal
* error overlay (see uiFatalErrorOpen) instead of starting anything if
* the asset fails to load.
* Loads and immediately starts a cutscene asset by file name, e.g.
* cutsceneSystemLoad("main_menu.jsonc") loads and starts
* assets/cutscenes/main_menu.jsonc, parsing it into
* CUTSCENE_LOADED_SCENE/ITEMS - the underlying JSON asset entry is
* unlocked and reaped immediately once that's done (see
* CUTSCENE_LOADED_ITEMS_MAX's doc comment for why), so it's always
* re-read+re-parsed fresh, never assumed still resident from a previous
* call. Opens the fatal error overlay (see uiFatalErrorOpen) instead of
* starting anything if the asset fails to load.
*
* @param file Cutscene file name (with .jsonc extension), relative to
* assets/cutscenes/.
@@ -33,9 +33,7 @@ bool_t cutsceneAudioFadeUpdate(
errorret_t cutsceneAudioFadeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
audiomixerchannel_t channelValue;
if(!cutsceneJsonLookupAudioChannel(
@@ -92,13 +92,9 @@ bool_t cutsceneAudioFadeUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_FADE needs no pool storage).
* @param poolOffset Unused (AUDIO_FADE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioFadeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -18,9 +18,7 @@ bool_t cutsceneAudioFadeWaitUpdate(
errorret_t cutsceneAudioFadeWaitLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioFadeWaitChannel);
}
@@ -37,13 +37,9 @@ bool_t cutsceneAudioFadeWaitUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_FADE_WAIT needs no pool storage).
* @param poolOffset Unused (AUDIO_FADE_WAIT needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioFadeWaitLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneAudioPauseUpdate(
errorret_t cutsceneAudioPauseLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioPauseChannel);
}
@@ -45,13 +45,9 @@ bool_t cutsceneAudioPauseUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_PAUSE needs no pool storage).
* @param poolOffset Unused (AUDIO_PAUSE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioPauseLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -34,9 +34,7 @@ bool_t cutsceneAudioPlayUpdate(
errorret_t cutsceneAudioPlayLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonCopyString(
itemObj, "file", NULL, item->audioPlay.file, AUDIO_PATH_MAX
@@ -99,13 +99,9 @@ bool_t cutsceneAudioPlayUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_PLAY needs no pool storage).
* @param poolOffset Unused (AUDIO_PLAY needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioPlayLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneAudioResumeUpdate(
errorret_t cutsceneAudioResumeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioResumeChannel);
}
@@ -45,13 +45,9 @@ bool_t cutsceneAudioResumeUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_RESUME needs no pool storage).
* @param poolOffset Unused (AUDIO_RESUME needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioResumeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -33,9 +33,7 @@ bool_t cutsceneAudioSetUpdate(
errorret_t cutsceneAudioSetLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
audiomixerchannel_t channelValue;
if(!cutsceneJsonLookupAudioChannel(
@@ -73,13 +73,9 @@ bool_t cutsceneAudioSetUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_SET needs no pool storage).
* @param poolOffset Unused (AUDIO_SET needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioSetLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -32,9 +32,7 @@ bool_t cutsceneAudioSetLoopUpdate(
errorret_t cutsceneAudioSetLoopLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
audiomixerchannel_t channelValue;
if(!cutsceneJsonLookupAudioChannel(
@@ -67,13 +67,9 @@ bool_t cutsceneAudioSetLoopUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_SET_LOOP needs no pool storage).
* @param poolOffset Unused (AUDIO_SET_LOOP needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioSetLoopLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -26,9 +26,7 @@ bool_t cutsceneAudioSetPanUpdate(
errorret_t cutsceneAudioSetPanLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
audiomixerchannel_t channelValue;
if(!cutsceneJsonLookupAudioChannel(
@@ -54,13 +54,9 @@ bool_t cutsceneAudioSetPanUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_SET_PAN needs no pool storage).
* @param poolOffset Unused (AUDIO_SET_PAN needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioSetPanLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneAudioStopUpdate(
errorret_t cutsceneAudioStopLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioStopChannel);
}
@@ -45,13 +45,9 @@ bool_t cutsceneAudioStopUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (AUDIO_STOP needs no pool storage).
* @param poolOffset Unused (AUDIO_STOP needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneAudioStopLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -26,9 +26,7 @@ bool_t cutsceneBattleForceActionUpdate(
errorret_t cutsceneBattleForceActionLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
item->battleForceAction.fighterIndex =
(uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "fighterIndex"));
@@ -56,13 +56,9 @@ bool_t cutsceneBattleForceActionUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (BATTLE_FORCE_ACTION needs no pool storage).
* @param poolOffset Unused (BATTLE_FORCE_ACTION needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneBattleForceActionLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -17,9 +17,7 @@ bool_t cutsceneBattleWaitStateUpdate(
errorret_t cutsceneBattleWaitStateLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
battlestate_t stateValue;
if(!cutsceneJsonLookupBattleState(
@@ -47,13 +47,9 @@ bool_t cutsceneBattleWaitStateUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (BATTLE_WAIT_STATE needs no pool storage).
* @param poolOffset Unused (BATTLE_WAIT_STATE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneBattleWaitStateLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -84,9 +84,7 @@ bool_t cutsceneStartBattleUpdate(
errorret_t cutsceneStartBattleLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
battleencountertype_t encounterValue;
if(!cutsceneJsonLookupBattleEncounter(
@@ -64,13 +64,9 @@ bool_t cutsceneStartBattleUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (START_BATTLE needs no pool storage).
* @param poolOffset Unused (START_BATTLE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneStartBattleLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -22,9 +22,7 @@ bool_t cutsceneMarkerUpdate(
errorret_t cutsceneMarkerLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name"));
if(name == NULL) {
@@ -52,13 +52,9 @@ bool_t cutsceneMarkerUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (MARKER needs no pool storage).
* @param poolOffset Unused (MARKER needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneMarkerLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneSceneUpdate(
errorret_t cutsceneSceneLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
scenetype_t sceneValue;
if(!cutsceneJsonLookupSceneType(
@@ -56,13 +56,9 @@ bool_t cutsceneSceneUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (SCENE needs no pool storage).
* @param poolOffset Unused (SCENE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneSceneLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneSetPauseUpdate(
errorret_t cutsceneSetPauseLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
yyjson_val *flags = yyjson_obj_get(itemObj, "flags");
cutscenepause_t value;
@@ -45,13 +45,9 @@ bool_t cutsceneSetPauseUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (SET_PAUSE needs no pool storage).
* @param poolOffset Unused (SET_PAUSE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneSetPauseLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -26,9 +26,7 @@ bool_t cutsceneWaitUpdate(
errorret_t cutsceneWaitLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
item->wait = cutsceneJsonGetFloat(itemObj, "seconds", 0.0f);
@@ -47,13 +47,9 @@ bool_t cutsceneWaitUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (WAIT needs no pool storage).
* @param poolOffset Unused (WAIT needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneWaitLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
+58 -26
View File
@@ -68,8 +68,7 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.init = cutsceneEntityWalkToStart,
.update = cutsceneEntityWalkToUpdate,
.jsonName = "ENTITY_WALK_TO",
.load = cutsceneEntityWalkToLoad,
.poolSize = cutsceneEntityWalkToPoolSize
.load = cutsceneEntityWalkToLoad
},
[CUTSCENE_ITEM_TYPE_FADE] = {
@@ -142,8 +141,7 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.init = cutsceneMapAreaWaitStart,
.update = cutsceneMapAreaWaitUpdate,
.jsonName = "MAP_AREA_WAIT",
.load = cutsceneMapAreaWaitLoad,
.poolSize = cutsceneMapAreaWaitPoolSize
.load = cutsceneMapAreaWaitLoad
},
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
@@ -403,25 +401,48 @@ const cutscene_t * cutsceneCutsceneResolve(const char_t *name) {
char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name);
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL);
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_JSON, NULL);
errorret_t result = assetRequireLoaded(entry);
// Released the instant loading finishes - see cutsceneSystemLoad's doc
// comment for why nothing further depends on this entry once loaded.
assetUnlockEntry(entry);
if(errorIsNotOk(result)) {
assetUnlockEntry(entry);
errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL);
return NULL;
}
// Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_LOADED_ITEMS' name pointers
// the moment this entry gets reaped.
yyjson_doc *doc = entry->data.json;
entry->data.json = NULL;
assetUnlockEntry(entry);
// Force this now-zero-ref entry to actually go away right away, rather
// than leaving it languishing as LOADED until something else happens to
// trigger a reap - assetRequireLoaded treats an already-LOADED entry as
// an instant cache hit with no reparsing, so a stale leftover entry for
// this same path would otherwise make a later resolve of it wrongly
// skip loading, still returning CUTSCENE_LOADED_SCENE even after some
// other cutscene has since overwritten it.
assetReapUnused();
errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_LOADED_SCENE, CUTSCENE_LOADED_ITEMS,
CUTSCENE_LOADED_ITEMS_MAX
);
if(errorIsNotOk(parseResult)) {
yyjson_doc_free(doc);
errorCatch(errorPrint(parseResult));
uiFatalErrorOpen(NULL);
return NULL;
}
cutsceneLoadedSetDoc(doc);
return &CUTSCENE_LOADED_SCENE;
}
errorret_t cutsceneCutsceneLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
return cutsceneCutsceneRefLoad(itemObj, &item->cutsceneRef, "CUTSCENE");
}
@@ -477,33 +498,44 @@ const cutscene_t * cutsceneInsertResolve(const char_t *name) {
char_t path[ASSET_FILE_NAME_MAX];
stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name);
char_t key[ASSET_FILE_NAME_MAX];
stringFormat(key, sizeof(key), "insert:%s", path);
assetloaderinput_t input;
memoryZero(&input, sizeof(input));
input.cutscene.dest = ASSET_CUTSCENE_LOAD_DEST_INSERT;
stringCopy(input.cutscene.path, path, ASSET_FILE_NAME_MAX);
assetentry_t *entry = assetLock(key, ASSET_LOADER_TYPE_CUTSCENE, &input);
assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_JSON, NULL);
errorret_t result = assetRequireLoaded(entry);
// Released the instant loading finishes - see cutsceneSystemLoad's doc
// comment for why nothing further depends on this entry once loaded.
assetUnlockEntry(entry);
if(errorIsNotOk(result)) {
assetUnlockEntry(entry);
errorCatch(errorPrint(result));
uiFatalErrorOpen(NULL);
return NULL;
}
// Steal the parsed doc before unlocking - assetJsonDispose would
// otherwise free it out from under CUTSCENE_INSERT_ITEMS' name pointers
// the moment this entry gets reaped.
yyjson_doc *doc = entry->data.json;
entry->data.json = NULL;
assetUnlockEntry(entry);
// See cutsceneCutsceneResolve's matching comment - forces a stale
// leftover entry for this same path out immediately, so a later resolve
// never wrongly skips reloading it.
assetReapUnused();
errorret_t parseResult = cutsceneParseDoc(
doc, &CUTSCENE_INSERT_SCENE, CUTSCENE_INSERT_ITEMS,
CUTSCENE_INSERT_ITEMS_MAX
);
if(errorIsNotOk(parseResult)) {
yyjson_doc_free(doc);
errorCatch(errorPrint(parseResult));
uiFatalErrorOpen(NULL);
return NULL;
}
cutsceneInsertSetDoc(doc);
return &CUTSCENE_INSERT_SCENE;
}
errorret_t cutsceneInsertLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
return cutsceneCutsceneRefLoad(itemObj, &item->insert, "INSERT");
}
+13 -30
View File
@@ -150,22 +150,14 @@ typedef bool_t (cutsceneitemupdatecallback_t)(
typedef errorret_t (cutsceneitemloadcallback_t)(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
// Returns how many pool bytes (see cutsceneJsonPoolAlign) this JSON item
// needs - only ENTITY_WALK_TO/MAP_AREA_WAIT need this; every other type's
// poolSize is NULL (0 bytes).
typedef size_t (cutsceneitempoolsizecallback_t)(yyjson_val *itemObj);
typedef struct {
cutsceneiteminitcallback_t *init;
cutsceneitemupdatecallback_t *update;
const char_t *jsonName; // NULL if not JSON-authorable
cutsceneitemloadcallback_t *load; // NULL if not JSON-authorable
cutsceneitempoolsizecallback_t *poolSize; // NULL if this type needs no pool
} cutsceneitemcallbacks_t;
extern cutsceneitemcallbacks_t
@@ -264,15 +256,11 @@ bool_t cutsceneCutsceneUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (CUTSCENE needs no pool storage).
* @param poolOffset Unused (CUTSCENE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneCutsceneLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
/**
@@ -301,8 +289,8 @@ errorret_t cutsceneCutsceneRefLoad(
* been resolved yet (a JSON-authored reference, only known by name so
* far), resolves it first via cutsceneInsertResolve - not
* cutsceneCutsceneResolve, so the referenced cutscene lands in the
* dedicated CUTSCENE_INSERT_ITEMS/POOL buffer rather than overwriting
* CUTSCENE_LOADED_ITEMS/POOL out from under whatever outer cutscene is
* dedicated CUTSCENE_INSERT_ITEMS buffer rather than overwriting
* CUTSCENE_LOADED_ITEMS out from under whatever outer cutscene is
* currently running (see cutscenesystem.h's CUTSCENE_INSERT_ITEMS_MAX doc
* comment). Not cached back into item, same reasoning as
* cutsceneCutsceneStart.
@@ -316,15 +304,14 @@ void cutsceneInsertStart(
);
/**
* Resolves a JSON-authored INSERT item's referenced cutscene by name,
* locking (under a synthetic "insert:cutscenes/<name>.jsonc" cache key,
* distinct from the plain "cutscenes/<name>.jsonc" key cutsceneCutsceneResolve
* uses for the same file, so a load driven by an INSERT item is never
* silently shared with - or overwritten by - a load driven by a CUTSCENE
* item, or vice versa - see assetGetEntry's name-only dedup) and blocking
* on assets/cutscenes/<name>.jsonc, parsed into CUTSCENE_INSERT_ITEMS/POOL
* rather than CUTSCENE_LOADED_ITEMS/POOL. On failure, opens the fatal
* error overlay and returns NULL.
* Resolves a JSON-authored INSERT item's referenced cutscene by name -
* locks+loads assets/cutscenes/<name>.jsonc (as a plain
* ASSET_LOADER_TYPE_JSON asset, same path/key convention as
* cutsceneCutsceneResolve - which destination buffer it lands in is
* decided purely by which resolve function is called, not stored on the
* asset entry itself), then parses it into CUTSCENE_INSERT_ITEMS rather
* than CUTSCENE_LOADED_ITEMS. On failure, opens the fatal error overlay
* and returns NULL.
*
* @param name Bare cutscene name (without "cutscenes/" prefix or
* ".jsonc" suffix).
@@ -350,13 +337,9 @@ bool_t cutsceneInsertUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (INSERT needs no pool storage).
* @param poolOffset Unused (INSERT needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneInsertLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -9,9 +9,7 @@
errorret_t cutsceneItemLoadNoop(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
errorOk();
}
@@ -90,13 +90,9 @@ typedef enum {
*
* @param itemObj The item's JSON object (unused).
* @param item Destination item (unused).
* @param pool Unused.
* @param poolOffset Unused.
* @return Always errorOk().
*/
errorret_t cutsceneItemLoadNoop(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
+1 -3
View File
@@ -25,9 +25,7 @@ bool_t cutscenePrintUpdate(
errorret_t cutscenePrintLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonCopyString(
itemObj, "text", NULL, item->print.text, CUTSCENE_PRINT_MAX_CHARS
+1 -5
View File
@@ -50,13 +50,9 @@ bool_t cutscenePrintUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (PRINT needs no pool storage).
* @param poolOffset Unused (PRINT needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutscenePrintLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -38,9 +38,7 @@ bool_t cutsceneEntityAddUpdate(
errorret_t cutsceneEntityAddLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
entitytype_t entityTypeValue;
if(!cutsceneJsonLookupEntityType(
@@ -56,13 +56,9 @@ bool_t cutsceneEntityAddUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (ENTITY_ADD needs no pool storage).
* @param poolOffset Unused (ENTITY_ADD needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEntityAddLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -29,9 +29,7 @@ bool_t cutsceneEntityRemoveUpdate(
errorret_t cutsceneEntityRemoveLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetEntityIndex(
yyjson_obj_get(itemObj, "entityIndex"), &item->entityRemove.entityIndex
@@ -50,13 +50,9 @@ bool_t cutsceneEntityRemoveUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (ENTITY_REMOVE needs no pool storage).
* @param poolOffset Unused (ENTITY_REMOVE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEntityRemoveLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -30,9 +30,7 @@ bool_t cutsceneEntityTeleportUpdate(
errorret_t cutsceneEntityTeleportLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetEntityIndex(
yyjson_obj_get(itemObj, "entityIndex"),
@@ -55,13 +55,9 @@ bool_t cutsceneEntityTeleportUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (ENTITY_TELEPORT needs no pool storage).
* @param poolOffset Unused (ENTITY_TELEPORT needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEntityTeleportLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -33,9 +33,7 @@ bool_t cutsceneEntityTurnUpdate(
errorret_t cutsceneEntityTurnLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetEntityIndex(
yyjson_obj_get(itemObj, "entityIndex"), &item->entityTurn.entityIndex
@@ -55,13 +55,9 @@ bool_t cutsceneEntityTurnUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (ENTITY_TURN needs no pool storage).
* @param poolOffset Unused (ENTITY_TURN needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEntityTurnLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -10,12 +10,16 @@
#include "rpg/entity/entitypathstep.h"
#include "rpg/cutscene/item/json/cutscenejsonentityindex.h"
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
#include "rpg/cutscene/item/json/cutscenejsonpool.h"
#include "assert/assert.h"
void cutsceneEntityWalkToStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
assertTrue(
item->entityWalkTo.count <= CUTSCENE_ENTITY_WALK_TO_MAX,
"Too many waypoints in CUTSCENE_ENTITY_WALK_TO"
);
data->entityWalkTo.currentIndex = 0;
}
@@ -40,9 +44,7 @@ bool_t cutsceneEntityWalkToUpdate(
errorret_t cutsceneEntityWalkToLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetEntityIndex(
yyjson_obj_get(itemObj, "entityIndex"), &item->entityWalkTo.entityIndex
@@ -54,20 +56,22 @@ errorret_t cutsceneEntityWalkToLoad(
yyjson_val *positions = yyjson_obj_get(itemObj, "positions");
size_t count = yyjson_arr_size(positions);
if(!yyjson_is_arr(positions) || count == 0 || count > UINT8_MAX) {
if(
!yyjson_is_arr(positions) || count == 0 ||
count > CUTSCENE_ENTITY_WALK_TO_MAX
) {
errorThrow(
"Cutscene item (ENTITY_WALK_TO): 'positions' must be a non-empty "
"array of [x, y, z]"
"Cutscene item (ENTITY_WALK_TO): 'positions' must have 1-%u entries "
"of [x, y, z]",
(uint32_t)CUTSCENE_ENTITY_WALK_TO_MAX
);
}
*poolOffset = cutsceneJsonPoolAlign(*poolOffset);
worldpos_t *dest = (worldpos_t *)(pool + *poolOffset);
yyjson_arr_iter iter = yyjson_arr_iter_with(positions);
yyjson_val *posVal;
size_t i = 0;
while((posVal = yyjson_arr_iter_next(&iter)) != NULL) {
if(!cutsceneJsonReadWorldPos(posVal, &dest[i])) {
if(!cutsceneJsonReadWorldPos(posVal, &item->entityWalkTo.positions[i])) {
errorThrow(
"Cutscene item (ENTITY_WALK_TO): position %u must be [x, y, z]",
(uint32_t)i
@@ -75,15 +79,7 @@ errorret_t cutsceneEntityWalkToLoad(
}
i++;
}
item->entityWalkTo.positions = dest;
item->entityWalkTo.count = (uint8_t)count;
*poolOffset += count * sizeof(worldpos_t);
errorOk();
}
size_t cutsceneEntityWalkToPoolSize(yyjson_val *itemObj) {
yyjson_val *positions = yyjson_obj_get(itemObj, "positions");
if(!yyjson_is_arr(positions)) return 0;
return yyjson_arr_size(positions) * sizeof(worldpos_t);
}
@@ -9,9 +9,14 @@
#include "rpg/overworld/worldpos.h"
#include "rpg/cutscene/item/cutsceneitembase.h"
// Maximum number of waypoints a single ENTITY_WALK_TO/
// CUTSCENE_ENTITY_WALK_PATH may hold - generous relative to every current
// asset/C-authored cutscene (none use more than a couple waypoints).
#define CUTSCENE_ENTITY_WALK_TO_MAX 16
typedef struct {
uint8_t entityIndex;
const worldpos_t *positions;
worldpos_t positions[CUTSCENE_ENTITY_WALK_TO_MAX];
uint8_t count;
bool_t walkAround;
} cutsceneentitywalkto_t;
@@ -32,29 +37,29 @@ typedef struct {
#define CUTSCENE_ENTITY_WALK_TO(ENTITY_INDEX, X, Y, Z) \
CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, entityWalkTo, { \
.entityIndex = ENTITY_INDEX, \
.positions = (const worldpos_t[]){ { X, Y, Z } }, \
.positions = { { X, Y, Z } }, \
.count = 1, \
.walkAround = true \
})
/**
* Declares a standalone, named cutsceneitem_t (not a list entry) that
* walks an entity through a fixed sequence of waypoints.
* walks an entity through a fixed sequence of waypoints. ... must not
* exceed CUTSCENE_ENTITY_WALK_TO_MAX entries - the compiler rejects the
* initializer with "excess elements in array initializer" if it does.
*
* @param NAME Suffix for the generated CUTSCENE_##NAME item/positions
* statics.
* @param NAME Suffix for the generated CUTSCENE_##NAME static.
* @param ENTITY_INDEX Entity index to move.
* @param ... One or more worldpos_t-shaped waypoint initializers, e.g.
* { X, Y, Z }, { X2, Y2, Z2 }.
* { X, Y, Z }, { X2, Y2, Z2 } - at most CUTSCENE_ENTITY_WALK_TO_MAX.
*/
#define CUTSCENE_ENTITY_WALK_PATH(NAME, ENTITY_INDEX, ...) \
static const worldpos_t CUTSCENE_##NAME##_POSITIONS[] = { __VA_ARGS__ }; \
static const cutsceneitem_t CUTSCENE_##NAME = { \
.type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \
.entityWalkTo = { \
.entityIndex = ENTITY_INDEX, \
.positions = CUTSCENE_##NAME##_POSITIONS, \
.count = sizeof(CUTSCENE_##NAME##_POSITIONS) / sizeof(worldpos_t), \
.positions = { __VA_ARGS__ }, \
.count = sizeof((const worldpos_t[]){ __VA_ARGS__ }) / sizeof(worldpos_t), \
.walkAround = true \
} \
};
@@ -84,26 +89,14 @@ bool_t cutsceneEntityWalkToUpdate(
/**
* Parses an ENTITY_WALK_TO cutscene item's JSON fields into item, writing
* its "positions" array into pool at *poolOffset and advancing it.
* its "positions" array directly into item->entityWalkTo.positions (at
* most CUTSCENE_ENTITY_WALK_TO_MAX entries).
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Base pointer of the entry's pool allocation.
* @param poolOffset In/out cursor into pool.
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEntityWalkToLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
/**
* Computes how many pool bytes this ENTITY_WALK_TO item's "positions"
* array will need.
*
* @param itemObj The item's JSON object.
* @return Pool bytes required (0 if "positions" is missing/not an array).
*/
size_t cutsceneEntityWalkToPoolSize(yyjson_val *itemObj);
@@ -43,9 +43,7 @@ bool_t cutsceneEntityWalkToEntityUpdate(
errorret_t cutsceneEntityWalkToEntityLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetEntityIndex(
yyjson_obj_get(itemObj, "entityIndex"),
@@ -69,13 +69,9 @@ bool_t cutsceneEntityWalkToEntityUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (ENTITY_WALK_TO_ENTITY needs no pool storage).
* @param poolOffset Unused (ENTITY_WALK_TO_ENTITY needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEntityWalkToEntityLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneItemGiveUpdate(
errorret_t cutsceneItemGiveLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
item->itemGive.item =
(itemid_t)yyjson_get_int(yyjson_obj_get(itemObj, "item"));
@@ -53,13 +53,9 @@ bool_t cutsceneItemGiveUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (ITEM_GIVE needs no pool storage).
* @param poolOffset Unused (ITEM_GIVE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneItemGiveLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -9,7 +9,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
cutscenejsonfield.c
cutscenejsonentityindex.c
cutscenejsonareaid.c
cutscenejsonpool.c
cutscenejsonpauseflags.c
cutscenejsoneasing.c
cutscenejsonemoji.c
@@ -12,7 +12,7 @@
/**
* Parses a JSON array of pause flag name strings, OR-ing each into out.
* Shared by the top-level "pause" field (assetCutsceneLoaderSync) and the
* Shared by the top-level "pause" field (cutsceneParseDoc) and the
* SET_PAUSE item's "flags" field - both authored as the same shape.
*
* @param arr JSON array of pause flag name strings.
@@ -1,12 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cutscenejsonpool.h"
size_t cutsceneJsonPoolAlign(const size_t size) {
return (size + 3) & ~((size_t)3);
}
@@ -1,19 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Rounds size up to the next multiple of 4 - every pool entry starts
* 4-byte aligned so worldpos_t/multi-byte reads out of it never trap on
* alignment-sensitive hardware (e.g. PSP's MIPS core).
*
* @param size Size to round up.
* @return size rounded up to the next multiple of 4.
*/
size_t cutsceneJsonPoolAlign(const size_t size);
@@ -26,9 +26,7 @@ bool_t cutsceneMapAreaRemoveUpdate(
errorret_t cutsceneMapAreaRemoveLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetAreaId(
yyjson_obj_get(itemObj, "areaId"), &item->mapAreaRemove.areaId
@@ -52,13 +52,9 @@ bool_t cutsceneMapAreaRemoveUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (MAP_AREA_REMOVE needs no pool storage).
* @param poolOffset Unused (MAP_AREA_REMOVE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneMapAreaRemoveLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -10,7 +10,6 @@
#include "rpg/overworld/maparea.h"
#include "assert/assert.h"
#include "rpg/cutscene/item/json/cutscenejsonareaid.h"
#include "rpg/cutscene/item/json/cutscenejsonpool.h"
void cutsceneMapAreaWaitStart(
const cutsceneitem_t *item,
@@ -43,9 +42,7 @@ bool_t cutsceneMapAreaWaitUpdate(
errorret_t cutsceneMapAreaWaitLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
yyjson_val *areaIds = yyjson_obj_get(itemObj, "areaIds");
size_t count = yyjson_arr_size(areaIds);
@@ -58,28 +55,18 @@ errorret_t cutsceneMapAreaWaitLoad(
(uint32_t)CUTSCENE_MAP_AREA_WAIT_MAX
);
}
*poolOffset = cutsceneJsonPoolAlign(*poolOffset);
uint8_t *dest = pool + *poolOffset;
yyjson_arr_iter iter = yyjson_arr_iter_with(areaIds);
yyjson_val *idVal;
size_t i = 0;
while((idVal = yyjson_arr_iter_next(&iter)) != NULL) {
if(!cutsceneJsonGetAreaId(idVal, &dest[i])) {
if(!cutsceneJsonGetAreaId(idVal, &item->mapAreaWait.areaIds[i])) {
errorThrow(
"Cutscene item (MAP_AREA_WAIT): areaIds[%u] is invalid", (uint32_t)i
);
}
i++;
}
item->mapAreaWait.areaIds = dest;
item->mapAreaWait.count = (uint8_t)count;
*poolOffset += count;
errorOk();
}
size_t cutsceneMapAreaWaitPoolSize(yyjson_val *itemObj) {
yyjson_val *areaIds = yyjson_obj_get(itemObj, "areaIds");
if(!yyjson_is_arr(areaIds)) return 0;
return yyjson_arr_size(areaIds);
}
@@ -12,7 +12,7 @@
#define CUTSCENE_MAP_AREA_WAIT_MAX 4
typedef struct {
const uint8_t *areaIds;
uint8_t areaIds[CUTSCENE_MAP_AREA_WAIT_MAX];
uint8_t count;
} cutscenemapareawait_t;
@@ -28,7 +28,7 @@ typedef struct {
*/
#define CUTSCENE_MAP_AREA_WAIT(...) \
CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, mapAreaWait, { \
.areaIds = (const uint8_t[]){ __VA_ARGS__ }, \
.areaIds = { __VA_ARGS__ }, \
.count = (uint8_t)( \
sizeof((const uint8_t[]){ __VA_ARGS__ }) / sizeof(uint8_t) \
) \
@@ -61,26 +61,13 @@ bool_t cutsceneMapAreaWaitUpdate(
/**
* Parses a MAP_AREA_WAIT cutscene item's JSON fields into item, writing its
* "areaIds" array into pool at *poolOffset and advancing it.
* "areaIds" array directly into item->mapAreaWait.areaIds.
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Base pointer of the entry's pool allocation.
* @param poolOffset In/out cursor into pool.
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneMapAreaWaitLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
/**
* Computes how many pool bytes this MAP_AREA_WAIT item's "areaIds" array
* will need.
*
* @param itemObj The item's JSON object.
* @return Pool bytes required (0 if "areaIds" is missing/not an array).
*/
size_t cutsceneMapAreaWaitPoolSize(yyjson_val *itemObj);
@@ -34,9 +34,7 @@ bool_t cutsceneSaveDeviceCheckUpdate(
errorret_t cutsceneSaveDeviceCheckLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
const char_t *successMarker =
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker"));
@@ -62,13 +62,9 @@ bool_t cutsceneSaveDeviceCheckUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (SAVE_DEVICE_CHECK needs no pool storage).
* @param poolOffset Unused (SAVE_DEVICE_CHECK needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneSaveDeviceCheckLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -32,9 +32,7 @@ bool_t cutsceneSaveLoadAllSlotsUpdate(
errorret_t cutsceneSaveLoadAllSlotsLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
const char_t *successMarker =
yyjson_get_str(yyjson_obj_get(itemObj, "successMarker"));
@@ -61,13 +61,9 @@ bool_t cutsceneSaveLoadAllSlotsUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (SAVE_LOAD_ALL_SLOTS needs no pool storage).
* @param poolOffset Unused (SAVE_LOAD_ALL_SLOTS needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneSaveLoadAllSlotsLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -29,9 +29,7 @@ bool_t cutsceneEmojiUpdate(
errorret_t cutsceneEmojiLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonGetEntityIndex(
yyjson_obj_get(itemObj, "entityIndex"), &item->emoji.entityIndex
@@ -59,13 +59,9 @@ bool_t cutsceneEmojiUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (EMOJI needs no pool storage).
* @param poolOffset Unused (EMOJI needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneEmojiLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
+1 -3
View File
@@ -35,9 +35,7 @@ bool_t cutsceneFadeUpdate(
errorret_t cutsceneFadeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
yyjson_val *from = yyjson_obj_get(itemObj, "from");
yyjson_val *to = yyjson_obj_get(itemObj, "to");
+1 -5
View File
@@ -91,13 +91,9 @@ bool_t cutsceneFadeUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (FADE needs no pool storage).
* @param poolOffset Unused (FADE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneFadeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -55,9 +55,7 @@ bool_t cutsceneModalCloseUpdate(
errorret_t cutsceneModalLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(yyjson_obj_get(itemObj, "options") != NULL) {
errorThrow(
@@ -137,15 +137,11 @@ bool_t cutsceneModalUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (MODAL needs no pool storage).
* @param poolOffset Unused (MODAL needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneModalLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
/**
@@ -52,9 +52,7 @@ bool_t cutsceneModalOptionsMarkersUpdate(
errorret_t cutsceneModalOptionsMarkersLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
cutsceneJsonCopyString(
itemObj, "title", "", item->modalOptionsMarkers.title,
@@ -102,13 +102,9 @@ bool_t cutsceneModalOptionsMarkersUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (MODAL_OPTIONS_MARKERS needs no pool storage).
* @param poolOffset Unused (MODAL_OPTIONS_MARKERS needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneModalOptionsMarkersLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -25,9 +25,7 @@ bool_t cutsceneShakeUpdate(
errorret_t cutsceneShakeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
item->shake.amount = (uint8_t)cutsceneJsonGetInt(itemObj, "amount", 0);
item->shake.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f);
@@ -54,13 +54,9 @@ bool_t cutsceneShakeUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (SHAKE needs no pool storage).
* @param poolOffset Unused (SHAKE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneShakeLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
+1 -3
View File
@@ -25,9 +25,7 @@ bool_t cutsceneTextUpdate(
errorret_t cutsceneTextLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonCopyString(
itemObj, "text", NULL, item->text.text, CUTSCENE_TEXT_MAX_CHARS
+1 -5
View File
@@ -50,13 +50,9 @@ bool_t cutsceneTextUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (TEXT needs no pool storage).
* @param poolOffset Unused (TEXT needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneTextLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -35,9 +35,7 @@ bool_t cutsceneTextMiniUpdate(
errorret_t cutsceneTextMiniLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
if(!cutsceneJsonCopyString(
itemObj, "text", NULL, item->textMini.text, CUTSCENE_TEXT_MINI_MAX_CHARS
@@ -62,13 +62,9 @@ bool_t cutsceneTextMiniUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (TEXT_MINI needs no pool storage).
* @param poolOffset Unused (TEXT_MINI needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneTextMiniLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -26,9 +26,7 @@ bool_t cutsceneTextMiniHideUpdate(
errorret_t cutsceneTextMiniHideLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
item->textMiniHide.index =
(uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "index"));
@@ -50,13 +50,9 @@ bool_t cutsceneTextMiniHideUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (TEXT_MINI_HIDE needs no pool storage).
* @param poolOffset Unused (TEXT_MINI_HIDE needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneTextMiniHideLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
@@ -29,9 +29,7 @@ bool_t cutsceneUIShowUpdate(
errorret_t cutsceneUIShowLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
) {
uiscreentype_t screenValue;
if(!cutsceneJsonLookupUiScreenType(
@@ -58,13 +58,9 @@ bool_t cutsceneUIShowUpdate(
*
* @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller.
* @param pool Unused (UI_SHOW needs no pool storage).
* @param poolOffset Unused (UI_SHOW needs no pool storage).
* @return Error code indicating success or failure of the parse.
*/
errorret_t cutsceneUIShowLoad(
yyjson_val *itemObj,
cutsceneitem_t *item,
uint8_t *pool,
size_t *poolOffset
cutsceneitem_t *item
);
+2 -2
View File
@@ -9,11 +9,11 @@
#include "rpg/cutscene/cutscene.h"
#include "rpg/cutscene/cutscenesystem.h"
CUTSCENE(TEST_ONE, 0, DEFAULT,
CUTSCENE(TEST_ONE, DEFAULT,
CUTSCENE_TEXT("Test One."),
);
CUTSCENE(TEST_TWO, 0, DEFAULT,
CUTSCENE(TEST_TWO, DEFAULT,
CUTSCENE_TEXT("Test Two."),
CUTSCENE_ENTITY_ADD(ENTITY_TYPE_NPC, 4, 4, 0),
CUTSCENE_TEXT_MINI("Hello!", 4, 4, 0, 3.0f),
+1 -2
View File
@@ -24,8 +24,7 @@ static const cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = {
static const cutscene_t CUTSCENE_TEST_SCRATCH = {
.items = CUTSCENE_TEST_SCRATCH_ITEMS,
.itemCount = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t),
.pause = CUTSCENE_PAUSE_NONE,
.dataSize = 0
.pause = CUTSCENE_PAUSE_NONE
};
static battlefighter_t *addFighter(
+18 -18
View File
@@ -20,11 +20,11 @@ static void recordCallback(void *userData) {
// the "nested cutscene is a one-way jump" behavior: cutsceneCutsceneStart
// replaces CUTSCENE_SYSTEM.scene outright, and the callback item's effect
// fires in Start, not Update, so it fires the same frame the jump happens.
CUTSCENE(TEST_INNER, 0, NONE,
CUTSCENE(TEST_INNER, NONE,
CUTSCENE_CALLBACK(recordCallback)
);
CUTSCENE(TEST_OUTER, 0, DEFAULT,
CUTSCENE(TEST_OUTER, DEFAULT,
CUTSCENE_WAIT(0.5f),
CUTSCENE_CUTSCENE(TEST_INNER)
);
@@ -75,7 +75,7 @@ static void test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene(void **state) {
assert_null(CUTSCENE_SYSTEM.scene);
}
CUTSCENE(TEST_SINGLE_WAIT, 0, NONE,
CUTSCENE(TEST_SINGLE_WAIT, NONE,
CUTSCENE_WAIT(1.0f)
);
@@ -188,13 +188,13 @@ static void recordOrder2(void *userData) { insertOrderLog[insertOrderLogCount++]
static void recordOrderA(void *userData) { insertOrderLog[insertOrderLogCount++] = 3; }
static void recordOrderB(void *userData) { insertOrderLog[insertOrderLogCount++] = 4; }
CUTSCENE(TEST_INSERT_SNIPPET, 0, NONE,
CUTSCENE(TEST_INSERT_SNIPPET, NONE,
CUTSCENE_CALLBACK(recordOrderA),
CUTSCENE_WAIT(2.0f),
CUTSCENE_CALLBACK(recordOrderB)
);
CUTSCENE(TEST_INSERT_OUTER, 0, NONE,
CUTSCENE(TEST_INSERT_OUTER, NONE,
CUTSCENE_CALLBACK(recordOrder0),
CUTSCENE_WAIT(1.0f),
CUTSCENE_CALLBACK(recordOrder1),
@@ -230,11 +230,11 @@ static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) {
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
}
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_SNIPPET, 0, NONE,
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_SNIPPET, NONE,
CUTSCENE_CALLBACK(recordOrderA)
);
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_OUTER, 0, NONE,
CUTSCENE(TEST_INSERT_AS_LAST_ITEM_OUTER, NONE,
CUTSCENE_CALLBACK(recordOrder0),
CUTSCENE_INSERT(TEST_INSERT_AS_LAST_ITEM_SNIPPET)
);
@@ -263,19 +263,19 @@ static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally(
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
}
CUTSCENE(TEST_INSERT_NESTED_INNER, 0, NONE,
CUTSCENE(TEST_INSERT_NESTED_INNER, NONE,
CUTSCENE_CALLBACK(recordOrderA)
);
// INSERT is MID's first item, so entering MID (from OUTER) cascades
// straight through into INNER within the same Start chain, one level
// deeper than a single insert.
CUTSCENE(TEST_INSERT_NESTED_MID, 0, NONE,
CUTSCENE(TEST_INSERT_NESTED_MID, NONE,
CUTSCENE_INSERT(TEST_INSERT_NESTED_INNER),
CUTSCENE_CALLBACK(recordOrderB)
);
CUTSCENE(TEST_INSERT_NESTED_OUTER, 0, NONE,
CUTSCENE(TEST_INSERT_NESTED_OUTER, NONE,
CUTSCENE_CALLBACK(recordOrder0),
CUTSCENE_INSERT(TEST_INSERT_NESTED_MID),
CUTSCENE_CALLBACK(recordOrder2)
@@ -306,22 +306,22 @@ static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) {
assert_memory_equal(insertOrderLog, expected, sizeof(expected));
}
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL5, 0, NONE,
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL5, NONE,
CUTSCENE_CALLBACK(recordOrderA)
);
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL4, 0, NONE,
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL4, NONE,
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL5)
);
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL3, 0, NONE,
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL3, NONE,
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL4)
);
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL2, 0, NONE,
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL2, NONE,
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL3)
);
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL1, 0, NONE,
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL1, NONE,
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL2)
);
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL0, 0, NONE,
CUTSCENE(TEST_INSERT_OVERFLOW_LEVEL0, NONE,
CUTSCENE_INSERT(TEST_INSERT_OVERFLOW_LEVEL1)
);
@@ -341,11 +341,11 @@ static void test_cutsceneInsertAssertsWhenNestingExceedsStackMax(
static void noopInsertCallback(void *userData) {}
CUTSCENE(TEST_INSERT_PERSIST_SNIPPET, 0, ALL,
CUTSCENE(TEST_INSERT_PERSIST_SNIPPET, ALL,
CUTSCENE_CALLBACK(noopInsertCallback)
);
CUTSCENE(TEST_INSERT_PERSIST_OUTER, 0, NPC,
CUTSCENE(TEST_INSERT_PERSIST_OUTER, NPC,
CUTSCENE_INSERT(TEST_INSERT_PERSIST_SNIPPET)
);
+1 -2
View File
@@ -17,8 +17,7 @@ static const cutsceneitem_t CUTSCENE_TEST_INTERACT_ITEMS[] = {
static const cutscene_t CUTSCENE_TEST_INTERACT = {
.items = CUTSCENE_TEST_INTERACT_ITEMS,
.itemCount = 1,
.pause = CUTSCENE_PAUSE_NONE,
.dataSize = 0
.pause = CUTSCENE_PAUSE_NONE
};
static entity_t *player;