almost done with the level reorganization
[vrlugburz] / src / level.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <treestore.h>
6 #include "level.h"
7 #include "tileset.h"
8 #include "fs.h"
9
10 static int detect_cell_tile(struct level *lvl, int x, int y, int *rot);
11
12
13 int init_level(struct level *lvl, int xsz, int ysz)
14 {
15         memset(lvl, 0, sizeof *lvl);
16
17         if(!(lvl->cells = calloc(xsz * ysz, sizeof *lvl->cells))) {
18                 free(lvl);
19                 return -1;
20         }
21         lvl->width = xsz;
22         lvl->height = ysz;
23         lvl->cell_size = DEF_CELL_SIZE;
24         lvl->px = lvl->py = -1;
25         return 0;
26 }
27
28 void destroy_level(struct level *lvl)
29 {
30         if(!lvl) return;
31         free(lvl->cells);
32         free(lvl->fname);
33         free(lvl->dirname);
34 }
35
36 int load_level(struct level *lvl, const char *fname)
37 {
38         struct ts_node *ts, *node, *iter;
39         int i, j, sz, cx, cy, tiletype;
40         struct cell *cell;
41         float *vecptr;
42         const char *str;
43
44         if(!(ts = ts_load(fname))) {
45                 fprintf(stderr, "failed to load level: %s\n", fname);
46                 return -1;
47         }
48         if(strcmp(ts->name, "dunged_level") != 0) {
49                 fprintf(stderr, "invalid or corrupted level file: %s\n", fname);
50                 ts_free_tree(ts);
51                 return -1;
52         }
53
54         if((sz = ts_get_attr_int(ts, "size", 0)) <= 0) {
55                 sz = 32;
56         }
57
58         if(init_level(lvl, sz, sz) == -1) {
59                 fprintf(stderr, "failed to initialize a %dx%d level\n", sz, sz);
60                 ts_free_tree(ts);
61                 return -1;
62         }
63
64         lvl->fname = strdup(fname);
65         if((lvl->dirname = malloc(strlen(fname) + 1))) {
66 #ifndef LEVEL_EDITOR
67                 path_dir(lvl->fname, lvl->dirname);
68 #endif
69         }
70
71         lvl->cell_size = ts_get_attr_num(ts, "cellsize", DEF_CELL_SIZE);
72
73         if((vecptr = ts_get_attr_vec(ts, "player", 0))) {
74                 lvl->px = vecptr[0];
75                 lvl->py = vecptr[1];
76         }
77
78         if((str = ts_get_attr_str(ts, "tileset", 0))) {
79                 lvl->tset = get_tileset(str);
80         }
81
82         iter = ts->child_list;
83         while(iter) {
84                 node = iter;
85                 iter = iter->next;
86
87                 if(strcmp(node->name, "cell") == 0) {
88                         cx = ts_get_attr_int(node, "x", -1);
89                         cy = ts_get_attr_int(node, "y", -1);
90                         if(cx < 0 || cy < 0 || cx >= sz || cy >= sz) {
91                                 fprintf(stderr, "ignoring cell with invalid or missing coordinates\n");
92                                 continue;
93                         }
94                         cell = lvl->cells + cy * sz + cx;
95                         cell->type = ts_get_attr_int(node, "blocked", 0) ? CELL_BLOCKED : CELL_WALK;
96
97                         /* abuse the next pointer to hang the treestore node temporarilly */
98                         cell->next = (struct cell*)node;
99                 }
100         }
101
102         cell = lvl->cells;
103         for(i=0; i<lvl->height; i++) {
104                 for(j=0; j<lvl->width; j++) {
105                         if(cell->type == CELL_SOLID) {
106                                 cell++;
107                                 continue;
108                         }
109
110                         node = (struct ts_node*)cell->next;
111                         cell->next = 0;
112
113                         if((tiletype = tile_type(ts_get_attr_str(node, "tiletype", 0))) == -1) {
114                                 /* no tile-type specified, try to guess */
115                                 tiletype = detect_cell_tile(lvl, j, i, &cell->tilerot);
116                         }
117
118                         cell->tile = get_tile(lvl->tset, tiletype);
119
120                         cell++;
121                 }
122         }
123
124         ts_free_tree(ts);
125         return 0;
126 }
127
128 /* TODO: save tileset info */
129 int save_level(struct level *lvl, const char *fname)
130 {
131         int i, j;
132         struct ts_node *root, *node;
133         struct ts_attr *attr;
134         struct cell *cell;
135
136         if(!(root = ts_alloc_node()) || ts_set_node_name(root, "dunged_level") == -1) {
137                 goto err;
138         }
139
140         if(!(attr = ts_alloc_attr()) || ts_set_attr_name(attr, "size") == -1) {
141                 ts_free_attr(attr);
142                 goto err;
143         }
144         ts_set_valuei(&attr->val, lvl->width);
145         ts_add_attr(root, attr);
146
147         if(lvl->cell_size && (attr = ts_alloc_attr())) {
148                 ts_set_attr_name(attr, "cellsize");
149                 ts_set_valuef(&attr->val, lvl->cell_size);
150                 ts_add_attr(root, attr);
151         }
152
153         if(lvl->px >= 0 && lvl->px < lvl->width && lvl->py >= 0 && lvl->py < lvl->height) {
154                 if((attr = ts_alloc_attr())) {
155                         ts_set_attr_name(attr, "player");
156                         ts_set_valueiv(&attr->val, 2, lvl->px, lvl->py);
157                         ts_add_attr(root, attr);
158                 }
159         }
160
161         for(i=0; i<lvl->height; i++) {
162                 for(j=0; j<lvl->width; j++) {
163                         cell = lvl->cells + i * lvl->width + j;
164                         if(cell->type == CELL_SOLID) continue;
165
166                         if(!(node = ts_alloc_node()) || ts_set_node_name(node, "cell") == -1) {
167                                 ts_free_node(node);
168                                 goto err;
169                         }
170                         if(!(attr = ts_alloc_attr()) || ts_set_attr_name(attr, "x") == -1) {
171                                 ts_free_attr(attr);
172                                 goto err;
173                         }
174                         ts_set_valuei(&attr->val, j);
175                         ts_add_attr(node, attr);
176                         if(!(attr = ts_alloc_attr()) || ts_set_attr_name(attr, "y") == -1) {
177                                 ts_free_attr(attr);
178                                 goto err;
179                         }
180                         ts_set_valuei(&attr->val, i);
181                         ts_add_attr(node, attr);
182
183                         if(cell->type == CELL_BLOCKED) {
184                                 if(!(attr = ts_alloc_attr()) || ts_set_attr_name(attr, "blocked") == -1) {
185                                         ts_free_attr(attr);
186                                         goto err;
187                                 }
188                                 ts_set_valuei(&attr->val, 1);
189                                 ts_add_attr(node, attr);
190                         }
191
192                         ts_add_child(root, node);
193                 }
194         }
195
196         if(ts_save(root, fname) == -1) {
197                 fprintf(stderr, "failed to save level: %s\n", fname);
198                 ts_free_tree(root);
199                 return -1;
200         }
201         ts_free_tree(root);
202         return 0;
203
204 err:
205         fprintf(stderr, "failed to construct treestore tree\n");
206         ts_free_tree(root);
207         return -1;
208 }
209
210 #ifndef LEVEL_EDITOR
211
212 static int get_cell_type(struct level *lvl, int x, int y)
213 {
214         if(x < 0 || x >= lvl->width || y < 0 || y >= lvl->height) {
215                 return CELL_SOLID;
216         }
217         return lvl->cells[y * lvl->width + x].type;
218 }
219
220 static int detect_cell_tile(struct level *lvl, int x, int y, int *rot)
221 {
222         int i, j, bit;
223         unsigned int adj = 0;
224
225         bit = 0;
226         for(i=0; i<3; i++) {
227                 for(j=0; j<3; j++) {
228                         if(i == 1 && j == 1) continue;
229                         if(get_cell_type(lvl, x + j - 1, y + i - 1) == CELL_SOLID) {
230                                 adj |= 1 << bit;
231                         }
232                         bit++;
233                 }
234         }
235
236         *rot = 0;
237
238         switch(adj) {
239         case 0:
240         case 0757:
241                 /* really we'd need a separate tile type for "all neighbors solid", but we
242                  * probably never going to need that in practice, so fuck it.
243                  */
244                 return TILE_OPEN;
245
246         case 0555:      /* N-S corridor */
247                 *rot = 1;
248         case 0707:      /* W-E corridor */
249                 return TILE_STR;
250
251         case 0745:      /* S-E corner */
252                 *rot = 1;
253         case 0715:      /* S-W corner */
254                 return TILE_CORNER;
255         case 0547:      /* N-E corner */
256                 *rot = 2;
257                 return TILE_CORNER;
258         case 0517:      /* N-W corner */
259                 *rot = 3;
260                 return TILE_CORNER;
261
262         case 0507:      /* N tee */
263                 *rot = 3;
264         case 0515:      /* W tee */
265                 return TILE_TEE;
266         case 0705:      /* S tee */
267                 *rot = 1;
268                 return TILE_TEE;
269         case 0545:      /* E tee */
270                 *rot = 2;
271                 return TILE_TEE;
272
273         case 0505:      /* cross */
274                 return TILE_CROSS;
275
276         case 0700:      /* S stropen */
277         case 0701:
278         case 0704:
279                 return TILE_STROPEN;
280         case 0444:      /* E stropen */
281         case 0445:
282         case 0544:
283                 *rot = 1;
284                 return TILE_STROPEN;
285         case 0007:      /* N stropen */
286         case 0407:
287         case 0107:
288                 *rot = 2;
289                 return TILE_STROPEN;
290         case 0111:      /* W stropen */
291         case 0511:
292         case 0115:
293                 *rot = 3;
294                 return TILE_STROPEN;
295
296         case 0404:      /* E str2open */
297                 return TILE_STR2OPEN;
298         case 0005:      /* N str2open */
299                 *rot = 1;
300                 return TILE_STR2OPEN;
301         case 0101:      /* W str2open */
302                 *rot = 2;
303                 return TILE_STR2OPEN;
304         case 0500:      /* S str2open */
305                 *rot = 3;
306                 return TILE_STR2OPEN;
307         }
308
309         return TILE_OPEN;
310 }
311
312 #endif  /* !LEVEL_EDITOR */