1 /*
2 * XPilot NG, a multiplayer space war game.
3 *
4 * Copyright (C) 1991-2001 by
5 *
6 * Bj�rn Stabell <bjoern@xpilot.org>
7 * Ken Ronny Schouten <ken@xpilot.org>
8 * Bert Gijsbers <bert@xpilot.org>
9 * Dick Balaska <dick@xpilot.org>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26 #include "xpserver.h"
27
28
29 bool updateScores = true;
30
31 int playerArrayNumber;
32 player_t **PlayersArray;
33 static int GetIndArray[NUM_IDS + MAX_SPECTATORS + 1];
34
35 /*
36 * Get index in Players array for player with id 'id'.
37 */
GetInd(int id)38 int GetInd(int id)
39 {
40 if (id == NO_ID)
41 return NO_IND;
42
43 /*
44 * kps - in some places where we look at the id we don't
45 * bother about spectators.
46 * This should be cleaned up in general.
47 */
48 if (id < 0 || id >= NELEM(GetIndArray)) {
49 /*warn("GetInd: id = %d, array size = %d\n",
50 id, NUM_IDS + MAX_SPECTATORS + 1);*/
51 return NO_IND;
52 }
53 return GetIndArray[id];
54 }
55
56 /********* **********
57 * Functions on player array.
58 */
59
Pick_startpos(player_t * pl)60 void Pick_startpos(player_t *pl)
61 {
62 int ind = GetInd(pl->id), i, num_free, pick = 0, seen = 0, order, min_order = INT_MAX;
63 static int prev_num_bases = 0;
64 static char *free_bases = NULL;
65
66 if (Player_is_tank(pl)) {
67 pl->home_base = Base_by_index(0);
68 return;
69 }
70
71 if (prev_num_bases != Num_bases()) {
72 prev_num_bases = Num_bases();
73 XFREE(free_bases);
74 free_bases = XMALLOC(char, Num_bases());
75 if (free_bases == NULL) {
76 error("Can't allocate memory for free_bases");
77 End_game();
78 }
79 }
80
81 for (i = 0; i < Num_bases(); i++) {
82 if (Base_by_index(i)->team == pl->team) {
83 free_bases[i] = 1;
84 } else {
85 free_bases[i] = 0; /* other team */
86 }
87 }
88
89 for (i = 0; i < NumPlayers; i++) {
90 player_t *pl_i = Player_by_index(i);
91
92 if (pl_i->id != pl->id
93 && !Player_is_tank(pl_i)
94 && pl_i->home_base
95 && free_bases[pl_i->home_base->ind]) {
96 free_bases[pl_i->home_base->ind] = 0; /* occupado */
97 }
98 }
99
100 /* find out the lowest order of all free bases */
101 for (i = 0; i < Num_bases(); i++) {
102 if (free_bases[i] != 0) {
103 order = Base_by_index(i)->order;
104 if (order < min_order) {
105 min_order = order;
106 }
107 }
108 }
109
110 /* mark all bases with higher order as occupied */
111 num_free = 0;
112 for (i = 0; i < Num_bases(); i++) {
113 if (free_bases[i] != 0) {
114 if (Base_by_index(i)->order <= min_order) {
115 num_free++;
116 } else {
117 free_bases[i] = 0;
118 }
119 }
120 }
121
122 /* pick a random base of all bases marked free */
123 pick = (int)(rfrac() * num_free);
124 seen = 0;
125 for (i = 0; i < Num_bases(); i++) {
126 if (free_bases[i] != 0) {
127 if (seen < pick)
128 seen++;
129 else
130 break;
131 }
132 }
133
134 if (i == Num_bases()) {
135 error("Can't pick startpos (ind=%d,num=%d,free=%d,pick=%d,seen=%d)",
136 ind, Num_bases(), num_free, pick, seen);
137 End_game();
138 } else {
139 pl->home_base = Base_by_index(i);
140 if (ind < NumPlayers) {
141 for (i = 0; i < spectatorStart + NumSpectators; i++) {
142 player_t *pl_i;
143
144 if (i == NumPlayers) {
145 i = spectatorStart - 1;
146 continue;
147 }
148 pl_i = Player_by_index(i);
149 if (pl_i->conn != NULL)
150 Send_base(pl_i->conn, pl->id, pl->home_base->ind);
151 }
152 if (Player_is_paused(pl)
153 || Player_is_waiting(pl)
154 || Player_is_dead(pl))
155 Go_home(pl);
156 }
157 }
158 }
159
Go_home(player_t * pl)160 void Go_home(player_t *pl)
161 {
162 int ind = GetInd(pl->id), i, dir, check;
163 double vx, vy, velo;
164 clpos_t pos, initpos;
165
166 if (Player_is_tank(pl)) {
167 /*NOTREACHED*/
168 /* Tanks have no homebase. */
169 warn("BUG: gohome tank");
170 return;
171 }
172
173 if (BIT(world->rules->mode, TIMING)
174 && pl->round
175 && !(Player_is_waiting(pl)
176 || Player_is_dead(pl))) {
177 if (pl->check)
178 check = pl->check - 1;
179 else
180 check = world->NumChecks - 1;
181 pos = Check_by_index(check)->pos;
182 vx = (rfrac() - 0.5) * 0.1;
183 vy = (rfrac() - 0.5) * 0.1;
184 velo = LENGTH(vx, vy);
185 dir = pl->last_check_dir;
186 dir = MOD2(dir + (int)((rfrac() - 0.5) * (RES / 8)), RES);
187 } else if (pl->home_base != NULL) {
188 pos = pl->home_base->pos;
189 dir = pl->home_base->dir;
190 vx = vy = velo = 0;
191 } else {
192 pos.cx = pos.cy = dir = 0;
193 vx = vy = velo = 0.0;
194 }
195
196 pl->dir = dir;
197 Player_set_float_dir(pl, (double)dir);
198 pl->wanted_float_dir = pl->float_dir;/*TURNQUEUE*/
199 initpos.cx = (click_t)(pos.cx + CLICK * vx);
200 initpos.cy = (click_t)(pos.cy + CLICK * vy);
201 Object_position_init_clpos(OBJ_PTR(pl), initpos);
202 pl->vel.x = vx;
203 pl->vel.y = vy;
204 pl->velocity = velo;
205 pl->acc.x = pl->acc.y = 0.0;
206 pl->turnacc = pl->turnvel = 0.0;
207 memset(pl->last_keyv, 0, sizeof(pl->last_keyv));
208 memset(pl->prev_keyv, 0, sizeof(pl->prev_keyv));
209 Emergency_shield(pl, false);
210 Player_used_kill(pl);
211 /*Player_init_items(pl);*/
212
213 if (options.playerStartsShielded) {
214 SET_BIT(pl->used, HAS_SHIELD);
215 if (!options.allowShields) {
216 pl->shield_time = SHIELD_TIME;
217 SET_BIT(pl->have, HAS_SHIELD);
218 }
219 if (Player_has_deflector(pl))
220 Deflector(pl, true);
221 }
222 Thrust(pl, false);
223 pl->updateVisibility = true;
224 for (i = 0; i < NumPlayers; i++) {
225 pl->visibility[i].lastChange = 0;
226 Player_by_index(i)->visibility[ind].lastChange = 0;
227 }
228
229 if (Player_is_robot(pl))
230 Robot_go_home(pl);
231 }
232
Base_set_option(base_t * base,const char * name,const char * value)233 void Base_set_option(base_t *base, const char *name, const char *value)
234 {
235 Item_t item;
236
237 item = Item_by_option_name(name);
238 if (item != NO_ITEM) {
239 base->initial_items[item] = atoi(value);
240 return;
241 }
242
243 warn("This server doesn't support option %s for bases.", name);
244 }
245
246 /*
247 * Compute the current sensor range for player 'pl'. This is based on the
248 * amount of fuel, the number of sensor items (each one adds 25%), and the
249 * minimum and maximum visibility limits in effect.
250 */
Compute_sensor_range(player_t * pl)251 void Compute_sensor_range(player_t *pl)
252 {
253 static int init = 0;
254 static double EnergyRangeFactor;
255
256 if (!init) {
257 if (options.minVisibilityDistance <= 0.0)
258 options.minVisibilityDistance = VISIBILITY_DISTANCE;
259 else
260 options.minVisibilityDistance *= BLOCK_SZ;
261 if (options.maxVisibilityDistance <= 0.0)
262 options.maxVisibilityDistance = world->hypotenuse;
263 else
264 options.maxVisibilityDistance *= BLOCK_SZ;
265
266 if (world->items[ITEM_FUEL].initial > 0.0) {
267 EnergyRangeFactor = options.minVisibilityDistance /
268 (world->items[ITEM_FUEL].initial
269 * (1.0 + ((double)world->items[ITEM_SENSOR].initial * 0.25)));
270 } else
271 EnergyRangeFactor = ENERGY_RANGE_FACTOR;
272 init = 1;
273 }
274
275 pl->sensor_range = pl->fuel.sum * EnergyRangeFactor;
276 pl->sensor_range *= (1.0 + ((double)pl->item[ITEM_SENSOR] * 0.25));
277 LIMIT(pl->sensor_range,
278 options.minVisibilityDistance, options.maxVisibilityDistance);
279 }
280
281 /*
282 * Give ship one more tank, if possible.
283 */
Player_add_tank(player_t * pl,double tank_fuel)284 void Player_add_tank(player_t *pl, double tank_fuel)
285 {
286 double tank_cap, add_fuel;
287
288 if (pl->fuel.num_tanks < MAX_TANKS) {
289 pl->fuel.num_tanks++;
290 tank_cap = TANK_CAP(pl->fuel.num_tanks);
291 add_fuel = tank_fuel;
292 LIMIT(add_fuel, 0.0, tank_cap);
293 pl->fuel.sum += add_fuel;
294 pl->fuel.max += tank_cap;
295 pl->fuel.tank[pl->fuel.num_tanks] = add_fuel;
296 pl->emptymass += TANK_MASS;
297 pl->item[ITEM_TANK] = pl->fuel.num_tanks;
298 }
299 }
300
301 /*
302 * Remove a tank from a ship, if possible.
303 */
Player_remove_tank(player_t * pl,int which_tank)304 void Player_remove_tank(player_t *pl, int which_tank)
305 {
306 int i, tank_ind;
307 double tank_fuel, tank_cap;
308
309 if (pl->fuel.num_tanks > 0) {
310 tank_ind = which_tank;
311 LIMIT(tank_ind, 1, pl->fuel.num_tanks);
312 pl->emptymass -= TANK_MASS;
313 tank_fuel = pl->fuel.tank[tank_ind];
314 tank_cap = TANK_CAP(tank_ind);
315 pl->fuel.max -= tank_cap;
316 pl->fuel.sum -= tank_fuel;
317 pl->fuel.num_tanks--;
318 if (pl->fuel.current > pl->fuel.num_tanks)
319 pl->fuel.current = 0;
320 else {
321 for (i = tank_ind; i <= pl->fuel.num_tanks; i++)
322 pl->fuel.tank[i] = pl->fuel.tank[i + 1];
323 }
324 pl->item[ITEM_TANK] = pl->fuel.num_tanks;
325 }
326 }
327
Player_hit_armor(player_t * pl)328 void Player_hit_armor(player_t *pl)
329 {
330 if (--pl->item[ITEM_ARMOR] <= 0)
331 CLR_BIT(pl->have, HAS_ARMOR);
332 }
333
334 /*
335 * Clear used bits.
336 */
Player_used_kill(player_t * pl)337 void Player_used_kill(player_t *pl)
338 {
339 pl->used &= ~USED_KILL;
340 if (!BIT(DEF_HAVE, HAS_SHIELD))
341 CLR_BIT(pl->have, HAS_SHIELD);
342 pl->used |= DEF_USED;
343 }
344
345 /*
346 * Calculate the mass of a player.
347 */
Player_set_mass(player_t * pl)348 void Player_set_mass(player_t *pl)
349 {
350 double sum_item_mass = 0.0, item_mass;
351 int item;
352
353 for (item = 0; item < NUM_ITEMS; item++) {
354 switch (item) {
355 case ITEM_FUEL:
356 case ITEM_TANK:
357 item_mass = 0.0;
358 break;
359 case ITEM_ARMOR:
360 item_mass = pl->item[ITEM_ARMOR] * ARMOR_MASS;
361 break;
362 default:
363 item_mass = pl->item[item] * options.minItemMass;
364 break;
365 }
366 sum_item_mass += item_mass;
367 }
368
369 pl->mass = pl->emptymass
370 + FUEL_MASS(pl->fuel.sum)
371 + sum_item_mass;
372 }
373
374 /*
375 * Give player the initial number of tanks and amount of fuel.
376 * Upto the maximum allowed.
377 */
Player_init_fuel(player_t * pl,double total_fuel)378 static void Player_init_fuel(player_t *pl, double total_fuel)
379 {
380 double fuel = total_fuel;
381 int i;
382
383 pl->fuel.num_tanks = 0;
384 pl->fuel.current = 0;
385 pl->fuel.max = TANK_CAP(0);
386 pl->fuel.sum = MIN(fuel, pl->fuel.max);
387 pl->fuel.tank[0] = pl->fuel.sum;
388 pl->emptymass = options.shipMass;
389 pl->item[ITEM_TANK] = pl->fuel.num_tanks;
390
391 fuel -= pl->fuel.sum;
392
393 for (i = 1; i <= world->items[ITEM_TANK].initial; i++) {
394 Player_add_tank(pl, fuel);
395 fuel -= pl->fuel.tank[i];
396 }
397 }
398
399 #if 0
400 /*
401 * Set initial items for a player.
402 * Number of initial items can depend on which base the player starts from.
403 */
404 void Player_init_items(player_t *pl)
405 {
406 int i, num_tanks;
407 double total_fuel;
408 base_t *base = pl->home_base;
409
410 for (i = 0; i < NUM_ITEMS; i++) {
411 if (i == ITEM_FUEL || i == ITEM_TANK))
412 continue;
413
414 if (base && base->initial_items[i] >= 0)
415 pl->item[i] = base->initial_items[i];
416 else
417 pl->item[i] = world->items[i].initial;
418 }
419
420 if (base && base->initial_items[ITEM_TANK] >= 0)
421 num_tanks = base->initial_items[ITEM_TANK];
422 else
423 num_tanks = world->items[ITEM_TANK].initial;
424
425 if (base && base->initial_items[ITEM_FUEL] >= 0)
426 total_fuel = (double)base->initial_items[ITEM_FUEL];
427 else
428 total_fuel = (double)world->items[ITEM_FUEL].initial;
429
430 Player_init_fuel(pl, num_tanks, total_fuel);
431 }
432 #endif
433
Player_init_items(player_t * pl)434 void Player_init_items(player_t *pl)
435 {
436 int i;
437
438 /*
439 * Give player an initial set of items.
440 */
441 for (i = 0; i < NUM_ITEMS; i++) {
442 if (i == ITEM_FUEL || i == ITEM_TANK)
443 pl->item[i] = 0;
444 else
445 pl->item[i] = world->items[i].initial;
446 }
447
448 Player_init_fuel(pl, (double)world->items[ITEM_FUEL].initial);
449
450 /*
451 * Remember the amount of initial items. This way we can
452 * later figure out what items the player has picked up.
453 */
454 for (i = 0; i < NUM_ITEMS; i++)
455 pl->initial_item[i] = pl->item[i];
456 }
457
Init_player(int ind,shipshape_t * ship,int type)458 int Init_player(int ind, shipshape_t *ship, int type)
459 {
460 player_t *pl = Player_by_index(ind);
461 visibility_t *v = pl->visibility;
462 int i;
463
464 memset(pl, 0, sizeof(player_t));
465 pl->visibility = v;
466
467 /*
468 * Make sure floats, doubles and pointers are correctly zeroed.
469 */
470 assert(pl->wall_time == 0);
471 assert(pl->turnspeed == 0);
472 assert(pl->conn == NULL);
473
474 pl->dir = DIR_UP;
475 Player_set_float_dir(pl, (double)pl->dir);
476
477 pl->mass = options.shipMass;
478 pl->emptymass = options.shipMass;
479
480 Player_init_items(pl);
481
482 if (options.allowShipShapes && ship)
483 pl->ship = ship;
484 else {
485 shipshape_t *tryship = Parse_shape_str(options.defaultShipShape);
486
487 if (tryship)
488 pl->ship = tryship;
489 else
490 pl->ship = Default_ship();
491 }
492
493 pl->power = pl->power_s = MAX_PLAYER_POWER;
494 pl->turnspeed = pl->turnspeed_s = MIN_PLAYER_TURNSPEED;
495 pl->type = OBJ_PLAYER;
496 pl->pl_type = type;
497 if (type == PL_TYPE_HUMAN)
498 pl->pl_type_mychar = ' ';
499 else if (type == PL_TYPE_ROBOT)
500 pl->pl_type_mychar = 'R';
501 else if (type == PL_TYPE_TANK)
502 pl->pl_type_mychar = 'T';
503
504 /*Player_init_items(pl);*/
505 Compute_sensor_range(pl);
506
507 pl->obj_status = GRAVITY;
508 assert(pl->pl_status == 0);
509 assert(pl->pl_state == PL_STATE_UNDEFINED);
510 Player_set_state(pl, PL_STATE_ALIVE);
511 pl->have = DEF_HAVE;
512 pl->used = DEF_USED;
513
514 if (pl->item[ITEM_CLOAK] > 0)
515 SET_BIT(pl->have, HAS_CLOAKING_DEVICE);
516
517 Mods_clear(&pl->mods);
518 for (i = 0; i < NUM_MODBANKS; i++)
519 Mods_clear(&pl->modbank[i]);
520
521 for (i = 0; i < LOCKBANK_MAX; i++)
522 pl->lockbank[i] = NO_ID;
523
524 {
525 static unsigned short pseudo_team_no = 0;
526
527 pl->pseudo_team = pseudo_team_no++;
528 }
529 pl->survival_time=0;
530 Player_set_life(pl, world->rules->lives);
531
532 pl->player_fps = 50; /* Client should send a value after startup */
533 pl->maxturnsps = MAX_SERVER_FPS;
534
535 pl->kills = 0;
536 pl->deaths = 0;
537
538 /*
539 * If limited lives you will have to wait 'til everyone gets GAME OVER.
540 *
541 * Indeed you have to! (Mara)
542 *
543 * At least don't make the player wait for a new round if he's the
544 * only one on the server. Mara's change (always too_late) meant
545 * there was a round reset when the first player joined. -uau
546 *
547 * In individual games, make the new players appear after a small delay.
548 */
549 if (NumPlayers > 0
550 && !Player_is_tank(pl)) {
551 if (BIT(world->rules->mode, LIMITED_LIVES))
552 Player_set_state(pl, PL_STATE_WAITING);
553 else
554 Player_set_state(pl, PL_STATE_APPEARING);
555 }
556
557 pl->team = TEAM_NOT_SET;
558
559 pl->alliance = ALLIANCE_NOT_SET;
560 pl->invite = NO_ID;
561
562 pl->lock.tagged = LOCK_NONE;
563 pl->lock.pl_id = 0; /* kps - ??? */
564
565 pl->id = peek_ID();
566 GetIndArray[pl->id] = ind;
567 if (!Is_player_id(pl->id))
568 warn("Init_player: Not a player id: %d", pl->id);
569
570 for (i = 0; i < MAX_RECORDED_SHOVES; i++)
571 pl->shove_record[i].pusher_id = NO_ID;
572
573 pl->update_score = true;
574
575 return pl->id;
576 }
577
578
579 static player_t *playerArray;
580 static visibility_t *visibilityArray;
581
Alloc_players(int number)582 void Alloc_players(int number)
583 {
584 player_t *p;
585 visibility_t *t;
586 size_t n = number;
587 int i;
588
589 /* Allocate space for pointers */
590 PlayersArray = XCALLOC(player_t *, n);
591
592 /* Allocate space for all entries, all player structs */
593 p = playerArray = XCALLOC(player_t, n);
594
595 /* Allocate space for all visibility arrays, n arrays of n entries */
596 t = visibilityArray = XCALLOC(visibility_t, n * n);
597
598 if (!PlayersArray || !playerArray || !visibilityArray) {
599 error("Not enough memory for Players.");
600 exit(1);
601 }
602
603 for (i = 0; i < number; i++) {
604 PlayersArray[i] = p++;
605 PlayersArray[i]->visibility = t;
606 /* Advance to next block/array */
607 t += number;
608 }
609
610 playerArrayNumber = number;
611
612 /* Initialize player id to index lookup table */
613 for (i = 0; i < NELEM(GetIndArray); i++)
614 GetIndArray[i] = NO_IND;
615 }
616
617
618
Free_players(void)619 void Free_players(void)
620 {
621 XFREE(PlayersArray);
622 XFREE(playerArray);
623 XFREE(visibilityArray);
624 }
625
626
627
Update_score_table(void)628 void Update_score_table(void)
629 {
630 int i, j, check;
631 player_t *pl;
632
633 for (j = 0; j < NumPlayers; j++) {
634 pl = Player_by_index(j);
635 if (pl->update_score) {
636 pl->update_score = false;
637 for (i = 0; i < NumPlayers; i++) {
638 player_t *pl_i = Player_by_index(i);
639
640 if (pl_i->conn != NULL)
641 Send_score(pl_i->conn, pl->id, Get_Score(pl), pl->pl_life,
642 pl->mychar, pl->alliance);
643 }
644 for (i = 0; i < NumSpectators; i++)
645 Send_score(Player_by_index(i + spectatorStart)->conn, pl->id,
646 Get_Score(pl), pl->pl_life, pl->mychar, pl->alliance);
647 }
648 if (BIT(world->rules->mode, TIMING)) {
649 if (pl->check != pl->prev_check
650 || pl->round != pl->prev_round) {
651 pl->prev_check = pl->check;
652 pl->prev_round = pl->round;
653 check = (pl->round == 0)
654 ? 0
655 : (pl->check == 0)
656 ? (world->NumChecks - 1)
657 : (pl->check - 1);
658 for (i = 0; i < NumPlayers; i++) {
659 player_t *pl_i = Player_by_index(i);
660
661 if (pl_i->conn != NULL)
662 Send_timing(pl_i->conn, pl->id, check, pl->round);
663 }
664 }
665 }
666 }
667 updateScores = false;
668 }
669
670
Reset_all_players(void)671 void Reset_all_players(void)
672 {
673 player_t *pl;
674 int i, j;
675
676 updateScores = true;
677
678 for (i = 0; i < NumPlayers; i++) {
679 pl = Player_by_index(i);
680
681 if (options.endOfRoundReset) {
682 if (Player_is_paused(pl))
683 Player_death_reset(pl, false);
684 else {
685 Kill_player(pl, false);
686 if (pl != Player_by_index(i)) {
687 i--;
688 continue;
689 }
690 }
691 }
692
693 pl->kills = 0;
694 pl->deaths = 0;
695
696 if (!Player_is_paused(pl)
697 && !Player_is_waiting(pl))
698 Rank_add_round(pl);
699
700 CLR_BIT(pl->have, HAS_BALL);
701 Player_reset_timing(pl);
702
703 if (!Player_is_paused(pl)) {
704 Player_set_state(pl, PL_STATE_APPEARING);
705 Player_set_life(pl, world->rules->lives);
706 }
707 }
708
709 if (BIT(world->rules->mode, TEAM_PLAY)) {
710 /* Detach any balls and kill ball */
711 /* We are starting all over again */
712 for (j = NumObjs - 1; j >= 0 ; j--) {
713 if (Obj[j]->type == OBJ_BALL) {
714 ballobject_t *ball = BALL_IND(j);
715
716 ball->id = NO_ID;
717 ball->life = 0;
718 /*
719 * why not -1 ???
720 * naive question, obviously yet another dirty hack
721 */
722 ball->ball_owner = 0;
723 CLR_BIT(ball->obj_status, RECREATE);
724 Delete_shot(j);
725 }
726 }
727
728 /* Reset the treasures */
729 for (i = 0; i < Num_treasures(); i++) {
730 treasure_t *treasure = Treasure_by_index(i);
731
732 treasure->destroyed = 0;
733 treasure->have = false;
734 Make_treasure_ball(treasure);
735 }
736
737 /* Reset the teams */
738 for (i = 0; i < MAX_TEAMS; i++) {
739 team_t *teamp = Team_by_index(i);
740
741 teamp->TreasuresDestroyed = 0;
742 teamp->TreasuresLeft
743 = teamp->NumTreasures - teamp->NumEmptyTreasures;
744 }
745
746 if (options.endOfRoundReset) {
747 /* Reset the targets */
748 for (i = 0; i < Num_targets(); i++) {
749 target_t *targ = Target_by_index(i);
750
751 if (targ->damage != TARGET_DAMAGE || targ->dead_ticks > 0)
752 World_restore_target(targ);
753 }
754 }
755 }
756
757 if (options.endOfRoundReset) {
758 for (i = 0; i < NumObjs; i++) {
759 object_t *obj = Obj[i];
760
761 if (BIT(OBJ_TYPEBIT(obj->type),
762 OBJ_SHOT_BIT|OBJ_MINE_BIT|OBJ_DEBRIS_BIT|OBJ_SPARK_BIT
763 |OBJ_CANNON_SHOT_BIT|OBJ_TORPEDO_BIT|OBJ_SMART_SHOT_BIT
764 |OBJ_HEAT_SHOT_BIT|OBJ_PULSE_BIT|OBJ_ITEM_BIT)) {
765 obj->life = 0;
766 if (BIT(OBJ_TYPEBIT(obj->type),
767 OBJ_TORPEDO_BIT|OBJ_SMART_SHOT_BIT|OBJ_HEAT_SHOT_BIT
768 |OBJ_CANNON_SHOT_BIT|OBJ_MINE_BIT))
769 /* Take care that no new explosions are made. */
770 obj->mass = 0;
771 }
772 }
773 }
774
775 roundtime = options.maxRoundTime * FPS;
776
777 Update_score_table();
778 }
779
780
Check_team_members(int team)781 void Check_team_members(int team)
782 {
783 player_t *pl;
784 team_t *teamp;
785 int members, i;
786
787 if (!BIT(world->rules->mode, TEAM_PLAY))
788 return;
789
790 for (members = i = 0; i < NumPlayers; i++) {
791 pl = Player_by_index(i);
792 if (!Player_is_tank(pl)
793 && pl->team == team
794 && pl->home_base != NULL)
795 members++;
796 }
797 teamp = Team_by_index(team);
798 if (teamp->NumMembers != members) {
799 warn("Server has reset team %d members from %d to %d",
800 team, teamp->NumMembers, members);
801 for (i = 0; i < NumPlayers; i++) {
802 pl = Player_by_index(i);
803 if (!Player_is_tank(pl)
804 && pl->team == team
805 && pl->home_base != NULL)
806 warn("Team %d currently has player %d: \"%s\"",
807 team, i+1, pl->name);
808 }
809 teamp->NumMembers = members;
810 }
811 }
812
813
Compute_end_of_round_values(double * average_score,int * num_best_players,double * best_ratio,int best_players[])814 static void Compute_end_of_round_values(double *average_score,
815 int *num_best_players,
816 double *best_ratio,
817 int best_players[])
818 {
819 int i, n = 0;
820 double ratio;
821
822 /* Initialize everything */
823 *average_score = 0;
824 *num_best_players = 0;
825 *best_ratio = -1.0;
826
827 /* Figure out what the average score is and who has the best kill/death */
828 /* ratio for this round */
829 for (i = 0; i < NumPlayers; i++) {
830 player_t *pl = Player_by_index(i);
831
832 if (Player_is_tank(pl)
833 || (Player_is_paused(pl) && pl->pause_count <= 0)
834 || Player_is_waiting(pl))
835 continue;
836
837 n++;
838 *average_score += Get_Score(pl);
839 ratio = (double) pl->kills / (pl->deaths + 1);
840 if (ratio > *best_ratio) {
841 *best_ratio = ratio;
842 best_players[0] = i;
843 *num_best_players = 1;
844 } else if (ratio == *best_ratio)
845 best_players[(*num_best_players)++] = i;
846 }
847 if (n != 0) /* Can this be 0? */
848 *average_score /= n;
849 }
850
851
Give_best_player_bonus(double average_score,int num_best_players,double best_ratio,int best_players[])852 static void Give_best_player_bonus(double average_score,
853 int num_best_players,
854 double best_ratio,
855 int best_players[])
856 {
857 int i;
858 double points;
859 char msg[MSG_LEN];
860
861 if (num_best_players == 0 || best_ratio == 0)
862 sprintf(msg, "There is no Deadly Player.");
863 else if (num_best_players == 1) {
864 player_t *bp = Player_by_index(best_players[0]);
865
866 sprintf(msg,
867 "%s is the Deadliest Player with a kill ratio of %d/%d.",
868 bp->name,
869 bp->kills, bp->deaths);
870 points = best_ratio * Rate(Get_Score(bp), average_score);
871 if (!options.zeroSumScoring) Score(bp, points, bp->pos, "[Deadliest]");
872 Rank_add_deadliest(bp);
873 /*if (options.zeroSumScoring);*//* TODO */
874 } else {
875 msg[0] = '\0';
876 for (i = 0; i < num_best_players; i++) {
877 player_t *bp = Player_by_index(best_players[i]);
878 double ratio = Rate(Get_Score(bp), average_score);
879 double score = (ratio + num_best_players) / num_best_players;
880
881 if (msg[0]) {
882 if (i == num_best_players - 1)
883 strcat(msg, " and ");
884 else
885 strcat(msg, ", ");
886 }
887 if (strlen(msg) + 8 + strlen(bp->name) >= sizeof(msg)) {
888 Set_message(msg);
889 msg[0] = '\0';
890 }
891 strcat(msg, bp->name);
892 points = best_ratio * score;
893 if (!options.zeroSumScoring) Score(bp, points, bp->pos, "[Deadly]");
894 Rank_add_deadliest(bp);
895 /*if (options.zeroSumScoring);*//* TODO */
896 }
897 if (strlen(msg) + 64 >= sizeof(msg)) {
898 Set_message(msg);
899 msg[0] = '\0';
900 }
901 sprintf(msg + strlen(msg),
902 " are the Deadly Players with kill ratios of %d/%d.",
903 Player_by_index(best_players[0])->kills,
904 Player_by_index(best_players[0])->deaths);
905 }
906 Set_message(msg);
907 }
908
Give_individual_bonus(player_t * pl,double average_score)909 static void Give_individual_bonus(player_t *pl, double average_score)
910 {
911 double ratio, points;
912
913 ratio = (double) pl->kills / (pl->deaths + 1);
914 points = ratio * Rate( Get_Score(pl), average_score);
915 if (!options.zeroSumScoring) Score(pl, points, pl->pos, "[Winner]");
916 /*if (options.zeroSumScoring);*//* TODO */
917 }
918
Count_rounds(void)919 void Count_rounds(void)
920 {
921 if (!options.roundsToPlay)
922 return;
923
924 ++roundsPlayed;
925
926 Set_message_f(" < Round %d out of %d completed. >",
927 roundsPlayed, options.roundsToPlay);
928 /* only do the game over once */
929 if (roundsPlayed == options.roundsToPlay)
930 Game_Over();
931 }
932
933
Team_game_over(int winning_team,const char * reason)934 void Team_game_over(int winning_team, const char *reason)
935 {
936 int i, j, num_best_players, *best_players;
937 double average_score, best_ratio;
938
939 if (!(best_players = XMALLOC(int, NumPlayers))) {
940 warn("no mem");
941 End_game();
942 }
943
944 /* Figure out the average score and who has the best kill/death ratio */
945 /* ratio for this round */
946 Compute_end_of_round_values(&average_score,
947 &num_best_players,
948 &best_ratio,
949 best_players);
950
951 /* Print out the results of the round */
952 if (winning_team != -1) {
953 Set_message_f(" < Team %d has won the round%s! >",
954 winning_team, reason);
955 sound_play_all(TEAM_WIN_SOUND);
956 } else {
957 Set_message_f(" < We have a draw%s! >", reason);
958 sound_play_all(TEAM_DRAW_SOUND);
959 }
960
961 /* Give bonus to the best player */
962 Give_best_player_bonus(average_score,
963 num_best_players,
964 best_ratio,
965 best_players);
966
967 /* Give bonuses to the winning team */
968 if (winning_team != -1) {
969 for (i = 0; i < NumPlayers; i++) {
970 player_t *pl_i = Player_by_index(i);
971
972 if (pl_i->team != winning_team)
973 continue;
974
975 if (Player_is_tank(pl_i)
976 || (Player_is_paused(pl_i) && pl_i->pause_count <= 0)
977 || Player_is_waiting(pl_i))
978 continue;
979
980 for (j = 0; j < num_best_players; j++) {
981 if (i == best_players[j])
982 break;
983 }
984 if (j == num_best_players)
985 Give_individual_bonus(pl_i, average_score);
986 }
987 }
988
989 teamcup_round_end(winning_team);
990
991 Reset_all_players();
992
993 Count_rounds();
994
995 free(best_players);
996
997 teamcup_round_start();
998
999 /* Ranking */
1000 Rank_write_webpage();
1001 Rank_write_rankfile();
1002 Rank_show_ranks();
1003 }
1004
Individual_game_over(int winner)1005 void Individual_game_over(int winner)
1006 {
1007 int i, j, num_best_players, *best_players;
1008 double average_score, best_ratio;
1009
1010 if (!(best_players = XMALLOC(int, NumPlayers))) {
1011 warn("no mem");
1012 End_game();
1013 }
1014
1015 /* Figure out what the average score is and who has the best kill/death */
1016 /* ratio for this round */
1017 Compute_end_of_round_values(&average_score, &num_best_players,
1018 &best_ratio, best_players);
1019
1020 /* Print out the results of the round */
1021 if (winner == -1) {
1022 Set_message(" < We have a draw! >");
1023 sound_play_all(PLAYER_DRAW_SOUND);
1024 }
1025 else if (winner == -2) {
1026 Set_message(" < The robots have won the round! >");
1027 /* Perhaps this should be a different sound? */
1028 sound_play_all(PLAYER_WIN_SOUND);
1029 } else {
1030 Set_message_f(" < %s has won the round! >",
1031 Player_by_index(winner)->name);
1032 sound_play_all(PLAYER_WIN_SOUND);
1033 }
1034
1035 /* Give bonus to the best player */
1036 Give_best_player_bonus(average_score,
1037 num_best_players,
1038 best_ratio,
1039 best_players);
1040
1041 /* Give bonus to the winning player */
1042 if (winner >= 0) {
1043 for (i = 0; i < num_best_players; i++) {
1044 if (winner == best_players[i])
1045 break;
1046 }
1047 if (i == num_best_players)
1048 Give_individual_bonus(Player_by_index(winner), average_score);
1049 }
1050 else if (winner == -2) {
1051 for (j = 0; j < NumPlayers; j++) {
1052 player_t *pl_j = Player_by_index(j);
1053
1054 if (Player_is_robot(pl_j)) {
1055 for (i = 0; i < num_best_players; i++) {
1056 if (j == best_players[i])
1057 break;
1058 }
1059 if (i == num_best_players)
1060 Give_individual_bonus(pl_j, average_score);
1061 }
1062 }
1063 }
1064
1065 Reset_all_players();
1066
1067 Count_rounds();
1068
1069 free(best_players);
1070 }
1071
Compute_game_status(void)1072 void Compute_game_status(void)
1073 {
1074 int i;
1075 char msg[MSG_LEN];
1076
1077 if (roundtime > 0)
1078 roundtime--;
1079
1080 if (BIT(world->rules->mode, TIMING))
1081 Race_compute_game_status();
1082 else if (BIT(world->rules->mode, TEAM_PLAY)) {
1083
1084 /* Do we have a winning team ? */
1085
1086 enum TeamState {
1087 TeamEmpty,
1088 TeamDead,
1089 TeamAlive
1090 } team_state[MAX_TEAMS];
1091 int num_dead_teams = 0;
1092 int num_alive_teams = 0;
1093 int winning_team = -1;
1094
1095 for (i = 0; i < MAX_TEAMS; i++)
1096 team_state[i] = TeamEmpty;
1097
1098 for (i = 0; i < NumPlayers; i++) {
1099 player_t *pl_i = Player_by_index(i);
1100
1101 if (Player_is_tank(pl_i))
1102 /* Ignore tanks. */
1103 continue;
1104 else if (Player_is_paused(pl_i))
1105 /* Ignore paused players. */
1106 continue;
1107 #if 0
1108 /* not all teammode maps have treasures. */
1109 else if (world->teams[pl_i->team].NumTreasures == 0)
1110 /* Ignore players with no treasure troves */
1111 continue;
1112 #endif
1113 else if (Player_is_waiting(pl_i)
1114 || Player_is_dead(pl_i)) {
1115 if (team_state[pl_i->team] == TeamEmpty) {
1116 /* Assume all teammembers are dead. */
1117 num_dead_teams++;
1118 team_state[pl_i->team] = TeamDead;
1119 }
1120 }
1121 /*
1122 * If the player is not paused and he is not in the
1123 * game over mode and his team owns treasures then he is
1124 * considered alive.
1125 * But he may not be playing though if the rest of the team
1126 * was genocided very quickly after game reset, while this
1127 * player was still being transported back to his homebase.
1128 */
1129 else if (team_state[pl_i->team] != TeamAlive) {
1130 if (team_state[pl_i->team] == TeamDead)
1131 /* Oops! Not all teammembers are dead yet. */
1132 num_dead_teams--;
1133 team_state[pl_i->team] = TeamAlive;
1134 ++num_alive_teams;
1135 /* Remember a team which was alive. */
1136 winning_team = pl_i->team;
1137 }
1138 }
1139
1140 if (num_alive_teams > 1) {
1141 char *bp;
1142 int teams_with_treasure = 0, team_win[MAX_TEAMS];
1143 double team_score[MAX_TEAMS], max_score = 0;
1144 int winners, max_destroyed = 0, max_left = 0;
1145 team_t *team_ptr, *specialballteam_ptr;
1146 bool no_special_balls_present = false;
1147 /*
1148 * Game is not over if more than one team which have treasures
1149 * still have one remaining in play. Note that it is possible
1150 * for max_destroyed to be zero, in the case where a team
1151 * destroys some treasures and then all quit, and the remaining
1152 * teams did not destroy any.
1153 */
1154 for (i = 0; i < MAX_TEAMS; i++) {
1155 team_score[i] = 0;
1156 if ((team_state[i] != TeamAlive) && (i != options.specialBallTeam)) {
1157 team_win[i] = 0;
1158 continue;
1159 }
1160
1161 team_win[i] = 1;
1162 team_ptr = &(world->teams[i]);
1163 specialballteam_ptr = Team_by_index(options.specialBallTeam);
1164
1165 if (options.specialBallTeam < 0 || options.specialBallTeam >=MAX_TEAMS ||
1166 specialballteam_ptr->NumTreasures == 0)
1167 no_special_balls_present = true;
1168
1169 if (team_ptr->TreasuresDestroyed > max_destroyed)
1170 max_destroyed = team_ptr->TreasuresDestroyed;
1171 if ((team_ptr->TreasuresLeft > 0) ||
1172 ((team_ptr->NumTreasures == team_ptr->NumEmptyTreasures) &&
1173 no_special_balls_present))
1174 teams_with_treasure++;
1175 }
1176
1177 /*
1178 * Game is not over if more than one team has treasure.
1179 */
1180 if ((teams_with_treasure > 1 || !max_destroyed)
1181 && (roundtime != 0 || options.maxRoundTime <= 0))
1182 return;
1183
1184 if (options.maxRoundTime > 0 && roundtime == 0)
1185 Set_message("Timer expired. Round ends now.");
1186
1187 /*
1188 * Find the winning team;
1189 * Team destroying most number of treasures;
1190 * If drawn; the one with most saved treasures,
1191 * If drawn; the team with the most points,
1192 * If drawn; an overall draw.
1193 */
1194 for (winners = i = 0; i < MAX_TEAMS; i++) {
1195 if (!team_win[i])
1196 continue;
1197 if (world->teams[i].TreasuresDestroyed == max_destroyed) {
1198 if (world->teams[i].TreasuresLeft > max_left)
1199 max_left = world->teams[i].TreasuresLeft;
1200 winning_team = i;
1201 winners++;
1202 } else
1203 team_win[i] = 0;
1204 }
1205 if (winners == 1) {
1206 sprintf(msg, " by destroying %d treasures", max_destroyed);
1207 Team_game_over(winning_team, msg);
1208 return;
1209 }
1210
1211 for (i = 0; i < NumPlayers; i++) {
1212 player_t *pl_i = Player_by_index(i);
1213
1214 if (Player_is_paused(pl_i) || Player_is_tank(pl_i))
1215 continue;
1216 team_score[pl_i->team] += Get_Score(pl_i);
1217 }
1218
1219 for (winners = i = 0; i < MAX_TEAMS; i++) {
1220 if (!team_win[i])
1221 continue;
1222 if (world->teams[i].TreasuresLeft == max_left) {
1223 if (team_score[i] > max_score)
1224 max_score = team_score[i];
1225 winning_team = i;
1226 winners++;
1227 } else
1228 team_win[i] = 0;
1229 }
1230 if (winners == 1) {
1231 sprintf(msg,
1232 " by destroying %d treasures"
1233 " and successfully defending %d",
1234 max_destroyed, max_left);
1235 Team_game_over(winning_team, msg);
1236 return;
1237 }
1238
1239 for (winners = i = 0; i < MAX_TEAMS; i++) {
1240 if (!team_win[i])
1241 continue;
1242 if (team_score[i] == max_score) {
1243 winning_team = i;
1244 winners++;
1245 } else
1246 team_win[i] = 0;
1247 }
1248 if (winners == 1) {
1249 sprintf(msg, " by destroying %d treasures, saving %d, and "
1250 "scoring %.2f points",
1251 max_destroyed, max_left, max_score);
1252 Team_game_over(winning_team, msg);
1253 return;
1254 }
1255
1256 /* Highly unlikely */
1257
1258 sprintf(msg, " between teams ");
1259 bp = msg + strlen(msg);
1260 for (i = 0; i < MAX_TEAMS; i++) {
1261 if (!team_win[i])
1262 continue;
1263 *bp++ = "0123456789"[i]; *bp++ = ','; *bp++ = ' ';
1264 }
1265 bp -= 2;
1266 *bp = '\0';
1267 Team_game_over(-1, msg);
1268
1269 }
1270 else if (num_dead_teams > 0) {
1271 if (num_alive_teams == 1)
1272 Team_game_over(winning_team, " by staying alive");
1273 else
1274 Team_game_over(-1, " as everyone died");
1275 }
1276 else {
1277 /*
1278 * num_alive_teams <= 1 && num_dead_teams == 0
1279 *
1280 * There is a possibility that the game has ended because players
1281 * quit, the game over state is needed to reset treasures. We
1282 * must count how many treasures are missing, if there are any
1283 * the playing team (if any) wins.
1284 */
1285 int j, treasures_destroyed;
1286
1287 for (treasures_destroyed = j = 0; j < MAX_TEAMS; j++)
1288 treasures_destroyed += (world->teams[j].NumTreasures
1289 - world->teams[j].NumEmptyTreasures
1290 - world->teams[j].TreasuresLeft);
1291 if (treasures_destroyed)
1292 Team_game_over(winning_team, " by staying in the game");
1293 }
1294
1295 } else {
1296
1297 /* Do we have a winner ? (No team play) */
1298 int num_alive_players = 0;
1299 int num_active_players = 0;
1300 int num_alive_robots = 0;
1301 int num_active_humans = 0;
1302 int winner = -1;
1303
1304 for (i = 0; i < NumPlayers; i++) {
1305 player_t *pl_i = Player_by_index(i);
1306
1307 if (Player_is_paused(pl_i) || Player_is_tank(pl_i))
1308 continue;
1309 if (!(Player_is_waiting(pl_i)
1310 || Player_is_dead(pl_i))) {
1311 num_alive_players++;
1312 if (Player_is_robot(pl_i))
1313 num_alive_robots++;
1314 winner = i; /* Tag player that's alive */
1315 }
1316 else if (Player_is_human(pl_i))
1317 num_active_humans++;
1318 num_active_players++;
1319 }
1320
1321 if (num_alive_players == 1 && num_active_players > 1)
1322 Individual_game_over(winner);
1323 else if (num_alive_players == 0 && num_active_players >= 1)
1324 Individual_game_over(-1);
1325 else if (num_alive_robots > 1
1326 && num_alive_players == num_alive_robots
1327 && num_active_humans > 0)
1328 Individual_game_over(-2);
1329 else if (options.maxRoundTime > 0 && roundtime == 0) {
1330 Set_message("Timer expired. Round ends now.");
1331 Individual_game_over(-1);
1332 }
1333 }
1334 }
1335
Delete_player(player_t * pl)1336 void Delete_player(player_t *pl)
1337 {
1338 int ind = GetInd(pl->id), i, j, id = pl->id;
1339 object_t *obj;
1340 team_t *teamp = Team_by_index(pl->team);
1341
1342 /* call before important player structures are destroyed */
1343 Leave_alliance(pl);
1344
1345 if (options.tagGame && tagItPlayerId == pl->id)
1346 tagItPlayerId = NO_ID;
1347
1348 if (Player_is_robot(pl))
1349 Robot_destroy(pl);
1350
1351 if (pl->isoperator) {
1352 if (!--NumOperators && game_lock) {
1353 game_lock = false;
1354 Set_message(" < The game has been unlocked as "
1355 "the last operator left! >");
1356 }
1357 }
1358
1359 /* Won't be swapping anywhere */
1360 for (i = MAX_TEAMS - 1; i >= 0; i--)
1361 if (world->teams[i].SwapperId == id)
1362 world->teams[i].SwapperId = -1;
1363
1364 /* Delete remaining shots */
1365 for (i = NumObjs - 1; i >= 0; i--) {
1366 obj = Obj[i];
1367 if (obj->id == id) {
1368 if (obj->type == OBJ_BALL) {
1369 Delete_shot(i);
1370 BALL_PTR(obj)->ball_owner = NO_ID;
1371 }
1372 else if (obj->type == OBJ_DEBRIS
1373 || obj->type == OBJ_SPARK)
1374 /* Okay, so you want robot explosions to exist,
1375 * even if the robot left the game. */
1376 obj->id = NO_ID;
1377 else {
1378 if (!options.keepShots) {
1379 obj->life = 0;
1380 if (BIT(OBJ_TYPEBIT(obj->type),
1381 OBJ_CANNON_SHOT_BIT|OBJ_MINE_BIT|OBJ_SMART_SHOT_BIT
1382 |OBJ_HEAT_SHOT_BIT|OBJ_TORPEDO_BIT))
1383 obj->mass = 0;
1384 }
1385 obj->id = NO_ID;
1386 if (obj->type == OBJ_MINE)
1387 MINE_PTR(obj)->mine_owner = NO_ID;
1388 }
1389 }
1390 else {
1391 if (obj->type == OBJ_MINE) {
1392 mineobject_t *mine = MINE_PTR(obj);
1393
1394 if (mine->mine_owner == id) {
1395 mine->mine_owner = NO_ID;
1396 if (!options.keepShots) {
1397 obj->life = 0;
1398 obj->mass = 0;
1399 }
1400 }
1401 }
1402 else if (obj->type == OBJ_BALL) {
1403 ballobject_t *ball = BALL_PTR(obj);
1404
1405 if (ball->ball_owner == id)
1406 ball->ball_owner = NO_ID;
1407 }
1408 }
1409 }
1410
1411 Free_ship_shape(pl->ship);
1412
1413 sound_close(pl);
1414
1415 NumPlayers--;
1416 if (Player_is_tank(pl))
1417 NumPseudoPlayers--;
1418
1419 if (pl->rank) {
1420 Rank_save_score(pl);
1421 if (NumPlayers == NumRobots + NumPseudoPlayers) {
1422 Rank_write_webpage();
1423 Rank_write_rankfile();
1424 }
1425 }
1426
1427 if (teamp && !Player_is_tank(pl) && pl->home_base) {
1428 teamp->NumMembers--;
1429 if (Player_is_robot(pl))
1430 teamp->NumRobots--;
1431 }
1432
1433 if (Player_is_robot(pl))
1434 NumRobots--;
1435
1436 /*
1437 * Swap entry no 'ind' with the last one.
1438 *
1439 * Change the PlayersArray[] pointer array to have
1440 * Player_by_index(ind) point to a valid player and move our leaving
1441 * player to PlayersArray[NumPlayers].
1442 */
1443 /* Swap pointers... */
1444 pl = Player_by_index(NumPlayers);
1445 PlayersArray[NumPlayers] = Player_by_index(ind);
1446 PlayersArray[ind] = pl;
1447 /* Restore pointer. */
1448 pl = Player_by_index(NumPlayers);
1449
1450 GetIndArray[Player_by_index(ind)->id] = ind;
1451 GetIndArray[Player_by_index(NumPlayers)->id] = NumPlayers;
1452
1453 Check_team_members(pl->team);
1454
1455 for (i = NumPlayers - 1; i >= 0; i--) {
1456 player_t *pl_i = Player_by_index(i);
1457
1458 if (Player_is_tank(pl_i)
1459 && pl_i->lock.pl_id == id) {
1460 /* remove tanks which were released by this player. */
1461 if (options.keepShots)
1462 pl_i->lock.pl_id = NO_ID;
1463 else
1464 Delete_player(pl_i);
1465 continue;
1466 }
1467 if (BIT(pl_i->lock.tagged, LOCK_PLAYER|LOCK_VISIBLE)
1468 && (pl_i->lock.pl_id == id || NumPlayers <= 1)) {
1469 CLR_BIT(pl_i->lock.tagged, LOCK_PLAYER|LOCK_VISIBLE);
1470 CLR_BIT(pl_i->used, USES_TRACTOR_BEAM);
1471 }
1472 if (Player_is_robot(pl_i)
1473 && Robot_war_on_player(pl_i) == id)
1474 Robot_reset_war(pl_i);
1475
1476 for (j = 0; j < LOCKBANK_MAX; j++) {
1477 if (pl_i->lockbank[j] == id)
1478 pl_i->lockbank[j] = NO_ID;
1479 }
1480 for (j = 0; j < MAX_RECORDED_SHOVES; j++) {
1481 if (pl_i->shove_record[j].pusher_id == id)
1482 pl_i->shove_record[j].pusher_id = NO_ID;
1483 }
1484 }
1485
1486 for (i = NumPlayers - 1; i >= 0; i--) {
1487 player_t *pl_i = Player_by_index(i);
1488
1489 if (pl_i->conn != NULL)
1490 Send_leave(pl_i->conn, id);
1491 else if (Player_is_tank(pl_i)) {
1492 if (pl_i->lock.pl_id == id)
1493 Delete_player(pl_i);
1494 }
1495 }
1496
1497 for (i = NumSpectators - 1; i >= 0; i--)
1498 Send_leave(Player_by_index(i + spectatorStart)->conn, id);
1499
1500 GetIndArray[id] = NO_IND;
1501 release_ID(id);
1502 }
1503
Add_spectator(player_t * pl)1504 void Add_spectator(player_t *pl)
1505 {
1506 pl->home_base = NULL;
1507 pl->team = 0;
1508 GetIndArray[pl->id] = spectatorStart + NumSpectators;
1509 Player_set_score(pl,-6666);
1510 Player_set_mychar(pl,'S');
1511 NumSpectators++;
1512 }
1513
Delete_spectator(player_t * pl)1514 void Delete_spectator(player_t *pl)
1515 {
1516 int i, ind = GetInd(pl->id);
1517
1518 NumSpectators--;
1519 /* Swap leaver last */
1520 pl = Player_by_index(spectatorStart + NumSpectators);
1521 PlayersArray[spectatorStart + NumSpectators] = Player_by_index(ind);
1522 PlayersArray[ind] = pl;
1523 pl = Player_by_index(spectatorStart + NumSpectators);
1524
1525 GetIndArray[Player_by_index(ind)->id] = ind;
1526 GetIndArray[pl->id] = spectatorStart + NumSpectators;
1527
1528 Free_ship_shape(pl->ship);
1529 for (i = NumSpectators - 1; i >= 0; i--)
1530 Send_leave(Player_by_index(i + spectatorStart)->conn, pl->id);
1531 }
1532
Detach_ball(player_t * pl,ballobject_t * ball)1533 void Detach_ball(player_t *pl, ballobject_t *ball)
1534 {
1535 int i, cnt;
1536
1537 if (ball == NULL || ball == pl->ball) {
1538 pl->ball = NULL;
1539 CLR_BIT(pl->used, USES_CONNECTOR);
1540 }
1541
1542 if (BIT(pl->have, HAS_BALL)) {
1543 for (cnt = i = 0; i < NumObjs; i++) {
1544 object_t *obj = Obj[i];
1545
1546 if (obj->type == OBJ_BALL && obj->id == pl->id) {
1547 if (ball == NULL || ball == BALL_PTR(obj))
1548 obj->id = NO_ID;
1549 /* Don't reset owner so you can throw balls */
1550 else
1551 cnt++;
1552 }
1553 }
1554 if (cnt == 0)
1555 CLR_BIT(pl->have, HAS_BALL);
1556 else
1557 sound_play_sensors(pl->pos, DROP_BALL_SOUND);
1558 }
1559 }
1560
Kill_player(player_t * pl,bool add_rank_death)1561 void Kill_player(player_t *pl, bool add_rank_death)
1562 {
1563 if (Player_is_killed(pl))
1564 Explode_fighter(pl);
1565 Player_death_reset(pl, add_rank_death);
1566 }
1567
Player_death_reset(player_t * pl,bool add_rank_death)1568 void Player_death_reset(player_t *pl, bool add_rank_death)
1569 {
1570 if (Player_is_tank(pl)) {
1571 Delete_player(pl);
1572 return;
1573 }
1574
1575 if (Player_is_paused(pl))
1576 return;
1577
1578 Detach_ball(pl, NULL);
1579 if (Player_uses_autopilot(pl)
1580 || Player_is_hoverpaused(pl)) {
1581 CLR_BIT(pl->pl_status, HOVERPAUSE);
1582 Autopilot(pl, false);
1583 }
1584
1585 pl->vel.x = pl->vel.y = 0.0;
1586 pl->acc.x = pl->acc.y = 0.0;
1587 pl->emptymass = pl->mass = options.shipMass;
1588 pl->obj_status &= ~(KILL_OBJ_BITS);
1589
1590 if (BIT(world->rules->mode, LIMITED_LIVES)) {
1591 bool waiting = Player_is_waiting(pl);
1592
1593 Player_set_life(pl, pl->pl_life - 1);
1594 Player_set_state(pl, PL_STATE_APPEARING);
1595
1596 if (pl->pl_life == -1) {
1597 Player_set_life(pl, 0);
1598 if (waiting)
1599 Player_set_state(pl, PL_STATE_WAITING);
1600 else
1601 Player_set_state(pl, PL_STATE_DEAD);
1602 Player_lock_closest(pl, false);
1603 }
1604 }
1605 else {
1606 Player_set_life(pl, pl->pl_life + 1);
1607 Player_set_state(pl, PL_STATE_APPEARING);
1608 }
1609
1610 Player_init_items(pl);
1611
1612 pl->forceVisible = 0;
1613 assert(pl->recovery_count == RECOVERY_DELAY);
1614 pl->ecmcount = 0;
1615 pl->emergency_thrust_left = 0;
1616 pl->emergency_shield_left = 0;
1617 pl->phasing_left = 0;
1618 pl->self_destruct_count = 0;
1619 pl->damaged = 0;
1620 pl->stunned = 0;
1621 pl->lock.distance = 0;
1622
1623 if (add_rank_death) {
1624 Rank_add_death(pl);
1625 pl->pl_deaths_since_join++;
1626 }
1627
1628 pl->have = DEF_HAVE;
1629 pl->used &= ~(USED_KILL);
1630 pl->used |= DEF_USED;
1631 pl->used &= pl->have;
1632 }
1633
1634 /* determines if two players are immune to eachother */
Team_immune(int id1,int id2)1635 bool Team_immune(int id1, int id2)
1636 {
1637 player_t *pl1, *pl2;
1638
1639 /* owned stuff is never team immune */
1640 if (id1 == id2)
1641 return false;
1642
1643 if (!options.teamImmunity)
1644 return false;
1645
1646 if (id1 == NO_ID || id2 == NO_ID)
1647 /* can't find owner for cannon stuff */
1648 return false;
1649
1650 pl1 = Player_by_id(id1);
1651 pl2 = Player_by_id(id2);
1652
1653 if (Players_are_teammates(pl1, pl2))
1654 return true;
1655
1656 if (Players_are_allies(pl1, pl2))
1657 return true;
1658
1659 return false;
1660 }
1661
old_status2str(int old_status)1662 static char *old_status2str(int old_status)
1663 {
1664 static char buf[256];
1665
1666 buf[0] = '\0';
1667
1668 if (old_status & OLD_PLAYING)
1669 strlcat(buf, "OLD_PLAYING ", sizeof(buf));
1670 if (old_status & OLD_PAUSE)
1671 strlcat(buf, "OLD_PAUSE ", sizeof(buf));
1672 if (old_status & OLD_GAME_OVER)
1673 strlcat(buf, "OLD_GAME_OVER ", sizeof(buf));
1674
1675 return buf;
1676 }
1677
state2str(int state)1678 static char *state2str(int state)
1679 {
1680 static char buf[256];
1681
1682 buf[0] = '\0';
1683
1684 if (state == PL_STATE_UNDEFINED)
1685 strlcat(buf, "PL_STATE_UNDEFINED", sizeof(buf));
1686 if (state == PL_STATE_WAITING)
1687 strlcat(buf, "PL_STATE_WAITING", sizeof(buf));
1688 if (state == PL_STATE_APPEARING)
1689 strlcat(buf, "PL_STATE_APPEARING", sizeof(buf));
1690 if (state == PL_STATE_ALIVE)
1691 strlcat(buf, "PL_STATE_ALIVE", sizeof(buf));
1692 if (state == PL_STATE_KILLED)
1693 strlcat(buf, "PL_STATE_KILLED", sizeof(buf));
1694 if (state == PL_STATE_DEAD)
1695 strlcat(buf, "PL_STATE_DEAD", sizeof(buf));
1696 if (state == PL_STATE_PAUSED)
1697 strlcat(buf, "PL_STATE_PAUSED", sizeof(buf));
1698
1699 return buf;
1700 }
1701
Player_print_state(player_t * pl,const char * funcname)1702 void Player_print_state(player_t *pl, const char *funcname)
1703 {
1704 warn("%-20s: %-16s (%c): %-20s %s ", funcname, pl->name, pl->mychar,
1705 state2str(pl->pl_state), old_status2str(pl->pl_old_status));
1706 }
1707
Player_set_state(player_t * pl,int state)1708 void Player_set_state(player_t *pl, int state)
1709 {
1710 pl->pl_state = state;
1711
1712 switch (state) {
1713 case PL_STATE_WAITING:
1714 Player_set_mychar(pl, 'W');
1715 Player_set_life(pl, 0);
1716 pl->pl_old_status = OLD_GAME_OVER;
1717 break;
1718 case PL_STATE_APPEARING:
1719 Player_set_mychar(pl, pl->pl_type_mychar);
1720 /*Player_set_mychar(pl, 'A');*/
1721 pl->pl_old_status = 0;
1722 pl->recovery_count = RECOVERY_DELAY;
1723 break;
1724 case PL_STATE_ALIVE:
1725 Player_set_mychar(pl, pl->pl_type_mychar);
1726 pl->pl_old_status = OLD_PLAYING;
1727 break;
1728 case PL_STATE_KILLED:
1729 break;
1730 case PL_STATE_DEAD:
1731 Player_set_mychar(pl, 'D');
1732 pl->pl_old_status = OLD_GAME_OVER;
1733 break;
1734 case PL_STATE_PAUSED:
1735 Player_set_mychar(pl, 'P');
1736 Player_set_life(pl, 0);
1737 pl->pl_old_status = OLD_PAUSE;
1738 break;
1739 default:
1740 break;
1741 }
1742 }
1743