quake4-sdk/source/mpgame/Game_local.cpp

8642 lines
220 KiB
C++

#include "../idlib/precompiled.h"
#pragma hdrstop
#include "Game_local.h"
#include "../sys/AutoVersion.h"
struct game_name_s {
game_name_s( void ) {
sprintf( string, "Q4MP %s", VERSION_STRING_DOTTED );
}
char string[256];
} game_name;
idCVar gamename( "gamename", game_name.string, CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "" );
// 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();
repeaterInfo.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;
ShutdownInstances();
clip.Clear();
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;
isTVClient = 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 ) );
ReallocViewers( 0 );
maxViewers = 0;
maxViewer = 0;
viewers = NULL;
viewerEntityStates = NULL;
viewerPVS = NULL;
viewerSnapshots = NULL;
viewerUnreliableMessages = NULL;
eventQueue.Init();
// RAVEN BEGIN
// bdube: client entities
clientSpawnCount = INITIAL_SPAWN_COUNT;
clientSpawnedEntities.Clear();
memset( clientEntities, 0, sizeof( clientEntities ) );
memset( clientSpawnIds, -1, sizeof( clientSpawnIds ) );
gameDebug.Shutdown ( );
gameLogLocal.Shutdown ( );
currentThinkingEntity = NULL;
// RAVEN END
demoState = DEMO_NONE;
serverDemo = false;
timeDemo = false;
memset( &usercmd, 0, sizeof( usercmd ) );
memset( &oldUsercmd, 0, sizeof( oldUsercmd ) );
followPlayer = -1; // start free flying
demo_protocol = 0;
instancesEntityIndexWatermarks.Clear();
clientInstanceFirstFreeIndex = MAX_CLIENTS;
minSpawnIndex = MAX_CLIENTS;
modList.Clear();
filterMod = -1;
if ( networkSystem ) {
networkSystem->UseSortFunction( filterByMod, false );
}
clientAckSequence = -1;
}
/*
===========
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<rvViseme> *visemeTable100;
extern idHashTable<rvViseme> *visemeTable66;
extern idHashTable<rvViseme> *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<rvViseme>;
visemeTable66 = new idHashTable<rvViseme>;
visemeTable33 = new idHashTable<rvViseme>;
// RAVEN END
Printf( "------------- Initializing Game -------------\n" );
Printf( "gamename: %s\n", game_name.string );
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<idDeclModelDef>, idDeclStreamAllocator<idDeclModelDef> );
//declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator<idDecl>, idDeclStreamAllocator<idDecl> );
declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator<idDeclCameraDef>, idDeclStreamAllocator<idDeclCameraDef> );
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<idDeclModelDef> );
declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator<idDecl> );
// RAVEN BEGIN
// rjohnson: camera is now contained in a def for frame commands
declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator<idDeclCameraDef> );
// 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<DECL_MODELDEF>, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" );
cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f<DECL_MODELDEF>, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl<DECL_MODELDEF> );
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;i<MAX_CLIENTS;i++)
{
persistentPlayerInfo[i].SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_PERMANENT));
}
entityHash.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
gravityInfo.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
scriptObjectProxies.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
entityUsageList.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
ambientLights.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
instances.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
clip.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_LEVEL));
#endif
// RAVEN END
networkSystem->AddSortFunction( filterByMod );
incompatibleMaps = 0;
}
/*
===========
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();
repeaterInfo.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
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<idThread *> 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 );
}
// RAVEN BEGIN
// abahr: saving scriptObject proxies
savegame.WriteInt( scriptObjectProxies.Num() );
for( i = 0; i < scriptObjectProxies.Num(); ++i ) {
scriptObjectProxies[i].Save( &savegame );
}
// abahr: save out client stuff
savegame.WriteInt( clientSpawnedEntities.Num() );
for( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) {
savegame.WriteObject( clientEnt );
}
// RAVEN END
savegame.WriteInt( activeEntities.Num() );
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<idPlayer *>(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];
va_start( argptr, fmt );
idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
va_end( argptr );
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<idPlayer *>( 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::RepeaterSetUserInfo
============
*/
const idDict* idGameLocal::RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) {
TIME_THIS_SCOPE( __FUNCLINE__);
assert( cvarSystem->GetCVarInteger( "ri_maxViewers" ) > clientNum );
ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) );
idDict *info;
int i;
bool modifiedInfo = false;
info = &viewers[ clientNum ].info;
*info = userInfo;
// don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server
if ( idStr::IsNumeric( info->GetString( "ui_name" ) ) ) {
info->Set( "ui_name", va( "%s_", info->GetString( "ui_name" ) ) );
modifiedInfo = true;
}
// don't allow dupe nicknames
for ( i = 0; i < maxViewer; i++ ) {
if ( i == clientNum ) {
continue;
}
if ( !viewers[ i ].active ) {
continue;
}
if ( !idStr::Icmp( info->GetString( "ui_name" ), viewers[ i ].info.GetString( "ui_name" ) ) ) {
info->Set( "ui_name", va( "%s_", info->GetString( "ui_name" ) ) );
modifiedInfo = true;
i = -1; // rescan
continue;
}
}
if ( modifiedInfo ) {
newInfo = *info;
return &newInfo;
}
return NULL;
}
/*
===========
idGameLocal::UpdateRepeaterInfo
============
*/
void idGameLocal::UpdateRepeaterInfo( bool transmit ) {
if ( !isRepeater ) {
return;
}
repeaterInfo = serverInfo;
repeaterInfo.Set( "si_tv", "1" );
// fudge a few cvars so old browsers display correctly
repeaterInfo.Set( "si_usePass", ri_useViewerPass.GetBool() ? "1" : "0" );
const char *ri_name = cvarSystem->GetCVarString( "ri_name" );
if ( ri_name[0] ) {
if ( !serverInfo.GetString( "ri_origName" ) [0] ) {
repeaterInfo.Set( "ri_origName", serverInfo.GetString( "si_name" ) );
}
repeaterInfo.Set( "si_name", ri_name );
}
const char *si_serverURL = cvarSystem->GetCVarString( "si_serverURL" );
if ( si_serverURL[0] ) {
if ( !serverInfo.GetString( "ri_origServerURL" ) [0] ) {
repeaterInfo.Set( "ri_origServerURL", serverInfo.GetString( "si_serverURL" ) );
}
repeaterInfo.Set( "si_serverURL", si_serverURL );
}
repeaterInfo.Copy( *cvarSystem->MoveCVarsToDict( CVAR_REPEATERINFO ) );
networkSystem->RepeaterSetInfo( repeaterInfo );
if ( transmit ) {
// Let our clients know the server info changed
idBitMsg outMsg;
byte msgBuf[MAX_GAME_MESSAGE_SIZE];
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO );
outMsg.WriteDeltaDict( gameLocal.repeaterInfo, NULL );
networkSystem->RepeaterSendReliableMessage( -1, outMsg );
}
}
/*
===========
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
UpdateRepeaterInfo( true );
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, true );
} else if ( isClient ) {
networkSystem->ClientSetServerInfo( gameLocal.serverInfo );
isTVClient = ( gameLocal.serverInfo.GetInt( "si_tv" ) == 1 ) ? true : false;
}
}
/*
===================
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 ) {
SetGameType();
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() );
if ( (++incompatibleMaps) >= 50 ) {
common->Warning( "too many incompatible maps for gametype %s; setting gametype to DM.", si_gameType.GetString() );
si_gameType.SetString( "DM" );
}
return;
}
incompatibleMaps = 0;
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
// some calculations about time are done from the frame, and
// that changes when si_fps changes. full restart resets to 0.
if ( keyval->GetValue().Icmp( keyval2->GetValue() ) &&
!keyval->GetKey().Icmp( "si_fps" ) ) {
break;
}
}
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, true );
if ( isRepeater ) {
outMsg.Init( msgBuf, sizeof( msgBuf ) );
outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART );
outMsg.WriteBits( 1, 1 );
outMsg.WriteDeltaDict( repeaterInfo, NULL );
networkSystem->RepeaterSendReliableMessage( -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
//asalmon: pick another map on Xenon
#ifdef _XENON
//Live()->PickMap();
return true;
#endif
//RAVEN END
// 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(), false );
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->isClient && gameLocal.serverInfo.GetInt( "si_tv" ) == 1 ) {
this->isTVClient = true;
}
if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 3 ) {
networkSystem->HTTPEnable( this->isServer || this->isRepeater );
}
if ( !this->isMultiplayer )
gameLocal.Error( "This mod is for multiplayer 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
SetGameType();
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<idClass *&>( 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<idClass *&>( 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<idClass *&>( world ) );
savegame.ReadInt( num );
for( i = 0; i < num; i++ ) {
savegame.ReadObject( reinterpret_cast<idClass *&>( 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<idClass *&>( clientEnt ) );
assert( clientEnt );
if ( clientEnt ) {
clientEnt->spawnNode.AddToEnd( clientSpawnedEntities );
}
}
// RAVEN END
savegame.ReadInt( num );
for( i = 0; i < num; i++ ) {
savegame.ReadObject( reinterpret_cast<idClass *&>( 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<idClass *&>( frameCommandThread ) );
// clip
// push
// pvs
// testmodel = "<NULL>"
// testFx = "<NULL>"
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<idClass *&>( locationEntities[ i ] ) );
}
}
savegame.ReadObject( reinterpret_cast<idClass *&>( 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 );
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 || i == ENTITYNUM_NONE ) {
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();
nextLagoCheck = 0;
}
// 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;
camera = NULL;
portalSky = NULL;
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<const idSoundShader *>(declManager->DeclByIndex( DECL_SOUND, i, false ));
parms = soundShader->GetParms();
if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) {
const_cast<idSoundShader *>(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
if ( !ent->IsType( idPlayer::GetClassType() ) ) {
Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() );
}
PACIFIER_UPDATE;
if ( clientNum != ENTITYNUM_NONE ) {
if ( clientNum >= numClients ) {
numClients = clientNum + 1;
}
}
mpGame.SpawnPlayer( clientNum );
}
/*
================
idGameLocal::GetClientByNum
================
*/
idPlayer *idGameLocal::GetClientByNum( int current ) const {
if ( current == MAX_CLIENTS ) {
return NULL;
}
if ( current == ENTITYNUM_NONE ) {
return static_cast<idPlayer *>( entities[ current ] );
}
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<idPlayer *>( 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<idPlayer *>( 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 == MAX_CLIENTS ) {
return static_cast<idPlayer *>( entities[ ENTITYNUM_NONE ] );
}
if ( localClientNum < 0 || localClientNum >= MAX_CLIENTS ) {
return NULL;
}
if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ) {
// not fully in game yet
return NULL;
}
return static_cast<idPlayer *>( 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<idPlayer*>( 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<idPlayer *>( 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();
do {
previousTime = time;
// sanity - game frame is counted on both sides of the DLL fence
// but I've seen cases where it would be offset and we need to make sure that doesn't happen
if ( serverGameFrame != framenum ) {
common->Warning( "core and game frame counters are out of sync: %d/%d", serverGameFrame, framenum );
if ( serverGameFrame > framenum ) {
int delta = serverGameFrame - framenum;
time += delta * GetMSec();
framenum = serverGameFrame;
} if ( serverGameFrame < framenum ) {
// don't do anything, let the core catchup to us
memset( &ret, 0, sizeof( ret ) );
return ret;
}
}
// update the game time
framenum++;
// 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;
// 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
/*
================
idGameLocal::RepeaterFrame
================
*/
void idGameLocal::RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ) {
int i;
// passing serverGameFrame is strictly a sanity check
// the adjustment in case of problem in RunFrame should cascade to here and never be a long term problem
static int FrameDelta = 0;
if ( serverGameFrame != framenum ) {
// don't want to spam the warning. don't have a good repro case to fix this but still want to track the error (seen it happen)
int delta = serverGameFrame - framenum;
if ( delta != FrameDelta ) {
if ( framenum != 0 ) { // to skip printing the initial messages
common->Warning( "repeater frame and game frame out of sync: %d/%d", serverGameFrame, framenum );
}
FrameDelta = delta;
}
} else {
if ( FrameDelta != 0 ) {
common->Warning( "repeater in sync with game frame at %d", serverGameFrame );
}
FrameDelta = 0;
}
ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) );
for ( i = 0; i < maxViewer; i++ ) {
if ( !viewers[ i ].active ) {
continue;
}
if ( viewers[ i ].nopvs ) {
continue;
}
viewers[ i ].origin = clientOrigins[ i ];
if ( viewers[ i ].origin.followClient >= 0 && entities[ viewers[ i ].origin.followClient ] && entities[ viewers[ i ].origin.followClient ]->IsType( idPlayer::GetClassType() ) ) {
// if the viewer is following someone, use their origin
idPlayer *player = static_cast< idPlayer * >( entities[ viewers[ i ].origin.followClient ] );
viewers[ i ].origin.origin = player->GetEyePosition();
}
viewers[ i ].pvsArea = pvs.GetPVSArea( viewers[ i ].origin.origin );
}
}
/*
======================================================================
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<idPlayer *>(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<idPlayer *>( 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; i<aasNames.Num(); i++ ) {
aas = GetAAS( i );
if ( !aas || !aas->IsValid() ) {
continue;
}
if ( aas->GetSettings()->debugDraw ) {
aas->ShowAreas( origin );
}
}
}
if ( aas_showProblemAreas.GetInteger() == 3 ) {
for( int i=0; i<aasNames.Num(); i++ ) {
aas = GetAAS( i );
if ( !aas || !aas->IsValid() ) {
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 false;
}
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 || ent == entities[ 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<idEntity *>(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<rvClientEntity*>(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<idEntity *>(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<const idDeclEntityDef *>( 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;
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 ) );
}
}
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 {
// 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();
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<idEntity> > &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<idEntity> &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<idPlayer *>(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<idActor *>( 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<idEntity*>(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<idAI *>( 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();
if ( !view ) {
player->CalculateRenderView();
view = player->GetRenderView();
assert( view );
}
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 ) {
clip[0]->Translation( trace, source, dest, NULL, mat3_identity, clipMask, NULL, NULL );
} else {
idClipModel cm;
cm.LoadModel( *trm, NULL );
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**)(&cent), 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<idEntity> 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<idStr> 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<idLocationEntity *>(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<idLocationEntity *>(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<idLocationEntity*>(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<idLocationEntity *>(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<const spawnSpot_t *>( ptr1 );
const spawnSpot_t *spot2 = static_cast<const spawnSpot_t *>( 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<idPlayerStart*>(spot) );
} else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) {
teamSpawnSpots[TEAM_MARINE].Append( static_cast<idPlayerStart*>(spot) );
}
// spawnSpots contains info_player_team as well as info_player_deathmatch
spawnSpots.Append ( static_cast<idPlayerStart*>(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<idPlayerStart*>(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<idPlayerStart*>(spot) );
} else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) {
teamSpawnSpots[TEAM_MARINE].Append( static_cast<idPlayerStart*>(spot) );
}
// spawnSpots contains info_player_team as well as info_player_deathmatch
spawnSpots.Append ( static_cast<idPlayerStart*>(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<spawnSpot_t> weightedSpawns;
idList<idPlayerStart*>* 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" ) || !keyval->GetKey().Icmp( "si_fps" ) ) ) {
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
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
idStr alternateEffect = effectName;
alternateEffect += "_mp";
idStr classname = args.GetString( "classname" );
if ( !alternateEffect.Icmp( "fx_fly_mp" ) ) {
if (
( !g_nailTrail.GetBool() && !classname.Icmpn( "projectile_nail", 15 ) ) ||
( !g_rocketTrail.GetBool() && !classname.Icmpn( "projectile_rocket", 17 ) ) ||
( !g_napalmTrail.GetBool() && !classname.Icmpn( "projectile_napalm", 17 ) ) )
{
alternateEffect += "_low";
} else if ( !g_grenadeTrail.GetBool() && !classname.Icmpn( "projectile_grenade", 18 ) )
{
return NULL;
}
} else if ( !alternateEffect.Icmp( "fx_path_mp" ) )
{
if ( !g_railTrail.GetBool() && !classname.Icmpn( "hitscan_railgun", 15 ) )
{
alternateEffect += "_low";
}
}
effectFile = args.GetString( alternateEffect );
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 predictedBit,
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.WriteFloat( endOrigin.x );
msg.WriteFloat( endOrigin.y );
msg.WriteFloat( endOrigin.z );
msg.WriteByte( category );
msg.WriteBits( loop, 1 );
msg.WriteBits( predictedBit, 1 );
// 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<idPlayer*>(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, 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, 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<idActor*>(ent);
if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) )
{
idAFAttachment* headEnt = static_cast<idAFAttachment*>(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, 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, 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;
}
}
/*
===================
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::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