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 * Kristian S�derblom <kps@users.sourceforge.net>
8 *
9 * Copyright (C) 1991-2001 by
10 *
11 * Bj�rn Stabell <bjoern@xpilot.org>
12 * Ken Ronny Schouten <ken@xpilot.org>
13 * Bert Gijsbers <bert@xpilot.org>
14 * Dick Balaska <dick@xpilot.org>
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 */
30
31 #include "xpserver.h"
32
33 /*
34 * Globals.
35 */
36 world_t World, *world;
37 bool is_polygon_map = false;
38
39 static void Find_base_direction(void);
40
41
Check_map_object_counters(void)42 static void Check_map_object_counters(void)
43 {
44 int i;
45
46 /*assert(world->NumCannons == 0);*/
47
48 /*assert(world->NumGravs == 0);*/
49 /*assert(world->NumWormholes == 0);*/
50 /*assert(world->NumTreasures == 0);*/
51 /*assert(world->NumTargets == 0);*/
52 /*assert(world->NumBases == 0);*/
53 /*assert(world->NumItemConcs == 0);
54 assert(world->NumAsteroidConcs == 0);
55 assert(world->NumFrictionAreas == 0);*/
56 /*assert(world->NumEcms == 0);*/
57 /*assert(world->NumTransporters == 0);*/
58
59 for (i = 0; i < MAX_TEAMS; i++) {
60 assert(world->teams[i].NumMembers == 0);
61 assert(world->teams[i].NumRobots == 0);
62 assert(world->teams[i].NumBases == 0);
63 assert(world->teams[i].NumTreasures == 0);
64 assert(world->teams[i].NumEmptyTreasures == 0);
65 assert(world->teams[i].TreasuresDestroyed == 0);
66 assert(world->teams[i].TreasuresLeft == 0);
67 assert(world->teams[i].SwapperId == NO_ID);
68 }
69 }
70
shrink(void ** pp,size_t size)71 static void shrink(void **pp, size_t size)
72 {
73 void *p;
74
75 p = realloc(*pp, size);
76 if (!p) {
77 warn("Realloc failed!");
78 exit(1);
79 }
80 *pp = p;
81 }
82
83 #define SHRINK(T,P,N,M) { \
84 if ((M) > (N)) { \
85 shrink((void **)&(P), (N) * sizeof(T)); \
86 M = (N); \
87 } } \
88
89
Realloc_map_objects(void)90 static void Realloc_map_objects(void)
91 {
92 /*SHRINK(cannon_t, world->cannons, world->NumCannons, world->MaxCannons);*/
93 /*SHRINK(fuel_t, world->fuels, world->NumFuels, world->MaxFuels);*/
94 /*SHRINK(grav_t, world->gravs, world->NumGravs, world->MaxGravs);*/
95 /*SHRINK(wormhole_t, world->wormholes,
96 world->NumWormholes, world->MaxWormholes);*/
97 /*SHRINK(treasure_t, world->treasures,
98 world->NumTreasures, world->MaxTreasures);*/
99 /*SHRINK(target_t, world->targets, world->NumTargets, world->MaxTargets);*/
100 /*SHRINK(base_t, world->bases, world->NumBases, world->MaxBases);*/
101 /*SHRINK(item_concentrator_t, world->itemConcs,
102 world->NumItemConcs, world->MaxItemConcs);
103 SHRINK(asteroid_concentrator_t, world->asteroidConcs,
104 world->NumAsteroidConcs, world->MaxAsteroidConcs);
105 SHRINK(friction_area_t, world->frictionAreas,
106 world->NumFrictionAreas, world->MaxFrictionAreas);*/
107 }
108
World_place_cannon(clpos_t pos,int dir,int team)109 int World_place_cannon(clpos_t pos, int dir, int team)
110 {
111 cannon_t t, *cannon;
112 int ind = Num_cannons(), i;
113
114 t.pos = pos;
115 t.dir = dir;
116 t.team = team;
117 t.dead_ticks = 0;
118 t.conn_mask = ~0;
119 t.group = NO_GROUP;
120 t.score = CANNON_SCORE;
121 t.id = ind + MIN_CANNON_ID;
122 assert(Is_cannon_id(t.id));
123 if (t.id > MAX_CANNON_ID) {
124 warn("The server supports only %d cannons per map.", NUM_CANNON_IDS);
125 exit(1);
126 }
127 for (i = 0; i < NUM_ITEMS; i++)
128 t.initial_items[i] = -1;
129 t.shot_speed = -1;
130 t.smartness = -1;
131 Arraylist_add(world->cannons, &t);
132 cannon = Cannon_by_index(ind);
133 assert(Cannon_by_id(t.id) == cannon);
134
135 return ind;
136 }
137
World_place_fuel(clpos_t pos,int team)138 int World_place_fuel(clpos_t pos, int team)
139 {
140 fuel_t t;
141 int ind = Num_fuels();
142
143 t.pos = pos;
144 t.fuel = START_STATION_FUEL;
145 t.conn_mask = ~0;
146 t.last_change = frame_loops;
147 t.team = team;
148 Arraylist_add(world->fuels, &t);
149
150 return ind;
151 }
152
World_place_base(clpos_t pos,int dir,int team,int order)153 int World_place_base(clpos_t pos, int dir, int team, int order)
154 {
155 base_t t;
156 int ind = Num_bases(), i;
157
158 t.pos = pos;
159 t.order = order;
160 /*
161 * The direction of the base should be so that it points
162 * up with respect to the gravity in the region. This
163 * is fixed in Find_base_direction() when the gravity has
164 * been computed.
165 */
166 if (dir < 0 || dir >= RES) {
167 warn("Base with direction %d in map.", dir);
168 warn("Valid base directions are from 0 to %d.", RES-1);
169 while (dir < 0)
170 dir += RES;
171 while (dir >= RES)
172 dir -= RES;
173 warn("Using direction %d for this base.", dir);
174 }
175
176 t.dir = dir;
177 if (BIT(world->rules->mode, TEAM_PLAY)) {
178 if (team < 0 || team >= MAX_TEAMS)
179 team = 0;
180 t.team = team;
181 world->teams[team].NumBases++;
182 if (world->teams[team].NumBases == 1)
183 world->NumTeamBases++;
184 } else
185 t.team = TEAM_NOT_SET;
186 t.ind = Num_bases();
187
188 for (i = 0; i < NUM_ITEMS; i++)
189 t.initial_items[i] = -1;
190 Arraylist_add(world->bases, &t);
191
192 return ind;
193 }
194
World_place_treasure(clpos_t pos,int team,bool empty,int ball_style)195 int World_place_treasure(clpos_t pos, int team, bool empty,
196 int ball_style)
197 {
198 treasure_t t;
199 int ind = Num_treasures();
200
201 t.pos = pos;
202 t.have = false;
203 t.destroyed = 0;
204 t.team = team;
205 t.empty = empty;
206 t.ball_style = ball_style;
207 if (team != TEAM_NOT_SET) {
208 world->teams[team].NumTreasures++;
209 world->teams[team].TreasuresLeft++;
210 }
211 Arraylist_add(world->treasures, &t);
212
213 return ind;
214 }
215
World_place_target(clpos_t pos,int team)216 int World_place_target(clpos_t pos, int team)
217 {
218 target_t t;
219 int ind = Num_targets();
220
221 t.pos = pos;
222 /*
223 * If we have a block based map, the team is determined in
224 * in Xpmap_find_map_object_teams().
225 */
226 t.team = team;
227 t.dead_ticks = 0;
228 t.damage = TARGET_DAMAGE;
229 t.conn_mask = ~0;
230 t.update_mask = 0;
231 t.last_change = frame_loops;
232 t.group = NO_GROUP;
233 Arraylist_add(world->targets, &t);
234
235 return ind;
236 }
237
World_place_wormhole(clpos_t pos,wormtype_t type)238 int World_place_wormhole(clpos_t pos, wormtype_t type)
239 {
240 wormhole_t t;
241 int ind = Num_wormholes();
242
243 t.pos = pos;
244 t.countdown = 0;
245 t.lastdest = NO_IND;
246 t.type = type;
247 t.lastblock = SPACE;
248 t.lastID = NO_ID;
249 t.group = NO_GROUP;
250 Arraylist_add(world->wormholes, &t);
251
252 return ind;
253 }
254
255
256 /*
257 * Allocate checkpoints for an xp map.
258 */
alloc_old_checks(void)259 static void alloc_old_checks(void)
260 {
261 int i;
262 check_t t;
263 clpos_t pos = { -1, -1 };
264
265 t.pos = pos;
266
267 for (i = 0; i < OLD_MAX_CHECKS; i++)
268 STORE(check_t, world->checks, world->NumChecks, world->MaxChecks, t);
269
270 SHRINK(check_t, world->checks, world->NumChecks, world->MaxChecks);
271 world->NumChecks = 0;
272 }
273
World_place_check(clpos_t pos,int ind)274 int World_place_check(clpos_t pos, int ind)
275 {
276 check_t t;
277
278 if (!BIT(world->rules->mode, TIMING)) {
279 warn("Checkpoint on map with no timing.");
280 return NO_IND;
281 }
282
283 /* kps - need to do this for other map object types ? */
284 if (!World_contains_clpos(pos)) {
285 warn("Checkpoint outside world, ignoring.");
286 return NO_IND;
287 }
288
289 /*
290 * On xp maps we can have only 26 checkpoints.
291 */
292 if (ind >= 0 && ind < OLD_MAX_CHECKS) {
293 check_t *check;
294
295 if (world->NumChecks == 0)
296 alloc_old_checks();
297
298 /*
299 * kps hack - we can't use Check_by_index because it might return
300 * NULL since ind can here be >= world->NumChecks.
301 */
302 check = &world->checks[ind];
303 if (World_contains_clpos(check->pos)) {
304 warn("Map contains too many '%c' checkpoints.", 'A' + ind);
305 return NO_IND;
306 }
307
308 check->pos = pos;
309 world->NumChecks++;
310 return ind;
311 }
312
313 ind = world->NumChecks;
314 t.pos = pos;
315 STORE(check_t, world->checks, world->NumChecks, world->MaxChecks, t);
316 return ind;
317 }
318
World_place_item_concentrator(clpos_t pos)319 int World_place_item_concentrator(clpos_t pos)
320 {
321 item_concentrator_t t;
322 int ind = Num_itemConcs();
323
324 t.pos = pos;
325 Arraylist_add(world->itemConcs, &t);
326
327 return ind;
328 }
329
World_place_asteroid_concentrator(clpos_t pos)330 int World_place_asteroid_concentrator(clpos_t pos)
331 {
332 asteroid_concentrator_t t;
333 int ind = Num_asteroidConcs();
334
335 t.pos = pos;
336 Arraylist_add(world->asteroidConcs, &t);
337
338 return ind;
339 }
340
World_place_grav(clpos_t pos,double force,int type)341 int World_place_grav(clpos_t pos, double force, int type)
342 {
343 grav_t t;
344 int ind = Num_gravs();
345
346 t.pos = pos;
347 t.force = force;
348 t.type = type;
349 Arraylist_add(world->gravs, &t);
350
351 return ind;
352 }
353
World_place_friction_area(clpos_t pos,double fric)354 int World_place_friction_area(clpos_t pos, double fric)
355 {
356 friction_area_t t;
357 int ind = Num_frictionAreas();
358
359 t.pos = pos;
360 t.friction_setting = fric;
361 /*t.friction = ... ; handled in timing setup */
362 Arraylist_add(world->frictionAreas, &t);
363
364 return ind;
365 }
366
367 shape_t filled_wire;
368 clpos_t filled_coords[4];
369
Filled_wire_init(void)370 static void Filled_wire_init(void)
371 {
372 int i, h;
373
374 filled_wire.num_points = 4;
375
376 for (i = 0; i < 4; i++)
377 filled_wire.pts[i] = &filled_coords[i];
378
379 h = BLOCK_CLICKS / 2;
380
381 /* whole (filled) block */
382 filled_coords[0].cx = -h;
383 filled_coords[0].cy = -h;
384 filled_coords[1].cx = h - 1;
385 filled_coords[1].cy = -h;
386 filled_coords[2].cx = h - 1;
387 filled_coords[2].cy = h - 1;
388 filled_coords[3].cx = -h;
389 filled_coords[3].cy = h - 1;
390 }
391
World_init(void)392 int World_init(void)
393 {
394 int i;
395
396 memset(world, 0, sizeof(world_t));
397
398 if ((world->asteroidConcs
399 = Arraylist_alloc(sizeof(asteroid_concentrator_t))) == NULL)
400 return -1;
401 if ((world->bases = Arraylist_alloc(sizeof(base_t))) == NULL)
402 return -1;
403 if ((world->cannons = Arraylist_alloc(sizeof(cannon_t))) == NULL)
404 return -1;
405 if ((world->ecms = Arraylist_alloc(sizeof(ecm_t))) == NULL)
406 return -1;
407 if ((world->frictionAreas
408 = Arraylist_alloc(sizeof(friction_area_t))) == NULL)
409 return -1;
410 if ((world->fuels = Arraylist_alloc(sizeof(fuel_t))) == NULL)
411 return -1;
412 if ((world->itemConcs
413 = Arraylist_alloc(sizeof(item_concentrator_t))) == NULL)
414 return -1;
415 if ((world->gravs = Arraylist_alloc(sizeof(grav_t))) == NULL)
416 return -1;
417 if ((world->targets = Arraylist_alloc(sizeof(target_t))) == NULL)
418 return -1;
419 if ((world->treasures = Arraylist_alloc(sizeof(treasure_t))) == NULL)
420 return -1;
421 if ((world->transporters
422 = Arraylist_alloc(sizeof(transporter_t))) == NULL)
423 return -1;
424 if ((world->wormholes = Arraylist_alloc(sizeof(wormhole_t))) == NULL)
425 return -1;
426
427 for (i = 0; i < MAX_TEAMS; i++)
428 Team_by_index(i)->SwapperId = NO_ID;
429
430 Filled_wire_init();
431
432 return 0;
433 }
434
World_free(void)435 void World_free(void)
436 {
437 XFREE(world->block);
438 XFREE(world->gravity);
439 /*XFREE(world->gravs);*/
440 /*XFREE(world->bases);*/
441 /*XFREE(world->cannons);*/
442 XFREE(world->checks);
443 /*XFREE(world->fuels);*/
444 /*XFREE(world->wormholes);*/
445 /*XFREE(world->itemConcs);
446 XFREE(world->asteroidConcs);
447 XFREE(world->frictionAreas);*/
448 }
449
World_alloc(void)450 static bool World_alloc(void)
451 {
452 int x;
453 unsigned char *map_line;
454 unsigned char **map_pointer;
455 vector_t *grav_line;
456 vector_t **grav_pointer;
457
458 assert(world->block == NULL);
459 assert(world->gravity == NULL);
460
461 world->block = (unsigned char **)
462 malloc(sizeof(unsigned char *) * world->x
463 + world->x * sizeof(unsigned char) * world->y);
464 world->gravity = (vector_t **)
465 malloc(sizeof(vector_t *) * world->x
466 + world->x * sizeof(vector_t) * world->y);
467
468 /*assert(world->gravs == NULL);*/
469 /*assert(world->bases == NULL);*/
470 /*assert(world->fuels == NULL);*/
471 /*assert(world->cannons == NULL);*/
472 assert(world->checks == NULL);
473 /*assert(world->wormholes == NULL);*/
474 /*assert(world->itemConcs == NULL);*/
475 /*assert(world->asteroidConcs == NULL);*/
476
477 if (world->block == NULL || world->gravity == NULL) {
478 World_free();
479 error("Couldn't allocate memory for map");
480 return false;
481 }
482
483 map_pointer = world->block;
484 map_line = (unsigned char*) ((unsigned char**)map_pointer + world->x);
485
486 grav_pointer = world->gravity;
487 grav_line = (vector_t*) ((vector_t**)grav_pointer + world->x);
488
489 for (x = 0; x < world->x; x++) {
490 *map_pointer = map_line;
491 map_pointer += 1;
492 map_line += world->y;
493 *grav_pointer = grav_line;
494 grav_pointer += 1;
495 grav_line += world->y;
496 }
497
498 return true;
499 }
500
501 /*
502 * This function can be called after the map options have been read.
503 */
Grok_map_size(void)504 static bool Grok_map_size(void)
505 {
506 bool bad = false;
507 int w = options.mapWidth, h = options.mapHeight;
508
509 if (!is_polygon_map) {
510 w *= BLOCK_SZ;
511 h *= BLOCK_SZ;
512 }
513
514 if (w < MIN_MAP_SIZE) {
515 warn("mapWidth too small, minimum is %d pixels (%d blocks).\n",
516 MIN_MAP_SIZE, MIN_MAP_SIZE / BLOCK_SZ + 1);
517 bad = true;
518 }
519 if (w > MAX_MAP_SIZE) {
520 warn("mapWidth too big, maximum is %d pixels (%d blocks).\n",
521 MAX_MAP_SIZE, MAX_MAP_SIZE / BLOCK_SZ);
522 bad = true;
523 }
524 if (h < MIN_MAP_SIZE) {
525 warn("mapHeight too small, minimum is %d pixels (%d blocks).\n",
526 MIN_MAP_SIZE, MIN_MAP_SIZE / BLOCK_SZ + 1);
527 bad = true;
528 }
529 if (h > MAX_MAP_SIZE) {
530 warn("mapWidth too big, maximum is %d pixels (%d blocks).\n",
531 MAX_MAP_SIZE, MAX_MAP_SIZE / BLOCK_SZ);
532 bad = true;
533 }
534
535 if (bad)
536 return false;
537
538 /* pixel sizes */
539 world->width = w;
540 world->height = h;
541 if (!is_polygon_map && options.extraBorder) {
542 world->width += 2 * BLOCK_SZ;
543 world->height += 2 * BLOCK_SZ;
544 }
545 world->hypotenuse = LENGTH(world->width, world->height);
546
547 /* click sizes */
548 world->cwidth = world->width * CLICK;
549 world->cheight = world->height * CLICK;
550
551 /* block sizes */
552 world->x = (world->width - 1) / BLOCK_SZ + 1; /* !@# */
553 world->y = (world->height - 1) / BLOCK_SZ + 1;
554 world->diagonal = LENGTH(world->x, world->y);
555 world->bwidth_floor = world->width / BLOCK_SZ;
556 world->bheight_floor = world->height / BLOCK_SZ;
557
558 return true;
559 }
560
Grok_map_options(void)561 bool Grok_map_options(void)
562 {
563 if (world->have_options)
564 return true;
565
566 Check_map_object_counters();
567
568 if (!Grok_map_size())
569 return false;
570
571 strlcpy(world->name, options.mapName, sizeof(world->name));
572 strlcpy(world->author, options.mapAuthor, sizeof(world->author));
573 strlcpy(world->dataURL, options.dataURL, sizeof(world->dataURL));
574
575 if (!World_alloc())
576 return false;
577
578 Set_world_rules();
579 Set_world_items();
580 Set_world_asteroids();
581
582 if (BIT(world->rules->mode, TEAM_PLAY|TIMING) == (TEAM_PLAY|TIMING)) {
583 warn("Cannot teamplay while in race mode -- ignoring teamplay");
584 CLR_BIT(world->rules->mode, TEAM_PLAY);
585 }
586
587 world->have_options = true;
588
589 return true;
590 }
591
Grok_map(void)592 bool Grok_map(void)
593 {
594 if (!Grok_map_options())
595 return false;
596
597 if (!is_polygon_map) {
598 Xpmap_grok_map_data();
599 Xpmap_tags_to_internal_data();
600 Xpmap_find_map_object_teams();
601 }
602
603 if (!Verify_wormhole_consistency())
604 return false;
605
606 if (BIT(world->rules->mode, TIMING) && world->NumChecks == 0) {
607 warn("No checkpoints found while race mode (timing) was set.");
608 warn("Turning off race mode.");
609 CLR_BIT(world->rules->mode, TIMING);
610 }
611
612 /* kps - what are these doing here ? */
613 if (options.maxRobots == -1)
614 options.maxRobots = Num_bases();
615
616 if (options.minRobots == -1)
617 options.minRobots = options.maxRobots;
618
619 Realloc_map_objects();
620
621 if (Num_bases() <= 0)
622 fatal("Map has no bases!");
623
624 xpprintf("World....: %s\nBases....: %d\nMapsize..: %dx%d pixels\n"
625 "Team play: %s\n",
626 world->name, Num_bases(), world->width, world->height,
627 BIT(world->rules->mode, TEAM_PLAY) ? "on" : "off");
628
629 if (!is_polygon_map)
630 Xpmap_blocks_to_polygons();
631
632 Compute_gravity();
633 Find_base_direction();
634
635 return true;
636 }
637
638 /*
639 * Return the team that is closest to this click position.
640 */
Find_closest_team(clpos_t pos)641 int Find_closest_team(clpos_t pos)
642 {
643 int team = TEAM_NOT_SET, i;
644 double closest = FLT_MAX, l;
645
646 for (i = 0; i < Num_bases(); i++) {
647 base_t *base = Base_by_index(i);
648
649 if (base->team == TEAM_NOT_SET)
650 continue;
651
652 l = Wrap_length(pos.cx - base->pos.cx, pos.cy - base->pos.cy);
653 if (l < closest) {
654 team = base->team;
655 closest = l;
656 }
657 }
658
659 return team;
660 }
661
662
Find_base_direction(void)663 static void Find_base_direction(void)
664 {
665 /* kps - this might go wrong if we run in -options.polygonMode ? */
666 if (!is_polygon_map)
667 Xpmap_find_base_direction();
668 }
669
Wrap_findDir(double dx,double dy)670 double Wrap_findDir(double dx, double dy)
671 {
672 dx = WRAP_DX(dx);
673 dy = WRAP_DY(dy);
674 return findDir(dx, dy);
675 }
676
Wrap_cfindDir(int dcx,int dcy)677 double Wrap_cfindDir(int dcx, int dcy)
678 {
679 dcx = WRAP_DCX(dcx);
680 dcy = WRAP_DCY(dcy);
681 return findDir((double)dcx, (double)dcy);
682 }
683
Wrap_length(int dcx,int dcy)684 double Wrap_length(int dcx, int dcy)
685 {
686 dcx = WRAP_DCX(dcx);
687 dcy = WRAP_DCY(dcy);
688 return LENGTH(dcx, dcy);
689 }
690