mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-11-29 23:22:55 +00:00
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] > level.teamTimes[j]) count++;
|
|
}
|
|
else if (level.teamScores[i] < level.teamScores[j]) 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;
|
|
}
|
|
|