mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-11-29 15:12:07 +00:00
0a5535d81a
There was an off-by-one when checking team scores array. Red checked FFA team, blue checked red team, and so on for green and yellow teams. So when blue was winning the game thought score was tied (FFA and red scores at 0) and did not check if blue reached the capture limit.
548 lines
13 KiB
C
548 lines
13 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2002-2015 Q3Rally Team (Per Thormann - q3rally@gmail.com)
|
|
|
|
This file is part of q3rally source code.
|
|
|
|
q3rally source code 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.
|
|
|
|
q3rally source code 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 q3rally; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
|
|
|
|
int GetTeamAtRank( int rank ){
|
|
int i, j, count;
|
|
int ranks[4];
|
|
int counts[4];
|
|
|
|
for (i = 0; i < 4; i++){
|
|
counts[i] = TeamCount( -1, TEAM_RED + i );
|
|
ranks[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++){
|
|
if (!counts[i]) continue;
|
|
|
|
count = 0;
|
|
for (j = 0; j < 4; j++){
|
|
if (!counts[j]) continue;
|
|
|
|
if (isRallyRace()){
|
|
if (level.teamTimes[i + TEAM_RED] > level.teamTimes[j + TEAM_RED]) count++;
|
|
}
|
|
else if (level.teamScores[i + TEAM_RED] < level.teamScores[j + TEAM_RED]) count++;
|
|
}
|
|
|
|
while( count < 4 && ranks[count] ) count++; // rank is taken so move to the next one
|
|
if (count < 4)
|
|
ranks[count] = TEAM_RED + i;
|
|
}
|
|
|
|
if (g_gametype.integer == GT_CTF && rank > 2){
|
|
return -1;
|
|
}
|
|
else {
|
|
return ranks[rank-1];
|
|
}
|
|
}
|
|
|
|
|
|
// UPDATE - send as command string instead?
|
|
void Cmd_RacePositions_f( void ) {
|
|
char entry[1024];
|
|
char string[1400];
|
|
gentity_t *player;
|
|
int i, count, j, stringlength;
|
|
|
|
string[0] = 0;
|
|
stringlength = 0;
|
|
|
|
for(i = 0, count = 0; i < level.maxclients; i++){
|
|
player = &g_entities[i];
|
|
if (!player->inuse) continue;
|
|
if (!player->client) continue;
|
|
|
|
Com_sprintf (entry, sizeof(entry)," %i %i", player->s.clientNum, player->client->ps.stats[STAT_POSITION]);
|
|
j = strlen(entry);
|
|
if (stringlength + j > 1024)
|
|
break;
|
|
strcpy (string + stringlength, entry);
|
|
stringlength += j;
|
|
|
|
count++;
|
|
}
|
|
|
|
G_LogPrintf("%s\n", va("positions %i%s", count, string));
|
|
trap_SendServerCommand( -1, va("positions %i%s\n", count, string) );
|
|
}
|
|
|
|
|
|
void Cmd_Times_f( gentity_t *ent ) {
|
|
/*
|
|
gentity_t *player;
|
|
int times[4];
|
|
int i, count;
|
|
|
|
for(i = 0; i < 4; i++){
|
|
times[i] = 0;
|
|
}
|
|
|
|
for(i = 0; i < MAX_CLIENTS; i++){
|
|
player = &g_entities[i];
|
|
if (!player->inuse) continue;
|
|
if (!player->client) continue;
|
|
if (player->client->sess.sessionTeam == TEAM_SPECTATOR) continue;
|
|
if (player->client->sess.sessionTeam == TEAM_FREE) continue;
|
|
if (!level.startRaceTime) continue;
|
|
|
|
if (player->client->finishRaceTime){
|
|
times[player->client->sess.sessionTeam - TEAM_RED] +=
|
|
(player->client->finishRaceTime - level.startRaceTime);
|
|
}
|
|
else {
|
|
times[player->client->sess.sessionTeam - TEAM_RED] +=
|
|
(level.time - level.startRaceTime);
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer == GT_TEAM_RACING_DM){
|
|
for(i = 0; i < 4; i++){
|
|
if (level.teamScores[i + TEAM_RED] > 0){
|
|
times[i] -= level.teamScores[i + TEAM_RED] * TIME_BONUS_PER_FRAG;
|
|
}
|
|
|
|
if (times[i] < 0)
|
|
times[i] = 0;
|
|
|
|
count = TeamCount( -1, TEAM_RED+i );
|
|
if (count){
|
|
times[i] /= count;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for(i = 0; i < 4; i++){
|
|
if (times[i] < 0)
|
|
times[i] = 0;
|
|
|
|
count = TeamCount( -1, TEAM_RED+i );
|
|
if (count){
|
|
times[i] /= count;
|
|
}
|
|
}
|
|
}
|
|
|
|
trap_SendServerCommand( ent-g_entities, va("times %i %i %i %i\n",
|
|
times[0], times[1], times[2], times[3]) );
|
|
*/
|
|
}
|
|
|
|
|
|
/*
|
|
================================================================================
|
|
GetDistanceToMarker
|
|
|
|
Used to calculate how far a player is from the marker.
|
|
Called to find out race positions of players.
|
|
================================================================================
|
|
*/
|
|
float GetDistanceToMarker( gentity_t *player, float markerNumber )
|
|
{
|
|
gentity_t *ent = NULL;
|
|
vec3_t dist;
|
|
|
|
if ( !markerNumber )
|
|
return 1<<30;
|
|
|
|
while ( (ent = G_Find (ent, FOFS(classname), "rally_checkpoint")) != NULL )
|
|
{
|
|
if( ent->number == markerNumber )
|
|
break;
|
|
}
|
|
|
|
if ( ent )
|
|
{
|
|
VectorSubtract(player->r.currentOrigin, ent->s.origin, dist);
|
|
return VectorLength(dist);
|
|
}
|
|
else
|
|
return 1<<30;
|
|
}
|
|
|
|
/*
|
|
================================================================================
|
|
IsCarAhead
|
|
|
|
Returns true if player one is ahead of two.
|
|
================================================================================
|
|
*/
|
|
qboolean IsCarAhead(gentity_t *one, gentity_t *two){
|
|
float dist1, dist2;
|
|
int time1, time2;
|
|
|
|
if (one->client->finishRaceTime && two->client->finishRaceTime){
|
|
time1 = one->client->finishRaceTime - level.startRaceTime;
|
|
if (one->client->ps.persistant[PERS_SCORE] > 0 && !isRallyNonDMRace()){
|
|
time1 -= (one->client->ps.persistant[PERS_SCORE] * TIME_BONUS_PER_FRAG);
|
|
}
|
|
|
|
time2 = two->client->finishRaceTime - level.startRaceTime;
|
|
if (two->client->ps.persistant[PERS_SCORE] > 0 && !isRallyNonDMRace()){
|
|
time2 -= (two->client->ps.persistant[PERS_SCORE] * TIME_BONUS_PER_FRAG);
|
|
}
|
|
|
|
if (time1 < time2){ // use frag modified times
|
|
// Com_Printf("Car 1 finished the race with less time than car 2\n");
|
|
return qtrue;
|
|
}
|
|
else {
|
|
// Com_Printf("Car 2 finished the race with less time than car 1\n");
|
|
return qfalse;
|
|
}
|
|
}
|
|
else if (one->client->finishRaceTime){
|
|
// Com_Printf("Car 1 finished the race, car 2 hasn't\n");
|
|
return qtrue;
|
|
}
|
|
else if (two->client->finishRaceTime){
|
|
// Com_Printf("Car 2 finished the race, car 1 hasn't\n");
|
|
return qfalse;
|
|
}
|
|
else if (one->currentLap < two->currentLap){
|
|
// Com_Printf("Car 1 is a lap behind car 2\n");
|
|
return qfalse;
|
|
}
|
|
else if (one->currentLap == two->currentLap && one->number < two->number){
|
|
// Com_Printf("Car 1 hat a target marker that is behind car 2's\n");
|
|
return qfalse;
|
|
}
|
|
else if (one->currentLap == two->currentLap && one->number == two->number){
|
|
dist1 = GetDistanceToMarker( one, one->number );
|
|
dist2 = GetDistanceToMarker( two, two->number );
|
|
|
|
if (dist1 > dist2){
|
|
// Com_Printf("Car 1 is %f to marker %i and car 2 is %f\n", dist1, one->number, dist2);
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
================================================================================
|
|
CalculatePlayerPositions
|
|
|
|
Calculates the order of all racers
|
|
================================================================================
|
|
*/
|
|
void CalculatePlayerPositions( void )
|
|
{
|
|
gentity_t *ent, *leader, *cur, *last;
|
|
int position;
|
|
qboolean positionChanged;
|
|
|
|
// if (level.startRaceTime + FRAMETIME > level.time || level.startRaceTime == 0){
|
|
// return;
|
|
// }
|
|
if (!isRallyRace()){
|
|
return;
|
|
}
|
|
|
|
positionChanged = qfalse;
|
|
leader = ent = last = NULL;
|
|
while ( (ent = G_Find (ent, FOFS(classname), "player")) != NULL )
|
|
{
|
|
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) continue;
|
|
// if ( isRaceObserver(ent->s.number) ) continue;
|
|
|
|
ent->carBehind = NULL;
|
|
|
|
if ( leader == NULL )
|
|
{
|
|
leader = ent;
|
|
continue;
|
|
}
|
|
|
|
cur = leader;
|
|
if ( IsCarAhead( ent, cur ) )
|
|
{
|
|
ent->carBehind = cur;
|
|
leader = ent;
|
|
continue;
|
|
}
|
|
|
|
while ( cur->carBehind != NULL )
|
|
{
|
|
if ( IsCarAhead( ent, cur->carBehind ) )
|
|
{
|
|
// ent->carBehind = cur->carBehind;
|
|
// cur->carBehind = ent;
|
|
last = cur;
|
|
cur = cur->carBehind;
|
|
break;
|
|
}
|
|
|
|
last = cur;
|
|
cur = cur->carBehind;
|
|
}
|
|
|
|
if ( IsCarAhead( ent, cur ) )
|
|
{
|
|
// cur->carBehind = NULL;
|
|
ent->carBehind = cur;
|
|
if (last) {
|
|
last->carBehind = ent;
|
|
}
|
|
}
|
|
else {
|
|
cur->carBehind = ent;
|
|
ent->carBehind = NULL;
|
|
}
|
|
}
|
|
|
|
if ( leader == NULL )
|
|
return;
|
|
|
|
cur = leader;
|
|
position = 1;
|
|
|
|
while( cur->carBehind != NULL )
|
|
{
|
|
if ( position != cur->client->ps.stats[STAT_POSITION] && cur->client ){
|
|
cur->client->ps.stats[STAT_POSITION] = position;
|
|
|
|
positionChanged = qtrue;
|
|
}
|
|
|
|
cur = cur->carBehind;
|
|
position++;
|
|
}
|
|
|
|
if ( position != cur->client->ps.stats[STAT_POSITION] && cur->client ){
|
|
cur->client->ps.stats[STAT_POSITION] = position;
|
|
|
|
positionChanged = qtrue;
|
|
}
|
|
|
|
if ( positionChanged )
|
|
{
|
|
Cmd_RacePositions_f();
|
|
CalculateRanks();
|
|
}
|
|
}
|
|
|
|
|
|
void RallyRace_Think( gentity_t *ent ){
|
|
ent->nextthink = level.time + 200;
|
|
|
|
CalculatePlayerPositions();
|
|
}
|
|
|
|
void RaceCountdown( char *s, int secondsLeft ){
|
|
trap_SendServerCommand( -1, va("rc \"%s\" %d", s, secondsLeft) );
|
|
}
|
|
|
|
void RallyStarter_Think( gentity_t *ent ){
|
|
gentity_t *player, *t;
|
|
int i, count;
|
|
qboolean start;
|
|
|
|
if (level.startRaceTime){
|
|
return;
|
|
}
|
|
|
|
// if no checkpoints dont do start sequence
|
|
if (isRallyRace()){
|
|
t = NULL;
|
|
t = G_Find (t, FOFS(classname), "rally_checkpoint");
|
|
if (t == NULL){
|
|
// start race right away
|
|
level.startRaceTime = level.time;
|
|
trap_SendServerCommand( -1, va("raceTime %i", level.startRaceTime) );
|
|
CenterPrint_All("GO..");
|
|
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
}
|
|
ent->nextthink = level.time + 1000;
|
|
t = NULL;
|
|
|
|
if ( ent->number == 0 ){
|
|
|
|
if( level.time - level.startTime < 7500 )
|
|
return;
|
|
|
|
start = qtrue;
|
|
for (i = 0, count = 0; i < MAX_CLIENTS; i++){
|
|
player = &g_entities[i];
|
|
if (!player->inuse) continue;
|
|
if (!player->client) continue;
|
|
if (player->client->sess.sessionTeam == TEAM_SPECTATOR) continue;
|
|
// bots are always ready
|
|
|
|
count++;
|
|
|
|
if (player->r.svFlags & SVF_BOT) continue;
|
|
|
|
if ( !player->ready ){
|
|
start = qfalse;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !count ){
|
|
return;
|
|
}
|
|
else if ( start && count ){
|
|
ent->number = 3;
|
|
}
|
|
else if ( level.time >= level.startTime + (g_forceEngineStart.integer * 1000) ) {
|
|
ent->number = 3; // force race start
|
|
}
|
|
else if (ent->number == 0 && level.time > level.startTime + (g_forceEngineStart.integer * 1000) - 10000){
|
|
CenterPrint_All( va("Forced engine start in %i...", 10 - ((level.time - (level.startTime + (g_forceEngineStart.integer * 1000) - 10000)) / 1000)) );
|
|
return;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( ent->pain_debounce_time == 0 )
|
|
ent->pain_debounce_time = level.time;
|
|
|
|
if ( level.time > ent->pain_debounce_time + 5000 ){
|
|
level.startRaceTime = level.time;
|
|
|
|
trap_SendServerCommand( -1, va("raceTime %i", level.startRaceTime) );
|
|
RaceCountdown("GO!", 0);
|
|
|
|
Rally_Sound( ent, EV_GLOBAL_SOUND, CHAN_ANNOUNCER, G_SoundIndex("sound/rally/race/go.wav") );
|
|
|
|
if (g_gametype.integer != GT_DERBY)
|
|
ent->think = RallyRace_Think;
|
|
}
|
|
else if ( level.time > ent->pain_debounce_time + 4000 ){
|
|
RaceCountdown("1", 1);
|
|
|
|
Rally_Sound( ent, EV_GLOBAL_SOUND, CHAN_ANNOUNCER, G_SoundIndex("sound/rally/race/one.wav") );
|
|
ent->number = -1;
|
|
}
|
|
else if ( level.time > ent->pain_debounce_time + 3000 ){
|
|
RaceCountdown("2", 2);
|
|
|
|
Rally_Sound( ent, EV_GLOBAL_SOUND, CHAN_ANNOUNCER, G_SoundIndex("sound/rally/race/two.wav") );
|
|
ent->number = 1;
|
|
}
|
|
else if ( level.time > ent->pain_debounce_time + 2000 ){
|
|
RaceCountdown("3", 3);
|
|
|
|
Rally_Sound( ent, EV_GLOBAL_SOUND, CHAN_ANNOUNCER, G_SoundIndex("sound/rally/race/three.wav") );
|
|
ent->number = 2;
|
|
}
|
|
else {
|
|
CenterPrint_All("Starting Race...");
|
|
}
|
|
}
|
|
|
|
void CreateRallyStarter( void ) {
|
|
gentity_t *ent;
|
|
|
|
ent = G_Spawn();
|
|
|
|
ent->think = RallyStarter_Think;
|
|
ent->nextthink = level.time + 2000;
|
|
ent->number = 0;
|
|
ent->classname = "rally_starter";
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
SelectLastMarkerForSpawn
|
|
|
|
Places cars at the last marker they visited during a race
|
|
|
|
============
|
|
*/
|
|
gentity_t *SelectLastMarkerForSpawn( gentity_t *ent, vec3_t origin, vec3_t angles, qboolean isbot ) {
|
|
gentity_t *spot;
|
|
int lastMarker;
|
|
|
|
spot = NULL;
|
|
lastMarker = ent->number - 1;
|
|
if (lastMarker <= 0){
|
|
lastMarker = level.numCheckpoints;
|
|
}
|
|
|
|
while ((spot = G_Find (spot, FOFS(classname), "rally_checkpoint")) != NULL) {
|
|
if ( spot->number == lastMarker) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !spot ) {
|
|
return SelectSpawnPoint( vec3_origin, origin, angles, isbot );
|
|
}
|
|
|
|
// spawn at last checkpoint
|
|
VectorCopy (spot->s.origin, origin);
|
|
VectorCopy (spot->s.angles, angles);
|
|
|
|
return spot;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
SelectGridPositionSpawn
|
|
|
|
Places cars at the start line in order, so that no one is telefragged
|
|
|
|
============
|
|
*/
|
|
gentity_t *SelectGridPositionSpawn( gentity_t *ent, vec3_t origin, vec3_t angles, qboolean isbot ) {
|
|
gentity_t *spot;
|
|
int gridPosition;
|
|
|
|
spot = NULL;
|
|
gridPosition = 1;
|
|
while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) {
|
|
if ( (spot->number == gridPosition || !spot->number) && !SpotWouldTelefrag( spot )) {
|
|
break;
|
|
}
|
|
else if (spot->number == gridPosition){
|
|
spot = NULL; // found spawn but someone is already there so restart search
|
|
gridPosition++;
|
|
}
|
|
}
|
|
|
|
if ( !spot || SpotWouldTelefrag( spot ) ) {
|
|
// FIXME: put into spectator mode instead?
|
|
G_Printf("Warning: No info_player_start found for race spawn, trying info_player_deathmatch\n");
|
|
return SelectSpawnPoint( vec3_origin, origin, angles, isbot );
|
|
}
|
|
|
|
VectorCopy (spot->s.origin, origin);
|
|
origin[2] += 9;
|
|
VectorCopy (spot->s.angles, angles);
|
|
|
|
return spot;
|
|
}
|
|
|