comparison libmlk-rpg/mlk/rpg/map-file.c @ 434:4e78f045e8c0

rpg: cleanup hierarchy
author David Demelier <markand@malikania.fr>
date Sat, 15 Oct 2022 21:24:17 +0200
parents src/libmlk-rpg/rpg/map-file.c@862b15c3a3ae
children 25a56ca53ac2
comparison
equal deleted inserted replaced
433:862b15c3a3ae 434:4e78f045e8c0
1 /*
2 * map-file.c -- map file loader
3 *
4 * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <assert.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <stddef.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <mlk/util/util.h>
28
29 #include <mlk/core/alloc.h>
30 #include <mlk/core/error.h>
31 #include <mlk/core/image.h>
32 #include <mlk/core/trace.h>
33 #include <mlk/core/util.h>
34
35 #include "map-file.h"
36
37 #define MAX_F(v) MAX_F_(v)
38 #define MAX_F_(v) "%" #v "[^\n|]"
39
40 struct context {
41 struct map_file *mf; /* Map loader. */
42 struct map *map; /* Map object to fill. */
43 FILE *fp; /* Map file pointer. */
44 char basedir[PATH_MAX]; /* Parent map directory */
45 };
46
47 static int
48 parse_layer_tiles(struct context *ctx, const char *layer_name)
49 {
50 enum map_layer_type layer_type;
51 size_t amount, current;
52
53 if (strcmp(layer_name, "background") == 0)
54 layer_type = MAP_LAYER_TYPE_BACKGROUND;
55 else if (strcmp(layer_name, "foreground") == 0)
56 layer_type = MAP_LAYER_TYPE_FOREGROUND;
57 else if (strcmp(layer_name, "above") == 0)
58 layer_type = MAP_LAYER_TYPE_ABOVE;
59 else
60 return errorf("invalid layer type: %s", layer_name);
61
62 amount = ctx->map->columns * ctx->map->rows;
63 current = 0;
64
65 /*
66 * The next line after a layer declaration is a list of plain integer
67 * that fill the layer tiles.
68 */
69 if (!(ctx->mf->layers[layer_type].tiles = alloc_array0(amount, sizeof (unsigned short))))
70 return -1;
71
72 for (int tile; fscanf(ctx->fp, "%d\n", &tile) && current < amount; ++current)
73 ctx->mf->layers[layer_type].tiles[current] = tile;
74
75 ctx->map->layers[layer_type].tiles = ctx->mf->layers[layer_type].tiles;
76
77 return 0;
78 }
79
80 static int
81 parse_actions(struct context *ctx)
82 {
83 char exec[128 + 1];
84 int x = 0, y = 0, block = 0;
85 unsigned int w = 0, h = 0;
86
87 while (fscanf(ctx->fp, "%d|%d|%u|%u|%d|%128[^\n]\n", &x, &y, &w, &h, &block, exec) >= 5) {
88 struct map_block *reg;
89
90 if (!ctx->mf->load_action) {
91 tracef("ignoring action %d,%d,%u,%u,%d,%s", x, y, w, h, block, exec);
92 continue;
93 }
94
95 ctx->mf->load_action(ctx->map, x, y, w, h, exec);
96
97 /*
98 * Actions do not have concept of collisions because they are
99 * not only used on maps. The map structure has its very own
100 * object to manage collisions but the .map file use the same
101 * directive for simplicity. So create a block region if the
102 * directive has one.
103 */
104 if (block) {
105 if (!(reg = alloc_pool_new(&ctx->mf->blocks)))
106 return -1;
107
108 reg->x = x;
109 reg->y = y;
110 reg->w = w;
111 reg->h = h;
112 }
113 }
114
115 /* Reference the blocks array from map_file. */
116 ctx->map->blocks = ctx->mf->blocks.data;
117 ctx->map->blocksz = ctx->mf->blocks.size;
118
119 return 0;
120 }
121
122 static int
123 parse_layer(struct context *ctx, const char *line)
124 {
125 char layer_name[32 + 1] = {0};
126
127 /* Check if weight/height has been specified. */
128 if (ctx->map->columns == 0 || ctx->map->rows == 0)
129 return errorf("missing map dimensions before layer");
130
131 /* Determine layer type. */
132 if (sscanf(line, "layer|%32s", layer_name) <= 0)
133 return errorf("missing layer type definition");
134
135 if (strcmp(layer_name, "actions") == 0)
136 return parse_actions(ctx);
137
138 return parse_layer_tiles(ctx, layer_name);
139 }
140
141 static int
142 parse_tileset(struct context *ctx, const char *line)
143 {
144 char path[PATH_MAX] = {0}, *p;
145 struct map_file *mf = ctx->mf;
146 struct tileset_file *tf = &mf->tileset_file;
147
148 if (!(p = strchr(line, '|')))
149 return errorf("could not parse tileset");
150
151 snprintf(path, sizeof (path), "%s/%s", ctx->basedir, p + 1);
152
153 if (tileset_file_open(tf, &mf->tileset, path) < 0)
154 return -1;
155
156 ctx->map->tileset = &mf->tileset;
157
158 return 0;
159 }
160
161 static int
162 parse_title(struct context *ctx, const char *line)
163 {
164 if (sscanf(line, "title|" MAX_F(MAP_FILE_TITLE_MAX), ctx->mf->title) != 1 || strlen(ctx->mf->title) == 0)
165 return errorf("null map title");
166
167 ctx->map->title = ctx->mf->title;
168
169 return 0;
170 }
171
172 static int
173 parse_columns(struct context *ctx, const char *line)
174 {
175 if (sscanf(line, "columns|%u", &ctx->map->columns) != 1 || ctx->map->columns == 0)
176 return errorf("null map columns");
177
178 return 0;
179 }
180
181 static int
182 parse_rows(struct context *ctx, const char *line)
183 {
184 if (sscanf(line, "rows|%u", &ctx->map->rows) != 1 || ctx->map->rows == 0)
185 return errorf("null map rows");
186
187 return 0;
188 }
189
190 static int
191 parse_origin(struct context *ctx, const char *line)
192 {
193 if (sscanf(line, "origin|%d|%d", &ctx->map->player_x, &ctx->map->player_y) != 2)
194 return errorf("invalid origin");
195
196 return 0;
197 }
198
199 static int
200 parse_line(struct context *ctx, const char *line)
201 {
202 static const struct {
203 const char *property;
204 int (*read)(struct context *, const char *);
205 } props[] = {
206 { "title", parse_title },
207 { "columns", parse_columns },
208 { "rows", parse_rows },
209 { "tileset", parse_tileset },
210 { "origin", parse_origin },
211 { "layer", parse_layer },
212 };
213
214 for (size_t i = 0; i < UTIL_SIZE(props); ++i)
215 if (strncmp(line, props[i].property, strlen(props[i].property)) == 0)
216 return props[i].read(ctx, line);
217
218 return 0;
219 }
220
221 static int
222 parse(struct context *ctx, const char *path)
223 {
224 char line[1024];
225 char basedir[PATH_MAX];
226
227 util_strlcpy(basedir, path, sizeof (basedir));
228 util_strlcpy(ctx->basedir, util_dirname(basedir), sizeof (ctx->basedir));
229
230 while (fgets(line, sizeof (line), ctx->fp)) {
231 /* Remove \n if any */
232 line[strcspn(line, "\r\n")] = '\0';
233
234 if (parse_line(ctx, line) < 0)
235 return -1;
236 }
237
238 return 0;
239 }
240
241 static int
242 check(struct map *map)
243 {
244 /*
245 * Check that we have parsed every required components.
246 */
247 if (!map->title)
248 return errorf("missing title");
249
250 /*
251 * We don't need to check width/height because parsing layers and
252 * tilesets already check for their presence, so only check layers.
253 */
254 if (!map->layers[0].tiles)
255 return errorf("missing background layer");
256 if (!map->layers[1].tiles)
257 return errorf("missing foreground layer");
258 if (!tileset_ok(map->tileset))
259 return errorf("missing tileset");
260
261 return 0;
262 }
263
264 int
265 map_file_open(struct map_file *file, struct map *map, const char *path)
266 {
267 assert(file);
268 assert(path);
269 assert(map);
270
271 struct context ctx = {
272 .mf = file,
273 .map = map,
274 };
275 int ret = 0;
276
277 memset(map, 0, sizeof (*map));
278 alloc_pool_init(&file->blocks, sizeof (*map->blocks), NULL);
279
280 if (!(ctx.fp = fopen(path, "r")))
281 goto fail;
282 if ((ret = parse(&ctx, path)) < 0 || (ret = check(map)) < 0)
283 goto fail;
284
285 fclose(ctx.fp);
286
287 return 0;
288
289 fail:
290 map_finish(map);
291 map_file_finish(file);
292
293 if (ctx.fp)
294 fclose(ctx.fp);
295
296 return -1;
297 }
298
299 void
300 map_file_finish(struct map_file *file)
301 {
302 assert(file);
303
304 free(file->layers[0].tiles);
305 free(file->layers[1].tiles);
306 free(file->layers[2].tiles);
307
308 tileset_file_finish(&file->tileset_file);
309 alloc_pool_finish(&file->blocks);
310
311 memset(file, 0, sizeof (*file));
312 }