# HG changeset patch # User David Demelier # Date 1678218300 -3600 # Node ID ffd972a3d0845328984caac60aaafbb253aebfab # Parent 856c2e96189daa5d76d9cd657ace02de6222af18 rpg: major rewrite of tilesets - Now tilesets can be opened using a custom allocator/loader. - A default mlk_tileset_loader_file implementation is provided. - Put a simple example-tileset example to demonstrate. diff -r 856c2e96189d -r ffd972a3d084 cmake/MlkBcc.cmake --- a/cmake/MlkBcc.cmake Mon Mar 06 20:44:43 2023 +0100 +++ b/cmake/MlkBcc.cmake Tue Mar 07 20:45:00 2023 +0100 @@ -46,6 +46,7 @@ set(args "-cs") endif () + message("===> ${output}") set(outputfile ${CMAKE_CURRENT_BINARY_DIR}/${output}) add_custom_command( diff -r 856c2e96189d -r ffd972a3d084 examples/CMakeLists.txt --- a/examples/CMakeLists.txt Mon Mar 06 20:44:43 2023 +0100 +++ b/examples/CMakeLists.txt Tue Mar 07 20:45:00 2023 +0100 @@ -32,6 +32,7 @@ example-message example-notify example-sprite + example-tileset example-trace example-ui ) diff -r 856c2e96189d -r ffd972a3d084 examples/example-tileset/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/example-tileset/CMakeLists.txt Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,33 @@ +# +# CMakeLists.txt -- CMake build system for Molko's Engine +# +# Copyright (c) 2020-2022 David Demelier +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +project(example-tileset) + +set( + SOURCES + ${example-tileset_SOURCE_DIR}/example-tileset.c +) + +mlk_executable( + NAME example-tileset + FOLDER examples + LIBRARIES libmlk-example + SOURCES ${SOURCES} +) + +source_group(TREE ${example-tileset_SOURCE_DIR} FILES ${SOURCES}) diff -r 856c2e96189d -r ffd972a3d084 examples/example-tileset/example-tileset.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/example-tileset/example-tileset.c Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,214 @@ +/* + * example-tileset.c -- example on how to use a tileset + * + * Copyright (c) 2020-2023 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +static struct mlk_tileset_loader_file loader_file; +static struct mlk_tileset_loader loader; +static struct mlk_tileset tileset; + +/* + * This is only to demonstrate how we can custom-allocate data, you can skip + * that part. + */ +static struct mlk_sprite sprites[16]; +static size_t spritesz; +static struct mlk_animation animations[16]; +static size_t animationsz; + +static const struct { + const char *basename; + struct mlk_texture *texture; +} table_textures[] = { + { "world.png", &mlk_registry_textures[MLK_REGISTRY_TEXTURE_WORLD] }, + { "animation-water.png", &mlk_registry_textures[MLK_REGISTRY_TEXTURE_WATER] }, + { NULL, NULL } +}; + +static struct mlk_texture * +init_texture(struct mlk_tileset_loader *loader, const char *ident) +{ + (void)loader; + + char filepath[MLK_PATH_MAX], filename[FILENAME_MAX + 1]; + + mlk_util_strlcpy(filepath, ident, sizeof (filepath)); + mlk_util_strlcpy(filename, mlk_util_basename(filepath), sizeof (filename)); + mlk_tracef("Searching for texture %s", filename); + + for (size_t i = 0; table_textures[i].basename != NULL; ++i) + if (strcmp(table_textures[i].basename, filename) == 0) + return table_textures[i].texture; + + return NULL; +} + +static struct mlk_sprite * +init_sprite(struct mlk_tileset_loader *loader) +{ + (void)loader; + + /* Just ensure we haven't reach the limit. */ + assert(spritesz < MLK_UTIL_SIZE(sprites)); + + return &sprites[spritesz++]; +} + +static struct mlk_animation * +init_animation(struct mlk_tileset_loader *loader) +{ + (void)loader; + + /* Just ensure we haven't reach the limit. */ + assert(animationsz < MLK_UTIL_SIZE(animations)); + + return &animations[animationsz++]; +} + +static void +init(void) +{ + if (mlk_example_init("example-tileset") < 0) + mlk_panic(); + + // TODO: temporary. + const char *path = "/home/markand/dev/molko/build/libmlk-example/tilesets/world.tileset"; + + /* + * Demonstrate how we can override functions to use different resources + * and/or allocations. + * + * Images are loaded from the libmlk-example registry from RAM and + * sprites animations are statically allocated. + */ + mlk_tileset_loader_file_init(&loader_file, &loader, path); + loader.init_texture = init_texture; + loader.init_sprite = init_sprite; + loader.init_animation = init_animation; + + if (mlk_tileset_loader_open(&loader, &tileset, path) < 0) + mlk_panic(); +} + +static void +handle(struct mlk_state *st, const union mlk_event *ev) +{ + (void)st; + + switch (ev->type) { + case MLK_EVENT_QUIT: + mlk_game_quit(); + break; + default: + break; + } +} + +static void +update(struct mlk_state *st, unsigned int ticks) +{ + (void)st; + + mlk_tileset_update(&tileset, ticks); +} + +static void +draw(struct mlk_state *st) +{ + (void)st; + + int nc, nr; + + mlk_painter_set_color(MLK_EXAMPLE_BG); + mlk_painter_clear(); + + /* + * Draw the animated tile all over the screen. + */ + nc = (MLK_EXAMPLE_W / tileset.sprite->cellw) + 1; + nr = (MLK_EXAMPLE_H / tileset.sprite->cellh) + 1; + + for (int r = 0; r < nr; ++r) { + for (int c = 0; c < nc; ++c) { + mlk_tileset_draw(&tileset, 7, 22, + c * tileset.sprite->cellw, + r * tileset.sprite->cellh); + } + } + + mlk_painter_present(); +} + +static void +run(void) +{ + struct mlk_state state = { + .handle = handle, + .update = update, + .draw = draw + }; + + mlk_game_init(); + mlk_game_push(&state); + mlk_game_loop(); +} + +static void +quit(void) +{ + mlk_tileset_loader_file_finish(&loader_file); + mlk_example_finish(); +} + +int +main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + init(); + run(); + quit(); +} diff -r 856c2e96189d -r ffd972a3d084 libmlk-example/CMakeLists.txt --- a/libmlk-example/CMakeLists.txt Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-example/CMakeLists.txt Tue Mar 07 20:45:00 2023 +0100 @@ -46,6 +46,8 @@ ${libmlk-example_SOURCE_DIR}/assets/sprites/numbers.png ${libmlk-example_SOURCE_DIR}/assets/sprites/people.png ${libmlk-example_SOURCE_DIR}/assets/sprites/ui-cursor.png + ${libmlk-example_SOURCE_DIR}/assets/sprites/water.png + ${libmlk-example_SOURCE_DIR}/assets/sprites/world.png ) set( @@ -58,14 +60,15 @@ ${libmlk-example_SOURCE_DIR}/assets/tilesets/world.json ) +mlk_maps("${MAPS}" ${libmlk-example_BINARY_DIR}/maps maps) +mlk_tilesets("${TILESETS}" ${libmlk-example_BINARY_DIR}/tilesets tilesets) + mlk_library( NAME libmlk-example SOURCES ${SOURCES} - ASSETS ${ASSETS} + ASSETS ${ASSETS} ${maps} ${tilesets} TYPE STATIC LIBRARIES libmlk-rpg - MAPS ${MAPS} - TILESETS ${TILESETS} INCLUDES PUBLIC $ diff -r 856c2e96189d -r ffd972a3d084 libmlk-example/mlk/example/registry.c --- a/libmlk-example/mlk/example/registry.c Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-example/mlk/example/registry.c Tue Mar 07 20:45:00 2023 +0100 @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include @@ -75,6 +77,7 @@ } textures[] = { MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_CURSOR, assets_sprites_ui_cursor, 24, 24), MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_EXPLOSION, assets_sprites_explosion, 256, 256), + MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_WATER, assets_sprites_water, 48, 48), MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_JOHN_SWORD, assets_sprites_john_sword, 256, 256), MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_JOHN_WALK, assets_sprites_john_walk, 256, 256), MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_HAUNTED_WOOD, assets_images_haunted_wood, 0, 0), @@ -82,7 +85,8 @@ MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_CHEST, assets_sprites_chest, 32, 32), MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_NUMBERS, assets_sprites_numbers, 48, 48), MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_SWORD, assets_images_sword, 0, 0), - MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_PEOPLE, assets_sprites_people, 48, 48) + MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_PEOPLE, assets_sprites_people, 48, 48), + MLK_REGISTRY_TEXTURE(MLK_REGISTRY_TEXTURE_WORLD, assets_sprites_world, 48, 48) }; static const struct { diff -r 856c2e96189d -r ffd972a3d084 libmlk-example/mlk/example/registry.h --- a/libmlk-example/mlk/example/registry.h Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-example/mlk/example/registry.h Tue Mar 07 20:45:00 2023 +0100 @@ -31,6 +31,7 @@ /* Animations. */ MLK_REGISTRY_TEXTURE_EXPLOSION, + MLK_REGISTRY_TEXTURE_WATER, /* Characters. */ MLK_REGISTRY_TEXTURE_JOHN_WALK, @@ -47,6 +48,9 @@ /* Sword by Icongeek26 (https://www.flaticon.com). */ MLK_REGISTRY_TEXTURE_SWORD, + /* Tileset textures. */ + MLK_REGISTRY_TEXTURE_WORLD, + /* Unused.*/ MLK_REGISTRY_TEXTURE_LAST }; diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/CMakeLists.txt --- a/libmlk-rpg/CMakeLists.txt Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-rpg/CMakeLists.txt Tue Mar 07 20:45:00 2023 +0100 @@ -20,10 +20,6 @@ set( SOURCES - ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-file.c - ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-file.h - ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map.c - ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map.h ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/message.c ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/message.h ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/property.c @@ -34,8 +30,10 @@ ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/rpg.h ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/save.c ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/save.h - ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset-file.c - ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset-file.h + ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset-loader-file.c + ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset-loader-file.h + ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset-loader.c + ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset-loader.h ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset.c ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/tileset.h ${libmlk-rpg_SOURCE_DIR}/mlk/rpg/walksprite.c diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/map-file.h --- a/libmlk-rpg/mlk/rpg/map-file.h Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-rpg/mlk/rpg/map-file.h Tue Mar 07 20:45:00 2023 +0100 @@ -25,7 +25,7 @@ #include "map.h" #include "tileset.h" -#include "tileset-file.h" +#include "tileset-loader.h" #define MAP_FILE_TITLE_MAX 64 @@ -35,7 +35,7 @@ char title[MAP_FILE_TITLE_MAX]; struct map_layer layers[MAP_LAYER_TYPE_NUM]; struct tileset_file tileset_file; - struct tileset tileset; + struct mlk_tileset tileset; struct mlk_alloc_pool blocks; }; diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset-file.c --- a/libmlk-rpg/mlk/rpg/tileset-file.c Mon Mar 06 20:44:43 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,343 +0,0 @@ -/* - * tileset-file.c -- tileset file loader - * - * Copyright (c) 2020-2023 David Demelier - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include "tileset-file.h" -#include "tileset.h" - -#define MAX_F(v) MAX_F_(v) -#define MAX_F_(v) "%" #v "[^\n|]" - -/* - * This is how memory for animations is allocated in the tileset_file - * structure. - * - * As animations require a texture and a sprite to be present, we need to store - * them locally in the tileset_file structure. - * - * tileset_file->anims[0] array (struct tileset_animation_block): - * - * [0] [1] [N] - * | texture | texture | texture - * | sprite | sprite | sprite - * | animation | animation | animation - * - * tileset_file->anims[1] array (struct tileset_animation): - * - * [0] [1] [N] - * | id | id | id - * | animation ^ | animation ^ | animation ^ - * - * The second array is the exposed array through the tileset->anims pointer, - * animations are referenced from the first array. This is because user may need - * or replace the tileset by itself and as such we need to keep track of the - * resource the tileset_file has allocated itself. - */ - -struct tileset_animation_block { - struct mlk_texture texture; - struct mlk_sprite sprite; - struct mlk_animation animation; -}; - -struct context { - struct tileset_file *tf; - struct tileset *tileset; - FILE *fp; - - char basedir[MLK_PATH_MAX]; - - /* - * The following properties aren't stored in the tileset because they - * are not needed after loading. - */ - unsigned int tilewidth; - unsigned int tileheight; - - /* Number of rows/columns in the image. */ - unsigned int nrows; - unsigned int ncolumns; -}; - -static void -tileset_animation_block_finish(void *data) -{ - struct tileset_animation_block *anim = data; - - mlk_texture_finish(&anim->texture); -} - -static int -tileset_tiledef_cmp(const void *d1, const void *d2) -{ - const struct tileset_tiledef *mtd1 = d1; - const struct tileset_tiledef *mtd2 = d2; - - if (mtd1->id < mtd2->id) - return -1; - if (mtd1->id > mtd2->id) - return 1; - - return 0; -} - -static int -tileset_animation_cmp(const void *d1, const void *d2) -{ - const struct tileset_animation *mtd1 = d1; - const struct tileset_animation *mtd2 = d2; - - if (mtd1->id < mtd2->id) - return -1; - if (mtd1->id > mtd2->id) - return 1; - - return 0; -} - -static int -parse_tilewidth(struct context *ctx, const char *line) -{ - if (sscanf(line, "tilewidth|%u", &ctx->tilewidth) != 1 || ctx->tilewidth == 0) - return mlk_errf("tilewidth is null"); - - return 0; -} - -static int -parse_tileheight(struct context *ctx, const char *line) -{ - if (sscanf(line, "tileheight|%u", &ctx->tileheight) != 1 || ctx->tileheight == 0) - return mlk_errf("tileheight is null"); - - return 0; -} - -static int -parse_tiledefs(struct context *ctx, const char *line) -{ - (void)line; - - short x, y; - unsigned short id, w, h; - struct tileset_tiledef *td; - - mlk_alloc_pool_init(&ctx->tf->tiledefs, 16, sizeof (*td), NULL); - - while (fscanf(ctx->fp, "%hu|%hd|%hd|%hu|%hu\n", &id, &x, &y, &w, &h) == 5) { - td = mlk_alloc_pool_new(&ctx->tf->tiledefs); - td->id = id; - td->x = x; - td->y = y; - td->w = w; - td->h = h; - } - - /* Sort the array and expose it through the tileset->tiledefs pointer. */ - qsort(ctx->tf->tiledefs.data, ctx->tf->tiledefs.size, ctx->tf->tiledefs.elemsize, tileset_tiledef_cmp); - ctx->tileset->tiledefs = ctx->tf->tiledefs.data; - ctx->tileset->tiledefsz = ctx->tf->tiledefs.size; - - return 0; -} - -static int -parse_animations(struct context *ctx, const char *line) -{ - (void)line; - - unsigned short id; - unsigned int delay; - char filename[FILENAME_MAX + 1]; - struct tileset_animation_block *anim; - - mlk_alloc_pool_init(&ctx->tf->anims[0], 16, - sizeof (struct tileset_animation_block), tileset_animation_block_finish); - mlk_alloc_pool_init(&ctx->tf->anims[1], 16, - sizeof (struct tileset_animation), NULL); - - /* - * 1. Create the first array of animation, sprite and texture that are - * owned by the tileset_file structure. - */ - while (fscanf(ctx->fp, "%hu|" MAX_F(FILENAME_MAX) "|%u", &id, filename, &delay) == 3) { - anim = mlk_alloc_pool_new(&ctx->tf->anims[0]); - - if (mlk_image_open(&anim->texture, mlk_util_pathf("%s/%s", ctx->basedir, filename)) < 0) - return -1; - - anim->sprite.texture = &anim->texture; - anim->sprite.cellw = ctx->tilewidth; - anim->sprite.cellh = ctx->tileheight; - - anim->animation.sprite = &anim->sprite; - anim->animation.delay = delay; - - mlk_sprite_init(&anim->sprite); - } - - /* - * 2. Create the second array that only consist of pointers to - * animations referencing the first array. - */ - for (size_t i = 0; i < ctx->tf->anims[0].size; ++i) { - struct tileset_animation_block *anim = mlk_alloc_pool_get(&ctx->tf->anims[0], i); - struct tileset_animation *ta; - - if (!(ta = mlk_alloc_pool_new(&ctx->tf->anims[1]))) - return -1; - - ta->id = id; - ta->animation = &anim->animation; - } - - /* - * 3. Finally expose the second array through the tileset->anims pointer - * and sort it. - */ - qsort(ctx->tf->anims[1].data, ctx->tf->anims[1].size, ctx->tf->anims[1].elemsize, tileset_animation_cmp); - ctx->tileset->anims = ctx->tf->anims[1].data; - ctx->tileset->animsz = ctx->tf->anims[1].size; - - return 0; -} - -static int -parse_image(struct context *ctx, const char *line) -{ - char *p; - - if (ctx->tilewidth == 0 || ctx->tileheight == 0) - return mlk_errf("missing tile dimensions before image"); - if (!(p = strchr(line, '|'))) - return mlk_errf("could not parse image"); - if (mlk_image_open(&ctx->tf->image, mlk_util_pathf("%s/%s", ctx->basedir, p + 1)) < 0) - return -1; - - ctx->tf->sprite.texture = &ctx->tf->image; - ctx->tf->sprite.cellw = ctx->tilewidth; - ctx->tf->sprite.cellh = ctx->tileheight; - mlk_sprite_init(&ctx->tf->sprite); - - ctx->tileset->sprite = &ctx->tf->sprite; - - return 0; -} - -static int -parse_line(struct context *ctx, const char *line) -{ - static const struct { - const char *property; - int (*read)(struct context *, const char *); - } props[] = { - { "tilewidth", parse_tilewidth }, - { "tileheight", parse_tileheight }, - { "tiledefs", parse_tiledefs }, - { "animations", parse_animations }, - { "image", parse_image } - }; - - for (size_t i = 0; i < MLK_UTIL_SIZE(props); ++i) { - if (strncmp(line, props[i].property, strlen(props[i].property)) == 0) - return props[i].read(ctx, line); - } - - return 0; -} - -static int -parse(struct context *ctx, const char *path) -{ - char line[1024], basedir[MLK_PATH_MAX]; - - mlk_util_strlcpy(basedir, path, sizeof (basedir)); - mlk_util_strlcpy(ctx->basedir, mlk_util_dirname(basedir), sizeof (ctx->basedir)); - - while (fgets(line, sizeof (line), ctx->fp)) { - /* Remove \n if any */ - line[strcspn(line, "\r\n")] = '\0'; - - if (parse_line(ctx, line) < 0) - return -1; - } - - return 0; -} - -static int -check(const struct tileset *tileset) -{ - if (!tileset->sprite) - return mlk_errf("missing tileset image"); - - return 0; -} - -int -tileset_file_open(struct tileset_file *tf, struct tileset *tileset, const char *path) -{ - assert(tf); - assert(tileset); - assert(path); - - struct context ctx = { - .tf = tf, - .tileset = tileset - }; - int ret = 0; - - memset(tileset, 0, sizeof (*tileset)); - - if (!(ctx.fp = fopen(path, "r"))) - return -1; - if ((ret = parse(&ctx, path)) < 0 || (ret = check(tileset)) < 0) - tileset_file_finish(tf); - - fclose(ctx.fp); - - return ret; -} - -void -tileset_file_finish(struct tileset_file *tf) -{ - assert(tf); - - mlk_alloc_pool_finish(&tf->tiledefs); - mlk_alloc_pool_finish(&tf->anims[0]); - mlk_alloc_pool_finish(&tf->anims[1]); - - mlk_texture_finish(&tf->image); - - memset(tf, 0, sizeof (*tf)); -} diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset-file.h --- a/libmlk-rpg/mlk/rpg/tileset-file.h Mon Mar 06 20:44:43 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/* - * tileset-file.h -- tileset file loader - * - * Copyright (c) 2020-2023 David Demelier - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MLK_RPG_TILESET_FILE_H -#define MLK_RPG_TILESET_FILE_H - -#include - -#include -#include -#include - -struct tileset; -struct tileset_tiledef; - -struct tileset_file { - struct mlk_alloc_pool tiledefs; - struct mlk_alloc_pool anims[2]; - struct mlk_texture image; - struct mlk_sprite sprite; -}; - -#if defined(__cplusplus) -extern "C" { -#endif - -int -tileset_file_open(struct tileset_file *, struct tileset *, const char *); - -void -tileset_file_finish(struct tileset_file *); - -#if defined(__cplusplus) -} -#endif - -#endif /* !MLK_RPG_TILESET_FILE_H */ diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset-loader-file.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.c Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,197 @@ +/* + * tileset-file.c -- tileset file loader implementation + * + * Copyright (c) 2020-2023 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "tileset-loader-file.h" +#include "tileset-loader.h" +#include "tileset.h" + +static inline void * +allocate(void ***array, size_t width) +{ + void **ptr, *elem; + + /* Not yet allocated? Allocate a new pointer element. */ + if (!*array) + ptr = mlk_alloc_new0(1, sizeof (void *)); + else + ptr = mlk_alloc_expand(*array, 1); + + if (!ptr) + return NULL; + + /* Now allocate the element itself because. */ + if (!(elem = mlk_alloc_new0(1, width))) + return NULL; + + /* Store it into the array of elements. */ + ptr[mlk_alloc_getn(ptr) - 1] = elem; + *array = ptr; + + return elem; +} + +static void * +expand(void **array, size_t n, size_t w) +{ + void *ptr; + + if (!*array) + ptr = mlk_alloc_new0(n, w); + else + ptr = mlk_alloc_expand(*array, n); + + if (ptr) + *array = ptr; + + return ptr; +} + +static void +finish(void ***ptr, void (*finish)(void *)) +{ + size_t len; + + /* Already cleared. */ + if (!*ptr) + return; + + len = mlk_alloc_getn(*ptr); + + for (size_t i = 0; i < len; ++i) + finish((*ptr)[i]); + + mlk_alloc_free(*ptr); + *ptr = NULL; +} + +static void +finish_texture(void *element) +{ + mlk_texture_finish(element); + mlk_alloc_free(element); +} + +static struct mlk_texture * +init_texture(struct mlk_tileset_loader *self, const char *ident) +{ + struct mlk_tileset_loader_file *file = self->data; + struct mlk_texture *texture; + char path[MLK_PATH_MAX]; + + snprintf(path, sizeof (path), "%s/%s", file->directory, ident); + + /* No need to deallocate, already done in finish anyway. */ + if (!(texture = allocate((void ***)&file->textures, sizeof (struct mlk_texture)))) + return NULL; + if (mlk_image_open(texture, path) < 0) + return NULL; + + return texture; +} + +static struct mlk_sprite * +init_sprite(struct mlk_tileset_loader *self) +{ + struct mlk_tileset_loader_file *file = self->data; + + return allocate((void ***)&file->sprites, sizeof (struct mlk_sprite)); +} + +static struct mlk_animation * +init_animation(struct mlk_tileset_loader *self) +{ + struct mlk_tileset_loader_file *file = self->data; + + return allocate((void ***)&file->animations, sizeof (struct mlk_animation)); +} + +struct mlk_tileset_collision * +expand_collisions(struct mlk_tileset_loader *self, + struct mlk_tileset_collision *array, + size_t arraysz) +{ + (void)array; + + struct mlk_tileset_loader_file *file = self->data; + + return expand((void **)&file->tilecollisions, arraysz, sizeof (struct mlk_tileset_collision)); +} + +struct mlk_tileset_animation * +expand_animations(struct mlk_tileset_loader *self, + struct mlk_tileset_animation *array, + size_t arraysz) +{ + (void)array; + + struct mlk_tileset_loader_file *file = self->data; + + return expand((void **)&file->tileanimations, arraysz, sizeof (struct mlk_tileset_animation)); +} + +void +mlk_tileset_loader_file_init(struct mlk_tileset_loader_file *file, + struct mlk_tileset_loader *loader, + const char *filename) +{ + assert(file); + assert(loader); + assert(filename); + + char filepath[MLK_PATH_MAX]; + + memset(file, 0, sizeof (*file)); + memset(loader, 0, sizeof (*loader)); + + /* Determine base filename base directory. */ + mlk_util_strlcpy(filepath, filename, sizeof (filepath)); + mlk_util_strlcpy(file->directory, mlk_util_dirname(filepath), sizeof (file->directory)); + + loader->data = file; + loader->init_texture = init_texture; + loader->init_sprite = init_sprite; + loader->init_animation = init_animation; + loader->expand_collisions = expand_collisions; + loader->expand_animations = expand_animations; +} + +void +mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file) +{ + assert(file); + + /* Finalize individual elements. */ + finish((void ***)&file->textures, finish_texture); + finish((void ***)&file->sprites, mlk_alloc_free); + finish((void ***)&file->animations, mlk_alloc_free); + + /* Clear array of collisions/animations .*/ + mlk_alloc_free(file->tilecollisions); + mlk_alloc_free(file->tileanimations); + + memset(file, 0, sizeof (*file)); +} diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset-loader-file.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.h Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,93 @@ +/* + * tileset-loader-file.h -- tileset file loader implementation + * + * Copyright (c) 2020-2023 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MLK_RPG_TILESET_LOADER_FILE_H +#define MLK_RPG_TILESET_LOADER_FILE_H + +/** + * \file mlk/rpg/tileset-loader-file.h + * \brief Tileset file loader implementation + * + * This convenient tileset loader loads tilesets from file and its associative + * resources relative to the tileset file directory. + * + * It also allocate memory for individual element and as such must be kept + * valid until the tileset is no longer used. If this behavior is not wanted, + * the allocator functions can be changed. + */ + +#include + +struct mlk_animation; +struct mlk_sprite; +struct mlk_texture; +struct mlk_tileset_animation; +struct mlk_tileset_collision; +struct mlk_tileset_loader; + +/** + * \struct mlk_tileset_loader_file + * \brief Tileset file loader structure + */ +struct mlk_tileset_loader_file { + /** + * (read-only) + * + * Computed tileset file directory. + */ + char directory[MLK_PATH_MAX]; + + /** \cond MLK_PRIVATE_DECLS */ + struct mlk_texture **textures; + struct mlk_sprite **sprites; + struct mlk_animation **animations; + struct mlk_tileset_collision *tilecollisions; + struct mlk_tileset_animation *tileanimations; + /** \endcond MLK_PRIVATE_DECLS */ +}; + +/** + * Fill the abstract loader with appropriate implementation. + * + * All loader member functions will be set and ::mlk_tileset_loader::data will + * be set to file loader. + * + * The file and loader structure are zero'ed before being initialized. + * + * \pre file != NULL + * \pre loader != NULL + * \pre filename != NULL + * \param file the file loader + * \param loader the abstract loader interface + * \param filename path to the tileset file + */ +void +mlk_tileset_loader_file_init(struct mlk_tileset_loader_file *file, + struct mlk_tileset_loader *loader, + const char *filename); + +/** + * Cleanup allocated resources by this file loader. + * + * \pre file != NULL + * \param file the file loader + */ +void +mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file); + +#endif /* !MLK_RPG_TILESET_LOADER_FILE_H */ diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset-loader.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-rpg/mlk/rpg/tileset-loader.c Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,318 @@ +/* + * tileset-loader.c -- abstract tileset loader + * + * Copyright (c) 2020-2023 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "tileset-loader.h" +#include "tileset.h" + +static int +collision_cmp(const void *d1, const void *d2) +{ + const struct mlk_tileset_collision *c1 = d1; + const struct mlk_tileset_collision *c2 = d2; + + if (c1->id < c2->id) + return -1; + if (c1->id > c2->id) + return 1; + + return 0; +} + +static int +animation_cmp(const void *d1, const void *d2) +{ + const struct mlk_tileset_animation *a1 = d1; + const struct mlk_tileset_animation *a2 = d2; + + if (a1->id < a2->id) + return -1; + if (a1->id > a2->id) + return 1; + + return 0; +} + +static int +parse_tilewidth(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)loader; + (void)tileset; + (void)fp; + + if (sscanf(line, "tilewidth|%u", &loader->tilewidth) != 1 || loader->tilewidth == 0) + return mlk_errf("tilewidth is null or invalid"); + + return 0; +} + +static int +parse_tileheight(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)loader; + (void)tileset; + (void)fp; + + if (sscanf(line, "tileheight|%u", &loader->tileheight) != 1 || loader->tileheight == 0) + return mlk_errf("tileheight is null or invalid"); + + return 0; +} + +static int +parse_collisions(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)line; + + struct mlk_tileset_collision *array, *collision, *collisions = NULL; + unsigned int id, w, h; + int x, y; + size_t collisionsz = 0; + + while (fscanf(fp, "%u|%d|%d|%u|%u\n", &id, &x, &y, &w, &h) == 5) { + if (!(array = loader->expand_collisions(loader, collisions, collisionsz + 1))) + return -1; + + collisions = array; + collision = &collisions[collisionsz++]; + collision->id = id; + collision->x = x; + collision->y = y; + collision->w = w; + collision->h = h; + } + + /* + * Sort and link this array in the final tileset, user has ownership of + * the data. + */ + qsort(collisions, collisionsz, sizeof (*collisions), collision_cmp); + tileset->collisions = collisions; + tileset->collisionsz = collisionsz; + + return 0; +} + +static int +parse_animations(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)line; + + char fmt[64], filename[MLK_PATH_MAX]; + unsigned int id, delay; + struct mlk_tileset_animation *array, *tileanimation, *tileanimations = NULL; + struct mlk_texture *texture; + struct mlk_sprite *sprite; + struct mlk_animation *animation; + size_t tileanimationsz = 0; + + /* Create a format string for fscanf. */ + snprintf(fmt, sizeof (fmt), "%%u|%%%zu[^|]|%%u", sizeof (filename)); + + /* + * When parsing animations, we have to create three different + * structures: + * + * 1. The texture itself. + * 2. The sprite object that will use the above texture. + * 3. The animation object. + * 4. Link the animation to the tileset animation. + */ + while (fscanf(fp, fmt, &id, filename, &delay) == 3) { + if (!(texture = loader->init_texture(loader, filename))) + return -1; + if (!(sprite = loader->init_sprite(loader))) + return -1; + if (!(animation = loader->init_animation(loader))) + return -1; + if (!(array = loader->expand_animations(loader, tileanimations, tileanimationsz + 1))) + return -1; + + /* Bind the texture to the new sprite. */ + sprite->texture = texture; + sprite->cellw = loader->tilewidth; + sprite->cellh = loader->tileheight; + mlk_sprite_init(sprite); + + /* Bind the sprite to the new animation. */ + animation->sprite = sprite; + animation->delay = delay; + + /* Add the animation to the array. */ + tileanimations = array; + tileanimation = &tileanimations[tileanimationsz++]; + tileanimation->id = id; + tileanimation->animation = animation; + } + + /* + * Sort and link this array in the final tileset, user has ownership of + * the data. + */ + qsort(tileanimations, tileanimationsz, sizeof (*tileanimations), animation_cmp); + tileset->animations = tileanimations; + tileset->animationsz = tileanimationsz; + + return 0; +} + +static int +parse_image(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)fp; + + const char *p; + struct mlk_texture *texture; + struct mlk_sprite *sprite; + + if (loader->tilewidth == 0 || loader->tileheight == 0) + return mlk_errf("missing tile dimensions before image"); + if (!(p = strchr(line, '|'))) + return mlk_errf("could not parse image"); + if (!(texture = loader->init_texture(loader, p + 1))) + return -1; + if (!(sprite = loader->init_sprite(loader))) + return -1; + + /* Initialize the sprite with the texture. */ + sprite->texture = texture; + sprite->cellw = loader->tilewidth; + sprite->cellh = loader->tileheight; + mlk_sprite_init(sprite); + + /* Link this texture to the final tileset. */ + tileset->sprite = sprite; + + return 0; +} + +static int +parse_line(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + static const struct { + const char *property; + int (*read)(struct mlk_tileset_loader *, struct mlk_tileset *, const char *, FILE *); + } props[] = { + { "tilewidth", parse_tilewidth }, + { "tileheight", parse_tileheight }, + { "collisions", parse_collisions }, + { "animations", parse_animations }, + { "image", parse_image } + }; + + for (size_t i = 0; i < MLK_UTIL_SIZE(props); ++i) { + if (strncmp(line, props[i].property, strlen(props[i].property)) == 0) + return props[i].read(loader, tileset, line, fp); + } + + return 0; +} + +static int +check(const struct mlk_tileset *tileset) +{ + if (!tileset->sprite) + return mlk_errf("missing tileset image"); + + return 0; +} + +static int +parse(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset, FILE *fp) +{ + char line[128]; + + while (fgets(line, sizeof (line), fp)) { + /* Remove \n if any */ + line[strcspn(line, "\r\n")] = 0; + + if (parse_line(loader, tileset, line, fp) < 0) + return -1; + } + + return check(tileset); +} + +int +mlk_tileset_loader_open(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *path) +{ + assert(loader); + assert(tileset); + assert(path); + + FILE *fp; + + memset(tileset, 0, sizeof (*tileset)); + + if (!(fp = fopen(path, "r"))) + return mlk_errf("%s", strerror(errno)); + + return parse(loader, tileset, fp); +} + +int +mlk_tileset_loader_openmem(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const void *data, + size_t datasz) +{ + assert(loader); + assert(tileset); + assert(data); + + FILE *fp; + + memset(tileset, 0, sizeof (*tileset)); + + if (!(fp = mlk_util_fmemopen((void *)data, datasz, "r"))) + return -1; + + return parse(loader, tileset, fp); +} diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset-loader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-rpg/mlk/rpg/tileset-loader.h Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,169 @@ +/* + * tileset-loader.h -- abstract tileset loader + * + * Copyright (c) 2020-2023 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MLK_RPG_TILESET_LOADER_H +#define MLK_RPG_TILESET_LOADER_H + +/** + * \file mlk/rpg/tileset-loader.h + * \brief Abstract tileset loader + * + * This module provides a generic way to open tilesets. It uses a callback + * system whenever an action has to be taken by the user. by itself, this + * module does not alloate nor owns any data. + * + * It is designed in mind that the loader knows how to decode a tileset data + * format file but has no indication on how it should allocate, arrange and + * find tileset images and other resources. + * + * See tileset-file.h for an implementation of this module using files. + */ + +#include + +struct mlk_animation; +struct mlk_sprite; +struct mlk_texture; +struct mlk_tileset; +struct mlk_tileset_animations; +struct mlk_tileset_collision; + +/** + * \struct mlk_tileset_loader + * \brief Abstract loader structure + * + * All function pointers must be set. + */ +struct mlk_tileset_loader { + /** + * (read-write, borrowed, optional) + * + * Arbitrary user data for callbacks. + */ + void *data; + + /** + * (read-write) + * + * Open a texture from the given ident name. + * + * \param self this loader + * \param ident the texture name (or path) + * \return a borrowed texture or NULL on failure + */ + struct mlk_texture * (*init_texture)(struct mlk_tileset_loader *self, + const char *ident); + + /** + * (read-write) + * + * Return a sprite that the loader needs. + * + * \param self this loader + * \return a unused sprite + * \return a borrowed sprite or NULL on failure + */ + struct mlk_sprite * (*init_sprite)(struct mlk_tileset_loader *self); + + /** + * (read-write) + * + * Return an animation that the loader needs. + * + * \param self this loader + * \return a unused animation + * \return a borrowed animation or NULL on failure + */ + struct mlk_animation * (*init_animation)(struct mlk_tileset_loader *self); + + /** + * (read-write) + * + * Expand the collision array by one element. + * + * \param self this loader + * \param array the old array (can be NULL) to reallocate + * \param arraysz the new array size (usually +1 than before) + * \return a unused animation + */ + struct mlk_tileset_collision * (*expand_collisions)(struct mlk_tileset_loader *self, + struct mlk_tileset_collision *array, + size_t arraysz); + + /** + * (read-write) + * + * Expand the animation array by one element. + * + * \param self this loader + * \param array the old array (can be NULL) to reallocate + * \param arraysz the new array size (usually +1 than before) + * \return a unused animation + */ + struct mlk_tileset_animation * (*expand_animations)(struct mlk_tileset_loader *self, + struct mlk_tileset_animation *array, + size_t arraysz); + + /** \cond MLK_PRIVATE_DECLS */ + unsigned int tilewidth; + unsigned int tileheight; + /** \endcond MLK_PRIVATE_DECLS */ +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * Open a tileset from a filesystem path. + * + * \pre loader != NULL + * \param loader the loader + * \param tileset the tileset destination + * \param path the path to the tileset file + * \return 0 on success or an error code on failure + */ +int +mlk_tileset_loader_open(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *path); + +/** + * Open a tileset from a const binary data. + * + * The binary data must be kept alive until the tileset loader is no longer + * used. + * + * \pre loader != NULL + * \param loader the loader + * \param tileset the tileset destination + * \param data the tileset content + * \param datasz the tileset content length + * \return 0 on success or an error code on failure + */ +int +mlk_tileset_loader_openmem(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const void *data, + size_t datasz); + +#if defined(__cplusplus) +} +#endif + +#endif /* !MLK_RPG_TILESET_LOADER_H */ diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset.c --- a/libmlk-rpg/mlk/rpg/tileset.c Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-rpg/mlk/rpg/tileset.c Tue Mar 07 20:45:00 2023 +0100 @@ -25,40 +25,44 @@ #include "tileset.h" static inline int -anim_cmp(const void *d1, const void *d2) +animation_cmp(const void *d1, const void *d2) { - const struct tileset_animation *mtd1 = d1; - const struct tileset_animation *mtd2 = d2; + const struct mlk_tileset_animation *a1 = d1; + const struct mlk_tileset_animation *a2 = d2; - if (mtd1->id < mtd2->id) + if (a1->id < a2->id) return -1; - if (mtd1->id > mtd2->id) + if (a1->id > a2->id) return 1; return 0; } -static inline const struct tileset_animation * -find(const struct tileset *ts, unsigned int r, unsigned int c) +static inline const struct mlk_tileset_animation * +find(const struct mlk_tileset *tileset, unsigned int r, unsigned int c) { - const struct tileset_animation key = { - .id = c + (r * ts->sprite->ncols) + const struct mlk_tileset_animation key = { + .id = c + (r * tileset->sprite->ncols) }; - return bsearch(&key, ts->anims, ts->animsz, sizeof (key), anim_cmp); + return bsearch(&key, tileset->animations, tileset->animationsz, sizeof (key), animation_cmp); } int -tileset_ok(const struct tileset *ts) +mlk_tileset_ok(const struct mlk_tileset *tileset) { - return ts && mlk_sprite_ok(ts->sprite); + return tileset && mlk_sprite_ok(tileset->sprite); } void -tileset_start(struct tileset *ts) +mlk_tileset_start(struct mlk_tileset *tileset) { - for (size_t i = 0; i < ts->animsz; ++i) { - struct tileset_animation *ta = &ts->anims[i]; + assert(mlk_tileset_ok(tileset)); + + struct mlk_tileset_animation *ta; + + for (size_t i = 0; i < tileset->animationsz; ++i) { + ta = &tileset->animations[i]; if (ta->animation) mlk_animation_start(ta->animation); @@ -66,28 +70,36 @@ } void -tileset_update(struct tileset *ts, unsigned int ticks) +mlk_tileset_update(struct mlk_tileset *tileset, unsigned int ticks) { - for (size_t i = 0; i < ts->animsz; ++i) { - struct tileset_animation *ta = &ts->anims[i]; + assert(mlk_tileset_ok(tileset)); + + struct mlk_tileset_animation *ta; + + for (size_t i = 0; i < tileset->animationsz; ++i) { + ta = &tileset->animations[i]; if (!ta->animation) continue; + /* Reset in case it ended, we loop animations. */ if (mlk_animation_update(ta->animation, ticks)) mlk_animation_start(ta->animation); } } -void -tileset_draw(const struct tileset *ts, unsigned int r, unsigned int c, int x, int y) +int +mlk_tileset_draw(const struct mlk_tileset *tileset, unsigned int r, unsigned int c, int x, int y) { - assert(ts); + assert(mlk_tileset_ok(tileset)); + + const struct mlk_tileset_animation *ta; + int ret; - const struct tileset_animation *ta; + if ((ta = find(tileset, r, c))) + ret = mlk_animation_draw(ta->animation, x, y); + else + ret = mlk_sprite_draw(tileset->sprite, r, c, x, y); - if ((ta = find(ts, r, c))) - mlk_animation_draw(ta->animation, x, y); - else - mlk_sprite_draw(ts->sprite, r, c, x, y); + return ret; } diff -r 856c2e96189d -r ffd972a3d084 libmlk-rpg/mlk/rpg/tileset.h --- a/libmlk-rpg/mlk/rpg/tileset.h Mon Mar 06 20:44:43 2023 +0100 +++ b/libmlk-rpg/mlk/rpg/tileset.h Tue Mar 07 20:45:00 2023 +0100 @@ -19,28 +19,115 @@ #ifndef MLK_RPG_TILESET_H #define MLK_RPG_TILESET_H +/** + * \file mlk/rpg/tileset.h + * \brief Map tileset definition + */ + #include +struct mlk_animation; struct mlk_sprite; -struct tileset_tiledef { - unsigned short id; - short x; - short y; - unsigned short w; - unsigned short h; +/** + * \struct mlk_tileset_collision + * \brief Describe a tile collision box. + */ +struct mlk_tileset_collision { + /** + * (read-write) + * + * The sprite cell index. + */ + unsigned int id; + + /** + * (read-write) + * + * Beginning of collision box in x. + */ + int x; + + /** + * (read-write) + * + * Beginning of collision box in y. + */ + int y; + + /** + * (read-write) + * + * Collision box width. + */ + unsigned int w; + + /** + * (read-write) + * + * Collision box height. + */ + unsigned int h; }; -struct tileset_animation { - unsigned short id; +/** + * \struct mlk_tileset_animation + * \brief Animation per tile + */ +struct mlk_tileset_animation { + /** + * (read-write) + * + * The sprite cell index. + */ + unsigned int id; + + /** + * (read-write, borrowed) + * + * Animation to used for this tile. + */ struct mlk_animation *animation; }; -struct tileset { - struct tileset_tiledef *tiledefs; - size_t tiledefsz; - struct tileset_animation *anims; - size_t animsz; +/** + * \struct mlk_tileset + * \brief Tileset structure + */ +struct mlk_tileset { + /** + * (read-write, borrowed, optional) + * + * Array of collision boxes per tile that MUST be order by tile id. + */ + struct mlk_tileset_collision *collisions; + + /** + * (read-write) + * + * Number of items in the ::mlk_tileset::collisions array. + */ + size_t collisionsz; + + /** + * (read-write, borrowed, optional) + * + * Array of animations per tile that MUST be order by tile id. + */ + struct mlk_tileset_animation *animations; + + /** + * (read-write) + * + * Number of items in the ::mlk_tileset::animations array. + */ + size_t animationsz; + + /** + * (read-write, borrowed) + * + * Sprite used to render the map. + */ struct mlk_sprite *sprite; }; @@ -48,17 +135,47 @@ extern "C" { #endif +/** + * Tells if the tileset is usable. + * + * \param tileset the tileset to check + * \return non-zero if the tileset structure is usable + */ int -tileset_ok(const struct tileset *); +mlk_tileset_ok(const struct mlk_tileset *tileset); +/** + * Start tileset animations. + * + * \pre tileset != NULL + * \param tileset the tileset + */ void -tileset_start(struct tileset *); +mlk_tileset_start(struct mlk_tileset *tileset); +/** + * Update the tileset animations. + * + * \pre tileset != NULL + * \param tileset the tileset + * \param ticks frame ticks + */ void -tileset_update(struct tileset *, unsigned int); +mlk_tileset_update(struct mlk_tileset *tileset, unsigned int ticks); -void -tileset_draw(const struct tileset *, unsigned int, unsigned int, int, int); +/** + * Draw a cell row/column into the given position. + * + * \pre tileset != NULL + * \param tileset the tileset + * \param r the cell row number + * \param c the cell column number + * \param x the x coordinate + * \param y the y coordinate + * \return 0 on success or an error code on failure + */ +int +mlk_tileset_draw(const struct mlk_tileset *tileset, unsigned int r, unsigned int c, int x, int y); #if defined(__cplusplus) } diff -r 856c2e96189d -r ffd972a3d084 tests/CMakeLists.txt --- a/tests/CMakeLists.txt Mon Mar 06 20:44:43 2023 +0100 +++ b/tests/CMakeLists.txt Tue Mar 07 20:45:00 2023 +0100 @@ -25,7 +25,6 @@ alloc color drawable - map save save-quest state diff -r 856c2e96189d -r ffd972a3d084 tests/assets/maps/error-image.tileset --- a/tests/assets/maps/error-image.tileset Mon Mar 06 20:44:43 2023 +0100 +++ b/tests/assets/maps/error-image.tileset Tue Mar 07 20:45:00 2023 +0100 @@ -1,6 +1,6 @@ tilewidth|64 tileheight|32 -tiledefs +collisions 129|8|0|56|40 130|0|0|62|40 132|0|0|64|40 diff -r 856c2e96189d -r ffd972a3d084 tests/assets/maps/error-tileheight.tileset --- a/tests/assets/maps/error-tileheight.tileset Mon Mar 06 20:44:43 2023 +0100 +++ b/tests/assets/maps/error-tileheight.tileset Tue Mar 07 20:45:00 2023 +0100 @@ -1,6 +1,6 @@ tilewidth|64 image|sample-tileset.png -tiledefs +collisions 129|8|0|56|40 130|0|0|62|40 132|0|0|64|40 diff -r 856c2e96189d -r ffd972a3d084 tests/assets/maps/error-tilewidth.tileset --- a/tests/assets/maps/error-tilewidth.tileset Mon Mar 06 20:44:43 2023 +0100 +++ b/tests/assets/maps/error-tilewidth.tileset Tue Mar 07 20:45:00 2023 +0100 @@ -1,6 +1,6 @@ tileheight|32 image|sample-tileset.png -tiledefs +collisions 129|8|0|56|40 130|0|0|62|40 132|0|0|64|40 diff -r 856c2e96189d -r ffd972a3d084 tests/assets/maps/sample-tileset.tileset --- a/tests/assets/maps/sample-tileset.tileset Mon Mar 06 20:44:43 2023 +0100 +++ b/tests/assets/maps/sample-tileset.tileset Tue Mar 07 20:45:00 2023 +0100 @@ -1,7 +1,7 @@ tilewidth|64 tileheight|32 image|sample-tileset.png -tiledefs +collisions 129|8|0|56|40 130|0|0|62|40 132|0|0|64|40 diff -r 856c2e96189d -r ffd972a3d084 tests/test-tileset.c --- a/tests/test-tileset.c Mon Mar 06 20:44:43 2023 +0100 +++ b/tests/test-tileset.c Tue Mar 07 20:45:00 2023 +0100 @@ -17,88 +17,121 @@ */ #include +#include +#include #include -#include +#include +#include #include #include -static void -test_basics_sample(void) -{ - struct tileset_file loader = {0}; - struct tileset tileset; - - DT_EQ_INT(tileset_file_open(&loader, &tileset, DIRECTORY "/maps/sample-tileset.tileset"), 0); - DT_EQ_UINT(tileset.sprite->cellw, 64U); - DT_EQ_UINT(tileset.sprite->cellh, 32U); - - DT_EQ_UINT(tileset.tiledefsz, 4U); - - DT_EQ_UINT(tileset.tiledefs[0].id, 129); - DT_EQ_UINT(tileset.tiledefs[0].x, 8); - DT_EQ_UINT(tileset.tiledefs[0].y, 0); - DT_EQ_UINT(tileset.tiledefs[0].w, 56); - DT_EQ_UINT(tileset.tiledefs[0].h, 40); +/* + * Convenient struct that pack all the required data. + */ +struct tileset { + struct mlk_tileset_loader_file file; + struct mlk_tileset_loader loader; + struct mlk_tileset tileset; +}; - DT_EQ_UINT(tileset.tiledefs[1].id, 130); - DT_EQ_UINT(tileset.tiledefs[1].x, 0); - DT_EQ_UINT(tileset.tiledefs[1].y, 0); - DT_EQ_UINT(tileset.tiledefs[1].w, 62); - DT_EQ_UINT(tileset.tiledefs[1].h, 40); +static inline int +tileset_open(struct tileset *ts, const char *path) +{ + mlk_tileset_loader_file_init(&ts->file, &ts->loader, path); - DT_EQ_UINT(tileset.tiledefs[2].id, 132); - DT_EQ_UINT(tileset.tiledefs[2].x, 0); - DT_EQ_UINT(tileset.tiledefs[2].y, 0); - DT_EQ_UINT(tileset.tiledefs[2].w, 64); - DT_EQ_UINT(tileset.tiledefs[2].h, 40); + return mlk_tileset_loader_open(&ts->loader, &ts->tileset, path); +} - DT_EQ_UINT(tileset.tiledefs[3].id, 133); - DT_EQ_UINT(tileset.tiledefs[3].x, 0); - DT_EQ_UINT(tileset.tiledefs[3].y, 0); - DT_EQ_UINT(tileset.tiledefs[3].w, 58); - DT_EQ_UINT(tileset.tiledefs[3].h, 40); - - tileset_file_finish(&loader); +static inline void +tileset_finish(struct tileset *ts) +{ + mlk_tileset_loader_file_finish(&ts->file); } static void -test_error_tilewidth(void) +test_basics_sample(struct tileset *ts) { - struct tileset_file loader = {0}; - struct tileset tileset = {0}; + DT_EQ_INT(tileset_open(ts, DIRECTORY "/maps/sample-tileset.tileset"), 0); + DT_EQ_UINT(ts->tileset.sprite->cellw, 64U); + DT_EQ_UINT(ts->tileset.sprite->cellh, 32U); + + DT_ASSERT(ts->tileset.collisions); + DT_EQ_UINT(ts->tileset.collisionsz, 4U); + + DT_EQ_UINT(ts->tileset.collisions[0].id, 129); + DT_EQ_UINT(ts->tileset.collisions[0].x, 8); + DT_EQ_UINT(ts->tileset.collisions[0].y, 0); + DT_EQ_UINT(ts->tileset.collisions[0].w, 56); + DT_EQ_UINT(ts->tileset.collisions[0].h, 40); - DT_EQ_INT(tileset_file_open(&loader, &tileset, DIRECTORY "/maps/error-tilewidth.tileset"), -1); + DT_EQ_UINT(ts->tileset.collisions[1].id, 130); + DT_EQ_UINT(ts->tileset.collisions[1].x, 0); + DT_EQ_UINT(ts->tileset.collisions[1].y, 0); + DT_EQ_UINT(ts->tileset.collisions[1].w, 62); + DT_EQ_UINT(ts->tileset.collisions[1].h, 40); + + DT_EQ_UINT(ts->tileset.collisions[2].id, 132); + DT_EQ_UINT(ts->tileset.collisions[2].x, 0); + DT_EQ_UINT(ts->tileset.collisions[2].y, 0); + DT_EQ_UINT(ts->tileset.collisions[2].w, 64); + DT_EQ_UINT(ts->tileset.collisions[2].h, 40); + + DT_EQ_UINT(ts->tileset.collisions[3].id, 133); + DT_EQ_UINT(ts->tileset.collisions[3].x, 0); + DT_EQ_UINT(ts->tileset.collisions[3].y, 0); + DT_EQ_UINT(ts->tileset.collisions[3].w, 58); + DT_EQ_UINT(ts->tileset.collisions[3].h, 40); } static void -test_error_tileheight(void) +test_error_tilewidth(struct tileset *ts) { - struct tileset_file loader = {0}; - struct tileset tileset = {0}; + DT_EQ_INT(tileset_open(ts, DIRECTORY "/maps/error-tilewidth.tileset"), -1); + DT_EQ_STR(mlk_err(), "missing tile dimensions before image"); +} - DT_EQ_INT(tileset_file_open(&loader, &tileset, DIRECTORY "/maps/error-tileheight.tileset"), -1); +static void +test_error_tileheight(struct tileset *ts) +{ + DT_EQ_INT(tileset_open(ts, DIRECTORY "/maps/error-tileheight.tileset"), -1); + DT_EQ_STR(mlk_err(), "missing tile dimensions before image"); } static void -test_error_image(void) +test_error_image(struct tileset *ts) { - struct tileset_file loader = {0}; - struct tileset tileset = {0}; + DT_EQ_INT(tileset_open(ts, DIRECTORY "/maps/error-image.tileset"), -1); + DT_EQ_STR(mlk_err(), "missing tileset image"); +} - DT_EQ_INT(tileset_file_open(&loader, &tileset, DIRECTORY "/maps/error-image.tileset"), -1); +static void +setup(struct tileset *ts) +{ + (void)ts; +} + +static void +teardown(struct tileset *ts) +{ + tileset_finish(ts); } int main(void) { + struct tileset ts; + if (mlk_core_init("fr.malikania", "test") < 0 || mlk_window_open("test-tileset", 100, 100) < 0) return 1; - DT_RUN(test_basics_sample); - DT_RUN(test_error_tilewidth); - DT_RUN(test_error_tileheight); - DT_RUN(test_error_image); + DT_RUN_EX(test_basics_sample, setup, teardown, &ts); + DT_RUN_EX(test_error_tilewidth, setup, teardown, &ts); + DT_RUN_EX(test_error_tileheight, setup, teardown, &ts); + DT_RUN_EX(test_error_image, setup, teardown, &ts); DT_SUMMARY(); + + mlk_window_finish(); + mlk_core_finish(); }