1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8 
9 Doom 3 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 Doom 3 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 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 Doom 3 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 <SDL_endian.h>
30 
31 #include "sys/platform.h"
32 #include "idlib/LangDict.h"
33 #include "idlib/Timer.h"
34 #include "framework/async/NetworkSystem.h"
35 #include "framework/BuildVersion.h"
36 #include "framework/DeclEntityDef.h"
37 #include "framework/FileSystem.h"
38 #include "renderer/ModelManager.h"
39 
40 #include "gamesys/SysCvar.h"
41 #include "gamesys/SysCmds.h"
42 #include "script/Script_Thread.h"
43 #include "ai/AI.h"
44 #include "anim/Anim_Testmodel.h"
45 #include "Camera.h"
46 #include "SmokeParticles.h"
47 #include "Player.h"
48 #include "WorldSpawn.h"
49 #include "Misc.h"
50 #include "Trigger.h"
51 
52 #include "framework/Licensee.h" // DG: for ID__DATE__
53 
54 #include "Game_local.h"
55 
56 const int NUM_RENDER_PORTAL_BITS	= idMath::BitsForInteger( PS_BLOCK_ALL );
57 
58 const float DEFAULT_GRAVITY			= 1066.0f;
59 const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY );
60 
61 const int	CINEMATIC_SKIP_DELAY	= SEC2MS( 2.0f );
62 
63 #ifdef GAME_DLL
64 
65 idSys *						sys = NULL;
66 idCommon *					common = NULL;
67 idCmdSystem *				cmdSystem = NULL;
68 idCVarSystem *				cvarSystem = NULL;
69 idFileSystem *				fileSystem = NULL;
70 idNetworkSystem *			networkSystem = NULL;
71 idRenderSystem *			renderSystem = NULL;
72 idSoundSystem *				soundSystem = NULL;
73 idRenderModelManager *		renderModelManager = NULL;
74 idUserInterfaceManager *	uiManager = NULL;
75 idDeclManager *				declManager = NULL;
76 idAASFileManager *			AASFileManager = NULL;
77 idCollisionModelManager *	collisionModelManager = NULL;
78 idCVar *					idCVar::staticVars = NULL;
79 
80 idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" );
81 
82 #endif
83 
84 idRenderWorld *				gameRenderWorld = NULL;		// all drawing is done to this world
85 idSoundWorld *				gameSoundWorld = NULL;		// all audio goes to this world
86 
87 static gameExport_t			gameExport;
88 
89 // global animation lib
90 idAnimManager				animationLib;
91 
92 // the rest of the engine will only reference the "game" variable, while all local aspects stay hidden
93 idGameLocal					gameLocal;
94 idGame *					game = &gameLocal;	// statically pointed at an idGameLocal
95 
96 const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = {
97 	"none",	"metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic",
98 	"ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15"
99 };
100 
101 #ifdef _D3XP
102 // List of all defs used by the player that will stay on the fast timeline
103 static const char* fastEntityList[] = {
104 	"player_doommarine",
105 		"weapon_chainsaw",
106 		"weapon_fists",
107 		"weapon_flashlight",
108 		"weapon_rocketlauncher",
109 		"projectile_rocket",
110 		"weapon_machinegun",
111 		"projectile_bullet_machinegun",
112 		"weapon_pistol",
113 		"projectile_bullet_pistol",
114 		"weapon_handgrenade",
115 		"projectile_grenade",
116 		"weapon_bfg",
117 		"projectile_bfg",
118 		"weapon_chaingun",
119 		"projectile_chaingunbullet",
120 		"weapon_pda",
121 		"weapon_plasmagun",
122 		"projectile_plasmablast",
123 		"weapon_shotgun",
124 		"projectile_bullet_shotgun",
125 		"weapon_soulcube",
126 		"projectile_soulblast",
127 		"weapon_shotgun_double",
128 		"projectile_shotgunbullet_double",
129 		"weapon_grabber",
130 		"weapon_bloodstone_active1",
131 		"weapon_bloodstone_active2",
132 		"weapon_bloodstone_active3",
133 		"weapon_bloodstone_passive",
134 		NULL };
135 #endif
136 /*
137 ===========
138 GetGameAPI
139 ============
140 */
GetGameAPI(gameImport_t * import)141 extern "C" ID_GAME_API gameExport_t *GetGameAPI( gameImport_t *import ) {
142 
143 	if ( import->version == GAME_API_VERSION ) {
144 
145 		// set interface pointers used by the game
146 		sys							= import->sys;
147 		common						= import->common;
148 		cmdSystem					= import->cmdSystem;
149 		cvarSystem					= import->cvarSystem;
150 		fileSystem					= import->fileSystem;
151 		networkSystem				= import->networkSystem;
152 		renderSystem				= import->renderSystem;
153 		soundSystem					= import->soundSystem;
154 		renderModelManager			= import->renderModelManager;
155 		uiManager					= import->uiManager;
156 		declManager					= import->declManager;
157 		AASFileManager				= import->AASFileManager;
158 		collisionModelManager		= import->collisionModelManager;
159 	}
160 
161 	// set interface pointers used by idLib
162 	idLib::sys					= sys;
163 	idLib::common				= common;
164 	idLib::cvarSystem			= cvarSystem;
165 	idLib::fileSystem			= fileSystem;
166 
167 	// setup export interface
168 	gameExport.version = GAME_API_VERSION;
169 	gameExport.game = game;
170 	gameExport.gameEdit = gameEdit;
171 
172 	return &gameExport;
173 }
174 
175 /*
176 ===========
177 TestGameAPI
178 ============
179 */
TestGameAPI(void)180 void TestGameAPI( void ) {
181 	gameImport_t testImport;
182 	gameExport_t testExport;
183 
184 	testImport.sys						= ::sys;
185 	testImport.common					= ::common;
186 	testImport.cmdSystem				= ::cmdSystem;
187 	testImport.cvarSystem				= ::cvarSystem;
188 	testImport.fileSystem				= ::fileSystem;
189 	testImport.networkSystem			= ::networkSystem;
190 	testImport.renderSystem				= ::renderSystem;
191 	testImport.soundSystem				= ::soundSystem;
192 	testImport.renderModelManager		= ::renderModelManager;
193 	testImport.uiManager				= ::uiManager;
194 	testImport.declManager				= ::declManager;
195 	testImport.AASFileManager			= ::AASFileManager;
196 	testImport.collisionModelManager	= ::collisionModelManager;
197 
198 	testExport = *GetGameAPI( &testImport );
199 }
200 
201 /*
202 ===========
203 idGameLocal::idGameLocal
204 ============
205 */
idGameLocal()206 idGameLocal::idGameLocal() {
207 	Clear();
208 }
209 
210 /*
211 ===========
212 idGameLocal::Clear
213 ============
214 */
Clear(void)215 void idGameLocal::Clear( void ) {
216 	int i;
217 
218 	serverInfo.Clear();
219 	numClients = 0;
220 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
221 		userInfo[i].Clear();
222 		persistentPlayerInfo[i].Clear();
223 	}
224 	memset( usercmds, 0, sizeof( usercmds ) );
225 	memset( entities, 0, sizeof( entities ) );
226 	memset( spawnIds, -1, sizeof( spawnIds ) );
227 	firstFreeIndex = 0;
228 	num_entities = 0;
229 	spawnedEntities.Clear();
230 	activeEntities.Clear();
231 	numEntitiesToDeactivate = 0;
232 	sortPushers = false;
233 	sortTeamMasters = false;
234 	persistentLevelInfo.Clear();
235 	memset( globalShaderParms, 0, sizeof( globalShaderParms ) );
236 	random.SetSeed( 0 );
237 	world = NULL;
238 	frameCommandThread = NULL;
239 	testmodel = NULL;
240 	testFx = NULL;
241 	clip.Shutdown();
242 	pvs.Shutdown();
243 	sessionCommand.Clear();
244 	locationEntities = NULL;
245 	smokeParticles = NULL;
246 	editEntities = NULL;
247 	entityHash.Clear( 1024, MAX_GENTITIES );
248 	inCinematic = false;
249 	cinematicSkipTime = 0;
250 	cinematicStopTime = 0;
251 	cinematicMaxSkipTime = 0;
252 	framenum = 0;
253 	previousTime = 0;
254 	time = 0;
255 	vacuumAreaNum = 0;
256 	mapFileName.Clear();
257 	mapFile = NULL;
258 	spawnCount = INITIAL_SPAWN_COUNT;
259 	mapSpawnCount = 0;
260 	camera = NULL;
261 	aasList.Clear();
262 	aasNames.Clear();
263 	lastAIAlertEntity = NULL;
264 	lastAIAlertTime = 0;
265 	spawnArgs.Clear();
266 	gravity.Set( 0, 0, -1 );
267 	playerPVS.h = -1;
268 	playerConnectedAreas.h = -1;
269 	gamestate = GAMESTATE_UNINITIALIZED;
270 	skipCinematic = false;
271 	influenceActive = false;
272 
273 	localClientNum = 0;
274 	isMultiplayer = false;
275 	isServer = false;
276 	isClient = false;
277 	realClientTime = 0;
278 	isNewFrame = true;
279 	clientSmoothing = 0.1f;
280 	entityDefBits = 0;
281 
282 	nextGibTime = 0;
283 	globalMaterial = NULL;
284 	newInfo.Clear();
285 	lastGUIEnt = NULL;
286 	lastGUI = 0;
287 
288 	memset( clientEntityStates, 0, sizeof( clientEntityStates ) );
289 	memset( clientPVS, 0, sizeof( clientPVS ) );
290 	memset( clientSnapshots, 0, sizeof( clientSnapshots ) );
291 
292 	eventQueue.Init();
293 	savedEventQueue.Init();
294 
295 	memset( lagometer, 0, sizeof( lagometer ) );
296 
297 #ifdef _D3XP
298 	portalSkyEnt			= NULL;
299 	portalSkyActive			= false;
300 
301 	ResetSlowTimeVars();
302 #endif
303 }
304 
305 /*
306 ===========
307 idGameLocal::Init
308 
309   initialize the game object, only happens once at startup, not each level load
310 ============
311 */
Init(void)312 void idGameLocal::Init( void ) {
313 	const idDict *dict;
314 	idAAS *aas;
315 
316 #ifndef GAME_DLL
317 
318 	TestGameAPI();
319 
320 #else
321 
322 	// initialize idLib
323 	idLib::Init();
324 
325 	// register static cvars declared in the game
326 	idCVar::RegisterStaticVars();
327 
328 	// initialize processor specific SIMD
329 	idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() );
330 
331 #endif
332 
333 	Printf( "----- Initializing Game -----\n" );
334 	Printf( "gamename: %s\n", GAME_VERSION );
335 	Printf( "gamedate: %s\n", ID__DATE__ );
336 
337 	// register game specific decl types
338 	declManager->RegisterDeclType( "model",				DECL_MODELDEF,		idDeclAllocator<idDeclModelDef> );
339 	declManager->RegisterDeclType( "export",			DECL_MODELEXPORT,	idDeclAllocator<idDecl> );
340 
341 	// register game specific decl folders
342 	declManager->RegisterDeclFolder( "def",				".def",				DECL_ENTITYDEF );
343 	declManager->RegisterDeclFolder( "fx",				".fx",				DECL_FX );
344 	declManager->RegisterDeclFolder( "particles",		".prt",				DECL_PARTICLE );
345 	declManager->RegisterDeclFolder( "af",				".af",				DECL_AF );
346 	declManager->RegisterDeclFolder( "newpdas",			".pda",				DECL_PDA );
347 
348 	cmdSystem->AddCommand( "listModelDefs", idListDecls_f<DECL_MODELDEF>, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" );
349 	cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f<DECL_MODELDEF>, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl<DECL_MODELDEF> );
350 
351 	Clear();
352 
353 	idEvent::Init();
354 	idClass::Init();
355 
356 	InitConsoleCommands();
357 
358 
359 #ifdef _D3XP
360 	if(!g_xp_bind_run_once.GetBool()) {
361 		//The default config file contains remapped controls that support the XP weapons
362 		//We want to run this once after the base doom config file has run so we can
363 		//have the correct xp binds
364 		cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec default.cfg\n" );
365 		cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "seta g_xp_bind_run_once 1\n" );
366 		cmdSystem->ExecuteCommandBuffer();
367 	}
368 #endif
369 
370 	// load default scripts
371 	program.Startup( SCRIPT_DEFAULT );
372 
373 #ifdef _D3XP
374 	//BSM Nerve: Loads a game specific main script file
375 	idStr gamedir;
376 	int i;
377 	for ( i = 0; i < 2; i++ ) {
378 		if ( i == 0 ) {
379 			gamedir = cvarSystem->GetCVarString( "fs_game_base" );
380 		} else if ( i == 1 ) {
381 			gamedir = cvarSystem->GetCVarString( "fs_game" );
382 		}
383 		if( gamedir.Length() > 0 ) {
384 			idStr scriptFile = va( "script/%s_main.script", gamedir.c_str() );
385 			if ( fileSystem->ReadFile( scriptFile.c_str(), NULL ) > 0 ) {
386 				program.CompileFile( scriptFile.c_str() );
387 				program.FinishCompilation();
388 			}
389 		}
390 	}
391 #endif
392 
393 	smokeParticles = new idSmokeParticles;
394 
395 	// set up the aas
396 	dict = FindEntityDefDict( "aas_types" );
397 	if ( !dict ) {
398 		Error( "Unable to find entityDef for 'aas_types'" );
399 	}
400 
401 	// allocate space for the aas
402 	const idKeyValue *kv = dict->MatchPrefix( "type" );
403 	while( kv != NULL ) {
404 		aas = idAAS::Alloc();
405 		aasList.Append( aas );
406 		aasNames.Append( kv->GetValue() );
407 		kv = dict->MatchPrefix( "type", kv );
408 	}
409 
410 	gamestate = GAMESTATE_NOMAP;
411 
412 	Printf( "...%d aas types\n", aasList.Num() );
413 }
414 
415 /*
416 ===========
417 idGameLocal::Shutdown
418 
419   shut down the entire game
420 ============
421 */
Shutdown(void)422 void idGameLocal::Shutdown( void ) {
423 
424 	if ( !common ) {
425 		return;
426 	}
427 
428 	Printf( "----- Game Shutdown -----\n" );
429 
430 	mpGame.Shutdown();
431 
432 	MapShutdown();
433 
434 	aasList.DeleteContents( true );
435 	aasNames.Clear();
436 
437 	idAI::FreeObstacleAvoidanceNodes();
438 
439 	// shutdown the model exporter
440 	idModelExport::Shutdown();
441 
442 	idEvent::Shutdown();
443 
444 	delete[] locationEntities;
445 	locationEntities = NULL;
446 
447 	delete smokeParticles;
448 	smokeParticles = NULL;
449 
450 	idClass::Shutdown();
451 
452 	// clear list with forces
453 	idForce::ClearForceList();
454 
455 	// free the program data
456 	program.FreeData();
457 
458 	// delete the .map file
459 	delete mapFile;
460 	mapFile = NULL;
461 
462 	// free the collision map
463 	collisionModelManager->FreeMap();
464 
465 	ShutdownConsoleCommands();
466 
467 	// free memory allocated by class objects
468 	Clear();
469 
470 	// shut down the animation manager
471 	animationLib.Shutdown();
472 
473 #ifdef GAME_DLL
474 
475 	// remove auto-completion function pointers pointing into this DLL
476 	cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME );
477 
478 	// enable leak test
479 	Mem_EnableLeakTest( "game" );
480 
481 	// shutdown idLib
482 	idLib::ShutDown();
483 
484 #endif
485 }
486 
487 /*
488 ===========
489 idGameLocal::SaveGame
490 
491 save the current player state, level name, and level state
492 the session may have written some data to the file already
493 ============
494 */
SaveGame(idFile * f)495 void idGameLocal::SaveGame( idFile *f ) {
496 	int i;
497 	idEntity *ent;
498 	idEntity *link;
499 
500 	idSaveGame savegame( f );
501 
502 	if (g_flushSave.GetBool( ) == true ) {
503 		// force flushing with each write... for tracking down
504 		// save game bugs.
505 		f->ForceFlush();
506 	}
507 
508 	savegame.WriteBuildNumber( BUILD_NUMBER );
509 
510 	// DG: add some more information to savegame to make future quirks easier
511 	savegame.WriteInt( INTERNAL_SAVEGAME_VERSION ); // to be independent of BUILD_NUMBER
512 	savegame.WriteString( D3_OSTYPE ); // operating system - from CMake
513 	savegame.WriteString( D3_ARCH ); // CPU architecture (e.g. "x86" or "x86_64") - from CMake
514 	savegame.WriteString( ENGINE_VERSION );
515 	savegame.WriteShort( (short)sizeof(void*) ); // tells us if it's from a 32bit (4) or 64bit system (8)
516 	savegame.WriteShort( SDL_BYTEORDER ) ; // SDL_LIL_ENDIAN or SDL_BIG_ENDIAN
517 	// DG end
518 
519 	// go through all entities and threads and add them to the object list
520 	for( i = 0; i < MAX_GENTITIES; i++ ) {
521 		ent = entities[i];
522 
523 		if ( ent ) {
524 			if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) {
525 				continue;
526 			}
527 			for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) {
528 				savegame.AddObject( link );
529 			}
530 		}
531 	}
532 
533 	idList<idThread *> threads;
534 	threads = idThread::GetThreads();
535 
536 	for( i = 0; i < threads.Num(); i++ ) {
537 		savegame.AddObject( threads[i] );
538 	}
539 
540 	// write out complete object list
541 	savegame.WriteObjectList();
542 
543 	program.Save( &savegame );
544 
545 	savegame.WriteInt( g_skill.GetInteger() );
546 
547 	savegame.WriteDict( &serverInfo );
548 
549 	savegame.WriteInt( numClients );
550 	for( i = 0; i < numClients; i++ ) {
551 		savegame.WriteDict( &userInfo[ i ] );
552 		savegame.WriteUsercmd( usercmds[ i ] );
553 		savegame.WriteDict( &persistentPlayerInfo[ i ] );
554 	}
555 
556 	for( i = 0; i < MAX_GENTITIES; i++ ) {
557 		savegame.WriteObject( entities[ i ] );
558 		savegame.WriteInt( spawnIds[ i ] );
559 	}
560 
561 	savegame.WriteInt( firstFreeIndex );
562 	savegame.WriteInt( num_entities );
563 
564 	// enityHash is restored by idEntity::Restore setting the entity name.
565 
566 	savegame.WriteObject( world );
567 
568 	savegame.WriteInt( spawnedEntities.Num() );
569 	for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
570 		savegame.WriteObject( ent );
571 	}
572 
573 	savegame.WriteInt( activeEntities.Num() );
574 	for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
575 		savegame.WriteObject( ent );
576 	}
577 
578 	savegame.WriteInt( numEntitiesToDeactivate );
579 	savegame.WriteBool( sortPushers );
580 	savegame.WriteBool( sortTeamMasters );
581 	savegame.WriteDict( &persistentLevelInfo );
582 
583 	for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) {
584 		savegame.WriteFloat( globalShaderParms[ i ] );
585 	}
586 
587 	savegame.WriteInt( random.GetSeed() );
588 	savegame.WriteObject( frameCommandThread );
589 
590 	// clip
591 	// push
592 	// pvs
593 
594 	testmodel = NULL;
595 	testFx = NULL;
596 
597 	savegame.WriteString( sessionCommand );
598 
599 	// FIXME: save smoke particles
600 
601 	savegame.WriteInt( cinematicSkipTime );
602 	savegame.WriteInt( cinematicStopTime );
603 	savegame.WriteInt( cinematicMaxSkipTime );
604 	savegame.WriteBool( inCinematic );
605 	savegame.WriteBool( skipCinematic );
606 
607 	savegame.WriteBool( isMultiplayer );
608 	savegame.WriteInt( gameType );
609 
610 	savegame.WriteInt( framenum );
611 	savegame.WriteInt( previousTime );
612 	savegame.WriteInt( time );
613 
614 #ifdef _D3XP
615 	savegame.WriteInt( msec );
616 #endif
617 
618 	savegame.WriteInt( vacuumAreaNum );
619 
620 	savegame.WriteInt( entityDefBits );
621 	savegame.WriteBool( isServer );
622 	savegame.WriteBool( isClient );
623 
624 	savegame.WriteInt( localClientNum );
625 
626 	// snapshotEntities is used for multiplayer only
627 
628 	savegame.WriteInt( realClientTime );
629 	savegame.WriteBool( isNewFrame );
630 	savegame.WriteFloat( clientSmoothing );
631 
632 #ifdef _D3XP
633 	portalSkyEnt.Save( &savegame );
634 	savegame.WriteBool( portalSkyActive );
635 
636 	fast.Save( &savegame );
637 	slow.Save( &savegame );
638 
639 	savegame.WriteInt( slowmoState );
640 	savegame.WriteFloat( slowmoMsec );
641 	savegame.WriteBool( quickSlowmoReset );
642 #endif
643 
644 	savegame.WriteBool( mapCycleLoaded );
645 	savegame.WriteInt( spawnCount );
646 
647 	if ( !locationEntities ) {
648 		savegame.WriteInt( 0 );
649 	} else {
650 		savegame.WriteInt( gameRenderWorld->NumAreas() );
651 		for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) {
652 			savegame.WriteObject( locationEntities[ i ] );
653 		}
654 	}
655 
656 	savegame.WriteObject( camera );
657 
658 	savegame.WriteMaterial( globalMaterial );
659 
660 	lastAIAlertEntity.Save( &savegame );
661 	savegame.WriteInt( lastAIAlertTime );
662 
663 	savegame.WriteDict( &spawnArgs );
664 
665 	savegame.WriteInt( playerPVS.i );
666 	savegame.WriteInt( playerPVS.h );
667 	savegame.WriteInt( playerConnectedAreas.i );
668 	savegame.WriteInt( playerConnectedAreas.h );
669 
670 	savegame.WriteVec3( gravity );
671 
672 	// gamestate
673 
674 	savegame.WriteBool( influenceActive );
675 	savegame.WriteInt( nextGibTime );
676 
677 	// spawnSpots
678 	// initialSpots
679 	// currentInitialSpot
680 	// newInfo
681 	// makingBuild
682 	// shakeSounds
683 
684 	// write out pending events
685 	idEvent::Save( &savegame );
686 
687 	savegame.Close();
688 }
689 
690 /*
691 ===========
692 idGameLocal::GetPersistentPlayerInfo
693 ============
694 */
GetPersistentPlayerInfo(int clientNum)695 const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) {
696 	idEntity	*ent;
697 
698 	persistentPlayerInfo[ clientNum ].Clear();
699 	ent = entities[ clientNum ];
700 	if ( ent && ent->IsType( idPlayer::Type ) ) {
701 		static_cast<idPlayer *>(ent)->SavePersistantInfo();
702 	}
703 
704 	return persistentPlayerInfo[ clientNum ];
705 }
706 
707 /*
708 ===========
709 idGameLocal::SetPersistentPlayerInfo
710 ============
711 */
SetPersistentPlayerInfo(int clientNum,const idDict & playerInfo)712 void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) {
713 	persistentPlayerInfo[ clientNum ] = playerInfo;
714 }
715 
716 /*
717 ============
718 idGameLocal::Printf
719 ============
720 */
Printf(const char * fmt,...) const721 void idGameLocal::Printf( const char *fmt, ... ) const {
722 	va_list		argptr;
723 	char		text[MAX_STRING_CHARS];
724 
725 	va_start( argptr, fmt );
726 	idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
727 	va_end( argptr );
728 
729 	common->Printf( "%s", text );
730 }
731 
732 /*
733 ============
734 idGameLocal::DPrintf
735 ============
736 */
DPrintf(const char * fmt,...) const737 void idGameLocal::DPrintf( const char *fmt, ... ) const {
738 	va_list		argptr;
739 	char		text[MAX_STRING_CHARS];
740 
741 	if ( !developer.GetBool() ) {
742 		return;
743 	}
744 
745 	va_start( argptr, fmt );
746 	idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
747 	va_end( argptr );
748 
749 	common->Printf( "%s", text );
750 }
751 
752 /*
753 ============
754 idGameLocal::Warning
755 ============
756 */
Warning(const char * fmt,...) const757 void idGameLocal::Warning( const char *fmt, ... ) const {
758 	va_list		argptr;
759 	char		text[MAX_STRING_CHARS];
760 	idThread *	thread;
761 
762 	va_start( argptr, fmt );
763 	idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
764 	va_end( argptr );
765 
766 	thread = idThread::CurrentThread();
767 	if ( thread ) {
768 		thread->Warning( "%s", text );
769 	} else {
770 		common->Warning( "%s", text );
771 	}
772 }
773 
774 /*
775 ============
776 idGameLocal::DWarning
777 ============
778 */
DWarning(const char * fmt,...) const779 void idGameLocal::DWarning( const char *fmt, ... ) const {
780 	va_list		argptr;
781 	char		text[MAX_STRING_CHARS];
782 	idThread *	thread;
783 
784 	if ( !developer.GetBool() ) {
785 		return;
786 	}
787 
788 	va_start( argptr, fmt );
789 	idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
790 	va_end( argptr );
791 
792 	thread = idThread::CurrentThread();
793 	if ( thread ) {
794 		thread->Warning( "%s", text );
795 	} else {
796 		common->DWarning( "%s", text );
797 	}
798 }
799 
800 /*
801 ============
802 idGameLocal::Error
803 ============
804 */
Error(const char * fmt,...) const805 void idGameLocal::Error( const char *fmt, ... ) const {
806 	va_list		argptr;
807 	char		text[MAX_STRING_CHARS];
808 	idThread *	thread;
809 
810 	va_start( argptr, fmt );
811 	idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
812 	va_end( argptr );
813 
814 	thread = idThread::CurrentThread();
815 	if ( thread ) {
816 		thread->Error( "%s", text );
817 	} else {
818 		common->Error( "%s", text );
819 	}
820 }
821 
822 /*
823 ===============
824 gameError
825 ===============
826 */
gameError(const char * fmt,...)827 void gameError( const char *fmt, ... ) {
828 	va_list		argptr;
829 	char		text[MAX_STRING_CHARS];
830 
831 	va_start( argptr, fmt );
832 	idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
833 	va_end( argptr );
834 
835 	gameLocal.Error( "%s", text );
836 }
837 
838 /*
839 ===========
840 idGameLocal::SetLocalClient
841 ============
842 */
SetLocalClient(int clientNum)843 void idGameLocal::SetLocalClient( int clientNum ) {
844 	localClientNum = clientNum;
845 }
846 
847 /*
848 ===========
849 idGameLocal::SetUserInfo
850 ============
851 */
SetUserInfo(int clientNum,const idDict & userInfo,bool isClient,bool canModify)852 const idDict* idGameLocal::SetUserInfo( int clientNum, const idDict &userInfo, bool isClient, bool canModify ) {
853 	int i;
854 	bool modifiedInfo = false;
855 
856 	this->isClient = isClient;
857 
858 	if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) {
859 		idGameLocal::userInfo[ clientNum ] = userInfo;
860 
861 		// server sanity
862 		if ( canModify ) {
863 
864 			// don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server
865 			if ( idStr::IsNumeric( this->userInfo[ clientNum ].GetString( "ui_name" ) ) ) {
866 				idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) );
867 				modifiedInfo = true;
868 			}
869 
870 			// don't allow dupe nicknames
871 			for ( i = 0; i < numClients; i++ ) {
872 				if ( i == clientNum ) {
873 					continue;
874 				}
875 				if ( entities[ i ] && entities[ i ]->IsType( idPlayer::Type ) ) {
876 					if ( !idStr::Icmp( idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ i ].GetString( "ui_name" ) ) ) {
877 						idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) );
878 						modifiedInfo = true;
879 						i = -1;	// rescan
880 						continue;
881 					}
882 				}
883 			}
884 		}
885 
886 		if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::Type ) ) {
887 			modifiedInfo |= static_cast<idPlayer *>( entities[ clientNum ] )->UserInfoChanged( canModify );
888 		}
889 
890 		if ( !isClient ) {
891 			// now mark this client in game
892 			mpGame.EnterGame( clientNum );
893 		}
894 	}
895 
896 	if ( modifiedInfo ) {
897 		assert( canModify );
898 		newInfo = idGameLocal::userInfo[ clientNum ];
899 		return &newInfo;
900 	}
901 	return NULL;
902 }
903 
904 /*
905 ===========
906 idGameLocal::GetUserInfo
907 ============
908 */
GetUserInfo(int clientNum)909 const idDict* idGameLocal::GetUserInfo( int clientNum ) {
910 	if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::Type ) ) {
911 		return &userInfo[ clientNum ];
912 	}
913 	return NULL;
914 }
915 
916 /*
917 ===========
918 idGameLocal::SetServerInfo
919 ============
920 */
SetServerInfo(const idDict & _serverInfo)921 void idGameLocal::SetServerInfo( const idDict &_serverInfo ) {
922 	idBitMsg	outMsg;
923 	byte		msgBuf[MAX_GAME_MESSAGE_SIZE];
924 
925 	serverInfo = _serverInfo;
926 	UpdateServerInfoFlags();
927 
928 	if ( !isClient ) {
929 		// Let our clients know the server info changed
930 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
931 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO );
932 		outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL );
933 		networkSystem->ServerSendReliableMessage( -1, outMsg );
934 	}
935 }
936 
937 
938 /*
939 ===================
940 idGameLocal::LoadMap
941 
942 Initializes all map variables common to both save games and spawned games.
943 ===================
944 */
LoadMap(const char * mapName,int randseed)945 void idGameLocal::LoadMap( const char *mapName, int randseed ) {
946 	int i;
947 	bool sameMap = (mapFile && idStr::Icmp(mapFileName, mapName) == 0);
948 
949 	// clear the sound system
950 	gameSoundWorld->ClearAllSoundEmitters();
951 
952 #ifdef _D3XP
953 	// clear envirosuit sound fx
954 	gameSoundWorld->SetEnviroSuit( false );
955 	gameSoundWorld->SetSlowmo( false );
956 #endif
957 
958 	InitAsyncNetwork();
959 
960 	if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) {
961 		// load the .map file
962 		if ( mapFile ) {
963 			delete mapFile;
964 		}
965 		mapFile = new idMapFile;
966 		if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) {
967 			delete mapFile;
968 			mapFile = NULL;
969 			Error( "Couldn't load %s", mapName );
970 		}
971 	}
972 	mapFileName = mapFile->GetName();
973 
974 	// load the collision map
975 	collisionModelManager->LoadMap( mapFile );
976 
977 	numClients = 0;
978 
979 	// initialize all entities for this game
980 	memset( entities, 0, sizeof( entities ) );
981 	memset( usercmds, 0, sizeof( usercmds ) );
982 	memset( spawnIds, -1, sizeof( spawnIds ) );
983 	spawnCount = INITIAL_SPAWN_COUNT;
984 
985 	spawnedEntities.Clear();
986 	activeEntities.Clear();
987 	numEntitiesToDeactivate = 0;
988 	sortTeamMasters = false;
989 	sortPushers = false;
990 	lastGUIEnt = NULL;
991 	lastGUI = 0;
992 
993 	globalMaterial = NULL;
994 
995 	memset( globalShaderParms, 0, sizeof( globalShaderParms ) );
996 
997 	// always leave room for the max number of clients,
998 	// even if they aren't all used, so numbers inside that
999 	// range are NEVER anything but clients
1000 	num_entities	= MAX_CLIENTS;
1001 	firstFreeIndex	= MAX_CLIENTS;
1002 
1003 	// reset the random number generator.
1004 	random.SetSeed( isMultiplayer ? randseed : 0 );
1005 
1006 	camera			= NULL;
1007 	world			= NULL;
1008 	testmodel		= NULL;
1009 	testFx			= NULL;
1010 
1011 	lastAIAlertEntity = NULL;
1012 	lastAIAlertTime = 0;
1013 
1014 	previousTime	= 0;
1015 	time			= 0;
1016 	framenum		= 0;
1017 	sessionCommand = "";
1018 	nextGibTime		= 0;
1019 
1020 #ifdef _D3XP
1021 	portalSkyEnt			= NULL;
1022 	portalSkyActive			= false;
1023 
1024 	ResetSlowTimeVars();
1025 #endif
1026 
1027 	vacuumAreaNum = -1;		// if an info_vacuum is spawned, it will set this
1028 
1029 	if ( !editEntities ) {
1030 		editEntities = new idEditEntities;
1031 	}
1032 
1033 	gravity.Set( 0, 0, -g_gravity.GetFloat() );
1034 
1035 	spawnArgs.Clear();
1036 
1037 	skipCinematic = false;
1038 	inCinematic = false;
1039 	cinematicSkipTime = 0;
1040 	cinematicStopTime = 0;
1041 	cinematicMaxSkipTime = 0;
1042 
1043 	clip.Init();
1044 	pvs.Init();
1045 	playerPVS.i = -1;
1046 	playerConnectedAreas.i = -1;
1047 
1048 	// load navigation system for all the different monster sizes
1049 	for( i = 0; i < aasNames.Num(); i++ ) {
1050 		aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() );
1051 	}
1052 
1053 	// clear the smoke particle free list
1054 	smokeParticles->Init();
1055 
1056 	// cache miscellanious media references
1057 	FindEntityDef( "preCacheExtras", false );
1058 
1059 	if ( !sameMap ) {
1060 		mapFile->RemovePrimitiveData();
1061 	}
1062 }
1063 
1064 /*
1065 ===================
1066 idGameLocal::LocalMapRestart
1067 ===================
1068 */
LocalMapRestart()1069 void idGameLocal::LocalMapRestart( ) {
1070 	int i, latchSpawnCount;
1071 
1072 	Printf( "----- Game Map Restart -----\n" );
1073 
1074 	gamestate = GAMESTATE_SHUTDOWN;
1075 
1076 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
1077 		if ( entities[ i ] && entities[ i ]->IsType( idPlayer::Type ) ) {
1078 			static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart();
1079 		}
1080 	}
1081 
1082 	eventQueue.Shutdown();
1083 	savedEventQueue.Shutdown();
1084 
1085 	MapClear( false );
1086 
1087 
1088 
1089 	// clear the smoke particle free list
1090 	smokeParticles->Init();
1091 
1092 	// clear the sound system
1093 	if ( gameSoundWorld ) {
1094 		gameSoundWorld->ClearAllSoundEmitters();
1095 #ifdef _D3XP
1096 		// clear envirosuit sound fx
1097 		gameSoundWorld->SetEnviroSuit( false );
1098 		gameSoundWorld->SetSlowmo( false );
1099 #endif
1100 	}
1101 
1102 	// the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId
1103 	// if we don't do that, network clients are confused and don't show any map entities
1104 	latchSpawnCount = spawnCount;
1105 	spawnCount = INITIAL_SPAWN_COUNT;
1106 
1107 	gamestate = GAMESTATE_STARTUP;
1108 
1109 	program.Restart();
1110 
1111 	InitScriptForMap();
1112 
1113 	MapPopulate();
1114 
1115 	// once the map is populated, set the spawnCount back to where it was so we don't risk any collision
1116 	// (note that if there are no players in the game, we could just leave it at it's current value)
1117 	spawnCount = latchSpawnCount;
1118 
1119 	// setup the client entities again
1120 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
1121 		if ( entities[ i ] && entities[ i ]->IsType( idPlayer::Type ) ) {
1122 			static_cast< idPlayer * >( entities[ i ] )->Restart();
1123 		}
1124 	}
1125 
1126 	gamestate = GAMESTATE_ACTIVE;
1127 }
1128 
1129 /*
1130 ===================
1131 idGameLocal::MapRestart
1132 ===================
1133 */
MapRestart()1134 void idGameLocal::MapRestart( ) {
1135 	idBitMsg	outMsg;
1136 	byte		msgBuf[MAX_GAME_MESSAGE_SIZE];
1137 	idDict		newInfo;
1138 	int			i;
1139 	const idKeyValue *keyval, *keyval2;
1140 
1141 #ifdef _D3XP
1142 	if ( isMultiplayer && isServer ) {
1143 		char buf[ MAX_STRING_CHARS ];
1144 		idStr gametype;
1145 		GetBestGameType( si_map.GetString(), si_gameType.GetString(), buf );
1146 		gametype = buf;
1147 		if ( gametype != si_gameType.GetString() ) {
1148 			cvarSystem->SetCVarString( "si_gameType", gametype );
1149 		}
1150 	}
1151 #endif
1152 
1153 
1154 
1155 	if ( isClient ) {
1156 		LocalMapRestart();
1157 	} else {
1158 		newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
1159 		for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) {
1160 			keyval = newInfo.GetKeyVal( i );
1161 			keyval2 = serverInfo.FindKey( keyval->GetKey() );
1162 			if ( !keyval2 ) {
1163 				break;
1164 			}
1165 			// a select set of si_ changes will cause a full restart of the server
1166 			if ( keyval->GetValue().Cmp( keyval2->GetValue() ) &&
1167 				( !keyval->GetKey().Cmp( "si_pure" ) || !keyval->GetKey().Cmp( "si_map" ) ) ) {
1168 				break;
1169 			}
1170 		}
1171 		cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1172 
1173 		if ( i != newInfo.GetNumKeyVals() ) {
1174 			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1175 		} else {
1176 			outMsg.Init( msgBuf, sizeof( msgBuf ) );
1177 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART );
1178 			outMsg.WriteBits( 1, 1 );
1179 			outMsg.WriteDeltaDict( serverInfo, NULL );
1180 			networkSystem->ServerSendReliableMessage( -1, outMsg );
1181 
1182 			LocalMapRestart();
1183 			mpGame.MapRestart();
1184 		}
1185 	}
1186 
1187 #ifdef CTF
1188 	if ( isMultiplayer ) {
1189 		gameLocal.mpGame.ReloadScoreboard();
1190 		//		gameLocal.mpGame.Reset();	// force reconstruct the GUIs when reloading maps, different gametypes have different GUIs
1191 		//		gameLocal.mpGame.UpdateMainGui();
1192 		//		gameLocal.mpGame.StartMenu();
1193 		//		gameLocal.mpGame.DisableMenu();
1194 		//		gameLocal.mpGame.Precache();
1195 	}
1196 #endif
1197 }
1198 
1199 /*
1200 ===================
1201 idGameLocal::MapRestart_f
1202 ===================
1203 */
MapRestart_f(const idCmdArgs & args)1204 void idGameLocal::MapRestart_f( const idCmdArgs &args ) {
1205 	if ( !gameLocal.isMultiplayer || gameLocal.isClient ) {
1206 		common->Printf( "server is not running - use spawnServer\n" );
1207 		cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" );
1208 		return;
1209 	}
1210 
1211 	gameLocal.MapRestart( );
1212 }
1213 
1214 /*
1215 ===================
1216 idGameLocal::NextMap
1217 ===================
1218 */
NextMap(void)1219 bool idGameLocal::NextMap( void ) {
1220 	const function_t	*func;
1221 	idThread			*thread;
1222 	idDict				newInfo;
1223 	const idKeyValue	*keyval, *keyval2;
1224 	int					i;
1225 
1226 	if ( !g_mapCycle.GetString()[0] ) {
1227 		Printf( common->GetLanguageDict()->GetString( "#str_04294" ) );
1228 		return false;
1229 	}
1230 	if ( fileSystem->ReadFile( g_mapCycle.GetString(), NULL, NULL ) < 0 ) {
1231 		if ( fileSystem->ReadFile( va( "%s.scriptcfg", g_mapCycle.GetString() ), NULL, NULL ) < 0 ) {
1232 			Printf( "map cycle script '%s': not found\n", g_mapCycle.GetString() );
1233 			return false;
1234 		} else {
1235 			g_mapCycle.SetString( va( "%s.scriptcfg", g_mapCycle.GetString() ) );
1236 		}
1237 	}
1238 
1239 	Printf( "map cycle script: '%s'\n", g_mapCycle.GetString() );
1240 	func = program.FindFunction( "mapcycle::cycle" );
1241 	if ( !func ) {
1242 		program.CompileFile( g_mapCycle.GetString() );
1243 		func = program.FindFunction( "mapcycle::cycle" );
1244 	}
1245 	if ( !func ) {
1246 		Printf( "Couldn't find mapcycle::cycle\n" );
1247 		return false;
1248 	}
1249 	thread = new idThread( func );
1250 	thread->Start();
1251 	delete thread;
1252 
1253 	newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
1254 	for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) {
1255 		keyval = newInfo.GetKeyVal( i );
1256 		keyval2 = serverInfo.FindKey( keyval->GetKey() );
1257 		if ( !keyval2 || keyval->GetValue().Cmp( keyval2->GetValue() ) ) {
1258 			break;
1259 		}
1260 	}
1261 	return ( i != newInfo.GetNumKeyVals() );
1262 }
1263 
1264 /*
1265 ===================
1266 idGameLocal::NextMap_f
1267 ===================
1268 */
NextMap_f(const idCmdArgs & args)1269 void idGameLocal::NextMap_f( const idCmdArgs &args ) {
1270 	if ( !gameLocal.isMultiplayer || gameLocal.isClient ) {
1271 		common->Printf( "server is not running\n" );
1272 		return;
1273 	}
1274 
1275 	gameLocal.NextMap( );
1276 	// next map was either voted for or triggered by a server command - always restart
1277 	gameLocal.MapRestart( );
1278 }
1279 
1280 /*
1281 ===================
1282 idGameLocal::MapPopulate
1283 ===================
1284 */
MapPopulate(void)1285 void idGameLocal::MapPopulate( void ) {
1286 
1287 	if ( isMultiplayer ) {
1288 		cvarSystem->SetCVarBool( "r_skipSpecular", false );
1289 	}
1290 	// parse the key/value pairs and spawn entities
1291 	SpawnMapEntities();
1292 
1293 	// mark location entities in all connected areas
1294 	SpreadLocations();
1295 
1296 	// prepare the list of randomized initial spawn spots
1297 	RandomizeInitialSpawns();
1298 
1299 	// spawnCount - 1 is the number of entities spawned into the map, their indexes started at MAX_CLIENTS (included)
1300 	// mapSpawnCount is used as the max index of map entities, it's the first index of non-map entities
1301 	mapSpawnCount = MAX_CLIENTS + spawnCount - 1;
1302 
1303 	// execute pending events before the very first game frame
1304 	// this makes sure the map script main() function is called
1305 	// before the physics are run so entities can bind correctly
1306 	Printf( "==== Processing events ====\n" );
1307 	idEvent::ServiceEvents();
1308 }
1309 
1310 /*
1311 ===================
1312 idGameLocal::InitFromNewMap
1313 ===================
1314 */
InitFromNewMap(const char * mapName,idRenderWorld * renderWorld,idSoundWorld * soundWorld,bool isServer,bool isClient,int randseed)1315 void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ) {
1316 
1317 	this->isServer = isServer;
1318 	this->isClient = isClient;
1319 	this->isMultiplayer = isServer || isClient;
1320 
1321 	if ( mapFileName.Length() ) {
1322 		MapShutdown();
1323 	}
1324 
1325 	Printf( "----- Game Map Init -----\n" );
1326 
1327 	gamestate = GAMESTATE_STARTUP;
1328 
1329 	gameRenderWorld = renderWorld;
1330 	gameSoundWorld = soundWorld;
1331 
1332 	LoadMap( mapName, randseed );
1333 
1334 	InitScriptForMap();
1335 
1336 	MapPopulate();
1337 
1338 	mpGame.Reset();
1339 
1340 	mpGame.Precache();
1341 
1342 	// free up any unused animations
1343 	animationLib.FlushUnusedAnims();
1344 
1345 	gamestate = GAMESTATE_ACTIVE;
1346 }
1347 
1348 /*
1349 =================
1350 idGameLocal::InitFromSaveGame
1351 =================
1352 */
InitFromSaveGame(const char * mapName,idRenderWorld * renderWorld,idSoundWorld * soundWorld,idFile * saveGameFile)1353 bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, idFile *saveGameFile ) {
1354 	int i;
1355 	int num;
1356 	idEntity *ent;
1357 	idDict si;
1358 
1359 	if ( mapFileName.Length() ) {
1360 		MapShutdown();
1361 	}
1362 
1363 	Printf( "----- Game Map Init SaveGame -----\n" );
1364 
1365 	gamestate = GAMESTATE_STARTUP;
1366 
1367 	gameRenderWorld = renderWorld;
1368 	gameSoundWorld = soundWorld;
1369 
1370 	idRestoreGame savegame( saveGameFile );
1371 
1372 	savegame.ReadBuildNumber();
1373 
1374 	// DG: I enhanced the information in savegames a bit for dhewm3 1.5.1
1375 	//     for which I bumped th BUILD_NUMBER to 1305
1376 	if( savegame.GetBuildNumber() >= 1305 )
1377 	{
1378 		savegame.ReadInternalSavegameVersion();
1379 		if( savegame.GetInternalSavegameVersion() > INTERNAL_SAVEGAME_VERSION ) {
1380 			Warning( "Savegame from newer dhewm3 version, don't know how to load! (its version is %d, only up to %d supported)",
1381 			         savegame.GetInternalSavegameVersion(), INTERNAL_SAVEGAME_VERSION );
1382 			return false;
1383 		}
1384 		idStr osType;
1385 		idStr cpuArch;
1386 		idStr engineVersion;
1387 		short ptrSize = 0;
1388 		short byteorder = 0;
1389 		savegame.ReadString( osType ); // operating system the savegame was crated on (written from D3_OSTYPE)
1390 		savegame.ReadString( cpuArch ); // written from D3_ARCH (which is set in CMake), like "x86" or "x86_64"
1391 		savegame.ReadString( engineVersion ); // written from ENGINE_VERSION
1392 		savegame.ReadShort( ptrSize ); // sizeof(void*) of system that created the savegame, 4 on 32bit systems, 8 on 64bit systems
1393 		savegame.ReadShort( byteorder ); // SDL_LIL_ENDIAN or SDL_BIG_ENDIAN
1394 
1395 		Printf( "Savegame was created by %s on %s %s. BuildNumber was %d, savegameversion %d\n",
1396 		        engineVersion.c_str(), osType.c_str(), cpuArch.c_str(), savegame.GetBuildNumber(),
1397 		        savegame.GetInternalSavegameVersion() );
1398 
1399 		// right now I have no further use for this information, but in the future
1400 		// it can be used for quirks for (then-) old savegames
1401 	}
1402 	// DG end
1403 
1404 	// Create the list of all objects in the game
1405 	savegame.CreateObjects();
1406 
1407 	// Load the idProgram, also checking to make sure scripting hasn't changed since the savegame
1408 	if ( program.Restore( &savegame ) == false ) {
1409 
1410 		// Abort the load process, and let the session know so that it can restart the level
1411 		// with the player persistent data.
1412 		savegame.DeleteObjects();
1413 		program.Restart();
1414 
1415 		return false;
1416 	}
1417 
1418 	// load the map needed for this savegame
1419 	LoadMap( mapName, 0 );
1420 
1421 	savegame.ReadInt( i );
1422 	g_skill.SetInteger( i );
1423 
1424 	// precache the player
1425 	FindEntityDef( "player_doommarine", false );
1426 
1427 	// precache any media specified in the map
1428 	for ( i = 0; i < mapFile->GetNumEntities(); i++ ) {
1429 		idMapEntity *mapEnt = mapFile->GetEntity( i );
1430 
1431 		if ( !InhibitEntitySpawn( mapEnt->epairs ) ) {
1432 			CacheDictionaryMedia( &mapEnt->epairs );
1433 			const char *classname;
1434 			if ( mapEnt->epairs.GetString( "classname", "", &classname ) ) {
1435 				FindEntityDef( classname, false );
1436 			}
1437 		}
1438 	}
1439 
1440 	savegame.ReadDict( &si );
1441 	SetServerInfo( si );
1442 
1443 	savegame.ReadInt( numClients );
1444 	for( i = 0; i < numClients; i++ ) {
1445 		savegame.ReadDict( &userInfo[ i ] );
1446 		savegame.ReadUsercmd( usercmds[ i ] );
1447 		savegame.ReadDict( &persistentPlayerInfo[ i ] );
1448 	}
1449 
1450 	for( i = 0; i < MAX_GENTITIES; i++ ) {
1451 		savegame.ReadObject( reinterpret_cast<idClass *&>( entities[ i ] ) );
1452 		savegame.ReadInt( spawnIds[ i ] );
1453 
1454 		// restore the entityNumber
1455 		if ( entities[ i ] != NULL ) {
1456 			entities[ i ]->entityNumber = i;
1457 		}
1458 	}
1459 
1460 	savegame.ReadInt( firstFreeIndex );
1461 	savegame.ReadInt( num_entities );
1462 
1463 	// enityHash is restored by idEntity::Restore setting the entity name.
1464 
1465 	savegame.ReadObject( reinterpret_cast<idClass *&>( world ) );
1466 
1467 	savegame.ReadInt( num );
1468 	for( i = 0; i < num; i++ ) {
1469 		savegame.ReadObject( reinterpret_cast<idClass *&>( ent ) );
1470 		assert( ent );
1471 		if ( ent ) {
1472 			ent->spawnNode.AddToEnd( spawnedEntities );
1473 		}
1474 	}
1475 
1476 	savegame.ReadInt( num );
1477 	for( i = 0; i < num; i++ ) {
1478 		savegame.ReadObject( reinterpret_cast<idClass *&>( ent ) );
1479 		assert( ent );
1480 		if ( ent ) {
1481 			ent->activeNode.AddToEnd( activeEntities );
1482 		}
1483 	}
1484 
1485 	savegame.ReadInt( numEntitiesToDeactivate );
1486 	savegame.ReadBool( sortPushers );
1487 	savegame.ReadBool( sortTeamMasters );
1488 	savegame.ReadDict( &persistentLevelInfo );
1489 
1490 	for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) {
1491 		savegame.ReadFloat( globalShaderParms[ i ] );
1492 	}
1493 
1494 	savegame.ReadInt( i );
1495 	random.SetSeed( i );
1496 
1497 	savegame.ReadObject( reinterpret_cast<idClass *&>( frameCommandThread ) );
1498 
1499 	// clip
1500 	// push
1501 	// pvs
1502 
1503 	// testmodel = "<NULL>"
1504 	// testFx = "<NULL>"
1505 
1506 	savegame.ReadString( sessionCommand );
1507 
1508 	// FIXME: save smoke particles
1509 
1510 	savegame.ReadInt( cinematicSkipTime );
1511 	savegame.ReadInt( cinematicStopTime );
1512 	savegame.ReadInt( cinematicMaxSkipTime );
1513 	savegame.ReadBool( inCinematic );
1514 	savegame.ReadBool( skipCinematic );
1515 
1516 	savegame.ReadBool( isMultiplayer );
1517 	savegame.ReadInt( (int &)gameType );
1518 
1519 	savegame.ReadInt( framenum );
1520 	savegame.ReadInt( previousTime );
1521 	savegame.ReadInt( time );
1522 
1523 #ifdef _D3XP
1524 	savegame.ReadInt( msec );
1525 #endif
1526 
1527 	savegame.ReadInt( vacuumAreaNum );
1528 
1529 	savegame.ReadInt( entityDefBits );
1530 	savegame.ReadBool( isServer );
1531 	savegame.ReadBool( isClient );
1532 
1533 	savegame.ReadInt( localClientNum );
1534 
1535 	// snapshotEntities is used for multiplayer only
1536 
1537 	savegame.ReadInt( realClientTime );
1538 	savegame.ReadBool( isNewFrame );
1539 	savegame.ReadFloat( clientSmoothing );
1540 
1541 #ifdef _D3XP
1542 	portalSkyEnt.Restore( &savegame );
1543 	savegame.ReadBool( portalSkyActive );
1544 
1545 	fast.Restore( &savegame );
1546 	slow.Restore( &savegame );
1547 
1548 	int blah;
1549 	savegame.ReadInt( blah );
1550 	slowmoState = (slowmoState_t)blah;
1551 
1552 	savegame.ReadFloat( slowmoMsec );
1553 	savegame.ReadBool( quickSlowmoReset );
1554 
1555 	if ( slowmoState == SLOWMO_STATE_OFF ) {
1556 		if ( gameSoundWorld ) {
1557 			gameSoundWorld->SetSlowmo( false );
1558 		}
1559 	}
1560 	else {
1561 		if ( gameSoundWorld ) {
1562 			gameSoundWorld->SetSlowmo( true );
1563 		}
1564 	}
1565 	if ( gameSoundWorld ) {
1566 		gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC );
1567 	}
1568 #endif
1569 
1570 	savegame.ReadBool( mapCycleLoaded );
1571 	savegame.ReadInt( spawnCount );
1572 
1573 	savegame.ReadInt( num );
1574 	if ( num ) {
1575 		if ( num != gameRenderWorld->NumAreas() ) {
1576 			savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." );
1577 		}
1578 
1579 		locationEntities = new idLocationEntity *[ num ];
1580 		for( i = 0; i < num; i++ ) {
1581 			savegame.ReadObject( reinterpret_cast<idClass *&>( locationEntities[ i ] ) );
1582 		}
1583 	}
1584 
1585 	savegame.ReadObject( reinterpret_cast<idClass *&>( camera ) );
1586 
1587 	savegame.ReadMaterial( globalMaterial );
1588 
1589 	lastAIAlertEntity.Restore( &savegame );
1590 	savegame.ReadInt( lastAIAlertTime );
1591 
1592 	savegame.ReadDict( &spawnArgs );
1593 
1594 	savegame.ReadInt( playerPVS.i );
1595 	savegame.ReadInt( (int &)playerPVS.h );
1596 	savegame.ReadInt( playerConnectedAreas.i );
1597 	savegame.ReadInt( (int &)playerConnectedAreas.h );
1598 
1599 	savegame.ReadVec3( gravity );
1600 
1601 	// gamestate is restored after restoring everything else
1602 
1603 	savegame.ReadBool( influenceActive );
1604 	savegame.ReadInt( nextGibTime );
1605 
1606 	// spawnSpots
1607 	// initialSpots
1608 	// currentInitialSpot
1609 	// newInfo
1610 	// makingBuild
1611 	// shakeSounds
1612 
1613 	// Read out pending events
1614 	idEvent::Restore( &savegame );
1615 
1616 	savegame.RestoreObjects();
1617 
1618 	mpGame.Reset();
1619 
1620 	mpGame.Precache();
1621 
1622 	// free up any unused animations
1623 	animationLib.FlushUnusedAnims();
1624 
1625 	gamestate = GAMESTATE_ACTIVE;
1626 
1627 	return true;
1628 }
1629 
1630 /*
1631 ===========
1632 idGameLocal::MapClear
1633 ===========
1634 */
MapClear(bool clearClients)1635 void idGameLocal::MapClear( bool clearClients ) {
1636 	int i;
1637 
1638 	for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) {
1639 		delete entities[ i ];
1640 		// ~idEntity is in charge of setting the pointer to NULL
1641 		// it will also clear pending events for this entity
1642 		assert( !entities[ i ] );
1643 		spawnIds[ i ] = -1;
1644 	}
1645 
1646 	entityHash.Clear( 1024, MAX_GENTITIES );
1647 
1648 	if ( !clearClients ) {
1649 		// add back the hashes of the clients
1650 		for ( i = 0; i < MAX_CLIENTS; i++ ) {
1651 			if ( !entities[ i ] ) {
1652 				continue;
1653 			}
1654 			entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i );
1655 		}
1656 	}
1657 
1658 	delete frameCommandThread;
1659 	frameCommandThread = NULL;
1660 
1661 	if ( editEntities ) {
1662 		delete editEntities;
1663 		editEntities = NULL;
1664 	}
1665 
1666 	delete[] locationEntities;
1667 	locationEntities = NULL;
1668 }
1669 
1670 /*
1671 ===========
1672 idGameLocal::MapShutdown
1673 ============
1674 */
MapShutdown(void)1675 void idGameLocal::MapShutdown( void ) {
1676 	Printf( "----- Game Map Shutdown -----\n" );
1677 
1678 	gamestate = GAMESTATE_SHUTDOWN;
1679 
1680 	if ( gameRenderWorld ) {
1681 		// clear any debug lines, text, and polygons
1682 		gameRenderWorld->DebugClearLines( 0 );
1683 		gameRenderWorld->DebugClearPolygons( 0 );
1684 	}
1685 
1686 	// clear out camera if we're in a cinematic
1687 	if ( inCinematic ) {
1688 		camera = NULL;
1689 		inCinematic = false;
1690 	}
1691 
1692 	MapClear( true );
1693 
1694 	// reset the script to the state it was before the map was started
1695 	program.Restart();
1696 
1697 	if ( smokeParticles ) {
1698 		smokeParticles->Shutdown();
1699 	}
1700 
1701 	pvs.Shutdown();
1702 
1703 	clip.Shutdown();
1704 	idClipModel::ClearTraceModelCache();
1705 
1706 	ShutdownAsyncNetwork();
1707 
1708 	mapFileName.Clear();
1709 
1710 	gameRenderWorld = NULL;
1711 	gameSoundWorld = NULL;
1712 
1713 	gamestate = GAMESTATE_NOMAP;
1714 }
1715 
1716 /*
1717 ===================
1718 idGameLocal::DumpOggSounds
1719 ===================
1720 */
DumpOggSounds(void)1721 void idGameLocal::DumpOggSounds( void ) {
1722 	int i, j, k, size, totalSize;
1723 	idFile *file;
1724 	idStrList oggSounds, weaponSounds;
1725 	const idSoundShader *soundShader;
1726 	const soundShaderParms_t *parms;
1727 	idStr soundName;
1728 
1729 	for ( i = 0; i < declManager->GetNumDecls( DECL_SOUND ); i++ ) {
1730 		soundShader = static_cast<const idSoundShader *>(declManager->DeclByIndex( DECL_SOUND, i, false ));
1731 		parms = soundShader->GetParms();
1732 
1733 		if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) {
1734 
1735 			const_cast<idSoundShader *>(soundShader)->EnsureNotPurged();
1736 
1737 			for ( j = 0; j < soundShader->GetNumSounds(); j++ ) {
1738 				soundName = soundShader->GetSound( j );
1739 				soundName.BackSlashesToSlashes();
1740 
1741 #ifdef _D3XP
1742 				// D3XP :: don't add sounds that are in Doom 3's pak files
1743 				if ( fileSystem->FileIsInPAK( soundName ) ) {
1744 					continue;
1745 				} else {
1746 					// Also check for a pre-ogg'd version in the pak file
1747 					idStr testName = soundName;
1748 
1749 					testName.SetFileExtension( ".ogg" );
1750 					if ( fileSystem->FileIsInPAK( testName ) ) {
1751 						continue;
1752 					}
1753 				}
1754 #endif
1755 				// don't OGG sounds that cause a shake because that would
1756 				// cause continuous seeking on the OGG file which is expensive
1757 				if ( parms->shakes != 0.0f ) {
1758 					shakeSounds.AddUnique( soundName );
1759 					continue;
1760 				}
1761 
1762 				// if not voice over or combat chatter
1763 				if (	soundName.Find( "/vo/", false ) == -1 &&
1764 						soundName.Find( "/combat_chatter/", false ) == -1 &&
1765 						soundName.Find( "/bfgcarnage/", false ) == -1 &&
1766 						soundName.Find( "/enpro/", false ) == - 1 &&
1767 						soundName.Find( "/soulcube/energize_01.wav", false ) == -1 ) {
1768 					// don't OGG weapon sounds
1769 					if (	soundName.Find( "weapon", false ) != -1 ||
1770 							soundName.Find( "gun", false ) != -1 ||
1771 							soundName.Find( "bullet", false ) != -1 ||
1772 							soundName.Find( "bfg", false ) != -1 ||
1773 							soundName.Find( "plasma", false ) != -1 ) {
1774 						weaponSounds.AddUnique( soundName );
1775 						continue;
1776 					}
1777 				}
1778 
1779 				for ( k = 0; k < shakeSounds.Num(); k++ ) {
1780 					if ( shakeSounds[k].IcmpPath( soundName ) == 0 ) {
1781 						break;
1782 					}
1783 				}
1784 				if ( k < shakeSounds.Num() ) {
1785 					continue;
1786 				}
1787 
1788 				oggSounds.AddUnique( soundName );
1789 			}
1790 		}
1791 	}
1792 
1793 	file = fileSystem->OpenFileWrite( "makeogg.bat", "fs_savepath" );
1794 	if ( file == NULL ) {
1795 		common->Warning( "Couldn't open makeogg.bat" );
1796 		return;
1797 	}
1798 
1799 	// list all the shake sounds
1800 	totalSize = 0;
1801 	for ( i = 0; i < shakeSounds.Num(); i++ ) {
1802 		size = fileSystem->ReadFile( shakeSounds[i], NULL, NULL );
1803 		totalSize += size;
1804 		shakeSounds[i].Replace( "/", "\\" );
1805 		file->Printf( "echo \"%s\" (%d kB)\n", shakeSounds[i].c_str(), size >> 10 );
1806 	}
1807 	file->Printf( "echo %d kB in shake sounds\n\n\n", totalSize >> 10 );
1808 
1809 	// list all the weapon sounds
1810 	totalSize = 0;
1811 	for ( i = 0; i < weaponSounds.Num(); i++ ) {
1812 		size = fileSystem->ReadFile( weaponSounds[i], NULL, NULL );
1813 		totalSize += size;
1814 		weaponSounds[i].Replace( "/", "\\" );
1815 		file->Printf( "echo \"%s\" (%d kB)\n", weaponSounds[i].c_str(), size >> 10 );
1816 	}
1817 	file->Printf( "echo %d kB in weapon sounds\n\n\n", totalSize >> 10 );
1818 
1819 	// list commands to convert all other sounds to ogg
1820 	totalSize = 0;
1821 	for ( i = 0; i < oggSounds.Num(); i++ ) {
1822 		size = fileSystem->ReadFile( oggSounds[i], NULL, NULL );
1823 		totalSize += size;
1824 		oggSounds[i].Replace( "/", "\\" );
1825 		file->Printf( "z:\\d3xp\\ogg\\oggenc -q 0 \"%s\\d3xp\\%s\"\n", cvarSystem->GetCVarString( "fs_basepath" ), oggSounds[i].c_str() );
1826 		file->Printf( "del \"%s\\d3xp\\%s\"\n", cvarSystem->GetCVarString( "fs_basepath" ), oggSounds[i].c_str() );
1827 	}
1828 	file->Printf( "\n\necho %d kB in OGG sounds\n\n\n", totalSize >> 10 );
1829 
1830 	fileSystem->CloseFile( file );
1831 
1832 	shakeSounds.Clear();
1833 }
1834 
1835 /*
1836 ===================
1837 idGameLocal::GetShakeSounds
1838 ===================
1839 */
GetShakeSounds(const idDict * dict)1840 void idGameLocal::GetShakeSounds( const idDict *dict ) {
1841 	const idSoundShader *soundShader;
1842 	const char *soundShaderName;
1843 	idStr soundName;
1844 
1845 	if ( dict->GetString( "s_shader", "", &soundShaderName )
1846 		 && dict->GetFloat( "s_shakes" ) != 0.0f )
1847 	{
1848 		soundShader = declManager->FindSound( soundShaderName );
1849 
1850 		for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) {
1851 			soundName = soundShader->GetSound( i );
1852 			soundName.BackSlashesToSlashes();
1853 
1854 			shakeSounds.AddUnique( soundName );
1855 		}
1856 	}
1857 }
1858 
1859 /*
1860 ===================
1861 idGameLocal::CacheDictionaryMedia
1862 
1863 This is called after parsing an EntityDef and for each entity spawnArgs before
1864 merging the entitydef.  It could be done post-merge, but that would
1865 avoid the fast pre-cache check associated with each entityDef
1866 ===================
1867 */
CacheDictionaryMedia(const idDict * dict)1868 void idGameLocal::CacheDictionaryMedia( const idDict *dict ) {
1869 	const idKeyValue *kv;
1870 
1871 	if ( dict == NULL ) {
1872 		if ( cvarSystem->GetCVarBool( "com_makingBuild") ) {
1873 			DumpOggSounds();
1874 		}
1875 		return;
1876 	}
1877 
1878 	if ( cvarSystem->GetCVarBool( "com_makingBuild" ) ) {
1879 		GetShakeSounds( dict );
1880 	}
1881 
1882 	kv = dict->MatchPrefix( "model" );
1883 	while( kv ) {
1884 		if ( kv->GetValue().Length() ) {
1885 			declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() );
1886 			// precache model/animations
1887 			if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) {
1888 				// precache the render model
1889 				renderModelManager->FindModel( kv->GetValue() );
1890 				// precache .cm files only
1891 				collisionModelManager->LoadModel( kv->GetValue(), true );
1892 			}
1893 		}
1894 		kv = dict->MatchPrefix( "model", kv );
1895 	}
1896 
1897 	kv = dict->FindKey( "s_shader" );
1898 	if ( kv && kv->GetValue().Length() ) {
1899 		declManager->FindType( DECL_SOUND, kv->GetValue() );
1900 	}
1901 
1902 	kv = dict->MatchPrefix( "snd", NULL );
1903 	while( kv ) {
1904 		if ( kv->GetValue().Length() ) {
1905 			declManager->FindType( DECL_SOUND, kv->GetValue() );
1906 		}
1907 		kv = dict->MatchPrefix( "snd", kv );
1908 	}
1909 
1910 
1911 	kv = dict->MatchPrefix( "gui", NULL );
1912 	while( kv ) {
1913 		if ( kv->GetValue().Length() ) {
1914 			if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" )
1915 				|| !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 )
1916 				|| !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) {
1917 				// unfortunate flag names, they aren't actually a gui
1918 			} else {
1919 				declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() );
1920 				idUserInterface *gui = uiManager->Alloc();
1921 				if ( gui ) {
1922 					gui->InitFromFile( kv->GetValue() );
1923 					uiManager->DeAlloc( gui );
1924 				}
1925 			}
1926 		}
1927 		kv = dict->MatchPrefix( "gui", kv );
1928 	}
1929 
1930 	kv = dict->FindKey( "texture" );
1931 	if ( kv && kv->GetValue().Length() ) {
1932 		declManager->FindType( DECL_MATERIAL, kv->GetValue() );
1933 	}
1934 
1935 	kv = dict->MatchPrefix( "mtr", NULL );
1936 	while( kv ) {
1937 		if ( kv->GetValue().Length() ) {
1938 			declManager->FindType( DECL_MATERIAL, kv->GetValue() );
1939 		}
1940 		kv = dict->MatchPrefix( "mtr", kv );
1941 	}
1942 
1943 	// handles hud icons
1944 	kv = dict->MatchPrefix( "inv_icon", NULL );
1945 	while ( kv ) {
1946 		if ( kv->GetValue().Length() ) {
1947 			declManager->FindType( DECL_MATERIAL, kv->GetValue() );
1948 		}
1949 		kv = dict->MatchPrefix( "inv_icon", kv );
1950 	}
1951 
1952 	// handles teleport fx.. this is not ideal but the actual decision on which fx to use
1953 	// is handled by script code based on the teleport number
1954 	kv = dict->MatchPrefix( "teleport", NULL );
1955 	if ( kv && kv->GetValue().Length() ) {
1956 		int teleportType = atoi( kv->GetValue() );
1957 		const char *p = ( teleportType ) ? va( "fx/teleporter%i.fx", teleportType ) : "fx/teleporter.fx";
1958 		declManager->FindType( DECL_FX, p );
1959 	}
1960 
1961 	kv = dict->MatchPrefix( "fx", NULL );
1962 	while( kv ) {
1963 		if ( kv->GetValue().Length() ) {
1964 			declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() );
1965 			declManager->FindType( DECL_FX, kv->GetValue() );
1966 		}
1967 		kv = dict->MatchPrefix( "fx", kv );
1968 	}
1969 
1970 	kv = dict->MatchPrefix( "smoke", NULL );
1971 	while( kv ) {
1972 		if ( kv->GetValue().Length() ) {
1973 			idStr prtName = kv->GetValue();
1974 			int dash = prtName.Find('-');
1975 			if ( dash > 0 ) {
1976 				prtName = prtName.Left( dash );
1977 			}
1978 			declManager->FindType( DECL_PARTICLE, prtName );
1979 		}
1980 		kv = dict->MatchPrefix( "smoke", kv );
1981 	}
1982 
1983 	kv = dict->MatchPrefix( "skin", NULL );
1984 	while( kv ) {
1985 		if ( kv->GetValue().Length() ) {
1986 			declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() );
1987 			declManager->FindType( DECL_SKIN, kv->GetValue() );
1988 		}
1989 		kv = dict->MatchPrefix( "skin", kv );
1990 	}
1991 
1992 	kv = dict->MatchPrefix( "def", NULL );
1993 	while( kv ) {
1994 		if ( kv->GetValue().Length() ) {
1995 			FindEntityDef( kv->GetValue().c_str(), false );
1996 		}
1997 		kv = dict->MatchPrefix( "def", kv );
1998 	}
1999 
2000 	kv = dict->MatchPrefix( "pda_name", NULL );
2001 	while( kv ) {
2002 		if ( kv->GetValue().Length() ) {
2003 			declManager->FindType( DECL_PDA, kv->GetValue().c_str(), false );
2004 		}
2005 		kv = dict->MatchPrefix( "pda_name", kv );
2006 	}
2007 
2008 	kv = dict->MatchPrefix( "video", NULL );
2009 	while( kv ) {
2010 		if ( kv->GetValue().Length() ) {
2011 			declManager->FindType( DECL_VIDEO, kv->GetValue().c_str(), false );
2012 		}
2013 		kv = dict->MatchPrefix( "video", kv );
2014 	}
2015 
2016 	kv = dict->MatchPrefix( "audio", NULL );
2017 	while( kv ) {
2018 		if ( kv->GetValue().Length() ) {
2019 			declManager->FindType( DECL_AUDIO, kv->GetValue().c_str(), false );
2020 		}
2021 		kv = dict->MatchPrefix( "audio", kv );
2022 	}
2023 }
2024 
2025 /*
2026 ===========
2027 idGameLocal::InitScriptForMap
2028 ============
2029 */
InitScriptForMap(void)2030 void idGameLocal::InitScriptForMap( void ) {
2031 	// create a thread to run frame commands on
2032 	frameCommandThread = new idThread();
2033 	frameCommandThread->ManualDelete();
2034 	frameCommandThread->SetThreadName( "frameCommands" );
2035 
2036 	// run the main game script function (not the level specific main)
2037 	const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC );
2038 	if ( func != NULL ) {
2039 		idThread *thread = new idThread( func );
2040 		if ( thread->Start() ) {
2041 			// thread has finished executing, so delete it
2042 			delete thread;
2043 		}
2044 	}
2045 }
2046 
2047 /*
2048 ===========
2049 idGameLocal::SpawnPlayer
2050 ============
2051 */
SpawnPlayer(int clientNum)2052 void idGameLocal::SpawnPlayer( int clientNum ) {
2053 	idEntity	*ent;
2054 	idDict		args;
2055 
2056 	// they can connect
2057 	Printf( "SpawnPlayer: %i\n", clientNum );
2058 
2059 	args.SetInt( "spawn_entnum", clientNum );
2060 	args.Set( "name", va( "player%d", clientNum + 1 ) );
2061 #ifdef CTF
2062 	if ( isMultiplayer && gameType != GAME_CTF )
2063 		args.Set( "classname", "player_doommarine_mp" );
2064 	else if ( isMultiplayer && gameType == GAME_CTF )
2065 		args.Set( "classname", "player_doommarine_ctf" );
2066 	else
2067 		args.Set( "classname", "player_doommarine" );
2068 #else
2069 	args.Set( "classname", isMultiplayer ? "player_doommarine_mp" : "player_doommarine" );
2070 #endif
2071 	if ( !SpawnEntityDef( args, &ent ) || !entities[ clientNum ] ) {
2072 		Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) );
2073 	}
2074 
2075 	// make sure it's a compatible class
2076 	if ( !ent->IsType( idPlayer::Type ) ) {
2077 		Error( "'%s' spawned the player as a '%s'.  Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() );
2078 	}
2079 
2080 	if ( clientNum >= numClients ) {
2081 		numClients = clientNum + 1;
2082 	}
2083 
2084 	mpGame.SpawnPlayer( clientNum );
2085 }
2086 
2087 /*
2088 ================
2089 idGameLocal::GetClientByNum
2090 ================
2091 */
GetClientByNum(int current) const2092 idPlayer *idGameLocal::GetClientByNum( int current ) const {
2093 	if ( current < 0 || current >= numClients ) {
2094 		current = 0;
2095 	}
2096 	if ( entities[current] ) {
2097 		return static_cast<idPlayer *>( entities[ current ] );
2098 	}
2099 	return NULL;
2100 }
2101 
2102 /*
2103 ================
2104 idGameLocal::GetClientByName
2105 ================
2106 */
GetClientByName(const char * name) const2107 idPlayer *idGameLocal::GetClientByName( const char *name ) const {
2108 	int i;
2109 	idEntity *ent;
2110 	for ( i = 0 ; i < numClients ; i++ ) {
2111 		ent = entities[ i ];
2112 		if ( ent && ent->IsType( idPlayer::Type ) ) {
2113 			if ( idStr::IcmpNoColor( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) {
2114 				return static_cast<idPlayer *>( ent );
2115 			}
2116 		}
2117 	}
2118 	return NULL;
2119 }
2120 
2121 /*
2122 ================
2123 idGameLocal::GetClientByCmdArgs
2124 ================
2125 */
GetClientByCmdArgs(const idCmdArgs & args) const2126 idPlayer *idGameLocal::GetClientByCmdArgs( const idCmdArgs &args ) const {
2127 	idPlayer *player;
2128 	idStr client = args.Argv( 1 );
2129 	if ( !client.Length() ) {
2130 		return NULL;
2131 	}
2132 	// we don't allow numeric ui_name so this can't go wrong
2133 	if ( client.IsNumeric() ) {
2134 		player = GetClientByNum( atoi( client.c_str() ) );
2135 	} else {
2136 		player = GetClientByName( client.c_str() );
2137 	}
2138 	if ( !player ) {
2139 		common->Printf( "Player '%s' not found\n", client.c_str() );
2140 	}
2141 	return player;
2142 }
2143 
2144 /*
2145 ================
2146 idGameLocal::GetNextClientNum
2147 ================
2148 */
GetNextClientNum(int _current) const2149 int idGameLocal::GetNextClientNum( int _current ) const {
2150 	int i, current;
2151 
2152 	current = 0;
2153 	for ( i = 0; i < numClients; i++) {
2154 		current = ( _current + i + 1 ) % numClients;
2155 		if ( entities[ current ] && entities[ current ]->IsType( idPlayer::Type ) ) {
2156 			return current;
2157 		}
2158 	}
2159 
2160 	return current;
2161 }
2162 
2163 /*
2164 ================
2165 idGameLocal::GetLocalPlayer
2166 
2167 Nothing in the game tic should EVER make a decision based on what the
2168 local client number is, it shouldn't even be aware that there is a
2169 draw phase even happening.  This just returns client 0, which will
2170 be correct for single player.
2171 ================
2172 */
GetLocalPlayer() const2173 idPlayer *idGameLocal::GetLocalPlayer() const {
2174 	if ( localClientNum < 0 ) {
2175 		return NULL;
2176 	}
2177 
2178 	if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::Type ) ) {
2179 		// not fully in game yet
2180 		return NULL;
2181 	}
2182 	return static_cast<idPlayer *>( entities[ localClientNum ] );
2183 }
2184 
2185 /*
2186 ================
2187 idGameLocal::SetupClientPVS
2188 ================
2189 */
GetClientPVS(idPlayer * player,pvsType_t type)2190 pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) {
2191 	if ( player->GetPrivateCameraView() ) {
2192 		return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() );
2193 	} else if ( camera ) {
2194 		return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() );
2195 	} else {
2196 		return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() );
2197 	}
2198 }
2199 
2200 /*
2201 ================
2202 idGameLocal::SetupPlayerPVS
2203 ================
2204 */
SetupPlayerPVS(void)2205 void idGameLocal::SetupPlayerPVS( void ) {
2206 	int			i;
2207 	idEntity *	ent;
2208 	idPlayer *	player;
2209 	pvsHandle_t	otherPVS, newPVS;
2210 
2211 	playerPVS.i = -1;
2212 	for ( i = 0; i < numClients; i++ ) {
2213 		ent = entities[i];
2214 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
2215 			continue;
2216 		}
2217 
2218 		player = static_cast<idPlayer *>(ent);
2219 
2220 		if ( playerPVS.i == -1 ) {
2221 			playerPVS = GetClientPVS( player, PVS_NORMAL );
2222 		} else {
2223 			otherPVS = GetClientPVS( player, PVS_NORMAL );
2224 			newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS );
2225 			pvs.FreeCurrentPVS( playerPVS );
2226 			pvs.FreeCurrentPVS( otherPVS );
2227 			playerPVS = newPVS;
2228 		}
2229 
2230 		if ( playerConnectedAreas.i == -1 ) {
2231 			playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS );
2232 		} else {
2233 			otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS );
2234 			newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS );
2235 			pvs.FreeCurrentPVS( playerConnectedAreas );
2236 			pvs.FreeCurrentPVS( otherPVS );
2237 			playerConnectedAreas = newPVS;
2238 		}
2239 
2240 #ifdef _D3XP
2241 		// if portalSky is preset, then merge into pvs so we get rotating brushes, etc
2242 		if ( portalSkyEnt.GetEntity() ) {
2243 			idEntity *skyEnt = portalSkyEnt.GetEntity();
2244 
2245 			otherPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() );
2246 			newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS );
2247 			pvs.FreeCurrentPVS( playerPVS );
2248 			pvs.FreeCurrentPVS( otherPVS );
2249 			playerPVS = newPVS;
2250 
2251 			otherPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() );
2252 			newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS );
2253 			pvs.FreeCurrentPVS( playerConnectedAreas );
2254 			pvs.FreeCurrentPVS( otherPVS );
2255 			playerConnectedAreas = newPVS;
2256 		}
2257 #endif
2258 	}
2259 }
2260 
2261 /*
2262 ================
2263 idGameLocal::FreePlayerPVS
2264 ================
2265 */
FreePlayerPVS(void)2266 void idGameLocal::FreePlayerPVS( void ) {
2267 	if ( playerPVS.i != -1 ) {
2268 		pvs.FreeCurrentPVS( playerPVS );
2269 		playerPVS.i = -1;
2270 	}
2271 	if ( playerConnectedAreas.i != -1 ) {
2272 		pvs.FreeCurrentPVS( playerConnectedAreas );
2273 		playerConnectedAreas.i = -1;
2274 	}
2275 }
2276 
2277 /*
2278 ================
2279 idGameLocal::InPlayerPVS
2280 
2281   should only be called during entity thinking and event handling
2282 ================
2283 */
InPlayerPVS(idEntity * ent) const2284 bool idGameLocal::InPlayerPVS( idEntity *ent ) const {
2285 	if ( playerPVS.i == -1 ) {
2286 		return false;
2287 	}
2288 	return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() );
2289 }
2290 
2291 /*
2292 ================
2293 idGameLocal::InPlayerConnectedArea
2294 
2295   should only be called during entity thinking and event handling
2296 ================
2297 */
InPlayerConnectedArea(idEntity * ent) const2298 bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const {
2299 	if ( playerConnectedAreas.i == -1 ) {
2300 		return false;
2301 	}
2302 	return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() );
2303 }
2304 
2305 /*
2306 ================
2307 idGameLocal::UpdateGravity
2308 ================
2309 */
UpdateGravity(void)2310 void idGameLocal::UpdateGravity( void ) {
2311 	idEntity *ent;
2312 
2313 	if ( g_gravity.IsModified() ) {
2314 		if ( g_gravity.GetFloat() == 0.0f ) {
2315 			g_gravity.SetFloat( 1.0f );
2316 		}
2317 		gravity.Set( 0, 0, -g_gravity.GetFloat() );
2318 
2319 		// update all physics objects
2320 		for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
2321 			if ( ent->IsType( idAFEntity_Generic::Type ) ) {
2322 				idPhysics *phys = ent->GetPhysics();
2323 				if ( phys ) {
2324 					phys->SetGravity( gravity );
2325 				}
2326 			}
2327 		}
2328 		g_gravity.ClearModified();
2329 	}
2330 }
2331 
2332 /*
2333 ================
2334 idGameLocal::GetGravity
2335 ================
2336 */
GetGravity(void) const2337 const idVec3 &idGameLocal::GetGravity( void ) const {
2338 	return gravity;
2339 }
2340 
2341 /*
2342 ================
2343 idGameLocal::SortActiveEntityList
2344 
2345   Sorts the active entity list such that pushing entities come first,
2346   actors come next and physics team slaves appear after their master.
2347 ================
2348 */
SortActiveEntityList(void)2349 void idGameLocal::SortActiveEntityList( void ) {
2350 	idEntity *ent, *next_ent, *master, *part;
2351 
2352 	// if the active entity list needs to be reordered to place physics team masters at the front
2353 	if ( sortTeamMasters ) {
2354 		for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) {
2355 			next_ent = ent->activeNode.Next();
2356 			master = ent->GetTeamMaster();
2357 			if ( master && master == ent ) {
2358 				ent->activeNode.Remove();
2359 				ent->activeNode.AddToFront( activeEntities );
2360 			}
2361 		}
2362 	}
2363 
2364 	// if the active entity list needs to be reordered to place pushers at the front
2365 	if ( sortPushers ) {
2366 
2367 		for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) {
2368 			next_ent = ent->activeNode.Next();
2369 			master = ent->GetTeamMaster();
2370 			if ( !master || master == ent ) {
2371 				// check if there is an actor on the team
2372 				for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) {
2373 					if ( part->GetPhysics()->IsType( idPhysics_Actor::Type ) ) {
2374 						break;
2375 					}
2376 				}
2377 				// if there is an actor on the team
2378 				if ( part ) {
2379 					ent->activeNode.Remove();
2380 					ent->activeNode.AddToFront( activeEntities );
2381 				}
2382 			}
2383 		}
2384 
2385 		for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) {
2386 			next_ent = ent->activeNode.Next();
2387 			master = ent->GetTeamMaster();
2388 			if ( !master || master == ent ) {
2389 				// check if there is an entity on the team using parametric physics
2390 				for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) {
2391 					if ( part->GetPhysics()->IsType( idPhysics_Parametric::Type ) ) {
2392 						break;
2393 					}
2394 				}
2395 				// if there is an entity on the team using parametric physics
2396 				if ( part ) {
2397 					ent->activeNode.Remove();
2398 					ent->activeNode.AddToFront( activeEntities );
2399 				}
2400 			}
2401 		}
2402 	}
2403 
2404 	sortTeamMasters = false;
2405 	sortPushers = false;
2406 }
2407 
2408 #ifdef _D3XP
2409 /*
2410 ================
2411 idGameLocal::RunTimeGroup2
2412 ================
2413 */
RunTimeGroup2()2414 void idGameLocal::RunTimeGroup2() {
2415 	idEntity *ent;
2416 	int num = 0;
2417 
2418 	fast.Increment();
2419 	fast.Get( time, previousTime, msec, framenum, realClientTime );
2420 
2421 	for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
2422 		if ( ent->timeGroup != TIME_GROUP2 ) {
2423 			continue;
2424 		}
2425 
2426 		ent->Think();
2427 		num++;
2428 	}
2429 
2430 	slow.Get( time, previousTime, msec, framenum, realClientTime );
2431 }
2432 #endif
2433 
2434 /*
2435 ================
2436 idGameLocal::RunFrame
2437 ================
2438 */
RunFrame(const usercmd_t * clientCmds)2439 gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) {
2440 	idEntity *	ent;
2441 	int			num;
2442 	float		ms;
2443 	idTimer		timer_think, timer_events, timer_singlethink;
2444 	gameReturn_t ret;
2445 	idPlayer	*player;
2446 	const renderView_t *view;
2447 
2448 #ifdef _DEBUG
2449 	if ( isMultiplayer ) {
2450 		assert( !isClient );
2451 	}
2452 #endif
2453 
2454 	player = GetLocalPlayer();
2455 
2456 #ifdef _D3XP
2457 	ComputeSlowMsec();
2458 
2459 	slow.Get( time, previousTime, msec, framenum, realClientTime );
2460 	msec = slowmoMsec;
2461 #endif
2462 
2463 	if ( !isMultiplayer && g_stopTime.GetBool() ) {
2464 		// clear any debug lines from a previous frame
2465 		gameRenderWorld->DebugClearLines( time + 1 );
2466 
2467 		// set the user commands for this frame
2468 		memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) );
2469 
2470 		if ( player ) {
2471 			player->Think();
2472 		}
2473 	} else do {
2474 		// update the game time
2475 		framenum++;
2476 		previousTime = time;
2477 		time += msec;
2478 		realClientTime = time;
2479 
2480 #ifdef _D3XP
2481 		slow.Set( time, previousTime, msec, framenum, realClientTime );
2482 #endif
2483 
2484 #ifdef GAME_DLL
2485 		// allow changing SIMD usage on the fly
2486 		if ( com_forceGenericSIMD.IsModified() ) {
2487 			idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() );
2488 		}
2489 #endif
2490 
2491 		// make sure the random number counter is used each frame so random events
2492 		// are influenced by the player's actions
2493 		random.RandomInt();
2494 
2495 		if ( player ) {
2496 			// update the renderview so that any gui videos play from the right frame
2497 			view = player->GetRenderView();
2498 			if ( view ) {
2499 				gameRenderWorld->SetRenderView( view );
2500 			}
2501 		}
2502 
2503 		// clear any debug lines from a previous frame
2504 		gameRenderWorld->DebugClearLines( time );
2505 
2506 		// clear any debug polygons from a previous frame
2507 		gameRenderWorld->DebugClearPolygons( time );
2508 
2509 		// set the user commands for this frame
2510 		memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) );
2511 
2512 		// free old smoke particles
2513 		smokeParticles->FreeSmokes();
2514 
2515 		// process events on the server
2516 		ServerProcessEntityNetworkEventQueue();
2517 
2518 		// update our gravity vector if needed.
2519 		UpdateGravity();
2520 
2521 		// create a merged pvs for all players
2522 		SetupPlayerPVS();
2523 
2524 		// sort the active entity list
2525 		SortActiveEntityList();
2526 
2527 		timer_think.Clear();
2528 		timer_think.Start();
2529 
2530 		// let entities think
2531 		if ( g_timeentities.GetFloat() ) {
2532 			num = 0;
2533 			for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
2534 				if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) {
2535 					ent->GetPhysics()->UpdateTime( time );
2536 					continue;
2537 				}
2538 				timer_singlethink.Clear();
2539 				timer_singlethink.Start();
2540 				ent->Think();
2541 				timer_singlethink.Stop();
2542 				ms = timer_singlethink.Milliseconds();
2543 				if ( ms >= g_timeentities.GetFloat() ) {
2544 					Printf( "%d: entity '%s': %f ms\n", time, ent->name.c_str(), ms );
2545 				}
2546 				num++;
2547 			}
2548 		} else {
2549 			if ( inCinematic ) {
2550 				num = 0;
2551 				for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
2552 					if ( g_cinematic.GetBool() && !ent->cinematic ) {
2553 						ent->GetPhysics()->UpdateTime( time );
2554 						continue;
2555 					}
2556 					ent->Think();
2557 					num++;
2558 				}
2559 			} else {
2560 				num = 0;
2561 				for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
2562 #ifdef _D3XP
2563 					if ( ent->timeGroup != TIME_GROUP1 ) {
2564 						continue;
2565 					}
2566 #endif
2567 					ent->Think();
2568 					num++;
2569 				}
2570 			}
2571 		}
2572 
2573 #ifdef _D3XP
2574 		RunTimeGroup2();
2575 #endif
2576 
2577 		// remove any entities that have stopped thinking
2578 		if ( numEntitiesToDeactivate ) {
2579 			idEntity *next_ent;
2580 			int c = 0;
2581 			for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) {
2582 				next_ent = ent->activeNode.Next();
2583 				if ( !ent->thinkFlags ) {
2584 					ent->activeNode.Remove();
2585 					c++;
2586 				}
2587 			}
2588 			//assert( numEntitiesToDeactivate == c );
2589 			numEntitiesToDeactivate = 0;
2590 		}
2591 
2592 		timer_think.Stop();
2593 		timer_events.Clear();
2594 		timer_events.Start();
2595 
2596 		// service any pending events
2597 		idEvent::ServiceEvents();
2598 
2599 #ifdef _D3XP
2600 		// service pending fast events
2601 		fast.Get( time, previousTime, msec, framenum, realClientTime );
2602 		idEvent::ServiceFastEvents();
2603 		slow.Get( time, previousTime, msec, framenum, realClientTime );
2604 #endif
2605 
2606 		timer_events.Stop();
2607 
2608 		// free the player pvs
2609 		FreePlayerPVS();
2610 
2611 		// do multiplayer related stuff
2612 		if ( isMultiplayer ) {
2613 			mpGame.Run();
2614 		}
2615 
2616 		// display how long it took to calculate the current game frame
2617 		if ( g_frametime.GetBool() ) {
2618 			Printf( "game %d: all:%u th:%u ev:%u %d ents \n",
2619 				time, timer_think.Milliseconds() + timer_events.Milliseconds(),
2620 				timer_think.Milliseconds(), timer_events.Milliseconds(), num );
2621 		}
2622 
2623 		// build the return value
2624 		ret.consistencyHash = 0;
2625 		ret.sessionCommand[0] = 0;
2626 
2627 		if ( !isMultiplayer && player ) {
2628 			ret.health = player->health;
2629 			ret.heartRate = player->heartRate;
2630 			ret.stamina = idMath::FtoiFast( player->stamina );
2631 			// combat is a 0-100 value based on lastHitTime and lastDmgTime
2632 			// each make up 50% of the time spread over 10 seconds
2633 			ret.combat = 0;
2634 			if ( player->lastDmgTime > 0 && time < player->lastDmgTime + 10000 ) {
2635 				ret.combat += 50.0f * (float) ( time - player->lastDmgTime ) / 10000;
2636 			}
2637 			if ( player->lastHitTime > 0 && time < player->lastHitTime + 10000 ) {
2638 				ret.combat += 50.0f * (float) ( time - player->lastHitTime ) / 10000;
2639 			}
2640 		}
2641 
2642 		// see if a target_sessionCommand has forced a changelevel
2643 		if ( sessionCommand.Length() ) {
2644 			strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) );
2645 			break;
2646 		}
2647 
2648 		// make sure we don't loop forever when skipping a cinematic
2649 		if ( skipCinematic && ( time > cinematicMaxSkipTime ) ) {
2650 			Warning( "Exceeded maximum cinematic skip length.  Cinematic may be looping infinitely." );
2651 			skipCinematic = false;
2652 			break;
2653 		}
2654 	} while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic );
2655 
2656 	ret.syncNextGameFrame = skipCinematic;
2657 	if ( skipCinematic ) {
2658 		soundSystem->SetMute( false );
2659 		skipCinematic = false;
2660 	}
2661 
2662 	// show any debug info for this frame
2663 	RunDebugInfo();
2664 	D_DrawDebugLines();
2665 
2666 	return ret;
2667 }
2668 
2669 
2670 /*
2671 ======================================================================
2672 
2673   Game view drawing
2674 
2675 ======================================================================
2676 */
2677 
2678 /*
2679 ====================
2680 idGameLocal::CalcFov
2681 
2682 Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio
2683 ====================
2684 */
CalcFov(float base_fov,float & fov_x,float & fov_y) const2685 void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const {
2686 	float	x;
2687 	float	y;
2688 	float	ratio_x;
2689 	float	ratio_y;
2690 
2691 	// first, calculate the vertical fov based on a 640x480 view
2692 	x = 640.0f / tan( base_fov / 360.0f * idMath::PI );
2693 	y = atan2( 480.0f, x );
2694 	fov_y = y * 360.0f / idMath::PI;
2695 
2696 	// FIXME: somehow, this is happening occasionally
2697 	assert( fov_y > 0 );
2698 	if ( fov_y <= 0 ) {
2699 		Error( "idGameLocal::CalcFov: bad result, fov_y == %f, base_fov == %f", fov_y, base_fov );
2700 	}
2701 
2702 	switch( r_aspectRatio.GetInteger() ) {
2703 	default :
2704 	case -1 :
2705 		// auto mode => use aspect ratio from resolution, assuming screen's pixels are squares
2706 		ratio_x = renderSystem->GetScreenWidth();
2707 		ratio_y = renderSystem->GetScreenHeight();
2708 		if(ratio_x <= 0.0f || ratio_y <= 0.0f)
2709 		{
2710 			// for some reason (maybe this is a dedicated server?) GetScreenWidth()/Height()
2711 			// returned 0. Assume default 4:3 to avoid assert()/Error() below.
2712 			fov_x = base_fov;
2713 			return;
2714 		}
2715 		break;
2716 	case 0 :
2717 		// 4:3
2718 		fov_x = base_fov;
2719 		return;
2720 		break;
2721 
2722 	case 1 :
2723 		// 16:9
2724 		ratio_x = 16.0f;
2725 		ratio_y = 9.0f;
2726 		break;
2727 
2728 	case 2 :
2729 		// 16:10
2730 		ratio_x = 16.0f;
2731 		ratio_y = 10.0f;
2732 		break;
2733 	}
2734 
2735 	y = ratio_y / tan( fov_y / 360.0f * idMath::PI );
2736 	fov_x = atan2( ratio_x, y ) * 360.0f / idMath::PI;
2737 
2738 	if ( fov_x < base_fov ) {
2739 		fov_x = base_fov;
2740 		x = ratio_x / tan( fov_x / 360.0f * idMath::PI );
2741 		fov_y = atan2( ratio_y, x ) * 360.0f / idMath::PI;
2742 	}
2743 
2744 	// FIXME: somehow, this is happening occasionally
2745 	assert( ( fov_x > 0 ) && ( fov_y > 0 ) );
2746 	if ( ( fov_y <= 0 ) || ( fov_x <= 0 ) ) {
2747 		Error( "idGameLocal::CalcFov: bad result" );
2748 	}
2749 }
2750 
2751 /*
2752 ================
2753 idGameLocal::Draw
2754 
2755 makes rendering and sound system calls
2756 ================
2757 */
Draw(int clientNum)2758 bool idGameLocal::Draw( int clientNum ) {
2759 	if ( isMultiplayer ) {
2760 		return mpGame.Draw( clientNum );
2761 	}
2762 
2763 	idPlayer *player = static_cast<idPlayer *>(entities[ clientNum ]);
2764 
2765 	if ( !player ) {
2766 		return false;
2767 	}
2768 
2769 	// render the scene
2770 	player->playerView.RenderPlayerView( player->hud );
2771 
2772 	return true;
2773 }
2774 
2775 /*
2776 ================
2777 idGameLocal::HandleESC
2778 ================
2779 */
HandleESC(idUserInterface ** gui)2780 escReply_t idGameLocal::HandleESC( idUserInterface **gui ) {
2781 	if ( isMultiplayer ) {
2782 		*gui = StartMenu();
2783 		// we may set the gui back to NULL to hide it
2784 		return ESC_GUI;
2785 	}
2786 	idPlayer *player = GetLocalPlayer();
2787 	if ( player ) {
2788 		if ( player->HandleESC() ) {
2789 			return ESC_IGNORE;
2790 		} else {
2791 			return ESC_MAIN;
2792 		}
2793 	}
2794 	return ESC_MAIN;
2795 }
2796 
2797 /*
2798 ================
2799 idGameLocal::StartMenu
2800 ================
2801 */
StartMenu(void)2802 idUserInterface* idGameLocal::StartMenu( void ) {
2803 	if ( !isMultiplayer ) {
2804 		return NULL;
2805 	}
2806 	return mpGame.StartMenu();
2807 }
2808 
2809 /*
2810 ================
2811 idGameLocal::HandleGuiCommands
2812 ================
2813 */
HandleGuiCommands(const char * menuCommand)2814 const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) {
2815 	if ( !isMultiplayer ) {
2816 		return NULL;
2817 	}
2818 	return mpGame.HandleGuiCommands( menuCommand );
2819 }
2820 
2821 /*
2822 ================
2823 idGameLocal::HandleMainMenuCommands
2824 ================
2825 */
HandleMainMenuCommands(const char * menuCommand,idUserInterface * gui)2826 void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { }
2827 
2828 /*
2829 ================
2830 idGameLocal::GetLevelMap
2831 
2832   should only be used for in-game level editing
2833 ================
2834 */
GetLevelMap(void)2835 idMapFile *idGameLocal::GetLevelMap( void ) {
2836 	if ( mapFile && mapFile->HasPrimitiveData()) {
2837 		return mapFile;
2838 	}
2839 	if ( !mapFileName.Length() ) {
2840 		return NULL;
2841 	}
2842 
2843 	if ( mapFile ) {
2844 		delete mapFile;
2845 	}
2846 
2847 	mapFile = new idMapFile;
2848 	if ( !mapFile->Parse( mapFileName ) ) {
2849 		delete mapFile;
2850 		mapFile = NULL;
2851 	}
2852 
2853 	return mapFile;
2854 }
2855 
2856 /*
2857 ================
2858 idGameLocal::GetMapName
2859 ================
2860 */
GetMapName(void) const2861 const char *idGameLocal::GetMapName( void ) const {
2862 	return mapFileName.c_str();
2863 }
2864 
2865 /*
2866 ================
2867 idGameLocal::CallFrameCommand
2868 ================
2869 */
CallFrameCommand(idEntity * ent,const function_t * frameCommand)2870 void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) {
2871 	frameCommandThread->CallFunction( ent, frameCommand, true );
2872 	frameCommandThread->Execute();
2873 }
2874 
2875 /*
2876 ================
2877 idGameLocal::CallObjectFrameCommand
2878 ================
2879 */
CallObjectFrameCommand(idEntity * ent,const char * frameCommand)2880 void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) {
2881 	const function_t *func;
2882 
2883 	func = ent->scriptObject.GetFunction( frameCommand );
2884 	if ( !func ) {
2885 		if ( !ent->IsType( idTestModel::Type ) ) {
2886 			Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() );
2887 		}
2888 	} else {
2889 		frameCommandThread->CallFunction( ent, func, true );
2890 		frameCommandThread->Execute();
2891 	}
2892 }
2893 
2894 /*
2895 ================
2896 idGameLocal::ShowTargets
2897 ================
2898 */
ShowTargets(void)2899 void idGameLocal::ShowTargets( void ) {
2900 	idMat3		axis = GetLocalPlayer()->viewAngles.ToMat3();
2901 	idVec3		up = axis[ 2 ] * 5.0f;
2902 	const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin();
2903 	idBounds	viewTextBounds( viewPos );
2904 	idBounds	viewBounds( viewPos );
2905 	idBounds	box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) );
2906 	idEntity	*ent;
2907 	idEntity	*target;
2908 	int			i;
2909 	idBounds	totalBounds;
2910 
2911 	viewTextBounds.ExpandSelf( 128.0f );
2912 	viewBounds.ExpandSelf( 512.0f );
2913 	for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
2914 		totalBounds = ent->GetPhysics()->GetAbsBounds();
2915 		for( i = 0; i < ent->targets.Num(); i++ ) {
2916 			target = ent->targets[ i ].GetEntity();
2917 			if ( target ) {
2918 				totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() );
2919 			}
2920 		}
2921 
2922 		if ( !viewBounds.IntersectsBounds( totalBounds ) ) {
2923 			continue;
2924 		}
2925 
2926 		float dist;
2927 		idVec3 dir = totalBounds.GetCenter() - viewPos;
2928 		dir.NormalizeFast();
2929 		totalBounds.RayIntersection( viewPos, dir, dist );
2930 		float frac = ( 512.0f - dist ) / 512.0f;
2931 		if ( frac < 0.0f ) {
2932 			continue;
2933 		}
2934 
2935 		gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() );
2936 		if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) {
2937 			idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter();
2938 			gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 );
2939 			gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 );
2940 			gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 );
2941 		}
2942 
2943 		for( i = 0; i < ent->targets.Num(); i++ ) {
2944 			target = ent->targets[ i ].GetEntity();
2945 			if ( target ) {
2946 				gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 );
2947 				gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() );
2948 			}
2949 		}
2950 	}
2951 }
2952 
2953 /*
2954 ================
2955 idGameLocal::RunDebugInfo
2956 ================
2957 */
RunDebugInfo(void)2958 void idGameLocal::RunDebugInfo( void ) {
2959 	idEntity *ent;
2960 	idPlayer *player;
2961 
2962 	player = GetLocalPlayer();
2963 	if ( !player ) {
2964 		return;
2965 	}
2966 
2967 	const idVec3 &origin = player->GetPhysics()->GetOrigin();
2968 
2969 	if ( g_showEntityInfo.GetBool() ) {
2970 		idMat3		axis = player->viewAngles.ToMat3();
2971 		idVec3		up = axis[ 2 ] * 5.0f;
2972 		idBounds	viewTextBounds( origin );
2973 		idBounds	viewBounds( origin );
2974 
2975 		viewTextBounds.ExpandSelf( 128.0f );
2976 		viewBounds.ExpandSelf( 512.0f );
2977 		for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
2978 			// don't draw the worldspawn
2979 			if ( ent == world ) {
2980 				continue;
2981 			}
2982 
2983 			// skip if the entity is very far away
2984 			if ( !viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) {
2985 				continue;
2986 			}
2987 
2988 			const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds();
2989 			int contents = ent->GetPhysics()->GetContents();
2990 			if ( contents & CONTENTS_BODY ) {
2991 				gameRenderWorld->DebugBounds( colorCyan, entBounds );
2992 			} else if ( contents & CONTENTS_TRIGGER ) {
2993 				gameRenderWorld->DebugBounds( colorOrange, entBounds );
2994 			} else if ( contents & CONTENTS_SOLID ) {
2995 				gameRenderWorld->DebugBounds( colorGreen, entBounds );
2996 			} else {
2997 				if ( !entBounds.GetVolume() ) {
2998 					gameRenderWorld->DebugBounds( colorMdGrey, entBounds.Expand( 8.0f ) );
2999 				} else {
3000 					gameRenderWorld->DebugBounds( colorMdGrey, entBounds );
3001 				}
3002 			}
3003 			if ( viewTextBounds.IntersectsBounds( entBounds ) ) {
3004 				gameRenderWorld->DrawText( ent->name.c_str(), entBounds.GetCenter(), 0.1f, colorWhite, axis, 1 );
3005 				gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), entBounds.GetCenter() + up, 0.1f, colorWhite, axis, 1 );
3006 			}
3007 		}
3008 	}
3009 
3010 	// debug tool to draw bounding boxes around active entities
3011 	if ( g_showActiveEntities.GetBool() ) {
3012 		for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
3013 			idBounds	b = ent->GetPhysics()->GetBounds();
3014 			if ( b.GetVolume() <= 0 ) {
3015 				b[0][0] = b[0][1] = b[0][2] = -8;
3016 				b[1][0] = b[1][1] = b[1][2] = 8;
3017 			}
3018 			if ( ent->fl.isDormant ) {
3019 				gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() );
3020 			} else {
3021 				gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() );
3022 			}
3023 		}
3024 	}
3025 
3026 	if ( g_showTargets.GetBool() ) {
3027 		ShowTargets();
3028 	}
3029 
3030 	if ( g_showTriggers.GetBool() ) {
3031 		idTrigger::DrawDebugInfo();
3032 	}
3033 
3034 	if ( ai_showCombatNodes.GetBool() ) {
3035 		idCombatNode::DrawDebugInfo();
3036 	}
3037 
3038 	if ( ai_showPaths.GetBool() ) {
3039 		idPathCorner::DrawDebugInfo();
3040 	}
3041 
3042 	if ( g_editEntityMode.GetBool() ) {
3043 		editEntities->DisplayEntities();
3044 	}
3045 
3046 	if ( g_showCollisionWorld.GetBool() ) {
3047 		collisionModelManager->DrawModel( 0, vec3_origin, mat3_identity, origin, 128.0f );
3048 	}
3049 
3050 	if ( g_showCollisionModels.GetBool() ) {
3051 		clip.DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player );
3052 	}
3053 
3054 	if ( g_showCollisionTraces.GetBool() ) {
3055 		clip.PrintStatistics();
3056 	}
3057 
3058 	if ( g_showPVS.GetInteger() ) {
3059 		pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL );
3060 	}
3061 
3062 	if ( aas_test.GetInteger() >= 0 ) {
3063 		idAAS *aas = GetAAS( aas_test.GetInteger() );
3064 		if ( aas ) {
3065 			aas->Test( origin );
3066 			if ( ai_testPredictPath.GetBool() ) {
3067 				idVec3 velocity;
3068 				predictedPath_t path;
3069 
3070 				velocity.x = cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f;
3071 				velocity.y = sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f;
3072 				velocity.z = 0.0f;
3073 				idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path );
3074 			}
3075 		}
3076 	}
3077 
3078 	if ( ai_showObstacleAvoidance.GetInteger() == 2 ) {
3079 		idAAS *aas = GetAAS( 0 );
3080 		if ( aas ) {
3081 			idVec3 seekPos;
3082 			obstaclePath_t path;
3083 
3084 			seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f;
3085 			idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path );
3086 		}
3087 	}
3088 
3089 	// collision map debug output
3090 	collisionModelManager->DebugOutput( player->GetEyePosition() );
3091 }
3092 
3093 /*
3094 ==================
3095 idGameLocal::NumAAS
3096 ==================
3097 */
NumAAS(void) const3098 int	idGameLocal::NumAAS( void ) const {
3099 	return aasList.Num();
3100 }
3101 
3102 /*
3103 ==================
3104 idGameLocal::GetAAS
3105 ==================
3106 */
GetAAS(int num) const3107 idAAS *idGameLocal::GetAAS( int num ) const {
3108 	if ( ( num >= 0 ) && ( num < aasList.Num() ) ) {
3109 		if ( aasList[ num ] && aasList[ num ]->GetSettings() ) {
3110 			return aasList[ num ];
3111 		}
3112 	}
3113 	return NULL;
3114 }
3115 
3116 /*
3117 ==================
3118 idGameLocal::GetAAS
3119 ==================
3120 */
GetAAS(const char * name) const3121 idAAS *idGameLocal::GetAAS( const char *name ) const {
3122 	int i;
3123 
3124 	for ( i = 0; i < aasNames.Num(); i++ ) {
3125 		if ( aasNames[ i ] == name ) {
3126 			if ( !aasList[ i ]->GetSettings() ) {
3127 				return NULL;
3128 			} else {
3129 				return aasList[ i ];
3130 			}
3131 		}
3132 	}
3133 	return NULL;
3134 }
3135 
3136 /*
3137 ==================
3138 idGameLocal::SetAASAreaState
3139 ==================
3140 */
SetAASAreaState(const idBounds & bounds,const int areaContents,bool closed)3141 void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) {
3142 	int i;
3143 
3144 	for( i = 0; i < aasList.Num(); i++ ) {
3145 		aasList[ i ]->SetAreaState( bounds, areaContents, closed );
3146 	}
3147 }
3148 
3149 /*
3150 ==================
3151 idGameLocal::AddAASObstacle
3152 ==================
3153 */
AddAASObstacle(const idBounds & bounds)3154 aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) {
3155 	int i;
3156 	aasHandle_t obstacle;
3157 	aasHandle_t check id_attribute((unused));
3158 
3159 	if ( !aasList.Num() ) {
3160 		return -1;
3161 	}
3162 
3163 	obstacle = aasList[ 0 ]->AddObstacle( bounds );
3164 	for( i = 1; i < aasList.Num(); i++ ) {
3165 		check = aasList[ i ]->AddObstacle( bounds );
3166 		assert( check == obstacle );
3167 	}
3168 
3169 	return obstacle;
3170 }
3171 
3172 /*
3173 ==================
3174 idGameLocal::RemoveAASObstacle
3175 ==================
3176 */
RemoveAASObstacle(const aasHandle_t handle)3177 void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) {
3178 	int i;
3179 
3180 	for( i = 0; i < aasList.Num(); i++ ) {
3181 		aasList[ i ]->RemoveObstacle( handle );
3182 	}
3183 }
3184 
3185 /*
3186 ==================
3187 idGameLocal::RemoveAllAASObstacles
3188 ==================
3189 */
RemoveAllAASObstacles(void)3190 void idGameLocal::RemoveAllAASObstacles( void ) {
3191 	int i;
3192 
3193 	for( i = 0; i < aasList.Num(); i++ ) {
3194 		aasList[ i ]->RemoveAllObstacles();
3195 	}
3196 }
3197 
3198 /*
3199 ==================
3200 idGameLocal::CheatsOk
3201 ==================
3202 */
CheatsOk(bool requirePlayer)3203 bool idGameLocal::CheatsOk( bool requirePlayer ) {
3204 	idPlayer *player;
3205 
3206 	if ( isMultiplayer && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) {
3207 		Printf( "Not allowed in multiplayer.\n" );
3208 		return false;
3209 	}
3210 
3211 	if ( developer.GetBool() ) {
3212 		return true;
3213 	}
3214 
3215 	player = GetLocalPlayer();
3216 	if ( !requirePlayer || ( player && ( player->health > 0 ) ) ) {
3217 		return true;
3218 	}
3219 
3220 	Printf( "You must be alive to use this command.\n" );
3221 
3222 	return false;
3223 }
3224 
3225 /*
3226 ===================
3227 idGameLocal::RegisterEntity
3228 ===================
3229 */
RegisterEntity(idEntity * ent)3230 void idGameLocal::RegisterEntity( idEntity *ent ) {
3231 	int spawn_entnum;
3232 
3233 	if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) {
3234 		Error( "idGameLocal::RegisterEntity: spawn count overflow" );
3235 	}
3236 
3237 	if ( !spawnArgs.GetInt( "spawn_entnum", "0", spawn_entnum ) ) {
3238 		while( entities[firstFreeIndex] && firstFreeIndex < ENTITYNUM_MAX_NORMAL ) {
3239 			firstFreeIndex++;
3240 		}
3241 		if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) {
3242 			Error( "no free entities" );
3243 		}
3244 		spawn_entnum = firstFreeIndex++;
3245 	}
3246 
3247 	entities[ spawn_entnum ] = ent;
3248 	spawnIds[ spawn_entnum ] = spawnCount++;
3249 	ent->entityNumber = spawn_entnum;
3250 	ent->spawnNode.AddToEnd( spawnedEntities );
3251 	ent->spawnArgs.TransferKeyValues( spawnArgs );
3252 
3253 	if ( spawn_entnum >= num_entities ) {
3254 		num_entities++;
3255 	}
3256 }
3257 
3258 /*
3259 ===================
3260 idGameLocal::UnregisterEntity
3261 ===================
3262 */
UnregisterEntity(idEntity * ent)3263 void idGameLocal::UnregisterEntity( idEntity *ent ) {
3264 	assert( ent );
3265 
3266 	if ( editEntities ) {
3267 		editEntities->RemoveSelectedEntity( ent );
3268 	}
3269 
3270 	if ( ( ent->entityNumber != ENTITYNUM_NONE ) && ( entities[ ent->entityNumber ] == ent ) ) {
3271 		ent->spawnNode.Remove();
3272 		entities[ ent->entityNumber ] = NULL;
3273 		spawnIds[ ent->entityNumber ] = -1;
3274 		if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeIndex ) {
3275 			firstFreeIndex = ent->entityNumber;
3276 		}
3277 		ent->entityNumber = ENTITYNUM_NONE;
3278 	}
3279 }
3280 
3281 /*
3282 ================
3283 idGameLocal::SpawnEntityType
3284 ================
3285 */
SpawnEntityType(const idTypeInfo & classdef,const idDict * args,bool bIsClientReadSnapshot)3286 idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) {
3287 	idClass *obj;
3288 
3289 #if _DEBUG
3290 	if ( isClient ) {
3291 		assert( bIsClientReadSnapshot );
3292 	}
3293 #endif
3294 
3295 	if ( !classdef.IsType( idEntity::Type ) ) {
3296 		Error( "Attempted to spawn non-entity class '%s'", classdef.classname );
3297 	}
3298 
3299 	try {
3300 		if ( args ) {
3301 			spawnArgs = *args;
3302 		} else {
3303 			spawnArgs.Clear();
3304 		}
3305 		obj = classdef.CreateInstance();
3306 		obj->CallSpawn();
3307 	}
3308 
3309 	catch( idAllocError & ) {
3310 		obj = NULL;
3311 	}
3312 	spawnArgs.Clear();
3313 
3314 	return static_cast<idEntity *>(obj);
3315 }
3316 
3317 /*
3318 ===================
3319 idGameLocal::SpawnEntityDef
3320 
3321 Finds the spawn function for the entity and calls it,
3322 returning false if not found
3323 ===================
3324 */
SpawnEntityDef(const idDict & args,idEntity ** ent,bool setDefaults)3325 bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) {
3326 	const char	*classname;
3327 	const char	*spawn;
3328 	idTypeInfo	*cls;
3329 	idClass		*obj;
3330 	idStr		error;
3331 	const char  *name;
3332 
3333 	if ( ent ) {
3334 		*ent = NULL;
3335 	}
3336 
3337 	spawnArgs = args;
3338 
3339 	if ( spawnArgs.GetString( "name", "", &name ) ) {
3340 		sprintf( error, " on '%s'", name);
3341 	}
3342 
3343 	spawnArgs.GetString( "classname", NULL, &classname );
3344 
3345 	const idDeclEntityDef *def = FindEntityDef( classname, false );
3346 
3347 	if ( !def ) {
3348 		Warning( "Unknown classname '%s'%s.", classname, error.c_str() );
3349 		return false;
3350 	}
3351 
3352 	spawnArgs.SetDefaults( &def->dict );
3353 
3354 #ifdef _D3XP
3355 	if ( !spawnArgs.FindKey( "slowmo" ) ) {
3356 		bool slowmo = true;
3357 
3358 		for ( int i = 0; fastEntityList[i]; i++ ) {
3359 			if ( !idStr::Cmp( classname, fastEntityList[i] ) ) {
3360 				slowmo = false;
3361 				break;
3362 			}
3363 		}
3364 
3365 		if ( !slowmo ) {
3366 			spawnArgs.SetBool( "slowmo", slowmo );
3367 		}
3368 	}
3369 #endif
3370 
3371 	// check if we should spawn a class object
3372 	spawnArgs.GetString( "spawnclass", NULL, &spawn );
3373 	if ( spawn ) {
3374 
3375 		cls = idClass::GetClass( spawn );
3376 		if ( !cls ) {
3377 			Warning( "Could not spawn '%s'.  Class '%s' not found %s.", classname, spawn, error.c_str() );
3378 			return false;
3379 		}
3380 
3381 		obj = cls->CreateInstance();
3382 		if ( !obj ) {
3383 			Warning( "Could not spawn '%s'. Instance could not be created %s.", classname, error.c_str() );
3384 			return false;
3385 		}
3386 
3387 		obj->CallSpawn();
3388 
3389 		if ( ent && obj->IsType( idEntity::Type ) ) {
3390 			*ent = static_cast<idEntity *>(obj);
3391 		}
3392 
3393 		return true;
3394 	}
3395 
3396 	// check if we should call a script function to spawn
3397 	spawnArgs.GetString( "spawnfunc", NULL, &spawn );
3398 	if ( spawn ) {
3399 		const function_t *func = program.FindFunction( spawn );
3400 		if ( !func ) {
3401 			Warning( "Could not spawn '%s'.  Script function '%s' not found%s.", classname, spawn, error.c_str() );
3402 			return false;
3403 		}
3404 		idThread *thread = new idThread( func );
3405 		thread->DelayedStart( 0 );
3406 		return true;
3407 	}
3408 
3409 	Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() );
3410 	return false;
3411 }
3412 
3413 /*
3414 ================
3415 idGameLocal::FindEntityDef
3416 ================
3417 */
FindEntityDef(const char * name,bool makeDefault) const3418 const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const {
3419 	const idDecl *decl = NULL;
3420 	if ( isMultiplayer ) {
3421 		decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false );
3422 	}
3423 	if ( !decl ) {
3424 		decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault );
3425 	}
3426 	return static_cast<const idDeclEntityDef *>( decl );
3427 }
3428 
3429 /*
3430 ================
3431 idGameLocal::FindEntityDefDict
3432 ================
3433 */
FindEntityDefDict(const char * name,bool makeDefault) const3434 const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const {
3435 	const idDeclEntityDef *decl = FindEntityDef( name, makeDefault );
3436 	return decl ? &decl->dict : NULL;
3437 }
3438 
3439 /*
3440 ================
3441 idGameLocal::InhibitEntitySpawn
3442 ================
3443 */
InhibitEntitySpawn(idDict & spawnArgs)3444 bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) {
3445 
3446 	bool result = false;
3447 
3448 	if ( isMultiplayer ) {
3449 		spawnArgs.GetBool( "not_multiplayer", "0", result );
3450 	} else if ( g_skill.GetInteger() == 0 ) {
3451 		spawnArgs.GetBool( "not_easy", "0", result );
3452 	} else if ( g_skill.GetInteger() == 1 ) {
3453 		spawnArgs.GetBool( "not_medium", "0", result );
3454 	} else {
3455 		spawnArgs.GetBool( "not_hard", "0", result );
3456 #ifdef _D3XP
3457 		if ( !result && g_skill.GetInteger() == 3 ) {
3458 			spawnArgs.GetBool( "not_nightmare", "0", result );
3459 		}
3460 #endif
3461 	}
3462 
3463 
3464 	const char *name;
3465 	if ( g_skill.GetInteger() == 3 ) {
3466 		name = spawnArgs.GetString( "classname" );
3467 		// _D3XP :: remove moveable medkit packs also
3468 		if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 ||
3469 			 idStr::Icmp( name, "moveable_item_medkit" ) == 0 || idStr::Icmp( name, "moveable_item_medkit_small" ) == 0 ) {
3470 
3471 			result = true;
3472 		}
3473 	}
3474 
3475 	if ( gameLocal.isMultiplayer ) {
3476 		name = spawnArgs.GetString( "classname" );
3477 		if ( idStr::Icmp( name, "weapon_bfg" ) == 0 || idStr::Icmp( name, "weapon_soulcube" ) == 0 ) {
3478 			result = true;
3479 		}
3480 	}
3481 
3482 	return result;
3483 }
3484 
3485 /*
3486 ================
3487 idGameLocal::SetSkill
3488 ================
3489 */
SetSkill(int value)3490 void idGameLocal::SetSkill( int value ) {
3491 	int skill_level;
3492 
3493 	if ( value < 0 ) {
3494 		skill_level = 0;
3495 	} else if ( value > 3 ) {
3496 		skill_level = 3;
3497 	} else {
3498 		skill_level = value;
3499 	}
3500 
3501 	g_skill.SetInteger( skill_level );
3502 }
3503 
3504 /*
3505 ==============
3506 idGameLocal::GameState
3507 
3508 Used to allow entities to know if they're being spawned during the initial spawn.
3509 ==============
3510 */
GameState(void) const3511 gameState_t	idGameLocal::GameState( void ) const {
3512 	return gamestate;
3513 }
3514 
3515 /*
3516 ==============
3517 idGameLocal::SpawnMapEntities
3518 
3519 Parses textual entity definitions out of an entstring and spawns gentities.
3520 ==============
3521 */
SpawnMapEntities(void)3522 void idGameLocal::SpawnMapEntities( void ) {
3523 	int			i;
3524 	int			num;
3525 	int			inhibit;
3526 	idMapEntity	*mapEnt;
3527 	int			numEntities;
3528 	idDict		args;
3529 
3530 	Printf( "Spawning entities\n" );
3531 
3532 	if ( mapFile == NULL ) {
3533 		Printf("No mapfile present\n");
3534 		return;
3535 	}
3536 
3537 	SetSkill( g_skill.GetInteger() );
3538 
3539 	numEntities = mapFile->GetNumEntities();
3540 	if ( numEntities == 0 ) {
3541 		Error( "...no entities" );
3542 	}
3543 
3544 	// the worldspawn is a special that performs any global setup
3545 	// needed by a level
3546 	mapEnt = mapFile->GetEntity( 0 );
3547 	args = mapEnt->epairs;
3548 	args.SetInt( "spawn_entnum", ENTITYNUM_WORLD );
3549 	if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::Type ) ) {
3550 		Error( "Problem spawning world entity" );
3551 	}
3552 
3553 	num = 1;
3554 	inhibit = 0;
3555 
3556 	for ( i = 1 ; i < numEntities ; i++ ) {
3557 		mapEnt = mapFile->GetEntity( i );
3558 		args = mapEnt->epairs;
3559 
3560 		if ( !InhibitEntitySpawn( args ) ) {
3561 			// precache any media specified in the map entity
3562 			CacheDictionaryMedia( &args );
3563 
3564 			SpawnEntityDef( args );
3565 			num++;
3566 		} else {
3567 			inhibit++;
3568 		}
3569 	}
3570 
3571 	Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit );
3572 }
3573 
3574 /*
3575 ================
3576 idGameLocal::AddEntityToHash
3577 ================
3578 */
AddEntityToHash(const char * name,idEntity * ent)3579 void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) {
3580 	if ( FindEntity( name ) ) {
3581 		Error( "Multiple entities named '%s'", name );
3582 	}
3583 	entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber );
3584 }
3585 
3586 /*
3587 ================
3588 idGameLocal::RemoveEntityFromHash
3589 ================
3590 */
RemoveEntityFromHash(const char * name,idEntity * ent)3591 bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) {
3592 	int hash, i;
3593 
3594 	hash = entityHash.GenerateKey( name, true );
3595 	for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) {
3596 		if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) {
3597 			entityHash.Remove( hash, i );
3598 			return true;
3599 		}
3600 	}
3601 	return false;
3602 }
3603 
3604 /*
3605 ================
3606 idGameLocal::GetTargets
3607 ================
3608 */
GetTargets(const idDict & args,idList<idEntityPtr<idEntity>> & list,const char * ref) const3609 int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr<idEntity> > &list, const char *ref ) const {
3610 	int i, num, refLength;
3611 	const idKeyValue *arg;
3612 	idEntity *ent;
3613 
3614 	list.Clear();
3615 
3616 	refLength = strlen( ref );
3617 	num = args.GetNumKeyVals();
3618 	for( i = 0; i < num; i++ ) {
3619 
3620 		arg = args.GetKeyVal( i );
3621 		if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) {
3622 
3623 			ent = FindEntity( arg->GetValue() );
3624 			if ( ent ) {
3625 				idEntityPtr<idEntity> &entityPtr = list.Alloc();
3626 				entityPtr = ent;
3627 			}
3628 		}
3629 	}
3630 
3631 	return list.Num();
3632 }
3633 
3634 /*
3635 =============
3636 idGameLocal::GetTraceEntity
3637 
3638 returns the master entity of a trace.  for example, if the trace entity is the player's head, it will return the player.
3639 =============
3640 */
GetTraceEntity(const trace_t & trace) const3641 idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const {
3642 	idEntity *master;
3643 
3644 	if ( !entities[ trace.c.entityNum ] ) {
3645 		return NULL;
3646 	}
3647 	master = entities[ trace.c.entityNum ]->GetBindMaster();
3648 	if ( master ) {
3649 		return master;
3650 	}
3651 	return entities[ trace.c.entityNum ];
3652 }
3653 
3654 /*
3655 =============
3656 idGameLocal::ArgCompletion_EntityName
3657 
3658 Argument completion for entity names
3659 =============
3660 */
ArgCompletion_EntityName(const idCmdArgs & args,void (* callback)(const char * s))3661 void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) {
3662 	int i;
3663 
3664 	for( i = 0; i < gameLocal.num_entities; i++ ) {
3665 		if ( gameLocal.entities[ i ] ) {
3666 			callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) );
3667 		}
3668 	}
3669 }
3670 
3671 /*
3672 =============
3673 idGameLocal::FindEntity
3674 
3675 Returns the entity whose name matches the specified string.
3676 =============
3677 */
FindEntity(const char * name) const3678 idEntity *idGameLocal::FindEntity( const char *name ) const {
3679 	int hash, i;
3680 
3681 	hash = entityHash.GenerateKey( name, true );
3682 	for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) {
3683 		if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) {
3684 			return entities[i];
3685 		}
3686 	}
3687 
3688 	return NULL;
3689 }
3690 
3691 /*
3692 =============
3693 idGameLocal::FindEntityUsingDef
3694 
3695 Searches all active entities for the next one using the specified entityDef.
3696 
3697 Searches beginning at the entity after from, or the beginning if NULL
3698 NULL will be returned if the end of the list is reached.
3699 =============
3700 */
FindEntityUsingDef(idEntity * from,const char * match) const3701 idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const {
3702 	idEntity	*ent;
3703 
3704 	if ( !from ) {
3705 		ent = spawnedEntities.Next();
3706 	} else {
3707 		ent = from->spawnNode.Next();
3708 	}
3709 
3710 	for ( ; ent != NULL; ent = ent->spawnNode.Next() ) {
3711 		assert( ent );
3712 		if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) {
3713 			return ent;
3714 		}
3715 	}
3716 
3717 	return NULL;
3718 }
3719 
3720 /*
3721 =============
3722 idGameLocal::FindTraceEntity
3723 
3724 Searches all active entities for the closest ( to start ) match that intersects
3725 the line start,end
3726 =============
3727 */
FindTraceEntity(idVec3 start,idVec3 end,const idTypeInfo & c,const idEntity * skip) const3728 idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const {
3729 	idEntity *ent;
3730 	idEntity *bestEnt;
3731 	float scale;
3732 	float bestScale;
3733 	idBounds b;
3734 
3735 	bestEnt = NULL;
3736 	bestScale = 1.0f;
3737 	for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
3738 		if ( ent->IsType( c ) && ent != skip ) {
3739 			b = ent->GetPhysics()->GetAbsBounds().Expand( 16 );
3740 			if ( b.RayIntersection( start, end-start, scale ) ) {
3741 				if ( scale >= 0.0f && scale < bestScale ) {
3742 					bestEnt = ent;
3743 					bestScale = scale;
3744 				}
3745 			}
3746 		}
3747 	}
3748 
3749 	return bestEnt;
3750 }
3751 
3752 /*
3753 ================
3754 idGameLocal::EntitiesWithinRadius
3755 ================
3756 */
EntitiesWithinRadius(const idVec3 org,float radius,idEntity ** entityList,int maxCount) const3757 int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const {
3758 	idEntity *ent;
3759 	idBounds bo( org );
3760 	int entCount = 0;
3761 
3762 	bo.ExpandSelf( radius );
3763 	for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
3764 		if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) {
3765 			entityList[entCount++] = ent;
3766 		}
3767 	}
3768 
3769 	return entCount;
3770 }
3771 
3772 /*
3773 =================
3774 idGameLocal::KillBox
3775 
3776 Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed.
3777 Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately.
3778 If catch_teleport, this only marks teleport players for death on exit
3779 =================
3780 */
KillBox(idEntity * ent,bool catch_teleport)3781 void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) {
3782 	int			i;
3783 	int			num;
3784 	idEntity *	hit;
3785 	idClipModel *cm;
3786 	idClipModel *clipModels[ MAX_GENTITIES ];
3787 	idPhysics	*phys;
3788 
3789 	phys = ent->GetPhysics();
3790 	if ( !phys->GetNumClipModels() ) {
3791 		return;
3792 	}
3793 
3794 	num = clip.ClipModelsTouchingBounds( phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES );
3795 	for ( i = 0; i < num; i++ ) {
3796 		cm = clipModels[ i ];
3797 
3798 		// don't check render entities
3799 		if ( cm->IsRenderModel() ) {
3800 			continue;
3801 		}
3802 
3803 		hit = cm->GetEntity();
3804 		if ( ( hit == ent ) || !hit->fl.takedamage ) {
3805 			continue;
3806 		}
3807 
3808 		if ( !phys->ClipContents( cm ) ) {
3809 			continue;
3810 		}
3811 
3812 		// nail it
3813 		if ( hit->IsType( idPlayer::Type ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) {
3814 			static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber );
3815 		} else if ( !catch_teleport ) {
3816 			hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT );
3817 		}
3818 
3819 		if ( !gameLocal.isMultiplayer ) {
3820 			// let the mapper know about it
3821 			Warning( "'%s' telefragged '%s'", ent->name.c_str(), hit->name.c_str() );
3822 		}
3823 	}
3824 }
3825 
3826 /*
3827 ================
3828 idGameLocal::RequirementMet
3829 ================
3830 */
RequirementMet(idEntity * activator,const idStr & requires,int removeItem)3831 bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) {
3832 	if ( requires.Length() ) {
3833 		if ( activator->IsType( idPlayer::Type ) ) {
3834 			idPlayer *player = static_cast<idPlayer *>(activator);
3835 			idDict *item = player->FindInventoryItem( requires );
3836 			if ( item ) {
3837 				if ( removeItem ) {
3838 					player->RemoveInventoryItem( item );
3839 				}
3840 				return true;
3841 			} else {
3842 				return false;
3843 			}
3844 		}
3845 	}
3846 
3847 	return true;
3848 }
3849 
3850 /*
3851 ============
3852 idGameLocal::AlertAI
3853 ============
3854 */
AlertAI(idEntity * ent)3855 void idGameLocal::AlertAI( idEntity *ent ) {
3856 	if ( ent && ent->IsType( idActor::Type ) ) {
3857 		// alert them for the next frame
3858 		lastAIAlertTime = time + msec;
3859 		lastAIAlertEntity = static_cast<idActor *>( ent );
3860 	}
3861 }
3862 
3863 /*
3864 ============
3865 idGameLocal::GetAlertEntity
3866 ============
3867 */
GetAlertEntity(void)3868 idActor *idGameLocal::GetAlertEntity( void ) {
3869 #ifdef _D3XP
3870 	int timeGroup = 0;
3871 	if ( lastAIAlertTime && lastAIAlertEntity.GetEntity() ) {
3872 		timeGroup = lastAIAlertEntity.GetEntity()->timeGroup;
3873 	}
3874 	SetTimeState ts( timeGroup );
3875 #endif
3876 
3877 	if ( lastAIAlertTime >= time ) {
3878 		return lastAIAlertEntity.GetEntity();
3879 	}
3880 
3881 	return NULL;
3882 }
3883 
3884 /*
3885 ============
3886 idGameLocal::RadiusDamage
3887 ============
3888 */
RadiusDamage(const idVec3 & origin,idEntity * inflictor,idEntity * attacker,idEntity * ignoreDamage,idEntity * ignorePush,const char * damageDefName,float dmgPower)3889 void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower ) {
3890 	float		dist, damageScale, attackerDamageScale, attackerPushScale;
3891 	idEntity *	ent;
3892 	idEntity *	entityList[ MAX_GENTITIES ];
3893 	int			numListedEntities;
3894 	idBounds	bounds;
3895 	idVec3		v, damagePoint, dir;
3896 	int			i, e, damage, radius, push;
3897 
3898 	const idDict *damageDef = FindEntityDefDict( damageDefName, false );
3899 	if ( !damageDef ) {
3900 		Warning( "Unknown damageDef '%s'", damageDefName );
3901 		return;
3902 	}
3903 
3904 	damageDef->GetInt( "damage", "20", damage );
3905 	damageDef->GetInt( "radius", "50", radius );
3906 	damageDef->GetInt( "push", va( "%d", damage * 100 ), push );
3907 	damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale );
3908 	damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale );
3909 
3910 	if ( radius < 1 ) {
3911 		radius = 1;
3912 	}
3913 
3914 	bounds = idBounds( origin ).Expand( radius );
3915 
3916 	// get all entities touching the bounds
3917 	numListedEntities = clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES );
3918 
3919 	if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) {
3920 		inflictor = static_cast<idAFAttachment*>(inflictor)->GetBody();
3921 	}
3922 	if ( attacker && attacker->IsType( idAFAttachment::Type ) ) {
3923 		attacker = static_cast<idAFAttachment*>(attacker)->GetBody();
3924 	}
3925 	if ( ignoreDamage && ignoreDamage->IsType( idAFAttachment::Type ) ) {
3926 		ignoreDamage = static_cast<idAFAttachment*>(ignoreDamage)->GetBody();
3927 	}
3928 
3929 	// apply damage to the entities
3930 	for ( e = 0; e < numListedEntities; e++ ) {
3931 		ent = entityList[ e ];
3932 		assert( ent );
3933 
3934 		if ( !ent->fl.takedamage ) {
3935 			continue;
3936 		}
3937 
3938 		if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast<idAFAttachment*>(ent)->GetBody() == inflictor ) ) {
3939 			continue;
3940 		}
3941 
3942 		if ( ent == ignoreDamage || ( ent->IsType( idAFAttachment::Type ) && static_cast<idAFAttachment*>(ent)->GetBody() == ignoreDamage ) ) {
3943 			continue;
3944 		}
3945 
3946 		// don't damage a dead player
3947 		if ( isMultiplayer && ent->entityNumber < MAX_CLIENTS && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >( ent )->health < 0 ) {
3948 			continue;
3949 		}
3950 
3951 		// find the distance from the edge of the bounding box
3952 		for ( i = 0; i < 3; i++ ) {
3953 			if ( origin[ i ] < ent->GetPhysics()->GetAbsBounds()[0][ i ] ) {
3954 				v[ i ] = ent->GetPhysics()->GetAbsBounds()[0][ i ] - origin[ i ];
3955 			} else if ( origin[ i ] > ent->GetPhysics()->GetAbsBounds()[1][ i ] ) {
3956 				v[ i ] = origin[ i ] - ent->GetPhysics()->GetAbsBounds()[1][ i ];
3957 			} else {
3958 				v[ i ] = 0;
3959 			}
3960 		}
3961 
3962 		dist = v.Length();
3963 		if ( dist >= radius ) {
3964 			continue;
3965 		}
3966 
3967 		if ( ent->CanDamage( origin, damagePoint ) ) {
3968 			// push the center of mass higher than the origin so players
3969 			// get knocked into the air more
3970 			dir = ent->GetPhysics()->GetOrigin() - origin;
3971 			dir[ 2 ] += 24;
3972 
3973 			// get the damage scale
3974 			damageScale = dmgPower * ( 1.0f - dist / radius );
3975 			if ( ent == attacker || ( ent->IsType( idAFAttachment::Type ) && static_cast<idAFAttachment*>(ent)->GetBody() == attacker ) ) {
3976 				damageScale *= attackerDamageScale;
3977 			}
3978 
3979 			ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT );
3980 		}
3981 	}
3982 
3983 	// push physics objects
3984 	if ( push ) {
3985 		RadiusPush( origin, radius, push * dmgPower, attacker, ignorePush, attackerPushScale, false );
3986 	}
3987 }
3988 
3989 /*
3990 ==============
3991 idGameLocal::RadiusPush
3992 ==============
3993 */
RadiusPush(const idVec3 & origin,const float radius,const float push,const idEntity * inflictor,const idEntity * ignore,float inflictorScale,const bool quake)3994 void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) {
3995 	int i, numListedClipModels;
3996 	idClipModel *clipModel;
3997 	idClipModel *clipModelList[ MAX_GENTITIES ];
3998 	idVec3 dir;
3999 	idBounds bounds;
4000 	modelTrace_t result;
4001 	idEntity *ent;
4002 	float scale;
4003 
4004 	dir.Set( 0.0f, 0.0f, 1.0f );
4005 
4006 	bounds = idBounds( origin ).Expand( radius );
4007 
4008 	// get all clip models touching the bounds
4009 	numListedClipModels = clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
4010 
4011 	if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) {
4012 		inflictor = static_cast<const idAFAttachment*>(inflictor)->GetBody();
4013 	}
4014 	if ( ignore && ignore->IsType( idAFAttachment::Type ) ) {
4015 		ignore = static_cast<const idAFAttachment*>(ignore)->GetBody();
4016 	}
4017 
4018 	// apply impact to all the clip models through their associated physics objects
4019 	for ( i = 0; i < numListedClipModels; i++ ) {
4020 
4021 		clipModel = clipModelList[i];
4022 
4023 		// never push render models
4024 		if ( clipModel->IsRenderModel() ) {
4025 			continue;
4026 		}
4027 
4028 		ent = clipModel->GetEntity();
4029 
4030 		// never push projectiles
4031 		if ( ent->IsType( idProjectile::Type ) ) {
4032 			continue;
4033 		}
4034 
4035 		// players use "knockback" in idPlayer::Damage
4036 		if ( ent->IsType( idPlayer::Type ) && !quake ) {
4037 			continue;
4038 		}
4039 
4040 		// don't push the ignore entity
4041 		if ( ent == ignore || ( ent->IsType( idAFAttachment::Type ) && static_cast<idAFAttachment*>(ent)->GetBody() == ignore ) ) {
4042 			continue;
4043 		}
4044 
4045 		if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) {
4046 			continue;
4047 		}
4048 
4049 		// scale the push for the inflictor
4050 		if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast<idAFAttachment*>(ent)->GetBody() == inflictor ) ) {
4051 			scale = inflictorScale;
4052 		} else {
4053 			scale = 1.0f;
4054 		}
4055 
4056 		if ( quake ) {
4057 			clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir );
4058 		} else {
4059 			RadiusPushClipModel( origin, scale * push, clipModel );
4060 		}
4061 	}
4062 }
4063 
4064 /*
4065 ==============
4066 idGameLocal::RadiusPushClipModel
4067 ==============
4068 */
RadiusPushClipModel(const idVec3 & origin,const float push,const idClipModel * clipModel)4069 void idGameLocal::RadiusPushClipModel( const idVec3 &origin, const float push, const idClipModel *clipModel ) {
4070 	int i, j;
4071 	float dot, dist, area;
4072 	const idTraceModel *trm;
4073 	const traceModelPoly_t *poly;
4074 	idFixedWinding w;
4075 	idVec3 v, localOrigin, center, impulse;
4076 
4077 	trm = clipModel->GetTraceModel();
4078 	if ( !trm || 1 ) {
4079 		impulse = clipModel->GetAbsBounds().GetCenter() - origin;
4080 		impulse.Normalize();
4081 		impulse.z += 1.0f;
4082 		clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), push * impulse );
4083 		return;
4084 	}
4085 
4086 	localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose();
4087 	for ( i = 0; i < trm->numPolys; i++ ) {
4088 		poly = &trm->polys[i];
4089 
4090 		center.Zero();
4091 		for ( j = 0; j < poly->numEdges; j++ ) {
4092 			v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INTSIGNBITSET( poly->edges[j] ) ] ];
4093 			center += v;
4094 			v -= localOrigin;
4095 			v.NormalizeFast();	// project point on a unit sphere
4096 			w.AddPoint( v );
4097 		}
4098 		center /= poly->numEdges;
4099 		v = center - localOrigin;
4100 		dist = v.NormalizeFast();
4101 		dot = v * poly->normal;
4102 		if ( dot > 0.0f ) {
4103 			continue;
4104 		}
4105 		area = w.GetArea();
4106 		// impulse in polygon normal direction
4107 		impulse = poly->normal * clipModel->GetAxis();
4108 		// always push up for nicer effect
4109 		impulse.z -= 1.0f;
4110 		// scale impulse based on visible surface area and polygon angle
4111 		impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) );
4112 		// scale away distance for nicer effect
4113 		impulse *= ( dist * 2.0f );
4114 		// impulse is applied to the center of the polygon
4115 		center = clipModel->GetOrigin() + center * clipModel->GetAxis();
4116 
4117 		clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), center, impulse );
4118 	}
4119 }
4120 
4121 /*
4122 ===============
4123 idGameLocal::ProjectDecal
4124 ===============
4125 */
ProjectDecal(const idVec3 & origin,const idVec3 & dir,float depth,bool parallel,float size,const char * material,float angle)4126 void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) {
4127 	float s, c;
4128 	idMat3 axis, axistemp;
4129 	idFixedWinding winding;
4130 	idVec3 windingOrigin, projectionOrigin;
4131 
4132 	static idVec3 decalWinding[4] = {
4133 		idVec3(  1.0f,  1.0f, 0.0f ),
4134 		idVec3( -1.0f,  1.0f, 0.0f ),
4135 		idVec3( -1.0f, -1.0f, 0.0f ),
4136 		idVec3(  1.0f, -1.0f, 0.0f )
4137 	};
4138 
4139 	if ( !g_decals.GetBool() ) {
4140 		return;
4141 	}
4142 
4143 	// randomly rotate the decal winding
4144 	idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c );
4145 
4146 	// winding orientation
4147 	axis[2] = dir;
4148 	axis[2].Normalize();
4149 	axis[2].NormalVectors( axistemp[0], axistemp[1] );
4150 	axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s;
4151 	axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c;
4152 
4153 	windingOrigin = origin + depth * axis[2];
4154 	if ( parallel ) {
4155 		projectionOrigin = origin - depth * axis[2];
4156 	} else {
4157 		projectionOrigin = origin;
4158 	}
4159 
4160 	size *= 0.5f;
4161 
4162 	winding.Clear();
4163 	winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) );
4164 	winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) );
4165 	winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) );
4166 	winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) );
4167 	gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), gameLocal.slow.time /* _D3XP */ );
4168 }
4169 
4170 /*
4171 ==============
4172 idGameLocal::BloodSplat
4173 ==============
4174 */
BloodSplat(const idVec3 & origin,const idVec3 & dir,float size,const char * material)4175 void idGameLocal::BloodSplat( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) {
4176 	float halfSize = size * 0.5f;
4177 	idVec3 verts[] = {	idVec3( 0.0f, +halfSize, +halfSize ),
4178 						idVec3( 0.0f, +halfSize, -halfSize ),
4179 						idVec3( 0.0f, -halfSize, -halfSize ),
4180 						idVec3( 0.0f, -halfSize, +halfSize ) };
4181 	idTraceModel trm;
4182 	idClipModel mdl;
4183 	trace_t results;
4184 
4185 	// FIXME: get from damage def
4186 	if ( !g_bloodEffects.GetBool() ) {
4187 		return;
4188 	}
4189 
4190 	size = halfSize + random.RandomFloat() * halfSize;
4191 	trm.SetupPolygon( verts, 4 );
4192 	mdl.LoadModel( trm );
4193 	clip.Translation( results, origin, origin + dir * 64.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL );
4194 	ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material );
4195 }
4196 
4197 /*
4198 =============
4199 idGameLocal::SetCamera
4200 =============
4201 */
SetCamera(idCamera * cam)4202 void idGameLocal::SetCamera( idCamera *cam ) {
4203 	int i;
4204 	idEntity *ent;
4205 	idAI *ai;
4206 
4207 	// this should fix going into a cinematic when dead.. rare but happens
4208 	idPlayer *client = GetLocalPlayer();
4209 	if ( client->health <= 0 || client->AI_DEAD ) {
4210 		return;
4211 	}
4212 
4213 	camera = cam;
4214 	if ( camera ) {
4215 		inCinematic = true;
4216 
4217 		if ( skipCinematic && camera->spawnArgs.GetBool( "disconnect" ) ) {
4218 			camera->spawnArgs.SetBool( "disconnect", false );
4219 			cvarSystem->SetCVarFloat( "r_znear", 3.0f );
4220 			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
4221 			skipCinematic = false;
4222 			return;
4223 		}
4224 
4225 		if ( time > cinematicStopTime ) {
4226 			cinematicSkipTime = time + CINEMATIC_SKIP_DELAY;
4227 		}
4228 
4229 		// set r_znear so that transitioning into/out of the player's head doesn't clip through the view
4230 		cvarSystem->SetCVarFloat( "r_znear", 1.0f );
4231 
4232 		// hide all the player models
4233 		for( i = 0; i < numClients; i++ ) {
4234 			if ( entities[ i ] ) {
4235 				client = static_cast< idPlayer* >( entities[ i ] );
4236 				client->EnterCinematic();
4237 			}
4238 		}
4239 
4240 		if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) {
4241 			// kill any active monsters that are enemies of the player
4242 			for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
4243 				if ( ent->cinematic || ent->fl.isDormant ) {
4244 					// only kill entities that aren't needed for cinematics and aren't dormant
4245 					continue;
4246 				}
4247 
4248 				if ( ent->IsType( idAI::Type ) ) {
4249 					ai = static_cast<idAI *>( ent );
4250 					if ( !ai->GetEnemy() || !ai->IsActive() ) {
4251 						// no enemy, or inactive, so probably safe to ignore
4252 						continue;
4253 					}
4254 				} else if ( ent->IsType( idProjectile::Type ) ) {
4255 					// remove all projectiles
4256 				} else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) {
4257 					// remove anything marked to be removed during cinematics
4258 				} else {
4259 					// ignore everything else
4260 					continue;
4261 				}
4262 
4263 				// remove it
4264 				DPrintf( "removing '%s' for cinematic\n", ent->GetName() );
4265 				ent->PostEventMS( &EV_Remove, 0 );
4266 			}
4267 		}
4268 
4269 	} else {
4270 		inCinematic = false;
4271 		cinematicStopTime = time + msec;
4272 
4273 		// restore r_znear
4274 		cvarSystem->SetCVarFloat( "r_znear", 3.0f );
4275 
4276 		// show all the player models
4277 		for( i = 0; i < numClients; i++ ) {
4278 			if ( entities[ i ] ) {
4279 				idPlayer *client = static_cast< idPlayer* >( entities[ i ] );
4280 				client->ExitCinematic();
4281 			}
4282 		}
4283 	}
4284 }
4285 
4286 /*
4287 =============
4288 idGameLocal::GetCamera
4289 =============
4290 */
GetCamera(void) const4291 idCamera *idGameLocal::GetCamera( void ) const {
4292 	return camera;
4293 }
4294 
4295 /*
4296 =============
4297 idGameLocal::SkipCinematic
4298 =============
4299 */
SkipCinematic(void)4300 bool idGameLocal::SkipCinematic( void ) {
4301 	if ( camera ) {
4302 		if ( camera->spawnArgs.GetBool( "disconnect" ) ) {
4303 			camera->spawnArgs.SetBool( "disconnect", false );
4304 			cvarSystem->SetCVarFloat( "r_znear", 3.0f );
4305 			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
4306 			skipCinematic = false;
4307 			return false;
4308 		}
4309 
4310 		if ( camera->spawnArgs.GetBool( "instantSkip" ) ) {
4311 			camera->Stop();
4312 			return false;
4313 		}
4314 	}
4315 
4316 	soundSystem->SetMute( true );
4317 	if ( !skipCinematic ) {
4318 		skipCinematic = true;
4319 		cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() );
4320 	}
4321 
4322 	return true;
4323 }
4324 
4325 
4326 /*
4327 ======================
4328 idGameLocal::SpreadLocations
4329 
4330 Now that everything has been spawned, associate areas with location entities
4331 ======================
4332 */
SpreadLocations()4333 void idGameLocal::SpreadLocations() {
4334 	idEntity *ent;
4335 
4336 	// allocate the area table
4337 	int	numAreas = gameRenderWorld->NumAreas();
4338 	locationEntities = new idLocationEntity *[ numAreas ];
4339 	memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) );
4340 
4341 	// for each location entity, make pointers from every area it touches
4342 	for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
4343 		if ( !ent->IsType( idLocationEntity::Type ) ) {
4344 			continue;
4345 		}
4346 		idVec3	point = ent->spawnArgs.GetVector( "origin" );
4347 		int areaNum = gameRenderWorld->PointInArea( point );
4348 		if ( areaNum < 0 ) {
4349 			Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) );
4350 			continue;
4351 		}
4352 		if ( areaNum >= numAreas ) {
4353 			Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" );
4354 		}
4355 		if ( locationEntities[areaNum] ) {
4356 			Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ),
4357 				locationEntities[areaNum]->spawnArgs.GetString( "name" ) );
4358 			continue;
4359 		}
4360 		locationEntities[areaNum] = static_cast<idLocationEntity *>(ent);
4361 
4362 		// spread to all other connected areas
4363 		for ( int i = 0 ; i < numAreas ; i++ ) {
4364 			if ( i == areaNum ) {
4365 				continue;
4366 			}
4367 			if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) {
4368 				locationEntities[i] = static_cast<idLocationEntity *>(ent);
4369 			}
4370 		}
4371 	}
4372 }
4373 
4374 /*
4375 ===================
4376 idGameLocal::LocationForPoint
4377 
4378 The player checks the location each frame to update the HUD text display
4379 May return NULL
4380 ===================
4381 */
LocationForPoint(const idVec3 & point)4382 idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) {
4383 	if ( !locationEntities ) {
4384 		// before SpreadLocations() has been called
4385 		return NULL;
4386 	}
4387 
4388 	int areaNum = gameRenderWorld->PointInArea( point );
4389 	if ( areaNum < 0 ) {
4390 		return NULL;
4391 	}
4392 	if ( areaNum >= gameRenderWorld->NumAreas() ) {
4393 		Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" );
4394 	}
4395 
4396 	return locationEntities[ areaNum ];
4397 }
4398 
4399 /*
4400 ============
4401 idGameLocal::SetPortalState
4402 ============
4403 */
SetPortalState(qhandle_t portal,int blockingBits)4404 void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) {
4405 	idBitMsg outMsg;
4406 	byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
4407 
4408 	if ( !gameLocal.isClient ) {
4409 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
4410 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTAL );
4411 		outMsg.WriteInt( portal );
4412 		outMsg.WriteBits( blockingBits, NUM_RENDER_PORTAL_BITS );
4413 		networkSystem->ServerSendReliableMessage( -1, outMsg );
4414 	}
4415 	gameRenderWorld->SetPortalState( portal, blockingBits );
4416 }
4417 
4418 /*
4419 ============
4420 idGameLocal::sortSpawnPoints
4421 ============
4422 */
sortSpawnPoints(const void * ptr1,const void * ptr2)4423 int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) {
4424 	const spawnSpot_t *spot1 = static_cast<const spawnSpot_t *>( ptr1 );
4425 	const spawnSpot_t *spot2 = static_cast<const spawnSpot_t *>( ptr2 );
4426 	float diff;
4427 
4428 	diff = spot1->dist - spot2->dist;
4429 	if ( diff < 0.0f ) {
4430 		return 1;
4431 	} else if ( diff > 0.0f ) {
4432 		return -1;
4433 	} else {
4434 		return 0;
4435 	}
4436 }
4437 
4438 /*
4439 ===========
4440 idGameLocal::RandomizeInitialSpawns
4441 randomize the order of the initial spawns
4442 prepare for a sequence of initial player spawns
4443 ============
4444 */
RandomizeInitialSpawns(void)4445 void idGameLocal::RandomizeInitialSpawns( void ) {
4446 	spawnSpot_t	spot;
4447 	int i, j;
4448 #ifdef CTF
4449 	int k;
4450 #endif
4451 
4452 	idEntity *ent;
4453 
4454 	if ( !isMultiplayer || isClient ) {
4455 		return;
4456 	}
4457 	spawnSpots.Clear();
4458 	initialSpots.Clear();
4459 #ifdef CTF
4460 	teamSpawnSpots[0].Clear();
4461 	teamSpawnSpots[1].Clear();
4462 	teamInitialSpots[0].Clear();
4463 	teamInitialSpots[1].Clear();
4464 #endif
4465 
4466 	spot.dist = 0;
4467 	spot.ent = FindEntityUsingDef( NULL, "info_player_deathmatch" );
4468 	while( spot.ent ) {
4469 #ifdef CTF
4470 		spot.ent->spawnArgs.GetInt( "team", "-1", spot.team );
4471 
4472 		if ( mpGame.IsGametypeFlagBased() ) /* CTF */
4473 		{
4474 			if ( spot.team == 0 || spot.team == 1 )
4475 				teamSpawnSpots[spot.team].Append( spot );
4476 			else
4477 				common->Warning( "info_player_deathmatch : invalid or no team attached to spawn point\n");
4478 		}
4479 #endif
4480 		spawnSpots.Append( spot );
4481 		if ( spot.ent->spawnArgs.GetBool( "initial" ) ) {
4482 #ifdef CTF
4483 			if ( mpGame.IsGametypeFlagBased() ) /* CTF */
4484 			{
4485 				assert( spot.team == 0 || spot.team == 1 );
4486 				teamInitialSpots[ spot.team ].Append( spot.ent );
4487 			}
4488 #endif
4489 
4490 			initialSpots.Append( spot.ent );
4491 		}
4492 		spot.ent = FindEntityUsingDef( spot.ent, "info_player_deathmatch" );
4493 	}
4494 
4495 #ifdef CTF
4496 	if ( mpGame.IsGametypeFlagBased() ) /* CTF */
4497 	{
4498 		if ( !teamSpawnSpots[0].Num() )
4499 			common->Warning( "red team : no info_player_deathmatch in map" );
4500 		if ( !teamSpawnSpots[1].Num() )
4501 			common->Warning( "blue team : no info_player_deathmatch in map" );
4502 
4503 		if ( !teamSpawnSpots[0].Num() || !teamSpawnSpots[1].Num() )
4504 			return;
4505 	}
4506 #endif
4507 
4508 	if ( !spawnSpots.Num() ) {
4509 		common->Warning( "no info_player_deathmatch in map" );
4510 		return;
4511 	}
4512 
4513 #ifdef CTF
4514 	if ( mpGame.IsGametypeFlagBased() ) /* CTF */
4515 	{
4516 		common->Printf( "red team : %d spawns (%d initials)\n", teamSpawnSpots[ 0 ].Num(), teamInitialSpots[ 0 ].Num() );
4517 		// if there are no initial spots in the map, consider they can all be used as initial
4518 		if ( !teamInitialSpots[ 0 ].Num() ) {
4519 			common->Warning( "red team : no info_player_deathmatch entities marked initial in map" );
4520 			for ( i = 0; i < teamSpawnSpots[ 0 ].Num(); i++ ) {
4521 				teamInitialSpots[ 0 ].Append( teamSpawnSpots[ 0 ][ i ].ent );
4522 			}
4523 		}
4524 
4525 		common->Printf( "blue team : %d spawns (%d initials)\n", teamSpawnSpots[ 1 ].Num(), teamInitialSpots[ 1 ].Num() );
4526 		// if there are no initial spots in the map, consider they can all be used as initial
4527 		if ( !teamInitialSpots[ 1 ].Num() ) {
4528 			common->Warning( "blue team : no info_player_deathmatch entities marked initial in map" );
4529 			for ( i = 0; i < teamSpawnSpots[ 1 ].Num(); i++ ) {
4530 				teamInitialSpots[ 1 ].Append( teamSpawnSpots[ 1 ][ i ].ent );
4531 			}
4532 		}
4533 	}
4534 #endif
4535 
4536 
4537 	common->Printf( "%d spawns (%d initials)\n", spawnSpots.Num(), initialSpots.Num() );
4538 	// if there are no initial spots in the map, consider they can all be used as initial
4539 	if ( !initialSpots.Num() ) {
4540 		common->Warning( "no info_player_deathmatch entities marked initial in map" );
4541 		for ( i = 0; i < spawnSpots.Num(); i++ ) {
4542 			initialSpots.Append( spawnSpots[ i ].ent );
4543 		}
4544 	}
4545 
4546 #ifdef CTF
4547 	for ( k = 0; k < 2; k++ )
4548 	for ( i = 0; i < teamInitialSpots[ k ].Num(); i++ ) {
4549 		j = random.RandomInt( teamInitialSpots[ k ].Num() );
4550 		ent = teamInitialSpots[ k ][ i ];
4551 		teamInitialSpots[ k ][ i ] = teamInitialSpots[ k ][ j ];
4552 		teamInitialSpots[ k ][ j ] = ent;
4553 	}
4554 #endif
4555 
4556 	for ( i = 0; i < initialSpots.Num(); i++ ) {
4557 		j = random.RandomInt( initialSpots.Num() );
4558 		ent = initialSpots[ i ];
4559 		initialSpots[ i ] = initialSpots[ j ];
4560 		initialSpots[ j ] = ent;
4561 	}
4562 	// reset the counter
4563 	currentInitialSpot = 0;
4564 
4565 #ifdef CTF
4566 	teamCurrentInitialSpot[0] = 0;
4567 	teamCurrentInitialSpot[1] = 0;
4568 #endif
4569 }
4570 
4571 /*
4572 ===========
4573 idGameLocal::SelectInitialSpawnPoint
4574 spectators are spawned randomly anywhere
4575 in-game clients are spawned based on distance to active players (randomized on the first half)
4576 upon map restart, initial spawns are used (randomized ordered list of spawns flagged "initial")
4577   if there are more players than initial spots, overflow to regular spawning
4578 ============
4579 */
SelectInitialSpawnPoint(idPlayer * player)4580 idEntity *idGameLocal::SelectInitialSpawnPoint( idPlayer *player ) {
4581 	int				i, j, which;
4582 	spawnSpot_t		spot;
4583 	idVec3			pos;
4584 	float			dist;
4585 	bool			alone;
4586 
4587 #ifdef CTF
4588 	if ( !isMultiplayer || !spawnSpots.Num() || ( mpGame.IsGametypeFlagBased() && ( !teamSpawnSpots[0].Num() || !teamSpawnSpots[1].Num() ) ) ) { /* CTF */
4589 #else
4590 	if ( !isMultiplayer || !spawnSpots.Num() ) {
4591 #endif
4592 		spot.ent = FindEntityUsingDef( NULL, "info_player_start" );
4593 		if ( !spot.ent ) {
4594 			Error( "No info_player_start on map.\n" );
4595 		}
4596 		return spot.ent;
4597 	}
4598 
4599 #ifdef CTF
4600 	bool useInitialSpots = false;
4601 	if ( mpGame.IsGametypeFlagBased() ) { /* CTF */
4602 		assert( player->team == 0 || player->team == 1 );
4603 		useInitialSpots = player->useInitialSpawns && teamCurrentInitialSpot[ player->team ] < teamInitialSpots[ player->team ].Num();
4604 	} else {
4605 		useInitialSpots = player->useInitialSpawns && currentInitialSpot < initialSpots.Num();
4606 	}
4607 #endif
4608 
4609 	if ( player->spectating ) {
4610 		// plain random spot, don't bother
4611 		return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ].ent;
4612 #ifdef CTF
4613 	} else if ( useInitialSpots ) {
4614 		if ( mpGame.IsGametypeFlagBased() ) { /* CTF */
4615 			assert( player->team == 0 || player->team == 1 );
4616 			player->useInitialSpawns = false;							// only use the initial spawn once
4617 			return teamInitialSpots[ player->team ][ teamCurrentInitialSpot[ player->team ]++ ];
4618 		}
4619 		return initialSpots[ currentInitialSpot++ ];
4620 #else
4621 	} else if ( player->useInitialSpawns && currentInitialSpot < initialSpots.Num() ) {
4622 		return initialSpots[ currentInitialSpot++ ];
4623 #endif
4624 	} else {
4625 		// check if we are alone in map
4626 		alone = true;
4627 		for ( j = 0; j < MAX_CLIENTS; j++ ) {
4628 			if ( entities[ j ] && entities[ j ] != player ) {
4629 				alone = false;
4630 				break;
4631 			}
4632 		}
4633 		if ( alone ) {
4634 #ifdef CTF
4635 			if ( mpGame.IsGametypeFlagBased() ) /* CTF */
4636 			{
4637 				assert( player->team == 0 || player->team == 1 );
4638 				return teamSpawnSpots[ player->team ][ random.RandomInt( teamSpawnSpots[ player->team ].Num() ) ].ent;
4639 			}
4640 #endif
4641 			// don't do distance-based
4642 			return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ].ent;
4643 		}
4644 
4645 #ifdef CTF
4646 		if ( mpGame.IsGametypeFlagBased() ) /* CTF */
4647 		{
4648 			// TODO : make as reusable method, same code as below
4649 			int team = player->team;
4650 			assert( team == 0 || team == 1 );
4651 
4652 			// find the distance to the closest active player for each spawn spot
4653 			for( i = 0; i < teamSpawnSpots[ team ].Num(); i++ ) {
4654 				pos = teamSpawnSpots[ team ][ i ].ent->GetPhysics()->GetOrigin();
4655 
4656 				// skip initial spawn points for CTF
4657 				if ( teamSpawnSpots[ team ][ i ].ent->spawnArgs.GetBool("initial") ) {
4658 					teamSpawnSpots[ team ][ i ].dist = 0x0;
4659 					continue;
4660 				}
4661 
4662 				teamSpawnSpots[ team ][ i ].dist = 0x7fffffff;
4663 
4664 				for( j = 0; j < MAX_CLIENTS; j++ ) {
4665 					if ( !entities[ j ] || !entities[ j ]->IsType( idPlayer::Type )
4666 						|| entities[ j ] == player
4667 						|| static_cast< idPlayer * >( entities[ j ] )->spectating ) {
4668 						continue;
4669 					}
4670 
4671 					dist = ( pos - entities[ j ]->GetPhysics()->GetOrigin() ).LengthSqr();
4672 					if ( dist < teamSpawnSpots[ team ][ i ].dist ) {
4673 						teamSpawnSpots[ team ][ i ].dist = dist;
4674 					}
4675 				}
4676 			}
4677 
4678 			// sort the list
4679 			qsort( ( void * )teamSpawnSpots[ team ].Ptr(), teamSpawnSpots[ team ].Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints );
4680 
4681 			// choose a random one in the top half
4682 			which = random.RandomInt( teamSpawnSpots[ team ].Num() / 2 );
4683 			spot = teamSpawnSpots[ team ][ which ];
4684 //			assert( teamSpawnSpots[ team ][ which ].dist != 0 );
4685 
4686 			return spot.ent;
4687 		}
4688 #endif
4689 
4690 		// find the distance to the closest active player for each spawn spot
4691 		for( i = 0; i < spawnSpots.Num(); i++ ) {
4692 			pos = spawnSpots[ i ].ent->GetPhysics()->GetOrigin();
4693 			spawnSpots[ i ].dist = 0x7fffffff;
4694 			for( j = 0; j < MAX_CLIENTS; j++ ) {
4695 				if ( !entities[ j ] || !entities[ j ]->IsType( idPlayer::Type )
4696 					|| entities[ j ] == player
4697 					|| static_cast< idPlayer * >( entities[ j ] )->spectating ) {
4698 					continue;
4699 				}
4700 
4701 				dist = ( pos - entities[ j ]->GetPhysics()->GetOrigin() ).LengthSqr();
4702 				if ( dist < spawnSpots[ i ].dist ) {
4703 					spawnSpots[ i ].dist = dist;
4704 				}
4705 			}
4706 		}
4707 
4708 		// sort the list
4709 		qsort( ( void * )spawnSpots.Ptr(), spawnSpots.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints );
4710 
4711 		// choose a random one in the top half
4712 		which = random.RandomInt( spawnSpots.Num() / 2 );
4713 		spot = spawnSpots[ which ];
4714 	}
4715 	return spot.ent;
4716 }
4717 
4718 /*
4719 ================
4720 idGameLocal::UpdateServerInfoFlags
4721 ================
4722 */
4723 void idGameLocal::UpdateServerInfoFlags() {
4724 	gameType = GAME_SP;
4725 	if ( ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "deathmatch" ) == 0 ) ) {
4726 		gameType = GAME_DM;
4727 	} else if ( ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Tourney" ) == 0 ) ) {
4728 		gameType = GAME_TOURNEY;
4729 	} else if ( ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Team DM" ) == 0 ) ) {
4730 		gameType = GAME_TDM;
4731 	} else if ( ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Last Man" ) == 0 ) ) {
4732 		gameType = GAME_LASTMAN;
4733 	}
4734 #ifdef CTF
4735 	else if ( ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "CTF" ) == 0 ) ) {
4736 		gameType = GAME_CTF;
4737 	}
4738 #endif
4739 
4740 	if ( gameType == GAME_LASTMAN ) {
4741 		if ( !serverInfo.GetInt( "si_warmup" ) ) {
4742 			common->Warning( "Last Man Standing - forcing warmup on" );
4743 			serverInfo.SetInt( "si_warmup", 1 );
4744 		}
4745 		if ( serverInfo.GetInt( "si_fraglimit" ) <= 0 ) {
4746 			common->Warning( "Last Man Standing - setting fraglimit 1" );
4747 			serverInfo.SetInt( "si_fraglimit", 1 );
4748 		}
4749 	}
4750 }
4751 
4752 
4753 /*
4754 ================
4755 idGameLocal::SetGlobalMaterial
4756 ================
4757 */
4758 void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) {
4759 	globalMaterial = mat;
4760 }
4761 
4762 /*
4763 ================
4764 idGameLocal::GetGlobalMaterial
4765 ================
4766 */
4767 const idMaterial *idGameLocal::GetGlobalMaterial() {
4768 	return globalMaterial;
4769 }
4770 
4771 /*
4772 ================
4773 idGameLocal::GetSpawnId
4774 ================
4775 */
4776 int idGameLocal::GetSpawnId( const idEntity* ent ) const {
4777 	return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber;
4778 }
4779 
4780 /*
4781 ================
4782 idGameLocal::ThrottleUserInfo
4783 ================
4784 */
4785 void idGameLocal::ThrottleUserInfo( void ) {
4786 	mpGame.ThrottleUserInfo();
4787 }
4788 
4789 #ifdef _D3XP
4790 /*
4791 =================
4792 idPlayer::SetPortalSkyEnt
4793 =================
4794 */
4795 void idGameLocal::SetPortalSkyEnt( idEntity *ent ) {
4796 	portalSkyEnt = ent;
4797 }
4798 
4799 /*
4800 =================
4801 idPlayer::IsPortalSkyAcive
4802 =================
4803 */
4804 bool idGameLocal::IsPortalSkyAcive() {
4805 	return portalSkyActive;
4806 }
4807 
4808 /*
4809 ===========
4810 idGameLocal::SelectTimeGroup
4811 ============
4812 */
4813 void idGameLocal::SelectTimeGroup( int timeGroup ) {
4814 	if ( timeGroup ) {
4815 		fast.Get( time, previousTime, msec, framenum, realClientTime );
4816 	} else {
4817 		slow.Get( time, previousTime, msec, framenum, realClientTime );
4818 	}
4819 }
4820 
4821 /*
4822 ===========
4823 idGameLocal::GetTimeGroupTime
4824 ============
4825 */
4826 int idGameLocal::GetTimeGroupTime( int timeGroup ) {
4827 	if ( timeGroup ) {
4828 		return fast.time;
4829 	} else {
4830 		return slow.time;
4831 	}
4832 }
4833 
4834 /*
4835 ===============
4836 idGameLocal::GetBestGameType
4837 ===============
4838 */
4839 void idGameLocal::GetBestGameType( const char* map, const char* gametype, char buf[ MAX_STRING_CHARS ] ) {
4840 	idStr aux = mpGame.GetBestGametype( map, gametype );
4841 	strncpy( buf, aux.c_str(), MAX_STRING_CHARS );
4842 	buf[ MAX_STRING_CHARS - 1 ] = '\0';
4843 }
4844 
4845 /*
4846 ===========
4847 idGameLocal::ComputeSlowMsec
4848 ============
4849 */
4850 void idGameLocal::ComputeSlowMsec() {
4851 	idPlayer *player;
4852 	bool powerupOn;
4853 	float delta;
4854 
4855 	// check if we need to do a quick reset
4856 	if ( quickSlowmoReset ) {
4857 		quickSlowmoReset = false;
4858 
4859 		// stop the sounds
4860 		if ( gameSoundWorld ) {
4861 			gameSoundWorld->SetSlowmo( false );
4862 			gameSoundWorld->SetSlowmoSpeed( 1 );
4863 		}
4864 
4865 		// stop the state
4866 		slowmoState = SLOWMO_STATE_OFF;
4867 		slowmoMsec = USERCMD_MSEC;
4868 	}
4869 
4870 	// check the player state
4871 	player = GetLocalPlayer();
4872 	powerupOn = false;
4873 
4874 	if ( player && player->PowerUpActive( HELLTIME ) ) {
4875 		powerupOn = true;
4876 	}
4877 	else if ( g_enableSlowmo.GetBool() ) {
4878 		powerupOn = true;
4879 	}
4880 
4881 	// determine proper slowmo state
4882 	if ( powerupOn && slowmoState == SLOWMO_STATE_OFF ) {
4883 		slowmoState = SLOWMO_STATE_RAMPUP;
4884 
4885 		slowmoMsec = msec;
4886 		if ( gameSoundWorld ) {
4887 			gameSoundWorld->SetSlowmo( true );
4888 			gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC );
4889 		}
4890 	}
4891 	else if ( !powerupOn && slowmoState == SLOWMO_STATE_ON ) {
4892 		slowmoState = SLOWMO_STATE_RAMPDOWN;
4893 
4894 		// play the stop sound
4895 		if ( player ) {
4896 			player->PlayHelltimeStopSound();
4897 		}
4898 	}
4899 
4900 	// do any necessary ramping
4901 	if ( slowmoState == SLOWMO_STATE_RAMPUP ) {
4902 		delta = 4 - slowmoMsec;
4903 
4904 		if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) {
4905 			slowmoMsec = 4;
4906 			slowmoState = SLOWMO_STATE_ON;
4907 		}
4908 		else {
4909 			slowmoMsec += delta * g_slowmoStepRate.GetFloat();
4910 		}
4911 
4912 		if ( gameSoundWorld ) {
4913 			gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC );
4914 		}
4915 	}
4916 	else if ( slowmoState == SLOWMO_STATE_RAMPDOWN ) {
4917 		delta = 16 - slowmoMsec;
4918 
4919 		if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) {
4920 			slowmoMsec = 16;
4921 			slowmoState = SLOWMO_STATE_OFF;
4922 			if ( gameSoundWorld ) {
4923 				gameSoundWorld->SetSlowmo( false );
4924 			}
4925 		}
4926 		else {
4927 			slowmoMsec += delta * g_slowmoStepRate.GetFloat();
4928 		}
4929 
4930 		if ( gameSoundWorld ) {
4931 			gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC );
4932 		}
4933 	}
4934 }
4935 
4936 /*
4937 ===========
4938 idGameLocal::ResetSlowTimeVars
4939 ============
4940 */
4941 void idGameLocal::ResetSlowTimeVars() {
4942 	msec				= USERCMD_MSEC;
4943 	slowmoMsec			= USERCMD_MSEC;
4944 	slowmoState			= SLOWMO_STATE_OFF;
4945 
4946 	fast.framenum		= 0;
4947 	fast.previousTime	= 0;
4948 	fast.time			= 0;
4949 	fast.msec			= USERCMD_MSEC;
4950 
4951 	slow.framenum		= 0;
4952 	slow.previousTime	= 0;
4953 	slow.time			= 0;
4954 	slow.msec			= USERCMD_MSEC;
4955 }
4956 
4957 /*
4958 ===========
4959 idGameLocal::QuickSlowmoReset
4960 ============
4961 */
4962 void idGameLocal::QuickSlowmoReset() {
4963 	quickSlowmoReset = true;
4964 }
4965 
4966 /*
4967 ===============
4968 idGameLocal::NeedRestart
4969 ===============
4970 */
4971 bool idGameLocal::NeedRestart() {
4972 
4973 	idDict		newInfo;
4974 	const idKeyValue *keyval, *keyval2;
4975 
4976 	newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO );
4977 
4978 	for ( int i = 0; i < newInfo.GetNumKeyVals(); i++ ) {
4979 		keyval = newInfo.GetKeyVal( i );
4980 		keyval2 = serverInfo.FindKey( keyval->GetKey() );
4981 		if ( !keyval2 ) {
4982 			return true;
4983 		}
4984 		// a select set of si_ changes will cause a full restart of the server
4985 		if ( keyval->GetValue().Cmp( keyval2->GetValue() ) && ( !keyval->GetKey().Cmp( "si_pure" ) || !keyval->GetKey().Cmp( "si_map" ) ) ) {
4986 			return true;
4987 		}
4988 	}
4989 	return false;
4990 }
4991 
4992 #endif
4993 
4994 /*
4995 ================
4996 idGameLocal::GetClientStats
4997 ================
4998 */
4999 void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) {
5000 	mpGame.PlayerStats( clientNum, data, len );
5001 }
5002 
5003 
5004 /*
5005 ================
5006 idGameLocal::SwitchTeam
5007 ================
5008 */
5009 void idGameLocal::SwitchTeam( int clientNum, int team ) {
5010 
5011 	idPlayer *   player;
5012 	player = static_cast< idPlayer * >( entities[ clientNum ] );
5013 	int oldTeam = player->team ;
5014 
5015 	// Put in spectator mode
5016 	if ( team == -1 ) {
5017 		static_cast< idPlayer * >( entities[ clientNum ] )->Spectate( true );
5018 	}
5019 	// Switch to a team
5020 	else {
5021 		mpGame.SwitchToTeam ( clientNum, oldTeam, team );
5022 	}
5023 	player->forceRespawn = true ;
5024 }
5025 
5026 /*
5027 ===============
5028 idGameLocal::GetMapLoadingGUI
5029 ===============
5030 */
5031 void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { }
5032