diff --git a/src/dusk/asset/loader/cutscene/assetcutsceneloader.c b/src/dusk/asset/loader/cutscene/assetcutsceneloader.c index 8041542a..55524e6c 100644 --- a/src/dusk/asset/loader/cutscene/assetcutsceneloader.c +++ b/src/dusk/asset/loader/cutscene/assetcutsceneloader.c @@ -13,323 +13,7 @@ #include "asset/loader/assetentry.h" #include "asset/loader/assetloader.h" #include "asset/asset.h" - -// Every known item type is named here for debugging/lookup purposes, but -// CALLBACK, CONCURRENT, MAP_AREA_ADD, KEYBOARD and the multi-option MODAL -// form aren't file-representable - assetCutsceneParseItem has no case for -// them, so authoring one of those in JSON still fails (at the parse switch -// rather than the name lookup). They still have to be authored as a -// compiled-in CUTSCENE(...) macro, see rpg/cutscene/cutscene.h. CUTSCENE is -// file-representable too, but unlike a native CUTSCENE(...) item (which -// points straight at a compile-time cutscene_t global) it resolves its -// 'name' to a sibling assets/cutscenes/.jsonc asset at parse time - -// see the CUTSCENE_ITEM_TYPE_CUTSCENE case below. -// Indexed directly by cutsceneitemtype_t rather than searched by value. -static const assetcutsceneitemtypeentry_t -ASSET_CUTSCENE_ITEM_TYPE_TABLE[CUTSCENE_ITEM_TYPE_COUNT] = { - [CUTSCENE_ITEM_TYPE_NULL] = { "NULL" }, - [CUTSCENE_ITEM_TYPE_TEXT] = { "TEXT" }, - [CUTSCENE_ITEM_TYPE_TEXT_MINI] = { "TEXT_MINI" }, - [CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = { "TEXT_MINI_HIDE" }, - [CUTSCENE_ITEM_TYPE_CALLBACK] = { "CALLBACK" }, - [CUTSCENE_ITEM_TYPE_WAIT] = { "WAIT" }, - [CUTSCENE_ITEM_TYPE_CUTSCENE] = { "CUTSCENE" }, - [CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = { "ENTITY_TELEPORT" }, - [CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = { "ENTITY_WALK_TO" }, - [CUTSCENE_ITEM_TYPE_FADE] = { "FADE" }, - [CUTSCENE_ITEM_TYPE_SET_PAUSE] = { "SET_PAUSE" }, - [CUTSCENE_ITEM_TYPE_CONCURRENT] = { "CONCURRENT" }, - [CUTSCENE_ITEM_TYPE_ITEM_GIVE] = { "ITEM_GIVE" }, - [CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = { "ENTITY_REMOVE" }, - [CUTSCENE_ITEM_TYPE_ENTITY_ADD] = { "ENTITY_ADD" }, - [CUTSCENE_ITEM_TYPE_ENTITY_TURN] = { "ENTITY_TURN" }, - [CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = { "ENTITY_WALK_TO_ENTITY" }, - [CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = { "MAP_AREA_ADD" }, - [CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = { "MAP_AREA_REMOVE" }, - [CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = { "MAP_AREA_WAIT" }, - [CUTSCENE_ITEM_TYPE_START_BATTLE] = { "START_BATTLE" }, - [CUTSCENE_ITEM_TYPE_EMOJI] = { "EMOJI" }, - [CUTSCENE_ITEM_TYPE_SHAKE] = { "SHAKE" }, - [CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE] = { "BATTLE_WAIT_STATE" }, - [CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION] = { "BATTLE_FORCE_ACTION" }, - [CUTSCENE_ITEM_TYPE_MODAL] = { "MODAL" }, - [CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS] = { "MODAL_OPTIONS_MARKERS" }, - [CUTSCENE_ITEM_TYPE_MODAL_CLOSE] = { "MODAL_CLOSE" }, - [CUTSCENE_ITEM_TYPE_PRINT] = { "PRINT" }, - [CUTSCENE_ITEM_TYPE_MARKER] = { "MARKER" }, - [CUTSCENE_ITEM_TYPE_RESTART] = { "RESTART" }, - [CUTSCENE_ITEM_TYPE_SCENE] = { "SCENE" }, - [CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = { "SAVE_DEVICE_CHECK" }, - [CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS] = { "SAVE_LOAD_ALL_SLOTS" }, - [CUTSCENE_ITEM_TYPE_KEYBOARD] = { "KEYBOARD" }, - [CUTSCENE_ITEM_TYPE_AUDIO_PLAY] = { "AUDIO_PLAY" }, - [CUTSCENE_ITEM_TYPE_AUDIO_STOP] = { "AUDIO_STOP" }, - [CUTSCENE_ITEM_TYPE_AUDIO_PAUSE] = { "AUDIO_PAUSE" }, - [CUTSCENE_ITEM_TYPE_AUDIO_RESUME] = { "AUDIO_RESUME" }, - [CUTSCENE_ITEM_TYPE_AUDIO_FADE] = { "AUDIO_FADE" }, - [CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT] = { "AUDIO_FADE_WAIT" }, - [CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN] = { "AUDIO_SET_PAN" }, - [CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP] = { "AUDIO_SET_LOOP" }, - [CUTSCENE_ITEM_TYPE_AUDIO_SET] = { "AUDIO_SET" }, - [CUTSCENE_ITEM_TYPE_IDLE] = { "IDLE" }, - [CUTSCENE_ITEM_TYPE_UI_SHOW] = { "UI_SHOW" }, -}; - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_PAUSE_FLAG_TABLE[] = { - { "NPC", CUTSCENE_PAUSE_NPC }, - { "PLAYER", CUTSCENE_PAUSE_PLAYER }, - { "WORLD", CUTSCENE_PAUSE_WORLD }, - { "BATTLE", CUTSCENE_PAUSE_BATTLE }, -}; -#define ASSET_CUTSCENE_PAUSE_FLAG_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_PAUSE_FLAG_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_ENTITY_TYPE_TABLE[] = { - { "NULL", ENTITY_TYPE_NULL }, - { "PLAYER", ENTITY_TYPE_PLAYER }, - { "NPC", ENTITY_TYPE_NPC }, - { "ITEM", ENTITY_TYPE_ITEM }, -}; -#define ASSET_CUTSCENE_ENTITY_TYPE_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_ENTITY_TYPE_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_ENTITY_DIR_TABLE[] = { - { "NORTH", ENTITY_DIR_NORTH }, - { "EAST", ENTITY_DIR_EAST }, - { "SOUTH", ENTITY_DIR_SOUTH }, - { "WEST", ENTITY_DIR_WEST }, - { "UP", ENTITY_DIR_UP }, - { "RIGHT", ENTITY_DIR_RIGHT }, - { "DOWN", ENTITY_DIR_DOWN }, - { "LEFT", ENTITY_DIR_LEFT }, -}; -#define ASSET_CUTSCENE_ENTITY_DIR_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_ENTITY_DIR_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_EASING_TABLE[] = { - { "LINEAR", EASING_LINEAR }, - { "IN_SINE", EASING_IN_SINE }, - { "OUT_SINE", EASING_OUT_SINE }, - { "IN_OUT_SINE", EASING_IN_OUT_SINE }, - { "IN_QUAD", EASING_IN_QUAD }, - { "OUT_QUAD", EASING_OUT_QUAD }, - { "IN_OUT_QUAD", EASING_IN_OUT_QUAD }, - { "IN_CUBIC", EASING_IN_CUBIC }, - { "OUT_CUBIC", EASING_OUT_CUBIC }, - { "IN_OUT_CUBIC", EASING_IN_OUT_CUBIC }, - { "IN_QUART", EASING_IN_QUART }, - { "OUT_QUART", EASING_OUT_QUART }, - { "IN_OUT_QUART", EASING_IN_OUT_QUART }, - { "IN_BACK", EASING_IN_BACK }, - { "OUT_BACK", EASING_OUT_BACK }, - { "IN_OUT_BACK", EASING_IN_OUT_BACK }, -}; -#define ASSET_CUTSCENE_EASING_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_EASING_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_UI_EMOJI_TABLE[] = { - { "NULL", UI_EMOJI_NULL }, - { "QUESTION_MARK", UI_EMOJI_QUESTION_MARK }, - { "EXCLAMATION_MARK", UI_EMOJI_EXCLAMATION_MARK }, -}; -#define ASSET_CUTSCENE_UI_EMOJI_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_UI_EMOJI_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t -ASSET_CUTSCENE_BATTLE_ENCOUNTER_TABLE[] = { - { "REGULAR", BATTLE_ENCOUNTER_REGULAR }, - { "PLAYER_ADVANTAGE", BATTLE_ENCOUNTER_PLAYER_ADVANTAGE }, - { "BACK_ATTACK", BATTLE_ENCOUNTER_BACK_ATTACK }, -}; -#define ASSET_CUTSCENE_BATTLE_ENCOUNTER_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_BATTLE_ENCOUNTER_TABLE) / \ - sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_BATTLE_STATE_TABLE[] = { - { "NONE", BATTLE_STATE_NONE }, - { "OPENING", BATTLE_STATE_OPENING }, - { "PRE_ROUND", BATTLE_STATE_PRE_ROUND }, - { "PLAYER_SELECTION", BATTLE_STATE_PLAYER_SELECTION }, - { "AI_SELECTION", BATTLE_STATE_AI_SELECTION }, - { "MOVES_EXECUTING", BATTLE_STATE_MOVES_EXECUTING }, - { "POST_ROUND", BATTLE_STATE_POST_ROUND }, - { "ENDED", BATTLE_STATE_ENDED }, -}; -#define ASSET_CUTSCENE_BATTLE_STATE_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_BATTLE_STATE_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_SCENE_TYPE_TABLE[] = { - { "NULL", SCENE_TYPE_NULL }, - { "INITIAL", SCENE_TYPE_INITIAL }, - { "OVERWORLD", SCENE_TYPE_OVERWORLD }, - { "BATTLE", SCENE_TYPE_BATTLE }, -}; -#define ASSET_CUTSCENE_SCENE_TYPE_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_SCENE_TYPE_TABLE) / sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_UI_SCREEN_TYPE_TABLE[] = { - { "MAIN_MENU", UI_SCREEN_TYPE_MAIN_MENU }, -}; -#define ASSET_CUTSCENE_UI_SCREEN_TYPE_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_UI_SCREEN_TYPE_TABLE) / \ - sizeof(assetcutsceneenumentry_t)) - -static const assetcutsceneenumentry_t ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE[] = { - { "BGM_0", AUDIO_MIXER_CHANNEL_BGM_0 }, - { "VOICE_0", AUDIO_MIXER_CHANNEL_VOICE_0 }, - { "VOICE_1", AUDIO_MIXER_CHANNEL_VOICE_1 }, - { "SFX_0", AUDIO_MIXER_CHANNEL_SFX_0 }, - { "SFX_1", AUDIO_MIXER_CHANNEL_SFX_1 }, - { "SFX_2", AUDIO_MIXER_CHANNEL_SFX_2 }, - { "SFX_3", AUDIO_MIXER_CHANNEL_SFX_3 }, -}; -#define ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT \ - (sizeof(ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE) / \ - sizeof(assetcutsceneenumentry_t)) - -// Sentinels accepted in place of a literal index/id - see -// cutscenesystem.h's CUTSCENE_ENTITY_INTERACT/INTERACTED/CUTSCENE_AREA_ -// LAST_CREATED. Kept local since they're a JSON-authoring convenience, -// not part of the item struct definitions themselves. -#define ASSET_CUTSCENE_ENTITY_SENTINEL_INTERACT ((uint8_t)0xFE) -#define ASSET_CUTSCENE_ENTITY_SENTINEL_INTERACTED ((uint8_t)0xFD) -#define ASSET_CUTSCENE_AREA_SENTINEL_LAST_CREATED ((uint8_t)0xFF) - -bool_t assetCutsceneLookupEnum( - const assetcutsceneenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -) { - if(name == NULL) return false; - for(size_t i = 0; i < tableCount; i++) { - if(stringEquals(table[i].name, name)) { - *outValue = table[i].value; - return true; - } - } - return false; -} - -bool_t assetCutsceneLookupItemType( - const char_t *name, - cutsceneitemtype_t *outType -) { - if(name == NULL) return false; - for(size_t i = 0; i < CUTSCENE_ITEM_TYPE_COUNT; i++) { - const char_t *entryName = ASSET_CUTSCENE_ITEM_TYPE_TABLE[i].name; - if(entryName != NULL && stringEquals(entryName, name)) { - *outType = (cutsceneitemtype_t)i; - return true; - } - } - return false; -} - -bool_t assetCutsceneLookupEnumInsensitive( - const assetcutsceneenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -) { - if(name == NULL) return false; - for(size_t i = 0; i < tableCount; i++) { - if(stringCompareInsensitive(table[i].name, name) == 0) { - *outValue = table[i].value; - return true; - } - } - return false; -} - -bool_t assetCutsceneResolveEntityIndex(yyjson_val *val, uint8_t *outIndex) { - if(yyjson_is_str(val)) { - const char_t *name = yyjson_get_str(val); - if(stringCompareInsensitive(name, "INTERACT") == 0) { - *outIndex = ASSET_CUTSCENE_ENTITY_SENTINEL_INTERACT; - return true; - } - if(stringCompareInsensitive(name, "INTERACTED") == 0) { - *outIndex = ASSET_CUTSCENE_ENTITY_SENTINEL_INTERACTED; - return true; - } - return false; - } - if(!yyjson_is_int(val)) return false; - *outIndex = (uint8_t)yyjson_get_int(val); - return true; -} - -bool_t assetCutsceneResolveAreaId(yyjson_val *val, uint8_t *outAreaId) { - if(yyjson_is_str(val)) { - const char_t *name = yyjson_get_str(val); - if(stringCompareInsensitive(name, "LAST_CREATED") == 0) { - *outAreaId = ASSET_CUTSCENE_AREA_SENTINEL_LAST_CREATED; - return true; - } - return false; - } - if(!yyjson_is_int(val)) return false; - *outAreaId = (uint8_t)yyjson_get_int(val); - return true; -} - -bool_t assetCutsceneCopyStringField( - yyjson_val *obj, - const char_t *key, - const char_t *defaultValue, - char_t *dest, - const size_t destCapacity -) { - yyjson_val *val = yyjson_obj_get(obj, key); - const char_t *str = yyjson_get_str(val); - size_t len; - if(str == NULL) { - if(defaultValue == NULL) return false; - str = defaultValue; - len = (size_t)stringFormat(NULL, 0, "%s", str); - } else { - len = yyjson_get_len(val); - } - if(len >= destCapacity) return false; - stringCopy(dest, str, destCapacity); - return true; -} - -bool_t assetCutsceneReadWorldPos(yyjson_val *arr, worldpos_t *out) { - if(!yyjson_is_arr(arr) || yyjson_arr_size(arr) != 3) return false; - yyjson_val *x = yyjson_arr_get(arr, 0); - yyjson_val *y = yyjson_arr_get(arr, 1); - yyjson_val *z = yyjson_arr_get(arr, 2); - if(!yyjson_is_int(x) || !yyjson_is_int(y) || !yyjson_is_int(z)) return false; - out->x = (worldunit_t)yyjson_get_int(x); - out->y = (worldunit_t)yyjson_get_int(y); - out->z = (worldunit_t)yyjson_get_int(z); - return true; -} - -size_t assetCutscenePoolAlign(const size_t size) { - return (size + 3) & ~((size_t)3); -} - -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")); - if(typeName == NULL) continue; - - if(stringEquals(typeName, "ENTITY_WALK_TO")) { - size_t count = yyjson_arr_size(yyjson_obj_get(itemVal, "positions")); - size = assetCutscenePoolAlign(size) + count * sizeof(worldpos_t); - } else if(stringEquals(typeName, "MAP_AREA_WAIT")) { - size_t count = yyjson_arr_size(yyjson_obj_get(itemVal, "areaIds")); - size = assetCutscenePoolAlign(size) + count; - } - } - return size; -} +#include "rpg/cutscene/item/json/cutscenejsonpauseflags.h" errorret_t assetCutsceneParseItem( assetloading_t *loading, @@ -348,7 +32,7 @@ errorret_t assetCutsceneParseItem( } const char_t *typeName = yyjson_get_str(yyjson_obj_get(itemObj, "type")); - if(!assetCutsceneLookupItemType(typeName, &item->type)) { + if(!cutsceneItemLookupType(typeName, &item->type)) { assetLoaderErrorThrow( loading, "Cutscene item %u: missing/unsupported item type %s", @@ -357,815 +41,48 @@ errorret_t assetCutsceneParseItem( ); } - switch(item->type) { - case CUTSCENE_ITEM_TYPE_TEXT: - if(!assetCutsceneCopyStringField( - itemObj, "text", NULL, item->text.text, CUTSCENE_TEXT_MAX_CHARS - )) { - assetLoaderErrorThrow( - loading, - "Cutscene item %u (TEXT): missing/too-long 'text'", - (uint32_t)index - ); - } - break; + 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 + ); + } - case CUTSCENE_ITEM_TYPE_TEXT_MINI: { - if(!assetCutsceneCopyStringField( - itemObj, "text", NULL, item->textMini.text, CUTSCENE_TEXT_MINI_MAX_CHARS - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (TEXT_MINI): missing/too-long 'text'", - (uint32_t)index - ); - } - yyjson_val *position = yyjson_obj_get(itemObj, "position"); - if(!yyjson_is_arr(position) || yyjson_arr_size(position) != 3) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (TEXT_MINI): 'position' must be [x, y, z]", - (uint32_t)index - ); - } - item->textMini.position[0] = - (float_t)yyjson_get_num(yyjson_arr_get(position, 0)); - item->textMini.position[1] = - (float_t)yyjson_get_num(yyjson_arr_get(position, 1)); - item->textMini.position[2] = - (float_t)yyjson_get_num(yyjson_arr_get(position, 2)); - item->textMini.duration = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "duration")); - break; - } - - case CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE: - item->textMiniHide.index = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "index")); - break; - - case CUTSCENE_ITEM_TYPE_WAIT: - item->wait = (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "seconds")); - break; - - case CUTSCENE_ITEM_TYPE_CUTSCENE: { - const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name")); - if(name == NULL) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (CUTSCENE): missing 'name'", - (uint32_t)index - ); - } - - // Referenced cutscene is a sibling asset (assets/cutscenes/.jsonc), - // resolved to a stable pointer right now rather than deferred to - // runtime - assetRequireLoaded re-enters assetUpdate() safely from - // within this sync load (see its ASSET_ENTRY_STATE_LOADING_SYNC case), - // and the entry is locked forever (never unlocked) so the pointer - // stays valid for the process lifetime, same convention as - // ui/screen/mainmenu/uimainmenu.c's MAIN_MENU_CUTSCENE_ENTRY. - char_t path[ASSET_FILE_NAME_MAX]; - int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name); - if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (CUTSCENE): 'name' too long", - (uint32_t)index - ); - } - stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name); - - assetentry_t *refEntry = - assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL); - assetLoaderErrorChain(loading, assetRequireLoaded(refEntry)); - item->cutscene = &refEntry->data.cutscene.cutscene; - break; - } - - case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT: - if(!assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), - &item->entityTeleport.entityIndex - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_TELEPORT): invalid 'entityIndex'", - (uint32_t)index - ); - } - if(!assetCutsceneReadWorldPos( - yyjson_obj_get(itemObj, "target"), &item->entityTeleport.target - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_TELEPORT): 'target' must be " - "[x, y, z]", (uint32_t)index - ); - } - break; - - case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO: { - if(!assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->entityWalkTo.entityIndex - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_WALK_TO): invalid 'entityIndex'", - (uint32_t)index - ); - } - yyjson_val *walkAround = yyjson_obj_get(itemObj, "walkAround"); - item->entityWalkTo.walkAround = - walkAround == NULL ? true : yyjson_get_bool(walkAround); - - 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) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_WALK_TO): 'positions' must be a " - "non-empty array of [x, y, z]", (uint32_t)index - ); - } - - *poolOffset = assetCutscenePoolAlign(*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(!assetCutsceneReadWorldPos(posVal, &dest[i])) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_WALK_TO): position %u must be " - "[x, y, z]", (uint32_t)index, (uint32_t)i - ); - } - i++; - } - item->entityWalkTo.positions = dest; - item->entityWalkTo.count = (uint8_t)count; - *poolOffset += count * sizeof(worldpos_t); - break; - } - - case CUTSCENE_ITEM_TYPE_FADE: { - yyjson_val *from = yyjson_obj_get(itemObj, "from"); - yyjson_val *to = yyjson_obj_get(itemObj, "to"); - if(!yyjson_is_arr(from) || yyjson_arr_size(from) != 4 || - !yyjson_is_arr(to) || yyjson_arr_size(to) != 4) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (FADE): 'from'/'to' must each be " - "[r, g, b, a]", (uint32_t)index - ); - } - item->fade.from.r = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 0)); - item->fade.from.g = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 1)); - item->fade.from.b = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 2)); - item->fade.from.a = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 3)); - item->fade.to.r = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 0)); - item->fade.to.g = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 1)); - item->fade.to.b = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 2)); - item->fade.to.a = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 3)); - item->fade.duration = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "duration")); - - const char_t *easingName = - yyjson_get_str(yyjson_obj_get(itemObj, "easing")); - int32_t easingValue = EASING_LINEAR; - if(easingName != NULL && !assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_EASING_TABLE, ASSET_CUTSCENE_EASING_TABLE_COUNT, - easingName, &easingValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (FADE): invalid 'easing'", (uint32_t)index - ); - } - item->fade.easing = (easingtype_t)easingValue; - break; - } - - case CUTSCENE_ITEM_TYPE_SET_PAUSE: { - yyjson_val *flags = yyjson_obj_get(itemObj, "flags"); - if(!yyjson_is_arr(flags)) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (SET_PAUSE): 'flags' must be an array", - (uint32_t)index - ); - } - cutscenepause_t value = CUTSCENE_PAUSE_NONE; - yyjson_arr_iter iter = yyjson_arr_iter_with(flags); - yyjson_val *flagVal; - while((flagVal = yyjson_arr_iter_next(&iter)) != NULL) { - int32_t flagValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_PAUSE_FLAG_TABLE, - ASSET_CUTSCENE_PAUSE_FLAG_TABLE_COUNT, - yyjson_get_str(flagVal), &flagValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (SET_PAUSE): invalid pause flag", - (uint32_t)index - ); - } - value |= (cutscenepause_t)flagValue; - } - item->setPause = value; - break; - } - - case CUTSCENE_ITEM_TYPE_ITEM_GIVE: - item->itemGive.item = - (itemid_t)yyjson_get_int(yyjson_obj_get(itemObj, "item")); - item->itemGive.quantity = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "quantity")); - break; - - case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE: - if(!assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->entityRemove.entityIndex - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_REMOVE): invalid 'entityIndex'", - (uint32_t)index - ); - } - break; - - case CUTSCENE_ITEM_TYPE_ENTITY_ADD: { - int32_t entityTypeValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_ENTITY_TYPE_TABLE, - ASSET_CUTSCENE_ENTITY_TYPE_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "entityType")), &entityTypeValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_ADD): invalid 'entityType'", - (uint32_t)index - ); - } - item->entityAdd.entityType = (entitytype_t)entityTypeValue; - if(!assetCutsceneReadWorldPos( - yyjson_obj_get(itemObj, "position"), &item->entityAdd.position - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_ADD): 'position' must be " - "[x, y, z]", (uint32_t)index - ); - } - break; - } - - case CUTSCENE_ITEM_TYPE_ENTITY_TURN: { - if(!assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->entityTurn.entityIndex - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_TURN): invalid 'entityIndex'", - (uint32_t)index - ); - } - int32_t dirValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_ENTITY_DIR_TABLE, ASSET_CUTSCENE_ENTITY_DIR_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "direction")), &dirValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_TURN): invalid 'direction'", - (uint32_t)index - ); - } - item->entityTurn.direction = (entitydir_t)dirValue; - break; - } - - case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY: - if(!assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), - &item->entityWalkToEntity.entityIndex - ) || !assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "targetEntityIndex"), - &item->entityWalkToEntity.targetEntityIndex - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (ENTITY_WALK_TO_ENTITY): invalid entity " - "index", (uint32_t)index - ); - } - item->entityWalkToEntity.offsetX = - (worldunit_t)yyjson_get_int(yyjson_obj_get(itemObj, "offsetX")); - item->entityWalkToEntity.offsetY = - (worldunit_t)yyjson_get_int(yyjson_obj_get(itemObj, "offsetY")); - break; - - case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE: - if(!assetCutsceneResolveAreaId( - yyjson_obj_get(itemObj, "areaId"), &item->mapAreaRemove.areaId - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MAP_AREA_REMOVE): invalid 'areaId'", - (uint32_t)index - ); - } - break; - - case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT: { - yyjson_val *areaIds = yyjson_obj_get(itemObj, "areaIds"); - size_t count = yyjson_arr_size(areaIds); - if( - !yyjson_is_arr(areaIds) || count == 0 || - count > CUTSCENE_MAP_AREA_WAIT_MAX - ) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MAP_AREA_WAIT): 'areaIds' must have " - "1-%u entries", (uint32_t)index, (uint32_t)CUTSCENE_MAP_AREA_WAIT_MAX - ); - } - *poolOffset = assetCutscenePoolAlign(*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(!assetCutsceneResolveAreaId(idVal, &dest[i])) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MAP_AREA_WAIT): areaIds[%u] is invalid", - (uint32_t)index, (uint32_t)i - ); - } - i++; - } - item->mapAreaWait.areaIds = dest; - item->mapAreaWait.count = (uint8_t)count; - *poolOffset += count; - break; - } - - case CUTSCENE_ITEM_TYPE_START_BATTLE: { - int32_t encounterValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_BATTLE_ENCOUNTER_TABLE, - ASSET_CUTSCENE_BATTLE_ENCOUNTER_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "encounterType")), - &encounterValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (START_BATTLE): invalid 'encounterType'", - (uint32_t)index - ); - } - item->startBattle.encounterType = (battleencountertype_t)encounterValue; - - yyjson_val *fleeAvailable = yyjson_obj_get(itemObj, "fleeAvailable"); - item->startBattle.fleeAvailable = - fleeAvailable == NULL ? true : yyjson_get_bool(fleeAvailable); - - yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies"); - size_t enemyCount = yyjson_arr_size(enemies); - if(!yyjson_is_arr(enemies) || enemyCount == 0 || - enemyCount > CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (START_BATTLE): 'enemies' must have 1-%u " - "entries", (uint32_t)index, - (uint32_t)CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX - ); - } - item->startBattle.enemyCount = (uint8_t)enemyCount; - - yyjson_arr_iter iter = yyjson_arr_iter_with(enemies); - yyjson_val *enemyVal; - size_t e = 0; - while((enemyVal = yyjson_arr_iter_next(&iter)) != NULL) { - yyjson_val *stats = yyjson_obj_get(enemyVal, "stats"); - if(!yyjson_is_obj(stats)) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (START_BATTLE): enemies[%u] is missing " - "'stats'", (uint32_t)index, (uint32_t)e - ); - } - cutscenestartbattleenemy_t *enemy = &item->startBattle.enemies[e]; - enemy->stats.attack = - (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "attack")); - enemy->stats.defense = - (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "defense")); - enemy->stats.magic = - (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "magic")); - enemy->stats.speed = - (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "speed")); - enemy->stats.luck = - (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "luck")); - enemy->healthMax = - (uint16_t)yyjson_get_int(yyjson_obj_get(enemyVal, "healthMax")); - enemy->mpMax = - (uint16_t)yyjson_get_int(yyjson_obj_get(enemyVal, "mpMax")); - e++; - } - break; - } - - case CUTSCENE_ITEM_TYPE_EMOJI: { - if(!assetCutsceneResolveEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->emoji.entityIndex - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (EMOJI): invalid 'entityIndex'", - (uint32_t)index - ); - } - item->emoji.duration = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "duration")); - int32_t emojiValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_UI_EMOJI_TABLE, ASSET_CUTSCENE_UI_EMOJI_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "emojiType")), &emojiValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (EMOJI): invalid 'emojiType'", - (uint32_t)index - ); - } - item->emoji.emojiType = (uiemojitype_t)emojiValue; - break; - } - - case CUTSCENE_ITEM_TYPE_SHAKE: - item->shake.amount = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "amount")); - item->shake.duration = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "duration")); - break; - - case CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE: { - int32_t stateValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_BATTLE_STATE_TABLE, - ASSET_CUTSCENE_BATTLE_STATE_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "state")), &stateValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (BATTLE_WAIT_STATE): invalid 'state'", - (uint32_t)index - ); - } - item->battleWaitState.state = (battlestate_t)stateValue; - break; - } - - case CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION: - item->battleForceAction.fighterIndex = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "fighterIndex")); - item->battleForceAction.targetIndex = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "targetIndex")); - break; - - case CUTSCENE_ITEM_TYPE_MODAL: - if(yyjson_obj_get(itemObj, "options") != NULL) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MODAL): options are not supported here - " - "use MODAL_OPTIONS_MARKERS instead", (uint32_t)index - ); - } - assetCutsceneCopyStringField( - itemObj, "title", "", item->modal.title, CUTSCENE_MODAL_TITLE_MAX_CHARS - ); - if(!assetCutsceneCopyStringField( - itemObj, "message", NULL, item->modal.message, - CUTSCENE_MODAL_MESSAGE_MAX_CHARS - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MODAL): missing/too-long 'message'", - (uint32_t)index - ); - } - break; - - case CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS: { - assetCutsceneCopyStringField( - itemObj, "title", "", item->modalOptionsMarkers.title, - CUTSCENE_MODAL_TITLE_MAX_CHARS - ); - if(!assetCutsceneCopyStringField( - itemObj, "message", NULL, item->modalOptionsMarkers.message, - CUTSCENE_MODAL_MESSAGE_MAX_CHARS - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MODAL_OPTIONS_MARKERS): missing/too-long " - "'message'", (uint32_t)index - ); - } - - yyjson_val *options = yyjson_obj_get(itemObj, "options"); - size_t optionCount = yyjson_arr_size(options); - if(!yyjson_is_arr(options) || optionCount == 0 || - optionCount > CUTSCENE_MODAL_OPTIONS_MARKERS_MAX) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MODAL_OPTIONS_MARKERS): 'options' must " - "have 1-%u entries", (uint32_t)index, - (uint32_t)CUTSCENE_MODAL_OPTIONS_MARKERS_MAX - ); - } - item->modalOptionsMarkers.optionCount = (uint8_t)optionCount; - - yyjson_arr_iter iter = yyjson_arr_iter_with(options); - yyjson_val *optionVal; - size_t o = 0; - while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) { - const char_t *text = yyjson_get_str(yyjson_obj_get(optionVal, "text")); - const char_t *marker = - yyjson_get_str(yyjson_obj_get(optionVal, "marker")); - if(text == NULL || marker == NULL) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MODAL_OPTIONS_MARKERS): options[%u] " - "needs 'text' and 'marker'", (uint32_t)index, (uint32_t)o - ); - } - item->modalOptionsMarkers.options[o] = text; - item->modalOptionsMarkers.markers[o] = marker; - o++; - } - break; - } - - case CUTSCENE_ITEM_TYPE_MODAL_CLOSE: - case CUTSCENE_ITEM_TYPE_RESTART: - case CUTSCENE_ITEM_TYPE_IDLE: - break; - - case CUTSCENE_ITEM_TYPE_UI_SHOW: { - int32_t screenValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_UI_SCREEN_TYPE_TABLE, - ASSET_CUTSCENE_UI_SCREEN_TYPE_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "name")), &screenValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (UI_SHOW): invalid 'name'", (uint32_t)index - ); - } - item->uiShow.screen = (uiscreentype_t)screenValue; - break; - } - - case CUTSCENE_ITEM_TYPE_PRINT: - if(!assetCutsceneCopyStringField( - itemObj, "text", NULL, item->print.text, CUTSCENE_PRINT_MAX_CHARS - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (PRINT): missing/too-long 'text'", - (uint32_t)index - ); - } - break; - - case CUTSCENE_ITEM_TYPE_MARKER: { - const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name")); - if(name == NULL) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (MARKER): missing 'name'", (uint32_t)index - ); - } - item->marker.name = name; - break; - } - - case CUTSCENE_ITEM_TYPE_SCENE: { - int32_t sceneValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_SCENE_TYPE_TABLE, ASSET_CUTSCENE_SCENE_TYPE_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "sceneType")), &sceneValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (SCENE): invalid 'sceneType'", - (uint32_t)index - ); - } - item->sceneChange.type = (scenetype_t)sceneValue; - break; - } - - case CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK: { - const char_t *successMarker = - yyjson_get_str(yyjson_obj_get(itemObj, "successMarker")); - const char_t *failureMarker = - yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker")); - if(successMarker == NULL || failureMarker == NULL) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (SAVE_DEVICE_CHECK): needs " - "'successMarker'/'failureMarker'", (uint32_t)index - ); - } - item->saveDeviceCheck.successMarker = successMarker; - item->saveDeviceCheck.failureMarker = failureMarker; - break; - } - - case CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS: { - const char_t *successMarker = - yyjson_get_str(yyjson_obj_get(itemObj, "successMarker")); - const char_t *failureMarker = - yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker")); - if(successMarker == NULL || failureMarker == NULL) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (SAVE_LOAD_ALL_SLOTS): needs " - "'successMarker'/'failureMarker'", (uint32_t)index - ); - } - item->saveLoadAllSlots.successMarker = successMarker; - item->saveLoadAllSlots.failureMarker = failureMarker; - break; - } - - case CUTSCENE_ITEM_TYPE_AUDIO_PLAY: { - if(!assetCutsceneCopyStringField( - itemObj, "file", NULL, item->audioPlay.file, AUDIO_PATH_MAX - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_PLAY): missing/too-long 'file'", - (uint32_t)index - ); - } - int32_t channelValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE, - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_PLAY): invalid 'channel'", - (uint32_t)index - ); - } - item->audioPlay.channel = (audiomixerchannel_t)channelValue; - - yyjson_val *volume = yyjson_obj_get(itemObj, "volume"); - item->audioPlay.volume = - volume == NULL ? 1.0f : (float_t)yyjson_get_num(volume); - yyjson_val *pan = yyjson_obj_get(itemObj, "pan"); - item->audioPlay.pan = pan == NULL ? 0.0f : (float_t)yyjson_get_num(pan); - yyjson_val *looping = yyjson_obj_get(itemObj, "looping"); - item->audioPlay.looping = - looping == NULL ? false : yyjson_get_bool(looping); - yyjson_val *loopCount = yyjson_obj_get(itemObj, "loopCount"); - item->audioPlay.loopCount = - loopCount == NULL ? 0 : (uint8_t)yyjson_get_int(loopCount); - yyjson_val *loopStart = yyjson_obj_get(itemObj, "loopStart"); - item->audioPlay.loopStart = - loopStart == NULL ? -1.0f : (float_t)yyjson_get_num(loopStart); - yyjson_val *loopTo = yyjson_obj_get(itemObj, "loopTo"); - item->audioPlay.loopTo = - loopTo == NULL ? 0.0f : (float_t)yyjson_get_num(loopTo); - break; - } - - case CUTSCENE_ITEM_TYPE_AUDIO_STOP: - case CUTSCENE_ITEM_TYPE_AUDIO_PAUSE: - case CUTSCENE_ITEM_TYPE_AUDIO_RESUME: - case CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT: { - int32_t channelValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE, - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u: invalid 'channel'", (uint32_t)index - ); - } - // All four share the same single-field shape (a channel enum stored - // directly in the item union, not a sub-struct) - see cutsceneitem.h. - switch(item->type) { - case CUTSCENE_ITEM_TYPE_AUDIO_STOP: - item->audioStopChannel = (audiomixerchannel_t)channelValue; - break; - case CUTSCENE_ITEM_TYPE_AUDIO_PAUSE: - item->audioPauseChannel = (audiomixerchannel_t)channelValue; - break; - case CUTSCENE_ITEM_TYPE_AUDIO_RESUME: - item->audioResumeChannel = (audiomixerchannel_t)channelValue; - break; - default:// CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT - item->audioFadeWaitChannel = (audiomixerchannel_t)channelValue; - break; - } - break; - } - - case CUTSCENE_ITEM_TYPE_AUDIO_FADE: { - int32_t channelValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE, - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_FADE): invalid 'channel'", - (uint32_t)index - ); - } - item->audioFade.channel = (audiomixerchannel_t)channelValue; - item->audioFade.from = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "from")); - item->audioFade.to = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "to")); - item->audioFade.duration = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "duration")); - - const char_t *easingName = - yyjson_get_str(yyjson_obj_get(itemObj, "easing")); - int32_t easingValue = EASING_LINEAR; - if(easingName != NULL && !assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_EASING_TABLE, ASSET_CUTSCENE_EASING_TABLE_COUNT, - easingName, &easingValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_FADE): invalid 'easing'", - (uint32_t)index - ); - } - item->audioFade.easing = (easingtype_t)easingValue; - break; - } - - case CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN: { - int32_t channelValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE, - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_SET_PAN): invalid 'channel'", - (uint32_t)index - ); - } - item->audioSetPan.channel = (audiomixerchannel_t)channelValue; - item->audioSetPan.pan = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "pan")); - break; - } - - case CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP: { - int32_t channelValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE, - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_SET_LOOP): invalid 'channel'", - (uint32_t)index - ); - } - item->audioSetLoop.channel = (audiomixerchannel_t)channelValue; - yyjson_val *looping = yyjson_obj_get(itemObj, "looping"); - item->audioSetLoop.looping = - looping == NULL ? true : yyjson_get_bool(looping); - yyjson_val *loopCount = yyjson_obj_get(itemObj, "loopCount"); - item->audioSetLoop.loopCount = - loopCount == NULL ? 0 : (uint8_t)yyjson_get_int(loopCount); - yyjson_val *loopStart = yyjson_obj_get(itemObj, "loopStart"); - item->audioSetLoop.loopStart = - loopStart == NULL ? -1.0f : (float_t)yyjson_get_num(loopStart); - yyjson_val *loopTo = yyjson_obj_get(itemObj, "loopTo"); - item->audioSetLoop.loopTo = - loopTo == NULL ? 0.0f : (float_t)yyjson_get_num(loopTo); - break; - } - - case CUTSCENE_ITEM_TYPE_AUDIO_SET: { - int32_t channelValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE, - ASSET_CUTSCENE_AUDIO_CHANNEL_TABLE_COUNT, - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - assetLoaderErrorThrow( - loading, "Cutscene item %u (AUDIO_SET): invalid 'channel'", - (uint32_t)index - ); - } - item->audioSet.channel = (audiomixerchannel_t)channelValue; - item->audioSet.pan = - (float_t)yyjson_get_num(yyjson_obj_get(itemObj, "pan")); - yyjson_val *looping = yyjson_obj_get(itemObj, "looping"); - item->audioSet.looping = - looping == NULL ? true : yyjson_get_bool(looping); - yyjson_val *loopCount = yyjson_obj_get(itemObj, "loopCount"); - item->audioSet.loopCount = - loopCount == NULL ? 0 : (uint8_t)yyjson_get_int(loopCount); - yyjson_val *loopStart = yyjson_obj_get(itemObj, "loopStart"); - item->audioSet.loopStart = - loopStart == NULL ? -1.0f : (float_t)yyjson_get_num(loopStart); - yyjson_val *loopTo = yyjson_obj_get(itemObj, "loopTo"); - item->audioSet.loopTo = - loopTo == NULL ? 0.0f : (float_t)yyjson_get_num(loopTo); - break; - } - - default: - // Unreachable - ASSET_CUTSCENE_ITEM_TYPE_TABLE only maps to types - // handled above. - 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(); } +// First pass over the parsed "items" array: sums the exact pool bytes +// ENTITY_WALK_TO/MAP_AREA_WAIT items will need, so the real parse pass can +// allocate the pool exactly once up front rather than growing/moving it +// (which would dangle pointers already written into earlier items). Any +// item with an invalid/missing type is silently skipped here - the real +// error for that gets thrown later during the actual per-item parse pass +// in assetCutsceneParseItem, this first pass is pool-sizing only. +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; +} + void assetCutsceneFreeParsed(assetcutsceneoutput_t *out) { if(out->items != NULL) { memoryFree(out->items); @@ -1266,21 +183,11 @@ errorret_t assetCutsceneLoaderSync(assetloading_t *loading) { cutscenepause_t pause = CUTSCENE_PAUSE_DEFAULT; yyjson_val *pauseArr = yyjson_obj_get(root, "pause"); if(yyjson_is_arr(pauseArr)) { - pause = CUTSCENE_PAUSE_NONE; - yyjson_arr_iter pauseIter = yyjson_arr_iter_with(pauseArr); - yyjson_val *flagVal; - while((flagVal = yyjson_arr_iter_next(&pauseIter)) != NULL) { - int32_t flagValue; - if(!assetCutsceneLookupEnumInsensitive( - ASSET_CUTSCENE_PAUSE_FLAG_TABLE, ASSET_CUTSCENE_PAUSE_FLAG_TABLE_COUNT, - yyjson_get_str(flagVal), &flagValue - )) { - assetCutsceneFreeParsed(out); - assetLoaderErrorThrow( - loading, "Cutscene JSON has an invalid 'pause' flag" - ); - } - pause |= (cutscenepause_t)flagValue; + if(!cutsceneJsonParsePauseFlags(pauseArr, &pause)) { + assetCutsceneFreeParsed(out); + assetLoaderErrorThrow( + loading, "Cutscene JSON has an invalid 'pause' flag" + ); } } diff --git a/src/dusk/asset/loader/cutscene/assetcutsceneloader.h b/src/dusk/asset/loader/cutscene/assetcutsceneloader.h index 197f4f0b..05128f4f 100644 --- a/src/dusk/asset/loader/cutscene/assetcutsceneloader.h +++ b/src/dusk/asset/loader/cutscene/assetcutsceneloader.h @@ -9,7 +9,7 @@ #include "asset/assetfile.h" #include "rpg/cutscene/cutscene.h" #include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/overworld/worldpos.h" +#include "rpg/cutscene/item/json/cutscenejsonpool.h" #include "yyjson.h" // Above this, something is almost certainly wrong (or malicious) rather @@ -54,144 +54,15 @@ typedef struct { yyjson_doc *doc; } assetcutsceneoutput_t; -typedef struct { - const char_t *name; - int32_t value; -} assetcutsceneenumentry_t; - -typedef struct { - const char_t *name; -} assetcutsceneitemtypeentry_t; - -/** - * Looks up name in a {name, value} table via stringEquals - exact case, - * used only for the top-level item "type" name (the one enum-ish field - * the original tool's ITEM_TYPE dict looked up without .upper()). - * - * @param table Table to search. - * @param tableCount Number of entries in table. - * @param name Name to search for. - * @param outValue Set to the matching entry's value on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t assetCutsceneLookupEnum( - const assetcutsceneenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -); - -/** - * Looks up name (exact case) against the item type table, which is indexed - * directly by cutsceneitemtype_t rather than searched by value. - * - * @param name Name to search for. - * @param outType Set to the matching item type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t assetCutsceneLookupItemType( - const char_t *name, - cutsceneitemtype_t *outType -); - -/** - * Looks up name in a {name, value} table via stringCompareInsensitive - - * every other enum-ish field (pause flags, entity/scene/battle/audio - * enums, ...) is authored case-insensitively, matching the original - * tool's ENUM_TABLE[name.upper()] lookups. - * - * @param table Table to search. - * @param tableCount Number of entries in table. - * @param name Name to search for. - * @param outValue Set to the matching entry's value on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t assetCutsceneLookupEnumInsensitive( - const assetcutsceneenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -); - -/** - * Resolves a JSON value that's either an entity index sentinel string - * ("INTERACT"/"INTERACTED") or a plain integer entity index. - * - * @param val JSON value to resolve (a string or a number). - * @param outIndex Set to the resolved index on success. - * @return true if val resolved to a valid index, false otherwise. - */ -bool_t assetCutsceneResolveEntityIndex(yyjson_val *val, uint8_t *outIndex); - -/** - * Resolves a JSON value that's either the "LAST_CREATED" map area sentinel - * string or a plain integer area id. - * - * @param val JSON value to resolve (a string or a number). - * @param outAreaId Set to the resolved area id on success. - * @return true if val resolved to a valid area id, false otherwise. - */ -bool_t assetCutsceneResolveAreaId(yyjson_val *val, uint8_t *outAreaId); - -/** - * Reads obj[key] as a string (falling back to defaultValue if the key is - * absent and defaultValue is non-NULL) into dest, a fixed-capacity - * char_t[destCapacity] item field. Fails rather than truncating/asserting - * if the string doesn't fit - this is untrusted, authored file content, - * not an in-memory invariant. - * - * @param obj The item's JSON object. - * @param key Field name to read. - * @param defaultValue Value to use if key is absent, or NULL to require it. - * @param dest Destination buffer. - * @param destCapacity Capacity of dest, including the null terminator. - * @return true on success, false if the field is missing (with no - * default) or exceeds destCapacity. - */ -bool_t assetCutsceneCopyStringField( - yyjson_val *obj, - const char_t *key, - const char_t *defaultValue, - char_t *dest, - const size_t destCapacity -); - -/** - * Reads a 3-element JSON array of integers into a worldpos_t. - * - * @param arr JSON array value, expected to hold exactly 3 numbers. - * @param out Set to the decoded position on success. - * @return true if arr was a valid 3-element numeric array, false otherwise. - */ -bool_t assetCutsceneReadWorldPos(yyjson_val *arr, worldpos_t *out); - -/** - * 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 assetCutscenePoolAlign(const size_t size); - -/** - * First pass over the parsed "items" array: sums the exact pool bytes - * ENTITY_WALK_TO/MAP_AREA_WAIT items will need, so the real parse pass can - * allocate the pool exactly once up front rather than growing/moving it - * (which would dangle pointers already written into earlier items). - * - * @param itemsArr The cutscene's "items" JSON array. - * @return Total pool bytes required (0 if none of the items need one). - */ -size_t assetCutsceneComputePoolSize(yyjson_val *itemsArr); - /** * 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. + * 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. diff --git a/src/dusk/rpg/cutscene/item/CMakeLists.txt b/src/dusk/rpg/cutscene/item/CMakeLists.txt index 0f7aaed9..9e6cf392 100755 --- a/src/dusk/rpg/cutscene/item/CMakeLists.txt +++ b/src/dusk/rpg/cutscene/item/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC cutsceneitem.c + cutsceneitembase.c cutscenecallback.c cutsceneprint.c ) @@ -18,3 +19,4 @@ add_subdirectory(ui) add_subdirectory(battle) add_subdirectory(save) add_subdirectory(audio) +add_subdirectory(json) diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c index 964cc7c0..a8a3be57 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c @@ -7,6 +7,9 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" +#include "rpg/cutscene/item/json/cutscenejsoneasing.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneAudioFadeStart( const cutsceneitem_t *item, @@ -27,3 +30,32 @@ bool_t cutsceneAudioFadeUpdate( ) { return true; } + +errorret_t cutsceneAudioFadeLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + audiomixerchannel_t channelValue; + if(!cutsceneJsonLookupAudioChannel( + yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue + )) { + errorThrow("Cutscene item (AUDIO_FADE): invalid 'channel'"); + } + item->audioFade.channel = channelValue; + item->audioFade.from = cutsceneJsonGetFloat(itemObj, "from", 0.0f); + item->audioFade.to = cutsceneJsonGetFloat(itemObj, "to", 0.0f); + item->audioFade.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); + + const char_t *easingName = + yyjson_get_str(yyjson_obj_get(itemObj, "easing")); + easingtype_t easingValue = EASING_LINEAR; + if(easingName != NULL && + !cutsceneJsonLookupEasing(easingName, &easingValue)) { + errorThrow("Cutscene item (AUDIO_FADE): invalid 'easing'"); + } + item->audioFade.easing = easingValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h index 7a873418..09631ae3 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h @@ -8,9 +8,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" #include "animation/easing.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { audiomixerchannel_t channel; @@ -44,3 +42,19 @@ bool_t cutsceneAudioFadeUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_FADE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c index 85e42b14..4b056b29 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" bool_t cutsceneAudioFadeWaitUpdate( const cutsceneitem_t *item, @@ -14,3 +15,12 @@ bool_t cutsceneAudioFadeWaitUpdate( ) { return !audioMixerIsFading(item->audioFadeWaitChannel); } + +errorret_t cutsceneAudioFadeWaitLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioFadeWaitChannel); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h index 918ad3a8..739db057 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Updates an audio fade-wait step, completing once the watched channel's @@ -24,3 +22,19 @@ bool_t cutsceneAudioFadeWaitUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_FADE_WAIT cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c index 59738064..896e3299 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" void cutsceneAudioPauseStart( const cutsceneitem_t *item, @@ -21,3 +22,12 @@ bool_t cutsceneAudioPauseUpdate( ) { return true; } + +errorret_t cutsceneAudioPauseLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioPauseChannel); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h index 4b283404..335e363d 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Starts an audio pause step (pauses the channel immediately). @@ -33,3 +31,19 @@ bool_t cutsceneAudioPauseUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_PAUSE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c index 9dd070cc..158c6020 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c @@ -6,6 +6,8 @@ */ #include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneAudioPlayStart( const cutsceneitem_t *item, @@ -29,3 +31,34 @@ bool_t cutsceneAudioPlayUpdate( ) { return true; } + +errorret_t cutsceneAudioPlayLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonCopyString( + itemObj, "file", NULL, item->audioPlay.file, AUDIO_PATH_MAX + )) { + errorThrow("Cutscene item (AUDIO_PLAY): missing/too-long 'file'"); + } + audiomixerchannel_t channelValue; + if(!cutsceneJsonLookupAudioChannel( + yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue + )) { + errorThrow("Cutscene item (AUDIO_PLAY): invalid 'channel'"); + } + item->audioPlay.channel = channelValue; + + item->audioPlay.volume = cutsceneJsonGetFloat(itemObj, "volume", 1.0f); + item->audioPlay.pan = cutsceneJsonGetFloat(itemObj, "pan", 0.0f); + item->audioPlay.looping = cutsceneJsonGetBool(itemObj, "looping", false); + item->audioPlay.loopCount = + (uint8_t)cutsceneJsonGetInt(itemObj, "loopCount", 0); + item->audioPlay.loopStart = + cutsceneJsonGetFloat(itemObj, "loopStart", -1.0f); + item->audioPlay.loopTo = cutsceneJsonGetFloat(itemObj, "loopTo", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h index fae99771..007f913b 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixer.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { char_t file[AUDIO_PATH_MAX]; @@ -46,3 +44,19 @@ bool_t cutsceneAudioPlayUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_PLAY cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c index 5fc2e349..480b314c 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" void cutsceneAudioResumeStart( const cutsceneitem_t *item, @@ -21,3 +22,12 @@ bool_t cutsceneAudioResumeUpdate( ) { return true; } + +errorret_t cutsceneAudioResumeLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioResumeChannel); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h index 86ae339c..e04ab810 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Starts an audio resume step (resumes the channel immediately). @@ -33,3 +31,19 @@ bool_t cutsceneAudioResumeUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_RESUME cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c index 83cd0ea1..7278b6d3 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c @@ -7,6 +7,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneAudioSetStart( const cutsceneitem_t *item, @@ -28,3 +30,27 @@ bool_t cutsceneAudioSetUpdate( ) { return true; } + +errorret_t cutsceneAudioSetLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + audiomixerchannel_t channelValue; + if(!cutsceneJsonLookupAudioChannel( + yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue + )) { + errorThrow("Cutscene item (AUDIO_SET): invalid 'channel'"); + } + item->audioSet.channel = channelValue; + item->audioSet.pan = cutsceneJsonGetFloat(itemObj, "pan", 0.0f); + item->audioSet.looping = cutsceneJsonGetBool(itemObj, "looping", true); + item->audioSet.loopCount = + (uint8_t)cutsceneJsonGetInt(itemObj, "loopCount", 0); + item->audioSet.loopStart = + cutsceneJsonGetFloat(itemObj, "loopStart", -1.0f); + item->audioSet.loopTo = cutsceneJsonGetFloat(itemObj, "loopTo", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h index c33ab7c8..4ae69c3d 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { audiomixerchannel_t channel; @@ -45,3 +43,19 @@ bool_t cutsceneAudioSetUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_SET cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c index 3a34a00d..74876402 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c @@ -7,6 +7,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneAudioSetLoopStart( const cutsceneitem_t *item, @@ -27,3 +29,26 @@ bool_t cutsceneAudioSetLoopUpdate( ) { return true; } + +errorret_t cutsceneAudioSetLoopLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + audiomixerchannel_t channelValue; + if(!cutsceneJsonLookupAudioChannel( + yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue + )) { + errorThrow("Cutscene item (AUDIO_SET_LOOP): invalid 'channel'"); + } + item->audioSetLoop.channel = channelValue; + item->audioSetLoop.looping = cutsceneJsonGetBool(itemObj, "looping", true); + item->audioSetLoop.loopCount = + (uint8_t)cutsceneJsonGetInt(itemObj, "loopCount", 0); + item->audioSetLoop.loopStart = + cutsceneJsonGetFloat(itemObj, "loopStart", -1.0f); + item->audioSetLoop.loopTo = cutsceneJsonGetFloat(itemObj, "loopTo", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h index 4ef2fd1b..518bb9da 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { audiomixerchannel_t channel; @@ -42,3 +40,19 @@ bool_t cutsceneAudioSetLoopUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_SET_LOOP cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c index ef930851..02270a96 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c @@ -7,6 +7,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneAudioSetPanStart( const cutsceneitem_t *item, @@ -21,3 +23,21 @@ bool_t cutsceneAudioSetPanUpdate( ) { return true; } + +errorret_t cutsceneAudioSetPanLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + audiomixerchannel_t channelValue; + if(!cutsceneJsonLookupAudioChannel( + yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue + )) { + errorThrow("Cutscene item (AUDIO_SET_PAN): invalid 'channel'"); + } + item->audioSetPan.channel = channelValue; + item->audioSetPan.pan = cutsceneJsonGetFloat(itemObj, "pan", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h index 227af26d..4599c415 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { audiomixerchannel_t channel; @@ -39,3 +37,19 @@ bool_t cutsceneAudioSetPanUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_SET_PAN cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c index d34a488f..76cf0cde 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "audio/mixer/audiomixer.h" +#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" void cutsceneAudioStopStart( const cutsceneitem_t *item, @@ -21,3 +22,12 @@ bool_t cutsceneAudioStopUpdate( ) { return true; } + +errorret_t cutsceneAudioStopLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioStopChannel); +} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h index fda1a329..ff366870 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h @@ -7,9 +7,7 @@ #pragma once #include "audio/mixer/audiomixerchannel.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Starts an audio stop step (stops the channel immediately). @@ -33,3 +31,19 @@ bool_t cutsceneAudioStopUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an AUDIO_STOP cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.c index c902430d..ceee7c32 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.c +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.c @@ -23,3 +23,17 @@ bool_t cutsceneBattleForceActionUpdate( ) { return true; } + +errorret_t cutsceneBattleForceActionLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + item->battleForceAction.fighterIndex = + (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "fighterIndex")); + item->battleForceAction.targetIndex = + (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "targetIndex")); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h index 6dcef3e2..52bebc95 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h @@ -7,15 +7,13 @@ #pragma once #include "rpg/battle/battle.h" +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t fighterIndex; uint8_t targetIndex; } cutscenebattleforceaction_t; -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - /** * Starts a battle force-action step: immediately queues an attack for the * given fighter against the given target, bypassing normal player/AI @@ -40,3 +38,19 @@ bool_t cutsceneBattleForceActionUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a BATTLE_FORCE_ACTION cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.c index 20ca60bf..acc098c5 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.c +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.c @@ -6,6 +6,7 @@ */ #include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/item/json/cutscenejsonbattlestate.h" bool_t cutsceneBattleWaitStateUpdate( const cutsceneitem_t *item, @@ -13,3 +14,20 @@ bool_t cutsceneBattleWaitStateUpdate( ) { return BATTLE.state == item->battleWaitState.state; } + +errorret_t cutsceneBattleWaitStateLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + battlestate_t stateValue; + if(!cutsceneJsonLookupBattleState( + yyjson_get_str(yyjson_obj_get(itemObj, "state")), &stateValue + )) { + errorThrow("Cutscene item (BATTLE_WAIT_STATE): invalid 'state'"); + } + item->battleWaitState.state = stateValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h index a780c5cc..e4b9d837 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h @@ -7,14 +7,12 @@ #pragma once #include "rpg/battle/battle.h" +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { battlestate_t state; } cutscenebattlewaitstate_t; -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - /** * Updates a battle wait-state step, completing once BATTLE.state reaches * the watched state. Has no Start callback -- there's nothing to do until @@ -28,3 +26,19 @@ bool_t cutsceneBattleWaitStateUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a BATTLE_WAIT_STATE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.c b/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.c index 8d8aab55..743a7cb6 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.c +++ b/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.c @@ -8,6 +8,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/battle/party.h" #include "scene/scene.h" +#include "rpg/cutscene/item/json/cutscenejsonbattleencounter.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneStartBattleStart( const cutsceneitem_t *item, @@ -69,3 +71,63 @@ bool_t cutsceneStartBattleUpdate( battleDispose(); return true; } + +errorret_t cutsceneStartBattleLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + battleencountertype_t encounterValue; + if(!cutsceneJsonLookupBattleEncounter( + yyjson_get_str(yyjson_obj_get(itemObj, "encounterType")), &encounterValue + )) { + errorThrow("Cutscene item (START_BATTLE): invalid 'encounterType'"); + } + item->startBattle.encounterType = encounterValue; + + item->startBattle.fleeAvailable = + cutsceneJsonGetBool(itemObj, "fleeAvailable", true); + + yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies"); + size_t enemyCount = yyjson_arr_size(enemies); + if(!yyjson_is_arr(enemies) || enemyCount == 0 || + enemyCount > CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX) { + errorThrow( + "Cutscene item (START_BATTLE): 'enemies' must have 1-%u entries", + (uint32_t)CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX + ); + } + item->startBattle.enemyCount = (uint8_t)enemyCount; + + yyjson_arr_iter iter = yyjson_arr_iter_with(enemies); + yyjson_val *enemyVal; + size_t e = 0; + while((enemyVal = yyjson_arr_iter_next(&iter)) != NULL) { + yyjson_val *stats = yyjson_obj_get(enemyVal, "stats"); + if(!yyjson_is_obj(stats)) { + errorThrow( + "Cutscene item (START_BATTLE): enemies[%u] is missing 'stats'", + (uint32_t)e + ); + } + cutscenestartbattleenemy_t *enemy = &item->startBattle.enemies[e]; + enemy->stats.attack = + (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "attack")); + enemy->stats.defense = + (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "defense")); + enemy->stats.magic = + (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "magic")); + enemy->stats.speed = + (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "speed")); + enemy->stats.luck = + (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "luck")); + enemy->healthMax = + (uint16_t)yyjson_get_int(yyjson_obj_get(enemyVal, "healthMax")); + enemy->mpMax = + (uint16_t)yyjson_get_int(yyjson_obj_get(enemyVal, "mpMax")); + e++; + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.h b/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.h index 770d1690..341a7d06 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.h +++ b/src/dusk/rpg/cutscene/item/battle/cutscenestartbattle.h @@ -7,6 +7,7 @@ #pragma once #include "rpg/battle/battle.h" +#include "rpg/cutscene/item/cutsceneitembase.h" #define CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX 4 @@ -16,9 +17,6 @@ typedef struct { uint16_t mpMax; } cutscenestartbattleenemy_t; -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - typedef struct { battleencountertype_t encounterType; bool_t fleeAvailable; @@ -51,3 +49,19 @@ bool_t cutsceneStartBattleUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a START_BATTLE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h b/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h index c61b8d2e..71b0ba1e 100644 --- a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h +++ b/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h @@ -6,12 +6,10 @@ */ #pragma once +#include "rpg/cutscene/item/cutsceneitembase.h" #include "cutscenewait.h" #include "rpg/cutscene/item/entity/cutsceneentitywalkto.h" -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - /** Maximum number of items that may run inside a CUTSCENE_CONCURRENT. */ #define CUTSCENE_CONCURRENT_MAX 8 diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneidle.h b/src/dusk/rpg/cutscene/item/control/cutsceneidle.h index 2249fce4..d819084e 100644 --- a/src/dusk/rpg/cutscene/item/control/cutsceneidle.h +++ b/src/dusk/rpg/cutscene/item/control/cutsceneidle.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Updates an idle item. Never completes on its own - the cutscene blocks @@ -24,3 +21,6 @@ bool_t cutsceneIdleUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +// IDLE has no fields of its own to parse - see cutsceneItemLoadNoop +// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.c b/src/dusk/rpg/cutscene/item/control/cutscenemarker.c index 79114d70..fddc305f 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.c +++ b/src/dusk/rpg/cutscene/item/control/cutscenemarker.c @@ -19,3 +19,18 @@ bool_t cutsceneMarkerUpdate( ) { return true; } + +errorret_t cutsceneMarkerLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name")); + if(name == NULL) { + errorThrow("Cutscene item (MARKER): missing 'name'"); + } + item->marker.name = name; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h index b8b033b4..3cb8faf5 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { const char_t *name; @@ -38,3 +35,19 @@ bool_t cutsceneMarkerUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a MARKER cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/control/cutscenerestart.h b/src/dusk/rpg/cutscene/item/control/cutscenerestart.h index 34957a8d..a1e05dd4 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenerestart.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenerestart.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Starts a restart item (restarts the currently running cutscene from @@ -36,3 +33,6 @@ bool_t cutsceneRestartUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +// RESTART has no fields of its own to parse - see cutsceneItemLoadNoop +// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/control/cutscenescene.c b/src/dusk/rpg/cutscene/item/control/cutscenescene.c index 2d66b772..ce491fc2 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenescene.c +++ b/src/dusk/rpg/cutscene/item/control/cutscenescene.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "scene/scene.h" +#include "rpg/cutscene/item/json/cutscenejsonscenetype.h" void cutsceneSceneStart( const cutsceneitem_t *item, @@ -21,3 +22,20 @@ bool_t cutsceneSceneUpdate( ) { return true; } + +errorret_t cutsceneSceneLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + scenetype_t sceneValue; + if(!cutsceneJsonLookupSceneType( + yyjson_get_str(yyjson_obj_get(itemObj, "sceneType")), &sceneValue + )) { + errorThrow("Cutscene item (SCENE): invalid 'sceneType'"); + } + item->sceneChange.type = sceneValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenescene.h b/src/dusk/rpg/cutscene/item/control/cutscenescene.h index 2f659e42..bde0493d 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenescene.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenescene.h @@ -6,12 +6,9 @@ */ #pragma once -#include "dusk.h" +#include "rpg/cutscene/item/cutsceneitembase.h" #include "scene/scenetype.h" -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - typedef struct { scenetype_t type; } cutscenescene_t; @@ -42,3 +39,19 @@ bool_t cutsceneSceneUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a SCENE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c index 1a22b47a..e0b8db7b 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c +++ b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" +#include "rpg/cutscene/item/json/cutscenejsonpauseflags.h" void cutsceneSetPauseStart( const cutsceneitem_t *item, @@ -21,3 +22,22 @@ bool_t cutsceneSetPauseUpdate( ) { return true; } + +errorret_t cutsceneSetPauseLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + yyjson_val *flags = yyjson_obj_get(itemObj, "flags"); + cutscenepause_t value; + if(!cutsceneJsonParsePauseFlags(flags, &value)) { + errorThrow( + "Cutscene item (SET_PAUSE): 'flags' must be an array of valid pause " + "flags" + ); + } + item->setPause = value; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h index bb04cf6a..ad75a046 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h @@ -7,9 +7,7 @@ #pragma once #include "rpg/cutscene/cutscenepause.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** * Starts a set-pause item (applies the new pause flags immediately). @@ -33,3 +31,19 @@ bool_t cutsceneSetPauseUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a SET_PAUSE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/control/cutscenewait.c b/src/dusk/rpg/cutscene/item/control/cutscenewait.c index f85f1d2c..b44ecb35 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenewait.c +++ b/src/dusk/rpg/cutscene/item/control/cutscenewait.c @@ -6,6 +6,7 @@ */ #include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" #include "time/time.h" void cutsceneWaitStart( @@ -22,3 +23,14 @@ bool_t cutsceneWaitUpdate( data->wait -= TIME.delta; return data->wait <= 0; } + +errorret_t cutsceneWaitLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + item->wait = cutsceneJsonGetFloat(itemObj, "seconds", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenewait.h b/src/dusk/rpg/cutscene/item/control/cutscenewait.h index 985dca67..7ac03793 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenewait.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenewait.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef float_t cutscenewait_t; typedef float_t cutscenewaitdata_t; @@ -35,4 +32,20 @@ void cutsceneWaitStart( bool_t cutsceneWaitUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data +); + +/** + * Parses a WAIT cutscene item's JSON fields into item. + * + * @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 ); \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/item/cutscenecallback.h b/src/dusk/rpg/cutscene/item/cutscenecallback.h index ab65d68a..41029607 100644 --- a/src/dusk/rpg/cutscene/item/cutscenecallback.h +++ b/src/dusk/rpg/cutscene/item/cutscenecallback.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef void (*cutscenecallback_t)(void *userData); diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index 8a3f8cf5..2192e74a 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -6,23 +6,34 @@ */ #include "rpg/cutscene/cutscenesystem.h" +#include "rpg/cutscene/item/cutsceneitembase.h" +#include "asset/asset.h" +#include "asset/assetfile.h" +#include "asset/loader/assetloader.h" +#include "util/string.h" cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { [CUTSCENE_ITEM_TYPE_NULL] = { 0 }, [CUTSCENE_ITEM_TYPE_TEXT] = { .init = cutsceneTextStart, - .update = cutsceneTextUpdate + .update = cutsceneTextUpdate, + .jsonName = "TEXT", + .load = cutsceneTextLoad }, [CUTSCENE_ITEM_TYPE_TEXT_MINI] = { .init = cutsceneTextMiniStart, - .update = cutsceneTextMiniUpdate + .update = cutsceneTextMiniUpdate, + .jsonName = "TEXT_MINI", + .load = cutsceneTextMiniLoad }, [CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = { .init = cutsceneTextMiniHideStart, - .update = cutsceneTextMiniHideUpdate + .update = cutsceneTextMiniHideUpdate, + .jsonName = "TEXT_MINI_HIDE", + .load = cutsceneTextMiniHideLoad }, [CUTSCENE_ITEM_TYPE_CALLBACK] = { @@ -32,32 +43,45 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { [CUTSCENE_ITEM_TYPE_WAIT] = { .init = cutsceneWaitStart, - .update = cutsceneWaitUpdate + .update = cutsceneWaitUpdate, + .jsonName = "WAIT", + .load = cutsceneWaitLoad }, [CUTSCENE_ITEM_TYPE_CUTSCENE] = { .init = cutsceneCutsceneStart, - .update = cutsceneCutsceneUpdate + .update = cutsceneCutsceneUpdate, + .jsonName = "CUTSCENE", + .load = cutsceneCutsceneLoad }, [CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = { .init = cutsceneEntityTeleportStart, - .update = cutsceneEntityTeleportUpdate + .update = cutsceneEntityTeleportUpdate, + .jsonName = "ENTITY_TELEPORT", + .load = cutsceneEntityTeleportLoad }, [CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = { .init = cutsceneEntityWalkToStart, - .update = cutsceneEntityWalkToUpdate + .update = cutsceneEntityWalkToUpdate, + .jsonName = "ENTITY_WALK_TO", + .load = cutsceneEntityWalkToLoad, + .poolSize = cutsceneEntityWalkToPoolSize }, [CUTSCENE_ITEM_TYPE_FADE] = { .init = cutsceneFadeStart, - .update = cutsceneFadeUpdate + .update = cutsceneFadeUpdate, + .jsonName = "FADE", + .load = cutsceneFadeLoad }, [CUTSCENE_ITEM_TYPE_SET_PAUSE] = { .init = cutsceneSetPauseStart, - .update = cutsceneSetPauseUpdate + .update = cutsceneSetPauseUpdate, + .jsonName = "SET_PAUSE", + .load = cutsceneSetPauseLoad }, [CUTSCENE_ITEM_TYPE_CONCURRENT] = { @@ -67,27 +91,37 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { [CUTSCENE_ITEM_TYPE_ITEM_GIVE] = { .init = cutsceneItemGiveStart, - .update = cutsceneItemGiveUpdate + .update = cutsceneItemGiveUpdate, + .jsonName = "ITEM_GIVE", + .load = cutsceneItemGiveLoad }, [CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = { .init = cutsceneEntityRemoveStart, - .update = cutsceneEntityRemoveUpdate + .update = cutsceneEntityRemoveUpdate, + .jsonName = "ENTITY_REMOVE", + .load = cutsceneEntityRemoveLoad }, [CUTSCENE_ITEM_TYPE_ENTITY_ADD] = { .init = cutsceneEntityAddStart, - .update = cutsceneEntityAddUpdate + .update = cutsceneEntityAddUpdate, + .jsonName = "ENTITY_ADD", + .load = cutsceneEntityAddLoad }, [CUTSCENE_ITEM_TYPE_ENTITY_TURN] = { .init = cutsceneEntityTurnStart, - .update = cutsceneEntityTurnUpdate + .update = cutsceneEntityTurnUpdate, + .jsonName = "ENTITY_TURN", + .load = cutsceneEntityTurnLoad }, [CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = { .init = cutsceneEntityWalkToEntityStart, - .update = cutsceneEntityWalkToEntityUpdate + .update = cutsceneEntityWalkToEntityUpdate, + .jsonName = "ENTITY_WALK_TO_ENTITY", + .load = cutsceneEntityWalkToEntityLoad }, [CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = { @@ -97,81 +131,114 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { [CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = { .init = cutsceneMapAreaRemoveStart, - .update = cutsceneMapAreaRemoveUpdate + .update = cutsceneMapAreaRemoveUpdate, + .jsonName = "MAP_AREA_REMOVE", + .load = cutsceneMapAreaRemoveLoad }, [CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = { .init = cutsceneMapAreaWaitStart, - .update = cutsceneMapAreaWaitUpdate + .update = cutsceneMapAreaWaitUpdate, + .jsonName = "MAP_AREA_WAIT", + .load = cutsceneMapAreaWaitLoad, + .poolSize = cutsceneMapAreaWaitPoolSize }, [CUTSCENE_ITEM_TYPE_START_BATTLE] = { .init = cutsceneStartBattleStart, - .update = cutsceneStartBattleUpdate + .update = cutsceneStartBattleUpdate, + .jsonName = "START_BATTLE", + .load = cutsceneStartBattleLoad }, [CUTSCENE_ITEM_TYPE_EMOJI] = { .init = cutsceneEmojiStart, - .update = cutsceneEmojiUpdate + .update = cutsceneEmojiUpdate, + .jsonName = "EMOJI", + .load = cutsceneEmojiLoad }, [CUTSCENE_ITEM_TYPE_SHAKE] = { .init = cutsceneShakeStart, - .update = cutsceneShakeUpdate + .update = cutsceneShakeUpdate, + .jsonName = "SHAKE", + .load = cutsceneShakeLoad }, [CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE] = { - .update = cutsceneBattleWaitStateUpdate + .update = cutsceneBattleWaitStateUpdate, + .jsonName = "BATTLE_WAIT_STATE", + .load = cutsceneBattleWaitStateLoad }, [CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION] = { .init = cutsceneBattleForceActionStart, - .update = cutsceneBattleForceActionUpdate + .update = cutsceneBattleForceActionUpdate, + .jsonName = "BATTLE_FORCE_ACTION", + .load = cutsceneBattleForceActionLoad }, [CUTSCENE_ITEM_TYPE_MODAL] = { .init = cutsceneModalStart, - .update = cutsceneModalUpdate + .update = cutsceneModalUpdate, + .jsonName = "MODAL", + .load = cutsceneModalLoad }, [CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS] = { .init = cutsceneModalOptionsMarkersStart, - .update = cutsceneModalOptionsMarkersUpdate + .update = cutsceneModalOptionsMarkersUpdate, + .jsonName = "MODAL_OPTIONS_MARKERS", + .load = cutsceneModalOptionsMarkersLoad }, [CUTSCENE_ITEM_TYPE_MODAL_CLOSE] = { .init = cutsceneModalCloseStart, - .update = cutsceneModalCloseUpdate + .update = cutsceneModalCloseUpdate, + .jsonName = "MODAL_CLOSE", + .load = cutsceneItemLoadNoop }, [CUTSCENE_ITEM_TYPE_PRINT] = { .init = cutscenePrintStart, - .update = cutscenePrintUpdate + .update = cutscenePrintUpdate, + .jsonName = "PRINT", + .load = cutscenePrintLoad }, [CUTSCENE_ITEM_TYPE_MARKER] = { .init = cutsceneMarkerStart, - .update = cutsceneMarkerUpdate + .update = cutsceneMarkerUpdate, + .jsonName = "MARKER", + .load = cutsceneMarkerLoad }, [CUTSCENE_ITEM_TYPE_RESTART] = { .init = cutsceneRestartStart, - .update = cutsceneRestartUpdate + .update = cutsceneRestartUpdate, + .jsonName = "RESTART", + .load = cutsceneItemLoadNoop }, [CUTSCENE_ITEM_TYPE_SCENE] = { .init = cutsceneSceneStart, - .update = cutsceneSceneUpdate + .update = cutsceneSceneUpdate, + .jsonName = "SCENE", + .load = cutsceneSceneLoad }, [CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = { .init = cutsceneSaveDeviceCheckStart, - .update = cutsceneSaveDeviceCheckUpdate + .update = cutsceneSaveDeviceCheckUpdate, + .jsonName = "SAVE_DEVICE_CHECK", + .load = cutsceneSaveDeviceCheckLoad }, [CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS] = { .init = cutsceneSaveLoadAllSlotsStart, - .update = cutsceneSaveLoadAllSlotsUpdate + .update = cutsceneSaveLoadAllSlotsUpdate, + .jsonName = "SAVE_LOAD_ALL_SLOTS", + .load = cutsceneSaveLoadAllSlotsLoad }, [CUTSCENE_ITEM_TYPE_KEYBOARD] = { @@ -181,58 +248,95 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { [CUTSCENE_ITEM_TYPE_AUDIO_PLAY] = { .init = cutsceneAudioPlayStart, - .update = cutsceneAudioPlayUpdate + .update = cutsceneAudioPlayUpdate, + .jsonName = "AUDIO_PLAY", + .load = cutsceneAudioPlayLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_STOP] = { .init = cutsceneAudioStopStart, - .update = cutsceneAudioStopUpdate + .update = cutsceneAudioStopUpdate, + .jsonName = "AUDIO_STOP", + .load = cutsceneAudioStopLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_PAUSE] = { .init = cutsceneAudioPauseStart, - .update = cutsceneAudioPauseUpdate + .update = cutsceneAudioPauseUpdate, + .jsonName = "AUDIO_PAUSE", + .load = cutsceneAudioPauseLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_RESUME] = { .init = cutsceneAudioResumeStart, - .update = cutsceneAudioResumeUpdate + .update = cutsceneAudioResumeUpdate, + .jsonName = "AUDIO_RESUME", + .load = cutsceneAudioResumeLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_FADE] = { .init = cutsceneAudioFadeStart, - .update = cutsceneAudioFadeUpdate + .update = cutsceneAudioFadeUpdate, + .jsonName = "AUDIO_FADE", + .load = cutsceneAudioFadeLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT] = { - .update = cutsceneAudioFadeWaitUpdate + .update = cutsceneAudioFadeWaitUpdate, + .jsonName = "AUDIO_FADE_WAIT", + .load = cutsceneAudioFadeWaitLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN] = { .init = cutsceneAudioSetPanStart, - .update = cutsceneAudioSetPanUpdate + .update = cutsceneAudioSetPanUpdate, + .jsonName = "AUDIO_SET_PAN", + .load = cutsceneAudioSetPanLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP] = { .init = cutsceneAudioSetLoopStart, - .update = cutsceneAudioSetLoopUpdate + .update = cutsceneAudioSetLoopUpdate, + .jsonName = "AUDIO_SET_LOOP", + .load = cutsceneAudioSetLoopLoad }, [CUTSCENE_ITEM_TYPE_AUDIO_SET] = { .init = cutsceneAudioSetStart, - .update = cutsceneAudioSetUpdate + .update = cutsceneAudioSetUpdate, + .jsonName = "AUDIO_SET", + .load = cutsceneAudioSetLoad }, [CUTSCENE_ITEM_TYPE_IDLE] = { - .update = cutsceneIdleUpdate + .update = cutsceneIdleUpdate, + .jsonName = "IDLE", + .load = cutsceneItemLoadNoop }, [CUTSCENE_ITEM_TYPE_UI_SHOW] = { .init = cutsceneUIShowStart, - .update = cutsceneUIShowUpdate + .update = cutsceneUIShowUpdate, + .jsonName = "UI_SHOW", + .load = cutsceneUIShowLoad } }; +bool_t cutsceneItemLookupType( + const char_t *name, + cutsceneitemtype_t *outType +) { + if(name == NULL) return false; + for(size_t i = 0; i < CUTSCENE_ITEM_TYPE_COUNT; i++) { + const char_t *jsonName = CUTSCENE_ITEM_CALLBACKS[i].jsonName; + if(jsonName != NULL && stringEquals(jsonName, name)) { + *outType = (cutsceneitemtype_t)i; + return true; + } + } + return false; +} + void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { cutsceneiteminitcallback_t *init = CUTSCENE_ITEM_CALLBACKS[item->type].init; if(init != NULL) init(item, data); @@ -262,3 +366,35 @@ bool_t cutsceneCutsceneUpdate( ) { return false; } + +errorret_t cutsceneCutsceneLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name")); + if(name == NULL) { + errorThrow("Cutscene item (CUTSCENE): missing 'name'"); + } + + // Referenced cutscene is a sibling asset (assets/cutscenes/.jsonc), + // resolved to a stable pointer right now rather than deferred to + // runtime - assetRequireLoaded re-enters assetUpdate() safely from + // within this sync load (see its ASSET_ENTRY_STATE_LOADING_SYNC case), + // and the entry is locked forever (never unlocked) so the pointer + // stays valid for the process lifetime, same convention as + // ui/screen/mainmenu/uimainmenu.c's MAIN_MENU_CUTSCENE_ENTRY. + char_t path[ASSET_FILE_NAME_MAX]; + int32_t pathLen = stringFormat(NULL, 0, "cutscenes/%s.jsonc", name); + if(pathLen < 0 || (size_t)pathLen >= ASSET_FILE_NAME_MAX) { + errorThrow("Cutscene item (CUTSCENE): 'name' too long"); + } + stringFormat(path, sizeof(path), "cutscenes/%s.jsonc", name); + + assetentry_t *refEntry = assetLock(path, ASSET_LOADER_TYPE_CUTSCENE, NULL); + errorChain(assetRequireLoaded(refEntry)); + item->cutscene = &refEntry->data.cutscene.cutscene; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index 0b567a56..b93c66bd 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -6,6 +6,8 @@ */ #pragma once +#include "error/error.h" +#include "yyjson.h" #include "cutscenecallback.h" #include "cutsceneprint.h" #include "control/cutscenewait.h" @@ -170,9 +172,24 @@ typedef bool_t (cutsceneitemupdatecallback_t)( cutsceneitemdata_t *data ); +typedef errorret_t (cutsceneitemloadcallback_t)( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +); + +// 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 @@ -201,6 +218,19 @@ bool_t cutsceneItemUpdate( cutsceneitemdata_t *data ); +/** + * Looks up name (exact case) against every JSON-authorable item type's + * jsonName. + * + * @param name Name to search for. + * @param outType Set to the matching item type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneItemLookupType( + const char_t *name, + cutsceneitemtype_t *outType +); + /** * Starts a nested-cutscene item, handing control over to the * referenced cutscene. @@ -226,3 +256,24 @@ bool_t cutsceneCutsceneUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a CUTSCENE cutscene item's JSON fields into item. Kept inline here + * (rather than its own file) since it's tightly bound to the asset/loader + * system itself - it resolves its 'name' to a sibling + * assets/cutscenes/.jsonc asset at parse time, mirroring how + * cutsceneCutsceneStart/cutsceneCutsceneUpdate stay inline in + * cutsceneitem.c rather than getting their own file. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/cutsceneitembase.c b/src/dusk/rpg/cutscene/item/cutsceneitembase.c new file mode 100644 index 00000000..5412dd5a --- /dev/null +++ b/src/dusk/rpg/cutscene/item/cutsceneitembase.c @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutsceneitembase.h" + +errorret_t cutsceneItemLoadNoop( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/cutsceneitembase.h b/src/dusk/rpg/cutscene/item/cutsceneitembase.h new file mode 100644 index 00000000..2a1c6556 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/cutsceneitembase.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "error/error.h" +#include "yyjson.h" + +typedef struct cutsceneitem_s cutsceneitem_t; +typedef union cutsceneitemdata_u cutsceneitemdata_t; + +/** + * Shared no-op Load for item types with no JSON fields of their own to + * parse (IDLE, RESTART, MODAL_CLOSE) - always succeeds without touching + * item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/cutsceneprint.c b/src/dusk/rpg/cutscene/item/cutsceneprint.c index 9ccff491..98fc1594 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneprint.c +++ b/src/dusk/rpg/cutscene/item/cutsceneprint.c @@ -7,6 +7,7 @@ #include "cutsceneitem.h" #include "console/console.h" +#include "json/cutscenejsonfield.h" void cutscenePrintStart( const cutsceneitem_t *item, @@ -21,3 +22,18 @@ bool_t cutscenePrintUpdate( ) { return true; } + +errorret_t cutscenePrintLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonCopyString( + itemObj, "text", NULL, item->print.text, CUTSCENE_PRINT_MAX_CHARS + )) { + errorThrow("Cutscene item (PRINT): missing/too-long 'text'"); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/cutsceneprint.h b/src/dusk/rpg/cutscene/item/cutsceneprint.h index 01abf3fd..9eaef022 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneprint.h +++ b/src/dusk/rpg/cutscene/item/cutsceneprint.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" #define CUTSCENE_PRINT_MAX_CHARS 128 @@ -39,3 +36,19 @@ bool_t cutscenePrintUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a PRINT cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c index 8ee42067..d3c905a5 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c @@ -9,6 +9,8 @@ #include "rpg/cutscene/cutscenesystem.h" #include "rpg/entity/entity.h" #include "assert/assert.h" +#include "rpg/cutscene/item/json/cutscenejsonentitytype.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneEntityAddStart( const cutsceneitem_t *item, @@ -33,3 +35,25 @@ bool_t cutsceneEntityAddUpdate( ) { return true; } + +errorret_t cutsceneEntityAddLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + entitytype_t entityTypeValue; + if(!cutsceneJsonLookupEntityType( + yyjson_get_str(yyjson_obj_get(itemObj, "entityType")), &entityTypeValue + )) { + errorThrow("Cutscene item (ENTITY_ADD): invalid 'entityType'"); + } + item->entityAdd.entityType = entityTypeValue; + if(!cutsceneJsonReadWorldPos( + yyjson_obj_get(itemObj, "position"), &item->entityAdd.position + )) { + errorThrow("Cutscene item (ENTITY_ADD): 'position' must be [x, y, z]"); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h index 5e0944b1..4bece21c 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h @@ -8,9 +8,7 @@ #pragma once #include "rpg/entity/entitytype.h" #include "rpg/overworld/worldpos.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { entitytype_t entityType; @@ -39,3 +37,19 @@ bool_t cutsceneEntityAddUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ENTITY_ADD cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c index a358fb91..ba0cce85 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c @@ -8,6 +8,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "rpg/entity/entity.h" +#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" void cutsceneEntityRemoveStart( const cutsceneitem_t *item, @@ -23,3 +24,18 @@ bool_t cutsceneEntityRemoveUpdate( ) { return true; } + +errorret_t cutsceneEntityRemoveLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "entityIndex"), &item->entityRemove.entityIndex + )) { + errorThrow("Cutscene item (ENTITY_REMOVE): invalid 'entityIndex'"); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h index 2c7c4950..beb7187a 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t entityIndex; @@ -37,3 +34,19 @@ bool_t cutsceneEntityRemoveUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ENTITY_REMOVE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c index 789e9e37..c35e52f6 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c @@ -8,6 +8,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "rpg/entity/entity.h" +#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneEntityTeleportStart( const cutsceneitem_t *item, @@ -25,3 +27,26 @@ bool_t cutsceneEntityTeleportUpdate( ) { return true; } + +errorret_t cutsceneEntityTeleportLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "entityIndex"), + &item->entityTeleport.entityIndex + )) { + errorThrow("Cutscene item (ENTITY_TELEPORT): invalid 'entityIndex'"); + } + if(!cutsceneJsonReadWorldPos( + yyjson_obj_get(itemObj, "target"), &item->entityTeleport.target + )) { + errorThrow( + "Cutscene item (ENTITY_TELEPORT): 'target' must be [x, y, z]" + ); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h index 3237f89e..9d474cea 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h @@ -7,9 +7,7 @@ #pragma once #include "rpg/overworld/worldpos.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t entityIndex; @@ -38,3 +36,19 @@ bool_t cutsceneEntityTeleportUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ENTITY_TELEPORT cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c index 707389b4..37f412b8 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c @@ -8,6 +8,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "rpg/entity/entity.h" +#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" +#include "rpg/cutscene/item/json/cutscenejsonentitydir.h" void cutsceneEntityTurnStart( const cutsceneitem_t *item, @@ -28,3 +30,25 @@ bool_t cutsceneEntityTurnUpdate( entityTurn(entity, item->entityTurn.direction); return false; } + +errorret_t cutsceneEntityTurnLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "entityIndex"), &item->entityTurn.entityIndex + )) { + errorThrow("Cutscene item (ENTITY_TURN): invalid 'entityIndex'"); + } + entitydir_t dirValue; + if(!cutsceneJsonLookupEntityDir( + yyjson_get_str(yyjson_obj_get(itemObj, "direction")), &dirValue + )) { + errorThrow("Cutscene item (ENTITY_TURN): invalid 'direction'"); + } + item->entityTurn.direction = dirValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h index a14bb31b..4d7a6213 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h @@ -7,9 +7,7 @@ #pragma once #include "rpg/entity/entitydir.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t entityIndex; @@ -40,3 +38,19 @@ bool_t cutsceneEntityTurnUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ENTITY_TURN cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c index d629df15..3f5241e6 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c @@ -8,6 +8,9 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #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" void cutsceneEntityWalkToStart( const cutsceneitem_t *item, @@ -34,3 +37,53 @@ bool_t cutsceneEntityWalkToUpdate( } return true; } + +errorret_t cutsceneEntityWalkToLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "entityIndex"), &item->entityWalkTo.entityIndex + )) { + errorThrow("Cutscene item (ENTITY_WALK_TO): invalid 'entityIndex'"); + } + item->entityWalkTo.walkAround = + cutsceneJsonGetBool(itemObj, "walkAround", true); + + 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) { + errorThrow( + "Cutscene item (ENTITY_WALK_TO): 'positions' must be a non-empty " + "array of [x, y, z]" + ); + } + + *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])) { + errorThrow( + "Cutscene item (ENTITY_WALK_TO): position %u must be [x, y, z]", + (uint32_t)i + ); + } + 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); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h index ecf4677c..705eeb66 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h @@ -7,9 +7,7 @@ #pragma once #include "rpg/overworld/worldpos.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t entityIndex; @@ -44,3 +42,29 @@ bool_t cutsceneEntityWalkToUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ENTITY_WALK_TO cutscene item's JSON fields into item, writing + * its "positions" array into pool at *poolOffset and advancing it. + * + * @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 +); + +/** + * 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); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c index 2b695545..0adb0425 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c @@ -10,6 +10,7 @@ #include "rpg/entity/entity.h" #include "rpg/entity/entitypathstep.h" #include "rpg/overworld/map.h" +#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" void cutsceneEntityWalkToEntityStart( const cutsceneitem_t *item, @@ -39,3 +40,28 @@ bool_t cutsceneEntityWalkToEntityUpdate( return entityPathStep(entity, dest, true); } + +errorret_t cutsceneEntityWalkToEntityLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "entityIndex"), + &item->entityWalkToEntity.entityIndex + ) || !cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "targetEntityIndex"), + &item->entityWalkToEntity.targetEntityIndex + )) { + errorThrow( + "Cutscene item (ENTITY_WALK_TO_ENTITY): invalid entity index" + ); + } + item->entityWalkToEntity.offsetX = + (worldunit_t)yyjson_get_int(yyjson_obj_get(itemObj, "offsetX")); + item->entityWalkToEntity.offsetY = + (worldunit_t)yyjson_get_int(yyjson_obj_get(itemObj, "offsetY")); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h index 0014ed4b..2d19a4bb 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h @@ -7,9 +7,7 @@ #pragma once #include "rpg/overworld/worldpos.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t entityIndex; @@ -44,3 +42,19 @@ bool_t cutsceneEntityWalkToEntityUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ENTITY_WALK_TO_ENTITY cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c index 69c27136..ddb2d147 100644 --- a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c +++ b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c @@ -22,3 +22,17 @@ bool_t cutsceneItemGiveUpdate( ) { return !uiTextboxMainIsActive(); } + +errorret_t cutsceneItemGiveLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + item->itemGive.item = + (itemid_t)yyjson_get_int(yyjson_obj_get(itemObj, "item")); + item->itemGive.quantity = + (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "quantity")); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h index 12a53c01..538d69e2 100644 --- a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h +++ b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h @@ -7,9 +7,7 @@ #pragma once #include "rpg/item/item.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { itemid_t item; @@ -38,3 +36,19 @@ bool_t cutsceneItemGiveUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an ITEM_GIVE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/json/CMakeLists.txt b/src/dusk/rpg/cutscene/item/json/CMakeLists.txt new file mode 100644 index 00000000..d15a1d9e --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/CMakeLists.txt @@ -0,0 +1,23 @@ +# 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 + cutscenejsonenum.c + cutscenejsonfield.c + cutscenejsonentityindex.c + cutscenejsonareaid.c + cutscenejsonpool.c + cutscenejsonpauseflags.c + cutscenejsoneasing.c + cutscenejsonemoji.c + cutscenejsonentitytype.c + cutscenejsonentitydir.c + cutscenejsonbattleencounter.c + cutscenejsonbattlestate.c + cutscenejsonscenetype.c + cutscenejsonuiscreentype.c + cutscenejsonaudiochannel.c +) diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c new file mode 100644 index 00000000..36d1db38 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonareaid.h" +#include "util/string.h" + +bool_t cutsceneJsonGetAreaId(yyjson_val *val, uint8_t *outAreaId) { + if(yyjson_is_str(val)) { + const char_t *name = yyjson_get_str(val); + if(stringCompareInsensitive(name, "LAST_CREATED") == 0) { + *outAreaId = CUTSCENE_JSON_AREA_SENTINEL_LAST_CREATED; + return true; + } + return false; + } + if(!yyjson_is_int(val)) return false; + *outAreaId = (uint8_t)yyjson_get_int(val); + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h new file mode 100644 index 00000000..a7b3aa25 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "yyjson.h" + +// Sentinel accepted in place of a literal area id - see cutscenesystem.h's +// CUTSCENE_AREA_LAST_CREATED. Kept local since it's a JSON-authoring +// convenience, not part of the item struct definitions themselves. +#define CUTSCENE_JSON_AREA_SENTINEL_LAST_CREATED ((uint8_t)0xFF) + +/** + * Resolves a JSON value that's either the "LAST_CREATED" map area sentinel + * string or a plain integer area id. + * + * @param val JSON value to resolve (a string or a number). + * @param outAreaId Set to the resolved area id on success. + * @return true if val resolved to a valid area id, false otherwise. + */ +bool_t cutsceneJsonGetAreaId(yyjson_val *val, uint8_t *outAreaId); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c new file mode 100644 index 00000000..065a6245 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonaudiochannel.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_AUDIO_CHANNEL_TABLE[] = { + { "BGM_0", AUDIO_MIXER_CHANNEL_BGM_0 }, + { "VOICE_0", AUDIO_MIXER_CHANNEL_VOICE_0 }, + { "VOICE_1", AUDIO_MIXER_CHANNEL_VOICE_1 }, + { "SFX_0", AUDIO_MIXER_CHANNEL_SFX_0 }, + { "SFX_1", AUDIO_MIXER_CHANNEL_SFX_1 }, + { "SFX_2", AUDIO_MIXER_CHANNEL_SFX_2 }, + { "SFX_3", AUDIO_MIXER_CHANNEL_SFX_3 }, +}; +#define CUTSCENE_JSON_AUDIO_CHANNEL_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_AUDIO_CHANNEL_TABLE) / \ + sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupAudioChannel( + const char_t *name, + audiomixerchannel_t *out +) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_AUDIO_CHANNEL_TABLE, + CUTSCENE_JSON_AUDIO_CHANNEL_TABLE_COUNT, name, &value + )) { + return false; + } + *out = (audiomixerchannel_t)value; + return true; +} + +errorret_t cutsceneJsonLoadAudioChannelOnly( + yyjson_val *itemObj, + audiomixerchannel_t *outChannel +) { + if(!cutsceneJsonLookupAudioChannel( + yyjson_get_str(yyjson_obj_get(itemObj, "channel")), outChannel + )) { + errorThrow("Cutscene item: invalid 'channel'"); + } + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h new file mode 100644 index 00000000..c6e2234f --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "error/error.h" +#include "yyjson.h" +#include "audio/mixer/audiomixerchannel.h" + +/** + * Looks up name (case-insensitively) against the audio mixer channel + * table. + * + * @param name Name to search for. + * @param out Set to the matching channel on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupAudioChannel( + const char_t *name, + audiomixerchannel_t *out +); + +/** + * Reads itemObj's "channel" field via cutsceneJsonLookupAudioChannel, + * erroring if missing/invalid - shared by every channel-only item type + * (AUDIO_STOP/PAUSE/RESUME/FADE_WAIT), which differ only in which item + * union field they store the result in. + * + * @param itemObj The item's JSON object. + * @param outChannel Set to the parsed channel on success. + * @return Error code indicating success or failure of the parse. + */ +errorret_t cutsceneJsonLoadAudioChannelOnly( + yyjson_val *itemObj, + audiomixerchannel_t *outChannel +); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattleencounter.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattleencounter.c new file mode 100644 index 00000000..8cd28e63 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattleencounter.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonbattleencounter.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t +CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE[] = { + { "REGULAR", BATTLE_ENCOUNTER_REGULAR }, + { "PLAYER_ADVANTAGE", BATTLE_ENCOUNTER_PLAYER_ADVANTAGE }, + { "BACK_ATTACK", BATTLE_ENCOUNTER_BACK_ATTACK }, +}; +#define CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE) / \ + sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupBattleEncounter( + const char_t *name, + battleencountertype_t *out +) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE, + CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE_COUNT, name, &value + )) { + return false; + } + *out = (battleencountertype_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattleencounter.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattleencounter.h new file mode 100644 index 00000000..20e8f76e --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattleencounter.h @@ -0,0 +1,23 @@ +/** + * 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/battle.h" + +/** + * Looks up name (case-insensitively) against the battle encounter type + * table. + * + * @param name Name to search for. + * @param out Set to the matching encounter type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupBattleEncounter( + const char_t *name, + battleencountertype_t *out +); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c new file mode 100644 index 00000000..1e9762aa --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonbattlestate.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_STATE_TABLE[] = { + { "NONE", BATTLE_STATE_NONE }, + { "OPENING", BATTLE_STATE_OPENING }, + { "PRE_ROUND", BATTLE_STATE_PRE_ROUND }, + { "PLAYER_SELECTION", BATTLE_STATE_PLAYER_SELECTION }, + { "AI_SELECTION", BATTLE_STATE_AI_SELECTION }, + { "MOVES_EXECUTING", BATTLE_STATE_MOVES_EXECUTING }, + { "POST_ROUND", BATTLE_STATE_POST_ROUND }, + { "ENDED", BATTLE_STATE_ENDED }, +}; +#define CUTSCENE_JSON_BATTLE_STATE_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_BATTLE_STATE_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupBattleState(const char_t *name, battlestate_t *out) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_BATTLE_STATE_TABLE, CUTSCENE_JSON_BATTLE_STATE_TABLE_COUNT, + name, &value + )) { + return false; + } + *out = (battlestate_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h new file mode 100644 index 00000000..1122430e --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h @@ -0,0 +1,19 @@ +/** + * 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/battle.h" + +/** + * Looks up name (case-insensitively) against the battle state table. + * + * @param name Name to search for. + * @param out Set to the matching battle state on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupBattleState(const char_t *name, battlestate_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c b/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c new file mode 100644 index 00000000..b70df9be --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsoneasing.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_EASING_TABLE[] = { + { "LINEAR", EASING_LINEAR }, + { "IN_SINE", EASING_IN_SINE }, + { "OUT_SINE", EASING_OUT_SINE }, + { "IN_OUT_SINE", EASING_IN_OUT_SINE }, + { "IN_QUAD", EASING_IN_QUAD }, + { "OUT_QUAD", EASING_OUT_QUAD }, + { "IN_OUT_QUAD", EASING_IN_OUT_QUAD }, + { "IN_CUBIC", EASING_IN_CUBIC }, + { "OUT_CUBIC", EASING_OUT_CUBIC }, + { "IN_OUT_CUBIC", EASING_IN_OUT_CUBIC }, + { "IN_QUART", EASING_IN_QUART }, + { "OUT_QUART", EASING_OUT_QUART }, + { "IN_OUT_QUART", EASING_IN_OUT_QUART }, + { "IN_BACK", EASING_IN_BACK }, + { "OUT_BACK", EASING_OUT_BACK }, + { "IN_OUT_BACK", EASING_IN_OUT_BACK }, +}; +#define CUTSCENE_JSON_EASING_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_EASING_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupEasing(const char_t *name, easingtype_t *out) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_EASING_TABLE, CUTSCENE_JSON_EASING_TABLE_COUNT, name, + &value + )) { + return false; + } + *out = (easingtype_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h b/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h new file mode 100644 index 00000000..26d282e5 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "animation/easing.h" + +/** + * Looks up name (case-insensitively) against the easing type table. + * + * @param name Name to search for. + * @param out Set to the matching easing type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupEasing(const char_t *name, easingtype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c new file mode 100644 index 00000000..ce878f41 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonemoji.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_UI_EMOJI_TABLE[] = { + { "NULL", UI_EMOJI_NULL }, + { "QUESTION_MARK", UI_EMOJI_QUESTION_MARK }, + { "EXCLAMATION_MARK", UI_EMOJI_EXCLAMATION_MARK }, +}; +#define CUTSCENE_JSON_UI_EMOJI_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_UI_EMOJI_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupEmoji(const char_t *name, uiemojitype_t *out) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_UI_EMOJI_TABLE, CUTSCENE_JSON_UI_EMOJI_TABLE_COUNT, name, + &value + )) { + return false; + } + *out = (uiemojitype_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h new file mode 100644 index 00000000..55b483c3 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "ui/rpg/uiemoji.h" + +/** + * Looks up name (case-insensitively) against the emoji type table. + * + * @param name Name to search for. + * @param out Set to the matching emoji type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupEmoji(const char_t *name, uiemojitype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c new file mode 100644 index 00000000..85ac7baa --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonentitydir.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_ENTITY_DIR_TABLE[] = { + { "NORTH", ENTITY_DIR_NORTH }, + { "EAST", ENTITY_DIR_EAST }, + { "SOUTH", ENTITY_DIR_SOUTH }, + { "WEST", ENTITY_DIR_WEST }, + { "UP", ENTITY_DIR_UP }, + { "RIGHT", ENTITY_DIR_RIGHT }, + { "DOWN", ENTITY_DIR_DOWN }, + { "LEFT", ENTITY_DIR_LEFT }, +}; +#define CUTSCENE_JSON_ENTITY_DIR_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_ENTITY_DIR_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupEntityDir(const char_t *name, entitydir_t *out) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_ENTITY_DIR_TABLE, CUTSCENE_JSON_ENTITY_DIR_TABLE_COUNT, + name, &value + )) { + return false; + } + *out = (entitydir_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h new file mode 100644 index 00000000..b3a3d226 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h @@ -0,0 +1,19 @@ +/** + * 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/entity/entitydir.h" + +/** + * Looks up name (case-insensitively) against the entity direction table. + * + * @param name Name to search for. + * @param out Set to the matching direction on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupEntityDir(const char_t *name, entitydir_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c new file mode 100644 index 00000000..9cc076bf --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonentityindex.h" +#include "util/string.h" + +bool_t cutsceneJsonGetEntityIndex(yyjson_val *val, uint8_t *outIndex) { + if(yyjson_is_str(val)) { + const char_t *name = yyjson_get_str(val); + if(stringCompareInsensitive(name, "INTERACT") == 0) { + *outIndex = CUTSCENE_JSON_ENTITY_SENTINEL_INTERACT; + return true; + } + if(stringCompareInsensitive(name, "INTERACTED") == 0) { + *outIndex = CUTSCENE_JSON_ENTITY_SENTINEL_INTERACTED; + return true; + } + return false; + } + if(!yyjson_is_int(val)) return false; + *outIndex = (uint8_t)yyjson_get_int(val); + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h new file mode 100644 index 00000000..e7740d63 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "yyjson.h" + +// Sentinels accepted in place of a literal entity index - see +// cutscenesystem.h's CUTSCENE_ENTITY_INTERACT/INTERACTED. Kept local +// since they're a JSON-authoring convenience, not part of the item +// struct definitions themselves. +#define CUTSCENE_JSON_ENTITY_SENTINEL_INTERACT ((uint8_t)0xFE) +#define CUTSCENE_JSON_ENTITY_SENTINEL_INTERACTED ((uint8_t)0xFD) + +/** + * Resolves a JSON value that's either an entity index sentinel string + * ("INTERACT"/"INTERACTED") or a plain integer entity index. + * + * @param val JSON value to resolve (a string or a number). + * @param outIndex Set to the resolved index on success. + * @return true if val resolved to a valid index, false otherwise. + */ +bool_t cutsceneJsonGetEntityIndex(yyjson_val *val, uint8_t *outIndex); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c new file mode 100644 index 00000000..ac2f9e13 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonentitytype.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_ENTITY_TYPE_TABLE[] = { + { "NULL", ENTITY_TYPE_NULL }, + { "PLAYER", ENTITY_TYPE_PLAYER }, + { "NPC", ENTITY_TYPE_NPC }, + { "ITEM", ENTITY_TYPE_ITEM }, +}; +#define CUTSCENE_JSON_ENTITY_TYPE_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_ENTITY_TYPE_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupEntityType(const char_t *name, entitytype_t *out) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_ENTITY_TYPE_TABLE, CUTSCENE_JSON_ENTITY_TYPE_TABLE_COUNT, + name, &value + )) { + return false; + } + *out = (entitytype_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h new file mode 100644 index 00000000..f08b45e8 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h @@ -0,0 +1,19 @@ +/** + * 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/entity/entitytype.h" + +/** + * Looks up name (case-insensitively) against the entity type table. + * + * @param name Name to search for. + * @param out Set to the matching entity type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupEntityType(const char_t *name, entitytype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c new file mode 100644 index 00000000..3c7a1d67 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonenum.h" +#include "util/string.h" + +bool_t cutsceneJsonLookupEnum( + const cutscenejsonenumentry_t *table, + const size_t tableCount, + const char_t *name, + int32_t *outValue +) { + if(name == NULL) return false; + for(size_t i = 0; i < tableCount; i++) { + if(stringEquals(table[i].name, name)) { + *outValue = table[i].value; + return true; + } + } + return false; +} + +bool_t cutsceneJsonLookupEnumInsensitive( + const cutscenejsonenumentry_t *table, + const size_t tableCount, + const char_t *name, + int32_t *outValue +) { + if(name == NULL) return false; + for(size_t i = 0; i < tableCount; i++) { + if(stringCompareInsensitive(table[i].name, name) == 0) { + *outValue = table[i].value; + return true; + } + } + return false; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h new file mode 100644 index 00000000..8130007d --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct { + const char_t *name; + int32_t value; +} cutscenejsonenumentry_t; + +/** + * Looks up name in a {name, value} table via stringEquals - exact case, + * used only for the top-level item "type" name (the one enum-ish field + * the original tool's ITEM_TYPE dict looked up without .upper()). + * + * @param table Table to search. + * @param tableCount Number of entries in table. + * @param name Name to search for. + * @param outValue Set to the matching entry's value on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupEnum( + const cutscenejsonenumentry_t *table, + const size_t tableCount, + const char_t *name, + int32_t *outValue +); + +/** + * Looks up name in a {name, value} table via stringCompareInsensitive - + * every other enum-ish field (pause flags, entity/scene/battle/audio + * enums, ...) is authored case-insensitively, matching the original + * tool's ENUM_TABLE[name.upper()] lookups. + * + * @param table Table to search. + * @param tableCount Number of entries in table. + * @param name Name to search for. + * @param outValue Set to the matching entry's value on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupEnumInsensitive( + const cutscenejsonenumentry_t *table, + const size_t tableCount, + const char_t *name, + int32_t *outValue +); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c new file mode 100644 index 00000000..25b78d86 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonfield.h" +#include "util/string.h" + +bool_t cutsceneJsonCopyString( + yyjson_val *obj, + const char_t *key, + const char_t *defaultValue, + char_t *dest, + const size_t destCapacity +) { + yyjson_val *val = yyjson_obj_get(obj, key); + const char_t *str = yyjson_get_str(val); + size_t len; + if(str == NULL) { + if(defaultValue == NULL) return false; + str = defaultValue; + len = (size_t)stringFormat(NULL, 0, "%s", str); + } else { + len = yyjson_get_len(val); + } + if(len >= destCapacity) return false; + stringCopy(dest, str, destCapacity); + return true; +} + +bool_t cutsceneJsonReadWorldPos(yyjson_val *arr, worldpos_t *out) { + if(!yyjson_is_arr(arr) || yyjson_arr_size(arr) != 3) return false; + yyjson_val *x = yyjson_arr_get(arr, 0); + yyjson_val *y = yyjson_arr_get(arr, 1); + yyjson_val *z = yyjson_arr_get(arr, 2); + if(!yyjson_is_int(x) || !yyjson_is_int(y) || !yyjson_is_int(z)) return false; + out->x = (worldunit_t)yyjson_get_int(x); + out->y = (worldunit_t)yyjson_get_int(y); + out->z = (worldunit_t)yyjson_get_int(z); + return true; +} + +int32_t cutsceneJsonGetInt( + yyjson_val *obj, + const char_t *key, + const int32_t defaultValue +) { + yyjson_val *val = yyjson_obj_get(obj, key); + return val == NULL ? defaultValue : (int32_t)yyjson_get_int(val); +} + +float_t cutsceneJsonGetFloat( + yyjson_val *obj, + const char_t *key, + const float_t defaultValue +) { + yyjson_val *val = yyjson_obj_get(obj, key); + return val == NULL ? defaultValue : (float_t)yyjson_get_num(val); +} + +bool_t cutsceneJsonGetBool( + yyjson_val *obj, + const char_t *key, + const bool_t defaultValue +) { + yyjson_val *val = yyjson_obj_get(obj, key); + return val == NULL ? defaultValue : yyjson_get_bool(val); +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h new file mode 100644 index 00000000..a73f07fe --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h @@ -0,0 +1,88 @@ +/** + * 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/overworld/worldpos.h" +#include "yyjson.h" + +/** + * Reads obj[key] as a string (falling back to defaultValue if the key is + * absent and defaultValue is non-NULL) into dest, a fixed-capacity + * char_t[destCapacity] item field. Fails rather than truncating/asserting + * if the string doesn't fit - this is untrusted, authored file content, + * not an in-memory invariant. + * + * @param obj The item's JSON object. + * @param key Field name to read. + * @param defaultValue Value to use if key is absent, or NULL to require it. + * @param dest Destination buffer. + * @param destCapacity Capacity of dest, including the null terminator. + * @return true on success, false if the field is missing (with no + * default) or exceeds destCapacity. + */ +bool_t cutsceneJsonCopyString( + yyjson_val *obj, + const char_t *key, + const char_t *defaultValue, + char_t *dest, + const size_t destCapacity +); + +/** + * Reads a 3-element JSON array of integers into a worldpos_t. + * + * @param arr JSON array value, expected to hold exactly 3 numbers. + * @param out Set to the decoded position on success. + * @return true if arr was a valid 3-element numeric array, false otherwise. + */ +bool_t cutsceneJsonReadWorldPos(yyjson_val *arr, worldpos_t *out); + +/** + * Reads obj[key] as an integer, falling back to defaultValue if the key + * is absent. + * + * @param obj The item's JSON object. + * @param key Field name to read. + * @param defaultValue Value to use if key is absent. + * @return The field's value, or defaultValue if absent. + */ +int32_t cutsceneJsonGetInt( + yyjson_val *obj, + const char_t *key, + const int32_t defaultValue +); + +/** + * Reads obj[key] as a float, falling back to defaultValue if the key is + * absent. + * + * @param obj The item's JSON object. + * @param key Field name to read. + * @param defaultValue Value to use if key is absent. + * @return The field's value, or defaultValue if absent. + */ +float_t cutsceneJsonGetFloat( + yyjson_val *obj, + const char_t *key, + const float_t defaultValue +); + +/** + * Reads obj[key] as a boolean, falling back to defaultValue if the key is + * absent. + * + * @param obj The item's JSON object. + * @param key Field name to read. + * @param defaultValue Value to use if key is absent. + * @return The field's value, or defaultValue if absent. + */ +bool_t cutsceneJsonGetBool( + yyjson_val *obj, + const char_t *key, + const bool_t defaultValue +); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c new file mode 100644 index 00000000..29eb1b54 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonpauseflags.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_PAUSE_FLAG_TABLE[] = { + { "NPC", CUTSCENE_PAUSE_NPC }, + { "PLAYER", CUTSCENE_PAUSE_PLAYER }, + { "WORLD", CUTSCENE_PAUSE_WORLD }, + { "BATTLE", CUTSCENE_PAUSE_BATTLE }, +}; +#define CUTSCENE_JSON_PAUSE_FLAG_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_PAUSE_FLAG_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonParsePauseFlags(yyjson_val *arr, cutscenepause_t *out) { + if(!yyjson_is_arr(arr)) return false; + + cutscenepause_t value = CUTSCENE_PAUSE_NONE; + yyjson_arr_iter iter = yyjson_arr_iter_with(arr); + yyjson_val *flagVal; + while((flagVal = yyjson_arr_iter_next(&iter)) != NULL) { + int32_t flagValue; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_PAUSE_FLAG_TABLE, CUTSCENE_JSON_PAUSE_FLAG_TABLE_COUNT, + yyjson_get_str(flagVal), &flagValue + )) { + return false; + } + value |= (cutscenepause_t)flagValue; + } + *out = value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h new file mode 100644 index 00000000..dafe95ea --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h @@ -0,0 +1,24 @@ +/** + * 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/cutscene/cutscenepause.h" +#include "yyjson.h" + +/** + * Parses a JSON array of pause flag name strings, OR-ing each into out. + * Shared by the top-level "pause" field (assetCutsceneLoaderSync) and the + * SET_PAUSE item's "flags" field - both authored as the same shape. + * + * @param arr JSON array of pause flag name strings. + * @param out Set to the OR'd-together flags on success (CUTSCENE_PAUSE_NONE + * if arr is empty). + * @return true on success, false if arr is not an array or contains an + * invalid flag name. + */ +bool_t cutsceneJsonParsePauseFlags(yyjson_val *arr, cutscenepause_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonpool.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonpool.c new file mode 100644 index 00000000..ca8928c4 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonpool.c @@ -0,0 +1,12 @@ +/** + * 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); +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonpool.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonpool.h new file mode 100644 index 00000000..39bae091 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonpool.h @@ -0,0 +1,19 @@ +/** + * 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); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c new file mode 100644 index 00000000..70a5616f --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonscenetype.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_SCENE_TYPE_TABLE[] = { + { "NULL", SCENE_TYPE_NULL }, + { "INITIAL", SCENE_TYPE_INITIAL }, + { "OVERWORLD", SCENE_TYPE_OVERWORLD }, + { "BATTLE", SCENE_TYPE_BATTLE }, +}; +#define CUTSCENE_JSON_SCENE_TYPE_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_SCENE_TYPE_TABLE) / sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupSceneType(const char_t *name, scenetype_t *out) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_SCENE_TYPE_TABLE, CUTSCENE_JSON_SCENE_TYPE_TABLE_COUNT, + name, &value + )) { + return false; + } + *out = (scenetype_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h new file mode 100644 index 00000000..58a38271 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "scene/scenetype.h" + +/** + * Looks up name (case-insensitively) against the scene type table. + * + * @param name Name to search for. + * @param out Set to the matching scene type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupSceneType(const char_t *name, scenetype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c new file mode 100644 index 00000000..4d53a349 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenejsonuiscreentype.h" +#include "cutscenejsonenum.h" + +static const cutscenejsonenumentry_t CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE[] = { + { "MAIN_MENU", UI_SCREEN_TYPE_MAIN_MENU }, +}; +#define CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE_COUNT \ + (sizeof(CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE) / \ + sizeof(cutscenejsonenumentry_t)) + +bool_t cutsceneJsonLookupUiScreenType( + const char_t *name, + uiscreentype_t *out +) { + int32_t value; + if(!cutsceneJsonLookupEnumInsensitive( + CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE, + CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE_COUNT, name, &value + )) { + return false; + } + *out = (uiscreentype_t)value; + return true; +} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h new file mode 100644 index 00000000..fd1f984d --- /dev/null +++ b/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h @@ -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/cutscene/item/ui/cutsceneuishow.h" + +/** + * Looks up name (case-insensitively) against the UI screen type table. + * + * @param name Name to search for. + * @param out Set to the matching screen type on success. + * @return true if a matching entry was found, false otherwise. + */ +bool_t cutsceneJsonLookupUiScreenType( + const char_t *name, + uiscreentype_t *out +); diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h index b1614335..14cb13db 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h @@ -6,11 +6,9 @@ */ #pragma once +#include "rpg/cutscene/item/cutsceneitembase.h" #include "rpg/overworld/maparea.h" -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - typedef struct { worldpos_t min; worldpos_t max; diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c index 58045ba9..84b0c77c 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c @@ -8,6 +8,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "rpg/overworld/maparea.h" +#include "rpg/cutscene/item/json/cutscenejsonareaid.h" void cutsceneMapAreaRemoveStart( const cutsceneitem_t *item, @@ -22,3 +23,18 @@ bool_t cutsceneMapAreaRemoveUpdate( ) { return true; } + +errorret_t cutsceneMapAreaRemoveLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetAreaId( + yyjson_obj_get(itemObj, "areaId"), &item->mapAreaRemove.areaId + )) { + errorThrow("Cutscene item (MAP_AREA_REMOVE): invalid 'areaId'"); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h index 3d1550dd..bca46048 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t areaId; @@ -38,3 +35,19 @@ bool_t cutsceneMapAreaRemoveUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a MAP_AREA_REMOVE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c index 7b0e3f2e..af94ab3c 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c @@ -9,6 +9,8 @@ #include "rpg/cutscene/cutscenesystem.h" #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, @@ -38,3 +40,46 @@ bool_t cutsceneMapAreaWaitUpdate( return false; } + +errorret_t cutsceneMapAreaWaitLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + yyjson_val *areaIds = yyjson_obj_get(itemObj, "areaIds"); + size_t count = yyjson_arr_size(areaIds); + if( + !yyjson_is_arr(areaIds) || count == 0 || + count > CUTSCENE_MAP_AREA_WAIT_MAX + ) { + errorThrow( + "Cutscene item (MAP_AREA_WAIT): 'areaIds' must have 1-%u entries", + (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])) { + 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); +} diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h index 039b4b76..e443fdcb 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" /** Maximum number of areas a single CUTSCENE_MAP_AREA_WAIT may watch. */ #define CUTSCENE_MAP_AREA_WAIT_MAX 4 @@ -47,3 +44,29 @@ bool_t cutsceneMapAreaWaitUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a MAP_AREA_WAIT cutscene item's JSON fields into item, writing its + * "areaIds" array into pool at *poolOffset and advancing it. + * + * @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 +); + +/** + * 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); diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c index 33bf87d9..4a4204ff 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c +++ b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c @@ -31,3 +31,25 @@ bool_t cutsceneSaveDeviceCheckUpdate( ) { return false; } + +errorret_t cutsceneSaveDeviceCheckLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + const char_t *successMarker = + yyjson_get_str(yyjson_obj_get(itemObj, "successMarker")); + const char_t *failureMarker = + yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker")); + if(successMarker == NULL || failureMarker == NULL) { + errorThrow( + "Cutscene item (SAVE_DEVICE_CHECK): needs 'successMarker'/" + "'failureMarker'" + ); + } + item->saveDeviceCheck.successMarker = successMarker; + item->saveDeviceCheck.failureMarker = failureMarker; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h index 17cfa901..895f5291 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h +++ b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { const char_t *successMarker; @@ -43,3 +40,19 @@ bool_t cutsceneSaveDeviceCheckUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a SAVE_DEVICE_CHECK cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c index 28958a3c..743f2b83 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c +++ b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c @@ -29,3 +29,25 @@ bool_t cutsceneSaveLoadAllSlotsUpdate( ) { return false; } + +errorret_t cutsceneSaveLoadAllSlotsLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + const char_t *successMarker = + yyjson_get_str(yyjson_obj_get(itemObj, "successMarker")); + const char_t *failureMarker = + yyjson_get_str(yyjson_obj_get(itemObj, "failureMarker")); + if(successMarker == NULL || failureMarker == NULL) { + errorThrow( + "Cutscene item (SAVE_LOAD_ALL_SLOTS): needs 'successMarker'/" + "'failureMarker'" + ); + } + item->saveLoadAllSlots.successMarker = successMarker; + item->saveLoadAllSlots.failureMarker = failureMarker; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h index 22f49243..1208c05d 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h +++ b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { const char_t *successMarker; @@ -42,3 +39,19 @@ bool_t cutsceneSaveLoadAllSlotsUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a SAVE_LOAD_ALL_SLOTS cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c index 2cd52bb7..3d41fe7f 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c @@ -8,6 +8,9 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "rpg/entity/entity.h" +#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" +#include "rpg/cutscene/item/json/cutscenejsonemoji.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneEmojiStart( const cutsceneitem_t *item, @@ -23,3 +26,26 @@ bool_t cutsceneEmojiUpdate( ) { return true; } + +errorret_t cutsceneEmojiLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonGetEntityIndex( + yyjson_obj_get(itemObj, "entityIndex"), &item->emoji.entityIndex + )) { + errorThrow("Cutscene item (EMOJI): invalid 'entityIndex'"); + } + item->emoji.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); + uiemojitype_t emojiValue; + if(!cutsceneJsonLookupEmoji( + yyjson_get_str(yyjson_obj_get(itemObj, "emojiType")), &emojiValue + )) { + errorThrow("Cutscene item (EMOJI): invalid 'emojiType'"); + } + item->emoji.emojiType = emojiValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h index 1c9deeca..de0cd558 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h @@ -6,12 +6,9 @@ */ #pragma once -#include "dusk.h" +#include "rpg/cutscene/item/cutsceneitembase.h" #include "ui/rpg/uiemoji.h" -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - typedef struct { uint8_t entityIndex; float_t duration; @@ -41,3 +38,19 @@ bool_t cutsceneEmojiUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses an EMOJI cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenefade.c b/src/dusk/rpg/cutscene/item/ui/cutscenefade.c index 8ffa4656..f52e4fe2 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenefade.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenefade.c @@ -7,6 +7,8 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "ui/overlay/uifullbox.h" +#include "rpg/cutscene/item/json/cutscenejsoneasing.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneFadeStart( const cutsceneitem_t *item, @@ -30,3 +32,37 @@ bool_t cutsceneFadeUpdate( UI_FULLBOX_OVER.time < UI_FULLBOX_OVER.duration ); } + +errorret_t cutsceneFadeLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + yyjson_val *from = yyjson_obj_get(itemObj, "from"); + yyjson_val *to = yyjson_obj_get(itemObj, "to"); + if(!yyjson_is_arr(from) || yyjson_arr_size(from) != 4 || + !yyjson_is_arr(to) || yyjson_arr_size(to) != 4) { + errorThrow("Cutscene item (FADE): 'from'/'to' must each be [r, g, b, a]"); + } + item->fade.from.r = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 0)); + item->fade.from.g = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 1)); + item->fade.from.b = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 2)); + item->fade.from.a = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 3)); + item->fade.to.r = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 0)); + item->fade.to.g = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 1)); + item->fade.to.b = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 2)); + item->fade.to.a = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 3)); + item->fade.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); + + const char_t *easingName = + yyjson_get_str(yyjson_obj_get(itemObj, "easing")); + easingtype_t easingValue = EASING_LINEAR; + if(easingName != NULL && + !cutsceneJsonLookupEasing(easingName, &easingValue)) { + errorThrow("Cutscene item (FADE): invalid 'easing'"); + } + item->fade.easing = easingValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenefade.h b/src/dusk/rpg/cutscene/item/ui/cutscenefade.h index c23b12f9..c80661c3 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenefade.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenefade.h @@ -8,9 +8,7 @@ #pragma once #include "display/color.h" #include "animation/easing.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { color_t from; @@ -41,3 +39,19 @@ bool_t cutsceneFadeUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a FADE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h index 6a7c7a98..ae97ebb2 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h @@ -6,12 +6,9 @@ */ #pragma once -#include "dusk.h" +#include "rpg/cutscene/item/cutsceneitembase.h" #include "ui/dialog/keyboard/uikeyboard.h" -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - typedef struct { // Passed straight through to uiKeyboardOpen - see uikeyboardopen_t. uikeyboardopen_t open; diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c index db73d060..4d2e5848 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c @@ -9,6 +9,7 @@ #include "rpg/cutscene/cutscenesystem.h" #include "assert/assert.h" #include "ui/widget/uimodal.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneModalStart( const cutsceneitem_t *item, @@ -51,3 +52,28 @@ bool_t cutsceneModalCloseUpdate( ) { return true; } + +errorret_t cutsceneModalLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(yyjson_obj_get(itemObj, "options") != NULL) { + errorThrow( + "Cutscene item (MODAL): options are not supported here - use " + "MODAL_OPTIONS_MARKERS instead" + ); + } + cutsceneJsonCopyString( + itemObj, "title", "", item->modal.title, CUTSCENE_MODAL_TITLE_MAX_CHARS + ); + if(!cutsceneJsonCopyString( + itemObj, "message", NULL, item->modal.message, + CUTSCENE_MODAL_MESSAGE_MAX_CHARS + )) { + errorThrow("Cutscene item (MODAL): missing/too-long 'message'"); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h index fd7207ef..761be491 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" #define CUTSCENE_MODAL_TITLE_MAX_CHARS 64 #define CUTSCENE_MODAL_MESSAGE_MAX_CHARS 256 @@ -82,6 +79,22 @@ bool_t cutsceneModalUpdate( cutsceneitemdata_t *data ); +/** + * Parses a MODAL cutscene item's JSON fields into item. + * + * @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 +); + /** * Starts a modal-close item (closes the currently open modal, if any). * Whatever should happen next belongs in the cutscene's own item @@ -107,3 +120,6 @@ bool_t cutsceneModalCloseUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +// MODAL_CLOSE has no fields of its own to parse - see cutsceneItemLoadNoop +// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c index 0611a15c..48e95cab 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c @@ -8,6 +8,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "ui/widget/uimodal.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneModalOptionsMarkersCallback( const uint8_t optionIndex, @@ -48,3 +49,54 @@ bool_t cutsceneModalOptionsMarkersUpdate( ) { return false; } + +errorret_t cutsceneModalOptionsMarkersLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + cutsceneJsonCopyString( + itemObj, "title", "", item->modalOptionsMarkers.title, + CUTSCENE_MODAL_TITLE_MAX_CHARS + ); + if(!cutsceneJsonCopyString( + itemObj, "message", NULL, item->modalOptionsMarkers.message, + CUTSCENE_MODAL_MESSAGE_MAX_CHARS + )) { + errorThrow( + "Cutscene item (MODAL_OPTIONS_MARKERS): missing/too-long 'message'" + ); + } + + yyjson_val *options = yyjson_obj_get(itemObj, "options"); + size_t optionCount = yyjson_arr_size(options); + if(!yyjson_is_arr(options) || optionCount == 0 || + optionCount > CUTSCENE_MODAL_OPTIONS_MARKERS_MAX) { + errorThrow( + "Cutscene item (MODAL_OPTIONS_MARKERS): 'options' must have 1-%u " + "entries", (uint32_t)CUTSCENE_MODAL_OPTIONS_MARKERS_MAX + ); + } + item->modalOptionsMarkers.optionCount = (uint8_t)optionCount; + + yyjson_arr_iter iter = yyjson_arr_iter_with(options); + yyjson_val *optionVal; + size_t o = 0; + while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) { + const char_t *text = yyjson_get_str(yyjson_obj_get(optionVal, "text")); + const char_t *marker = + yyjson_get_str(yyjson_obj_get(optionVal, "marker")); + if(text == NULL || marker == NULL) { + errorThrow( + "Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs 'text' " + "and 'marker'", (uint32_t)o + ); + } + item->modalOptionsMarkers.options[o] = text; + item->modalOptionsMarkers.markers[o] = marker; + o++; + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h index 15c60178..642dbf92 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" // Backs CUTSCENE_MODAL_OPTIONS_ONE and CUTSCENE_MODAL_OPTIONS_TWO, so two // is the most either macro ever needs. @@ -55,3 +52,19 @@ bool_t cutsceneModalOptionsMarkersUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a MODAL_OPTIONS_MARKERS cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c index eb1eca92..b5d570f3 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/rpgcamera.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneShakeStart( const cutsceneitem_t *item, @@ -21,3 +22,15 @@ bool_t cutsceneShakeUpdate( ) { return true; } + +errorret_t cutsceneShakeLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + item->shake.amount = (uint8_t)cutsceneJsonGetInt(itemObj, "amount", 0); + item->shake.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h index cc0c0f24..8e72a104 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t amount; @@ -39,3 +36,19 @@ bool_t cutsceneShakeUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a SHAKE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetext.c b/src/dusk/rpg/cutscene/item/ui/cutscenetext.c index 9ad60c75..aadd0d9e 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetext.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetext.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "ui/rpg/textbox/uitextboxmain.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneTextStart( const cutsceneitem_t *item, @@ -21,3 +22,18 @@ bool_t cutsceneTextUpdate( ) { return !uiTextboxMainIsActive(); } + +errorret_t cutsceneTextLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonCopyString( + itemObj, "text", NULL, item->text.text, CUTSCENE_TEXT_MAX_CHARS + )) { + errorThrow("Cutscene item (TEXT): missing/too-long 'text'"); + } + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetext.h b/src/dusk/rpg/cutscene/item/ui/cutscenetext.h index 61017806..a5a2abb0 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetext.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetext.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" #define CUTSCENE_TEXT_MAX_CHARS 256 @@ -38,4 +35,20 @@ void cutsceneTextStart( bool_t cutsceneTextUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data +); + +/** + * Parses a TEXT cutscene item's JSON fields into item. + * + * @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 ); \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c index f4d7f750..783d4499 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c @@ -8,6 +8,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/cutscenesystem.h" #include "ui/rpg/textbox/uitextboxminilist.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" void cutsceneTextMiniStart( const cutsceneitem_t *item, @@ -31,3 +32,29 @@ bool_t cutsceneTextMiniUpdate( ) { return true; } + +errorret_t cutsceneTextMiniLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + if(!cutsceneJsonCopyString( + itemObj, "text", NULL, item->textMini.text, CUTSCENE_TEXT_MINI_MAX_CHARS + )) { + errorThrow("Cutscene item (TEXT_MINI): missing/too-long 'text'"); + } + yyjson_val *position = yyjson_obj_get(itemObj, "position"); + if(!yyjson_is_arr(position) || yyjson_arr_size(position) != 3) { + errorThrow("Cutscene item (TEXT_MINI): 'position' must be [x, y, z]"); + } + item->textMini.position[0] = + (float_t)yyjson_get_num(yyjson_arr_get(position, 0)); + item->textMini.position[1] = + (float_t)yyjson_get_num(yyjson_arr_get(position, 1)); + item->textMini.position[2] = + (float_t)yyjson_get_num(yyjson_arr_get(position, 2)); + item->textMini.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h index fb2628e7..905d033b 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" #define CUTSCENE_TEXT_MINI_MAX_CHARS 128 @@ -42,3 +39,19 @@ bool_t cutsceneTextMiniUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a TEXT_MINI cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c index c02da2be..5041d026 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c @@ -23,3 +23,15 @@ bool_t cutsceneTextMiniHideUpdate( ) { return true; } + +errorret_t cutsceneTextMiniHideLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + item->textMiniHide.index = + (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "index")); + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h index 9dbd7f8e..6f063e8a 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" typedef struct { uint8_t index; @@ -37,3 +34,19 @@ bool_t cutsceneTextMiniHideUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a TEXT_MINI_HIDE cutscene item's JSON fields into item. + * + * @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 +); diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c index 6184e7b0..1cc8b74d 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c @@ -7,6 +7,7 @@ #include "rpg/cutscene/item/cutsceneitem.h" #include "ui/screen/mainmenu/uimainmenu.h" +#include "rpg/cutscene/item/json/cutscenejsonuiscreentype.h" void cutsceneUIShowStart( const cutsceneitem_t *item, @@ -25,3 +26,20 @@ bool_t cutsceneUIShowUpdate( ) { return true; } + +errorret_t cutsceneUIShowLoad( + yyjson_val *itemObj, + cutsceneitem_t *item, + uint8_t *pool, + size_t *poolOffset +) { + uiscreentype_t screenValue; + if(!cutsceneJsonLookupUiScreenType( + yyjson_get_str(yyjson_obj_get(itemObj, "name")), &screenValue + )) { + errorThrow("Cutscene item (UI_SHOW): invalid 'name'"); + } + item->uiShow.screen = screenValue; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h index 40f8eb5a..58eba6bc 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h @@ -6,10 +6,7 @@ */ #pragma once -#include "dusk.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; +#include "rpg/cutscene/item/cutsceneitembase.h" // Full-screen UI panels a cutscene can declaratively open via UI_SHOW - not // modals/dialogs (those already have their own dedicated item types, e.g. @@ -46,3 +43,19 @@ bool_t cutsceneUIShowUpdate( const cutsceneitem_t *item, cutsceneitemdata_t *data ); + +/** + * Parses a UI_SHOW cutscene item's JSON fields into item. + * + * @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 +);