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