changeset 554:cdbc13ceff85

rpg: do the same for map
author David Demelier <markand@malikania.fr>
date Tue, 07 Mar 2023 22:15:35 +0100
parents cb4508f45048
children 6c911cbc1fd7
files examples/CMakeLists.txt examples/example-map/CMakeLists.txt examples/example-map/example-map.c libmlk-example/CMakeLists.txt libmlk-example/assets/sprites/john.png libmlk-example/mlk/example/registry.c libmlk-example/mlk/example/registry.h libmlk-rpg/CMakeLists.txt libmlk-rpg/mlk/rpg/map-file.c libmlk-rpg/mlk/rpg/map-file.h libmlk-rpg/mlk/rpg/map-loader-file.c libmlk-rpg/mlk/rpg/map-loader-file.h libmlk-rpg/mlk/rpg/map-loader.c libmlk-rpg/mlk/rpg/map-loader.h libmlk-rpg/mlk/rpg/map.c libmlk-rpg/mlk/rpg/map.h libmlk-rpg/mlk/rpg/tileset-loader-file.h libmlk-rpg/mlk/rpg/tileset-loader.c libmlk-rpg/mlk/rpg/tileset-loader.h mlk-map/mlk-map.c
diffstat 20 files changed, 1047 insertions(+), 522 deletions(-) [+]
line wrap: on
line diff
--- a/examples/CMakeLists.txt	Tue Mar 07 20:58:00 2023 +0100
+++ b/examples/CMakeLists.txt	Tue Mar 07 22:15:35 2023 +0100
@@ -29,6 +29,7 @@
 	example-font
 	example-gridmenu
 	example-label
+	example-map
 	example-message
 	example-notify
 	example-sprite
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-map/CMakeLists.txt	Tue Mar 07 22:15:35 2023 +0100
@@ -0,0 +1,33 @@
+#
+# CMakeLists.txt -- CMake build system for Molko's Engine
+#
+# Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
+#
+# 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-map)
+
+set(
+	SOURCES
+	${example-map_SOURCE_DIR}/example-map.c
+)
+
+mlk_executable(
+	NAME example-map
+	FOLDER examples
+	LIBRARIES libmlk-example
+	SOURCES ${SOURCES}
+)
+
+source_group(TREE ${example-map_SOURCE_DIR} FILES ${SOURCES})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-map/example-map.c	Tue Mar 07 22:15:35 2023 +0100
@@ -0,0 +1,203 @@
+/*
+ * example-map.c -- example on how to use a map
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <mlk/core/animation.h>
+#include <mlk/core/core.h>
+#include <mlk/core/err.h>
+#include <mlk/core/event.h>
+#include <mlk/core/game.h>
+#include <mlk/core/image.h>
+#include <mlk/core/key.h>
+#include <mlk/core/painter.h>
+#include <mlk/core/panic.h>
+#include <mlk/core/state.h>
+#include <mlk/core/sys.h>
+#include <mlk/core/trace.h>
+#include <mlk/core/util.h>
+#include <mlk/core/window.h>
+
+#include <mlk/ui/label.h>
+#include <mlk/ui/ui.h>
+
+#include <mlk/rpg/map-loader-file.h>
+#include <mlk/rpg/map-loader.h>
+#include <mlk/rpg/map.h>
+#include <mlk/rpg/tileset-loader-file.h>
+#include <mlk/rpg/tileset-loader.h>
+#include <mlk/rpg/tileset.h>
+
+#include <mlk/example/example.h>
+#include <mlk/example/registry.h>
+
+#include <assets/maps/world.h>
+#include <assets/tilesets/world.h>
+
+static struct mlk_tileset_loader_file tileset_loader_file;
+static struct mlk_tileset_loader tileset_loader;
+static struct mlk_tileset tileset;
+
+static struct mlk_map_loader_file map_loader_file;
+static struct mlk_map_loader map_loader;
+static struct mlk_map map;
+
+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;
+}
+
+struct mlk_tileset *
+init_tileset(struct mlk_map_loader *loader, struct mlk_map *map, const char *ident)
+{
+	(void)loader;
+	(void)map;
+	(void)ident;
+
+	/*
+	 * For this example, we assume that the ident contains "world.tileset"
+	 * because there are no other in the map we will load.
+	 */
+	mlk_tracef("Searching tileset %s", ident);
+
+	if (mlk_tileset_loader_openmem(&tileset_loader, &tileset, assets_tilesets_world, sizeof (assets_tilesets_world)) < 0)
+		mlk_panic();
+
+	return &tileset;
+}
+
+static void
+init(void)
+{
+	if (mlk_example_init("example-map") < 0)
+		mlk_panic();
+
+	/*
+	 * Just like in example-tileset.c, we want to open images from the
+	 * registry which is not supported by itself in
+	 * mlk_tileset_loader_file.
+	 */
+	mlk_tileset_loader_file_init(&tileset_loader_file, &tileset_loader, "");
+	tileset_loader.init_texture = init_texture;
+
+	/*
+	 * Create our map loader. It will also search for a tileset to be found
+	 * on disk by default which we would like to avoid. We override the
+	 * init_tileset function.
+	 */
+	mlk_map_loader_file_init(&map_loader_file, &map_loader, "");
+	map_loader.init_tileset = init_tileset;
+
+	if (mlk_map_loader_openmem(&map_loader, &map, assets_maps_world, sizeof (assets_maps_world)) < 0)
+		mlk_panic();
+
+	// TODO: this not handled by the map yet.
+	map.player_sprite = &mlk_registry_sprites[MLK_REGISTRY_TEXTURE_JOHN];
+	mlk_map_init(&map);
+}
+
+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:
+		mlk_map_handle(&map, ev);
+		break;
+	}
+}
+
+static void
+update(struct mlk_state *st, unsigned int ticks)
+{
+	(void)st;
+
+	mlk_map_update(&map, ticks);
+}
+
+static void
+draw(struct mlk_state *st)
+{
+	(void)st;
+
+	mlk_painter_set_color(MLK_EXAMPLE_BG);
+	mlk_painter_clear();
+	mlk_map_draw(&map);
+	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_map_loader_file_finish(&map_loader_file);
+	mlk_tileset_loader_file_finish(&tileset_loader_file);
+	mlk_example_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- a/libmlk-example/CMakeLists.txt	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-example/CMakeLists.txt	Tue Mar 07 22:15:35 2023 +0100
@@ -43,6 +43,7 @@
 	${libmlk-example_SOURCE_DIR}/assets/sprites/explosion.png
 	${libmlk-example_SOURCE_DIR}/assets/sprites/john-sword.png
 	${libmlk-example_SOURCE_DIR}/assets/sprites/john-walk.png
+	${libmlk-example_SOURCE_DIR}/assets/sprites/john.png
 	${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
Binary file libmlk-example/assets/sprites/john.png has changed
--- a/libmlk-example/mlk/example/registry.c	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-example/mlk/example/registry.c	Tue Mar 07 22:15:35 2023 +0100
@@ -35,6 +35,7 @@
 #include <assets/sprites/explosion.h>
 #include <assets/sprites/john-sword.h>
 #include <assets/sprites/john-walk.h>
+#include <assets/sprites/john.h>
 #include <assets/sprites/numbers.h>
 #include <assets/sprites/people.h>
 #include <assets/sprites/ui-cursor.h>
@@ -78,6 +79,7 @@
 	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, assets_sprites_john, 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),
--- a/libmlk-example/mlk/example/registry.h	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-example/mlk/example/registry.h	Tue Mar 07 22:15:35 2023 +0100
@@ -34,6 +34,7 @@
 	MLK_REGISTRY_TEXTURE_WATER,
 
 	/* Characters. */
+	MLK_REGISTRY_TEXTURE_JOHN,
 	MLK_REGISTRY_TEXTURE_JOHN_WALK,
 	MLK_REGISTRY_TEXTURE_JOHN_SWORD,
 	MLK_REGISTRY_TEXTURE_PEOPLE,
--- a/libmlk-rpg/CMakeLists.txt	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-rpg/CMakeLists.txt	Tue Mar 07 22:15:35 2023 +0100
@@ -20,6 +20,12 @@
 
 set(
 	SOURCES
+	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-loader-file.c
+	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-loader-file.h
+	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-loader.c
+	${libmlk-rpg_SOURCE_DIR}/mlk/rpg/map-loader.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
--- a/libmlk-rpg/mlk/rpg/map-file.c	Tue Mar 07 20:58:00 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,310 +0,0 @@
-/*
- * map-file.c -- map file loader
- *
- * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
- *
- * 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 <assert.h>
-#include <errno.h>
-#include <limits.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <mlk/util/util.h>
-
-#include <mlk/core/alloc.h>
-#include <mlk/core/image.h>
-#include <mlk/core/trace.h>
-#include <mlk/core/util.h>
-
-#include "map-file.h"
-
-#define MAX_F(v) MAX_F_(v)
-#define MAX_F_(v) "%" #v "[^\n|]"
-
-struct context {
-	struct map_file *mf;            /* Map loader. */
-	struct map *map;                /* Map object to fill. */
-	FILE *fp;                       /* Map file pointer. */
-	char basedir[MLK_PATH_MAX];     /* Parent map directory */
-};
-
-static int
-parse_layer_tiles(struct context *ctx, const char *layer_name)
-{
-	enum map_layer_type layer_type;
-	size_t amount, current;
-
-	if (strcmp(layer_name, "background") == 0)
-		layer_type = MAP_LAYER_TYPE_BACKGROUND;
-	else if (strcmp(layer_name, "foreground") == 0)
-		layer_type = MAP_LAYER_TYPE_FOREGROUND;
-	else if (strcmp(layer_name, "above") == 0)
-		layer_type = MAP_LAYER_TYPE_ABOVE;
-	else
-		return mlk_errf("invalid layer type: %s", layer_name);
-
-	amount = ctx->map->columns * ctx->map->rows;
-	current = 0;
-
-	/*
-	 * The next line after a layer declaration is a list of plain integer
-	 * that fill the layer tiles.
-	 */
-	if (!(ctx->mf->layers[layer_type].tiles = mlk_alloc_new0(amount, sizeof (unsigned short))))
-		return -1;
-
-	for (int tile; fscanf(ctx->fp, "%d\n", &tile) && current < amount; ++current)
-		ctx->mf->layers[layer_type].tiles[current] = tile;
-
-	ctx->map->layers[layer_type].tiles = ctx->mf->layers[layer_type].tiles;
-
-	return 0;
-}
-
-static int
-parse_actions(struct context *ctx)
-{
-	char exec[128 + 1];
-	int x = 0, y = 0, block = 0;
-	unsigned int w = 0, h = 0;
-
-	while (fscanf(ctx->fp, "%d|%d|%u|%u|%d|%128[^\n]\n", &x, &y, &w, &h, &block, exec) >= 5) {
-		struct map_block *reg;
-
-		if (!ctx->mf->load_action) {
-			mlk_tracef("ignoring action %d,%d,%u,%u,%d,%s", x, y, w, h, block, exec);
-			continue;
-		}
-
-		ctx->mf->load_action(ctx->map, x, y, w, h, exec);
-
-		/*
-		 * Actions do not have concept of collisions because they are
-		 * not only used on maps. The map structure has its very own
-		 * object to manage collisions but the .map file use the same
-		 * directive for simplicity. So create a block region if the
-		 * directive has one.
-		 */
-		if (block) {
-			if (!(reg = mlk_alloc_pool_new(&ctx->mf->blocks)))
-				return -1;
-
-			reg->x = x;
-			reg->y = y;
-			reg->w = w;
-			reg->h = h;
-		}
-	}
-
-	/* Reference the blocks array from map_file. */
-	ctx->map->blocks = ctx->mf->blocks.data;
-	ctx->map->blocksz = ctx->mf->blocks.size;
-
-	return 0;
-}
-
-static int
-parse_layer(struct context *ctx, const char *line)
-{
-	char layer_name[32 + 1] = {0};
-
-	/* Check if weight/height has been specified. */
-	if (ctx->map->columns == 0 || ctx->map->rows == 0)
-		return mlk_errf("missing map dimensions before layer");
-
-	/* Determine layer type. */
-	if (sscanf(line, "layer|%32s", layer_name) <= 0)
-		return mlk_errf("missing layer type definition");
-
-	if (strcmp(layer_name, "actions") == 0)
-		return parse_actions(ctx);
-
-	return parse_layer_tiles(ctx, layer_name);
-}
-
-static int
-parse_tileset(struct context *ctx, const char *line)
-{
-	char path[MLK_PATH_MAX] = {0}, *p;
-	struct map_file *mf = ctx->mf;
-	struct tileset_file *tf = &mf->tileset_file;
-
-	if (!(p = strchr(line, '|')))
-		return mlk_errf("could not parse tileset");
-
-	snprintf(path, sizeof (path), "%s/%s", ctx->basedir, p + 1);
-
-	if (tileset_file_open(tf, &mf->tileset, path) < 0)
-		return -1;
-
-	ctx->map->tileset = &mf->tileset;
-
-	return 0;
-}
-
-static int
-parse_title(struct context *ctx, const char *line)
-{
-	if (sscanf(line, "title|" MAX_F(MAP_FILE_TITLE_MAX), ctx->mf->title) != 1 || strlen(ctx->mf->title) == 0)
-		return mlk_errf("null map title");
-
-	ctx->map->title = ctx->mf->title;
-
-	return 0;
-}
-
-static int
-parse_columns(struct context *ctx, const char *line)
-{
-	if (sscanf(line, "columns|%u", &ctx->map->columns) != 1 || ctx->map->columns == 0)
-		return mlk_errf("null map columns");
-
-	return 0;
-}
-
-static int
-parse_rows(struct context *ctx, const char *line)
-{
-	if (sscanf(line, "rows|%u", &ctx->map->rows) != 1 || ctx->map->rows == 0)
-		return mlk_errf("null map rows");
-
-	return 0;
-}
-
-static int
-parse_origin(struct context *ctx, const char *line)
-{
-	if (sscanf(line, "origin|%d|%d", &ctx->map->player_x, &ctx->map->player_y) != 2)
-		return mlk_errf("invalid origin");
-
-	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[] = {
-		{ "title",      parse_title             },
-		{ "columns",    parse_columns           },
-		{ "rows",       parse_rows              },
-		{ "tileset",    parse_tileset           },
-		{ "origin",     parse_origin            },
-		{ "layer",      parse_layer             },
-	};
-
-	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(struct map *map)
-{
-	/*
-	 * Check that we have parsed every required components.
-	 */
-	if (!map->title)
-		return mlk_errf("missing title");
-
-	/*
-	 * We don't need to check width/height because parsing layers and
-	 * tilesets already check for their presence, so only check layers.
-	 */
-	if (!map->layers[0].tiles)
-		return mlk_errf("missing background layer");
-	if (!map->layers[1].tiles)
-		return mlk_errf("missing foreground layer");
-	if (!tileset_ok(map->tileset))
-		return mlk_errf("missing tileset");
-
-	return 0;
-}
-
-int
-map_file_open(struct map_file *file, struct map *map, const char *path)
-{
-	assert(file);
-	assert(path);
-	assert(map);
-
-	struct context ctx = {
-		.mf = file,
-		.map = map,
-	};
-	int ret = 0;
-
-	memset(map, 0, sizeof (*map));
-	mlk_alloc_pool_init(&file->blocks, 16, sizeof (*map->blocks), NULL);
-
-	if (!(ctx.fp = fopen(path, "r")))
-		goto fail;
-	if ((ret = parse(&ctx, path)) < 0 || (ret = check(map)) < 0)
-		goto fail;
-
-	fclose(ctx.fp);
-
-	return 0;
-
-fail:
-	map_finish(map);
-	map_file_finish(file);
-
-	if (ctx.fp)
-		fclose(ctx.fp);
-
-	return -1;
-}
-
-void
-map_file_finish(struct map_file *file)
-{
-	assert(file);
-
-	mlk_alloc_free(file->layers[0].tiles);
-	mlk_alloc_free(file->layers[1].tiles);
-	mlk_alloc_free(file->layers[2].tiles);
-
-	tileset_file_finish(&file->tileset_file);
-	mlk_alloc_pool_finish(&file->blocks);
-
-	memset(file, 0, sizeof (*file));
-}
--- a/libmlk-rpg/mlk/rpg/map-file.h	Tue Mar 07 20:58:00 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- * map-file.h -- map file loader
- *
- * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
- *
- * 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_MAP_FILE_H
-#define MLK_RPG_MAP_FILE_H
-
-#include <mlk/core/alloc.h>
-#include <mlk/core/sprite.h>
-#include <mlk/core/texture.h>
-
-#include "map.h"
-#include "tileset.h"
-#include "tileset-loader.h"
-
-#define MAP_FILE_TITLE_MAX 64
-
-struct map_file {
-	void (*load_action)(struct map *, int, int, int, int, const char *);
-
-	char title[MAP_FILE_TITLE_MAX];
-	struct map_layer layers[MAP_LAYER_TYPE_NUM];
-	struct tileset_file tileset_file;
-	struct mlk_tileset tileset;
-	struct mlk_alloc_pool blocks;
-};
-
-#if defined(__cplusplus)
-extern "C" {
-#endif
-
-int
-map_file_open(struct map_file *file, struct map *map, const char *path);
-
-void
-map_file_finish(struct map_file *file);
-
-#if defined(__cplusplus)
-}
-#endif
-
-#endif /* !MLK_RPG_MAP_FILE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/mlk/rpg/map-loader-file.c	Tue Mar 07 22:15:35 2023 +0100
@@ -0,0 +1,110 @@
+/*
+ * map-loader-file.c -- map file loader implementation
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * 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 <assert.h>
+
+#include <mlk/core/alloc.h>
+
+#include "map-loader-file.h"
+#include "map-loader.h"
+#include "map.h"
+#include "tileset-loader.h"
+
+static struct mlk_tileset *
+init_tileset(struct mlk_map_loader *self,
+             struct mlk_map *map,
+             const char *ident)
+{
+	(void)map;
+
+	struct mlk_map_loader_file *file = self->data;
+	char path[MLK_PATH_MAX];
+
+	snprintf(path, sizeof (path), "%s/%s", file->directory, ident);
+
+	if (mlk_tileset_loader_open(file->tileset_loader, &file->tileset, path) < 0)
+		return NULL;
+
+	return &file->tileset;
+}
+
+static unsigned int *
+alloc_tiles(struct mlk_map_loader *self,
+            struct mlk_map *map,
+            enum mlk_map_layer_type type,
+            size_t n)
+{
+	(void)map;
+
+	struct mlk_map_loader_file *file = self->data;
+
+	return file->tiles[type] = mlk_alloc_new0(n, sizeof (unsigned int));
+}
+
+static struct mlk_map_block *
+expand_blocks(struct mlk_map_loader *self,
+              struct mlk_map_block *blocks,
+              size_t blocksz)
+{
+	struct mlk_map_loader_file *file = self->data;
+	struct mlk_map_block *ptr;
+
+	if (!file->blocks)
+		ptr = mlk_alloc_new0(1, sizeof (*ptr));
+	else
+		ptr = mlk_alloc_expand(file->blocks, blocksz);
+
+	if (ptr)
+		file->blocks = blocks;
+
+	return ptr;
+}
+
+void
+mlk_map_loader_file_init(struct mlk_map_loader_file *file,
+                         struct mlk_map_loader *loader,
+                         const char *filename)
+{
+	assert(file);
+	assert(loader);
+
+	char filepath[MLK_PATH_MAX];
+
+	/* 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_tileset = init_tileset;
+	loader->alloc_tiles = alloc_tiles;
+	loader->expand_blocks = expand_blocks;
+}
+
+void
+mlk_map_loader_file_finish(struct mlk_map_loader_file *file)
+{
+	assert(file);
+
+	for (int i = 0; i < MLK_MAP_LAYER_TYPE_LAST; ++i) {
+		mlk_alloc_free(file->tiles[i]);
+		file->tiles[i] = NULL;
+	}
+
+	mlk_alloc_free(file->blocks);
+	file->blocks = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/mlk/rpg/map-loader-file.h	Tue Mar 07 22:15:35 2023 +0100
@@ -0,0 +1,89 @@
+/*
+ * map-loader-file.h -- map file loader implementation
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * 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_MAP_LOADER_FILE_H
+#define MLK_RPG_MAP_LOADER_FILE_H
+
+/**
+ * \file map-loader-file.h
+ * \brief Map file loader implementation
+ */
+
+#include <mlk/util/util.h>
+
+#include "map.h"
+#include "tileset.h"
+
+struct mlk_map_loader;
+struct mlk_tileset_loader;
+
+/**
+ * \struct mlk_map_loader_file
+ * \brief Map loader file structure
+ */
+struct mlk_map_loader_file {
+	/**
+	 * (read-only)
+	 *
+	 * Computed map file directory.
+	 */
+	char directory[MLK_PATH_MAX];
+
+	/**
+	 * (read-write, borrowed)
+	 *
+	 * The tileset loader to use when finding tilesets in maps.
+	 */
+	struct mlk_tileset_loader *tileset_loader;
+
+	/** \cond MLK_PRIVATE_DECLS */
+	unsigned int *tiles[MLK_MAP_LAYER_TYPE_LAST];
+	struct mlk_tileset tileset;
+	struct mlk_map_block *blocks;
+	/** \endcond MLK_PRIVATE_DECLS */
+};
+
+/**
+ * Initialize the map loader.
+ *
+ * All loader member functions will be set and ::mlk_map_loader::data will be
+ * set to file loader.
+ *
+ * \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 map file
+ */
+void
+mlk_map_loader_file_init(struct mlk_map_loader_file *file,
+                         struct mlk_map_loader *loader,
+                         const char *filename);
+
+/**
+ * Cleanup allocated resources by this file loader.
+ *
+ * \pre file != NULL
+ * \param file the file loader
+ * \warning the map loaded with this loader must not be used
+ */
+void
+mlk_map_loader_file_finish(struct mlk_map_loader_file *file);
+
+#endif /* !MLK_RPG_MAP_LOADER_FILE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/mlk/rpg/map-loader.c	Tue Mar 07 22:15:35 2023 +0100
@@ -0,0 +1,291 @@
+/*
+ * map-loader.c -- abstract map loader
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * 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 <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <mlk/util/util.h>
+
+#include <mlk/core/err.h>
+#include <mlk/core/trace.h>
+#include <mlk/core/util.h>
+
+#include "map-loader.h"
+#include "map.h"
+
+static int
+parse_layer_tiles(struct mlk_map_loader *loader, struct mlk_map *map, const char *layer_name, FILE *fp)
+{
+	enum mlk_map_layer_type layer_type;
+	size_t amount, current;
+	unsigned int *tiles;
+
+	if (strcmp(layer_name, "background") == 0)
+		layer_type = MLK_MAP_LAYER_TYPE_BG;
+	else if (strcmp(layer_name, "foreground") == 0)
+		layer_type = MLK_MAP_LAYER_TYPE_FG;
+	else if (strcmp(layer_name, "above") == 0)
+		layer_type = MLK_MAP_LAYER_TYPE_ABOVE;
+	else
+		return mlk_errf("invalid layer type: %s", layer_name);
+
+	amount = map->columns * map->rows;
+	current = 0;
+
+	/*
+	 * The next line after a layer declaration is a list of plain integer
+	 * that fill the layer tiles.
+	 */
+	if (!(tiles = loader->alloc_tiles(loader, map, layer_type, amount)))
+		return -1;
+
+	for (unsigned int tile; fscanf(fp, "%u\n", &tile) && current < amount; ++current)
+		tiles[current] = tile;
+
+	map->layers[layer_type].tiles = tiles;
+
+	return 0;
+}
+
+static int
+parse_objects(struct mlk_map_loader *loader,
+              struct mlk_map *map,
+              const char *line,
+              FILE *fp)
+{
+	(void)line;
+
+	char fmt[64] = {0}, exec[256] = {0};
+	int x = 0, y = 0, isblock = 0;
+	unsigned int w = 0, h = 0;
+	struct mlk_map_block *array, *block, *blocks = NULL;
+	size_t blocksz = 0;
+
+	snprintf(fmt, sizeof (fmt), "%%d|%%d|%%u|%%u|%%d|%%%zu[^\n]\n", sizeof (exec) - 1);
+
+	while (fscanf(fp, fmt, &x, &y, &w, &h, &isblock, exec) >= 5) {
+		if (!loader->load_object) {
+			mlk_tracef("ignoring object %d,%d,%u,%u,%d,%s", x, y, w, h, isblock, exec);
+			continue;
+		}
+
+		loader->load_object(loader, map, x, y, w, h, exec);
+
+		/*
+		 * Actions do not have concept of collisions because they are
+		 * not only used on maps. The map structure has its very own
+		 * object to manage collisions but the .map file use the same
+		 * directive for simplicity. So create a block region if the
+		 * directive has one.
+		 */
+		if (isblock) {
+			if (!(array = loader->expand_blocks(loader, blocks, blocksz + 1)))
+				return -1;
+
+			blocks = array;
+			block = &blocks[blocksz++];
+			block->x = x;
+			block->y = y;
+			block->w = w;
+			block->h = h;
+		}
+	}
+
+	/* Reference the blocks array from map_file. */
+	map->blocks = blocks;
+	map->blocksz = blocksz;
+
+	return 0;
+}
+
+static int
+parse_layer(struct mlk_map_loader *loader,
+            struct mlk_map *map,
+            const char *line,
+            FILE *fp)
+{
+	char fmt[32] = {0}, layer_name[32] = {0};
+
+	/* Check if weight/height has been specified. */
+	if (map->columns == 0 || map->rows == 0)
+		return mlk_errf("missing map dimensions before layer");
+
+	/* Determine layer type. */
+	snprintf(fmt, sizeof (fmt), "layer|%%%zus", sizeof (layer_name) - 1);
+
+	if (sscanf(line, fmt, layer_name) <= 0)
+		return mlk_errf("missing layer type definition");
+	if (strcmp(layer_name, "actions") == 0)
+		return parse_objects(loader, map, line, fp);
+
+	return parse_layer_tiles(loader, map, layer_name, fp);
+}
+
+static int
+parse_tileset(struct mlk_map_loader *loader,
+              struct mlk_map *map,
+              const char *line,
+              FILE *fp)
+{
+	(void)fp;
+
+	const char *p;
+
+	if (!(p = strchr(line, '|')))
+		return mlk_errf("could not parse tileset");
+	if (!(map->tileset = loader->init_tileset(loader, map, p + 1)))
+		return -1;
+
+	return 0;
+}
+
+static int
+parse_columns(struct mlk_map_loader *loader,
+              struct mlk_map *map,
+              const char *line,
+              FILE *fp)
+{
+	(void)loader;
+	(void)fp;
+
+	if (sscanf(line, "columns|%u", &map->columns) != 1 || map->columns == 0)
+		return mlk_errf("null map columns");
+
+	return 0;
+}
+
+static int
+parse_rows(struct mlk_map_loader *loader,
+           struct mlk_map *map,
+           const char *line,
+           FILE *fp)
+{
+	(void)loader;
+	(void)fp;
+
+	if (sscanf(line, "rows|%u", &map->rows) != 1 || map->rows == 0)
+		return mlk_errf("null map rows");
+
+	return 0;
+}
+
+static int
+parse_origin(struct mlk_map_loader *loader,
+             struct mlk_map *map,
+             const char *line,
+             FILE *fp)
+{
+	(void)loader;
+	(void)fp;
+
+	if (sscanf(line, "origin|%d|%d", &map->player_x, &map->player_y) != 2)
+		return mlk_errf("invalid origin");
+
+	return 0;
+}
+
+static int
+parse_line(struct mlk_map_loader *loader,
+           struct mlk_map *map,
+           const char *line,
+           FILE *fp)
+{
+	static const struct {
+		const char *property;
+		int (*read)(struct mlk_map_loader *, struct mlk_map *, const char *, FILE *);
+	} props[] = {
+		{ "columns",    parse_columns           },
+		{ "rows",       parse_rows              },
+		{ "tileset",    parse_tileset           },
+		{ "origin",     parse_origin            },
+		{ "layer",      parse_layer             },
+	};
+
+	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, map, line, fp);
+
+	return 0;
+}
+
+static int
+check(struct mlk_map *map)
+{
+	/*
+	 * We don't need to check width/height because parsing layers and
+	 * tilesets already check for their presence, so only check layers.
+	 */
+	if (!map->layers[0].tiles)
+		return mlk_errf("missing background layer");
+	if (!map->layers[1].tiles)
+		return mlk_errf("missing foreground layer");
+
+	return 0;
+}
+
+static int
+parse(struct mlk_map_loader *loader, struct mlk_map *map, 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, map, line, fp) < 0)
+			return -1;
+	}
+
+	return check(map);
+}
+
+int
+mlk_map_loader_open(struct mlk_map_loader *loader, struct mlk_map *map, const char *path)
+{
+	assert(loader);
+	assert(map);
+	assert(path);
+
+	FILE *fp;
+
+	memset(map, 0, sizeof (*map));
+
+	if (!(fp = fopen(path, "r")))
+		return mlk_errf("%s", strerror(errno));
+
+	return parse(loader, map, fp);
+}
+
+int
+mlk_map_loader_openmem(struct mlk_map_loader *loader, struct mlk_map *map, const void *data, size_t datasz)
+{
+	assert(loader);
+	assert(map);
+	assert(data);
+
+	FILE *fp;
+
+	memset(map, 0, sizeof (*map));
+
+	if (!(fp = mlk_util_fmemopen((void *)data, datasz, "r")))
+		return -1;
+
+	return parse(loader, map, fp);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/mlk/rpg/map-loader.h	Tue Mar 07 22:15:35 2023 +0100
@@ -0,0 +1,154 @@
+/*
+ * map-loader.h -- abstract map loader
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * 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_MAP_LOADER_H
+#define MLK_RPG_MAP_LOADER_H
+
+#include <stddef.h>
+
+enum mlk_map_layer_type;
+
+struct mlk_map;
+struct mlk_map_block;
+struct mlk_tileset;
+
+/**
+ * \file mlk/rpg/map-loader.h
+ * \brief Abstract map loader
+ *
+ * This module provides a generic way to open maps. It uses a callback similar
+ * to the ::mlk_tileset_loader.
+ */
+struct mlk_map_loader {
+	/**
+	 * (read-write, borrowed, optional)
+	 *
+	 * Arbitrary user data for callbacks.
+	 */
+	void *data;
+
+	/**
+	 * (read-write)
+	 *
+	 * Obtain a tileset that this map requires.
+	 *
+	 * \param self this loader
+	 * \param map the underlying map being loaded
+	 * \param ident the texture name (or path)
+	 * \return the tileset to use or NULL on failure
+	 */
+	struct mlk_tileset * (*init_tileset)(struct mlk_map_loader *self,
+	                                     struct mlk_map *map,
+	                                     const char *ident);
+
+	/**
+	 * (read-write)
+	 *
+	 * Allocate the number of tiles required to fill a layer.
+	 *
+	 * \param self this loader
+	 * \param map the underlying map being loaded
+	 * \param type the layer type to allocate
+	 * \param n the number of tile items (rows * columns)
+	 * \return a pointer to a usable area or NULL on failure
+	 */
+	unsigned int * (*alloc_tiles)(struct mlk_map_loader *self,
+	                              struct mlk_map *map,
+	                              enum mlk_map_layer_type type,
+	                              size_t n);
+
+	/**
+	 * (read-write, optional)
+	 *
+	 * Load a map object from the special object layer.
+	 *
+	 * \param self this loader
+	 * \param map the underlying map being loaded
+	 * \param x the x object coordinate
+	 * \param y the y object coordinate
+	 * \param w the object width
+	 * \param h the object height
+	 * \param argument optional data to pass to the object
+	 */
+	void (*load_object)(struct mlk_map_loader *self,
+	                    struct mlk_map *map,
+	                    int x,
+	                    int y,
+	                    unsigned int w,
+	                    unsigned int h,
+	                    const char *argument);
+
+	/**
+	 * (read-write)
+	 *
+	 * Expand the array required to populate extra map collision blocks.
+	 *
+	 * \param self this loader
+	 * \param map the underlying map being loaded
+	 * \param blocks the current blocks array
+	 * \param blocksz the number of element to *append* (not the new size)
+	 * \return a pointer to a usable area or NULL on failure
+	 */
+	struct mlk_map_block * (*expand_blocks)(struct mlk_map_loader *self,
+                                                struct mlk_map *map,
+                                                struct mlk_map_block *blocks,
+                                                size_t blocksz);
+};
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Try to open a map from the given path.
+ *
+ * \pre loader != NULL
+ * \pre map != NULL
+ * \pre path != NULL
+ * \param loader the loader interface
+ * \param map the map destination
+ * \param path the path to the map file (usually ending in .map)
+ * \return 0 on success or -1 on error
+ */
+int
+mlk_map_loader_open(struct mlk_map_loader *loader,
+                    struct mlk_map *map,
+                    const char *path);
+
+/**
+ * Try to open a map from the given path.
+ *
+ * \pre loader != NULL
+ * \pre map != NULL
+ * \param loader the loader interface
+ * \param map the map destination
+ * \param data the map content
+ * \param datasz the map content length
+ * \return 0 on success or -1 on error
+ */
+int
+mlk_map_loader_openmem(struct mlk_map_loader *loader,
+                       struct mlk_map *map,
+                       const void *data,
+                       size_t datasz);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* !MLK_RPG_MAP_FILE_H */
--- a/libmlk-rpg/mlk/rpg/map.c	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/map.c	Tue Mar 07 22:15:35 2023 +0100
@@ -97,8 +97,8 @@
  * to check.
  */
 static int
-is_block_relevant(const struct map *map,
-                    const struct map_block *block,
+is_block_relevant(const struct mlk_map *map,
+                    const struct mlk_map_block *block,
                     int drow,
                     int dcol)
 {
@@ -130,8 +130,8 @@
  * new block coordinates with the previous one.
  */
 static int
-is_block_better(const struct map_block *now,
-                const struct map_block *new,
+is_block_better(const struct mlk_map_block *now,
+                const struct mlk_map_block *new,
                 int drow,
                 int dcol)
 {
@@ -143,7 +143,7 @@
 }
 
 static void
-center(struct map *map)
+center(struct mlk_map *map)
 {
 	map->view_x = map->player_x - (int)(map->view_w / 2);
 	map->view_y = map->player_y - (int)(map->view_h / 2);
@@ -160,7 +160,7 @@
 }
 
 static void
-init(struct map *map)
+init(struct mlk_map *map)
 {
 	/* Adjust view. */
 	map->view_w = mlk_window.w;
@@ -178,7 +178,7 @@
 }
 
 static void
-handle_keydown(struct map *map, const union mlk_event *event)
+handle_keydown(struct mlk_map *map, const union mlk_event *event)
 {
 	switch (event->key.key) {
 	case MLK_KEY_UP:
@@ -197,15 +197,15 @@
 		break;
 	}
 
-	map->player_angle = orientations[map->player_movement];
+	map->player_a = orientations[map->player_movement];
 }
 
 static void
-handle_keyup(struct map *map, const union mlk_event *event)
+handle_keyup(struct mlk_map *map, const union mlk_event *event)
 {
 	switch (event->key.key) {
-	case MLK_KEY_TAB:
-		map->flags ^= MAP_FLAGS_SHOW_GRID | MAP_FLAGS_SHOW_COLLIDE;
+	case MLK_KEY_F12:
+		map->flags ^= MLK_MAP_FLAGS_SHOW_GRID | MLK_MAP_FLAGS_SHOW_COLLIDE;
 		break;
 	case MLK_KEY_UP:
 		map->player_movement &= ~(MOVING_UP);
@@ -224,37 +224,39 @@
 	}
 }
 
+// TODO: merge this code in tileset maybe.
 static int
-cmp_tile(const struct tileset_tiledef *td1, const struct tileset_tiledef *td2)
+collision_cmp(const void *d1, const void *d2)
 {
-	if (td1->id < td2->id)
+	const struct mlk_tileset_collision *c1 = d1;
+	const struct mlk_tileset_collision *c2 = d2;
+
+	if (c1->id < c2->id)
 		return -1;
-	if (td1->id > td2->id)
+	if (c1->id > c2->id)
 		return 1;
 
 	return 0;
 }
 
-static struct tileset_tiledef *
-find_tiledef_by_id(const struct map *map, unsigned short id)
+static inline struct mlk_tileset_collision *
+find_collision_by_id(const struct mlk_map *map, unsigned int id)
 {
-	typedef int (*cmp)(const void *, const void *);
-
-	const struct tileset_tiledef key = {
+	const struct mlk_tileset_collision key = {
 		.id = id
 	};
 
-	return bsearch(&key, map->tileset->tiledefs, map->tileset->tiledefsz,
-	    sizeof (key), (cmp)cmp_tile);
+	return bsearch(&key, map->tileset->collisions, map->tileset->collisionsz,
+	    sizeof (key), collision_cmp);
 }
 
-static struct tileset_tiledef *
-find_tiledef_by_row_column_in_layer(const struct map *map,
-                                    const struct map_layer *layer,
+static struct mlk_tileset_collision *
+find_collision_by_row_column_in_layer(const struct mlk_map *map,
+                                    const struct mlk_map_layer *layer,
                                     int row,
                                     int col)
 {
-	unsigned short id;
+	unsigned int id;
 
 	if (row < 0 || (unsigned int)row >= map->rows ||
 	    col < 0 || (unsigned int)col >= map->columns)
@@ -263,24 +265,24 @@
 	if ((id = layer->tiles[col + row * map->columns]) == 0)
 		return NULL;
 
-	return find_tiledef_by_id(map, id - 1);
+	return find_collision_by_id(map, id - 1);
 }
 
-static struct tileset_tiledef *
-find_tiledef_by_row_column(const struct map *map, int row, int col)
+static struct mlk_tileset_collision *
+find_collision_by_row_column(const struct mlk_map *map, int row, int col)
 {
-	struct tileset_tiledef *tile;
+	struct mlk_tileset_collision *tc;
 
 	/* TODO: probably a for loop when we have indefinite layers. */
-	if (!(tile = find_tiledef_by_row_column_in_layer(map, &map->layers[1], row, col)))
-		tile = find_tiledef_by_row_column_in_layer(map, &map->layers[0], row, col);
+	if (!(tc = find_collision_by_row_column_in_layer(map, &map->layers[1], row, col)))
+		tc = find_collision_by_row_column_in_layer(map, &map->layers[0], row, col);
 
-	return tile;
+	return tc;
 }
 
 static void
-find_block_iterate(const struct map *map,
-                   struct map_block *block,
+find_block_iterate(const struct mlk_map *map,
+                   struct mlk_map_block *block,
                    int rowstart,
                    int rowend,
                    int colstart,
@@ -291,20 +293,21 @@
 	assert(map);
 	assert(block);
 
+	const struct mlk_tileset_collision *tc;
+	const struct mlk_map_block *b;
+	struct mlk_map_block tmp;
+
 	/* First, check with tiledefs. */
 	for (int r = rowstart; r <= rowend; ++r) {
 		for (int c = colstart; c <= colend; ++c) {
-			struct tileset_tiledef *td;
-			struct map_block tmp;
-
-			if (!(td = find_tiledef_by_row_column(map, r, c)))
+			if (!(tc = find_collision_by_row_column(map, r, c)))
 				continue;
 
 			/* Convert to absolute values. */
-			tmp.x = td->x + c * map->tileset->sprite->cellw;
-			tmp.y = td->y + r * map->tileset->sprite->cellh;
-			tmp.w = td->w;
-			tmp.h = td->h;
+			tmp.x = tc->x + c * map->tileset->sprite->cellw;
+			tmp.y = tc->y + r * map->tileset->sprite->cellh;
+			tmp.w = tc->w;
+			tmp.h = tc->h;
 
 			/* This tiledef is out of context. */
 			if (!is_block_relevant(map, &tmp, drow, dcol))
@@ -321,20 +324,20 @@
 
 	/* Now check if there are objects closer than tiledefs. */
 	for (size_t i = 0; i < map->blocksz; ++i) {
-		const struct map_block *new = &map->blocks[i];
+		b = &map->blocks[i];
 
-		if (is_block_relevant(map, new, drow, dcol) &&
-		    is_block_better(block, new, drow, dcol)) {
-			block->x = new->x;
-			block->y = new->y;
-			block->w = new->w;
-			block->h = new->h;
+		if (is_block_relevant(map, b, drow, dcol) &&
+		    is_block_better(block, b, drow, dcol)) {
+			block->x = b->x;
+			block->y = b->y;
+			block->w = b->w;
+			block->h = b->h;
 		}
 	}
 }
 
 static void
-find_collision(const struct map *map, struct map_block *block, int drow, int dcolumn)
+find_collision(const struct mlk_map *map, struct mlk_map_block *block, int drow, int dcolumn)
 {
 	assert((drow && !dcolumn) || (dcolumn && !drow));
 
@@ -386,9 +389,9 @@
 }
 
 static void
-move_x(struct map *map, int delta)
+move_x(struct mlk_map *map, int delta)
 {
-	struct map_block block;
+	struct mlk_map_block block;
 
 	find_collision(map, &block, 0, delta < 0 ? -1 : +1);
 
@@ -410,9 +413,9 @@
 }
 
 static void
-move_y(struct map *map, int delta)
+move_y(struct mlk_map *map, int delta)
 {
-	struct map_block block;
+	struct mlk_map_block block;
 
 	find_collision(map, &block, delta < 0 ? -1 : +1, 0);
 
@@ -434,7 +437,7 @@
 }
 
 static void
-move(struct map *map, unsigned int ticks)
+move(struct mlk_map *map, unsigned int ticks)
 {
 	/* This is the amount of pixels the player must move. */
 	const int delta = SPEED * ticks / SEC;
@@ -468,8 +471,8 @@
 }
 
 static inline void
-draw_layer_tile(const struct map *map,
-                const struct map_layer *layer,
+draw_layer_tile(const struct mlk_map *map,
+                const struct mlk_map_layer *layer,
                 struct mlk_texture *colbox,
                 int start_col,
                 int start_row,
@@ -478,7 +481,7 @@
                 unsigned int r,
                 unsigned int c)
 {
-	const struct tileset_tiledef *td;
+	const struct mlk_tileset_collision *tc;
 	int index, id, sc, sr, mx, my;
 
 	index = (start_col + c) + ((start_row + r) * map->columns);
@@ -496,12 +499,13 @@
 	mx = start_x + (int)c * (int)map->tileset->sprite->cellw;
 	my = start_y + (int)r * (int)map->tileset->sprite->cellh;
 
-	tileset_draw(map->tileset, sr, sc, mx, my);
+	mlk_tileset_draw(map->tileset, sr, sc, mx, my);
 
-	if ((td = find_tiledef_by_id(map, id)) && mlk_texture_ok(colbox))
-		mlk_texture_scale(colbox, 0, 0, 5, 5, mx + td->x, my + td->y, td->w, td->h, 0);
+	/* Draw collision box if colbox is non NULL. */
+	if ((tc = find_collision_by_id(map, id)) && mlk_texture_ok(colbox))
+		mlk_texture_scale(colbox, 0, 0, 5, 5, mx + tc->x, my + tc->y, tc->w, tc->h, 0);
 
-	if (map->flags & MAP_FLAGS_SHOW_GRID) {
+	if (map->flags & MLK_MAP_FLAGS_SHOW_GRID) {
 		mlk_painter_set_color(0x202e37ff);
 		mlk_painter_draw_line(mx, my, mx + (int)map->tileset->sprite->cellw, my);
 		mlk_painter_draw_line(
@@ -511,7 +515,7 @@
 }
 
 static void
-draw_layer(const struct map *map, const struct map_layer *layer)
+draw_layer(const struct mlk_map *map, const struct mlk_map_layer *layer)
 {
 	assert(map);
 	assert(layer);
@@ -534,7 +538,7 @@
 		return;
 
 	/* Show collision box if requested. */
-	if (map->flags & MAP_FLAGS_SHOW_COLLIDE && mlk_texture_new(&colbox, 16, 16) == 0) {
+	if (map->flags & MLK_MAP_FLAGS_SHOW_COLLIDE && mlk_texture_new(&colbox, 16, 16) == 0) {
 		mlk_texture_set_blend_mode(&colbox, MLK_TEXTURE_BLEND_BLEND);
 		mlk_texture_set_alpha_mod(&colbox, 100);
 		MLK_PAINTER_BEGIN(&colbox);
@@ -557,11 +561,11 @@
 }
 
 static void
-draw_collide(const struct map *map)
+draw_collide(const struct mlk_map *map)
 {
 	struct mlk_texture box = {0};
 
-	if (map->flags & MAP_FLAGS_SHOW_COLLIDE && mlk_texture_new(&box, 64, 64) == 0) {
+	if (map->flags & MLK_MAP_FLAGS_SHOW_COLLIDE && mlk_texture_new(&box, 64, 64) == 0) {
 		/* Draw collide box around player if requested. */
 		mlk_texture_set_alpha_mod(&box, 100);
 		mlk_texture_set_blend_mode(&box, MLK_TEXTURE_BLEND_BLEND);
@@ -591,18 +595,18 @@
 }
 
 int
-map_init(struct map *map)
+mlk_map_init(struct mlk_map *map)
 {
 	assert(map);
 
 	init(map);
-	tileset_start(map->tileset);
+	mlk_tileset_start(map->tileset);
 
 	return 0;
 }
 
 void
-map_handle(struct map *map, const union mlk_event *ev)
+mlk_map_handle(struct mlk_map *map, const union mlk_event *ev)
 {
 	assert(map);
 	assert(ev);
@@ -617,55 +621,38 @@
 	default:
 		break;
 	}
-
-	mlk_action_stack_handle(&map->astack_par, ev);
-	mlk_action_stack_handle(&map->astack_seq, ev);
 }
 
 void
-map_update(struct map *map, unsigned int ticks)
+mlk_map_update(struct mlk_map *map, unsigned int ticks)
 {
 	assert(map);
 
-	mlk_action_stack_update(&map->astack_par, ticks);
-	mlk_action_stack_update(&map->astack_seq, ticks);
-
-	tileset_update(map->tileset, ticks);
-
-	/* No movements if the sequential actions are running. */
-	if (mlk_action_stack_completed(&map->astack_seq))
-		move(map, ticks);
+	mlk_tileset_update(map->tileset, ticks);
+	move(map, ticks);
 }
 
 void
-map_draw(const struct map *map)
+mlk_map_draw(const struct mlk_map *map)
 {
 	assert(map);
 
 	/* Draw the texture about background/foreground. */
-	draw_layer(map, &map->layers[MAP_LAYER_TYPE_BACKGROUND]);
-	draw_layer(map, &map->layers[MAP_LAYER_TYPE_FOREGROUND]);
+	draw_layer(map, &map->layers[MLK_MAP_LAYER_TYPE_BG]);
+	draw_layer(map, &map->layers[MLK_MAP_LAYER_TYPE_FG]);
 
 	walksprite_draw(
 		&map->player_ws,
-		map->player_angle,
+		map->player_a,
 		map->player_x - map->view_x,
 		map->player_y - map->view_y);
 
-	draw_layer(map, &map->layers[MAP_LAYER_TYPE_ABOVE]);
+	draw_layer(map, &map->layers[MLK_MAP_LAYER_TYPE_ABOVE]);
 	draw_collide(map);
-
-	mlk_action_stack_draw(&map->astack_par);
-	mlk_action_stack_draw(&map->astack_seq);
 }
 
 void
-map_finish(struct map *map)
+mlk_map_finish(struct mlk_map *map)
 {
 	assert(map);
-
-	mlk_action_stack_finish(&map->astack_par);
-	mlk_action_stack_finish(&map->astack_seq);
-
-	memset(map, 0, sizeof (*map));
 }
--- a/libmlk-rpg/mlk/rpg/map.h	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/map.h	Tue Mar 07 22:15:35 2023 +0100
@@ -21,100 +21,103 @@
 
 #include <stddef.h>
 
-#include <mlk/core/action.h>
-#include <mlk/core/action-stack.h>
-
 #include "walksprite.h"
 
-struct tileset;
+struct mlk_map;
+struct mlk_tileset;
 
 union mlk_event;
 
-enum map_layer_type {
-	MAP_LAYER_TYPE_BACKGROUND,
-	MAP_LAYER_TYPE_FOREGROUND,
-	MAP_LAYER_TYPE_ABOVE,
-	MAP_LAYER_TYPE_NUM
+enum mlk_map_layer_type {
+	MLK_MAP_LAYER_TYPE_BG,
+	MLK_MAP_LAYER_TYPE_FG,
+	MLK_MAP_LAYER_TYPE_ABOVE,
+	MLK_MAP_LAYER_TYPE_LAST
 };
 
-struct map_layer {
-	unsigned short *tiles;
+struct mlk_map_layer {
+	unsigned int *tiles;
 };
 
-enum map_flags {
-	MAP_FLAGS_NONE          = 0,
-	MAP_FLAGS_SHOW_GRID     = (1 << 0),
-	MAP_FLAGS_SHOW_COLLIDE  = (1 << 2)
+enum mlk_map_flags {
+	MLK_MAP_FLAGS_NONE              = 0,
+	MLK_MAP_FLAGS_SHOW_GRID         = (1 << 0),
+	MLK_MAP_FLAGS_SHOW_COLLIDE      = (1 << 2)
 };
 
-struct map_block {
+struct mlk_map_block {
 	int x;
 	int y;
 	unsigned int w;
 	unsigned int h;
 };
 
-struct map {
-	const char *title;              /*!< (+) Map title name. */
-	unsigned int columns;           /*!< (-) Number of columns. */
-	unsigned int rows;              /*!< (-) Number of rows. */
+struct mlk_map_style {
+	unsigned long grid_color;
+	unsigned long collision_color;
+};
 
-	/* Tileset. */
-	struct tileset *tileset;        /*!< (+&?) Tileset to use. */
+struct mlk_map_delegate {
+	void *data;
+	void (*update)(struct mlk_map_delegate *self, struct mlk_map *map, unsigned int ticks);
+	void (*draw)(struct mlk_map_delegate *self, const struct mlk_map *map);
+};
 
-	/* View options. */
-	enum map_flags flags;           /*!< (+) View options. */
+struct mlk_map {
+	unsigned int columns;
+	unsigned int rows;
 
-	/* Extra collisions blocks. */
-	struct map_block *blocks;       /*!< (+&?) Extra collisions. */
-	size_t blocksz;                 /*!< (+) Number of collisions. */
+	struct mlk_tileset *tileset;
 
-	/* List of actions. */
-	struct mlk_action_stack astack_par; /*!< (+) Parallel actions. */
-	struct mlk_action_stack astack_seq; /*!< (+) Blocking actions. */
+	enum mlk_map_flags flags;
+
+	const struct mlk_map_block *blocks;
+	size_t blocksz;
 
-	/* Player. */
-	struct mlk_sprite *player_sprite;   /*!< (+) The sprite to use */
-	struct walksprite player_ws;    /*!< (-) Walking sprite for moving the player. */
-	int player_x;                   /*!< (+) Player position in x */
-	int player_y;                   /*!< (+) Player position in y */
-	int player_angle;               /*!< (+) Player angle (see walksprite) */
-	unsigned int player_movement;   /*!< (*) Current player movements. */
+	struct mlk_sprite *player_sprite;
+	int player_x;
+	int player_y;
+	int player_a;
+	unsigned int player_movement;
+	struct walksprite player_ws;
+
+	int view_x;
+	int view_y;
+	unsigned int view_w;
+	unsigned int view_h;
 
-	/* View to zoom/locate. */
-	int view_x;                     /*!< (+) Position in x */
-	int view_y;                     /*!< (+) Position in y */
-	unsigned int view_w;            /*!< (+) View width */
-	unsigned int view_h;            /*!< (+) View height */
+	int margin_x;
+	int margin_y;
+	unsigned int margin_w;
+	unsigned int margin_h;
+
+	struct mlk_map_layer layers[MLK_MAP_LAYER_TYPE_LAST];
 
-	/* View margin. */
-	int margin_x;                   /*!< (+) View margin in x. */
-	int margin_y;                   /*!< (+) View margin in y. */
-	unsigned int margin_w;          /*!< (+) Margin width. */
-	unsigned int margin_h;          /*!< (+) Margin height. */
+	struct mlk_button_style *style;
+	struct mlk_button_delegate *delegate;
+};
 
-	/* Different tile layers. */
-	struct map_layer layers[MAP_LAYER_TYPE_NUM];
-};
+extern struct mlk_map_style mlk_map_style;
+extern struct mlk_map_delegate mlk_map_delegate;
 
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 int
-map_init(struct map *map);
+mlk_map_init(struct mlk_map *map);
 
 void
-map_handle(struct map *map, const union mlk_event *ev);
+mlk_map_handle(struct mlk_map *map, const union mlk_event *ev);
 
 void
-map_update(struct map *map, unsigned int ticks);
+mlk_map_update(struct mlk_map *map, unsigned int ticks);
 
 void
-map_draw(const struct map *map);
+mlk_map_draw(const struct mlk_map *map);
 
 void
-map_finish(struct map *map);
+mlk_map_finish(struct mlk_map *map);
 
 #if defined(__cplusplus)
 }
--- a/libmlk-rpg/mlk/rpg/tileset-loader-file.h	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader-file.h	Tue Mar 07 22:15:35 2023 +0100
@@ -61,6 +61,10 @@
 	/** \endcond MLK_PRIVATE_DECLS */
 };
 
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
 /**
  * Fill the abstract loader with appropriate implementation.
  *
@@ -90,4 +94,8 @@
 void
 mlk_tileset_loader_file_finish(struct mlk_tileset_loader_file *file);
 
+#if defined(__cplusplus)
+}
+#endif
+
 #endif /* !MLK_RPG_TILESET_LOADER_FILE_H */
--- a/libmlk-rpg/mlk/rpg/tileset-loader.c	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader.c	Tue Mar 07 22:15:35 2023 +0100
@@ -146,7 +146,7 @@
 	size_t tileanimationsz = 0;
 
 	/* Create a format string for fscanf. */
-	snprintf(fmt, sizeof (fmt), "%%u|%%%zu[^|]|%%u", sizeof (filename));
+	snprintf(fmt, sizeof (fmt), "%%u|%%%zu[^|]|%%u", sizeof (filename) - 1);
 
 	/*
 	 * When parsing animations, we have to create three different
--- a/libmlk-rpg/mlk/rpg/tileset-loader.h	Tue Mar 07 20:58:00 2023 +0100
+++ b/libmlk-rpg/mlk/rpg/tileset-loader.h	Tue Mar 07 22:15:35 2023 +0100
@@ -133,6 +133,8 @@
  * Open a tileset from a filesystem path.
  *
  * \pre loader != NULL
+ * \pre tileset != NULL
+ * \pre data != NULL
  * \param loader the loader
  * \param tileset the tileset destination
  * \param path the path to the tileset file
@@ -150,6 +152,8 @@
  * used.
  *
  * \pre loader != NULL
+ * \pre tileset != NULL
+ * \pre data != NULL
  * \param loader the loader
  * \param tileset the tileset destination
  * \param data the tileset content
--- a/mlk-map/mlk-map.c	Tue Mar 07 20:58:00 2023 +0100
+++ b/mlk-map/mlk-map.c	Tue Mar 07 22:15:35 2023 +0100
@@ -212,8 +212,7 @@
 static void
 write_tileset(const json_t *tilesets)
 {
-	char path[MLK_PATH_MAX];
-	char filename[FILENAME_MAX] = {0}, *ext;
+	char path[MLK_PATH_MAX], *ext;
 	const json_t *tileset, *source;
 
 	if (json_array_size(tilesets) != 1)
@@ -227,14 +226,13 @@
 
 	/* We need to replace the .json extension to .tileset. */
 	snprintf(path, sizeof (path), "%s", json_string_value(source));
-	snprintf(filename, sizeof (filename), "%s", mlk_util_basename(path));
 
-	if (!(ext = strstr(filename, ".json")))
+	if (!(ext = strstr(path, ".json")))
 		die("could not determine tileset extension");
 
 	*ext = '\0';
 
-	printf("tileset|%s.tileset\n", filename);
+	printf("tileset|%s.tileset\n", path);
 }
 
 int