1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 2003-2004 Kristian S�derblom <kps@users.sourceforge.net>
5  *
6  * Copyright (C) 1991-2001 by
7  *
8  *      Bj�rn Stabell        <bjoern@xpilot.org>
9  *      Ken Ronny Schouten   <ken@xpilot.org>
10  *      Bert Gijsbers        <bert@xpilot.org>
11  *      Dick Balaska         <dick@xpilot.org>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26  */
27 
28 #include "xpserver.h"
29 
30 static int Compress_map(unsigned char *map, size_t size);
31 
32 static void Xpmap_treasure_to_polygon(int treasure_ind);
33 static void Xpmap_target_to_polygon(int target_ind);
34 static void Xpmap_cannon_to_polygon(int cannon_ind);
35 static void Xpmap_wormhole_to_polygon(int wormhole_ind);
36 static void Xpmap_friction_area_to_polygon(int fa_ind);
37 
38 static bool		compress_maps = true;
39 
Xpmap_extra_error(int line_num)40 static void Xpmap_extra_error(int line_num)
41 {
42     static int prev_line_num, error_count;
43     const int max_error = 5;
44 
45     if (line_num > prev_line_num) {
46 	prev_line_num = line_num;
47 	if (++error_count <= max_error)
48 	    warn("Map file contains extraneous characters on line %d",
49 		 line_num);
50 	else if (error_count - max_error == 1)
51 	    warn("And so on...");
52     }
53 }
54 
55 
Xpmap_missing_error(int line_num)56 static void Xpmap_missing_error(int line_num)
57 {
58     static int prev_line_num, error_count;
59     const int max_error = 5;
60 
61     if (line_num > prev_line_num) {
62 	prev_line_num = line_num;
63 	if (++error_count <= max_error)
64 	    warn("Not enough map data on map data line %d", line_num);
65 	else if (error_count - max_error == 1)
66 	    warn("And so on...");
67     }
68 }
69 
70 
71 
72 /*
73  * Compress the map data using a simple Run Length Encoding algorithm.
74  * If there is more than one consecutive byte with the same type
75  * then we set the high bit of the byte and then the next byte
76  * gives the number of repetitions.
77  * This works well for most maps which have lots of series of the
78  * same map object and is simple enough to got implemented quickly.
79  */
Compress_map(unsigned char * map,size_t size)80 static int Compress_map(unsigned char *map, size_t size)
81 {
82     int			i, j, k;
83 
84     for (i = j = 0; i < (int)size; i++, j++) {
85 	if (i + 1 < (int)size
86 	    && map[i] == map[i + 1]) {
87 	    for (k = 2; i + k < (int)size; k++) {
88 		if (map[i] != map[i + k])
89 		    break;
90 		if (k == 255)
91 		    break;
92 	    }
93 	    map[j] = (map[i] | SETUP_COMPRESSED);
94 	    map[++j] = k;
95 	    i += k - 1;
96 	} else
97 	    map[j] = map[i];
98     }
99     return j;
100 }
101 
102 
Create_blockmap_from_polygons(void)103 void Create_blockmap_from_polygons(void)
104 {
105     int i, h, type;
106     blkpos_t blk;
107     clpos_t pos;
108     shape_t r_wire, u_wire, l_wire, d_wire;
109     clpos_t r_coords[3], u_coords[3], l_coords[3], d_coords[3];
110 
111     r_wire.num_points = 3;
112     u_wire.num_points = 3;
113     l_wire.num_points = 3;
114     d_wire.num_points = 3;
115 
116     for (i = 0; i < 3; i++)
117 	r_wire.pts[i] = &r_coords[i];
118     for (i = 0; i < 3; i++)
119 	u_wire.pts[i] = &u_coords[i];
120     for (i = 0; i < 3; i++)
121 	l_wire.pts[i] = &l_coords[i];
122     for (i = 0; i < 3; i++)
123 	d_wire.pts[i] = &d_coords[i];
124 
125     /*
126      * Block is divided to 4 parts, r, u, l and d, the middle of the block
127      * looking like this. Each char represents a square click. The middle
128      * of the block is at 'x', square clicks with 'x' or ' ' are considered
129      * not part of any of the block parts r, u, l or d.
130      *
131      *  uuu
132      * l U r
133      * lLxRr
134      * l D r
135      *  ddd
136      *
137      */
138 
139     h = BLOCK_CLICKS / 2 - 2;
140 
141     /* right part of block */
142     r_coords[0].cx = 0;
143     r_coords[0].cy = 0; /* this is the R position in the block */
144     r_coords[1].cx = h;
145     r_coords[1].cy = -h;
146     r_coords[2].cx = h;
147     r_coords[2].cy = h;
148 
149     /* up part of block */
150     u_coords[0].cx = 0;
151     u_coords[0].cy = 0;
152     u_coords[1].cx = h;
153     u_coords[1].cy = h;
154     u_coords[2].cx = -h;
155     u_coords[2].cy = h;
156 
157     /* left part of block */
158     l_coords[0].cx = 0;
159     l_coords[0].cy = 0;
160     l_coords[1].cx = -h;
161     l_coords[1].cy = h;
162     l_coords[2].cx = -h;
163     l_coords[2].cy = -h;
164 
165     /* down part of block */
166     d_coords[0].cx = 0;
167     d_coords[0].cy = 0;
168     d_coords[1].cx = -h;
169     d_coords[1].cy = -h;
170     d_coords[2].cx = h;
171     d_coords[2].cy = -h;
172 
173     /*
174      * Create blocks out of polygons.
175      */
176     for (blk.by = 0; blk.by < world->y; blk.by++)
177 	for (blk.bx = 0; blk.bx < world->x; blk.bx++)
178 	    World_set_block(blk, SPACE);
179 
180 
181     for (blk.by = 0; blk.by < world->bheight_floor; blk.by++) {
182 	for (blk.bx = 0; blk.bx < world->bwidth_floor; blk.bx++) {
183 	    int num_inside = 0;
184 	    bool r_inside = false, u_inside = false;
185 	    bool l_inside = false, d_inside = false;
186 
187 	    pos = Block_get_center_clpos(blk);
188 
189 	    if (shape_is_inside(pos.cx+1, pos.cy, 0, NULL, &r_wire, 0) == 0) {
190 		r_inside = true;
191 		num_inside++;
192 	    }
193 	    if (shape_is_inside(pos.cx, pos.cy+1, 0, NULL, &u_wire, 0) == 0) {
194 		u_inside = true;
195 		num_inside++;
196 	    }
197 	    if (shape_is_inside(pos.cx-1, pos.cy, 0, NULL, &l_wire, 0) == 0) {
198 		l_inside = true;
199 		num_inside++;
200 	    }
201 	    if (shape_is_inside(pos.cx, pos.cy-1, 0, NULL, &d_wire, 0) == 0) {
202 		d_inside = true;
203 		num_inside++;
204 	    }
205 
206 	    if (num_inside > 2)
207 		World_set_block(blk, FILLED);
208 
209 	    if (num_inside == 2) {
210 		if (r_inside && u_inside)
211 		    World_set_block(blk, REC_RU);
212 		if (u_inside && l_inside)
213 		    World_set_block(blk, REC_LU);
214 		if (l_inside && d_inside)
215 		    World_set_block(blk, REC_LD);
216 		if (d_inside && r_inside)
217 		    World_set_block(blk, REC_RD);
218 		if (u_inside && d_inside)
219 		    World_set_block(blk, FILLED);
220 		if (r_inside && l_inside)
221 		    World_set_block(blk, FILLED);
222 	    }
223 
224 	    if (num_inside == 1) {
225 		if (r_inside)
226 		    World_set_block(blk, REC_RU);
227 		if (u_inside)
228 		    World_set_block(blk, REC_LU);
229 		if (l_inside)
230 		    World_set_block(blk, REC_LD);
231 		if (d_inside)
232 		    World_set_block(blk, REC_RD);
233 	    }
234 	}
235     }
236 
237     /*
238      * Create blocks out of map objects. Note that some of these
239      * may be in the same block, which might cause a client error.
240      */
241     for (i = 0; i < Num_fuels(); i++) {
242 	fuel_t *fs = Fuel_by_index(i);
243 
244 	blk = Clpos_to_blkpos(fs->pos);
245 	World_set_block(blk, FUEL);
246     }
247 
248     for (i = 0; i < Num_asteroidConcs(); i++) {
249 	asteroid_concentrator_t *aconc = AsteroidConc_by_index(i);
250 
251 	blk = Clpos_to_blkpos(aconc->pos);
252 	World_set_block(blk, ASTEROID_CONCENTRATOR);
253     }
254 
255     for (i = 0; i < Num_itemConcs(); i++) {
256 	item_concentrator_t *iconc = ItemConc_by_index(i);
257 
258 	blk = Clpos_to_blkpos(iconc->pos);
259 	World_set_block(blk, ITEM_CONCENTRATOR);
260     }
261 
262     for (i = 0; i < Num_wormholes(); i++) {
263 	wormhole_t *wh = Wormhole_by_index(i);
264 
265 	blk = Clpos_to_blkpos(wh->pos);
266 	World_set_block(blk, WORMHOLE);
267     }
268 
269     /* find balltargets */
270     for (blk.by = 0; blk.by < world->bheight_floor; blk.by++) {
271 	for (blk.bx = 0; blk.bx < world->bwidth_floor; blk.bx++) {
272 	    int group;
273 	    group_t *gp;
274 
275 	    pos = Block_get_center_clpos(blk);
276 	    group = shape_is_inside(pos.cx, pos.cy,
277 				    BALL_BIT, NULL, &filled_wire, 0);
278 	    if (group == NO_GROUP || group == 0)
279 		continue;
280 	    gp = groupptr_by_id(group);
281 	    if (gp == NULL)
282 		continue;
283 	    if (gp->type == TREASURE && gp->hitmask == NONBALL_BIT)
284 		World_set_block(blk, TREASURE);
285 	}
286     }
287 
288     /*
289      * Handle bases. Note that on polygon maps there can be several
290      * bases on the area of a block. To handle this so that old clients
291      * won't get "Bad homebase index" error, we put excess bases somewhere
292      * else than on the same block.
293      */
294 
295     /*
296      * First mark all blocks having a base.
297      * We use a base attractor for this.
298      */
299     for (i = 0; i < Num_bases(); i++) {
300 	base_t *base = Base_by_index(i);
301 
302 	blk = Clpos_to_blkpos(base->pos);
303 	type = World_get_block(blk);
304 
305 	/* don't put the base on top of a fuel or treasure */
306 	if (type == FUEL || type == TREASURE)
307 	    continue;
308 	World_set_block(blk, BASE_ATTRACTOR);
309     }
310 
311     /*
312      * Put bases where there are base attractors or somewhere else
313      * if the block already has some other important type.
314      */
315     for (i = 0; i < Num_bases(); i++) {
316 	base_t *base = Base_by_index(i);
317 	bool done;
318 
319 	blk = Clpos_to_blkpos(base->pos);
320 	type = World_get_block(blk);
321 	done = false;
322 
323 	if (type == FUEL || type == TREASURE || type == BASE) {
324 	    /*
325 	     * The block where the base should be put already has some
326 	     * important type. We need to put this base somewhere else.
327 	     * Let's just line up excess bases close to the origin of the map.
328 	     */
329 	    for (blk.by = 0; blk.by < world->y; blk.by++) {
330 		for (blk.bx = 0; blk.bx < world->x; blk.bx++) {
331 		    type = World_get_block(blk);
332 		    /*
333 		     * Check for base attractor here too because we might
334 		     * have marked this block in the earlier loop over all
335 		     * bases.
336 		     */
337 		    if (type == FUEL || type == TREASURE
338 			|| type == BASE || type == BASE_ATTRACTOR)
339 			continue;
340 		    /* put base attractor here so that assert is happy */
341 		    type = BASE_ATTRACTOR;
342 		    World_set_block(blk, type);
343 		    done = true;
344 		    break;
345 		}
346 		if (done)
347 		    break;
348 	    }
349 
350 	    /* this probably doesn't happen very often */
351 	    if (!done)
352 		fatal("Create_blockmap_from_polygons:\n"
353 		      "Couldn't find any place on map to put base on "
354 		      "(%d, %d).", base->pos.cx, base->pos.cy);
355 	}
356 
357 	assert(type == BASE_ATTRACTOR);
358 	World_set_block(blk, BASE);
359     }
360 }
361 
Xpmap_init_setup(void)362 setup_t *Xpmap_init_setup(void)
363 {
364     int i, x, y, team, type = -1, dir, wtype;
365     int wormhole_i = 0, treasure_i = 0, target_i = 0, base_i = 0, cannon_i = 0;
366     unsigned char *mapdata, *mapptr;
367     size_t size, numblocks;
368     setup_t *setup;
369 
370     numblocks = world->x * world->y;
371     if ((mapdata = XMALLOC(unsigned char, numblocks)) == NULL) {
372 	error("No memory for mapdata");
373 	return NULL;
374     }
375     memset(mapdata, SETUP_SPACE, numblocks);
376     mapptr = mapdata;
377     errno = 0;
378     for (x = 0; x < world->x; x++) {
379 	for (y = 0; y < world->y; y++, mapptr++) {
380 	    type = world->block[x][y];
381 	    switch (type) {
382 	    case ACWISE_GRAV:
383 	    case CWISE_GRAV:
384 	    case POS_GRAV:
385 	    case NEG_GRAV:
386 	    case UP_GRAV:
387 	    case DOWN_GRAV:
388 	    case RIGHT_GRAV:
389 	    case LEFT_GRAV:
390 		if (!options.gravityVisible)
391 		    type = SPACE;
392 		break;
393 	    case WORMHOLE:
394 		if (!options.wormholeVisible)
395 		    type = SPACE;
396 		break;
397 	    case ITEM_CONCENTRATOR:
398 		if (!options.itemConcentratorVisible)
399 		    type = SPACE;
400 		break;
401 	    case ASTEROID_CONCENTRATOR:
402 		if (!options.asteroidConcentratorVisible)
403 		    type = SPACE;
404 		break;
405 	    case FRICTION:
406 		if (!options.blockFrictionVisible)
407 		    type = SPACE;
408 		else
409 		    type = DECOR_FILLED;
410 		break;
411 	    default:
412 		break;
413 	    }
414 	    switch (type) {
415 	    case SPACE:		*mapptr = SETUP_SPACE; break;
416 	    case FILLED:	*mapptr = SETUP_FILLED; break;
417 	    case REC_RU:	*mapptr = SETUP_REC_RU; break;
418 	    case REC_RD:	*mapptr = SETUP_REC_RD; break;
419 	    case REC_LU:	*mapptr = SETUP_REC_LU; break;
420 	    case REC_LD:	*mapptr = SETUP_REC_LD; break;
421 	    case FUEL:		*mapptr = SETUP_FUEL; break;
422 	    case ACWISE_GRAV:	*mapptr = SETUP_ACWISE_GRAV; break;
423 	    case CWISE_GRAV:	*mapptr = SETUP_CWISE_GRAV; break;
424 	    case POS_GRAV:	*mapptr = SETUP_POS_GRAV; break;
425 	    case NEG_GRAV:	*mapptr = SETUP_NEG_GRAV; break;
426 	    case UP_GRAV:	*mapptr = SETUP_UP_GRAV; break;
427 	    case DOWN_GRAV:	*mapptr = SETUP_DOWN_GRAV; break;
428 	    case RIGHT_GRAV:	*mapptr = SETUP_RIGHT_GRAV; break;
429 	    case LEFT_GRAV:	*mapptr = SETUP_LEFT_GRAV; break;
430 	    case ITEM_CONCENTRATOR:
431 		*mapptr = SETUP_ITEM_CONCENTRATOR; break;
432 
433 	    case ASTEROID_CONCENTRATOR:
434 		*mapptr = SETUP_ASTEROID_CONCENTRATOR; break;
435 
436 	    case DECOR_FILLED:	*mapptr = SETUP_DECOR_FILLED; break;
437 	    case DECOR_RU:	*mapptr = SETUP_DECOR_RU; break;
438 	    case DECOR_RD:	*mapptr = SETUP_DECOR_RD; break;
439 	    case DECOR_LU:	*mapptr = SETUP_DECOR_LU; break;
440 	    case DECOR_LD:	*mapptr = SETUP_DECOR_LD; break;
441 
442 	    case WORMHOLE:
443 		if (wormhole_i >= Num_wormholes()) {
444 		    /*
445 		     * This can happen on an xp2 map if the block mapdata
446 		     * contains more wormholes than is specified in the
447 		     * xml data.
448 		     */
449 		    warn("Too many wormholes in block mapdata.");
450 		    *mapptr = SETUP_SPACE;
451 		    break;
452 		}
453 		wtype = Wormhole_by_index(wormhole_i)->type;
454 		wormhole_i++;
455 		switch (wtype) {
456 		case WORM_NORMAL:
457 		case WORM_FIXED:
458 		    *mapptr = SETUP_WORM_NORMAL;
459 		    break;
460 		case WORM_IN:
461 		    *mapptr = SETUP_WORM_IN;
462 		    break;
463 		case WORM_OUT:
464 		    *mapptr = SETUP_WORM_OUT;
465 		    break;
466 		default:
467 		    warn("Bad wormhole (%d,%d).", x, y);
468 		    *mapptr = SETUP_SPACE;
469 		    break;
470 		}
471 		break;
472 
473 	    case TREASURE:
474 		if (treasure_i >= Num_treasures()) {
475 		    warn("Too many treasures in block mapdata.");
476 		    *mapptr = SETUP_SPACE;
477 		    break;
478 		}
479 		team = Treasure_by_index(treasure_i)->team;
480 		treasure_i++;
481 		if (team == TEAM_NOT_SET)
482 		    team = 0;
483 		*mapptr = SETUP_TREASURE + team;
484 		break;
485 
486 	    case TARGET:
487 		if (target_i >= Num_targets()) {
488 		    warn("Too many targets in block mapdata.");
489 		    *mapptr = SETUP_SPACE;
490 		    break;
491 		}
492 		team = Target_by_index(target_i)->team;
493 		target_i++;
494 		if (team == TEAM_NOT_SET)
495 		    team = 0;
496 		*mapptr = SETUP_TARGET + team;
497 		break;
498 
499 	    case BASE:
500 		if (base_i >= Num_bases()) {
501 		    warn("Too many bases in block mapdata.");
502 		    *mapptr = SETUP_SPACE;
503 		    break;
504 		}
505 		team = Base_by_index(base_i)->team;
506 		if (team == TEAM_NOT_SET)
507 		    team = 0;
508 		dir = Base_by_index(base_i)->dir;
509 		base_i++;
510 		/* other code should take care of this */
511 		assert(dir >= 0);
512 		assert(dir < RES);
513 		/* round to nearest direction */
514 		dir = (((dir + (RES/8)) / (RES/4)) * (RES/4)) % RES;
515 		assert(dir == DIR_UP || dir == DIR_RIGHT
516 		       || dir == DIR_DOWN || dir == DIR_LEFT);
517 		switch (dir) {
518 		case DIR_UP:    *mapptr = SETUP_BASE_UP + team; break;
519 		case DIR_RIGHT: *mapptr = SETUP_BASE_RIGHT + team; break;
520 		case DIR_DOWN:  *mapptr = SETUP_BASE_DOWN + team; break;
521 		case DIR_LEFT:  *mapptr = SETUP_BASE_LEFT + team; break;
522 		default:
523 		    /* should never happen */
524 		    warn("Bad base at (%d,%d), (dir = %d).", x, y, dir);
525 		    *mapptr = SETUP_BASE_UP + team;
526 		    break;
527 		}
528 		break;
529 
530 	    case CANNON:
531 		if (cannon_i >= Num_cannons()) {
532 		    warn("Too many cannons in block mapdata.");
533 		    *mapptr = SETUP_SPACE;
534 		    break;
535 		}
536 		dir = Cannon_by_index(cannon_i)->dir;
537 		cannon_i++;
538 		switch (dir) {
539 		case DIR_UP:	*mapptr = SETUP_CANNON_UP; break;
540 		case DIR_RIGHT:	*mapptr = SETUP_CANNON_RIGHT; break;
541 		case DIR_DOWN:	*mapptr = SETUP_CANNON_DOWN; break;
542 		case DIR_LEFT:	*mapptr = SETUP_CANNON_LEFT; break;
543 		default:
544 		    warn("Bad cannon at (%d,%d), (dir = %d).", x, y, dir);
545 		    *mapptr = SETUP_CANNON_UP;
546 		    break;
547 		}
548 		break;
549 
550 	    case CHECK:
551 		for (i = 0; i < world->NumChecks; i++) {
552 		    check_t *check = Check_by_index(i);
553 		    blkpos_t bpos = Clpos_to_blkpos(check->pos);
554 
555 		    if (x != bpos.bx || y != bpos.by)
556 			continue;
557 		    *mapptr = SETUP_CHECK + i;
558 		    break;
559 		}
560 		if (i >= world->NumChecks) {
561 		    warn("Bad checkpoint at (%d,%d).", x, y);
562 		    *mapptr = SETUP_SPACE;
563 		    break;
564 		}
565 		break;
566 
567 	    default:
568 		warn("Unknown map type (%d) at (%d,%d).", type, x, y);
569 		*mapptr = SETUP_SPACE;
570 		break;
571 	    }
572 	}
573     }
574     if (!compress_maps) {
575 	type = SETUP_MAP_UNCOMPRESSED;
576 	size = numblocks;
577     } else {
578 	type = SETUP_MAP_ORDER_XY;
579 	size = Compress_map(mapdata, numblocks);
580 	if (size <= 0 || size > numblocks) {
581 	    warn("Map compression error (%d)", size);
582 	    free(mapdata);
583 	    return NULL;
584 	}
585 	if ((mapdata = XREALLOC(unsigned char, mapdata, size)) == NULL) {
586 	    error("Cannot reallocate mapdata");
587 	    return NULL;
588 	}
589     }
590 
591     if (type != SETUP_MAP_UNCOMPRESSED)
592 	xpprintf("%s Block map compression ratio is %-4.2f%%\n",
593 		 showtime(), 100.0 * size / numblocks);
594 
595     if ((setup = (setup_t *)malloc(sizeof(setup_t) + size)) == NULL) {
596 	error("No memory to hold oldsetup");
597 	free(mapdata);
598 	return NULL;
599     }
600     memset(setup, 0, sizeof(setup_t) + size);
601     memcpy(setup->map_data, mapdata, size);
602     free(mapdata);
603     setup->setup_size = ((char *) &setup->map_data[0]
604 			 - (char *) setup) + size;
605     setup->map_data_len = size;
606     setup->map_order = type;
607     setup->lives = world->rules->lives;
608     setup->mode = world->rules->mode;
609     setup->x = world->x;
610     setup->y = world->y;
611     strlcpy(setup->name, world->name, sizeof(setup->name));
612     strlcpy(setup->author, world->author, sizeof(setup->author));
613 
614     return setup;
615 }
616 
617 
618 
619 
620 /*
621  * Grok block based map data.
622  *
623  * Create world->block using options.mapData.
624  * Free options.mapData.
625  */
Xpmap_grok_map_data(void)626 void Xpmap_grok_map_data(void)
627 {
628     int x = -1, y = world->y - 1, c;
629     char *s = options.mapData;
630     blkpos_t blk;
631 
632     if (options.mapData == NULL) {
633 	warn("Map didn't have any mapData.");
634 	return;
635     }
636 
637     while (y >= 0) {
638 
639 	x++;
640 
641 	if (options.extraBorder && (x == 0 || x == world->x - 1
642 	    || y == 0 || y == world->y - 1)) {
643 	    if (x >= world->x) {
644 		x = -1;
645 		y--;
646 		continue;
647 	    } else
648 		/* make extra border of solid rock */
649 		c = XPMAP_FILLED;
650 	}
651 	else {
652 	    c = *s;
653 	    if (c == '\0' || c == EOF) {
654 		if (x < world->x) {
655 		    /* not enough map data on this line */
656 		    Xpmap_missing_error(world->y - y);
657 		    c = XPMAP_SPACE;
658 		} else
659 		    c = '\n';
660 	    } else {
661 		if (c == '\n' && x < world->x) {
662 		    /* not enough map data on this line */
663 		    Xpmap_missing_error(world->y - y);
664 		    c = XPMAP_SPACE;
665 		} else
666 		    s++;
667 	    }
668 	}
669 	if (x >= world->x || c == '\n') {
670 	    y--; x = -1;
671 	    if (c != '\n') {			/* Get rest of line */
672 		Xpmap_extra_error(world->y - y);
673 		while (c != '\n' && c != EOF)
674 		    c = *s++;
675 	    }
676 	    continue;
677 	}
678 	blk.bx = x;
679 	blk.by = y;
680 	World_set_block(blk, c);
681     }
682 
683     XFREE(options.mapData);
684 }
685 
686 
687 /*
688  * Determining which team these belong to is done later,
689  * in Find_closest_team().
690  */
Xpmap_place_cannon(blkpos_t blk,int dir)691 static void Xpmap_place_cannon(blkpos_t blk, int dir)
692 {
693     clpos_t pos;
694     int ind;
695 
696     switch (dir) {
697     case DIR_UP:
698 	pos.cx = (click_t)((blk.bx + 0.5) * BLOCK_CLICKS);
699 	pos.cy = (click_t)((blk.by + 0.333) * BLOCK_CLICKS);
700 	break;
701     case DIR_LEFT:
702 	pos.cx = (click_t)((blk.bx + 0.667) * BLOCK_CLICKS);
703 	pos.cy = (click_t)((blk.by + 0.5) * BLOCK_CLICKS);
704 	break;
705     case DIR_RIGHT:
706 	pos.cx = (click_t)((blk.bx + 0.333) * BLOCK_CLICKS);
707 	pos.cy = (click_t)((blk.by + 0.5) * BLOCK_CLICKS);
708 	break;
709     case DIR_DOWN:
710 	pos.cx = (click_t)((blk.bx + 0.5) * BLOCK_CLICKS);
711 	pos.cy = (click_t)((blk.by + 0.667) * BLOCK_CLICKS);
712 	break;
713     default:
714  	/* can't happen */
715 	assert(0 && "Unknown cannon direction.");
716 	break;
717     }
718 
719     World_set_block(blk, CANNON);
720     ind = World_place_cannon(pos, dir, TEAM_NOT_SET);
721     Cannon_init(Cannon_by_index(ind));
722 }
723 
724 /*
725  * The direction of the base should be so that it points
726  * up with respect to the gravity in the region.  This
727  * is fixed in Find_base_dir() when the gravity has
728  * been computed.
729  */
Xpmap_place_base(blkpos_t blk,int team)730 static void Xpmap_place_base(blkpos_t blk, int team)
731 {
732     World_set_block(blk, BASE);
733     World_place_base(Block_get_center_clpos(blk), DIR_UP, team, 0);
734 }
735 
Xpmap_place_fuel(blkpos_t blk)736 static void Xpmap_place_fuel(blkpos_t blk)
737 {
738     World_set_block(blk, FUEL);
739     World_place_fuel(Block_get_center_clpos(blk), TEAM_NOT_SET);
740 }
741 
Xpmap_place_treasure(blkpos_t blk,bool empty)742 static void Xpmap_place_treasure(blkpos_t blk, bool empty)
743 {
744     World_set_block(blk, TREASURE);
745     World_place_treasure(Block_get_center_clpos(blk),
746 			 TEAM_NOT_SET, empty, 0xff);
747 }
748 
Xpmap_place_wormhole(blkpos_t blk,wormtype_t type)749 static void Xpmap_place_wormhole(blkpos_t blk, wormtype_t type)
750 {
751     World_set_block(blk, WORMHOLE);
752     World_place_wormhole(Block_get_center_clpos(blk), type);
753 }
754 
Xpmap_place_target(blkpos_t blk)755 static void Xpmap_place_target(blkpos_t blk)
756 {
757     World_set_block(blk, TARGET);
758     World_place_target(Block_get_center_clpos(blk), TEAM_NOT_SET);
759 }
760 
Xpmap_place_check(blkpos_t blk,int ind)761 static void Xpmap_place_check(blkpos_t blk, int ind)
762 {
763     if (!BIT(world->rules->mode, TIMING)) {
764 	World_set_block(blk, SPACE);
765 	return;
766     }
767 
768     World_set_block(blk, CHECK);
769     World_place_check(Block_get_center_clpos(blk), ind);
770 }
771 
Xpmap_place_item_concentrator(blkpos_t blk)772 static void Xpmap_place_item_concentrator(blkpos_t blk)
773 {
774     World_set_block(blk, ITEM_CONCENTRATOR);
775     World_place_item_concentrator(Block_get_center_clpos(blk));
776 }
777 
Xpmap_place_asteroid_concentrator(blkpos_t blk)778 static void Xpmap_place_asteroid_concentrator(blkpos_t blk)
779 {
780     World_set_block(blk, ASTEROID_CONCENTRATOR);
781     World_place_asteroid_concentrator(Block_get_center_clpos(blk));
782 }
783 
Xpmap_place_grav(blkpos_t blk,double force,int type)784 static void Xpmap_place_grav(blkpos_t blk,
785 			     double force, int type)
786 {
787     World_set_block(blk, type);
788     World_place_grav(Block_get_center_clpos(blk), force, type);
789 }
790 
Xpmap_place_friction_area(blkpos_t blk)791 static void Xpmap_place_friction_area(blkpos_t blk)
792 {
793     World_set_block(blk, FRICTION);
794     World_place_friction_area(Block_get_center_clpos(blk),
795 			      options.blockFriction);
796 }
797 
Xpmap_place_block(blkpos_t blk,int type)798 static void Xpmap_place_block(blkpos_t blk, int type)
799 {
800     World_set_block(blk, type);
801 }
802 
803 /*
804  * Change read tags to internal data, create objects if 'create' is true.
805  */
Xpmap_tags_to_internal_data(void)806 void Xpmap_tags_to_internal_data(void)
807 {
808     int x, y;
809     char c;
810 
811     for (x = 0; x < world->x; x++) {
812 
813 	for (y = 0; y < world->y; y++) {
814 
815 	    blkpos_t blk;
816 
817 	    blk.bx = x;
818 	    blk.by = y;
819 
820 	    c = world->block[x][y];
821 
822 	    switch (c) {
823 	    case XPMAP_SPACE:
824 	    case XPMAP_SPACE_ALT:
825 	    default:
826 		Xpmap_place_block(blk, SPACE);
827 		break;
828 
829 	    case XPMAP_FILLED:
830 		Xpmap_place_block(blk, FILLED);
831 		break;
832 	    case XPMAP_REC_LU:
833 		Xpmap_place_block(blk, REC_LU);
834 		break;
835 	    case XPMAP_REC_RU:
836 		Xpmap_place_block(blk, REC_RU);
837 		break;
838 	    case XPMAP_REC_LD:
839 		Xpmap_place_block(blk, REC_LD);
840 		break;
841 	    case XPMAP_REC_RD:
842 		Xpmap_place_block(blk, REC_RD);
843 		break;
844 
845 	    case XPMAP_CANNON_UP:
846 		Xpmap_place_cannon(blk, DIR_UP);
847 		break;
848 	    case XPMAP_CANNON_LEFT:
849 		Xpmap_place_cannon(blk, DIR_LEFT);
850 		break;
851 	    case XPMAP_CANNON_RIGHT:
852 		Xpmap_place_cannon(blk, DIR_RIGHT);
853 		break;
854 	    case XPMAP_CANNON_DOWN:
855 		Xpmap_place_cannon(blk, DIR_DOWN);
856 		break;
857 
858 	    case XPMAP_FUEL:
859 		Xpmap_place_fuel(blk);
860 		break;
861 	    case XPMAP_TREASURE:
862 		Xpmap_place_treasure(blk, false);
863 		break;
864 	    case XPMAP_EMPTY_TREASURE:
865 		Xpmap_place_treasure(blk, true);
866 		break;
867 	    case XPMAP_TARGET:
868 		Xpmap_place_target(blk);
869 		break;
870 	    case XPMAP_ITEM_CONCENTRATOR:
871 		Xpmap_place_item_concentrator(blk);
872 		break;
873 	    case XPMAP_ASTEROID_CONCENTRATOR:
874 		Xpmap_place_asteroid_concentrator(blk);
875 		break;
876 	    case XPMAP_BASE_ATTRACTOR:
877 		Xpmap_place_block(blk, BASE_ATTRACTOR);
878 		break;
879 	    case XPMAP_BASE:
880 		Xpmap_place_base(blk, TEAM_NOT_SET);
881 		break;
882 	    case XPMAP_BASE_TEAM_0:
883 	    case XPMAP_BASE_TEAM_1:
884 	    case XPMAP_BASE_TEAM_2:
885 	    case XPMAP_BASE_TEAM_3:
886 	    case XPMAP_BASE_TEAM_4:
887 	    case XPMAP_BASE_TEAM_5:
888 	    case XPMAP_BASE_TEAM_6:
889 	    case XPMAP_BASE_TEAM_7:
890 	    case XPMAP_BASE_TEAM_8:
891 	    case XPMAP_BASE_TEAM_9:
892 		Xpmap_place_base(blk, (int) (c - XPMAP_BASE_TEAM_0));
893 		break;
894 
895 	    case XPMAP_POS_GRAV:
896 		Xpmap_place_grav(blk, -GRAVS_POWER, POS_GRAV);
897 		break;
898 	    case XPMAP_NEG_GRAV:
899 		Xpmap_place_grav(blk, GRAVS_POWER, NEG_GRAV);
900 		break;
901 	    case XPMAP_CWISE_GRAV:
902 		Xpmap_place_grav(blk, GRAVS_POWER, CWISE_GRAV);
903 		break;
904 	    case XPMAP_ACWISE_GRAV:
905 		Xpmap_place_grav(blk, -GRAVS_POWER, ACWISE_GRAV);
906 		break;
907 	    case XPMAP_UP_GRAV:
908 		Xpmap_place_grav(blk, GRAVS_POWER, UP_GRAV);
909 		break;
910 	    case XPMAP_DOWN_GRAV:
911 		Xpmap_place_grav(blk, -GRAVS_POWER, DOWN_GRAV);
912 		break;
913 	    case XPMAP_RIGHT_GRAV:
914 		Xpmap_place_grav(blk, GRAVS_POWER, RIGHT_GRAV);
915 		break;
916 	    case XPMAP_LEFT_GRAV:
917 		Xpmap_place_grav(blk, -GRAVS_POWER, LEFT_GRAV);
918 		break;
919 
920 	    case XPMAP_WORMHOLE_NORMAL:
921 		Xpmap_place_wormhole(blk, WORM_NORMAL);
922 		break;
923 	    case XPMAP_WORMHOLE_IN:
924 		Xpmap_place_wormhole(blk, WORM_IN);
925 		break;
926 	    case XPMAP_WORMHOLE_OUT:
927 		Xpmap_place_wormhole(blk, WORM_OUT);
928 		break;
929 
930 	    case XPMAP_CHECK_0:	    case XPMAP_CHECK_1:
931 	    case XPMAP_CHECK_2:	    case XPMAP_CHECK_3:
932 	    case XPMAP_CHECK_4:	    case XPMAP_CHECK_5:
933 	    case XPMAP_CHECK_6:	    case XPMAP_CHECK_7:
934 	    case XPMAP_CHECK_8:	    case XPMAP_CHECK_9:
935 	    case XPMAP_CHECK_10:    case XPMAP_CHECK_11:
936 	    case XPMAP_CHECK_12:    case XPMAP_CHECK_13:
937 	    case XPMAP_CHECK_14:    case XPMAP_CHECK_15:
938 	    case XPMAP_CHECK_16:    case XPMAP_CHECK_17:
939 	    case XPMAP_CHECK_18:    case XPMAP_CHECK_19:
940 	    case XPMAP_CHECK_20:    case XPMAP_CHECK_21:
941 	    case XPMAP_CHECK_22:    case XPMAP_CHECK_23:
942 	    case XPMAP_CHECK_24:    case XPMAP_CHECK_25:
943 		Xpmap_place_check(blk, (int) (c - XPMAP_CHECK_0));
944 		break;
945 
946 	    case XPMAP_FRICTION_AREA:
947 		Xpmap_place_friction_area(blk);
948 		break;
949 
950 	    case XPMAP_DECOR_FILLED:
951 		Xpmap_place_block(blk, DECOR_FILLED);
952 		break;
953 	    case XPMAP_DECOR_LU:
954 		Xpmap_place_block(blk, DECOR_LU);
955 		break;
956 	    case XPMAP_DECOR_RU:
957 		Xpmap_place_block(blk, DECOR_RU);
958 		break;
959 	    case XPMAP_DECOR_LD:
960 		Xpmap_place_block(blk, DECOR_LD);
961 		break;
962 	    case XPMAP_DECOR_RD:
963 		Xpmap_place_block(blk, DECOR_RD);
964 		break;
965 	    }
966 	}
967     }
968 }
969 
970 
Xpmap_find_map_object_teams(void)971 void Xpmap_find_map_object_teams(void)
972 {
973     int i;
974     clpos_t pos = { 0, 0 };
975 
976     if (!BIT(world->rules->mode, TEAM_PLAY))
977 	return;
978 
979     /* This could return -1 */
980     if (Find_closest_team(pos) == TEAM_NOT_SET) {
981 	warn("Broken map: Couldn't find teams for map objects.");
982 	return;
983     }
984 
985     /*
986      * Determine which team a stuff belongs to.
987      */
988     for (i = 0; i < Num_treasures(); i++) {
989 	treasure_t *treasure = Treasure_by_index(i);
990 	team_t *teamp;
991 
992 	treasure->team = Find_closest_team(treasure->pos);
993 	teamp = Team_by_index(treasure->team);
994 	assert(teamp != NULL);
995 
996 	teamp->NumTreasures++;
997 	if (treasure->empty)
998 	    teamp->NumEmptyTreasures++;
999 	else
1000 	    teamp->TreasuresLeft++;
1001     }
1002 
1003     for (i = 0; i < Num_targets(); i++) {
1004 	target_t *targ = Target_by_index(i);
1005 
1006 	targ->team = Find_closest_team(targ->pos);
1007     }
1008 
1009     if (options.teamCannons) {
1010 	for (i = 0; i < Num_cannons(); i++) {
1011 	    cannon_t *cannon = Cannon_by_index(i);
1012 
1013 	    cannon->team = Find_closest_team(cannon->pos);
1014 	}
1015     }
1016 
1017     for (i = 0; i < Num_fuels(); i++) {
1018 	fuel_t *fs = Fuel_by_index(i);
1019 
1020 	fs->team = Find_closest_team(fs->pos);
1021     }
1022 }
1023 
1024 
1025 /*
1026  * Find the correct direction of the base, according to the gravity in
1027  * the base region.
1028  *
1029  * If a base attractor is adjacent to a base then the base will point
1030  * to the attractor.
1031  */
Xpmap_find_base_direction(void)1032 void Xpmap_find_base_direction(void)
1033 {
1034     int	i;
1035     blkpos_t blk;
1036 
1037     for (i = 0; i < Num_bases(); i++) {
1038 	base_t *base = Base_by_index(i);
1039 	int x, y, dir, att;
1040 	vector_t gravity = World_gravity(base->pos);
1041 
1042 	if (gravity.x == 0.0 && gravity.y == 0.0)
1043 	    /*
1044 	     * Undefined direction
1045 	     * Should be set to direction of gravity!
1046 	     */
1047 	    dir = DIR_UP;
1048 	else {
1049 	    double a = findDir(-gravity.x, -gravity.y);
1050 
1051 	    dir = MOD2((int) (a + 0.5), RES);
1052 	    dir = ((dir + RES/8) / (RES/4)) * (RES/4);	/* round it */
1053 	    dir = MOD2(dir, RES);
1054 	}
1055 	att = -1;
1056 
1057 	x = CLICK_TO_BLOCK(base->pos.cx);
1058 	y = CLICK_TO_BLOCK(base->pos.cy);
1059 
1060 	/* First check upwards attractor */
1061 	if (y == world->y - 1 && world->block[x][0] == BASE_ATTRACTOR
1062 	    && BIT(world->rules->mode, WRAP_PLAY)) {
1063 	    if (att == -1 || dir == DIR_UP)
1064 		att = DIR_UP;
1065 	}
1066 	if (y < world->y - 1 && world->block[x][y + 1] == BASE_ATTRACTOR) {
1067 	    if (att == -1 || dir == DIR_UP)
1068 		att = DIR_UP;
1069 	}
1070 
1071 	/* then downwards */
1072 	if (y == 0 && world->block[x][world->y-1] == BASE_ATTRACTOR
1073 	    && BIT(world->rules->mode, WRAP_PLAY)) {
1074 	    if (att == -1 || dir == DIR_DOWN)
1075 		att = DIR_DOWN;
1076 	}
1077 	if (y > 0 && world->block[x][y - 1] == BASE_ATTRACTOR) {
1078 	    if (att == -1 || dir == DIR_DOWN)
1079 		att = DIR_DOWN;
1080 	}
1081 
1082 	/* then rightwards */
1083 	if (x == world->x - 1 && world->block[0][y] == BASE_ATTRACTOR
1084 	    && BIT(world->rules->mode, WRAP_PLAY)) {
1085 	    if (att == -1 || dir == DIR_RIGHT)
1086 		att = DIR_RIGHT;
1087 	}
1088 	if (x < world->x - 1 && world->block[x + 1][y] == BASE_ATTRACTOR) {
1089 	    if (att == -1 || dir == DIR_RIGHT)
1090 		att = DIR_RIGHT;
1091 	}
1092 
1093 	/* then leftwards */
1094 	if (x == 0 && world->block[world->x-1][y] == BASE_ATTRACTOR
1095 	    && BIT(world->rules->mode, WRAP_PLAY)) {
1096 	    if (att == -1 || dir == DIR_LEFT)
1097 		att = DIR_LEFT;
1098 	}
1099 	if (x > 0 && world->block[x - 1][y] == BASE_ATTRACTOR) {
1100 	    if (att == -1 || dir == DIR_LEFT)
1101 		att = DIR_LEFT;
1102 	}
1103 
1104 	if (att != -1)
1105 	    dir = att;
1106 	base->dir = dir;
1107     }
1108     for (blk.bx = 0; blk.bx < world->x; blk.bx++) {
1109 	for (blk.by = 0; blk.by < world->y; blk.by++) {
1110 	    if (World_get_block(blk) == BASE_ATTRACTOR)
1111 		World_set_block(blk, SPACE);
1112 	}
1113     }
1114 }
1115 
1116 
1117 /*
1118  * The following functions is for converting the block based map data
1119  * to polygons.
1120  */
1121 
1122 /* number of vertices in polygon */
1123 #define N (2 + 12)
Xpmap_treasure_to_polygon(int treasure_ind)1124 static void Xpmap_treasure_to_polygon(int treasure_ind)
1125 {
1126     int cx, cy, i, r, n;
1127     double angle;
1128     int polystyle, edgestyle;
1129     treasure_t *treasure = Treasure_by_index(treasure_ind);
1130     clpos_t pos[N + 1];
1131 
1132     polystyle = P_get_poly_id("treasure_ps");
1133     edgestyle = P_get_edge_id("treasure_es");
1134 
1135     cx = treasure->pos.cx - BLOCK_CLICKS / 2;
1136     cy = treasure->pos.cy - BLOCK_CLICKS / 2;
1137 
1138     pos[0].cx = cx;
1139     pos[0].cy = cy;
1140     pos[1].cx = cx + (BLOCK_CLICKS - 1);
1141     pos[1].cy = cy;
1142 
1143     cx = treasure->pos.cx;
1144     cy = treasure->pos.cy;
1145     /* -1 is to ensure the vertices are inside the block */
1146     r = (BLOCK_CLICKS / 2) - 1;
1147     /* number of points in half circle */
1148     n = N - 2;
1149 
1150     for (i = 0; i < n; i++) {
1151 	angle = (((double)i)/(n - 1)) * PI;
1152 	pos[i + 2].cx = (click_t)(cx + r * cos(angle));
1153 	pos[i + 2].cy = (click_t)(cy + r * sin(angle));
1154     }
1155 
1156     pos[N] = pos[0];
1157 
1158     /* create balltarget */
1159     P_start_balltarget(treasure->team, treasure_ind);
1160     P_start_polygon(pos[0], polystyle);
1161     for (i = 1; i <= N; i++)
1162 	P_vertex(pos[i], edgestyle);
1163     P_end_polygon();
1164     P_end_balltarget();
1165 
1166     /* create ballarea */
1167     P_start_ballarea();
1168     P_start_polygon(pos[0], polystyle);
1169     for (i = 1; i <= N; i++)
1170 	P_vertex(pos[i], edgestyle);
1171     P_end_polygon();
1172     P_end_ballarea();
1173 }
1174 #undef N
1175 
1176 
Xpmap_block_polygon(clpos_t bpos,int polystyle,int edgestyle,int destroyed_style)1177 static void Xpmap_block_polygon(clpos_t bpos, int polystyle, int edgestyle,
1178 				int destroyed_style)
1179 {
1180     clpos_t pos[5];
1181     int i;
1182 
1183     bpos.cx = CLICK_TO_BLOCK(bpos.cx) * BLOCK_CLICKS;
1184     bpos.cy = CLICK_TO_BLOCK(bpos.cy) * BLOCK_CLICKS;
1185 
1186     pos[0].cx = bpos.cx;
1187     pos[0].cy = bpos.cy;
1188     pos[1].cx = bpos.cx + (BLOCK_CLICKS - 1);
1189     pos[1].cy = bpos.cy;
1190     pos[2].cx = bpos.cx + (BLOCK_CLICKS - 1);
1191     pos[2].cy = bpos.cy + (BLOCK_CLICKS - 1);
1192     pos[3].cx = bpos.cx;
1193     pos[3].cy = bpos.cy + (BLOCK_CLICKS - 1);
1194     pos[4] = pos[0];
1195 
1196     P_start_polygon(pos[0], polystyle);
1197     if (destroyed_style >= 0)
1198 	P_style("destroyed", destroyed_style);
1199     for (i = 1; i <= 4; i++)
1200 	P_vertex(pos[i], edgestyle);
1201     P_end_polygon();
1202 }
1203 
1204 
Xpmap_target_to_polygon(int target_ind)1205 static void Xpmap_target_to_polygon(int target_ind)
1206 {
1207     int ps, es, ds;
1208     target_t *targ = Target_by_index(target_ind);
1209 
1210     ps = P_get_poly_id("target_ps");
1211     es = P_get_edge_id("target_es");
1212     ds = P_get_poly_id("destroyed_ps");
1213 
1214     /* create target polygon */
1215     P_start_target(target_ind);
1216     Xpmap_block_polygon(targ->pos, ps, es, ds);
1217     P_end_target();
1218 }
1219 
1220 
Xpmap_cannon_polygon(cannon_t * cannon,int polystyle,int edgestyle)1221 static void Xpmap_cannon_polygon(cannon_t *cannon,
1222 				 int polystyle, int edgestyle)
1223 {
1224     clpos_t pos[4], cpos = cannon->pos;
1225     int i, ds;
1226 
1227     pos[0] = cannon->pos;
1228 
1229     cpos.cx = CLICK_TO_BLOCK(cpos.cx) * BLOCK_CLICKS;
1230     cpos.cy = CLICK_TO_BLOCK(cpos.cy) * BLOCK_CLICKS;
1231 
1232     switch (cannon->dir) {
1233     case DIR_RIGHT:
1234 	pos[1].cx = cpos.cx;
1235 	pos[1].cy = cpos.cy + (BLOCK_CLICKS - 1);
1236 	pos[2].cx = cpos.cx;
1237 	pos[2].cy = cpos.cy;
1238 	break;
1239     case DIR_UP:
1240 	pos[1].cx = cpos.cx;
1241 	pos[1].cy = cpos.cy;
1242 	pos[2].cx = cpos.cx + (BLOCK_CLICKS - 1);
1243 	pos[2].cy = cpos.cy;
1244 	break;
1245     case DIR_LEFT:
1246 	pos[1].cx = cpos.cx + (BLOCK_CLICKS - 1);
1247 	pos[1].cy = cpos.cy;
1248 	pos[2].cx = cpos.cx + (BLOCK_CLICKS - 1);
1249 	pos[2].cy = cpos.cy + (BLOCK_CLICKS - 1);
1250 	break;
1251     case DIR_DOWN:
1252 	pos[1].cx = cpos.cx + (BLOCK_CLICKS - 1);
1253 	pos[1].cy = cpos.cy + (BLOCK_CLICKS - 1);
1254 	pos[2].cx = cpos.cx;
1255 	pos[2].cy = cpos.cy + (BLOCK_CLICKS - 1);
1256 	break;
1257     default:
1258  	/* can't happen */
1259 	assert(0 && "Unknown cannon direction.");
1260 	break;
1261     }
1262     pos[3] = pos[0];
1263 
1264     ds = P_get_poly_id("destroyed_ps");
1265     P_start_polygon(pos[0], polystyle);
1266     P_style("destroyed", ds);
1267     for (i = 1; i <= 3; i++)
1268 	P_vertex(pos[i], edgestyle);
1269     P_end_polygon();
1270 }
1271 
1272 
Xpmap_cannon_to_polygon(int cannon_ind)1273 static void Xpmap_cannon_to_polygon(int cannon_ind)
1274 {
1275     int ps, es;
1276     cannon_t *cannon = Cannon_by_index(cannon_ind);
1277 
1278     ps = P_get_poly_id("cannon_ps");
1279     es = P_get_edge_id("cannon_es");
1280 
1281     P_start_cannon(cannon_ind);
1282     Xpmap_cannon_polygon(cannon, ps, es);
1283     P_end_cannon();
1284 }
1285 
1286 #define N 12
Xpmap_wormhole_to_polygon(int wormhole_ind)1287 static void Xpmap_wormhole_to_polygon(int wormhole_ind)
1288 {
1289     int ps, es, i, r;
1290     double angle;
1291     wormhole_t *wormhole = Wormhole_by_index(wormhole_ind);
1292     clpos_t pos[N + 1], wpos;
1293 
1294     /* don't make a polygon for an out wormhole */
1295     if (wormhole->type == WORM_OUT)
1296 	return;
1297 
1298     ps = P_get_poly_id("wormhole_ps");
1299     es = P_get_edge_id("wormhole_es");
1300 
1301     wpos = wormhole->pos;
1302     r = WORMHOLE_RADIUS;
1303 
1304     for (i = 0; i < N; i++) {
1305 	angle = (((double)i)/ N) * 2 * PI;
1306 	pos[i].cx = (click_t)(wpos.cx + r * cos(angle));
1307 	pos[i].cy = (click_t)(wpos.cy + r * sin(angle));
1308     }
1309     pos[N] = pos[0];
1310 
1311     P_start_wormhole(wormhole_ind);
1312     P_start_polygon(pos[0], ps);
1313     for (i = 1; i <= N; i++)
1314 	P_vertex(pos[i], es);
1315     P_end_polygon();
1316     P_end_wormhole();
1317 }
1318 
Xpmap_friction_area_to_polygon(int fa_ind)1319 static void Xpmap_friction_area_to_polygon(int fa_ind)
1320 {
1321     int ps, es;
1322     friction_area_t *fa = FrictionArea_by_index(fa_ind);
1323 
1324     ps = P_get_poly_id("fa_ps");
1325     es = P_get_edge_id("fa_es");
1326 
1327     P_start_friction_area(fa_ind);
1328     Xpmap_block_polygon(fa->pos, ps, es, -1);
1329     P_end_friction_area();
1330 }
1331 
1332 /*
1333  * Add a wall polygon
1334  *
1335  * The polygon consists of a start block and and endblock and possibly
1336  * some full wall/fuel blocks in between. A total number of numblocks
1337  * blocks are part of the polygon and must be 1 or more. If numblocks
1338  * is one, the startblock and endblock are the same block.
1339  *
1340  * The block coordinates of the first block is (bx, by)
1341  *
1342  * The polygon will have 3 or 4 vertices.
1343  *
1344  * Idea: first assume the polygon is a rectangle, then move
1345  * the vertices depending on the start and end blocks.
1346  *
1347  * The vertex index:
1348  * 0: upper left vertex
1349  * 1: lower left vertex
1350  * 2: lower right vertex
1351  * 3: upper right vertex
1352  * 4: upper left vertex, second time
1353  */
Xpmap_wall_poly(int bx,int by,int startblock,int endblock,int numblocks,int polystyle,int edgestyle)1354 static void Xpmap_wall_poly(int bx, int by,
1355 			    int startblock, int endblock, int numblocks,
1356 			    int polystyle, int edgestyle)
1357 {
1358     int i;
1359     clpos_t pos[5]; /* positions of vertices */
1360 
1361     if (numblocks < 1)
1362 	return;
1363 
1364     /* first assume we have a rectangle */
1365     pos[0].cx = bx * BLOCK_CLICKS;
1366     pos[0].cy = (by + 1) * BLOCK_CLICKS - 1;
1367     pos[1].cx = bx * BLOCK_CLICKS;
1368     pos[1].cy = by * BLOCK_CLICKS;
1369     pos[2].cx = (bx + numblocks) * BLOCK_CLICKS - 1;
1370     pos[2].cy = by * BLOCK_CLICKS;
1371     pos[3].cx = (bx + numblocks) * BLOCK_CLICKS - 1;
1372     pos[3].cy = (by + 1) * BLOCK_CLICKS - 1;
1373 
1374     /* move the vertices depending on the startblock and endblock */
1375     switch (startblock) {
1376     case FILLED:
1377     case REC_LU:
1378     case REC_LD:
1379     case FUEL:
1380 	/* no need to move the leftmost 2 vertices */
1381 	break;
1382     case REC_RU:
1383 	/* move lower left vertex to the right */
1384 	pos[1].cx += (BLOCK_CLICKS - 1);
1385 	break;
1386     case REC_RD:
1387 	/* move upper left vertex to the right */
1388 	pos[0].cx += (BLOCK_CLICKS - 1);
1389 	break;
1390     default:
1391 	return;
1392     }
1393 
1394     switch (endblock) {
1395     case FILLED:
1396     case FUEL:
1397     case REC_RU:
1398     case REC_RD:
1399 	/* no need to move the rightmost 2 vertices */
1400 	break;
1401     case REC_LU:
1402 	pos[2].cx -= (BLOCK_CLICKS - 1);
1403 	break;
1404     case REC_LD:
1405 	pos[3].cx -= (BLOCK_CLICKS - 1);
1406 	break;
1407     default:
1408 	return;
1409     }
1410 
1411     /*
1412      * Since we want to form a closed loop of line segments, the
1413      * last vertex must equal the first.
1414      */
1415     pos[4] = pos[0];
1416 
1417     P_start_polygon(pos[0], polystyle);
1418     for (i = 1; i <= 4; i++)
1419 	P_vertex(pos[i], edgestyle);
1420     P_end_polygon();
1421 }
1422 
1423 
Xpmap_walls_to_polygons(void)1424 static void Xpmap_walls_to_polygons(void)
1425 {
1426     int x, y, x0 = 0;
1427     int numblocks = 0;
1428     int inside = false;
1429     int startblock = 0, endblock = 0, block;
1430     int maxblocks = POLYGON_MAX_OFFSET / BLOCK_CLICKS;
1431     int ps, es;
1432 
1433     ps = P_get_poly_id("wall_ps");
1434     es = P_get_edge_id("wall_es");
1435 
1436     /*
1437      * x, FILLED = solid wall
1438      * s, REC_LU = wall triangle pointing left and up
1439      * a, REC_RU = wall triangle pointing right and up
1440      * w, REC_LD = wall triangle pointing left and down
1441      * q, REC_RD = wall triangle pointing right and down
1442      * #, FUEL   = fuel block
1443      */
1444 
1445     for (y = world->y - 1; y >= 0; y--) {
1446 	for (x = 0; x < world->x; x++) {
1447 	    block = world->block[x][y];
1448 
1449 	    if (!inside) {
1450 		switch (block) {
1451 		case FILLED:
1452 		case REC_RU:
1453 		case REC_RD:
1454 		case FUEL:
1455 		    x0 = x;
1456 		    startblock = endblock = block;
1457 		    inside = true;
1458 		    numblocks = 1;
1459 		    break;
1460 
1461 		case REC_LU:
1462 		case REC_LD:
1463 		    Xpmap_wall_poly(x, y, block, block, 1, ps, es);
1464 		    break;
1465 		default:
1466 		    break;
1467 		}
1468 	    } else {
1469 
1470 		switch (block) {
1471 		case FILLED:
1472 		case FUEL:
1473 		    numblocks++;
1474 		    endblock = block;
1475 		    break;
1476 
1477 		case REC_RU:
1478 		case REC_RD:
1479 		    /* old polygon ends */
1480 		    Xpmap_wall_poly(x0, y, startblock, endblock,
1481 				    numblocks, ps, es);
1482 		    /* and a new one starts */
1483 		    x0 = x;
1484 		    startblock = endblock = block;
1485 		    numblocks = 1;
1486 		    break;
1487 
1488 		case REC_LU:
1489 		case REC_LD:
1490 		    numblocks++;
1491 		    endblock = block;
1492 		    Xpmap_wall_poly(x0, y, startblock, endblock,
1493 				    numblocks, ps, es);
1494 		    inside = false;
1495 		    break;
1496 
1497 		default:
1498 		    /* none of the above, polygon ends */
1499 		    Xpmap_wall_poly(x0, y, startblock, endblock,
1500 				    numblocks, ps, es);
1501 		    inside = false;
1502 		    break;
1503 		}
1504 	    }
1505 
1506 	    /*
1507 	     * We don't want the polygon to have offsets that are too big.
1508 	     */
1509 	    if (inside && numblocks == maxblocks) {
1510 		Xpmap_wall_poly(x0, y, startblock, endblock,
1511 				numblocks, ps, es);
1512 		inside = false;
1513 	    }
1514 
1515 	}
1516 
1517 	/* end of row */
1518 	if (inside) {
1519 	    Xpmap_wall_poly(x0, y, startblock, endblock,
1520 			    numblocks, ps, es);
1521 	    inside = false;
1522 	}
1523     }
1524 }
1525 
1526 
Xpmap_blocks_to_polygons(void)1527 void Xpmap_blocks_to_polygons(void)
1528 {
1529     int i;
1530 
1531     /* create edgestyles and polystyles */
1532     P_edgestyle("wall_es", -1, 0x2244EE, 0);
1533     P_polystyle("wall_ps", 0x0033AA, 0, P_get_edge_id("wall_es"), 0);
1534 
1535     P_edgestyle("treasure_es", -1, 0xFF0000, 0);
1536     P_polystyle("treasure_ps", 0xFF0000, 0, P_get_edge_id("treasure_es"), 0);
1537 
1538     P_edgestyle("target_es", 3, 0xFF7700, 0);
1539     P_polystyle("target_ps", 0xFF7700, 3, P_get_edge_id("target_es"), 0);
1540 
1541     P_edgestyle("cannon_es", 3, 0xFFFFFF, 0);
1542     P_polystyle("cannon_ps", 0xFFFFFF, 2, P_get_edge_id("cannon_es"), 0);
1543 
1544     P_edgestyle("destroyed_es", 3, 0xFF0000, 0);
1545     P_polystyle("destroyed_ps", 0xFF0000, 2, P_get_edge_id("destroyed_es"),
1546 		STYLE_INVISIBLE|STYLE_INVISIBLE_RADAR);
1547 
1548     P_edgestyle("wormhole_es", -1, 0x00FFFF, 0);
1549     P_polystyle("wormhole_ps", 0x00FFFF, 2, P_get_edge_id("wormhole_es"), 0);
1550 
1551     P_edgestyle("fa_es", 2, 0xFF1F00, 0);
1552     P_polystyle("fa_ps", 0xCF1F00, 2, P_get_edge_id("fa_es"), 0);
1553 
1554     Xpmap_walls_to_polygons();
1555 
1556     if (options.polygonMode)
1557 	is_polygon_map = true;
1558 
1559     for (i = 0; i < Num_treasures(); i++)
1560 	Xpmap_treasure_to_polygon(i);
1561 
1562     for (i = 0; i < Num_targets(); i++)
1563 	Xpmap_target_to_polygon(i);
1564 
1565     for (i = 0; i < Num_cannons(); i++)
1566 	Xpmap_cannon_to_polygon(i);
1567 
1568     for (i = 0; i < Num_wormholes(); i++)
1569 	Xpmap_wormhole_to_polygon(i);
1570 
1571     for (i = 0; i < Num_frictionAreas(); i++)
1572 	Xpmap_friction_area_to_polygon(i);
1573 
1574     /*xpprintf("Created %d polygons.\n", num_polys);*/
1575 }
1576