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