Fix the build after archiving rpg/ui/scene: drop their last live dependents
rpg/ui/scene moved to archive/ earlier left a handful of otherwise-generic systems with dead references into them: - asset/loader/chunk existed solely to load rpg overworld map chunks (tile_t, worldpos_t) - deleted outright, along with its ASSET_LOADER_TYPE_CHUNK entry in assetloader.c/h. - save/slot wrapped party_t/storyflagval_t/map-name/cutscene-path, which is 100% rpg save data - deleted outright (save.c/h, savedevice.c/h and the Linux save backend keep their generic settings/device-discovery logic, just lose the slot read/write pieces that only existed for it). - locale/localemanager.c and test/CMakeLists.txt/test/save/* had a few stray includes and test cases that only existed for the now-gone code. Also restores assets/locale/en_US.po (copied back from archive/assets/) - locale init still needs a real locale file to boot into, even with the asset-moving/archiving done separately. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
f6b884bf7f
commit
fdfdfa3a66
31 files changed
+292
-1994
No files matched your search
@@ -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"
|
||||||
@@ -15,7 +15,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
add_subdirectory(display)
|
add_subdirectory(display)
|
||||||
add_subdirectory(locale)
|
add_subdirectory(locale)
|
||||||
add_subdirectory(json)
|
add_subdirectory(json)
|
||||||
add_subdirectory(chunk)
|
|
||||||
add_subdirectory(dmf)
|
add_subdirectory(dmf)
|
||||||
add_subdirectory(wav)
|
add_subdirectory(wav)
|
||||||
add_subdirectory(mp3)
|
add_subdirectory(mp3)
|
||||||
@@ -46,12 +46,6 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
|
|||||||
.dispose = assetJsonDispose
|
.dispose = assetJsonDispose
|
||||||
},
|
},
|
||||||
|
|
||||||
[ASSET_LOADER_TYPE_CHUNK] = {
|
|
||||||
.loadSync = assetChunkLoaderSync,
|
|
||||||
.loadAsync = assetChunkLoaderAsync,
|
|
||||||
.dispose = assetChunkDispose
|
|
||||||
},
|
|
||||||
|
|
||||||
[ASSET_LOADER_TYPE_WAV] = {
|
[ASSET_LOADER_TYPE_WAV] = {
|
||||||
.loadSync = assetWavLoaderSync,
|
.loadSync = assetWavLoaderSync,
|
||||||
.loadAsync = assetWavLoaderAsync,
|
.loadAsync = assetWavLoaderAsync,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "asset/loader/display/assettilesetloader.h"
|
#include "asset/loader/display/assettilesetloader.h"
|
||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
#include "asset/loader/locale/assetlocaleloader.h"
|
||||||
#include "asset/loader/json/assetjsonloader.h"
|
#include "asset/loader/json/assetjsonloader.h"
|
||||||
#include "asset/loader/chunk/assetchunkloader.h"
|
|
||||||
#include "asset/loader/wav/assetwavloader.h"
|
#include "asset/loader/wav/assetwavloader.h"
|
||||||
#include "asset/loader/mp3/assetmp3loader.h"
|
#include "asset/loader/mp3/assetmp3loader.h"
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ typedef enum {
|
|||||||
ASSET_LOADER_TYPE_TILESET,
|
ASSET_LOADER_TYPE_TILESET,
|
||||||
ASSET_LOADER_TYPE_LOCALE,
|
ASSET_LOADER_TYPE_LOCALE,
|
||||||
ASSET_LOADER_TYPE_JSON,
|
ASSET_LOADER_TYPE_JSON,
|
||||||
ASSET_LOADER_TYPE_CHUNK,
|
|
||||||
ASSET_LOADER_TYPE_WAV,
|
ASSET_LOADER_TYPE_WAV,
|
||||||
ASSET_LOADER_TYPE_MP3,
|
ASSET_LOADER_TYPE_MP3,
|
||||||
|
|
||||||
@@ -39,7 +37,6 @@ typedef union {
|
|||||||
assettilesetloaderloading_t tileset;
|
assettilesetloaderloading_t tileset;
|
||||||
assetlocaleloaderloading_t locale;
|
assetlocaleloaderloading_t locale;
|
||||||
assetjsonloaderloading_t json;
|
assetjsonloaderloading_t json;
|
||||||
assetchunkloaderloading_t chunk;
|
|
||||||
assetwavloaderloading_t wav;
|
assetwavloaderloading_t wav;
|
||||||
assetmp3loaderloading_t mp3;
|
assetmp3loaderloading_t mp3;
|
||||||
} assetloaderloading_t;
|
} assetloaderloading_t;
|
||||||
@@ -51,7 +48,6 @@ typedef union {
|
|||||||
assettilesetoutput_t tileset;
|
assettilesetoutput_t tileset;
|
||||||
assetlocaleoutput_t locale;
|
assetlocaleoutput_t locale;
|
||||||
assetjsonoutput_t json;
|
assetjsonoutput_t json;
|
||||||
assetchunkoutput_t chunk;
|
|
||||||
assetwavoutput_t wav;
|
assetwavoutput_t wav;
|
||||||
assetmp3output_t mp3;
|
assetmp3output_t mp3;
|
||||||
} assetloaderoutput_t;
|
} assetloaderoutput_t;
|
||||||
@@ -61,7 +57,6 @@ typedef union {
|
|||||||
assettilesetloaderinput_t tileset;
|
assettilesetloaderinput_t tileset;
|
||||||
assetlocaleloaderinput_t locale;
|
assetlocaleloaderinput_t locale;
|
||||||
assetjsonloaderinput_t json;
|
assetjsonloaderinput_t json;
|
||||||
assetchunkloaderinput_t chunk;
|
|
||||||
assetwavloaderinput_t wav;
|
assetwavloaderinput_t wav;
|
||||||
assetmp3loaderinput_t mp3;
|
assetmp3loaderinput_t mp3;
|
||||||
} assetloaderinput_t;
|
} assetloaderinput_t;
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -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/<basename>.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();
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "ui/ui.h"
|
|
||||||
#include "system/system.h"
|
#include "system/system.h"
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
|
|
||||||
@@ -39,9 +38,6 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale) {
|
|||||||
assetEntryLock(LOCALE.entry);
|
assetEntryLock(LOCALE.entry);
|
||||||
errorChain(assetRequireLoaded(LOCALE.entry));
|
errorChain(assetRequireLoaded(LOCALE.entry));
|
||||||
|
|
||||||
// TODO : Trigger UI update.
|
|
||||||
errorChain(uiUpdateTranslations());
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,5 +11,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
add_subdirectory(slot)
|
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
+1
-80
@@ -8,7 +8,6 @@
|
|||||||
#include "save.h"
|
#include "save.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
|
|
||||||
save_t SAVE;
|
save_t SAVE;
|
||||||
@@ -17,16 +16,9 @@ errorret_t saveInit() {
|
|||||||
memoryZero(&SAVE, sizeof(save_t));
|
memoryZero(&SAVE, sizeof(save_t));
|
||||||
|
|
||||||
SAVE.deviceCurrent = 0xFF;// No current device.
|
SAVE.deviceCurrent = 0xFF;// No current device.
|
||||||
SAVE.slotCurrent = 0xFF;// No current slot.
|
|
||||||
|
|
||||||
// Initialize the slots and settings
|
// Initialize the settings
|
||||||
saveSettingsInit(&SAVE.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.
|
// Start by initializing each of the save devices.
|
||||||
savedevice_t *device = &SAVE.devices[0];
|
savedevice_t *device = &SAVE.devices[0];
|
||||||
@@ -67,10 +59,6 @@ errorret_t saveUpdate() {
|
|||||||
} else {
|
} else {
|
||||||
// Device was found, cache in the data.
|
// Device was found, cache in the data.
|
||||||
errorChain(saveLoadSettings());
|
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
|
// TEST: write settings back out immediately once a device is
|
||||||
// selected, to confirm the write path actually works on hardware.
|
// 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]);
|
SAVE.deviceCurrent = (uint8_t)(device - &SAVE.devices[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t saveCanSave() {
|
|
||||||
return CUTSCENE_SYSTEM.scene == NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t saveSaveSettings() {
|
errorret_t saveSaveSettings() {
|
||||||
assertTrue(SAVE.deviceCurrent != 0xFF, "No current device");
|
assertTrue(SAVE.deviceCurrent != 0xFF, "No current device");
|
||||||
|
|
||||||
@@ -204,69 +188,6 @@ errorret_t saveLoadSettings() {
|
|||||||
errorOk();
|
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() {
|
errorret_t saveDispose() {
|
||||||
// Dispose each device.
|
// Dispose each device.
|
||||||
savedevice_t *device = &SAVE.devices[0];
|
savedevice_t *device = &SAVE.devices[0];
|
||||||
|
|||||||
@@ -7,18 +7,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "savedevice.h"
|
#include "savedevice.h"
|
||||||
#include "save/slot/saveslot.h"
|
|
||||||
#include "save/settings/savesettings.h"
|
#include "save/settings/savesettings.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// File state
|
// File state
|
||||||
savesettings_t settings;
|
savesettings_t settings;
|
||||||
saveslotcache_t caches[SAVE_SLOT_COUNT];
|
|
||||||
saveslot_t slot;
|
|
||||||
|
|
||||||
uint8_t slotCurrent;
|
|
||||||
bool_t settingsDirty;
|
bool_t settingsDirty;
|
||||||
bool_t slotDirty;
|
|
||||||
|
|
||||||
// Device state
|
// Device state
|
||||||
savedevice_t devices[SAVE_DEVICE_COUNT];
|
savedevice_t devices[SAVE_DEVICE_COUNT];
|
||||||
@@ -67,14 +61,6 @@ void saveFindAvailableDevice(
|
|||||||
*/
|
*/
|
||||||
void saveOnDeviceAvailabilityChecked(savedevice_t *device, void *user);
|
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.
|
* Saves the current settings to the current device.
|
||||||
*
|
*
|
||||||
@@ -89,29 +75,6 @@ errorret_t saveSaveSettings();
|
|||||||
*/
|
*/
|
||||||
errorret_t saveLoadSettings();
|
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.
|
* Disposes of the save system.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -473,40 +473,6 @@ errorret_t saveDeviceRawFetchItem(
|
|||||||
|
|
||||||
#endif// defined(SAVE_DEVICE_DATA_RAW)
|
#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(
|
errorret_t saveDeviceSettingsWrite(
|
||||||
savedevice_t *device,
|
savedevice_t *device,
|
||||||
savesettings_t *settings
|
savesettings_t *settings
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
|
|
||||||
typedef struct savedevice_s savedevice_t;
|
typedef struct savedevice_s savedevice_t;
|
||||||
typedef struct savesettings_s savesettings_t;
|
typedef struct savesettings_s savesettings_t;
|
||||||
typedef struct saveslot_s saveslot_t;
|
|
||||||
|
|
||||||
typedef void (*savedevicestatecallback_t)(savedevice_t *device, void *user);
|
typedef void (*savedevicestatecallback_t)(savedevice_t *device, void *user);
|
||||||
|
|
||||||
@@ -102,34 +101,6 @@ void saveDeviceCheckAvailability(
|
|||||||
void *user
|
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.
|
* Writes the given save settings to the device.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -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
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "save/savedevice.h"
|
#include "save/savedevice.h"
|
||||||
#include "save/savedevicelinux.h"
|
#include "save/savedevicelinux.h"
|
||||||
#include "save/slot/saveslot.h"
|
|
||||||
#include "save/settings/savesettings.h"
|
#include "save/settings/savesettings.h"
|
||||||
#include "save/savejson.h"
|
#include "save/savejson.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
@@ -86,92 +85,6 @@ errorret_t saveDeviceLinuxGetSettingsPath(
|
|||||||
errorOk();
|
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(
|
errorret_t saveDeviceLinuxSettingsWrite(
|
||||||
savedevice_t *device,
|
savedevice_t *device,
|
||||||
savesettings_t *settings
|
savesettings_t *settings
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
// settings and each save slot as their own separate file.
|
// settings and each save slot as their own separate file.
|
||||||
#define SAVE_DEVICE_LINUX_SAVES_SUBDIR_NAME "saves"
|
#define SAVE_DEVICE_LINUX_SAVES_SUBDIR_NAME "saves"
|
||||||
#define SAVE_DEVICE_LINUX_SETTINGS_FILENAME "settings.json"
|
#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
|
#define SAVE_DEVICE_LINUX_PATH_MAX FILENAME_MAX
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -22,7 +21,6 @@ typedef struct {
|
|||||||
} savedeviceplatform_t;
|
} savedeviceplatform_t;
|
||||||
|
|
||||||
typedef struct savedevice_s savedevice_t;
|
typedef struct savedevice_s savedevice_t;
|
||||||
typedef struct saveslot_s saveslot_t;
|
|
||||||
typedef struct savesettings_s savesettings_t;
|
typedef struct savesettings_s savesettings_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,49 +69,6 @@ errorret_t saveDeviceLinuxGetSettingsPath(
|
|||||||
const size_t destSize
|
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.
|
* Writes the given save settings out to the settings file on disk.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,7 +14,5 @@
|
|||||||
#define saveDevicePlatformUpdate saveDeviceLinuxUpdate
|
#define saveDevicePlatformUpdate saveDeviceLinuxUpdate
|
||||||
#define saveDeviceCheckAvailabilityPlatform saveDeviceLinuxCheckAvailability
|
#define saveDeviceCheckAvailabilityPlatform saveDeviceLinuxCheckAvailability
|
||||||
#define saveDevicePlatformDispose saveDeviceLinuxDispose
|
#define saveDevicePlatformDispose saveDeviceLinuxDispose
|
||||||
#define saveDeviceSlotWritePlatform saveDeviceLinuxSlotWrite
|
|
||||||
#define saveDeviceSlotReadPlatform saveDeviceLinuxSlotRead
|
|
||||||
#define saveDeviceSettingsWritePlatform saveDeviceLinuxSettingsWrite
|
#define saveDeviceSettingsWritePlatform saveDeviceLinuxSettingsWrite
|
||||||
#define saveDeviceSettingsReadPlatform saveDeviceLinuxSettingsRead
|
#define saveDeviceSettingsReadPlatform saveDeviceLinuxSettingsRead
|
||||||
@@ -13,8 +13,6 @@ endif()
|
|||||||
add_subdirectory(error)
|
add_subdirectory(error)
|
||||||
add_subdirectory(thread)
|
add_subdirectory(thread)
|
||||||
add_subdirectory(display)
|
add_subdirectory(display)
|
||||||
add_subdirectory(rpg)
|
|
||||||
add_subdirectory(item)
|
|
||||||
add_subdirectory(save)
|
add_subdirectory(save)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
add_subdirectory(util)
|
add_subdirectory(util)
|
||||||
@@ -7,8 +7,6 @@ include(dusktest)
|
|||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
dusktest(test_savesettings.c)
|
dusktest(test_savesettings.c)
|
||||||
dusktest(test_saveslot.c)
|
|
||||||
target_sources(test_saveslot PRIVATE savetestfixture.c)
|
|
||||||
|
|
||||||
dusktest(test_savedevicelinux.c)
|
dusktest(test_savedevicelinux.c)
|
||||||
target_sources(test_savedevicelinux PRIVATE savetestfixture.c)
|
target_sources(test_savedevicelinux PRIVATE savetestfixture.c)
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
|
|
||||||
#include "savetestfixture.h"
|
#include "savetestfixture.h"
|
||||||
#include "assert/assert.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/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include <ftw.h>
|
#include <ftw.h>
|
||||||
@@ -25,51 +22,6 @@ static char_t SAVE_TEST_FIXTURE_HOME[PATH_MAX];
|
|||||||
static char_t SAVE_TEST_FIXTURE_REAL_HOME[PATH_MAX];
|
static char_t SAVE_TEST_FIXTURE_REAL_HOME[PATH_MAX];
|
||||||
static bool_t SAVE_TEST_FIXTURE_HAD_REAL_HOME;
|
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(
|
static int_t saveTestFixtureRemoveEntry(
|
||||||
const char *path,
|
const char *path,
|
||||||
const struct stat *statBuffer,
|
const struct stat *statBuffer,
|
||||||
@@ -97,8 +49,6 @@ int saveTestFixtureSetup(void **state) {
|
|||||||
|
|
||||||
assertTrue(setenv("HOME", SAVE_TEST_FIXTURE_HOME, 1) == 0, "setenv failed");
|
assertTrue(setenv("HOME", SAVE_TEST_FIXTURE_HOME, 1) == 0, "setenv failed");
|
||||||
|
|
||||||
saveTestFixtureStubPartyAsset();
|
|
||||||
|
|
||||||
errorret_t ret = saveInit();
|
errorret_t ret = saveInit();
|
||||||
assertTrue(errorIsOk(ret), "saveInit failed in test fixture");
|
assertTrue(errorIsOk(ret), "saveInit failed in test fixture");
|
||||||
|
|
||||||
@@ -106,8 +56,6 @@ int saveTestFixtureSetup(void **state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int saveTestFixtureTeardown(void **state) {
|
int saveTestFixtureTeardown(void **state) {
|
||||||
saveTestFixtureUnstubPartyAsset();
|
|
||||||
|
|
||||||
nftw(
|
nftw(
|
||||||
SAVE_TEST_FIXTURE_HOME, saveTestFixtureRemoveEntry, 16,
|
SAVE_TEST_FIXTURE_HOME, saveTestFixtureRemoveEntry, 16,
|
||||||
FTW_DEPTH | FTW_PHYS
|
FTW_DEPTH | FTW_PHYS
|
||||||
|
|||||||
@@ -35,21 +35,3 @@ int saveTestFixtureTeardown(void **state);
|
|||||||
* filesystem permission tricks. Must be called after saveTestFixtureSetup.
|
* filesystem permission tricks. Must be called after saveTestFixtureSetup.
|
||||||
*/
|
*/
|
||||||
void saveTestFixtureBlockSaveDirectory(void);
|
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);
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "dusktest.h"
|
#include "dusktest.h"
|
||||||
#include "savetestfixture.h"
|
#include "savetestfixture.h"
|
||||||
#include "save/save.h"
|
#include "save/save.h"
|
||||||
#include "save/savedevicelinux.h"
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -54,16 +53,10 @@ static void test_saveInit_setsDefaults(void **state) {
|
|||||||
// saveTestFixtureSetup already called the real saveInit() - just assert
|
// saveTestFixtureSetup already called the real saveInit() - just assert
|
||||||
// on the state it left behind.
|
// on the state it left behind.
|
||||||
assert_int_equal(SAVE.deviceCurrent, 0xFF);
|
assert_int_equal(SAVE.deviceCurrent, 0xFF);
|
||||||
assert_int_equal(SAVE.slotCurrent, 0xFF);
|
|
||||||
assert_false(SAVE.findingAvailableDevice);
|
assert_false(SAVE.findingAvailableDevice);
|
||||||
assert_false(SAVE.noAvailableDeviceFound);
|
assert_false(SAVE.noAvailableDeviceFound);
|
||||||
assert_true(SAVE.settings.audioMasterVolume == 1.0f);
|
assert_true(SAVE.settings.audioMasterVolume == 1.0f);
|
||||||
assert_int_equal(SAVE.devices[0].state, SAVE_DEVICE_STATE_UNKNOWN);
|
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);
|
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
|
// saveDispose
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -566,46 +385,6 @@ int main(void) {
|
|||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
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(
|
cmocka_unit_test_setup_teardown(
|
||||||
test_saveDispose_succeeds, saveTestFixtureSetup, saveTestFixtureTeardown
|
test_saveDispose_succeeds, saveTestFixtureSetup, saveTestFixtureTeardown
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
#include "dusktest.h"
|
#include "dusktest.h"
|
||||||
#include "savetestfixture.h"
|
#include "savetestfixture.h"
|
||||||
#include "save/savedevice.h"
|
#include "save/savedevice.h"
|
||||||
#include "save/slot/saveslot.h"
|
|
||||||
#include "save/settings/savesettings.h"
|
#include "save/settings/savesettings.h"
|
||||||
#include "save/save.h"
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|
||||||
@@ -150,28 +148,10 @@ static void test_saveDeviceCheckAvailability_resolvesSynchronouslyOnLinux(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// saveDeviceSlotWrite / saveDeviceSlotRead / saveDeviceSettingsWrite /
|
// saveDeviceSettingsWrite / saveDeviceSettingsRead - dispatch to the
|
||||||
// saveDeviceSettingsRead - dispatch to the platform's four hooks.
|
// 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) {
|
static void test_saveDeviceSettingsWrite_nullAsserts(void **state) {
|
||||||
savedevice_t device;
|
savedevice_t device;
|
||||||
savesettings_t settings;
|
savesettings_t settings;
|
||||||
@@ -190,32 +170,6 @@ static void test_saveDeviceSettingsRead_nullAsserts(void **state) {
|
|||||||
expect_assert_failure(saveDeviceSettingsRead(&device, NULL));
|
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) {
|
static void test_saveDeviceSettings_dispatchesToPlatform(void **state) {
|
||||||
savedevice_t device;
|
savedevice_t device;
|
||||||
errorret_t initRet = saveDeviceInit(&device);
|
errorret_t initRet = saveDeviceInit(&device);
|
||||||
@@ -303,14 +257,6 @@ int main(void) {
|
|||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
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(
|
cmocka_unit_test_setup_teardown(
|
||||||
test_saveDeviceSettingsWrite_nullAsserts,
|
test_saveDeviceSettingsWrite_nullAsserts,
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
saveTestFixtureSetup, saveTestFixtureTeardown
|
||||||
@@ -319,10 +265,6 @@ int main(void) {
|
|||||||
test_saveDeviceSettingsRead_nullAsserts,
|
test_saveDeviceSettingsRead_nullAsserts,
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
saveTestFixtureSetup, saveTestFixtureTeardown
|
||||||
),
|
),
|
||||||
cmocka_unit_test_setup_teardown(
|
|
||||||
test_saveDeviceSlot_dispatchesToPlatform,
|
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
|
||||||
),
|
|
||||||
cmocka_unit_test_setup_teardown(
|
cmocka_unit_test_setup_teardown(
|
||||||
test_saveDeviceSettings_dispatchesToPlatform,
|
test_saveDeviceSettings_dispatchesToPlatform,
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
saveTestFixtureSetup, saveTestFixtureTeardown
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
#include "savetestfixture.h"
|
#include "savetestfixture.h"
|
||||||
#include "save/savedevice.h"
|
#include "save/savedevice.h"
|
||||||
#include "save/savedevicelinux.h"
|
#include "save/savedevicelinux.h"
|
||||||
#include "save/slot/saveslot.h"
|
|
||||||
#include "save/settings/savesettings.h"
|
#include "save/settings/savesettings.h"
|
||||||
#include "save/save.h"
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -86,27 +84,6 @@ static void test_getSettingsPath_homeUnset_errors(void **state) {
|
|||||||
errorCatch(ret);
|
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
|
// saveDeviceLinuxCheckAvailability
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -175,105 +152,6 @@ static void test_checkAvailability_mkdirpFails(void **state) {
|
|||||||
assert_true(g_callbackFired);
|
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
|
// saveDeviceLinuxSettingsWrite / saveDeviceLinuxSettingsRead
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -381,14 +259,6 @@ int main(void) {
|
|||||||
test_getSettingsPath_homeUnset_errors,
|
test_getSettingsPath_homeUnset_errors,
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
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(
|
cmocka_unit_test_setup_teardown(
|
||||||
test_checkAvailability_wrongStateAsserts,
|
test_checkAvailability_wrongStateAsserts,
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
saveTestFixtureSetup, saveTestFixtureTeardown
|
||||||
@@ -410,25 +280,6 @@ int main(void) {
|
|||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
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(
|
cmocka_unit_test_setup_teardown(
|
||||||
test_settingsWrite_nullAsserts,
|
test_settingsWrite_nullAsserts,
|
||||||
saveTestFixtureSetup, saveTestFixtureTeardown
|
saveTestFixtureSetup, saveTestFixtureTeardown
|
||||||
|
|||||||
Reference in new issue
Block a user