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