1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23
24 #include "g_local.h"
25
26 level_locals_t level;
27
28 typedef struct {
29 vmCvar_t *vmCvar;
30 char *cvarName;
31 char *defaultString;
32 int cvarFlags;
33 int modificationCount; // for tracking changes
34 qboolean trackChange; // track this variable, and announce if changed
35 qboolean teamShader; // track and if changed, update shader state
36 } cvarTable_t;
37
38 gentity_t g_entities[MAX_GENTITIES];
39 gclient_t g_clients[MAX_CLIENTS];
40
41 vmCvar_t g_gametype;
42 vmCvar_t g_dmflags;
43 vmCvar_t g_fraglimit;
44 vmCvar_t g_timelimit;
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 #ifdef MISSIONPACK
85 vmCvar_t g_obeliskHealth;
86 vmCvar_t g_obeliskRegenPeriod;
87 vmCvar_t g_obeliskRegenAmount;
88 vmCvar_t g_obeliskRespawnDelay;
89 vmCvar_t g_cubeTimeout;
90 vmCvar_t g_redteam;
91 vmCvar_t g_blueteam;
92 vmCvar_t g_singlePlayer;
93 vmCvar_t g_enableDust;
94 vmCvar_t g_enableBreath;
95 vmCvar_t g_proxMineTimeout;
96 #endif
97
98 static cvarTable_t gameCvarTable[] = {
99 // don't override the cheat state set by the system
100 { &g_cheats, "sv_cheats", "", 0, 0, qfalse },
101
102 // noset vars
103 { NULL, "gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
104 { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse },
105 { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse },
106 { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
107
108 // latched vars
109 { &g_gametype, "g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH, 0, qfalse },
110
111 { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
112 { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse },
113
114 // change anytime vars
115 { &g_dmflags, "dmflags", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue },
116 { &g_fraglimit, "fraglimit", "20", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
117 { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
118 { &g_capturelimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
119
120 { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse },
121
122 { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE, 0, qtrue },
123
124 { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE },
125 { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE },
126
127 { &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue },
128 { &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue },
129 { &g_logfile, "g_log", "games.log", CVAR_ARCHIVE, 0, qfalse },
130 { &g_logfileSync, "g_logsync", "0", CVAR_ARCHIVE, 0, qfalse },
131
132 { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse },
133
134 { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse },
135 { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse },
136
137 { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse },
138
139 { &g_dedicated, "dedicated", "0", 0, 0, qfalse },
140
141 { &g_speed, "g_speed", "320", 0, 0, qtrue },
142 { &g_gravity, "g_gravity", "800", 0, 0, qtrue },
143 { &g_knockback, "g_knockback", "1000", 0, 0, qtrue },
144 { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue },
145 { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue },
146 { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
147 { &g_forcerespawn, "g_forcerespawn", "20", 0, 0, qtrue },
148 { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
149 { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
150 { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse },
151 { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
152 { &g_motd, "g_motd", "", 0, 0, qfalse },
153 { &g_blood, "com_blood", "1", 0, 0, qfalse },
154
155 { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
156 { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
157
158 { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse },
159 { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
160
161 #ifdef MISSIONPACK
162 { &g_obeliskHealth, "g_obeliskHealth", "2500", 0, 0, qfalse },
163 { &g_obeliskRegenPeriod, "g_obeliskRegenPeriod", "1", 0, 0, qfalse },
164 { &g_obeliskRegenAmount, "g_obeliskRegenAmount", "15", 0, 0, qfalse },
165 { &g_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO, 0, qfalse },
166
167 { &g_cubeTimeout, "g_cubeTimeout", "30", 0, 0, qfalse },
168 { &g_redteam, "g_redteam", "Stroggs", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO , 0, qtrue, qtrue },
169 { &g_blueteam, "g_blueteam", "Pagans", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO , 0, qtrue, qtrue },
170 { &g_singlePlayer, "ui_singlePlayerActive", "", 0, 0, qfalse, qfalse },
171
172 { &g_enableDust, "g_enableDust", "0", CVAR_SERVERINFO, 0, qtrue, qfalse },
173 { &g_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO, 0, qtrue, qfalse },
174 { &g_proxMineTimeout, "g_proxMineTimeout", "20000", 0, 0, qfalse },
175 #endif
176 { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
177 { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
178 { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
179
180 { &g_rankings, "g_rankings", "0", 0, 0, qfalse}
181
182 };
183
184 static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[0] );
185
186
187 void G_InitGame( int levelTime, int randomSeed, int restart );
188 void G_RunFrame( int levelTime );
189 void G_ShutdownGame( int restart );
190 void CheckExitRules( void );
191
192
193 /*
194 ================
195 vmMain
196
197 This is the only way control passes into the module.
198 This must be the very first function compiled into the .q3vm file
199 ================
200 */
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)201 intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) {
202 switch ( command ) {
203 case GAME_INIT:
204 G_InitGame( arg0, arg1, arg2 );
205 return 0;
206 case GAME_SHUTDOWN:
207 G_ShutdownGame( arg0 );
208 return 0;
209 case GAME_CLIENT_CONNECT:
210 return (intptr_t)ClientConnect( arg0, arg1, arg2 );
211 case GAME_CLIENT_THINK:
212 ClientThink( arg0 );
213 return 0;
214 case GAME_CLIENT_USERINFO_CHANGED:
215 ClientUserinfoChanged( arg0 );
216 return 0;
217 case GAME_CLIENT_DISCONNECT:
218 ClientDisconnect( arg0 );
219 return 0;
220 case GAME_CLIENT_BEGIN:
221 ClientBegin( arg0 );
222 return 0;
223 case GAME_CLIENT_COMMAND:
224 ClientCommand( arg0 );
225 return 0;
226 case GAME_RUN_FRAME:
227 G_RunFrame( arg0 );
228 return 0;
229 case GAME_CONSOLE_COMMAND:
230 return ConsoleCommand();
231 case BOTAI_START_FRAME:
232 return BotAIStartFrame( arg0 );
233 }
234
235 return -1;
236 }
237
238
G_Printf(const char * fmt,...)239 void QDECL G_Printf( const char *fmt, ... ) {
240 va_list argptr;
241 char text[1024];
242
243 va_start (argptr, fmt);
244 Q_vsnprintf (text, sizeof(text), fmt, argptr);
245 va_end (argptr);
246
247 trap_Printf( text );
248 }
249
G_Error(const char * fmt,...)250 void QDECL G_Error( const char *fmt, ... ) {
251 va_list argptr;
252 char text[1024];
253
254 va_start (argptr, fmt);
255 Q_vsnprintf (text, sizeof(text), fmt, argptr);
256 va_end (argptr);
257
258 trap_Error( text );
259 }
260
261 /*
262 ================
263 G_FindTeams
264
265 Chain together all entities with a matching team field.
266 Entity teams are used for item groups and multi-entity mover groups.
267
268 All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
269 All but the last will have the teamchain field set to the next one
270 ================
271 */
G_FindTeams(void)272 void G_FindTeams( void ) {
273 gentity_t *e, *e2;
274 int i, j;
275 int c, c2;
276
277 c = 0;
278 c2 = 0;
279 for ( i=1, e=g_entities+i ; i < level.num_entities ; i++,e++ ){
280 if (!e->inuse)
281 continue;
282 if (!e->team)
283 continue;
284 if (e->flags & FL_TEAMSLAVE)
285 continue;
286 e->teammaster = e;
287 c++;
288 c2++;
289 for (j=i+1, e2=e+1 ; j < level.num_entities ; j++,e2++)
290 {
291 if (!e2->inuse)
292 continue;
293 if (!e2->team)
294 continue;
295 if (e2->flags & FL_TEAMSLAVE)
296 continue;
297 if (!strcmp(e->team, e2->team))
298 {
299 c2++;
300 e2->teamchain = e->teamchain;
301 e->teamchain = e2;
302 e2->teammaster = e;
303 e2->flags |= FL_TEAMSLAVE;
304
305 // make sure that targets only point at the master
306 if ( e2->targetname ) {
307 e->targetname = e2->targetname;
308 e2->targetname = NULL;
309 }
310 }
311 }
312 }
313
314 G_Printf ("%i teams with %i entities\n", c, c2);
315 }
316
G_RemapTeamShaders(void)317 void G_RemapTeamShaders( void ) {
318 #ifdef MISSIONPACK
319 char string[1024];
320 float f = level.time * 0.001;
321 Com_sprintf( string, sizeof(string), "team_icon/%s_red", g_redteam.string );
322 AddRemap("textures/ctf2/redteam01", string, f);
323 AddRemap("textures/ctf2/redteam02", string, f);
324 Com_sprintf( string, sizeof(string), "team_icon/%s_blue", g_blueteam.string );
325 AddRemap("textures/ctf2/blueteam01", string, f);
326 AddRemap("textures/ctf2/blueteam02", string, f);
327 trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig());
328 #endif
329 }
330
331
332 /*
333 =================
334 G_RegisterCvars
335 =================
336 */
G_RegisterCvars(void)337 void G_RegisterCvars( void ) {
338 int i;
339 cvarTable_t *cv;
340 qboolean remapped = qfalse;
341
342 for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
343 trap_Cvar_Register( cv->vmCvar, cv->cvarName,
344 cv->defaultString, cv->cvarFlags );
345 if ( cv->vmCvar )
346 cv->modificationCount = cv->vmCvar->modificationCount;
347
348 if (cv->teamShader) {
349 remapped = qtrue;
350 }
351 }
352
353 if (remapped) {
354 G_RemapTeamShaders();
355 }
356
357 // check some things
358 if ( g_gametype.integer < 0 || g_gametype.integer >= GT_MAX_GAME_TYPE ) {
359 G_Printf( "g_gametype %i is out of range, defaulting to 0\n", g_gametype.integer );
360 trap_Cvar_Set( "g_gametype", "0" );
361 }
362
363 level.warmupModificationCount = g_warmup.modificationCount;
364 }
365
366 /*
367 =================
368 G_UpdateCvars
369 =================
370 */
G_UpdateCvars(void)371 void G_UpdateCvars( void ) {
372 int i;
373 cvarTable_t *cv;
374 qboolean remapped = qfalse;
375
376 for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
377 if ( cv->vmCvar ) {
378 trap_Cvar_Update( cv->vmCvar );
379
380 if ( cv->modificationCount != cv->vmCvar->modificationCount ) {
381 cv->modificationCount = cv->vmCvar->modificationCount;
382
383 if ( cv->trackChange ) {
384 trap_SendServerCommand( -1, va("print \"Server: %s changed to %s\n\"",
385 cv->cvarName, cv->vmCvar->string ) );
386 }
387
388 if (cv->teamShader) {
389 remapped = qtrue;
390 }
391 }
392 }
393 }
394
395 if (remapped) {
396 G_RemapTeamShaders();
397 }
398 }
399
400 /*
401 ============
402 G_InitGame
403
404 ============
405 */
G_InitGame(int levelTime,int randomSeed,int restart)406 void G_InitGame( int levelTime, int randomSeed, int restart ) {
407 int i;
408
409 G_Printf ("------- Game Initialization -------\n");
410 G_Printf ("gamename: %s\n", GAMEVERSION);
411 G_Printf ("gamedate: %s\n", __DATE__);
412
413 srand( randomSeed );
414
415 G_RegisterCvars();
416
417 G_ProcessIPBans();
418
419 G_InitMemory();
420
421 // set some level globals
422 memset( &level, 0, sizeof( level ) );
423 level.time = levelTime;
424 level.startTime = levelTime;
425
426 level.snd_fry = G_SoundIndex("sound/player/fry.wav"); // FIXME standing in lava / slime
427
428 if ( g_gametype.integer != GT_SINGLE_PLAYER && g_logfile.string[0] ) {
429 if ( g_logfileSync.integer ) {
430 trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND_SYNC );
431 } else {
432 trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND );
433 }
434 if ( !level.logFile ) {
435 G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logfile.string );
436 } else {
437 char serverinfo[MAX_INFO_STRING];
438
439 trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
440
441 G_LogPrintf("------------------------------------------------------------\n" );
442 G_LogPrintf("InitGame: %s\n", serverinfo );
443 }
444 } else {
445 G_Printf( "Not logging to disk.\n" );
446 }
447
448 G_InitWorldSession();
449
450 // initialize all entities for this game
451 memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) );
452 level.gentities = g_entities;
453
454 // initialize all clients for this game
455 level.maxclients = g_maxclients.integer;
456 memset( g_clients, 0, MAX_CLIENTS * sizeof(g_clients[0]) );
457 level.clients = g_clients;
458
459 // set client fields on player ents
460 for ( i=0 ; i<level.maxclients ; i++ ) {
461 g_entities[i].client = level.clients + i;
462 }
463
464 // always leave room for the max number of clients,
465 // even if they aren't all used, so numbers inside that
466 // range are NEVER anything but clients
467 level.num_entities = MAX_CLIENTS;
468
469 // let the server system know where the entites are
470 trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
471 &level.clients[0].ps, sizeof( level.clients[0] ) );
472
473 // reserve some spots for dead player bodies
474 InitBodyQue();
475
476 ClearRegisteredItems();
477
478 // parse the key/value pairs and spawn gentities
479 G_SpawnEntitiesFromString();
480
481 // general initialization
482 G_FindTeams();
483
484 // make sure we have flags for CTF, etc
485 if( g_gametype.integer >= GT_TEAM ) {
486 G_CheckTeamItems();
487 }
488
489 SaveRegisteredItems();
490
491 G_Printf ("-----------------------------------\n");
492
493 if( g_gametype.integer == GT_SINGLE_PLAYER || trap_Cvar_VariableIntegerValue( "com_buildScript" ) ) {
494 G_ModelIndex( SP_PODIUM_MODEL );
495 G_SoundIndex( "sound/player/gurp1.wav" );
496 G_SoundIndex( "sound/player/gurp2.wav" );
497 }
498
499 if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
500 BotAISetup( restart );
501 BotAILoadMap( restart );
502 G_InitBots( restart );
503 }
504
505 G_RemapTeamShaders();
506
507 }
508
509
510
511 /*
512 =================
513 G_ShutdownGame
514 =================
515 */
G_ShutdownGame(int restart)516 void G_ShutdownGame( int restart ) {
517 G_Printf ("==== ShutdownGame ====\n");
518
519 if ( level.logFile ) {
520 G_LogPrintf("ShutdownGame:\n" );
521 G_LogPrintf("------------------------------------------------------------\n" );
522 trap_FS_FCloseFile( level.logFile );
523 }
524
525 // write all the client session data so we can get it back
526 G_WriteSessionData();
527
528 if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
529 BotAIShutdown( restart );
530 }
531 }
532
533
534
535 //===================================================================
536
Com_Error(int level,const char * error,...)537 void QDECL Com_Error ( int level, const char *error, ... ) {
538 va_list argptr;
539 char text[1024];
540
541 va_start (argptr, error);
542 Q_vsnprintf (text, sizeof(text), error, argptr);
543 va_end (argptr);
544
545 G_Error( "%s", text);
546 }
547
Com_Printf(const char * msg,...)548 void QDECL Com_Printf( const char *msg, ... ) {
549 va_list argptr;
550 char text[1024];
551
552 va_start (argptr, msg);
553 Q_vsnprintf (text, sizeof(text), msg, argptr);
554 va_end (argptr);
555
556 G_Printf ("%s", text);
557 }
558
559 /*
560 ========================================================================
561
562 PLAYER COUNTING / SCORE SORTING
563
564 ========================================================================
565 */
566
567 /*
568 =============
569 AddTournamentPlayer
570
571 If there are less than two tournament players, put a
572 spectator in the game and restart
573 =============
574 */
AddTournamentPlayer(void)575 void AddTournamentPlayer( void ) {
576 int i;
577 gclient_t *client;
578 gclient_t *nextInLine;
579
580 if ( level.numPlayingClients >= 2 ) {
581 return;
582 }
583
584 // never change during intermission
585 if ( level.intermissiontime ) {
586 return;
587 }
588
589 nextInLine = NULL;
590
591 for ( i = 0 ; i < level.maxclients ; i++ ) {
592 client = &level.clients[i];
593 if ( client->pers.connected != CON_CONNECTED ) {
594 continue;
595 }
596 if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
597 continue;
598 }
599 // never select the dedicated follow or scoreboard clients
600 if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ||
601 client->sess.spectatorClient < 0 ) {
602 continue;
603 }
604
605 if ( !nextInLine || client->sess.spectatorTime < nextInLine->sess.spectatorTime ) {
606 nextInLine = client;
607 }
608 }
609
610 if ( !nextInLine ) {
611 return;
612 }
613
614 level.warmupTime = -1;
615
616 // set them to free-for-all team
617 SetTeam( &g_entities[ nextInLine - level.clients ], "f" );
618 }
619
620 /*
621 =======================
622 RemoveTournamentLoser
623
624 Make the loser a spectator at the back of the line
625 =======================
626 */
RemoveTournamentLoser(void)627 void RemoveTournamentLoser( void ) {
628 int clientNum;
629
630 if ( level.numPlayingClients != 2 ) {
631 return;
632 }
633
634 clientNum = level.sortedClients[1];
635
636 if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
637 return;
638 }
639
640 // make them a spectator
641 SetTeam( &g_entities[ clientNum ], "s" );
642 }
643
644 /*
645 =======================
646 RemoveTournamentWinner
647 =======================
648 */
RemoveTournamentWinner(void)649 void RemoveTournamentWinner( void ) {
650 int clientNum;
651
652 if ( level.numPlayingClients != 2 ) {
653 return;
654 }
655
656 clientNum = level.sortedClients[0];
657
658 if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
659 return;
660 }
661
662 // make them a spectator
663 SetTeam( &g_entities[ clientNum ], "s" );
664 }
665
666 /*
667 =======================
668 AdjustTournamentScores
669 =======================
670 */
AdjustTournamentScores(void)671 void AdjustTournamentScores( void ) {
672 int clientNum;
673
674 clientNum = level.sortedClients[0];
675 if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
676 level.clients[ clientNum ].sess.wins++;
677 ClientUserinfoChanged( clientNum );
678 }
679
680 clientNum = level.sortedClients[1];
681 if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
682 level.clients[ clientNum ].sess.losses++;
683 ClientUserinfoChanged( clientNum );
684 }
685
686 }
687
688 /*
689 =============
690 SortRanks
691
692 =============
693 */
SortRanks(const void * a,const void * b)694 int QDECL SortRanks( const void *a, const void *b ) {
695 gclient_t *ca, *cb;
696
697 ca = &level.clients[*(int *)a];
698 cb = &level.clients[*(int *)b];
699
700 // sort special clients last
701 if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) {
702 return 1;
703 }
704 if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0 ) {
705 return -1;
706 }
707
708 // then connecting clients
709 if ( ca->pers.connected == CON_CONNECTING ) {
710 return 1;
711 }
712 if ( cb->pers.connected == CON_CONNECTING ) {
713 return -1;
714 }
715
716
717 // then spectators
718 if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) {
719 if ( ca->sess.spectatorTime < cb->sess.spectatorTime ) {
720 return -1;
721 }
722 if ( ca->sess.spectatorTime > cb->sess.spectatorTime ) {
723 return 1;
724 }
725 return 0;
726 }
727 if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) {
728 return 1;
729 }
730 if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) {
731 return -1;
732 }
733
734 // then sort by score
735 if ( ca->ps.persistant[PERS_SCORE]
736 > cb->ps.persistant[PERS_SCORE] ) {
737 return -1;
738 }
739 if ( ca->ps.persistant[PERS_SCORE]
740 < cb->ps.persistant[PERS_SCORE] ) {
741 return 1;
742 }
743 return 0;
744 }
745
746 /*
747 ============
748 CalculateRanks
749
750 Recalculates the score ranks of all players
751 This will be called on every client connect, begin, disconnect, death,
752 and team change.
753 ============
754 */
CalculateRanks(void)755 void CalculateRanks( void ) {
756 int i;
757 int rank;
758 int score;
759 int newScore;
760 gclient_t *cl;
761
762 level.follow1 = -1;
763 level.follow2 = -1;
764 level.numConnectedClients = 0;
765 level.numNonSpectatorClients = 0;
766 level.numPlayingClients = 0;
767 level.numVotingClients = 0; // don't count bots
768 for ( i = 0; i < TEAM_NUM_TEAMS; i++ ) {
769 level.numteamVotingClients[i] = 0;
770 }
771 for ( i = 0 ; i < level.maxclients ; i++ ) {
772 if ( level.clients[i].pers.connected != CON_DISCONNECTED ) {
773 level.sortedClients[level.numConnectedClients] = i;
774 level.numConnectedClients++;
775
776 if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) {
777 level.numNonSpectatorClients++;
778
779 // decide if this should be auto-followed
780 if ( level.clients[i].pers.connected == CON_CONNECTED ) {
781 level.numPlayingClients++;
782 if ( !(g_entities[i].r.svFlags & SVF_BOT) ) {
783 level.numVotingClients++;
784 if ( level.clients[i].sess.sessionTeam == TEAM_RED )
785 level.numteamVotingClients[0]++;
786 else if ( level.clients[i].sess.sessionTeam == TEAM_BLUE )
787 level.numteamVotingClients[1]++;
788 }
789 if ( level.follow1 == -1 ) {
790 level.follow1 = i;
791 } else if ( level.follow2 == -1 ) {
792 level.follow2 = i;
793 }
794 }
795 }
796 }
797 }
798
799 qsort( level.sortedClients, level.numConnectedClients,
800 sizeof(level.sortedClients[0]), SortRanks );
801
802 // set the rank value for all clients that are connected and not spectators
803 if ( g_gametype.integer >= GT_TEAM ) {
804 // in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied
805 for ( i = 0; i < level.numConnectedClients; i++ ) {
806 cl = &level.clients[ level.sortedClients[i] ];
807 if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) {
808 cl->ps.persistant[PERS_RANK] = 2;
809 } else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) {
810 cl->ps.persistant[PERS_RANK] = 0;
811 } else {
812 cl->ps.persistant[PERS_RANK] = 1;
813 }
814 }
815 } else {
816 rank = -1;
817 score = 0;
818 for ( i = 0; i < level.numPlayingClients; i++ ) {
819 cl = &level.clients[ level.sortedClients[i] ];
820 newScore = cl->ps.persistant[PERS_SCORE];
821 if ( i == 0 || newScore != score ) {
822 rank = i;
823 // assume we aren't tied until the next client is checked
824 level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank;
825 } else {
826 // we are tied with the previous client
827 level.clients[ level.sortedClients[i-1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
828 level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
829 }
830 score = newScore;
831 if ( g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1 ) {
832 level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
833 }
834 }
835 }
836
837 // set the CS_SCORES1/2 configstrings, which will be visible to everyone
838 if ( g_gametype.integer >= GT_TEAM ) {
839 trap_SetConfigstring( CS_SCORES1, va("%i", level.teamScores[TEAM_RED] ) );
840 trap_SetConfigstring( CS_SCORES2, va("%i", level.teamScores[TEAM_BLUE] ) );
841 } else {
842 if ( level.numConnectedClients == 0 ) {
843 trap_SetConfigstring( CS_SCORES1, va("%i", SCORE_NOT_PRESENT) );
844 trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) );
845 } else if ( level.numConnectedClients == 1 ) {
846 trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
847 trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) );
848 } else {
849 trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
850 trap_SetConfigstring( CS_SCORES2, va("%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) );
851 }
852 }
853
854 // see if it is time to end the level
855 CheckExitRules();
856
857 // if we are at the intermission, send the new info to everyone
858 if ( level.intermissiontime ) {
859 SendScoreboardMessageToAllClients();
860 }
861 }
862
863
864 /*
865 ========================================================================
866
867 MAP CHANGING
868
869 ========================================================================
870 */
871
872 /*
873 ========================
874 SendScoreboardMessageToAllClients
875
876 Do this at BeginIntermission time and whenever ranks are recalculated
877 due to enters/exits/forced team changes
878 ========================
879 */
SendScoreboardMessageToAllClients(void)880 void SendScoreboardMessageToAllClients( void ) {
881 int i;
882
883 for ( i = 0 ; i < level.maxclients ; i++ ) {
884 if ( level.clients[ i ].pers.connected == CON_CONNECTED ) {
885 DeathmatchScoreboardMessage( g_entities + i );
886 }
887 }
888 }
889
890 /*
891 ========================
892 MoveClientToIntermission
893
894 When the intermission starts, this will be called for all players.
895 If a new client connects, this will be called after the spawn function.
896 ========================
897 */
MoveClientToIntermission(gentity_t * ent)898 void MoveClientToIntermission( gentity_t *ent ) {
899 // take out of follow mode if needed
900 if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
901 StopFollowing( ent );
902 }
903
904
905 // move to the spot
906 VectorCopy( level.intermission_origin, ent->s.origin );
907 VectorCopy( level.intermission_origin, ent->client->ps.origin );
908 VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
909 ent->client->ps.pm_type = PM_INTERMISSION;
910
911 // clean up powerup info
912 memset( ent->client->ps.powerups, 0, sizeof(ent->client->ps.powerups) );
913
914 ent->client->ps.eFlags = 0;
915 ent->s.eFlags = 0;
916 ent->s.eType = ET_GENERAL;
917 ent->s.modelindex = 0;
918 ent->s.loopSound = 0;
919 ent->s.event = 0;
920 ent->r.contents = 0;
921 }
922
923 /*
924 ==================
925 FindIntermissionPoint
926
927 This is also used for spectator spawns
928 ==================
929 */
FindIntermissionPoint(void)930 void FindIntermissionPoint( void ) {
931 gentity_t *ent, *target;
932 vec3_t dir;
933
934 // find the intermission spot
935 ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
936 if ( !ent ) { // the map creator forgot to put in an intermission point...
937 SelectSpawnPoint ( vec3_origin, level.intermission_origin, level.intermission_angle );
938 } else {
939 VectorCopy (ent->s.origin, level.intermission_origin);
940 VectorCopy (ent->s.angles, level.intermission_angle);
941 // if it has a target, look towards it
942 if ( ent->target ) {
943 target = G_PickTarget( ent->target );
944 if ( target ) {
945 VectorSubtract( target->s.origin, level.intermission_origin, dir );
946 vectoangles( dir, level.intermission_angle );
947 }
948 }
949 }
950
951 }
952
953 /*
954 ==================
955 BeginIntermission
956 ==================
957 */
BeginIntermission(void)958 void BeginIntermission( void ) {
959 int i;
960 gentity_t *client;
961
962 if ( level.intermissiontime ) {
963 return; // already active
964 }
965
966 // if in tournement mode, change the wins / losses
967 if ( g_gametype.integer == GT_TOURNAMENT ) {
968 AdjustTournamentScores();
969 }
970
971 level.intermissiontime = level.time;
972 FindIntermissionPoint();
973
974 #ifdef MISSIONPACK
975 if (g_singlePlayer.integer) {
976 trap_Cvar_Set("ui_singlePlayerActive", "0");
977 UpdateTournamentInfo();
978 }
979 #else
980 // if single player game
981 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
982 UpdateTournamentInfo();
983 SpawnModelsOnVictoryPads();
984 }
985 #endif
986
987 // move all clients to the intermission point
988 for (i=0 ; i< level.maxclients ; i++) {
989 client = g_entities + i;
990 if (!client->inuse)
991 continue;
992 // respawn if dead
993 if (client->health <= 0) {
994 respawn(client);
995 }
996 MoveClientToIntermission( client );
997 }
998
999 // send the current scoring to all clients
1000 SendScoreboardMessageToAllClients();
1001
1002 }
1003
1004
1005 /*
1006 =============
1007 ExitLevel
1008
1009 When the intermission has been exited, the server is either killed
1010 or moved to a new level based on the "nextmap" cvar
1011
1012 =============
1013 */
ExitLevel(void)1014 void ExitLevel (void) {
1015 int i;
1016 gclient_t *cl;
1017 char nextmap[MAX_STRING_CHARS];
1018 char d1[MAX_STRING_CHARS];
1019
1020 //bot interbreeding
1021 BotInterbreedEndMatch();
1022
1023 // if we are running a tournement map, kick the loser to spectator status,
1024 // which will automatically grab the next spectator and restart
1025 if ( g_gametype.integer == GT_TOURNAMENT ) {
1026 if ( !level.restarted ) {
1027 RemoveTournamentLoser();
1028 trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1029 level.restarted = qtrue;
1030 level.changemap = NULL;
1031 level.intermissiontime = 0;
1032 }
1033 return;
1034 }
1035
1036 trap_Cvar_VariableStringBuffer( "nextmap", nextmap, sizeof(nextmap) );
1037 trap_Cvar_VariableStringBuffer( "d1", d1, sizeof(d1) );
1038
1039 if( !Q_stricmp( nextmap, "map_restart 0" ) && Q_stricmp( d1, "" ) ) {
1040 trap_Cvar_Set( "nextmap", "vstr d2" );
1041 trap_SendConsoleCommand( EXEC_APPEND, "vstr d1\n" );
1042 } else {
1043 trap_SendConsoleCommand( EXEC_APPEND, "vstr nextmap\n" );
1044 }
1045
1046 level.changemap = NULL;
1047 level.intermissiontime = 0;
1048
1049 // reset all the scores so we don't enter the intermission again
1050 level.teamScores[TEAM_RED] = 0;
1051 level.teamScores[TEAM_BLUE] = 0;
1052 for ( i=0 ; i< g_maxclients.integer ; i++ ) {
1053 cl = level.clients + i;
1054 if ( cl->pers.connected != CON_CONNECTED ) {
1055 continue;
1056 }
1057 cl->ps.persistant[PERS_SCORE] = 0;
1058 }
1059
1060 // we need to do this here before chaning to CON_CONNECTING
1061 G_WriteSessionData();
1062
1063 // change all client states to connecting, so the early players into the
1064 // next level will know the others aren't done reconnecting
1065 for (i=0 ; i< g_maxclients.integer ; i++) {
1066 if ( level.clients[i].pers.connected == CON_CONNECTED ) {
1067 level.clients[i].pers.connected = CON_CONNECTING;
1068 }
1069 }
1070
1071 }
1072
1073 /*
1074 =================
1075 G_LogPrintf
1076
1077 Print to the logfile with a time stamp if it is open
1078 =================
1079 */
G_LogPrintf(const char * fmt,...)1080 void QDECL G_LogPrintf( const char *fmt, ... ) {
1081 va_list argptr;
1082 char string[1024];
1083 int min, tens, sec;
1084
1085 sec = level.time / 1000;
1086
1087 min = sec / 60;
1088 sec -= min * 60;
1089 tens = sec / 10;
1090 sec -= tens * 10;
1091
1092 Com_sprintf( string, sizeof(string), "%3i:%i%i ", min, tens, sec );
1093
1094 va_start( argptr, fmt );
1095 Q_vsnprintf(string + 7, sizeof(string - 7), fmt, argptr);
1096 va_end( argptr );
1097
1098 if ( g_dedicated.integer ) {
1099 G_Printf( "%s", string + 7 );
1100 }
1101
1102 if ( !level.logFile ) {
1103 return;
1104 }
1105
1106 trap_FS_Write( string, strlen( string ), level.logFile );
1107 }
1108
1109 /*
1110 ================
1111 LogExit
1112
1113 Append information about this game to the log file
1114 ================
1115 */
LogExit(const char * string)1116 void LogExit( const char *string ) {
1117 int i, numSorted;
1118 gclient_t *cl;
1119 #ifdef MISSIONPACK
1120 qboolean won = qtrue;
1121 #endif
1122 G_LogPrintf( "Exit: %s\n", string );
1123
1124 level.intermissionQueued = level.time;
1125
1126 // this will keep the clients from playing any voice sounds
1127 // that will get cut off when the queued intermission starts
1128 trap_SetConfigstring( CS_INTERMISSION, "1" );
1129
1130 // don't send more than 32 scores (FIXME?)
1131 numSorted = level.numConnectedClients;
1132 if ( numSorted > 32 ) {
1133 numSorted = 32;
1134 }
1135
1136 if ( g_gametype.integer >= GT_TEAM ) {
1137 G_LogPrintf( "red:%i blue:%i\n",
1138 level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE] );
1139 }
1140
1141 for (i=0 ; i < numSorted ; i++) {
1142 int ping;
1143
1144 cl = &level.clients[level.sortedClients[i]];
1145
1146 if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) {
1147 continue;
1148 }
1149 if ( cl->pers.connected == CON_CONNECTING ) {
1150 continue;
1151 }
1152
1153 ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
1154
1155 G_LogPrintf( "score: %i ping: %i client: %i %s\n", cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i], cl->pers.netname );
1156 #ifdef MISSIONPACK
1157 if (g_singlePlayer.integer && g_gametype.integer == GT_TOURNAMENT) {
1158 if (g_entities[cl - level.clients].r.svFlags & SVF_BOT && cl->ps.persistant[PERS_RANK] == 0) {
1159 won = qfalse;
1160 }
1161 }
1162 #endif
1163
1164 }
1165
1166 #ifdef MISSIONPACK
1167 if (g_singlePlayer.integer) {
1168 if (g_gametype.integer >= GT_CTF) {
1169 won = level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE];
1170 }
1171 trap_SendConsoleCommand( EXEC_APPEND, (won) ? "spWin\n" : "spLose\n" );
1172 }
1173 #endif
1174
1175
1176 }
1177
1178
1179 /*
1180 =================
1181 CheckIntermissionExit
1182
1183 The level will stay at the intermission for a minimum of 5 seconds
1184 If all players wish to continue, the level will then exit.
1185 If one or more players have not acknowledged the continue, the game will
1186 wait 10 seconds before going on.
1187 =================
1188 */
CheckIntermissionExit(void)1189 void CheckIntermissionExit( void ) {
1190 int ready, notReady, playerCount;
1191 int i;
1192 gclient_t *cl;
1193 int readyMask;
1194
1195 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
1196 return;
1197 }
1198
1199 // see which players are ready
1200 ready = 0;
1201 notReady = 0;
1202 readyMask = 0;
1203 playerCount = 0;
1204 for (i=0 ; i< g_maxclients.integer ; i++) {
1205 cl = level.clients + i;
1206 if ( cl->pers.connected != CON_CONNECTED ) {
1207 continue;
1208 }
1209 if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) {
1210 continue;
1211 }
1212
1213 playerCount++;
1214 if ( cl->readyToExit ) {
1215 ready++;
1216 if ( i < 16 ) {
1217 readyMask |= 1 << i;
1218 }
1219 } else {
1220 notReady++;
1221 }
1222 }
1223
1224 // copy the readyMask to each player's stats so
1225 // it can be displayed on the scoreboard
1226 for (i=0 ; i< g_maxclients.integer ; i++) {
1227 cl = level.clients + i;
1228 if ( cl->pers.connected != CON_CONNECTED ) {
1229 continue;
1230 }
1231 cl->ps.stats[STAT_CLIENTS_READY] = readyMask;
1232 }
1233
1234 // never exit in less than five seconds
1235 if ( level.time < level.intermissiontime + 5000 ) {
1236 return;
1237 }
1238
1239 // only test ready status when there are real players present
1240 if ( playerCount > 0 ) {
1241 // if nobody wants to go, clear timer
1242 if ( !ready ) {
1243 level.readyToExit = qfalse;
1244 return;
1245 }
1246
1247 // if everyone wants to go, go now
1248 if ( !notReady ) {
1249 ExitLevel();
1250 return;
1251 }
1252 }
1253
1254 // the first person to ready starts the ten second timeout
1255 if ( !level.readyToExit ) {
1256 level.readyToExit = qtrue;
1257 level.exitTime = level.time;
1258 }
1259
1260 // if we have waited ten seconds since at least one player
1261 // wanted to exit, go ahead
1262 if ( level.time < level.exitTime + 10000 ) {
1263 return;
1264 }
1265
1266 ExitLevel();
1267 }
1268
1269 /*
1270 =============
1271 ScoreIsTied
1272 =============
1273 */
ScoreIsTied(void)1274 qboolean ScoreIsTied( void ) {
1275 int a, b;
1276
1277 if ( level.numPlayingClients < 2 ) {
1278 return qfalse;
1279 }
1280
1281 if ( g_gametype.integer >= GT_TEAM ) {
1282 return level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE];
1283 }
1284
1285 a = level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE];
1286 b = level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE];
1287
1288 return a == b;
1289 }
1290
1291 /*
1292 =================
1293 CheckExitRules
1294
1295 There will be a delay between the time the exit is qualified for
1296 and the time everyone is moved to the intermission spot, so you
1297 can see the last frag.
1298 =================
1299 */
CheckExitRules(void)1300 void CheckExitRules( void ) {
1301 int i;
1302 gclient_t *cl;
1303 // if at the intermission, wait for all non-bots to
1304 // signal ready, then go to next level
1305 if ( level.intermissiontime ) {
1306 CheckIntermissionExit ();
1307 return;
1308 }
1309
1310 if ( level.intermissionQueued ) {
1311 #ifdef MISSIONPACK
1312 int time = (g_singlePlayer.integer) ? SP_INTERMISSION_DELAY_TIME : INTERMISSION_DELAY_TIME;
1313 if ( level.time - level.intermissionQueued >= time ) {
1314 level.intermissionQueued = 0;
1315 BeginIntermission();
1316 }
1317 #else
1318 if ( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME ) {
1319 level.intermissionQueued = 0;
1320 BeginIntermission();
1321 }
1322 #endif
1323 return;
1324 }
1325
1326 // check for sudden death
1327 if ( ScoreIsTied() ) {
1328 // always wait for sudden death
1329 return;
1330 }
1331
1332 if ( g_timelimit.integer && !level.warmupTime ) {
1333 if ( level.time - level.startTime >= g_timelimit.integer*60000 ) {
1334 trap_SendServerCommand( -1, "print \"Timelimit hit.\n\"");
1335 LogExit( "Timelimit hit." );
1336 return;
1337 }
1338 }
1339
1340 if ( level.numPlayingClients < 2 ) {
1341 return;
1342 }
1343
1344 if ( g_gametype.integer < GT_CTF && g_fraglimit.integer ) {
1345 if ( level.teamScores[TEAM_RED] >= g_fraglimit.integer ) {
1346 trap_SendServerCommand( -1, "print \"Red hit the fraglimit.\n\"" );
1347 LogExit( "Fraglimit hit." );
1348 return;
1349 }
1350
1351 if ( level.teamScores[TEAM_BLUE] >= g_fraglimit.integer ) {
1352 trap_SendServerCommand( -1, "print \"Blue hit the fraglimit.\n\"" );
1353 LogExit( "Fraglimit hit." );
1354 return;
1355 }
1356
1357 for ( i=0 ; i< g_maxclients.integer ; i++ ) {
1358 cl = level.clients + i;
1359 if ( cl->pers.connected != CON_CONNECTED ) {
1360 continue;
1361 }
1362 if ( cl->sess.sessionTeam != TEAM_FREE ) {
1363 continue;
1364 }
1365
1366 if ( cl->ps.persistant[PERS_SCORE] >= g_fraglimit.integer ) {
1367 LogExit( "Fraglimit hit." );
1368 trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " hit the fraglimit.\n\"",
1369 cl->pers.netname ) );
1370 return;
1371 }
1372 }
1373 }
1374
1375 if ( g_gametype.integer >= GT_CTF && g_capturelimit.integer ) {
1376
1377 if ( level.teamScores[TEAM_RED] >= g_capturelimit.integer ) {
1378 trap_SendServerCommand( -1, "print \"Red hit the capturelimit.\n\"" );
1379 LogExit( "Capturelimit hit." );
1380 return;
1381 }
1382
1383 if ( level.teamScores[TEAM_BLUE] >= g_capturelimit.integer ) {
1384 trap_SendServerCommand( -1, "print \"Blue hit the capturelimit.\n\"" );
1385 LogExit( "Capturelimit hit." );
1386 return;
1387 }
1388 }
1389 }
1390
1391
1392
1393 /*
1394 ========================================================================
1395
1396 FUNCTIONS CALLED EVERY FRAME
1397
1398 ========================================================================
1399 */
1400
1401
1402 /*
1403 =============
1404 CheckTournament
1405
1406 Once a frame, check for changes in tournement player state
1407 =============
1408 */
CheckTournament(void)1409 void CheckTournament( void ) {
1410 // check because we run 3 game frames before calling Connect and/or ClientBegin
1411 // for clients on a map_restart
1412 if ( level.numPlayingClients == 0 ) {
1413 return;
1414 }
1415
1416 if ( g_gametype.integer == GT_TOURNAMENT ) {
1417
1418 // pull in a spectator if needed
1419 if ( level.numPlayingClients < 2 ) {
1420 AddTournamentPlayer();
1421 }
1422
1423 // if we don't have two players, go back to "waiting for players"
1424 if ( level.numPlayingClients != 2 ) {
1425 if ( level.warmupTime != -1 ) {
1426 level.warmupTime = -1;
1427 trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1428 G_LogPrintf( "Warmup:\n" );
1429 }
1430 return;
1431 }
1432
1433 if ( level.warmupTime == 0 ) {
1434 return;
1435 }
1436
1437 // if the warmup is changed at the console, restart it
1438 if ( g_warmup.modificationCount != level.warmupModificationCount ) {
1439 level.warmupModificationCount = g_warmup.modificationCount;
1440 level.warmupTime = -1;
1441 }
1442
1443 // if all players have arrived, start the countdown
1444 if ( level.warmupTime < 0 ) {
1445 if ( level.numPlayingClients == 2 ) {
1446 // fudge by -1 to account for extra delays
1447 if ( g_warmup.integer > 1 ) {
1448 level.warmupTime = level.time + ( g_warmup.integer - 1 ) * 1000;
1449 } else {
1450 level.warmupTime = 0;
1451 }
1452
1453 trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1454 }
1455 return;
1456 }
1457
1458 // if the warmup time has counted down, restart
1459 if ( level.time > level.warmupTime ) {
1460 level.warmupTime += 10000;
1461 trap_Cvar_Set( "g_restarted", "1" );
1462 trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1463 level.restarted = qtrue;
1464 return;
1465 }
1466 } else if ( g_gametype.integer != GT_SINGLE_PLAYER && level.warmupTime != 0 ) {
1467 int counts[TEAM_NUM_TEAMS];
1468 qboolean notEnough = qfalse;
1469
1470 if ( g_gametype.integer > GT_TEAM ) {
1471 counts[TEAM_BLUE] = TeamCount( -1, TEAM_BLUE );
1472 counts[TEAM_RED] = TeamCount( -1, TEAM_RED );
1473
1474 if (counts[TEAM_RED] < 1 || counts[TEAM_BLUE] < 1) {
1475 notEnough = qtrue;
1476 }
1477 } else if ( level.numPlayingClients < 2 ) {
1478 notEnough = qtrue;
1479 }
1480
1481 if ( notEnough ) {
1482 if ( level.warmupTime != -1 ) {
1483 level.warmupTime = -1;
1484 trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1485 G_LogPrintf( "Warmup:\n" );
1486 }
1487 return; // still waiting for team members
1488 }
1489
1490 if ( level.warmupTime == 0 ) {
1491 return;
1492 }
1493
1494 // if the warmup is changed at the console, restart it
1495 if ( g_warmup.modificationCount != level.warmupModificationCount ) {
1496 level.warmupModificationCount = g_warmup.modificationCount;
1497 level.warmupTime = -1;
1498 }
1499
1500 // if all players have arrived, start the countdown
1501 if ( level.warmupTime < 0 ) {
1502 // fudge by -1 to account for extra delays
1503 level.warmupTime = level.time + ( g_warmup.integer - 1 ) * 1000;
1504 trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) );
1505 return;
1506 }
1507
1508 // if the warmup time has counted down, restart
1509 if ( level.time > level.warmupTime ) {
1510 level.warmupTime += 10000;
1511 trap_Cvar_Set( "g_restarted", "1" );
1512 trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1513 level.restarted = qtrue;
1514 return;
1515 }
1516 }
1517 }
1518
1519
1520 /*
1521 ==================
1522 CheckVote
1523 ==================
1524 */
CheckVote(void)1525 void CheckVote( void ) {
1526 if ( level.voteExecuteTime && level.voteExecuteTime < level.time ) {
1527 level.voteExecuteTime = 0;
1528 trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) );
1529 }
1530 if ( !level.voteTime ) {
1531 return;
1532 }
1533 if ( level.time - level.voteTime >= VOTE_TIME ) {
1534 trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
1535 } else {
1536 // ATVI Q3 1.32 Patch #9, WNF
1537 if ( level.voteYes > level.numVotingClients/2 ) {
1538 // execute the command, then remove the vote
1539 trap_SendServerCommand( -1, "print \"Vote passed.\n\"" );
1540 level.voteExecuteTime = level.time + 3000;
1541 } else if ( level.voteNo >= level.numVotingClients/2 ) {
1542 // same behavior as a timeout
1543 trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
1544 } else {
1545 // still waiting for a majority
1546 return;
1547 }
1548 }
1549 level.voteTime = 0;
1550 trap_SetConfigstring( CS_VOTE_TIME, "" );
1551
1552 }
1553
1554 /*
1555 ==================
1556 PrintTeam
1557 ==================
1558 */
PrintTeam(int team,char * message)1559 void PrintTeam(int team, char *message) {
1560 int i;
1561
1562 for ( i = 0 ; i < level.maxclients ; i++ ) {
1563 if (level.clients[i].sess.sessionTeam != team)
1564 continue;
1565 trap_SendServerCommand( i, message );
1566 }
1567 }
1568
1569 /*
1570 ==================
1571 SetLeader
1572 ==================
1573 */
SetLeader(int team,int client)1574 void SetLeader(int team, int client) {
1575 int i;
1576
1577 if ( level.clients[client].pers.connected == CON_DISCONNECTED ) {
1578 PrintTeam(team, va("print \"%s is not connected\n\"", level.clients[client].pers.netname) );
1579 return;
1580 }
1581 if (level.clients[client].sess.sessionTeam != team) {
1582 PrintTeam(team, va("print \"%s is not on the team anymore\n\"", level.clients[client].pers.netname) );
1583 return;
1584 }
1585 for ( i = 0 ; i < level.maxclients ; i++ ) {
1586 if (level.clients[i].sess.sessionTeam != team)
1587 continue;
1588 if (level.clients[i].sess.teamLeader) {
1589 level.clients[i].sess.teamLeader = qfalse;
1590 ClientUserinfoChanged(i);
1591 }
1592 }
1593 level.clients[client].sess.teamLeader = qtrue;
1594 ClientUserinfoChanged( client );
1595 PrintTeam(team, va("print \"%s is the new team leader\n\"", level.clients[client].pers.netname) );
1596 }
1597
1598 /*
1599 ==================
1600 CheckTeamLeader
1601 ==================
1602 */
CheckTeamLeader(int team)1603 void CheckTeamLeader( int team ) {
1604 int i;
1605
1606 for ( i = 0 ; i < level.maxclients ; i++ ) {
1607 if (level.clients[i].sess.sessionTeam != team)
1608 continue;
1609 if (level.clients[i].sess.teamLeader)
1610 break;
1611 }
1612 if (i >= level.maxclients) {
1613 for ( i = 0 ; i < level.maxclients ; i++ ) {
1614 if (level.clients[i].sess.sessionTeam != team)
1615 continue;
1616 if (!(g_entities[i].r.svFlags & SVF_BOT)) {
1617 level.clients[i].sess.teamLeader = qtrue;
1618 break;
1619 }
1620 }
1621 for ( i = 0 ; i < level.maxclients ; i++ ) {
1622 if (level.clients[i].sess.sessionTeam != team)
1623 continue;
1624 level.clients[i].sess.teamLeader = qtrue;
1625 break;
1626 }
1627 }
1628 }
1629
1630 /*
1631 ==================
1632 CheckTeamVote
1633 ==================
1634 */
CheckTeamVote(int team)1635 void CheckTeamVote( int team ) {
1636 int cs_offset;
1637
1638 if ( team == TEAM_RED )
1639 cs_offset = 0;
1640 else if ( team == TEAM_BLUE )
1641 cs_offset = 1;
1642 else
1643 return;
1644
1645 if ( !level.teamVoteTime[cs_offset] ) {
1646 return;
1647 }
1648 if ( level.time - level.teamVoteTime[cs_offset] >= VOTE_TIME ) {
1649 trap_SendServerCommand( -1, "print \"Team vote failed.\n\"" );
1650 } else {
1651 if ( level.teamVoteYes[cs_offset] > level.numteamVotingClients[cs_offset]/2 ) {
1652 // execute the command, then remove the vote
1653 trap_SendServerCommand( -1, "print \"Team vote passed.\n\"" );
1654 //
1655 if ( !Q_strncmp( "leader", level.teamVoteString[cs_offset], 6) ) {
1656 //set the team leader
1657 SetLeader(team, atoi(level.teamVoteString[cs_offset] + 7));
1658 }
1659 else {
1660 trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.teamVoteString[cs_offset] ) );
1661 }
1662 } else if ( level.teamVoteNo[cs_offset] >= level.numteamVotingClients[cs_offset]/2 ) {
1663 // same behavior as a timeout
1664 trap_SendServerCommand( -1, "print \"Team vote failed.\n\"" );
1665 } else {
1666 // still waiting for a majority
1667 return;
1668 }
1669 }
1670 level.teamVoteTime[cs_offset] = 0;
1671 trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" );
1672
1673 }
1674
1675
1676 /*
1677 ==================
1678 CheckCvars
1679 ==================
1680 */
CheckCvars(void)1681 void CheckCvars( void ) {
1682 static int lastMod = -1;
1683
1684 if ( g_password.modificationCount != lastMod ) {
1685 lastMod = g_password.modificationCount;
1686 if ( *g_password.string && Q_stricmp( g_password.string, "none" ) ) {
1687 trap_Cvar_Set( "g_needpass", "1" );
1688 } else {
1689 trap_Cvar_Set( "g_needpass", "0" );
1690 }
1691 }
1692 }
1693
1694 /*
1695 =============
1696 G_RunThink
1697
1698 Runs thinking code for this frame if necessary
1699 =============
1700 */
G_RunThink(gentity_t * ent)1701 void G_RunThink (gentity_t *ent) {
1702 float thinktime;
1703
1704 thinktime = ent->nextthink;
1705 if (thinktime <= 0) {
1706 return;
1707 }
1708 if (thinktime > level.time) {
1709 return;
1710 }
1711
1712 ent->nextthink = 0;
1713 if (!ent->think) {
1714 G_Error ( "NULL ent->think");
1715 }
1716 ent->think (ent);
1717 }
1718
1719 /*
1720 ================
1721 G_RunFrame
1722
1723 Advances the non-player objects in the world
1724 ================
1725 */
G_RunFrame(int levelTime)1726 void G_RunFrame( int levelTime ) {
1727 int i;
1728 gentity_t *ent;
1729 int msec;
1730 int start, end;
1731
1732 // if we are waiting for the level to restart, do nothing
1733 if ( level.restarted ) {
1734 return;
1735 }
1736
1737 level.framenum++;
1738 level.previousTime = level.time;
1739 level.time = levelTime;
1740 msec = level.time - level.previousTime;
1741
1742 // get any cvar changes
1743 G_UpdateCvars();
1744
1745 //
1746 // go through all allocated objects
1747 //
1748 start = trap_Milliseconds();
1749 ent = &g_entities[0];
1750 for (i=0 ; i<level.num_entities ; i++, ent++) {
1751 if ( !ent->inuse ) {
1752 continue;
1753 }
1754
1755 // clear events that are too old
1756 if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) {
1757 if ( ent->s.event ) {
1758 ent->s.event = 0; // &= EV_EVENT_BITS;
1759 if ( ent->client ) {
1760 ent->client->ps.externalEvent = 0;
1761 // predicted events should never be set to zero
1762 //ent->client->ps.events[0] = 0;
1763 //ent->client->ps.events[1] = 0;
1764 }
1765 }
1766 if ( ent->freeAfterEvent ) {
1767 // tempEntities or dropped items completely go away after their event
1768 G_FreeEntity( ent );
1769 continue;
1770 } else if ( ent->unlinkAfterEvent ) {
1771 // items that will respawn will hide themselves after their pickup event
1772 ent->unlinkAfterEvent = qfalse;
1773 trap_UnlinkEntity( ent );
1774 }
1775 }
1776
1777 // temporary entities don't think
1778 if ( ent->freeAfterEvent ) {
1779 continue;
1780 }
1781
1782 if ( !ent->r.linked && ent->neverFree ) {
1783 continue;
1784 }
1785
1786 if ( ent->s.eType == ET_MISSILE ) {
1787 G_RunMissile( ent );
1788 continue;
1789 }
1790
1791 if ( ent->s.eType == ET_ITEM || ent->physicsObject ) {
1792 G_RunItem( ent );
1793 continue;
1794 }
1795
1796 if ( ent->s.eType == ET_MOVER ) {
1797 G_RunMover( ent );
1798 continue;
1799 }
1800
1801 if ( i < MAX_CLIENTS ) {
1802 G_RunClient( ent );
1803 continue;
1804 }
1805
1806 G_RunThink( ent );
1807 }
1808 end = trap_Milliseconds();
1809
1810 start = trap_Milliseconds();
1811 // perform final fixups on the players
1812 ent = &g_entities[0];
1813 for (i=0 ; i < level.maxclients ; i++, ent++ ) {
1814 if ( ent->inuse ) {
1815 ClientEndFrame( ent );
1816 }
1817 }
1818 end = trap_Milliseconds();
1819
1820 // see if it is time to do a tournement restart
1821 CheckTournament();
1822
1823 // see if it is time to end the level
1824 CheckExitRules();
1825
1826 // update to team status?
1827 CheckTeamStatus();
1828
1829 // cancel vote if timed out
1830 CheckVote();
1831
1832 // check team votes
1833 CheckTeamVote( TEAM_RED );
1834 CheckTeamVote( TEAM_BLUE );
1835
1836 // for tracking changes
1837 CheckCvars();
1838
1839 if (g_listEntity.integer) {
1840 for (i = 0; i < MAX_GENTITIES; i++) {
1841 G_Printf("%4i: %s\n", i, g_entities[i].classname);
1842 }
1843 trap_Cvar_Set("g_listEntity", "0");
1844 }
1845 }
1846