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