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