diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po new file mode 100644 index 00000000..9f1242b0 --- /dev/null +++ b/assets/locale/en_US.po @@ -0,0 +1,289 @@ +msgid "" +msgstr "" +"Project-Id-Version: ExampleApp 1.0\n" +"Language: en\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n<7 ? 2 : 3));\n" + +# Initial Scene +msgid "initial.checking_save.title" +msgstr "Checking for save data" + +msgid "initial.checking_save.message" +msgstr "Please wait..." + +msgid "initial.no_device.title" +msgstr "No Save Device Found" + +msgid "initial.no_device.message" +msgstr "Could not find a save device, ensure it is connected and try again. You can continue, but progress will not be saved." + +msgid "initial.no_device.retry" +msgstr "Try again" + +msgid "initial.no_device.continue" +msgstr "Continue without saving" + + +# Main Menu Scene +msgid "main_menu.start_game" +msgstr "Start Game" + +msgid "main_menu.options" +msgstr "Options" + +msgid "main_menu.quit" +msgstr "Quit Game" + +msgid "main_menu.quit_confirm" +msgstr "Are you sure you want to quit?" + +msgid "main_menu.checking_save.title" +msgstr "Checking for save data" + +msgid "main_menu.checking_save.message" +msgstr "Please wait..." + +msgid "main_menu.no_device.title" +msgstr "No Save Device Found" + +msgid "main_menu.no_device.message" +msgstr "Could not find a save device, ensure it is connected and try again. You can continue, but progress will not be saved." + +msgid "main_menu.no_device.retry" +msgstr "Try again" + +msgid "main_menu.no_device.continue" +msgstr "Continue without saving" + +msgid "main_menu.save_load_error.title" +msgstr "Error" + +msgid "main_menu.save_load_error.message" +msgstr "Failed to load save data. Please try again." + +msgid "main_menu.save_load_error.retry" +msgstr "Try Again" + + +# Select Save Screen +msgid "ui.select_save.title" +msgstr "Select Save" + +msgid "ui.select_save.empty" +msgstr "Empty Slot" + +msgid "ui.select_save.corrupt" +msgstr "Corrupted Save" + +msgid "ui.select_save.corrupt_warning" +msgstr "This save file is corrupted and could not be loaded. Please contact support if this issue persists." + +msgid "ui.select_save.slot_format" +msgstr "%s Lv.%d %s" + +msgid "ui.select_save.delete_mode" +msgstr "Delete a Save" + +msgid "ui.select_save.delete_confirm" +msgstr "Are you sure you want to delete this save?" + +msgid "ui.select_save.name_title" +msgstr "Enter game save file name" + +msgid "ui.save_slot.number_format" +msgstr "Slot %d" + + + + + + + + + + + +#: ui/menu.c:10 +msgid "ui.title" +msgstr "" +"Welcome" + +msgid "save.linux.mkdirp_failed" +msgstr "Failed to create save directory, check the disk is not full or write-protected." + +#: src/dusk/ui/frame/settings/uisettings.c +msgid "ui.settings.tabs.general" +msgstr "General" + +#: src/dusk/ui/frame/settings/uisettings.c +msgid "ui.settings.tabs.input" +msgstr "Input" + +#: src/dusk/ui/frame/settings/uisettings.c +msgid "ui.settings.tabs.display" +msgstr "Display" + +#: src/dusk/ui/frame/settings/uisettings.c +msgid "ui.settings.tabs.audio" +msgstr "Audio" + +msgid "ui.settings.input.deadzone" +msgstr "Deadzone" + +#: src/dusk/ui/frame/settings/uisettingsgeneral.c +msgid "ui.settings.general.language" +msgstr "Language" + +msgid "ui.settings.general.language_detail" +msgstr "Takes effect after restarting the application." + +#: src/dusk/ui/frame/settings/uisettings.c +msgid "ui.settings.apply" +msgstr "Apply" + +#: src/dusk/ui/frame/uiconfirm.c +msgid "ui.confirm.discard_changes" +msgstr "Discard unsaved changes?" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.characters" +msgstr "Characters" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.items" +msgstr "Items" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.settings" +msgstr "Settings" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save" +msgstr "Save" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_success" +msgstr "Game saved." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_cancelled" +msgstr "Save cancelled." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_unavailable" +msgstr "Can't save - no save device found." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_temporary" +msgstr "This session is temporary - no save device was found, so saving is disabled." + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_create_confirm" +msgstr "No save data found. Create a new save?" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_failed_format" +msgstr "Save failed: %s" + +#: src/dusk/ui/frame/game/uigamemenu.c +msgid "ui.game_menu.save_check_failed_format" +msgstr "Can't save: %s" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.message" +msgstr "No save device found. You can continue, but\nprogress will not be saved." + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.retry" +msgstr "Retry" + +#: src/dusk/ui/frame/initial/uiinitialnocard.c +msgid "ui.initial.no_card.continue" +msgstr "Continue Anyway" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.message" +msgstr "No save data found. Create a new save?" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.yes" +msgstr "Yes" + +#: src/dusk/ui/frame/initial/uiinitialcreatesave.c +msgid "ui.initial.create_save.no" +msgstr "No" + +#: src/dusk/ui/frame/uiconfirm.c +msgid "ui.confirm.confirm" +msgstr "Confirm" + +#: src/dusk/ui/frame/uiconfirm.c +msgid "ui.confirm.cancel" +msgstr "Cancel" + +#: src/dusk/ui/frame/battle/uibattlemenu.c +msgid "ui.battle.menu.attack" +msgstr "Attack" + +#: src/dusk/ui/frame/battle/uibattlemenu.c +msgid "ui.battle.menu.flee" +msgstr "Flee" + +#: src/dusk/ui/frame/battle/uibattlemenu.c +msgid "ui.battle.menu.target_format" +msgstr "Enemy %u (%u/%u HP)" + +#: src/dusk/ui/frame/battle/uibattlehud.c +msgid "ui.battle.hud.hp_format" +msgstr "HP %u/%u" + +#: src/dusk/ui/frame/battle/uibattlehud.c +msgid "ui.battle.hud.mp_format" +msgstr "MP %u/%u" + +#: src/dusk/ui/frame/settings/uisettingsaudio.c +msgid "ui.settings.audio.placeholder" +msgstr "No audio settings yet" + +#: src/dusk/ui/frame/settings/uisettingsdisplay.c +msgid "ui.settings.display.placeholder" +msgstr "No display settings yet" + +#: src/dusk/ui/frame/settings/uisettingsinput.c +msgid "ui.settings.input.placeholder" +msgstr "No input settings yet" + +#: src/dusk/ui/frame/backpack/uibackpack.c +msgid "ui.backpack.category_format" +msgstr "Category %u" + +#: src/dusk/ui/overlay/uiloading.c +msgid "ui.loading.text" +msgstr "loading" + +#: src/dusk/ui/overlay/uiautosave.c +msgid "ui.autosave.saving" +msgstr "SAVING" + + +# +# Items +# +msgid "item.potion.name" +msgstr "Potion" + +msgid "item.potato.name" +msgstr "Potato" + +msgid "item.apple.name" +msgstr "Apple" + +# +# Moves +# +msgid "move.fire.name" +msgstr "Fire" + +msgid "move.slash.name" +msgstr "Slash" \ No newline at end of file diff --git a/src/dusk/asset/loader/CMakeLists.txt b/src/dusk/asset/loader/CMakeLists.txt index 21932838..5ef3dccf 100644 --- a/src/dusk/asset/loader/CMakeLists.txt +++ b/src/dusk/asset/loader/CMakeLists.txt @@ -15,7 +15,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} add_subdirectory(display) add_subdirectory(locale) add_subdirectory(json) -add_subdirectory(chunk) add_subdirectory(dmf) add_subdirectory(wav) add_subdirectory(mp3) \ No newline at end of file diff --git a/src/dusk/asset/loader/assetloader.c b/src/dusk/asset/loader/assetloader.c index ad808032..c66a5f28 100644 --- a/src/dusk/asset/loader/assetloader.c +++ b/src/dusk/asset/loader/assetloader.c @@ -46,12 +46,6 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = { .dispose = assetJsonDispose }, - [ASSET_LOADER_TYPE_CHUNK] = { - .loadSync = assetChunkLoaderSync, - .loadAsync = assetChunkLoaderAsync, - .dispose = assetChunkDispose - }, - [ASSET_LOADER_TYPE_WAV] = { .loadSync = assetWavLoaderSync, .loadAsync = assetWavLoaderAsync, diff --git a/src/dusk/asset/loader/assetloader.h b/src/dusk/asset/loader/assetloader.h index c18c108f..ae339546 100644 --- a/src/dusk/asset/loader/assetloader.h +++ b/src/dusk/asset/loader/assetloader.h @@ -12,7 +12,6 @@ #include "asset/loader/display/assettilesetloader.h" #include "asset/loader/locale/assetlocaleloader.h" #include "asset/loader/json/assetjsonloader.h" -#include "asset/loader/chunk/assetchunkloader.h" #include "asset/loader/wav/assetwavloader.h" #include "asset/loader/mp3/assetmp3loader.h" @@ -25,7 +24,6 @@ typedef enum { ASSET_LOADER_TYPE_TILESET, ASSET_LOADER_TYPE_LOCALE, ASSET_LOADER_TYPE_JSON, - ASSET_LOADER_TYPE_CHUNK, ASSET_LOADER_TYPE_WAV, ASSET_LOADER_TYPE_MP3, @@ -39,7 +37,6 @@ typedef union { assettilesetloaderloading_t tileset; assetlocaleloaderloading_t locale; assetjsonloaderloading_t json; - assetchunkloaderloading_t chunk; assetwavloaderloading_t wav; assetmp3loaderloading_t mp3; } assetloaderloading_t; @@ -51,7 +48,6 @@ typedef union { assettilesetoutput_t tileset; assetlocaleoutput_t locale; assetjsonoutput_t json; - assetchunkoutput_t chunk; assetwavoutput_t wav; assetmp3output_t mp3; } assetloaderoutput_t; @@ -61,7 +57,6 @@ typedef union { assettilesetloaderinput_t tileset; assetlocaleloaderinput_t locale; assetjsonloaderinput_t json; - assetchunkloaderinput_t chunk; assetwavloaderinput_t wav; assetmp3loaderinput_t mp3; } assetloaderinput_t; diff --git a/src/dusk/asset/loader/chunk/CMakeLists.txt b/src/dusk/asset/loader/chunk/CMakeLists.txt deleted file mode 100644 index 64e16290..00000000 --- a/src/dusk/asset/loader/chunk/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - assetchunkloader.c -) diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.c b/src/dusk/asset/loader/chunk/assetchunkloader.c deleted file mode 100644 index a2325027..00000000 --- a/src/dusk/asset/loader/chunk/assetchunkloader.c +++ /dev/null @@ -1,484 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "assetchunkloader.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "asset/asset.h" -#include "asset/loader/assetloading.h" -#include "asset/loader/assetentry.h" -#include "asset/loader/assetloader.h" -#include "asset/loader/json/assetjsonloader.h" -#include "display/color.h" -#include "display/texture/texture.h" -#include "rpg/overworld/worldpos.h" -#include "yyjson.h" - -#define ASSET_CHUNK_TERRAIN_TEXTURE "tiles.png" - -// Per-shape corner Z offsets (sw, se, ne, nw) as offsets from a tile's own -// base Z, ported 1:1 from tools/asset/chunk/__main__.py's _RAMP_CORNERS. -// TILE_SHAPE_NULL's row is never read - assetChunkTilesHaveTerrain/ -// assetChunkBuildTerrain skip those tiles outright. -static const float_t CHUNK_RAMP_CORNERS[TILE_SHAPE_COUNT][4] = { - [TILE_SHAPE_GROUND] = {0.0f, 0.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_NORTH] = {0.0f, 0.0f, 1.0f, 1.0f}, - [TILE_SHAPE_RAMP_SOUTH] = {1.0f, 1.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_EAST] = {0.0f, 1.0f, 1.0f, 0.0f}, - [TILE_SHAPE_RAMP_WEST] = {1.0f, 0.0f, 0.0f, 1.0f}, - [TILE_SHAPE_RAMP_NORTHEAST] = {0.0f, 0.0f, 1.0f, 0.0f}, - [TILE_SHAPE_RAMP_NORTHWEST] = {0.0f, 0.0f, 0.0f, 1.0f}, - [TILE_SHAPE_RAMP_SOUTHEAST] = {0.0f, 1.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_SOUTHWEST] = {1.0f, 0.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_NORTHEAST_INNER] = {0.0f, 1.0f, 1.0f, 1.0f}, - [TILE_SHAPE_RAMP_NORTHWEST_INNER] = {1.0f, 0.0f, 1.0f, 1.0f}, - [TILE_SHAPE_RAMP_SOUTHEAST_INNER] = {1.0f, 1.0f, 1.0f, 0.0f}, - [TILE_SHAPE_RAMP_SOUTHWEST_INNER] = {1.0f, 1.0f, 0.0f, 1.0f}, -}; - -errorret_t assetChunkLoaderAsync(assetloading_t *loading) { - assertNotNull(loading, "Loading cannot be NULL"); - assertNotMainThread("Should be called from an async thread."); - errorOk(); -} - -static bool_t assetChunkTilesHaveTerrain(const tile_t *tiles) { - for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) { - if(tiles[i].shape != TILE_SHAPE_NULL) return true; - } - return false; -} - -// Reads the "tiles" array - a dense, fixed-size CHUNK_TILE_COUNT grid, -// one slot per (x, y) column at index x + y * CHUNK_WIDTH (position is -// implicit in the index, not stored). Each slot is either `null` (empty -// column, already zeroed = TILE_SHAPE_NULL) or a 2-element [type, z] -// array. -static void assetChunkParseTiles(yyjson_val *root, tile_t *tiles) { - memoryZero(tiles, CHUNK_TILE_COUNT * sizeof(tile_t)); - - yyjson_val *tilesArr = yyjson_obj_get(root, "tiles"); - if(!yyjson_is_arr(tilesArr)) return; - - size_t idx, len; - yyjson_val *tileVal; - yyjson_arr_foreach(tilesArr, idx, len, tileVal) { - if(idx >= CHUNK_TILE_COUNT) break; - if(!yyjson_is_arr(tileVal) || yyjson_arr_size(tileVal) < 2) continue; - - int32_t type = (int32_t)yyjson_get_int(yyjson_arr_get(tileVal, 0)); - int32_t z = (int32_t)yyjson_get_int(yyjson_arr_get(tileVal, 1)); - tiles[idx] = (tile_t){ .shape = (tileshape_t)type, .z = (uint8_t)z }; - } -} - -// Resolves "meshes" (external building references, by bare filename) into -// out->modelNames/meshOffsets starting at *ioIndex, advancing it as -// entries are written. Filenames are resolved as -// "models/buildings/.json" - a hardcoded stand-in for -// tools/asset/chunk/__main__.py's find_model() recursive search, valid -// as long as every building model lives flat under that one directory -// (true today - see the raw-json-maps plan notes for the tradeoff). -static void assetChunkParseMeshes( - yyjson_val *root, assetchunkoutput_t *out, uint8_t *ioIndex -) { - yyjson_val *meshesArr = yyjson_obj_get(root, "meshes"); - if(!yyjson_is_arr(meshesArr)) return; - - size_t idx, len; - yyjson_val *meshVal; - yyjson_arr_foreach(meshesArr, idx, len, meshVal) { - if(*ioIndex >= CHUNK_MESH_COUNT_MAX) break; - - yyjson_val *fileVal = yyjson_obj_get(meshVal, "file"); - if(!fileVal || !yyjson_is_str(fileVal)) continue; - - char_t baseName[CHUNK_MESH_NAME_MAX]; - stringCopy(baseName, yyjson_get_str(fileVal), sizeof(baseName)); - char_t *dot = stringFindLastChar(baseName, '.'); - if(dot != NULL) *dot = '\0'; - - uint8_t m = *ioIndex; - stringFormat( - out->modelNames[m], CHUNK_MESH_NAME_MAX, - "models/buildings/%s.json", baseName - ); - - vec3 offset = {0.0f, 0.0f, 0.0f}; - yyjson_val *posArr = yyjson_obj_get(meshVal, "pos"); - if(yyjson_is_arr(posArr) && yyjson_arr_size(posArr) >= 3) { - offset[0] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 0)); - offset[1] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 1)); - offset[2] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 2)); - } - memoryCopy(out->meshOffsets[m], offset, sizeof(vec3)); - - (*ioIndex)++; - } -} - -static void assetChunkParseEntities(yyjson_val *root, assetchunkoutput_t *out) { - out->entitySpawnCount = 0; - - yyjson_val *arr = yyjson_obj_get(root, "entities"); - if(!yyjson_is_arr(arr)) return; - - size_t idx, len; - yyjson_val *val; - yyjson_arr_foreach(arr, idx, len, val) { - if(out->entitySpawnCount >= CHUNK_ENTITY_SPAWN_COUNT_MAX) break; - - const char_t *type = yyjson_get_str(yyjson_obj_get(val, "type")); - yyjson_val *posArr = yyjson_obj_get(val, "pos"); - if(type == NULL || !yyjson_is_arr(posArr) || yyjson_arr_size(posArr) < 3) { - continue; - } - worldpos_t pos = { - .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 0)), - .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 1)), - .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 2)), - }; - - chunkentityspawn_t *spawn = &out->entitySpawns[out->entitySpawnCount]; - if(stringEquals(type, "global")) { - spawn->kind = CHUNK_ENTITY_SPAWN_KIND_GLOBAL; - spawn->globalId = - (uint16_t)yyjson_get_int(yyjson_obj_get(val, "globalId")); - spawn->itemId = 0; - spawn->itemQuantity = 0; - } else if(stringEquals(type, "item")) { - spawn->kind = CHUNK_ENTITY_SPAWN_KIND_ITEM; - spawn->globalId = 0; - spawn->itemId = (uint16_t)yyjson_get_int(yyjson_obj_get(val, "itemId")); - spawn->itemQuantity = - (uint8_t)yyjson_get_int(yyjson_obj_get(val, "quantity")); - } else { - continue; - } - spawn->position = pos; - out->entitySpawnCount++; - } -} - -static void assetChunkParseAreas(yyjson_val *root, assetchunkoutput_t *out) { - out->areaSpawnCount = 0; - - yyjson_val *arr = yyjson_obj_get(root, "areas"); - if(!yyjson_is_arr(arr)) return; - - size_t idx, len; - yyjson_val *val; - yyjson_arr_foreach(arr, idx, len, val) { - if(out->areaSpawnCount >= CHUNK_AREA_COUNT_MAX) break; - - yyjson_val *minArr = yyjson_obj_get(val, "min"); - yyjson_val *maxArr = yyjson_obj_get(val, "max"); - if( - !yyjson_is_arr(minArr) || yyjson_arr_size(minArr) < 3 || - !yyjson_is_arr(maxArr) || yyjson_arr_size(maxArr) < 3 - ) continue; - - chunkareaspawn_t *area = &out->areaSpawns[out->areaSpawnCount]; - area->min = (worldpos_t){ - .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 0)), - .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 1)), - .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 2)), - }; - area->max = (worldpos_t){ - .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 0)), - .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 1)), - .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 2)), - }; - area->callbackId = - (uint16_t)yyjson_get_int(yyjson_obj_get(val, "callbackId")); - area->notify = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "notify")); - area->trigger = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "trigger")); - out->areaSpawnCount++; - } -} - -// Six vertices (2 CCW triangles) for one tile's quad, matching -// tools/asset/chunk/__main__.py's _tile_quad. Corner order: SW=(x,y), -// SE=(x+1,y), NE=(x+1,y+1), NW=(x,y+1). NORTH=+Y, EAST=+X. -static void assetChunkAppendTileQuad( - meshvertex_t *verts, uint32_t *vertCount, - const uint32_t x, const uint32_t y, - const float_t sw, const float_t se, const float_t ne, const float_t nw -) { - const float_t u0 = (float_t)x / (float_t)CHUNK_WIDTH; - const float_t u1 = (float_t)(x + 1) / (float_t)CHUNK_WIDTH; - const float_t v0 = (float_t)y / (float_t)CHUNK_HEIGHT; - const float_t v1 = (float_t)(y + 1) / (float_t)CHUNK_HEIGHT; - const float_t fx = (float_t)x; - const float_t fy = (float_t)y; - - meshvertex_t *v = verts + *vertCount; - v[0] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} }; - v[1] = (meshvertex_t){ .uv = {u1, v0}, .pos = {fx + 1, fy, se} }; - v[2] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} }; - v[3] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} }; - v[4] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} }; - v[5] = (meshvertex_t){ .uv = {u0, v1}, .pos = {fx, fy + 1, nw} }; - *vertCount += 6; -} - -// Builds a mesh_t straight from the tile grid - one quad per non-null -// tile column, shaped by CHUNK_RAMP_CORNERS - the runtime equivalent of -// tools/asset/chunk/__main__.py's build_terrain_verts. Only called once -// assetChunkTilesHaveTerrain has confirmed at least one real tile exists. -// Mirrors assetMeshLoaderSync's meshInit/meshFlush/free-after-upload -// sequence (assetmeshloader.c) since this produces the same -// assetmeshoutput_t shape, just from generated verts instead of a file. -static errorret_t assetChunkBuildTerrain( - const tile_t *tiles, assetmeshoutput_t *out -) { - meshvertex_t *verts = memoryAllocate( - CHUNK_TILE_COUNT * 6 * sizeof(meshvertex_t) - ); - uint32_t vertCount = 0; - - for(uint32_t y = 0; y < CHUNK_HEIGHT; y++) { - for(uint32_t x = 0; x < CHUNK_WIDTH; x++) { - const tile_t *tile = &tiles[x + y * CHUNK_WIDTH]; - if(tile->shape == TILE_SHAPE_NULL) continue; - - const float_t *corners = CHUNK_RAMP_CORNERS[tile->shape]; - const float_t fz = (float_t)tile->z; - assetChunkAppendTileQuad( - verts, &vertCount, x, y, - (fz + corners[0]) * WORLD_LAYER_HEIGHT, - (fz + corners[1]) * WORLD_LAYER_HEIGHT, - (fz + corners[2]) * WORLD_LAYER_HEIGHT, - (fz + corners[3]) * WORLD_LAYER_HEIGHT - ); - } - } - - assertTrue(vertCount > 0, "Terrain build called with no terrain tiles."); - - out->vertices = verts; - errorret_t ret = meshInit( - &out->mesh, MESH_PRIMITIVE_TYPE_TRIANGLES, (int32_t)vertCount, verts - ); - if(errorIsNotOk(ret)) { - memoryFree(out->vertices); - out->vertices = NULL; - errorChain(ret); - } - out->meshInitialized = true; - - ret = meshFlush(&out->mesh, 0, (int32_t)vertCount); - if(errorIsNotOk(ret)) { - meshDispose(&out->mesh); - out->meshInitialized = false; - memoryFree(out->vertices); - out->vertices = NULL; - errorChain(ret); - } - - #if defined(DUSK_OPENGL) && !defined(DUSK_OPENGL_LEGACY) - memoryFree(out->vertices); - out->vertices = NULL; - #endif - - errorOk(); -} - -errorret_t assetChunkLoaderSync(assetloading_t *loading) { - assertNotNull(loading, "Loading cannot be NULL"); - assertTrue(loading->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); - assertIsMainThread("Must be called from the main thread."); - - assetchunkoutput_t *out = &loading->entry->data.chunk; - - switch(loading->loading.chunk.state) { - case ASSET_CHUNK_LOADING_STATE_INITIAL: - loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE; - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); - - case ASSET_CHUNK_LOADING_STATE_PARSE: - break; - - case ASSET_CHUNK_LOADING_STATE_LOAD_MODELS: - while(loading->loading.chunk.modelIndex < out->meshCount) { - uint8_t m = loading->loading.chunk.modelIndex; - if(out->modelEntries[m] == NULL) { - out->modelEntries[m] = assetLock( - out->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL - ); - assertNotNull(out->modelEntries[m], "Failed to lock model."); - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); - } - if(out->modelEntries[m]->state == ASSET_ENTRY_STATE_ERROR) { - assetLoaderErrorThrow( - loading, "Model failed to load: %s", out->modelNames[m] - ); - } - if(out->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) { - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); - } - loading->loading.chunk.modelIndex++; - } - loading->entry->state = ASSET_ENTRY_STATE_LOADED; - errorOk(); - - default: - errorOk(); - } - - // --- PARSE: lock+require the chunk's own JSON as a sub-asset, exactly - // like assetmodelloader.c's ASSET_MODEL_LOADING_STATE_LOCK_ASSETS does - // for its model descriptor, then read everything out of it before - // unlocking (the yyjson_doc/vals only stay valid while jsonEntry does). - char_t jsonKey[ASSET_FILE_NAME_MAX]; - stringFormat(jsonKey, sizeof(jsonKey), "json:%s", loading->entry->name); - assetloaderinput_t jsonInput; - memoryZero(&jsonInput, sizeof(jsonInput)); - stringCopy( - jsonInput.json.path, loading->entry->name, ASSET_FILE_NAME_MAX - ); - assetentry_t *jsonEntry = - assetLock(jsonKey, ASSET_LOADER_TYPE_JSON, &jsonInput); - errorret_t jsonRet = assetRequireLoaded(jsonEntry); - if(errorIsNotOk(jsonRet)) { - assetUnlockEntry(jsonEntry); - assetLoaderErrorThrow( - loading, "Failed to load chunk JSON: %s", loading->entry->name - ); - } - - yyjson_val *root = yyjson_doc_get_root(jsonEntry->data.json); - - tile_t tiles[CHUNK_TILE_COUNT]; - assetChunkParseTiles(root, tiles); - out->hasTerrain = assetChunkTilesHaveTerrain(tiles); - - uint8_t meshIndex = out->hasTerrain ? 1 : 0; - assetChunkParseMeshes(root, out, &meshIndex); - out->meshCount = meshIndex; - - assetChunkParseEntities(root, out); - assetChunkParseAreas(root, out); - - assetUnlockEntry(jsonEntry); - - out->tiles = memoryAllocate(CHUNK_TILE_COUNT * sizeof(tile_t)); - memoryCopy(out->tiles, tiles, CHUNK_TILE_COUNT * sizeof(tile_t)); - - // Terrain: build the mesh, then wrap it in synthetic (non-cache, - // non-refcounted) mesh/model entries so modelEntries[0] behaves exactly - // like a real file-backed model entry for every downstream consumer - - // see the header's doc comment on hasTerrain/terrainMeshEntry. - if(out->hasTerrain) { - assetmeshoutput_t terrainMesh; - memoryZero(&terrainMesh, sizeof(terrainMesh)); - errorret_t terrainRet = assetChunkBuildTerrain(out->tiles, &terrainMesh); - if(errorIsNotOk(terrainRet)) { - memoryFree(out->tiles); - out->tiles = NULL; - assetLoaderErrorThrow( - loading, "Failed to build chunk terrain mesh: %s", - loading->entry->name - ); - } - - assetloaderinput_t texInput = { .texture = TEXTURE_FORMAT_RGBA }; - assetentry_t *texEntry = assetLock( - ASSET_CHUNK_TERRAIN_TEXTURE, ASSET_LOADER_TYPE_TEXTURE, &texInput - ); - errorret_t texRet = assetRequireLoaded(texEntry); - if(errorIsNotOk(texRet)) { - assetUnlockEntry(texEntry); - meshDispose(&terrainMesh.mesh); - memoryFree(out->tiles); - out->tiles = NULL; - assetLoaderErrorThrow( - loading, "Failed to load chunk terrain texture: %s", - loading->entry->name - ); - } - out->terrainTexEntry = texEntry; - - out->terrainMeshEntry = memoryAllocate(sizeof(assetentry_t)); - memoryZero(out->terrainMeshEntry, sizeof(assetentry_t)); - out->terrainMeshEntry->type = ASSET_LOADER_TYPE_MESH; - out->terrainMeshEntry->state = ASSET_ENTRY_STATE_LOADED; - out->terrainMeshEntry->data.mesh = terrainMesh; - - out->terrainModelEntry = memoryAllocate(sizeof(assetentry_t)); - memoryZero(out->terrainModelEntry, sizeof(assetentry_t)); - out->terrainModelEntry->type = ASSET_LOADER_TYPE_MODEL; - out->terrainModelEntry->state = ASSET_ENTRY_STATE_LOADED; - out->terrainModelEntry->data.model.meshEntry = out->terrainMeshEntry; - out->terrainModelEntry->data.model.texEntry = out->terrainTexEntry; - out->terrainModelEntry->data.model.color = COLOR_WHITE; - - out->modelEntries[0] = out->terrainModelEntry; - } - - if(out->meshCount == 0) { - loading->entry->state = ASSET_ENTRY_STATE_LOADED; - errorOk(); - } - - loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_LOAD_MODELS; - loading->loading.chunk.modelIndex = out->hasTerrain ? 1 : 0; - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); -} - -errorret_t assetChunkDispose(assetentry_t *entry) { - assertNotNull(entry, "Entry cannot be NULL"); - assertTrue(entry->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); - assertIsMainThread("Must be called from the main thread."); - - assetchunkoutput_t *out = &entry->data.chunk; - - if(out->tiles != NULL) { - memoryFree(out->tiles); - out->tiles = NULL; - } - - for(uint8_t m = 0; m < out->meshCount; m++) { - if(out->modelEntries[m] == NULL) continue; - if(out->hasTerrain && m == 0) continue; // synthetic - freed below. - assetUnlockEntry(out->modelEntries[m]); - out->modelEntries[m] = NULL; - } - - if(out->hasTerrain) { - if(out->terrainMeshEntry != NULL) { - assetmeshoutput_t *meshOut = &out->terrainMeshEntry->data.mesh; - if(meshOut->meshInitialized) { - meshDispose(&meshOut->mesh); - meshOut->meshInitialized = false; - } - if(meshOut->vertices != NULL) { - memoryFree(meshOut->vertices); - meshOut->vertices = NULL; - } - memoryFree(out->terrainMeshEntry); - out->terrainMeshEntry = NULL; - } - if(out->terrainModelEntry != NULL) { - memoryFree(out->terrainModelEntry); - out->terrainModelEntry = NULL; - } - if(out->terrainTexEntry != NULL) { - assetUnlockEntry(out->terrainTexEntry); - out->terrainTexEntry = NULL; - } - out->modelEntries[0] = NULL; - out->hasTerrain = false; - } - - errorOk(); -} diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h deleted file mode 100644 index 38e76032..00000000 --- a/src/dusk/asset/loader/chunk/assetchunkloader.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "asset/assetfile.h" -#include "rpg/overworld/chunk.h" - -typedef struct assetloading_s assetloading_t; -typedef struct assetentry_s assetentry_t; - -typedef struct { - void *nothing; -} assetchunkloaderinput_t; - -typedef enum { - ASSET_CHUNK_LOADING_STATE_INITIAL, - ASSET_CHUNK_LOADING_STATE_PARSE, - ASSET_CHUNK_LOADING_STATE_LOAD_MODELS, - ASSET_CHUNK_LOADING_STATE_DONE -} assetchunkloadingstate_t; - -typedef struct { - assetchunkloadingstate_t state; - - // Index into assetchunkoutput_t.modelNames/modelEntries the external - // (non-terrain) model polling loop has reached - see - // ASSET_CHUNK_LOADING_STATE_LOAD_MODELS. - uint8_t modelIndex; -} assetchunkloaderloading_t; - -typedef enum { - CHUNK_ENTITY_SPAWN_KIND_GLOBAL, - CHUNK_ENTITY_SPAWN_KIND_ITEM -} chunkentityspawnkind_t; - -typedef struct { - chunkentityspawnkind_t kind; - uint16_t globalId; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_GLOBAL. - uint16_t itemId; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_ITEM. - uint8_t itemQuantity; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_ITEM. - worldpos_t position; -} chunkentityspawn_t; - -typedef struct { - worldpos_t min; - worldpos_t max; - uint16_t callbackId; // Index into MAP_AREA_CALLBACK_LIST. - uint8_t notify; - uint8_t trigger; -} chunkareaspawn_t; - -typedef struct { - tile_t *tiles; - uint8_t meshCount; - char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; - vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; - assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX]; - - // Terrain mesh generated at load time from the tile grid (no baked - // .dmf/.json - see assetChunkBuildTerrain in assetchunkloader.c). NULL - // (with hasTerrain false) for a chunk with no non-null tiles, in which - // case modelEntries[0] (if meshCount > 0) is the first external mesh - // instead - matches the old baked pipeline's "only write a terrain - // model if it has vertices" behavior. - // - // terrainMeshEntry/terrainModelEntry are heap-allocated, plain - // assetentry_t values that are never passed to assetLock/assetGetEntry/ - // assetUnlockEntry (only a real, name-keyed, ref-counted entry may be) - // - they exist purely so modelEntries[0] can point at something with - // the same ->state/->data.model/->data.mesh.mesh shape every other - // model/mesh entry has, so chunk.c/map.c/sceneoverworld.c don't need to - // know terrain isn't file-backed. Freed directly (not assetUnlockEntry) - // in assetChunkDispose. terrainTexEntry is a real, ref-counted texture - // entry (the shared "tiles.png") and must be assetUnlockEntry'd there. - bool_t hasTerrain; - assetentry_t *terrainMeshEntry; - assetentry_t *terrainModelEntry; - assetentry_t *terrainTexEntry; - - uint8_t entitySpawnCount; - chunkentityspawn_t entitySpawns[CHUNK_ENTITY_SPAWN_COUNT_MAX]; - - uint8_t areaSpawnCount; - chunkareaspawn_t areaSpawns[CHUNK_AREA_COUNT_MAX]; -} assetchunkoutput_t; - -/** - * Async loader stub for chunk assets. Chunk loading is performed entirely - * on the main thread (it delegates to the JSON asset loader and to - * meshInit/meshFlush, both main-thread-only); this callback is never - * reached. - * - * @param loading Loading information for the asset being loaded. - * @return Error code indicating success or failure of the load operation. - */ -errorret_t assetChunkLoaderAsync(assetloading_t *loading); - -/** - * Synchronous loader for chunk assets. Locks the chunk's JSON descriptor - * as a sub-asset, parses tiles/external mesh references/entity spawns/ - * area spawns, generates the terrain mesh directly from the tile grid, - * and locks/waits for every referenced model (and the terrain's shared - * tileset texture) to finish loading. - * - * @param loading Loading information for the asset being loaded. - * @return Error code indicating success or failure of the load operation. - */ -errorret_t assetChunkLoaderSync(assetloading_t *loading); - -/** - * Disposer for chunk assets. - * - * @param entry Asset entry containing the chunk data to dispose. - * @return Error code indicating success or failure of the dispose operation. - */ -errorret_t assetChunkDispose(assetentry_t *entry); diff --git a/src/dusk/locale/localemanager.c b/src/dusk/locale/localemanager.c index 878a7e53..b71b1990 100644 --- a/src/dusk/locale/localemanager.c +++ b/src/dusk/locale/localemanager.c @@ -9,7 +9,6 @@ #include "util/memory.h" #include "util/string.h" #include "assert/assert.h" -#include "ui/ui.h" #include "system/system.h" #include "console/console.h" @@ -39,9 +38,6 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale) { assetEntryLock(LOCALE.entry); errorChain(assetRequireLoaded(LOCALE.entry)); - // TODO : Trigger UI update. - errorChain(uiUpdateTranslations()); - errorOk(); } diff --git a/src/dusk/save/CMakeLists.txt b/src/dusk/save/CMakeLists.txt index e75182ea..3ed2c8b7 100644 --- a/src/dusk/save/CMakeLists.txt +++ b/src/dusk/save/CMakeLists.txt @@ -11,5 +11,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} ) # Subdirs -add_subdirectory(slot) add_subdirectory(settings) diff --git a/src/dusk/save/save.c b/src/dusk/save/save.c index 9c8ece44..5935f024 100644 --- a/src/dusk/save/save.c +++ b/src/dusk/save/save.c @@ -8,7 +8,6 @@ #include "save.h" #include "util/memory.h" #include "assert/assert.h" -#include "rpg/cutscene/cutscenesystem.h" #include "console/console.h" save_t SAVE; @@ -17,16 +16,9 @@ errorret_t saveInit() { memoryZero(&SAVE, sizeof(save_t)); SAVE.deviceCurrent = 0xFF;// No current device. - SAVE.slotCurrent = 0xFF;// No current slot. - // Initialize the slots and settings + // Initialize the settings saveSettingsInit(&SAVE.settings); - errorChain(saveSlotInit(&SAVE.slot)); - - // Update caches to match default data. - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - SAVE.caches[i] = SAVE.slot.cachedData; - } // Start by initializing each of the save devices. savedevice_t *device = &SAVE.devices[0]; @@ -67,10 +59,6 @@ errorret_t saveUpdate() { } else { // Device was found, cache in the data. errorChain(saveLoadSettings()); - errorChain(saveLoadAllSlots()); - - // Reset slot, implying no slot has been selected. - SAVE.slotCurrent = 0xFF; // TEST: write settings back out immediately once a device is // selected, to confirm the write path actually works on hardware. @@ -166,10 +154,6 @@ void saveOnDeviceAvailabilityChecked(savedevice_t *device, void *user) { SAVE.deviceCurrent = (uint8_t)(device - &SAVE.devices[0]); } -bool_t saveCanSave() { - return CUTSCENE_SYSTEM.scene == NULL; -} - errorret_t saveSaveSettings() { assertTrue(SAVE.deviceCurrent != 0xFF, "No current device"); @@ -204,69 +188,6 @@ errorret_t saveLoadSettings() { errorOk(); } -errorret_t saveSaveSlot() { - assertTrue(SAVE.deviceCurrent != 0xFF, "No current device"); - assertTrue(SAVE.slotCurrent < SAVE_SLOT_COUNT, "Invalid slot index"); - - errorChain(saveDeviceSlotWrite( - &SAVE.devices[SAVE.deviceCurrent], - &SAVE.slot, - SAVE.slotCurrent - )); - - // Update cache - SAVE.caches[SAVE.slotCurrent] = SAVE.slot.cachedData; - SAVE.slotDirty = false; - - errorOk(); -} - -errorret_t saveLoadSlot() { - assertTrue(SAVE.slotCurrent < SAVE_SLOT_COUNT, "Invalid slot index"); - - if(SAVE.deviceCurrent == 0xFF) { - // No save device available, so we assume an empty slot. - errorChain(saveSlotInit(&SAVE.slot)); - SAVE.caches[SAVE.slotCurrent] = SAVE.slot.cachedData; - errorOk(); - } - - errorret_t ret = saveDeviceSlotRead( - &SAVE.devices[SAVE.deviceCurrent], - &SAVE.slot, - SAVE.slotCurrent - ); - if(errorIsNotOk(ret)) { - // Corrupt or incompatible (e.g. wrong/missing schema version) slot - // file - don't take the whole game down over it, treat it the same as - // an empty/unused slot. - errorCatch(errorPrint(ret)); - consolePrint( - "Save slot %u could not be loaded, treating as empty.\n", - (uint32_t)SAVE.slotCurrent - ); - errorChain(saveSlotInit(&SAVE.slot)); - SAVE.slot.cachedData.corrupt = true; - } - - - // Update cache - SAVE.caches[SAVE.slotCurrent] = SAVE.slot.cachedData; - SAVE.slotDirty = false; - - errorOk(); -} - -errorret_t saveLoadAllSlots() { - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - SAVE.slotCurrent = i; - errorChain(saveSlotInit(&SAVE.slot)); - errorChain(saveLoadSlot());// Load slot updates the cache. - } - - errorOk(); -} - errorret_t saveDispose() { // Dispose each device. savedevice_t *device = &SAVE.devices[0]; diff --git a/src/dusk/save/save.h b/src/dusk/save/save.h index 2d13e701..09bef4a0 100644 --- a/src/dusk/save/save.h +++ b/src/dusk/save/save.h @@ -7,18 +7,12 @@ #pragma once #include "savedevice.h" -#include "save/slot/saveslot.h" #include "save/settings/savesettings.h" typedef struct { // File state savesettings_t settings; - saveslotcache_t caches[SAVE_SLOT_COUNT]; - saveslot_t slot; - - uint8_t slotCurrent; bool_t settingsDirty; - bool_t slotDirty; // Device state savedevice_t devices[SAVE_DEVICE_COUNT]; @@ -67,14 +61,6 @@ void saveFindAvailableDevice( */ void saveOnDeviceAvailabilityChecked(savedevice_t *device, void *user); -/** - * Whether the game is in a state where saving is currently allowed. - * Currently just checks that no cutscene is running. - * - * @return True if saving is currently allowed, false otherwise. - */ -bool_t saveCanSave(); - /** * Saves the current settings to the current device. * @@ -89,29 +75,6 @@ errorret_t saveSaveSettings(); */ errorret_t saveLoadSettings(); -/** - * Saves the current slot to the current device, at the current slot index. - * - * @return Error state if any. - */ -errorret_t saveSaveSlot(); - -/** - * Loads the current slot from the current device, at the current slot index. - * - * @return Error state if any. - */ -errorret_t saveLoadSlot(); - -/** - * Resets and loads every slot from the current device, updating the caches - * along the way. Leaves SAVE.slotCurrent pointing at the last slot index - * once done. - * - * @return Error state if any. - */ -errorret_t saveLoadAllSlots(); - /** * Disposes of the save system. * diff --git a/src/dusk/save/savedevice.c b/src/dusk/save/savedevice.c index 111fdb08..0f16b63e 100644 --- a/src/dusk/save/savedevice.c +++ b/src/dusk/save/savedevice.c @@ -473,40 +473,6 @@ errorret_t saveDeviceRawFetchItem( #endif// defined(SAVE_DEVICE_DATA_RAW) -errorret_t saveDeviceSlotWrite( - savedevice_t *device, - saveslot_t *slot, - const uint8_t slotIndex -) { - assertNotNull(device, "device cannot be null"); - assertNotNull(slot, "slot cannot be null"); - - #if defined(SAVE_DEVICE_DATA_RAW) - errorChain(saveDeviceRawStoreItem(device, NULL, slot, slotIndex)); - #elif defined(saveDeviceSlotWritePlatform) - errorChain(saveDeviceSlotWritePlatform(device, slot, slotIndex)); - #endif - - errorOk(); -} - -errorret_t saveDeviceSlotRead( - savedevice_t *device, - saveslot_t *slot, - const uint8_t slotIndex -) { - assertNotNull(device, "device cannot be null"); - assertNotNull(slot, "slot cannot be null"); - - #if defined(SAVE_DEVICE_DATA_RAW) - errorChain(saveDeviceRawFetchItem(device, NULL, slot, slotIndex)); - #elif defined(saveDeviceSlotReadPlatform) - errorChain(saveDeviceSlotReadPlatform(device, slot, slotIndex)); - #endif - - errorOk(); -} - errorret_t saveDeviceSettingsWrite( savedevice_t *device, savesettings_t *settings diff --git a/src/dusk/save/savedevice.h b/src/dusk/save/savedevice.h index 543f5054..8da11481 100644 --- a/src/dusk/save/savedevice.h +++ b/src/dusk/save/savedevice.h @@ -44,7 +44,6 @@ typedef struct savedevice_s savedevice_t; typedef struct savesettings_s savesettings_t; -typedef struct saveslot_s saveslot_t; typedef void (*savedevicestatecallback_t)(savedevice_t *device, void *user); @@ -102,34 +101,6 @@ void saveDeviceCheckAvailability( void *user ); -/** - * Writes the given save slot to the device at the given slot index. - * - * @param device The save device to write to. - * @param slot The save slot to write. - * @param slotIndex The slot index to write to. - * @return Error state if any. - */ -errorret_t saveDeviceSlotWrite( - savedevice_t *device, - saveslot_t *slot, - const uint8_t slotIndex -); - -/** - * Reads a save slot from the device at the given slot index. - * - * @param device The save device to read from. - * @param slot The save slot to read into. - * @param slotIndex The slot index to read from. - * @return Error state if any. - */ -errorret_t saveDeviceSlotRead( - savedevice_t *device, - saveslot_t *slot, - const uint8_t slotIndex -); - /** * Writes the given save settings to the device. * diff --git a/src/dusk/save/slot/CMakeLists.txt b/src/dusk/save/slot/CMakeLists.txt deleted file mode 100644 index de4c3dcd..00000000 --- a/src/dusk/save/slot/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - saveslot.c - saveslotcurrent.c - saveslotver1.c -) diff --git a/src/dusk/save/slot/saveslot.c b/src/dusk/save/slot/saveslot.c deleted file mode 100644 index cb966442..00000000 --- a/src/dusk/save/slot/saveslot.c +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "saveslot.h" -#include "save/slot/saveslotcurrent.h" -#include "save/save.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" - -errorret_t saveSlotInit(saveslot_t *slot) { - assertNotNull(slot, "Slot cannot be null"); - - // Zero the save slot. - memoryZero(slot, sizeof(saveslot_t)); - slot->version = 1; - - // Set the default story flags. - for(storyflag_t flag = 0; flag < STORY_FLAG_COUNT; flag++) { - slot->storyFlags[flag] = STORY_FLAG_MAP[flag].initial; - } - - // Set the default party. - errorChain(partyInit(&slot->party)); - - // Set the default current cutscene. - stringCopy( - slot->currentCutscene, SAVE_SLOT_CUTSCENE_DEFAULT, - sizeof(slot->currentCutscene) - 1 - ); - - errorOk(); -} - -errorret_t saveSlotNewGame(void) { - errorChain(saveSlotInit(&SAVE.slot)); - - stringCopy( - SAVE.slot.cachedData.mapName, SAVE_SLOT_MAP_NAME_DEFAULT, - sizeof(SAVE.slot.cachedData.mapName) - ); - - errorOk(); -} - -void saveSlotSetCurrentCutscene(const char_t *file) { - assertNotNull(file, "File cannot be null"); - - stringCopy( - SAVE.slot.currentCutscene, file, sizeof(SAVE.slot.currentCutscene) - 1 - ); - SAVE.slotDirty = true; -} - -bool_t saveSlotInUse(saveslotcache_t *slot) { - assertNotNull(slot, "Slot cannot be null"); - return slot->name[0] != '\0'; -} - -bool_t saveSlotHasSaved(saveslotcache_t *slot) { - assertNotNull(slot, "Slot cannot be null"); - return slot->time.time != 0; -} - -errorret_t saveSlotWriteJSON( - saveslot_t *slot, - yyjson_mut_doc *doc, - yyjson_mut_val *object -) { - errorChain(saveSlotCurrentWriteJSON(slot, doc, object)); - errorOk(); -} - -errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object) { - assertNotNull(slot, "Slot cannot be null"); - assertNotNull(object, "Object cannot be null"); - - // saveSlotInit() now takes slot directly, so this can reset through it - // rather than duplicating the memSet/version stub - saveSlotCurrent - // ReadJSON() fully overwrites every field it sets anyway (including - // party.members/order), so seeding a fresh party.jsonc roster here only - // to replace it is wasted work, but keeps this in one reset path rather - // than two subtly different ones. - errorChain(saveSlotInit(slot)); - - errorChain(saveSlotCurrentReadJSON(slot, object)); - errorOk(); -} diff --git a/src/dusk/save/slot/saveslot.h b/src/dusk/save/slot/saveslot.h deleted file mode 100644 index 6a934f08..00000000 --- a/src/dusk/save/slot/saveslot.h +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "save/savedevice.h" -#include "rpg/overworld/map.h" -#include "rpg/battle/party.h" -#include "rpg/story/story.h" -#include "time/timeepoch.h" -#include "asset/assetfile.h" -#include "yyjson.h" - -#define SAVE_SLOT_NAME_LENGTH 8 -#ifndef SAVE_SLOT_COUNT - #define SAVE_SLOT_COUNT 3 -#endif - -#define SAVE_SLOT_MAP_NAME_DEFAULT "overworld" -#define SAVE_SLOT_CUTSCENE_NAME_MAX ASSET_FILE_NAME_MAX -#define SAVE_SLOT_CUTSCENE_DEFAULT "cutscenes/new_game.jsonc" - -typedef struct { - char_t name[SAVE_SLOT_NAME_LENGTH + 1];// 8 characters + null terminator - dusktimeepoch_t time; - int32_t playerLevel; - char_t mapName[MAP_NAME_MAX]; - bool_t corrupt; -} saveslotcache_t; - -typedef struct saveslot_s { - uint8_t version; - uint8_t dataType; - saveslotcache_t cachedData; - party_t party; - storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX]; - // Asset path of the cutscene currently in progress for this slot (e.g. - // resumed on load) - defaults to SAVE_SLOT_CUTSCENE_DEFAULT on a fresh - // slot (see saveSlotInit). - char_t currentCutscene[SAVE_SLOT_CUTSCENE_NAME_MAX]; -} saveslot_t; - -/** - * Inits slot with the default state, this is functionally "new game" but - * will not set the player name, as that is what we use to determine if a - * save slot is "in use" or not. Seeds the party roster from party.jsonc - * (see partyInit). - * - * @param slot The save slot to initialize. - * @return Error if the starting party roster (party.jsonc) fails to - * load/parse. - */ -errorret_t saveSlotInit(saveslot_t *slot); - -/** - * Sets up the current save slot (SAVE.slot) for a brand new game: calls - * saveSlotInit (which seeds the starting party roster) and sets the - * default starting map. Use this (not saveSlotInit directly) whenever a - * new save is actually being created for a player. Callers creating a - * new save at a specific index should set SAVE.slotCurrent first. - * - * @return Error if saveSlotInit fails. - */ -errorret_t saveSlotNewGame(void); - -/** - * Sets the current save slot's (SAVE.slot) in-progress cutscene and - * dirties the save (SAVE.slotDirty), same as storySetFlagValue - so the - * next save write persists it. Use this (not writing SAVE.slot. - * currentCutscene directly) anywhere gameplay needs to change it. - * - * @param file Asset path of the cutscene now in progress, or "" for none. - */ -void saveSlotSetCurrentCutscene(const char_t *file); - -/** - * Checks if the save slot is in use, this is determined by checking if the - * player name is set or not. - * - * @param slot The save slot to check. - * @return True if the save slot is in use, false otherwise. - */ -bool_t saveSlotInUse(saveslotcache_t *slot); - -/** - * Returns whether or not the given save slot has ever saved (has an epoc non 0) - * - * @param slot The save slot to check. - * @return True if the save slot has ever saved, false otherwise. - */ -bool_t saveSlotHasSaved(saveslotcache_t *slot); - -/** - * Writes the given save slot out to the given JSON object. - * - * @param slot The save slot to write. - * @param doc The mutable JSON document that owns the object. - * @param object The mutable JSON object to write into. - * @return Error state if any. - */ -errorret_t saveSlotWriteJSON( - saveslot_t *slot, - yyjson_mut_doc *doc, - yyjson_mut_val *object -); - -/** - * Reads the given save slot in from the given JSON object. - * - * @param slot The save slot to read into. - * @param object The JSON object to read from. - * @return Error state if any. - */ -errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object); diff --git a/src/dusk/save/slot/saveslotcurrent.c b/src/dusk/save/slot/saveslotcurrent.c deleted file mode 100644 index 76c199e4..00000000 --- a/src/dusk/save/slot/saveslotcurrent.c +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "saveslotcurrent.h" -#include "assert/assert.h" -#include "util/string.h" -#include "util/memory.h" -#include "time/time.h" - -errorret_t saveSlotCurrentWriteJSON( - saveslot_t *slot, - yyjson_mut_doc *doc, - yyjson_mut_val *object -) { - assertNotNull(slot, "Slot cannot be null"); - assertNotNull(doc, "Doc cannot be null"); - assertNotNull(object, "Object cannot be null"); - - // Stamped on the stable struct (not the throwaway ver1 copy below) so the - // fresh timestamp survives the write - saveSlotHasSaved() depends on it. - slot->cachedData.time = timeGetEpoch(); - - saveslotver1_t ver1; - stringCopy(ver1.name, slot->cachedData.name, sizeof(ver1.name)); - ver1.time = slot->cachedData.time; - ver1.playerLevel = slot->cachedData.playerLevel; - stringCopy(ver1.mapName, slot->cachedData.mapName, sizeof(ver1.mapName)); - - memoryCopy( - ver1.partyMembers, slot->party.members, sizeof(ver1.partyMembers) - ); - memoryCopy(ver1.partyOrder, slot->party.order, sizeof(ver1.partyOrder)); - - memoryCopy(ver1.storyFlags, slot->storyFlags, sizeof(ver1.storyFlags)); - - stringCopy( - ver1.currentCutscene, slot->currentCutscene, sizeof(ver1.currentCutscene) - ); - - errorChain(saveSlotVer1WriteJSON(&ver1, doc, object)); - errorOk(); -} - -errorret_t saveSlotCurrentReadJSON(saveslot_t *slot, yyjson_val *object) { - assertNotNull(slot, "Slot cannot be null"); - assertNotNull(object, "Object cannot be null"); - - // Zeroed up front: saveSlotVer1ReadJSON returns early (leaving everything - // past "name" untouched) for a blank/never-used slot, so this must not be - // left as uninitialized stack garbage. - saveslotver1_t ver1; - memoryZero(&ver1, sizeof(ver1)); - errorChain(saveSlotVer1ReadJSON(&ver1, object)); - - stringCopy(slot->cachedData.name, ver1.name, sizeof(slot->cachedData.name)); - slot->cachedData.time = ver1.time; - slot->cachedData.playerLevel = ver1.playerLevel; - stringCopy( - slot->cachedData.mapName, ver1.mapName, sizeof(slot->cachedData.mapName) - ); - - memoryCopy( - slot->party.members, ver1.partyMembers, sizeof(slot->party.members) - ); - memoryCopy(slot->party.order, ver1.partyOrder, sizeof(slot->party.order)); - - memoryCopy(slot->storyFlags, ver1.storyFlags, sizeof(slot->storyFlags)); - - stringCopy( - slot->currentCutscene, ver1.currentCutscene, - sizeof(slot->currentCutscene) - ); - - errorOk(); -} diff --git a/src/dusk/save/slot/saveslotcurrent.h b/src/dusk/save/slot/saveslotcurrent.h deleted file mode 100644 index a4666fb8..00000000 --- a/src/dusk/save/slot/saveslotcurrent.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "save/slot/saveslot.h" -#include "save/slot/saveslotver1.h" - -/** - * Converts the given save slot to whichever schema SAVE_SLOT_CURRENT - * (saveslotver.h) selects and writes it out to the given JSON object. - * Update this (only) when bumping to a new SAVE_SLOT_VER_N. - * - * @param slot The save slot to write. - * @param doc The mutable JSON document that owns the object. - * @param object The mutable JSON object to write into. - * @return Error state if any. - */ -errorret_t saveSlotCurrentWriteJSON( - saveslot_t *slot, - yyjson_mut_doc *doc, - yyjson_mut_val *object -); - -/** - * Reads whichever schema SAVE_SLOT_CURRENT (saveslotver.h) selects from the - * given JSON object and converts it into the given save slot. Update this - * (only) when bumping to a new SAVE_SLOT_VER_N. - * - * @param slot The save slot to read into. - * @param object The JSON object to read from. - * @return Error state if any. - */ -errorret_t saveSlotCurrentReadJSON(saveslot_t *slot, yyjson_val *object); diff --git a/src/dusk/save/slot/saveslotver.h b/src/dusk/save/slot/saveslotver.h deleted file mode 100644 index 2774e9ee..00000000 --- a/src/dusk/save/slot/saveslotver.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once - -// One constant per save slot JSON schema this codebase has ever shipped. -// Never remove or renumber an existing entry - old save files on disk may -// still declare it. -#define SAVE_SLOT_VER_1 1 - -// The schema saveSlotCurrentWriteJSON()/saveSlotCurrentReadJSON() use today. -// Bump this to a new SAVE_SLOT_VER_N when the slot JSON shape changes. -#define SAVE_SLOT_CURRENT SAVE_SLOT_VER_1 diff --git a/src/dusk/save/slot/saveslotver1.c b/src/dusk/save/slot/saveslotver1.c deleted file mode 100644 index 247166b6..00000000 --- a/src/dusk/save/slot/saveslotver1.c +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "saveslotver1.h" -#include "assert/assert.h" -#include "save/savejson.h" - -errorret_t saveSlotVer1WriteJSON( - saveslotver1_t *slot, - yyjson_mut_doc *doc, - yyjson_mut_val *object -) { - assertNotNull(slot, "Slot cannot be null"); - assertNotNull(doc, "Doc cannot be null"); - assertNotNull(object, "Object cannot be null"); - - writeVersion(SAVE_SLOT_CURRENT); - writeString("name", slot->name); - writeTime("time", slot->time); - writeInt32("playerLevel", slot->playerLevel); - writeString("mapName", slot->mapName); - - yyjson_mut_val *partyMembersArr = yyjson_mut_arr(doc); - for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) { - yyjson_mut_val *memberObj = - battleFighterSerialize(&slot->partyMembers[i], doc); - yyjson_mut_arr_add_val(partyMembersArr, memberObj); - } - writeValue("partyMembers", partyMembersArr); - - writeUInt8Array("partyOrder", slot->partyOrder, PARTY_ACTIVE_SIZE_MAX); - - yyjson_mut_val *storyFlagsObj = yyjson_mut_obj_add_obj(doc, object, "storyFlags"); - for(storyflag_t flag = 0; flag < STORY_FLAG_COUNT; flag++) { - yyjson_mut_obj_add_uint( - doc, storyFlagsObj, storyGetNameByFlag(flag), slot->storyFlags[flag] - ); - } - - writeString("currentCutscene", slot->currentCutscene); - - errorOk(); -} - -errorret_t saveSlotVer1ReadJSON(saveslotver1_t *slot, yyjson_val *object) { - assertNotNull(slot, "Slot cannot be null"); - assertNotNull(object, "Object cannot be null"); - - char_t saveJsonStringBuffer[SAVE_JSON_STRING_BUFFER_SIZE]; - - requireVersion(SAVE_SLOT_CURRENT); - - readString("name", slot->name, "", SAVE_SLOT_VER1_NAME_LENGTH); - if(slot->name[0] == '\0') errorOk(); - - readTime("time", slot->time); - readInt32("playerLevel", slot->playerLevel, 1); - requireStrMin("mapName", 1); - readString( - "mapName", slot->mapName, "", SAVE_SLOT_VER1_MAP_NAME_MAX - 1 - ); - - requireArrayOfSize( - "partyMembers", partyMembersArr, PARTY_MEMBER_COUNT_MAX - ); - - size_t memberIdx, memberLen; - yyjson_val *memberObj; - yyjson_arr_foreach( - partyMembersArr, memberIdx, memberLen, memberObj - ) { - battlefighter_t *member = &slot->partyMembers[memberIdx]; - member->id = (uint8_t)memberIdx; - errorChain(battleFighterParse(member, memberObj)); - } - - readUInt8Array( - "partyOrder", slot->partyOrder, PARTY_ACTIVE_SIZE_MAX, - PARTY_ORDER_EMPTY - ); - - yyjson_val *storyFlagsObj = yyjson_obj_get(object, "storyFlags"); - if(storyFlagsObj != NULL) { - yyjson_obj_iter iter = yyjson_obj_iter_with(storyFlagsObj); - yyjson_val *flagKey; - while((flagKey = yyjson_obj_iter_next(&iter))) { - storyflag_t flag = storyGetFlagByName(yyjson_get_str(flagKey)); - if(flag >= STORY_FLAG_COUNT) continue;// Flag no longer exists, ignore - - slot->storyFlags[flag] = - (storyflagval_t)yyjson_get_uint(yyjson_obj_iter_get_val(flagKey)); - } - } - - readString( - "currentCutscene", slot->currentCutscene, SAVE_SLOT_VER1_CUTSCENE_DEFAULT, - SAVE_SLOT_VER1_CUTSCENE_NAME_MAX - 1 - ); - - errorOk(); -} diff --git a/src/dusk/save/slot/saveslotver1.h b/src/dusk/save/slot/saveslotver1.h deleted file mode 100644 index 77040c74..00000000 --- a/src/dusk/save/slot/saveslotver1.h +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "time/timeepoch.h" -#include "save/slot/saveslotver.h" -#include "rpg/battle/party.h" -#include "rpg/story/story.h" -#include "yyjson.h" - -// This schema's own frozen copies of save/slot/saveslot.h's field sizes - -// never reference SAVE_SLOT_NAME_LENGTH/MAP_NAME_MAX (or any other live -// constant) here. Once shipped, this file's on-disk shape must never change -// underneath old save files, even if the live engine constants do. The -// party arrays below are the exception - saveSlotVer1ReadJSON() checks their -// JSON array lengths explicitly, so PARTY_MEMBER_COUNT_MAX/ -// PARTY_ACTIVE_SIZE_MAX (rpg/battle/party.h) can be referenced directly. -// storyFlags is a further exception, for a different reason - it's written -// keyed by flag name (not position), so its on-disk shape is decoupled from -// STORY_FLAG_COUNT_MAX entirely; a save simply loses whatever flags no -// longer resolve by name (see storyGetFlagByName) if that cap ever shrinks. -// -// partyMembers is a THIRD exception, deliberately broken pre-release: its -// per-member shape now comes from battleFighterSerialize/battleFighterParse -// (rpg/battle/fighter/battlefighter.h) - the shared fighter JSON format -// used everywhere else (party.jsonc, BATTLE_START enemies) - instead of its -// own hand-rolled field set. This is a real on-disk break (different key -// names, no more "statAttack" etc.) with no migration - an old v1 save -// simply reads back as corrupt. Acceptable only because nothing has -// shipped yet; don't repeat this once real saves exist without a version -// bump instead. -#define SAVE_SLOT_VER1_NAME_LENGTH 8 -#define SAVE_SLOT_VER1_MAP_NAME_MAX 10 -#define SAVE_SLOT_VER1_CUTSCENE_NAME_MAX 128 -// Own frozen copy of save/slot/saveslot.h's SAVE_SLOT_CUTSCENE_DEFAULT, for -// the same reason as the sizes above - never reference the live constant. -#define SAVE_SLOT_VER1_CUTSCENE_DEFAULT "cutscenes/new_game.jsonc" - -// Independent from saveslotcache_t (save/slot/saveslot.h) by design - this -// is what version 1 actually put on disk, frozen forever. saveSlotCurrent -// WriteJSON()/ReadJSON() (saveslotcurrent.c) manually copy field-by-field -// between the two, since they're separate types that merely happen to -// currently look alike. -typedef struct saveslotver1_s { - char_t name[SAVE_SLOT_VER1_NAME_LENGTH + 1]; - dusktimeepoch_t time; - int32_t playerLevel; - char_t mapName[SAVE_SLOT_VER1_MAP_NAME_MAX]; - - battlefighter_t partyMembers[PARTY_MEMBER_COUNT_MAX]; - uint8_t partyOrder[PARTY_ACTIVE_SIZE_MAX]; - - storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX]; - - char_t currentCutscene[SAVE_SLOT_VER1_CUTSCENE_NAME_MAX]; -} saveslotver1_t; - -/** - * Writes the given v1 save slot data out to the given JSON object, stamping - * SAVE_SLOT_CURRENT as the "version" field. - * - * @param slot The v1 save slot data to write. - * @param doc The mutable JSON document that owns the object. - * @param object The mutable JSON object to write into. - * @return Error state if any. - */ -errorret_t saveSlotVer1WriteJSON( - saveslotver1_t *slot, - yyjson_mut_doc *doc, - yyjson_mut_val *object -); - -/** - * Reads v1 save slot data in from the given JSON object. Throws if the - * JSON's "version" is missing or doesn't match SAVE_SLOT_CURRENT. - * - * @param slot The v1 save slot data to read into. - * @param object The JSON object to read from. - * @return Error state if any. - */ -errorret_t saveSlotVer1ReadJSON(saveslotver1_t *slot, yyjson_val *object); diff --git a/src/dusklinux/save/savedevicelinux.c b/src/dusklinux/save/savedevicelinux.c index 9f586b0f..f4fee33d 100644 --- a/src/dusklinux/save/savedevicelinux.c +++ b/src/dusklinux/save/savedevicelinux.c @@ -7,7 +7,6 @@ #include "save/savedevice.h" #include "save/savedevicelinux.h" -#include "save/slot/saveslot.h" #include "save/settings/savesettings.h" #include "save/savejson.h" #include "asset/asset.h" @@ -86,92 +85,6 @@ errorret_t saveDeviceLinuxGetSettingsPath( errorOk(); } -errorret_t saveDeviceLinuxGetSlotPath( - char_t *dest, - const size_t destSize, - const uint8_t slotIndex -) { - assertNotNull(dest, "dest cannot be null"); - - char_t directory[SAVE_DEVICE_LINUX_PATH_MAX]; - errorChain(saveDeviceLinuxGetDirectory(directory, sizeof(directory))); - - char_t filename[SAVE_SLOT_NAME_LENGTH + 16]; - stringFormat( - filename, sizeof(filename) - 1, - SAVE_DEVICE_LINUX_SLOT_FILENAME_FORMAT, (uint32_t)slotIndex - ); - - stringFormat(dest, destSize, "%s/%s", directory, filename); - errorOk(); -} - -errorret_t saveDeviceLinuxSlotWrite( - savedevice_t *device, - saveslot_t *slot, - uint8_t slotIndex -) { - assertNotNull(device, "device cannot be null"); - assertNotNull(slot, "slot cannot be null"); - - char_t path[SAVE_DEVICE_LINUX_PATH_MAX]; - errorChain(saveDeviceLinuxGetSlotPath(path, sizeof(path), slotIndex)); - - writeInit(); - - errorret_t writeResult = saveSlotWriteJSON(slot, doc, object); - if(errorIsNotOk(writeResult)) { - yyjson_mut_doc_free(doc); - errorChain(writeResult); - } - - yyjson_write_err writeErr; - bool_t written = yyjson_mut_write_file(path, doc, 0, NULL, &writeErr); - yyjson_mut_doc_free(doc); - - if(!written) { - errorThrow( - "Failed to write save slot file: %s (%s)", path, writeErr.msg - ); - } - - errorOk(); -} - -errorret_t saveDeviceLinuxSlotRead( - savedevice_t *device, - saveslot_t *slot, - uint8_t slotIndex -) { - assertNotNull(device, "device cannot be null"); - assertNotNull(slot, "slot cannot be null"); - - char_t path[SAVE_DEVICE_LINUX_PATH_MAX]; - errorChain(saveDeviceLinuxGetSlotPath(path, sizeof(path), slotIndex)); - - yyjson_read_err readErr; - yyjson_doc *jsonDoc = yyjson_read_file(path, 0, NULL, &readErr); - if(jsonDoc == NULL) { - // No save file at this slot yet, leave the slot as-is. - if(readErr.code == YYJSON_READ_ERROR_FILE_OPEN) errorOk(); - errorThrow( - "Failed to parse save slot file: %s (%s)", path, readErr.msg - ); - } - - yyjson_val *object = yyjson_doc_get_root(jsonDoc); - if(object == NULL) { - yyjson_doc_free(jsonDoc); - errorThrow("Save slot file missing root object: %s", path); - } - - errorret_t readResult = saveSlotReadJSON(slot, object); - yyjson_doc_free(jsonDoc); - errorChain(readResult); - - errorOk(); -} - errorret_t saveDeviceLinuxSettingsWrite( savedevice_t *device, savesettings_t *settings diff --git a/src/dusklinux/save/savedevicelinux.h b/src/dusklinux/save/savedevicelinux.h index 99ccbf89..40c9fc78 100644 --- a/src/dusklinux/save/savedevicelinux.h +++ b/src/dusklinux/save/savedevicelinux.h @@ -14,7 +14,6 @@ // settings and each save slot as their own separate file. #define SAVE_DEVICE_LINUX_SAVES_SUBDIR_NAME "saves" #define SAVE_DEVICE_LINUX_SETTINGS_FILENAME "settings.json" -#define SAVE_DEVICE_LINUX_SLOT_FILENAME_FORMAT "slot%u.json" #define SAVE_DEVICE_LINUX_PATH_MAX FILENAME_MAX typedef struct { @@ -22,7 +21,6 @@ typedef struct { } savedeviceplatform_t; typedef struct savedevice_s savedevice_t; -typedef struct saveslot_s saveslot_t; typedef struct savesettings_s savesettings_t; /** @@ -71,49 +69,6 @@ errorret_t saveDeviceLinuxGetSettingsPath( const size_t destSize ); -/** - * Builds the absolute path to the given save slot's file. - * - * @param dest The destination buffer to write the path into. - * @param destSize The size of the destination buffer, exc. null terminator. - * @param slotIndex The slot index to build the path for. - * @return Error state if any. - */ -errorret_t saveDeviceLinuxGetSlotPath( - char_t *dest, - const size_t destSize, - const uint8_t slotIndex -); - -/** - * Writes the given save slot out to its file on disk. - * - * @param device The save device to write to. - * @param slot The save slot to write. - * @param slotIndex The slot index to write to. - * @return Error state if any. - */ -errorret_t saveDeviceLinuxSlotWrite( - savedevice_t *device, - saveslot_t *slot, - uint8_t slotIndex -); - -/** - * Reads a save slot in from its file on disk. If no file exists yet for the - * given slot index, this is not an error - the slot is simply left as-is. - * - * @param device The save device to read from. - * @param slot The save slot to read into. - * @param slotIndex The slot index to read from. - * @return Error state if any. - */ -errorret_t saveDeviceLinuxSlotRead( - savedevice_t *device, - saveslot_t *slot, - uint8_t slotIndex -); - /** * Writes the given save settings out to the settings file on disk. * diff --git a/src/dusklinux/save/savedeviceplatform.h b/src/dusklinux/save/savedeviceplatform.h index 11bd2c89..cc8a5b8d 100644 --- a/src/dusklinux/save/savedeviceplatform.h +++ b/src/dusklinux/save/savedeviceplatform.h @@ -14,7 +14,5 @@ #define saveDevicePlatformUpdate saveDeviceLinuxUpdate #define saveDeviceCheckAvailabilityPlatform saveDeviceLinuxCheckAvailability #define saveDevicePlatformDispose saveDeviceLinuxDispose -#define saveDeviceSlotWritePlatform saveDeviceLinuxSlotWrite -#define saveDeviceSlotReadPlatform saveDeviceLinuxSlotRead #define saveDeviceSettingsWritePlatform saveDeviceLinuxSettingsWrite #define saveDeviceSettingsReadPlatform saveDeviceLinuxSettingsRead \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e63b0cfc..c24e8075 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -13,8 +13,6 @@ endif() add_subdirectory(error) add_subdirectory(thread) add_subdirectory(display) -add_subdirectory(rpg) -add_subdirectory(item) add_subdirectory(save) add_subdirectory(time) add_subdirectory(util) \ No newline at end of file diff --git a/test/save/CMakeLists.txt b/test/save/CMakeLists.txt index d4164abb..431173c0 100644 --- a/test/save/CMakeLists.txt +++ b/test/save/CMakeLists.txt @@ -7,8 +7,6 @@ include(dusktest) # Tests dusktest(test_savesettings.c) -dusktest(test_saveslot.c) -target_sources(test_saveslot PRIVATE savetestfixture.c) dusktest(test_savedevicelinux.c) target_sources(test_savedevicelinux PRIVATE savetestfixture.c) diff --git a/test/save/savetestfixture.c b/test/save/savetestfixture.c index eb1aee01..5db6bb3b 100644 --- a/test/save/savetestfixture.c +++ b/test/save/savetestfixture.c @@ -10,9 +10,6 @@ #include "savetestfixture.h" #include "assert/assert.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" -#include "asset/loader/assetentry.h" #include "util/memory.h" #include "util/string.h" #include @@ -25,51 +22,6 @@ static char_t SAVE_TEST_FIXTURE_HOME[PATH_MAX]; static char_t SAVE_TEST_FIXTURE_REAL_HOME[PATH_MAX]; static bool_t SAVE_TEST_FIXTURE_HAD_REAL_HOME; -static const char_t *SAVE_TEST_FIXTURE_PARTY_JSON = - "{\"members\":{\"hero\":{" - "\"attack\":10,\"defense\":5,\"magic\":0,\"speed\":10,\"luck\":0," - "\"healthMax\":30,\"mpMax\":10" - "}}}"; - -static yyjson_doc *SAVE_TEST_FIXTURE_PARTY_DOC = NULL; - -errorret_t saveTestFixturePartyJsonLoadSyncStub(assetloading_t *loading) { - loading->entry->data.json = SAVE_TEST_FIXTURE_PARTY_DOC; - loading->entry->state = ASSET_ENTRY_STATE_LOADED; - errorOk(); -} - -errorret_t saveTestFixturePartyJsonDisposeStub(assetentry_t *entry) { - errorOk(); -} - -void saveTestFixtureStubPartyAsset(void) { - memoryZero(&ASSET, sizeof(ASSET)); - for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) { - threadMutexInit(&ASSET.loading[i].mutex); - } - - if(SAVE_TEST_FIXTURE_PARTY_DOC == NULL) { - SAVE_TEST_FIXTURE_PARTY_DOC = yyjson_read( - SAVE_TEST_FIXTURE_PARTY_JSON, strlen(SAVE_TEST_FIXTURE_PARTY_JSON), 0 - ); - assertNotNull( - SAVE_TEST_FIXTURE_PARTY_DOC, "Failed to parse party JSON fixture" - ); - } - - ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_JSON].loadSync = - saveTestFixturePartyJsonLoadSyncStub; - ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_JSON].dispose = - saveTestFixturePartyJsonDisposeStub; -} - -void saveTestFixtureUnstubPartyAsset(void) { - if(SAVE_TEST_FIXTURE_PARTY_DOC == NULL) return; - yyjson_doc_free(SAVE_TEST_FIXTURE_PARTY_DOC); - SAVE_TEST_FIXTURE_PARTY_DOC = NULL; -} - static int_t saveTestFixtureRemoveEntry( const char *path, const struct stat *statBuffer, @@ -97,8 +49,6 @@ int saveTestFixtureSetup(void **state) { assertTrue(setenv("HOME", SAVE_TEST_FIXTURE_HOME, 1) == 0, "setenv failed"); - saveTestFixtureStubPartyAsset(); - errorret_t ret = saveInit(); assertTrue(errorIsOk(ret), "saveInit failed in test fixture"); @@ -106,8 +56,6 @@ int saveTestFixtureSetup(void **state) { } int saveTestFixtureTeardown(void **state) { - saveTestFixtureUnstubPartyAsset(); - nftw( SAVE_TEST_FIXTURE_HOME, saveTestFixtureRemoveEntry, 16, FTW_DEPTH | FTW_PHYS diff --git a/test/save/savetestfixture.h b/test/save/savetestfixture.h index 2ac2bde8..e4ed7c03 100644 --- a/test/save/savetestfixture.h +++ b/test/save/savetestfixture.h @@ -35,21 +35,3 @@ int saveTestFixtureTeardown(void **state); * filesystem permission tricks. Must be called after saveTestFixtureSetup. */ void saveTestFixtureBlockSaveDirectory(void); - -/** - * Stubs the JSON asset loader's sync callback to hand back a small - * in-memory party.jsonc-shaped doc instead of doing a real file/zip read - - * saveInit()/saveSlotInit() seed the party roster via partyInit(), which - * loads party.jsonc through the real asset system, and there's no real - * asset bundle available under the test harness. No thread, no real I/O. - * Resets ASSET to a fresh, otherwise-empty state each call, so it's safe - * to call once per test. Called automatically by saveTestFixtureSetup. - */ -void saveTestFixtureStubPartyAsset(void); - -/** - * Frees the in-memory doc created by saveTestFixtureStubPartyAsset. Safe - * to call even if that function was never called. Called automatically - * by saveTestFixtureTeardown. - */ -void saveTestFixtureUnstubPartyAsset(void); diff --git a/test/save/test_save.c b/test/save/test_save.c index a04946b8..d43b510e 100644 --- a/test/save/test_save.c +++ b/test/save/test_save.c @@ -8,7 +8,6 @@ #include "dusktest.h" #include "savetestfixture.h" #include "save/save.h" -#include "save/savedevicelinux.h" #include "util/memory.h" #include "util/string.h" #include @@ -54,16 +53,10 @@ static void test_saveInit_setsDefaults(void **state) { // saveTestFixtureSetup already called the real saveInit() - just assert // on the state it left behind. assert_int_equal(SAVE.deviceCurrent, 0xFF); - assert_int_equal(SAVE.slotCurrent, 0xFF); assert_false(SAVE.findingAvailableDevice); assert_false(SAVE.noAvailableDeviceFound); assert_true(SAVE.settings.audioMasterVolume == 1.0f); assert_int_equal(SAVE.devices[0].state, SAVE_DEVICE_STATE_UNKNOWN); - - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - assert_int_equal(SAVE.caches[i].name[0], '\0'); - assert_int_equal(SAVE.caches[i].playerLevel, 0); - } } // ============================================================ @@ -281,180 +274,6 @@ static void test_saveLoadSettings_readsAndClearsDirtyFlag(void **state) { assert_false(SAVE.settingsDirty); } -// ============================================================ -// saveSaveSlot / saveLoadSlot -// ============================================================ - -static void test_saveSaveSlot_noCurrentDeviceAsserts(void **state) { - expect_assert_failure(saveSaveSlot()); -} - -static void test_saveSaveSlot_invalidSlotIndexAsserts(void **state) { - makeDeviceAvailable(); - // slotCurrent is still 0xFF (unset) from saveInit(). - expect_assert_failure(saveSaveSlot()); -} - -static void test_saveSaveSlot_success(void **state) { - makeDeviceAvailable(); - SAVE.slotCurrent = 1; - saveSlotInit(&SAVE.slot); - stringCopy( - SAVE.slot.cachedData.name, "Hero", sizeof(SAVE.slot.cachedData.name) - ); - SAVE.slot.cachedData.playerLevel = 12; - SAVE.slotDirty = true; - - errorret_t ret = saveSaveSlot(); - assert_true(errorIsOk(ret)); - assert_false(SAVE.slotDirty); - assert_true(stringEquals(SAVE.caches[1].name, "Hero")); - assert_int_equal(SAVE.caches[1].playerLevel, 12); - - // No pre-init needed - saveSlotReadJSON() (via saveDeviceSlotRead) fully - // resets/overwrites its destination struct regardless of prior content. - saveslot_t onDisk; - errorret_t readRet = saveDeviceSlotRead(&SAVE.devices[0], &onDisk, 1); - assert_true(errorIsOk(readRet)); - assert_true(stringEquals(onDisk.cachedData.name, "Hero")); - assert_int_equal(onDisk.cachedData.playerLevel, 12); -} - -static void test_saveLoadSlot_noCurrentDeviceAsserts(void **state) { - expect_assert_failure(saveLoadSlot()); -} - -static void test_saveLoadSlot_invalidSlotIndexAsserts(void **state) { - makeDeviceAvailable(); - expect_assert_failure(saveLoadSlot()); -} - -static void test_saveLoadSlot_success(void **state) { - makeDeviceAvailable(); - - // Write slot 2's on-disk file via SAVE.slot itself (saveSlotInit() only - // ever operates on SAVE.slot now), then reset it again below - the - // write and the load-under-test don't overlap in time. - SAVE.slotCurrent = 2; - saveSlotInit(&SAVE.slot); - stringCopy( - SAVE.slot.cachedData.name, "Zelda", sizeof(SAVE.slot.cachedData.name) - ); - SAVE.slot.cachedData.playerLevel = 30; - errorret_t writeRet = saveDeviceSlotWrite(&SAVE.devices[0], &SAVE.slot, 2); - assert_true(errorIsOk(writeRet)); - - SAVE.slotCurrent = 2; - saveSlotInit(&SAVE.slot); - SAVE.slotDirty = true; - - errorret_t ret = saveLoadSlot(); - assert_true(errorIsOk(ret)); - assert_false(SAVE.slotDirty); - assert_true(stringEquals(SAVE.slot.cachedData.name, "Zelda")); - assert_int_equal(SAVE.slot.cachedData.playerLevel, 30); - assert_true(stringEquals(SAVE.caches[2].name, "Zelda")); - assert_int_equal(SAVE.caches[2].playerLevel, 30); -} - -static void test_saveLoadSlot_deviceReadFails_cacheStaysStale(void **state) { - makeDeviceAvailable(); - - char_t slotPath[512]; - errorret_t pathRet = saveDeviceLinuxGetSlotPath( - slotPath, sizeof(slotPath), 0 - ); - assert_true(errorIsOk(pathRet)); - FILE *file = fopen(slotPath, "w"); - assert_non_null(file); - fprintf(file, "not valid json {{{"); - fclose(file); - - SAVE.slotCurrent = 0; - saveSlotInit(&SAVE.slot); - SAVE.caches[0].playerLevel = 77;// sentinel - - errorret_t ret = saveLoadSlot(); - assert_true(errorIsNotOk(ret)); - errorCatch(ret); - - // The device read failed before the cache-sync line ever ran. - assert_int_equal(SAVE.caches[0].playerLevel, 77); -} - -// ============================================================ -// saveLoadAllSlots -// ============================================================ - -static void test_saveLoadAllSlots_loadsAllAndEndsAtLastIndex(void **state) { - makeDeviceAvailable(); - - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - SAVE.slotCurrent = i; - saveSlotInit(&SAVE.slot); - SAVE.slot.cachedData.playerLevel = (int32_t)(i + 1); - errorret_t saveRet = saveSaveSlot(); - assert_true(errorIsOk(saveRet)); - } - - errorret_t ret = saveLoadAllSlots(); - assert_true(errorIsOk(ret)); - assert_int_equal(SAVE.slotCurrent, SAVE_SLOT_COUNT - 1); - - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - assert_int_equal(SAVE.caches[i].playerLevel, (int32_t)(i + 1)); - } -} - -static void test_saveLoadAllSlots_missingFileLeavesInitDefaults(void **state) { - makeDeviceAvailable(); - - SAVE.slotCurrent = 0; - saveSlotInit(&SAVE.slot); - SAVE.slot.cachedData.playerLevel = 5; - errorret_t saveRet = saveSaveSlot(); - assert_true(errorIsOk(saveRet)); - // Slots 1 and 2 are never written - no file exists for them. - - errorret_t ret = saveLoadAllSlots(); - assert_true(errorIsOk(ret)); - - assert_int_equal(SAVE.caches[0].playerLevel, 5); - // Missing-file slots fall back to saveSlotInit()'s default (0), not the - // JSON-missing-field default of 1 that a corrupt-but-present file with no - // playerLevel key would produce - these are two different defaults. - assert_int_equal(SAVE.caches[1].playerLevel, 0); - assert_int_equal(SAVE.caches[2].playerLevel, 0); -} - -static void test_saveLoadAllSlots_middleSlotCorrupt_stopsAtFailingIndex( - void **state -) { - makeDeviceAvailable(); - - SAVE.slotCurrent = 0; - saveSlotInit(&SAVE.slot); - errorret_t saveRet = saveSaveSlot(); - assert_true(errorIsOk(saveRet)); - - char_t slotPath[512]; - errorret_t pathRet = saveDeviceLinuxGetSlotPath( - slotPath, sizeof(slotPath), 1 - ); - assert_true(errorIsOk(pathRet)); - FILE *file = fopen(slotPath, "w"); - assert_non_null(file); - fprintf(file, "not valid json {{{"); - fclose(file); - - errorret_t ret = saveLoadAllSlots(); - assert_true(errorIsNotOk(ret)); - errorCatch(ret); - - // The loop bails out as soon as slot 1 fails - it never reaches slot 2. - assert_int_equal(SAVE.slotCurrent, 1); -} - // ============================================================ // saveDispose // ============================================================ @@ -566,46 +385,6 @@ int main(void) { saveTestFixtureSetup, saveTestFixtureTeardown ), - cmocka_unit_test_setup_teardown( - test_saveSaveSlot_noCurrentDeviceAsserts, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveSaveSlot_invalidSlotIndexAsserts, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveSaveSlot_success, saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveLoadSlot_noCurrentDeviceAsserts, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveLoadSlot_invalidSlotIndexAsserts, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveLoadSlot_success, saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveLoadSlot_deviceReadFails_cacheStaysStale, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - - cmocka_unit_test_setup_teardown( - test_saveLoadAllSlots_loadsAllAndEndsAtLastIndex, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveLoadAllSlots_missingFileLeavesInitDefaults, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveLoadAllSlots_middleSlotCorrupt_stopsAtFailingIndex, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( test_saveDispose_succeeds, saveTestFixtureSetup, saveTestFixtureTeardown ), diff --git a/test/save/test_savedevice.c b/test/save/test_savedevice.c index 7f4bf449..79a2bf23 100644 --- a/test/save/test_savedevice.c +++ b/test/save/test_savedevice.c @@ -8,9 +8,7 @@ #include "dusktest.h" #include "savetestfixture.h" #include "save/savedevice.h" -#include "save/slot/saveslot.h" #include "save/settings/savesettings.h" -#include "save/save.h" #include "util/memory.h" #include "util/string.h" @@ -150,28 +148,10 @@ static void test_saveDeviceCheckAvailability_resolvesSynchronouslyOnLinux( } // ============================================================ -// saveDeviceSlotWrite / saveDeviceSlotRead / saveDeviceSettingsWrite / -// saveDeviceSettingsRead - dispatch to the platform's four hooks. +// saveDeviceSettingsWrite / saveDeviceSettingsRead - dispatch to the +// platform's hooks. // ============================================================ -static void test_saveDeviceSlotWrite_nullAsserts(void **state) { - savedevice_t device; - // Content is never touched - both calls assert on a NULL argument - // before either device or slot would actually be read. - saveslot_t slot; - - expect_assert_failure(saveDeviceSlotWrite(NULL, &slot, 0)); - expect_assert_failure(saveDeviceSlotWrite(&device, NULL, 0)); -} - -static void test_saveDeviceSlotRead_nullAsserts(void **state) { - savedevice_t device; - saveslot_t slot; - - expect_assert_failure(saveDeviceSlotRead(NULL, &slot, 0)); - expect_assert_failure(saveDeviceSlotRead(&device, NULL, 0)); -} - static void test_saveDeviceSettingsWrite_nullAsserts(void **state) { savedevice_t device; savesettings_t settings; @@ -190,32 +170,6 @@ static void test_saveDeviceSettingsRead_nullAsserts(void **state) { expect_assert_failure(saveDeviceSettingsRead(&device, NULL)); } -static void test_saveDeviceSlot_dispatchesToPlatform(void **state) { - savedevice_t device; - errorret_t initRet = saveDeviceInit(&device); - assert_true(errorIsOk(initRet)); - // The save directory must exist before a write can succeed - normally - // saveDeviceCheckAvailability does this via the platform's mkdirp. - saveDeviceCheckAvailability(&device, updateCallback, NULL); - saveDeviceUpdate(&device); - - // saveSlotInit() only ever operates on SAVE.slot now, so that's the - // write source here. - saveSlotInit(&SAVE.slot); - SAVE.slot.cachedData.playerLevel = 21; - - errorret_t writeRet = saveDeviceSlotWrite(&device, &SAVE.slot, 0); - assert_true(errorIsOk(writeRet)); - - // No pre-init needed - saveSlotReadJSON() (via saveDeviceSlotRead) fully - // resets/overwrites its destination struct regardless of prior content. - saveslot_t read; - errorret_t readRet = saveDeviceSlotRead(&device, &read, 0); - assert_true(errorIsOk(readRet)); - - assert_int_equal(read.cachedData.playerLevel, 21); -} - static void test_saveDeviceSettings_dispatchesToPlatform(void **state) { savedevice_t device; errorret_t initRet = saveDeviceInit(&device); @@ -303,14 +257,6 @@ int main(void) { saveTestFixtureSetup, saveTestFixtureTeardown ), - cmocka_unit_test_setup_teardown( - test_saveDeviceSlotWrite_nullAsserts, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_saveDeviceSlotRead_nullAsserts, - saveTestFixtureSetup, saveTestFixtureTeardown - ), cmocka_unit_test_setup_teardown( test_saveDeviceSettingsWrite_nullAsserts, saveTestFixtureSetup, saveTestFixtureTeardown @@ -319,10 +265,6 @@ int main(void) { test_saveDeviceSettingsRead_nullAsserts, saveTestFixtureSetup, saveTestFixtureTeardown ), - cmocka_unit_test_setup_teardown( - test_saveDeviceSlot_dispatchesToPlatform, - saveTestFixtureSetup, saveTestFixtureTeardown - ), cmocka_unit_test_setup_teardown( test_saveDeviceSettings_dispatchesToPlatform, saveTestFixtureSetup, saveTestFixtureTeardown diff --git a/test/save/test_savedevicelinux.c b/test/save/test_savedevicelinux.c index a9ba2ea2..7abfe5ea 100644 --- a/test/save/test_savedevicelinux.c +++ b/test/save/test_savedevicelinux.c @@ -9,9 +9,7 @@ #include "savetestfixture.h" #include "save/savedevice.h" #include "save/savedevicelinux.h" -#include "save/slot/saveslot.h" #include "save/settings/savesettings.h" -#include "save/save.h" #include "util/memory.h" #include "util/string.h" #include @@ -86,27 +84,6 @@ static void test_getSettingsPath_homeUnset_errors(void **state) { errorCatch(ret); } -static void test_getSlotPath_success(void **state) { - char_t path[512]; - errorret_t ret = saveDeviceLinuxGetSlotPath(path, sizeof(path), 2); - assert_true(errorIsOk(ret)); - - char_t expected[512]; - stringFormat( - expected, sizeof(expected), "%s/.dusk/saves/slot2.json", getenv("HOME") - ); - assert_true(stringEquals(path, expected)); -} - -static void test_getSlotPath_homeUnset_errors(void **state) { - unsetenv("HOME"); - - char_t path[512]; - errorret_t ret = saveDeviceLinuxGetSlotPath(path, sizeof(path), 0); - assert_true(errorIsNotOk(ret)); - errorCatch(ret); -} - // ============================================================ // saveDeviceLinuxCheckAvailability // ============================================================ @@ -175,105 +152,6 @@ static void test_checkAvailability_mkdirpFails(void **state) { assert_true(g_callbackFired); } -// ============================================================ -// saveDeviceLinuxSlotWrite / saveDeviceLinuxSlotRead -// ============================================================ - -static void test_slotWrite_nullAsserts(void **state) { - savedevice_t device; - // Content is never touched - both calls assert on a NULL argument - // before either device or slot would actually be read. - saveslot_t slot; - - expect_assert_failure(saveDeviceLinuxSlotWrite(NULL, &slot, 0)); - expect_assert_failure(saveDeviceLinuxSlotWrite(&device, NULL, 0)); -} - -static void test_slotRead_nullAsserts(void **state) { - savedevice_t device; - saveslot_t slot; - - expect_assert_failure(saveDeviceLinuxSlotRead(NULL, &slot, 0)); - expect_assert_failure(saveDeviceLinuxSlotRead(&device, NULL, 0)); -} - -static void test_slotWriteRead_roundTrip(void **state) { - savedevice_t device; - memoryZero(&device, sizeof(device)); - // The save directory must exist before a write can succeed - normally - // saveDeviceLinuxCheckAvailability does this via mkdirp. - checkAvailabilityAndDrain(&device); - - // saveSlotInit() only ever operates on SAVE.slot now, so that's the - // write source here. - saveSlotInit(&SAVE.slot); - stringCopy( - SAVE.slot.cachedData.name, "Hero", sizeof(SAVE.slot.cachedData.name) - ); - SAVE.slot.cachedData.playerLevel = 9; - - errorret_t writeRet = saveDeviceLinuxSlotWrite(&device, &SAVE.slot, 1); - assert_true(errorIsOk(writeRet)); - - // No pre-init needed - saveSlotReadJSON() (via saveDeviceLinuxSlotRead) - // fully resets/overwrites its destination struct regardless of prior - // content. - saveslot_t read; - errorret_t readRet = saveDeviceLinuxSlotRead(&device, &read, 1); - assert_true(errorIsOk(readRet)); - - assert_true(stringEquals(read.cachedData.name, "Hero")); - assert_int_equal(read.cachedData.playerLevel, 9); -} - -static void test_slotRead_noFileYet_leavesSlotUntouched(void **state) { - savedevice_t device; - memoryZero(&device, sizeof(device)); - - // Deliberately not saveSlotInit()'d - a missing slot file means - // saveDeviceLinuxSlotRead never even calls saveSlotReadJSON, so slot - // must come in exactly as the caller left it and go out unchanged. - saveslot_t slot; - memoryZero(&slot, sizeof(slot)); - stringCopy(slot.cachedData.name, "sentinel", sizeof(slot.cachedData.name)); - slot.cachedData.playerLevel = 555; - - errorret_t ret = saveDeviceLinuxSlotRead(&device, &slot, 2); - assert_true(errorIsOk(ret)); - - assert_true(stringEquals(slot.cachedData.name, "sentinel")); - assert_int_equal(slot.cachedData.playerLevel, 555); -} - -static void test_slotRead_corruptFile_errors(void **state) { - savedevice_t device; - memoryZero(&device, sizeof(device)); - - // The directory must exist before we can drop a file into it - normally - // saveDeviceLinuxCheckAvailability does this via mkdirp. - savedevice_t tmpDevice; - memoryZero(&tmpDevice, sizeof(tmpDevice)); - tmpDevice.state = SAVE_DEVICE_STATE_CHECKING_AVAILABILITY; - saveDeviceLinuxCheckAvailability(&tmpDevice); - assert_int_equal(tmpDevice.state, SAVE_DEVICE_STATE_AVAILABLE); - - char_t slotPath[512]; - errorret_t pathRet = - saveDeviceLinuxGetSlotPath(slotPath, sizeof(slotPath), 0); - assert_true(errorIsOk(pathRet)); - - FILE *file = fopen(slotPath, "w"); - assert_non_null(file); - fprintf(file, "not valid json {{{"); - fclose(file); - - saveslot_t slot; - - errorret_t ret = saveDeviceLinuxSlotRead(&device, &slot, 0); - assert_true(errorIsNotOk(ret)); - errorCatch(ret); -} - // ============================================================ // saveDeviceLinuxSettingsWrite / saveDeviceLinuxSettingsRead // ============================================================ @@ -381,14 +259,6 @@ int main(void) { test_getSettingsPath_homeUnset_errors, saveTestFixtureSetup, saveTestFixtureTeardown ), - cmocka_unit_test_setup_teardown( - test_getSlotPath_success, saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_getSlotPath_homeUnset_errors, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( test_checkAvailability_wrongStateAsserts, saveTestFixtureSetup, saveTestFixtureTeardown @@ -410,25 +280,6 @@ int main(void) { saveTestFixtureSetup, saveTestFixtureTeardown ), - cmocka_unit_test_setup_teardown( - test_slotWrite_nullAsserts, saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_slotRead_nullAsserts, saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_slotWriteRead_roundTrip, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_slotRead_noFileYet_leavesSlotUntouched, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( - test_slotRead_corruptFile_errors, - saveTestFixtureSetup, saveTestFixtureTeardown - ), - cmocka_unit_test_setup_teardown( test_settingsWrite_nullAsserts, saveTestFixtureSetup, saveTestFixtureTeardown