diff --git a/include/akgl/SDL_GameControllerDB.h b/include/akgl/SDL_GameControllerDB.h index b033fbe..1419a9f 100644 --- a/include/akgl/SDL_GameControllerDB.h +++ b/include/akgl/SDL_GameControllerDB.h @@ -1,7 +1,7 @@ #ifndef _SDL_GAMECONTROLLERDB_H_ #define _SDL_GAMECONTROLLERDB_H_ -// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Tue Jun 2 01:14:32 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Tue Jun 2 04:45:53 PM EDT 2026 #define AKGL_SDL_GAMECONTROLLER_DB_LEN 2229 diff --git a/include/akgl/json_helpers.h b/include/akgl/json_helpers.h index 227f925..837c609 100644 --- a/include/akgl/json_helpers.h +++ b/include/akgl/json_helpers.h @@ -8,10 +8,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_object_value(json_t *obj, char akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_boolean_value(json_t *obj, char *key, bool *dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_integer_value(json_t *obj, char *key, int *dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_number_value(json_t *obj, char *key, float *dest); +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_double_value(json_t *obj, char *key, double *dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_string_value(json_t *obj, char *key, akgl_String **dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_value(json_t *obj, char *key, json_t **dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_object(json_t *array, int index, json_t **dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_integer(json_t *array, int index, int *dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_string(json_t *array, int index, akgl_String **dest); +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *e, void *defval, void *dest, uint32_t defsize); + #endif // _JSON_HELPERS_H_ diff --git a/include/akgl/physics.h b/include/akgl/physics.h index e5e33ca..d23c9a6 100644 --- a/include/akgl/physics.h +++ b/include/akgl/physics.h @@ -5,6 +5,7 @@ #include #include #include +#include typedef struct akgl_PhysicsBackend { akerr_ErrorContext AKERR_NOIGNORE *(*simulate)(struct akgl_PhysicsBackend *self, akgl_Iterator *opflags); @@ -33,7 +34,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_collide(akgl_PhysicsBacke akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_move(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt); akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend *self); - +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type); akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags); diff --git a/include/akgl/tilemap.h b/include/akgl/tilemap.h index a02a0fe..296207e 100644 --- a/include/akgl/tilemap.h +++ b/include/akgl/tilemap.h @@ -2,8 +2,9 @@ #define _TILEMAP_H_ #include -#include "actor.h" -#include "staticstring.h" +#include +#include +#include #include #define AKGL_TILEMAP_MAX_WIDTH 512 @@ -98,6 +99,10 @@ typedef struct { float p_rate; akgl_Tileset tilesets[AKGL_TILEMAP_MAX_TILESETS]; akgl_TilemapLayer layers[AKGL_TILEMAP_MAX_LAYERS]; + + // Different levels may have different physics. + bool use_own_physics; + akgl_PhysicsBackend physics; } akgl_Tilemap; akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load(char *fname, akgl_Tilemap *dest); diff --git a/src/json_helpers.c b/src/json_helpers.c index 9ad9308..b36a5ea 100644 --- a/src/json_helpers.c +++ b/src/json_helpers.c @@ -52,6 +52,17 @@ akerr_ErrorContext *akgl_get_json_number_value(json_t *obj, char *key, float *de SUCCEED_RETURN(errctx); } +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_double_value(json_t *obj, char *key, double *dest) +{ + PREPARE_ERROR(errctx); + FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference"); + json_t *value = json_object_get(obj, key); + FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key); + FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key); + *dest = json_number_value(value); + SUCCEED_RETURN(errctx); +} + akerr_ErrorContext *akgl_get_json_string_value(json_t *obj, char *key, akgl_String **dest) { json_t *value = NULL; @@ -129,3 +140,26 @@ akerr_ErrorContext *akgl_get_json_array_index_string(json_t *array, int index, a strncpy((char *)&(*dest)->data, json_string_value(value), AKGL_MAX_STRING_LENGTH); SUCCEED_RETURN(errctx); } + +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *err, void *defval, void *dest, uint32_t defsize) +{ + PREPARE_ERROR(e); + if ( err == NULL ) { + SUCCEED_RETURN(e); + } + int docopy = 0; + + FAIL_ZERO_RETURN(e, err, AKERR_NULLPOINTER, "err"); + FAIL_ZERO_RETURN(e, defval, AKERR_NULLPOINTER, "defval"); + FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest"); + + ATTEMPT { + } CLEANUP { + } PROCESS(err) { + } HANDLE_GROUP(err, AKERR_KEY) { + } HANDLE_GROUP(err, AKERR_INDEX) { + memcpy(dest, defval, defsize); + } FINISH(err, true); + + SUCCEED_RETURN(e); +} diff --git a/src/physics.c b/src/physics.c index af2e6d9..83d86be 100644 --- a/src/physics.c +++ b/src/physics.c @@ -47,14 +47,20 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_gravity(akgl_PhysicsBacke PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor"); - - // Assume the X origin is - (screen left) - actor->ex -= (self->gravity_x * dt); - // Assume Y origin is + (down screen) - actor->ey += (self->gravity_y * dt); - // Assume Z origin is - (behind the camera) - actor->ez -= (self->gravity_z * dt); + if ( self->gravity_x != 0 ) { + // Assume the X origin is - (screen left) + actor->ex -= (self->gravity_x * dt); + } + if ( self->gravity_y != 0 ) { + // Assume Y origin is + (down screen) + actor->ey += (self->gravity_y * dt); + } + if ( self->gravity_z != 0 ) { + // Assume Z origin is - (behind the camera) + actor->ez -= (self->gravity_z * dt); + } + SUCCEED_RETURN(e); } @@ -186,9 +192,15 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se PASS(e, self->gravity(self, actor, dt)); // Counteract velocity with atmospheric drag - actor->ex -= actor->ex * self->drag_x * dt; - actor->ey -= actor->ey * self->drag_y * dt; - actor->ez -= actor->ez * self->drag_z * dt; + if ( self->drag_x != 0 ) { + actor->ex -= actor->ex * self->drag_x * dt; + } + if ( self->drag_y != 0 ) { + actor->ey -= actor->ey * self->drag_y * dt; + } + if ( self->drag_z != 0 ) { + actor->ez -= actor->ez * self->drag_z * dt; + } actor->vx = actor->ex + actor->tx; actor->vy = actor->ey + actor->ty; @@ -204,3 +216,21 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se self->gravity_time = curtime; SUCCEED_RETURN(e); } + +akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type) +{ + uint32_t hashval; + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + FAIL_ZERO_RETURN(e, type, AKERR_NULLPOINTER, "type"); + + if ( strncmp(type->data, "null", 4) == 0) { + PASS(e, akgl_physics_init_null(self)); + SUCCEED_RETURN(e); + } + if ( strncmp(type->data, "arcade", 6) == 0) { + PASS(e, akgl_physics_init_arcade(self)); + SUCCEED_RETURN(e); + } + FAIL_RETURN(e, AKERR_KEY, "Invalid physics engine %s", type->data); +} diff --git a/src/tilemap.c b/src/tilemap.c index 810c4a6..dd3483d 100644 --- a/src/tilemap.c +++ b/src/tilemap.c @@ -64,15 +64,11 @@ akerr_ErrorContext *akgl_get_json_properties_string(json_t *obj, char *key, akgl PREPARE_ERROR(errctx); json_t *property; - ATTEMPT { - CATCH(errctx, akgl_get_json_tilemap_property(obj, key, "string", &property)); - CATCH(errctx, akgl_heap_next_string(dest)); - CATCH(errctx, akgl_string_initialize(*dest, NULL)); - CATCH(errctx, akgl_get_json_string_value(property, "value", dest)); - } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true); - + PASS(errctx, akgl_get_json_tilemap_property(obj, key, "string", &property)); + PASS(errctx, akgl_heap_next_string(dest)); + PASS(errctx, akgl_string_initialize(*dest, NULL)); + PASS(errctx, akgl_get_json_string_value(property, "value", dest)); + SUCCEED_RETURN(errctx); } @@ -80,12 +76,8 @@ akerr_ErrorContext *akgl_get_json_properties_integer(json_t *obj, char *key, int { PREPARE_ERROR(errctx); json_t *property = NULL; - ATTEMPT { - CATCH(errctx, akgl_get_json_tilemap_property(obj, key, "int", &property)); - CATCH(errctx, akgl_get_json_integer_value(property, "value", dest)); - } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true); + PASS(errctx, akgl_get_json_tilemap_property(obj, key, "int", &property)); + PASS(errctx, akgl_get_json_integer_value(property, "value", dest)); SUCCEED_RETURN(errctx); } @@ -94,12 +86,8 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa { PREPARE_ERROR(errctx); json_t *property = NULL; - ATTEMPT { - CATCH(errctx, akgl_get_json_tilemap_property(obj, key, "number", &property)); - CATCH(errctx, akgl_get_json_number_value(property, "value", dest)); - } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true); + PASS(errctx, akgl_get_json_tilemap_property(obj, key, "number", &property)); + PASS(errctx, akgl_get_json_number_value(property, "value", dest)); SUCCEED_RETURN(errctx); } @@ -108,12 +96,18 @@ akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float { PREPARE_ERROR(errctx); json_t *property = NULL; - ATTEMPT { - CATCH(errctx, akgl_get_json_tilemap_property(obj, key, "float", &property)); - CATCH(errctx, akgl_get_json_number_value(property, "value", dest)); - } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true); + PASS(errctx, akgl_get_json_tilemap_property(obj, key, "float", &property)); + PASS(errctx, akgl_get_json_number_value(property, "value", dest)); + + SUCCEED_RETURN(errctx); +} + +akerr_ErrorContext *akgl_get_json_properties_double(json_t *obj, char *key, double *dest) +{ + PREPARE_ERROR(errctx); + json_t *property = NULL; + PASS(errctx, akgl_get_json_tilemap_property(obj, key, "float", &property)); + PASS(errctx, akgl_get_json_double_value(property, "value", dest)); SUCCEED_RETURN(errctx); } @@ -297,41 +291,36 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t * FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination tilemap reference"); FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "NULL tilemap root reference"); - ATTEMPT { - CATCH(errctx, akgl_get_json_array_value(root, "objects", &layerdata)); - len = json_array_size((json_t *)layerdata); - curlayer = &dest->layers[layerid]; - for ( j = 0; j < len; j++ ) { - CATCH(errctx, akgl_get_json_array_index_object((json_t *)layerdata, j, &layerdatavalue)); - curobj = &curlayer->objects[j]; - CATCH(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "name", &tmpstr)); - strncpy((char *)curobj->name, tmpstr->data, AKGL_ACTOR_MAX_NAME_LENGTH); - CATCH(errctx, akgl_heap_release_string(tmpstr)); - CATCH(errctx, akgl_get_json_number_value((json_t *)layerdatavalue, "x", &curobj->x)); - CATCH(errctx, akgl_get_json_number_value((json_t *)layerdatavalue, "y", &curobj->y)); - CATCH(errctx, akgl_get_json_boolean_value((json_t *)layerdatavalue, "visible", &curobj->visible)); - - CATCH(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "type", &tmpstr)); - if ( strcmp(tmpstr->data, "actor") == 0 ) { - CATCH(errctx, akgl_tilemap_load_layer_object_actor(curobj, layerdatavalue, layerid, dirname)); - } else if ( strcmp(tmpstr->data, "perspective") == 0 ) { - curobj->visible = false; - if ( strcmp((char *)curobj->name, "p_foreground") == 0 ) { - dest->p_foreground_y = curobj->y; - CATCH(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_foreground_h)); - CATCH(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_foreground_scale)); - } else if ( strcmp((char *)curobj->name, "p_vanishing") == 0 ) { - dest->p_vanishing_y = curobj->y; - CATCH(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_vanishing_h)); - CATCH(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_vanishing_scale)); - } - } - - layerdatavalue = NULL; - } - } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true); + PASS(errctx, akgl_get_json_array_value(root, "objects", &layerdata)); + len = json_array_size((json_t *)layerdata); + curlayer = &dest->layers[layerid]; + for ( j = 0; j < len; j++ ) { + PASS(errctx, akgl_get_json_array_index_object((json_t *)layerdata, j, &layerdatavalue)); + curobj = &curlayer->objects[j]; + PASS(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "name", &tmpstr)); + strncpy((char *)curobj->name, tmpstr->data, AKGL_ACTOR_MAX_NAME_LENGTH); + PASS(errctx, akgl_heap_release_string(tmpstr)); + PASS(errctx, akgl_get_json_number_value((json_t *)layerdatavalue, "x", &curobj->x)); + PASS(errctx, akgl_get_json_number_value((json_t *)layerdatavalue, "y", &curobj->y)); + PASS(errctx, akgl_get_json_boolean_value((json_t *)layerdatavalue, "visible", &curobj->visible)); + + PASS(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "type", &tmpstr)); + if ( strcmp(tmpstr->data, "actor") == 0 ) { + PASS(errctx, akgl_tilemap_load_layer_object_actor(curobj, layerdatavalue, layerid, dirname)); + } else if ( strcmp(tmpstr->data, "perspective") == 0 ) { + curobj->visible = false; + if ( strcmp((char *)curobj->name, "p_foreground") == 0 ) { + dest->p_foreground_y = curobj->y; + PASS(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_foreground_h)); + PASS(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_foreground_scale)); + } else if ( strcmp((char *)curobj->name, "p_vanishing") == 0 ) { + dest->p_vanishing_y = curobj->y; + PASS(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_vanishing_h)); + PASS(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_vanishing_scale)); + } + } + layerdatavalue = NULL; + } SUCCEED_RETURN(errctx); } @@ -451,6 +440,88 @@ akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, a SUCCEED_RETURN(errctx); } +akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root) +{ + PREPARE_ERROR(e); + json_t *props = NULL; + akgl_String *tmpval = NULL; + double defzero = 0.0; + + ATTEMPT { + CATCH(e, akgl_heap_next_string(&tmpval)); + CATCH(e, akgl_get_json_array_value((json_t *)root, "properties", &props)); + } CLEANUP { + IGNORE(akgl_heap_release_string(tmpval)); + } PROCESS(e) { + } HANDLE(e, AKERR_KEY) { + // Map has no properties, do nothing + SDL_Log("Map has no properties"); + SUCCEED_RETURN(e); + } FINISH(e, true); + + ATTEMPT { + CATCH(e, akgl_heap_next_string(&tmpval)); + CATCH(e, akgl_get_json_properties_string( + root, + "physics.model", + &tmpval + ) + ); + PASS(e, akgl_physics_factory(&dest->physics, tmpval)); + dest->use_own_physics = true; + + CATCH(e, akgl_get_json_with_default( + akgl_get_json_properties_double( + root, "physics.gravity.x", &dest->physics.gravity_x + ), + (void *)&defzero, + (void *)&dest->physics.gravity_x, + sizeof(double))); + CATCH(e, akgl_get_json_with_default( + akgl_get_json_properties_double( + root, "physics.gravity.y", &dest->physics.gravity_y + ), + (void *)&defzero, + (void *)&dest->physics.gravity_y, + sizeof(double))); + CATCH(e, akgl_get_json_with_default( + akgl_get_json_properties_double( + root, "physics.gravity.z", &dest->physics.gravity_z + ), + (void *)&defzero, + (void *)&dest->physics.gravity_z, + sizeof(double))); + CATCH(e, akgl_get_json_with_default( + akgl_get_json_properties_double( + root, "physics.drag.x", &dest->physics.drag_x + ), + (void *)&defzero, + (void *)&dest->physics.drag_x, + sizeof(double))); + CATCH(e, akgl_get_json_with_default( + akgl_get_json_properties_double( + root, "physics.drag.y", &dest->physics.drag_y + ), + (void *)&defzero, + (void *)&dest->physics.drag_y, + sizeof(double))); + CATCH(e, akgl_get_json_with_default( + akgl_get_json_properties_double( + root, "physics.drag.z", &dest->physics.drag_z + ), + (void *)&defzero, + (void *)&dest->physics.drag_z, + sizeof(double))); + } CLEANUP { + IGNORE(akgl_heap_release_string(tmpval)); + } PROCESS(e) { + } HANDLE(e, AKERR_KEY) { + SDL_Log("Map uses game physics"); + } FINISH(e, true); + + SUCCEED_RETURN(e); +} + akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest) { PREPARE_ERROR(errctx); @@ -484,6 +555,7 @@ akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest) error.line, error.text ); + CATCH(errctx, akgl_tilemap_load_physics(dest, json)); CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "tileheight", &dest->tileheight)); CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "tilewidth", &dest->tilewidth)); CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "height", &dest->height));