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