DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Game_local.cpp (134111B)


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