#include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" // RAVEN BEGIN #include "../bse/BSEInterface.h" #include "Projectile.h" #include "client/ClientEffect.h" #include "ai/AI.h" #include "ai/AI_Manager.h" #include "ai/AAS_tactical.h" #include "Game_Log.h" // RAVEN END //#define UI_DEBUG 1 #ifdef GAME_DLL idSys * sys = NULL; idCommon * common = NULL; idCmdSystem * cmdSystem = NULL; idCVarSystem * cvarSystem = NULL; idFileSystem * fileSystem = NULL; idNetworkSystem * networkSystem = NULL; idRenderSystem * renderSystem = NULL; idSoundSystem * soundSystem = NULL; idRenderModelManager * renderModelManager = NULL; idUserInterfaceManager * uiManager = NULL; idDeclManager * declManager = NULL; idAASFileManager * AASFileManager = NULL; idCollisionModelManager * collisionModelManager = NULL; // RAVEN BEGIN // jscott: game interface to the fx system rvBSEManager * bse = NULL; // RAVEN END idCVar * idCVar::staticVars = NULL; // RAVEN BEGIN // rjohnson: new help system for cvar ui idCVarHelp * idCVarHelp::staticCVarHelps = NULL; idCVarHelp * idCVarHelp::staticCVarHelpsTail = NULL; // RAVEN END idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" ); #endif idRenderWorld * gameRenderWorld = NULL; // all drawing is done to this world static gameExport_t gameExport; // global animation lib // RAVEN BEGIN // jsinger: changed to a pointer to prevent its constructor from allocating // memory before the unified allocator could be initialized idAnimManager *animationLib = NULL; // RAVEN END // the rest of the engine will only reference the "game" variable, while all local aspects stay hidden idGameLocal gameLocal; idGame * game = &gameLocal; // statically pointed at an idGameLocal const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = { "none", "metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic", "ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15" }; /* =========== GetGameAPI ============ */ #if __GNUC__ >= 4 #pragma GCC visibility push(default) #endif extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) { if ( import->version == GAME_API_VERSION ) { // set interface pointers used by the game sys = import->sys; common = import->common; cmdSystem = import->cmdSystem; cvarSystem = import->cvarSystem; fileSystem = import->fileSystem; networkSystem = import->networkSystem; renderSystem = import->renderSystem; soundSystem = import->soundSystem; renderModelManager = import->renderModelManager; uiManager = import->uiManager; declManager = import->declManager; AASFileManager = import->AASFileManager; collisionModelManager = import->collisionModelManager; // RAVEN BEGIN // jscott: import the fx system bse = import->bse; // RAVEN END // RAVEN BEGIN // dluetscher: import the memory system variables #ifdef _RV_MEM_SYS_SUPPORT ::currentHeapArena = import->heapArena; rvSetAllSysHeaps( import->systemHeapArray ); #endif // RAVEN END } // set interface pointers used by idLib idLib::sys = sys; idLib::common = common; idLib::cvarSystem = cvarSystem; idLib::fileSystem = fileSystem; // setup export interface gameExport.version = GAME_API_VERSION; gameExport.game = game; gameExport.gameEdit = gameEdit; // RAVEN BEGIN // bdube: game log gameExport.gameLog = gameLog; // RAVEN END return &gameExport; } #if __GNUC__ >= 4 #pragma GCC visibility pop #endif /* =========== TestGameAPI ============ */ void TestGameAPI( void ) { gameImport_t testImport; gameExport_t testExport; testImport.sys = ::sys; testImport.common = ::common; testImport.cmdSystem = ::cmdSystem; testImport.cvarSystem = ::cvarSystem; testImport.fileSystem = ::fileSystem; testImport.networkSystem = ::networkSystem; testImport.renderSystem = ::renderSystem; testImport.soundSystem = ::soundSystem; testImport.renderModelManager = ::renderModelManager; testImport.uiManager = ::uiManager; testImport.declManager = ::declManager; testImport.AASFileManager = ::AASFileManager; testImport.collisionModelManager = ::collisionModelManager; // RAVEN BEGIN // jscott: import the fx system testImport.bse = ::bse; // RAVEN END testExport = *GetGameAPI( &testImport ); } /* ================ idGameLocal::BuildModList ================ */ void idGameLocal::BuildModList( ) { int i; idStr currentMod; int numServers = networkSystem->GetNumScannedServers(); if ( filterMod >= 0 && filterMod < modList.Num() ) { currentMod = modList[ filterMod ]; } else { currentMod = ""; } modList.Clear(); for (i = 0; i < numServers; i++) { const scannedServer_t *server; idStr modname; server = networkSystem->GetScannedServerInfo( i ); server->serverInfo.GetString( "fs_game", "", modname ); modname.ToLower(); modList.AddUnique( modname ); } modList.Sort(); if ( modList.Num() > 0 && (modList[ 0 ].Cmp( "" ) == 0) ) { modList.RemoveIndex( 0 ); } filterMod = modList.Num(); for (i = 0; i < modList.Num(); i++) { if ( modList[ i ].Icmp( currentMod ) == 0 ) { filterMod = i; } } } /* ================ FilterByMod ================ */ static int FilterByMod( const int* serverIndex ) { const scannedServer_t *server; if ( gameLocal.filterMod < 0 || gameLocal.filterMod >= gameLocal.modList.Num() ) { return (int)false; } server = networkSystem->GetScannedServerInfo( *serverIndex ); return (int)(gameLocal.modList[ gameLocal.filterMod ].Icmp( server->serverInfo.GetString( "fs_game" ) ) != 0); } static sortInfo_t filterByMod = { SC_ALL, NULL, FilterByMod, "#str_123006" }; /* =========== idGameLocal::idGameLocal ============ */ idGameLocal::idGameLocal() { Clear(); } /* =========== idGameLocal::Clear ============ */ void idGameLocal::Clear( void ) { int i; serverInfo.Clear(); numClients = 0; for ( i = 0; i < MAX_CLIENTS; i++ ) { userInfo[i].Clear(); persistentPlayerInfo[i].Clear(); } usercmds = NULL; memset( entities, 0, sizeof( entities ) ); memset( spawnIds, -1, sizeof( spawnIds ) ); firstFreeIndex = 0; num_entities = 0; spawnedEntities.Clear(); activeEntities.Clear(); numEntitiesToDeactivate = 0; sortPushers = false; sortTeamMasters = false; persistentLevelInfo.Clear(); memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); random.SetSeed( 0 ); world = NULL; frameCommandThread = NULL; testmodel = NULL; // RAVEN BEGIN // bdube: not using id effects // testFx = NULL; // ddynerman: multiple clip worlds ShutdownInstances(); // mwhitlock: Dynamic memory consolidation clip.Clear(); // RAVEN END for ( i = 0; i < MAX_CLIENTS; i++ ) { clientsPVS[ i ].i = -1; clientsPVS[ i ].h = -1; } freePlayerPVS = false; pvs.Shutdown(); sessionCommand.Clear(); locationEntities = NULL; editEntities = NULL; entityHash.Clear( 1024, MAX_GENTITIES ); inCinematic = false; cinematicSkipTime = 0; cinematicStopTime = 0; cinematicMaxSkipTime = 0; framenum = 0; previousTime = 0; time = 0; vacuumAreaNum = 0; // RAVEN BEGIN // abahr gravityInfo.Clear(); scriptObjectProxies.Clear(); // RAVEN END mapFileName.Clear(); // RAVEN BEGIN // rjohnson: entity usage stats mapFileNameStripped.Clear(); // RAVEN END mapFile = NULL; spawnCount = INITIAL_SPAWN_COUNT; memset( isMapEntity, 0, sizeof( bool ) * MAX_GENTITIES ); camera = NULL; // RAVEN BEGIN // jscott: for portal skies portalSky = NULL; // RAVEN END aasList.Clear(); aasNames.Clear(); // RAVEN BEGIN // bdube: added lastAIAlertEntity = NULL; lastAIAlertEntityTime = 0; lastAIAlertActor = NULL; lastAIAlertActorTime = 0; // RAVEN END spawnArgs.Clear(); gravity.Set( 0, 0, -1 ); playerPVS.i = -1; playerPVS.h = -1; playerConnectedAreas.i = -1; playerConnectedAreas.h = -1; gamestate = GAMESTATE_UNINITIALIZED; skipCinematic = false; influenceActive = false; localClientNum = 0; isMultiplayer = false; isServer = false; isClient = false; realClientTime = 0; isNewFrame = true; entityDefBits = 0; nextGibTime = 0; // RITUAL BEGIN // squirrel: added DeadZone multiplayer mode unFreezeTime = 0; isFrozen = false; // RITUAL END globalMaterial = NULL; newInfo.Clear(); lastGUIEnt = NULL; lastGUI = 0; memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); memset( clientPVS, 0, sizeof( clientPVS ) ); memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); eventQueue.Init(); clientSpawnCount = INITIAL_SPAWN_COUNT; clientSpawnedEntities.Clear(); memset( clientEntities, 0, sizeof( clientEntities ) ); memset( clientSpawnIds, -1, sizeof( clientSpawnIds ) ); gameDebug.Shutdown(); gameLogLocal.Shutdown(); currentThinkingEntity = NULL; memset( lagometer, 0, sizeof( lagometer ) ); demoState = DEMO_NONE; serverDemo = false; timeDemo = false; memset( &usercmd, 0, sizeof( usercmd ) ); memset( &oldUsercmd, 0, sizeof( oldUsercmd ) ); followPlayer = -1; // start free flying demo_hud = NULL; demo_mphud = NULL; demo_cursor = NULL; demo_protocol = 0; instancesEntityIndexWatermarks.Clear(); clientInstanceFirstFreeIndex = MAX_CLIENTS; minSpawnIndex = MAX_CLIENTS; modList.Clear(); filterMod = -1; if ( networkSystem ) { networkSystem->UseSortFunction( filterByMod, false ); } } /* =========== idGameLocal::Init initialize the game object, only happens once at startup, not each level load ============ */ // RAVEN BEGIN // jsinger: attempt to eliminate cross-DLL allocation issues extern idHashTable *visemeTable100; extern idHashTable *visemeTable66; extern idHashTable *visemeTable33; #ifdef RV_UNIFIED_ALLOCATOR void idGameLocal::Init( void *(*allocator)(size_t size), void (*deallocator)( void *ptr ), size_t (*msize)(void *ptr) ) { #else void idGameLocal::Init( void ) { #endif // RAVEN END const idDict *dict; idAAS *aas; #ifndef GAME_DLL TestGameAPI(); #else mHz = common->GetUserCmdHz(); msec = common->GetUserCmdMSec(); // RAVEN BEGIN // jsinger: attempt to eliminate cross-DLL allocation issues #ifdef RV_UNIFIED_ALLOCATOR Memory::InitAllocator(allocator, deallocator, msize); #endif // RAVEN END // initialize idLib idLib::Init(); // register static cvars declared in the game idCVar::RegisterStaticVars(); // initialize processor specific SIMD idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); #endif // RAVEN BEGIN // jsinger: these need to be initialized after the InitAllocator call above in order // to avoid crashes when the allocator is used. animationLib = new idAnimManager(); visemeTable100 = new idHashTable; visemeTable66 = new idHashTable; visemeTable33 = new idHashTable; // RAVEN END Printf( "------------- Initializing Game -------------\n" ); Printf( "gamename: %s\n", GAME_VERSION ); Printf( "gamedate: %s\n", __DATE__ ); // RAVEN BEGIN // rjohnson: new help system for cvar ui idCVarHelp::RegisterStatics(); // jsinger: added to support serialization/deserialization of binary decls #ifdef RV_BINARYDECLS idStr prefix=""; if(cvarSystem->GetCVarBool("com_binaryDeclRead")) { prefix = "binary/"; } declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator, idDeclStreamAllocator ); //declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator, idDeclStreamAllocator ); declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator, idDeclStreamAllocator ); declManager->RegisterDeclFolderWrapper( prefix + "def", ".def", DECL_ENTITYDEF ); declManager->RegisterDeclFolderWrapper( prefix + "af", ".af", DECL_AF ); #else // RAVEN END // register game specific decl types declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); // RAVEN BEGIN // rjohnson: camera is now contained in a def for frame commands declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator ); // RAVEN END // register game specific decl folders // RAVEN BEGIN #ifndef RV_SINGLE_DECL_FILE declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); // bdube: not used in quake 4 // declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); // declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); // declManager->RegisterDeclFolderWrapper( "newpdas", ".pda", DECL_PDA ); #else if(!cvarSystem->GetCVarBool("com_SingleDeclFile")) { declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); } else { // loads the second set of decls from the file which will contain // modles, cameras declManager->LoadDeclsFromFile(); declManager->FinishLoadingDecls(); } #endif #endif // RV_BINARYDECLS // RAVEN END cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); Clear(); idEvent::Init(); // RAVEN BEGIN // jnewquist: Register subclasses explicitly so they aren't dead-stripped idClass::RegisterClasses(); // RAVEN END idClass::Init(); InitConsoleCommands(); // load default scripts program.Startup( SCRIPT_DEFAULT ); // set up the aas // RAVEN BEGIN // ddynerman: added false as 2nd parameter, otherwise def will be created dict = FindEntityDefDict( "aas_types", false ); // RAVEN END if ( !dict ) { Error( "Unable to find entityDef for 'aas_types'" ); } // allocate space for the aas const idKeyValue *kv = dict->MatchPrefix( "type" ); while( kv != NULL ) { // RAVEN BEGIN // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG(tag,MA_AAS); // RAVEN END aas = idAAS::Alloc(); aasList.Append( aas ); aasNames.Append( kv->GetValue() ); kv = dict->MatchPrefix( "type", kv ); } gamestate = GAMESTATE_NOMAP; Printf( "...%d aas types\n", aasList.Num() ); Printf( "game initialized.\n" ); Printf( "---------------------------------------------\n" ); // RAVEN BEGIN // bdube: debug stuff gameDebug.Init(); gameLogLocal.Init(); // jscott: facial animation init if( !FAS_Init( "annosoft" ) ) { common->Warning( "Failed to load viseme file" ); } // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG(tag,MA_RENDER); // shouchard: make sure ban list starts out in a known state banListLoaded = false; banListChanged = false; memset( clientGuids, 0, sizeof( clientGuids ) ); // RAVEN END // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation #if defined(_RV_MEM_SYS_SUPPORT) for(int i=0;iAddSortFunction( filterByMod ); } /* =========== idGameLocal::Shutdown shut down the entire game ============ */ void idGameLocal::Shutdown( void ) { int i; if ( !common ) { return; } // RAVEN BEGIN // jscott: FAS FAS_Shutdown(); // shouchard: clean up ban list stuff SaveBanList(); FlushBanList(); // RAVEN END Printf( "--------------- Game Shutdown ---------------\n" ); networkSystem->RemoveSortFunction( filterByMod ); mpGame.Shutdown(); MapShutdown(); aasList.DeleteContents( true ); aasNames.Clear(); idAI::FreeObstacleAvoidanceNodes(); // shutdown the model exporter idModelExport::Shutdown(); idEvent::Shutdown(); program.Shutdown(); idClass::Shutdown(); // clear list with forces idForce::ClearForceList(); // free the program data program.FreeData(); // delete the .map file delete mapFile; mapFile = NULL; // free the collision map collisionModelManager->FreeMap( GetMapName() ); // RAVEN BEGIN // jscott: free up static objects for( i = 0; i < MAX_CLIENTS; i++ ) { userInfo[i].Clear(); persistentPlayerInfo[i].Clear(); } for( i = 0; i < entityUsageList.Num(); i++ ) { entityUsageList[i].Clear(); } serverInfo.Clear(); persistentLevelInfo.Clear(); sessionCommand.Clear(); mapFileName.Clear(); mapFileNameStripped.Clear(); entityUsageList.Clear(); newInfo.Clear(); spawnArgs.Clear(); shakeSounds.Clear(); aiManager.Clear(); // RAVEN END ShutdownConsoleCommands(); // free memory allocated by class objects Clear(); // shut down the animation manager // RAVEN BEGIN // jsinger: animationLib changed to a pointer animationLib->Shutdown(); // RAVEN END // RAVEN BEGIN // rjohnson: entity usage stats entityUsageList.Clear(); // RAVEN END freeView.Shutdown(); Printf( "---------------------------------------------\n" ); instances.DeleteContents( true ); #ifdef GAME_DLL // remove auto-completion function pointers pointing into this DLL cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME ); // enable leak test Mem_EnableLeakTest( "game" ); // shutdown idLib idLib::ShutDown(); #endif } /* =========== idGameLocal::SaveGame save the current player state, level name, and level state the session may have written some data to the file already ============ */ // RAVEN BEGIN // mekberg: added saveTypes void idGameLocal::SaveGame( idFile *f, saveType_t saveType ) { // RAVEN END int i; idEntity *ent; idEntity *link; //remove weapon effect entites from the world if( GetLocalPlayer() && !GetLocalPlayer()->IsInVehicle() && GetLocalPlayer()->weapon ) { GetLocalPlayer()->weapon->PreSave(); } idSaveGame savegame( f ); if (g_flushSave.GetBool( ) == true ) { // force flushing with each write... for tracking down // save game bugs. f->ForceFlush(); } savegame.WriteBuildNumber( BUILD_NUMBER ); // go through all entities and threads and add them to the object list for( i = 0; i < MAX_GENTITIES; i++ ) { ent = entities[i]; if ( ent ) { if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) { continue; } for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) { savegame.AddObject( link ); } } } idList threads; threads = idThread::GetThreads(); for( i = 0; i < threads.Num(); i++ ) { savegame.AddObject( threads[i] ); } // RAVEN BEGIN // abahr: saving clientEntities rvClientEntity* clientEnt = NULL; for( i = 0; i < MAX_CENTITIES; ++i ) { clientEnt = clientEntities[ i ]; if( !clientEnt ) { continue; } // if( clientEnt->IsType( rvClientEffect::GetClassType() )){ // continue; // } savegame.AddObject( clientEnt ); } // RAVEN END // write out complete object list savegame.WriteObjectList(); program.Save( &savegame ); savegame.WriteInt( g_skill.GetInteger() ); savegame.WriteDict( &serverInfo ); savegame.WriteInt( numClients ); for( i = 0; i < numClients; i++ ) { // RAVEN BEGIN // mekberg: don't write out userinfo. Grab from cvars // savegame.WriteDict( &userInfo[ i ] ); // RAVEN END // savegame.WriteUsercmd( usercmds[ i ] ); savegame.WriteDict( &persistentPlayerInfo[ i ] ); } for( i = 0; i < MAX_GENTITIES; i++ ) { savegame.WriteObject( entities[ i ] ); savegame.WriteInt( spawnIds[ i ] ); } // RAVEN BEGIN // abahr: more clientEntities saving for( i = 0; i < MAX_CENTITIES; i++ ) { // if( clientEntities[ i ] && clientEntities[ i ]->IsType( rvClientEffect::GetClassType() )){ // continue; // } savegame.WriteObject( clientEntities[ i ] ); savegame.WriteInt( clientSpawnIds[ i ] ); } // RAVEN END savegame.WriteInt( firstFreeIndex ); savegame.WriteInt( num_entities ); // enityHash is restored by idEntity::Restore setting the entity name. savegame.WriteObject( world ); savegame.WriteInt( spawnedEntities.Num() ); for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { savegame.WriteObject( ent ); } savegame.WriteInt( scriptObjectProxies.Num() ); for( i = 0; i < scriptObjectProxies.Num(); ++i ) { scriptObjectProxies[i].Save( &savegame ); } // used to write clientSpawnedEntities.Num() then iterate through the spawn nodes // this is fragile however, as some elsewhere get those out of sync (mantis #70) int countClientSpawnedEntities = 0; for ( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { countClientSpawnedEntities++; } if ( countClientSpawnedEntities != clientSpawnedEntities.Num() ) { common->Warning( "countClientSpawnedEntities %d != clientSpawnedEntities.Num() %d\n", countClientSpawnedEntities, clientSpawnedEntities.Num() ); } savegame.WriteInt( countClientSpawnedEntities ); for ( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { savegame.WriteObject( clientEnt ); } // same as above. haven't seen a problem with that one but I can't trust it either int countActiveEntities = 0; for ( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { countActiveEntities++; } if ( countActiveEntities != activeEntities.Num() ) { common->Warning( "countActiveEntities %d != activeEntities.Num() %d\n", countActiveEntities, activeEntities.Num() ); } savegame.WriteInt( countActiveEntities ); for ( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { savegame.WriteObject( ent ); } savegame.WriteInt( numEntitiesToDeactivate ); savegame.WriteBool( sortPushers ); savegame.WriteBool( sortTeamMasters ); savegame.WriteDict( &persistentLevelInfo ); for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { savegame.WriteFloat( globalShaderParms[ i ] ); } savegame.WriteInt( random.GetSeed() ); savegame.WriteObject( frameCommandThread ); // clip // push // pvs testmodel = NULL; // RAVEN BEGIN // bdube: no test fx // testFx = NULL; // RAVEN END savegame.WriteString( sessionCommand ); // RAVEN BEGIN // nmckenzie: Let the AI system save itself too. aiManager.Save( &savegame ); // RAVEN END // FIXME: save smoke particles savegame.WriteInt( cinematicSkipTime ); savegame.WriteInt( cinematicStopTime ); savegame.WriteInt( cinematicMaxSkipTime ); savegame.WriteBool( inCinematic ); savegame.WriteBool( skipCinematic ); savegame.WriteBool( isMultiplayer ); savegame.WriteInt( gameType ); savegame.WriteInt( framenum ); savegame.WriteInt( previousTime ); savegame.WriteInt( time ); savegame.WriteInt( vacuumAreaNum ); savegame.WriteInt( entityDefBits ); savegame.WriteBool( isServer ); savegame.WriteBool( isClient ); // RAVEN BEGIN savegame.WriteBool( isListenServer ); // RAVEN END savegame.WriteInt( localClientNum ); // snapshotEntities is used for multiplayer only savegame.WriteInt( realClientTime ); savegame.WriteBool( isNewFrame ); savegame.WriteBool( mapCycleLoaded ); savegame.WriteInt( spawnCount ); if ( !locationEntities ) { savegame.WriteInt( 0 ); } else { savegame.WriteInt( gameRenderWorld->NumAreas() ); for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) { savegame.WriteObject( locationEntities[ i ] ); } } savegame.WriteObject( camera ); savegame.WriteMaterial( globalMaterial ); // RAVEN BEGIN // bdube: added lastAIAlertActor.Save( &savegame ); lastAIAlertEntity.Save( &savegame ); savegame.WriteInt( lastAIAlertEntityTime ); savegame.WriteInt( lastAIAlertActorTime ); // RAVEN END savegame.WriteDict( &spawnArgs ); savegame.WriteInt( playerPVS.i ); savegame.WriteInt( playerPVS.h ); savegame.WriteInt( playerConnectedAreas.i ); savegame.WriteInt( playerConnectedAreas.h ); savegame.WriteVec3( gravity ); // gamestate savegame.WriteBool( influenceActive ); savegame.WriteInt( nextGibTime ); // spawnSpots // initialSpots // currentInitialSpot // newInfo // makingBuild // shakeSounds // write out pending events idEvent::Save( &savegame ); savegame.Close(); // RAVEN BEGIN // mekberg: added saveTypes and wrapped saveMessage if ( saveType != ST_AUTO && GetLocalPlayer() && GetLocalPlayer()->GetHud() ) { GetLocalPlayer()->SaveMessage(); } // jshepard: resume weapon operation if( GetLocalPlayer() && !GetLocalPlayer()->IsInVehicle() && GetLocalPlayer()->weapon ) { GetLocalPlayer()->weapon->PostSave(); } // RAVEN END } /* =========== idGameLocal::GetPersistentPlayerInfo ============ */ const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) { idEntity *ent; persistentPlayerInfo[ clientNum ].Clear(); ent = entities[ clientNum ]; // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { // RAVEN END static_cast(ent)->SavePersistantInfo(); } return persistentPlayerInfo[ clientNum ]; } /* =========== idGameLocal::SetPersistentPlayerInfo ============ */ void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) { TIME_THIS_SCOPE( __FUNCLINE__); persistentPlayerInfo[ clientNum ] = playerInfo; } /* ============ idGameLocal::Printf ============ */ void idGameLocal::Printf( const char *fmt, ... ) const { va_list argptr; char text[MAX_STRING_CHARS]; va_start( argptr, fmt ); idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); common->Printf( "%s", text ); } /* ============ idGameLocal::DPrintf ============ */ void idGameLocal::DPrintf( const char *fmt, ... ) const { va_list argptr; char text[MAX_STRING_CHARS]; if ( !developer.GetBool() ) { return; } va_start( argptr, fmt ); idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); common->Printf( "%s", text ); } /* ============ idGameLocal::Warning ============ */ void idGameLocal::Warning( const char *fmt, ... ) const { va_list argptr; char text[MAX_STRING_CHARS]; idThread * thread; va_start( argptr, fmt ); idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); thread = idThread::CurrentThread(); // MERGE:FIXME - the following now gets into a recursive stack overflow when enemies are killed. Not sure why. // nmckenzie: /* if ( thread ) { thread->Warning( "%s", text ); } else {*/ common->Warning( "%s", text ); // } } /* ============ idGameLocal::DWarning ============ */ void idGameLocal::DWarning( const char *fmt, ... ) const { va_list argptr; char text[MAX_STRING_CHARS]; idThread * thread; if ( !developer.GetBool() ) { return; } va_start( argptr, fmt ); idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); thread = idThread::CurrentThread(); if ( thread ) { thread->Warning( "%s", text ); } else { common->DWarning( "%s", text ); } } /* ============ idGameLocal::Error ============ */ void idGameLocal::Error( const char *fmt, ... ) const { va_list argptr; char text[MAX_STRING_CHARS]; idThread * thread; va_start( argptr, fmt ); idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); // RAVEN BEGIN // scork: some model errors arrive here during validation which kills the whole process, so let's just warn about them instead... if ( common->DoingDeclValidation() ) { this->Warning( "%s", text ); return; } // RAVEN END thread = idThread::CurrentThread(); if ( thread ) { thread->Error( "%s", text ); } else { common->Error( "%s", text ); } } /* =============== gameError =============== */ void gameError( const char *fmt, ... ) { va_list argptr; char text[MAX_STRING_CHARS]; va_start( argptr, fmt ); idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); gameLocal.Error( "%s", text ); } /* =========== idGameLocal::SetLocalClient ============ */ void idGameLocal::SetLocalClient( int clientNum ) { localClientNum = clientNum; } /* =========== idGameLocal::SetUserInfo ============ */ const idDict* idGameLocal::SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) { TIME_THIS_SCOPE( __FUNCLINE__); int i; bool modifiedInfo = false; this->isClient = isClient; if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { idGameLocal::userInfo[ clientNum ] = userInfo; // server sanity if ( !isClient ) { // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server if ( idStr::IsNumeric( this->userInfo[ clientNum ].GetString( "ui_name" ) ) ) { #if UI_DEBUG common->Printf( "SetUserInfo: client %d changed name from %s to %s_\n", clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ); #endif idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); modifiedInfo = true; } // don't allow dupe nicknames for ( i = 0; i < numClients; i++ ) { if ( i == clientNum ) { continue; } // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) ) { // RAVEN END if ( !idStr::Icmp( idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ i ].GetString( "ui_name" ) ) ) { #if UI_DEBUG common->Printf( "SetUserInfo: client %d changed name from %s to %s_ because of client %d\n", clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), i ); #endif idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); modifiedInfo = true; i = -1; // rescan continue; } } } } // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { // RAVEN END modifiedInfo |= static_cast( entities[ clientNum ] )->UserInfoChanged(); } if ( !isClient ) { // now mark this client in game mpGame.EnterGame( clientNum ); } } if ( modifiedInfo ) { newInfo = idGameLocal::userInfo[ clientNum ]; return &newInfo; } return NULL; } /* =========== idGameLocal::GetUserInfo ============ */ const idDict* idGameLocal::GetUserInfo( int clientNum ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { // RAVEN END return &userInfo[ clientNum ]; } return NULL; } /* =========== idGameLocal::IsClientActive ============ */ bool idGameLocal::IsClientActive( int clientNum ) { if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { return true; } return false; } /* =========== idGameLocal::SetServerInfo ============ */ void idGameLocal::SetServerInfo( const idDict &_serverInfo ) { TIME_THIS_SCOPE( __FUNCLINE__); idBitMsg outMsg; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; bool timeLimitChanged = false; // RAVEN BEGIN // mekberg: clear announcer and reschedule time announcements if ( ( isClient || isListenServer ) && mpGame.GetGameState( ) && mpGame.GetGameState( )->GetMPGameState( ) == GAMEON && serverInfo.GetInt( "si_timelimit" ) != _serverInfo.GetInt( "si_timelimit" ) ) { timeLimitChanged = true; } serverInfo = _serverInfo; if ( timeLimitChanged ) { mpGame.ClearAnnouncerSounds( ); mpGame.ScheduleTimeAnnouncements( ); } // RAVEN END if ( isServer ) { // Let our clients know the server info changed outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); networkSystem->ServerSendReliableMessage( -1, outMsg ); } } /* =================== idGameLocal::LoadMap Initializes all map variables common to both save games and spawned games. =================== */ void idGameLocal::LoadMap( const char *mapName, int randseed ) { int i; // RAVEN BEGIN // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG(tag,MA_PARSER); // RAVEN END bool sameMap = (mapFile && idStr::Icmp(mapFile->GetName(), mapName) == 0); networkSystem->SetLoadingText( mapName ); InitAsyncNetwork(); // these can changed based upon sp / mp mHz = common->GetUserCmdHz(); msec = common->GetUserCmdMSec(); if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) { // load the .map file if ( mapFile ) { delete mapFile; } mapFile = new idMapFile; if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) { delete mapFile; mapFile = NULL; Error( "Couldn't load %s", mapName ); } // RAVEN BEGIN // rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data mapFile->Resolve(); // RAVEN END } mapFileName = mapFile->GetName(); assert(!idStr::Cmp(mapFileName, mapFile->GetName())); // RAVEN BEGIN // rjohnson: entity usage stats mapFileNameStripped = mapFileName; mapFileNameStripped.StripFileExtension(); mapFileNameStripped.StripPath(); const char* entityFilter; gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); if ( entityFilter && *entityFilter ) { mapFileNameStripped += " "; mapFileNameStripped += entityFilter; } // RAVEN END // load the collision map networkSystem->SetLoadingText( common->GetLocalizedString( "#str_107668" ) ); collisionModelManager->LoadMap( mapFile, false ); numClients = 0; // initialize all entities for this game memset( entities, 0, sizeof( entities ) ); usercmds = NULL; memset( spawnIds, -1, sizeof( spawnIds ) ); spawnCount = INITIAL_SPAWN_COUNT; spawnedEntities.Clear(); activeEntities.Clear(); numEntitiesToDeactivate = 0; sortTeamMasters = false; sortPushers = false; lastGUIEnt = NULL; lastGUI = 0; // RAVEN BEGIN // bdube: client entities clientSpawnCount = INITIAL_SPAWN_COUNT; clientSpawnedEntities.Clear(); memset ( clientSpawnIds, -1, sizeof(clientSpawnIds ) ); memset ( clientEntities, 0, sizeof(clientEntities) ); firstFreeClientIndex = 0; // RAVEN END globalMaterial = NULL; memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); // always leave room for the max number of clients, // even if they aren't all used, so numbers inside that // range are NEVER anything but clients num_entities = MAX_CLIENTS; firstFreeIndex = MAX_CLIENTS; // reset the random number generator. random.SetSeed( isMultiplayer ? randseed : 0 ); camera = NULL; world = NULL; testmodel = NULL; // RAVEN BEGIN // bdube: not using id effects // testFx = NULL; // RAVEN END // RAVEN BEGIN // bdube: merged lastAIAlertEntity = NULL; lastAIAlertEntityTime = 0; lastAIAlertActor = NULL; lastAIAlertActorTime = 0; // RAVEN END previousTime = 0; time = 0; framenum = 0; sessionCommand = ""; nextGibTime = 0; // RITUAL BEGIN // squirrel: added DeadZone multiplayer mode unFreezeTime = 0; isFrozen = 0; // RITUAL END vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this // RAVEN BEGIN // abahr gravityInfo.Clear(); scriptObjectProxies.Clear(); // RAVEN END if ( !editEntities ) { editEntities = new idEditEntities; } if ( gameLocal.isMultiplayer ) { gravity.Set( 0, 0, -g_mp_gravity.GetFloat() ); } else { gravity.Set( 0, 0, -g_gravity.GetFloat() ); } spawnArgs.Clear(); // RAVEN BEGIN // nmckenzie: //make sure we clear all reachabilities we marked as blocked! aiManager.UnMarkAllReachBlocked(); aiManager.Clear(); // RAVEN END skipCinematic = false; inCinematic = false; cinematicSkipTime = 0; cinematicStopTime = 0; cinematicMaxSkipTime = 0; // RAVEN BEGIN // ddynerman: main world instance PACIFIER_UPDATE; AddInstance( 0, true ); assert( instances.Num() == 1 && instances[ 0 ]->GetInstanceID() == 0 ); // RAVEN END pvs.Init(); // RAVEN BEGIN // mwhitlock: Xenon texture streaming #if defined(_XENON) // Experimental use of game's PVS for reducing amount of stuff streamed. Will // do this a bit more cleanly if I decide to keep this. extern idPVS* pvsForStreaming; pvsForStreaming=&pvs; #endif // RAVEN END playerPVS.i = -1; playerConnectedAreas.i = -1; // load navigation system for all the different monster sizes for( i = 0; i < aasNames.Num(); i++ ) { aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); } // RAVEN BEGIN // cdr: Obstacle Avoidance AI_MoveInitialize(); // RAVEN END FindEntityDef( "preCacheExtras", false ); if ( !sameMap ) { mapFile->RemovePrimitiveData(); } // RAVEN BEGIN // ddynerman: ambient light list ambientLights.Clear(); // RAVEN END } /* =================== idGameLocal::LocalMapRestart =================== */ void idGameLocal::LocalMapRestart( int instance ) { int i, latchSpawnCount; Printf( "----------- Game Map Restart (%s) ------------\n", instance == -1 ? "all instances" : va( "instance %d", instance ) ); // client always respawns everything, so make sure it picks up the right map entities if( instance == -1 || isClient ) { memset( isMapEntity, 0, sizeof(bool) * MAX_GENTITIES ); } else { assert( instance >= 0 && instance < instances.Num() ); for( int i = 0; i < instances[ instance ]->GetNumMapEntities(); i++ ) { if ( instances[ instance ]->GetMapEntityNumber( i ) >= 0 && instances[ instance ]->GetMapEntityNumber( i ) < MAX_GENTITIES ) { isMapEntity[ instances[ instance ]->GetMapEntityNumber( i ) ] = false; } } } gamestate = GAMESTATE_SHUTDOWN; for ( i = 0; i < MAX_CLIENTS; i++ ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { // RAVEN END static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart(); } } eventQueue.Shutdown(); MapClear( false, instance ); // clear the sound system soundSystem->StopAllSounds( SOUNDWORLD_GAME ); // clear icons iconManager->Shutdown(); // the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId // if we don't do that, network clients are confused and don't show any map entities latchSpawnCount = spawnCount; spawnCount = INITIAL_SPAWN_COUNT; gamestate = GAMESTATE_STARTUP; program.Restart(); InitScriptForMap(); MapPopulate( instance ); // once the map is populated, set the spawnCount back to where it was so we don't risk any collision // (note that if there are no players in the game, we could just leave it at it's current value) spawnCount = latchSpawnCount; // setup the client entities again for ( i = 0; i < MAX_CLIENTS; i++ ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { // RAVEN END static_cast< idPlayer * >( entities[ i ] )->Restart(); } } gamestate = GAMESTATE_ACTIVE; Printf( "--------------------------------------\n" ); } /* =================== idGameLocal::MapRestart =================== */ void idGameLocal::MapRestart( int instance ) { idBitMsg outMsg; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; idDict newInfo; int i; const idKeyValue *keyval, *keyval2; if ( isClient ) { // RAVEN BEGIN // ddynerman: check if gametype changed SetGameType(); // RAVEN END LocalMapRestart( instance ); } else { if ( mpGame.PickMap( "", true ) ) { common->Warning( "map %s and gametype %s are incompatible, aborting map change", si_map.GetString(), si_gameType.GetString() ); return; } newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); // this has to be after the cvars are moved to the dict for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { keyval = newInfo.GetKeyVal( i ); keyval2 = serverInfo.FindKey( keyval->GetKey() ); if ( !keyval2 ) { break; } // a select set of si_ changes will cause a full restart of the server if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { break; } //RAVEN BEGIN //asalmon: need to restart if the game type has changed but the map has not cause someone could be connecting if( keyval->GetValue().Icmp( keyval2->GetValue() ) && (!keyval->GetKey().Icmp("si_gametype"))) { break; } //RAVEN END } cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); SetGameType(); mpGame.isBuyingAllowedRightNow = false; if ( i != newInfo.GetNumKeyVals() ) { gameLocal.sessionCommand = "nextMap"; } else { outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); outMsg.WriteBits( 1, 1 ); outMsg.WriteDeltaDict( serverInfo, NULL ); networkSystem->ServerSendReliableMessage( -1, outMsg ); LocalMapRestart( instance ); mpGame.MapRestart(); } } } /* =================== idGameLocal::VerifyServerSettings_f =================== */ void idGameLocal::VerifyServerSettings_f( const idCmdArgs &args ) { gameLocal.mpGame.PickMap( si_gameType.GetString() ); } /* =================== idGameLocal::MapRestart_f =================== */ void idGameLocal::MapRestart_f( const idCmdArgs &args ) { if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { common->Printf( "server is not running - use spawnServer\n" ); cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); return; } int instance = -1; if( args.Argc() > 1 ) { instance = atoi( args.Args( 1 ) ); if( instance < 0 || instance >= gameLocal.GetNumInstances() ) { common->Warning( "idGameLocal::MapRestart_f() - Invalid instance '%d' specified\n", instance ); return; } gameLocal.LocalMapRestart( instance ); return; } gameLocal.MapRestart( instance ); } /* =================== idGameLocal::NextMap =================== */ bool idGameLocal::NextMap( void ) { const function_t *func; idThread *thread; idDict newInfo; const idKeyValue *keyval, *keyval2; int i; const char *mapCycleList, *currentMap; // RAVEN BEGIN // rjohnson: traditional map cycle // si_mapCycle "mp/q4dm4;mp/q4dm5;mp/q4dm6" mapCycleList = si_mapCycle.GetString(); if ( mapCycleList && strlen( mapCycleList ) ) { idLexer src; idToken token, firstFound; int numMaps = 0; bool foundMap = false; src.FreeSource(); src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); src.LoadMemory( mapCycleList, strlen( mapCycleList ), "idGameLocal::NextMap" ); if ( src.IsLoaded() ) { currentMap = si_map.GetString(); while( src.ReadToken( &token ) ) { if ( token == ";" ) { continue; } numMaps++; if ( numMaps == 1 ) { // guarantee that we use a map no matter what ( or when we wrap ) firstFound = token; } if ( foundMap ) { firstFound = token; break; } if ( idStr::Icmp( token, currentMap ) == 0 ) { foundMap = true; } } if ( firstFound != "" ) { si_map.SetString( firstFound ); return true; } } return false; } // RAVEN END if ( !g_mapCycle.GetString()[0] ) { Printf( common->GetLocalizedString( "#str_104294" ) ); return false; } if ( fileSystem->ReadFile( g_mapCycle.GetString(), NULL, NULL ) < 0 ) { if ( fileSystem->ReadFile( va( "%s.scriptcfg", g_mapCycle.GetString() ), NULL, NULL ) < 0 ) { Printf( "map cycle script '%s': not found\n", g_mapCycle.GetString() ); return false; } else { g_mapCycle.SetString( va( "%s.scriptcfg", g_mapCycle.GetString() ) ); } } Printf( "map cycle script: '%s'\n", g_mapCycle.GetString() ); func = program.FindFunction( "mapcycle::cycle" ); if ( !func ) { program.CompileFile( g_mapCycle.GetString() ); func = program.FindFunction( "mapcycle::cycle" ); } if ( !func ) { Printf( "Couldn't find mapcycle::cycle\n" ); return false; } thread = new idThread( func ); thread->Start(); delete thread; newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { keyval = newInfo.GetKeyVal( i ); keyval2 = serverInfo.FindKey( keyval->GetKey() ); if ( !keyval2 || keyval->GetValue().Icmp( keyval2->GetValue() ) ) { break; } } return ( i != newInfo.GetNumKeyVals() ); } /* =================== idGameLocal::NextMap_f =================== */ void idGameLocal::NextMap_f( const idCmdArgs &args ) { if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { common->Printf( "server is not running\n" ); return; } gameLocal.NextMap( ); // next map was either voted for or triggered by a server command - always restart gameLocal.MapRestart( ); } /* =============== idGameLocal::GetStartingIndexForInstance =============== */ int idGameLocal::GetStartingIndexForInstance( int instanceID ) { if ( isServer ) { assert( instancesEntityIndexWatermarks.Num() >= instanceID ); if ( instanceID == 0 ) { return MAX_CLIENTS; } else { // the high watermark of the previous instance is the starting index of the next one return instancesEntityIndexWatermarks[ instanceID - 1 ]; } } else { assert( instanceID == 0 ); return clientInstanceFirstFreeIndex; } } /* =============== idGameLocal::ServerSetEntityIndexWatermark keep track of the entity layout at the server - specially when there are multiple instances ( tourney ) =============== */ void idGameLocal::ServerSetEntityIndexWatermark( int instanceID ) { if ( isClient ) { return; } instancesEntityIndexWatermarks.AssureSize( instanceID + 1, MAX_CLIENTS ); // make sure there is no drift. if a value was already set it has to match // otherwise that means the server is repopulating with different indexes, and that would likely lead to net corruption assert( instancesEntityIndexWatermarks[ instanceID ] == MAX_CLIENTS || instancesEntityIndexWatermarks[ instanceID ] == firstFreeIndex ); instancesEntityIndexWatermarks[ instanceID ] = firstFreeIndex; } /* =============== idGameLocal::ServerSetMinSpawnIndex =============== */ void idGameLocal::ServerSetMinSpawnIndex( void ) { if ( isClient ) { return; } // setup minSpawnIndex with enough headroom so gameplay entities don't cause bad offsets // only needed on server, clients are completely slaved up to server entity layout if ( !idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Tourney" ) ) { minSpawnIndex = MAX_CLIENTS + GetNumMapEntities() * MAX_INSTANCES; } } /* =================== idGameLocal::MapPopulate =================== */ void idGameLocal::MapPopulate( int instance ) { // RAVEN BEGIN // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG(tag,MA_ENTITY); // RAVEN END if ( isMultiplayer ) { cvarSystem->SetCVarBool( "r_skipSpecular", false ); } minSpawnIndex = MAX_CLIENTS; // parse the key/value pairs and spawn entities // RAVEN BEGIN // ddynerman: instance code // reload the instances if ( instance == -1 ) { int i; firstFreeIndex = MAX_CLIENTS; for ( i = 0; i < instances.Num(); i++ ) { if ( instances[ i ] ) { instances[ i ]->Restart(); ServerSetEntityIndexWatermark( i ); } } } else { assert( instance >= 0 && instance < instances.Num() ); instances[ instance ]->Restart(); } // RAVEN END ServerSetMinSpawnIndex(); // mark location entities in all connected areas SpreadLocations(); // RAVEN BEGIN // ddynerman: prepare the list of spawn spots InitializeSpawns(); // RAVEN END // execute pending events before the very first game frame // this makes sure the map script main() function is called // before the physics are run so entities can bind correctly Printf( "------------ Processing events --------------\n" ); idEvent::ServiceEvents(); } /* =================== idGameLocal::InitFromNewMap =================== */ void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) { TIME_THIS_SCOPE( __FUNCLINE__); this->isServer = isServer; this->isClient = isClient; // RAVEN BEGIN // ddynerman: listen server this->isListenServer = isServer && !cvarSystem->GetCVarBool( "net_serverDedicated" ); // RAVEN END this->isMultiplayer = isServer || isClient; if ( this->isMultiplayer ) gameLocal.Error( "This mod is for singleplayer only" ); PACIFIER_UPDATE; //RAVEN BEGIN //asalmon: stats for single player if (!this->isMultiplayer) { #ifdef _MPBETA return; #else statManager->EndGame(); #ifdef _XENON Live()->DeleteSPSession(true); #endif statManager->Shutdown(); statManager->Init(); statManager->BeginGame(); statManager->ClientConnect(0); #ifdef _XENON Live()->CreateSPSession(); #endif #endif // _MPBETA } //RAVEN END if ( mapFileName.Length() ) { MapShutdown(); } Printf( "-------------- Game Map Init ----------------\n" ); gamestate = GAMESTATE_STARTUP; gameRenderWorld = renderWorld; // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation #if defined(_RV_MEM_SYS_SUPPORT) animationLib->BeginLevelLoad(); #endif // ddynerman: set gametype SetGameType(); // RAVEN END LoadMap( mapName, randseed ); InitScriptForMap(); MapPopulate(); mpGame.Reset(); mpGame.Precache(); // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation #if defined(_RV_MEM_SYS_SUPPORT) animationLib->EndLevelLoad(); #endif // RAVEN END // free up any unused animations // RAVEN BEGIN // jsinger: animationLib changed to a pointer animationLib->FlushUnusedAnims(); // RAVEN END gamestate = GAMESTATE_ACTIVE; Printf( "---------------------------------------------\n" ); } /* ================= idGameLocal::InitFromSaveGame ================= */ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) { TIME_THIS_SCOPE( __FUNCLINE__); int i; int num; idEntity *ent; idDict si; if ( mapFileName.Length() ) { MapShutdown(); } Printf( "---------- Game Map Init SaveGame -----------\n" ); gamestate = GAMESTATE_STARTUP; gameRenderWorld = renderWorld; idRestoreGame savegame( saveGameFile ); savegame.ReadBuildNumber(); // Create the list of all objects in the game savegame.CreateObjects(); // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame if ( program.Restore( &savegame ) == false ) { // Abort the load process, and let the session know so that it can restart the level // with the player persistent data. savegame.DeleteObjects(); program.Restart(); return false; } // load the map needed for this savegame LoadMap( mapName, 0 ); savegame.ReadInt( i ); g_skill.SetInteger( i ); // precache any media specified in the map for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { idMapEntity *mapEnt = mapFile->GetEntity( i ); if ( !InhibitEntitySpawn( mapEnt->epairs ) ) { CacheDictionaryMedia( &mapEnt->epairs ); const char *classname = mapEnt->epairs.GetString( "classname" ); if ( classname != '\0' ) { FindEntityDef( classname, false ); } } } savegame.ReadDict( &si ); SetServerInfo( si ); savegame.ReadInt( numClients ); for( i = 0; i < numClients; i++ ) { // RAVEN BEGIN // mekberg: don't read in userinfo. Grab from cvars // savegame.ReadDict( &userInfo[ i ] ); // RAVEN END // savegame.ReadUsercmd( usercmds[ i ] ); savegame.ReadDict( &persistentPlayerInfo[ i ] ); } for( i = 0; i < MAX_GENTITIES; i++ ) { savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); savegame.ReadInt( spawnIds[ i ] ); // restore the entityNumber if ( entities[ i ] != NULL ) { entities[ i ]->entityNumber = i; } } // Precache the player // RAVEN BEGIN // bdube: changed so we actually cache stuff FindEntityDef( idPlayer::GetSpawnClassname() ); // abahr: saving clientEntities for( i = 0; i < MAX_CENTITIES; i++ ) { savegame.ReadObject( reinterpret_cast( clientEntities[ i ] ) ); savegame.ReadInt( clientSpawnIds[ i ] ); // restore the entityNumber if ( clientEntities[ i ] != NULL ) { clientEntities[ i ]->entityNumber = i; } } // RAVEN END savegame.ReadInt( firstFreeIndex ); savegame.ReadInt( num_entities ); // enityHash is restored by idEntity::Restore setting the entity name. savegame.ReadObject( reinterpret_cast( world ) ); savegame.ReadInt( num ); for( i = 0; i < num; i++ ) { savegame.ReadObject( reinterpret_cast( ent ) ); assert( ent ); if ( ent ) { ent->spawnNode.AddToEnd( spawnedEntities ); } } // RAVEN BEGIN // abahr: save scriptObject proxies savegame.ReadInt( num ); scriptObjectProxies.SetNum( num ); for( i = 0; i < num; ++i ) { scriptObjectProxies[i].Restore( &savegame ); } // abahr: save client entity stuff rvClientEntity* clientEnt = NULL; savegame.ReadInt( num ); for( i = 0; i < num; ++i ) { savegame.ReadObject( reinterpret_cast( clientEnt ) ); assert( clientEnt ); if ( clientEnt ) { clientEnt->spawnNode.AddToEnd( clientSpawnedEntities ); } } // RAVEN END savegame.ReadInt( num ); for( i = 0; i < num; i++ ) { savegame.ReadObject( reinterpret_cast( ent ) ); assert( ent ); if ( ent ) { ent->activeNode.AddToEnd( activeEntities ); } } savegame.ReadInt( numEntitiesToDeactivate ); savegame.ReadBool( sortPushers ); savegame.ReadBool( sortTeamMasters ); savegame.ReadDict( &persistentLevelInfo ); for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { savegame.ReadFloat( globalShaderParms[ i ] ); } savegame.ReadInt( i ); random.SetSeed( i ); savegame.ReadObject( reinterpret_cast( frameCommandThread ) ); // clip // push // pvs // testmodel = "" // testFx = "" savegame.ReadString( sessionCommand ); // RAVEN BEGIN // nmckenzie: Let the AI system load itself too. aiManager.Restore( &savegame ); // RAVEN END // FIXME: save smoke particles savegame.ReadInt( cinematicSkipTime ); savegame.ReadInt( cinematicStopTime ); savegame.ReadInt( cinematicMaxSkipTime ); savegame.ReadBool( inCinematic ); savegame.ReadBool( skipCinematic ); savegame.ReadBool( isMultiplayer ); savegame.ReadInt( (int &)gameType ); savegame.ReadInt( framenum ); savegame.ReadInt( previousTime ); savegame.ReadInt( time ); savegame.ReadInt( vacuumAreaNum ); savegame.ReadInt( entityDefBits ); savegame.ReadBool( isServer ); savegame.ReadBool( isClient ); // RAVEN BEGIN savegame.ReadBool( isListenServer ); // RAVEN END savegame.ReadInt( localClientNum ); // snapshotEntities is used for multiplayer only savegame.ReadInt( realClientTime ); savegame.ReadBool( isNewFrame ); savegame.ReadBool( mapCycleLoaded ); savegame.ReadInt( spawnCount ); savegame.ReadInt( num ); if ( num ) { if ( num != gameRenderWorld->NumAreas() ) { savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." ); } locationEntities = new idLocationEntity *[ num ]; for( i = 0; i < num; i++ ) { savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); } } savegame.ReadObject( reinterpret_cast( camera ) ); savegame.ReadMaterial( globalMaterial ); // RAVEN BEGIN // bdube: added lastAIAlertEntity.Restore( &savegame ); savegame.ReadInt( lastAIAlertEntityTime ); lastAIAlertActor.Restore( &savegame ); savegame.ReadInt( lastAIAlertActorTime ); // RAVEN END savegame.ReadDict( &spawnArgs ); savegame.ReadInt( playerPVS.i ); savegame.ReadInt( (int &)playerPVS.h ); savegame.ReadInt( playerConnectedAreas.i ); savegame.ReadInt( (int &)playerConnectedAreas.h ); savegame.ReadVec3( gravity ); // gamestate is restored after restoring everything else savegame.ReadBool( influenceActive ); savegame.ReadInt( nextGibTime ); // spawnSpots // initialSpots // currentInitialSpot // newInfo // makingBuild // shakeSounds // Read out pending events idEvent::Restore( &savegame ); // call the restore functions to read out all the data from the entities savegame.RestoreObjects(); mpGame.Reset(); mpGame.Precache(); // free up any unused animations // RAVEN BEGIN // jsinger: animationLib changed to a pointer animationLib->FlushUnusedAnims(); // RAVEN END gamestate = GAMESTATE_ACTIVE; Printf( "--------------------------------------\n" ); return true; } /* =========== idGameLocal::MapClear =========== */ // RAVEN BEGIN // ddynerman: multiple instances void idGameLocal::MapClear( bool clearClients, int instance ) { // RAVEN END int i; // RAVEN BEGIN // bdube: delete client entities first since they reference real entities for( i = 0; i < MAX_CENTITIES; i++ ) { // on the server we need to keep around client entities bound to entities in our instance2 if( gameLocal.isServer && gameLocal.GetLocalPlayer() && instance != -1 && instance != gameLocal.GetLocalPlayer()->GetInstance() && clientEntities[ i ] && clientEntities[ i ]->GetBindMaster() && clientEntities[ i ]->GetBindMaster()->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { continue; } delete clientEntities[ i ]; clientEntities[ i ] = NULL; clientSpawnIds[ i ] = -1; } // RAVEN END for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { if( instance >= 0 && entities[ i ] && entities[ i ]->GetInstance() != instance ) { continue; } delete entities[ i ]; // ~idEntity is in charge of setting the pointer to NULL // it will also clear pending events for this entity assert( !entities[ i ] ); // RAVEN BEGIN // see FIXME in idRestoreGame::Error entities[ i ] = NULL; // RAVEN END spawnIds[ i ] = -1; } entityHash.Clear( 1024, MAX_GENTITIES ); // RAVEN BEGIN // rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) if ( instance == -1 ) { spawnedEntities.Clear(); } // RAVEN END if ( !clearClients ) { // add back the hashes of the clients/stuff in other instances for ( i = 0; i < MAX_GENTITIES; i++ ) { if ( !entities[ i ] ) { continue; } entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); // RAVEN BEGIN // rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) if ( instance == -1 ) { entities[ i ]->spawnNode.AddToEnd( spawnedEntities ); } // RAVEN END } } // RAVEN BEGIN // jscott: clear out portal skies portalSky = NULL; // abahr: gravityInfo.Clear(); scriptObjectProxies.Clear(); // RAVEN END delete frameCommandThread; frameCommandThread = NULL; if ( editEntities ) { delete editEntities; editEntities = NULL; } delete[] locationEntities; locationEntities = NULL; // RAVEN BEGIN // ddynerman: mp clear if( gameLocal.isMultiplayer ) { ClearForwardSpawns(); for( i = 0; i < TEAM_MAX; i++ ) { teamSpawnSpots[ i ].Clear(); } mpGame.ClearMap(); } ambientLights.Clear(); // RAVEN END // set the free index back at MAX_CLIENTS for registering entities again // the deletion of map entities causes the index to go down, but it may not have been left exactly at 32 // under such conditions, the map populate that will follow may be offset firstFreeIndex = MAX_CLIENTS; } // RAVEN BEGIN // ddynerman: instance-specific clear void idGameLocal::InstanceClear( void ) { // note: clears all ents EXCEPT those in the instance int i; for( i = 0; i < MAX_CENTITIES; i++ ) { delete clientEntities[ i ]; assert( !clientEntities[ i ] ); clientSpawnIds[ i ] = -1; } for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { if( i == ENTITYNUM_CLIENT || i == ENTITYNUM_WORLD ) { continue; } if( entities[ i ] && entities[ i ]->fl.persistAcrossInstances ) { //common->DPrintf( "Instance %d: persistant: excluding ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); continue; } //if( entities[ i ] ) { //Printf( "Instance %d: CLEARING ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); //} delete entities[ i ]; // ~idEntity is in charge of setting the pointer to NULL // it will also clear pending events for this entity assert( !entities[ i ] ); // RAVEN BEGIN // see FIXME in idRestoreGame::Error entities[ i ] = NULL; // RAVEN END spawnIds[ i ] = -1; } entityHash.Clear( 1024, MAX_GENTITIES ); for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( !entities[ i ] ) { continue; } entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); } // RAVEN BEGIN // jscott: clear out portal skies portalSky = NULL; // abahr: gravityInfo.Clear(); scriptObjectProxies.Clear(); // RAVEN END delete frameCommandThread; frameCommandThread = NULL; if ( editEntities ) { delete editEntities; editEntities = NULL; } delete[] locationEntities; locationEntities = NULL; // RAVEN BEGIN // ddynerman: mp clear if( gameLocal.isMultiplayer ) { ClearForwardSpawns(); for( i = 0; i < TEAM_MAX; i++ ) { teamSpawnSpots[ i ].Clear(); } mpGame.ClearMap(); } ambientLights.Clear(); } // RAVEN END /* =========== idGameLocal::MapShutdown ============ */ void idGameLocal::MapShutdown( void ) { Printf( "------------ Game Map Shutdown --------------\n" ); gamestate = GAMESTATE_SHUTDOWN; if ( soundSystem ) { soundSystem->ResetListener(); } // RAVEN BEGIN // rjohnson: new blur special effect renderSystem->ShutdownSpecialEffects(); // RAVEN END // clear out camera if we're in a cinematic if ( inCinematic ) { camera = NULL; inCinematic = false; } // RAVEN BEGIN // jscott: cleanup playbacks gameEdit->ShutdownPlaybacks(); // RAVEN END MapClear( true ); instancesEntityIndexWatermarks.Clear(); clientInstanceFirstFreeIndex = MAX_CLIENTS; // RAVEN BEGIN // jscott: make sure any spurious events are killed idEvent::ClearEventList(); // reset the script to the state it was before the map was started program.Restart(); // bdube: game debug gameDebug.Shutdown( ); gameLogLocal.Shutdown( ); // RAVEN END iconManager->Shutdown(); pvs.Shutdown(); // RAVEN BEGIN // ddynerman: MP multiple instances ShutdownInstances(); // mwhitlock: Dynamic memory consolidation clip.Clear(); // RAVEN END idClipModel::ClearTraceModelCache(); // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation #if defined(_RV_MEM_SYS_SUPPORT) idForce::ClearForceList(); #endif // RAVEN END ShutdownAsyncNetwork(); mapFileName.Clear(); gameRenderWorld = NULL; gamestate = GAMESTATE_NOMAP; Printf( "---------------------------------------------\n" ); } /* =================== idGameLocal::DumpOggSounds =================== */ void idGameLocal::DumpOggSounds( void ) { int i, j, k, size, totalSize; idFile *file; idStrList oggSounds, weaponSounds; const idSoundShader *soundShader; const soundShaderParms_t *parms; idStr soundName; for ( i = 0; i < declManager->GetNumDecls( DECL_SOUND ); i++ ) { soundShader = static_cast(declManager->DeclByIndex( DECL_SOUND, i, false )); parms = soundShader->GetParms(); if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) { const_cast(soundShader)->EnsureNotPurged(); for ( j = 0; j < soundShader->GetNumSounds(); j++ ) { soundName = soundShader->GetSound( j ); soundName.BackSlashesToSlashes(); // don't OGG sounds that cause a shake because that would // cause continuous seeking on the OGG file which is expensive if ( parms->shakes != 0.0f ) { shakeSounds.AddUnique( soundName ); continue; } // if not voice over or combat chatter if ( soundName.Find( "/vo/", false ) == -1 && soundName.Find( "/combat_chatter/", false ) == -1 && soundName.Find( "/bfgcarnage/", false ) == -1 && soundName.Find( "/enpro/", false ) == - 1 && soundName.Find( "/soulcube/energize_01.wav", false ) == -1 ) { // don't OGG weapon sounds if ( soundName.Find( "weapon", false ) != -1 || soundName.Find( "gun", false ) != -1 || soundName.Find( "bullet", false ) != -1 || soundName.Find( "bfg", false ) != -1 || soundName.Find( "plasma", false ) != -1 ) { weaponSounds.AddUnique( soundName ); continue; } } for ( k = 0; k < shakeSounds.Num(); k++ ) { if ( shakeSounds[k].IcmpPath( soundName ) == 0 ) { break; } } if ( k < shakeSounds.Num() ) { continue; } oggSounds.AddUnique( soundName ); } } } file = fileSystem->OpenFileWrite( "makeogg.bat", "fs_savepath" ); if ( file == NULL ) { common->Warning( "Couldn't open makeogg.bat" ); return; } // list all the shake sounds totalSize = 0; for ( i = 0; i < shakeSounds.Num(); i++ ) { size = fileSystem->ReadFile( shakeSounds[i], NULL, NULL ); totalSize += size; shakeSounds[i].Replace( "/", "\\" ); file->Printf( "echo \"%s\" (%d kB)\n", shakeSounds[i].c_str(), size >> 10 ); } file->Printf( "echo %d kB in shake sounds\n\n\n", totalSize >> 10 ); // list all the weapon sounds totalSize = 0; for ( i = 0; i < weaponSounds.Num(); i++ ) { size = fileSystem->ReadFile( weaponSounds[i], NULL, NULL ); totalSize += size; weaponSounds[i].Replace( "/", "\\" ); file->Printf( "echo \"%s\" (%d kB)\n", weaponSounds[i].c_str(), size >> 10 ); } file->Printf( "echo %d kB in weapon sounds\n\n\n", totalSize >> 10 ); // list commands to convert all other sounds to ogg totalSize = 0; for ( i = 0; i < oggSounds.Num(); i++ ) { // RAVEN BEGIN // rjohnson: changed path to raven's directories // jnewquist: Use filesystem search path to get absolute file path idStr tempFile; idFile *f = fileSystem->OpenFileRead( oggSounds[i] ); if ( !f ) { continue; } size = f->Length(); totalSize += size; tempFile = f->GetFullPath(); fileSystem->CloseFile(f); f = NULL; tempFile.Replace( "/", "\\" ); // jnewquist: prevent alterations to files relative to cdpath const char *cdPath = cvarSystem->GetCVarString("fs_cdpath"); const int cdPathLen = idStr::Length(cdPath); if ( cdPathLen > 0 && idStr::Icmpn(cdPath, tempFile, cdPathLen) == 0 ) { file->Printf( "rem Ignored file from CD path: %s\n", tempFile.c_str() ); continue; } file->Printf( "echo %d / %d\n", i, oggSounds.Num() ); file->Printf( "k:\\utility\\oggenc -q 0 \"%s\"\n", tempFile.c_str() ); file->Printf( "del \"%s\"\n", tempFile.c_str() ); // RAVEN END } file->Printf( "\n\necho %d kB in OGG sounds\n\n\n", totalSize >> 10 ); fileSystem->CloseFile( file ); shakeSounds.Clear(); } /* =================== idGameLocal::GetShakeSounds =================== */ void idGameLocal::GetShakeSounds( const idDict *dict ) { const idSoundShader *soundShader; const char *soundShaderName; idStr soundName; soundShaderName = dict->GetString( "s_shader" ); if ( soundShaderName != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) { soundShader = declManager->FindSound( soundShaderName ); for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) { soundName = soundShader->GetSound( i ); soundName.BackSlashesToSlashes(); shakeSounds.AddUnique( soundName ); } } } /* =================== idGameLocal::CacheDictionaryMedia This is called after parsing an EntityDef and for each entity spawnArgs before merging the entitydef. It could be done post-merge, but that would avoid the fast pre-cache check associated with each entityDef =================== */ void idGameLocal::CacheDictionaryMedia( const idDict *dict ) { idDict spawnerArgs; TIME_THIS_SCOPE( __FUNCLINE__); if ( dict == NULL ) { #ifndef _CONSOLE if ( cvarSystem->GetCVarBool( "com_makingBuild") ) { DumpOggSounds(); } #endif return; } #ifndef _CONSOLE if ( cvarSystem->GetCVarBool( "com_makingBuild" ) ) { GetShakeSounds( dict ); } #endif int numVals = dict->GetNumKeyVals(); for ( int i = 0; i < numVals; ++i ) { const idKeyValue *kv = dict->GetKeyVal( i ); #define MATCH(s) \ (!kv->GetKey().Icmpn( s, strlen(s) )) /**/ if ( !kv || !kv->GetValue().Length() ) { continue; } if ( MATCH( "model" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MODEL); declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() ); // precache model/animations if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) { // precache the render model renderModelManager->FindModel( kv->GetValue() ); // precache .cm files only collisionModelManager->PreCacheModel( GetMapName(), kv->GetValue() ); } } else if ( MATCH( "s_shader" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_SOUND); declManager->FindType( DECL_SOUND, kv->GetValue() ); } else if ( MATCH( "snd_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_SOUND); declManager->FindType( DECL_SOUND, kv->GetValue() ); } else if ( MATCH( "gui_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_GUI); if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" ) || !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 ) || !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) { // unfortunate flag names, they aren't actually a gui } else { declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() ); uiManager->FindGui( kv->GetValue().c_str(), true ); } } else if ( MATCH( "texture" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MATERIAL); declManager->FindType( DECL_MATERIAL, kv->GetValue() ); } else if ( MATCH( "mtr_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MATERIAL); declManager->FindType( DECL_MATERIAL, kv->GetValue() ); } else if ( MATCH( "screenShot" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MATERIAL); declManager->FindType( DECL_MATERIAL, kv->GetValue() ); } else if ( MATCH( "inv_icon" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MATERIAL); declManager->FindType( DECL_MATERIAL, kv->GetValue() ); } else if ( MATCH( "teleport" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_EFFECT); int teleportType = atoi( kv->GetValue() ); const char *p = ( teleportType ) ? va( "effects/teleporter%i.fx", teleportType ) : "effects/teleporter.fx"; declManager->FindType( DECL_EFFECT, p ); } else if ( MATCH( "fx_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_EFFECT); declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() ); declManager->FindEffect( kv->GetValue() ); } else if ( MATCH( "skin" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MATERIAL); declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() ); declManager->FindType( DECL_SKIN, kv->GetValue() ); } else if ( MATCH( "def_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_DECL); FindEntityDef( kv->GetValue().c_str(), false ); } else if ( MATCH( "playback_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_ANIM); declManager->MediaPrint( "Precaching playback %s\n", kv->GetValue().c_str() ); declManager->FindPlayback( kv->GetValue() ); } else if ( MATCH( "lipsync_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_ANIM); declManager->MediaPrint( "Precaching lipsync %s\n", kv->GetValue().c_str() ); declManager->FindLipSync( kv->GetValue() ); declManager->FindSound ( kv->GetValue() ); } else if ( MATCH( "icon " ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_MATERIAL); idLexer src ( LEXFL_ALLOWPATHNAMES ); idToken token; idToken token2; src.LoadMemory( kv->GetValue(), kv->GetValue().Length(), "icon" ); src.ReadToken ( &token ); if ( src.CheckTokenString ( "," ) ) { int x, y, w, h; src.ReadToken ( &token2 ) ; x = token2.GetIntValue ( ); src.ExpectTokenString ( "," ); src.ReadToken ( &token2 ) ; y = token2.GetIntValue ( ); src.ExpectTokenString ( "," ); src.ReadToken ( &token2 ) ; w = token2.GetIntValue ( ); src.ExpectTokenString ( "," ); src.ReadToken ( &token2 ) ; h = token2.GetIntValue ( ); uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token, x, y, w, h ); } else { uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token ); } } else if ( MATCH( "spawn_" ) ) { TIME_THIS_SCOPE( __FUNCLINE__); MEM_SCOPED_TAG(tag,MA_DECL); spawnerArgs.Set ( kv->GetKey ( ).c_str() + 6, kv->GetValue ( ) ); } #undef MATCH } if ( spawnerArgs.GetNumKeyVals() ) { CacheDictionaryMedia ( &spawnerArgs ); } // RAVEN END } /* =========== idGameLocal::InitScriptForMap ============ */ void idGameLocal::InitScriptForMap( void ) { // RAVEN BEGIN // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG(tag,MA_DEFAULT); // RAVEN END // create a thread to run frame commands on frameCommandThread = new idThread(); frameCommandThread->ManualDelete(); frameCommandThread->SetThreadName( "frameCommands" ); // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG_SET(tag,MA_SCRIPT); // run the main game script function (not the level specific main) const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC ); // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG_SET(tag,MA_DEFAULT); if ( func != NULL ) { idThread *thread = new idThread( func ); if ( thread->Start() ) { // thread has finished executing, so delete it delete thread; } } // RAVEN END } /* =========== idGameLocal::SpawnPlayer ============ */ void idGameLocal::SpawnPlayer( int clientNum ) { TIME_THIS_SCOPE( __FUNCLINE__); idEntity *ent; idDict args; // RAVEN BEGIN // jnewquist: Tag scope and callees to track allocations using "new". MEM_SCOPED_TAG(tag,MA_ENTITY); // RAVEN END // they can connect common->DPrintf( "SpawnPlayer: %i\n", clientNum ); args.SetInt( "spawn_entnum", clientNum ); args.Set( "name", va( "player%d", clientNum + 1 ) ); // RAVEN BEGIN // bdube: changed marine class args.Set( "classname", idPlayer::GetSpawnClassname() ); // RAVEN END // This takes a really long time. PACIFIER_UPDATE; if ( !SpawnEntityDef( args, &ent ) || !entities[ clientNum ] ) { Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) ); } // make sure it's a compatible class // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( !ent->IsType( idPlayer::GetClassType() ) ) { // RAVEN END Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() ); } if ( clientNum >= numClients ) { numClients = clientNum + 1; } PACIFIER_UPDATE; mpGame.SpawnPlayer( clientNum ); } /* ================ idGameLocal::GetClientByNum ================ */ idPlayer *idGameLocal::GetClientByNum( int current ) const { if ( current == MAX_CLIENTS ) { return NULL; } if ( current < 0 || current >= numClients ) { // that's a bit nasty but I suppose it has it's use current = 0; } if ( entities[current] ) { return static_cast( entities[ current ] ); } return NULL; } /* ================ idGameLocal::GetClientByName ================ */ idPlayer *idGameLocal::GetClientByName( const char *name ) const { int i; idEntity *ent; for ( i = 0 ; i < numClients ; i++ ) { ent = entities[ i ]; // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { // RAVEN END // RAVEN BEGIN // bdube: escape codes if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { // RAVEN END return static_cast( ent ); } } } return NULL; } /* ================ idGameLocal::GetClientByCmdArgs ================ */ idPlayer *idGameLocal::GetClientByCmdArgs( const idCmdArgs &args ) const { idPlayer *player; idStr client = args.Argv( 1 ); if ( !client.Length() ) { return NULL; } // we don't allow numeric ui_name so this can't go wrong if ( client.IsNumeric() ) { player = GetClientByNum( atoi( client.c_str() ) ); } else { player = GetClientByName( client.c_str() ); } if ( !player ) { common->Printf( "Player '%s' not found\n", client.c_str() ); } return player; } /* ================ idGameLocal::GetClientNumByName ================ */ int idGameLocal::GetClientNumByName( const char *name ) const { int i; idEntity *ent; for ( i = 0 ; i < numClients ; i++ ) { ent = entities[ i ]; if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { return i; } } } return -1; } /* ================ idGameLocal::GetNextClientNum ================ */ int idGameLocal::GetNextClientNum( int _current ) const { int i, current; current = 0; for ( i = 0; i < numClients; i++) { current = ( _current + i + 1 ) % numClients; // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( entities[ current ] && entities[ current ]->IsType( idPlayer::GetClassType() ) ) { // RAVEN END return current; } } return current; } /* ================ idGameLocal::GetLocalPlayer Nothing in the game tic should EVER make a decision based on what the local client number is, it shouldn't even be aware that there is a draw phase even happening. localClientNum == MAX_CLIENTS when playing server demos don't send back the wrong entity obviously ================ */ idPlayer *idGameLocal::GetLocalPlayer() const { if ( localClientNum < 0 || localClientNum == MAX_CLIENTS ) { return NULL; } if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ) { // not fully in game yet return NULL; } // through idGameLocal::MapShutdown, deleting player ents, calls idEntity::StopSound // calls in here and triggers the assert because proper type info is already gone //assert( gamestate == GAMESTATE_SHUTDOWN || entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ); return static_cast( entities[ localClientNum ] ); } /* ================ idGameLocal::SetupClientPVS for client spectating others, get the pvs of spectated ================ */ pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) { if ( player->GetPrivateCameraView() ) { return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() ); } else if ( camera ) { return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() ); } else { if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { player = static_cast( entities[ player->spectator ] ); } return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() ); } } // RAVEN BEGIN // jscott: for portal skies /* ================ idGameLocal::GetSpawnCount ================ */ int idGameLocal::GetSpawnCount ( void ) const { return spawnCount; } /* ================ idGameLocal::SetupPortalSkyPVS ================ */ bool idGameLocal::SetupPortalSkyPVS( idPlayer *player ) { int i, count, numAreas; const int *areaNums; bool *visibleAreas; if( !portalSky ) { return( false ); } // Allocate room for the area flags numAreas = gameRenderWorld->NumAreas(); visibleAreas = ( bool * )_alloca( numAreas ); memset( visibleAreas, 0, numAreas ); // Grab the areas the player can see.... count = player->GetNumPVSAreas(); areaNums = player->GetPVSAreas(); for( i = 0; i < count; i++ ) { // Work out the referenced areas gameRenderWorld->FindVisibleAreas( player->GetPhysics()->GetOrigin(), areaNums[i], visibleAreas ); } // Do any of the visible areas have a skybox? for( i = 0; i < numAreas; i++ ) { if( !visibleAreas[i] ) { continue; } if( gameRenderWorld->HasSkybox( i ) ) { break; } } // .. if any one has a skybox component, then merge in the portal sky return ( i != numAreas ); } // RAVEN END /* =============== idGameLocal::UpdateClientsPVS =============== */ void idGameLocal::UpdateClientsPVS( void ) { int i; for ( i = 0; i < numClients; i++ ) { if ( !entities[ i ] ) { continue; } assert( clientsPVS[ i ].i == -1 ); clientsPVS[ i ] = GetClientPVS( static_cast< idPlayer * >( entities[ i ] ), PVS_NORMAL ); } } /* ================ idGameLocal::SetupPlayerPVS ================ */ void idGameLocal::SetupPlayerPVS( void ) { int i = 0; idPlayer * player = NULL; pvsHandle_t otherPVS, newPVS; UpdateClientsPVS( ); playerPVS.i = -1; for ( i = 0; i < numClients; i++ ) { if ( !entities[i] ) { return; } assert( entities[i]->IsType( idPlayer::GetClassType() ) ); player = static_cast( entities[ i ] ); if ( playerPVS.i == -1 ) { playerPVS = clientsPVS[ i ]; freePlayerPVS = false; // don't try to free it as long as we stick to the client PVS } else { otherPVS = clientsPVS[ i ]; newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); if ( freePlayerPVS ) { pvs.FreeCurrentPVS( playerPVS ); freePlayerPVS = false; } playerPVS = newPVS; freePlayerPVS = true; // that merged one will need to be freed } // RAVEN BEGIN // jscott: for portal skies portalSkyVisible = SetupPortalSkyPVS( player ); if ( portalSkyVisible ) { otherPVS = pvs.SetupCurrentPVS( portalSky->GetPVSAreas(), portalSky->GetNumPVSAreas() ); newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); if ( freePlayerPVS ) { pvs.FreeCurrentPVS( playerPVS ); freePlayerPVS = false; } pvs.FreeCurrentPVS( otherPVS ); playerPVS = newPVS; freePlayerPVS = true; } // RAVEN END if ( playerConnectedAreas.i == -1 ) { playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS ); } else { otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS ); newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); pvs.FreeCurrentPVS( playerConnectedAreas ); pvs.FreeCurrentPVS( otherPVS ); playerConnectedAreas = newPVS; } } } /* ================ idGameLocal::FreePlayerPVS ================ */ void idGameLocal::FreePlayerPVS( void ) { int i; // only clear playerPVS if it's a different handle than the one in clientsPVS if ( freePlayerPVS && playerPVS.i != -1 ) { pvs.FreeCurrentPVS( playerPVS ); freePlayerPVS = false; } playerPVS.i = -1; for ( i = 0; i < MAX_CLIENTS; i++ ) { if ( clientsPVS[ i ].i >= 0 ) { pvs.FreeCurrentPVS( clientsPVS[ i ] ); clientsPVS[i].i = -1; } } if ( playerConnectedAreas.i != -1 ) { pvs.FreeCurrentPVS( playerConnectedAreas ); playerConnectedAreas.i = -1; } } /* ================ idGameLocal::InPlayerPVS should only be called during entity thinking and event handling ================ */ bool idGameLocal::InPlayerPVS( idEntity *ent ) const { if ( playerPVS.i == -1 ) { return false; } return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); } /* ================ idGameLocal::InPlayerConnectedArea should only be called during entity thinking and event handling ================ */ bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const { if ( playerConnectedAreas.i == -1 ) { return false; } return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); } /* ================ idGameLocal::UpdateGravity ================ */ void idGameLocal::UpdateGravity( void ) { idEntity *ent; idCVar* gravityCVar = NULL; if( gameLocal.isMultiplayer ) { gravityCVar = &g_mp_gravity; } else { gravityCVar = &g_gravity; } if ( gravityCVar->IsModified() ) { if ( gravityCVar->GetFloat() == 0.0f ) { gravityCVar->SetFloat( 1.0f ); } gravity.Set( 0, 0, -gravityCVar->GetFloat() ); // update all physics objects for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent->IsType( idAFEntity_Generic::GetClassType() ) ) { // RAVEN END idPhysics *phys = ent->GetPhysics(); if ( phys ) { phys->SetGravity( gravity ); } // RAVEN BEGIN // ddynerman: jump pads } else if ( ent->IsType( rvJumpPad::GetClassType() ) ) { ent->PostEventMS( &EV_FindTargets, 0 ); } // RAVEN END } gravityCVar->ClearModified(); } } /* ================ idGameLocal::GetGravity ================ */ const idVec3 &idGameLocal::GetGravity( void ) const { return gravity; } /* ================ idGameLocal::SortActiveEntityList Sorts the active entity list such that pushing entities come first, actors come next and physics team slaves appear after their master. ================ */ void idGameLocal::SortActiveEntityList( void ) { idEntity *ent, *next_ent, *master, *part; // if the active entity list needs to be reordered to place physics team masters at the front if ( sortTeamMasters ) { // Sort bind masters first for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { next_ent = ent->activeNode.Next(); for ( part = ent->GetBindMaster ( ); part; part = part->GetBindMaster ( ) ) { // Ensure we dont rerun the whole active entity list if our cached next_ent is one // of the entities we are moving if ( next_ent == part ) { next_ent = next_ent->activeNode.Next(); part = ent->GetBindMaster ( ); continue; } part->activeNode.Remove(); part->activeNode.AddToFront( activeEntities ); } } for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { next_ent = ent->activeNode.Next(); master = ent->GetTeamMaster(); if ( master && master == ent ) { ent->activeNode.Remove(); ent->activeNode.AddToFront( activeEntities ); } } } // if the active entity list needs to be reordered to place pushers at the front if ( sortPushers ) { for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { next_ent = ent->activeNode.Next(); master = ent->GetTeamMaster(); if ( !master || master == ent ) { // check if there is an actor on the team for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( part->GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { // RAVEN END break; } } // if there is an actor on the team if ( part ) { ent->activeNode.Remove(); ent->activeNode.AddToFront( activeEntities ); } } } for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { next_ent = ent->activeNode.Next(); master = ent->GetTeamMaster(); if ( !master || master == ent ) { // check if there is an entity on the team using parametric physics for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { break; } if ( part->GetPhysics()->IsType( rvPhysics_Spline::GetClassType() ) ) { break; } } // if there is an entity on the team using parametric physics if ( part ) { ent->activeNode.Remove(); ent->activeNode.AddToFront( activeEntities ); } } } } sortTeamMasters = false; sortPushers = false; } /* ================ idGameLocal::MenuFrame Called each session frame when a map is not running (e.g. usually in the main menu) ================ */ void idGameLocal::MenuFrame( void ) { } /* ================ idGameLocal::RunFrame ================ */ // RAVEN BEGIN gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) { idEntity * ent; int num; float ms; idTimer timer_think, timer_events, timer_singlethink; idTimer timer_misc, timer_misc2; gameReturn_t ret; idPlayer *player; const renderView_t *view; editors = activeEditors; isLastPredictFrame = lastCatchupFrame; assert( !isClient ); player = GetLocalPlayer(); if ( !isMultiplayer && g_stopTime.GetBool() ) { // set the user commands for this frame usercmds = clientCmds; if ( player ) { // ddynerman: save the current thinking entity for instance-dependent currentThinkingEntity = player; player->Think(); currentThinkingEntity = NULL; } } else do { // update the game time framenum++; previousTime = time; // bdube: use GetMSec access rather than USERCMD_TIME time += GetMSec(); realClientTime = time; { TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()"); // bdube: added advanced debug support gameDebug.BeginFrame( ); gameLogLocal.BeginFrame( time ); } #ifdef GAME_DLL // allow changing SIMD usage on the fly if ( com_forceGenericSIMD.IsModified() ) { idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); } #endif // make sure the random number counter is used each frame so random events // are influenced by the player's actions random.RandomInt(); if ( player ) { // update the renderview so that any gui videos play from the right frame view = player->GetRenderView(); if ( view ) { gameRenderWorld->SetRenderView( view ); } } // If modview is running then let it think common->ModViewThink( ); // rjohnson: added option for guis to always think common->RunAlwaysThinkGUIs( time ); // nmckenzie: Let AI System stuff update itself. if ( !isMultiplayer ) { #ifndef _MPBETA aiManager.RunFrame(); #endif // !_MPBETA } timer_misc.Start(); // set the user commands for this frame usercmds = clientCmds; // create a merged pvs for all players // do this before we process events, which may rely on PVS info SetupPlayerPVS(); // process events on the server ServerProcessEntityNetworkEventQueue(); // update our gravity vector if needed. UpdateGravity(); if ( isLastPredictFrame ) { // jscott: effect system uses gravity and the player PVS bse->StartFrame(); } // sort the active entity list SortActiveEntityList(); timer_think.Clear(); timer_think.Start(); // jscott: for timing and effect handling timer_misc2.Start(); timer_misc.Stop(); // let entities think if ( g_timeentities.GetFloat() ) { // rjohnson: will now draw entity info for long thinkers idPlayer *player; idVec3 origin; player = GetLocalPlayer(); if ( player ) { origin = player->GetPhysics()->GetOrigin(); } idBounds viewTextBounds( origin ); viewTextBounds.ExpandSelf( 128.0f ); num = 0; for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { ent->GetPhysics()->UpdateTime( time ); continue; } timer_singlethink.Clear(); timer_singlethink.Start(); // ddynerman: save the current thinking entity for instance-dependent currentThinkingEntity = ent; ent->Think(); currentThinkingEntity = NULL; timer_singlethink.Stop(); ms = timer_singlethink.Milliseconds(); if ( ms >= g_timeentities.GetFloat() ) { // rjohnson: will now draw entity info for long thinkers Printf( "%d: entity '%s' [%s]: %.1f ms\n", time, ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(), ms ); if ( ms >= g_timeentities.GetFloat() * 3.0f ) { ent->mLastLongThinkColor = colorRed; } else { ent->mLastLongThinkColor[0] = 1.0f; ent->mLastLongThinkColor[1] = 2.0f - (( ms - g_timeentities.GetFloat()) / g_timeentities.GetFloat() ); ent->mLastLongThinkColor[2] = 0.0f; ent->mLastLongThinkColor[3] = 1.0f; } ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); ent->mLastLongThinkTime = time + 2000; } else if ( ent->mLastLongThinkTime > time ) { ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); } num++; } } else { if ( inCinematic ) { num = 0; for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( g_cinematic.GetBool() && !ent->cinematic ) { ent->GetPhysics()->UpdateTime( time ); continue; } // ddynerman: save the current thinking entity for instance-dependent currentThinkingEntity = ent; ent->Think(); currentThinkingEntity = NULL; num++; } } else { num = 0; for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { // ddynerman: save the current thinking entity for instance-dependent currentThinkingEntity = ent; ent->Think(); currentThinkingEntity = NULL; num++; } } } // remove any entities that have stopped thinking if ( numEntitiesToDeactivate ) { idEntity *next_ent; int c = 0; for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { next_ent = ent->activeNode.Next(); if ( !ent->thinkFlags ) { ent->activeNode.Remove(); c++; } } //assert( numEntitiesToDeactivate == c ); numEntitiesToDeactivate = 0; } timer_think.Stop(); timer_events.Clear(); timer_events.Start(); if ( isLastPredictFrame ) { // bdube: client entities rvClientEntity* cent; for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { cent->Think(); } } // service any pending events idEvent::ServiceEvents(); // nrausch: player could have been deleted in an event player = GetLocalPlayer(); timer_events.Stop(); if ( isLastPredictFrame ) { // jscott: effect system uses gravity and the player PVS bse->EndFrame(); } // do multiplayer related stuff if ( isMultiplayer ) { mpGame.Run(); } // free the player pvs FreePlayerPVS(); // display how long it took to calculate the current game frame if ( g_frametime.GetBool() ) { Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", time, timer_think.Milliseconds() + timer_events.Milliseconds(), timer_think.Milliseconds(), timer_events.Milliseconds(), num ); } // build the return value ret.consistencyHash = 0; ret.sessionCommand[0] = 0; if ( !isMultiplayer && player ) { ret.health = player->health; ret.heartRate = 0.0f; ret.stamina = 0.0f; // combat is a 0-100 value based on lastHitTime and lastDmgTime // each make up 50% of the time spread over 10 seconds ret.combat = 0; if ( player->lastDmgTime > 0 && time < player->lastDmgTime + 10000 ) { ret.combat += 50.0f * (float) ( time - player->lastDmgTime ) / 10000; } if ( player->lastHitTime > 0 && time < player->lastHitTime + 10000 ) { ret.combat += 50.0f * (float) ( time - player->lastHitTime ) / 10000; } } // see if a target_sessionCommand has forced a changelevel if ( sessionCommand.Length() ) { strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); sessionCommand = ""; break; } // make sure we don't loop forever when skipping a cinematic if ( skipCinematic && ( time > cinematicMaxSkipTime ) ) { Warning( "Exceeded maximum cinematic skip length. Cinematic may be looping infinitely." ); skipCinematic = false; break; } // jscott: additional timings timer_misc2.Stop(); if ( g_frametime.GetInteger() > 1 ) { gameLocal.Printf( "misc:%.1f misc2:%.1f\n", timer_misc.Milliseconds(), timer_misc2.Milliseconds() ); } // bdube: let gameDebug know that its not in a game frame anymore gameDebug.EndFrame( ); gameLogLocal.EndFrame( ); } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); ret.syncNextGameFrame = skipCinematic; if ( skipCinematic ) { soundSystem->EndCinematic(); soundSystem->SetMute( false ); skipCinematic = false; } // show any debug info for this frame RunDebugInfo(); D_DrawDebugLines(); g_simpleItems.ClearModified(); return ret; } // RAVEN END /* ====================================================================== Game view drawing ====================================================================== */ /* ==================== idGameLocal::CalcFov Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio ==================== */ void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const { float x; float y; float ratio_x; float ratio_y; if ( !sys->FPU_StackIsEmpty() ) { Printf( sys->FPU_GetState() ); Error( "idGameLocal::CalcFov: FPU stack not empty" ); } // RAVEN BEGIN // jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes if ( g_fixedHorizFOV.GetBool() ) { int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); switch( aspectChoice ) { default : case 0 : // 4:3 ratio_x = 4.0f; ratio_y = 3.0f; break; case 1 : // 16:9 ratio_x = 16.0f; ratio_y = 9.0f; break; case 2 : // 16:10 ratio_x = 16.0f; ratio_y = 10.0f; break; } x = ratio_x / idMath::Tan( base_fov / 360.0f * idMath::PI ); y = idMath::ATan( ratio_y, x ); fov_y = y * 360.0f / idMath::PI; fov_x = base_fov; return; } // RAVEN END // first, calculate the vertical fov based on a 640x480 view x = 640.0f / idMath::Tan( base_fov / 360.0f * idMath::PI ); y = idMath::ATan( 480.0f, x ); fov_y = y * 360.0f / idMath::PI; // FIXME: somehow, this is happening occasionally assert( fov_y > 0 ); if ( fov_y <= 0 ) { Printf( sys->FPU_GetState() ); Error( "idGameLocal::CalcFov: bad result" ); } int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); switch( aspectChoice ) { default : case 0 : // 4:3 fov_x = base_fov; return; break; case 1 : // 16:9 ratio_x = 16.0f; ratio_y = 9.0f; break; case 2 : // 16:10 ratio_x = 16.0f; ratio_y = 10.0f; break; } y = ratio_y / idMath::Tan( fov_y / 360.0f * idMath::PI ); fov_x = idMath::ATan( ratio_x, y ) * 360.0f / idMath::PI; if ( fov_x < base_fov ) { fov_x = base_fov; x = ratio_x / idMath::Tan( fov_x / 360.0f * idMath::PI ); fov_y = idMath::ATan( ratio_y, x ) * 360.0f / idMath::PI; } // FIXME: somehow, this is happening occasionally assert( ( fov_x > 0 ) && ( fov_y > 0 ) ); if ( ( fov_y <= 0 ) || ( fov_x <= 0 ) ) { Printf( sys->FPU_GetState() ); Error( "idGameLocal::CalcFov: bad result" ); } } void ClearClipProfile( void ); void DisplayClipProfile( void ); /* ================ idGameLocal::Draw makes rendering and sound system calls ================ */ bool idGameLocal::Draw( int clientNum ) { // DisplayClipProfile( ); // ClearClipProfile( ); if ( isMultiplayer ) { return mpGame.Draw( clientNum ); } idPlayer *player = static_cast(entities[ clientNum ]); if ( !player ) { return false; } // RAVEN BEGIN // mwhitlock: Xenon texture streaming. #if defined(_XENON) renderView_t *view = player->GetRenderView(); // nrausch: view doesn't necessarily exist yet if ( !view ) { player->CalculateRenderView(); view = player->GetRenderView(); } #endif // RAVEN END // render the scene player->playerView.RenderPlayerView( player->hud ); // RAVEN BEGIN // bdube: debugging HUD gameDebug.DrawHud( ); // RAVEN END return true; } /* ================ idGameLocal::HandleESC ================ */ escReply_t idGameLocal::HandleESC( idUserInterface **gui ) { //RAVEN BEGIN //asalmon: xbox dedicated server needs to bring up a special menu #ifdef _XBOX if( cvarSystem->GetCVarBool( "net_serverDedicated" )) { return ESC_MAIN; } #endif //RAVEN END if ( isMultiplayer ) { *gui = StartMenu(); // we may set the gui back to NULL to hide it return ESC_GUI; } idPlayer *player = GetLocalPlayer(); if ( player ) { if ( player->HandleESC() ) { return ESC_IGNORE; } else { return ESC_MAIN; } } return ESC_MAIN; } /* ================ idGameLocal::UpdatePlayerPostMainMenu ================ */ void idGameLocal::UpdatePlayerPostMainMenu() { idPlayer* player = GetLocalPlayer(); //dedicated server? if( !player) { return; } //crosshair may have changed player->UpdateHudWeapon(); } /* ================ idGameLocal::StartMenu ================ */ idUserInterface* idGameLocal::StartMenu( void ) { if ( !isMultiplayer ) { return NULL; } return mpGame.StartMenu(); } /* ================ idGameLocal::GetClientStats ================ */ void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) { mpGame.PlayerStats( clientNum, data, len ); } /* ================ idGameLocal::SwitchTeam ================ */ void idGameLocal::SwitchTeam( int clientNum, int team ) { idPlayer * player; player = clientNum >= 0 ? static_cast( gameLocal.entities[ clientNum ] ) : NULL; if ( !player ) { return; } int oldTeam = player->team; // Put in spectator mode if ( team == -1 ) { static_cast< idPlayer * >( entities[ clientNum ] )->Spectate( true ); } // Switch to a team else { mpGame.SwitchToTeam ( clientNum, oldTeam, team ); } } /* ================ idGameLocal::HandleGuiCommands ================ */ const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) { if ( !isMultiplayer ) { return NULL; } return mpGame.HandleGuiCommands( menuCommand ); } /* ================ idGameLocal::HandleMainMenuCommands ================ */ void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { if ( !idStr::Icmp( menuCommand, "initCreateServerSettings" ) ) { int guiValue = 0; switch ( mpGame.GameTypeToVote( si_gameType.GetString() ) ) { case idMultiplayerGame::VOTE_GAMETYPE_DM: guiValue = 0; break; case idMultiplayerGame::VOTE_GAMETYPE_TOURNEY: guiValue = 1; break; case idMultiplayerGame::VOTE_GAMETYPE_TDM: guiValue = 2; break; case idMultiplayerGame::VOTE_GAMETYPE_CTF: guiValue = 3; break; case idMultiplayerGame::VOTE_GAMETYPE_ARENA_CTF: guiValue = 4; break; case idMultiplayerGame::VOTE_GAMETYPE_DEADZONE: guiValue = 5; break; } gui->SetStateInt( "currentGametype", guiValue ); } else if ( !idStr::Icmp( menuCommand, "filterByNextMod" ) ) { BuildModList(); if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { filterMod = 0; networkSystem->UseSortFunction( filterByMod, true ); gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); } else { ++filterMod; if ( filterMod < modList.Num() ) { networkSystem->UseSortFunction( filterByMod, true ); gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); } else { filterMod = -1; networkSystem->UseSortFunction( filterByMod, false ); gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); } } } else if ( !idStr::Icmp( menuCommand, "filterByPrevMod" ) ) { BuildModList(); if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { filterMod = modList.Num() - 1; networkSystem->UseSortFunction( filterByMod, true ); gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); } else { --filterMod; if ( filterMod >= 0 ) { networkSystem->UseSortFunction( filterByMod, true ); gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); } else { filterMod = -1; networkSystem->UseSortFunction( filterByMod, false ); gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); } } } else if ( !idStr::Icmp( menuCommand, "updateFilterByMod" ) ) { if ( filterMod < 0 || filterMod >= modList.Num() ) { gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); } else { gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); } } else if ( !idStr::Icmp( menuCommand, "server_clearSort" ) ) { filterMod = -1; gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); } return; } /* ================ idGameLocal::GetLevelMap should only be used for in-game level editing ================ */ idMapFile *idGameLocal::GetLevelMap( void ) { // RAVEN BEGIN // rhummer: Added the HasBeenResolved check, if resolve has been run then it doesn't have func_groups. // Which we probably don't want, so force the map to be reloaded. if ( mapFile && mapFile->HasPrimitiveData() && !mapFile->HasBeenResloved() ) { // RAVEN END return mapFile; } if ( !mapFileName.Length() ) { return NULL; } if ( mapFile ) { delete mapFile; } mapFile = new idMapFile; if ( !mapFile->Parse( mapFileName ) ) { delete mapFile; mapFile = NULL; } return mapFile; } /* ================ idGameLocal::GetMapName ================ */ const char *idGameLocal::GetMapName( void ) const { return mapFileName.c_str(); } /* ================ idGameLocal::CallFrameCommand ================ */ void idGameLocal::CallFrameCommand( idEntity* ent, const char* frameCommand ) { const function_t *func = program.FindFunction( frameCommand ); if ( !func ) { Warning( "Script function '%s' not found.", frameCommand ); return; } CallFrameCommand ( ent, func ); } void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) { frameCommandThread->CallFunction( ent, frameCommand, true ); frameCommandThread->Execute(); } /* ================ idGameLocal::CallObjectFrameCommand ================ */ void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) { const function_t *func; func = ent->scriptObject.GetFunction( frameCommand ); if ( !func ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( !ent->IsType( idTestModel::GetClassType() ) ) { // RAVEN END Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() ); } } else { frameCommandThread->CallFunction( ent, func, true ); frameCommandThread->Execute(); } } /* ================ idGameLocal::ShowTargets ================ */ void idGameLocal::ShowTargets( void ) { idMat3 axis = GetLocalPlayer()->viewAngles.ToMat3(); idVec3 up = axis[ 2 ] * 5.0f; const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin(); idBounds viewTextBounds( viewPos ); idBounds viewBounds( viewPos ); idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); idEntity *ent; idEntity *target; int i; idBounds totalBounds; viewTextBounds.ExpandSelf( 128.0f ); viewBounds.ExpandSelf( 512.0f ); for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { totalBounds = ent->GetPhysics()->GetAbsBounds(); for( i = 0; i < ent->targets.Num(); i++ ) { target = ent->targets[ i ].GetEntity(); if ( target ) { totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() ); } } if ( !viewBounds.IntersectsBounds( totalBounds ) ) { continue; } float dist; idVec3 dir = totalBounds.GetCenter() - viewPos; dir.NormalizeFast(); totalBounds.RayIntersection( viewPos, dir, dist ); float frac = ( 512.0f - dist ) / 512.0f; if ( frac < 0.0f ) { continue; } gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() ); if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter(); gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 ); gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 ); gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 ); } for( i = 0; i < ent->targets.Num(); i++ ) { target = ent->targets[ i ].GetEntity(); if ( target ) { gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() ); } } } } /* ================ idGameLocal::RunDebugInfo ================ */ void idGameLocal::RunDebugInfo( void ) { idEntity *ent; idPlayer *player; player = GetLocalPlayer(); if ( !player ) { return; } const idVec3 &origin = player->GetPhysics()->GetOrigin(); if ( g_showEntityInfo.GetBool() ) { idBounds viewTextBounds( origin ); idBounds viewBounds( origin ); viewTextBounds.ExpandSelf( 128.0f ); viewBounds.ExpandSelf( 512.0f ); for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { // don't draw the worldspawn if ( ent == world ) { continue; } // RAVEN BEGIN // rjohnson: moved entity info out of idGameLocal into its own function ent->DrawDebugEntityInfo(&viewBounds, &viewTextBounds); // RAVEN END } } // debug tool to draw bounding boxes around active entities if ( g_showActiveEntities.GetBool() ) { for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { idBounds b = ent->GetPhysics()->GetBounds(); if ( b.GetVolume() <= 0 ) { b[0][0] = b[0][1] = b[0][2] = -8; b[1][0] = b[1][1] = b[1][2] = 8; } if ( ent->fl.isDormant ) { gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() ); } else { gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() ); } } } // RAVEN BEGIN // bdube: client entities if ( cl_showEntityInfo.GetBool ( ) ) { rvClientEntity* cent; for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { cent->DrawDebugInfo ( ); } } // RAVEN END if ( g_showTargets.GetBool() ) { ShowTargets(); } if ( g_showTriggers.GetBool() ) { idTrigger::DrawDebugInfo(); } if ( ai_showCombatNodes.GetBool() ) { // idCombatNode::DrawDebugInfo(); } if ( ai_showPaths.GetBool() ) { idPathCorner::DrawDebugInfo(); } if ( g_editEntityMode.GetBool() ) { if ( gameLocal.isMultiplayer ) { g_editEntityMode.SetInteger(0); Printf( "Not allowed in multiplayer.\n" ); } else { editEntities->DisplayEntities(); } } if ( g_showCollisionWorld.GetBool() ) { // use g_maxShowDistance value instead of 128.0f collisionModelManager->DrawModel( clip[0]->GetWorldCollisionModel(), vec3_origin, mat3_identity, origin, mat3_identity, g_maxShowDistance.GetFloat() ); } if ( g_showCollisionModels.GetBool() ) { if( g_showCollisionModels.GetInteger() == 2 ) { clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player, &idPlayer::GetClassType() ); } else { clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player ); } } if ( g_showCollisionTraces.GetBool() ) { clip[ 0 ]->PrintStatistics(); } if ( g_showPVS.GetInteger() ) { pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL ); } // RAVEN BEGIN // rjohnson: added debug hud if ( gameDebug.IsHudActive ( DBGHUD_PHYSICS ) ) { clip[ 0 ]->DebugHudStatistics(); } clip[ 0 ]->ClearStatistics(); // RAVEN END if ( aas_test.GetInteger() >= 0 ) { idAAS *aas = GetAAS( aas_test.GetInteger() ); if ( aas ) { aas->Test( origin ); if ( ai_testPredictPath.GetBool() ) { idVec3 velocity; predictedPath_t path; velocity.x = idMath::Cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; velocity.y = idMath::Sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; velocity.z = 0.0f; idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path ); } } // RAVEN BEGIN // rjohnson: added more debug drawing if ( aas_showAreas.GetInteger() == 3 ) { for( int i=0; iIsValid() ) { continue; } if ( aas->GetSettings()->debugDraw ) { aas->ShowAreas( origin ); } } } if ( aas_showProblemAreas.GetInteger() == 3 ) { for( int i=0; iIsValid() ) { continue; } if ( aas->GetSettings()->debugDraw ) { aas->ShowAreas( origin, true ); } } } // RAVEN END } if ( ai_showObstacleAvoidance.GetInteger() == 2 ) { idAAS *aas = GetAAS( 0 ); if ( aas ) { idVec3 seekPos; obstaclePath_t path; seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f; idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path ); } } // collision map debug output collisionModelManager->DebugOutput( player->GetEyePosition(), mat3_identity ); // RAVEN BEGIN // jscott: for debugging playbacks if( g_showPlayback.GetInteger() ) { gameEdit->DrawPlaybackDebugInfo(); } // RAVEN END // RAVEN BEGIN // ddynerman: SD's clip sector code if ( g_showClipSectors.GetBool() ) { clip[ 0 ]->DrawClipSectors(); } if ( g_showAreaClipSectors.GetFloat() ) { clip[ 0 ]->DrawAreaClipSectors( g_showAreaClipSectors.GetFloat() ); } // RAVEN END } /* ================== idGameLocal::NumAAS ================== */ int idGameLocal::NumAAS( void ) const { return aasList.Num(); } /* ================== idGameLocal::GetAAS ================== */ idAAS *idGameLocal::GetAAS( int num ) const { if ( ( num >= 0 ) && ( num < aasList.Num() ) ) { if ( aasList[ num ] && aasList[ num ]->GetSettings() ) { return aasList[ num ]; } } return NULL; } /* ================== idGameLocal::GetAAS ================== */ idAAS *idGameLocal::GetAAS( const char *name ) const { int i; for ( i = 0; i < aasNames.Num(); i++ ) { if ( aasNames[ i ] == name ) { if ( !aasList[ i ]->GetSettings() ) { return NULL; } else { return aasList[ i ]; } } } return NULL; } /* ================== idGameLocal::SetAASAreaState ================== */ void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) { int i; for( i = 0; i < aasList.Num(); i++ ) { aasList[ i ]->SetAreaState( bounds, areaContents, closed ); } } /* ================== idGameLocal::AddAASObstacle ================== */ aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) { int i; aasHandle_t obstacle; aasHandle_t check; if ( !aasList.Num() ) { return -1; } obstacle = aasList[ 0 ]->AddObstacle( bounds ); for( i = 1; i < aasList.Num(); i++ ) { check = aasList[ i ]->AddObstacle( bounds ); assert( check == obstacle ); } return obstacle; } /* ================== idGameLocal::RemoveAASObstacle ================== */ void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) { int i; for( i = 0; i < aasList.Num(); i++ ) { aasList[ i ]->RemoveObstacle( handle ); } } /* ================== idGameLocal::RemoveAllAASObstacles ================== */ void idGameLocal::RemoveAllAASObstacles( void ) { int i; for( i = 0; i < aasList.Num(); i++ ) { aasList[ i ]->RemoveAllObstacles(); } } // RAVEN BEGIN // mwhitlock: added entity memory usage stuff. /* ================== idGameLocal::GetEntityMemoryUsage Compute combined total memory footprint of server and client entity storage. ================== */ size_t idGameLocal::GetEntityMemoryUsage ( void ) const { // Server ents. size_t serverEntitiesSize = 0; for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { serverEntitiesSize += sizeof( idEntity ); } // Client ents. size_t clientEntitiesSize = 0; for( rvClientEntity *ent = gameLocal.clientSpawnedEntities.Next() ; ent != NULL; ent = ent->spawnNode.Next() ) { clientEntitiesSize += ent->Size(); } return serverEntitiesSize + clientEntitiesSize; } // RAVEN END /* ================== idGameLocal::CheatsOk ================== */ bool idGameLocal::CheatsOk( bool requirePlayer ) { idPlayer *player; if ( isMultiplayer && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { Printf( "Not allowed in multiplayer.\n" ); return false; } if ( developer.GetBool() ) { return true; } player = GetLocalPlayer(); if ( !requirePlayer || ( player && ( player->health > 0 ) ) ) { return true; } Printf( "You must be alive to use this command.\n" ); return false; } /* =================== idGameLocal::RegisterEntity =================== */ void idGameLocal::RegisterEntity( idEntity *ent ) { int spawn_entnum; if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) { Error( "idGameLocal::RegisterEntity: spawn count overflow" ); } firstFreeIndex = Max( minSpawnIndex, firstFreeIndex ); if ( !spawnArgs.GetInt( "spawn_entnum", "0", spawn_entnum ) ) { while ( entities[firstFreeIndex] && firstFreeIndex < ENTITYNUM_MAX_NORMAL ) { firstFreeIndex++; } if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { Error( "no free entities" ); } spawn_entnum = firstFreeIndex++; } else { assert( spawn_entnum < MAX_CLIENTS || spawn_entnum >= minSpawnIndex ); } entities[ spawn_entnum ] = ent; spawnIds[ spawn_entnum ] = spawnCount++; ent->entityNumber = spawn_entnum; ent->spawnNode.AddToEnd( spawnedEntities ); ent->spawnArgs.TransferKeyValues( spawnArgs ); if ( spawn_entnum >= num_entities ) { num_entities++; } // RAVEN BEGIN // bdube: keep track of last time an entity was registered entityRegisterTime = time; // RAVEN END } /* =================== idGameLocal::UnregisterEntity =================== */ void idGameLocal::UnregisterEntity( idEntity *ent ) { assert( ent ); if ( editEntities ) { editEntities->RemoveSelectedEntity( ent ); } if ( ( ent->entityNumber != ENTITYNUM_NONE ) && ( entities[ ent->entityNumber ] == ent ) ) { ent->spawnNode.Remove(); entities[ ent->entityNumber ] = NULL; spawnIds[ ent->entityNumber ] = -1; if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeIndex ) { firstFreeIndex = ent->entityNumber; } ent->entityNumber = ENTITYNUM_NONE; } // RAVEN BEGIN // bdube: keep track of last time an entity was registered entityRegisterTime = time; // RAVEN END } /* =============== idGameLocal::SkipEntityIndex =============== */ void idGameLocal::SkipEntityIndex( void ) { assert( entities[ firstFreeIndex ] == NULL ); firstFreeIndex++; if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { Error( "no free entities" ); } } /* ================ idGameLocal::SpawnEntityType ================ */ idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) { idClass *obj; #ifdef _DEBUG if ( isClient ) { assert( bIsClientReadSnapshot ); } #endif // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( !classdef.IsType( idEntity::GetClassType() ) ) { // RAVEN END Error( "Attempted to spawn non-entity class '%s'", classdef.classname ); } try { if ( args ) { spawnArgs = *args; } else { spawnArgs.Clear(); } obj = classdef.CreateInstance(); obj->CallSpawn(); } catch( idAllocError & ) { obj = NULL; } spawnArgs.Clear(); return static_cast(obj); } /* =================== idGameLocal::SpawnClientEntityDef Finds the spawn function for the client entity and calls it, returning false if not found =================== */ bool idGameLocal::SpawnClientEntityDef( const idDict &args, rvClientEntity **cent, bool setDefaults, const char* spawn ) { const char *classname; idTypeInfo *cls; idClass *obj; idStr error; const char *name; if ( cent ) { *cent = NULL; } spawnArgs = args; if ( spawnArgs.GetBool( "nospawn" ) ){ //not meant to actually spawn, just there for some compiling process return false; } if ( spawnArgs.GetString( "name", "", &name ) ) { error = va( " on '%s'", name ); } spawnArgs.GetString( "classname", NULL, &classname ); const idDeclEntityDef *def = FindEntityDef( classname, false ); if ( !def ) { // RAVEN BEGIN // jscott: a NULL classname would crash Warning() if( classname ) { Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); } // RAVEN END return false; } spawnArgs.SetDefaults( &def->dict ); // check if we should spawn a class object if( spawn == NULL ) { spawnArgs.GetString( "spawnclass", NULL, &spawn ); } if ( spawn ) { cls = idClass::GetClass( spawn ); if ( !cls ) { Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); return false; } obj = cls->CreateInstance(); if ( !obj ) { Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); return false; } obj->CallSpawn(); if ( cent && obj->IsType( rvClientEntity::GetClassType() ) ) { *cent = static_cast(obj); } return true; } Warning( "%s doesn't include a spawnfunc%s.", classname, error.c_str() ); return false; } /* =================== idGameLocal::SpawnEntityDef Finds the spawn function for the entity and calls it, returning false if not found =================== */ bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) { const char *classname; const char *spawn; idTypeInfo *cls; idClass *obj; idStr error; const char *name; TIME_THIS_SCOPE( __FUNCLINE__); if ( ent ) { *ent = NULL; } spawnArgs = args; if ( spawnArgs.GetBool( "nospawn" ) ) {//not meant to actually spawn, just there for some compiling process return false; } if ( spawnArgs.GetString( "name", "", &name ) ) { // RAVEN BEGIN // jscott: fixed sprintf to idStr error = va( " on '%s'", name ); // RAVEN END } spawnArgs.GetString( "classname", NULL, &classname ); const idDeclEntityDef *def = FindEntityDef( classname, false ); if ( !def ) { // RAVEN BEGIN // jscott: a NULL classname would crash Warning() if( classname ) { Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); } // RAVEN END return false; } spawnArgs.SetDefaults( &def->dict ); // RAVEN BEGIN // rjohnson: entity usage stats if ( g_keepEntityStats.GetBool() ) { if ( idStr::Icmp( classname, "func_spawner" ) == 0 || idStr::Icmp( classname, "func_spawner_enemy" ) == 0 ) { // special case for spawners for( int i = 1; ; i++ ) { char tempSpawn[128]; const char *tempClassname; sprintf( tempSpawn, "def_spawn_type%d", i ); tempClassname = spawnArgs.GetString( tempSpawn, NULL ); if ( tempClassname ) { const idDeclEntityDef *tempDef = FindEntityDef( tempClassname, false ); if ( tempDef ) { idDict tempArgs = tempDef->dict; tempArgs.Set( "mapFileName", mapFileNameStripped ); entityUsageList.Insert( tempArgs ); } } else { break; } } } else if ( def->dict.GetBool( "report_stats" ) ) { idDict tempArgs = spawnArgs; tempArgs.Set( "mapFileName", mapFileNameStripped ); entityUsageList.Insert( tempArgs ); } } // RAVEN END // check if we should spawn a class object spawnArgs.GetString( "spawnclass", NULL, &spawn ); if ( spawn ) { cls = idClass::GetClass( spawn ); if ( !cls ) { Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); return false; } obj = cls->CreateInstance(); if ( !obj ) { Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); return false; } obj->CallSpawn(); // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent && obj->IsType( idEntity::GetClassType() ) ) { // RAVEN END *ent = static_cast(obj); } return true; } // check if we should call a script function to spawn spawnArgs.GetString( "spawnfunc", NULL, &spawn ); if ( spawn ) { const function_t *func = program.FindFunction( spawn ); if ( !func ) { Warning( "Could not spawn '%s'. Script function '%s' not found%s.", classname, spawn, error.c_str() ); return false; } idThread *thread = new idThread( func ); thread->DelayedStart( 0 ); return true; } Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() ); return false; } // abahr: idEntity* idGameLocal::SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs ) { idDict finalArgs; const idDict* entityDict = NULL; idEntity* entity = NULL; TIME_THIS_SCOPE( __FUNCLINE__); if( !entityDefName ) { return NULL; } entityDict = FindEntityDefDict( entityDefName, false ); if( !entityDict ) { return NULL; } if( !additionalArgs ) { SpawnEntityDef( *entityDict, &entity ); } else { finalArgs.Copy( *entityDict ); finalArgs.Copy( *additionalArgs ); SpawnEntityDef( finalArgs, &entity ); } return entity; } // RAVEN END /* ================ idGameLocal::FindEntityDef ================ */ const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const { TIME_THIS_SCOPE( __FUNCLINE__); const idDecl *decl = NULL; if ( isMultiplayer ) { decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false ); } if ( !decl ) { decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault ); } return static_cast( decl ); } /* ================ idGameLocal::FindEntityDefDict ================ */ const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const { TIME_THIS_SCOPE( __FUNCLINE__); const idDeclEntityDef *decl = FindEntityDef( name, makeDefault ); return decl ? &decl->dict : NULL; } /* ================ idGameLocal::InhibitEntitySpawn ================ */ bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { bool result = false; if ( isMultiplayer ) { spawnArgs.GetBool( "not_multiplayer", "0", result ); } else if ( g_skill.GetInteger() == 0 ) { spawnArgs.GetBool( "not_easy", "0", result ); } else if ( g_skill.GetInteger() == 1 ) { spawnArgs.GetBool( "not_medium", "0", result ); } else { spawnArgs.GetBool( "not_hard", "0", result ); } const char *name; #ifndef ID_DEMO_BUILD if ( g_skill.GetInteger() == 3 ) { name = spawnArgs.GetString( "classname" ); if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 ) { result = true; } } #endif // RAVEN BEGIN // bdube: suppress ents that don't match the entity filter const char* entityFilter; if ( serverInfo.GetString( "si_entityFilter", "", &entityFilter ) && *entityFilter ) { if ( spawnArgs.MatchPrefix ( "filter_" ) && !spawnArgs.GetBool ( va("filter_%s", entityFilter) ) ) { return true; } } // RAVEN END // RITUAL BEGIN // squirrel: suppress ents that aren't supported in Buying modes (if that's the mode we're in) if ( mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { if ( spawnArgs.GetBool( "disableSpawnInBuying", "0" ) ) { return true; } /// Don't spawn weapons or armor vests or ammo in Buying modes idStr classname = spawnArgs.GetString( "classname" ); if( idStr::FindText( classname, "weapon_" ) == 0 || idStr::FindText( classname, "item_armor_small" ) == 0 || idStr::FindText( classname, "ammo_" ) == 0 || idStr::FindText( classname, "item_armor_large" ) == 0 ) { return true; } } // RITUAL END // suppress deadzone triggers if we're not running DZ if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "DeadZone" ) != 0 ) { if ( idStr::Icmp( spawnArgs.GetString( "classname" ), "trigger_controlzone" ) == 0 ) { return true; } } return result; } /* ================ idGameLocal::SetSkill ================ */ void idGameLocal::SetSkill( int value ) { int skill_level; if ( value < 0 ) { skill_level = 0; } else if ( value > 3 ) { skill_level = 3; } else { skill_level = value; } g_skill.SetInteger( skill_level ); } /* ============== idGameLocal::GameState Used to allow entities to know if they're being spawned during the initial spawn. ============== */ gameState_t idGameLocal::GameState( void ) const { return gamestate; } /* ============== idGameLocal::SpawnMapEntities Parses textual entity definitions out of an entstring and spawns gentities. ============== */ // RAVEN BEGIN // ddynerman: multiple game instances void idGameLocal::SpawnMapEntities( int instance, unsigned short* entityNumIn, unsigned short* entityNumOut, int* startSpawnCount ) { // RAVEN END int i; int num; int inhibit; int latchedSpawnCount = -1; idMapEntity *mapEnt; int numEntities; idDict args; idDict items; bool proto69 = ( GetCurrentDemoProtocol() == 69 ); Printf( "Spawning entities\n" ); TIME_THIS_SCOPE( __FUNCLINE__); if ( mapFile == NULL ) { Printf("No mapfile present\n"); return; } SetSkill( g_skill.GetInteger() ); numEntities = mapFile->GetNumEntities(); if ( numEntities == 0 ) { Error( "...no entities" ); } // RAVEN BEGIN // ddynerman: on the server, perform world-initialization when spawning instance 0 only and while starting up // on the client, perform world-init when spawning while starting up, as client main instance may not be ID 0 // always spawn world-init in single-player num = inhibit = 0; // the worldspawn is a special that performs any global setup // needed by a level if ( ( gameLocal.isServer && instance == 0 && gamestate == GAMESTATE_STARTUP ) || ( gameLocal.isClient && gamestate == GAMESTATE_STARTUP ) || !gameLocal.isMultiplayer ) { mapEnt = mapFile->GetEntity( 0 ); args = mapEnt->epairs; args.SetInt( "spawn_entnum", ENTITYNUM_WORLD ); // on the client, spawnCount won't always start at INITIAL_SPAWN_COUNT (see rvInstance::Populate()) // make sure the world and physics ent get the right spawn id if( gameLocal.isClient && spawnCount != INITIAL_SPAWN_COUNT ) { latchedSpawnCount = spawnCount; spawnCount = INITIAL_SPAWN_COUNT; } // abahr: precache stuff on worldSpawn like player def and other misc things CacheDictionaryMedia( &args ); // jnewquist: Use accessor for static class type if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::GetClassType() ) ) { Error( "Problem spawning world entity" ); } num++; // bdube: dummy entity for client entities with physics args.Clear(); args.SetInt( "spawn_entnum", ENTITYNUM_CLIENT ); // jnewquist: Use accessor for static class type if ( !SpawnEntityType( rvClientPhysics::GetClassType(), &args, true ) || !entities[ ENTITYNUM_CLIENT ] ) { Error( "Problem spawning client physics entity" ); } if( gameLocal.isClient && latchedSpawnCount != -1 ) { spawnCount = latchedSpawnCount; } isMapEntity[ ENTITYNUM_CLIENT ] = true; isMapEntity[ ENTITYNUM_WORLD ] = true; // RAVEN END } // capture spawn count of start of map entities (after we've spawned in the physics ents) if ( startSpawnCount ) { (*startSpawnCount) = spawnCount; } for ( i = 1 ; i < numEntities ; i++ ) { mapEnt = mapFile->GetEntity( i ); args = mapEnt->epairs; // RAVEN BEGIN // ddynerman: merge the dicts ahead of SpawnEntityDef() so we can inhibit using merged info const idDeclEntityDef* entityDef = FindEntityDef( args.GetString( "classname" ), false ); if( entityDef == NULL ) { gameLocal.Error( "idGameLocal::SpawnMapEntities() - Unknown entity classname '%s'\n", args.GetString( "classname" ) ); return; } args.SetDefaults( &(entityDef->dict) ); // RAVEN END if ( !InhibitEntitySpawn( args ) ) { if( args.GetBool( "inv_item" ) ) { if( !items.GetBool( args.GetString( "inv_icon" ) ) ) { networkSystem->AddLoadingIcon( args.GetString( "inv_icon" ) ); networkSystem->SetLoadingText( common->GetLocalizedString( args.GetString( "inv_name" ) ) ); items.SetBool( args.GetString( "inv_icon" ), true ); } } // precache any media specified in the map entity CacheDictionaryMedia( &args ); // RAVEN BEGIN if ( instance != 0 ) { // ddynerman: allow this function to be called multiple-times to respawn map entities in other instances args.SetInt( "instance", instance ); args.Set( "name", va( "%s_instance%d", args.GetString( "name" ), instance ) ); //gameLocal.Printf( "Instance %d: Spawning %s (class: %s)\n", instance, args.GetString( "name" ), args.GetString( "classname" ) ); // need a better way to do this - map entities that target other map entities need // to target the ones in the correct instance. idStr target; if( args.GetString( "target", "", target ) ) { args.Set( "target", va( "%s_instance%d", target.c_str(), instance ) ); } // and also udpate binds on a per-instance bases idStr bind; if( args.GetString( "bind", "", bind ) ) { args.Set( "bind", va( "%s_instance%d", bind.c_str(), instance ) ); } // and also door triggers idStr triggerOnOpen; if( args.GetString( "triggerOnOpen", "", triggerOnOpen ) ) { args.Set( "triggerOnOpen", va( "%s_instance%d", triggerOnOpen.c_str(), instance ) ); } idStr triggerOpened; if( args.GetString( "triggerOpened", "", triggerOpened ) ) { args.Set( "triggerOpened", va( "%s_instance%d", triggerOpened.c_str(), instance ) ); } } // backward compatible 1.2 playback: proto69 reads the entity mapping list from the server // only 1.2 backward replays should get passed a != NULL entityNumIn assert( entityNumIn == NULL || proto69 ); if ( proto69 && entityNumIn ) { assert( gameLocal.isClient ); if ( entityNumIn[ i ] < 0 || entityNumIn[ i ] >= MAX_GENTITIES ) { // this entity was not spawned in on the server, ignore it here // this is one of the symptoms of net corruption <= 1.2 - fix here acted as a band aid // if you replay a 1.2 netdemo and hit this, it's likely other things will go wrong in the replay common->Warning( "entity inhibited on server not properly inhibited on client - map ent %d ( %s )", i, args.GetString( "name" ) ); inhibit++; continue; } args.SetInt( "spawn_entnum", entityNumIn[ i ] ); } idEntity* ent = NULL; SpawnEntityDef( args, &ent ); //common->Printf( "pop: spawn map ent %d at %d ( %s )\n", i, ent->entityNumber, args.GetString( "name" ) ); if ( ent && entityNumOut ) { entityNumOut[ i ] = ent->entityNumber; } if ( gameLocal.GetLocalPlayer() && ent && gameLocal.isServer && instance != gameLocal.GetLocalPlayer()->GetInstance() ) { // if we're spawning entities not in our instance, tell them not to draw ent->BecomeInactive( TH_UPDATEVISUALS ); } if ( ent ) { isMapEntity[ ent->entityNumber ] = true; } // RAVEN END num++; } else { if ( !proto69 ) { // keep counting and leave an empty slot in the entity list for inhibited entities // this so we maintain the same layout as the server and don't change it across restarts with different inhibit schemes //common->Printf( "pop: skip map ent %d at index %d ( %s )\n", i, firstFreeIndex, args.GetString( "name" ) ); SkipEntityIndex(); } else { // backward 1.2 netdemo replay compatibility - no skipping // they might net corrupt but there's no fix client side assert( isClient ); } inhibit++; } } Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit ); } /* ================ idGameLocal::AddEntityToHash ================ */ void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { if ( FindEntity( name ) ) { Error( "Multiple entities named '%s'", name ); } entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); } /* ================ idGameLocal::RemoveEntityFromHash ================ */ bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { int hash, i; hash = entityHash.GenerateKey( name, true ); for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { entityHash.Remove( hash, i ); return true; } } return false; } /* ================ idGameLocal::GetTargets ================ */ int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const { int i, num, refLength; const idKeyValue *arg; idEntity *ent; list.Clear(); refLength = strlen( ref ); num = args.GetNumKeyVals(); for( i = 0; i < num; i++ ) { arg = args.GetKeyVal( i ); if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) { ent = FindEntity( arg->GetValue() ); if ( ent ) { idEntityPtr &entityPtr = list.Alloc(); entityPtr = ent; } } } return list.Num(); } /* ============= idGameLocal::GetTraceEntity returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. ============= */ idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const { idEntity *master; if ( !entities[ trace.c.entityNum ] ) { return NULL; } master = entities[ trace.c.entityNum ]->GetBindMaster(); if ( master ) { return master; } return entities[ trace.c.entityNum ]; } /* ============= idGameLocal::ArgCompletion_EntityName Argument completion for entity names ============= */ void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) { int i; for( i = 0; i < gameLocal.num_entities; i++ ) { if ( gameLocal.entities[ i ] ) { callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); } } } /* ============= idGameLocal::ArgCompletion_AIName Argument completion for idAI entity names ============= */ void idGameLocal::ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ) { int i; for( i = 0; i < gameLocal.num_entities; i++ ) { if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idAI::GetClassType() ) ) { callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); } } } /* ============= idGameLocal::FindEntity Returns the entity whose name matches the specified string. ============= */ idEntity *idGameLocal::FindEntity( const char *name ) const { int hash, i; hash = entityHash.GenerateKey( name, true ); for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) { return entities[i]; } } return NULL; } /* ============= idGameLocal::FindEntityUsingDef Searches all active entities for the next one using the specified entityDef. Searches beginning at the entity after from, or the beginning if NULL NULL will be returned if the end of the list is reached. ============= */ idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const { idEntity *ent; if ( !from ) { ent = spawnedEntities.Next(); } else { ent = from->spawnNode.Next(); } for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { assert( ent ); if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) { return ent; } } return NULL; } /* ============= idGameLocal::FindTraceEntity Searches all active entities for the closest ( to start ) match that intersects the line start,end ============= */ idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const { idEntity *ent; idEntity *bestEnt; float scale; float bestScale; idBounds b; bestEnt = NULL; bestScale = 1.0f; for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( ent->IsType( c ) && ent != skip ) { b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); if ( b.RayIntersection( start, end-start, scale ) ) { if ( scale >= 0.0f && scale < bestScale ) { bestEnt = ent; bestScale = scale; } } } } return bestEnt; } /* ================ idGameLocal::EntitiesWithinRadius ================ */ int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const { idEntity *ent; idBounds bo( org ); int entCount = 0; bo.ExpandSelf( radius ); for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { entityList[entCount++] = ent; } } return entCount; } /* ================= idGameLocal::KillBox Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed. Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately. If catch_teleport, this only marks teleport players for death on exit ================= */ void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { int i; int num; idEntity * hit; idClipModel *cm; idClipModel *clipModels[ MAX_GENTITIES ]; idPhysics *phys; phys = ent->GetPhysics(); if ( !phys->GetNumClipModels() ) { return; } // RAVEN BEGIN // ddynerman: multiple clip worlds num = ClipModelsTouchingBounds( ent, phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES ); // RAVEN END for ( i = 0; i < num; i++ ) { cm = clipModels[ i ]; // don't check render entities if ( cm->IsRenderModel() ) { continue; } hit = cm->GetEntity(); if ( ( hit == ent ) || !hit->fl.takedamage ) { continue; } if ( !phys->ClipContents( cm ) ) { continue; } // nail it // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( hit->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) { // RAVEN END static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber ); } else if ( !catch_teleport ) { hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); } if ( !gameLocal.isMultiplayer ) { // let the mapper know about it Warning( "'%s' telefragged '%s'", ent->name.c_str(), hit->name.c_str() ); } } } /* ================ idGameLocal::RequirementMet ================ */ bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) { if ( requires.Length() ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( activator->IsType( idPlayer::GetClassType() ) ) { // RAVEN END idPlayer *player = static_cast(activator); idDict *item = player->FindInventoryItem( requires ); if ( item ) { if ( removeItem ) { player->RemoveInventoryItem( item ); } return true; } else { return false; } } } return true; } /* ================ idGameLocal::IsWeaponsStayOn ================ */ bool idGameLocal::IsWeaponsStayOn( void ) { /// Override weapons stay when buying is active if( isMultiplayer && mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { return false; } return serverInfo.GetBool( "si_weaponStay" ); } /* ============ idGameLocal::AlertAI ============ */ void idGameLocal::AlertAI( idEntity *ent ) { // RAVEN BEGIN // bdube: merged if ( ent ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent->IsType( idActor::GetClassType() ) ) { // RAVEN END // alert them for the next frame lastAIAlertActorTime = time + GetMSec(); lastAIAlertActor = static_cast( ent ); } else { lastAIAlertEntityTime = time + GetMSec(); lastAIAlertEntity = ent; } } else { lastAIAlertEntityTime = 0; lastAIAlertActorTime = 0; lastAIAlertEntity = NULL; lastAIAlertActor = NULL; } // RAVEN END } // RAVEN BEGIN // bdube: alert entity returns an entity, alert actor a an actor /* ============ idGameLocal::GetAlertEntity ============ */ idEntity *idGameLocal::GetAlertEntity( void ) { if ( lastAIAlertEntityTime >= time ) { return lastAIAlertEntity.GetEntity(); } return NULL; } /* ============ idGameLocal::GetAlertActor ============ */ idActor *idGameLocal::GetAlertActor( void ) { if ( lastAIAlertActorTime >= time ) { return lastAIAlertActor.GetEntity(); } return NULL; } // RAVEN END /* ============ SortClipModelsByEntity ============ */ static int SortClipModelsByEntity( const void* left, const void* right ) { idEntity* leftEntity = left ? (*((const idClipModel**)left))->GetEntity() : NULL; idEntity* rightEntity = right ? (*((const idClipModel**)right))->GetEntity() : NULL; int entityNumLeft = (leftEntity) ? leftEntity->entityNumber : 0; int entityNumRight = (rightEntity) ? rightEntity->entityNumber : 0; return entityNumLeft - entityNumRight; } // RAVEN BEGIN /* ============ idGameLocal::RadiusDamage Returns the number of actors damaged ============ */ // abahr: changed to work with deathPush void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower, int* hitCount ) { float dist, damageScale, attackerDamageScale, attackerPushScale; idEntity* ent = NULL; idEntity* lastEnt = NULL; idClipModel* clipModel = NULL; idClipModel* clipModelList[ MAX_GENTITIES ]; int numListedClipModels; modelTrace_t result; idVec3 v, damagePoint, dir; int i, damage, radius, push; const idDict *damageDef = FindEntityDefDict( damageDefName, false ); if ( !damageDef ) { Warning( "Unknown damageDef '%s'", damageDefName ); return; } damageDef->GetInt( "damage", "20", damage ); damageDef->GetInt( "radius", "50", radius ); damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); if( gameLocal.isMultiplayer ) { damageDef->GetFloat( "attackerPushScale", "2", attackerPushScale ); } else { damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); } if ( radius < 1 ) { radius = 1; } // ddynerman: multiple clip worlds numListedClipModels = ClipModelsTouchingBounds( inflictor, idBounds(origin).Expand(radius), MASK_ALL, clipModelList, MAX_GENTITIES ); if( numListedClipModels > 0 ) { //Sort list by unique entities for easier searching qsort( clipModelList, numListedClipModels, sizeof(clipModelList[0]), SortClipModelsByEntity ); } if ( inflictor ) { inflictor = inflictor->GetDamageEntity ( ); } if ( attacker ) { attacker = attacker->GetDamageEntity ( ); } if ( ignoreDamage ) { ignoreDamage = ignoreDamage->GetDamageEntity ( ); } for( int c = 0; c < numListedClipModels; ++c ) { clipModel = clipModelList[ c ]; assert( clipModel ); ent = clipModel->GetEntity(); // Skip all entitys that arent primary damage entities if ( !ent || ent != ent->GetDamageEntity ( ) ) { continue; } // Dont damage inflictor or the ignore entity if( ent == inflictor || ent == ignoreDamage ) { continue; } idBounds absBounds = clipModel->GetAbsBounds(); // find the distance from the edge of the bounding box for ( i = 0; i < 3; i++ ) { if ( origin[ i ] < absBounds[0][ i ] ) { v[ i ] = absBounds[0][ i ] - origin[ i ]; } else if ( origin[ i ] > absBounds[1][ i ] ) { v[ i ] = origin[ i ] - absBounds[1][ i ]; } else { v[ i ] = 0; } } dist = v.Length(); if ( dist >= radius ) { continue; } if( gameRenderWorld->FastWorldTrace(result, origin, absBounds.GetCenter()) ) { continue; } RadiusPushClipModel ( inflictor, origin, push, clipModel ); // Only damage unique entities. This works because we have a sorted list if( lastEnt == ent ) { continue; } lastEnt = ent; if ( ent->CanDamage( origin, damagePoint, ignoreDamage ) ) { // push the center of mass higher than the origin so players // get knocked into the air more if( gameLocal.isMultiplayer ) { // fudge the direction in MP to account for player height difference and origin shift // 31.875 = origin is 23.875 units lower in Q4 than Q3 + player is 8 units taller in Q4 dir = ( ent->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 31.875f ) ) - origin; } else { dir = ent->GetPhysics()->GetOrigin() - origin; } dir[2] += 24; // get the damage scale damageScale = dmgPower * ( 1.0f - dist / radius ); if ( ent == attacker ) { damageScale *= attackerDamageScale; } dir.Normalize(); ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(ent->GetPhysics()->GetClipModel()->GetId()) ); // for stats, count the first if( attacker && attacker->IsType( idPlayer::GetClassType() ) && inflictor && inflictor->IsType( idProjectile::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && hitCount ) { // with splash damage projectiles, one projectile fire can damage multiple people. If anyone is hit, // the shot counts as a hit but only report accuracy on the first one to avoid accuracies > 100% statManager->WeaponHit( (const idActor*)attacker, ent, ((idProjectile*)inflictor)->methodOfDeath, (*hitCount) == 0 ); (*hitCount)++; } } } } // RAVEN END /* ============== idGameLocal::RadiusPush ============== */ void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { int i, numListedClipModels; idClipModel *clipModel; idClipModel *clipModelList[ MAX_GENTITIES ]; idVec3 dir; idBounds bounds; modelTrace_t result; idEntity *ent; float scale; // RAVEN BEGIN // abahr: need to use gravity instead of assuming z is up dir = -GetCurrentGravity( const_cast(inflictor) ).ToNormal(); // RAVEN END bounds = idBounds( origin ).Expand( radius ); // get all clip models touching the bounds // RAVEN BEGIN // ddynerman: multiple clip worlds numListedClipModels = ClipModelsTouchingBounds( inflictor, bounds, -1, clipModelList, MAX_GENTITIES ); // bdube: getdamageentity if ( inflictor ) { inflictor = ((idEntity*)inflictor)->GetDamageEntity ( ); } if ( ignore ) { ignore = ((idEntity*)ignore)->GetDamageEntity ( ); } // RAVEN END // apply impact to all the clip models through their associated physics objects for ( i = 0; i < numListedClipModels; i++ ) { clipModel = clipModelList[i]; // never push render models if ( clipModel->IsRenderModel() ) { continue; } ent = clipModel->GetEntity(); // RAVEN BEGIN // bdube: damage entity if ( !ent || ent != ent->GetDamageEntity ( ) ) { continue; } // RAVEN END // never push projectiles // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent->IsType( idProjectile::GetClassType() ) ) { // RAVEN END continue; } // players use "knockback" in idPlayer::Damage // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent->IsType( idPlayer::GetClassType() ) && !quake ) { // RAVEN END continue; } // don't push the ignore entity if ( ent == ignore ) { continue; } if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) { continue; } // scale the push for the inflictor if ( ent == inflictor ) { scale = inflictorScale; } else { scale = 1.0f; } if ( quake ) { clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); } else { // RAVEN BEGIN // bdube: inflictor RadiusPushClipModel( (idEntity*)inflictor, origin, scale * push, clipModel ); // RAVEN END } } } /* ============== idGameLocal::RadiusPushClipModel ============== */ // RAVEN BEGIN // bdube: inflictor void idGameLocal::RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ) { // RAVEN END int i, j; float dot, dist, area; const idTraceModel *trm; const traceModelPoly_t *poly; idFixedWinding w; idVec3 v, localOrigin, center, impulse; trm = clipModel->GetTraceModel(); if ( !trm || 1 ) { impulse = clipModel->GetAbsBounds().GetCenter() - origin; impulse.Normalize(); // RAVEN BEGIN // abahr: removed because z isn't always up //impulse.z += 1.0f; //impulse = idVec3( 0.0, 0.0, 1.0 ); clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), clipModel->GetOrigin(), push * impulse, true ); // RAVEN END return; } localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose(); for ( i = 0; i < trm->numPolys; i++ ) { poly = &trm->polys[i]; center.Zero(); for ( j = 0; j < poly->numEdges; j++ ) { v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INTSIGNBITSET( poly->edges[j] ) ] ]; center += v; v -= localOrigin; v.NormalizeFast(); // project point on a unit sphere w.AddPoint( v ); } center /= poly->numEdges; v = center - localOrigin; dist = v.NormalizeFast(); dot = v * poly->normal; if ( dot > 0.0f ) { continue; } area = w.GetArea(); // impulse in polygon normal direction impulse = poly->normal * clipModel->GetAxis(); // always push up for nicer effect impulse.z -= 1.0f; // scale impulse based on visible surface area and polygon angle impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) ); // scale away distance for nicer effect impulse *= ( dist * 2.0f ); // impulse is applied to the center of the polygon center = clipModel->GetOrigin() + center * clipModel->GetAxis(); // RAVEN BEGIN // bdube: inflictor clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), center, impulse ); // RAVEN END } } /* =============== idGameLocal::ProjectDecal =============== */ void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) { float s, c; idMat3 axis, axistemp; idFixedWinding winding; idVec3 windingOrigin, projectionOrigin; static idVec3 decalWinding[4] = { idVec3( 1.0f, 1.0f, 0.0f ), idVec3( -1.0f, 1.0f, 0.0f ), idVec3( -1.0f, -1.0f, 0.0f ), idVec3( 1.0f, -1.0f, 0.0f ) }; if ( !g_decals.GetBool() ) { return; } // RAVEN BEGIN // rjohnson: no decals on dedicated server if ( isMultiplayer && !isClient && !isListenServer ) { // no decals on dedicated server return; } // RAVEN END // randomly rotate the decal winding idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c ); // winding orientation axis[2] = dir; axis[2].Normalize(); axis[2].NormalVectors( axistemp[0], axistemp[1] ); axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; windingOrigin = origin + depth * axis[2]; if ( parallel ) { projectionOrigin = origin - depth * axis[2]; } else { projectionOrigin = origin; } size *= 0.5f; winding.Clear(); winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) ); winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) ); winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) ); winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) ); gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), time ); } /* ============== idGameLocal::BloodSplat ============== */ // RAVEN BEGIN // ddynerman: multiple collision models void idGameLocal::BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dirArg, float size, const char *material ) { float halfSize = size * 0.5f; idVec3 verts[] = { idVec3( 0.0f, +halfSize, +halfSize ), idVec3( 0.0f, +halfSize, -halfSize ), idVec3( 0.0f, -halfSize, -halfSize ), idVec3( 0.0f, -halfSize, +halfSize ) }; idVec3 dir = dirArg; idTraceModel trm; idClipModel mdl; trace_t results; // RAVEN BEGIN // mekberg: changed from g_bloodEffects to g_decals if ( !g_decals.GetBool() ) { return; } // RAVEN END size = halfSize + random.RandomFloat() * halfSize; trm.SetupPolygon( verts, 4 ); mdl.LoadModel( trm, NULL ); // I don't want dir to be axis aligned, as it is more likely to streak them (because most architecture is axis aligned dir.Set( dirArg.x*.1f, dirArg.y*.1f, -1 ); dir.Normalize(); // RAVEN BEGIN // ddynerman: multiple clip worlds Translation( ent, results, origin, origin + dir * 72.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL ); // RAVEN END ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material ); } /* ============= idGameLocal::SetCamera ============= */ void idGameLocal::SetCamera( idCamera *cam ) { int i; idEntity *ent; idAI *ai; // this should fix going into a cinematic when dead.. rare but happens idPlayer *client = GetLocalPlayer(); if ( client->health <= 0 || client->pfl.dead ) { return; } camera = cam; if ( camera ) { // RAVEN BEGIN // bdube: tool support inCinematic = false; if( !( gameLocal.editors & ( EDITOR_MODVIEW | EDITOR_PLAYBACKS ) ) ) { inCinematic = true; } // RAVEN END if ( skipCinematic && camera->spawnArgs.GetBool( "disconnect" ) ) { camera->spawnArgs.SetBool( "disconnect", false ); cvarSystem->SetCVarFloat( "r_znear", 3.0f ); cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); skipCinematic = false; return; } if ( time > cinematicStopTime ) { cinematicSkipTime = time + CINEMATIC_SKIP_DELAY; } // set r_znear so that transitioning into/out of the player's head doesn't clip through the view cvarSystem->SetCVarFloat( "r_znear", 1.0f ); // hide all the player models for( i = 0; i < numClients; i++ ) { if ( entities[ i ] ) { client = static_cast< idPlayer* >( entities[ i ] ); client->EnterCinematic(); } } if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) { // kill any active monsters that are enemies of the player for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { if ( ent->cinematic || ent->fl.isDormant ) { // only kill entities that aren't needed for cinematics and aren't dormant continue; } // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( ent->IsType( idAI::GetClassType() ) ) { // RAVEN END ai = static_cast( ent ); if ( !ai->GetEnemy() || !ai->IsActive() ) { // no enemy, or inactive, so probably safe to ignore continue; } // RAVEN BEGIN // jnewquist: Use accessor for static class type } else if ( ent->IsType( idProjectile::GetClassType() ) ) { // RAVEN END // remove all projectiles } else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) { // remove anything marked to be removed during cinematics } else { // ignore everything else continue; } // remove it DPrintf( "removing '%s' for cinematic\n", ent->GetName() ); ent->PostEventMS( &EV_Remove, 0 ); } } } else { inCinematic = false; cinematicStopTime = time + msec; // restore r_znear cvarSystem->SetCVarFloat( "r_znear", 3.0f ); // show all the player models for( i = 0; i < numClients; i++ ) { if ( entities[ i ] ) { idPlayer *client = static_cast< idPlayer* >( entities[ i ] ); client->ExitCinematic(); } } } } // RAVEN BEGIN // jscott: for portal skies /* ============= idGameLocal::GetPortalSky ============= */ idCamera *idGameLocal::GetPortalSky( void ) const { if( !portalSkyVisible ) { return( NULL ); } return( portalSky ); } /* ============= idGameLocal::SetPortalSky ============= */ void idGameLocal::SetPortalSky( idCamera *cam ) { portalSky = cam; } // RAVEN END /* ============= idGameLocal::GetCamera ============= */ idCamera *idGameLocal::GetCamera( void ) const { return camera; } /* ============= idGameLocal::SkipCinematic ============= */ bool idGameLocal::SkipCinematic( void ) { if ( camera ) { if ( camera->spawnArgs.GetBool( "disconnect" ) ) { camera->spawnArgs.SetBool( "disconnect", false ); cvarSystem->SetCVarFloat( "r_znear", 3.0f ); cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); skipCinematic = false; return false; } if ( camera->spawnArgs.GetBool( "instantSkip" ) ) { camera->Stop(); return false; } } soundSystem->SetMute( true ); if ( !skipCinematic ) { skipCinematic = true; cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() ); } return true; } // RAVEN BEGIN /* ====================== idGameLocal::StartViewEffect For passing in an effect triggered view effect ====================== */ void idGameLocal::StartViewEffect( int type, float time, float scale ) { idPlayer *player; idPlayerView *view; player = GetLocalPlayer(); if( player ) { view = &player->playerView; switch( type ) { case VIEWEFFECT_DOUBLEVISION: view->SetDoubleVisionParms( time, scale ); break; case VIEWEFFECT_SHAKE: if( !gameLocal.isMultiplayer ) { view->SetShakeParms( time, scale ); } break; case VIEWEFFECT_TUNNEL: view->SetTunnelParms( time, scale ); break; default: gameLocal.Warning( "Invalid view effect" ); break; } } } /* ====================== idGameLocal::GetPlayerView ====================== */ void idGameLocal::GetPlayerView( idVec3 &origin, idMat3 &axis ) { idPlayer *player; renderView_t *view; player = GetLocalPlayer(); if( player ) { view = player->GetRenderView(); origin = view->vieworg; axis = view->viewaxis; } else { origin = vec3_origin; axis = mat3_identity; } } /* ====================== idGameLocal::Translation small portion of physics required for the effects system ====================== */ void idGameLocal::Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) { if( !trm ) { // HACK clip[0]->Translation( trace, source, dest, NULL, mat3_identity, clipMask, NULL, NULL ); } else { idClipModel cm; cm.LoadModel( *trm, NULL ); // HACK clip[0]->Translation( trace, source, dest, &cm, mat3_identity, clipMask, NULL, NULL ); } } /* ====================== idGameLocal::SpawnClientMoveable ====================== */ void idGameLocal::SpawnClientMoveable( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) { // find the debris def const idDict* args = gameLocal.FindEntityDefDict( name, false ); if ( !args ) { return; } // Ensure client moveables never last forever if ( lifetime <= 0 ) { lifetime = SEC2MS(args->GetFloat( "duration", "5" )); } int burn_time = idMath::ClampInt( 0, lifetime, SEC2MS(args->GetFloat( "burn_time", "2.5" )) ); // Spawn the debris rvClientMoveable* cent = NULL; // force the args to spawn a rvClientMoveable SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); if( !cent ) { return; } cent->SetOrigin( origin ); cent->SetAxis( axis ); cent->GetPhysics()->SetLinearVelocity( velocity ); cent->GetPhysics()->SetAngularVelocity( angular_velocity ); if ( !burn_time ) { //just disappear cent->PostEventMS( &EV_Remove, lifetime ); } else { cent->PostEventMS( &CL_FadeOut, lifetime-burn_time, burn_time ); } } /* ====================== idGameLocal::DebugSet ====================== */ void idGameLocal::DebugSetString( const char* name, const char* value ) { gameDebug.SetString( name, value ); } void idGameLocal::DebugSetFloat( const char* name, float value ) { gameDebug.SetFloat( name, value ); } void idGameLocal::DebugSetInt( const char* name, int value ) { gameDebug.SetInt( name, value ); } /* ====================== idGameLocal::DebugGetStat ====================== */ const char* idGameLocal::DebugGetStatString ( const char* name ) { return gameDebug.GetStatString( name ); } int idGameLocal::DebugGetStatInt ( const char* name ) { return gameDebug.GetStatInt( name ); } float idGameLocal::DebugGetStatFloat ( const char* name ) { return gameDebug.GetStatFloat( name ); } /* ====================== idGameLocal::IsDebugHudActive ====================== */ bool idGameLocal::IsDebugHudActive ( void ) const { return gameDebug.IsHudActive( DBGHUD_ANY ); } // rjohnson: added player info support for note taking system /* ====================== idGameLocal::GetPlayerInfo ====================== */ bool idGameLocal::GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum, idAngles *deltaViewAngles, int reqClientNum ) { idPlayer *player; if ( PlayerNum == -1 ) { player = GetLocalPlayer(); } else { player = GetClientByNum( PlayerNum ); } if( reqClientNum != -1 ) { idPlayer* reqClient = GetClientByNum( reqClientNum ); if( reqClient && player ) { if( reqClient->GetInstance() != player->GetInstance() ) { return false; } } } if ( !player ) { return false; } player->GetViewPos( origin, axis ); origin = player->GetPhysics()->GetOrigin(); if ( deltaViewAngles ) { *deltaViewAngles = player->GetDeltaViewAngles(); } return true; }; /* ====================== idGameLocal::SetCurrentPlayerInfo ====================== */ void idGameLocal::SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum ) { idPlayer *player; if ( PlayerNum == -1 ) { player = GetLocalPlayer(); } else { player = GetClientByNum( PlayerNum ); } if ( !player ) { return; } player->Teleport( origin, axis.ToAngles(), NULL ); // RAVEN BEGIN // ddynerman: save the current thinking entity for instance-dependent currentThinkingEntity = player; player->CalculateFirstPersonView(); player->CalculateRenderView(); currentThinkingEntity = NULL; // RAVEN END return; }; bool idGameLocal::PlayerChatDisabled( int clientNum ) { if( clientNum < 0 || clientNum >= MAX_CLIENTS || !entities[ clientNum ] ) { return false; } return !( ((idPlayer*)entities[ clientNum ])->isChatting || ((idPlayer*)entities[ clientNum ])->pfl.dead ); } void idGameLocal::SetViewComments( const char *text ) { idPlayer *player; player = GetLocalPlayer(); if ( !player ) { return; } if ( text ) { player->hud->SetStateString( "viewcomments", text ); player->hud->HandleNamedEvent( "showViewComments" ); } else { player->hud->SetStateString( "viewcomments", "" ); player->hud->HandleNamedEvent( "hideViewComments" ); } } /* =================== idGameLocal::GetNumGravityAreas =================== */ int idGameLocal::GetNumGravityAreas() const { return gravityInfo.Num(); } /* =================== idGameLocal::GetGravityInfo =================== */ const rvGravityArea* idGameLocal::GetGravityInfo( int index ) const { return gravityInfo[ index ]; } /* =================== idGameLocal::SetGravityArea =================== */ void idGameLocal::SetGravityInfo( int index, rvGravityArea* info ) { gravityInfo[ index ] = info; } /* =================== idGameLocal::AddUniqueGravityInfo =================== */ void idGameLocal::AddUniqueGravityInfo( rvGravityArea* info ) { gravityInfo.AddUnique( info ); } /* =================== idGameLocal::GetCurrentGravityInfoIndex =================== */ int idGameLocal::GetCurrentGravityInfoIndex( const idVec3& origin ) const { int numGravityAreas = GetNumGravityAreas(); if( !numGravityAreas ) { return -1; } int areaNum = gameRenderWorld->PointInArea( origin ); for( int ix = 0; ix < numGravityAreas; ++ix ) { if( !gameRenderWorld->AreasAreConnected(GetGravityInfo(ix)->GetArea(), areaNum, PS_BLOCK_GRAVITY) ) { continue; } return ix; } return -1; } /* =================== idGameLocal::InGravityArea =================== */ bool idGameLocal::InGravityArea( idEntity* entity ) const { return GetCurrentGravityInfoIndex( entity ) >= 0; } /* =================== idGameLocal::GetCurrentGravityInfoIndex =================== */ int idGameLocal::GetCurrentGravityInfoIndex( idEntity* entity ) const { return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); } /* =================== idGameLocal::GetCurrentGravity =================== */ const idVec3 idGameLocal::GetCurrentGravity( idEntity* entity ) const { int index = GetCurrentGravityInfoIndex( entity ); return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); } /* =================== idGameLocal::GetCurrentGravity =================== */ const idVec3 idGameLocal::GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const { int index = GetCurrentGravityInfoIndex( origin ); return (index >= 0) ? gravityInfo[ index ]->GetGravity(origin, axis, MASK_SOLID, NULL) : GetGravity(); } /* =================== idGameLocal::InGravityArea =================== */ bool idGameLocal::InGravityArea( rvClientEntity* entity ) const { return GetCurrentGravityInfoIndex( entity ) >= 0; } /* =================== idGameLocal::GetCurrentGravityInfoIndex =================== */ int idGameLocal::GetCurrentGravityInfoIndex( rvClientEntity* entity ) const { return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); } /* =================== idGameLocal::GetCurrentGravity =================== */ const idVec3 idGameLocal::GetCurrentGravity( rvClientEntity* entity ) const { int index = GetCurrentGravityInfoIndex( entity ); return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); } /* =================== idGameLocal::ReferenceScriptObjectProxy =================== */ idEntity* idGameLocal::ReferenceScriptObjectProxy( const char* scriptObjectName ) { idEntity* proxy = NULL; idEntityPtr safeProxy; idDict args; idScriptObject* object = NULL; for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { proxy = scriptObjectProxies[ ix ].GetEntity(); assert( proxy ); object = &proxy->scriptObject; if( !object->data ) { object->SetType( scriptObjectName ); proxy->ConstructScriptObject(); return proxy; } } args.Set( "classname", "func_static" ); args.Set( "scriptobject", scriptObjectName ); args.SetBool( "noclipmodel", true ); bool spawned = SpawnEntityDef(args, &proxy); if ( !spawned ) { assert( 0 ); } safeProxy = proxy; scriptObjectProxies.AddUnique( safeProxy ); return proxy; } /* =================== idGameLocal::ReleaseScriptObjectProxy =================== */ void idGameLocal::ReleaseScriptObjectProxy( const char* proxyName ) { idScriptObject* object = NULL; idEntity* entity = NULL; for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { entity = scriptObjectProxies[ ix ].GetEntity(); if( entity && !idStr::Icmp(entity->GetName(), proxyName) ) { object = &entity->scriptObject; if( !object ) { continue; } entity->DeconstructScriptObject(); object->Free(); } } } // RAVEN BEGIN // rjohnson: entity usage stats void idGameLocal::ListEntityStats( const idCmdArgs &args ) { int i, j; idStr currentMap; idList uniqueMapNames; for( i = 1; i < args.Argc(); i++ ) { if ( idStr::Icmp( args.Argv( i ), "clear" ) == 0 ) { entityUsageList.Clear(); common->Printf("Entity stats cleared.\n"); return; } } for( i = 0; i < entityUsageList.Num(); i++ ) { entityUsageList[ i ].SetInt( "reported_stat", false ); } for( i = 0; i < entityUsageList.Num(); i++ ) { idStr mapFileName, className; int count; if ( entityUsageList[ i ].GetInt( "reported_stat" ) ) { continue; } entityUsageList[ i ].GetString( "mapFileName", "none", mapFileName ); if ( currentMap != mapFileName ) { if ( i ) { common->Printf( "\n" ); } common->Printf( "================ %s ================\n", mapFileName.c_str() ); currentMap = mapFileName; uniqueMapNames.Insert( mapFileName ); } entityUsageList[ i ].GetString( "classname", "none", className ); count = 0; for( j = i; j < entityUsageList.Num(); j++ ) { idStr checkMapFileName, checkClassName; entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); if ( checkMapFileName != mapFileName ) { break; } entityUsageList[ j ].GetString( "classname", "none", checkClassName ); if ( checkClassName == className ) { entityUsageList[ j ].SetInt( "reported_stat", 1 ); count++; } } common->Printf("%d\t%s\n", count, className.c_str() ); } common->Printf( "\n" ); common->Printf( "\n" ); common->Printf( "================ OVERALL ================\n" ); for( i = 0; i < entityUsageList.Num(); i++ ) { idStr mapFileName, className; int count; if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 2 ) { continue; } entityUsageList[ i ].GetString( "classname", "none", className ); count = 0; for( j = i; j < entityUsageList.Num(); j++ ) { idStr checkClassName; entityUsageList[ j ].GetString( "classname", "none", checkClassName ); if ( checkClassName == className ) { entityUsageList[ j ].SetInt( "reported_stat", 2 ); count++; } } common->Printf("%d\t%s\n", count, className.c_str() ); } idFile *FH = fileSystem->OpenFileWrite( "EntityStats.csv" ); if ( FH ) { int size = sizeof( int ) * uniqueMapNames.Num(); int *count = ( int * )_alloca( size ); FH->Printf("\"Definition\""); for( i = 0; i < uniqueMapNames.Num(); i++ ) { FH->Printf( ",\"%s\"", uniqueMapNames[ i ].c_str() ); } FH->Printf(",Total\n"); for( i = 0; i < entityUsageList.Num(); i++ ) { idStr className; int total; if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 3 ) { continue; } entityUsageList[ i ].GetString( "classname", "none", className ); memset( count, 0, size ); for( j = i; j < entityUsageList.Num(); j++ ) { idStr checkMapFileName, checkClassName; entityUsageList[ j ].GetString( "classname", "none", checkClassName ); if ( checkClassName == className ) { entityUsageList[ j ].SetInt( "reported_stat", 3 ); entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); int loc = uniqueMapNames.FindIndex( checkMapFileName ); if ( loc >= 0 ) { count[ loc ]++; } } } total = 0; FH->Printf( "\"%s\"", className.c_str() ); for( j = 0; j < uniqueMapNames.Num(); j++ ) { FH->Printf( ",%d", count[ j ] ); total += count[ j ]; } FH->Printf( ",%d\n", total ); } fileSystem->CloseFile( FH ); } } // RAVEN END /* ====================== idGameLocal::SpreadLocations Now that everything has been spawned, associate areas with location entities ====================== */ void idGameLocal::SpreadLocations() { idEntity *ent; // RAVEN BEGIN if( !gameRenderWorld ) { common->Error( "GameRenderWorld is NULL!" ); } // RAVEN END // allocate the area table int numAreas = gameRenderWorld->NumAreas(); locationEntities = new idLocationEntity *[ numAreas ]; memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) ); // for each location entity, make pointers from every area it touches for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if ( !ent->IsType( idLocationEntity::GetClassType() ) ) { // RAVEN END continue; } idVec3 point = ent->spawnArgs.GetVector( "origin" ); int areaNum = gameRenderWorld->PointInArea( point ); if ( areaNum < 0 ) { Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) ); continue; } if ( areaNum >= numAreas ) { Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" ); } if ( locationEntities[areaNum] ) { Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ), locationEntities[areaNum]->spawnArgs.GetString( "name" ) ); continue; } locationEntities[areaNum] = static_cast(ent); // spread to all other connected areas for ( int i = 0 ; i < numAreas ; i++ ) { if ( i == areaNum ) { continue; } if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { locationEntities[i] = static_cast(ent); } } } } /* =================== idGameLocal::AddLocation =================== */ idLocationEntity* idGameLocal::AddLocation( const idVec3& point, const char* name ) { int areaNum = gameRenderWorld->PointInArea( point ); if ( areaNum < 0 ) { Warning ( "idGameLocal::AddLocation: cannot add location entity '%s' at '%g %g %g'\n", name, point.x, point.y, point.z ); return NULL; } if ( areaNum >= gameRenderWorld->NumAreas() ) { Error( "idGameLocal::AddLocation: areaNum >= gameRenderWorld->NumAreas()" ); } if ( locationEntities[areaNum] ) { Warning ( "idGameLocal::AddLocation: location '%s' already exists at '%g %g %g'\n", locationEntities[areaNum]->GetName(), point.x, point.y, point.z ); return NULL; } // Spawn the new location entity idDict args; args.Set ( "location", name ); locationEntities[areaNum] = static_cast(SpawnEntityType ( idLocationEntity::GetClassType(), &args )); // spread to all other connected areas for ( int i = gameRenderWorld->NumAreas() - 1 ; i >= 0 ; i-- ) { if ( i == areaNum ) { continue; } if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { locationEntities[i] = static_cast(locationEntities[areaNum]); } } return locationEntities[areaNum]; } /* =================== idGameLocal::LocationForPoint The player checks the location each frame to update the HUD text display May return NULL =================== */ idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) { if ( !locationEntities ) { // before SpreadLocations() has been called return NULL; } int areaNum = gameRenderWorld->PointInArea( point ); if ( areaNum < 0 ) { return NULL; } if ( areaNum >= gameRenderWorld->NumAreas() ) { Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" ); } return locationEntities[ areaNum ]; } /* ============ idGameLocal::SetPortalState ============ */ void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) { idBitMsg outMsg; byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; if ( !gameLocal.isClient ) { outMsg.Init( msgBuf, sizeof( msgBuf ) ); outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTAL ); outMsg.WriteLong( portal ); outMsg.WriteBits( blockingBits, NUM_RENDER_PORTAL_BITS ); networkSystem->ServerSendReliableMessage( -1, outMsg ); } gameRenderWorld->SetPortalState( portal, blockingBits ); } /* ============ idGameLocal::sortSpawnPoints ============ */ int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) { const spawnSpot_t *spot1 = static_cast( ptr1 ); const spawnSpot_t *spot2 = static_cast( ptr2 ); float diff; diff = spot1->dist - spot2->dist; if ( diff < 0.0f ) { return 1; } else if ( diff > 0.0f ) { return -1; } else { return 0; } } // RAVEN BEGIN // ddynerman: new gametype specific spawn code // TODO this should be moved to idMultiplayerGame /* =========== idGameLocal::InitializeSpawns randomize the order of the initial spawns prepare for a sequence of initial player spawns ============ */ void idGameLocal::InitializeSpawns( void ) { idEntity* spot = NULL; // initialize the spawns for clients as well, need them for free fly demo replays if ( !isMultiplayer ) { return; } spawnSpots.Clear(); for( int i = 0; i < TEAM_MAX; i++ ) { teamSpawnSpots[i].Clear(); } spot = FindEntityUsingDef( NULL, "info_player_team" ); while( spot ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if( spot->IsType ( idPlayerStart::GetClassType() ) ) { // RAVEN END if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); } // spawnSpots contains info_player_team as well as info_player_deathmatch spawnSpots.Append ( static_cast(spot) ); } spot = FindEntityUsingDef( spot, "info_player_team" ); } spot = FindEntityUsingDef( NULL, "info_player_deathmatch" ); while( spot ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if( spot->IsType ( idPlayerStart::GetClassType() ) ) { // RAVEN END spawnSpots.Append ( static_cast(spot) ); } spot = FindEntityUsingDef( spot, "info_player_deathmatch" ); } while( spot ) { // RAVEN BEGIN // jnewquist: Use accessor for static class type if( spot->IsType ( idPlayerStart::GetClassType() ) ) { // RAVEN END if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); } // spawnSpots contains info_player_team as well as info_player_deathmatch spawnSpots.Append ( static_cast(spot) ); } spot = FindEntityUsingDef( spot, "info_player_team" ); } if( IsFlagGameType() && ( teamSpawnSpots[ TEAM_STROGG ].Num() == 0 || teamSpawnSpots[ TEAM_MARINE ].Num() == 0 ) ) { Error( "InitializeSpawns() - Map must have at least one Marine and one Strogg spawn for CTF gametype."); } if( spawnSpots.Num() == 0 ) { Error( "InitializeSpawns() - Map must have a spawn spot." ); } common->Printf( "%d general spawns\n", spawnSpots.Num() ); common->Printf( "%d team spawns (%d strogg/%d marine)\n", teamSpawnSpots[TEAM_STROGG].Num() + teamSpawnSpots[TEAM_MARINE].Num(), teamSpawnSpots[TEAM_STROGG].Num(), teamSpawnSpots[TEAM_MARINE].Num()); } /* =========== idGameLocal::UpdateForwardSpawn ddynerman: Updates forward spawn lists =========== */ void idGameLocal::UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ) { teamForwardSpawnSpots[ team ].Append( point ); } /* =========== idGameLocal::ClearForwardSpawn ddynerman: Clears forward spawn lists =========== */ void idGameLocal::ClearForwardSpawns( void ) { for( int i = 0; i < TEAM_MAX; i++ ) { teamForwardSpawnSpots[ i ].Clear(); } } /* =========== idGameLocal::SpotWouldTelefrag =========== */ bool idGameLocal::SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ) { idPlayer* playerList[ MAX_CLIENTS ]; idBounds bound = player->GetPhysics()->GetBounds(); bound.TranslateSelf( spawn->GetPhysics()->GetOrigin() ); int numEntities = PlayersTouchingBounds( player, bound, CONTENTS_BODY, playerList, MAX_CLIENTS ); return !( numEntities == 0 ); } /* =========== idGameLocal::SelectSpawnSpot ddynerman: Selects a spawn spot randomly from spots furthest from the player This is taken from q3 =========== */ idEntity* idGameLocal::SelectSpawnPoint( idPlayer* player ) { if( !isMultiplayer ) { idEntity* ent = FindEntityUsingDef( NULL, "info_player_start" ); if ( !ent ) { Error( "No info_player_start on map.\n" ); } return ent; } if ( player == NULL ) { return NULL; } // Give spectators any old random spot if ( player->team < 0 || player->team >= TEAM_MAX || player->spectating ) { common->DPrintf("Returning a random spot\n"); return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; } idList weightedSpawns; idList* spawnArray = NULL; // Pick which spawns to use based on gametype // RITUAL BEGIN // squirrel: added DeadZone multiplayer mode if( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_TOURNEY ) { spawnArray = &spawnSpots; } else if( IsFlagGameType() || gameLocal.gameType == GAME_DEADZONE ) { if( teamForwardSpawnSpots[ player->team ].Num() ) { spawnArray = &teamForwardSpawnSpots[ player->team ]; } else { spawnArray = &teamSpawnSpots[ player->team ]; } } // RITUAL END if ( spawnArray == NULL ) { Error( "SelectSpawnPoint() - invalid spawn list." ); return NULL; } idVec3 refPos; if ( player->lastKiller != NULL && !player->lastKiller->spectating && player->lastKiller->GetInstance() == player->GetInstance() ) { refPos = player->lastKiller->GetPhysics()->GetOrigin(); } else { refPos = player->GetPhysics()->GetOrigin(); } for ( int i = 0; i < spawnArray->Num(); i++ ) { idPlayerStart* spot = (*spawnArray)[i]; if ( spot->GetInstance() != player->GetInstance() || SpotWouldTelefrag( player, spot ) ) { continue; } idVec3 pos = spot->GetPhysics()->GetOrigin(); float dist = ( pos - refPos ).LengthSqr(); spawnSpot_t newSpot; newSpot.dist = dist; newSpot.ent = (*spawnArray)[ i ]; weightedSpawns.Append( newSpot ); } if ( weightedSpawns.Num() == 0 ) { // no spawns avaialable, spawn randomly common->DPrintf("no spawns avaialable, spawn randomly\n"); return (*spawnArray)[ random.RandomInt( spawnArray->Num() ) ]; } qsort( ( void * )weightedSpawns.Ptr(), weightedSpawns.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); int rnd = rvRandom::flrand( 0.0, 1.0 ) * (weightedSpawns.Num() / 2); return weightedSpawns[ rnd ].ent; } /* ================ idGameLocal::UpdateServerInfoFlags ================ */ // RAVEN BEGIN // ddynerman: new gametype strings void idGameLocal::SetGameType( void ) { gameType = GAME_SP; if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "singleplayer" ) ) { mpGame.SetGameType(); } } // RAVEN END /* ================ idGameLocal::SetGlobalMaterial ================ */ void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) { globalMaterial = mat; } /* ================ idGameLocal::GetGlobalMaterial ================ */ const idMaterial *idGameLocal::GetGlobalMaterial() { return globalMaterial; } /* ================ idGameLocal::GetSpawnId ================ */ int idGameLocal::GetSpawnId( const idEntity* ent ) const { return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber; } /* ================ idGameLocal::ThrottleUserInfo ================ */ void idGameLocal::ThrottleUserInfo( void ) { mpGame.ThrottleUserInfo(); } /* =========== idGameLocal::ValidateServerSettings ============ */ bool idGameLocal::ValidateServerSettings( const char* map, const char* gametype ) { // PickMap uses si_map directly // PickMap returns wether we would have to change the maps, which means settings are invalid assert( !idStr::Icmp( si_map.GetString(), map ) ); if ( mpGame.PickMap( gametype, true ) ) { common->Printf( "map '%s' and gametype '%s' are not compatible\n", map, gametype ); return false; } return true; } /* =========== idGameLocal::NeedRestart ============ */ bool idGameLocal::NeedRestart() { idDict newInfo; const idKeyValue *keyval, *keyval2; newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); for ( int i = 0; i < newInfo.GetNumKeyVals(); i++ ) { keyval = newInfo.GetKeyVal( i ); keyval2 = serverInfo.FindKey( keyval->GetKey() ); if ( !keyval2 ) { return true; } // a select set of si_ changes will cause a full restart of the server if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { return true; } } return false; } // RAVEN BEGIN // jshepard: update player hud to alert to end of level /* =================== idGameLocal::UpdateEndLevel =================== */ void idGameLocal::UpdateEndLevel() { idPlayer * player = GetLocalPlayer(); if( player && player->GetHud() ) { player->GetHud()->HandleNamedEvent( "showExit" ); } } // bdube: added /* ================ idGameLocal::GetEffect Get the handle of the effect with the given name ================ */ const idDecl *idGameLocal::GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType ) { const char *effectFile = NULL; float chance = args.GetFloat ( idStr("effectchance ") + effectName, "1" ); if ( random.RandomFloat ( ) > chance ) { return NULL; } // we should ALWAYS be playing sounds from the def. // hardcoded sounds MUST be avoided at all times because they won't get precached. assert( !idStr::Icmpn( effectName, "fx_", 3 ) ); if ( materialType ) { idStr temp; const char* result = NULL; temp = effectName; temp += "_"; temp += materialType->GetName(); // See if the given material effect is specified if ( isMultiplayer ) { idStr testMP = temp; testMP += "_mp"; result = args.GetString( testMP ); } if ( !result || !*result ) { result = args.GetString( temp ); } if ( result && *result) { return( ( const idDecl * )declManager->FindEffect( result ) ); } } // grab the non material effect name if ( isMultiplayer ) { idStr testMP = effectName; testMP += "_mp"; effectFile = args.GetString( testMP ); } if ( !effectFile || !*effectFile ) { effectFile = args.GetString( effectName ); } if ( !effectFile || !*effectFile ) { return NULL; } return( ( const idDecl * )declManager->FindEffect( effectFile ) ); } /* ================ idGameLocal::PlayEffect Plays an effect at the given origin using the given direction ================ */ rvClientEffect* idGameLocal::PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { if ( !effect ) { return NULL; } if ( !gameLocal.isNewFrame ) { return NULL; } if ( isServer && broadcast ) { idBitMsg msg; byte msgBuf[MAX_GAME_MESSAGE_SIZE]; idCQuat quat; quat = axis.ToCQuat(); msg.Init( msgBuf, sizeof( msgBuf ) ); msg.BeginWriting(); msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EFFECT ); idGameLocal::WriteDecl( msg, effect ); msg.WriteFloat( origin.x ); msg.WriteFloat( origin.y ); msg.WriteFloat( origin.z ); msg.WriteFloat( quat.x ); msg.WriteFloat( quat.y ); msg.WriteFloat( quat.z ); msg.WriteBits( loop, 1 ); msg.WriteFloat( endOrigin.x ); msg.WriteFloat( endOrigin.y ); msg.WriteFloat( endOrigin.z ); msg.WriteByte( category ); // send to everyone who has start or end in it's PVS SendUnreliableMessagePVS( msg, currentThinkingEntity, pvs.GetPVSArea( origin ), pvs.GetPVSArea( endOrigin ) ); } if ( isServer && localClientNum < 0 ) { // no effects on dedicated server return NULL; } if ( bse->Filtered( effect->GetName(), category ) ) { // Effect filtered out return NULL; } // RAVEN END if ( gameLocal.isListenServer && currentThinkingEntity && gameLocal.GetLocalPlayer() ) { if ( currentThinkingEntity->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { return NULL; } } // mwhitlock: Dynamic memory consolidation RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); rvClientEffect* clientEffect = new rvClientEffect( effect ); RV_POP_HEAP(); if( !clientEffect ) { common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); return NULL; } if( clientEffect->entityNumber == -1 ) { common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); delete clientEffect; return NULL; } clientEffect->SetOrigin( origin ); clientEffect->SetAxis( axis ); clientEffect->SetGravity( GetCurrentGravity( origin, axis ) ); if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { delete clientEffect; return NULL; } clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; return clientEffect; } void idGameLocal::CheckPlayerWhizzBy( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ) { //FIXME: make this client-side? Work in MP? if ( gameLocal.isMultiplayer ) { return; } idPlayer* player = gameLocal.GetLocalPlayer(); if ( !player ) { return; } if ( player->IsHidden() ) { return; } if ( player == attacker ) { return; } if ( player == hitEnt ) { return; } idVec3 center = player->firstPersonViewOrigin; idVec3 diff = end-center; if ( diff.Length() < 64.0f ) { //hit too close - didn't actually pass by, will hear impact sound instead return; } idVec3 closestPoint = player->firstPersonViewOrigin; if ( closestPoint.ProjectToLineSeg( start, end ) ) { //on line seg diff = closestPoint-center; if ( diff.Length() < 48.0f ) { //close enough to hear whizz-by idVec3 dir = end-start; dir.Normalize(); idVec3 fxStart = closestPoint+dir*-32.0f; idVec3 fxEnd = closestPoint+dir*32.0f; player->PlayEffect( "fx_whizby", fxStart, player->firstPersonViewAxis, false, fxEnd ); } } } /* ================ idGameLocal::HitScan Run a hitscan trace from the given origin and direction ================ */ idEntity* idGameLocal::HitScan( const idDict& hitscanDict, const idVec3& origOrigin, const idVec3& origDir, const idVec3& origFxOrigin, idEntity* owner, bool noFX, float damageScale, // twhitaker: added additionalIgnore parameter idEntity* additionalIgnore, int areas[ 2 ] ) { idVec3 dir; idVec3 origin; idVec3 fxOrigin; idVec3 fxDir; idVec3 impulse; idVec4 hitscanTint( 1.0f, 1.0f, 1.0f, 1.0f ); int reflect; float tracerChance; idEntity* ignore; float penetrate; if ( areas ) { areas[ 0 ] = pvs.GetPVSArea( origFxOrigin ); areas[ 1 ] = -1; } ignore = owner; penetrate = hitscanDict.GetFloat( "penetrate" ); if( hitscanDict.GetBool( "hitscanTint" ) && owner->IsType( idPlayer::GetClassType() ) ) { hitscanTint = ((idPlayer*)owner)->GetHitscanTint(); } // twhitaker: additionalIgnore parameter if ( !additionalIgnore ) { additionalIgnore = ignore; } origin = origOrigin; fxOrigin = origFxOrigin; dir = origDir; tracerChance = ((g_perfTest_weaponNoFX.GetBool())?0:hitscanDict.GetFloat( "tracerchance", "0" )); // Apply player powerups if ( owner && owner->IsType( idPlayer::GetClassType() ) ) { damageScale *= static_cast(owner)->PowerUpModifier(PMOD_PROJECTILE_DAMAGE); } // Run reflections for ( reflect = hitscanDict.GetFloat( "reflect", "0" ); reflect >= 0; reflect-- ) { idVec3 start; idVec3 end; idEntity* ent; idEntity* actualHitEnt; trace_t tr; int contents; int collisionArea; idVec3 collisionPoint; bool tracer; // Calculate the end point of the trace start = origin; if ( g_perfTest_hitscanShort.GetBool() ) { end = start + (dir.ToMat3() * idVec3(idMath::ClampFloat(0,2048,hitscanDict.GetFloat ( "range", "2048" )),0,0)); } else { end = start + (dir.ToMat3() * idVec3(hitscanDict.GetFloat ( "range", "40000" ),0,0)); } if ( g_perfTest_hitscanBBox.GetBool() ) { contents = MASK_SHOT_BOUNDINGBOX|CONTENTS_PROJECTILE; } else { contents = MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE; } // Loop the traces to handle cases where something can be shot through while ( 1 ) { // Trace to see if we hit any entities // RAVEN BEGIN // ddynerman: multiple clip worlds if ( hitscanDict.GetFloat( "trace_size", "0" ) > 0.0f ) { float range = hitscanDict.GetFloat ( "range", "1024" ); if ( range > 4096.0f ) { assert( !(range > 4096.0f) ); Warning( "idGameLocal::HitScan: hitscan def (%s) with trace_size must have max range of 4096!", hitscanDict.GetString( "classname" ) ); range = idMath::ClampFloat( 0.0f, 4096.0f, range ); } end = start + (dir * range); idBounds traceBounds; traceBounds.Zero(); traceBounds.ExpandSelf( hitscanDict.GetFloat( "trace_size", "0" ) ); // twhitaker: additionalIgnore parameter TraceBounds( owner, tr, start, end, traceBounds, contents, additionalIgnore ); } else { // twhitaker: additionalIgnore parameter TracePoint( owner, tr, start, end, contents, additionalIgnore ); } //gameRenderWorld->DebugArrow( colorRed, start, end, 10, 5000 ); // RAVEN END // If the hitscan hit a no impact surface we can just return out //assert( tr.c.material ); if ( tr.fraction >= 1.0f || (tr.c.material && tr.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { PlayEffect( hitscanDict, "fx_path", fxOrigin, dir.ToMat3(), false, tr.endpos, false, EC_IGNORE, hitscanTint ); if ( random.RandomFloat( ) < tracerChance ) { PlayEffect( hitscanDict, "fx_tracer", fxOrigin, dir.ToMat3(), false, tr.endpos ); tracer = true; } else { tracer = false; } if ( areas ) { collisionArea = pvs.GetPVSArea( tr.endpos ); if ( collisionArea != areas[0] ) { areas[1] = collisionArea; } } return NULL; } // computing the collisionArea from the collisionPoint fails sometimes if ( areas ) { collisionArea = pvs.GetPVSArea( tr.c.point ); if ( collisionArea != areas[0] ) { areas[1] = collisionArea; } } collisionPoint = tr.c.point - ( tr.c.normal * tr.c.point - tr.c.dist ) * tr.c.normal; ent = entities[ tr.c.entityNum ]; actualHitEnt = NULL; start = collisionPoint; // Keep tracing if we hit water if ( (ent->GetPhysics()->GetContents() & CONTENTS_WATER) || (tr.c.material && (tr.c.material->GetContentFlags() & CONTENTS_WATER)) ) { // Apply force to the water entity that was hit ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -(hitscanDict.GetFloat( "push", "5000" )) * tr.c.normal ); // Continue on excluding water contents &= (~CONTENTS_WATER); if ( !g_perfTest_weaponNoFX.GetBool() ) { if ( ent->CanPlayImpactEffect( owner, ent ) ) { if ( ent->IsType( idMover::GetClassType( ) ) ) { ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, EC_IMPACT, hitscanTint ); } else { gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); } } } continue; // Reflect off a bounce target? } else if ( (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) && !hitscanDict.GetBool ( "noBounce" ) ) { reflect++; } // If the hit entity is bound to an actor use the actor instead if ( ent->fl.takedamage && ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { actualHitEnt = ent; ent = ent->GetTeamMaster( ); } if ( !gameLocal.isClient ) { // Apply force to the entity that was hit ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -tr.c.normal, &hitscanDict ); // Handle damage to the entity if ( ent->fl.takedamage && !(( tr.c.material != NULL ) && ( tr.c.material->GetSurfaceFlags() & SURF_NODAMAGE )) ) { const char* damage; damage = NULL; // RAVEN BEGIN // jdischler: code from the other project..to ensure that if an attached head is hit, the body will use the head joint // otherwise damage zones for head attachments no-worky int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(tr.c.id); if ( ent->IsType(idActor::GetClassType()) ) { idActor* entActor = static_cast(ent); if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) { idAFAttachment* headEnt = static_cast(entActor->GetHead()); if ( headEnt && headEnt->entityNumber == tr.c.entityNum ) {//hit ent's head, get the proper joint for the head hitJoint = entActor->GetAnimator()->GetJointHandle("head"); } } } // RAVEN END // Inflict damage if ( tr.c.materialType ) { damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); } if ( !damage || !*damage ) { damage = hitscanDict.GetString ( "def_damage" ); } if ( damage && damage[0] ) { // RAVEN BEGIN // ddynerman: stats if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { statManager->WeaponHit( (idActor*)owner, ent, ((idPlayer*)owner)->GetCurrentWeapon() ); } // RAVEN END ent->Damage( owner, owner, dir, damage, damageScale, hitJoint ); } // Let the entity add its own damage effect if ( !g_perfTest_weaponNoFX.GetBool() ) { ent->AddDamageEffect ( tr, dir, damage, owner ); } } else { if ( actualHitEnt && actualHitEnt != ent && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) {//bleh... const char* damage = NULL; // Inflict damage if ( tr.c.materialType ) { damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); } if ( !damage || !*damage ) { damage = hitscanDict.GetString ( "def_damage" ); } if ( damage && damage[0] ) { actualHitEnt->Damage( owner, owner, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( tr.c.id ) ); } } if ( !g_perfTest_weaponNoFX.GetBool() ) { ent->AddDamageEffect( tr, dir, hitscanDict.GetString ( "def_damage" ), owner ); } } } // Pass through actors if ( ent->IsType ( idActor::GetClassType() ) && penetrate > 0.0f ) { start = collisionPoint; additionalIgnore = ent; damageScale *= penetrate; continue; } break; } // Path effect fxDir = collisionPoint - fxOrigin; fxDir.Normalize( ); PlayEffect( hitscanDict, "fx_path", fxOrigin, fxDir.ToMat3(), false, collisionPoint, false, EC_IGNORE, hitscanTint ); if ( !ent->fl.takedamage && random.RandomFloat ( ) < tracerChance ) { PlayEffect( hitscanDict, "fx_tracer", fxOrigin, fxDir.ToMat3(), false, collisionPoint ); tracer = true; } else { tracer = false; } if ( !reflect ) { //on initial trace only if ( hitscanDict.GetBool( "doWhizz" ) ) { //play whizz-by sound if trace is close to player's head CheckPlayerWhizzBy( origin, collisionPoint, ent, owner ); } } // Play a different effect when reflecting if ( !reflect || ent->fl.takedamage ) { idMat3 axis; // Effect axis when hitting actors is along the direction of impact because actor models are // very detailed. if ( ent->IsType ( idActor::GetClassType() ) ) { axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); } else { axis = tr.c.normal.ToMat3(); } if ( !g_perfTest_weaponNoFX.GetBool() ) { if ( ent->CanPlayImpactEffect( owner, ent ) ) { if ( ent->IsType( idMover::GetClassType( ) ) ) { ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, EC_IMPACT, hitscanTint ); } else { gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); } } } // End of reflection return ent; } else { PlayEffect( GetEffect( hitscanDict, "fx_reflect", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3() ); } // Calc new diretion based on bounce origin = start; fxOrigin = start; dir = ( dir - ( 2.0f * DotProduct( dir, tr.c.normal ) * tr.c.normal ) ); dir.Normalize( ); // Increase damage scale on reflect damageScale += hitscanDict.GetFloat( "reflect_powerup", "0" ); } assert( false ); return NULL; } /* =================== idGameLocal::RegisterClientEntity =================== */ void idGameLocal::RegisterClientEntity( rvClientEntity *cent ) { int entityNumber; assert ( cent ); if ( clientSpawnCount >= ( 1 << ( 32 - CENTITYNUM_BITS ) ) ) { // Error( "idGameLocal::RegisterClientEntity: spawn count overflow" ); clientSpawnCount = INITIAL_SPAWN_COUNT; } // Find a free entity index to use while( clientEntities[firstFreeClientIndex] && firstFreeClientIndex < MAX_CENTITIES ) { firstFreeClientIndex++; } if ( firstFreeClientIndex >= MAX_CENTITIES ) { cent->PostEventMS ( &EV_Remove, 0 ); Warning( "idGameLocal::RegisterClientEntity: no free client entities" ); return; } entityNumber = firstFreeClientIndex++; // Add the client entity to the lists clientEntities[ entityNumber ] = cent; clientSpawnIds[ entityNumber ] = clientSpawnCount++; cent->entityNumber = entityNumber; cent->spawnNode.AddToEnd( clientSpawnedEntities ); cent->spawnArgs.TransferKeyValues( spawnArgs ); if ( entityNumber >= num_clientEntities ) { num_clientEntities++; } } /* =================== idGameLocal::UnregisterClientEntity =================== */ void idGameLocal::UnregisterClientEntity( rvClientEntity* cent ) { assert( cent ); // No entity number then it failed to register if ( cent->entityNumber == -1 ) { return; } cent->spawnNode.Remove ( ); cent->bindNode.Remove ( ); if ( clientEntities [ cent->entityNumber ] == cent ) { clientEntities [ cent->entityNumber ] = NULL; clientSpawnIds[ cent->entityNumber ] = -1; if ( cent->entityNumber < firstFreeClientIndex ) { firstFreeClientIndex = cent->entityNumber; } cent->entityNumber = -1; } } // RAVEN BEGIN // ddynerman: idClip wrapper functions /* =================== idGameLocal::Translation =================== */ bool idGameLocal::Translation( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->Translation( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); } return false; } /* =================== idGameLocal::Rotation =================== */ bool idGameLocal::Rotation( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); } return false; } /* =================== idGameLocal::Motion =================== */ bool idGameLocal::Motion( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->Motion( results, start, end, rotation, mdl, trmAxis, contentMask, passEntity ); } return false; } /* =================== idGameLocal::Contacts =================== */ int idGameLocal::Contacts( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->Contacts( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, passEntity ); } return 0; } /* =================== idGameLocal::Contents =================== */ int idGameLocal::Contents( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->Contents( start, mdl, trmAxis, contentMask, passEntity, touchedEntity ); } return 0; } /* =================== idGameLocal::TracePoint =================== */ bool idGameLocal::TracePoint( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->TracePoint( results, start, end, contentMask, passEntity ); } return false; } /* =================== idGameLocal::TraceBounds =================== */ bool idGameLocal::TraceBounds( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->TraceBounds( results, start, end, bounds, contentMask, passEntity ); } return false; } /* =================== idGameLocal::TranslationModel =================== */ void idGameLocal::TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { clipWorld->TranslationModel( results, start, end, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); } } /* =================== idGameLocal::RotationModel =================== */ void idGameLocal::RotationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { clipWorld->RotationModel( results, start, rotation, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); } } /* =================== idGameLocal::ContactsModel =================== */ int idGameLocal::ContactsModel( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->ContactsModel( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); } return 0; } /* =================== idGameLocal::ContentsModel =================== */ int idGameLocal::ContentsModel( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->ContentsModel( start, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); } return 0; } /* =================== idGameLocal::TranslationEntities =================== */ void idGameLocal::TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { clipWorld->TranslationEntities( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); } } /* =================== idGameLocal::GetModelContactFeature =================== */ bool idGameLocal::GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { const idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->GetModelContactFeature( contact, clipModel, winding ); } return false; } /* =================== idGameLocal::EntitiesTouchingBounds =================== */ int idGameLocal::EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { const idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->EntitiesTouchingBounds( bounds, contentMask, entityList, maxCount ); } return 0; } /* =================== idGameLocal::ClipModelsTouchingBounds =================== */ int idGameLocal::ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { const idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->ClipModelsTouchingBounds( bounds, contentMask, clipModelList, maxCount ); } return 0; } /* =================== idGameLocal::PlayersTouchingBounds =================== */ int idGameLocal::PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const { const idClip* clipWorld = GetEntityClipWorld( ent ); if( clipWorld ) { return clipWorld->PlayersTouchingBounds( bounds, contentMask, entityList, maxCount ); } return 0; } /* =================== idGameLocal::GetWorldBounds =================== */ const idBounds& idGameLocal::GetWorldBounds( const idEntity* ent ) const { const idClip* clipWorld = GetEntityClipWorld( ent ); if ( clipWorld ) { return clipWorld->GetWorldBounds(); } return clip[ 0 ]->GetWorldBounds(); } /* =================== idGameLocal::GetEntityClipWorld =================== */ idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) { if( ent == NULL ) { return clip[ 0 ]; } if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); return NULL; } return clip[ ent->GetClipWorld() ]; } /* =================== idGameLocal::GetEntityClipWorld =================== */ const idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) const { if( ent == NULL ) { return clip[ 0 ]; } if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); return NULL; } return clip[ ent->GetClipWorld() ]; } /* =================== idGameLocal::AddClipWorld =================== */ int idGameLocal::AddClipWorld( int id ) { if( id >= clip.Num() ) { // if we want an index higher in the list, fill the intermediate indices with empties for( int i = clip.Num(); i <= id; i++ ) { clip.Append( NULL ); } } if( clip[ id ] == NULL ) { // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); // RAVEN END clip[ id ] = new idClip(); // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_POP_HEAP(); // RAVEN END clip[ id ]->Init(); } return id; } /* =================== idGameLocal::RemoveClipWorld =================== */ void idGameLocal::RemoveClipWorld( int id ) { assert( id >= 0 && id < clip.Num() ); clip[ id ]->Shutdown(); delete clip[ id ]; clip[ id ] = NULL; } /* =================== idGameLocal::ShutdownInstances =================== */ void idGameLocal::ShutdownInstances( void ) { if( gamestate == GAMESTATE_UNINITIALIZED ) { return; } instances.DeleteContents( true ); // free the trace model used for the defaultClipModel idClip::FreeDefaultClipModel(); } /* =================== idGameLocal::AddInstance =================== */ int idGameLocal::AddInstance( int id, bool deferPopulate ) { if ( id == -1 ) { id = instances.Num(); } if ( id >= instances.Num() ) { // if we want an index higher in the list, fill the intermediate indices with empties for( int i = instances.Num(); i <= id; i++ ) { instances.Append( NULL ); } } if ( instances[ id ] == NULL ) { // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); instances[ id ] = new rvInstance( id, deferPopulate ); RV_POP_HEAP(); // RAVEN END if ( !deferPopulate ) { // keep track of the high watermark ServerSetEntityIndexWatermark( id ); } common->DPrintf( "idGameLocal::AddInstance(): Adding instance %d\n", instances[ id ]->GetInstanceID() ); } else { common->DPrintf( "idGameLocal::AddInstance(): Instance %d already exists\n", instances[ id ]->GetInstanceID() ); } // keep the min spawn index correctly set ServerSetMinSpawnIndex(); return instances[ id ]->GetInstanceID(); } /* =================== idGameLocal::RemoveInstance =================== */ void idGameLocal::RemoveInstance( int id ) { delete instances[ id ]; instances[ id ] = NULL; } /* =================== idGameLocal::GetPlayerName Returns the specified player name, max of 64 chars =================== */ void idGameLocal::GetPlayerName( int clientNum, char* name ) { if( !gameLocal.entities[ clientNum ] ) { return; } strncpy( name, gameLocal.GetUserInfo( clientNum )->GetString( "ui_name" ), 64 ); name[ 63 ] = 0; } /* =================== idGameLocal::GetPlayerClan Returns the specified player clan, max of 64 chars =================== */ void idGameLocal::GetPlayerClan( int clientNum, char* clan ) { if( !gameLocal.entities[ clientNum ] ) { return; } strncpy( clan, gameLocal.GetUserInfo( clientNum )->GetString( "ui_clan" ), 64 ); clan[ 63 ] = 0; } /* =================== idGameLocal::SetFriend =================== */ void idGameLocal::SetFriend( int clientNum, bool isFriend ) { if( !gameLocal.GetLocalPlayer() ) { Warning( "idGameLocal::SetFriend() - SetFriend() called with NULL local player\n" ); return; } gameLocal.GetLocalPlayer()->SetFriend( clientNum, isFriend ); } /* =================== idGameLocal::GetLongGametypeName =================== */ const char* idGameLocal::GetLongGametypeName( const char* gametype ) { return mpGame.GetLongGametypeName( gametype ); } void idGameLocal::Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ) { int instance = 0; if ( args.Argc() > 1 ) { instance = atoi( args.Argv( 1 ) ); } if( gameLocal.instances[ instance ] ) { gameLocal.instances[ instance ]->PrintMapNumbers(); } } void idGameLocal::Cmd_PrintSpawnIds_f( const idCmdArgs& args ) { for( int i = 0; i < MAX_GENTITIES; i++ ) { if( gameLocal.entities[ i ] ) { gameLocal.Printf( "Spawn id %d: %d\n", i, gameLocal.spawnIds[ i ] ); } } } /* =============== idGameLocal::GetDemoHud =============== */ idUserInterface *idGameLocal::GetDemoHud( void ) { if ( !demo_hud ) { demo_hud = uiManager->FindGui( "guis/hud.gui", true, false, true ); assert( demo_hud ); } return demo_hud; } /* =============== idGameLocal::GetDemoMphud =============== */ idUserInterface *idGameLocal::GetDemoMphud( void ) { if ( !demo_mphud ) { demo_mphud = uiManager->FindGui( "guis/mphud.gui", true, false, true ); assert( demo_mphud ); } return demo_mphud; } /* =============== idGameLocal::GetDemoCursor =============== */ idUserInterface *idGameLocal::GetDemoCursor( void ) { if ( !demo_cursor ) { demo_cursor = uiManager->FindGui( "guis/cursor.gui", true, false, true ); assert( demo_cursor ); } return demo_cursor; } /* =============== idGameLocal::IsTeamPowerups =============== */ bool idGameLocal::IsTeamPowerups( void ) { if ( !serverInfo.GetBool( "si_isBuyingEnabled" ) ) { return false; } if ( !IsTeamGameType() ) { return false; } return ( gameType != GAME_ARENA_CTF ); } // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation #if defined(_RV_MEM_SYS_SUPPORT) /* =================== idGameLocal::FlushBeforelevelLoad =================== */ void idGameLocal::FlushBeforelevelLoad( void ) { TIME_THIS_SCOPE( __FUNCLINE__); #ifndef _XENON MapShutdown(); #else mpGame.Clear(); #endif for(int i = 0; i < aasNames.Num(); i++) { aasList[i]->Shutdown(); } } #endif // RAVEN END // dluetscher: moved the overloaded new/delete to sys_local.cpp and Game_local.cpp (from Heap.h) // so that the tools.dll will link. #if !defined(_XBOX) && (defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT)) #undef new #undef delete #undef Mem_Alloc #undef Mem_Free #ifdef ID_DEBUG_MEMORY void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ) { return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); } void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ) { Mem_Free( p, fileName, lineNumber ); } void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ) { return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); } void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ) { Mem_Free( p, fileName, lineNumber ); } void *operator new( size_t s ) { return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); } void operator delete( void *p ) { Mem_Free( p, "", 0 ); } void *operator new[]( size_t s ) { return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); } void operator delete[]( void *p ) { Mem_Free( p, "", 0 ); } #else // #ifdef ID_DEBUG_MEMORY void *operator new( size_t s ) { return Mem_Alloc( s, MemScopedTag_GetTopTag() ); } void operator delete( void *p ) { Mem_Free( p ); } void *operator new[]( size_t s ) { return Mem_Alloc( s, MemScopedTag_GetTopTag() ); } void operator delete[]( void *p ) { Mem_Free( p ); } #endif // #else #ifdef ID_DEBUG_MEMORY #endif // #if defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) // RAVEN END