diff librpg/rpg/map-file.c @ 197:852d0b7817ce

rpg: map, extreme cleanup, closes #2508 @4h
author David Demelier <markand@malikania.fr>
date Mon, 09 Nov 2020 10:37:36 +0100
parents
children 70e6ed74940d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/map-file.c	Mon Nov 09 10:37:36 2020 +0100
@@ -0,0 +1,290 @@
+/*
+ * map-file.c -- map file loader
+ *
+ * Copyright (c) 2020 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.
+ */
+
+#define _XOPEN_SOURCE 700
+#include <assert.h>
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <core/alloc.h>
+#include <core/error.h>
+#include <core/image.h>
+
+#include "map-file.h"
+
+/* Create %<v>c string literal for scanf */
+#define MAX_F(v) MAX_F_(v)
+#define MAX_F_(v) "%" #v "c"
+
+struct parser {
+	struct map_file *mf;            /* Map loader. */
+	struct map *map;                /* Map object to fill. */
+	FILE *fp;                       /* Map file pointer. */
+	char basedir[PATH_MAX];         /* Parent map directory */
+};
+
+static bool
+parse_layer(struct parser *ps, const char *line)
+{
+	char layer_name[32 + 1] = {0};
+	enum map_layer_type layer_type;
+	size_t amount, current;
+
+	/* Check if weight/height has been specified. */
+	if (ps->map->w == 0 || ps->map->h == 0)
+		return errorf("missing map dimensions before layer");
+
+	/* Determine layer type. */
+	if (sscanf(line, "layer|%32s", layer_name) <= 0)
+		return errorf("missing layer type definition");
+
+	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
+		return errorf("invalid layer type: %s", layer_name);
+
+	amount = ps->map->w * ps->map->h;
+	current = 0;
+
+	/*
+	 * The next line after a layer declaration is a list of plain integer
+	 * that fill the layer tiles.
+	 */
+	if (!(ps->mf->layers[layer_type].tiles = alloc_zero(amount, sizeof (unsigned short))))
+		return false;
+
+	for (int tile; fscanf(ps->fp, "%d", &tile) && current < amount; ++current)
+		ps->mf->layers[layer_type].tiles[current] = tile;
+
+	ps->map->layers[layer_type].tiles = ps->mf->layers[layer_type].tiles;
+
+	return true;
+}
+
+static bool
+parse_tileset(struct parser *ps, const char *line)
+{
+	char filename[FILENAME_MAX + 1] = {0};
+	char filepath[PATH_MAX];
+	int ret;
+
+	if (ps->map->tile_w == 0 || ps->map->tile_h == 0)
+		return errorf("missing map dimensions before tileset");
+
+	if ((ret = sscanf(line, "tileset|" MAX_F(FILENAME_MAX), filename)) == 1) {
+		snprintf(filepath, sizeof (filepath), "%s/%s", ps->basedir, filename);
+
+		if (!image_open(&ps->mf->tileset, filepath))
+			return false;
+	}
+
+	/* Initialize sprite. */
+	sprite_init(&ps->mf->sprite, &ps->mf->tileset, ps->map->tile_w, ps->map->tile_h);
+	ps->map->tileset = &ps->mf->sprite;
+
+	return true;
+}
+
+static bool
+parse_title(struct parser *ps, const char *line)
+{
+	if (sscanf(line, "title|" MAX_F(MAP_FILE_TITLE_MAX), ps->mf->title) != 1 || strlen(ps->mf->title) == 0)
+		return errorf("null map title");
+
+	ps->map->title = ps->mf->title;
+
+	return true;
+}
+
+static bool
+parse_width(struct parser *ps, const char *line)
+{
+	if (sscanf(line, "width|%u", &ps->map->w) != 1 || ps->map->w == 0)
+		return errorf("null map width");
+
+	return true;
+}
+
+static bool
+parse_height(struct parser *ps, const char *line)
+{
+	if (sscanf(line, "height|%u", &ps->map->h) != 1 || ps->map->h == 0)
+		return errorf("null map height");
+
+	return true;
+}
+
+static bool
+parse_tilewidth(struct parser *ps, const char *line)
+{
+	if (sscanf(line, "tilewidth|%hu", &ps->map->tile_w) != 1 || ps->map->tile_w == 0)
+		return errorf("null map tile width");
+	if (ps->map->w == 0)
+		return errorf("missing map width before tilewidth");
+
+	ps->map->real_w = ps->map->w * ps->map->tile_w;
+
+	return true;
+}
+
+static bool
+parse_tileheight(struct parser *ps, const char *line)
+{
+	if (sscanf(line, "tileheight|%hu", &ps->map->tile_h) != 1 || ps->map->tile_h == 0)
+		return errorf("null map tile height");
+	if (ps->map->h == 0)
+		return errorf("missing map height before tileheight");
+
+	ps->map->real_h = ps->map->h * ps->map->tile_h;
+
+	return true;
+}
+
+static bool
+parse_origin(struct parser *ps, const char *line)
+{
+	if (sscanf(line, "origin|%d|%d", &ps->map->origin_x, &ps->map->origin_y) != 2)
+		return errorf("invalid origin");
+
+	/*
+	 * We adjust the player position here because it should not be done in
+	 * the map_init function. This is because the player should not move
+	 * magically each time we re-use the map (saving position).
+	 */
+	ps->map->player_x = ps->map->origin_x;
+	ps->map->player_y = ps->map->origin_y;
+
+	return true;
+}
+
+static bool
+parse_line(struct parser *ps, const char *line)
+{
+	static const struct {
+		const char *property;
+		bool (*read)(struct parser *, const char *);
+	} props[] = {
+		{ "title",      parse_title             },
+		{ "width",      parse_width             },
+		{ "height",     parse_height            },
+		{ "tilewidth",  parse_tilewidth         },
+		{ "tileheight", parse_tileheight        },
+		{ "tileset",    parse_tileset           },
+		{ "origin",     parse_origin            },
+		{ "layer",      parse_layer             }
+	};
+
+	for (size_t i = 0; i < NELEM(props); ++i)
+		if (strncmp(line, props[i].property, strlen(props[i].property)) == 0)
+			return props[i].read(ps, line);
+
+	return true;
+}
+
+static bool
+parse(struct map_file *loader, const char *path, struct map *map, FILE *fp)
+{
+	char line[1024];
+	struct parser ps = {
+		.mf = loader,
+		.map = map,
+		.fp = fp
+	};
+
+	/*
+	 * Even though dirname(3) usually not modify the path as argument it may
+	 * do according to POSIX specification, as such we still need a
+	 * temporary buffer.
+	 */
+	snprintf(ps.basedir, sizeof (ps.basedir), "%s", path);
+	snprintf(ps.basedir, sizeof (ps.basedir), "%s", dirname(ps.basedir));
+
+	while (fgets(line, sizeof (line), fp)) {
+		/* Remove \n if any */
+		line[strcspn(line, "\n")] = '\0';
+
+		if (!parse_line(&ps, line))
+			return false;
+	}
+
+	return true;
+}
+
+static bool
+check(struct map *map)
+{
+	/*
+	 * Check that we have parsed every required components.
+	 */
+	if (!map->title)
+		return errorf("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 errorf("missing background layer");
+	if (!map->layers[1].tiles)
+		return errorf("missing foreground layer");
+	if (!sprite_ok(map->tileset))
+		return errorf("missing tileset");
+
+	return true;
+}
+
+bool
+map_file_open(struct map_file *file, const char *path, struct map *map)
+{
+	assert(file);
+	assert(path);
+	assert(map);
+
+	FILE *fp;
+	bool ret = true;
+
+	memset(file, 0, sizeof (*file));
+	memset(map, 0, sizeof (*map));
+
+	if (!(fp = fopen(path, "r")))
+		return errorf("%s", strerror(errno));
+
+	if (!(ret = parse(file, path, map, fp)) || !(ret = check(map))) {
+		map_finish(map);
+		map_file_finish(file);
+	}
+
+	fclose(fp);
+
+	return ret;
+}
+
+void
+map_file_finish(struct map_file *file)
+{
+	assert(file);
+
+	texture_finish(&file->tileset);
+	memset(file, 0, sizeof (*file));
+}