1 /*
2 * XPilot NG, a multiplayer space war game.
3 *
4 * Copyright (C) 2000-2004 by
5 *
6 * Uoti Urpala <uau@users.sourceforge.net>
7 * Juha Lindstr�m <juhal@users.sourceforge.net>
8 * Kristian S�derblom <kps@users.sourceforge.net>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25 #include <zlib.h>
26 #include "xpserver.h"
27
28 #define DEFAULT_POS { -1, -1 }
29
30 /*
31 * The world whose map we are currently parsing.
32 */
33 static bool parsing_general_options = false;
34 static cannon_t *current_cannon = NULL;
35 static base_t *current_base = NULL;
36
tagstart(void * data,const char * el,const char ** attr)37 static void tagstart(void *data, const char *el, const char **attr)
38 {
39 static double scale = 1;
40 static bool xptag = false;
41
42 UNUSED_PARAM(data);
43 if (!strcasecmp(el, "XPilotMap")) {
44 double version = 0;
45 while (*attr) {
46 if (!strcasecmp(*attr, "version"))
47 version = atof(*(attr + 1));
48 attr += 2;
49 }
50 if (version == 0) {
51 warn("Old(?) map file with no version number");
52 warn("Not guaranteed to work");
53 }
54 else if (version < 1)
55 warn("Impossible version in map file");
56 else if (version > 1.2) {
57 warn("Map file has newer version than this server recognizes.");
58 warn("The map file might use unsupported features.");
59 }
60 xptag = true;
61 return;
62 }
63
64 if (!xptag) {
65 fatal("This doesn't look like a map file "
66 " (XPilotMap must be first tag).");
67 return; /* not reached */
68 }
69
70 if (!strcasecmp(el, "Polystyle")) {
71 char id[100];
72 int color = 0, texture_id = 0, defedge_id = 0, flags = 0;
73
74 while (*attr) {
75 if (!strcasecmp(*attr, "id"))
76 strlcpy(id, *(attr + 1), sizeof(id));
77 if (!strcasecmp(*attr, "color"))
78 color = strtol(*(attr + 1), NULL, 16);
79 if (!strcasecmp(*attr, "texture"))
80 texture_id = P_get_bmp_id(*(attr + 1));
81 if (!strcasecmp(*attr, "defedge"))
82 defedge_id = P_get_edge_id(*(attr + 1));
83 if (!strcasecmp(*attr, "flags"))
84 flags = atoi(*(attr + 1)); /* names @!# */
85 attr += 2;
86 }
87 P_polystyle(id, color, texture_id, defedge_id, flags);
88 return;
89 }
90
91 if (!strcasecmp(el, "Edgestyle")) {
92 char id[100];
93 int width = 0, color = 0, style = 0;
94
95 while (*attr) {
96 if (!strcasecmp(*attr, "id"))
97 strlcpy(id, *(attr + 1), sizeof(estyles[0].id));
98 if (!strcasecmp(*attr, "width"))
99 width = atoi(*(attr + 1));
100 if (!strcasecmp(*attr, "color"))
101 color = strtol(*(attr + 1), NULL, 16);
102 if (!strcasecmp(*attr, "style")) /* !@# names later */
103 style = atoi(*(attr + 1));
104 attr += 2;
105 }
106 P_edgestyle(id, width, color, style);
107 return;
108 }
109
110 if (!strcasecmp(el, "Bmpstyle")) {
111 char id[100];
112 char filename[30];
113 int flags = 0;
114
115 /* add checks that these are filled !@# */
116 while (*attr) {
117 if (!strcasecmp(*attr, "id"))
118 strlcpy(id, *(attr + 1), sizeof(id));
119 if (!strcasecmp(*attr, "filename"))
120 strlcpy(filename, *(attr + 1), sizeof(filename));
121 if (!strcasecmp(*attr, "scalable"))
122 if (!strcasecmp(*(attr + 1), "yes"))
123 flags |= 1;
124 attr += 2;
125 }
126 P_bmpstyle(id, filename, flags);
127 return;
128 }
129
130 if (!strcasecmp(el, "Scale")) { /* "Undocumented feature" */
131 if (!*attr || strcasecmp(*attr, "value"))
132 warn("Invalid Scale");
133 else
134 scale = atof(*(attr + 1));
135 return;
136 }
137
138 if (!strcasecmp(el, "BallArea")) {
139 P_start_ballarea();
140 return;
141 }
142
143 if (!strcasecmp(el, "BallTarget")) {
144 int team = TEAM_NOT_SET;
145 while (*attr) {
146 if (!strcasecmp(*attr, "team"))
147 team = atoi(*(attr + 1));
148 attr += 2;
149 }
150 /*
151 * kps - Currently we don't know mapobj for balltargets,
152 * this means that options.captureTheFlag stuff does not work
153 * on xp2 maps.
154 */
155 P_start_balltarget(team, NO_IND);
156 return;
157 }
158
159 if (!strcasecmp(el, "Decor")) {
160 P_start_decor();
161 return;
162 }
163
164 if (!strcasecmp(el, "Polygon")) {
165 clpos_t pos = DEFAULT_POS;
166 int style = -1;
167
168 while (*attr) {
169 if (!strcasecmp(*attr, "x"))
170 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
171 if (!strcasecmp(*attr, "y"))
172 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
173 if (!strcasecmp(*attr, "style"))
174 style = P_get_poly_id(*(attr + 1));
175 attr += 2;
176 }
177 P_start_polygon(pos, style);
178 return;
179 }
180
181 if (!strcasecmp(el, "Style")) {
182 char state[100];
183 int style = -1;
184
185 while (*attr) {
186 if (!strcasecmp(*attr, "state"))
187 strlcpy(state, *(attr + 1), sizeof(state));
188 if (!strcasecmp(*attr, "id"))
189 style = P_get_poly_id(*(attr + 1));
190 attr += 2;
191 }
192 P_style(state, style);
193 return;
194 }
195
196 if (!strcasecmp(el, "Check")) {
197 clpos_t pos = DEFAULT_POS;
198
199 while (*attr) {
200 if (!strcasecmp(*attr, "x"))
201 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
202 if (!strcasecmp(*attr, "y"))
203 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
204 attr += 2;
205 }
206 World_place_check(pos, -1);
207 return;
208 }
209
210 if (!strcasecmp(el, "Fuel")) {
211 int team = TEAM_NOT_SET;
212 clpos_t pos = DEFAULT_POS;
213
214 while (*attr) {
215 if (!strcasecmp(*attr, "team"))
216 team = atoi(*(attr + 1));
217 if (!strcasecmp(*attr, "x"))
218 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
219 if (!strcasecmp(*attr, "y"))
220 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
221 attr += 2;
222 }
223 World_place_fuel(pos, team);
224 return;
225 }
226
227 if (!strcasecmp(el, "Base")) {
228 int team = TEAM_NOT_SET, dir = DIR_UP, order = 0;
229 clpos_t pos = DEFAULT_POS;
230 int ind;
231
232 while (*attr) {
233 if (!strcasecmp(*attr, "team"))
234 team = atoi(*(attr + 1));
235 if (!strcasecmp(*attr, "x"))
236 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
237 if (!strcasecmp(*attr, "y"))
238 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
239 if (!strcasecmp(*attr, "dir"))
240 dir = atoi(*(attr + 1));
241 if (!strcasecmp(*attr, "order"))
242 order = atoi(*(attr + 1));
243 attr += 2;
244 }
245 if (team < 0 || team >= MAX_TEAMS) {
246 warn("Illegal team number in base tag.\n");
247 exit(1);
248 }
249 ind = World_place_base(pos, dir, team, order);
250 current_base = Base_by_index(ind);
251 return;
252 }
253
254 if (!strcasecmp(el, "Ball")) {
255 int team = TEAM_NOT_SET;
256 clpos_t pos = DEFAULT_POS;
257 int style = 0xff; /* default - client draws ball however it wants */
258
259 while (*attr) {
260 if (!strcasecmp(*attr, "team"))
261 team = atoi(*(attr + 1));
262 if (!strcasecmp(*attr, "x"))
263 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
264 if (!strcasecmp(*attr, "y"))
265 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
266 if (!strcasecmp(*attr, "style"))
267 style = P_get_poly_id(*(attr + 1));
268 attr += 2;
269 }
270 World_place_treasure(pos, team, false, style);
271 return;
272 }
273
274 if (!strcasecmp(el, "Cannon")) {
275 int team = TEAM_NOT_SET, dir = DIR_UP, cannon_ind;
276 clpos_t pos = DEFAULT_POS;
277
278 while (*attr) {
279 if (!strcasecmp(*attr, "team"))
280 team = atoi(*(attr + 1));
281 else if (!strcasecmp(*attr, "x"))
282 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
283 else if (!strcasecmp(*attr, "y"))
284 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
285 else if (!strcasecmp(*attr, "dir"))
286 dir = atoi(*(attr + 1));
287 attr += 2;
288 }
289 cannon_ind = World_place_cannon(pos, dir, team);
290 P_start_cannon(cannon_ind);
291 current_cannon = Cannon_by_index(cannon_ind);
292 return;
293 }
294
295 if (!strcasecmp(el, "Target")) {
296 int team = TEAM_NOT_SET, target_ind;
297 clpos_t pos = DEFAULT_POS;
298
299 while (*attr) {
300 if (!strcasecmp(*attr, "team"))
301 team = atoi(*(attr + 1));
302 else if (!strcasecmp(*attr, "x"))
303 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
304 else if (!strcasecmp(*attr, "y"))
305 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
306 attr += 2;
307 }
308 target_ind = World_place_target(pos, team);
309 P_start_target(target_ind);
310 return;
311 }
312
313 if (!strcasecmp(el, "ItemConcentrator")) {
314 clpos_t pos = DEFAULT_POS;
315
316 while (*attr) {
317 if (!strcasecmp(*attr, "x"))
318 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
319 if (!strcasecmp(*attr, "y"))
320 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
321 attr += 2;
322 }
323 World_place_item_concentrator(pos);
324 return;
325 }
326
327 if (!strcasecmp(el, "AsteroidConcentrator")) {
328 clpos_t pos = DEFAULT_POS;
329
330 while (*attr) {
331 if (!strcasecmp(*attr, "x"))
332 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
333 if (!strcasecmp(*attr, "y"))
334 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
335 attr += 2;
336 }
337 World_place_asteroid_concentrator(pos);
338 return;
339 }
340
341 if (!strcasecmp(el, "Grav")) {
342 clpos_t pos = DEFAULT_POS;
343 double force = 0.0;
344 int type = SPACE;
345
346 while (*attr) {
347 if (!strcasecmp(*attr, "x"))
348 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
349 else if (!strcasecmp(*attr, "y"))
350 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
351 else if (!strcasecmp(*attr, "force"))
352 force = atof(*(attr + 1));
353 else if (!strcasecmp(*attr, "type")) {
354 const char *s = *(attr + 1);
355
356 if (!strcasecmp(s, "pos"))
357 type = POS_GRAV;
358 else if (!strcasecmp(s, "neg"))
359 type = NEG_GRAV;
360 else if (!strcasecmp(s, "cwise"))
361 type = CWISE_GRAV;
362 else if (!strcasecmp(s, "acwise"))
363 type = ACWISE_GRAV;
364 else if (!strcasecmp(s, "up"))
365 type = UP_GRAV;
366 else if (!strcasecmp(s, "down"))
367 type = DOWN_GRAV;
368 else if (!strcasecmp(s, "right"))
369 type = RIGHT_GRAV;
370 else if (!strcasecmp(s, "left"))
371 type = LEFT_GRAV;
372 }
373
374 attr += 2;
375 }
376 if (type == SPACE) {
377 warn("Illegal type in grav tag.\n");
378 exit(1);
379 }
380 World_place_grav(pos, force, type);
381 return;
382 }
383
384 if (!strcasecmp(el, "Wormhole")) {
385 clpos_t pos = DEFAULT_POS;
386 wormtype_t type = WORM_NORMAL;
387 int wh_ind;
388
389 while (*attr) {
390 if (!strcasecmp(*attr, "x"))
391 pos.cx = (click_t)(atoi(*(attr + 1)) * scale);
392 else if (!strcasecmp(*attr, "y"))
393 pos.cy = (click_t)(atoi(*(attr + 1)) * scale);
394 else if (!strcasecmp(*attr, "type")) {
395 const char *s = *(attr + 1);
396
397 if (!strcasecmp(s, "normal"))
398 type = WORM_NORMAL;
399 else if (!strcasecmp(s, "in"))
400 type = WORM_IN;
401 else if (!strcasecmp(s, "out"))
402 type = WORM_OUT;
403 else if (!strcasecmp(s, "fixed"))
404 type = WORM_FIXED;
405 }
406
407 attr += 2;
408 }
409 wh_ind = World_place_wormhole(pos, type);
410 P_start_wormhole(wh_ind);
411 return;
412 }
413
414 if (!strcasecmp(el, "FrictionArea")) {
415 double fric = 0.0;
416 int area_ind;
417 clpos_t pos = { 0, 0 }; /* unused place holder */
418
419 while (*attr) {
420 if (!strcasecmp(*attr, "friction"))
421 fric = atof(*(attr + 1));
422 attr += 2;
423 }
424 area_ind = World_place_friction_area(pos, fric);
425 P_start_friction_area(area_ind);
426 return;
427 }
428
429 if (!strcasecmp(el, "Option")) {
430 const char *name = NULL, *value = NULL;
431 while (*attr) {
432 if (!strcasecmp(*attr, "name"))
433 name = *(attr + 1);
434 if (!strcasecmp(*attr, "value"))
435 value = *(attr + 1);
436 attr += 2;
437 }
438 if (parsing_general_options)
439 Option_set_value(name, value, 0, OPT_MAP);
440 else if (current_base)
441 Base_set_option(current_base, name, value);
442 else if (current_cannon)
443 Cannon_set_option(current_cannon, name, value);
444 else {
445 warn("Options can be specified for:");
446 warn("<GeneralOptions>, <Base> or <Cannon>.");
447 warn("Option %s given out of context in map.", name);
448 exit(1);
449 }
450 return;
451 }
452
453 if (!strcasecmp(el, "Offset")) {
454 clpos_t offset = DEFAULT_POS;
455 int edgestyle = -1;
456 while (*attr) {
457 if (!strcasecmp(*attr, "x"))
458 offset.cx = (click_t)(atoi(*(attr + 1)) * scale);
459 if (!strcasecmp(*attr, "y"))
460 offset.cy = (click_t)(atoi(*(attr + 1)) * scale);
461 if (!strcasecmp(*attr, "style"))
462 edgestyle = P_get_edge_id(*(attr + 1));
463 attr += 2;
464 }
465 P_offset(offset, edgestyle);
466 return;
467 }
468
469 if (!strcasecmp(el, "GeneralOptions")) {
470 parsing_general_options = true;
471 return;
472 }
473
474 warn("Unknown map tag: \"%s\"", el);
475 return;
476 }
477
478
tagend(void * data,const char * el)479 static void tagend(void *data, const char *el)
480 {
481 UNUSED_PARAM(data);
482 if (!strcasecmp(el, "Decor"))
483 P_end_decor();
484 else if (!strcasecmp(el, "Base"))
485 current_base = NULL;
486 else if (!strcasecmp(el, "BallArea"))
487 P_end_ballarea();
488 else if (!strcasecmp(el, "BallTarget"))
489 P_end_balltarget();
490 else if (!strcasecmp(el, "Cannon")) {
491 P_end_cannon();
492 Cannon_init(current_cannon);
493 current_cannon = NULL;
494 } else if (!strcasecmp(el, "FrictionArea"))
495 P_end_friction_area();
496 else if (!strcasecmp(el, "Target"))
497 P_end_target();
498 else if (!strcasecmp(el, "Wormhole"))
499 P_end_wormhole();
500 else if (!strcasecmp(el, "Polygon"))
501 P_end_polygon();
502
503 if (!strcasecmp(el, "GeneralOptions")) {
504 parsing_general_options = false;
505 /* ok, got to the end of options */
506 Options_parse();
507 /* kps - this can fail - fix */
508 Grok_map_options();
509 }
510 return;
511 }
512
513
isXp2MapFile(FILE * ifile)514 bool isXp2MapFile(FILE* ifile)
515 {
516 char start[] = "<XPilotMap";
517 char buf[16];
518 int n;
519
520 n = fread(buf, 1, sizeof(buf), ifile);
521 if (n < 0) {
522 error("Error reading map!");
523 return false;
524 }
525 if (n == 0)
526 return false;
527
528 /* assume this works */
529 fseek(ifile, 0, SEEK_SET);
530 /* gz magic from gzio.h */
531 if (buf[0] == (char)0x1f && buf[1] == (char)0x8b)
532 return true;
533 if (!strncmp(start, buf, strlen(start)))
534 return true;
535 return false;
536 }
537
parseXp2MapFile(char * fname,optOrigin opt_origin)538 bool parseXp2MapFile(char* fname, optOrigin opt_origin)
539 {
540 gzFile in;
541 char buff[8192];
542 int len, last_chunk;
543 unsigned left;
544 XML_Parser p = XML_ParserCreate(NULL);
545
546 UNUSED_PARAM(opt_origin);
547 if (!p) {
548 warn("Creating Expat instance for map parsing failed.\n");
549 return false;
550 }
551 XML_SetElementHandler(p, tagstart, tagend);
552
553 in = gzopen(fname, "rb");
554 if (in == NULL) {
555 error("Error reading map!");
556 return false;
557 }
558 if (gzgets(in, buff, 8192) == Z_NULL) {
559 error("Error reading map!");
560 gzclose(in);
561 return false;
562 }
563 left = 1 << 30;
564 if (strncmp("XPD ", buff, 4) == 0) {
565 if (gzgets(in, buff, 8192) == Z_NULL
566 || sscanf(buff, "%*s %u", &left) != 1) {
567 error("Bad xpd file header");
568 gzclose(in);
569 return false;
570 }
571 } else {
572 if (gzrewind(in) == -1) {
573 error("Error reading map!");
574 gzclose(in);
575 return false;
576 }
577 }
578 do {
579 len = gzread(in, buff, MIN(8192, left));
580 if (len < 0) {
581 error("Error reading map!");
582 gzclose(in);
583 return false;
584 }
585 left -= len;
586 last_chunk = (left == 0 || len < 8192);
587 if (!XML_Parse(p, buff, len, last_chunk)) {
588 warn("Parse error reading map at line %d:\n%s\n",
589 XML_GetCurrentLineNumber(p),
590 XML_ErrorString(XML_GetErrorCode(p)));
591 gzclose(in);
592 return false;
593 }
594 } while (!last_chunk);
595 gzclose(in);
596 return true;
597 }
598