Mercurial > molko
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 } |