implement game configuration file

this integrates https://github.com/cktan/tomlc99 into the repo as a dependency
This commit is contained in:
2024-09-30 21:13:58 -03:00
committed by veclavtalica
parent ec15d8ec4b
commit 57fe5e8946
165 changed files with 4797 additions and 92 deletions

View File

@ -9,6 +9,7 @@
#include <SDL2/SDL.h>
#include <physfs.h>
#include <stb_ds.h>
#include <toml.h>
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
@ -193,14 +194,78 @@ static bool initialize(void) {
return false;
}
/* debug mode defaults to being enabled on debug builds. */
/* pass --debug to enable it on release builds */
/* or, on debug builds, pass --release to disable it */
#ifndef NDEBUG
ctx.game.debug = true;
#else
ctx.game.debug = false;
#endif
/* first things first, most things here will be loaded from the config file */
/* it's expected to be present in the data directory, no matter what */
/* that is why PhysicsFS is initialized before anything else */
toml_set_memutil(SDL_malloc, SDL_free);
/* load the config file into an opaque table */
{
char *config_file = file_to_str("/twn.toml");
if (config_file == NULL) {
CRY_PHYSFS("Configuration file loading failed");
goto fail;
}
char errbuf[256]; /* tomlc99 example implies that this is enough... */
ctx.config_table = toml_parse(config_file, errbuf, sizeof errbuf);
SDL_free(config_file);
if (ctx.config_table == NULL) {
CRY("Configuration file loading failed", errbuf);
goto fail;
}
}
/* at this point we can save references to the tables within the parent one for later */
toml_table_t *about = toml_table_in(ctx.config_table, "about");
if (about == NULL) {
CRY("Initialization failed", "[about] table expected in configuration file");
goto fail;
}
toml_table_t *game = toml_table_in(ctx.config_table, "game");
if (game == NULL) {
CRY("Initialization failed", "[game] table expected in configuration file");
goto fail;
}
toml_table_t *engine = toml_table_in(ctx.config_table, "engine");
if (engine == NULL) {
CRY("Initialization failed", "[engine] table expected in configuration file");
goto fail;
}
/* configure physicsfs write dir */
{
toml_datum_t datum_dev_id = toml_string_in(about, "dev_id");
if (!datum_dev_id.ok) {
CRY("Initialization failed", "Valid about.dev_id expected in configuration file");
goto fail;
}
toml_datum_t datum_app_id = toml_string_in(about, "app_id");
if (!datum_app_id.ok) {
CRY("Initialization failed", "Valid about.app_id expected in configuration file");
goto fail;
}
if (!PHYSFS_setSaneConfig(datum_dev_id.u.s, datum_app_id.u.s, PACKAGE_EXTENSION, false, true)) {
CRY_PHYSFS("Filesystem initialization failed");
goto fail;
}
SDL_free(datum_dev_id.u.s);
SDL_free(datum_app_id.u.s);
}
/* debug mode defaults to being enabled */
/* pass --debug or --release to force a mode, ignoring configuration */
toml_datum_t datum_debug = toml_bool_in(game, "debug");
if (!datum_debug.ok) {
ctx.game.debug = true;
} else {
ctx.game.debug = datum_debug.u.b;
}
#ifdef EMSCRIPTEN
/* emscripten interpretes those as GL ES version against WebGL */
@ -225,14 +290,48 @@ static bool initialize(void) {
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
/* init got far enough to create a window */
ctx.window = SDL_CreateWindow("townengine",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
RENDER_BASE_WIDTH,
RENDER_BASE_HEIGHT,
// SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_RESIZABLE |
SDL_WINDOW_OPENGL);
{
toml_datum_t datum_title = toml_string_in(about, "title");
if (!datum_title.ok) {
CRY("Initialization failed", "Valid about.title expected in configuration file");
goto fail;
}
/* not yet used
toml_datum_t datum_developer = toml_string_in(about, "developer");
if (!datum_developer.ok) {
CRY("Initialization failed", "Valid about.developer expected in configuration file");
goto fail;
}
*/
toml_datum_t datum_base_render_width = toml_int_in(game, "base_render_width");
if (!datum_base_render_width.ok) {
CRY("Initialization failed", "Valid game.base_render_width expected in configuration file");
goto fail;
}
ctx.base_render_width = datum_base_render_width.u.i;
ctx.game.base_draw_w = (int)ctx.base_render_width;
toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
if (!datum_base_render_height.ok) {
CRY("Initialization failed", "Valid game.base_render_height expected in configuration file");
goto fail;
}
ctx.base_render_height = datum_base_render_height.u.i;
ctx.game.base_draw_h = (int)ctx.base_render_height;
ctx.window = SDL_CreateWindow(datum_title.u.s,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
(int)datum_base_render_width.u.i,
(int)datum_base_render_height.u.i,
//SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_RESIZABLE |
SDL_WINDOW_OPENGL);
SDL_free(datum_title.u.s);
//SDL_free(datum_developer.u.s);
}
if (ctx.window == NULL) {
CRY_SDL("Window creation failed.");
goto fail;
@ -270,12 +369,12 @@ static bool initialize(void) {
/* might need this to have multiple windows */
ctx.window_id = SDL_GetWindowID(ctx.window);
glViewport(0, 0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT);
glViewport(0, 0, ctx.base_render_width, ctx.base_render_height);
/* TODO: */
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
ctx.game.window_w = RENDER_BASE_WIDTH;
ctx.game.window_h = RENDER_BASE_HEIGHT;
ctx.game.window_w = (int)ctx.base_render_width;
ctx.game.window_h = (int)ctx.base_render_height;
/* audio initialization */
{
@ -297,15 +396,6 @@ static bool initialize(void) {
}
/* filesystem time */
/* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */
if (!PHYSFS_init(ctx.argv[0]) ||
!PHYSFS_setSaneConfig(ORGANIZATION_NAME, APP_NAME, PACKAGE_EXTENSION, false, true))
{
CRY_PHYSFS("Filesystem initialization failed.");
goto fail;
}
/* you could change this at runtime if you wanted */
ctx.game.update_multiplicity = 1;
@ -325,11 +415,22 @@ static bool initialize(void) {
stbds_rand_seed(ctx.game.random_seed);
/* main loop machinery */
toml_datum_t datum_ticks_per_second = toml_int_in(engine, "ticks_per_second");
if (!datum_ticks_per_second.ok) {
ctx.ticks_per_second = TICKS_PER_SECOND_DEFAULT;
} else {
if (datum_ticks_per_second.u.i < 8) {
ctx.ticks_per_second = TICKS_PER_SECOND_DEFAULT;
} else {
ctx.ticks_per_second = datum_ticks_per_second.u.i;
}
}
ctx.game.is_running = true;
ctx.resync_flag = true;
ctx.clocks_per_second = SDL_GetPerformanceFrequency();
ctx.prev_frame_time = SDL_GetPerformanceCounter();
ctx.desired_frametime = ctx.clocks_per_second / TICKS_PER_SECOND;
ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second;
ctx.frame_accumulator = 0;
ctx.game.tick_count = 0;
@ -340,6 +441,56 @@ static bool initialize(void) {
}
/* rendering */
/* configuration */
{
toml_datum_t datum_texture_atlas_size = toml_int_in(engine, "texture_atlas_size");
if (!datum_texture_atlas_size.ok) {
ctx.texture_atlas_size = TEXTURE_ATLAS_SIZE_DEFAULT;
} else {
if (datum_texture_atlas_size.u.i < 32) {
ctx.texture_atlas_size = TEXTURE_ATLAS_SIZE_DEFAULT;
} else {
ctx.texture_atlas_size = datum_texture_atlas_size.u.i;
}
}
toml_datum_t datum_font_texture_size = toml_int_in(engine, "font_texture_size");
if (!datum_font_texture_size.ok) {
ctx.font_texture_size = TEXT_FONT_TEXTURE_SIZE_DEFAULT;
} else {
if (datum_font_texture_size.u.i < 1024) {
ctx.font_texture_size = TEXT_FONT_TEXTURE_SIZE_DEFAULT;
} else {
ctx.font_texture_size = datum_font_texture_size.u.i;
}
}
toml_datum_t datum_font_oversampling = toml_int_in(engine, "font_oversampling");
if (!datum_font_oversampling.ok) {
ctx.font_oversampling = TEXT_FONT_OVERSAMPLING_DEFAULT;
} else {
if (datum_font_oversampling.u.i < 0) {
ctx.font_oversampling = TEXT_FONT_OVERSAMPLING_DEFAULT;
} else {
ctx.font_oversampling = datum_font_oversampling.u.i;
}
}
toml_datum_t datum_font_filtering = toml_string_in(engine, "font_filtering");
if (!datum_font_filtering.ok) {
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
} else {
if (SDL_strcmp(datum_font_filtering.u.s, "nearest") == 0) {
ctx.font_filtering = TEXTURE_FILTER_NEAREAST;
} else if (SDL_strcmp(datum_font_filtering.u.s, "linear") == 0) {
ctx.font_filtering = TEXTURE_FILTER_LINEAR;
} else {
ctx.font_filtering = TEXT_FONT_FILTERING_DEFAULT;
}
}
SDL_free(datum_font_filtering.u.s);
}
/* these are dynamic arrays and will be allocated lazily by stb_ds */
ctx.render_queue_2d = NULL;
ctx.uncolored_mesh_batches = NULL;
@ -348,6 +499,16 @@ static bool initialize(void) {
text_cache_init(&ctx.text_cache);
/* input */
toml_datum_t datum_keybind_slots = toml_int_in(engine, "keybind_slots");
if (!datum_keybind_slots.ok) {
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
} else {
if (datum_keybind_slots.u.i < 1) {
ctx.keybind_slots = KEYBIND_SLOTS_DEFAULT;
} else {
ctx.keybind_slots = datum_keybind_slots.u.i;
}
}
input_state_init(&ctx.game.input);
/* scripting */
@ -377,6 +538,7 @@ static void clean_up(void) {
arrfree(ctx.render_queue_2d);
toml_free(ctx.config_table);
PHYSFS_deinit();
SDL_Quit();
}
@ -392,11 +554,35 @@ int enter_loop(int argc, char **argv) {
ctx.argc = argc;
ctx.argv = argv;
if (!initialize())
/* needs to be done before anything else so config can be loaded */
/* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */
if (!PHYSFS_init(ctx.argv[0]) || !PHYSFS_mount(PHYSFS_getBaseDir(), NULL, true)) {
CRY_PHYSFS("Filesystem initialization failed");
return EXIT_FAILURE;
}
/* base dir is fine, but we'd like to look into all the data archives, too */
char **files_here = PHYSFS_enumerateFiles("/");
if (files_here == NULL) {
CRY_PHYSFS("Filesystem initialization failed");
return EXIT_FAILURE;
}
for (char **ptr = files_here; *ptr != NULL; ++ptr) {
char *file = *ptr;
if (!strends(file, "." PACKAGE_EXTENSION)) {
continue;
}
if (!PHYSFS_mount(file, "/", true)) {
CRY_PHYSFS("Filesystem initialization failed");
return EXIT_FAILURE;
}
}
PHYSFS_freeList(files_here);
/* process arguments */
bool force_debug = false;
bool force_release = false;
for (int i = 1; i < argc; ++i) {
/* override data directory */
if (SDL_strcmp(argv[i], "--data-dir") == 0) {
@ -415,17 +601,29 @@ int enter_loop(int argc, char **argv) {
/* force debug mode */
if (SDL_strcmp(argv[i], "--debug") == 0) {
ctx.game.debug = true;
force_debug = true;
continue;
}
/* force release mode */
if (SDL_strcmp(argv[i], "--release") == 0) {
ctx.game.debug = false;
force_release = false;
continue;
}
}
if (!initialize())
return EXIT_FAILURE;
/* good time to override anything that was set in initialize() */
if (force_debug) {
ctx.game.debug = true;
}
if (force_release) {
ctx.game.debug = false;
}
/* now we can actually start doing stuff */
game_object_load();
ctx.was_successful = true;
@ -440,6 +638,7 @@ int enter_loop(int argc, char **argv) {
main_loop();
}
/* loop is over */
game_object_unload();
clean_up();