1 /* $Id: player.c,v 5.31 2003/09/16 21:01:39 bertg Exp $
2 *
3 * XPilot, a multiplayer gravity war game. Copyright (C) 1991-2001 by
4 *
5 * Bj�rn Stabell <bjoern@xpilot.org>
6 * Ken Ronny Schouten <ken@xpilot.org>
7 * Bert Gijsbers <bert@xpilot.org>
8 * Dick Balaska <dick@xpilot.org>
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., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <math.h>
28 #include <stdio.h>
29 #include <errno.h>
30
31 #ifdef _WINDOWS
32 # include "NT/winServer.h"
33 #endif
34
35 #define SERVER
36 #include "version.h"
37 #include "config.h"
38 #include "serverconst.h"
39 #include "global.h"
40 #include "proto.h"
41 #include "map.h"
42 #include "score.h"
43 #include "bit.h"
44 #include "netserver.h"
45 #include "saudio.h"
46 #include "error.h"
47 #include "objpos.h"
48 #include "draw.h"
49 #include "commonproto.h"
50
51 char player_version[] = VERSION;
52
53
54 bool updateScores = true;
55
56
57 /********* **********
58 * Functions on player array.
59 */
60
Pick_startpos(int ind)61 void Pick_startpos(int ind)
62 {
63 player *pl = Players[ind];
64 int i, num_free;
65 int pick = 0, seen = 0;
66 static int prev_num_bases = 0;
67 static char *free_bases = NULL;
68
69 if (IS_TANK_PTR(pl)) {
70 pl->home_base = 0;
71 return;
72 }
73
74 if (prev_num_bases != World.NumBases) {
75 prev_num_bases = World.NumBases;
76 if (free_bases != NULL) {
77 free(free_bases);
78 }
79 free_bases = (char *) malloc(World.NumBases * sizeof(*free_bases));
80 if (free_bases == NULL) {
81 error("Can't allocate memory for free_bases");
82 End_game();
83 }
84 }
85
86 num_free = 0;
87 for (i = 0; i < World.NumBases; i++) {
88 if (World.base[i].team == pl->team) {
89 num_free++;
90 free_bases[i] = 1;
91 } else {
92 free_bases[i] = 0; /* other team */
93 }
94 }
95
96 for (i = 0; i < NumPlayers; i++) {
97 if (i != ind
98 && !IS_TANK_IND(i)
99 && free_bases[Players[i]->home_base]) {
100 free_bases[Players[i]->home_base] = 0; /* occupado */
101 num_free--;
102 }
103 }
104
105 if (BIT(World.rules->mode, TIMING)) { /* pick first free base */
106 for (i=0; i < World.NumBases; i++) {
107 if (free_bases[World.baseorder[i].base_idx]) {
108 break;
109 }
110 }
111 } else {
112 pick = (int)(rfrac() * num_free);
113 seen = 0;
114 for (i = 0; i < World.NumBases; i++) {
115 if (free_bases[i] != 0) {
116 if (seen < pick) {
117 seen++;
118 } else {
119 break;
120 }
121 }
122 }
123 }
124
125 if (i == World.NumBases) {
126 error("Can't pick startpos (ind=%d,num=%d,free=%d,pick=%d,seen=%d)",
127 ind, World.NumBases, num_free, pick, seen);
128 End_game();
129 } else {
130 pl->home_base = BIT(World.rules->mode, TIMING) ?
131 World.baseorder[i].base_idx : i;
132 if (ind < NumPlayers) {
133 for (i = 0; i < NumPlayers; i++) {
134 if (Players[i]->conn != NOT_CONNECTED) {
135 Send_base(Players[i]->conn,
136 pl->id,
137 pl->home_base);
138 }
139 }
140 if (BIT(pl->status, PLAYING) == 0) {
141 pl->count = RECOVERY_DELAY;
142 }
143 else if (BIT(pl->status, PAUSE|GAME_OVER)) {
144 Go_home(ind);
145 }
146 }
147 }
148 }
149
150
Go_home(int ind)151 void Go_home(int ind)
152 {
153 player *pl = Players[ind];
154 int i, x, y, dir, check;
155 DFLOAT vx, vy, velo;
156
157 if (IS_TANK_PTR(pl)) {
158 /*NOTREACHED*/
159 /* Tanks have no homebase. */
160 error("BUG: gohome tank");
161 return;
162 }
163
164 if (BIT(World.rules->mode, TIMING)
165 && pl->round
166 && !BIT(pl->status, GAME_OVER)) {
167 if (pl->check)
168 check = pl->check - 1;
169 else
170 check = World.NumChecks - 1;
171 x = World.check[check].x;
172 y = World.check[check].y;
173 vx = (rfrac() - 0.5) * 0.1;
174 vy = (rfrac() - 0.5) * 0.1;
175 velo = LENGTH(vx, vy);
176 dir = pl->last_check_dir;
177 dir = MOD2(dir + (int)((rfrac() - 0.5) * (RES / 8)), RES);
178 } else {
179 x = World.base[pl->home_base].pos.x;
180 y = World.base[pl->home_base].pos.y;
181 dir = World.base[pl->home_base].dir;
182 vx = vy = velo = 0;
183 }
184
185 pl->dir = dir;
186 pl->float_dir = dir;
187 Player_position_init_pixels(pl,
188 (x + 0.5) * BLOCK_SZ + vx,
189 (y + 0.5) * BLOCK_SZ + vy);
190 pl->vel.x = vx;
191 pl->vel.y = vy;
192 pl->velocity = velo;
193 pl->acc.x = pl->acc.y = 0.0;
194 pl->turnacc = pl->turnvel = 0.0;
195 memset(pl->last_keyv, 0, sizeof(pl->last_keyv));
196 memset(pl->prev_keyv, 0, sizeof(pl->prev_keyv));
197 Player_used_kill(ind);
198
199 if (playerStartsShielded != 0) {
200 SET_BIT(pl->used, HAS_SHIELD);
201 if (playerShielding == 0) {
202 pl->shield_time = 2 * FPS;
203 SET_BIT(pl->have, HAS_SHIELD);
204 }
205 if (BIT(pl->have, HAS_DEFLECTOR)) {
206 Deflector(ind, true);
207 }
208 }
209 CLR_BIT(pl->status, THRUSTING);
210 pl->updateVisibility = 1;
211 for (i = 0; i < NumPlayers; i++) {
212 pl->visibility[i].lastChange = 0;
213 Players[i]->visibility[ind].lastChange = 0;
214 }
215
216 if (IS_ROBOT_PTR(pl)) {
217 Robot_go_home(ind);
218 }
219 }
220
221 /*
222 * Compute the current sensor range for player `pl'. This is based on the
223 * amount of fuel, the number of sensor items (each one adds 25%), and the
224 * minimum and maximum visibility limits in effect.
225 */
Compute_sensor_range(player * pl)226 void Compute_sensor_range(player *pl)
227 {
228 static int init = 0;
229 static DFLOAT EnergyRangeFactor;
230
231 if (!init) {
232 if (minVisibilityDistance <= 0.0)
233 minVisibilityDistance = VISIBILITY_DISTANCE;
234 else
235 minVisibilityDistance *= BLOCK_SZ;
236 if (maxVisibilityDistance <= 0.0)
237 maxVisibilityDistance = World.hypotenuse;
238 else
239 maxVisibilityDistance *= BLOCK_SZ;
240
241 if (World.items[ITEM_FUEL].initial > 0.0) {
242 EnergyRangeFactor = minVisibilityDistance /
243 (World.items[ITEM_FUEL].initial
244 * (1.0 + ((DFLOAT)World.items[ITEM_SENSOR].initial * 0.25)));
245 EnergyRangeFactor /= FUEL_SCALE_FACT;
246 } else {
247 EnergyRangeFactor = ENERGY_RANGE_FACTOR;
248 }
249 init = 1;
250 }
251
252 pl->sensor_range = pl->fuel.sum * EnergyRangeFactor;
253 pl->sensor_range *= (1.0 + ((DFLOAT)pl->item[ITEM_SENSOR] * 0.25));
254 if (pl->sensor_range < minVisibilityDistance)
255 pl->sensor_range = minVisibilityDistance;
256 if (pl->sensor_range > maxVisibilityDistance)
257 pl->sensor_range = maxVisibilityDistance;
258 }
259
260 /*
261 * Give ship one more tank, if possible.
262 */
Player_add_tank(int ind,long tank_fuel)263 void Player_add_tank(int ind, long tank_fuel)
264 {
265 player *pl = Players[ind];
266 long tank_cap, add_fuel;
267
268 if (pl->fuel.num_tanks < MAX_TANKS) {
269 pl->fuel.num_tanks++;
270 tank_cap = TANK_CAP(pl->fuel.num_tanks);
271 add_fuel = tank_fuel;
272 LIMIT(add_fuel, 0, tank_cap);
273 pl->fuel.sum += add_fuel;
274 pl->fuel.max += tank_cap;
275 pl->fuel.tank[pl->fuel.num_tanks] = add_fuel;
276 pl->emptymass += TANK_MASS;
277 pl->item[ITEM_TANK] = pl->fuel.num_tanks;
278 }
279 }
280
281 /*
282 * Remove a tank from a ship, if possible.
283 */
Player_remove_tank(int ind,int which_tank)284 void Player_remove_tank(int ind, int which_tank)
285 {
286 player *pl = Players[ind];
287 int i, tank_ind;
288 long tank_fuel, tank_cap;
289
290 if (pl->fuel.num_tanks > 0) {
291 tank_ind = which_tank;
292 LIMIT(tank_ind, 1, pl->fuel.num_tanks);
293 pl->emptymass -= TANK_MASS;
294 tank_fuel = pl->fuel.tank[tank_ind];
295 tank_cap = TANK_CAP(tank_ind);
296 pl->fuel.max -= tank_cap;
297 pl->fuel.sum -= tank_fuel;
298 pl->fuel.num_tanks--;
299 if (pl->fuel.current > pl->fuel.num_tanks) {
300 pl->fuel.current = 0;
301 } else {
302 for (i = tank_ind; i <= pl->fuel.num_tanks; i++) {
303 pl->fuel.tank[i] = pl->fuel.tank[i + 1];
304 }
305 }
306 pl->item[ITEM_TANK] = pl->fuel.num_tanks;
307 }
308 }
309
Player_hit_armor(int ind)310 void Player_hit_armor(int ind)
311 {
312 player *pl = Players[ind];
313
314 if (--pl->item[ITEM_ARMOR] <= 0)
315 CLR_BIT(pl->have, HAS_ARMOR);
316 }
317
Player_used_kill(int ind)318 void Player_used_kill(int ind)
319 {
320 player *pl = Players[ind];
321
322 pl->used &= ~USED_KILL;
323 if (!BIT(DEF_HAVE, HAS_SHIELD)) {
324 CLR_BIT(pl->have, HAS_SHIELD);
325 }
326 }
327
328 /*
329 * Calculate the mass of a player.
330 */
Player_set_mass(int ind)331 void Player_set_mass(int ind)
332 {
333 player *pl = Players[ind];
334 DFLOAT sum_item_mass = 0;
335 DFLOAT item_mass;
336 int item;
337
338 for (item = 0; item < NUM_ITEMS; item++) {
339
340 switch (item) {
341
342 case ITEM_FUEL:
343 case ITEM_TANK:
344 item_mass = 0;
345 break;
346
347 case ITEM_ARMOR:
348 item_mass = pl->item[ITEM_ARMOR] * ARMOR_MASS;
349 break;
350
351 default:
352 item_mass = pl->item[item] * minItemMass;
353 break;
354 }
355
356 sum_item_mass += item_mass;
357 }
358
359 pl->mass = pl->emptymass
360 + FUEL_MASS(pl->fuel.sum)
361 + sum_item_mass;
362 }
363
364 /*
365 * Give player the initial number of tanks and amount of fuel.
366 * Upto the maximum allowed.
367 */
Player_init_fuel(int ind,long total_fuel)368 static void Player_init_fuel(int ind, long total_fuel)
369 {
370 player *pl = Players[ind];
371 long fuel = total_fuel;
372 int i;
373
374 pl->fuel.num_tanks = 0;
375 pl->fuel.current = 0;
376 pl->fuel.max = TANK_CAP(0);
377 pl->fuel.sum = MIN(fuel, pl->fuel.max);
378 pl->fuel.tank[0] = pl->fuel.sum;
379 pl->emptymass = ShipMass;
380 pl->item[ITEM_TANK] = pl->fuel.num_tanks;
381
382 fuel -= pl->fuel.sum;
383
384 for (i = 1; i <= World.items[ITEM_TANK].initial; i++) {
385 Player_add_tank(ind, fuel);
386 fuel -= pl->fuel.tank[i];
387 }
388 }
389
Init_player(int ind,shipobj * ship)390 int Init_player(int ind, shipobj *ship)
391 {
392 player *pl = Players[ind];
393 bool too_late = false;
394 int i;
395
396
397 pl->vel.x = pl->vel.y = 0.0;
398 pl->acc.x = pl->acc.y = 0.0;
399 pl->float_dir = pl->dir = DIR_UP;
400 pl->turnvel = 0.0;
401 pl->oldturnvel = 0.0;
402 pl->turnacc = 0.0;
403 pl->mass = ShipMass;
404 pl->emptymass = ShipMass;
405
406 for (i = 0; i < NUM_ITEMS; i++) {
407 if (!BIT(1U << i, ITEM_BIT_FUEL | ITEM_BIT_TANK)) {
408 pl->item[i] = World.items[i].initial;
409 }
410 }
411
412 pl->fuel.sum = World.items[ITEM_FUEL].initial << FUEL_SCALE_BITS;
413 Player_init_fuel(ind, pl->fuel.sum);
414
415 if (allowShipShapes == true && ship) {
416 pl->ship = ship;
417 }
418 else {
419 /*
420 pl->ship = Default_ship();
421 */
422 shipobj *tryship = Parse_shape_str(defaultShipShape);
423
424 if (tryship)
425 pl->ship = tryship;
426 else
427 pl->ship = Default_ship();
428 }
429
430 pl->power = 45.0;
431 pl->turnspeed = 30.0;
432 pl->turnresistance = 0.12;
433 pl->power_s = 35.0;
434 pl->turnspeed_s = 25.0;
435 pl->turnresistance_s = 0.12;
436
437 pl->check = 0;
438 pl->round = 0;
439 pl->time = 0;
440 pl->last_lap_time = 0;
441 pl->last_lap = 0;
442 pl->best_lap = 0;
443 pl->count = -1;
444 pl->shield_time = 0;
445 pl->last_wall_touch = 0;
446
447 pl->type = OBJ_PLAYER;
448 pl->type_ext = 0; /* assume human player */
449 pl->shots = 0;
450 pl->missile_rack = 0;
451 pl->forceVisible = 0;
452 Compute_sensor_range(pl);
453 pl->shot_max = ShotsMax;
454 pl->shot_time = 0;
455 pl->color = WHITE;
456 pl->score = 0;
457 pl->prev_score = 0;
458 pl->prev_check = 0;
459 pl->prev_round = 0;
460 pl->fs = 0;
461 pl->repair_target = 0;
462 pl->name[0] = '\0';
463 pl->num_pulses = 0;
464 pl->emergency_thrust_left = 0;
465 pl->emergency_thrust_max = 0;
466 pl->emergency_shield_left = 0;
467 pl->emergency_shield_max = 0;
468 pl->phasing_left = 0;
469 pl->phasing_max = 0;
470 pl->ecmcount = 0;
471 pl->damaged = 0;
472 pl->stunned = 0;
473
474 pl->status = PLAYING | GRAVITY | DEF_BITS;
475 pl->have = DEF_HAVE;
476 pl->used = DEF_USED;
477
478 if (pl->item[ITEM_CLOAK] > 0) {
479 SET_BIT(pl->have, HAS_CLOAKING_DEVICE);
480 }
481
482 CLEAR_MODS(pl->mods);
483 for (i = 0; i < NUM_MODBANKS; i++)
484 CLEAR_MODS(pl->modbank[i]);
485 for (i = 0; i < LOCKBANK_MAX; i++)
486 pl->lockbank[i] = NOT_CONNECTED;
487
488 {
489 static unsigned short pseudo_team_no = 0;
490 pl->pseudo_team = pseudo_team_no++;
491 }
492 pl->mychar = ' ';
493 pl->prev_mychar = pl->mychar;
494 pl->life = World.rules->lives;
495 pl->prev_life = pl->life;
496 pl->ball = NULL;
497
498 pl->player_fps = FPS;
499 pl->player_round = 0;
500 pl->player_count = 0;
501
502 pl->kills = 0;
503 pl->deaths = 0;
504
505 /*
506 * If limited lives and if nobody has lost a life yet, you may enter
507 * now, otherwise you will have to wait 'til everyone gets GAME OVER.
508 */
509 if (BIT(World.rules->mode, LIMITED_LIVES)) {
510 for (i = 0; i < NumPlayers; i++) {
511 /* If a non-team member has lost a life,
512 * then it's too late to join. */
513 if (Players[i]->life < World.rules->lives && !TEAM(ind, i)) {
514 too_late = true;
515 break;
516 }
517 }
518 if (too_late) {
519 pl->mychar = 'W';
520 pl->prev_life = pl->life = 0;
521 SET_BIT(pl->status, GAME_OVER);
522 }
523 }
524
525 pl->team = TEAM_NOT_SET;
526
527 pl->alliance = ALLIANCE_NOT_SET;
528 pl->prev_alliance = ALLIANCE_NOT_SET;
529 pl->invite = NO_ID;
530
531 pl->lock.tagged = LOCK_NONE;
532 pl->lock.pl_id = 0;
533
534 pl->robot_data_ptr = NULL;
535
536 pl->wormDrawCount = 0;
537
538 pl->id = peek_ID();
539 GetInd[pl->id] = ind;
540 pl->conn = NOT_CONNECTED;
541 pl->audio = NULL;
542
543 pl->lose_item = 0;
544 pl->lose_item_state = 0;
545
546 pl->shove_next = 0;
547 for (i = 0; i < MAX_RECORDED_SHOVES; i++) {
548 pl->shove_record[i].pusher_id = NO_ID;
549 }
550
551 pl->frame_last_busy = frame_loops;
552
553 pl->isowner = 0;
554 pl->isoperator = 0;
555
556 return pl->id;
557 }
558
559
560 static player *playerArray;
561 static struct _visibility *visibilityArray;
562
Alloc_players(int number)563 void Alloc_players(int number)
564 {
565 player *p;
566 struct _visibility *t;
567 int i;
568
569
570 /* Allocate space for pointers */
571 Players = (player **) calloc(number + 1, sizeof(player *));
572
573 /* Allocate space for all entries, all player structs */
574 p = playerArray = (player *) calloc(number, sizeof(player));
575
576 /* Allocate space for all visibility arrays, n arrays of n entries */
577 t = visibilityArray =
578 (struct _visibility *) calloc(number * number,
579 sizeof(struct _visibility));
580
581 if (!Players || !playerArray || !visibilityArray) {
582 error("Not enough memory for Players.");
583 exit(1);
584 }
585
586 /* Players[-1] should evaluate to NULL. */
587 Players++;
588
589 for (i = 0; i < number; i++) {
590 Players[i] = p++;
591 Players[i]->visibility = t;
592 /* Advance to next block/array */
593 t += number;
594 }
595 }
596
597
598
Free_players(void)599 void Free_players(void)
600 {
601 if (Players) {
602 --Players;
603 free(Players);
604 Players = NULL;
605
606 free(playerArray);
607 free(visibilityArray);
608 }
609 }
610
611
612
Update_score_table(void)613 void Update_score_table(void)
614 {
615 int i, j, check;
616 player *pl;
617
618 for (j = 0; j < NumPlayers; j++) {
619 pl = Players[j];
620 if (pl->score != pl->prev_score
621 || pl->life != pl->prev_life
622 || pl->mychar != pl->prev_mychar
623 || pl->alliance != pl->prev_alliance) {
624 pl->prev_score = pl->score;
625 pl->prev_life = pl->life;
626 pl->prev_mychar = pl->mychar;
627 pl->prev_alliance = pl->alliance;
628 for (i = 0; i < NumPlayers; i++) {
629 if (Players[i]->conn != NOT_CONNECTED) {
630 Send_score(Players[i]->conn, pl->id,
631 pl->score, pl->life,
632 pl->mychar, pl->alliance);
633 }
634 }
635 }
636 if (BIT(World.rules->mode, TIMING)) {
637 if (pl->check != pl->prev_check
638 || pl->round != pl->prev_round) {
639 pl->prev_check = pl->check;
640 pl->prev_round = pl->round;
641 check = (pl->round == 0)
642 ? 0
643 : (pl->check == 0)
644 ? (World.NumChecks - 1)
645 : (pl->check - 1);
646 for (i = 0; i < NumPlayers; i++) {
647 if (Players[i]->conn != NOT_CONNECTED) {
648 Send_timing(Players[i]->conn, pl->id, check, pl->round);
649 }
650 }
651 }
652 }
653 }
654 if (BIT(World.rules->mode, TEAM_PLAY)) {
655 team_t *team;
656 for (j = 0; j < MAX_TEAMS; j++) {
657 team = &(World.teams[j]);
658 if (team->score != team->prev_score) {
659 team->prev_score = team->score;
660 for (i = 0; i < NumPlayers; i++) {
661 if (Players[i]->conn != NOT_CONNECTED) {
662 Send_team_score(Players[i]->conn, j, team->score);
663 }
664 }
665 }
666 }
667 }
668 updateScores = false;
669 #ifdef _WINDOWS
670 SendDialogUpdate();
671 #endif
672 }
673
674
Reset_all_players(void)675 void Reset_all_players(void)
676 {
677 player *pl;
678 int i, j;
679 char msg[MSG_LEN];
680
681 updateScores = true;
682
683 for (i = 0; i < NumPlayers; i++) {
684 pl = Players[i];
685 if (endOfRoundReset) {
686 if (BIT(pl->status, PAUSE)) {
687 Player_death_reset(i);
688 } else {
689 Kill_player(i);
690 if (pl != Players[i]) {
691 /* player was deleted. */
692 i--;
693 continue;
694 }
695 }
696 }
697 CLR_BIT(pl->status, GAME_OVER);
698 CLR_BIT(pl->have, HAS_BALL);
699 pl->kills = 0;
700 pl->deaths = 0;
701 pl->round = 0;
702 pl->check = 0;
703 pl->time = 0;
704 pl->best_lap = 0;
705 pl->last_lap = 0;
706 pl->last_lap_time = 0;
707 if (!BIT(pl->status, PAUSE)) {
708 pl->mychar = ' ';
709 pl->frame_last_busy = frame_loops;
710 pl->life = World.rules->lives;
711 if (BIT(World.rules->mode, TIMING)) {
712 pl->count = RECOVERY_DELAY;
713 }
714 }
715 if (IS_TANK_PTR(pl))
716 pl->mychar = 'T';
717 else if (IS_ROBOT_PTR(pl))
718 pl->mychar = 'R';
719 }
720 if (BIT(World.rules->mode, TEAM_PLAY)) {
721
722 /* Detach any balls and kill ball */
723 /* We are starting all over again */
724 for (j = NumObjs - 1; j >= 0 ; j--) {
725 if (BIT(Obj[j]->type, OBJ_BALL)) {
726 ballobject *ball = BALL_IND(j);
727 ball->id = NO_ID;
728 ball->life = 0;
729 ball->owner = 0; /* why not -1 ??? */
730 CLR_BIT(ball->status, RECREATE);
731 Delete_shot(j);
732 }
733 }
734
735 /* Reset the treasures */
736 for (i = 0; i < World.NumTreasures; i++) {
737 World.treasures[i].destroyed = 0;
738 World.treasures[i].have = false;
739 Make_treasure_ball(i);
740 }
741
742 /* Reset the teams */
743 for (i = 0; i < MAX_TEAMS; i++) {
744 World.teams[i].TreasuresDestroyed = 0;
745 World.teams[i].TreasuresLeft = World.teams[i].NumTreasures - World.teams[i].NumEmptyTreasures;
746 }
747
748 if (endOfRoundReset) {
749 /* Reset the targets */
750 for (i = 0; i < World.NumTargets; i++) {
751 if (World.targets[i].damage != TARGET_DAMAGE
752 || World.targets[i].dead_time != 0) {
753 World.block[World.targets[i].pos.x][World.targets[i].pos.y]
754 = TARGET;
755 World.targets[i].dead_time = 0;
756 World.targets[i].damage = TARGET_DAMAGE;
757 World.targets[i].conn_mask = 0;
758 World.targets[i].update_mask = (unsigned)-1;
759 World.targets[i].last_change = frame_loops;
760 }
761 }
762 }
763 }
764
765 if (endOfRoundReset) {
766 for (i = 0; i < NumObjs; i++) {
767 object *obj = Obj[i];
768 if (BIT(obj->type, OBJ_SHOT|OBJ_MINE|OBJ_DEBRIS|OBJ_SPARK
769 |OBJ_CANNON_SHOT|OBJ_TORPEDO|OBJ_SMART_SHOT
770 |OBJ_HEAT_SHOT|OBJ_ITEM)) {
771 obj->life = 0;
772 if (BIT(obj->type, OBJ_TORPEDO|OBJ_SMART_SHOT|OBJ_HEAT_SHOT
773 |OBJ_CANNON_SHOT|OBJ_MINE)) {
774 /* Take care that no new explosions are made. */
775 obj->mass = 0;
776 }
777 }
778 }
779 }
780
781 if (round_delay_send > 0) {
782 round_delay_send--;
783 }
784 if (roundDelaySeconds) {
785 /* Hold your horses! The next round will start in a few moments. */
786 round_delay = roundDelaySeconds * FPS;
787 /* Send him an extra seconds worth to be sure he gets the 0. */
788 round_delay_send = round_delay+FPS;
789 roundtime = -1;
790 sprintf(msg, "Delaying %d seconds until start of next %s.",
791 roundDelaySeconds,
792 (BIT(World.rules->mode, TIMING)? "race" : "round"));
793 Set_message(msg);
794 } else {
795 roundtime = maxRoundTime * FPS;
796 }
797
798 Update_score_table();
799 }
800
801
Check_team_members(int team)802 void Check_team_members(int team)
803 {
804 player *pl;
805 int members, i;
806
807 if (! BIT(World.rules->mode, TEAM_PLAY))
808 return;
809
810 for (members = i = 0; i < NumPlayers; i++) {
811 pl = Players[i];
812 if (pl->team != TEAM_NOT_SET
813 && !IS_TANK_PTR(pl)
814 && pl->team == team)
815 members++;
816 }
817 if (World.teams[team].NumMembers != members) {
818 error ("Server has reset team %d members from %d to %d",
819 team, World.teams[team].NumMembers, members);
820 for (i = 0; i < NumPlayers; i++) {
821 pl = Players[i];
822 if (pl->team != TEAM_NOT_SET
823 && !IS_TANK_PTR(pl)
824 && pl->team == team)
825 error ("Team %d currently has player %d: \"%s\"",
826 team, i+1, pl->name);
827 }
828 World.teams[team].NumMembers = members;
829 }
830 }
831
832
Compute_end_of_round_values(DFLOAT * average_score,int * num_best_players,DFLOAT * best_ratio,int best_players[])833 static void Compute_end_of_round_values(DFLOAT *average_score,
834 int *num_best_players,
835 DFLOAT *best_ratio,
836 int best_players[])
837 {
838 int i;
839 DFLOAT ratio;
840
841 /* Initialize everything */
842 *average_score = 0;
843 *num_best_players = 0;
844 *best_ratio = -1.0;
845
846 /* Figure out what the average score is and who has the best kill/death */
847 /* ratio for this round */
848 for (i = 0; i < NumPlayers; i++) {
849 if (IS_TANK_IND(i)
850 || (BIT(Players[i]->status, PAUSE)
851 && Players[i]->count <= 0)) {
852 continue;
853 }
854 *average_score += Players[i]->score;
855 ratio = (DFLOAT) Players[i]->kills / (Players[i]->deaths + 1);
856 if (ratio > *best_ratio) {
857 *best_ratio = ratio;
858 best_players[0] = i;
859 *num_best_players = 1;
860 }
861 else if (ratio == *best_ratio) {
862 best_players[(*num_best_players)++] = i;
863 }
864 }
865 *average_score /= NumPlayers;
866 }
867
868
Give_best_player_bonus(DFLOAT average_score,int num_best_players,DFLOAT best_ratio,int best_players[])869 static void Give_best_player_bonus(DFLOAT average_score,
870 int num_best_players,
871 DFLOAT best_ratio,
872 int best_players[])
873 {
874 int i;
875 DFLOAT points;
876 char msg[MSG_LEN];
877
878
879 if (best_ratio == 0) {
880 sprintf(msg, "There is no Deadly Player");
881 }
882 else if (num_best_players == 1) {
883 player *bp = Players[best_players[0]];
884
885 sprintf(msg,
886 "%s is the Deadliest Player with a kill ratio of %d/%d.",
887 bp->name,
888 bp->kills, bp->deaths);
889 points = best_ratio * Rate(bp->score, average_score);
890 SCORE(best_players[0], points,
891 OBJ_X_IN_BLOCKS(bp),
892 OBJ_Y_IN_BLOCKS(bp),
893 "[Deadliest]");
894 }
895 else {
896 msg[0] = '\0';
897 for (i = 0; i < num_best_players; i++) {
898 player *bp = Players[best_players[i]];
899 DFLOAT ratio = Rate(bp->score, average_score);
900 DFLOAT score = (ratio + num_best_players)
901 / num_best_players;
902
903 if (msg[0]) {
904 if (i == num_best_players - 1)
905 strcat(msg, " and ");
906 else
907 strcat(msg, ", ");
908 }
909 if (strlen(msg) + 8 + strlen(bp->name) >= sizeof(msg)) {
910 Set_message(msg);
911 msg[0] = '\0';
912 }
913 strcat(msg, bp->name);
914 points = (int) (best_ratio * score);
915 SCORE(best_players[i], points,
916 OBJ_X_IN_BLOCKS(bp),
917 OBJ_Y_IN_BLOCKS(bp),
918 "[Deadly]");
919 }
920 if (strlen(msg) + 64 >= sizeof(msg)) {
921 Set_message(msg);
922 msg[0] = '\0';
923 }
924 sprintf(msg + strlen(msg),
925 " are the Deadly Players with kill ratios of %d/%d.",
926 Players[best_players[0]]->kills,
927 Players[best_players[0]]->deaths);
928 }
929 Set_message(msg);
930 }
931
Give_individual_bonus(int ind,DFLOAT average_score)932 static void Give_individual_bonus(int ind, DFLOAT average_score)
933 {
934 DFLOAT ratio;
935 DFLOAT points;
936
937 ratio = (DFLOAT) Players[ind]->kills / (Players[ind]->deaths + 1);
938 points = ratio * Rate(Players[ind]->score, average_score);
939 SCORE(ind, points,
940 OBJ_X_IN_BLOCKS(Players[ind]),
941 OBJ_Y_IN_BLOCKS(Players[ind]),
942 "[Winner]");
943 }
944
945
Count_rounds(void)946 static void Count_rounds(void)
947 {
948 char msg[MSG_LEN];
949
950 if (!roundsToPlay) {
951 return;
952 }
953
954 ++roundsPlayed;
955
956 sprintf(msg, " < Round %d out of %d completed. >",
957 roundsPlayed, roundsToPlay);
958 Set_message(msg);
959 if (roundsPlayed >= roundsToPlay) {
960 Game_Over();
961 }
962 }
963
964
Team_game_over(int winning_team,const char * reason)965 void Team_game_over(int winning_team, const char *reason)
966 {
967 int i, j;
968 DFLOAT average_score;
969 int num_best_players;
970 int *best_players;
971 DFLOAT best_ratio;
972 char msg[MSG_LEN];
973
974 if (!(best_players = (int *)malloc(NumPlayers * sizeof(int)))) {
975 error("no mem");
976 End_game();
977 }
978
979 /* Figure out the average score and who has the best kill/death ratio */
980 /* ratio for this round */
981 Compute_end_of_round_values(&average_score,
982 &num_best_players,
983 &best_ratio,
984 best_players);
985
986 /* Print out the results of the round */
987 if (winning_team != -1) {
988 sprintf(msg, " < Team %d has won the game%s! >", winning_team,
989 reason);
990 sound_play_all(TEAM_WIN_SOUND);
991 } else {
992 sprintf(msg, " < We have a draw%s! >", reason);
993 sound_play_all(TEAM_DRAW_SOUND);
994 }
995 Set_message(msg);
996
997 /* Give bonus to the best player */
998 Give_best_player_bonus(average_score,
999 num_best_players,
1000 best_ratio,
1001 best_players);
1002
1003 /* Give bonuses to the winning team */
1004 if (winning_team != -1) {
1005 for (i = 0; i < NumPlayers; i++) {
1006 if (Players[i]->team != winning_team) {
1007 continue;
1008 }
1009 if (IS_TANK_IND(i)
1010 || (BIT(Players[i]->status, PAUSE)
1011 && Players[i]->count <= 0)
1012 || (BIT(Players[i]->status, GAME_OVER)
1013 && Players[i]->mychar == 'W'
1014 && Players[i]->score == 0)) {
1015 continue;
1016 }
1017 for (j = 0; j < num_best_players; j++) {
1018 if (i == best_players[j]) {
1019 break;
1020 }
1021 }
1022 if (j == num_best_players) {
1023 Give_individual_bonus(i, average_score);
1024 }
1025 }
1026 }
1027
1028 Reset_all_players();
1029
1030 Count_rounds();
1031
1032 free(best_players);
1033 }
1034
Individual_game_over(int winner)1035 void Individual_game_over(int winner)
1036 {
1037 int i, j;
1038 DFLOAT average_score;
1039 int num_best_players;
1040 int *best_players;
1041 DFLOAT best_ratio;
1042 char msg[MSG_LEN];
1043
1044 if (!(best_players = (int *)malloc(NumPlayers * sizeof(int)))) {
1045 error("no mem");
1046 End_game();
1047 }
1048
1049 /* Figure out what the average score is and who has the best kill/death */
1050 /* ratio for this round */
1051 Compute_end_of_round_values(&average_score, &num_best_players,
1052 &best_ratio, best_players);
1053
1054 /* Print out the results of the round */
1055 if (winner == -1) {
1056 Set_message(" < We have a draw! >");
1057 sound_play_all(PLAYER_DRAW_SOUND);
1058 }
1059 else if (winner == -2) {
1060 Set_message(" < The robots have won the game! >");
1061 /* Perhaps this should be a different sound? */
1062 sound_play_all(PLAYER_WIN_SOUND);
1063 } else {
1064 sprintf(msg, " < %s has won the game! >", Players[winner]->name);
1065 Set_message(msg);
1066 sound_play_all(PLAYER_WIN_SOUND);
1067 }
1068
1069 /* Give bonus to the best player */
1070 Give_best_player_bonus(average_score,
1071 num_best_players,
1072 best_ratio,
1073 best_players);
1074
1075 /* Give bonus to the winning player */
1076 if (winner >= 0) {
1077 for (i = 0; i < num_best_players; i++) {
1078 if (winner == best_players[i]) {
1079 break;
1080 }
1081 }
1082 if (i == num_best_players) {
1083 Give_individual_bonus(winner, average_score);
1084 }
1085 }
1086 else if (winner == -2) {
1087 for (j = 0; j < NumPlayers; j++) {
1088 if (IS_ROBOT_IND(j)) {
1089 for (i = 0; i < num_best_players; i++) {
1090 if (j == best_players[i]) {
1091 break;
1092 }
1093 }
1094 if (i == num_best_players) {
1095 Give_individual_bonus(j, average_score);
1096 }
1097 }
1098 }
1099 }
1100
1101 Reset_all_players();
1102
1103 free(best_players);
1104 }
1105
Race_game_over(void)1106 void Race_game_over(void)
1107 {
1108 player *pl;
1109 int i,
1110 j,
1111 k,
1112 bestlap = 0,
1113 num_best_players = 0,
1114 num_active_players = 0,
1115 num_ordered_players = 0;
1116 int *order;
1117 char msg[MSG_LEN];
1118
1119 /*
1120 * Reassign players's starting posisitions based upon
1121 * personal best lap times.
1122 */
1123 if ((order = (int *)malloc(NumPlayers * sizeof(int))) != NULL) {
1124 for (i = 0; i < NumPlayers; i++) {
1125 pl = Players[i];
1126 if (IS_TANK_PTR(pl)) {
1127 continue;
1128 }
1129 if (BIT(pl->status, PAUSE)
1130 || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W')
1131 || pl->best_lap <= 0) {
1132 j = i;
1133 }
1134 else {
1135 for (j = 0; j < i; j++) {
1136 if (pl->best_lap < Players[order[j]]->best_lap) {
1137 break;
1138 }
1139 if (BIT(Players[order[j]]->status, PAUSE)
1140 || (BIT(Players[order[j]]->status, GAME_OVER)
1141 && Players[order[j]]->mychar == 'W')) {
1142 break;
1143 }
1144 }
1145 }
1146 for (k = i - 1; k >= j; k--) {
1147 order[k + 1] = order[k];
1148 }
1149 order[j] = i;
1150 num_ordered_players++;
1151 }
1152 for (i = 0; i < num_ordered_players; i++) {
1153 pl = Players[order[i]];
1154 if (pl->home_base != World.baseorder[i].base_idx) {
1155 pl->home_base = World.baseorder[i].base_idx;
1156 for (j = 0; j < NumPlayers; j++) {
1157 if (Players[j]->conn != NOT_CONNECTED) {
1158 Send_base(Players[j]->conn,
1159 pl->id,
1160 pl->home_base);
1161 }
1162 }
1163 if (BIT(pl->status, PAUSE)) {
1164 Go_home(order[i]);
1165 }
1166 }
1167 }
1168 free(order);
1169 }
1170
1171 for (i = NumPlayers - 1; i >= 0; i--) {
1172 pl = Players[i];
1173 CLR_BIT(pl->status, RACE_OVER | FINISH);
1174 if (BIT(pl->status, PAUSE)
1175 || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W')
1176 || IS_TANK_PTR(pl)) {
1177 continue;
1178 }
1179 num_active_players++;
1180
1181 /* Kill any remaining players */
1182 if (!BIT(pl->status, GAME_OVER))
1183 Kill_player(i);
1184 else
1185 Player_death_reset(i);
1186 if (pl != Players[i]) {
1187 continue;
1188 }
1189 if ((pl->best_lap < bestlap || bestlap == 0) &&
1190 pl->best_lap > 0) {
1191 bestlap = pl->best_lap;
1192 num_best_players = 0;
1193 }
1194 if (pl->best_lap == bestlap)
1195 num_best_players++;
1196 }
1197
1198 /* If someone completed a lap */
1199 if (bestlap > 0) {
1200 for (i = 0; i < NumPlayers; i++) {
1201 pl = Players[i];
1202 if (BIT(pl->status, PAUSE)
1203 || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W')
1204 || IS_TANK_PTR(pl)) {
1205 continue;
1206 }
1207 if (pl->best_lap == bestlap) {
1208 sprintf(msg,
1209 "%s %s the best lap time of %.2fs",
1210 pl->name,
1211 (num_best_players == 1) ? "had" : "shares",
1212 (DFLOAT) bestlap / FPS);
1213 Set_message(msg);
1214 SCORE(i, 5 + num_active_players,
1215 OBJ_X_IN_BLOCKS(pl),
1216 OBJ_Y_IN_BLOCKS(pl),
1217 (num_best_players == 1) ? "[Fastest lap]" : "[Joint fastest lap]");
1218 }
1219 }
1220
1221 updateScores = true;
1222 }
1223 else if (num_active_players > NumRobots) {
1224 Set_message("No-one even managed to complete one lap, you should be "
1225 "ashamed of yourselves.");
1226 }
1227
1228 Reset_all_players();
1229
1230 Count_rounds();
1231 }
1232
1233
Compute_game_status(void)1234 void Compute_game_status(void)
1235 {
1236 int i;
1237 player *pl;
1238 char msg[MSG_LEN];
1239
1240 if (round_delay_send > 0) {
1241 round_delay_send--;
1242 }
1243 if (round_delay > 0) {
1244 if (!--round_delay) {
1245 sprintf(msg, "%s starts now.",
1246 (BIT(World.rules->mode, TIMING) ? "Race" : "Round"));
1247 Set_message(msg);
1248 roundtime = maxRoundTime * FPS;
1249 /* make sure players get the full 60 seconds of allowed idle time */
1250 for (i = 0; i < NumPlayers; i++) {
1251 Players[i]->frame_last_busy = frame_loops;
1252 }
1253 }
1254 }
1255
1256 if (roundtime > 0) {
1257 roundtime--;
1258 }
1259
1260 if (BIT(World.rules->mode, TIMING)) {
1261 /*
1262 * We need a completely separate scoring system for race mode.
1263 * I'm not sure how race mode should interact with team mode,
1264 * so for the moment race mode takes priority.
1265 *
1266 * Race mode and limited lives mode interact. With limited lives on,
1267 * race ends after all players have completed the course, or have died.
1268 * With limited lives mode off, the race ends when the first player
1269 * completes the course - all remaining players are then killed to
1270 * reset them.
1271 *
1272 * In limited lives mode, where the race can be run to completion,
1273 * points are awarded not just to the winner but to everyone who
1274 * completes the course (with more going to the winner). These
1275 * points are awarded as the player crosses the line. At the end
1276 * of the race, a bonus is awarded to the player with the fastest lap.
1277 *
1278 * In unlimited lives mode, just the winner and the holder of the
1279 * fastest lap get points.
1280 */
1281
1282 player *alive = NULL;
1283 int num_alive_players = 0,
1284 num_active_players = 0,
1285 num_finished_players = 0,
1286 num_race_over_players = 0,
1287 num_waiting_players = 0,
1288 position = 1,
1289 total_pts;
1290 DFLOAT pts;
1291
1292 /* First count the players */
1293 for (i = 0; i < NumPlayers; i++) {
1294 pl = Players[i];
1295 if (BIT(pl->status, PAUSE)
1296 || IS_TANK_PTR(pl)) {
1297 continue;
1298 }
1299 if (!BIT(pl->status, GAME_OVER)) {
1300 num_alive_players++;
1301 }
1302 else if (pl->mychar == 'W') {
1303 num_waiting_players++;
1304 continue;
1305 }
1306
1307 if (BIT(pl->status, RACE_OVER)) {
1308 num_race_over_players++;
1309 position++;
1310 }
1311 else if (BIT(pl->status, FINISH)) {
1312 num_finished_players++;
1313 }
1314 else if (!BIT(pl->status, GAME_OVER)) {
1315 alive = pl;
1316 }
1317
1318 /*
1319 * An active player is one who is:
1320 * still in the race.
1321 * reached the finish line just now.
1322 * has finished the race in a previous frame.
1323 * died too often.
1324 */
1325 num_active_players++;
1326 }
1327 if (num_active_players == 0 && num_waiting_players == 0) {
1328 return;
1329 }
1330
1331 /* Now if any players are unaccounted for */
1332 if (num_finished_players > 0) {
1333 /*
1334 * Ok, update positions. Everyone who finished the race in the last
1335 * frame gets the current position.
1336 */
1337
1338 /* Only play the sound for the first person to cross the finish */
1339 if (position == 1)
1340 sound_play_all(PLAYER_WIN_SOUND);
1341
1342 total_pts = 0;
1343 for (i = 0; i < num_finished_players; i++) {
1344 total_pts += (10 + 2 * num_active_players) >> (position - 1 + i);
1345 }
1346 pts = total_pts / num_finished_players;
1347
1348 for (i = 0; i < NumPlayers; i++) {
1349 pl = Players[i];
1350 if (BIT(pl->status, PAUSE)
1351 || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W')
1352 || IS_TANK_PTR(pl)) {
1353 continue;
1354 }
1355 if (BIT(pl->status, FINISH)) {
1356 CLR_BIT(pl->status, FINISH);
1357 SET_BIT(pl->status, RACE_OVER);
1358 if (pts > 0) {
1359 sprintf(msg,
1360 "%s finishes %sin position %d "
1361 "scoring %.2f point%s.",
1362 pl->name,
1363 (num_finished_players == 1) ? "" : "jointly ",
1364 position, pts,
1365 (pts == 1) ? "" : "s");
1366 Set_message(msg);
1367 sprintf(msg, "[Position %d%s]", position,
1368 (num_finished_players == 1) ? "" : " (jointly)");
1369 SCORE(i, pts,
1370 OBJ_X_IN_BLOCKS(pl),
1371 OBJ_Y_IN_BLOCKS(pl),
1372 msg);
1373 }
1374 else {
1375 sprintf(msg,
1376 "%s finishes %sin position %d.",
1377 pl->name,
1378 (num_finished_players == 1) ? "" : "jointly ",
1379 position);
1380 Set_message(msg);
1381 }
1382 }
1383 }
1384 }
1385
1386 /*
1387 * If the maximum allowed time for this race is over, end it.
1388 */
1389 if (maxRoundTime > 0 && roundtime == 0) {
1390 Set_message("Timer expired. Race ends now.");
1391 Race_game_over();
1392 return;
1393 }
1394
1395 /*
1396 * In limited lives mode, wait for everyone to die, except
1397 * for the last player.
1398 */
1399 if (BIT(World.rules->mode, LIMITED_LIVES)) {
1400 if (num_alive_players > 1) {
1401 return;
1402 }
1403 if (num_alive_players == 1) {
1404 if (num_finished_players + num_race_over_players == 0) {
1405 return;
1406 }
1407 if (!alive || alive->round == 0) {
1408 return;
1409 }
1410 }
1411 }
1412 else if (num_finished_players == 0) {
1413 return;
1414 }
1415
1416 Race_game_over();
1417
1418 } else if (BIT(World.rules->mode, TEAM_PLAY)) {
1419
1420 /* Do we have a winning team ? */
1421
1422 enum TeamState {
1423 TeamEmpty,
1424 TeamDead,
1425 TeamAlive
1426 } team_state[MAX_TEAMS];
1427 int num_dead_teams = 0;
1428 int num_alive_teams = 0;
1429 int winning_team = -1;
1430
1431 for (i = 0; i < MAX_TEAMS; i++) {
1432 team_state[i] = TeamEmpty;
1433 }
1434
1435 for (i = 0; i < NumPlayers; i++) {
1436 if (IS_TANK_IND(i)) {
1437 /* Ignore tanks. */
1438 continue;
1439 }
1440 else if (BIT(Players[i]->status, PAUSE)) {
1441 /* Ignore paused players. */
1442 continue;
1443 }
1444 #if 0
1445 /* not all teammode maps have treasures. */
1446 else if (World.teams[Players[i]->team].NumTreasures == 0) {
1447 /* Ignore players with no treasure troves */
1448 continue;
1449 }
1450 #endif
1451 else if (BIT(Players[i]->status, GAME_OVER)) {
1452 if (team_state[Players[i]->team] == TeamEmpty) {
1453 /* Assume all teammembers are dead. */
1454 num_dead_teams++;
1455 team_state[Players[i]->team] = TeamDead;
1456 }
1457 }
1458 /*
1459 * If the player is not paused and he is not in the
1460 * game over mode and his team owns treasures then he is
1461 * considered alive.
1462 * But he may not be playing though if the rest of the team
1463 * was genocided very quickly after game reset, while this
1464 * player was still being transported back to his homebase.
1465 */
1466 else if (team_state[Players[i]->team] != TeamAlive) {
1467 if (team_state[Players[i]->team] == TeamDead) {
1468 /* Oops! Not all teammembers are dead yet. */
1469 num_dead_teams--;
1470 }
1471 team_state[Players[i]->team] = TeamAlive;
1472 ++num_alive_teams;
1473 /* Remember a team which was alive. */
1474 winning_team = Players[i]->team;
1475 }
1476 }
1477
1478 if (num_alive_teams > 1) {
1479 char *bp;
1480 int teams_with_treasure = 0;
1481 int team_win[MAX_TEAMS];
1482 DFLOAT team_score[MAX_TEAMS];
1483 int winners;
1484 int max_destroyed = 0;
1485 int max_left = 0;
1486 DFLOAT max_score = 0;
1487 team_t *team_ptr;
1488
1489 /*
1490 * Game is not over if more than one team which have treasures
1491 * still have one remaining in play. Note that it is possible
1492 * for max_destroyed to be zero, in the case where a team
1493 * destroys some treasures and then all quit, and the remaining
1494 * teams did not destroy any.
1495 */
1496 for (i = 0; i < MAX_TEAMS; i++) {
1497 team_score[i] = 0;
1498 if (team_state[i] != TeamAlive) {
1499 team_win[i] = 0;
1500 continue;
1501 }
1502 team_win[i] = 1;
1503 team_ptr = &(World.teams[i]);
1504 if (team_ptr->TreasuresDestroyed > max_destroyed) {
1505 max_destroyed = team_ptr->TreasuresDestroyed;
1506 }
1507 if ((team_ptr->TreasuresLeft > 0) ||
1508 (team_ptr->NumTreasures == team_ptr->NumEmptyTreasures)) {
1509 teams_with_treasure++;
1510 }
1511 }
1512
1513 /*
1514 * Game is not over if more than one team has treasure.
1515 */
1516 if ((teams_with_treasure > 1 || !max_destroyed)
1517 && (roundtime != 0 || maxRoundTime <= 0)) {
1518 return;
1519 }
1520
1521 if (maxRoundTime > 0 && roundtime == 0) {
1522 Set_message("Timer expired. Round ends now.");
1523 }
1524
1525 /*
1526 * Find the winning team;
1527 * Team destroying most number of treasures;
1528 * If drawn; the one with most saved treasures,
1529 * If drawn; the team with the most points,
1530 * If drawn; an overall draw.
1531 */
1532 for (winners = i = 0; i < MAX_TEAMS; i++) {
1533 if (!team_win[i])
1534 continue;
1535 if (World.teams[i].TreasuresDestroyed == max_destroyed) {
1536 if (World.teams[i].TreasuresLeft > max_left)
1537 max_left = World.teams[i].TreasuresLeft;
1538 winning_team = i;
1539 winners++;
1540 } else {
1541 team_win[i] = 0;
1542 }
1543 }
1544 if (winners == 1) {
1545 sprintf(msg, " by destroying %d treasures", max_destroyed);
1546 Team_game_over(winning_team, msg);
1547 return;
1548 }
1549
1550 for (i = 0; i < NumPlayers; i++) {
1551 if (BIT(Players[i]->status, PAUSE)
1552 || IS_TANK_IND(i)) {
1553 continue;
1554 }
1555 team_score[Players[i]->team] += Players[i]->score;
1556 }
1557
1558 for (winners = i = 0; i < MAX_TEAMS; i++) {
1559 if (!team_win[i])
1560 continue;
1561 if (World.teams[i].TreasuresLeft == max_left) {
1562 if (team_score[i] > max_score)
1563 max_score = team_score[i];
1564 winning_team = i;
1565 winners++;
1566 } else {
1567 team_win[i] = 0;
1568 }
1569 }
1570 if (winners == 1) {
1571 sprintf(msg,
1572 " by destroying %d treasures"
1573 " and successfully defending %d",
1574 max_destroyed, max_left);
1575 Team_game_over(winning_team, msg);
1576 return;
1577 }
1578
1579 for (winners = i = 0; i < MAX_TEAMS; i++) {
1580 if (!team_win[i])
1581 continue;
1582 if (team_score[i] == max_score) {
1583 winning_team = i;
1584 winners++;
1585 } else {
1586 team_win[i] = 0;
1587 }
1588 }
1589 if (winners == 1) {
1590 sprintf(msg, " by destroying %d treasures, saving %d, and "
1591 "scoring %.2f points",
1592 max_destroyed, max_left, max_score);
1593 Team_game_over(winning_team, msg);
1594 return;
1595 }
1596
1597 /* Highly unlikely */
1598
1599 sprintf(msg, " between teams ");
1600 bp = msg + strlen(msg);
1601 for (i = 0; i < MAX_TEAMS; i++) {
1602 if (!team_win[i])
1603 continue;
1604 *bp++ = "0123456789"[i]; *bp++ = ','; *bp++ = ' ';
1605 }
1606 bp -= 2;
1607 *bp = '\0';
1608 Team_game_over(-1, msg);
1609
1610 }
1611 else if (num_dead_teams > 0) {
1612 if (num_alive_teams == 1)
1613 Team_game_over(winning_team, " by staying alive");
1614 else
1615 Team_game_over(-1, " as everyone died");
1616 }
1617 else {
1618 /*
1619 * num_alive_teams <= 1 && num_dead_teams == 0
1620 *
1621 * There is a possibility that the game has ended because players
1622 * quit, the game over state is needed to reset treasures. We
1623 * must count how many treasures are missing, if there are any
1624 * the playing team (if any) wins.
1625 */
1626 int i, treasures_destroyed;
1627
1628 for (treasures_destroyed = i = 0; i < MAX_TEAMS; i++)
1629 treasures_destroyed += (World.teams[i].NumTreasures
1630 - World.teams[i].NumEmptyTreasures
1631 - World.teams[i].TreasuresLeft);
1632 if (treasures_destroyed)
1633 Team_game_over(winning_team, " by staying in the game");
1634 }
1635
1636 } else {
1637
1638 /* Do we have a winner ? (No team play) */
1639 int num_alive_players = 0;
1640 int num_active_players = 0;
1641 int num_alive_robots = 0;
1642 int num_active_humans = 0;
1643 int winner = -1;
1644
1645 for (i=0; i<NumPlayers; i++) {
1646 if (BIT(Players[i]->status, PAUSE)
1647 || IS_TANK_IND(i)) {
1648 continue;
1649 }
1650 if (!BIT(Players[i]->status, GAME_OVER)) {
1651 num_alive_players++;
1652 if (IS_ROBOT_IND(i)) {
1653 num_alive_robots++;
1654 }
1655 winner = i; /* Tag player that's alive */
1656 }
1657 else if (IS_HUMAN_IND(i)) {
1658 num_active_humans++;
1659 }
1660 num_active_players++;
1661 }
1662
1663 if (num_alive_players == 1 && num_active_players > 1) {
1664 Individual_game_over(winner);
1665 }
1666 else if (num_alive_players == 0 && num_active_players >= 1) {
1667 Individual_game_over(-1);
1668 }
1669 else if (num_alive_robots > 1
1670 && num_alive_players == num_alive_robots
1671 && num_active_humans > 0) {
1672 Individual_game_over(-2);
1673 }
1674 else if (maxRoundTime > 0 && roundtime == 0) {
1675 Set_message("Timer expired. Round ends now.");
1676 Individual_game_over(-1);
1677 }
1678 }
1679 }
1680
Delete_player(int ind)1681 void Delete_player(int ind)
1682 {
1683 player *pl = Players[ind];
1684 object *obj;
1685 int i, j,
1686 id = pl->id;
1687
1688 /* call before important player structures are destroyed */
1689 Leave_alliance(ind);
1690
1691 if (IS_ROBOT_PTR(pl)) {
1692 Robot_destroy(ind);
1693 }
1694
1695 /* Delete remaining shots */
1696 for (i = NumObjs - 1; i >= 0; i--) {
1697 obj = Obj[i];
1698 if (obj->id == id) {
1699 if (obj->type == OBJ_BALL) {
1700 Delete_shot(i);
1701 BALL_PTR(obj)->owner = NO_ID;
1702 }
1703 else if (BIT(obj->type, OBJ_DEBRIS | OBJ_SPARK)) {
1704 /* Okay, so you want robot explosions to exist,
1705 * even if the robot left the game. */
1706 obj->id = NO_ID;
1707 }
1708 else {
1709 if (!keepShots) {
1710 obj->life = 0;
1711 if (BIT(obj->type,
1712 OBJ_CANNON_SHOT|OBJ_MINE|OBJ_SMART_SHOT
1713 |OBJ_HEAT_SHOT|OBJ_TORPEDO)) {
1714 obj->mass = 0;
1715 }
1716 }
1717 obj->id = NO_ID;
1718 if (BIT(obj->type, OBJ_MINE)) {
1719 MINE_PTR(obj)->owner = NO_ID;
1720 }
1721 }
1722 }
1723 else {
1724 if (BIT(obj->type, OBJ_MINE)) {
1725 mineobject *mine = MINE_PTR(obj);
1726 if (mine->owner == id) {
1727 mine->owner = NO_ID;
1728 if (!keepShots) {
1729 obj->life = 0;
1730 obj->mass = 0;
1731 }
1732 }
1733 }
1734 else if (BIT(obj->type, OBJ_CANNON_SHOT)) {
1735 if (!keepShots) {
1736 obj->life = 0;
1737 obj->mass = 0;
1738 }
1739 }
1740 else if (BIT(obj->type, OBJ_BALL)) {
1741 ballobject *ball = BALL_PTR(obj);
1742 if (ball->owner == id) {
1743 ball->owner = NO_ID;
1744 }
1745 }
1746 }
1747 }
1748
1749 if (pl->num_pulses) {
1750 for (i = 0; i < NumPulses; i++) {
1751 if (Pulses[i]->id == pl->id) {
1752 free(Pulses[i]);
1753 if (--NumPulses > i) {
1754 Pulses[i] = Pulses[NumPulses];
1755 i--;
1756 }
1757 }
1758 }
1759 pl->num_pulses = 0;
1760 }
1761 Free_ship_shape(pl->ship);
1762
1763 sound_close(pl);
1764
1765 NumPlayers--;
1766 if (IS_TANK_PTR(pl)) {
1767 NumPseudoPlayers--;
1768 }
1769
1770 if (pl->team != TEAM_NOT_SET && !IS_TANK_PTR(pl)) {
1771 World.teams[pl->team].NumMembers--;
1772 if (teamShareScore)
1773 TEAM_SCORE(pl->team, -(pl->score)); /* recalculate teamscores */
1774 if (IS_ROBOT_PTR(pl))
1775 World.teams[pl->team].NumRobots--;
1776 }
1777
1778 if (IS_ROBOT_PTR(pl)) {
1779 NumRobots--;
1780 }
1781
1782 /*
1783 * Swap entry no 'ind' with the last one.
1784 *
1785 * Change the Players[] pointer array to have Players[ind] point to
1786 * a valid player and move our leaving player to Players[NumPlayers].
1787 */
1788 pl = Players[NumPlayers]; /* Swap pointers... */
1789 Players[NumPlayers] = Players[ind];
1790 Players[ind] = pl;
1791 pl = Players[NumPlayers]; /* Restore pointer. */
1792
1793 GetInd[Players[ind]->id] = ind;
1794 GetInd[Players[NumPlayers]->id] = NumPlayers;
1795
1796 Check_team_members(pl->team);
1797
1798 for (i = NumPlayers - 1; i >= 0; i--) {
1799 if (IS_TANK_IND(i)
1800 && Players[i]->lock.pl_id == id) {
1801 /* remove tanks which were released by this player. */
1802 if (keepShots) {
1803 Players[i]->lock.pl_id = NO_ID;
1804 } else {
1805 Delete_player(i);
1806 }
1807 continue;
1808 }
1809 if (BIT(Players[i]->lock.tagged, LOCK_PLAYER|LOCK_VISIBLE)
1810 && (Players[i]->lock.pl_id == id || NumPlayers <= 1)) {
1811 CLR_BIT(Players[i]->lock.tagged, LOCK_PLAYER|LOCK_VISIBLE);
1812 CLR_BIT(Players[i]->used, HAS_TRACTOR_BEAM);
1813 }
1814 if (IS_ROBOT_IND(i)
1815 && Robot_war_on_player(i) == id) {
1816 Robot_reset_war(i);
1817 }
1818 for (j = 0; j < LOCKBANK_MAX; j++) {
1819 if (Players[i]->lockbank[j] == id)
1820 Players[i]->lockbank[j] = NOT_CONNECTED;
1821 }
1822 for (j = 0; j < MAX_RECORDED_SHOVES; j++) {
1823 if (Players[i]->shove_record[j].pusher_id == id) {
1824 Players[i]->shove_record[j].pusher_id = NO_ID;
1825 }
1826 }
1827 }
1828
1829 for (i = NumPlayers - 1; i >= 0; i--) {
1830 if (Players[i]->conn != NOT_CONNECTED) {
1831 Send_leave(Players[i]->conn, id);
1832 }
1833 else if (IS_TANK_IND(i)) {
1834 if (Players[i]->lock.pl_id == id) {
1835 Delete_player(i);
1836 }
1837 }
1838 }
1839
1840 release_ID(id);
1841 }
1842
Detach_ball(int ind,int obj)1843 void Detach_ball(int ind, int obj)
1844 {
1845 int i, cnt;
1846
1847 if (obj == -1 || BALL_PTR(Obj[obj]) == Players[ind]->ball) {
1848 Players[ind]->ball = NULL;
1849 CLR_BIT(Players[ind]->used, HAS_CONNECTOR);
1850 }
1851
1852 if (BIT(Players[ind]->have, HAS_BALL)) {
1853 for (cnt = i = 0; i < NumObjs; i++) {
1854 if (Obj[i]->type == OBJ_BALL && Obj[i]->id == Players[ind]->id) {
1855 if (obj == -1 || obj == i) {
1856 Obj[i]->id = NO_ID;
1857 /* Don't reset owner so you can throw balls */
1858 } else {
1859 cnt++;
1860 }
1861 }
1862 }
1863 if (cnt == 0)
1864 CLR_BIT(Players[ind]->have, HAS_BALL);
1865 else {
1866 sound_play_sensors(Players[ind]->pos.x, Players[ind]->pos.y,
1867 DROP_BALL_SOUND);
1868 }
1869 }
1870 }
1871
Kill_player(int ind)1872 void Kill_player(int ind)
1873 {
1874 Explode_fighter(ind);
1875 Player_death_reset(ind);
1876 }
1877
Player_death_reset(int ind)1878 void Player_death_reset(int ind)
1879 {
1880 player *pl = Players[ind];
1881 long minfuel;
1882 int i;
1883
1884
1885 if (IS_TANK_PTR(pl)) {
1886 Delete_player(ind);
1887 return;
1888 }
1889
1890 Detach_ball(ind, -1);
1891 if (BIT(pl->used, HAS_AUTOPILOT) || BIT(pl->status, HOVERPAUSE)) {
1892 CLR_BIT(pl->status, HOVERPAUSE);
1893 Autopilot (ind, 0);
1894 }
1895
1896 pl->vel.x = pl->vel.y = 0.0;
1897 pl->acc.x = pl->acc.y = 0.0;
1898 pl->emptymass = pl->mass = ShipMass;
1899 pl->status |= DEF_BITS;
1900 pl->status &= ~(KILL_BITS);
1901
1902 for (i = 0; i < NUM_ITEMS; i++) {
1903 if (!BIT(1U << i, ITEM_BIT_FUEL | ITEM_BIT_TANK)) {
1904 pl->item[i] = World.items[i].initial;
1905 }
1906 }
1907
1908 pl->forceVisible = 0;
1909 pl->shot_max = ShotsMax;
1910 pl->count = MAX(RECOVERY_DELAY, pl->count);
1911 pl->ecmcount = 0;
1912 pl->emergency_thrust_left = 0;
1913 pl->emergency_thrust_max = 0;
1914 pl->emergency_shield_left = 0;
1915 pl->emergency_shield_max = 0;
1916 pl->phasing_left = 0;
1917 pl->phasing_max = 0;
1918 pl->damaged = 0;
1919 pl->stunned = 0;
1920 pl->lock.distance = 0;
1921
1922 pl->fuel.sum = (long)(pl->fuel.sum*0.90); /* Loose 10% of fuel */
1923 minfuel = (World.items[ITEM_FUEL].initial * FUEL_SCALE_FACT);
1924 minfuel += (int)(rfrac() * (1 + minfuel) * 0.2f);
1925 pl->fuel.sum = MAX(pl->fuel.sum, minfuel);
1926 Player_init_fuel(ind, pl->fuel.sum);
1927
1928 /*-BA Handle the combination of limited life games and
1929 *-BA robotLeaveLife by making a robot leave iff it gets
1930 *-BA eliminated in any round. Means that robotLeaveLife
1931 *-BA is ignored, but that robotsLeave is still respected.
1932 *-KK Added check on race mode. Since in race mode everyone
1933 *-KK gets killed at the end of the round, all robots would
1934 *-KK be replaced in the next round. I don't think that's
1935 *-KK the Right Thing to do.
1936 *-KK Also, only check a robot's score at the end of the round.
1937 *-KK 27-2-98 Check on team mode too. It's very confusing to
1938 *-KK have different robots in your team every round.
1939 */
1940
1941 if (!BIT(pl->status, PAUSE)) {
1942
1943 pl->deaths++;
1944
1945 if (BIT(World.rules->mode, LIMITED_LIVES)) {
1946 pl->life--;
1947 if (pl->life == -1) {
1948 if (IS_ROBOT_PTR(pl)) {
1949 if (!BIT(World.rules->mode, TIMING|TEAM_PLAY)
1950 || (robotsLeave && pl->score < robotLeaveScore)) {
1951 Robot_delete(ind, false);
1952 return;
1953 }
1954 }
1955 pl->life = 0;
1956 SET_BIT(pl->status, GAME_OVER);
1957 pl->mychar = 'D';
1958 Player_lock_closest(ind, 0);
1959 }
1960 }
1961 else {
1962 pl->life++;
1963 }
1964 }
1965
1966 pl->have = DEF_HAVE;
1967 pl->used |= DEF_USED;
1968 pl->used &= ~(USED_KILL);
1969 pl->used &= pl->have;
1970 }
1971
1972 /* determines if two players are immune to eachother */
Team_immune(int id1,int id2)1973 int Team_immune(int id1, int id2)
1974 {
1975 int ind1, ind2;
1976
1977 if (id1 == id2) {
1978 /* owned stuff is never team immune */
1979 return 0;
1980 }
1981 if (!teamImmunity) {
1982 return 0;
1983 }
1984 if (id1 == NO_ID
1985 || id2 == NO_ID) {
1986 /* can't find owner for cannon stuff */
1987 return 0;
1988 }
1989
1990 ind1 = GetInd[id1];
1991 ind2 = GetInd[id2];
1992
1993 if (TEAM(ind1, ind2)) {
1994 /* players are teammates */
1995 return 1;
1996 }
1997
1998 if (ALLIANCE(ind1, ind2)) {
1999 /* players are allies */
2000 return 1;
2001 }
2002
2003 return 0;
2004 }
2005
2006