2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
7
Quake III Arena source code is free software; you can redistribute it
8
and/or modify it under the terms of the GNU General Public License as
9
published by the Free Software Foundation; either version 2 of the License,
10
or (at your option) any later version.
12
Quake III Arena source code is distributed in the hope that it will be
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with Quake III Arena source code; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
===========================================================================
33
int modificationCount; // for tracking changes
34
qboolean trackChange; // track this variable, and announce if changed
35
qboolean teamShader; // track and if changed, update shader state
38
gentity_t g_entities[MAX_GENTITIES];
39
gclient_t g_clients[MAX_CLIENTS];
45
vmCvar_t g_capturelimit;
46
vmCvar_t g_friendlyFire;
49
vmCvar_t g_maxclients;
50
vmCvar_t g_maxGameClients;
56
vmCvar_t g_quadfactor;
57
vmCvar_t g_forcerespawn;
58
vmCvar_t g_inactivity;
60
vmCvar_t g_debugDamage;
61
vmCvar_t g_debugAlloc;
62
vmCvar_t g_weaponRespawn;
63
vmCvar_t g_weaponTeamRespawn;
65
vmCvar_t g_synchronousClients;
70
vmCvar_t g_logfileSync;
72
vmCvar_t g_podiumDist;
73
vmCvar_t g_podiumDrop;
75
vmCvar_t g_teamAutoJoin;
76
vmCvar_t g_teamForceBalance;
79
vmCvar_t g_smoothClients;
83
vmCvar_t g_listEntity;
85
vmCvar_t g_obeliskHealth;
86
vmCvar_t g_obeliskRegenPeriod;
87
vmCvar_t g_obeliskRegenAmount;
88
vmCvar_t g_obeliskRespawnDelay;
89
vmCvar_t g_cubeTimeout;
92
vmCvar_t g_singlePlayer;
93
vmCvar_t g_enableDust;
94
vmCvar_t g_enableBreath;
95
vmCvar_t g_proxMineTimeout;
98
// bk001129 - made static to avoid aliasing
99
static cvarTable_t gameCvarTable[] = {
100
// don't override the cheat state set by the system
101
{ &g_cheats, "sv_cheats", "", 0, 0, qfalse },
104
{ NULL, "gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
105
{ NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse },
106
{ &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse },
107
{ NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
110
{ &g_gametype, "g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH, 0, qfalse },
112
{ &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
113
{ &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
115
// change anytime vars
116
{ &g_dmflags, "dmflags", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue },
117
{ &g_fraglimit, "fraglimit", "20", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
118
{ &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
119
{ &g_capturelimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
121
{ &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse },
123
{ &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE, 0, qtrue },
125
{ &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE },
126
{ &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE },
128
{ &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue },
129
{ &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue },
130
{ &g_logfile, "g_logfile", "games.log", CVAR_ARCHIVE, 0, qfalse },
131
{ &g_logfileSync, "g_logfileSync", "0", CVAR_ARCHIVE, 0, qfalse },
133
{ &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse },
135
{ &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse },
136
{ &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse },
138
{ &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
140
{ &g_dedicated, "dedicated", "0", 0, 0, qfalse },
142
{ &g_speed, "g_speed", "320", 0, 0, qtrue },
143
{ &g_gravity, "g_gravity", "800", 0, 0, qtrue },
144
{ &g_knockback, "g_knockback", "1000", 0, 0, qtrue },
145
{ &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue },
146
{ &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue },
147
{ &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
148
{ &g_forcerespawn, "g_forcerespawn", "20", 0, 0, qtrue },
149
{ &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
150
{ &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
151
{ &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
152
{ &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
153
{ &g_motd, "g_motd", "", 0, 0, qfalse },
154
{ &g_blood, "com_blood", "1", 0, 0, qfalse },
156
{ &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
157
{ &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
159
{ &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
160
{ &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
163
{ &g_obeliskHealth, "g_obeliskHealth", "2500", 0, 0, qfalse },
164
{ &g_obeliskRegenPeriod, "g_obeliskRegenPeriod", "1", 0, 0, qfalse },
165
{ &g_obeliskRegenAmount, "g_obeliskRegenAmount", "15", 0, 0, qfalse },
166
{ &g_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO, 0, qfalse },
168
{ &g_cubeTimeout, "g_cubeTimeout", "30", 0, 0, qfalse },
169
{ &g_redteam, "g_redteam", "Stroggs", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO , 0, qtrue, qtrue },
170
{ &g_blueteam, "g_blueteam", "Pagans", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO , 0, qtrue, qtrue },
171
{ &g_singlePlayer, "ui_singlePlayerActive", "", 0, 0, qfalse, qfalse },
173
{ &g_enableDust, "g_enableDust", "0", CVAR_SERVERINFO, 0, qtrue, qfalse },
174
{ &g_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO, 0, qtrue, qfalse },
175
{ &g_proxMineTimeout, "g_proxMineTimeout", "20000", 0, 0, qfalse },
177
{ &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
178
{ &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
179
{ &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
181
{ &g_rankings, "g_rankings", "0", 0, 0, qfalse}
185
// bk001129 - made static to avoid aliasing
186
static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[0] );
189
void G_InitGame( int levelTime, int randomSeed, int restart );
190
void G_RunFrame( int levelTime );
191
void G_ShutdownGame( int restart );
192
void CheckExitRules( void );
199
This is the only way control passes into the module.
200
This must be the very first function compiled into the .q3vm file
203
intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) {
206
G_InitGame( arg0, arg1, arg2 );
209
G_ShutdownGame( arg0 );
211
case GAME_CLIENT_CONNECT:
212
return (intptr_t)ClientConnect( arg0, arg1, arg2 );
213
case GAME_CLIENT_THINK:
216
case GAME_CLIENT_USERINFO_CHANGED:
217
ClientUserinfoChanged( arg0 );
219
case GAME_CLIENT_DISCONNECT:
220
ClientDisconnect( arg0 );
222
case GAME_CLIENT_BEGIN:
225
case GAME_CLIENT_COMMAND:
226
ClientCommand( arg0 );
231
case GAME_CONSOLE_COMMAND:
232
return ConsoleCommand();
233
case BOTAI_START_FRAME:
234
return BotAIStartFrame( arg0 );
241
void QDECL G_Printf( const char *fmt, ... ) {
245
va_start (argptr, fmt);
246
vsprintf (text, fmt, argptr);
252
void QDECL G_Error( const char *fmt, ... ) {
256
va_start (argptr, fmt);
257
vsprintf (text, fmt, argptr);
267
Chain together all entities with a matching team field.
268
Entity teams are used for item groups and multi-entity mover groups.
270
All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
271
All but the last will have the teamchain field set to the next one
274
void G_FindTeams( void ) {
281
for ( i=1, e=g_entities+i ; i < level.num_entities ; i++,e++ ){
286
if (e->flags & FL_TEAMSLAVE)
291
for (j=i+1, e2=e+1 ; j < level.num_entities ; j++,e2++)
297
if (e2->flags & FL_TEAMSLAVE)
299
if (!strcmp(e->team, e2->team))
302
e2->teamchain = e->teamchain;
305
e2->flags |= FL_TEAMSLAVE;
307
// make sure that targets only point at the master
308
if ( e2->targetname ) {
309
e->targetname = e2->targetname;
310
e2->targetname = NULL;
316
G_Printf ("%i teams with %i entities\n", c, c2);
319
void G_RemapTeamShaders( void ) {
322
float f = level.time * 0.001;
323
Com_sprintf( string, sizeof(string), "team_icon/%s_red", g_redteam.string );
324
AddRemap("textures/ctf2/redteam01", string, f);
325
AddRemap("textures/ctf2/redteam02", string, f);
326
Com_sprintf( string, sizeof(string), "team_icon/%s_blue", g_blueteam.string );
327
AddRemap("textures/ctf2/blueteam01", string, f);
328
AddRemap("textures/ctf2/blueteam02", string, f);
329
trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig());
339
void G_RegisterCvars( void ) {
342
qboolean remapped = qfalse;
344
for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
345
trap_Cvar_Register( cv->vmCvar, cv->cvarName,
346
cv->defaultString, cv->cvarFlags );
348
cv->modificationCount = cv->vmCvar->modificationCount;
350
if (cv->teamShader) {
356
G_RemapTeamShaders();
360
if ( g_gametype.integer < 0 || g_gametype.integer >= GT_MAX_GAME_TYPE ) {
361
G_Printf( "g_gametype %i is out of range, defaulting to 0\n", g_gametype.integer );
362
trap_Cvar_Set( "g_gametype", "0" );
365
level.warmupModificationCount = g_warmup.modificationCount;
373
void G_UpdateCvars( void ) {
376
qboolean remapped = qfalse;
378
for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
380
trap_Cvar_Update( cv->vmCvar );
382
if ( cv->modificationCount != cv->vmCvar->modificationCount ) {
383
cv->modificationCount = cv->vmCvar->modificationCount;
385
if ( cv->trackChange ) {
386
trap_SendServerCommand( -1, va("print \"Server: %s changed to %s\n\"",
387
cv->cvarName, cv->vmCvar->string ) );
390
if (cv->teamShader) {
398
G_RemapTeamShaders();
408
void G_InitGame( int levelTime, int randomSeed, int restart ) {
411
G_Printf ("------- Game Initialization -------\n");
412
G_Printf ("gamename: %s\n", GAMEVERSION);
413
G_Printf ("gamedate: %s\n", __DATE__);
423
// set some level globals
424
memset( &level, 0, sizeof( level ) );
425
level.time = levelTime;
426
level.startTime = levelTime;
428
level.snd_fry = G_SoundIndex("sound/player/fry.wav"); // FIXME standing in lava / slime
430
if ( g_gametype.integer != GT_SINGLE_PLAYER && g_logfile.string[0] ) {
431
if ( g_logfileSync.integer ) {
432
trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND_SYNC );
434
trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND );
436
if ( !level.logFile ) {
437
G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logfile.string );
439
char serverinfo[MAX_INFO_STRING];
441
trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
443
G_LogPrintf("------------------------------------------------------------\n" );
444
G_LogPrintf("InitGame: %s\n", serverinfo );
447
G_Printf( "Not logging to disk.\n" );
450
G_InitWorldSession();
452
// initialize all entities for this game
453
memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) );
454
level.gentities = g_entities;
456
// initialize all clients for this game
457
level.maxclients = g_maxclients.integer;
458
memset( g_clients, 0, MAX_CLIENTS * sizeof(g_clients[0]) );
459
level.clients = g_clients;
461
// set client fields on player ents
462
for ( i=0 ; i<level.maxclients ; i++ ) {
463
g_entities[i].client = level.clients + i;
466
// always leave room for the max number of clients,
467
// even if they aren't all used, so numbers inside that
468
// range are NEVER anything but clients
469
level.num_entities = MAX_CLIENTS;
471
// let the server system know where the entites are
472
trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
473
&level.clients[0].ps, sizeof( level.clients[0] ) );
475
// reserve some spots for dead player bodies
478
ClearRegisteredItems();
480
// parse the key/value pairs and spawn gentities
481
G_SpawnEntitiesFromString();
483
// general initialization
486
// make sure we have flags for CTF, etc
487
if( g_gametype.integer >= GT_TEAM ) {
491
SaveRegisteredItems();
493
G_Printf ("-----------------------------------\n");
495
if( g_gametype.integer == GT_SINGLE_PLAYER || trap_Cvar_VariableIntegerValue( "com_buildScript" ) ) {
496
G_ModelIndex( SP_PODIUM_MODEL );
497
G_SoundIndex( "sound/player/gurp1.wav" );
498
G_SoundIndex( "sound/player/gurp2.wav" );
501
if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
502
BotAISetup( restart );
503
BotAILoadMap( restart );
504
G_InitBots( restart );
507
G_RemapTeamShaders();
518
void G_ShutdownGame( int restart ) {
519
G_Printf ("==== ShutdownGame ====\n");
521
if ( level.logFile ) {
522
G_LogPrintf("ShutdownGame:\n" );
523
G_LogPrintf("------------------------------------------------------------\n" );
524
trap_FS_FCloseFile( level.logFile );
527
// write all the client session data so we can get it back
528
G_WriteSessionData();
530
if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
531
BotAIShutdown( restart );
537
//===================================================================
539
void QDECL Com_Error ( int level, const char *error, ... ) {
543
va_start (argptr, error);
544
vsprintf (text, error, argptr);
547
G_Error( "%s", text);
550
void QDECL Com_Printf( const char *msg, ... ) {
554
va_start (argptr, msg);
555
vsprintf (text, msg, argptr);
558
G_Printf ("%s", text);
562
========================================================================
564
PLAYER COUNTING / SCORE SORTING
566
========================================================================
573
If there are less than two tournament players, put a
574
spectator in the game and restart
577
void AddTournamentPlayer( void ) {
580
gclient_t *nextInLine;
582
if ( level.numPlayingClients >= 2 ) {
586
// never change during intermission
587
if ( level.intermissiontime ) {
593
for ( i = 0 ; i < level.maxclients ; i++ ) {
594
client = &level.clients[i];
595
if ( client->pers.connected != CON_CONNECTED ) {
598
if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
601
// never select the dedicated follow or scoreboard clients
602
if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ||
603
client->sess.spectatorClient < 0 ) {
607
if ( !nextInLine || client->sess.spectatorTime < nextInLine->sess.spectatorTime ) {
616
level.warmupTime = -1;
618
// set them to free-for-all team
619
SetTeam( &g_entities[ nextInLine - level.clients ], "f" );
623
=======================
624
RemoveTournamentLoser
626
Make the loser a spectator at the back of the line
627
=======================
629
void RemoveTournamentLoser( void ) {
632
if ( level.numPlayingClients != 2 ) {
636
clientNum = level.sortedClients[1];
638
if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
642
// make them a spectator
643
SetTeam( &g_entities[ clientNum ], "s" );
647
=======================
648
RemoveTournamentWinner
649
=======================
651
void RemoveTournamentWinner( void ) {
654
if ( level.numPlayingClients != 2 ) {
658
clientNum = level.sortedClients[0];
660
if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
664
// make them a spectator
665
SetTeam( &g_entities[ clientNum ], "s" );
669
=======================
670
AdjustTournamentScores
671
=======================
673
void AdjustTournamentScores( void ) {
676
clientNum = level.sortedClients[0];
677
if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
678
level.clients[ clientNum ].sess.wins++;
679
ClientUserinfoChanged( clientNum );
682
clientNum = level.sortedClients[1];
683
if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
684
level.clients[ clientNum ].sess.losses++;
685
ClientUserinfoChanged( clientNum );
696
int QDECL SortRanks( const void *a, const void *b ) {
699
ca = &level.clients[*(int *)a];
700
cb = &level.clients[*(int *)b];
702
// sort special clients last
703
if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) {
706
if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0 ) {
710
// then connecting clients
711
if ( ca->pers.connected == CON_CONNECTING ) {
714
if ( cb->pers.connected == CON_CONNECTING ) {
720
if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) {
721
if ( ca->sess.spectatorTime < cb->sess.spectatorTime ) {
724
if ( ca->sess.spectatorTime > cb->sess.spectatorTime ) {
729
if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) {
732
if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) {
736
// then sort by score
737
if ( ca->ps.persistant[PERS_SCORE]
738
> cb->ps.persistant[PERS_SCORE] ) {
741
if ( ca->ps.persistant[PERS_SCORE]
742
< cb->ps.persistant[PERS_SCORE] ) {
752
Recalculates the score ranks of all players
753
This will be called on every client connect, begin, disconnect, death,
757
void CalculateRanks( void ) {
766
level.numConnectedClients = 0;
767
level.numNonSpectatorClients = 0;
768
level.numPlayingClients = 0;
769
level.numVotingClients = 0; // don't count bots
770
for ( i = 0; i < TEAM_NUM_TEAMS; i++ ) {
771
level.numteamVotingClients[i] = 0;
773
for ( i = 0 ; i < level.maxclients ; i++ ) {
774
if ( level.clients[i].pers.connected != CON_DISCONNECTED ) {
775
level.sortedClients[level.numConnectedClients] = i;
776
level.numConnectedClients++;
778
if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) {
779
level.numNonSpectatorClients++;
781
// decide if this should be auto-followed
782
if ( level.clients[i].pers.connected == CON_CONNECTED ) {
783
level.numPlayingClients++;
784
if ( !(g_entities[i].r.svFlags & SVF_BOT) ) {
785
level.numVotingClients++;
786
if ( level.clients[i].sess.sessionTeam == TEAM_RED )
787
level.numteamVotingClients[0]++;
788
else if ( level.clients[i].sess.sessionTeam == TEAM_BLUE )
789
level.numteamVotingClients[1]++;
791
if ( level.follow1 == -1 ) {
793
} else if ( level.follow2 == -1 ) {
801
qsort( level.sortedClients, level.numConnectedClients,
802
sizeof(level.sortedClients[0]), SortRanks );
804
// set the rank value for all clients that are connected and not spectators
805
if ( g_gametype.integer >= GT_TEAM ) {
806
// in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied
807
for ( i = 0; i < level.numConnectedClients; i++ ) {
808
cl = &level.clients[ level.sortedClients[i] ];
809
if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) {
810
cl->ps.persistant[PERS_RANK] = 2;
811
} else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) {
812
cl->ps.persistant[PERS_RANK] = 0;
814
cl->ps.persistant[PERS_RANK] = 1;
820
for ( i = 0; i < level.numPlayingClients; i++ ) {
821
cl = &level.clients[ level.sortedClients[i] ];
822
newScore = cl->ps.persistant[PERS_SCORE];
823
if ( i == 0 || newScore != score ) {
825
// assume we aren't tied until the next client is checked
826
level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank;
828
// we are tied with the previous client
829
level.clients[ level.sortedClients[i-1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
830
level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
833
if ( g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1 ) {
834
level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
839
// set the CS_SCORES1/2 configstrings, which will be visible to everyone
840
if ( g_gametype.integer >= GT_TEAM ) {
841
trap_SetConfigstring( CS_SCORES1, va("%i", level.teamScores[TEAM_RED] ) );
842
trap_SetConfigstring( CS_SCORES2, va("%i", level.teamScores[TEAM_BLUE] ) );
844
if ( level.numConnectedClients == 0 ) {
845
trap_SetConfigstring( CS_SCORES1, va("%i", SCORE_NOT_PRESENT) );
846
trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) );
847
} else if ( level.numConnectedClients == 1 ) {
848
trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
849
trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) );
851
trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
852
trap_SetConfigstring( CS_SCORES2, va("%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) );
856
// see if it is time to end the level
859
// if we are at the intermission, send the new info to everyone
860
if ( level.intermissiontime ) {
861
SendScoreboardMessageToAllClients();
867
========================================================================
871
========================================================================
875
========================
876
SendScoreboardMessageToAllClients
878
Do this at BeginIntermission time and whenever ranks are recalculated
879
due to enters/exits/forced team changes
880
========================
882
void SendScoreboardMessageToAllClients( void ) {
885
for ( i = 0 ; i < level.maxclients ; i++ ) {
886
if ( level.clients[ i ].pers.connected == CON_CONNECTED ) {
887
DeathmatchScoreboardMessage( g_entities + i );
893
========================
894
MoveClientToIntermission
896
When the intermission starts, this will be called for all players.
897
If a new client connects, this will be called after the spawn function.
898
========================
900
void MoveClientToIntermission( gentity_t *ent ) {
901
// take out of follow mode if needed
902
if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
903
StopFollowing( ent );
908
VectorCopy( level.intermission_origin, ent->s.origin );
909
VectorCopy( level.intermission_origin, ent->client->ps.origin );
910
VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
911
ent->client->ps.pm_type = PM_INTERMISSION;
913
// clean up powerup info
914
memset( ent->client->ps.powerups, 0, sizeof(ent->client->ps.powerups) );
916
ent->client->ps.eFlags = 0;
918
ent->s.eType = ET_GENERAL;
919
ent->s.modelindex = 0;
920
ent->s.loopSound = 0;
927
FindIntermissionPoint
929
This is also used for spectator spawns
932
void FindIntermissionPoint( void ) {
933
gentity_t *ent, *target;
936
// find the intermission spot
937
ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
938
if ( !ent ) { // the map creator forgot to put in an intermission point...
939
SelectSpawnPoint ( vec3_origin, level.intermission_origin, level.intermission_angle );
941
VectorCopy (ent->s.origin, level.intermission_origin);
942
VectorCopy (ent->s.angles, level.intermission_angle);
943
// if it has a target, look towards it
945
target = G_PickTarget( ent->target );
947
VectorSubtract( target->s.origin, level.intermission_origin, dir );
948
vectoangles( dir, level.intermission_angle );
960
void BeginIntermission( void ) {
964
if ( level.intermissiontime ) {
965
return; // already active
968
// if in tournement mode, change the wins / losses
969
if ( g_gametype.integer == GT_TOURNAMENT ) {
970
AdjustTournamentScores();
973
level.intermissiontime = level.time;
974
FindIntermissionPoint();
977
if (g_singlePlayer.integer) {
978
trap_Cvar_Set("ui_singlePlayerActive", "0");
979
UpdateTournamentInfo();
982
// if single player game
983
if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
984
UpdateTournamentInfo();
985
SpawnModelsOnVictoryPads();
989
// move all clients to the intermission point
990
for (i=0 ; i< level.maxclients ; i++) {
991
client = g_entities + i;
995
if (client->health <= 0) {
998
MoveClientToIntermission( client );
1001
// send the current scoring to all clients
1002
SendScoreboardMessageToAllClients();
1011
When the intermission has been exited, the server is either killed
1012
or moved to a new level based on the "nextmap" cvar
1016
void ExitLevel (void) {
1019
char nextmap[MAX_STRING_CHARS];
1020
char d1[MAX_STRING_CHARS];
1023
BotInterbreedEndMatch();
1025
// if we are running a tournement map, kick the loser to spectator status,
1026
// which will automatically grab the next spectator and restart
1027
if ( g_gametype.integer == GT_TOURNAMENT ) {
1028
if ( !level.restarted ) {
1029
RemoveTournamentLoser();
1030
trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1031
level.restarted = qtrue;
1032
level.changemap = NULL;
1033
level.intermissiontime = 0;
1038
trap_Cvar_VariableStringBuffer( "nextmap", nextmap, sizeof(nextmap) );
1039
trap_Cvar_VariableStringBuffer( "d1", d1, sizeof(d1) );
1041
if( !Q_stricmp( nextmap, "map_restart 0" ) && Q_stricmp( d1, "" ) ) {
1042
trap_Cvar_Set( "nextmap", "vstr d2" );
1043
trap_SendConsoleCommand( EXEC_APPEND, "vstr d1\n" );
1045
trap_SendConsoleCommand( EXEC_APPEND, "vstr nextmap\n" );
1048
level.changemap = NULL;
1049
level.intermissiontime = 0;
1051
// reset all the scores so we don't enter the intermission again
1052
level.teamScores[TEAM_RED] = 0;
1053
level.teamScores[TEAM_BLUE] = 0;
1054
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
1055
cl = level.clients + i;
1056
if ( cl->pers.connected != CON_CONNECTED ) {
1059
cl->ps.persistant[PERS_SCORE] = 0;
1062
// we need to do this here before chaning to CON_CONNECTING
1063
G_WriteSessionData();
1065
// change all client states to connecting, so the early players into the
1066
// next level will know the others aren't done reconnecting
1067
for (i=0 ; i< g_maxclients.integer ; i++) {
1068
if ( level.clients[i].pers.connected == CON_CONNECTED ) {
1069
level.clients[i].pers.connected = CON_CONNECTING;
1079
Print to the logfile with a time stamp if it is open
1082
void QDECL G_LogPrintf( const char *fmt, ... ) {
1087
sec = level.time / 1000;
1094
Com_sprintf( string, sizeof(string), "%3i:%i%i ", min, tens, sec );
1096
va_start( argptr, fmt );
1097
vsprintf( string +7 , fmt,argptr );
1100
if ( g_dedicated.integer ) {
1101
G_Printf( "%s", string + 7 );
1104
if ( !level.logFile ) {
1108
trap_FS_Write( string, strlen( string ), level.logFile );
1115
Append information about this game to the log file
1118
void LogExit( const char *string ) {
1121
#ifdef MISSIONPACK // bk001205
1122
qboolean won = qtrue;
1124
G_LogPrintf( "Exit: %s\n", string );
1126
level.intermissionQueued = level.time;
1128
// this will keep the clients from playing any voice sounds
1129
// that will get cut off when the queued intermission starts
1130
trap_SetConfigstring( CS_INTERMISSION, "1" );
1132
// don't send more than 32 scores (FIXME?)
1133
numSorted = level.numConnectedClients;
1134
if ( numSorted > 32 ) {
1138
if ( g_gametype.integer >= GT_TEAM ) {
1139
G_LogPrintf( "red:%i blue:%i\n",
1140
level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE] );
1143
for (i=0 ; i < numSorted ; i++) {
1146
cl = &level.clients[level.sortedClients[i]];
1148
if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) {
1151
if ( cl->pers.connected == CON_CONNECTING ) {
1155
ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
1157
G_LogPrintf( "score: %i ping: %i client: %i %s\n", cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i], cl->pers.netname );
1159
if (g_singlePlayer.integer && g_gametype.integer == GT_TOURNAMENT) {
1160
if (g_entities[cl - level.clients].r.svFlags & SVF_BOT && cl->ps.persistant[PERS_RANK] == 0) {
1169
if (g_singlePlayer.integer) {
1170
if (g_gametype.integer >= GT_CTF) {
1171
won = level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE];
1173
trap_SendConsoleCommand( EXEC_APPEND, (won) ? "spWin\n" : "spLose\n" );
1183
CheckIntermissionExit
1185
The level will stay at the intermission for a minimum of 5 seconds
1186
If all players wish to continue, the level will then exit.
1187
If one or more players have not acknowledged the continue, the game will
1188
wait 10 seconds before going on.
1191
void CheckIntermissionExit( void ) {
1192
int ready, notReady;
1197
if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1201
// see which players are ready
1205
for (i=0 ; i< g_maxclients.integer ; i++) {
1206
cl = level.clients + i;
1207
if ( cl->pers.connected != CON_CONNECTED ) {
1210
if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
1214
if ( cl->readyToExit ) {
1217
readyMask |= 1 << i;
1224
// copy the readyMask to each player's stats so
1225
// it can be displayed on the scoreboard
1226
for (i=0 ; i< g_maxclients.integer ; i++) {
1227
cl = level.clients + i;
1228
if ( cl->pers.connected != CON_CONNECTED ) {
1231
cl->ps.stats[STAT_CLIENTS_READY] = readyMask;
1234
// never exit in less than five seconds
1235
if ( level.time < level.intermissiontime + 5000 ) {
1239
// if nobody wants to go, clear timer
1241
level.readyToExit = qfalse;
1245
// if everyone wants to go, go now
1251
// the first person to ready starts the ten second timeout
1252
if ( !level.readyToExit ) {
1253
level.readyToExit = qtrue;
1254
level.exitTime = level.time;
1257
// if we have waited ten seconds since at least one player
1258
// wanted to exit, go ahead
1259
if ( level.time < level.exitTime + 10000 ) {
1271
qboolean ScoreIsTied( void ) {
1274
if ( level.numPlayingClients < 2 ) {
1278
if ( g_gametype.integer >= GT_TEAM ) {
1279
return level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE];
1282
a = level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE];
1283
b = level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE];
1292
There will be a delay between the time the exit is qualified for
1293
and the time everyone is moved to the intermission spot, so you
1294
can see the last frag.
1297
void CheckExitRules( void ) {
1300
// if at the intermission, wait for all non-bots to
1301
// signal ready, then go to next level
1302
if ( level.intermissiontime ) {
1303
CheckIntermissionExit ();
1307
if ( level.intermissionQueued ) {
1309
int time = (g_singlePlayer.integer) ? SP_INTERMISSION_DELAY_TIME : INTERMISSION_DELAY_TIME;
1310
if ( level.time - level.intermissionQueued >= time ) {
1311
level.intermissionQueued = 0;
1312
BeginIntermission();
1315
if ( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME ) {
1316
level.intermissionQueued = 0;
1317
BeginIntermission();
1323
// check for sudden death
1324
if ( ScoreIsTied() ) {
1325
// always wait for sudden death
1329
if ( g_timelimit.integer && !level.warmupTime ) {
1330
if ( level.time - level.startTime >= g_timelimit.integer*60000 ) {
1331
trap_SendServerCommand( -1, "print \"Timelimit hit.\n\"");
1332
LogExit( "Timelimit hit." );
1337
if ( level.numPlayingClients < 2 ) {
1341
if ( g_gametype.integer < GT_CTF && g_fraglimit.integer ) {
1342
if ( level.teamScores[TEAM_RED] >= g_fraglimit.integer ) {
1343
trap_SendServerCommand( -1, "print \"Red hit the fraglimit.\n\"" );
1344
LogExit( "Fraglimit hit." );
1348
if ( level.teamScores[TEAM_BLUE] >= g_fraglimit.integer ) {
1349
trap_SendServerCommand( -1, "print \"Blue hit the fraglimit.\n\"" );
1350
LogExit( "Fraglimit hit." );
1354
for ( i=0 ; i< g_maxclients.integer ; i++ ) {
1355
cl = level.clients + i;
1356
if ( cl->pers.connected != CON_CONNECTED ) {
1359
if ( cl->sess.sessionTeam != TEAM_FREE ) {
1363
if ( cl->ps.persistant[PERS_SCORE] >= g_fraglimit.integer ) {
1364
LogExit( "Fraglimit hit." );
1365
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " hit the fraglimit.\n\"",
1366
cl->pers.netname ) );
1372
if ( g_gametype.integer >= GT_CTF && g_capturelimit.integer ) {
1374
if ( level.teamScores[TEAM_RED] >= g_capturelimit.integer ) {
1375
trap_SendServerCommand( -1, "print \"Red hit the capturelimit.\n\"" );
1376
LogExit( "Capturelimit hit." );
1380
if ( level.teamScores[TEAM_BLUE] >= g_capturelimit.integer ) {
1381
trap_SendServerCommand( -1, "print \"Blue hit the capturelimit.\n\"" );
1382
LogExit( "Capturelimit hit." );
1391
========================================================================
1393
FUNCTIONS CALLED EVERY FRAME
1395
========================================================================
1403
Once a frame, check for changes in tournement player state
1406
void CheckTournament( void ) {
1407
// check because we run 3 game frames before calling Connect and/or ClientBegin
1408
// for clients on a map_restart
1409
if ( level.numPlayingClients == 0 ) {
1413
if ( g_gametype.integer == GT_TOURNAMENT ) {
1415
// pull in a spectator if needed
1416
if ( level.numPlayingClients < 2 ) {
1417
AddTournamentPlayer();
1420
// if we don't have two players, go back to "waiting for players"
1421
if ( level.numPlayingClients != 2 ) {
1422
if ( level.warmupTime != -1 ) {
1423
level.warmupTime = -1;
1424
trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1425
G_LogPrintf( "Warmup:\n" );
1430
if ( level.warmupTime == 0 ) {
1434
// if the warmup is changed at the console, restart it
1435
if ( g_warmup.modificationCount != level.warmupModificationCount ) {
1436
level.warmupModificationCount = g_warmup.modificationCount;
1437
level.warmupTime = -1;
1440
// if all players have arrived, start the countdown
1441
if ( level.warmupTime < 0 ) {
1442
if ( level.numPlayingClients == 2 ) {
1443
// fudge by -1 to account for extra delays
1444
if ( g_warmup.integer > 1 ) {
1445
level.warmupTime = level.time + ( g_warmup.integer - 1 ) * 1000;
1447
level.warmupTime = 0;
1450
trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1455
// if the warmup time has counted down, restart
1456
if ( level.time > level.warmupTime ) {
1457
level.warmupTime += 10000;
1458
trap_Cvar_Set( "g_restarted", "1" );
1459
trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1460
level.restarted = qtrue;
1463
} else if ( g_gametype.integer != GT_SINGLE_PLAYER && level.warmupTime != 0 ) {
1464
int counts[TEAM_NUM_TEAMS];
1465
qboolean notEnough = qfalse;
1467
if ( g_gametype.integer > GT_TEAM ) {
1468
counts[TEAM_BLUE] = TeamCount( -1, TEAM_BLUE );
1469
counts[TEAM_RED] = TeamCount( -1, TEAM_RED );
1471
if (counts[TEAM_RED] < 1 || counts[TEAM_BLUE] < 1) {
1474
} else if ( level.numPlayingClients < 2 ) {
1479
if ( level.warmupTime != -1 ) {
1480
level.warmupTime = -1;
1481
trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1482
G_LogPrintf( "Warmup:\n" );
1484
return; // still waiting for team members
1487
if ( level.warmupTime == 0 ) {
1491
// if the warmup is changed at the console, restart it
1492
if ( g_warmup.modificationCount != level.warmupModificationCount ) {
1493
level.warmupModificationCount = g_warmup.modificationCount;
1494
level.warmupTime = -1;
1497
// if all players have arrived, start the countdown
1498
if ( level.warmupTime < 0 ) {
1499
// fudge by -1 to account for extra delays
1500
level.warmupTime = level.time + ( g_warmup.integer - 1 ) * 1000;
1501
trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1505
// if the warmup time has counted down, restart
1506
if ( level.time > level.warmupTime ) {
1507
level.warmupTime += 10000;
1508
trap_Cvar_Set( "g_restarted", "1" );
1509
trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1510
level.restarted = qtrue;
1522
void CheckVote( void ) {
1523
if ( level.voteExecuteTime && level.voteExecuteTime < level.time ) {
1524
level.voteExecuteTime = 0;
1525
trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) );
1527
if ( !level.voteTime ) {
1530
if ( level.time - level.voteTime >= VOTE_TIME ) {
1531
trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
1533
// ATVI Q3 1.32 Patch #9, WNF
1534
if ( level.voteYes > level.numVotingClients/2 ) {
1535
// execute the command, then remove the vote
1536
trap_SendServerCommand( -1, "print \"Vote passed.\n\"" );
1537
level.voteExecuteTime = level.time + 3000;
1538
} else if ( level.voteNo >= level.numVotingClients/2 ) {
1539
// same behavior as a timeout
1540
trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
1542
// still waiting for a majority
1547
trap_SetConfigstring( CS_VOTE_TIME, "" );
1556
void PrintTeam(int team, char *message) {
1559
for ( i = 0 ; i < level.maxclients ; i++ ) {
1560
if (level.clients[i].sess.sessionTeam != team)
1562
trap_SendServerCommand( i, message );
1571
void SetLeader(int team, int client) {
1574
if ( level.clients[client].pers.connected == CON_DISCONNECTED ) {
1575
PrintTeam(team, va("print \"%s is not connected\n\"", level.clients[client].pers.netname) );
1578
if (level.clients[client].sess.sessionTeam != team) {
1579
PrintTeam(team, va("print \"%s is not on the team anymore\n\"", level.clients[client].pers.netname) );
1582
for ( i = 0 ; i < level.maxclients ; i++ ) {
1583
if (level.clients[i].sess.sessionTeam != team)
1585
if (level.clients[i].sess.teamLeader) {
1586
level.clients[i].sess.teamLeader = qfalse;
1587
ClientUserinfoChanged(i);
1590
level.clients[client].sess.teamLeader = qtrue;
1591
ClientUserinfoChanged( client );
1592
PrintTeam(team, va("print \"%s is the new team leader\n\"", level.clients[client].pers.netname) );
1600
void CheckTeamLeader( int team ) {
1603
for ( i = 0 ; i < level.maxclients ; i++ ) {
1604
if (level.clients[i].sess.sessionTeam != team)
1606
if (level.clients[i].sess.teamLeader)
1609
if (i >= level.maxclients) {
1610
for ( i = 0 ; i < level.maxclients ; i++ ) {
1611
if (level.clients[i].sess.sessionTeam != team)
1613
if (!(g_entities[i].r.svFlags & SVF_BOT)) {
1614
level.clients[i].sess.teamLeader = qtrue;
1618
for ( i = 0 ; i < level.maxclients ; i++ ) {
1619
if (level.clients[i].sess.sessionTeam != team)
1621
level.clients[i].sess.teamLeader = qtrue;
1632
void CheckTeamVote( int team ) {
1635
if ( team == TEAM_RED )
1637
else if ( team == TEAM_BLUE )
1642
if ( !level.teamVoteTime[cs_offset] ) {
1645
if ( level.time - level.teamVoteTime[cs_offset] >= VOTE_TIME ) {
1646
trap_SendServerCommand( -1, "print \"Team vote failed.\n\"" );
1648
if ( level.teamVoteYes[cs_offset] > level.numteamVotingClients[cs_offset]/2 ) {
1649
// execute the command, then remove the vote
1650
trap_SendServerCommand( -1, "print \"Team vote passed.\n\"" );
1652
if ( !Q_strncmp( "leader", level.teamVoteString[cs_offset], 6) ) {
1653
//set the team leader
1654
SetLeader(team, atoi(level.teamVoteString[cs_offset] + 7));
1657
trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.teamVoteString[cs_offset] ) );
1659
} else if ( level.teamVoteNo[cs_offset] >= level.numteamVotingClients[cs_offset]/2 ) {
1660
// same behavior as a timeout
1661
trap_SendServerCommand( -1, "print \"Team vote failed.\n\"" );
1663
// still waiting for a majority
1667
level.teamVoteTime[cs_offset] = 0;
1668
trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" );
1678
void CheckCvars( void ) {
1679
static int lastMod = -1;
1681
if ( g_password.modificationCount != lastMod ) {
1682
lastMod = g_password.modificationCount;
1683
if ( *g_password.string && Q_stricmp( g_password.string, "none" ) ) {
1684
trap_Cvar_Set( "g_needpass", "1" );
1686
trap_Cvar_Set( "g_needpass", "0" );
1695
Runs thinking code for this frame if necessary
1698
void G_RunThink (gentity_t *ent) {
1701
thinktime = ent->nextthink;
1702
if (thinktime <= 0) {
1705
if (thinktime > level.time) {
1711
G_Error ( "NULL ent->think");
1720
Advances the non-player objects in the world
1723
void G_RunFrame( int levelTime ) {
1729
// if we are waiting for the level to restart, do nothing
1730
if ( level.restarted ) {
1735
level.previousTime = level.time;
1736
level.time = levelTime;
1737
msec = level.time - level.previousTime;
1739
// get any cvar changes
1743
// go through all allocated objects
1745
start = trap_Milliseconds();
1746
ent = &g_entities[0];
1747
for (i=0 ; i<level.num_entities ; i++, ent++) {
1748
if ( !ent->inuse ) {
1752
// clear events that are too old
1753
if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) {
1754
if ( ent->s.event ) {
1755
ent->s.event = 0; // &= EV_EVENT_BITS;
1756
if ( ent->client ) {
1757
ent->client->ps.externalEvent = 0;
1758
// predicted events should never be set to zero
1759
//ent->client->ps.events[0] = 0;
1760
//ent->client->ps.events[1] = 0;
1763
if ( ent->freeAfterEvent ) {
1764
// tempEntities or dropped items completely go away after their event
1765
G_FreeEntity( ent );
1767
} else if ( ent->unlinkAfterEvent ) {
1768
// items that will respawn will hide themselves after their pickup event
1769
ent->unlinkAfterEvent = qfalse;
1770
trap_UnlinkEntity( ent );
1774
// temporary entities don't think
1775
if ( ent->freeAfterEvent ) {
1779
if ( !ent->r.linked && ent->neverFree ) {
1783
if ( ent->s.eType == ET_MISSILE ) {
1784
G_RunMissile( ent );
1788
if ( ent->s.eType == ET_ITEM || ent->physicsObject ) {
1793
if ( ent->s.eType == ET_MOVER ) {
1798
if ( i < MAX_CLIENTS ) {
1805
end = trap_Milliseconds();
1807
start = trap_Milliseconds();
1808
// perform final fixups on the players
1809
ent = &g_entities[0];
1810
for (i=0 ; i < level.maxclients ; i++, ent++ ) {
1812
ClientEndFrame( ent );
1815
end = trap_Milliseconds();
1817
// see if it is time to do a tournement restart
1820
// see if it is time to end the level
1823
// update to team status?
1826
// cancel vote if timed out
1830
CheckTeamVote( TEAM_RED );
1831
CheckTeamVote( TEAM_BLUE );
1833
// for tracking changes
1836
if (g_listEntity.integer) {
1837
for (i = 0; i < MAX_GENTITIES; i++) {
1838
G_Printf("%4i: %s\n", i, g_entities[i].classname);
1840
trap_Cvar_Set("g_listEntity", "0");