comparison 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
comparison
equal deleted inserted replaced
196:658ee50b8bcb 197:852d0b7817ce
1 /*
2 * map-file.c -- map file loader
3 *
4 * Copyright (c) 2020 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 #define _XOPEN_SOURCE 700
20 #include <assert.h>
21 #include <errno.h>
22 #include <libgen.h>
23 #include <limits.h>
24 #include <stddef.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include <core/alloc.h>
29 #include <core/error.h>
30 #include <core/image.h>
31
32 #include "map-file.h"
33
34 /* Create %<v>c string literal for scanf */
35 #define MAX_F(v) MAX_F_(v)
36 #define MAX_F_(v) "%" #v "c"
37
38 struct parser {
39 struct map_file *mf; /* Map loader. */
40 struct map *map; /* Map object to fill. */
41 FILE *fp; /* Map file pointer. */
42 char basedir[PATH_MAX]; /* Parent map directory */
43 };
44
45 static bool
46 parse_layer(struct parser *ps, const char *line)
47 {
48 char layer_name[32 + 1] = {0};
49 enum map_layer_type layer_type;
50 size_t amount, current;
51
52 /* Check if weight/height has been specified. */
53 if (ps->map->w == 0 || ps->map->h == 0)
54 return errorf("missing map dimensions before layer");
55
56 /* Determine layer type. */
57 if (sscanf(line, "layer|%32s", layer_name) <= 0)
58 return errorf("missing layer type definition");
59
60 if (strcmp(layer_name, "background") == 0)
61 layer_type = MAP_LAYER_TYPE_BACKGROUND;
62 else if (strcmp(layer_name, "foreground") == 0)
63 layer_type = MAP_LAYER_TYPE_FOREGROUND;
64 else
65 return errorf("invalid layer type: %s", layer_name);
66
67 amount = ps->map->w * ps->map->h;
68 current = 0;
69
70 /*
71 * The next line after a layer declaration is a list of plain integer
72 * that fill the layer tiles.
73 */
74 if (!(ps->mf->layers[layer_type].tiles = alloc_zero(amount, sizeof (unsigned short))))
75 return false;
76
77 for (int tile; fscanf(ps->fp, "%d", &tile) && current < amount; ++current)
78 ps->mf->layers[layer_type].tiles[current] = tile;
79
80 ps->map->layers[layer_type].tiles = ps->mf->layers[layer_type].tiles;
81
82 return true;
83 }
84
85 static bool
86 parse_tileset(struct parser *ps, const char *line)
87 {
88 char filename[FILENAME_MAX + 1] = {0};
89 char filepath[PATH_MAX];
90 int ret;
91
92 if (ps->map->tile_w == 0 || ps->map->tile_h == 0)
93 return errorf("missing map dimensions before tileset");
94
95 if ((ret = sscanf(line, "tileset|" MAX_F(FILENAME_MAX), filename)) == 1) {
96 snprintf(filepath, sizeof (filepath), "%s/%s", ps->basedir, filename);
97
98 if (!image_open(&ps->mf->tileset, filepath))
99 return false;
100 }
101
102 /* Initialize sprite. */
103 sprite_init(&ps->mf->sprite, &ps->mf->tileset, ps->map->tile_w, ps->map->tile_h);
104 ps->map->tileset = &ps->mf->sprite;
105
106 return true;
107 }
108
109 static bool
110 parse_title(struct parser *ps, const char *line)
111 {
112 if (sscanf(line, "title|" MAX_F(MAP_FILE_TITLE_MAX), ps->mf->title) != 1 || strlen(ps->mf->title) == 0)
113 return errorf("null map title");
114
115 ps->map->title = ps->mf->title;
116
117 return true;
118 }
119
120 static bool
121 parse_width(struct parser *ps, const char *line)
122 {
123 if (sscanf(line, "width|%u", &ps->map->w) != 1 || ps->map->w == 0)
124 return errorf("null map width");
125
126 return true;
127 }
128
129 static bool
130 parse_height(struct parser *ps, const char *line)
131 {
132 if (sscanf(line, "height|%u", &ps->map->h) != 1 || ps->map->h == 0)
133 return errorf("null map height");
134
135 return true;
136 }
137
138 static bool
139 parse_tilewidth(struct parser *ps, const char *line)
140 {
141 if (sscanf(line, "tilewidth|%hu", &ps->map->tile_w) != 1 || ps->map->tile_w == 0)
142 return errorf("null map tile width");
143 if (ps->map->w == 0)
144 return errorf("missing map width before tilewidth");
145
146 ps->map->real_w = ps->map->w * ps->map->tile_w;
147
148 return true;
149 }
150
151 static bool
152 parse_tileheight(struct parser *ps, const char *line)
153 {
154 if (sscanf(line, "tileheight|%hu", &ps->map->tile_h) != 1 || ps->map->tile_h == 0)
155 return errorf("null map tile height");
156 if (ps->map->h == 0)
157 return errorf("missing map height before tileheight");
158
159 ps->map->real_h = ps->map->h * ps->map->tile_h;
160
161 return true;
162 }
163
164 static bool
165 parse_origin(struct parser *ps, const char *line)
166 {
167 if (sscanf(line, "origin|%d|%d", &ps->map->origin_x, &ps->map->origin_y) != 2)
168 return errorf("invalid origin");
169
170 /*
171 * We adjust the player position here because it should not be done in
172 * the map_init function. This is because the player should not move
173 * magically each time we re-use the map (saving position).
174 */
175 ps->map->player_x = ps->map->origin_x;
176 ps->map->player_y = ps->map->origin_y;
177
178 return true;
179 }
180
181 static bool
182 parse_line(struct parser *ps, const char *line)
183 {
184 static const struct {
185 const char *property;
186 bool (*read)(struct parser *, const char *);
187 } props[] = {
188 { "title", parse_title },
189 { "width", parse_width },
190 { "height", parse_height },
191 { "tilewidth", parse_tilewidth },
192 { "tileheight", parse_tileheight },
193 { "tileset", parse_tileset },
194 { "origin", parse_origin },
195 { "layer", parse_layer }
196 };
197
198 for (size_t i = 0; i < NELEM(props); ++i)
199 if (strncmp(line, props[i].property, strlen(props[i].property)) == 0)
200 return props[i].read(ps, line);
201
202 return true;
203 }
204
205 static bool
206 parse(struct map_file *loader, const char *path, struct map *map, FILE *fp)
207 {
208 char line[1024];
209 struct parser ps = {
210 .mf = loader,
211 .map = map,
212 .fp = fp
213 };
214
215 /*
216 * Even though dirname(3) usually not modify the path as argument it may
217 * do according to POSIX specification, as such we still need a
218 * temporary buffer.
219 */
220 snprintf(ps.basedir, sizeof (ps.basedir), "%s", path);
221 snprintf(ps.basedir, sizeof (ps.basedir), "%s", dirname(ps.basedir));
222
223 while (fgets(line, sizeof (line), fp)) {
224 /* Remove \n if any */
225 line[strcspn(line, "\n")] = '\0';
226
227 if (!parse_line(&ps, line))
228 return false;
229 }
230
231 return true;
232 }
233
234 static bool
235 check(struct map *map)
236 {
237 /*
238 * Check that we have parsed every required components.
239 */
240 if (!map->title)
241 return errorf("missing title");
242
243 /*
244 * We don't need to check width/height because parsing layers and
245 * tilesets already check for their presence, so only check layers.
246 */
247 if (!map->layers[0].tiles)
248 return errorf("missing background layer");
249 if (!map->layers[1].tiles)
250 return errorf("missing foreground layer");
251 if (!sprite_ok(map->tileset))
252 return errorf("missing tileset");
253
254 return true;
255 }
256
257 bool
258 map_file_open(struct map_file *file, const char *path, struct map *map)
259 {
260 assert(file);
261 assert(path);
262 assert(map);
263
264 FILE *fp;
265 bool ret = true;
266
267 memset(file, 0, sizeof (*file));
268 memset(map, 0, sizeof (*map));
269
270 if (!(fp = fopen(path, "r")))
271 return errorf("%s", strerror(errno));
272
273 if (!(ret = parse(file, path, map, fp)) || !(ret = check(map))) {
274 map_finish(map);
275 map_file_finish(file);
276 }
277
278 fclose(fp);
279
280 return ret;
281 }
282
283 void
284 map_file_finish(struct map_file *file)
285 {
286 assert(file);
287
288 texture_finish(&file->tileset);
289 memset(file, 0, sizeof (*file));
290 }