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