2026-05-24 09:51:58 -04:00
|
|
|
#include <string.h>
|
|
|
|
|
#include <libgen.h>
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
|
|
|
|
#include <jansson.h>
|
2026-05-24 09:51:58 -04:00
|
|
|
|
2026-01-05 08:58:06 -05:00
|
|
|
#include <akerror.h>
|
2026-05-24 09:51:58 -04:00
|
|
|
#include <akstdlib.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/tilemap.h>
|
|
|
|
|
#include <akgl/actor.h>
|
|
|
|
|
#include <akgl/json_helpers.h>
|
|
|
|
|
#include <akgl/heap.h>
|
|
|
|
|
#include <akgl/registry.h>
|
|
|
|
|
#include <akgl/staticstring.h>
|
|
|
|
|
#include <akgl/game.h>
|
2026-05-24 19:57:43 -04:00
|
|
|
#include <akgl/util.h>
|
2026-05-24 09:51:58 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_get_json_tilemap_property(json_t *obj, char *key, char *type, json_t **dest)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *properties = NULL;
|
|
|
|
|
json_t *property = NULL;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstr = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
int i = 0;
|
|
|
|
|
// This is not a generic JSON helper. It assumes we are receiving an object with a 'properties' key
|
|
|
|
|
// inside of it. That key is an array of objects, and each object has a name, type, and value.
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL json obj reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL key string");
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_value(obj, "properties", &properties));
|
2025-08-03 10:07:35 -04:00
|
|
|
for (i = 0; i < json_array_size(properties); i++) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_object(properties, i, &property));
|
|
|
|
|
CATCH(errctx, akgl_get_json_string_value(property, "name", &tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( strcmp(tmpstr->data, key) != 0 ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_release_string(tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_string_value(property, "type", &tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( strcmp(tmpstr->data, type) != 0 ) {
|
2026-05-13 23:36:49 -04:00
|
|
|
FAIL_BREAK(errctx, AKERR_TYPE, "Object property is present but is incorrect type");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
*dest = property;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( tmpstr != NULL ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_RETURN(errctx, AKERR_KEY, "Property not found in properties map");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_get_json_properties_string(json_t *obj, char *key, akgl_String **dest)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *property;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
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));
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_get_json_properties_integer(json_t *obj, char *key, int *dest)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *property = NULL;
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_tilemap_property(obj, key, "int", &property));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value(property, "value", dest));
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 23:36:49 -04:00
|
|
|
akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, float *dest)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx, akgl_String *dirname)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
2026-05-24 19:57:43 -04:00
|
|
|
PREPARE_ERROR(e);
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstr = NULL;
|
2026-05-24 19:57:43 -04:00
|
|
|
akgl_String *tmppath = NULL;
|
|
|
|
|
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "columns", &dest->tilesets[tsidx].columns));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "firstgid", &dest->tilesets[tsidx].firstgid));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "imageheight", &dest->tilesets[tsidx].imageheight));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "imagewidth", &dest->tilesets[tsidx].imagewidth));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "margin", &dest->tilesets[tsidx].margin));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "spacing", &dest->tilesets[tsidx].spacing));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "tilecount", &dest->tilesets[tsidx].tilecount));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "tileheight", &dest->tilesets[tsidx].tileheight));
|
|
|
|
|
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "tilewidth", &dest->tilesets[tsidx].tilewidth));
|
|
|
|
|
|
|
|
|
|
PASS(e, akgl_get_json_string_value((json_t *)tileset, "name", &tmpstr));
|
|
|
|
|
PASS(e, akgl_heap_next_string(&tmpstr));
|
|
|
|
|
PASS(e, akgl_heap_next_string(&tmppath));
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
|
|
|
|
strncpy((char *)&dest->tilesets[tsidx].name,
|
|
|
|
|
(char *)&tmpstr->data,
|
2026-05-06 23:18:42 -04:00
|
|
|
AKGL_TILEMAP_MAX_TILESET_NAME_SIZE
|
2025-08-03 10:07:35 -04:00
|
|
|
);
|
2026-05-24 19:57:43 -04:00
|
|
|
|
|
|
|
|
CATCH(e, akgl_get_json_string_value((json_t *)tileset, "image", &tmpstr));
|
|
|
|
|
CATCH(e, akgl_path_relative((char *)&dirname->data, (char *)&tmpstr->data, tmppath));
|
|
|
|
|
strncpy((char *)&dest->tilesets[tsidx].imagefilename, tmppath->data, AKGL_MAX_STRING_LENGTH);
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
2026-05-24 19:57:43 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstr));
|
|
|
|
|
IGNORE(akgl_heap_release_string(tmppath));
|
|
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH(e, true);
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-24 19:57:43 -04:00
|
|
|
dest->tilesets[tsidx].texture = IMG_LoadTexture(renderer, (char *)&dest->tilesets[tsidx].imagefilename);
|
|
|
|
|
FAIL_ZERO_RETURN(e, dest->tilesets[tsidx].texture, AKERR_NULLPOINTER, "Failed loading tileset image : %s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(e);
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int tilesetidx)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
int x_offset = 0;
|
|
|
|
|
int y_offset = 0;
|
|
|
|
|
int x_col = 0;
|
|
|
|
|
int y_col = 0;
|
|
|
|
|
int j = 0;
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
/* FIXME: THIS DOES NOT PROPERLY ACCOUNT FOR MARGINS
|
|
|
|
|
* It should be possible to make it work easily I just didn't feel like accounting for them in the
|
|
|
|
|
* initial math.
|
|
|
|
|
*/
|
|
|
|
|
/*SDL_Log("Tileset %s has %d rows %d columns",
|
|
|
|
|
dest->tilesets[tilesetidx].name,
|
|
|
|
|
(dest->tilesets[tilesetidx].tilecount / dest->tilesets[tilesetidx].columns),
|
|
|
|
|
dest->tilesets[tilesetidx].columns);*/
|
|
|
|
|
for (j = 0; j <= (dest->tilesets[tilesetidx].tilecount); j++) {
|
|
|
|
|
/*
|
|
|
|
|
* For a given 8x2 tilemap like this with 10x10 tiles and 0 spacing and 0 margin
|
|
|
|
|
*
|
|
|
|
|
* 01234567
|
|
|
|
|
* 89ABCDEF
|
|
|
|
|
*
|
|
|
|
|
* tile 0 would be offset (0,0)
|
|
|
|
|
* tile 4 would be offset (40,1)
|
|
|
|
|
* tile 7 would be offset (70,1)
|
|
|
|
|
* tile 8 would be offset (1,8)
|
|
|
|
|
* tile C would be offset (40,8)
|
|
|
|
|
* tile F would be offset (70,8)
|
|
|
|
|
*/
|
|
|
|
|
if ( j >= dest->tilesets[tilesetidx].columns ) {
|
|
|
|
|
x_col = (j % dest->tilesets[tilesetidx].columns);
|
|
|
|
|
y_col = (j / dest->tilesets[tilesetidx].columns);
|
|
|
|
|
x_offset = x_col * (dest->tilesets[tilesetidx].tilewidth + dest->tilesets[tilesetidx].spacing);
|
|
|
|
|
y_offset = y_col * (dest->tilesets[tilesetidx].tileheight + dest->tilesets[tilesetidx].spacing);
|
|
|
|
|
} else {
|
|
|
|
|
x_col = j;
|
|
|
|
|
y_col = 0;
|
|
|
|
|
x_offset = (j * (dest->tilesets[tilesetidx].tilewidth + dest->tilesets[tilesetidx].spacing));
|
|
|
|
|
y_offset = dest->tilesets[tilesetidx].spacing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dest->tilesets[tilesetidx].tile_offsets[j][0] = x_offset;
|
|
|
|
|
dest->tilesets[tilesetidx].tile_offsets[j][1] = y_offset;
|
|
|
|
|
/* SDL_Log("Tileset %s index (%d, %d) is offset (%d, %d)",
|
|
|
|
|
dest->tilesets[tilesetidx].name,
|
|
|
|
|
x_col,
|
|
|
|
|
y_col,
|
|
|
|
|
x_offset,
|
|
|
|
|
y_offset);*/
|
|
|
|
|
// SDL_Log("Processed %d total tiles for tileset", j);
|
|
|
|
|
}
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root, akgl_String *dirname)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "Received NULL tilemap pointer");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "Received NULL json object pointer");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
json_t *tilesets = NULL;
|
|
|
|
|
json_t *jstileset = NULL;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
dest->numtilesets = 0;
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_value(root, "tilesets", &tilesets))
|
2025-08-03 10:07:35 -04:00
|
|
|
for (i = 0; i < json_array_size((json_t *)tilesets); i++) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_object((json_t *)tilesets, i, &jstileset));
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_load_tilesets_each(jstileset, dest, i, dirname));
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(dest, i));
|
2025-08-03 10:07:35 -04:00
|
|
|
dest->numtilesets += 1;
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstr = NULL;
|
|
|
|
|
akgl_Actor *actorobj = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
curobj->type = AKGL_TILEMAP_OBJECT_TYPE_ACTOR;
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( strlen((char *)&curobj->name) == 0 ) {
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_RETURN(errctx, AKERR_KEY, "Actor in tile object layer cannot have empty name");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
|
|
|
|
actorobj = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, (char *)&curobj->name, NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( actorobj == NULL ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_actor(&actorobj));
|
|
|
|
|
CATCH(errctx, akgl_actor_initialize((akgl_Actor *)actorobj, (char *)&curobj->name));
|
|
|
|
|
CATCH(errctx, akgl_get_json_properties_string((json_t *)layerdatavalue, "character", &tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
CATCH(errctx,
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_actor_set_character(
|
|
|
|
|
(akgl_Actor *)actorobj,
|
2025-08-03 10:07:35 -04:00
|
|
|
(char *)&tmpstr->data
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
actorobj->refcount += 1;
|
|
|
|
|
}
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_properties_integer((json_t *)layerdatavalue, "state", &actorobj->state));
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
if ( tmpstr != NULL ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
actorobj->layer = layerid;
|
|
|
|
|
actorobj->x = curobj->x;
|
|
|
|
|
actorobj->y = curobj->y;
|
|
|
|
|
actorobj->visible = curobj->visible;
|
2026-05-06 23:18:42 -04:00
|
|
|
curobj->actorptr = (akgl_Actor *)actorobj;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *layerdata = NULL;
|
|
|
|
|
json_t *layerdatavalue = NULL;
|
|
|
|
|
int j;
|
|
|
|
|
int len;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_TilemapLayer *curlayer = NULL;
|
|
|
|
|
akgl_TilemapObject *curobj = NULL;
|
|
|
|
|
akgl_String *tmpstr = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination tilemap reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "NULL tilemap root reference");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_value(root, "objects", &layerdata));
|
2025-08-03 10:07:35 -04:00
|
|
|
len = json_array_size((json_t *)layerdata);
|
|
|
|
|
curlayer = &dest->layers[layerid];
|
|
|
|
|
for ( j = 0; j < len; j++ ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_object((json_t *)layerdata, j, &layerdatavalue));
|
2025-08-03 10:07:35 -04:00
|
|
|
curobj = &curlayer->objects[j];
|
2026-05-06 23:18:42 -04:00
|
|
|
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));
|
2026-05-13 09:57:24 -04:00
|
|
|
CATCH(errctx, akgl_get_json_boolean_value((json_t *)layerdatavalue, "visible", &curobj->visible));
|
2026-05-06 23:18:42 -04:00
|
|
|
|
|
|
|
|
CATCH(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "type", &tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( strcmp(tmpstr->data, "actor") == 0 ) {
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_load_layer_object_actor(curobj, layerdatavalue, layerid, dirname));
|
2026-05-13 09:57:24 -04:00
|
|
|
} 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));
|
2026-05-13 23:36:49 -04:00
|
|
|
CATCH(errctx, akgl_get_json_properties_number((json_t *)layerdatavalue, "scale", &dest->p_foreground_scale));
|
2026-05-13 09:57:24 -04:00
|
|
|
} 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));
|
2026-05-13 23:36:49 -04:00
|
|
|
CATCH(errctx, akgl_get_json_properties_number((json_t *)layerdatavalue, "scale", &dest->p_vanishing_scale));
|
2026-05-13 09:57:24 -04:00
|
|
|
}
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
2026-05-13 09:57:24 -04:00
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
layerdatavalue = NULL;
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *layerdata = NULL;
|
|
|
|
|
int j;
|
|
|
|
|
int layerdatalen;
|
|
|
|
|
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination tilemap reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "NULL tilemap root reference");
|
2026-05-24 09:51:58 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dirname, AKERR_NULLPOINTER, "dirname");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_integer_value(root, "height", &dest->layers[layerid].height));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value(root, "width", &dest->layers[layerid].width));
|
|
|
|
|
CATCH(errctx, akgl_get_json_array_value(root, "data", &layerdata));
|
2025-08-03 10:07:35 -04:00
|
|
|
layerdatalen = (dest->layers[layerid].width * dest->layers[layerid].height);
|
2026-05-06 23:18:42 -04:00
|
|
|
if ( layerdatalen >= (AKGL_TILEMAP_MAX_WIDTH * AKGL_TILEMAP_MAX_HEIGHT) ) {
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_BREAK(errctx, AKERR_OUTOFBOUNDS, "Map layer exceeds the maximum size");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
for ( j = 0; j < layerdatalen; j++ ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_integer(layerdata, j, &dest->layers[layerid].data[j]));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname)
|
2026-05-13 08:02:59 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
akgl_String *tmpstr;
|
|
|
|
|
akgl_String *fpath;
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination tilemap reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "NULL tilemap root reference");
|
2026-05-24 09:51:58 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dirname, AKERR_NULLPOINTER, "dirname");
|
2026-05-13 08:02:59 -04:00
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
|
|
|
|
CATCH(errctx, akgl_heap_next_string(&fpath));
|
|
|
|
|
CATCH(errctx, akgl_get_json_string_value(root, "image", &tmpstr));
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
DISABLE_GCC_WARNING_FORMAT_TRUNCATION
|
2026-05-13 08:02:59 -04:00
|
|
|
snprintf((char *)&fpath->data,
|
|
|
|
|
AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE,
|
2026-05-24 09:51:58 -04:00
|
|
|
"%s/%s",
|
|
|
|
|
dirname->data,
|
2026-05-13 08:02:59 -04:00
|
|
|
tmpstr->data
|
|
|
|
|
);
|
2026-05-24 09:51:58 -04:00
|
|
|
RESTORE_GCC_WARNINGS
|
2026-05-13 08:02:59 -04:00
|
|
|
|
|
|
|
|
dest->layers[layerid].texture = IMG_LoadTexture(renderer, (char *)fpath->data);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, dest->layers[layerid].texture, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
dest->layers[layerid].width = dest->layers[layerid].texture->w;
|
|
|
|
|
dest->layers[layerid].height = dest->layers[layerid].texture->h;
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(akgl_heap_release_string(tmpstr));
|
|
|
|
|
IGNORE(akgl_heap_release_string(fpath));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, akgl_String *dirname)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "akgl_tilemap_load_layers received NULL tilemap pointer");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, root, AKERR_NULLPOINTER, "akgl_tilemap_load_layers received NULL json object pointer");
|
2026-05-24 09:51:58 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dirname, AKERR_NULLPOINTER, "dirname");
|
2025-08-03 10:07:35 -04:00
|
|
|
json_t *layers = NULL;
|
|
|
|
|
json_t *layer = NULL;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstr = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
int i;
|
2026-05-13 08:02:59 -04:00
|
|
|
int layerid = 0;
|
|
|
|
|
int tmpint = 0;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_value(root, "layers", &layers));
|
2025-08-03 10:07:35 -04:00
|
|
|
dest->numlayers = json_array_size((json_t *)layers);
|
|
|
|
|
for ( i = 0; i < dest->numlayers; i++) {
|
2026-05-06 23:18:42 -04:00
|
|
|
if ( i >= AKGL_TILEMAP_MAX_LAYERS ) {
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_BREAK(errctx, AKERR_OUTOFBOUNDS, "Map exceeds the maximum number of layers");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_object((json_t *)layers, i, &layer));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value((json_t *)layer, "id", &tmpint));
|
2026-05-13 08:02:59 -04:00
|
|
|
CATCH(errctx, akgl_get_json_number_value((json_t *)layer, "opacity", &dest->layers[layerid].opacity));
|
|
|
|
|
CATCH(errctx, akgl_get_json_boolean_value((json_t *)layer, "visible", &dest->layers[layerid].visible));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value((json_t *)layer, "id", &dest->layers[layerid].id));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value((json_t *)layer, "x", &dest->layers[layerid].x));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value((json_t *)layer, "y", &dest->layers[layerid].y));
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_string_value((json_t *)layer, "type", &tmpstr));
|
2026-05-13 08:02:59 -04:00
|
|
|
SDL_Log("Layer %d has type %s", layerid, tmpstr->data);
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( strncmp((char *)tmpstr->data, "objectgroup", strlen((char *)tmpstr->data)) == 0 ) {
|
2026-05-13 08:02:59 -04:00
|
|
|
dest->layers[layerid].type = AKGL_TILEMAP_LAYER_TYPE_OBJECTS;
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_load_layer_objects((akgl_Tilemap *)dest, (json_t *)layer, layerid, dirname));
|
2025-08-03 10:07:35 -04:00
|
|
|
} else if ( strncmp((char *)tmpstr->data, "tilelayer", strlen((char *)tmpstr->data)) == 0 ) {
|
2026-05-13 08:02:59 -04:00
|
|
|
dest->layers[layerid].type = AKGL_TILEMAP_LAYER_TYPE_TILES;
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_load_layer_tile((akgl_Tilemap *)dest, (json_t *)layer, layerid, dirname));
|
2026-05-13 08:02:59 -04:00
|
|
|
} else if ( strncmp((char *)tmpstr->data, "imagelayer", strlen((char *)tmpstr->data)) == 0 ) {
|
|
|
|
|
dest->layers[layerid].type = AKGL_TILEMAP_LAYER_TYPE_IMAGE;
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_load_layer_image((akgl_Tilemap *)dest, (json_t *)layer, layerid, dirname));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
layer = NULL;
|
2026-05-13 08:02:59 -04:00
|
|
|
layerid += 1;
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *json = NULL;
|
2026-05-06 23:18:42 -04:00
|
|
|
//akgl_String *tmpstr = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
json_error_t error;
|
2026-05-24 09:51:58 -04:00
|
|
|
akgl_String *dirnamestr = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, fname, AKERR_NULLPOINTER, "load_tilemap received null filename");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "load_tilemap received null tilemap");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
memset(dest, 0x00, sizeof(akgl_Tilemap));
|
2026-05-13 23:36:49 -04:00
|
|
|
dest->p_foreground_scale = 1.0;
|
|
|
|
|
dest->p_vanishing_scale = 1.0;
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
PASS(errctx, akgl_heap_next_string(&dirnamestr));
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
//CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
|
|
|
|
//CATCH(errctx, akgl_string_initialize(tmpstr, NULL));
|
|
|
|
|
//SDL_snprintf(tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname);
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, aksl_realpath(fname, (char *)&dirnamestr->data));
|
|
|
|
|
dirname((char *)&dirnamestr->data);
|
2026-05-24 19:57:43 -04:00
|
|
|
|
2025-08-09 08:54:12 -04:00
|
|
|
json = json_load_file(fname, 0, &error);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
json,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_NULLPOINTER,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Error while loading tilemap from %s on line %d: %s-",
|
2025-08-09 08:54:12 -04:00
|
|
|
fname,
|
2025-08-03 10:07:35 -04:00
|
|
|
error.line,
|
|
|
|
|
error.text
|
|
|
|
|
);
|
2026-05-06 23:18:42 -04:00
|
|
|
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));
|
|
|
|
|
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "width", &dest->width));
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
dest->orientation = 0;
|
2026-05-06 23:18:42 -04:00
|
|
|
if ( (dest->width * dest->height) >= (AKGL_TILEMAP_MAX_WIDTH * AKGL_TILEMAP_MAX_HEIGHT) ) {
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Map exceeds the maximum size");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-24 09:51:58 -04:00
|
|
|
CATCH(errctx, akgl_tilemap_load_layers((akgl_Tilemap *)dest, (json_t *)json, dirnamestr));
|
|
|
|
|
CATCH(errctx, akgl_tilemap_load_tilesets((akgl_Tilemap *)dest, (json_t *)json, dirnamestr));
|
2026-05-13 09:57:24 -04:00
|
|
|
|
|
|
|
|
if ( dest->p_foreground_y && dest->p_vanishing_y ) {
|
|
|
|
|
// How much bigger is the foreground vs the vanishing point?
|
|
|
|
|
// if vanishing is height 16, and foreground is height 32, that is a 2x scale difference
|
2026-05-13 23:36:49 -04:00
|
|
|
/* dest->p_scale = ((float)dest->p_foreground_h / (float)dest->p_vanishing_h); */
|
|
|
|
|
/* SDL_Log("Map perspective scale is (%d/%d) = %f", dest->p_foreground_h, dest->p_vanishing_h, dest->p_scale); */
|
|
|
|
|
// Sprites are scale N (default 1.0) at the foreground, so how much do we need to
|
2026-05-13 09:57:24 -04:00
|
|
|
// scale them for every pixel above foreground_y before they reach vanishing_y?
|
|
|
|
|
// If vanishing is at 320 and foreground is at 640, that is a 320 line difference
|
2026-05-13 23:36:49 -04:00
|
|
|
// If our scaling rate is 2x, then our rate is (((N=1.0) / (640 - 320)) / (dest->p_scale = 2)), or
|
|
|
|
|
// 0.0066% scale per pixel.
|
|
|
|
|
dest->p_rate = ((dest->p_foreground_scale - dest->p_vanishing_scale) / (dest->p_foreground_y - dest->p_vanishing_y));
|
2026-05-13 09:57:24 -04:00
|
|
|
SDL_Log("Map perspective rate is %f", dest->p_rate);
|
|
|
|
|
}
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
2026-05-06 23:18:42 -04:00
|
|
|
//IGNORE(akgl_heap_release_string(tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_draw(SDL_Renderer *renderer, akgl_Tilemap *map, SDL_FRect *viewport, int layeridx)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
SDL_FRect dest = {.x = 0, .y = 0, .w = 0, .h = 0};;
|
|
|
|
|
SDL_FRect src = {.x = 0, .y = 0, .w = 0, .h = 0};
|
|
|
|
|
int start_x = 0;
|
|
|
|
|
int start_y = 0;
|
|
|
|
|
int end_x = 0;
|
|
|
|
|
int end_y = 0;
|
|
|
|
|
int yidx = 0;
|
|
|
|
|
int xidx = 0;
|
|
|
|
|
int tilesetidx = 0;
|
|
|
|
|
int tilenum = 0;
|
|
|
|
|
int offset = 0;
|
|
|
|
|
/*
|
|
|
|
|
* Render every tile in the map that partially intersects the viewport
|
|
|
|
|
*
|
|
|
|
|
* For an 8x2 tilemap with 16 pixel square tiles like this
|
|
|
|
|
*
|
|
|
|
|
* 01234567
|
|
|
|
|
* 89ABCDEF
|
|
|
|
|
*
|
|
|
|
|
* With a viewport of (x=20, y=8, w=90, y=20), we would render:
|
|
|
|
|
*
|
|
|
|
|
* 123456
|
|
|
|
|
* 9ABCDE
|
|
|
|
|
*
|
|
|
|
|
* 0 and 8 would not be rendered. 1, 9, 6, and E would be partially rendered at their corner.
|
|
|
|
|
* 2,3,4,5 and A,B,C,D would be partially rendered with a slice from their center.
|
|
|
|
|
*/
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, map, AKERR_NULLPOINTER, "akgl_tilemap_draw received NULL pointer to tilemap");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, viewport, AKERR_NULLPOINTER, "akgl_tilemap_draw received NULL pointer to viewport");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
/* Only try to render the stuff that is partially within the viewport */
|
|
|
|
|
|
|
|
|
|
start_x = viewport->x / map->tilewidth;
|
|
|
|
|
start_y = viewport->y / map->tileheight;
|
|
|
|
|
end_x = (viewport->x + viewport->w) / map->tilewidth;
|
|
|
|
|
end_y = (viewport->y + viewport->h) / map->tileheight;
|
|
|
|
|
if ( end_x > map->width ) {
|
|
|
|
|
end_x = map->width;
|
|
|
|
|
}
|
|
|
|
|
if ( end_y > map->height ) {
|
|
|
|
|
end_y = map->height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*SDL_Log("Rendering map into viewport from (%d, %d) to (%d, %d)",
|
|
|
|
|
start_x, start_y, end_x, end_y);*/
|
|
|
|
|
|
2026-05-13 08:02:59 -04:00
|
|
|
if ( map->layers[layeridx].type == AKGL_TILEMAP_LAYER_TYPE_IMAGE ) {
|
|
|
|
|
dest.x = 0;
|
|
|
|
|
dest.y = 0;
|
|
|
|
|
src.w = map->layers[layeridx].width;
|
|
|
|
|
src.h = map->layers[layeridx].height;
|
|
|
|
|
dest.w = map->layers[layeridx].width;
|
|
|
|
|
dest.h = map->layers[layeridx].height;
|
|
|
|
|
SDL_RenderTexture(renderer, map->layers[layeridx].texture, &src, &dest);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
dest.x = 0;
|
|
|
|
|
dest.y = 0;
|
|
|
|
|
dest.w = map->tilewidth;
|
|
|
|
|
dest.h = map->tileheight;
|
|
|
|
|
for ( yidx = start_y; yidx < end_y; yidx++ ) {
|
|
|
|
|
dest.x = 0;
|
|
|
|
|
for ( xidx = start_x; xidx < end_x; xidx++ ) {
|
|
|
|
|
if ( yidx == 0 ) {
|
|
|
|
|
offset = xidx;
|
|
|
|
|
} else {
|
|
|
|
|
offset = xidx + (yidx * (map->width));
|
|
|
|
|
}
|
|
|
|
|
tilenum = map->layers[layeridx].data[offset];
|
|
|
|
|
// FIXME: This is probably not very efficient. Need a better way to look up
|
|
|
|
|
// tile offsets within the tilesets by their tile ID.
|
|
|
|
|
for ( tilesetidx = 0; tilesetidx < map->numtilesets ; tilesetidx++ ) {
|
|
|
|
|
if ( map->tilesets[tilesetidx].firstgid <= tilenum &&
|
|
|
|
|
(map->tilesets[tilesetidx].firstgid + map->tilesets[tilesetidx].tilecount) >= tilenum ) {
|
|
|
|
|
// Render this tile to the correct screen position
|
|
|
|
|
// FIXME: These conditionals are probably not very efficient. Need a better way of getting
|
|
|
|
|
// the intersection of this tile with the viewport and rendering only that portion.
|
|
|
|
|
if ( xidx == 0 ) {
|
|
|
|
|
src.x += (int)viewport->x % map->tilewidth;
|
|
|
|
|
src.w = map->tilewidth - ((int)viewport->x % map->tilewidth);
|
|
|
|
|
} else {
|
|
|
|
|
src.x = map->tilesets[tilesetidx].tile_offsets[tilenum - map->tilesets[tilesetidx].firstgid][0];
|
|
|
|
|
src.w = map->tilewidth;
|
|
|
|
|
}
|
|
|
|
|
if ( yidx == 0 ) {
|
|
|
|
|
src.y += (int)viewport->y % map->tileheight;
|
|
|
|
|
src.h = map->tileheight - ((int)viewport->y % map->tileheight);
|
|
|
|
|
} else {
|
|
|
|
|
src.y = map->tilesets[tilesetidx].tile_offsets[tilenum - map->tilesets[tilesetidx].firstgid][1];
|
|
|
|
|
src.h = map->tileheight;
|
|
|
|
|
}
|
|
|
|
|
/*SDL_Log("Blitting tile #%d (local tileset id %d from offset %d) from map layer %d map (x=%d,y=%d) tileset %d (x=%f,y=%f,w=%f,h=%f) to (x=%f,y=%f,w=%f,h=%f)",
|
|
|
|
|
tilenum,
|
|
|
|
|
(tilenum - map->tilesets[tilesetidx].firstgid),
|
|
|
|
|
offset,
|
|
|
|
|
layeridx,
|
|
|
|
|
xidx,
|
|
|
|
|
yidx,
|
|
|
|
|
tilesetidx,
|
|
|
|
|
src.x,
|
|
|
|
|
src.y,
|
|
|
|
|
src.w,
|
|
|
|
|
src.h,
|
|
|
|
|
dest.x,
|
|
|
|
|
dest.y,
|
|
|
|
|
dest.w,
|
|
|
|
|
dest.h);*/
|
|
|
|
|
SDL_RenderTexture(renderer, map->tilesets[tilesetidx].texture, &src, &dest);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dest.x += map->tilewidth;
|
|
|
|
|
}
|
|
|
|
|
dest.y += map->tileheight;
|
|
|
|
|
}
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_tilemap_draw_tileset(SDL_Renderer *renderer, akgl_Tilemap *map, int tilesetidx)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
SDL_FRect dest;
|
|
|
|
|
SDL_FRect src;
|
|
|
|
|
int tilenum = 0;
|
|
|
|
|
/*
|
|
|
|
|
* Render every tile in a tileset to the given renderer
|
|
|
|
|
* (this is a debugging tool that shows that the recorded tile offsets are correct,
|
|
|
|
|
* by proving that we can reconstruct the original tileset image)
|
|
|
|
|
*/
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, map, AKERR_NULLPOINTER, "akgl_tilemap_draw_tileset received NULL pointer to tilemap");
|
|
|
|
|
FAIL_NONZERO_RETURN(errctx, (tilesetidx >= map->numtilesets), AKERR_OUTOFBOUNDS, "akgl_tilemap_draw_tileset received a tileset index out of bounds");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
for ( tilenum = 0; tilenum < map->tilesets[tilesetidx].tilecount; tilenum++) {
|
|
|
|
|
// Render this tile to the correct screen position
|
|
|
|
|
// FIXME: These conditionals are probably not very efficient. Need a better way of getting
|
|
|
|
|
// the intersection of this tile with the viewport and rendering only that portion.
|
|
|
|
|
src.x = map->tilesets[tilesetidx].tile_offsets[tilenum][0];
|
|
|
|
|
src.y = map->tilesets[tilesetidx].tile_offsets[tilenum][1];
|
|
|
|
|
src.w = map->tilewidth;
|
|
|
|
|
src.h = map->tileheight;
|
|
|
|
|
dest.x = tilenum * map->tilewidth;
|
|
|
|
|
if ( tilenum >= map->tilesets[tilesetidx].columns ) {
|
|
|
|
|
dest.x = (tilenum % (map->tilesets[tilesetidx].columns)) * map->tilewidth;
|
|
|
|
|
}
|
|
|
|
|
if ( tilenum >= (map->tilesets[tilesetidx].columns) ) {
|
|
|
|
|
dest.y = (tilenum / (map->tilesets[tilesetidx].columns)) * map->tileheight;
|
|
|
|
|
} else {
|
|
|
|
|
dest.y = 0;
|
|
|
|
|
}
|
|
|
|
|
dest.w = src.w;
|
|
|
|
|
dest.h = src.h;
|
|
|
|
|
/*SDL_Log("Blitting tile #%d from map tileset %d (x=%f,y=%f,w=%f,h=%f) to (x=%f,y=%f,w=%f,h=%f)",
|
|
|
|
|
tilenum,
|
|
|
|
|
tilesetidx,
|
|
|
|
|
src.x,
|
|
|
|
|
src.y,
|
|
|
|
|
src.w,
|
|
|
|
|
src.h,
|
|
|
|
|
dest.x,
|
|
|
|
|
dest.y,
|
|
|
|
|
dest.w,
|
|
|
|
|
dest.h);*/
|
|
|
|
|
SDL_RenderTexture(renderer, map->tilesets[tilesetidx].texture, &src, &dest);
|
|
|
|
|
}
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
2026-05-13 09:57:24 -04:00
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_RETURN(e, map, AKERR_NULLPOINTER, "NULL map");
|
|
|
|
|
FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "NULL actor");
|
|
|
|
|
|
|
|
|
|
if ( actor->y <= map->p_vanishing_y ) {
|
2026-05-13 23:36:49 -04:00
|
|
|
actor->scale = map->p_vanishing_scale;
|
2026-05-13 09:57:24 -04:00
|
|
|
} else if ( actor->y >= map->p_foreground_y ) {
|
2026-05-13 23:36:49 -04:00
|
|
|
actor->scale = map->p_foreground_scale;
|
2026-05-13 09:57:24 -04:00
|
|
|
} else {
|
2026-05-13 23:36:49 -04:00
|
|
|
actor->scale = map->p_foreground_scale - (map->p_rate * (map->p_foreground_y - actor->y));
|
2026-05-13 09:57:24 -04:00
|
|
|
}
|
2026-05-13 16:56:24 -04:00
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest)
|
|
|
|
|
{
|
|
|
|
|
// Release all tileset textures
|
|
|
|
|
// Release all image layer textures
|
|
|
|
|
// Memset to zero
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "NULL map");
|
|
|
|
|
int i = 0;
|
|
|
|
|
for ( i = 0; i < AKGL_TILEMAP_MAX_TILESETS; i++ ) {
|
|
|
|
|
if ( dest->tilesets[i].texture != NULL ) {
|
|
|
|
|
SDL_DestroyTexture(dest->tilesets[i].texture);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for ( i = 0; i < AKGL_TILEMAP_MAX_LAYERS; i++ ) {
|
|
|
|
|
if ( dest->layers[i].texture != NULL ) {
|
|
|
|
|
SDL_DestroyTexture(dest->tilesets[i].texture);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SUCCEED_RETURN(e);
|
2026-05-13 09:57:24 -04:00
|
|
|
}
|