diff libmlk-rpg/mlk/rpg/map-loader.c @ 554:cdbc13ceff85

rpg: do the same for map
author David Demelier <markand@malikania.fr>
date Tue, 07 Mar 2023 22:15:35 +0100
parents libmlk-rpg/mlk/rpg/map-file.c@c7664b679a95
children 6c911cbc1fd7
line wrap: on
line diff
--- /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);
+}