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