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