#include "precompiled.h" #pragma hdrstop #include "prey_local.h" //#include "../prey/win32/fxdlg.h" extern idCVar com_forceGenericSIMD; //HUMANHEAD: aob - needed for networking to send the least amount of bits const int DECL_MAX_TYPES_NUM_BITS = hhMath::BitsForInteger( DECL_MAX_TYPES ); //HUMANHEAD END //============================================================================= // Overridden functions //============================================================================= //--------------------------------------------------- // // hhGameLocal::Init // //--------------------------------------------------- void hhGameLocal::Init( void ) { //HUMANHEAD rww - moved into idGameLocal::Init, this should be done after the managed heap is initialized. //ddaManager = new hhDDAManager; //must be before calling idGameLocal::Init(), otherwise if the map can't load, will cause a crash. //HUMANHEAD END idGameLocal::Init(); dwWorldClipModel = NULL; #if _HH_INLINED_PROC_CLIPMODELS inlinedProcClipModels.Clear(); //HUMANHEAD rww #endif sunCorona = NULL; // CJR lastAIAlertRadius = 0; P_InitConsoleCommands(); //HUMANHEAD rww - check lglcd validity and reset values logitechLCDEnabled = sys->LGLCD_Valid(); logitechLCDDisplayAlt = false; logitechLCDButtonsLast = 0; logitechLCDUpdateTime = 0; //HUMANHEAD END } //--------------------------------------------------- // // hhGameLocal::Shutdown // //--------------------------------------------------- void hhGameLocal::Shutdown( void ) { #if INGAME_DEBUGGER_ENABLED debugger.Shutdown(); // HUMANHEAD pdm: Shut down the debugger before idDict gets shut down #endif idGameLocal::Shutdown(); //HUMANHEAD rww - this must be done before managed heap destruction to correspond //delete ddaManager; //ddaManager = NULL; //HUMANHEAD END } //--------------------------------------------------- // // hhGameLocal::UnregisterEntity // //--------------------------------------------------- void hhGameLocal::UnregisterEntity( idEntity *ent ) { assert( ent ); if ( talonTargets.Find( ent ) ) { talonTargets.Remove( ent ); } idGameLocal::UnregisterEntity( ent ); } //--------------------------------------------------- // // hhGameLocal::MapShutdown // //--------------------------------------------------- void hhGameLocal::MapShutdown( void ) { //HUMANHEAD: mdc - added support for automatically dumping stats on map switch/game exit if( !isMultiplayer && gamestate != GAMESTATE_NOMAP && g_dumpDDA.GetBool() ) { //make sure we actually have a valid map to export GetDDA()->Export(NULL); //export the stats } //Always clear our tracking stats and create a default node GetDDA()->ClearTracking(); //Clear Tracking statistics // GetDDA()->CreateDefaultSectionNode(); //Setup our default section-node idGameLocal::MapShutdown(); sunCorona = NULL; talonTargets.Clear(); } //--------------------------------------------------- // // hhGameLocal::InitFromNewMap // //--------------------------------------------------- void hhGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ) { //HUMANHEAD rww - throw up the prey logo if using the logitech lcd screen if (logitechLCDEnabled) { sys->LGLCD_UploadImage(NULL, -1, -1, false, true); } //HUMANHEAD END talonTargets.Clear(); // CJR: Must be before idGameLocal::InitFromNewMap() #if INGAME_DEBUGGER_ENABLED debugger.Reset(); #endif hands.Clear(); idGameLocal::InitFromNewMap(mapName, renderWorld, soundWorld, isServer, isClient, randseed); // CJR: Determine if this map is a LOTA map by looking for a special entity on the map bIsLOTA = false; if ( FindEntity( "LOTA_ThisMapIsLOTA" ) ) { bIsLOTA = true; } } //============================================================================= // // hhGameLocal::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 // // HUMANHEAD pdm: Override for doing our own precaching //============================================================================= void hhGameLocal::CacheDictionaryMedia( const idDict *dict ) { /* HUMANHEAD mdc - fix so com_makingBuild stuff will work correctly... (this was causing too early of an out) if ( dict == NULL ) { return; } */ const idKeyValue *kv = NULL; const idDecl *decl = NULL; idFile *file = NULL; idStr fxName; idStr buffer; idGameLocal::CacheDictionaryMedia( dict ); if( dict == NULL ) { //mdc - added early out return; } //HUMANHEAD mdc - skip caching if we are in development and not making a build if( !g_precache.GetBool() && !sys_forceCache.GetBool() && !cvarSystem->GetCVarBool("com_makingBuild") ) { return; } //HUMANHEAD END // TEMP: precache clip models until they become model_clip kv = dict->MatchPrefix( "clipmodel", NULL ); while( kv ) { if ( kv->GetValue().Length() ) { declManager->MediaPrint( "Precaching clipmodel: %s\n", kv->GetValue().c_str() ); renderModelManager->FindModel( kv->GetValue() ); } kv = dict->MatchPrefix( "clipmodel", kv ); } //HUMANHEAD bjk: removed smoke_wound_. now using fx_wound and is cached in fx_. no seperate handling needed kv = dict->MatchPrefix( "beam", NULL ); while( kv ) { if ( kv->GetValue().Length() ) { if (kv->GetValue().Find(".beam") >= 0) { declManager->MediaPrint( "Precaching beam %s\n", kv->GetValue().c_str() ); declManager->FindType(DECL_BEAM, kv->GetValue(), false); // Cache the beam decl renderModelManager->FindModel(kv->GetValue()); // Ensure the beam model is cached as well, since beams are a combination of a decl and a rendermodel } else { // Must be a reference to a beam entity declManager->MediaPrint( "Precaching beam entityDef %s\n", kv->GetValue().c_str() ); FindEntityDef( kv->GetValue().c_str(), false ); } } kv = dict->MatchPrefix( "beam", kv ); } } //============================================================================= // New functionality //============================================================================= //============================================================================= // // hhGameLocal::SpawnAppendedMapEntities // // HUMANHEAD pdm: Support for level appending //============================================================================= #if DEATHWALK_AUTOLOAD void hhGameLocal::SpawnAppendedMapEntities() { if (additionalMapFile) { // Swap mapFile and additionalMapFile idMapFile *mainMapFile = mapFile; mapFile = additionalMapFile; // This routine based largely on idGameLocal::SpawnMapEntities // parse the key/value pairs and spawn entities for dw // From SpawnMapEntities(); idMapEntity *mapEnt; int numEntities; idDict args; numEntities = mapFile->GetNumEntities(); if ( numEntities == 0 ) { Error( "...no entities" ); } for ( int i = 1 ; i < numEntities ; i++ ) { // skip worldspawn mapEnt = mapFile->GetEntity( i ); args = mapEnt->epairs; if ( !InhibitEntitySpawn( args ) ) { // precache any media specified in the map entity gameLocal.CacheDictionaryMedia( &args ); //HUMANHEAD rww - ok on client SpawnEntityDef( args, NULL, true, false, true ); } } // Restore main mapfile delete additionalMapFile; additionalMapFile = NULL; mapFile = mainMapFile; // Since the world collision is always collision model 0, link our model in like an entity would dwWorldClipModel = new idClipModel("dw_worldmap"); dwWorldClipModel->SetContents(CONTENTS_SOLID); dwWorldClipModel->SetEntity(world); dwWorldClipModel->Link(clip); } } #endif //============================================================================= // // hhGameLocal::RegisterTalonTarget // //============================================================================= void hhGameLocal::RegisterTalonTarget( idEntity *ent ) { talonTargets.Append(ent); } //============================================================================= // // hhGameLocal::SpawnClientObject // rww - meant for spawning client ents only. //============================================================================= idEntity* hhGameLocal::SpawnClientObject( const char* objectName, idDict* additionalArgs ) { idDict localArgs; idDict* args = NULL; idEntity* object = NULL; bool clientEnt = true; if (!gameLocal.isClient) { //if it's the server spawning a client entity we want to spawn it for listen servers but keep it local clientEnt = false; } if( !objectName || !objectName[0]) { Error( "hhGameLocal::SpawnObject: Invalid object name\n" ); } args = ( additionalArgs != NULL ) ? additionalArgs : &localArgs; args->Set( "classname", objectName ); if( !SpawnEntityDef( *args, &object, true, clientEnt ) ) { Error( "hhGameLocal::SpawnObject: Failed to spawn %s\n", objectName ); } if (object) { object->fl.clientEntity = clientEnt; object->fl.networkSync = false; } return object; } //============================================================================= // // hhGameLocal::SpawnObject // //============================================================================= idEntity* hhGameLocal::SpawnObject( const char* objectName, idDict* additionalArgs ) { idDict localArgs; idDict* args = NULL; idEntity* object = NULL; //rww - are you hitting this? then the code it's coming from is doing something it shouldn't be. //the client should never, ever call SpawnObject. assert(!gameLocal.isClient); if( !objectName || !objectName[0]) { Error( "hhGameLocal::SpawnObject: Invalid object name\n" ); } args = ( additionalArgs != NULL ) ? additionalArgs : &localArgs; args->Set( "classname", objectName ); if( !SpawnEntityDef( *args, &object ) ) { Error( "hhGameLocal::SpawnObject: Failed to spawn %s\n", objectName ); } return object; } // // hhGameLocal::RadiusDamage() // PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge // void hhGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower ) { float distSquared, radiusSquared, damageScale, attackerDamageScale, attackerPushScale; idEntity * ent; idEntity * entityList[ MAX_GENTITIES ]; int numListedEntities; idBounds bounds; idVec3 v, damagePoint, dir; int i, e, damage, radius, push; // HUMANHEAD AOB int pushRadius; trace_t result; // HUMANHEAD END const idDict *damageDef = FindEntityDefDict( damageDefName, false ); if ( !damageDef ) { Warning( "Unknown damageDef '%s'", damageDefName ); return; } damageDef->GetInt( "damage", "0", damage ); // HUMANHEAD JRM - changed to ZERO not 20 default damageDef->GetInt( "radius", "50", radius ); damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); // HUMANHEAD aob pushRadius = Max( 1.0f, damageDef->GetFloat("push_radius", va("%d", radius)) ); if ( damageDef->GetBool( "nopush" ) ) { push = 0; } // HUMANHEAD END if ( radius < 1 ) { radius = 1; } radiusSquared = radius*radius; bounds = idBounds( origin ).Expand( radius ); // get all entities touching the bounds numListedEntities = clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { inflictor = static_cast(inflictor)->GetBody(); } if ( attacker && attacker->IsType( idAFAttachment::Type ) ) { attacker = static_cast(attacker)->GetBody(); } if ( ignoreDamage && ignoreDamage->IsType( idAFAttachment::Type ) ) { ignoreDamage = static_cast(ignoreDamage)->GetBody(); } // apply damage to the entities if( damage > 0 ) { // HUMANHEAD JRM - only do damage if WE HAVE damage for ( e = 0; e < numListedEntities; e++ ) { ent = entityList[ e ]; assert( ent ); if ( !ent->fl.takedamage ) { continue; } if ( ent == inflictor /*|| ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor )*/ ) { continue; } if ( ent == ignoreDamage /*|| ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignoreDamage )*/ ) { continue; } if ( ent->IsType( idAFAttachment::Type ) ) { // bjk: no double splash damage from heads continue; } // don't damage a dead player if ( isMultiplayer && ent->entityNumber < MAX_CLIENTS && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >( ent )->health < 0 ) { continue; } // find the distance from the edge of the bounding box for ( i = 0; i < 3; i++ ) { if ( origin[ i ] < ent->GetPhysics()->GetAbsBounds()[0][ i ] ) { v[ i ] = ent->GetPhysics()->GetAbsBounds()[0][ i ] - origin[ i ]; } else if ( origin[ i ] > ent->GetPhysics()->GetAbsBounds()[1][ i ] ) { v[ i ] = origin[ i ] - ent->GetPhysics()->GetAbsBounds()[1][ i ]; } else { v[ i ] = 0; } } distSquared = v.LengthSqr(); if ( distSquared >= radiusSquared ) { continue; } //HUMANHEAD: aob - see if radius damage is blocked. CanDamage is used by more than just RadiusDamage clip.TracePoint( result, origin, (ent->GetPhysics()->GetAbsBounds()[0] + ent->GetPhysics()->GetAbsBounds()[1]) * 0.5f, CONTENTS_BLOCK_RADIUSDAMAGE, ignoreDamage ); if( result.fraction < 1.0f && result.c.entityNum != ent->entityNumber ) { continue; } //HUMANHEAD END bool canDamage = true; if ( GERMAN_VERSION || g_nogore.GetBool() ) { if ( ent->IsType(idActor::Type) ) { idActor *actor = reinterpret_cast< idActor * > ( ent ); if ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) { canDamage = false; } } } if ( canDamage && ent->CanDamage( origin, damagePoint ) ) { // push the center of mass higher than the origin so players // get knocked into the air more dir = ent->GetPhysics()->GetOrigin() - origin; dir[ 2 ] += 24; // get the damage scale damageScale = dmgPower * ( 1.0f - (distSquared / radiusSquared) ); if ( ent == attacker || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == attacker ) ) { damageScale *= attackerDamageScale; } ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT ); } } } // HUMANHEAD END // push physics objects if ( push ) { //HUMANHEAD: aob - changed radius to pushRadius RadiusPush( origin, pushRadius, push * dmgPower, attacker, ignorePush, attackerPushScale, false ); } } /* ============== hhGameLocal::RadiusPush PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge ============== */ void hhGameLocal::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; trace_t trace; //HUMANHEAD: aob dir.Set( 0.0f, 0.0f, 1.0f ); bounds = idBounds( origin ).Expand( radius ); // get all clip models touching the bounds numListedClipModels = clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { inflictor = static_cast(inflictor)->GetBody(); } if ( ignore && ignore->IsType( idAFAttachment::Type ) ) { ignore = static_cast(ignore)->GetBody(); } // 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(); // never push projectiles if ( ent->IsType( idProjectile::Type ) ) { continue; } // players use "knockback" in idPlayer::Damage if ( ent->IsType( idPlayer::Type ) && !quake ) { continue; } // don't push the ignore entity if ( ent == ignore || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignore ) ) { continue; } if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetAbsBounds().GetCenter() ) ) { continue; } // Don't affect ragdolls in non-gore mode if ( GERMAN_VERSION || g_nogore.GetBool() ) { if ( ent->IsType(idActor::Type) ) { idActor *actor = reinterpret_cast< idActor * > ( ent ); if ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) { continue; } } } // scale the push for the inflictor if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor ) ) { scale = inflictorScale; } else if ( ent->IsType(hhMoveable::Type) ) { scale = ent->spawnArgs.GetFloat("radiusPush", "4.0"); } else if( !ent->IsType(idActor::Type) ) { scale = 4.0f; } else { scale = 1.0f; } //HUMANHEAD: aob - see if radius damage is blocked clip.TracePoint( trace, origin, clipModel->GetEntity()->GetOrigin(), CONTENTS_BLOCK_RADIUSDAMAGE, ignore ); if( trace.fraction < 1.0f && trace.c.entityNum != clipModel->GetEntity()->entityNumber ) { continue; } //HUMANHEAD END if ( quake ) { clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); } else { //RadiusPushClipModel( origin, scale * push, clipModel ); //HUMANHEAD bjk idVec3 impulse = clipModel->GetAbsBounds().GetCenter() - origin; float dist = impulse.Normalize() / radius; impulse.z += 1.0f; dist = 0.6f - 0.3f*dist; impulse *= push * dist * scale; clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), impulse ); //HUMANHEAD END } } } //HUMANHEAD rww void hhGameLocal::LogitechLCDUpdate(void) { hhPlayer *pl = (hhPlayer *)GetLocalPlayer(); if (!pl) { return; } DWORD buttons; sys->LGLCD_ReadSoftButtons(&buttons); if (logitechLCDButtonsLast != buttons && buttons) { logitechLCDDisplayAlt = !logitechLCDDisplayAlt; } logitechLCDButtonsLast = buttons; if (logitechLCDUpdateTime >= gameLocal.time) { //only update the screen at 20fps return; } logitechLCDUpdateTime = gameLocal.time + 50; sys->LGLCD_DrawBegin(); if (logitechLCDDisplayAlt) { //primary/secondary ammo char *ammoStr = va("%s%s", common->GetLanguageDict()->GetString("#str_41150"), common->GetLanguageDict()->GetString("#str_41152")); //"Ammo: N/A"; char *ammoAltStr = va("%s%s", common->GetLanguageDict()->GetString("#str_41151"), common->GetLanguageDict()->GetString("#str_41152")); //"Alt. Ammo: N/A"; if (pl->weapon.IsValid()) { int a = pl->weapon->AmmoAvailable(); if (a >= 0) { if (a > 999) { a = 999; } ammoStr = va("%s%i", common->GetLanguageDict()->GetString("#str_41150"), a); //"Ammo: %i" } if (pl->weapon->GetAmmoType() != pl->weapon->GetAltAmmoType()) { a = pl->weapon->AltAmmoAvailable(); if (a >= 0) { if (a > 999) { a = 999; } ammoAltStr = va("%s%i", common->GetLanguageDict()->GetString("#str_41151"), a); //"Alt. Ammo: %i" } } } sys->LGLCD_DrawText(ammoStr, 46, 6, true); sys->LGLCD_DrawText(ammoAltStr, 46, 22, true); //draw the weapon icon (disabled for now, since it isn't very..recognizable) //sys->LGLCD_DrawShape(3, 54, 0, 0, 0, 0, true); } else { //health/spirit int v; v = pl->health; if (v > 999) { v = 999; } else if (v < 0) { v = 0; } sys->LGLCD_DrawText(va("%s%i", common->GetLanguageDict()->GetString("#str_41153"), v), 70, 6, true); //"Health: %i" v = pl->GetSpiritPower(); if (v > 999) { v = 999; } else if (v < 0) { v = 0; } sys->LGLCD_DrawText(va("%s%i", common->GetLanguageDict()->GetString("#str_41154"), v), 70, 22, true); //"Spirit: %i" //draw the tommy and talon float eyePitch; if (pl->InVehicle()) { eyePitch = 0.0f; } else { eyePitch = pl->GetEyeAxis()[2].ToAngles().pitch+90.0f; if (eyePitch < 1.0f && eyePitch > -1.0f) { eyePitch = 0.0f; } } sys->LGLCD_DrawShape(2, 42, 0, 0, 0, (int)eyePitch, true); } //draw the compass sys->LGLCD_DrawShape(0, 0, 0, 0, 0, 0, false); //base sys->LGLCD_DrawShape(1, 21, 21, 13, 1, (int)-pl->GetViewAngles().yaw, true); //line sys->LGLCD_DrawFinish(false); } //HUMANHEAD END // // RunFrame() // // PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge gameReturn_t hhGameLocal::RunFrame( const usercmd_t *clientCmds ) { idEntity * ent; int num; float ms; idTimer timer_think, timer_events, timer_singlethink; gameReturn_t ret; idPlayer *player; const renderView_t *view; // HUMANHEAD pdm idTimer timer_singledormant; bool dormant; const float thinkalpha = 0.98f; // filter with historical timings // HUMANHEAD END ret.sessionCommand[0] = 0; #ifdef _DEBUG if ( isMultiplayer ) { assert( !isClient ); } #endif player = GetLocalPlayer(); if ( !isMultiplayer && g_stopTime.GetBool() ) { // clear any debug lines from a previous frame gameRenderWorld->DebugClearLines( time + 1 ); // set the user commands for this frame memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) ); // Fake these categories so it still displays any that are in the history when time is stopped { PROFILE_SCOPE("Misc_Think", PROFMASK_NORMAL); } { PROFILE_SCOPE("Dormant Tests", PROFMASK_NORMAL); } { PROFILE_SCOPE("Sound", PROFMASK_NORMAL); } { PROFILE_SCOPE("Animation", PROFMASK_NORMAL); } { PROFILE_SCOPE("Scripting", PROFMASK_NORMAL); } if ( player ) { player->Think(); if ( player->InVehicle() && player->GetVehicleInterface() ) { if ( player->GetVehicleInterface()->GetVehicle() ) { player->GetVehicleInterface()->GetVehicle()->Think(); } } } } else do { // update the game time framenum++; previousTime = time; time += msec; realClientTime = time; timeRandom = time; //HUMANHEAD rww #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 ); } } // clear any debug lines from a previous frame gameRenderWorld->DebugClearLines( time ); // clear any debug polygons from a previous frame gameRenderWorld->DebugClearPolygons( time ); // set the user commands for this frame memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) ); // free old smoke particles smokeParticles->FreeSmokes(); // process events on the server ServerProcessEntityNetworkEventQueue(); // update our gravity vector if needed. UpdateGravity(); // create a merged pvs for all players SetupPlayerPVS(); // sort the active entity list SortActiveEntityList(); timer_think.Clear(); timer_think.Start(); PROFILE_START("Misc_Think", PROFMASK_NORMAL); // HUMANHEAD pdm // HUMANHEAD pdm: This loop reworked to support debugger and dormant timings // let entities think if ( g_timeentities.GetFloat() || g_debugger.GetInteger() || g_dormanttests.GetBool()) { num = 0; for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { ent->GetPhysics()->UpdateTime( time ); continue; } dormant = false; timer_singledormant.Clear(); if (!ent->fl.neverDormant) { timer_singledormant.Start(); dormant = ent->CheckDormant(); timer_singledormant.Stop(); } timer_singlethink.Clear(); if( !dormant ) { timer_singlethink.Start(); ent->Think(); timer_singlethink.Stop(); } ms = timer_singlethink.Milliseconds(); if ( g_timeentities.GetFloat() && ms >= g_timeentities.GetFloat() ) { gameLocal.Printf( "%d: entity '%s': %.1f ms\n", time, ent->name.c_str(), ms ); } if (g_debugger.GetInteger() || g_dormanttests.GetBool()) { ent->thinkMS = ent->thinkMS*thinkalpha + (1.0-thinkalpha)*ms; } if (g_dormanttests.GetBool() && !ent->fl.neverDormant) { float msDormant = timer_singledormant.Milliseconds(); ent->dormantMS = ent->dormantMS*thinkalpha + (1.0f-thinkalpha)*msDormant; if (ent->dormantMS > ent->thinkMS && !dormant) { // If we're spending more time on dormant checks than on actually thinking, // turn dormant checks off on that class. Printf("%30s Dms=%6.4f Tms=%6.4f\n", ent->GetClassname(), ent->dormantMS, ent->thinkMS); } } 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; } // HUMANHEAD JRM if( !ent->CheckDormant() ) { ent->Think(); } num++; } } else { num = 0; for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { // HUMANHEAD JRM if( !ent->CheckDormant() ) { ent->Think(); } 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; } PROFILE_STOP("Misc_Think", PROFMASK_NORMAL); timer_think.Stop(); timer_events.Clear(); timer_events.Start(); // service any pending events idEvent::ServiceEvents(); timer_events.Stop(); // free the player pvs FreePlayerPVS(); // do multiplayer related stuff if ( isMultiplayer ) { mpGame.Run(); } // display how long it took to calculate the current game frame if ( g_frametime.GetBool() ) { Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", time, timer_think.Milliseconds() + timer_events.Milliseconds(), timer_think.Milliseconds(), timer_events.Milliseconds(), num ); } // build the return value ret.consistencyHash = 0; ret.sessionCommand[0] = 0; if ( !isMultiplayer && player ) { ret.health = player->health; ret.heartRate = 0; //player->heartRate; // HUMANHEAD pdm: not used ret.stamina = idMath::FtoiFast( player->stamina ); // combat is a 0-100 value based on lastHitTime and lastDmgTime // each make up 50% of the time spread over 10 seconds ret.combat = 0; if ( player->lastDmgTime > 0 && time < player->lastDmgTime + 10000 ) { ret.combat += 50.0f * (float) ( time - player->lastDmgTime ) / 10000; } if ( player->lastHitTime > 0 && time < player->lastHitTime + 10000 ) { ret.combat += 50.0f * (float) ( time - player->lastHitTime ) / 10000; } } // see if a target_sessionCommand has forced a changelevel if ( sessionCommand.Length() ) { strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); 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; } } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); ret.syncNextGameFrame = skipCinematic; if ( skipCinematic ) { soundSystem->SetMute( false ); skipCinematic = false; } // show any debug info for this frame RunDebugInfo(); D_DrawDebugLines(); //HUMANHEAD rww if (logitechLCDEnabled) { PROFILE_START("LogitechLCDUpdate", PROFMASK_NORMAL); LogitechLCDUpdate(); PROFILE_STOP("LogitechLCDUpdate", PROFMASK_NORMAL); } //HUMANHEAD END return ret; } // PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge bool hhGameLocal::Draw( int clientNum ) { //HUMANHEAD rww - dedicated server update if (clientNum == -1) { gameSoundWorld->PlaceListener( vec3_origin, mat3_identity, -1, gameLocal.time, "Undefined" ); return false; } //HUMANHEAD END if ( isMultiplayer ) { return mpGame.Draw( clientNum ); } //HUMANHEAD: aob - changed idPlayer to hhPlayer hhPlayer *player = static_cast(entities[ clientNum ]); if ( !player ) { return false; } // render the scene // HUMANHEAD pdm: added case of vehicle determining the player hud if (player->InVehicle()) { player->playerView.RenderPlayerView( player->GetVehicleInterfaceLocal()->GetHUD() ); } else { player->playerView.RenderPlayerView( player->hud ); } // HUMANHEAD END #if INGAME_DEBUGGER_ENABLED // HUMANHEAD pdm debugger.UpdateDebugger(); #endif return true; } float hhGameLocal::GetTimeScale() const { return hhMath::ClampFloat( VECTOR_EPSILON, 10.0f, cvarSystem->GetCVarFloat("timescale") ); } // // SimpleMonstersWithinRadius() // TODO: Use this exclusively and remove MonstersWithinRadius when old AI is removed int hhGameLocal::SimpleMonstersWithinRadius( const idVec3 org, float radius, idAI **monstList, int maxCount ) const { idBounds bo( org ); int entCount = 0; bo.ExpandSelf( radius ); for(int i=0;iGetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { monstList[entCount++] = hhMonsterAI::allSimpleMonsters[i]; } } return entCount; } // // SpawnMapEntities() // void hhGameLocal::SpawnMapEntities( void ) { if ( reactionHandler ) { delete reactionHandler; } reactionHandler = new hhReactionHandler; idGameLocal::SpawnMapEntities(); } /* ============== hhGameLocal::SendMessageAI ============== */ void hhGameLocal::SendMessageAI( const idEntity* entity, const idVec3& origin, float radius, const idEventDef& message ) { idAI *simplemonsters[ MAX_GENTITIES ]; if( radius < VECTOR_EPSILON || isClient ) { return; } for( int i = gameLocal.SimpleMonstersWithinRadius(origin, radius, simplemonsters) - 1; i >= 0; --i ) { if( simplemonsters[i]->GetHealth() > 0 && simplemonsters[i]->IsActive() && !simplemonsters[i]->IsHidden() ) { simplemonsters[i]->ProcessEvent( &message, entity ); } } } /* ============== hhGameLocal::MatterTypeToMatterName ============== */ const char* hhGameLocal::MatterTypeToMatterName( surfTypes_t type ) const { return sufaceTypeNames[ type ]; } /* ============== hhGameLocal::MatterTypeToMatterKey ============== */ const char* hhGameLocal::MatterTypeToMatterKey( const char* prefix, surfTypes_t type ) const { return va( "%s_%s", prefix, MatterTypeToMatterName(type) ); } /* ============== hhGameLocal::MatterNameToMatterType ============== */ surfTypes_t hhGameLocal::MatterNameToMatterType( const char* name ) const { surfTypes_t type = SURFTYPE_NONE; for( int ix = 0; ix < MAX_SURFACE_TYPES; ++ix ) { type = (surfTypes_t)ix; if( idStr::Icmp(name, MatterTypeToMatterName(type)) ) { continue; } return type; } return SURFTYPE_NONE; } /* ============== hhGameLocal::GetMatterType ============== */ surfTypes_t hhGameLocal::GetMatterType( const trace_t& trace, const char* descriptor ) const { return GetMatterType( gameLocal.GetTraceEntity(trace), trace.c.material, descriptor ); } /* ============== hhGameLocal::GetMatterType ============== */ surfTypes_t hhGameLocal::GetMatterType( const idEntity *ent, const idMaterial *material, const char* descriptor ) const { surfTypes_t type = SURFTYPE_NONE; const char *matterName = NULL; const idMaterial *remapped = material; // If the entityDef has a matter specified, always use it // Otherwise, use the materials matter. If none, default to metal. if( ent && ent->spawnArgs.GetString("matter", NULL, &matterName ) ) { type = MatterNameToMatterType( matterName ); } else { if (ent && ent->GetSkin()) { remapped = ent->GetSkin()->RemapShaderBySkin(material); } type = (remapped != NULL) ? remapped->GetSurfaceType() : SURFTYPE_METAL; } // OBS: Will never happen if( !type ) { #if 0 Warning("No matter for hit surface"); Warning(" Entity: %s", ent ? ent->name.c_str() : "none"); Warning(" Class: %s", ent ? ent->GetClassname() : "none"); Warning(" Material: %s", material ? material->GetName() : "none"); #endif type = SURFTYPE_METAL; } if( g_debugMatter.GetInteger() > 0 && descriptor && descriptor[0] ) { Printf("%s: [%s] ent=[%s] mat=[%s]\n", descriptor, MatterTypeToMatterName(type), ent ? ent->GetName() : "none", material ? material->GetName() : "none"); } return type; } void hhGameLocal::AlertAI( idEntity *ent ) { if ( ent && ent->IsType( idActor::Type ) ) { // alert them for the next frame lastAIAlertTime = time + msec; lastAIAlertEntity = static_cast( ent ); lastAIAlertRadius = 0; //no radius required by default } } void hhGameLocal::AlertAI( idEntity *ent, float radius ) { if ( ent && ent->IsType( idActor::Type ) ) { // alert them for the next frame lastAIAlertTime = time + msec; lastAIAlertEntity = static_cast( ent ); lastAIAlertRadius = radius; //radius of effect } } //================ //hhGameLocal::Save //================ void hhGameLocal::Save( idSaveGame *savefile ) const { int i, num = talonTargets.Num(); savefile->WriteInt( num ); for( i = 0; i < num; i++ ) { savefile->WriteObject( talonTargets[i] ); } reactionHandler->Save( savefile ); savefile->WriteClipModel( dwWorldClipModel ); //HUMANHEAD rww #if _HH_INLINED_PROC_CLIPMODELS savefile->WriteInt(inlinedProcClipModels.Num()); for (i = 0; i < inlinedProcClipModels.Num(); i++) { savefile->WriteClipModel(inlinedProcClipModels[i]); } #endif //HUMANHEAD END savefile->WriteVec3( gravityNormal ); savefile->WriteObject( sunCorona ); ddaManager->Save( savefile ); savefile->WriteBool( bIsLOTA ); savefile->WriteFloat( lastAIAlertRadius ); num = staticClipModels.Num(); savefile->WriteInt( num ); for( i = 0; i < num; i++ ) { savefile->WriteClipModel( staticClipModels[i] ); } num = staticRenderEntities.Num(); savefile->WriteInt( num ); for( i = 0; i < num; i++ ) { savefile->WriteRenderEntity( *staticRenderEntities[i] ); } savefile->WriteInt( hands.Num() ); for ( i = 0; i < hands.Num(); i++ ) { hands[i].Save( savefile ); } } //================ //hhGameLocal::Restore //================ void hhGameLocal::Restore( idRestoreGame *savefile ) { idEntity *ent; int i, num; idClipModel *model; renderEntity_t *renderEnt; savefile->ReadInt( num ); talonTargets.SetNum( num ); for( i = 0; i < num; i++ ) { savefile->ReadObject( reinterpret_cast ( ent ) ); talonTargets[i] = ent; } reactionHandler = new hhReactionHandler; reactionHandler->Restore( savefile ); savefile->ReadClipModel( dwWorldClipModel ); //HUMANHEAD rww #if _HH_INLINED_PROC_CLIPMODELS int numInlinedProcClipModels; savefile->ReadInt(numInlinedProcClipModels); for (i = 0; i < numInlinedProcClipModels; i++) { savefile->ReadClipModel(inlinedProcClipModels[i]); } #endif //HUMANHEAD END savefile->ReadVec3( gravityNormal ); savefile->ReadObject( reinterpret_cast ( sunCorona ) ); ddaManager->Restore ( savefile ); savefile->ReadBool( bIsLOTA ); savefile->ReadFloat( lastAIAlertRadius ); savefile->ReadInt( num ); staticClipModels.DeleteContents( false ); staticClipModels.SetNum( num ); for( i = 0; i < num; i++ ) { savefile->ReadClipModel( model ); staticClipModels[i] = model; } savefile->ReadInt( num ); staticRenderEntities.DeleteContents( false ); staticRenderEntities.SetNum( num ); for( i = 0; i < num; i++ ) { renderEnt = new renderEntity_t; savefile->ReadRenderEntity( *renderEnt ); gameRenderWorld->AddEntityDef( renderEnt ); staticRenderEntities[i] = renderEnt; } savefile->ReadInt( num ); hands.SetNum( num ); for ( i = 0; i < num; i++ ) { hands[i].Restore( savefile ); } } bool hhGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { const char *modelName = spawnArgs.GetString( "model" ); int inlineEnt = spawnArgs.GetInt( "inline" ); if( idStr::Icmp( spawnArgs.GetString( "classname", NULL ), "func_static" ) == 0 && // Only deal with func_static objects !spawnArgs.GetBool( "neverInline", "0" ) && // and we're not flagged neverInline ( inlineEnt != 0 || // and we're flagged explicit inline world->spawnArgs.GetBool( "inlineAllStatics" ) ) ) { // or we're inlining all statics #if _HH_INLINED_PROC_CLIPMODELS if (inlineEnt != 3) { //HUMANHEAD rww #endif // Handle explicit clip models idClipModel *model = NULL; const char *temp = spawnArgs.GetString( "clipmodel", NULL ); if( temp ) { if ( idClipModel::CheckModel( temp ) ) { model = new idClipModel( temp ); } } if( model || !spawnArgs.GetBool( "noclipmodel", "0" ) ) { // Get the origin and axis of the object idMat3 axis; // get the rotation matrix in either full form, or single angle form if ( !spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { float angle = spawnArgs.GetFloat( "angle" ); if ( angle != 0.0f ) { axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); } else { axis.Identity(); } } idVec3 origin = spawnArgs.GetVector( "origin" ); // Create a clip model for the static object and position it correctly if( !model ) { model = new idClipModel( spawnArgs.GetString( "model" ) ); } if( spawnArgs.GetBool( "bulletsOnly", "0" ) ) { model->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); } else if( spawnArgs.GetBool( "solid", "1" ) ) { model->SetContents( CONTENTS_SOLID ); } else { model->SetContents( 0 ); } model->SetPosition( origin, axis ); model->SetEntity( world ); model->Link( clip ); staticClipModels.Append( model ); } #if _HH_INLINED_PROC_CLIPMODELS } #endif if ( inlineEnt == 1 ) { // This means we must hand the model, too renderEntity_t *renderEnt = new renderEntity_t; gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, renderEnt ); HH_ASSERT( renderEnt->hModel && !renderEnt->callback && renderEnt->shaderParms[ SHADERPARM_ANY_DEFORM ] == DEFORMTYPE_NONE ); // Inlined statics can't have a callback renderEnt->entityNum = 0; // WorldSpawn gameRenderWorld->AddEntityDef( renderEnt ); staticRenderEntities.Append( renderEnt ); } // Don't spawn an entity for an inlined static return true; } else { return idGameLocal::InhibitEntitySpawn( spawnArgs ); } } //HUMANHEAD rww #if _HH_INLINED_PROC_CLIPMODELS void hhGameLocal::CreateInlinedProcClip(idEntity *clipOwner) { assert(world); assert(inlinedProcClipModels.Num() == 0); //should never have existing models when this is called int n = collisionModelManager->GetNumInlinedProcClipModels(); if (n <= 0) { //nothing to work with, leave return; } for (int i = 0; i < n; i++) { idClipModel *chunk = new idClipModel(va("%s%i", PROC_CLIPMODEL_STRING_PRFX, i)); chunk->SetContents(CONTENTS_SOLID); chunk->SetEntity(clipOwner); chunk->Link(clip); inlinedProcClipModels.Append(chunk); } } #endif //HUMANHEAD END // Finds entities matching a specified type. If last is NULL it starts at the beginning of the list. // If last is valid, starts looking at that entities position in the list idEntity *hhGameLocal::FindEntityOfType(const idTypeInfo &type, idEntity *last) { idEntity *ent; if (last) { ent = last->activeNode.Next(); } else { ent = activeEntities.Next(); } for( ; ent != NULL; ent = ent->activeNode.Next() ) { if (ent->IsType(type)) { return ent; } } return NULL; } float hhGameLocal::GetDDAValue( void ) { if ( !ddaManager ) { return 0.5; } else { return ddaManager->GetDifficulty(); } } void hhGameLocal::ClearStaticData( void ) { delete dwWorldClipModel; //rww - this needs to be done as well, dw clipmodel expects world as owner. dwWorldClipModel = NULL; #if _HH_INLINED_PROC_CLIPMODELS inlinedProcClipModels.DeleteContents(true); //HUMANHEAD rww #endif staticClipModels.DeleteContents( true ); // Clear any inlined static clip models staticRenderEntities.DeleteContents( true ); // Clear any inlined static render entities } float hhGameLocal::TimeBasedRandomFloat(void) { if (gameLocal.isMultiplayer) { //rand based on time step timeRandom = (1103515245 * timeRandom + 12345)%(1<<31); return (timeRandom)/( float )( (1<<31) + 1 ); } else { //give sp complete randomness return random.RandomFloat(); } } bool hhGameLocal::PlayerIsDeathwalking(void) { HH_ASSERT( !isMultiplayer ); hhPlayer *player = static_cast (GetLocalPlayer()); HH_ASSERT( player ); return player->IsDeathWalking(); } // Abstraction to hide the ugly code for text exchange between engine and game void hhGameLocal::GetTip(const char *binding, idStr &keyMaterialString, idStr &keyString, bool &isWide) { char keyMaterial[256]; // No passing idStr between game and engine char key[256]; // No passing idStr between game and engine keyMaterial[0] = '\0'; key[0] = '\0'; if (binding) { common->MaterialKeyForBinding(binding, keyMaterial, key, &isWide); keyMaterialString = keyMaterial; keyString = key; } } /* hhGameLocal::SetTip gui gui to display tip binding key binding to display associated key tip textual tip to display top optional top image mtr optional material, overrides binding image prefix optional prefix for gui state keys */ bool hhGameLocal::SetTip(idUserInterface* gui, const char *binding, const char *tip, const char *topMaterial, const char *overrideMaterial, const char *prefix) { assert(gui); idStr keyMaterial, key; bool keywide = false; if ( !spawnArgs.GetString("mtr_override", "", keyMaterial) ) { if (binding) { GetTip(binding, keyMaterial, key, keywide); } } const char *translated = common->GetLanguageDict()->GetString(tip); if (prefix != NULL) { gui->SetStateBool( va("%s_keywide", prefix), keywide ); gui->SetStateString( va("%s_tip", prefix), translated ? translated : "" ); gui->SetStateString( va("%s_key", prefix), key.c_str() ); gui->SetStateString( va("%s_keyMaterial", prefix), keyMaterial.c_str() ); gui->SetStateString( va("%s_topMaterial", prefix), topMaterial ? topMaterial : "" ); } else { gui->SetStateBool( "keywide", keywide ); gui->SetStateString( "tip", translated ? translated : "" ); gui->SetStateString( "key", key.c_str() ); gui->SetStateString( "keyMaterial", keyMaterial.c_str() ); gui->SetStateString( "topMaterial", topMaterial ? topMaterial : "" ); } return (keyMaterial.Length() > 0); }