1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5 
6 This file is part of Tremulous.
7 
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12 
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 
26 level_locals_t  level;
27 
28 typedef struct
29 {
30   vmCvar_t  *vmCvar;
31   char    *cvarName;
32   char    *defaultString;
33   int     cvarFlags;
34   int     modificationCount;  // for tracking changes
35   qboolean  trackChange;  // track this variable, and announce if changed
36   qboolean teamShader;        // track and if changed, update shader state
37 } cvarTable_t;
38 
39 gentity_t   g_entities[ MAX_GENTITIES ];
40 gclient_t   g_clients[ MAX_CLIENTS ];
41 
42 vmCvar_t  g_fraglimit;
43 vmCvar_t  g_timelimit;
44 vmCvar_t  g_suddenDeathTime;
45 vmCvar_t  g_capturelimit;
46 vmCvar_t  g_friendlyFire;
47 vmCvar_t  g_password;
48 vmCvar_t  g_needpass;
49 vmCvar_t  g_maxclients;
50 vmCvar_t  g_maxGameClients;
51 vmCvar_t  g_dedicated;
52 vmCvar_t  g_speed;
53 vmCvar_t  g_gravity;
54 vmCvar_t  g_cheats;
55 vmCvar_t  g_knockback;
56 vmCvar_t  g_quadfactor;
57 vmCvar_t  g_forcerespawn;
58 vmCvar_t  g_inactivity;
59 vmCvar_t  g_debugMove;
60 vmCvar_t  g_debugDamage;
61 vmCvar_t  g_debugAlloc;
62 vmCvar_t  g_weaponRespawn;
63 vmCvar_t  g_weaponTeamRespawn;
64 vmCvar_t  g_motd;
65 vmCvar_t  g_synchronousClients;
66 vmCvar_t  g_warmup;
67 vmCvar_t  g_doWarmup;
68 vmCvar_t  g_restarted;
69 vmCvar_t  g_logFile;
70 vmCvar_t  g_logFileSync;
71 vmCvar_t  g_blood;
72 vmCvar_t  g_podiumDist;
73 vmCvar_t  g_podiumDrop;
74 vmCvar_t  g_allowVote;
75 vmCvar_t  g_teamAutoJoin;
76 vmCvar_t  g_teamForceBalance;
77 vmCvar_t  g_banIPs;
78 vmCvar_t  g_filterBan;
79 vmCvar_t  g_smoothClients;
80 vmCvar_t  pmove_fixed;
81 vmCvar_t  pmove_msec;
82 vmCvar_t  g_rankings;
83 vmCvar_t  g_listEntity;
84 vmCvar_t  g_minCommandPeriod;
85 
86 //TA
87 vmCvar_t  g_humanBuildPoints;
88 vmCvar_t  g_alienBuildPoints;
89 vmCvar_t  g_humanStage;
90 vmCvar_t  g_humanKills;
91 vmCvar_t  g_humanMaxStage;
92 vmCvar_t  g_humanStage2Threshold;
93 vmCvar_t  g_humanStage3Threshold;
94 vmCvar_t  g_alienStage;
95 vmCvar_t  g_alienKills;
96 vmCvar_t  g_alienMaxStage;
97 vmCvar_t  g_alienStage2Threshold;
98 vmCvar_t  g_alienStage3Threshold;
99 
100 vmCvar_t  g_disabledEquipment;
101 vmCvar_t  g_disabledClasses;
102 vmCvar_t  g_disabledBuildables;
103 
104 vmCvar_t  g_debugMapRotation;
105 vmCvar_t  g_currentMapRotation;
106 vmCvar_t  g_currentMap;
107 vmCvar_t  g_initialMapRotation;
108 
109 static cvarTable_t   gameCvarTable[ ] =
110 {
111   // don't override the cheat state set by the system
112   { &g_cheats, "sv_cheats", "", 0, 0, qfalse },
113 
114   // noset vars
115   { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },
116   { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse  },
117   { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse  },
118   { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },
119 
120   // latched vars
121 
122   { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  },
123   { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  },
124 
125   // change anytime vars
126   { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
127   { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
128 
129   { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse  },
130 
131   { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE, 0, qtrue  },
132 
133   { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE  },
134   { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE  },
135 
136   { &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue  },
137   { &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue  },
138   { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse  },
139   { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse  },
140 
141   { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse  },
142 
143   { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse  },
144   { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse  },
145 
146   { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
147 
148   { &g_dedicated, "dedicated", "0", 0, 0, qfalse  },
149 
150   { &g_speed, "g_speed", "320", 0, 0, qtrue  },
151   { &g_gravity, "g_gravity", "800", 0, 0, qtrue  },
152   { &g_knockback, "g_knockback", "1000", 0, 0, qtrue  },
153   { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue  },
154   { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue  },
155   { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
156   { &g_forcerespawn, "g_forcerespawn", "20", 0, 0, qtrue },
157   { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
158   { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
159   { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
160   { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
161   { &g_motd, "g_motd", "", 0, 0, qfalse },
162   { &g_blood, "com_blood", "1", 0, 0, qfalse },
163 
164   { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
165   { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
166 
167   { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
168   { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
169   { &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse},
170 
171   { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
172   { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
173   { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
174 
175   { &g_humanBuildPoints, "g_humanBuildPoints", "100", 0, 0, qfalse  },
176   { &g_alienBuildPoints, "g_alienBuildPoints", "100", 0, 0, qfalse  },
177   { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse  },
178   { &g_humanKills, "g_humanKills", "0", 0, 0, qfalse  },
179   { &g_humanMaxStage, "g_humanMaxStage", "2", 0, 0, qfalse  },
180   { &g_humanStage2Threshold, "g_humanStage2Threshold", "20", 0, 0, qfalse  },
181   { &g_humanStage3Threshold, "g_humanStage3Threshold", "40", 0, 0, qfalse  },
182   { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse  },
183   { &g_alienKills, "g_alienKills", "0", 0, 0, qfalse  },
184   { &g_alienMaxStage, "g_alienMaxStage", "2", 0, 0, qfalse  },
185   { &g_alienStage2Threshold, "g_alienStage2Threshold", "20", 0, 0, qfalse  },
186   { &g_alienStage3Threshold, "g_alienStage3Threshold", "40", 0, 0, qfalse  },
187 
188   { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse  },
189   { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse  },
190   { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse  },
191 
192   { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse  },
193   { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse  }, // -1 = NOT_ROTATING
194   { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse  },
195   { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse  },
196 
197   { &g_rankings, "g_rankings", "0", 0, 0, qfalse}
198 };
199 
200 static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] );
201 
202 
203 void G_InitGame( int levelTime, int randomSeed, int restart );
204 void G_RunFrame( int levelTime );
205 void G_ShutdownGame( int restart );
206 void CheckExitRules( void );
207 
208 void G_CountSpawns( void );
209 void G_CalculateBuildPoints( void );
210 
211 /*
212 ================
213 vmMain
214 
215 This is the only way control passes into the module.
216 This must be the very first function compiled into the .q3vm file
217 ================
218 */
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)219 intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4,
220                               int arg5, int arg6, int arg7, int arg8, int arg9,
221                               int arg10, int arg11 )
222 {
223   switch( command )
224   {
225     case GAME_INIT:
226       G_InitGame( arg0, arg1, arg2 );
227       return 0;
228 
229     case GAME_SHUTDOWN:
230       G_ShutdownGame( arg0 );
231       return 0;
232 
233     case GAME_CLIENT_CONNECT:
234       return (intptr_t)ClientConnect( arg0, arg1, arg2 );
235 
236     case GAME_CLIENT_THINK:
237       ClientThink( arg0 );
238       return 0;
239 
240     case GAME_CLIENT_USERINFO_CHANGED:
241       ClientUserinfoChanged( arg0 );
242       return 0;
243 
244     case GAME_CLIENT_DISCONNECT:
245       ClientDisconnect( arg0 );
246       return 0;
247 
248     case GAME_CLIENT_BEGIN:
249       ClientBegin( arg0 );
250       return 0;
251 
252     case GAME_CLIENT_COMMAND:
253       ClientCommand( arg0 );
254       return 0;
255 
256     case GAME_RUN_FRAME:
257       G_RunFrame( arg0 );
258       return 0;
259 
260     case GAME_CONSOLE_COMMAND:
261       return ConsoleCommand( );
262   }
263 
264   return -1;
265 }
266 
267 
G_Printf(const char * fmt,...)268 void QDECL G_Printf( const char *fmt, ... )
269 {
270   va_list argptr;
271   char    text[ 1024 ];
272 
273   va_start( argptr, fmt );
274   vsprintf( text, fmt, argptr );
275   va_end( argptr );
276 
277   trap_Printf( text );
278 }
279 
G_Error(const char * fmt,...)280 void QDECL G_Error( const char *fmt, ... )
281 {
282   va_list argptr;
283   char    text[ 1024 ];
284 
285   va_start( argptr, fmt );
286   vsprintf( text, fmt, argptr );
287   va_end( argptr );
288 
289   trap_Error( text );
290 }
291 
292 /*
293 ================
294 G_FindTeams
295 
296 Chain together all entities with a matching team field.
297 Entity teams are used for item groups and multi-entity mover groups.
298 
299 All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
300 All but the last will have the teamchain field set to the next one
301 ================
302 */
G_FindTeams(void)303 void G_FindTeams( void )
304 {
305   gentity_t *e, *e2;
306   int       i, j;
307   int       c, c2;
308 
309   c = 0;
310   c2 = 0;
311 
312   for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ )
313   {
314     if( !e->inuse )
315       continue;
316 
317     if( !e->team )
318       continue;
319 
320     if( e->flags & FL_TEAMSLAVE )
321       continue;
322 
323     e->teammaster = e;
324     c++;
325     c2++;
326 
327     for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ )
328     {
329       if( !e2->inuse )
330         continue;
331 
332       if( !e2->team )
333         continue;
334 
335       if( e2->flags & FL_TEAMSLAVE )
336         continue;
337 
338       if( !strcmp( e->team, e2->team ) )
339       {
340         c2++;
341         e2->teamchain = e->teamchain;
342         e->teamchain = e2;
343         e2->teammaster = e;
344         e2->flags |= FL_TEAMSLAVE;
345 
346         // make sure that targets only point at the master
347         if( e2->targetname )
348         {
349           e->targetname = e2->targetname;
350           e2->targetname = NULL;
351         }
352       }
353     }
354   }
355 
356   G_Printf( "%i teams with %i entities\n", c, c2 );
357 }
358 
G_RemapTeamShaders(void)359 void G_RemapTeamShaders( void )
360 {
361 }
362 
363 
364 /*
365 =================
366 G_RegisterCvars
367 =================
368 */
G_RegisterCvars(void)369 void G_RegisterCvars( void )
370 {
371   int         i;
372   cvarTable_t *cv;
373   qboolean    remapped = qfalse;
374 
375   for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )
376   {
377     trap_Cvar_Register( cv->vmCvar, cv->cvarName,
378       cv->defaultString, cv->cvarFlags );
379 
380     if( cv->vmCvar )
381       cv->modificationCount = cv->vmCvar->modificationCount;
382 
383     if( cv->teamShader )
384       remapped = qtrue;
385   }
386 
387   if( remapped )
388     G_RemapTeamShaders( );
389 
390   // check some things
391   level.warmupModificationCount = g_warmup.modificationCount;
392 }
393 
394 /*
395 =================
396 G_UpdateCvars
397 =================
398 */
G_UpdateCvars(void)399 void G_UpdateCvars( void )
400 {
401   int         i;
402   cvarTable_t *cv;
403   qboolean    remapped = qfalse;
404 
405   for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )
406   {
407     if( cv->vmCvar )
408     {
409       trap_Cvar_Update( cv->vmCvar );
410 
411       if( cv->modificationCount != cv->vmCvar->modificationCount )
412       {
413         cv->modificationCount = cv->vmCvar->modificationCount;
414 
415         if( cv->trackChange )
416           G_SendCommandFromServer( -1, va( "print \"Server: %s changed to %s\n\"",
417             cv->cvarName, cv->vmCvar->string ) );
418 
419         if( cv->teamShader )
420           remapped = qtrue;
421       }
422     }
423   }
424 
425   if( remapped )
426     G_RemapTeamShaders( );
427 }
428 
429 /*
430 ============
431 G_InitGame
432 
433 ============
434 */
G_InitGame(int levelTime,int randomSeed,int restart)435 void G_InitGame( int levelTime, int randomSeed, int restart )
436 {
437   int i;
438 
439   srand( randomSeed );
440 
441   G_RegisterCvars( );
442 
443   G_Printf( "------- Game Initialization -------\n" );
444   G_Printf( "gamename: %s\n", GAME_VERSION );
445   G_Printf( "gamedate: %s\n", __DATE__ );
446 
447   G_ProcessIPBans( );
448 
449   G_InitMemory( );
450 
451   // set some level globals
452   memset( &level, 0, sizeof( level ) );
453   level.time = levelTime;
454   level.startTime = levelTime;
455   level.alienStage2Time = level.alienStage3Time =
456     level.humanStage2Time = level.humanStage3Time = level.startTime;
457 
458   level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime
459 
460   if( g_logFile.string[ 0 ] )
461   {
462     if( g_logFileSync.integer )
463       trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC );
464     else
465       trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND );
466 
467     if( !level.logFile )
468       G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string );
469     else
470     {
471       char serverinfo[ MAX_INFO_STRING ];
472 
473       trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
474 
475       G_LogPrintf( "------------------------------------------------------------\n" );
476       G_LogPrintf( "InitGame: %s\n", serverinfo );
477     }
478   }
479   else
480     G_Printf( "Not logging to disk\n" );
481 
482   // initialize all entities for this game
483   memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) );
484   level.gentities = g_entities;
485 
486   // initialize all clients for this game
487   level.maxclients = g_maxclients.integer;
488   memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[ 0 ] ) );
489   level.clients = g_clients;
490 
491   // set client fields on player ents
492   for( i = 0; i < level.maxclients; i++ )
493     g_entities[ i ].client = level.clients + i;
494 
495   // always leave room for the max number of clients,
496   // even if they aren't all used, so numbers inside that
497   // range are NEVER anything but clients
498   level.num_entities = MAX_CLIENTS;
499 
500   // let the server system know where the entites are
501   trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
502     &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) );
503 
504   trap_SetConfigstring( CS_INTERMISSION, "0" );
505 
506   // parse the key/value pairs and spawn gentities
507   G_SpawnEntitiesFromString( );
508 
509   // the map might disable some things
510   BG_InitAllowedGameElements( );
511 
512   // general initialization
513   G_FindTeams( );
514 
515   //TA:
516   BG_InitClassOverrides( );
517   BG_InitBuildableOverrides( );
518   G_InitDamageLocations( );
519   G_InitMapRotations( );
520   G_InitSpawnQueue( &level.alienSpawnQueue );
521   G_InitSpawnQueue( &level.humanSpawnQueue );
522 
523   if( g_debugMapRotation.integer )
524     G_PrintRotations( );
525 
526   //reset stages
527   trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) );
528   trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) );
529   trap_Cvar_Set( "g_alienKills", 0 );
530   trap_Cvar_Set( "g_humanKills", 0 );
531 
532   G_Printf( "-----------------------------------\n" );
533 
534   G_RemapTeamShaders( );
535 
536   //TA: so the server counts the spawns without a client attached
537   G_CountSpawns( );
538 
539   G_ResetPTRConnections( );
540 }
541 
542 
543 
544 /*
545 =================
546 G_ShutdownGame
547 =================
548 */
G_ShutdownGame(int restart)549 void G_ShutdownGame( int restart )
550 {
551   G_Printf( "==== ShutdownGame ====\n" );
552 
553   if( level.logFile )
554   {
555     G_LogPrintf( "ShutdownGame:\n" );
556     G_LogPrintf( "------------------------------------------------------------\n" );
557     trap_FS_FCloseFile( level.logFile );
558   }
559 
560   // write all the client session data so we can get it back
561   G_WriteSessionData( );
562 }
563 
564 
565 
566 //===================================================================
567 
Com_Error(int level,const char * error,...)568 void QDECL Com_Error( int level, const char *error, ... )
569 {
570   va_list argptr;
571   char    text[ 1024 ];
572 
573   va_start( argptr, error );
574   vsprintf( text, error, argptr );
575   va_end( argptr );
576 
577   G_Error( "%s", text );
578 }
579 
Com_Printf(const char * msg,...)580 void QDECL Com_Printf( const char *msg, ... )
581 {
582   va_list argptr;
583   char    text[ 1024 ];
584 
585   va_start( argptr, msg );
586   vsprintf( text, msg, argptr );
587   va_end( argptr );
588 
589   G_Printf( "%s", text );
590 }
591 
592 /*
593 ========================================================================
594 
595 PLAYER COUNTING / SCORE SORTING
596 
597 ========================================================================
598 */
599 
600 
601 /*
602 =============
603 SortRanks
604 
605 =============
606 */
SortRanks(const void * a,const void * b)607 int QDECL SortRanks( const void *a, const void *b )
608 {
609   gclient_t *ca, *cb;
610 
611   ca = &level.clients[ *(int *)a ];
612   cb = &level.clients[ *(int *)b ];
613 
614   // then sort by score
615   if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] )
616     return -1;
617   else if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] )
618     return 1;
619   else
620     return 0;
621 }
622 
623 /*
624 ============
625 G_InitSpawnQueue
626 
627 Initialise a spawn queue
628 ============
629 */
G_InitSpawnQueue(spawnQueue_t * sq)630 void G_InitSpawnQueue( spawnQueue_t *sq )
631 {
632   int i;
633 
634   sq->back = sq->front = 0;
635   sq->back = QUEUE_MINUS1( sq->back );
636 
637   //0 is a valid clientNum, so use something else
638   for( i = 0; i < MAX_CLIENTS; i++ )
639     sq->clients[ i ] = -1;
640 }
641 
642 /*
643 ============
644 G_GetSpawnQueueLength
645 
646 Return tha length of a spawn queue
647 ============
648 */
G_GetSpawnQueueLength(spawnQueue_t * sq)649 int G_GetSpawnQueueLength( spawnQueue_t *sq )
650 {
651   int length = sq->back - sq->front + 1;
652 
653   while( length < 0 )
654     length += MAX_CLIENTS;
655 
656   while( length >= MAX_CLIENTS )
657     length -= MAX_CLIENTS;
658 
659   return length;
660 }
661 
662 /*
663 ============
664 G_PopSpawnQueue
665 
666 Remove from front element from a spawn queue
667 ============
668 */
G_PopSpawnQueue(spawnQueue_t * sq)669 int G_PopSpawnQueue( spawnQueue_t *sq )
670 {
671   int clientNum = sq->clients[ sq->front ];
672 
673   if( G_GetSpawnQueueLength( sq ) > 0 )
674   {
675     sq->clients[ sq->front ] = -1;
676     sq->front = QUEUE_PLUS1( sq->front );
677     g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;
678 
679     return clientNum;
680   }
681   else
682     return -1;
683 }
684 
685 /*
686 ============
687 G_PeekSpawnQueue
688 
689 Look at front element from a spawn queue
690 ============
691 */
G_PeekSpawnQueue(spawnQueue_t * sq)692 int G_PeekSpawnQueue( spawnQueue_t *sq )
693 {
694   return sq->clients[ sq->front ];
695 }
696 
697 /*
698 ============
699 G_PushSpawnQueue
700 
701 Add an element to the back of the spawn queue
702 ============
703 */
G_PushSpawnQueue(spawnQueue_t * sq,int clientNum)704 void G_PushSpawnQueue( spawnQueue_t *sq, int clientNum )
705 {
706   sq->back = QUEUE_PLUS1( sq->back );
707   sq->clients[ sq->back ] = clientNum;
708 
709   g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED;
710 }
711 
712 /*
713 ============
714 G_RemoveFromSpawnQueue
715 
716 remove a specific client from a spawn queue
717 ============
718 */
G_RemoveFromSpawnQueue(spawnQueue_t * sq,int clientNum)719 qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum )
720 {
721   int i = sq->front;
722 
723   if( G_GetSpawnQueueLength( sq ) )
724   {
725     do
726     {
727       if( sq->clients[ i ] == clientNum )
728       {
729         //and this kids is why it would have
730         //been better to use an LL for internal
731         //representation
732         do
733         {
734           sq->clients[ i ] = sq->clients[ QUEUE_PLUS1( i ) ];
735 
736           i = QUEUE_PLUS1( i );
737         } while( i != QUEUE_PLUS1( sq->back ) );
738 
739         sq->back = QUEUE_MINUS1( sq->back );
740         g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;
741 
742         return qtrue;
743       }
744 
745       i = QUEUE_PLUS1( i );
746     } while( i != QUEUE_PLUS1( sq->back ) );
747   }
748 
749   return qfalse;
750 }
751 
752 /*
753 ============
754 G_GetPosInSpawnQueue
755 
756 Get the position of a client in a spawn queue
757 ============
758 */
G_GetPosInSpawnQueue(spawnQueue_t * sq,int clientNum)759 int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum )
760 {
761   int i = sq->front;
762 
763   if( G_GetSpawnQueueLength( sq ) )
764   {
765     do
766     {
767       if( sq->clients[ i ] == clientNum )
768       {
769         if( i < sq->front )
770           return i + MAX_CLIENTS - sq->front;
771         else
772           return i - sq->front;
773       }
774 
775       i = QUEUE_PLUS1( i );
776     } while( i != QUEUE_PLUS1( sq->back ) );
777   }
778 
779   return -1;
780 }
781 
782 /*
783 ============
784 G_PrintSpawnQueue
785 
786 Print the contents of a spawn queue
787 ============
788 */
G_PrintSpawnQueue(spawnQueue_t * sq)789 void G_PrintSpawnQueue( spawnQueue_t *sq )
790 {
791   int i = sq->front;
792   int length = G_GetSpawnQueueLength( sq );
793 
794   G_Printf( "l:%d f:%d b:%d    :", length, sq->front, sq->back );
795 
796   if( length > 0 )
797   {
798     do
799     {
800       if( sq->clients[ i ] == -1 )
801         G_Printf( "*:" );
802       else
803         G_Printf( "%d:", sq->clients[ i ] );
804 
805       i = QUEUE_PLUS1( i );
806     } while( i != QUEUE_PLUS1( sq->back ) );
807   }
808 
809   G_Printf( "\n" );
810 }
811 
812 /*
813 ============
814 G_SpawnClients
815 
816 Spawn queued clients
817 ============
818 */
G_SpawnClients(pTeam_t team)819 void G_SpawnClients( pTeam_t team )
820 {
821   int           clientNum;
822   gentity_t     *ent, *spawn;
823   vec3_t        spawn_origin, spawn_angles;
824   spawnQueue_t  *sq = NULL;
825   int           numSpawns = 0;
826 
827   if( team == PTE_ALIENS )
828   {
829     sq = &level.alienSpawnQueue;
830     numSpawns = level.numAlienSpawns;
831   }
832   else if( team == PTE_HUMANS )
833   {
834     sq = &level.humanSpawnQueue;
835     numSpawns = level.numHumanSpawns;
836   }
837 
838   if( G_GetSpawnQueueLength( sq ) > 0 && numSpawns > 0 )
839   {
840     clientNum = G_PeekSpawnQueue( sq );
841     ent = &g_entities[ clientNum ];
842 
843     if( ( spawn = SelectTremulousSpawnPoint( team,
844             ent->client->pers.lastDeathLocation,
845             spawn_origin, spawn_angles ) ) )
846     {
847       clientNum = G_PopSpawnQueue( sq );
848 
849       if( clientNum < 0 )
850         return;
851 
852       ent = &g_entities[ clientNum ];
853 
854       ent->client->sess.sessionTeam = TEAM_FREE;
855       ClientUserinfoChanged( clientNum );
856       ClientSpawn( ent, spawn, spawn_origin, spawn_angles );
857     }
858   }
859 }
860 
861 /*
862 ============
863 G_CountSpawns
864 
865 Counts the number of spawns for each team
866 ============
867 */
G_CountSpawns(void)868 void G_CountSpawns( void )
869 {
870   int i;
871   gentity_t *ent;
872 
873   level.numAlienSpawns = 0;
874   level.numHumanSpawns = 0;
875 
876   for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
877   {
878     if( !ent->inuse )
879       continue;
880 
881     if( ent->s.modelindex == BA_A_SPAWN && ent->health > 0 )
882       level.numAlienSpawns++;
883 
884     if( ent->s.modelindex == BA_H_SPAWN && ent->health > 0 )
885       level.numHumanSpawns++;
886   }
887 
888   //let the client know how many spawns there are
889   trap_SetConfigstring( CS_SPAWNS, va( "%d %d",
890         level.numAlienSpawns, level.numHumanSpawns ) );
891 }
892 
893 
894 #define PLAYER_COUNT_MOD 5.0f
895 
896 /*
897 ============
898 G_CalculateBuildPoints
899 
900 Recalculate the quantity of building points available to the teams
901 ============
902 */
G_CalculateBuildPoints(void)903 void G_CalculateBuildPoints( void )
904 {
905   int         i;
906   buildable_t buildable;
907   gentity_t   *ent;
908   int         localHTP = g_humanBuildPoints.integer,
909               localATP = g_alienBuildPoints.integer;
910 
911   if( g_suddenDeathTime.integer && !level.warmupTime &&
912     ( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 ) )
913   {
914     localHTP = 0;
915     localATP = 0;
916   }
917   else
918   {
919     localHTP = g_humanBuildPoints.integer;
920     localATP = g_alienBuildPoints.integer;
921   }
922 
923   level.humanBuildPoints = level.humanBuildPointsPowered = localHTP;
924   level.alienBuildPoints = localATP;
925 
926   level.reactorPresent = qfalse;
927   level.overmindPresent = qfalse;
928 
929   for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
930   {
931     if( !ent->inuse )
932       continue;
933 
934     if( ent->s.eType != ET_BUILDABLE )
935       continue;
936 
937     buildable = ent->s.modelindex;
938 
939     if( buildable != BA_NONE )
940     {
941       if( buildable == BA_H_REACTOR && ent->spawned && ent->health > 0 )
942         level.reactorPresent = qtrue;
943 
944       if( buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0 )
945         level.overmindPresent = qtrue;
946 
947       if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS )
948       {
949         level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable );
950 
951         if( ent->powered )
952           level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable );
953       }
954       else
955       {
956         level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable );
957       }
958     }
959   }
960 
961   if( level.humanBuildPoints < 0 )
962   {
963     localHTP -= level.humanBuildPoints;
964     level.humanBuildPointsPowered -= level.humanBuildPoints;
965     level.humanBuildPoints = 0;
966   }
967 
968   if( level.alienBuildPoints < 0 )
969   {
970     localATP -= level.alienBuildPoints;
971     level.alienBuildPoints = 0;
972   }
973 
974   trap_SetConfigstring( CS_BUILDPOINTS,
975                         va( "%d %d %d %d %d", level.alienBuildPoints,
976                                               localATP,
977                                               level.humanBuildPoints,
978                                               localHTP,
979                                               level.humanBuildPointsPowered ) );
980 
981   //may as well pump the stages here too
982   {
983     float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
984     float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
985     int   alienNextStageThreshold, humanNextStageThreshold;
986 
987     if( alienPlayerCountMod < 0.1f )
988       alienPlayerCountMod = 0.1f;
989 
990     if( humanPlayerCountMod < 0.1f )
991       humanPlayerCountMod = 0.1f;
992 
993     if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )
994       alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) );
995     else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )
996       alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) );
997     else
998       alienNextStageThreshold = -1;
999 
1000     if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )
1001       humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) );
1002     else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )
1003       humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) );
1004     else
1005       humanNextStageThreshold = -1;
1006 
1007     trap_SetConfigstring( CS_STAGES, va( "%d %d %d %d %d %d",
1008           g_alienStage.integer, g_humanStage.integer,
1009           g_alienKills.integer, g_humanKills.integer,
1010           alienNextStageThreshold, humanNextStageThreshold ) );
1011   }
1012 }
1013 
1014 /*
1015 ============
1016 G_CalculateStages
1017 ============
1018 */
G_CalculateStages(void)1019 void G_CalculateStages( void )
1020 {
1021   float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD;
1022   float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD;
1023 
1024   if( alienPlayerCountMod < 0.1f )
1025     alienPlayerCountMod = 0.1f;
1026 
1027   if( humanPlayerCountMod < 0.1f )
1028     humanPlayerCountMod = 0.1f;
1029 
1030   if( g_alienKills.integer >=
1031       (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) &&
1032       g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )
1033   {
1034     G_Checktrigger_stages( PTE_ALIENS, S2 );
1035     trap_Cvar_Set( "g_alienStage", va( "%d", S2 ) );
1036     level.alienStage2Time = level.time;
1037   }
1038 
1039   if( g_alienKills.integer >=
1040       (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) &&
1041       g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )
1042   {
1043     G_Checktrigger_stages( PTE_ALIENS, S3 );
1044     trap_Cvar_Set( "g_alienStage", va( "%d", S3 ) );
1045     level.alienStage3Time = level.time;
1046   }
1047 
1048   if( g_humanKills.integer >=
1049       (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) &&
1050       g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )
1051   {
1052     G_Checktrigger_stages( PTE_HUMANS, S2 );
1053     trap_Cvar_Set( "g_humanStage", va( "%d", S2 ) );
1054     level.humanStage2Time = level.time;
1055   }
1056 
1057   if( g_humanKills.integer >=
1058       (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) &&
1059       g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )
1060   {
1061     G_Checktrigger_stages( PTE_HUMANS, S3 );
1062     trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) );
1063     level.humanStage3Time = level.time;
1064   }
1065 }
1066 
1067 /*
1068 ============
1069 CalculateAvgPlayers
1070 
1071 Calculates the average number of players playing this game
1072 ============
1073 */
G_CalculateAvgPlayers(void)1074 void G_CalculateAvgPlayers( void )
1075 {
1076   //there are no clients or only spectators connected, so
1077   //reset the number of samples in order to avoid the situation
1078   //where the average tends to 0
1079   if( !level.numAlienClients )
1080   {
1081     level.numAlienSamples = 0;
1082     trap_Cvar_Set( "g_alienKills", "0" );
1083   }
1084 
1085   if( !level.numHumanClients )
1086   {
1087     level.numHumanSamples = 0;
1088     trap_Cvar_Set( "g_humanKills", "0" );
1089   }
1090 
1091   //calculate average number of clients for stats
1092   level.averageNumAlienClients =
1093     ( ( level.averageNumAlienClients * level.numAlienSamples )
1094       + level.numAlienClients ) /
1095     (float)( level.numAlienSamples + 1 );
1096   level.numAlienSamples++;
1097 
1098   level.averageNumHumanClients =
1099     ( ( level.averageNumHumanClients * level.numHumanSamples )
1100       + level.numHumanClients ) /
1101     (float)( level.numHumanSamples + 1 );
1102   level.numHumanSamples++;
1103 }
1104 
1105 /*
1106 ============
1107 CalculateRanks
1108 
1109 Recalculates the score ranks of all players
1110 This will be called on every client connect, begin, disconnect, death,
1111 and team change.
1112 ============
1113 */
CalculateRanks(void)1114 void CalculateRanks( void )
1115 {
1116   int       i;
1117   int       rank;
1118   int       score;
1119   int       newScore;
1120   gclient_t *cl;
1121 
1122   level.follow1 = -1;
1123   level.follow2 = -1;
1124   level.numConnectedClients = 0;
1125   level.numNonSpectatorClients = 0;
1126   level.numPlayingClients = 0;
1127   level.numVotingClients = 0;   // don't count bots
1128   level.numAlienClients = 0;
1129   level.numHumanClients = 0;
1130   level.numLiveAlienClients = 0;
1131   level.numLiveHumanClients = 0;
1132 
1133   for( i = 0; i < TEAM_NUM_TEAMS; i++ )
1134     level.numteamVotingClients[ i ] = 0;
1135 
1136   for( i = 0; i < level.maxclients; i++ )
1137   {
1138     if ( level.clients[ i ].pers.connected != CON_DISCONNECTED )
1139     {
1140       level.sortedClients[ level.numConnectedClients ] = i;
1141       level.numConnectedClients++;
1142 
1143       if( !( level.clients[ i ].ps.pm_flags & PMF_FOLLOW ) )
1144       {
1145         //so we know when the game ends and for team leveling
1146         if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1147         {
1148           level.numAlienClients++;
1149           if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
1150             level.numLiveAlienClients++;
1151         }
1152 
1153         if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1154         {
1155           level.numHumanClients++;
1156           if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
1157             level.numLiveHumanClients++;
1158         }
1159       }
1160 
1161       if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR )
1162       {
1163         level.numNonSpectatorClients++;
1164 
1165         // decide if this should be auto-followed
1166         if( level.clients[ i ].pers.connected == CON_CONNECTED )
1167         {
1168           level.numPlayingClients++;
1169           if( !(g_entities[ i ].r.svFlags & SVF_BOT) )
1170             level.numVotingClients++;
1171 
1172           if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1173             level.numteamVotingClients[ 0 ]++;
1174           else if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1175             level.numteamVotingClients[ 1 ]++;
1176 
1177           if( level.follow1 == -1 )
1178             level.follow1 = i;
1179           else if( level.follow2 == -1 )
1180             level.follow2 = i;
1181         }
1182 
1183       }
1184     }
1185   }
1186 
1187   qsort( level.sortedClients, level.numConnectedClients,
1188     sizeof( level.sortedClients[ 0 ] ), SortRanks );
1189 
1190   // set the rank value for all clients that are connected and not spectators
1191   rank = -1;
1192   score = 0;
1193   for( i = 0;  i < level.numPlayingClients; i++ )
1194   {
1195     cl = &level.clients[ level.sortedClients[ i ] ];
1196     newScore = cl->ps.persistant[ PERS_SCORE ];
1197 
1198     if( i == 0 || newScore != score )
1199     {
1200       rank = i;
1201       // assume we aren't tied until the next client is checked
1202       level.clients[ level.sortedClients[ i ] ].ps.persistant[ PERS_RANK ] = rank;
1203     }
1204     else
1205     {
1206       // we are tied with the previous client
1207       level.clients[ level.sortedClients[ i - 1 ] ].ps.persistant[ PERS_RANK ] = rank;
1208       level.clients[ level.sortedClients[ i ] ].ps.persistant[ PERS_RANK ] = rank;
1209     }
1210 
1211     score = newScore;
1212   }
1213 
1214   // set the CS_SCORES1/2 configstrings, which will be visible to everyone
1215   if( level.numConnectedClients == 0 )
1216   {
1217     trap_SetConfigstring( CS_SCORES1, va( "%i", SCORE_NOT_PRESENT ) );
1218     trap_SetConfigstring( CS_SCORES2, va( "%i", SCORE_NOT_PRESENT ) );
1219   }
1220   else if( level.numConnectedClients == 1 )
1221   {
1222     trap_SetConfigstring( CS_SCORES1, va( "%i",
1223           level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ] ) );
1224     trap_SetConfigstring( CS_SCORES2, va( "%i", SCORE_NOT_PRESENT ) );
1225   }
1226   else
1227   {
1228     trap_SetConfigstring( CS_SCORES1, va( "%i",
1229           level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ] ) );
1230     trap_SetConfigstring( CS_SCORES2, va( "%i",
1231           level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ] ) );
1232   }
1233 
1234   // see if it is time to end the level
1235   CheckExitRules( );
1236 
1237   // if we are at the intermission, send the new info to everyone
1238   if( level.intermissiontime )
1239     SendScoreboardMessageToAllClients( );
1240 }
1241 
1242 
1243 /*
1244 ========================================================================
1245 
1246 MAP CHANGING
1247 
1248 ========================================================================
1249 */
1250 
1251 /*
1252 ========================
1253 SendScoreboardMessageToAllClients
1254 
1255 Do this at BeginIntermission time and whenever ranks are recalculated
1256 due to enters/exits/forced team changes
1257 ========================
1258 */
SendScoreboardMessageToAllClients(void)1259 void SendScoreboardMessageToAllClients( void )
1260 {
1261   int   i;
1262 
1263   for( i = 0; i < level.maxclients; i++ )
1264   {
1265     if( level.clients[ i ].pers.connected == CON_CONNECTED )
1266       ScoreboardMessage( g_entities + i );
1267   }
1268 }
1269 
1270 /*
1271 ========================
1272 MoveClientToIntermission
1273 
1274 When the intermission starts, this will be called for all players.
1275 If a new client connects, this will be called after the spawn function.
1276 ========================
1277 */
MoveClientToIntermission(gentity_t * ent)1278 void MoveClientToIntermission( gentity_t *ent )
1279 {
1280   // take out of follow mode if needed
1281   if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
1282     G_StopFollowing( ent );
1283 
1284   // move to the spot
1285   VectorCopy( level.intermission_origin, ent->s.origin );
1286   VectorCopy( level.intermission_origin, ent->client->ps.origin );
1287   VectorCopy( level.intermission_angle, ent->client->ps.viewangles );
1288   ent->client->ps.pm_type = PM_INTERMISSION;
1289 
1290   // clean up powerup info
1291   memset( ent->client->ps.powerups, 0, sizeof( ent->client->ps.powerups ) );
1292 
1293   ent->client->ps.eFlags = 0;
1294   ent->s.eFlags = 0;
1295   ent->s.eType = ET_GENERAL;
1296   ent->s.modelindex = 0;
1297   ent->s.loopSound = 0;
1298   ent->s.event = 0;
1299   ent->r.contents = 0;
1300 }
1301 
1302 /*
1303 ==================
1304 FindIntermissionPoint
1305 
1306 This is also used for spectator spawns
1307 ==================
1308 */
FindIntermissionPoint(void)1309 void FindIntermissionPoint( void )
1310 {
1311   gentity_t *ent, *target;
1312   vec3_t    dir;
1313 
1314   // find the intermission spot
1315   ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" );
1316 
1317   if( !ent )
1318   { // the map creator forgot to put in an intermission point...
1319     SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle );
1320   }
1321   else
1322   {
1323     VectorCopy( ent->s.origin, level.intermission_origin );
1324     VectorCopy( ent->s.angles, level.intermission_angle );
1325     // if it has a target, look towards it
1326     if( ent->target )
1327     {
1328       target = G_PickTarget( ent->target );
1329 
1330       if( target )
1331       {
1332         VectorSubtract( target->s.origin, level.intermission_origin, dir );
1333         vectoangles( dir, level.intermission_angle );
1334       }
1335     }
1336   }
1337 
1338 }
1339 
1340 /*
1341 ==================
1342 BeginIntermission
1343 ==================
1344 */
BeginIntermission(void)1345 void BeginIntermission( void )
1346 {
1347   int     i;
1348   gentity_t *client;
1349 
1350   if( level.intermissiontime )
1351     return;   // already active
1352 
1353   level.intermissiontime = level.time;
1354   FindIntermissionPoint( );
1355 
1356   // move all clients to the intermission point
1357   for( i = 0; i < level.maxclients; i++ )
1358   {
1359     client = g_entities + i;
1360 
1361     if( !client->inuse )
1362       continue;
1363 
1364     // respawn if dead
1365     if( client->health <= 0 )
1366       respawn(client);
1367 
1368     MoveClientToIntermission( client );
1369   }
1370 
1371   // send the current scoring to all clients
1372   SendScoreboardMessageToAllClients( );
1373 }
1374 
1375 
1376 /*
1377 =============
1378 ExitLevel
1379 
1380 When the intermission has been exited, the server is either killed
1381 or moved to a new level based on the "nextmap" cvar
1382 
1383 =============
1384 */
ExitLevel(void)1385 void ExitLevel( void )
1386 {
1387   int       i;
1388   gclient_t *cl;
1389 
1390   if( G_MapRotationActive( ) )
1391     G_AdvanceMapRotation( );
1392   else
1393     trap_SendConsoleCommand( EXEC_APPEND, "vstr nextmap\n" );
1394 
1395   level.changemap = NULL;
1396   level.intermissiontime = 0;
1397 
1398   // reset all the scores so we don't enter the intermission again
1399   for( i = 0; i < g_maxclients.integer; i++ )
1400   {
1401     cl = level.clients + i;
1402     if( cl->pers.connected != CON_CONNECTED )
1403       continue;
1404 
1405     cl->ps.persistant[ PERS_SCORE ] = 0;
1406   }
1407 
1408   // we need to do this here before chaning to CON_CONNECTING
1409   G_WriteSessionData( );
1410 
1411   // change all client states to connecting, so the early players into the
1412   // next level will know the others aren't done reconnecting
1413   for( i = 0; i < g_maxclients.integer; i++ )
1414   {
1415     if( level.clients[ i ].pers.connected == CON_CONNECTED )
1416       level.clients[ i ].pers.connected = CON_CONNECTING;
1417   }
1418 
1419 }
1420 
1421 /*
1422 =================
1423 G_LogPrintf
1424 
1425 Print to the logfile with a time stamp if it is open
1426 =================
1427 */
G_LogPrintf(const char * fmt,...)1428 void QDECL G_LogPrintf( const char *fmt, ... )
1429 {
1430   va_list argptr;
1431   char    string[ 1024 ];
1432   int     min, tens, sec;
1433 
1434   sec = level.time / 1000;
1435 
1436   min = sec / 60;
1437   sec -= min * 60;
1438   tens = sec / 10;
1439   sec -= tens * 10;
1440 
1441   Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
1442 
1443   va_start( argptr, fmt );
1444   vsprintf( string +7 , fmt,argptr );
1445   va_end( argptr );
1446 
1447   if( g_dedicated.integer )
1448     G_Printf( "%s", string + 7 );
1449 
1450   if( !level.logFile )
1451     return;
1452 
1453   trap_FS_Write( string, strlen( string ), level.logFile );
1454 }
1455 
1456 /*
1457 =================
1458 G_SendGameStat
1459 =================
1460 */
G_SendGameStat(pTeam_t team)1461 void G_SendGameStat( pTeam_t team )
1462 {
1463   char      map[ MAX_STRING_CHARS ];
1464   char      teamChar;
1465   char      data[ BIG_INFO_STRING ];
1466   char      entry[ MAX_STRING_CHARS ];
1467   int       i, dataLength, entryLength;
1468   gclient_t *cl;
1469 
1470   trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
1471 
1472   switch( team )
1473   {
1474     case PTE_ALIENS:  teamChar = 'A'; break;
1475     case PTE_HUMANS:  teamChar = 'H'; break;
1476     case PTE_NONE:    teamChar = 'L'; break;
1477     default: return;
1478   }
1479 
1480   Com_sprintf( data, BIG_INFO_STRING,
1481       "%s T:%c A:%f H:%f M:%s D:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d",
1482       Q3_VERSION,
1483       teamChar,
1484       level.averageNumAlienClients,
1485       level.averageNumHumanClients,
1486       map,
1487       level.time - level.startTime,
1488       g_alienStage.integer,
1489       level.alienStage2Time - level.startTime,
1490       level.alienStage3Time - level.startTime,
1491       g_humanStage.integer,
1492       level.humanStage2Time - level.startTime,
1493       level.humanStage3Time - level.startTime,
1494       level.numConnectedClients );
1495 
1496   dataLength = strlen( data );
1497 
1498   for( i = 0; i < level.numConnectedClients; i++ )
1499   {
1500     int ping;
1501 
1502     cl = &level.clients[ level.sortedClients[ i ] ];
1503 
1504     if( cl->pers.connected == CON_CONNECTING )
1505       ping = -1;
1506     else
1507       ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
1508 
1509     switch( cl->ps.stats[ STAT_PTEAM ] )
1510     {
1511       case PTE_ALIENS:  teamChar = 'A'; break;
1512       case PTE_HUMANS:  teamChar = 'H'; break;
1513       case PTE_NONE:    teamChar = 'S'; break;
1514       default: return;
1515     }
1516 
1517     Com_sprintf( entry, MAX_STRING_CHARS,
1518       " %s %c %d %d %d",
1519       cl->pers.netname,
1520       teamChar,
1521       cl->ps.persistant[ PERS_SCORE ],
1522       ping,
1523       ( level.time - cl->pers.enterTime ) / 60000 );
1524 
1525     entryLength = strlen( entry );
1526 
1527     if( dataLength + entryLength > MAX_STRING_CHARS )
1528       break;
1529 
1530     Q_strncpyz( data + dataLength, entry, BIG_INFO_STRING );
1531     dataLength += entryLength;
1532   }
1533 
1534   trap_SendGameStat( data );
1535 }
1536 
1537 /*
1538 ================
1539 LogExit
1540 
1541 Append information about this game to the log file
1542 ================
1543 */
LogExit(const char * string)1544 void LogExit( const char *string )
1545 {
1546   int         i, numSorted;
1547   gclient_t   *cl;
1548   gentity_t   *ent;
1549 
1550   G_LogPrintf( "Exit: %s\n", string );
1551 
1552   level.intermissionQueued = level.time;
1553 
1554   // this will keep the clients from playing any voice sounds
1555   // that will get cut off when the queued intermission starts
1556   trap_SetConfigstring( CS_INTERMISSION, "1" );
1557 
1558   // don't send more than 32 scores (FIXME?)
1559   numSorted = level.numConnectedClients;
1560   if( numSorted > 32 )
1561     numSorted = 32;
1562 
1563   for( i = 0; i < numSorted; i++ )
1564   {
1565     int   ping;
1566 
1567     cl = &level.clients[ level.sortedClients[ i ] ];
1568 
1569     if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE )
1570       continue;
1571 
1572     if( cl->pers.connected == CON_CONNECTING )
1573       continue;
1574 
1575     ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
1576 
1577     G_LogPrintf( "score: %i  ping: %i  client: %i %s\n",
1578       cl->ps.persistant[ PERS_SCORE ], ping, level.sortedClients[ i ],
1579       cl->pers.netname );
1580 
1581   }
1582 
1583   for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
1584   {
1585     if( !ent->inuse )
1586       continue;
1587 
1588     if( !Q_stricmp( ent->classname, "trigger_win" ) )
1589     {
1590       if( level.lastWin == ent->stageTeam )
1591         ent->use( ent, ent, ent );
1592     }
1593   }
1594 
1595   G_SendGameStat( level.lastWin );
1596 }
1597 
1598 
1599 /*
1600 =================
1601 CheckIntermissionExit
1602 
1603 The level will stay at the intermission for a minimum of 5 seconds
1604 If all players wish to continue, the level will then exit.
1605 If one or more players have not acknowledged the continue, the game will
1606 wait 10 seconds before going on.
1607 =================
1608 */
CheckIntermissionExit(void)1609 void CheckIntermissionExit( void )
1610 {
1611   int       ready, notReady, numPlayers;
1612   int       i;
1613   gclient_t *cl;
1614   int       readyMask;
1615 
1616   //if no clients are connected, just exit
1617   if( !level.numConnectedClients )
1618   {
1619     ExitLevel( );
1620     return;
1621   }
1622 
1623   // see which players are ready
1624   ready = 0;
1625   notReady = 0;
1626   readyMask = 0;
1627   numPlayers = 0;
1628   for( i = 0; i < g_maxclients.integer; i++ )
1629   {
1630     cl = level.clients + i;
1631     if( cl->pers.connected != CON_CONNECTED )
1632       continue;
1633 
1634     if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE )
1635       continue;
1636 
1637     if( g_entities[ cl->ps.clientNum ].r.svFlags & SVF_BOT )
1638       continue;
1639 
1640     if( cl->readyToExit )
1641     {
1642       ready++;
1643       if( i < 16 )
1644         readyMask |= 1 << i;
1645     }
1646     else
1647       notReady++;
1648 
1649     numPlayers++;
1650   }
1651 
1652   trap_SetConfigstring( CS_CLIENTS_READY, va( "%d", readyMask ) );
1653 
1654   // never exit in less than five seconds
1655   if( level.time < level.intermissiontime + 5000 )
1656     return;
1657 
1658   // if nobody wants to go, clear timer
1659   if( !ready && numPlayers )
1660   {
1661     level.readyToExit = qfalse;
1662     return;
1663   }
1664 
1665   // if everyone wants to go, go now
1666   if( !notReady )
1667   {
1668     ExitLevel( );
1669     return;
1670   }
1671 
1672   // the first person to ready starts the thirty second timeout
1673   if( !level.readyToExit )
1674   {
1675     level.readyToExit = qtrue;
1676     level.exitTime = level.time;
1677   }
1678 
1679   // if we have waited thirty seconds since at least one player
1680   // wanted to exit, go ahead
1681   if( level.time < level.exitTime + 30000 )
1682     return;
1683 
1684   ExitLevel( );
1685 }
1686 
1687 /*
1688 =============
1689 ScoreIsTied
1690 =============
1691 */
ScoreIsTied(void)1692 qboolean ScoreIsTied( void )
1693 {
1694   int   a, b;
1695 
1696   if( level.numPlayingClients < 2 )
1697     return qfalse;
1698 
1699   a = level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ];
1700   b = level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ];
1701 
1702   return a == b;
1703 }
1704 
1705 /*
1706 =================
1707 CheckExitRules
1708 
1709 There will be a delay between the time the exit is qualified for
1710 and the time everyone is moved to the intermission spot, so you
1711 can see the last frag.
1712 =================
1713 */
CheckExitRules(void)1714 void CheckExitRules( void )
1715 {
1716   // if at the intermission, wait for all non-bots to
1717   // signal ready, then go to next level
1718   if( level.intermissiontime )
1719   {
1720     CheckIntermissionExit( );
1721     return;
1722   }
1723 
1724   if( level.intermissionQueued )
1725   {
1726     if( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME )
1727     {
1728       level.intermissionQueued = 0;
1729       BeginIntermission( );
1730     }
1731 
1732     return;
1733   }
1734 
1735   if( g_timelimit.integer && !level.warmupTime )
1736   {
1737     if( level.time - level.startTime >= g_timelimit.integer * 60000 )
1738     {
1739       level.lastWin = PTE_NONE;
1740       G_SendCommandFromServer( -1, "print \"Timelimit hit\n\"" );
1741       LogExit( "Timelimit hit." );
1742       return;
1743     }
1744   }
1745 
1746   if( level.uncondHumanWin ||
1747       ( ( level.time > level.startTime + 1000 ) &&
1748         ( level.numAlienSpawns == 0 ) &&
1749         ( level.numLiveAlienClients == 0 ) ) )
1750   {
1751     //humans win
1752     level.lastWin = PTE_HUMANS;
1753     G_SendCommandFromServer( -1, "print \"Humans win\n\"");
1754     LogExit( "Humans win." );
1755   }
1756   else if( level.uncondAlienWin ||
1757            ( ( level.time > level.startTime + 1000 ) &&
1758              ( level.numHumanSpawns == 0 ) &&
1759              ( level.numLiveHumanClients == 0 ) ) )
1760   {
1761     //aliens win
1762     level.lastWin = PTE_ALIENS;
1763     G_SendCommandFromServer( -1, "print \"Aliens win\n\"");
1764     LogExit( "Aliens win." );
1765   }
1766 }
1767 
1768 
1769 
1770 /*
1771 ========================================================================
1772 
1773 FUNCTIONS CALLED EVERY FRAME
1774 
1775 ========================================================================
1776 */
1777 
1778 
1779 /*
1780 ==================
1781 CheckVote
1782 ==================
1783 */
CheckVote(void)1784 void CheckVote( void )
1785 {
1786   if( level.voteExecuteTime && level.voteExecuteTime < level.time )
1787   {
1788     level.voteExecuteTime = 0;
1789 
1790     //SUPAR HAK
1791     if( !Q_stricmp( level.voteString, "vstr nextmap" ) )
1792     {
1793       level.lastWin = PTE_NONE;
1794       LogExit( "Vote for next map." );
1795     }
1796     else
1797       trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
1798   }
1799 
1800   if( !level.voteTime )
1801     return;
1802 
1803   if( level.time - level.voteTime >= VOTE_TIME )
1804   {
1805     if( level.voteYes > level.voteNo )
1806     {
1807       // execute the command, then remove the vote
1808       G_SendCommandFromServer( -1, "print \"Vote passed\n\"" );
1809       level.voteExecuteTime = level.time + 3000;
1810     }
1811     else
1812     {
1813       // same behavior as a timeout
1814       G_SendCommandFromServer( -1, "print \"Vote failed\n\"" );
1815     }
1816   }
1817   else
1818   {
1819     if( level.voteYes > level.numConnectedClients / 2 )
1820     {
1821       // execute the command, then remove the vote
1822       G_SendCommandFromServer( -1, "print \"Vote passed\n\"" );
1823       level.voteExecuteTime = level.time + 3000;
1824     }
1825     else if( level.voteNo >= level.numConnectedClients / 2 )
1826     {
1827       // same behavior as a timeout
1828       G_SendCommandFromServer( -1, "print \"Vote failed\n\"" );
1829     }
1830     else
1831     {
1832       // still waiting for a majority
1833       return;
1834     }
1835   }
1836 
1837   level.voteTime = 0;
1838   trap_SetConfigstring( CS_VOTE_TIME, "" );
1839 }
1840 
1841 
1842 /*
1843 ==================
1844 CheckTeamVote
1845 ==================
1846 */
CheckTeamVote(int team)1847 void CheckTeamVote( int team )
1848 {
1849   int cs_offset;
1850 
1851   if ( team == PTE_HUMANS )
1852     cs_offset = 0;
1853   else if ( team == PTE_ALIENS )
1854     cs_offset = 1;
1855   else
1856     return;
1857 
1858   if( !level.teamVoteTime[ cs_offset ] )
1859     return;
1860 
1861   if( level.time - level.teamVoteTime[ cs_offset ] >= VOTE_TIME )
1862   {
1863     G_SendCommandFromServer( -1, "print \"Team vote failed\n\"" );
1864   }
1865   else
1866   {
1867     if( level.teamVoteYes[ cs_offset ] > level.numteamVotingClients[ cs_offset ] / 2 )
1868     {
1869       // execute the command, then remove the vote
1870       G_SendCommandFromServer( -1, "print \"Team vote passed\n\"" );
1871       //
1872       trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) );
1873     }
1874     else if( level.teamVoteNo[ cs_offset ] >= level.numteamVotingClients[ cs_offset ] / 2 )
1875     {
1876       // same behavior as a timeout
1877       G_SendCommandFromServer( -1, "print \"Team vote failed\n\"" );
1878     }
1879     else
1880     {
1881       // still waiting for a majority
1882       return;
1883     }
1884   }
1885 
1886   level.teamVoteTime[ cs_offset ] = 0;
1887   trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" );
1888 }
1889 
1890 
1891 /*
1892 ==================
1893 CheckCvars
1894 ==================
1895 */
CheckCvars(void)1896 void CheckCvars( void )
1897 {
1898   static int lastMod = -1;
1899 
1900   if( g_password.modificationCount != lastMod )
1901   {
1902     lastMod = g_password.modificationCount;
1903 
1904     if( *g_password.string && Q_stricmp( g_password.string, "none" ) )
1905       trap_Cvar_Set( "g_needpass", "1" );
1906     else
1907       trap_Cvar_Set( "g_needpass", "0" );
1908   }
1909 }
1910 
1911 /*
1912 =============
1913 G_RunThink
1914 
1915 Runs thinking code for this frame if necessary
1916 =============
1917 */
G_RunThink(gentity_t * ent)1918 void G_RunThink( gentity_t *ent )
1919 {
1920   float thinktime;
1921 
1922   thinktime = ent->nextthink;
1923   if( thinktime <= 0 )
1924     return;
1925 
1926   if( thinktime > level.time )
1927     return;
1928 
1929   ent->nextthink = 0;
1930   if( !ent->think )
1931     G_Error( "NULL ent->think" );
1932 
1933   ent->think( ent );
1934 }
1935 
1936 /*
1937 =============
1938 G_EvaluateAcceleration
1939 
1940 Calculates the acceleration for an entity
1941 =============
1942 */
G_EvaluateAcceleration(gentity_t * ent,int msec)1943 void G_EvaluateAcceleration( gentity_t *ent, int msec )
1944 {
1945   vec3_t  deltaVelocity;
1946   vec3_t  deltaAccel;
1947 
1948   VectorSubtract( ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity );
1949   VectorScale( deltaVelocity, 1.0f / (float)msec, ent->acceleration );
1950 
1951   VectorSubtract( ent->acceleration, ent->oldAccel, deltaAccel );
1952   VectorScale( deltaAccel, 1.0f / (float)msec, ent->jerk );
1953 
1954   VectorCopy( ent->s.pos.trDelta, ent->oldVelocity );
1955   VectorCopy( ent->acceleration, ent->oldAccel );
1956 }
1957 
1958 /*
1959 ================
1960 G_RunFrame
1961 
1962 Advances the non-player objects in the world
1963 ================
1964 */
G_RunFrame(int levelTime)1965 void G_RunFrame( int levelTime )
1966 {
1967   int       i;
1968   gentity_t *ent;
1969   int       msec;
1970   int       start, end;
1971 
1972   // if we are waiting for the level to restart, do nothing
1973   if( level.restarted )
1974     return;
1975 
1976   level.framenum++;
1977   level.previousTime = level.time;
1978   level.time = levelTime;
1979   msec = level.time - level.previousTime;
1980 
1981   //TA: seed the rng
1982   srand( level.framenum );
1983 
1984   // get any cvar changes
1985   G_UpdateCvars( );
1986 
1987   //
1988   // go through all allocated objects
1989   //
1990   start = trap_Milliseconds( );
1991   ent = &g_entities[ 0 ];
1992 
1993   for( i = 0; i < level.num_entities; i++, ent++ )
1994   {
1995     if( !ent->inuse )
1996       continue;
1997 
1998     // clear events that are too old
1999     if( level.time - ent->eventTime > EVENT_VALID_MSEC )
2000     {
2001       if( ent->s.event )
2002       {
2003         ent->s.event = 0; // &= EV_EVENT_BITS;
2004         if ( ent->client )
2005         {
2006           ent->client->ps.externalEvent = 0;
2007           //ent->client->ps.events[0] = 0;
2008           //ent->client->ps.events[1] = 0;
2009         }
2010       }
2011 
2012       if( ent->freeAfterEvent )
2013       {
2014         // tempEntities or dropped items completely go away after their event
2015         G_FreeEntity( ent );
2016         continue;
2017       }
2018       else if( ent->unlinkAfterEvent )
2019       {
2020         // items that will respawn will hide themselves after their pickup event
2021         ent->unlinkAfterEvent = qfalse;
2022         trap_UnlinkEntity( ent );
2023       }
2024     }
2025 
2026     // temporary entities don't think
2027     if( ent->freeAfterEvent )
2028       continue;
2029 
2030     //TA: calculate the acceleration of this entity
2031     if( ent->evaluateAcceleration )
2032       G_EvaluateAcceleration( ent, msec );
2033 
2034     if( !ent->r.linked && ent->neverFree )
2035       continue;
2036 
2037     if( ent->s.eType == ET_MISSILE )
2038     {
2039       G_RunMissile( ent );
2040       continue;
2041     }
2042 
2043     if( ent->s.eType == ET_BUILDABLE )
2044     {
2045       G_BuildableThink( ent, msec );
2046       continue;
2047     }
2048 
2049     if( ent->s.eType == ET_CORPSE || ent->physicsObject )
2050     {
2051       G_Physics( ent, msec );
2052       continue;
2053     }
2054 
2055     if( ent->s.eType == ET_MOVER )
2056     {
2057       G_RunMover( ent );
2058       continue;
2059     }
2060 
2061     if( i < MAX_CLIENTS )
2062     {
2063       G_RunClient( ent );
2064       continue;
2065     }
2066 
2067     G_RunThink( ent );
2068   }
2069   end = trap_Milliseconds();
2070 
2071   start = trap_Milliseconds();
2072 
2073   // perform final fixups on the players
2074   ent = &g_entities[ 0 ];
2075 
2076   for( i = 0; i < level.maxclients; i++, ent++ )
2077   {
2078     if( ent->inuse )
2079       ClientEndFrame( ent );
2080   }
2081 
2082   end = trap_Milliseconds();
2083 
2084   //TA:
2085   G_CountSpawns( );
2086   G_CalculateBuildPoints( );
2087   G_CalculateStages( );
2088   G_SpawnClients( PTE_ALIENS );
2089   G_SpawnClients( PTE_HUMANS );
2090   G_CalculateAvgPlayers( );
2091   G_UpdateZaps( msec );
2092 
2093   //send any pending commands
2094   G_ProcessCommandQueues( );
2095 
2096   // see if it is time to end the level
2097   CheckExitRules( );
2098 
2099   // update to team status?
2100   CheckTeamStatus( );
2101 
2102   // cancel vote if timed out
2103   CheckVote( );
2104 
2105   // check team votes
2106   CheckTeamVote( PTE_HUMANS );
2107   CheckTeamVote( PTE_ALIENS );
2108 
2109   // for tracking changes
2110   CheckCvars( );
2111 
2112   if( g_listEntity.integer )
2113   {
2114     for( i = 0; i < MAX_GENTITIES; i++ )
2115       G_Printf( "%4i: %s\n", i, g_entities[ i ].classname );
2116 
2117     trap_Cvar_Set( "g_listEntity", "0" );
2118   }
2119 }
2120 
2121