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