/* $Id: player.c,v 5.29 2002/04/21 19:08:15 kimiko Exp $ * * XPilot, a multiplayer gravity war game. Copyright (C) 1991-2001 by * * Bjørn Stabell * Ken Ronny Schouten * Bert Gijsbers * Dick Balaska * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #ifdef _WINDOWS # include "NT/winServer.h" #endif #define SERVER #include "version.h" #include "config.h" #include "serverconst.h" #include "global.h" #include "proto.h" #include "map.h" #include "score.h" #include "bit.h" #include "netserver.h" #include "saudio.h" #include "error.h" #include "objpos.h" #include "draw.h" char player_version[] = VERSION; bool updateScores = true; /********* ********** * Functions on player array. */ void Pick_startpos(int ind) { player *pl = Players[ind]; int i, num_free; int pick = 0, seen = 0; static int prev_num_bases = 0; static char *free_bases = NULL; if (IS_TANK_PTR(pl)) { pl->home_base = 0; return; } if (prev_num_bases != World.NumBases) { prev_num_bases = World.NumBases; if (free_bases != NULL) { free(free_bases); } free_bases = (char *) malloc(World.NumBases * sizeof(*free_bases)); if (free_bases == NULL) { error("Can't allocate memory for free_bases"); End_game(); } } num_free = 0; for (i = 0; i < World.NumBases; i++) { if (World.base[i].team == pl->team) { num_free++; free_bases[i] = 1; } else { free_bases[i] = 0; /* other team */ } } for (i = 0; i < NumPlayers; i++) { if (i != ind && !IS_TANK_IND(i) && free_bases[Players[i]->home_base]) { free_bases[Players[i]->home_base] = 0; /* occupado */ num_free--; } } if (BIT(World.rules->mode, TIMING)) { /* pick first free base */ for (i=0; i < World.NumBases; i++) { if (free_bases[World.baseorder[i].base_idx]) { break; } } } else { pick = (int)(rfrac() * num_free); seen = 0; for (i = 0; i < World.NumBases; i++) { if (free_bases[i] != 0) { if (seen < pick) { seen++; } else { break; } } } } if (i == World.NumBases) { error("Can't pick startpos (ind=%d,num=%d,free=%d,pick=%d,seen=%d)", ind, World.NumBases, num_free, pick, seen); End_game(); } else { pl->home_base = BIT(World.rules->mode, TIMING) ? World.baseorder[i].base_idx : i; if (ind < NumPlayers) { for (i = 0; i < NumPlayers; i++) { if (Players[i]->conn != NOT_CONNECTED) { Send_base(Players[i]->conn, pl->id, pl->home_base); } } if (BIT(pl->status, PLAYING) == 0) { pl->count = RECOVERY_DELAY; } else if (BIT(pl->status, PAUSE|GAME_OVER)) { Go_home(ind); } } } } void Go_home(int ind) { player *pl = Players[ind]; int i, x, y, dir, check; DFLOAT vx, vy, velo; if (IS_TANK_PTR(pl)) { /*NOTREACHED*/ /* Tanks have no homebase. */ error("BUG: gohome tank"); return; } if (BIT(World.rules->mode, TIMING) && pl->round && !BIT(pl->status, GAME_OVER)) { if (pl->check) check = pl->check - 1; else check = World.NumChecks - 1; x = World.check[check].x; y = World.check[check].y; vx = (rfrac() - 0.5) * 0.1; vy = (rfrac() - 0.5) * 0.1; velo = LENGTH(vx, vy); dir = pl->last_check_dir; dir = MOD2(dir + (int)((rfrac() - 0.5) * (RES / 8)), RES); } else { x = World.base[pl->home_base].pos.x; y = World.base[pl->home_base].pos.y; dir = World.base[pl->home_base].dir; vx = vy = velo = 0; } pl->dir = dir; pl->float_dir = dir; Player_position_init_pixels(pl, (x + 0.5) * BLOCK_SZ + vx, (y + 0.5) * BLOCK_SZ + vy); pl->vel.x = vx; pl->vel.y = vy; pl->velocity = velo; pl->acc.x = pl->acc.y = 0.0; pl->turnacc = pl->turnvel = 0.0; memset(pl->last_keyv, 0, sizeof(pl->last_keyv)); memset(pl->prev_keyv, 0, sizeof(pl->prev_keyv)); Player_used_kill(ind); if (playerStartsShielded != 0) { SET_BIT(pl->used, HAS_SHIELD); if (playerShielding == 0) { pl->shield_time = 2 * FPS; SET_BIT(pl->have, HAS_SHIELD); } if (BIT(pl->have, HAS_DEFLECTOR)) { Deflector(ind, true); } } CLR_BIT(pl->status, THRUSTING); pl->updateVisibility = 1; for (i = 0; i < NumPlayers; i++) { pl->visibility[i].lastChange = 0; Players[i]->visibility[ind].lastChange = 0; } if (IS_ROBOT_PTR(pl)) { Robot_go_home(ind); } } /* * Compute the current sensor range for player `pl'. This is based on the * amount of fuel, the number of sensor items (each one adds 25%), and the * minimum and maximum visibility limits in effect. */ void Compute_sensor_range(player *pl) { static int init = 0; static DFLOAT EnergyRangeFactor; if (!init) { if (minVisibilityDistance <= 0.0) minVisibilityDistance = VISIBILITY_DISTANCE; else minVisibilityDistance *= BLOCK_SZ; if (maxVisibilityDistance <= 0.0) maxVisibilityDistance = World.hypotenuse; else maxVisibilityDistance *= BLOCK_SZ; if (World.items[ITEM_FUEL].initial > 0.0) { EnergyRangeFactor = minVisibilityDistance / (World.items[ITEM_FUEL].initial * (1.0 + ((DFLOAT)World.items[ITEM_SENSOR].initial * 0.25))); EnergyRangeFactor /= FUEL_SCALE_FACT; } else { EnergyRangeFactor = ENERGY_RANGE_FACTOR; } init = 1; } pl->sensor_range = pl->fuel.sum * EnergyRangeFactor; pl->sensor_range *= (1.0 + ((DFLOAT)pl->item[ITEM_SENSOR] * 0.25)); if (pl->sensor_range < minVisibilityDistance) pl->sensor_range = minVisibilityDistance; if (pl->sensor_range > maxVisibilityDistance) pl->sensor_range = maxVisibilityDistance; } /* * Give ship one more tank, if possible. */ void Player_add_tank(int ind, long tank_fuel) { player *pl = Players[ind]; long tank_cap, add_fuel; if (pl->fuel.num_tanks < MAX_TANKS) { pl->fuel.num_tanks++; tank_cap = TANK_CAP(pl->fuel.num_tanks); add_fuel = tank_fuel; LIMIT(add_fuel, 0, tank_cap); pl->fuel.sum += add_fuel; pl->fuel.max += tank_cap; pl->fuel.tank[pl->fuel.num_tanks] = add_fuel; pl->emptymass += TANK_MASS; pl->item[ITEM_TANK] = pl->fuel.num_tanks; } } /* * Remove a tank from a ship, if possible. */ void Player_remove_tank(int ind, int which_tank) { player *pl = Players[ind]; int i, tank_ind; long tank_fuel, tank_cap; if (pl->fuel.num_tanks > 0) { tank_ind = which_tank; LIMIT(tank_ind, 1, pl->fuel.num_tanks); pl->emptymass -= TANK_MASS; tank_fuel = pl->fuel.tank[tank_ind]; tank_cap = TANK_CAP(tank_ind); pl->fuel.max -= tank_cap; pl->fuel.sum -= tank_fuel; pl->fuel.num_tanks--; if (pl->fuel.current > pl->fuel.num_tanks) { pl->fuel.current = 0; } else { for (i = tank_ind; i <= pl->fuel.num_tanks; i++) { pl->fuel.tank[i] = pl->fuel.tank[i + 1]; } } pl->item[ITEM_TANK] = pl->fuel.num_tanks; } } void Player_hit_armor(int ind) { player *pl = Players[ind]; if (--pl->item[ITEM_ARMOR] <= 0) CLR_BIT(pl->have, HAS_ARMOR); } void Player_used_kill(int ind) { player *pl = Players[ind]; pl->used &= ~USED_KILL; if (!BIT(DEF_HAVE, HAS_SHIELD)) { CLR_BIT(pl->have, HAS_SHIELD); } } /* * Give player the initial number of tanks and amount of fuel. * Upto the maximum allowed. */ static void Player_init_fuel(int ind, long total_fuel) { player *pl = Players[ind]; long fuel = total_fuel; int i; pl->fuel.num_tanks = 0; pl->fuel.current = 0; pl->fuel.max = TANK_CAP(0); pl->fuel.sum = MIN(fuel, pl->fuel.max); pl->fuel.tank[0] = pl->fuel.sum; pl->emptymass = ShipMass; pl->item[ITEM_TANK] = pl->fuel.num_tanks; fuel -= pl->fuel.sum; for (i = 1; i <= World.items[ITEM_TANK].initial; i++) { Player_add_tank(ind, fuel); fuel -= pl->fuel.tank[i]; } } int Init_player(int ind, shipobj *ship) { player *pl = Players[ind]; bool too_late = false; int i; pl->vel.x = pl->vel.y = 0.0; pl->acc.x = pl->acc.y = 0.0; pl->float_dir = pl->dir = DIR_UP; pl->turnvel = 0.0; pl->oldturnvel = 0.0; pl->turnacc = 0.0; pl->mass = ShipMass; pl->emptymass = ShipMass; for (i = 0; i < NUM_ITEMS; i++) { if (!BIT(1U << i, ITEM_BIT_FUEL | ITEM_BIT_TANK)) { pl->item[i] = World.items[i].initial; } } pl->fuel.sum = World.items[ITEM_FUEL].initial << FUEL_SCALE_BITS; Player_init_fuel(ind, pl->fuel.sum); if (allowShipShapes == true && ship) { pl->ship = ship; } else { /* pl->ship = Default_ship(); */ shipobj *tryship = Parse_shape_str(defaultShipShape); if (tryship) pl->ship = tryship; else pl->ship = Default_ship(); } pl->power = 45.0; pl->turnspeed = 30.0; pl->turnresistance = 0.12; pl->power_s = 35.0; pl->turnspeed_s = 25.0; pl->turnresistance_s = 0.12; pl->check = 0; pl->round = 0; pl->time = 0; pl->last_lap_time = 0; pl->last_lap = 0; pl->best_lap = 0; pl->count = -1; pl->shield_time = 0; pl->last_wall_touch = 0; pl->type = OBJ_PLAYER; pl->type_ext = 0; /* assume human player */ pl->shots = 0; pl->missile_rack = 0; pl->forceVisible = 0; Compute_sensor_range(pl); pl->shot_max = ShotsMax; pl->shot_time = 0; pl->color = WHITE; pl->score = 0; pl->prev_score = 0; pl->prev_check = 0; pl->prev_round = 0; pl->fs = 0; pl->repair_target = 0; pl->name[0] = '\0'; pl->num_pulses = 0; pl->emergency_thrust_left = 0; pl->emergency_thrust_max = 0; pl->emergency_shield_left = 0; pl->emergency_shield_max = 0; pl->phasing_left = 0; pl->phasing_max = 0; pl->ecmcount = 0; pl->damaged = 0; pl->stunned = 0; pl->status = PLAYING | GRAVITY | DEF_BITS; pl->have = DEF_HAVE; pl->used = DEF_USED; if (pl->item[ITEM_CLOAK] > 0) { SET_BIT(pl->have, HAS_CLOAKING_DEVICE); } CLEAR_MODS(pl->mods); for (i = 0; i < NUM_MODBANKS; i++) CLEAR_MODS(pl->modbank[i]); for (i = 0; i < LOCKBANK_MAX; i++) pl->lockbank[i] = NOT_CONNECTED; { static unsigned short pseudo_team_no = 0; pl->pseudo_team = pseudo_team_no++; } pl->mychar = ' '; pl->prev_mychar = pl->mychar; pl->life = World.rules->lives; pl->prev_life = pl->life; pl->ball = NULL; pl->player_fps = FPS; pl->player_round = 0; pl->player_count = 0; pl->kills = 0; pl->deaths = 0; /* * If limited lives and if nobody has lost a life yet, you may enter * now, otherwise you will have to wait 'til everyone gets GAME OVER. */ if (BIT(World.rules->mode, LIMITED_LIVES)) { for (i = 0; i < NumPlayers; i++) { /* If a non-team member has lost a life, * then it's too late to join. */ if (Players[i]->life < World.rules->lives && !TEAM(ind, i)) { too_late = true; break; } } if (too_late) { pl->mychar = 'W'; pl->prev_life = pl->life = 0; SET_BIT(pl->status, GAME_OVER); } } pl->team = TEAM_NOT_SET; pl->alliance = ALLIANCE_NOT_SET; pl->prev_alliance = ALLIANCE_NOT_SET; pl->invite = NO_ID; pl->lock.tagged = LOCK_NONE; pl->lock.pl_id = 0; pl->robot_data_ptr = NULL; pl->wormDrawCount = 0; pl->id = peek_ID(); GetInd[pl->id] = ind; pl->conn = NOT_CONNECTED; pl->audio = NULL; pl->lose_item = 0; pl->lose_item_state = 0; pl->shove_next = 0; for (i = 0; i < MAX_RECORDED_SHOVES; i++) { pl->shove_record[i].pusher_id = NO_ID; } pl->frame_last_busy = frame_loops; pl->isowner = 0; pl->isoperator = 0; return pl->id; } static player *playerArray; static struct _visibility *visibilityArray; void Alloc_players(int number) { player *p; struct _visibility *t; int i; /* Allocate space for pointers */ Players = (player **) calloc(number + 1, sizeof(player *)); /* Allocate space for all entries, all player structs */ p = playerArray = (player *) calloc(number, sizeof(player)); /* Allocate space for all visibility arrays, n arrays of n entries */ t = visibilityArray = (struct _visibility *) calloc(number * number, sizeof(struct _visibility)); if (!Players || !playerArray || !visibilityArray) { error("Not enough memory for Players."); exit(1); } /* Players[-1] should evaluate to NULL. */ Players++; for (i = 0; i < number; i++) { Players[i] = p++; Players[i]->visibility = t; /* Advance to next block/array */ t += number; } } void Free_players(void) { if (Players) { --Players; free(Players); Players = NULL; free(playerArray); free(visibilityArray); } } void Update_score_table(void) { int i, j, check; player *pl; for (j = 0; j < NumPlayers; j++) { pl = Players[j]; if (pl->score != pl->prev_score || pl->life != pl->prev_life || pl->mychar != pl->prev_mychar || pl->alliance != pl->prev_alliance) { pl->prev_score = pl->score; pl->prev_life = pl->life; pl->prev_mychar = pl->mychar; pl->prev_alliance = pl->alliance; for (i = 0; i < NumPlayers; i++) { if (Players[i]->conn != NOT_CONNECTED) { Send_score(Players[i]->conn, pl->id, pl->score, pl->life, pl->mychar, pl->alliance); } } } if (BIT(World.rules->mode, TIMING)) { if (pl->check != pl->prev_check || pl->round != pl->prev_round) { pl->prev_check = pl->check; pl->prev_round = pl->round; check = (pl->round == 0) ? 0 : (pl->check == 0) ? (World.NumChecks - 1) : (pl->check - 1); for (i = 0; i < NumPlayers; i++) { if (Players[i]->conn != NOT_CONNECTED) { Send_timing(Players[i]->conn, pl->id, check, pl->round); } } } } } if (BIT(World.rules->mode, TEAM_PLAY)) { team_t *team; for (j = 0; j < MAX_TEAMS; j++) { team = &(World.teams[j]); if (team->score != team->prev_score) { team->prev_score = team->score; for (i = 0; i < NumPlayers; i++) { if (Players[i]->conn != NOT_CONNECTED) { Send_team_score(Players[i]->conn, j, team->score); } } } } } updateScores = false; #ifdef _WINDOWS SendDialogUpdate(); #endif } void Reset_all_players(void) { player *pl; int i, j; char msg[MSG_LEN]; updateScores = true; for (i = 0; i < NumPlayers; i++) { pl = Players[i]; if (endOfRoundReset) { if (BIT(pl->status, PAUSE)) { Player_death_reset(i); } else { Kill_player(i); if (pl != Players[i]) { /* player was deleted. */ i--; continue; } } } CLR_BIT(pl->status, GAME_OVER); CLR_BIT(pl->have, HAS_BALL); pl->kills = 0; pl->deaths = 0; pl->round = 0; pl->check = 0; pl->time = 0; pl->best_lap = 0; pl->last_lap = 0; pl->last_lap_time = 0; if (!BIT(pl->status, PAUSE)) { pl->mychar = ' '; pl->frame_last_busy = frame_loops; pl->life = World.rules->lives; if (BIT(World.rules->mode, TIMING)) { pl->count = RECOVERY_DELAY; } } if (IS_TANK_PTR(pl)) pl->mychar = 'T'; else if (IS_ROBOT_PTR(pl)) pl->mychar = 'R'; } if (BIT(World.rules->mode, TEAM_PLAY)) { /* Detach any balls and kill ball */ /* We are starting all over again */ for (j = NumObjs - 1; j >= 0 ; j--) { if (BIT(Obj[j]->type, OBJ_BALL)) { ballobject *ball = BALL_IND(j); ball->id = NO_ID; ball->life = 0; ball->owner = 0; /* why not -1 ??? */ CLR_BIT(ball->status, RECREATE); Delete_shot(j); } } /* Reset the treasures */ for (i = 0; i < World.NumTreasures; i++) { World.treasures[i].destroyed = 0; World.treasures[i].have = false; Make_treasure_ball(i); } /* Reset the teams */ for (i = 0; i < MAX_TEAMS; i++) { World.teams[i].TreasuresDestroyed = 0; World.teams[i].TreasuresLeft = World.teams[i].NumTreasures - World.teams[i].NumEmptyTreasures; } if (endOfRoundReset) { /* Reset the targets */ for (i = 0; i < World.NumTargets; i++) { if (World.targets[i].damage != TARGET_DAMAGE || World.targets[i].dead_time != 0) { World.block[World.targets[i].pos.x][World.targets[i].pos.y] = TARGET; World.targets[i].dead_time = 0; World.targets[i].damage = TARGET_DAMAGE; World.targets[i].conn_mask = 0; World.targets[i].update_mask = (unsigned)-1; World.targets[i].last_change = frame_loops; } } } } if (endOfRoundReset) { for (i = 0; i < NumObjs; i++) { object *obj = Obj[i]; if (BIT(obj->type, OBJ_SHOT|OBJ_MINE|OBJ_DEBRIS|OBJ_SPARK |OBJ_CANNON_SHOT|OBJ_TORPEDO|OBJ_SMART_SHOT |OBJ_HEAT_SHOT|OBJ_ITEM)) { obj->life = 0; if (BIT(obj->type, OBJ_TORPEDO|OBJ_SMART_SHOT|OBJ_HEAT_SHOT |OBJ_CANNON_SHOT|OBJ_MINE)) { /* Take care that no new explosions are made. */ obj->mass = 0; } } } } if (round_delay_send > 0) { round_delay_send--; } if (roundDelaySeconds) { /* Hold your horses! The next round will start in a few moments. */ round_delay = roundDelaySeconds * FPS; /* Send him an extra seconds worth to be sure he gets the 0. */ round_delay_send = round_delay+FPS; roundtime = -1; sprintf(msg, "Delaying %d seconds until start of next %s.", roundDelaySeconds, (BIT(World.rules->mode, TIMING)? "race" : "round")); Set_message(msg); } else { roundtime = maxRoundTime * FPS; } Update_score_table(); } void Check_team_members(int team) { player *pl; int members, i; if (! BIT(World.rules->mode, TEAM_PLAY)) return; for (members = i = 0; i < NumPlayers; i++) { pl = Players[i]; if (pl->team != TEAM_NOT_SET && !IS_TANK_PTR(pl) && pl->team == team) members++; } if (World.teams[team].NumMembers != members) { error ("Server has reset team %d members from %d to %d", team, World.teams[team].NumMembers, members); for (i = 0; i < NumPlayers; i++) { pl = Players[i]; if (pl->team != TEAM_NOT_SET && !IS_TANK_PTR(pl) && pl->team == team) error ("Team %d currently has player %d: \"%s\"", team, i+1, pl->name); } World.teams[team].NumMembers = members; } } static void Compute_end_of_round_values(DFLOAT *average_score, int *num_best_players, DFLOAT *best_ratio, int best_players[]) { int i; DFLOAT ratio; /* Initialize everything */ *average_score = 0; *num_best_players = 0; *best_ratio = -1.0; /* Figure out what the average score is and who has the best kill/death */ /* ratio for this round */ for (i = 0; i < NumPlayers; i++) { if (IS_TANK_IND(i) || (BIT(Players[i]->status, PAUSE) && Players[i]->count <= 0)) { continue; } *average_score += Players[i]->score; ratio = (DFLOAT) Players[i]->kills / (Players[i]->deaths + 1); if (ratio > *best_ratio) { *best_ratio = ratio; best_players[0] = i; *num_best_players = 1; } else if (ratio == *best_ratio) { best_players[(*num_best_players)++] = i; } } *average_score /= NumPlayers; } static void Give_best_player_bonus(DFLOAT average_score, int num_best_players, DFLOAT best_ratio, int best_players[]) { int i; DFLOAT points; char msg[MSG_LEN]; if (best_ratio == 0) { sprintf(msg, "There is no Deadly Player"); } else if (num_best_players == 1) { player *bp = Players[best_players[0]]; sprintf(msg, "%s is the Deadliest Player with a kill ratio of %d/%d.", bp->name, bp->kills, bp->deaths); points = best_ratio * Rate(bp->score, average_score); SCORE(best_players[0], points, OBJ_X_IN_BLOCKS(bp), OBJ_Y_IN_BLOCKS(bp), "[Deadliest]"); } else { msg[0] = '\0'; for (i = 0; i < num_best_players; i++) { player *bp = Players[best_players[i]]; DFLOAT ratio = Rate(bp->score, average_score); DFLOAT score = (ratio + num_best_players) / num_best_players; if (msg[0]) { if (i == num_best_players - 1) strcat(msg, " and "); else strcat(msg, ", "); } if (strlen(msg) + 8 + strlen(bp->name) >= sizeof(msg)) { Set_message(msg); msg[0] = '\0'; } strcat(msg, bp->name); points = (int) (best_ratio * score); SCORE(best_players[i], points, OBJ_X_IN_BLOCKS(bp), OBJ_Y_IN_BLOCKS(bp), "[Deadly]"); } if (strlen(msg) + 64 >= sizeof(msg)) { Set_message(msg); msg[0] = '\0'; } sprintf(msg + strlen(msg), " are the Deadly Players with kill ratios of %d/%d.", Players[best_players[0]]->kills, Players[best_players[0]]->deaths); } Set_message(msg); } static void Give_individual_bonus(int ind, DFLOAT average_score) { DFLOAT ratio; DFLOAT points; ratio = (DFLOAT) Players[ind]->kills / (Players[ind]->deaths + 1); points = ratio * Rate(Players[ind]->score, average_score); SCORE(ind, points, OBJ_X_IN_BLOCKS(Players[ind]), OBJ_Y_IN_BLOCKS(Players[ind]), "[Winner]"); } static void Count_rounds(void) { char msg[MSG_LEN]; if (!roundsToPlay) { return; } ++roundsPlayed; sprintf(msg, " < Round %d out of %d completed. >", roundsPlayed, roundsToPlay); Set_message(msg); if (roundsPlayed >= roundsToPlay) { Game_Over(); } } void Team_game_over(int winning_team, const char *reason) { int i, j; DFLOAT average_score; int num_best_players; int *best_players; DFLOAT best_ratio; char msg[MSG_LEN]; if (!(best_players = (int *)malloc(NumPlayers * sizeof(int)))) { error("no mem"); End_game(); } /* Figure out the average score and who has the best kill/death ratio */ /* ratio for this round */ Compute_end_of_round_values(&average_score, &num_best_players, &best_ratio, best_players); /* Print out the results of the round */ if (winning_team != -1) { sprintf(msg, " < Team %d has won the game%s! >", winning_team, reason); sound_play_all(TEAM_WIN_SOUND); } else { sprintf(msg, " < We have a draw%s! >", reason); sound_play_all(TEAM_DRAW_SOUND); } Set_message(msg); /* Give bonus to the best player */ Give_best_player_bonus(average_score, num_best_players, best_ratio, best_players); /* Give bonuses to the winning team */ if (winning_team != -1) { for (i = 0; i < NumPlayers; i++) { if (Players[i]->team != winning_team) { continue; } if (IS_TANK_IND(i) || (BIT(Players[i]->status, PAUSE) && Players[i]->count <= 0) || (BIT(Players[i]->status, GAME_OVER) && Players[i]->mychar == 'W' && Players[i]->score == 0)) { continue; } for (j = 0; j < num_best_players; j++) { if (i == best_players[j]) { break; } } if (j == num_best_players) { Give_individual_bonus(i, average_score); } } } Reset_all_players(); Count_rounds(); free(best_players); } void Individual_game_over(int winner) { int i, j; DFLOAT average_score; int num_best_players; int *best_players; DFLOAT best_ratio; char msg[MSG_LEN]; if (!(best_players = (int *)malloc(NumPlayers * sizeof(int)))) { error("no mem"); End_game(); } /* Figure out what the average score is and who has the best kill/death */ /* ratio for this round */ Compute_end_of_round_values(&average_score, &num_best_players, &best_ratio, best_players); /* Print out the results of the round */ if (winner == -1) { Set_message(" < We have a draw! >"); sound_play_all(PLAYER_DRAW_SOUND); } else if (winner == -2) { Set_message(" < The robots have won the game! >"); /* Perhaps this should be a different sound? */ sound_play_all(PLAYER_WIN_SOUND); } else { sprintf(msg, " < %s has won the game! >", Players[winner]->name); Set_message(msg); sound_play_all(PLAYER_WIN_SOUND); } /* Give bonus to the best player */ Give_best_player_bonus(average_score, num_best_players, best_ratio, best_players); /* Give bonus to the winning player */ if (winner >= 0) { for (i = 0; i < num_best_players; i++) { if (winner == best_players[i]) { break; } } if (i == num_best_players) { Give_individual_bonus(winner, average_score); } } else if (winner == -2) { for (j = 0; j < NumPlayers; j++) { if (IS_ROBOT_IND(j)) { for (i = 0; i < num_best_players; i++) { if (j == best_players[i]) { break; } } if (i == num_best_players) { Give_individual_bonus(j, average_score); } } } } Reset_all_players(); free(best_players); } void Race_game_over(void) { player *pl; int i, j, k, bestlap = 0, num_best_players = 0, num_active_players = 0, num_ordered_players = 0; int *order; char msg[MSG_LEN]; /* * Reassign players's starting posisitions based upon * personal best lap times. */ if ((order = (int *)malloc(NumPlayers * sizeof(int))) != NULL) { for (i = 0; i < NumPlayers; i++) { pl = Players[i]; if (IS_TANK_PTR(pl)) { continue; } if (BIT(pl->status, PAUSE) || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W') || pl->best_lap <= 0) { j = i; } else { for (j = 0; j < i; j++) { if (pl->best_lap < Players[order[j]]->best_lap) { break; } if (BIT(Players[order[j]]->status, PAUSE) || (BIT(Players[order[j]]->status, GAME_OVER) && Players[order[j]]->mychar == 'W')) { break; } } } for (k = i - 1; k >= j; k--) { order[k + 1] = order[k]; } order[j] = i; num_ordered_players++; } for (i = 0; i < num_ordered_players; i++) { pl = Players[order[i]]; if (pl->home_base != World.baseorder[i].base_idx) { pl->home_base = World.baseorder[i].base_idx; for (j = 0; j < NumPlayers; j++) { if (Players[j]->conn != NOT_CONNECTED) { Send_base(Players[j]->conn, pl->id, pl->home_base); } } if (BIT(pl->status, PAUSE)) { Go_home(order[i]); } } } free(order); } for (i = NumPlayers - 1; i >= 0; i--) { pl = Players[i]; CLR_BIT(pl->status, RACE_OVER | FINISH); if (BIT(pl->status, PAUSE) || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W') || IS_TANK_PTR(pl)) { continue; } num_active_players++; /* Kill any remaining players */ if (!BIT(pl->status, GAME_OVER)) Kill_player(i); else Player_death_reset(i); if (pl != Players[i]) { continue; } if ((pl->best_lap < bestlap || bestlap == 0) && pl->best_lap > 0) { bestlap = pl->best_lap; num_best_players = 0; } if (pl->best_lap == bestlap) num_best_players++; } /* If someone completed a lap */ if (bestlap > 0) { for (i = 0; i < NumPlayers; i++) { pl = Players[i]; if (BIT(pl->status, PAUSE) || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W') || IS_TANK_PTR(pl)) { continue; } if (pl->best_lap == bestlap) { sprintf(msg, "%s %s the best lap time of %.2fs", pl->name, (num_best_players == 1) ? "had" : "shares", (DFLOAT) bestlap / FPS); Set_message(msg); SCORE(i, 5 + num_active_players, OBJ_X_IN_BLOCKS(pl), OBJ_Y_IN_BLOCKS(pl), (num_best_players == 1) ? "[Fastest lap]" : "[Joint fastest lap]"); } } updateScores = true; } else if (num_active_players > NumRobots) { Set_message("No-one even managed to complete one lap, you should be " "ashamed of yourselves."); } Reset_all_players(); Count_rounds(); } void Compute_game_status(void) { int i; player *pl; char msg[MSG_LEN]; if (round_delay_send > 0) { round_delay_send--; } if (round_delay > 0) { if (!--round_delay) { sprintf(msg, "%s starts now.", (BIT(World.rules->mode, TIMING) ? "Race" : "Round")); Set_message(msg); roundtime = maxRoundTime * FPS; /* make sure players get the full 60 seconds of allowed idle time */ for (i = 0; i < NumPlayers; i++) { Players[i]->frame_last_busy = frame_loops; } } } if (roundtime > 0) { roundtime--; } if (BIT(World.rules->mode, TIMING)) { /* * We need a completely separate scoring system for race mode. * I'm not sure how race mode should interact with team mode, * so for the moment race mode takes priority. * * Race mode and limited lives mode interact. With limited lives on, * race ends after all players have completed the course, or have died. * With limited lives mode off, the race ends when the first player * completes the course - all remaining players are then killed to * reset them. * * In limited lives mode, where the race can be run to completion, * points are awarded not just to the winner but to everyone who * completes the course (with more going to the winner). These * points are awarded as the player crosses the line. At the end * of the race, a bonus is awarded to the player with the fastest lap. * * In unlimited lives mode, just the winner and the holder of the * fastest lap get points. */ player *alive = NULL; int num_alive_players = 0, num_active_players = 0, num_finished_players = 0, num_race_over_players = 0, num_waiting_players = 0, position = 1, total_pts; DFLOAT pts; /* First count the players */ for (i = 0; i < NumPlayers; i++) { pl = Players[i]; if (BIT(pl->status, PAUSE) || IS_TANK_PTR(pl)) { continue; } if (!BIT(pl->status, GAME_OVER)) { num_alive_players++; } else if (pl->mychar == 'W') { num_waiting_players++; continue; } if (BIT(pl->status, RACE_OVER)) { num_race_over_players++; position++; } else if (BIT(pl->status, FINISH)) { num_finished_players++; } else if (!BIT(pl->status, GAME_OVER)) { alive = pl; } /* * An active player is one who is: * still in the race. * reached the finish line just now. * has finished the race in a previous frame. * died too often. */ num_active_players++; } if (num_active_players == 0 && num_waiting_players == 0) { return; } /* Now if any players are unaccounted for */ if (num_finished_players > 0) { /* * Ok, update positions. Everyone who finished the race in the last * frame gets the current position. */ /* Only play the sound for the first person to cross the finish */ if (position == 1) sound_play_all(PLAYER_WIN_SOUND); total_pts = 0; for (i = 0; i < num_finished_players; i++) { total_pts += (10 + 2 * num_active_players) >> (position - 1 + i); } pts = total_pts / num_finished_players; for (i = 0; i < NumPlayers; i++) { pl = Players[i]; if (BIT(pl->status, PAUSE) || (BIT(pl->status, GAME_OVER) && pl->mychar == 'W') || IS_TANK_PTR(pl)) { continue; } if (BIT(pl->status, FINISH)) { CLR_BIT(pl->status, FINISH); SET_BIT(pl->status, RACE_OVER); if (pts > 0) { sprintf(msg, "%s finishes %sin position %d " "scoring %.2f point%s.", pl->name, (num_finished_players == 1) ? "" : "jointly ", position, pts, (pts == 1) ? "" : "s"); Set_message(msg); sprintf(msg, "[Position %d%s]", position, (num_finished_players == 1) ? "" : " (jointly)"); SCORE(i, pts, OBJ_X_IN_BLOCKS(pl), OBJ_Y_IN_BLOCKS(pl), msg); } else { sprintf(msg, "%s finishes %sin position %d.", pl->name, (num_finished_players == 1) ? "" : "jointly ", position); Set_message(msg); } } } } /* * If the maximum allowed time for this race is over, end it. */ if (maxRoundTime > 0 && roundtime == 0) { Set_message("Timer expired. Race ends now."); Race_game_over(); return; } /* * In limited lives mode, wait for everyone to die, except * for the last player. */ if (BIT(World.rules->mode, LIMITED_LIVES)) { if (num_alive_players > 1) { return; } if (num_alive_players == 1) { if (num_finished_players + num_race_over_players == 0) { return; } if (!alive || alive->round == 0) { return; } } } else if (num_finished_players == 0) { return; } Race_game_over(); } else if (BIT(World.rules->mode, TEAM_PLAY)) { /* Do we have a winning team ? */ enum TeamState { TeamEmpty, TeamDead, TeamAlive } team_state[MAX_TEAMS]; int num_dead_teams = 0; int num_alive_teams = 0; int winning_team = -1; for (i = 0; i < MAX_TEAMS; i++) { team_state[i] = TeamEmpty; } for (i = 0; i < NumPlayers; i++) { if (IS_TANK_IND(i)) { /* Ignore tanks. */ continue; } else if (BIT(Players[i]->status, PAUSE)) { /* Ignore paused players. */ continue; } #if 0 /* not all teammode maps have treasures. */ else if (World.teams[Players[i]->team].NumTreasures == 0) { /* Ignore players with no treasure troves */ continue; } #endif else if (BIT(Players[i]->status, GAME_OVER)) { if (team_state[Players[i]->team] == TeamEmpty) { /* Assume all teammembers are dead. */ num_dead_teams++; team_state[Players[i]->team] = TeamDead; } } /* * If the player is not paused and he is not in the * game over mode and his team owns treasures then he is * considered alive. * But he may not be playing though if the rest of the team * was genocided very quickly after game reset, while this * player was still being transported back to his homebase. */ else if (team_state[Players[i]->team] != TeamAlive) { if (team_state[Players[i]->team] == TeamDead) { /* Oops! Not all teammembers are dead yet. */ num_dead_teams--; } team_state[Players[i]->team] = TeamAlive; ++num_alive_teams; /* Remember a team which was alive. */ winning_team = Players[i]->team; } } if (num_alive_teams > 1) { char *bp; int teams_with_treasure = 0; int team_win[MAX_TEAMS]; DFLOAT team_score[MAX_TEAMS]; int winners; int max_destroyed = 0; int max_left = 0; DFLOAT max_score = 0; team_t *team_ptr; /* * Game is not over if more than one team which have treasures * still have one remaining in play. Note that it is possible * for max_destroyed to be zero, in the case where a team * destroys some treasures and then all quit, and the remaining * teams did not destroy any. */ for (i = 0; i < MAX_TEAMS; i++) { team_score[i] = 0; if (team_state[i] != TeamAlive) { team_win[i] = 0; continue; } team_win[i] = 1; team_ptr = &(World.teams[i]); if (team_ptr->TreasuresDestroyed > max_destroyed) { max_destroyed = team_ptr->TreasuresDestroyed; } if ((team_ptr->TreasuresLeft > 0) || (team_ptr->NumTreasures == team_ptr->NumEmptyTreasures)) { teams_with_treasure++; } } /* * Game is not over if more than one team has treasure. */ if ((teams_with_treasure > 1 || !max_destroyed) && (roundtime != 0 || maxRoundTime <= 0)) { return; } if (maxRoundTime > 0 && roundtime == 0) { Set_message("Timer expired. Round ends now."); } /* * Find the winning team; * Team destroying most number of treasures; * If drawn; the one with most saved treasures, * If drawn; the team with the most points, * If drawn; an overall draw. */ for (winners = i = 0; i < MAX_TEAMS; i++) { if (!team_win[i]) continue; if (World.teams[i].TreasuresDestroyed == max_destroyed) { if (World.teams[i].TreasuresLeft > max_left) max_left = World.teams[i].TreasuresLeft; winning_team = i; winners++; } else { team_win[i] = 0; } } if (winners == 1) { sprintf(msg, " by destroying %d treasures", max_destroyed); Team_game_over(winning_team, msg); return; } for (i = 0; i < NumPlayers; i++) { if (BIT(Players[i]->status, PAUSE) || IS_TANK_IND(i)) { continue; } team_score[Players[i]->team] += Players[i]->score; } for (winners = i = 0; i < MAX_TEAMS; i++) { if (!team_win[i]) continue; if (World.teams[i].TreasuresLeft == max_left) { if (team_score[i] > max_score) max_score = team_score[i]; winning_team = i; winners++; } else { team_win[i] = 0; } } if (winners == 1) { sprintf(msg, " by destroying %d treasures" " and successfully defending %d", max_destroyed, max_left); Team_game_over(winning_team, msg); return; } for (winners = i = 0; i < MAX_TEAMS; i++) { if (!team_win[i]) continue; if (team_score[i] == max_score) { winning_team = i; winners++; } else { team_win[i] = 0; } } if (winners == 1) { sprintf(msg, " by destroying %d treasures, saving %d, and " "scoring %.2f points", max_destroyed, max_left, max_score); Team_game_over(winning_team, msg); return; } /* Highly unlikely */ sprintf(msg, " between teams "); bp = msg + strlen(msg); for (i = 0; i < MAX_TEAMS; i++) { if (!team_win[i]) continue; *bp++ = "0123456789"[i]; *bp++ = ','; *bp++ = ' '; } bp -= 2; *bp = '\0'; Team_game_over(-1, msg); } else if (num_dead_teams > 0) { if (num_alive_teams == 1) Team_game_over(winning_team, " by staying alive"); else Team_game_over(-1, " as everyone died"); } else { /* * num_alive_teams <= 1 && num_dead_teams == 0 * * There is a possibility that the game has ended because players * quit, the game over state is needed to reset treasures. We * must count how many treasures are missing, if there are any * the playing team (if any) wins. */ int i, treasures_destroyed; for (treasures_destroyed = i = 0; i < MAX_TEAMS; i++) treasures_destroyed += (World.teams[i].NumTreasures - World.teams[i].NumEmptyTreasures - World.teams[i].TreasuresLeft); if (treasures_destroyed) Team_game_over(winning_team, " by staying in the game"); } } else { /* Do we have a winner ? (No team play) */ int num_alive_players = 0; int num_active_players = 0; int num_alive_robots = 0; int num_active_humans = 0; int winner = -1; for (i=0; istatus, PAUSE) || IS_TANK_IND(i)) { continue; } if (!BIT(Players[i]->status, GAME_OVER)) { num_alive_players++; if (IS_ROBOT_IND(i)) { num_alive_robots++; } winner = i; /* Tag player that's alive */ } else if (IS_HUMAN_IND(i)) { num_active_humans++; } num_active_players++; } if (num_alive_players == 1 && num_active_players > 1) { Individual_game_over(winner); } else if (num_alive_players == 0 && num_active_players >= 1) { Individual_game_over(-1); } else if (num_alive_robots > 1 && num_alive_players == num_alive_robots && num_active_humans > 0) { Individual_game_over(-2); } else if (maxRoundTime > 0 && roundtime == 0) { Set_message("Timer expired. Round ends now."); Individual_game_over(-1); } } } void Delete_player(int ind) { player *pl = Players[ind]; object *obj; int i, j, id = pl->id; /* call before important player structures are destroyed */ Leave_alliance(ind); if (IS_ROBOT_PTR(pl)) { Robot_destroy(ind); } /* Delete remaining shots */ for (i = NumObjs - 1; i >= 0; i--) { obj = Obj[i]; if (obj->id == id) { if (obj->type == OBJ_BALL) { Delete_shot(i); BALL_PTR(obj)->owner = NO_ID; } else if (BIT(obj->type, OBJ_DEBRIS | OBJ_SPARK)) { /* Okay, so you want robot explosions to exist, * even if the robot left the game. */ obj->id = NO_ID; } else { if (!keepShots) { obj->life = 0; if (BIT(obj->type, OBJ_CANNON_SHOT|OBJ_MINE|OBJ_SMART_SHOT |OBJ_HEAT_SHOT|OBJ_TORPEDO)) { obj->mass = 0; } } obj->id = NO_ID; if (BIT(obj->type, OBJ_MINE)) { MINE_PTR(obj)->owner = NO_ID; } } } else { if (BIT(obj->type, OBJ_MINE)) { mineobject *mine = MINE_PTR(obj); if (mine->owner == id) { mine->owner = NO_ID; if (!keepShots) { obj->life = 0; obj->mass = 0; } } } else if (BIT(obj->type, OBJ_CANNON_SHOT)) { if (!keepShots) { obj->life = 0; obj->mass = 0; } } else if (BIT(obj->type, OBJ_BALL)) { ballobject *ball = BALL_PTR(obj); if (ball->owner == id) { ball->owner = NO_ID; } } } } if (pl->num_pulses) { for (i = 0; i < NumPulses; i++) { if (Pulses[i]->id == pl->id) { free(Pulses[i]); if (--NumPulses > i) { Pulses[i] = Pulses[NumPulses]; i--; } } } pl->num_pulses = 0; } Free_ship_shape(pl->ship); sound_close(pl); NumPlayers--; if (IS_TANK_PTR(pl)) { NumPseudoPlayers--; } if (pl->team != TEAM_NOT_SET && !IS_TANK_PTR(pl)) { World.teams[pl->team].NumMembers--; if (teamShareScore) TEAM_SCORE(pl->team, -(pl->score)); /* recalculate teamscores */ if (IS_ROBOT_PTR(pl)) World.teams[pl->team].NumRobots--; } if (IS_ROBOT_PTR(pl)) { NumRobots--; } /* * Swap entry no 'ind' with the last one. * * Change the Players[] pointer array to have Players[ind] point to * a valid player and move our leaving player to Players[NumPlayers]. */ pl = Players[NumPlayers]; /* Swap pointers... */ Players[NumPlayers] = Players[ind]; Players[ind] = pl; pl = Players[NumPlayers]; /* Restore pointer. */ GetInd[Players[ind]->id] = ind; GetInd[Players[NumPlayers]->id] = NumPlayers; Check_team_members(pl->team); for (i = NumPlayers - 1; i >= 0; i--) { if (IS_TANK_IND(i) && Players[i]->lock.pl_id == id) { /* remove tanks which were released by this player. */ if (keepShots) { Players[i]->lock.pl_id = NO_ID; } else { Delete_player(i); } continue; } if (BIT(Players[i]->lock.tagged, LOCK_PLAYER|LOCK_VISIBLE) && (Players[i]->lock.pl_id == id || NumPlayers <= 1)) { CLR_BIT(Players[i]->lock.tagged, LOCK_PLAYER|LOCK_VISIBLE); CLR_BIT(Players[i]->used, HAS_TRACTOR_BEAM); } if (IS_ROBOT_IND(i) && Robot_war_on_player(i) == id) { Robot_reset_war(i); } for (j = 0; j < LOCKBANK_MAX; j++) { if (Players[i]->lockbank[j] == id) Players[i]->lockbank[j] = NOT_CONNECTED; } for (j = 0; j < MAX_RECORDED_SHOVES; j++) { if (Players[i]->shove_record[j].pusher_id == id) { Players[i]->shove_record[j].pusher_id = NO_ID; } } } for (i = NumPlayers - 1; i >= 0; i--) { if (Players[i]->conn != NOT_CONNECTED) { Send_leave(Players[i]->conn, id); } else if (IS_TANK_IND(i)) { if (Players[i]->lock.pl_id == id) { Delete_player(i); } } } release_ID(id); } void Detach_ball(int ind, int obj) { int i, cnt; if (obj == -1 || BALL_PTR(Obj[obj]) == Players[ind]->ball) { Players[ind]->ball = NULL; CLR_BIT(Players[ind]->used, HAS_CONNECTOR); } if (BIT(Players[ind]->have, HAS_BALL)) { for (cnt = i = 0; i < NumObjs; i++) { if (Obj[i]->type == OBJ_BALL && Obj[i]->id == Players[ind]->id) { if (obj == -1 || obj == i) { Obj[i]->id = NO_ID; /* Don't reset owner so you can throw balls */ } else { cnt++; } } } if (cnt == 0) CLR_BIT(Players[ind]->have, HAS_BALL); else { sound_play_sensors(Players[ind]->pos.x, Players[ind]->pos.y, DROP_BALL_SOUND); } } } void Kill_player(int ind) { Explode_fighter(ind); Player_death_reset(ind); } void Player_death_reset(int ind) { player *pl = Players[ind]; long minfuel; int i; if (IS_TANK_PTR(pl)) { Delete_player(ind); return; } Detach_ball(ind, -1); if (BIT(pl->used, HAS_AUTOPILOT) || BIT(pl->status, HOVERPAUSE)) { CLR_BIT(pl->status, HOVERPAUSE); Autopilot (ind, 0); } pl->vel.x = pl->vel.y = 0.0; pl->acc.x = pl->acc.y = 0.0; pl->emptymass = pl->mass = ShipMass; pl->status |= DEF_BITS; pl->status &= ~(KILL_BITS); for (i = 0; i < NUM_ITEMS; i++) { if (!BIT(1U << i, ITEM_BIT_FUEL | ITEM_BIT_TANK)) { pl->item[i] = World.items[i].initial; } } pl->forceVisible = 0; pl->shot_max = ShotsMax; pl->count = MAX(RECOVERY_DELAY, pl->count); pl->ecmcount = 0; pl->emergency_thrust_left = 0; pl->emergency_thrust_max = 0; pl->emergency_shield_left = 0; pl->emergency_shield_max = 0; pl->phasing_left = 0; pl->phasing_max = 0; pl->damaged = 0; pl->stunned = 0; pl->lock.distance = 0; pl->fuel.sum = (long)(pl->fuel.sum*0.90); /* Loose 10% of fuel */ minfuel = (World.items[ITEM_FUEL].initial * FUEL_SCALE_FACT); minfuel += (int)(rfrac() * (1 + minfuel) * 0.2f); pl->fuel.sum = MAX(pl->fuel.sum, minfuel); Player_init_fuel(ind, pl->fuel.sum); /*-BA Handle the combination of limited life games and *-BA robotLeaveLife by making a robot leave iff it gets *-BA eliminated in any round. Means that robotLeaveLife *-BA is ignored, but that robotsLeave is still respected. *-KK Added check on race mode. Since in race mode everyone *-KK gets killed at the end of the round, all robots would *-KK be replaced in the next round. I don't think that's *-KK the Right Thing to do. *-KK Also, only check a robot's score at the end of the round. *-KK 27-2-98 Check on team mode too. It's very confusing to *-KK have different robots in your team every round. */ if (!BIT(pl->status, PAUSE)) { pl->deaths++; if (BIT(World.rules->mode, LIMITED_LIVES)) { pl->life--; if (pl->life == -1) { if (IS_ROBOT_PTR(pl)) { if (!BIT(World.rules->mode, TIMING|TEAM_PLAY) || (robotsLeave && pl->score < robotLeaveScore)) { Robot_delete(ind, false); return; } } pl->life = 0; SET_BIT(pl->status, GAME_OVER); pl->mychar = 'D'; Player_lock_closest(ind, 0); } } else { pl->life++; } } pl->have = DEF_HAVE; pl->used |= DEF_USED; pl->used &= ~(USED_KILL); pl->used &= pl->have; } /* determines if two players are immune to eachother */ int Team_immune(int id1, int id2) { int ind1, ind2; if (id1 == id2) { /* owned stuff is never team immune */ return 0; } if (!teamImmunity) { return 0; } if (id1 == NO_ID || id2 == NO_ID) { /* can't find owner for cannon stuff */ return 0; } ind1 = GetInd[id1]; ind2 = GetInd[id2]; if (TEAM(ind1, ind2)) { /* players are teammates */ return 1; } if (ALLIANCE(ind1, ind2)) { /* players are allies */ return 1; } return 0; }