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