1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”).
8 
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 #include "g_local.h"
30 
31 level_locals_t level;
32 
33 typedef struct {
34 	vmCvar_t    *vmCvar;
35 	char        *cvarName;
36 	char        *defaultString;
37 	int cvarFlags;
38 	int modificationCount;          // for tracking changes
39 	qboolean trackChange;           // track this variable, and announce if changed
40 	qboolean teamShader;      // track and if changed, update shader state
41 } cvarTable_t;
42 
43 gentity_t g_entities[MAX_GENTITIES];
44 gclient_t g_clients[MAX_CLIENTS];
45 
46 gentity_t       *g_camEnt = NULL;   //----(SA)	script camera
47 
48 // Rafael gameskill
49 extern int bg_pmove_gameskill_integer;
50 // done
51 
52 vmCvar_t g_gametype;
53 
54 // Rafael gameskill
55 vmCvar_t g_gameskill;
56 // done
57 
58 vmCvar_t g_dmflags;
59 vmCvar_t g_fraglimit;
60 vmCvar_t g_timelimit;
61 vmCvar_t g_capturelimit;
62 vmCvar_t g_friendlyFire;
63 vmCvar_t g_password;
64 vmCvar_t g_maxclients;
65 vmCvar_t g_maxGameClients;
66 vmCvar_t g_minGameClients;          // NERVE - SMF
67 vmCvar_t g_dedicated;
68 vmCvar_t g_speed;
69 vmCvar_t g_gravity;
70 vmCvar_t g_cheats;
71 vmCvar_t g_knockback;
72 vmCvar_t g_quadfactor;
73 vmCvar_t g_forcerespawn;
74 vmCvar_t g_inactivity;
75 vmCvar_t g_debugMove;
76 vmCvar_t g_debugDamage;
77 vmCvar_t g_debugAlloc;
78 vmCvar_t g_debugBullets;    //----(SA)	added
79 vmCvar_t g_weaponRespawn;
80 vmCvar_t g_motd;
81 vmCvar_t g_synchronousClients;
82 vmCvar_t g_warmup;
83 
84 // NERVE - SMF
85 vmCvar_t g_warmupLatch;
86 vmCvar_t g_nextTimeLimit;
87 vmCvar_t g_showHeadshotRatio;
88 vmCvar_t g_userTimeLimit;
89 vmCvar_t g_userAlliedRespawnTime;
90 vmCvar_t g_userAxisRespawnTime;
91 vmCvar_t g_currentRound;
92 vmCvar_t g_noTeamSwitching;
93 vmCvar_t g_altStopwatchMode;
94 vmCvar_t g_gamestate;
95 vmCvar_t g_swapteams;
96 // -NERVE - SMF
97 
98 vmCvar_t g_restarted;
99 vmCvar_t g_logfile;
100 vmCvar_t g_logfileSync;
101 vmCvar_t g_podiumDist;
102 vmCvar_t g_podiumDrop;
103 vmCvar_t g_voteFlags;
104 vmCvar_t g_complaintlimit;          // DHM - Nerve
105 vmCvar_t g_maxlives;                // DHM - Nerve
106 vmCvar_t g_voiceChatsAllowed;       // DHM - Nerve
107 vmCvar_t g_alliedmaxlives;          // Xian
108 vmCvar_t g_axismaxlives;            // Xian
109 vmCvar_t g_fastres;                 // Xian
110 vmCvar_t g_fastResMsec;
111 vmCvar_t g_knifeonly;               // Xian
112 vmCvar_t g_enforcemaxlives;         // Xian
113 
114 vmCvar_t g_needpass;
115 vmCvar_t g_weaponTeamRespawn;
116 vmCvar_t g_doWarmup;
117 vmCvar_t g_teamAutoJoin;
118 vmCvar_t g_teamForceBalance;
119 vmCvar_t g_listEntity;
120 vmCvar_t g_banIPs;
121 vmCvar_t g_filterBan;
122 vmCvar_t g_rankings;
123 vmCvar_t g_enableBreath;
124 vmCvar_t g_smoothClients;
125 vmCvar_t pmove_fixed;
126 vmCvar_t pmove_msec;
127 
128 // Rafael
129 vmCvar_t g_autoactivate;
130 vmCvar_t g_testPain;
131 
132 vmCvar_t g_missionStats;
133 vmCvar_t ai_scriptName;         // name of AI script file to run (instead of default for that map)
134 vmCvar_t g_scriptName;          // name of script file to run (instead of default for that map)
135 
136 vmCvar_t g_developer;
137 
138 vmCvar_t g_userAim;
139 
140 vmCvar_t g_forceModel;
141 
142 vmCvar_t g_mg42arc;
143 
144 vmCvar_t g_footstepAudibleRange;
145 // JPW NERVE multiplayer reinforcement times
146 vmCvar_t g_redlimbotime;
147 vmCvar_t g_bluelimbotime;
148 // charge times for character class special weapons
149 vmCvar_t g_medicChargeTime;
150 vmCvar_t g_engineerChargeTime;
151 vmCvar_t g_LTChargeTime;
152 vmCvar_t g_soldierChargeTime;
153 // screen shakey magnitude multiplier
154 vmCvar_t sv_screenshake;
155 // jpw
156 
157 // Gordon
158 vmCvar_t g_antilag;
159 
160 vmCvar_t mod_url;
161 vmCvar_t url;
162 
163 vmCvar_t g_dbgRevive;
164 
165 vmCvar_t g_localTeamPref;
166 
167 cvarTable_t gameCvarTable[] = {
168 	// don't override the cheat state set by the system
169 	{ &g_cheats, "sv_cheats", "", 0, qfalse },
170 
171 	// noset vars
172 	{ NULL, "gamename", GAMEVERSION, CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },
173 	{ NULL, "gamedate", PRODUCT_DATE, CVAR_ROM, 0, qfalse  },
174 	{ &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse  },
175 
176 	// latched vars
177 	// DHM - Nerve :: default to GT_WOLF
178 	{ &g_gametype, "g_gametype", "5", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse  },
179 
180 	// Rafael gameskill
181 	{ &g_gameskill, "g_gameskill", "3", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse  },
182 	// done
183 
184 // JPW NERVE multiplayer stuffs
185 	{ &sv_screenshake, "sv_screenshake", "5", CVAR_ARCHIVE,0,qfalse},
186 	{ &g_redlimbotime, "g_redlimbotime", "30000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse },
187 	{ &g_bluelimbotime, "g_bluelimbotime", "30000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse },
188 	{ &g_medicChargeTime, "g_medicChargeTime", "45000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse },
189 	{ &g_engineerChargeTime, "g_engineerChargeTime", "30000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse },
190 	{ &g_LTChargeTime, "g_LTChargeTime", "40000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse },
191 	{ &g_soldierChargeTime, "g_soldierChargeTime", "20000", CVAR_SERVERINFO | CVAR_LATCH, 0, qfalse },
192 // jpw
193 
194 	{ &g_maxclients, "sv_maxclients", "20", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  },            // NERVE - SMF - made 20 from 8
195 	{ &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  },
196 	{ &g_minGameClients, "g_minGameClients", "8", CVAR_SERVERINFO, 0, qfalse  },                              // NERVE - SMF
197 
198 	// change anytime vars
199 	{ &g_dmflags, "dmflags", "0", /*CVAR_SERVERINFO |*/ CVAR_ARCHIVE, 0, qtrue  },
200 	{ &g_fraglimit, "fraglimit", "0", /*CVAR_SERVERINFO |*/ CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
201 	{ &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
202 	{ &g_capturelimit, "capturelimit", "8", /*CVAR_SERVERINFO |*/ CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue },
203 
204 	{ &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse  },
205 
206 	{ &g_friendlyFire, "g_friendlyFire", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  },
207 
208 	{ &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE  },
209 	{ &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE  },                            // NERVE - SMF - merge from team arena
210 
211 	{ &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue  },
212 	{ &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue  },
213 
214 	// NERVE - SMF
215 	{ &g_warmupLatch, "g_warmupLatch", "1", 0, 0, qfalse },
216 
217 	{ &g_nextTimeLimit, "g_nextTimeLimit", "0", CVAR_WOLFINFO, 0, qfalse  },
218 	{ &g_currentRound, "g_currentRound", "0", CVAR_WOLFINFO, 0, qfalse  },
219 	{ &g_altStopwatchMode, "g_altStopwatchMode", "0", CVAR_ARCHIVE, 0, qtrue  },
220 	{ &g_gamestate, "gamestate", "-1", CVAR_WOLFINFO | CVAR_ROM, 0, qfalse  },
221 
222 	{ &g_noTeamSwitching, "g_noTeamSwitching", "0", CVAR_ARCHIVE, 0, qtrue  },
223 
224 	{ &g_showHeadshotRatio, "g_showHeadshotRatio", "0", 0, 0, qfalse  },
225 
226 	{ &g_userTimeLimit, "g_userTimeLimit", "0", 0, 0, qfalse  },
227 	{ &g_userAlliedRespawnTime, "g_userAlliedRespawnTime", "0", 0, 0, qfalse  },
228 	{ &g_userAxisRespawnTime, "g_userAxisRespawnTime", "0", 0, 0, qfalse  },
229 
230 	{ &g_swapteams, "g_swapteams", "0", CVAR_ROM, 0, qfalse },
231 	// -NERVE - SMF
232 
233 	{ &g_logfile, "g_log", "games.log", CVAR_ARCHIVE, 0, qfalse  },
234 	{ &g_logfileSync, "g_logsync", "0", CVAR_ARCHIVE, 0, qfalse  },
235 
236 	{ &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse  },
237 	{ &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse  },
238 	// show_bug.cgi?id=500
239 	{ &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse  },
240 
241 	{ &g_dedicated, "dedicated", "0", 0, 0, qfalse  },
242 
243 	{ &g_speed, "g_speed", "320", 0, 0, qtrue  },
244 	{ &g_gravity, "g_gravity", "800", 0, 0, qtrue  },
245 	{ &g_knockback, "g_knockback", "1000", 0, 0, qtrue  },
246 	{ &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue  },
247 	{ &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue  },
248 	{ &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue },
249 	{ &g_forcerespawn, "g_forcerespawn", "0", 0, 0, qtrue },
250 	{ &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },
251 	{ &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },
252 	{ &g_debugDamage, "g_debugDamage", "0", CVAR_CHEAT, 0, qfalse },
253 	{ &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },
254 	{ &g_debugBullets, "g_debugBullets", "0", CVAR_CHEAT, 0, qfalse}, //----(SA)	added
255 	{ &g_motd, "g_motd", "", CVAR_ARCHIVE, 0, qfalse },
256 
257 	{ &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse },
258 	{ &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },
259 
260 	{ &g_voteFlags, "g_voteFlags", "255", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse },
261 	{ &g_listEntity, "g_listEntity", "0", 0, 0, qfalse },
262 
263 	{ &g_complaintlimit, "g_complaintlimit", "3", CVAR_ARCHIVE, 0, qtrue},                        // DHM - Nerve
264 	{ &g_maxlives, "g_maxlives", "0", CVAR_ARCHIVE | CVAR_LATCH | CVAR_SERVERINFO, 0, qtrue},     // DHM - Nerve
265 	{ &g_voiceChatsAllowed, "g_voiceChatsAllowed", "4", CVAR_ARCHIVE, 0, qfalse},             // DHM - Nerve
266 
267 	{ &g_alliedmaxlives, "g_alliedmaxlives", "0", CVAR_LATCH | CVAR_SERVERINFO, 0, qtrue},      // Xian
268 	{ &g_axismaxlives, "g_axismaxlives", "0", CVAR_LATCH | CVAR_SERVERINFO, 0, qtrue},      // Xian
269 	{ &g_fastres, "g_fastres", "0", CVAR_ARCHIVE, 0, qtrue},                                  // Xian - Fast Medic Resing
270 	{ &g_fastResMsec, "g_fastResMsec", "1000", CVAR_ARCHIVE, 0, qtrue},                                   // Xian - Fast Medic Resing
271 	{ &g_knifeonly, "g_knifeonly", "0", 0, 0, qtrue},                                         // Xian - Fast Medic Resing
272 	{ &g_enforcemaxlives, "g_enforcemaxlives", "1", CVAR_ARCHIVE, 0, qtrue},                              // Xian - Gestapo enforce maxlives stuff by temp banning
273 
274 	{ &g_enableBreath, "g_enableBreath", "1", CVAR_SERVERINFO, 0, qtrue},
275 	{ &g_testPain, "g_testPain", "0", CVAR_CHEAT, 0, qfalse },
276 	{ &g_missionStats, "g_missionStats", "0", CVAR_ROM, 0, qfalse },
277 	{ &g_developer, "developer", "0", CVAR_TEMP, 0, qfalse },
278 	{ &g_rankings, "g_rankings", "0", 0, 0, qfalse},
279 	{ &g_userAim, "g_userAim", "1", CVAR_CHEAT, 0, qfalse },
280 
281 	{ &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse},
282 	{ &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},
283 	{ &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse},
284 
285 	{&g_mg42arc, "g_mg42arc", "0", CVAR_TEMP, 0, qfalse},
286 
287 	{&g_footstepAudibleRange, "g_footstepAudibleRange", "256", CVAR_CHEAT, 0, qfalse},
288 
289 	{&g_scriptName, "g_scriptName", "", CVAR_ROM, 0, qfalse},
290 	{&ai_scriptName, "ai_scriptName", "", CVAR_ROM, 0, qfalse},
291 
292 	// points to the URL for mod information, should not be modified by server admin
293 	{&mod_url, "mod_url", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse},
294 	// configured by the server admin, points to the web pages for the server
295 	{&url, "URL", "", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse},
296 
297 	{&g_antilag, "g_antilag", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse},
298 
299 	{&g_dbgRevive, "g_dbgRevive", "0", 0, 0, qfalse},
300 
301 	{ &g_localTeamPref, "g_localTeamPref", "", 0, 0, qfalse }
302 };
303 
304 static int gameCvarTableSize = ARRAY_LEN( gameCvarTable );
305 
306 
307 void G_InitGame( int levelTime, int randomSeed, int restart );
308 void G_RunFrame( int levelTime );
309 void G_ShutdownGame( int restart );
310 void CheckExitRules( void );
311 
312 // Ridah, Cast AI
313 qboolean AICast_VisibleFromPos( vec3_t srcpos, int srcnum,
314 								vec3_t destpos, int destnum, qboolean updateVisPos );
315 qboolean AICast_CheckAttackAtPos( int entnum, int enemy, vec3_t pos, qboolean ducking, qboolean allowHitWorld );
316 void AICast_Init( void );
317 // done.
318 
319 void G_RetrieveMoveSpeedsFromClient( int entnum, char *text );
320 
321 /*
322 ================
323 vmMain
324 
325 This is the only way control passes into the module.
326 This must be the very first function compiled into the .q3vm file
327 ================
328 */
vmMain(intptr_t command,intptr_t arg0,intptr_t arg1,intptr_t arg2,intptr_t arg3,intptr_t arg4,intptr_t arg5,intptr_t arg6)329 Q_EXPORT intptr_t vmMain( intptr_t command, intptr_t arg0, intptr_t arg1, intptr_t arg2, intptr_t arg3, intptr_t arg4, intptr_t arg5, intptr_t arg6 ) {
330 	switch ( command ) {
331 	case GAME_INIT:
332 		G_InitGame( arg0, arg1, arg2 );
333 		return 0;
334 	case GAME_SHUTDOWN:
335 		G_ShutdownGame( arg0 );
336 		return 0;
337 	case GAME_CLIENT_CONNECT:
338 		return (intptr_t)ClientConnect( arg0, arg1, arg2 );
339 	case GAME_CLIENT_THINK:
340 		ClientThink( arg0 );
341 		return 0;
342 	case GAME_CLIENT_USERINFO_CHANGED:
343 		ClientUserinfoChanged( arg0 );
344 		return 0;
345 	case GAME_CLIENT_DISCONNECT:
346 		ClientDisconnect( arg0 );
347 		return 0;
348 	case GAME_CLIENT_BEGIN:
349 		ClientBegin( arg0 );
350 		return 0;
351 	case GAME_CLIENT_COMMAND:
352 		ClientCommand( arg0 );
353 		return 0;
354 	case GAME_RUN_FRAME:
355 		G_RunFrame( arg0 );
356 		return 0;
357 	case GAME_CONSOLE_COMMAND:
358 		return ConsoleCommand();
359 	case BOTAI_START_FRAME:
360 		return BotAIStartFrame( arg0 );
361 		// Ridah, Cast AI
362 	case AICAST_VISIBLEFROMPOS:
363 		return AICast_VisibleFromPos( (float *)arg0, arg1, (float *)arg2, arg3, arg4 );
364 	case AICAST_CHECKATTACKATPOS:
365 		return AICast_CheckAttackAtPos( arg0, arg1, (float *)arg2, arg3, arg4 );
366 		// done.
367 
368 	case GAME_RETRIEVE_MOVESPEEDS_FROM_CLIENT:
369 		G_RetrieveMoveSpeedsFromClient( arg0, (char *)arg1 );
370 		return 0;
371 	}
372 
373 	return -1;
374 }
375 
G_Printf(const char * fmt,...)376 void QDECL G_Printf( const char *fmt, ... ) {
377 	va_list argptr;
378 	char text[1024];
379 
380 	va_start( argptr, fmt );
381 	Q_vsnprintf( text, sizeof( text ), fmt, argptr );
382 	va_end( argptr );
383 
384 	trap_Print( text );
385 }
386 
G_DPrintf(const char * fmt,...)387 void QDECL G_DPrintf( const char *fmt, ... ) {
388 	va_list argptr;
389 	char text[1024];
390 
391 	if ( !g_developer.integer ) {
392 		return;
393 	}
394 
395 	va_start( argptr, fmt );
396 	Q_vsnprintf( text, sizeof( text ), fmt, argptr );
397 	va_end( argptr );
398 
399 	trap_Print( text );
400 }
401 
G_Error(const char * fmt,...)402 void QDECL G_Error( const char *fmt, ... ) {
403 	va_list argptr;
404 	char text[1024];
405 
406 	va_start( argptr, fmt );
407 	Q_vsnprintf( text, sizeof( text ), fmt, argptr );
408 	va_end( argptr );
409 
410 	trap_Error( text );
411 }
412 
413 
414 #define CH_KNIFE_DIST       48  // from g_weapon.c
415 #define CH_LADDER_DIST      100
416 #define CH_WATER_DIST       100
417 #define CH_BREAKABLE_DIST   64
418 #define CH_DOOR_DIST        96
419 #define CH_ACTIVATE_DIST    96
420 #define CH_EXIT_DIST        256
421 
422 #define CH_MAX_DIST         256     // use the largest value from above
423 #define CH_MAX_DIST_ZOOM    8192    // max dist for zooming hints
424 /*
425 ==============
426 G_CheckForCursorHints
427 	non-AI's check for cursor hint contacts
428 
429 	server-side because there's info we want to show that the client
430 	just doesn't know about.  (health or other info of an explosive,invisible_users,items,etc.)
431 
432 	traceEnt is the ent hit by the trace, checkEnt is the ent that is being
433 	checked against (in case the traceent was an invisible_user or something)
434 
435 ==============
436 */
G_CheckForCursorHints(gentity_t * ent)437 void G_CheckForCursorHints( gentity_t *ent ) {
438 	vec3_t forward, right, up, offset, end;
439 	trace_t     *tr;
440 	float dist;
441 	gentity_t   *checkEnt, *traceEnt = 0;
442 	//gentity_t *traceEnt2 = 0; // JPW NERVE // TTimo unused
443 	playerState_t *ps;
444 	int hintType, hintDist, hintVal;
445 	qboolean zooming;
446 	int trace_contents;                 // DHM - Nerve
447 
448 	// FIXME:	no need at all to do this trace/string comparison every frame.
449 	//			stagger it at least a little bit
450 
451 //	if(!servercursorhints)
452 //		return;
453 
454 	if ( !ent->client ) {
455 		return;
456 	}
457 
458 	ps = &ent->client->ps;
459 
460 	if ( ps->aiChar != AICHAR_NONE ) {
461 		return;
462 	}
463 
464 	zooming = (qboolean)( ps->eFlags & EF_ZOOMING );
465 
466 	AngleVectors( ps->viewangles, forward, right, up );
467 
468 	VectorCopy( ps->origin, offset );
469 	offset[2] += ps->viewheight;
470 
471 	// lean
472 	if ( ps->leanf ) {
473 		VectorMA( offset, ps->leanf, right, offset );
474 	}
475 
476 //	SnapVector( offset );
477 
478 	if ( zooming ) {
479 		VectorMA( offset, CH_MAX_DIST_ZOOM, forward, end );
480 	} else {
481 		VectorMA( offset, CH_MAX_DIST, forward, end );
482 	}
483 
484 	tr = &ps->serverCursorHintTrace;
485 
486 	// DHM - Nerve
487 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
488 		trace_contents = ( CONTENTS_TRIGGER | CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY );
489 	} else {
490 		trace_contents = ( CONTENTS_TRIGGER | CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY | CONTENTS_CORPSE );
491 	}
492 
493 	trap_Trace( tr, offset, NULL, NULL, end, ps->clientNum, trace_contents );
494 	// dhm - end
495 
496 	// reset all
497 	hintType    = ps->serverCursorHint      = HINT_NONE;
498 	hintVal     = ps->serverCursorHintVal   = 0;
499 
500 	if ( zooming ) {
501 		dist        = tr->fraction * CH_MAX_DIST_ZOOM;
502 		hintDist    = CH_MAX_DIST_ZOOM;
503 	} else {
504 		dist        = tr->fraction * CH_MAX_DIST;
505 		hintDist    = CH_MAX_DIST;
506 	}
507 
508 	if ( tr->fraction == 1 ) {
509 		return;
510 	}
511 
512 	traceEnt = &g_entities[tr->entityNum];
513 
514 	// DHM - Nerve :: Ignore trigger_objective_info
515 	if ( ( ps->stats[ STAT_PLAYER_CLASS ] != PC_MEDIC && traceEnt->client )
516 		 || !( strcmp( traceEnt->classname, "trigger_objective_info" ) )
517 		 || !( strcmp( traceEnt->classname, "trigger_multiple" ) ) ) {
518 
519 		trap_Trace( tr, offset, NULL, NULL, end, traceEnt->s.number, trace_contents );
520 		if ( zooming ) {
521 			dist        = tr->fraction * CH_MAX_DIST_ZOOM;
522 			hintDist    = CH_MAX_DIST_ZOOM;
523 		} else {
524 			dist        = tr->fraction * CH_MAX_DIST;
525 			hintDist    = CH_MAX_DIST;
526 		}
527 		if ( tr->fraction == 1 ) {
528 			return;
529 		}
530 		traceEnt = &g_entities[tr->entityNum];
531 	}
532 
533 	//
534 	// WORLD
535 	//
536 	if ( tr->entityNum == ENTITYNUM_WORLD ) {
537 		if ( ( tr->contents & CONTENTS_WATER ) && !( ps->powerups[PW_BREATHER] ) ) {
538 			hintDist = CH_WATER_DIST;
539 			hintType = HINT_WATER;
540 		}
541 		// ladder
542 		else if ( ( tr->surfaceFlags & SURF_LADDER ) && !( ps->pm_flags & PMF_LADDER ) ) {
543 			hintDist = CH_LADDER_DIST;
544 			hintType = HINT_LADDER;
545 		}
546 	}
547 	//
548 	// PEOPLE
549 	//
550 	else if ( tr->entityNum < MAX_CLIENTS ) {
551 
552 		// DHM - Nerve
553 		if ( g_gametype.integer >= GT_WOLF ) {
554 			// Show medics a syringe if they can revive someone
555 			if ( traceEnt->client && traceEnt->client->sess.sessionTeam == ent->client->sess.sessionTeam
556 				 && ps->stats[ STAT_PLAYER_CLASS ] == PC_MEDIC
557 				 && traceEnt->client->ps.pm_type == PM_DEAD
558 				 && !( traceEnt->client->ps.pm_flags & PMF_LIMBO ) ) {
559 				hintDist    = 48;     // JPW NERVE matches weapon_syringe in g_weapon.c
560 				hintType    = HINT_REVIVE;
561 			}
562 		}
563 		// dhm - Nerve
564 
565 	}
566 	//
567 	// OTHER ENTITIES
568 	//
569 	else {
570 		checkEnt = traceEnt;
571 
572 		// check invisible_users first since you don't want to draw a hint based
573 		// on that ent, but rather on what they are targeting.
574 		// so find the target and set checkEnt to that to show the proper hint.
575 		if ( traceEnt->s.eType == ET_GENERAL ) {
576 
577 			// ignore trigger_aidoor.  can't just not trace for triggers, since I need invisible_users...
578 			// damn, I would like to ignore some of these triggers though.
579 
580 			if ( !Q_stricmp( traceEnt->classname, "trigger_aidoor" ) ) {
581 				return;
582 			}
583 
584 			if ( !Q_stricmp( traceEnt->classname, "func_invisible_user" ) ) {
585 				// DHM - Nerve :: Put this back in only in multiplayer
586 				if ( g_gametype.integer >= GT_WOLF && traceEnt->s.dmgFlags ) { // hint icon specified in entity
587 					hintType = traceEnt->s.dmgFlags;
588 					hintDist = CH_ACTIVATE_DIST;
589 					checkEnt = 0;
590 				} else {                      // use target for hint icon
591 					checkEnt = G_Find( NULL, FOFS( targetname ), traceEnt->target );
592 					if ( !checkEnt ) {     // no target found
593 						hintType = HINT_BAD_USER;
594 						hintDist = CH_MAX_DIST_ZOOM;    // show this one from super far for debugging
595 					}
596 				}
597 			}
598 		}
599 
600 
601 		if ( checkEnt ) {
602 			if ( checkEnt->s.eType == ET_GENERAL || checkEnt->s.eType == ET_MG42_BARREL ) {
603 
604 				// this is effectively an 'exit' brush.  they should be created with:
605 				//
606 				// classname = 'ai_trigger'
607 				// ainame = 'player'
608 				// target = 'endmap'
609 				//
610 				if ( !Q_stricmp( traceEnt->classname, "ai_trigger" ) ) {
611 					if ( ( !Q_stricmp( traceEnt->aiName, "player" ) ) &&
612 						 ( !Q_stricmp( traceEnt->target, "endmap" ) ) ) {
613 						hintDist = CH_EXIT_DIST;
614 						hintType = HINT_EXIT;
615 
616 						// show distance in the cursorhint bar
617 						hintVal = (int)dist;        // range for this hint is 256, so it happens to translate nicely
618 					}
619 				}
620 
621 				if ( ( // general mg42 hint conditions
622 						 !Q_stricmp( traceEnt->classname, "misc_mg42" ) ) &&
623 					 ( ps->weapon != WP_SNIPERRIFLE ) && // JPW NERVE no hint if you're scoped in sniperwise
624 					 // ATVI #470
625 					 // mount hint conditions only, if busted MG42, no check
626 					 (
627 						 ( traceEnt->health < 255 ) ||
628 						 (
629 							 !( ent->client->ps.pm_flags & PMF_DUCKED ) &&
630 							 ( traceEnt->r.currentOrigin[2] - ent->r.currentOrigin[2] < 40 ) &&
631 							 ( traceEnt->r.currentOrigin[2] - ent->r.currentOrigin[2] > 0 ) &&
632 							 !infront( traceEnt, ent )
633 						 )
634 					 )
635 					 ) {
636 
637 					hintDist = CH_ACTIVATE_DIST;
638 					hintType = HINT_MG42;
639 
640 // JPW NERVE -- busted MG42 shouldn't show a use hint by default
641 					if ( traceEnt->health <= 0 ) {
642 						hintDist = 0;
643 						hintType = HINT_FORCENONE;
644 						ps->serverCursorHint = HINT_FORCENONE;
645 					}
646 // jpw
647 
648 					// DHM - Nerve :: Engineers can repair turrets
649 					if ( g_gametype.integer >= GT_WOLF ) {
650 						if ( ps->stats[ STAT_PLAYER_CLASS ] == PC_ENGINEER ) {
651 							if ( !traceEnt->takedamage ) {
652 								hintType = HINT_BUILD;
653 								hintDist = CH_BREAKABLE_DIST;
654 								hintVal = traceEnt->health;
655 								if ( hintVal > 255 ) {
656 									hintVal = 255;
657 								}
658 							}
659 						}
660 					}
661 					// dhm - end
662 				}
663 			} else if ( checkEnt->s.eType == ET_EXPLOSIVE )      {
664 //				if(traceEnt->s.dmgFlags) {	 // override flag		// hint icon specified in entity, this overrides type
665 //					hintType = traceEnt->s.dmgFlags;
666 //				} else {
667 				if ( ( checkEnt->health > 0 ) || ( checkEnt->spawnflags & 64 ) ) {  // JPW NERVE they are now if it's dynamite			// 0 health explosives are not breakable
668 					hintDist    = CH_BREAKABLE_DIST;
669 					hintType    = HINT_BREAKABLE;
670 
671 					// DHM - Nerve :: Show different hint if it can only be destroyed with dynamite
672 					if ( g_gametype.integer >= GT_WOLF && ( checkEnt->spawnflags & 64 ) ) {
673 // JPW NERVE only show hint for players who can blow it up
674 						vec3_t mins,maxs,range = { 40, 40, 52 };
675 						int i,num;
676 						//int defendingTeam=0; // TTimo unused
677 						int touch[MAX_GENTITIES];
678 						gentity_t *hit = NULL;
679 
680 						VectorSubtract( ent->client->ps.origin, range, mins );
681 						VectorAdd( ent->client->ps.origin, range, maxs );
682 						num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
683 						for ( i = 0 ; i < num ; i++ ) {
684 							hit = &g_entities[touch[i]];
685 							if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
686 								continue;
687 							}
688 							if ( !strcmp( hit->classname,"trigger_objective_info" ) ) {
689 								if ( !( hit->spawnflags & ( AXIS_OBJECTIVE | ALLIED_OBJECTIVE ) ) ) {
690 									continue;
691 								}
692 // we're in a trigger_objective_info field with at least one owner, so use this one and bail
693 								break;
694 							}
695 						}
696 						if ( ( hit ) &&
697 							 ( ( ( ent->client->sess.sessionTeam == TEAM_RED ) && ( hit->spawnflags & ALLIED_OBJECTIVE ) ) ||
698 							   ( ( ent->client->sess.sessionTeam == TEAM_BLUE ) && ( hit->spawnflags & AXIS_OBJECTIVE ) ) )
699 							 ) {
700 							hintDist = CH_BREAKABLE_DIST * 2;
701 							hintType = HINT_BREAKABLE_DYNAMITE;
702 						} else {
703 							hintDist = 0;
704 							hintType = ps->serverCursorHint     = HINT_FORCENONE;
705 						}
706 // jpw
707 					}
708 					// dhm - end
709 
710 					hintVal     = checkEnt->health;         // also send health to client for visualization
711 				}
712 //				}
713 			} else if ( checkEnt->s.eType == ET_ALARMBOX )      {
714 				if ( checkEnt->health > 0 ) {
715 //					hintDist	= CH_BREAKABLE_DIST;
716 					hintType    = HINT_ACTIVATE;
717 				}
718 			} else if ( checkEnt->s.eType == ET_ITEM )      {
719 				gitem_t *it;
720 				it = &bg_itemlist[checkEnt->item - bg_itemlist];
721 
722 				hintDist = CH_ACTIVATE_DIST;
723 
724 				switch ( it->giType ) {
725 				case IT_HEALTH:
726 					hintType = HINT_HEALTH;
727 					break;
728 				case IT_TREASURE:
729 					hintType = HINT_TREASURE;
730 					break;
731 				case IT_CLIPBOARD:
732 					hintType = HINT_CLIPBOARD;
733 					break;
734 				case IT_WEAPON:
735 // JPW NERVE changed this to be more specific
736 					if ( it->giTag != WP_LUGER && it->giTag != WP_COLT ) {
737 						if ( ps->stats[STAT_PLAYER_CLASS] == PC_SOLDIER ) {   // soldier can pick up all weapons
738 							hintType = HINT_WEAPON;
739 							break;
740 						}
741 						if ( ps->stats[STAT_PLAYER_CLASS] != PC_LT ) { // medics & engrs can't pick up squat
742 							break;
743 						}
744 						if ( it->giTag == WP_THOMPSON || it->giTag == WP_MP40 || it->giTag == WP_STEN ) {
745 							hintType = HINT_WEAPON;
746 						}
747 					}
748 // jpw
749 					break;
750 				case IT_AMMO:
751 					hintType = HINT_AMMO;
752 					break;
753 				case IT_ARMOR:
754 					hintType = HINT_ARMOR;
755 					break;
756 				case IT_POWERUP:
757 					hintType = HINT_POWERUP;
758 					break;
759 				case IT_HOLDABLE:
760 					hintType = HINT_HOLDABLE;
761 					break;
762 				case IT_KEY:
763 					hintType = HINT_INVENTORY;
764 					break;
765 				case IT_TEAM:
766 					if ( !Q_stricmp( traceEnt->classname, "team_CTF_redflag" ) && ent->client->sess.sessionTeam == TEAM_BLUE ) {
767 						hintType = HINT_POWERUP;
768 					} else if ( !Q_stricmp( traceEnt->classname, "team_CTF_blueflag" ) && ent->client->sess.sessionTeam == TEAM_RED ) {
769 						hintType = HINT_POWERUP;
770 					}
771 					break;
772 				case IT_BAD:
773 				default:
774 					break;
775 				}
776 			} else if ( checkEnt->s.eType == ET_MOVER )     {
777 				if ( !Q_stricmp( checkEnt->classname, "func_door_rotating" ) ) {
778 					if ( checkEnt->moverState == MOVER_POS1ROTATE ) {  // stationary/closed
779 						hintDist = CH_DOOR_DIST;
780 						hintType = HINT_DOOR_ROTATING;
781 
782 						if ( checkEnt->key == -1 ) {   // locked
783 							//						hintType = HINT_DOOR_ROTATING_LOCKED;
784 						}
785 					}
786 				} else if ( !Q_stricmp( checkEnt->classname, "func_door" ) )         {
787 					if ( checkEnt->moverState == MOVER_POS1 ) {    // stationary/closed
788 						hintDist = CH_DOOR_DIST;
789 						hintType = HINT_DOOR;
790 
791 						if ( checkEnt->key == -1 ) {   // locked
792 							//						hintType = HINT_DOOR_LOCKED;
793 						}
794 					}
795 				} else if ( !Q_stricmp( checkEnt->classname, "func_button" ) )         {
796 					hintDist = CH_ACTIVATE_DIST;
797 					hintType = HINT_BUTTON;
798 				}/*
799 				else if(checkEnt->s.dmgFlags == HINT_CHAIR) {
800 					hintDist = CH_ACTIVATE_DIST;
801 					hintType = HINT_CHAIR;
802 				}*/
803 			}
804 
805 			// DHM - Nerve :: Handle wolf multiplayer hints
806 			if ( g_gametype.integer >= GT_WOLF ) {
807 
808 				if ( checkEnt->s.eType == ET_MISSILE ) {
809 					if ( ps->stats[ STAT_PLAYER_CLASS ] == PC_ENGINEER ) {
810 						hintDist    = CH_BREAKABLE_DIST;
811 						hintType    = HINT_DISARM;
812 						hintVal     = checkEnt->health;     // also send health to client for visualization
813 						if ( hintVal > 255 ) {
814 							hintVal = 255;
815 						}
816 					}
817 				}
818 
819 			}
820 			// dhm - end
821 
822 			// hint icon specified in entity (and proper contact was made, so hintType was set)
823 			// first try the checkent...
824 			if ( checkEnt->s.dmgFlags && hintType ) {
825 				hintType = checkEnt->s.dmgFlags;
826 			}
827 		}
828 
829 		// then the traceent
830 		if ( traceEnt->s.dmgFlags && hintType ) {
831 			hintType = traceEnt->s.dmgFlags;
832 		}
833 
834 	}
835 
836 	if ( zooming ) {
837 
838 		hintDist = CH_MAX_DIST_ZOOM;
839 
840 		// zooming can eat a lot of potential hints
841 		switch ( hintType ) {
842 
843 			// allow while zooming
844 		case HINT_PLAYER:
845 		case HINT_TREASURE:
846 		case HINT_LADDER:
847 		case HINT_EXIT:
848 		case HINT_PLYR_FRIEND:
849 		case HINT_PLYR_NEUTRAL:
850 		case HINT_PLYR_ENEMY:
851 		case HINT_PLYR_UNKNOWN:
852 			break;
853 
854 		default:
855 			return;
856 		}
857 	}
858 
859 	if ( dist <= hintDist ) {
860 		ps->serverCursorHint = hintType;
861 		ps->serverCursorHintVal = hintVal;
862 	}
863 
864 
865 //	Com_Printf("hint: %d\n", ps->serverCursorHint);
866 }
867 
868 
869 /*
870 ================
871 G_FindTeams
872 
873 Chain together all entities with a matching team field.
874 Entity teams are used for item groups and multi-entity mover groups.
875 
876 All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
877 All but the last will have the teamchain field set to the next one
878 ================
879 */
G_FindTeams(void)880 void G_FindTeams( void ) {
881 	gentity_t   *e, *e2;
882 	int i, j;
883 	int c, c2;
884 
885 	c = 0;
886 	c2 = 0;
887 	for ( i = MAX_CLIENTS, e = g_entities + i ; i < level.num_entities ; i++,e++ ) {
888 		if ( !e->inuse ) {
889 			continue;
890 		}
891 
892 		if ( !e->team ) {
893 			continue;
894 		}
895 
896 		if ( e->flags & FL_TEAMSLAVE ) {
897 			continue;
898 		}
899 
900 		if ( !Q_stricmp( e->classname, "func_tramcar" ) ) {
901 			if ( e->spawnflags & 8 ) { // leader
902 				e->teammaster = e;
903 			} else
904 			{
905 				continue;
906 			}
907 		}
908 
909 		c++;
910 		c2++;
911 		for ( j = i + 1, e2 = e + 1 ; j < level.num_entities ; j++,e2++ )
912 		{
913 			if ( !e2->inuse ) {
914 				continue;
915 			}
916 			if ( !e2->team ) {
917 				continue;
918 			}
919 			if ( e2->flags & FL_TEAMSLAVE ) {
920 				continue;
921 			}
922 			if ( !strcmp( e->team, e2->team ) ) {
923 				c2++;
924 				e2->teamchain = e->teamchain;
925 				e->teamchain = e2;
926 				e2->teammaster = e;
927 //				e2->key = e->key;	// (SA) I can't set the key here since the master door hasn't finished spawning yet and therefore has a key of -1
928 				e2->flags |= FL_TEAMSLAVE;
929 
930 				if ( !Q_stricmp( e2->classname, "func_tramcar" ) ) {
931 					trap_UnlinkEntity( e2 );
932 				}
933 
934 				// make sure that targets only point at the master
935 				if ( e2->targetname ) {
936 					e->targetname = e2->targetname;
937 
938 					// Rafael
939 					// note to self: added this because of problems
940 					// pertaining to keys and double doors
941 					if ( Q_stricmp( e2->classname, "func_door_rotating" ) ) {
942 						e2->targetname = NULL;
943 					}
944 				}
945 			}
946 		}
947 	}
948 
949 	if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) {
950 		G_Printf( "%i teams with %i entities\n", c, c2 );
951 	}
952 }
953 
954 
955 /*
956 ==============
957 G_RemapTeamShaders
958 ==============
959 */
G_RemapTeamShaders(void)960 void G_RemapTeamShaders( void ) {
961 #ifdef MISSIONPACK
962 	char string[1024];
963 	float f = level.time * 0.001;
964 	Com_sprintf( string, sizeof( string ), "team_icon/%s_red", g_redteam.string );
965 	AddRemap( "textures/ctf2/redteam01", string, f );
966 	AddRemap( "textures/ctf2/redteam02", string, f );
967 	Com_sprintf( string, sizeof( string ), "team_icon/%s_blue", g_blueteam.string );
968 	AddRemap( "textures/ctf2/blueteam01", string, f );
969 	AddRemap( "textures/ctf2/blueteam02", string, f );
970 	trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig() );
971 #endif
972 }
973 
974 
975 /*
976 =================
977 G_RegisterCvars
978 =================
979 */
G_RegisterCvars(void)980 void G_RegisterCvars( void ) {
981 	int i;
982 	cvarTable_t *cv;
983 	qboolean remapped = qfalse;
984 
985 	for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
986 		trap_Cvar_Register( cv->vmCvar, cv->cvarName,
987 							cv->defaultString, cv->cvarFlags );
988 		if ( cv->vmCvar ) {
989 			cv->modificationCount = cv->vmCvar->modificationCount;
990 		}
991 
992 		if ( cv->teamShader ) {
993 			remapped = qtrue;
994 		}
995 	}
996 
997 	if ( remapped ) {
998 		G_RemapTeamShaders();
999 	}
1000 
1001 	// check some things
1002 	// DHM - Gametype is currently restricted to supported types only
1003 	if ( g_gametype.integer < GT_WOLF || g_gametype.integer > GT_WOLF_CPH ) { // JPW NERVE WOLF_CPH now highest type
1004 		G_Printf( "g_gametype %i is out of range, defaulting to GT_WOLF(5)\n", g_gametype.integer );
1005 		trap_Cvar_Set( "g_gametype", "5" );
1006 		trap_Cvar_Update( &g_gametype );
1007 	}
1008 
1009 	// Rafael gameskill
1010 	if ( g_gameskill.integer < GSKILL_EASY || g_gameskill.integer > GSKILL_VERYHARD ) {
1011 		G_Printf( "g_gameskill %i is out of range, default to medium\n", g_gameskill.integer );
1012 		trap_Cvar_Set( "g_gameskill", "3" ); // default to medium
1013 	}
1014 
1015 	bg_pmove_gameskill_integer = g_gameskill.integer;
1016 	// done
1017 
1018 	level.warmupModificationCount = g_warmup.modificationCount;
1019 }
1020 
1021 /*
1022 =================
1023 G_UpdateCvars
1024 =================
1025 */
G_UpdateCvars(void)1026 void G_UpdateCvars( void ) {
1027 	int i;
1028 	cvarTable_t *cv;
1029 	qboolean remapped = qfalse;
1030 
1031 	for ( i = 0, cv = gameCvarTable ; i < gameCvarTableSize ; i++, cv++ ) {
1032 		if ( cv->vmCvar ) {
1033 			trap_Cvar_Update( cv->vmCvar );
1034 
1035 			if ( cv->modificationCount != cv->vmCvar->modificationCount ) {
1036 				cv->modificationCount = cv->vmCvar->modificationCount;
1037 
1038 				if ( cv->trackChange ) {
1039 					trap_SendServerCommand( -1, va( "print \"Server:[lof] %s [lon]changed to[lof] %s\n\"",
1040 													cv->cvarName, cv->vmCvar->string ) );
1041 				}
1042 
1043 				if ( cv->teamShader ) {
1044 					remapped = qtrue;
1045 				}
1046 			}
1047 		}
1048 	}
1049 
1050 	if ( remapped ) {
1051 		G_RemapTeamShaders();
1052 	}
1053 }
1054 
1055 
1056 
1057 /*
1058 ==============
1059 G_SpawnScriptCamera
1060 	create the game entity that's used for camera<->script communication and portal location for camera view
1061 ==============
1062 */
G_SpawnScriptCamera(void)1063 void G_SpawnScriptCamera( void ) {
1064 	if ( g_camEnt ) {
1065 		G_FreeEntity( g_camEnt );
1066 	}
1067 
1068 	g_camEnt = G_Spawn();
1069 
1070 	g_camEnt->scriptName = "scriptcamera";
1071 
1072 	g_camEnt->s.eType = ET_CAMERA;
1073 	g_camEnt->s.apos.trType = TR_STATIONARY;
1074 	g_camEnt->s.apos.trTime = 0;
1075 	g_camEnt->s.apos.trDuration = 0;
1076 	VectorCopy( g_camEnt->s.angles, g_camEnt->s.apos.trBase );
1077 	VectorClear( g_camEnt->s.apos.trDelta );
1078 
1079 	g_camEnt->s.frame = 0;
1080 
1081 	g_camEnt->r.svFlags |= SVF_NOCLIENT;        // only broadcast when in use
1082 
1083 	if ( g_camEnt->s.number >= MAX_CLIENTS && g_camEnt->scriptName ) {
1084 		G_Script_ScriptParse( g_camEnt );
1085 		G_Script_ScriptEvent( g_camEnt, "spawn", "" );
1086 	}
1087 
1088 }
1089 
1090 /*
1091 ============
1092 G_InitGame
1093 
1094 ============
1095 */
G_InitGame(int levelTime,int randomSeed,int restart)1096 void G_InitGame( int levelTime, int randomSeed, int restart ) {
1097 	int i;
1098 	char cs[MAX_INFO_STRING];
1099 
1100 	if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) {
1101 		G_Printf( "------- Game Initialization -------\n" );
1102 		G_Printf( "gamename: %s\n", GAMEVERSION );
1103 		G_Printf( "gamedate: %s\n", PRODUCT_DATE );
1104 	}
1105 
1106 	srand( randomSeed );
1107 
1108 	G_RegisterCvars();
1109 
1110 	// Xian enforcemaxlives stuff
1111 	/*
1112 	we need to clear the list even if enforce maxlives is not active
1113 	in cas ethe g_maxlives was changed, and a map_restart happened
1114 	*/
1115 	ClearMaxLivesGUID();
1116 
1117 	// just for verbosity
1118 	if ( g_enforcemaxlives.integer && ( g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) ) {
1119 		G_Printf( "EnforceMaxLives-Cleared GUID List\n" );
1120 	}
1121 
1122 	G_ProcessIPBans();
1123 
1124 	G_InitMemory();
1125 
1126 	// NERVE - SMF - intialize gamestate
1127 	if ( g_gamestate.integer == GS_INITIALIZE ) {
1128 		if ( g_noTeamSwitching.integer ) {
1129 			trap_Cvar_Set( "gamestate", va( "%i", GS_WAITING_FOR_PLAYERS ) );
1130 		} else {
1131 			trap_Cvar_Set( "gamestate", va( "%i", GS_WARMUP ) );
1132 		}
1133 	}
1134 
1135 	// set some level globals
1136 	memset( &level, 0, sizeof( level ) );
1137 	level.time = levelTime;
1138 	level.startTime = levelTime;
1139 
1140 	level.snd_fry = G_SoundIndex( "sound/player/fry.wav" );    // FIXME standing in lava / slime
1141 	level.bulletRicochetSound = G_SoundIndex( "bulletRicochet" );
1142 	level.snipersound = G_SoundIndex( "sound/weapons/mauser/mauserf1.wav" );
1143 	level.knifeSound[0] = G_SoundIndex( "sound/weapons/knife/knife_hitwall1.wav" );
1144 
1145 	// RF, init the anim scripting
1146 	level.animScriptData.soundIndex = G_SoundIndex;
1147 	level.animScriptData.playSound = G_AnimScriptSound;
1148 
1149 	if ( g_gametype.integer != GT_SINGLE_PLAYER && g_logfile.string[0] ) {
1150 		if ( g_logfileSync.integer ) {
1151 			trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND_SYNC );
1152 		} else {
1153 			trap_FS_FOpenFile( g_logfile.string, &level.logFile, FS_APPEND );
1154 		}
1155 		if ( !level.logFile ) {
1156 			G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logfile.string );
1157 		} else {
1158 			char serverinfo[MAX_INFO_STRING];
1159 
1160 			trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );
1161 
1162 			G_LogPrintf( "------------------------------------------------------------\n" );
1163 			G_LogPrintf( "InitGame: %s\n", serverinfo );
1164 		}
1165 	} else {
1166 		if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) {
1167 			G_Printf( "Not logging to disk.\n" );
1168 		}
1169 	}
1170 
1171 	G_InitWorldSession();
1172 
1173 	// DHM - Nerve :: Clear out spawn target config strings
1174 	if ( g_gametype.integer >= GT_WOLF ) {
1175 		trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) );
1176 		Info_SetValueForKey( cs, "numspawntargets", "0" );
1177 		trap_SetConfigstring( CS_MULTI_INFO, cs );
1178 
1179 		for ( i = CS_MULTI_SPAWNTARGETS; i < CS_MULTI_SPAWNTARGETS + MAX_MULTI_SPAWNTARGETS; i++ ) {
1180 			trap_SetConfigstring( i, "" );
1181 		}
1182 	}
1183 
1184 	// initialize all entities for this game
1185 	memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[0] ) );
1186 	level.gentities = g_entities;
1187 
1188 	// initialize all clients for this game
1189 	level.maxclients = g_maxclients.integer;
1190 	memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[0] ) );
1191 	level.clients = g_clients;
1192 
1193 	// set client fields on player ents
1194 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1195 		g_entities[i].client = level.clients + i;
1196 	}
1197 
1198 	// always leave room for the max number of clients,
1199 	// even if they aren't all used, so numbers inside that
1200 	// range are NEVER anything but clients
1201 	level.num_entities = MAX_CLIENTS;
1202 
1203 	for ( i=0 ; i<MAX_CLIENTS ; i++ ) {
1204 		g_entities[i].classname = "clientslot";
1205 	}
1206 
1207 	// let the server system know where the entites are
1208 	trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
1209 						 &level.clients[0].ps, sizeof( level.clients[0] ) );
1210 
1211 	// load level script
1212 	G_Script_ScriptLoad();
1213 
1214 	// reserve some spots for dead player bodies
1215 	InitBodyQue();
1216 
1217 	ClearRegisteredItems();
1218 
1219 	// parse the key/value pairs and spawn gentities
1220 	G_SpawnEntitiesFromString();
1221 
1222 	// create the camera entity that will communicate with the scripts
1223 	G_SpawnScriptCamera();
1224 
1225 	// general initialization
1226 	G_FindTeams();
1227 
1228 	// make sure we have flags for CTF, etc
1229 	if ( g_gametype.integer >= GT_TEAM ) {
1230 		G_CheckTeamItems();
1231 	}
1232 
1233 	SaveRegisteredItems();
1234 
1235 	if ( trap_Cvar_VariableIntegerValue( "g_gametype" ) != GT_SINGLE_PLAYER ) {
1236 		G_Printf( "-----------------------------------\n" );
1237 	}
1238 
1239 	if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
1240 		BotAISetup( restart );
1241 		BotAILoadMap( restart );
1242 		G_InitBots( restart );
1243 	}
1244 
1245 	G_RemapTeamShaders();
1246 
1247 	trap_SetConfigstring( CS_INTERMISSION, "" );
1248 }
1249 
1250 
1251 
1252 /*
1253 =================
1254 G_ShutdownGame
1255 =================
1256 */
G_ShutdownGame(int restart)1257 void G_ShutdownGame( int restart ) {
1258 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1259 		G_Printf( "==== ShutdownGame ====\n" );
1260 	}
1261 
1262 	if ( level.logFile ) {
1263 		G_LogPrintf( "ShutdownGame:\n" );
1264 		G_LogPrintf( "------------------------------------------------------------\n" );
1265 		trap_FS_FCloseFile( level.logFile );
1266 		level.logFile = 0;
1267 	}
1268 
1269 	// Ridah, shutdown the Botlib, so weapons and things get reset upon doing a "map xxx" command
1270 	if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
1271 		int i;
1272 
1273 		// Ridah, kill AI cast's
1274 		for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
1275 			if ( g_entities[i].r.svFlags & SVF_CASTAI ) {
1276 				trap_DropClient( i, "Drop Cast AI" );
1277 			}
1278 		}
1279 		// done.
1280 	}
1281 	// done.
1282 
1283 	// write all the client session data so we can get it back
1284 	G_WriteSessionData();
1285 
1286 
1287 	if ( trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
1288 		BotAIShutdown( restart );
1289 	}
1290 }
1291 
1292 
1293 
1294 //===================================================================
1295 
1296 
Com_Error(int level,const char * error,...)1297 void QDECL Com_Error( int level, const char *error, ... ) {
1298 	va_list argptr;
1299 	char text[1024];
1300 
1301 	va_start( argptr, error );
1302 	Q_vsnprintf( text, sizeof( text ), error, argptr );
1303 	va_end( argptr );
1304 
1305 	trap_Error( text );
1306 }
1307 
Com_Printf(const char * msg,...)1308 void QDECL Com_Printf( const char *msg, ... ) {
1309 	va_list argptr;
1310 	char text[1024];
1311 
1312 	va_start( argptr, msg );
1313 	Q_vsnprintf( text, sizeof( text ), msg, argptr );
1314 	va_end( argptr );
1315 
1316 	trap_Print( text );
1317 }
1318 
1319 /*
1320 ========================================================================
1321 
1322 PLAYER COUNTING / SCORE SORTING
1323 
1324 ========================================================================
1325 */
1326 
1327 /*
1328 =============
1329 AddTournamentPlayer
1330 
1331 If there are less than two tournament players, put a
1332 spectator in the game and restart
1333 =============
1334 */
AddTournamentPlayer(void)1335 void AddTournamentPlayer( void ) {
1336 	int i;
1337 	gclient_t   *client;
1338 	gclient_t   *nextInLine;
1339 
1340 	if ( level.numPlayingClients >= 2 ) {
1341 		return;
1342 	}
1343 
1344 	// never change during intermission
1345 	if ( level.intermissiontime ) {
1346 		return;
1347 	}
1348 
1349 	nextInLine = NULL;
1350 
1351 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1352 		client = &level.clients[i];
1353 		if ( client->pers.connected != CON_CONNECTED ) {
1354 			continue;
1355 		}
1356 		if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
1357 			continue;
1358 		}
1359 		// never select the dedicated follow or scoreboard clients
1360 		if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ||
1361 			 client->sess.spectatorClient < 0  ) {
1362 			continue;
1363 		}
1364 
1365 		if(!nextInLine || client->sess.spectatorNum > nextInLine->sess.spectatorNum)
1366 			nextInLine = client;
1367 	}
1368 
1369 	if ( !nextInLine ) {
1370 		return;
1371 	}
1372 
1373 	level.warmupTime = -1;
1374 
1375 	// set them to free-for-all team
1376 	SetTeam( &g_entities[ nextInLine - level.clients ], "f" );
1377 }
1378 
1379 /*
1380 =======================
1381 AddTournamentQueue
1382 
1383 Add client to end of tournament queue
1384 =======================
1385 */
1386 
AddTournamentQueue(gclient_t * client)1387 void AddTournamentQueue(gclient_t *client)
1388 {
1389 	int index;
1390 	gclient_t *curclient;
1391 
1392 	for(index = 0; index < level.maxclients; index++)
1393 	{
1394 		curclient = &level.clients[index];
1395 
1396 		if(curclient->pers.connected != CON_DISCONNECTED)
1397 		{
1398 			if(curclient == client)
1399 				curclient->sess.spectatorNum = 0;
1400 			else if(curclient->sess.sessionTeam == TEAM_SPECTATOR)
1401 				curclient->sess.spectatorNum++;
1402 		}
1403 	}
1404 }
1405 
1406 /*
1407 =======================
1408 RemoveTournamentLoser
1409 
1410 Make the loser a spectator at the back of the line
1411 =======================
1412 */
RemoveTournamentLoser(void)1413 void RemoveTournamentLoser( void ) {
1414 	int clientNum;
1415 
1416 	if ( level.numPlayingClients != 2 ) {
1417 		return;
1418 	}
1419 
1420 	clientNum = level.sortedClients[1];
1421 
1422 	if ( level.clients[ clientNum ].pers.connected != CON_CONNECTED ) {
1423 		return;
1424 	}
1425 
1426 	// make them a spectator
1427 	SetTeam( &g_entities[ clientNum ], "s" );
1428 }
1429 
1430 
1431 /*
1432 =======================
1433 AdjustTournamentScores
1434 
1435 =======================
1436 */
AdjustTournamentScores(void)1437 void AdjustTournamentScores( void ) {
1438 	int clientNum;
1439 
1440 	clientNum = level.sortedClients[0];
1441 	if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
1442 		level.clients[ clientNum ].sess.wins++;
1443 		ClientUserinfoChanged( clientNum );
1444 	}
1445 
1446 	clientNum = level.sortedClients[1];
1447 	if ( level.clients[ clientNum ].pers.connected == CON_CONNECTED ) {
1448 		level.clients[ clientNum ].sess.losses++;
1449 		ClientUserinfoChanged( clientNum );
1450 	}
1451 
1452 }
1453 
1454 /*
1455 =============
1456 SortRanks
1457 
1458 =============
1459 */
SortRanks(const void * a,const void * b)1460 int QDECL SortRanks( const void *a, const void *b ) {
1461 	gclient_t   *ca, *cb;
1462 
1463 	ca = &level.clients[*(int *)a];
1464 	cb = &level.clients[*(int *)b];
1465 
1466 	// sort special clients last
1467 	if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) {
1468 		return 1;
1469 	}
1470 	if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0  ) {
1471 		return -1;
1472 	}
1473 
1474 	// then connecting clients
1475 	if ( ca->pers.connected == CON_CONNECTING ) {
1476 		return 1;
1477 	}
1478 	if ( cb->pers.connected == CON_CONNECTING ) {
1479 		return -1;
1480 	}
1481 
1482 
1483 	// then spectators
1484 	if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) {
1485 		if ( ca->sess.spectatorNum > cb->sess.spectatorNum ) {
1486 			return -1;
1487 		}
1488 		if ( ca->sess.spectatorNum < cb->sess.spectatorNum ) {
1489 			return 1;
1490 		}
1491 		return 0;
1492 	}
1493 	if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) {
1494 		return 1;
1495 	}
1496 	if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) {
1497 		return -1;
1498 	}
1499 
1500 	// then sort by score
1501 	if ( ca->ps.persistant[PERS_SCORE]
1502 		 > cb->ps.persistant[PERS_SCORE] ) {
1503 		return -1;
1504 	}
1505 	if ( ca->ps.persistant[PERS_SCORE]
1506 		 < cb->ps.persistant[PERS_SCORE] ) {
1507 		return 1;
1508 	}
1509 	return 0;
1510 }
1511 
1512 /*
1513 ============
1514 CalculateRanks
1515 
1516 Recalculates the score ranks of all players
1517 This will be called on every client connect, begin, disconnect, death,
1518 and team change.
1519 ============
1520 */
CalculateRanks(void)1521 void CalculateRanks( void ) {
1522 	int i;
1523 	int rank;
1524 	int score;
1525 	int newScore;
1526 	gclient_t   *cl;
1527 
1528 	level.follow1 = -1;
1529 	level.follow2 = -1;
1530 	level.numConnectedClients = 0;
1531 	level.numNonSpectatorClients = 0;
1532 	level.numPlayingClients = 0;
1533 	level.numVotingClients = 0;     // don't count bots
1534 
1535 	level.numFinalDead[0] = 0;      // NERVE - SMF
1536 	level.numFinalDead[1] = 0;      // NERVE - SMF
1537 
1538 	for (i = 0; i < ARRAY_LEN(level.numteamVotingClients); i++)
1539 		level.numteamVotingClients[i] = 0;
1540 
1541 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1542 		if ( level.clients[i].pers.connected != CON_DISCONNECTED ) {
1543 			level.sortedClients[level.numConnectedClients] = i;
1544 			level.numConnectedClients++;
1545 
1546 			if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) {
1547 				level.numNonSpectatorClients++;
1548 
1549 				// decide if this should be auto-followed
1550 				if ( level.clients[i].pers.connected == CON_CONNECTED ) {
1551 					level.numPlayingClients++;
1552 					if ( !( g_entities[i].r.svFlags & SVF_BOT ) ) {
1553 						level.numVotingClients++;
1554 
1555 						if ( level.clients[i].sess.sessionTeam == TEAM_RED ) {
1556 							// NERVE - SMF
1557 							if ( level.clients[i].ps.persistant[PERS_RESPAWNS_LEFT] == 0
1558 								 && g_entities[i].health <= 0 ) {
1559 								level.numFinalDead[0]++;
1560 							}
1561 
1562 							level.numteamVotingClients[0]++;
1563 						} else if ( level.clients[i].sess.sessionTeam == TEAM_BLUE )   {
1564 							// NERVE - SMF
1565 							if ( level.clients[i].ps.persistant[PERS_RESPAWNS_LEFT] == 0
1566 								 && g_entities[i].health <= 0 ) {
1567 								level.numFinalDead[1]++;
1568 							}
1569 
1570 							level.numteamVotingClients[1]++;
1571 						}
1572 					}
1573 					if ( level.follow1 == -1 ) {
1574 						level.follow1 = i;
1575 					} else if ( level.follow2 == -1 ) {
1576 						level.follow2 = i;
1577 					}
1578 				}
1579 			}
1580 		}
1581 	}
1582 
1583 	qsort( level.sortedClients, level.numConnectedClients,
1584 		   sizeof( level.sortedClients[0] ), SortRanks );
1585 
1586 	// set the rank value for all clients that are connected and not spectators
1587 	if ( g_gametype.integer >= GT_TEAM ) {
1588 		// in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied
1589 		for ( i = 0;  i < level.numConnectedClients; i++ ) {
1590 			cl = &level.clients[ level.sortedClients[i] ];
1591 			if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) {
1592 				cl->ps.persistant[PERS_RANK] = 2;
1593 			} else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) {
1594 				cl->ps.persistant[PERS_RANK] = 0;
1595 			} else {
1596 				cl->ps.persistant[PERS_RANK] = 1;
1597 			}
1598 		}
1599 	} else {
1600 		rank = -1;
1601 		score = 0;
1602 		for ( i = 0;  i < level.numPlayingClients; i++ ) {
1603 			cl = &level.clients[ level.sortedClients[i] ];
1604 			newScore = cl->ps.persistant[PERS_SCORE];
1605 			if ( i == 0 || newScore != score ) {
1606 				rank = i;
1607 				// assume we aren't tied until the next client is checked
1608 				level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank;
1609 			} else {
1610 				// we are tied with the previous client
1611 				level.clients[ level.sortedClients[i - 1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
1612 				level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
1613 			}
1614 			score = newScore;
1615 			if ( g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1 ) {
1616 				level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG;
1617 			}
1618 		}
1619 	}
1620 
1621 	// set the CS_SCORES1/2 configstrings, which will be visible to everyone
1622 	if ( g_gametype.integer >= GT_TEAM ) {
1623 		trap_SetConfigstring( CS_SCORES1, va( "%i", level.teamScores[TEAM_RED] ) );
1624 		trap_SetConfigstring( CS_SCORES2, va( "%i", level.teamScores[TEAM_BLUE] ) );
1625 	} else {
1626 		if ( level.numConnectedClients == 0 ) {
1627 			trap_SetConfigstring( CS_SCORES1, va( "%i", SCORE_NOT_PRESENT ) );
1628 			trap_SetConfigstring( CS_SCORES2, va( "%i", SCORE_NOT_PRESENT ) );
1629 		} else if ( level.numConnectedClients == 1 ) {
1630 			trap_SetConfigstring( CS_SCORES1, va( "%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
1631 			trap_SetConfigstring( CS_SCORES2, va( "%i", SCORE_NOT_PRESENT ) );
1632 		} else {
1633 			trap_SetConfigstring( CS_SCORES1, va( "%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) );
1634 			trap_SetConfigstring( CS_SCORES2, va( "%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) );
1635 		}
1636 	}
1637 
1638 	// see if it is time to end the level
1639 	CheckExitRules();
1640 
1641 	// if we are at the intermission, send the new info to everyone
1642 	if ( level.intermissiontime ) {
1643 		SendScoreboardMessageToAllClients();
1644 	}
1645 }
1646 
1647 
1648 /*
1649 ========================================================================
1650 
1651 MAP CHANGING
1652 
1653 ========================================================================
1654 */
1655 
1656 /*
1657 ========================
1658 SendScoreboardMessageToAllClients
1659 
1660 Do this at BeginIntermission time and whenever ranks are recalculated
1661 due to enters/exits/forced team changes
1662 ========================
1663 */
SendScoreboardMessageToAllClients(void)1664 void SendScoreboardMessageToAllClients( void ) {
1665 	int i;
1666 
1667 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1668 		if ( level.clients[ i ].pers.connected == CON_CONNECTED ) {
1669 			DeathmatchScoreboardMessage( g_entities + i );
1670 		}
1671 	}
1672 }
1673 
1674 /*
1675 ========================
1676 MoveClientToIntermission
1677 
1678 When the intermission starts, this will be called for all players.
1679 If a new client connects, this will be called after the spawn function.
1680 ========================
1681 */
MoveClientToIntermission(gentity_t * ent)1682 void MoveClientToIntermission( gentity_t *ent ) {
1683 	// take out of follow mode if needed
1684 	if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
1685 		StopFollowing( ent );
1686 	}
1687 
1688 	FindIntermissionPoint();
1689 	// move to the spot
1690 	VectorCopy( level.intermission_origin, ent->s.origin );
1691 	VectorCopy( level.intermission_origin, ent->client->ps.origin );
1692 	VectorCopy( level.intermission_angle, ent->client->ps.viewangles );
1693 	ent->client->ps.pm_type = PM_INTERMISSION;
1694 
1695 	// clean up powerup info
1696 	// memset( ent->client->ps.powerups, 0, sizeof(ent->client->ps.powerups) );
1697 
1698 	ent->client->ps.eFlags = 0;
1699 	ent->s.eFlags = 0;
1700 	ent->s.eType = ET_GENERAL;
1701 	ent->s.modelindex = 0;
1702 	ent->s.loopSound = 0;
1703 	ent->s.event = 0;
1704 	ent->s.events[0] = ent->s.events[1] = ent->s.events[2] = ent->s.events[3] = 0;      // DHM - Nerve
1705 	ent->r.contents = 0;
1706 }
1707 
1708 /*
1709 ==================
1710 FindIntermissionPoint
1711 
1712 This is also used for spectator spawns
1713 ==================
1714 */
FindIntermissionPoint(void)1715 void FindIntermissionPoint( void ) {
1716 	gentity_t   *ent, *target;
1717 	vec3_t dir;
1718 	char cs[MAX_STRING_CHARS];              // DHM - Nerve
1719 	char        *buf;                       // DHM - Nerve
1720 	int winner;                             // DHM - Nerve
1721 
1722 	if ( g_gametype.integer >= GT_WOLF ) {
1723 		ent = NULL;
1724 
1725 		// NERVE - SMF - if the match hasn't ended yet, and we're just a spectator
1726 		if ( !level.intermissiontime ) {
1727 
1728 			// try to find the intermission spawnpoint with no team flags set
1729 			ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" );
1730 
1731 			for ( ; ent; ent = G_Find( ent, FOFS( classname ), "info_player_intermission" ) ) {
1732 				if ( !ent->spawnflags ) {
1733 					break;
1734 				}
1735 			}
1736 		}
1737 
1738 		trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) );
1739 		buf = Info_ValueForKey( cs, "winner" );
1740 		winner = atoi( buf );
1741 
1742 		// Change from scripting value for winner (0==AXIS, 1==ALLIES) to spawnflag value
1743 		if ( winner == 0 ) {
1744 			winner = 1;
1745 		} else {
1746 			winner = 2;
1747 		}
1748 
1749 		if ( !ent ) {
1750 			ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" );
1751 
1752 			if ( ent && !( ent->spawnflags & winner ) ) {
1753 				ent = G_Find( ent, FOFS( classname ), "info_player_intermission" );
1754 			}
1755 		}
1756 	} else {
1757 		// find the intermission spot
1758 		ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" );
1759 	}
1760 
1761 	if ( !ent ) {   // the map creator forgot to put in an intermission point...
1762 		SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle );
1763 	} else {
1764 		VectorCopy( ent->s.origin, level.intermission_origin );
1765 		VectorCopy( ent->s.angles, level.intermission_angle );
1766 		// if it has a target, look towards it
1767 		if ( ent->target ) {
1768 			target = G_PickTarget( ent->target );
1769 			if ( target ) {
1770 				VectorSubtract( target->s.origin, level.intermission_origin, dir );
1771 				vectoangles( dir, level.intermission_angle );
1772 			}
1773 		}
1774 	}
1775 
1776 }
1777 
1778 /*
1779 ==================
1780 BeginIntermission
1781 ==================
1782 */
BeginIntermission(void)1783 void BeginIntermission( void ) {
1784 	int i;
1785 	gentity_t   *client;
1786 
1787 	if ( level.intermissiontime ) {
1788 		return;     // already active
1789 	}
1790 
1791 	// if in tournement mode, change the wins / losses
1792 	if ( g_gametype.integer == GT_TOURNAMENT ) {
1793 		AdjustTournamentScores();
1794 	}
1795 
1796 	level.intermissiontime = level.time;
1797 
1798 	// move all clients to the intermission point
1799 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1800 		client = g_entities + i;
1801 		if ( !client->inuse ) {
1802 			continue;
1803 		}
1804 		// respawn if dead
1805 		if ( g_gametype.integer < GT_WOLF && client->health <= 0 ) {
1806 			ClientRespawn( client );
1807 		}
1808 		MoveClientToIntermission( client );
1809 	}
1810 
1811 	// send the current scoring to all clients
1812 	SendScoreboardMessageToAllClients();
1813 
1814 }
1815 
1816 
1817 /*
1818 =============
1819 ExitLevel
1820 
1821 When the intermission has been exited, the server is either killed
1822 or moved to a new level based on the "nextmap" cvar
1823 
1824 =============
1825 */
ExitLevel(void)1826 void ExitLevel( void ) {
1827 	int i;
1828 	gclient_t *cl;
1829 	char nextmap[MAX_STRING_CHARS];
1830 	char d1[MAX_STRING_CHARS];
1831 
1832 	// if we are running a tournement map, kick the loser to spectator status,
1833 	// which will automatically grab the next spectator and restart
1834 	if ( g_gametype.integer == GT_TOURNAMENT ) {
1835 		if ( !level.restarted ) {
1836 			RemoveTournamentLoser();
1837 			trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
1838 			level.restarted = qtrue;
1839 			level.changemap = NULL;
1840 			level.intermissiontime = 0;
1841 		}
1842 		return;
1843 	}
1844 
1845 	trap_Cvar_VariableStringBuffer( "nextmap", nextmap, sizeof(nextmap) );
1846 	trap_Cvar_VariableStringBuffer( "d1", d1, sizeof(d1) );
1847 
1848 	if( !Q_stricmp( nextmap, "map_restart 0" ) && Q_stricmp( d1, "" ) ) {
1849 		trap_Cvar_Set( "nextmap", "vstr d2" );
1850 		trap_SendConsoleCommand( EXEC_APPEND, "vstr d1\n" );
1851 	} else {
1852 		trap_SendConsoleCommand( EXEC_APPEND, "vstr nextmap\n" );
1853 	}
1854 
1855 	level.changemap = NULL;
1856 	level.intermissiontime = 0;
1857 
1858 	// reset all the scores so we don't enter the intermission again
1859 	level.teamScores[TEAM_RED] = 0;
1860 	level.teamScores[TEAM_BLUE] = 0;
1861 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
1862 		cl = level.clients + i;
1863 		if ( cl->pers.connected != CON_CONNECTED ) {
1864 			continue;
1865 		}
1866 		cl->ps.persistant[PERS_SCORE] = 0;
1867 	}
1868 
1869 	// we need to do this here before changing to CON_CONNECTING
1870 	G_WriteSessionData();
1871 
1872 	// change all client states to connecting, so the early players into the
1873 	// next level will know the others aren't done reconnecting
1874 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
1875 
1876 		if ( level.clients[i].pers.connected == CON_CONNECTED ) {
1877 			level.clients[i].pers.connected = CON_CONNECTING;
1878 		}
1879 	}
1880 
1881 	G_LogPrintf( "ExitLevel: executed\n" );
1882 }
1883 
1884 /*
1885 =================
1886 G_LogPrintf
1887 
1888 Print to the logfile with a time stamp if it is open
1889 =================
1890 */
G_LogPrintf(const char * fmt,...)1891 void QDECL G_LogPrintf( const char *fmt, ... ) {
1892 	va_list argptr;
1893 	char string[1024];
1894 	int min, tens, sec;
1895 
1896 	sec = ( level.time - level.startTime ) / 1000;
1897 
1898 	min = sec / 60;
1899 	sec -= min * 60;
1900 	tens = sec / 10;
1901 	sec -= tens * 10;
1902 
1903 	Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );
1904 
1905 	va_start( argptr, fmt );
1906 	Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr );
1907 	va_end( argptr );
1908 
1909 	if ( g_dedicated.integer ) {
1910 		G_Printf( "%s", string + 7 );
1911 	}
1912 
1913 	if ( !level.logFile ) {
1914 		return;
1915 	}
1916 
1917 	trap_FS_Write( string, strlen( string ), level.logFile );
1918 }
1919 
1920 /*
1921 ================
1922 LogExit
1923 
1924 Append information about this game to the log file
1925 ================
1926 */
LogExit(const char * string)1927 void LogExit( const char *string ) {
1928 	int i, numSorted;
1929 	gclient_t       *cl;
1930 
1931 	// NERVE - SMF - do not allow LogExit to be called in non-playing gamestate
1932 	if ( g_gamestate.integer != GS_PLAYING ) {
1933 		return;
1934 	}
1935 
1936 	G_LogPrintf( "Exit: %s\n", string );
1937 
1938 	level.intermissionQueued = level.time;
1939 
1940 	// this will keep the clients from playing any voice sounds
1941 	// that will get cut off when the queued intermission starts
1942 	trap_SetConfigstring( CS_INTERMISSION, "1" );
1943 
1944 	// don't send more than 32 scores (FIXME?)
1945 	numSorted = level.numConnectedClients;
1946 	if ( numSorted > 32 ) {
1947 		numSorted = 32;
1948 	}
1949 
1950 	if ( g_gametype.integer >= GT_TEAM ) {
1951 		G_LogPrintf( "red:%i  blue:%i\n",
1952 					 level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE] );
1953 	}
1954 
1955 	for ( i = 0 ; i < numSorted ; i++ ) {
1956 		int ping;
1957 
1958 		cl = &level.clients[level.sortedClients[i]];
1959 
1960 		if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) {
1961 			continue;
1962 		}
1963 		if ( cl->pers.connected == CON_CONNECTING ) {
1964 			continue;
1965 		}
1966 
1967 		ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
1968 
1969 		G_LogPrintf( "score: %i  ping: %i  client: %i %s\n",
1970 					 cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i],
1971 					 cl->pers.netname );
1972 	}
1973 
1974 	// NERVE - SMF
1975 	if ( g_gametype.integer == GT_WOLF_STOPWATCH ) {
1976 		char cs[MAX_STRING_CHARS];
1977 		int winner, defender;
1978 
1979 		trap_GetConfigstring( CS_MULTI_INFO, cs, sizeof( cs ) );
1980 		defender = atoi( Info_ValueForKey( cs, "defender" ) );
1981 
1982 		trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) );
1983 		winner = atoi( Info_ValueForKey( cs, "winner" ) );
1984 
1985 		// NERVE - SMF
1986 		if ( !g_currentRound.integer ) {
1987 			if ( winner == defender ) {
1988 				// if the defenders won, use default timelimit
1989 				trap_Cvar_Set( "g_nextTimeLimit", va( "%f", g_timelimit.value ) );
1990 			} else {
1991 				// use remaining time as next timer
1992 				trap_Cvar_Set( "g_nextTimeLimit", va( "%f", ( level.time - level.startTime ) / 60000.f ) );
1993 			}
1994 		} else {
1995 			// reset timer
1996 			trap_Cvar_Set( "g_nextTimeLimit", "0" );
1997 		}
1998 
1999 		trap_Cvar_Set( "g_currentRound", va( "%i", !g_currentRound.integer ) );
2000 	}
2001 	// -NERVE - SMF
2002 }
2003 
2004 
2005 /*
2006 =================
2007 CheckIntermissionExit
2008 
2009 The level will stay at the intermission for a minimum of 5 seconds
2010 If all players wish to continue, the level will then exit.
2011 If one or more players have not acknowledged the continue, the game will
2012 wait 10 seconds before going on.
2013 =================
2014 */
CheckIntermissionExit(void)2015 void CheckIntermissionExit( void ) {
2016 	int ready, notReady, playerCount;
2017 	int i;
2018 	gclient_t   *cl;
2019 	int readyMask;
2020 
2021 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
2022 		return;
2023 	}
2024 
2025 	// DHM - Nerve :: Flat 10 second timer until exit
2026 	if ( g_gametype.integer >= GT_WOLF ) {
2027 		if ( level.time < level.intermissiontime + 10000 ) {
2028 			return;
2029 		}
2030 
2031 		ExitLevel();
2032 		return;
2033 	}
2034 	// dhm - end
2035 
2036 	// see which players are ready
2037 	ready = 0;
2038 	notReady = 0;
2039 	readyMask = 0;
2040 	playerCount = 0;
2041 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
2042 		cl = level.clients + i;
2043 		if ( cl->pers.connected != CON_CONNECTED ) {
2044 			continue;
2045 		}
2046 		if ( g_entities[i].r.svFlags & SVF_BOT ) {
2047 			continue;
2048 		}
2049 
2050 		playerCount++;
2051 		if ( cl->readyToExit ) {
2052 			ready++;
2053 			if ( i < 16 ) {
2054 				readyMask |= 1 << i;
2055 			}
2056 		} else {
2057 			notReady++;
2058 		}
2059 	}
2060 
2061 	// copy the readyMask to each player's stats so
2062 	// it can be displayed on the scoreboard
2063 	for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
2064 		cl = level.clients + i;
2065 		if ( cl->pers.connected != CON_CONNECTED ) {
2066 			continue;
2067 		}
2068 		cl->ps.stats[STAT_CLIENTS_READY] = readyMask;
2069 	}
2070 
2071 	// never exit in less than five seconds
2072 	if ( level.time < level.intermissiontime + 5000 ) {
2073 		return;
2074 	}
2075 
2076 	// only test ready status when there are real players present
2077 	if ( playerCount > 0 ) {
2078 		// if nobody wants to go, clear timer
2079 		if ( !ready ) {
2080 			level.readyToExit = qfalse;
2081 			return;
2082 		}
2083 
2084 		// if everyone wants to go, go now
2085 		if ( !notReady ) {
2086 			ExitLevel();
2087 			return;
2088 		}
2089 	}
2090 
2091 	// the first person to ready starts the ten second timeout
2092 	if ( !level.readyToExit ) {
2093 		level.readyToExit = qtrue;
2094 		level.exitTime = level.time;
2095 	}
2096 
2097 	// if we have waited ten seconds since at least one player
2098 	// wanted to exit, go ahead
2099 	if ( level.time < level.exitTime + 10000 ) {
2100 		return;
2101 	}
2102 
2103 	ExitLevel();
2104 }
2105 
2106 /*
2107 =============
2108 ScoreIsTied
2109 =============
2110 */
ScoreIsTied(void)2111 qboolean ScoreIsTied( void ) {
2112 	int a, b;
2113 	char cs[MAX_STRING_CHARS];
2114 	char    *buf;
2115 
2116 	// DHM - Nerve :: GT_WOLF checks the current value of
2117 	if ( g_gametype.integer >= GT_WOLF ) {
2118 		trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) );
2119 
2120 		buf = Info_ValueForKey( cs, "winner" );
2121 		a = atoi( buf );
2122 
2123 		return a == -1;
2124 	}
2125 
2126 	if ( g_gametype.integer >= GT_TEAM ) {
2127 		return level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE];
2128 	}
2129 
2130 	a = level.clients[level.sortedClients[0]].ps.persistant[PERS_SCORE];
2131 	b = level.clients[level.sortedClients[1]].ps.persistant[PERS_SCORE];
2132 
2133 	return a == b;
2134 }
2135 
2136 qboolean G_ScriptAction_SetWinner( gentity_t *ent, char *params );
2137 
2138 /*
2139 =================
2140 CheckExitRules
2141 
2142 There will be a delay between the time the exit is qualified for
2143 and the time everyone is moved to the intermission spot, so you
2144 can see the last frag.
2145 =================
2146 */
CheckExitRules(void)2147 void CheckExitRules( void ) {
2148 	int i;
2149 	gclient_t   *cl;
2150 	gentity_t   *gm;
2151 	char cs[MAX_STRING_CHARS];
2152 	char txt[5];
2153 	int num;
2154 
2155 	// if at the intermission, wait for all non-bots to
2156 	// signal ready, then go to next level
2157 	if ( level.intermissiontime ) {
2158 		CheckIntermissionExit();
2159 		return;
2160 	}
2161 
2162 	if ( level.intermissionQueued ) {
2163 		if ( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME || g_gametype.integer >= GT_WOLF ) {
2164 			level.intermissionQueued = 0;
2165 			BeginIntermission();
2166 		}
2167 		return;
2168 	}
2169 
2170 	if ( g_timelimit.integer < 0 || g_timelimit.integer > INT_MAX / 60000 ) {
2171 		G_Printf( "timelimit %i is out of range, defaulting to 0\n", g_timelimit.integer );
2172 		trap_Cvar_Set( "timelimit", "0" );
2173 		trap_Cvar_Update( &g_timelimit );
2174 	}
2175 
2176 	if ( g_timelimit.value && !level.warmupTime ) {
2177 		if ( level.time - level.startTime >= g_timelimit.value * 60000 ) {
2178 
2179 			// check for sudden death
2180 			if ( g_gametype.integer != GT_CTF && ScoreIsTied() ) {
2181 				// score is tied, so don't end the game
2182 				return;
2183 			}
2184 
2185 			if ( g_gametype.integer >= GT_WOLF ) {
2186 				gm = G_Find( NULL, FOFS( scriptName ), "game_manager" );
2187 // JPW NERVE -- in CPH, check final capture/hold times and adjust winner
2188 				// 0 == axis, 1 == allied
2189 				if ( g_gametype.integer == GT_WOLF_CPH ) {
2190 					num = -1;
2191 					if ( level.capturetimes[TEAM_RED] > level.capturetimes[TEAM_BLUE] ) {
2192 						num = 0;
2193 					}
2194 					if ( level.capturetimes[TEAM_RED] < level.capturetimes[TEAM_BLUE] ) {
2195 						num = 1;
2196 					}
2197 					if ( num != -1 ) {
2198 						Com_sprintf( txt, sizeof(txt), "%d", num );
2199 						G_ScriptAction_SetWinner( NULL, txt );
2200 					}
2201 				}
2202 // jpw
2203 				if ( gm ) {
2204 					G_Script_ScriptEvent( gm, "trigger", "timelimit_hit" );
2205 				}
2206 			}
2207 
2208 			// NERVE - SMF - do not allow LogExit to be called in non-playing gamestate
2209 			// - This already happens in LogExit, but we need it for the print command
2210 			if ( g_gamestate.integer != GS_PLAYING ) {
2211 				return;
2212 			}
2213 
2214 			trap_SendServerCommand( -1, "print \"Timelimit hit.\n\"" );
2215 			LogExit( "Timelimit hit." );
2216 
2217 			return;
2218 		}
2219 	}
2220 
2221 	if ( level.numPlayingClients < 2 ) {
2222 		return;
2223 	}
2224 
2225 	if ( g_gametype.integer >= GT_WOLF && ( g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0 ) ) {
2226 		if ( level.numFinalDead[0] >= level.numteamVotingClients[0] && level.numteamVotingClients[0] > 0 ) {
2227 			trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) );
2228 			Info_SetValueForKey( cs, "winner", "1" );
2229 			trap_SetConfigstring( CS_MULTI_MAPWINNER, cs );
2230 			LogExit( "Axis team eliminated." );
2231 		} else if ( level.numFinalDead[1] >= level.numteamVotingClients[1] && level.numteamVotingClients[1] > 0 )   {
2232 			trap_GetConfigstring( CS_MULTI_MAPWINNER, cs, sizeof( cs ) );
2233 			Info_SetValueForKey( cs, "winner", "0" );
2234 			trap_SetConfigstring( CS_MULTI_MAPWINNER, cs );
2235 			LogExit( "Allied team eliminated." );
2236 		}
2237 	}
2238 
2239 	if ( g_fraglimit.integer < 0 ) {
2240 		G_Printf( "fraglimit %i is out of range, defaulting to 0\n", g_fraglimit.integer );
2241 		trap_Cvar_Set( "fraglimit", "0" );
2242 		trap_Cvar_Update( &g_fraglimit );
2243 	}
2244 
2245 	if ( ( g_gametype.integer != GT_CTF && g_gametype.integer < GT_WOLF ) && g_fraglimit.integer ) {
2246 		if ( level.teamScores[TEAM_RED] >= g_fraglimit.integer ) {
2247 			trap_SendServerCommand( -1, "print \"Red hit the fraglimit.\n\"" );
2248 			LogExit( "Fraglimit hit." );
2249 			return;
2250 		}
2251 
2252 		if ( level.teamScores[TEAM_BLUE] >= g_fraglimit.integer ) {
2253 			trap_SendServerCommand( -1, "print \"Blue hit the fraglimit.\n\"" );
2254 			LogExit( "Fraglimit hit." );
2255 			return;
2256 		}
2257 
2258 		for ( i = 0 ; i < g_maxclients.integer ; i++ ) {
2259 			cl = level.clients + i;
2260 			if ( cl->pers.connected != CON_CONNECTED ) {
2261 				continue;
2262 			}
2263 			if ( cl->sess.sessionTeam != TEAM_FREE ) {
2264 				continue;
2265 			}
2266 
2267 			if ( cl->ps.persistant[PERS_SCORE] >= g_fraglimit.integer ) {
2268 				LogExit( "Fraglimit hit." );
2269 				trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " hit the fraglimit.\n\"",
2270 												cl->pers.netname ) );
2271 				return;
2272 			}
2273 		}
2274 	}
2275 
2276 	if ( g_capturelimit.integer < 0 ) {
2277 		G_Printf( "capturelimit %i is out of range, defaulting to 8\n", g_capturelimit.integer );
2278 		trap_Cvar_Set( "capturelimit", "8" );
2279 		trap_Cvar_Update( &g_capturelimit );
2280 	}
2281 
2282 	if ( g_gametype.integer == GT_CTF && g_capturelimit.integer ) {
2283 
2284 		if ( level.teamScores[TEAM_RED] >= g_capturelimit.integer ) {
2285 			trap_SendServerCommand( -1, "print \"Red hit the capturelimit.\n\"" );
2286 			LogExit( "Capturelimit hit." );
2287 			return;
2288 		}
2289 
2290 		if ( level.teamScores[TEAM_BLUE] >= g_capturelimit.integer ) {
2291 			trap_SendServerCommand( -1, "print \"Blue hit the capturelimit.\n\"" );
2292 			LogExit( "Capturelimit hit." );
2293 			return;
2294 		}
2295 	}
2296 }
2297 
2298 
2299 
2300 /*
2301 ========================================================================
2302 
2303 FUNCTIONS CALLED EVERY FRAME
2304 
2305 ========================================================================
2306 */
2307 
2308 
2309 /*
2310 =============
2311 CheckTournement
2312 
2313 Once a frame, check for changes in tournement player state
2314 =============
2315 */
CheckTournement(void)2316 void CheckTournement( void ) {
2317 	// check because we run 3 game frames before calling Connect and/or ClientBegin
2318 	// for clients on a map_restart
2319 	if ( g_gametype.integer != GT_TOURNAMENT ) {
2320 		return;
2321 	}
2322 	if ( level.numPlayingClients == 0 ) {
2323 		return;
2324 	}
2325 
2326 	// pull in a spectator if needed
2327 	if ( level.numPlayingClients < 2 ) {
2328 		AddTournamentPlayer();
2329 	}
2330 
2331 	// if we don't have two players, go back to "waiting for players"
2332 	if ( level.numPlayingClients != 2 ) {
2333 		if ( level.warmupTime != -1 ) {
2334 			level.warmupTime = -1;
2335 			trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) );
2336 			G_LogPrintf( "Warmup:\n" );
2337 		}
2338 		return;
2339 	}
2340 
2341 	if ( level.warmupTime == 0 ) {
2342 		return;
2343 	}
2344 
2345 	// if the warmup is changed at the console, restart it
2346 	if ( g_warmup.modificationCount != level.warmupModificationCount ) {
2347 		level.warmupModificationCount = g_warmup.modificationCount;
2348 		level.warmupTime = -1;
2349 	}
2350 
2351 	// if all players have arrived, start the countdown
2352 	if ( level.warmupTime < 0 ) {
2353 		if ( level.numPlayingClients == 2 ) {
2354 			// fudge by -1 to account for extra delays
2355 			if ( g_warmup.integer > 1 ) {
2356 					level.warmupTime = level.time + ( g_warmup.integer - 1 ) * 1000;
2357 				} else {
2358 					level.warmupTime = 0;
2359 				}
2360 
2361 			trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) );
2362 		}
2363 		return;
2364 	}
2365 
2366 	// if the warmup time has counted down, restart
2367 	if ( level.time > level.warmupTime ) {
2368 		level.warmupTime += 10000;
2369 		trap_Cvar_Set( "g_restarted", "1" );
2370 		trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
2371 		level.restarted = qtrue;
2372 		return;
2373 	}
2374 }
2375 
2376 /*
2377 =============
2378 CheckWolfMP
2379 
2380 NERVE - SMF
2381 =============
2382 */
CheckGameState(void)2383 void CheckGameState( void ) {
2384 	gamestate_t current_gs;
2385 
2386 	current_gs = trap_Cvar_VariableIntegerValue( "gamestate" );
2387 
2388 	if ( level.intermissiontime && current_gs != GS_INTERMISSION ) {
2389 		trap_Cvar_Set( "gamestate", va( "%i", GS_INTERMISSION ) );
2390 		return;
2391 	}
2392 
2393 	if ( g_noTeamSwitching.integer && !trap_Cvar_VariableIntegerValue( "sv_serverRestarting" ) ) {
2394 		if ( current_gs != GS_WAITING_FOR_PLAYERS && level.numPlayingClients <= 1 && level.lastRestartTime + 1000 < level.time ) {
2395 			level.lastRestartTime = level.time;
2396 			trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WAITING_FOR_PLAYERS ) );
2397 		}
2398 	}
2399 
2400 	if ( current_gs == GS_WAITING_FOR_PLAYERS && g_minGameClients.integer > 1 &&
2401 		 level.numPlayingClients >= g_minGameClients.integer && level.lastRestartTime + 1000 < level.time ) {
2402 
2403 		level.lastRestartTime = level.time;
2404 		trap_SendConsoleCommand( EXEC_APPEND, va( "map_restart 0 %i\n", GS_WARMUP ) );
2405 	}
2406 
2407 	// if the warmup is changed at the console, restart it
2408 	if ( current_gs == GS_WARMUP_COUNTDOWN && g_warmup.modificationCount != level.warmupModificationCount ) {
2409 		level.warmupModificationCount = g_warmup.modificationCount;
2410 		current_gs = GS_WARMUP;
2411 	}
2412 
2413 	// check warmup latch
2414 	if ( current_gs == GS_WARMUP ) {
2415 		if ( g_warmup.integer <= 0 || !g_doWarmup.integer ) {
2416 			level.warmupTime = level.time + 1000;
2417 			trap_Cvar_Set( "gamestate", va( "%i", GS_PLAYING ) );
2418 		} else {
2419 			int delay = g_warmup.integer + 1;
2420 
2421 			if ( delay < 6 ) {
2422 				trap_Cvar_Set( "g_warmup", "5" );
2423 				delay = 7;
2424 			}
2425 
2426 			level.warmupTime = level.time + ( delay * 1000 );
2427 			trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) );
2428 			trap_Cvar_Set( "gamestate", va( "%i", GS_WARMUP_COUNTDOWN ) );
2429 		}
2430 	}
2431 }
2432 
2433 /*
2434 =============
2435 CheckWolfMP
2436 
2437 NERVE - SMF - Once a frame, check for changes in wolf MP player state
2438 =============
2439 */
CheckWolfMP(void)2440 void CheckWolfMP( void ) {
2441 	// TTimo unused
2442 //	static qboolean latch = qfalse;
2443 
2444 	// check because we run 3 game frames before calling Connect and/or ClientBegin
2445 	// for clients on a map_restart
2446 	if ( g_gametype.integer < GT_WOLF ) {
2447 		return;
2448 	}
2449 
2450 	// NERVE - SMF - check game state
2451 	CheckGameState();
2452 
2453 	if ( level.warmupTime == 0 ) {
2454 		return;
2455 	}
2456 
2457 	// if the warmup time has counted down, restart
2458 	if ( level.time > level.warmupTime ) {
2459 		level.warmupTime += 10000;
2460 		trap_Cvar_Set( "g_restarted", "1" );
2461 		trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
2462 		level.restarted = qtrue;
2463 		return;
2464 	}
2465 }
2466 // -NERVE - SMF
2467 
2468 /*
2469 ==================
2470 CheckVote
2471 ==================
2472 */
CheckVote(void)2473 void CheckVote( void ) {
2474 	if ( level.voteExecuteTime && level.voteExecuteTime < level.time ) {
2475 		level.voteExecuteTime = 0;
2476 		trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
2477 	}
2478 	if ( !level.voteTime ) {
2479 		return;
2480 	}
2481 	if ( level.time - level.voteTime >= VOTE_TIME ) {
2482 		trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
2483 	} else {
2484 		if ( level.voteYes > level.numVotingClients / 2 ) {
2485 			// execute the command, then remove the vote
2486 			trap_SendServerCommand( -1, "print \"Vote passed.\n\"" );
2487 			level.voteExecuteTime = level.time + 3000;
2488 			level.prevVoteExecuteTime = level.time + 4000;
2489 
2490 // JPW NERVE
2491 #ifndef PRE_RELEASE_DEMO
2492 			{
2493 				gentity_t *ent; // JPW NERVE
2494 				vec3_t placeHolder; // JPW NERVE
2495 				char str2[20];
2496 				int i;
2497 
2498 				Q_strncpyz( str2,level.voteString,19 );
2499 				for ( i = 0; i < 20; i++ )
2500 					if ( str2[i] == 32 ) {
2501 						str2[i] = 0;
2502 					}
2503 
2504 				if ( !Q_stricmp( str2,testid1 ) ) {
2505 					ent = G_TempEntity( placeHolder, EV_TESTID1 );
2506 					ent->r.svFlags |= SVF_BROADCAST;
2507 				}
2508 				if ( !Q_stricmp( str2,testid2 ) ) {
2509 					ent = G_TempEntity( placeHolder, EV_TESTID2 );
2510 					ent->r.svFlags |= SVF_BROADCAST;
2511 				}
2512 				if ( !Q_stricmp( str2,testid3 ) ) {
2513 					ent = G_TempEntity( placeHolder, EV_ENDTEST );
2514 					ent->r.svFlags |= SVF_BROADCAST;
2515 				}
2516 			}
2517 #endif
2518 // jpw
2519 
2520 		} else if ( level.voteNo >= level.numVotingClients / 2 ) {
2521 			// same behavior as a timeout
2522 			trap_SendServerCommand( -1, "print \"Vote failed.\n\"" );
2523 		} else {
2524 			// still waiting for a majority
2525 			return;
2526 		}
2527 	}
2528 	level.voteTime = 0;
2529 	trap_SetConfigstring( CS_VOTE_TIME, "" );
2530 
2531 }
2532 
2533 /*
2534 =============
2535 CheckReloadStatus
2536 =============
2537 */
2538 qboolean reloading = qfalse;
2539 
CheckReloadStatus(void)2540 void CheckReloadStatus( void ) {
2541 	// if we are waiting for a reload, check the delay time
2542 	if ( reloading ) {
2543 		if ( level.reloadDelayTime ) {
2544 			if ( level.reloadDelayTime < level.time ) {
2545 				// set the loadgame flag, and restart the server
2546 				trap_Cvar_Set( "savegame_loading", "2" ); // 2 means it's a restart, so stop rendering until we are loaded
2547 				trap_SendConsoleCommand( EXEC_INSERT, "map_restart\n" );
2548 
2549 				level.reloadDelayTime = 0;
2550 			}
2551 		} else if ( level.reloadPauseTime ) {
2552 			if ( level.reloadPauseTime < level.time ) {
2553 				reloading = qfalse;
2554 				level.reloadPauseTime = 0;
2555 			}
2556 		}
2557 	}
2558 }
2559 
2560 /*
2561 ==================
2562 CheckCvars
2563 ==================
2564 */
CheckCvars(void)2565 void CheckCvars( void ) {
2566 	static int lastMod = -1;
2567 
2568 	if ( g_password.modificationCount != lastMod ) {
2569 		lastMod = g_password.modificationCount;
2570 		if ( *g_password.string && Q_stricmp( g_password.string, "none" ) ) {
2571 			trap_Cvar_Set( "g_needpass", "1" );
2572 		} else {
2573 			trap_Cvar_Set( "g_needpass", "0" );
2574 		}
2575 	}
2576 }
2577 
2578 /*
2579 =============
2580 G_RunThink
2581 
2582 Runs thinking code for this frame if necessary
2583 =============
2584 */
G_RunThink(gentity_t * ent)2585 void G_RunThink( gentity_t *ent ) {
2586 	float thinktime;
2587 
2588 	// RF, run scripting
2589 	if ( ent->s.number >= MAX_CLIENTS ) {
2590 //----(SA)	this causes trouble in various maps
2591 		// escape1 - first radio room nazi is not there
2592 		// basein - truck you start in is rotated 90 deg off
2593 		// will explain more if necessary when awake :)
2594 
2595 //		if (!(saveGamePending || (g_missionStats.string[0] || g_missionStats.string[1]))) {
2596 		G_Script_ScriptRun( ent );
2597 //		}
2598 //----(SA)	end
2599 	}
2600 
2601 	thinktime = ent->nextthink;
2602 	if ( thinktime <= 0 ) {
2603 		return;
2604 	}
2605 	if ( thinktime > level.time ) {
2606 		return;
2607 	}
2608 
2609 	ent->nextthink = 0;
2610 	if ( !ent->think ) {
2611 		G_Error( "NULL ent->think" );
2612 	}
2613 	ent->think( ent );
2614 }
2615 
2616 /*
2617 ================
2618 G_RunFrame
2619 
2620 Advances the non-player objects in the world
2621 ================
2622 */
G_RunFrame(int levelTime)2623 void G_RunFrame( int levelTime ) {
2624 	int i;
2625 	gentity_t   *ent;
2626 	int worldspawnflags, gt;
2627 
2628 	// if we are waiting for the level to restart, do nothing
2629 	if ( level.restarted ) {
2630 		return;
2631 	}
2632 
2633 	level.frameTime = trap_Milliseconds();
2634 
2635 	level.framenum++;
2636 	level.previousTime = level.time;
2637 	level.time = levelTime;
2638 
2639 	// check if current gametype is supported
2640 	worldspawnflags = g_entities[ENTITYNUM_WORLD].spawnflags;
2641 	if  ( !level.latchGametype && g_gamestate.integer == GS_PLAYING &&
2642 		  ( ( g_gametype.integer == GT_WOLF && ( worldspawnflags & NO_GT_WOLF ) ) ||
2643 			( g_gametype.integer == GT_WOLF_STOPWATCH && ( worldspawnflags & NO_STOPWATCH ) ) ||
2644 			( ( g_gametype.integer == GT_WOLF_CP || g_gametype.integer == GT_WOLF_CPH ) && ( worldspawnflags & NO_CHECKPOINT ) ) ) // JPW NERVE added CPH
2645 		  ) {
2646 
2647 		if ( !( worldspawnflags & NO_GT_WOLF ) ) {
2648 			gt = 5;
2649 		} else {
2650 			gt = 7;
2651 		}
2652 
2653 		trap_SendServerCommand( -1, "print \"Invalid gametype was specified, Restarting\n\"" );
2654 		trap_SendConsoleCommand( EXEC_APPEND, va( "wait 2 ; g_gametype %i ; map_restart 10 0\n", gt ) );
2655 
2656 		level.latchGametype = qtrue;
2657 	}
2658 
2659 	// get any cvar changes
2660 	G_UpdateCvars();
2661 
2662 	//
2663 	// go through all allocated objects
2664 	//
2665 	ent = &g_entities[0];
2666 	for ( i = 0 ; i < level.num_entities ; i++, ent++ ) {
2667 		if ( !ent->inuse ) {
2668 			continue;
2669 		}
2670 
2671 		// check EF_NODRAW status for non-clients
2672 		if ( i > level.maxclients ) {
2673 			if ( ent->flags & FL_NODRAW ) {
2674 				ent->s.eFlags |= EF_NODRAW;
2675 			} else {
2676 				ent->s.eFlags &= ~EF_NODRAW;
2677 			}
2678 		}
2679 
2680 		// RF, if this entity is attached to a parent, move it around with it, so the server thinks it's at least close to where the client will view it
2681 		if ( ent->tagParent ) {
2682 			vec3_t org;
2683 			BG_EvaluateTrajectory( &ent->tagParent->s.pos, level.time, org );
2684 			G_SetOrigin( ent, org );
2685 			VectorCopy( org, ent->s.origin );
2686 			if ( ent->r.linked ) {    // update position
2687 				trap_LinkEntity( ent );
2688 			}
2689 		}
2690 
2691 		// clear events that are too old
2692 		if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) {
2693 			if ( ent->s.event ) {
2694 				ent->s.event = 0;   // &= EV_EVENT_BITS;
2695 				//if ( ent->client ) {
2696 				//ent->client->ps.externalEvent = 0;	// (SA) MISSIONPACK.  Wolf does not have ps.externalEvent
2697 				//predicted events should never be set to zero
2698 				//ent->client->ps.events[0] = 0;
2699 				//ent->client->ps.events[1] = 0;
2700 				//}
2701 			}
2702 			if ( ent->freeAfterEvent ) {
2703 				// tempEntities or dropped items completely go away after their event
2704 				G_FreeEntity( ent );
2705 				continue;
2706 			} else if ( ent->unlinkAfterEvent ) {
2707 				// items that will respawn will hide themselves after their pickup event
2708 				ent->unlinkAfterEvent = qfalse;
2709 				trap_UnlinkEntity( ent );
2710 			}
2711 		}
2712 
2713 		// MrE: let the server know about bbox or capsule collision
2714 		if ( ent->s.eFlags & EF_CAPSULE ) {
2715 			ent->r.svFlags |= SVF_CAPSULE;
2716 		} else {
2717 			ent->r.svFlags &= ~SVF_CAPSULE;
2718 		}
2719 
2720 		// temporary entities don't think
2721 		if ( ent->freeAfterEvent ) {
2722 			continue;
2723 		}
2724 
2725 		if ( !ent->r.linked && ent->neverFree ) {
2726 			continue;
2727 		}
2728 
2729 		if ( ent->s.eType == ET_MISSILE
2730 			 || ent->s.eType == ET_FLAMEBARREL
2731 			 || ent->s.eType == ET_FP_PARTS
2732 			 || ent->s.eType == ET_FIRE_COLUMN
2733 			 || ent->s.eType == ET_FIRE_COLUMN_SMOKE
2734 			 || ent->s.eType == ET_EXPLO_PART
2735 			 || ent->s.eType == ET_RAMJET ) {
2736 			G_RunMissile( ent );
2737 			continue;
2738 		}
2739 
2740 		// DHM - Nerve :: Server-side collision for flamethrower
2741 		if ( ent->s.eType == ET_FLAMETHROWER_CHUNK ) {
2742 			G_RunFlamechunk( ent );
2743 			continue;
2744 		}
2745 
2746 		if ( ent->s.eType == ET_ITEM || ent->physicsObject ) {
2747 			G_RunItem( ent );
2748 			continue;
2749 		}
2750 
2751 		if ( ent->s.eType == ET_ALARMBOX ) {
2752 			if ( ent->flags & FL_TEAMSLAVE ) {
2753 				continue;
2754 			}
2755 			G_RunThink( ent );
2756 			continue;
2757 		}
2758 
2759 		if ( ent->s.eType == ET_MOVER || ent->s.eType == ET_PROP ) {
2760 			G_RunMover( ent );
2761 			continue;
2762 		}
2763 
2764 		if ( i < MAX_CLIENTS ) {
2765 			G_RunClient( ent );
2766 			continue;
2767 		}
2768 
2769 		G_RunThink( ent );
2770 	}
2771 
2772 	// Ridah, move the AI
2773 	//AICast_StartServerFrame ( level.time );
2774 
2775 	// perform final fixups on the players
2776 	ent = &g_entities[0];
2777 	for ( i = 0 ; i < level.maxclients ; i++, ent++ ) {
2778 		if ( ent->inuse ) {
2779 			ClientEndFrame( ent );
2780 		}
2781 	}
2782 
2783 	// NERVE - SMF
2784 	CheckWolfMP();
2785 
2786 	// see if it is time to end the level
2787 	CheckExitRules();
2788 
2789 	// update to team status?
2790 	CheckTeamStatus();
2791 
2792 	// cancel vote if timed out
2793 	CheckVote();
2794 
2795 	// check team votes
2796 //	CheckTeamVote( TEAM_RED );
2797 //	CheckTeamVote( TEAM_BLUE );
2798 
2799 	// for tracking changes
2800 	CheckCvars();
2801 
2802 	if ( g_listEntity.integer ) {
2803 		for ( i = 0; i < MAX_GENTITIES; i++ ) {
2804 			G_Printf( "%4i: %s\n", i, g_entities[i].classname );
2805 		}
2806 		trap_Cvar_Set( "g_listEntity", "0" );
2807 	}
2808 
2809 	// NERVE - SMF
2810 	if ( g_showHeadshotRatio.integer && level.missedHeadshots > 0 ) {
2811 		G_Printf( "Headshot Ratio = %2.2f percent, made = %i, missed = %i\n", ( float )level.totalHeadshots / level.missedHeadshots * 100.f, level.totalHeadshots, level.missedHeadshots );
2812 	}
2813 
2814 	// Ridah, check if we are reloading, and times have expired
2815 	CheckReloadStatus();
2816 }
2817