diff --git a/neo/d3xp/Camera.cpp b/neo/d3xp/Camera.cpp index dedba94fc..d15776adf 100644 --- a/neo/d3xp/Camera.cpp +++ b/neo/d3xp/Camera.cpp @@ -514,7 +514,7 @@ void idCameraAnim::Think( void ) { return; } - if ( frameRate == USERCMD_HZ ) { + if ( frameRate == gameLocal.gameFps ) { frameTime = gameLocal.time - starttime; frame = frameTime / gameLocal.msec; } else { @@ -568,7 +568,7 @@ void idCameraAnim::GetViewParms( renderView_t *view ) { SetTimeState ts( timeGroup ); #endif - if ( frameRate == USERCMD_HZ ) { + if ( frameRate == gameLocal.gameFps ) { frameTime = gameLocal.time - starttime; frame = frameTime / gameLocal.msec; lerp = 0.0f; diff --git a/neo/d3xp/Game_local.cpp b/neo/d3xp/Game_local.cpp index dc0903727..7fab32508 100644 --- a/neo/d3xp/Game_local.cpp +++ b/neo/d3xp/Game_local.cpp @@ -248,6 +248,7 @@ void idGameLocal::Clear( void ) { framenum = 0; previousTime = 0; time = 0; + preciseTime = 0.0f; vacuumAreaNum = 0; mapFileName.Clear(); mapFile = NULL; @@ -309,6 +310,10 @@ void idGameLocal::Init( void ) { const idDict *dict; idAAS *aas; + msec = 16.0; //60fps + gameMsec = msec; + gameFps = 60; //60fps + #ifndef GAME_DLL TestGameAPI(); @@ -326,6 +331,12 @@ void idGameLocal::Init( void ) { #endif + //Update MSEC and gameFps + gameFps = cvarSystem->GetCVarInteger("com_gameHz"); + msec = 1000.0f / cvarSystem->GetCVarFloat("com_gameHz"); + msec *= 0.96f*0.96f; //HACK to emulate OG D3 msec error, in order to have exactly the same game logic speed + gameMsec = msec; + Printf( "----- Initializing Game -----\n" ); Printf( "gamename: %s\n", GAME_VERSION ); Printf( "gamedate: %s\n", __DATE__ ); @@ -594,12 +605,14 @@ void idGameLocal::SaveGame( idFile *f ) { savegame.WriteBool( isMultiplayer ); savegame.WriteInt( gameType ); + savegame.WriteFloat(preciseTime); + savegame.WriteInt( framenum ); savegame.WriteInt( previousTime ); savegame.WriteInt( time ); #ifdef _D3XP - savegame.WriteInt( msec ); + savegame.WriteFloat( msec ); #endif savegame.WriteInt( vacuumAreaNum ); @@ -1000,6 +1013,7 @@ void idGameLocal::LoadMap( const char *mapName, int randseed ) { previousTime = 0; time = 0; + preciseTime = 0.0f; framenum = 0; sessionCommand = ""; nextGibTime = 0; @@ -1473,12 +1487,14 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo savegame.ReadBool( isMultiplayer ); savegame.ReadInt( (int &)gameType ); + savegame.ReadFloat(preciseTime); + savegame.ReadInt( framenum ); savegame.ReadInt( previousTime ); savegame.ReadInt( time ); #ifdef _D3XP - savegame.ReadInt( msec ); + savegame.ReadFloat( msec ); #endif savegame.ReadInt( vacuumAreaNum ); @@ -1520,7 +1536,7 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo } } if ( gameSoundWorld ) { - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)gameMsec ); } #endif @@ -2431,7 +2447,8 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { // update the game time framenum++; previousTime = time; - time += msec; + preciseTime += msec; + time = (int)idMath::Rint(preciseTime); realClientTime = time; #ifdef _D3XP @@ -3812,7 +3829,7 @@ idGameLocal::AlertAI void idGameLocal::AlertAI( idEntity *ent ) { if ( ent && ent->IsType( idActor::Type ) ) { // alert them for the next frame - lastAIAlertTime = time + msec; + lastAIAlertTime = time + (int)idMath::Rint(msec); lastAIAlertEntity = static_cast( ent ); } } @@ -4225,7 +4242,7 @@ void idGameLocal::SetCamera( idCamera *cam ) { } else { inCinematic = false; - cinematicStopTime = time + msec; + cinematicStopTime = time + idMath::Rint(msec); // restore r_znear cvarSystem->SetCVarFloat( "r_znear", 3.0f ); @@ -4821,7 +4838,7 @@ void idGameLocal::ComputeSlowMsec() { // stop the state slowmoState = SLOWMO_STATE_OFF; - slowmoMsec = USERCMD_MSEC; + slowmoMsec = gameMsec; } // check the player state @@ -4842,7 +4859,7 @@ void idGameLocal::ComputeSlowMsec() { slowmoMsec = msec; if ( gameSoundWorld ) { gameSoundWorld->SetSlowmo( true ); - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / gameMsec ); } } else if ( !powerupOn && slowmoState == SLOWMO_STATE_ON ) { @@ -4856,10 +4873,10 @@ void idGameLocal::ComputeSlowMsec() { // do any necessary ramping if ( slowmoState == SLOWMO_STATE_RAMPUP ) { - delta = 4 - slowmoMsec; + delta = 4.0 * 60.0 / (float)gameFps - slowmoMsec; if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) { - slowmoMsec = 4; + slowmoMsec = 4.0 * 60.0 / (float)gameFps; slowmoState = SLOWMO_STATE_ON; } else { @@ -4867,14 +4884,14 @@ void idGameLocal::ComputeSlowMsec() { } if ( gameSoundWorld ) { - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / gameMsec ); } } else if ( slowmoState == SLOWMO_STATE_RAMPDOWN ) { - delta = 16 - slowmoMsec; + delta = 16.0 * 60.0 / gameFps - slowmoMsec; if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) { - slowmoMsec = 16; + slowmoMsec = 16.0*60.0/(float)gameFps; slowmoState = SLOWMO_STATE_OFF; if ( gameSoundWorld ) { gameSoundWorld->SetSlowmo( false ); @@ -4885,7 +4902,7 @@ void idGameLocal::ComputeSlowMsec() { } if ( gameSoundWorld ) { - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / gameMsec ); } } } @@ -4896,19 +4913,19 @@ idGameLocal::ResetSlowTimeVars ============ */ void idGameLocal::ResetSlowTimeVars() { - msec = USERCMD_MSEC; - slowmoMsec = USERCMD_MSEC; + msec = gameMsec; + slowmoMsec = gameMsec; slowmoState = SLOWMO_STATE_OFF; fast.framenum = 0; fast.previousTime = 0; fast.time = 0; - fast.msec = USERCMD_MSEC; + fast.msec = gameMsec; slow.framenum = 0; slow.previousTime = 0; slow.time = 0; - slow.msec = USERCMD_MSEC; + slow.msec = gameMsec; } /* @@ -4985,4 +5002,4 @@ void idGameLocal::SwitchTeam( int clientNum, int team ) { idGameLocal::GetMapLoadingGUI =============== */ -void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { } +void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { } \ No newline at end of file diff --git a/neo/d3xp/Game_local.h b/neo/d3xp/Game_local.h index cb229cd99..620fc290e 100644 --- a/neo/d3xp/Game_local.h +++ b/neo/d3xp/Game_local.h @@ -225,14 +225,14 @@ class idEntityPtr { struct timeState_t { int time; int previousTime; - int msec; + float msec; int framenum; int realClientTime; - void Set( int t, int pt, int ms, int f, int rct ) { time = t; previousTime = pt; msec = ms; framenum = f; realClientTime = rct; }; - void Get( int& t, int& pt, int& ms, int& f, int& rct ) { t = time; pt = previousTime; ms = msec; f = framenum; rct = realClientTime; }; - void Save( idSaveGame *savefile ) const { savefile->WriteInt( time ); savefile->WriteInt( previousTime ); savefile->WriteInt( msec ); savefile->WriteInt( framenum ); savefile->WriteInt( realClientTime ); } - void Restore( idRestoreGame *savefile ) { savefile->ReadInt( time ); savefile->ReadInt( previousTime ); savefile->ReadInt( msec ); savefile->ReadInt( framenum ); savefile->ReadInt( realClientTime ); } + void Set( int t, int pt, float ms, int f, int rct ) { time = t; previousTime = pt; msec = ms; framenum = f; realClientTime = rct; }; + void Get( int& t, int& pt, float& ms, int& f, int& rct ) { t = time; pt = previousTime; ms = msec; f = framenum; rct = realClientTime; }; + void Save( idSaveGame *savefile ) const { savefile->WriteInt( time ); savefile->WriteInt( previousTime ); savefile->WriteFloat( msec ); savefile->WriteInt( framenum ); savefile->WriteInt( realClientTime ); } + void Restore( idRestoreGame *savefile ) { savefile->ReadInt( time ); savefile->ReadInt( previousTime ); savefile->ReadFloat( msec ); savefile->ReadInt( framenum ); savefile->ReadInt( realClientTime ); } void Increment() { framenum++; previousTime = time; time += msec; realClientTime = time; }; }; @@ -298,7 +298,10 @@ class idGameLocal : public idGame { int framenum; int previousTime; // time in msec of last frame int time; // in msec - int msec; // time since last update in milliseconds + float msec; // time since last update in milliseconds + float preciseTime; // added by Stradex for cm_gameHz fidelity + int gameFps; //added by Stradex for com_gameHz + float gameMsec; //added by Stradex for com_gameHz (ROE) int vacuumAreaNum; // -1 if level doesn't have any outside areas @@ -483,7 +486,7 @@ class idGameLocal : public idGame { // added the following to assist licensees with merge issues int GetFrameNum() const { return framenum; }; int GetTime() const { return time; }; - int GetMSec() const { return msec; }; + int GetMSec() const { return (int)idMath::Rint(msec); }; int GetNextClientNum( int current ) const; idPlayer * GetClientByNum( int current ) const; diff --git a/neo/d3xp/Game_network.cpp b/neo/d3xp/Game_network.cpp index 36d799eee..3b180af89 100644 --- a/neo/d3xp/Game_network.cpp +++ b/neo/d3xp/Game_network.cpp @@ -1008,7 +1008,7 @@ void idGameLocal::ClientReadSnapshot( int clientNum, int sequence, const int gam // update the game time framenum = gameFrame; time = gameTime; - previousTime = time - msec; + previousTime = time - idMath::Rint(msec); // so that StartSound/StopSound doesn't risk skipping isNewFrame = true; @@ -1532,7 +1532,8 @@ gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clie // update the game time framenum++; previousTime = time; - time += msec; + preciseTime += msec; + time = (int)idMath::Rint(preciseTime); // update the real client time and the new frame flag if ( time > realClientTime ) { diff --git a/neo/d3xp/Moveable.cpp b/neo/d3xp/Moveable.cpp index 0ba26a851..0efc11edf 100644 --- a/neo/d3xp/Moveable.cpp +++ b/neo/d3xp/Moveable.cpp @@ -460,14 +460,14 @@ bool idMoveable::FollowInitialSplinePath( void ) { if ( initialSpline != NULL ) { if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); - idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * USERCMD_HZ; + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * gameLocal.gameFps; physicsObj.SetLinearVelocity( linearVelocity ); idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); idVec3 dir = initialSplineDir * physicsObj.GetAxis(); idVec3 angularVelocity = dir.Cross( splineDir ); angularVelocity.Normalize(); - angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * USERCMD_HZ; + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * gameLocal.gameFps; physicsObj.SetAngularVelocity( angularVelocity ); return true; } else { diff --git a/neo/d3xp/Mover.cpp b/neo/d3xp/Mover.cpp index 4e4160fb1..4c327f8bf 100644 --- a/neo/d3xp/Mover.cpp +++ b/neo/d3xp/Mover.cpp @@ -2838,9 +2838,9 @@ void idMover_Binary::Use_BinaryMover( idEntity *activator ) { activatedBy = activator; if ( moverState == MOVER_POS1 ) { - // FIXME: start moving USERCMD_MSEC later, because if this was player + // FIXME: start moving gameLocal.msec later, because if this was player // triggered, gameLocal.time hasn't been advanced yet - MatchActivateTeam( MOVER_1TO2, gameLocal.slow.time + USERCMD_MSEC ); + MatchActivateTeam( MOVER_1TO2, gameLocal.slow.time + gameLocal.gameMsec ); SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); // open areaportal diff --git a/neo/d3xp/PlayerView.cpp b/neo/d3xp/PlayerView.cpp index 750e20d07..01d47c3e1 100644 --- a/neo/d3xp/PlayerView.cpp +++ b/neo/d3xp/PlayerView.cpp @@ -308,7 +308,7 @@ void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef ) if ( blobTime ) { screenBlob_t* blob = GetScreenBlob(); blob->startFadeTime = gameLocal.slow.time; - blob->finishTime = gameLocal.slow.time + blobTime * g_blobTime.GetFloat() * ( ( float )gameLocal.msec / USERCMD_MSEC ); + blob->finishTime = gameLocal.slow.time + blobTime * g_blobTime.GetFloat() * ( ( float )gameLocal.msec / gameLocal.gameMsec ); const char* materialName = damageDef->GetString( "mtr_blob" ); blob->material = declManager->FindMaterial( materialName ); diff --git a/neo/d3xp/Projectile.cpp b/neo/d3xp/Projectile.cpp index 51023b6de..59c6fe949 100644 --- a/neo/d3xp/Projectile.cpp +++ b/neo/d3xp/Projectile.cpp @@ -1610,7 +1610,7 @@ void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const i angles = vel.ToAngles(); speed = vel.Length(); rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); - turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / ( float )USERCMD_HZ; + turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.gameFps; clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); burstMode = spawnArgs.GetBool( "burstMode" ); unGuided = false; diff --git a/neo/d3xp/SmokeParticles.cpp b/neo/d3xp/SmokeParticles.cpp index 710baecf2..03e0f20f0 100644 --- a/neo/d3xp/SmokeParticles.cpp +++ b/neo/d3xp/SmokeParticles.cpp @@ -236,7 +236,7 @@ bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemS if ( nowCount >= stage->totalParticles ) { nowCount = stage->totalParticles-1; } - prevCount = floor( ((float)( deltaMsec - gameLocal.msec /*_D3XP - FIX - was USERCMD_MSEC*/ ) / finalParticleTime) * stage->totalParticles ); + prevCount = floor( ((float)( deltaMsec - (int)idMath::Rint(gameLocal.msec) /*_D3XP - FIX - was gameLocal.msec*/ ) / finalParticleTime) * stage->totalParticles ); if ( prevCount < -1 ) { prevCount = -1; } diff --git a/neo/d3xp/physics/Force_Drag.cpp b/neo/d3xp/physics/Force_Drag.cpp index f8682a11e..4b36356e5 100644 --- a/neo/d3xp/physics/Force_Drag.cpp +++ b/neo/d3xp/physics/Force_Drag.cpp @@ -29,6 +29,7 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" #include "framework/UsercmdGen.h" +#include "Game_local.h" #include "physics/Physics.h" #include "physics/Force_Drag.h" @@ -139,9 +140,9 @@ void idForce_Drag::Evaluate( int time ) { l2 = dir2.Normalize(); rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); - physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( USERCMD_MSEC ), id ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( gameLocal.gameMsec ), id ); - velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( USERCMD_MSEC ) ); + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.gameMsec ) ); physics->SetLinearVelocity( velocity, id ); } diff --git a/neo/d3xp/physics/Physics.cpp b/neo/d3xp/physics/Physics.cpp index 86e35d464..ef1ad1d86 100644 --- a/neo/d3xp/physics/Physics.cpp +++ b/neo/d3xp/physics/Physics.cpp @@ -75,6 +75,6 @@ idPhysics::SnapTimeToPhysicsFrame */ int idPhysics::SnapTimeToPhysicsFrame( int t ) { int s; - s = t + USERCMD_MSEC - 1; - return ( s - s % USERCMD_MSEC ); + s = t + (int)idMath::Rint(gameLocal.gameMsec) - 1; + return ( s - s % (int)idMath::Rint(gameLocal.gameMsec) ); } diff --git a/neo/d3xp/physics/Physics_AF.cpp b/neo/d3xp/physics/Physics_AF.cpp index 098a4bfc7..c2fef5575 100644 --- a/neo/d3xp/physics/Physics_AF.cpp +++ b/neo/d3xp/physics/Physics_AF.cpp @@ -6608,7 +6608,7 @@ idPhysics_AF::idPhysics_AF( void ) { memset( ¤t, 0, sizeof( current ) ); current.atRest = -1; - current.lastTimeStep = USERCMD_MSEC; + current.lastTimeStep = gameLocal.gameMsec; saved = current; linearFriction = 0.005f; diff --git a/neo/d3xp/physics/Physics_RigidBody.cpp b/neo/d3xp/physics/Physics_RigidBody.cpp index 838065699..503ba456c 100644 --- a/neo/d3xp/physics/Physics_RigidBody.cpp +++ b/neo/d3xp/physics/Physics_RigidBody.cpp @@ -447,7 +447,7 @@ idPhysics_RigidBody::idPhysics_RigidBody( void ) { memset( ¤t, 0, sizeof( current ) ); current.atRest = -1; - current.lastTimeStep = USERCMD_MSEC; + current.lastTimeStep = gameLocal.gameMsec; current.i.position.Zero(); current.i.orientation.Identity(); diff --git a/neo/d3xp/script/Script_Thread.cpp b/neo/d3xp/script/Script_Thread.cpp index cb8263310..2b972314b 100644 --- a/neo/d3xp/script/Script_Thread.cpp +++ b/neo/d3xp/script/Script_Thread.cpp @@ -691,7 +691,7 @@ bool idThread::Execute( void ) { if ( waitingUntil > lastExecuteTime ) { PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); } else if ( interpreter.MultiFrameEventInProgress() ) { - PostEventMS( &EV_Thread_Execute, gameLocal.msec ); + PostEventMS( &EV_Thread_Execute, idMath::Rint(gameLocal.msec) ); } } @@ -932,7 +932,7 @@ void idThread::WaitFrame( void ) { // manual control threads don't set waitingUntil so that they can be run again // that frame if necessary. if ( !manualControl ) { - waitingUntil = gameLocal.time + gameLocal.msec; + waitingUntil = gameLocal.time + idMath::Rint(gameLocal.msec); } } @@ -1849,7 +1849,7 @@ idThread::Event_GetTicsPerSecond ================ */ void idThread::Event_GetTicsPerSecond( void ) { - idThread::ReturnFloat( USERCMD_HZ ); + idThread::ReturnFloat( gameLocal.gameFps ); } /* diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index 37648c907..62b9b9c4f 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -104,6 +104,15 @@ idCVar com_updateLoadSize( "com_updateLoadSize", "0", CVAR_BOOL | CVAR_SYSTEM | idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "Extension to use when creating language files." ); + +//Stradex: start +idCVar com_gameHz("com_gameHz", "60", CVAR_INTEGER | CVAR_ARCHIVE | CVAR_SYSTEM, "Frames per second the game runs at", 10, 1024); + +float com_gameMSRate = 1000.0f / 60.0f; //Refreshed later (changed to float to improve precision) +int com_realGameHz = 60; +bool tReloadingEngine = false; +//Stradex: end + // com_speeds times int time_gameFrame; int time_gameDraw; @@ -181,6 +190,8 @@ class idCommonLocal : public idCommon { // NOTE: this doesn't do anything yet, but allows to add ugly mod-specific hacks without breaking the Game interface virtual bool GetAdditionalFunction(idCommon::FunctionType ft, idCommon::FunctionPointer* out_fnptr, void** out_userArg); + virtual float Get_com_gameMSRate(void); + // DG end void InitGame( void ); @@ -1516,14 +1527,24 @@ void Com_ReloadEngine_f( const idCmdArgs &args ) { } common->Printf( "============= ReloadEngine start =============\n" ); + + tReloadingEngine = true; + if ( !menu ) { Sys_ShowConsole( 1, false ); } + + + com_realGameHz = com_gameHz.GetInteger(); + commonLocal.ShutdownGame( true ); commonLocal.InitGame(); if ( !menu && !idAsyncNetwork::serverDedicated.GetBool() ) { Sys_ShowConsole( 0, false ); } + + tReloadingEngine = false; + common->Printf( "============= ReloadEngine end ===============\n" ); if ( !cmdSystem->PostReloadEngine() ) { @@ -2404,7 +2425,7 @@ void idCommonLocal::Frame( void ) { eventLoop->RunEventLoop(); - com_frameTime = com_ticNumber * USERCMD_MSEC; + com_frameTime = FRAME_TO_MSEC(com_ticNumber); idAsyncNetwork::RunFrame(); @@ -2450,7 +2471,7 @@ idCommonLocal::GUIFrame void idCommonLocal::GUIFrame( bool execCmd, bool network ) { Sys_GenerateEvents(); eventLoop->RunEventLoop( execCmd ); // and execute any commands - com_frameTime = com_ticNumber * USERCMD_MSEC; + com_frameTime = FRAME_TO_MSEC(com_ticNumber); if ( network ) { idAsyncNetwork::RunFrame(); } @@ -2488,7 +2509,7 @@ typedef struct { static const int MAX_ASYNC_STATS = 1024; asyncStats_t com_asyncStats[MAX_ASYNC_STATS]; // indexed by com_ticNumber int prevAsyncMsec; -int lastTicMsec; +float lastTicMsec; //float for better precision with framerate void idCommonLocal::SingleAsyncTic( void ) { // main thread code can prevent this from happening while modifying @@ -2528,9 +2549,9 @@ idCommonLocal::Async ================= */ void idCommonLocal::Async( void ) { - int msec = Sys_Milliseconds(); + float msec = static_cast(Sys_Milliseconds()); if ( !lastTicMsec ) { - lastTicMsec = msec - USERCMD_MSEC; + lastTicMsec = msec - com_gameMSRate; } if ( !com_preciseTic.GetBool() ) { @@ -2539,21 +2560,21 @@ void idCommonLocal::Async( void ) { return; } - int ticMsec = USERCMD_MSEC; + float ticMsec = com_gameMSRate; // the number of msec per tic can be varies with the timescale cvar float timescale = com_timescale.GetFloat(); if ( timescale != 1.0f ) { ticMsec /= timescale; - if ( ticMsec < 1 ) { - ticMsec = 1; + if ( ticMsec < 1.0 ) { + ticMsec = 1.0; } } // don't skip too many if ( timescale == 1.0f ) { - if ( lastTicMsec + 10 * USERCMD_MSEC < msec ) { - lastTicMsec = msec - 10*USERCMD_MSEC; + if ( lastTicMsec + 10.0 * com_gameMSRate < msec ) { + lastTicMsec = msec - 10.0* com_gameMSRate; } } @@ -2753,7 +2774,7 @@ static unsigned int AsyncTimer(unsigned int interval, void *) { // calculate the next interval to get as close to 60fps as possible unsigned int now = SDL_GetTicks(); - unsigned int tick = com_ticNumber * USERCMD_MSEC; + unsigned int tick = FRAME_TO_MSEC(com_ticNumber); if (now >= tick) return 1; @@ -2940,6 +2961,12 @@ void idCommonLocal::Init( int argc, char **argv ) { // game specific initialization InitGame(); + //stradex: start + com_realGameHz = com_gameHz.GetInteger(); + com_gameMSRate = 1000.0f / static_cast(com_gameHz.GetInteger()); + //com_gameMSRate *= 0.96f; //HACK: force OG D3 msec error to ensure the games feels exactly the same in terms of speed. + //stradex: end + // don't add startup commands if no CD key is present #if ID_ENFORCE_KEY if ( !session->CDKeysAreValid( false ) || !AddStartupCommands() ) { @@ -2972,7 +2999,7 @@ void idCommonLocal::Init( int argc, char **argv ) { Sys_Error( "Error during initialization" ); } - async_timer = SDL_AddTimer(USERCMD_MSEC, AsyncTimer, NULL); + async_timer = SDL_AddTimer((int)idMath::Floor(com_gameMSRate), AsyncTimer, NULL); if (!async_timer) Sys_Error("Error while starting the async timer: %s", SDL_GetError()); @@ -3106,6 +3133,17 @@ void idCommonLocal::InitGame( void ) { // if any archived cvars are modified after this, we will trigger a writing of the config file cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); +#ifndef ID_DEDICATED + if (tReloadingEngine) { + com_gameHz.SetInteger(com_realGameHz); + com_gameMSRate = 1000.0f / static_cast(com_gameHz.GetInteger()); + //com_gameMSRate *= 0.96f; //HACK: force OG D3 msec error to ensure the games feels exactly the same in terms of speed. + //reset time and tics + com_frameNumber = 0; + com_ticNumber = 0; + } +#endif + // init the user command input code usercmdGen->Init(); @@ -3272,6 +3310,11 @@ bool idCommonLocal::GetAdditionalFunction(idCommon::FunctionType ft, idCommon::F } } +float idCommonLocal::Get_com_gameMSRate() { + return com_gameMSRate; +} + + idGameCallbacks gameCallbacks; diff --git a/neo/framework/Common.h b/neo/framework/Common.h index c0400bb47..a0fe3ce71 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -74,6 +74,10 @@ extern idCVar com_showSoundDecoders; extern idCVar com_makingBuild; extern idCVar com_updateLoadSize; +extern idCVar com_gameHz; +extern float com_gameMSRate; +extern int com_realGameHz; + extern int time_gameFrame; // game logic time extern int time_gameDraw; // game present time extern int time_frontend; // renderer frontend time @@ -271,8 +275,32 @@ class idCommon { // *out_fnptr will be the function (you'll have to cast it probably) // *out_userArg will be an argument you have to pass to the function, if appropriate (else NULL) virtual bool GetAdditionalFunction(FunctionType ft, FunctionPointer* out_fnptr, void** out_userArg) = 0; + + virtual float Get_com_gameMSRate(void) = 0; }; extern idCommon * common; +// Returns the msec the frame starts on +ID_INLINE int FRAME_TO_MSEC(int frame) +{ + return (int)idMath::Rint(static_cast(frame) * common->Get_com_gameMSRate()); +} +// Rounds DOWN to the nearest frame +ID_INLINE int MSEC_TO_FRAME_FLOOR(int msec) +{ + return (int)idMath::Floor(static_cast(msec) / common->Get_com_gameMSRate()); +} +// Rounds UP to the nearest frame +ID_INLINE int MSEC_TO_FRAME_CEIL(int msec) +{ + return (int)idMath::Ceil(static_cast(msec) / common->Get_com_gameMSRate()); +} +// Aligns msec so it starts on a frame bondary +ID_INLINE int MSEC_ALIGN_TO_FRAME(int msec) +{ + return FRAME_TO_MSEC(MSEC_TO_FRAME_CEIL(msec)); +} + + #endif /* !__COMMON_H__ */ diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index 73585a2eb..5179054f2 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -497,7 +497,7 @@ void idSessionLocal::StartWipe( const char *_wipeMaterial, bool hold ) { wipeMaterial = declManager->FindMaterial( _wipeMaterial, false ); wipeStartTic = com_ticNumber; - wipeStopTic = wipeStartTic + 1000.0f / USERCMD_MSEC * com_wipeSeconds.GetFloat(); + wipeStopTic = wipeStartTic + 1000.0f / com_gameMSRate * com_wipeSeconds.GetFloat(); wipeHold = hold; } @@ -538,14 +538,14 @@ void idSessionLocal::ShowLoadingGui() { int stop = Sys_Milliseconds() + 1000; int force = 10; while ( Sys_Milliseconds() < stop || force-- > 0 ) { - com_frameTime = com_ticNumber * USERCMD_MSEC; + com_frameTime = FRAME_TO_MSEC(com_ticNumber); session->Frame(); session->UpdateScreen( false ); } #else - int stop = com_ticNumber + 1000.0f / USERCMD_MSEC * 1.0f; + int stop = com_ticNumber + 1000.0f / com_gameMSRate * 1.0f; while ( com_ticNumber < stop ) { - com_frameTime = com_ticNumber * USERCMD_MSEC; + com_frameTime = com_ticNumber * com_gameMSRate; session->Frame(); session->UpdateScreen( false ); } @@ -2545,7 +2545,7 @@ void idSessionLocal::Frame() { name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviTicStart ); - float ratio = 30.0f / ( 1000.0f / USERCMD_MSEC / com_aviDemoTics.GetInteger() ); + float ratio = 30.0f / ( 1000.0f / com_gameMSRate / com_aviDemoTics.GetInteger() ); aviDemoFrameCount += ratio; if ( aviTicStart + 1 != ( int )aviDemoFrameCount ) { // skipped frames so write them out @@ -2669,7 +2669,7 @@ void idSessionLocal::Frame() { // don't let a long onDemand sound load unsync everything if ( timeHitch ) { - int skip = timeHitch / USERCMD_MSEC; + int skip = timeHitch / (int)idMath::Rint(com_gameMSRate); lastGameTic += skip; numCmdsToRun -= skip; timeHitch = 0; diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index f1b213f7b..57bc68cd6 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -532,9 +532,9 @@ void idUsercmdGenLocal::AdjustAngles( void ) { float speed; if ( toggled_run.on ^ ( in_alwaysRun.GetBool() && idAsyncNetwork::IsActive() ) ) { - speed = idMath::M_MS2SEC * USERCMD_MSEC * in_angleSpeedKey.GetFloat(); + speed = idMath::M_MS2SEC * com_gameMSRate * in_angleSpeedKey.GetFloat(); } else { - speed = idMath::M_MS2SEC * USERCMD_MSEC; + speed = idMath::M_MS2SEC * com_gameMSRate; } if ( !ButtonState( UB_STRAFE ) ) { @@ -681,9 +681,9 @@ void idUsercmdGenLocal::JoystickMove( void ) { float anglespeed; if ( toggled_run.on ^ ( in_alwaysRun.GetBool() && idAsyncNetwork::IsActive() ) ) { - anglespeed = idMath::M_MS2SEC * USERCMD_MSEC * in_angleSpeedKey.GetFloat(); + anglespeed = idMath::M_MS2SEC * com_gameMSRate * in_angleSpeedKey.GetFloat(); } else { - anglespeed = idMath::M_MS2SEC * USERCMD_MSEC; + anglespeed = idMath::M_MS2SEC * com_gameMSRate; } if ( !ButtonState( UB_STRAFE ) ) { diff --git a/neo/framework/UsercmdGen.h b/neo/framework/UsercmdGen.h index 17e88fe8f..656fb7df5 100644 --- a/neo/framework/UsercmdGen.h +++ b/neo/framework/UsercmdGen.h @@ -37,9 +37,6 @@ If you have questions concerning this license or the applicable additional terms =============================================================================== */ -const int USERCMD_HZ = 60; // 60 frames per second -const int USERCMD_MSEC = 1000 / USERCMD_HZ; - // usercmd_t->button bits const int BUTTON_ATTACK = BIT(0); const int BUTTON_RUN = BIT(1); diff --git a/neo/framework/async/AsyncClient.cpp b/neo/framework/async/AsyncClient.cpp index 306c458f5..3ea492059 100644 --- a/neo/framework/async/AsyncClient.cpp +++ b/neo/framework/async/AsyncClient.cpp @@ -78,7 +78,7 @@ void idAsyncClient::Clear( void ) { snapshotSequence = 0; gameInitId = GAME_INIT_ID_INVALID; gameFrame = 0; - gameTimeResidual = 0; + gameTimeResidual = 0.0f; gameTime = 0; memset( userCmds, 0, sizeof( userCmds ) ); backgroundDownload.completed = true; @@ -718,7 +718,7 @@ void idAsyncClient::InitGame( int serverGameInitId, int serverGameFrame, int ser gameInitId = serverGameInitId; gameFrame = snapshotGameFrame = serverGameFrame; gameTime = snapshotGameTime = serverGameTime; - gameTimeResidual = 0; + gameTimeResidual = 0.0f; memset( userCmds, 0, sizeof( userCmds ) ); for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { @@ -831,7 +831,7 @@ void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) { // if this is the first snapshot after a game init was received if ( clientState == CS_CONNECTED ) { - gameTimeResidual = 0; + gameTimeResidual = 0.0f; clientState = CS_INGAME; assert( !sessLocal.GetActiveMenu( ) ); if ( idAsyncNetwork::verbose.GetInteger() ) { @@ -843,7 +843,7 @@ void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) { if ( gameTime < snapshotGameTime || gameTime > snapshotGameTime + idAsyncNetwork::clientMaxPrediction.GetInteger() ) { gameFrame = snapshotGameFrame; gameTime = snapshotGameTime; - gameTimeResidual = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), gameTimeResidual ); + gameTimeResidual = idMath::ClampFloat( -idAsyncNetwork::clientMaxPrediction.GetFloat(), idAsyncNetwork::clientMaxPrediction.GetFloat(), gameTimeResidual ); clientPredictTime = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPredictTime ); } @@ -1055,6 +1055,7 @@ idAsyncClient::ProcessChallengeResponseMessage */ void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const idBitMsg &msg ) { char serverGame[ MAX_STRING_CHARS ], serverGameBase[ MAX_STRING_CHARS ]; + short int serverGameHz; if ( clientState != CS_CHALLENGING ) { common->Printf( "Unwanted challenge response received.\n" ); @@ -1063,6 +1064,7 @@ void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const serverChallenge = msg.ReadInt(); serverId = msg.ReadShort(); + serverGameHz = msg.ReadShort(); msg.ReadString( serverGameBase, MAX_STRING_CHARS ); msg.ReadString( serverGame, MAX_STRING_CHARS ); @@ -1080,12 +1082,23 @@ void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const } common->Printf( "The server is running a different mod (%s-%s). Restarting..\n", serverGameBase, serverGame ); cvarSystem->SetCVarString( "fs_game_base", serverGameBase ); + cvarSystem->SetCVarInteger("com_gameHz", serverGameHz); //Lets update com_Gamehz just to gain some time cvarSystem->SetCVarString( "fs_game", serverGame ); cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" ); cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" ); return; } + //added by Stradex to update client fps before joining the game + + if (serverGameHz != com_realGameHz) { //we need to reload fps + common->Printf("Server is running with different com_gameHz (%d). Restarting..\n", serverGameHz); + cvarSystem->SetCVarInteger("com_gameHz", serverGameHz); + cmdSystem->BufferCommandText(CMD_EXEC_NOW, "reloadEngine"); + cmdSystem->BufferCommandText(CMD_EXEC_APPEND, "reconnect\n"); + return; + } + common->Printf( "received challenge response 0x%x from %s\n", serverChallenge, Sys_NetAdrToString( from ) ); // start sending connect packets instead of challenge request packets @@ -1768,7 +1781,7 @@ void idAsyncClient::RunFrame( void ) { // handle ongoing pk4 downloads and patch downloads HandleDownloads(); - gameTimeResidual += msec; + gameTimeResidual += (float)msec; // spin in place processing incoming packets until enough time lapsed to run a new game frame do { @@ -1776,7 +1789,7 @@ void idAsyncClient::RunFrame( void ) { do { // blocking read with game time residual timeout - newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - ( gameTimeResidual + clientPredictTime ) - 1 ); + newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), (int)idMath::Rint(com_gameMSRate - (gameTimeResidual + (float)clientPredictTime) - 1.0f) ); if ( newPacket ) { msg.Init( msgBuf, sizeof( msgBuf ) ); msg.SetSize( size ); @@ -1785,18 +1798,18 @@ void idAsyncClient::RunFrame( void ) { } msec = UpdateTime( 100 ); - gameTimeResidual += msec; + gameTimeResidual += (float)msec; } while( newPacket ); - } while( gameTimeResidual + clientPredictTime < USERCMD_MSEC ); + } while( gameTimeResidual + (float)clientPredictTime < com_gameMSRate); // update server list serverList.RunFrame(); if ( clientState == CS_DISCONNECTED ) { usercmdGen->GetDirectUsercmd(); - gameTimeResidual = USERCMD_MSEC - 1; + gameTimeResidual = com_gameMSRate - 1.0f; clientPredictTime = 0; return; } @@ -1804,7 +1817,7 @@ void idAsyncClient::RunFrame( void ) { if ( clientState == CS_PURERESTART ) { clientState = CS_DISCONNECTED; Reconnect(); - gameTimeResidual = USERCMD_MSEC - 1; + gameTimeResidual = com_gameMSRate - 1.0f; clientPredictTime = 0; return; } @@ -1814,7 +1827,7 @@ void idAsyncClient::RunFrame( void ) { // also need to read mouse for the connecting guis usercmdGen->GetDirectUsercmd(); SetupConnection(); - gameTimeResidual = USERCMD_MSEC - 1; + gameTimeResidual = com_gameMSRate - 1.0f; clientPredictTime = 0; return; } @@ -1826,7 +1839,7 @@ void idAsyncClient::RunFrame( void ) { // if not yet in the game send empty messages to keep data flowing through the channel if ( clientState < CS_INGAME ) { Idle(); - gameTimeResidual = 0; + gameTimeResidual = 0.0f; return; } @@ -1838,20 +1851,20 @@ void idAsyncClient::RunFrame( void ) { cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); } - if ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) { + if ( gameTimeResidual + (float)clientPredictTime >= com_gameMSRate) { lastFrameDelta = 0; } // generate user commands for the predicted time - while ( gameTimeResidual + clientPredictTime >= USERCMD_MSEC ) { + while ( gameTimeResidual + (float)clientPredictTime >= com_gameMSRate) { // send the user commands of this client to the server SendUsercmdsToServer(); // update time gameFrame++; - gameTime += USERCMD_MSEC; - gameTimeResidual -= USERCMD_MSEC; + gameTime = FRAME_TO_MSEC(gameFrame); + gameTimeResidual -= com_gameMSRate; // run from the snapshot up to the local game frame while ( snapshotGameFrame < gameFrame ) { @@ -1862,7 +1875,7 @@ void idAsyncClient::RunFrame( void ) { DuplicateUsercmds( snapshotGameFrame, snapshotGameTime ); // indicate the last prediction frame before a render - bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC ); + bool lastPredictFrame = ( (snapshotGameFrame + 1 >= gameFrame) && (gameTimeResidual + (float)clientPredictTime < com_gameMSRate) ); // run client prediction gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame ); @@ -1870,7 +1883,7 @@ void idAsyncClient::RunFrame( void ) { idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand ); snapshotGameFrame++; - snapshotGameTime += USERCMD_MSEC; + snapshotGameTime = FRAME_TO_MSEC(snapshotGameFrame); } } } diff --git a/neo/framework/async/AsyncClient.h b/neo/framework/async/AsyncClient.h index 2609f8db4..e5fa654c2 100644 --- a/neo/framework/async/AsyncClient.h +++ b/neo/framework/async/AsyncClient.h @@ -156,7 +156,7 @@ class idAsyncClient { int gameInitId; // game initialization identification int gameFrame; // local game frame int gameTime; // local game time - int gameTimeResidual; // left over time from previous frame + float gameTimeResidual; // left over time from previous frame usercmd_t userCmds[MAX_USERCMD_BACKUP][MAX_ASYNC_CLIENTS]; diff --git a/neo/framework/async/AsyncServer.cpp b/neo/framework/async/AsyncServer.cpp index 9f6981fcc..343825096 100644 --- a/neo/framework/async/AsyncServer.cpp +++ b/neo/framework/async/AsyncServer.cpp @@ -1515,6 +1515,7 @@ void idAsyncServer::ProcessChallengeMessage( const netadr_t from, const idBitMsg outMsg.WriteString( "challengeResponse" ); outMsg.WriteInt( challenges[i].challenge ); outMsg.WriteShort( serverId ); + outMsg.WriteShort(static_cast(cvarSystem->GetCVarInteger("com_gameHz"))); //added by Stradex to force client sync gamehz outMsg.WriteString( cvarSystem->GetCVarString( "fs_game_base" ) ); outMsg.WriteString( cvarSystem->GetCVarString( "fs_game" ) ); @@ -2377,7 +2378,7 @@ void idAsyncServer::RunFrame( void ) { do { // blocking read with game time residual timeout - newPacket = serverPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), USERCMD_MSEC - gameTimeResidual - 1 ); + newPacket = serverPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), (int)idMath::Rint(com_gameMSRate) - gameTimeResidual - 1 ); if ( newPacket ) { msg.Init( msgBuf, sizeof( msgBuf ) ); msg.SetSize( size ); @@ -2392,7 +2393,7 @@ void idAsyncServer::RunFrame( void ) { } while( newPacket ); - } while( gameTimeResidual < USERCMD_MSEC ); + } while( gameTimeResidual < (int)idMath::Rint(com_gameMSRate) ); // send heart beat to master servers MasterHeartbeat(); @@ -2433,7 +2434,7 @@ void idAsyncServer::RunFrame( void ) { } // advance the server game - while( gameTimeResidual >= USERCMD_MSEC ) { + while( gameTimeResidual >= (int)idMath::Rint(com_gameMSRate) ) { // sample input for the local client LocalClientInput(); @@ -2448,8 +2449,8 @@ void idAsyncServer::RunFrame( void ) { // update time gameFrame++; - gameTime += USERCMD_MSEC; - gameTimeResidual -= USERCMD_MSEC; + gameTime = FRAME_TO_MSEC(gameFrame); + gameTimeResidual -= (int)idMath::Rint(com_gameMSRate); } // duplicate usercmds so there is always at least one available to send with snapshots diff --git a/neo/game/Camera.cpp b/neo/game/Camera.cpp index eddef3733..117f19a37 100644 --- a/neo/game/Camera.cpp +++ b/neo/game/Camera.cpp @@ -514,7 +514,7 @@ void idCameraAnim::Think( void ) { return; } - if ( frameRate == USERCMD_HZ ) { + if ( frameRate == gameLocal.gameFps ) { frameTime = gameLocal.time - starttime; frame = frameTime / gameLocal.msec; } else { @@ -564,7 +564,7 @@ void idCameraAnim::GetViewParms( renderView_t *view ) { return; } - if ( frameRate == USERCMD_HZ ) { + if ( frameRate == gameLocal.gameFps ) { frameTime = gameLocal.time - starttime; frame = frameTime / gameLocal.msec; lerp = 0.0f; diff --git a/neo/game/Game_local.cpp b/neo/game/Game_local.cpp index 41f11767a..065fdc566 100644 --- a/neo/game/Game_local.cpp +++ b/neo/game/Game_local.cpp @@ -211,6 +211,7 @@ void idGameLocal::Clear( void ) { framenum = 0; previousTime = 0; time = 0; + preciseTime = 0.0f; vacuumAreaNum = 0; mapFileName.Clear(); mapFile = NULL; @@ -279,6 +280,9 @@ void idGameLocal::Init( void ) { const idDict *dict; idAAS *aas; + msec = 16; //60fps + gameFps = 60; //60fps + #ifndef GAME_DLL TestGameAPI(); @@ -296,6 +300,12 @@ void idGameLocal::Init( void ) { #endif + //Update MSEC and gameFps + gameFps = cvarSystem->GetCVarInteger("com_gameHz"); + msec = 1000.0f/ cvarSystem->GetCVarFloat("com_gameHz"); + msec *= 0.96f*0.96f; //HACK to emulate OG D3 msec error, in order to have exactly the same game logic speed + Printf("msec: %d\n", msec); + Printf( "----- Initializing Game -----\n" ); Printf( "gamename: %s\n", GAME_VERSION ); Printf( "gamedate: %s\n", __DATE__ ); @@ -536,6 +546,8 @@ void idGameLocal::SaveGame( idFile *f ) { savegame.WriteBool( isMultiplayer ); savegame.WriteInt( gameType ); + savegame.WriteFloat( preciseTime ); + savegame.WriteInt( framenum ); savegame.WriteInt( previousTime ); savegame.WriteInt( time ); @@ -920,6 +932,7 @@ void idGameLocal::LoadMap( const char *mapName, int randseed ) { previousTime = 0; time = 0; + preciseTime = 0.0f; framenum = 0; sessionCommand = ""; nextGibTime = 0; @@ -1353,6 +1366,8 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo savegame.ReadBool( isMultiplayer ); savegame.ReadInt( (int &)gameType ); + savegame.ReadFloat( preciseTime ); + savegame.ReadInt( framenum ); savegame.ReadInt( previousTime ); savegame.ReadInt( time ); @@ -2203,7 +2218,9 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { // update the game time framenum++; previousTime = time; - time += msec; + preciseTime += msec; + time = (int)idMath::Rint(preciseTime); + //time = FRAME_TO_MSEC(framenum); realClientTime = time; #ifdef GAME_DLL @@ -3538,7 +3555,7 @@ idGameLocal::AlertAI void idGameLocal::AlertAI( idEntity *ent ) { if ( ent && ent->IsType( idActor::Type ) ) { // alert them for the next frame - lastAIAlertTime = time + msec; + lastAIAlertTime = time + (int)idMath::Rint(msec); lastAIAlertEntity = static_cast( ent ); } } @@ -3943,7 +3960,7 @@ void idGameLocal::SetCamera( idCamera *cam ) { } else { inCinematic = false; - cinematicStopTime = time + msec; + cinematicStopTime = time + (int)idMath::Rint(msec); // restore r_znear cvarSystem->SetCVarFloat( "r_znear", 3.0f ); @@ -4385,4 +4402,4 @@ void idGameLocal::SwitchTeam( int clientNum, int team ) { idGameLocal::GetMapLoadingGUI =============== */ -void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { } +void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { } \ No newline at end of file diff --git a/neo/game/Game_local.h b/neo/game/Game_local.h index 1f724434b..ec0e5bb56 100644 --- a/neo/game/Game_local.h +++ b/neo/game/Game_local.h @@ -272,7 +272,9 @@ class idGameLocal : public idGame { int framenum; int previousTime; // time in msec of last frame int time; // in msec - static const int msec = USERCMD_MSEC; // time since last update in milliseconds + float msec; // time since last update in milliseconds + int gameFps; //added by Stradex for com_gameHz + float preciseTime; // added by Stradex for cm_gameHz fidelity int vacuumAreaNum; // -1 if level doesn't have any outside areas @@ -421,7 +423,7 @@ class idGameLocal : public idGame { // added the following to assist licensees with merge issues int GetFrameNum() const { return framenum; }; int GetTime() const { return time; }; - int GetMSec() const { return msec; }; + int GetMSec() const { return (int)idMath::Rint(msec); }; int GetNextClientNum( int current ) const; idPlayer * GetClientByNum( int current ) const; diff --git a/neo/game/Game_network.cpp b/neo/game/Game_network.cpp index c26d9068d..f277c650b 100644 --- a/neo/game/Game_network.cpp +++ b/neo/game/Game_network.cpp @@ -994,7 +994,8 @@ void idGameLocal::ClientReadSnapshot( int clientNum, int sequence, const int gam // update the game time framenum = gameFrame; time = gameTime; - previousTime = time - msec; + preciseTime = (float)gameTime; + previousTime = time - idMath::Rint(msec); // so that StartSound/StopSound doesn't risk skipping isNewFrame = true; @@ -1492,7 +1493,9 @@ gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clie // update the game time framenum++; previousTime = time; - time += msec; + preciseTime += msec; + time = (int)idMath::Rint(preciseTime); + //time = FRAME_TO_MSEC(framenum); // update the real client time and the new frame flag if ( time > realClientTime ) { diff --git a/neo/game/Moveable.cpp b/neo/game/Moveable.cpp index e13ecd6a4..aaf9d9328 100644 --- a/neo/game/Moveable.cpp +++ b/neo/game/Moveable.cpp @@ -392,14 +392,14 @@ bool idMoveable::FollowInitialSplinePath( void ) { if ( initialSpline != NULL ) { if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); - idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * USERCMD_HZ; + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * gameLocal.gameFps; physicsObj.SetLinearVelocity( linearVelocity ); idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); idVec3 dir = initialSplineDir * physicsObj.GetAxis(); idVec3 angularVelocity = dir.Cross( splineDir ); angularVelocity.Normalize(); - angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * USERCMD_HZ; + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * gameLocal.gameFps; physicsObj.SetAngularVelocity( angularVelocity ); return true; } else { diff --git a/neo/game/Mover.cpp b/neo/game/Mover.cpp index 9f1a8199c..aa3759689 100644 --- a/neo/game/Mover.cpp +++ b/neo/game/Mover.cpp @@ -2809,7 +2809,7 @@ void idMover_Binary::Use_BinaryMover( idEntity *activator ) { if ( moverState == MOVER_POS1 ) { // FIXME: start moving USERCMD_MSEC later, because if this was player // triggered, gameLocal.time hasn't been advanced yet - MatchActivateTeam( MOVER_1TO2, gameLocal.time + USERCMD_MSEC ); + MatchActivateTeam( MOVER_1TO2, gameLocal.time + (int)idMath::Rint(gameLocal.msec) ); SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); // open areaportal diff --git a/neo/game/Player.cpp b/neo/game/Player.cpp index 8caaab4b4..7bfaf40c9 100644 --- a/neo/game/Player.cpp +++ b/neo/game/Player.cpp @@ -1295,6 +1295,7 @@ void idPlayer::Init( void ) { stamina = pm_stamina.GetFloat(); // air always initialized to maximum too + pm_airTics.SetFloat((static_cast(gameLocal.gameFps) / 60.0) * pm_airTics.GetFloat()); //update for com_gameHz airTics = pm_airTics.GetFloat(); airless = false; diff --git a/neo/game/Projectile.cpp b/neo/game/Projectile.cpp index c353ca0c6..ae1f85905 100644 --- a/neo/game/Projectile.cpp +++ b/neo/game/Projectile.cpp @@ -858,7 +858,8 @@ void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) { renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" ); lightStartTime = gameLocal.time; - lightEndTime = gameLocal.time + SEC2MS( light_fadetime ); + //lightEndTime = MSEC_ALIGN_TO_FRAME(gameLocal.time + SEC2MS( light_fadetime )); + lightEndTime = gameLocal.time + SEC2MS(light_fadetime); BecomeActive( TH_THINK ); } @@ -1430,7 +1431,7 @@ void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const i angles = vel.ToAngles(); speed = vel.Length(); rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); - turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / ( float )USERCMD_HZ; + turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.gameFps; clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); burstMode = spawnArgs.GetBool( "burstMode" ); unGuided = false; diff --git a/neo/game/SmokeParticles.cpp b/neo/game/SmokeParticles.cpp index e63307d4c..bb9ea3f3b 100644 --- a/neo/game/SmokeParticles.cpp +++ b/neo/game/SmokeParticles.cpp @@ -221,7 +221,7 @@ bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemS if ( nowCount >= stage->totalParticles ) { nowCount = stage->totalParticles-1; } - prevCount = floor( ((float)( deltaMsec - USERCMD_MSEC ) / finalParticleTime) * stage->totalParticles ); + prevCount = floor( ((float)( deltaMsec - (int)idMath::Rint(gameLocal.msec)) / finalParticleTime) * stage->totalParticles ); if ( prevCount < -1 ) { prevCount = -1; } diff --git a/neo/game/physics/Force_Drag.cpp b/neo/game/physics/Force_Drag.cpp index f8682a11e..d6978f585 100644 --- a/neo/game/physics/Force_Drag.cpp +++ b/neo/game/physics/Force_Drag.cpp @@ -29,6 +29,7 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" #include "framework/UsercmdGen.h" +#include "Game_local.h" #include "physics/Physics.h" #include "physics/Force_Drag.h" @@ -139,9 +140,9 @@ void idForce_Drag::Evaluate( int time ) { l2 = dir2.Normalize(); rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); - physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( USERCMD_MSEC ), id ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC(gameLocal.msec ), id ); - velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( USERCMD_MSEC ) ); + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.msec) ); physics->SetLinearVelocity( velocity, id ); } diff --git a/neo/game/physics/Physics.cpp b/neo/game/physics/Physics.cpp index 86e35d464..3a84c82df 100644 --- a/neo/game/physics/Physics.cpp +++ b/neo/game/physics/Physics.cpp @@ -75,6 +75,7 @@ idPhysics::SnapTimeToPhysicsFrame */ int idPhysics::SnapTimeToPhysicsFrame( int t ) { int s; - s = t + USERCMD_MSEC - 1; - return ( s - s % USERCMD_MSEC ); + s = t + gameLocal.GetMSec() - 1; + return (s - s % gameLocal.GetMSec()); + //return MSEC_ALIGN_TO_FRAME(t); } diff --git a/neo/game/physics/Physics_AF.cpp b/neo/game/physics/Physics_AF.cpp index 373048076..8ecdddac2 100644 --- a/neo/game/physics/Physics_AF.cpp +++ b/neo/game/physics/Physics_AF.cpp @@ -6607,7 +6607,7 @@ idPhysics_AF::idPhysics_AF( void ) { memset( ¤t, 0, sizeof( current ) ); current.atRest = -1; - current.lastTimeStep = USERCMD_MSEC; + current.lastTimeStep = gameLocal.msec; saved = current; linearFriction = 0.005f; diff --git a/neo/game/physics/Physics_RigidBody.cpp b/neo/game/physics/Physics_RigidBody.cpp index 838065699..6187fb99c 100644 --- a/neo/game/physics/Physics_RigidBody.cpp +++ b/neo/game/physics/Physics_RigidBody.cpp @@ -447,7 +447,7 @@ idPhysics_RigidBody::idPhysics_RigidBody( void ) { memset( ¤t, 0, sizeof( current ) ); current.atRest = -1; - current.lastTimeStep = USERCMD_MSEC; + current.lastTimeStep = gameLocal.msec; current.i.position.Zero(); current.i.orientation.Identity(); diff --git a/neo/game/script/Script_Thread.cpp b/neo/game/script/Script_Thread.cpp index 58daa5d92..a1ac709e0 100644 --- a/neo/game/script/Script_Thread.cpp +++ b/neo/game/script/Script_Thread.cpp @@ -669,7 +669,7 @@ bool idThread::Execute( void ) { if ( waitingUntil > lastExecuteTime ) { PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); } else if ( interpreter.MultiFrameEventInProgress() ) { - PostEventMS( &EV_Thread_Execute, gameLocal.msec ); + PostEventMS( &EV_Thread_Execute, (int)idMath::Rint(gameLocal.msec) ); } } @@ -910,7 +910,7 @@ void idThread::WaitFrame( void ) { // manual control threads don't set waitingUntil so that they can be run again // that frame if necessary. if ( !manualControl ) { - waitingUntil = gameLocal.time + gameLocal.msec; + waitingUntil = gameLocal.time + (int)idMath::Rint(gameLocal.msec); } } @@ -1769,7 +1769,7 @@ idThread::Event_GetTicsPerSecond ================ */ void idThread::Event_GetTicsPerSecond( void ) { - idThread::ReturnFloat( USERCMD_HZ ); + idThread::ReturnFloat( gameLocal.gameFps ); } /* diff --git a/neo/idlib/Parser.cpp b/neo/idlib/Parser.cpp index 86a57d34d..c72ca66fd 100644 --- a/neo/idlib/Parser.cpp +++ b/neo/idlib/Parser.cpp @@ -249,6 +249,7 @@ define_t *idParser::CopyDefine( define_t *define ) { newdefine->hashnext = NULL; //copy the define tokens newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) { newtoken = new idToken(token); newtoken->next = NULL; @@ -772,6 +773,7 @@ int idParser::ExpandDefine( idToken *deftoken, define_t *define, idToken **first if ( define->builtin ) { return idParser::ExpandBuiltinDefine( deftoken, define, firsttoken, lasttoken ); } + // if the define has parameters if ( define->numparms ) { if ( !idParser::ReadDefineParms( define, parms, MAX_DEFINEPARMS ) ) { @@ -1098,6 +1100,8 @@ int idParser::Directive_define( void ) { if ( !idParser::ReadLine( &token ) ) { return true; } + + // if it is a define with parameters if ( token.WhiteSpaceBeforeToken() == 0 && token == "(" ) { // read the define parameters @@ -1142,10 +1146,32 @@ int idParser::Directive_define( void ) { } } } + if ( !idParser::ReadLine( &token ) ) { return true; } } + + + //UGLY HACK Stradex: workaround to have com_gameHz working without having to modify script files and avoiding new pak data + if (idStr::Icmp(define->name, "GAME_FPS") == 0) { + token = cvarSystem->GetCVarString("com_gameHz"); + } + else if (idStr::Icmp(define->name, "GAME_FRAMETIME") == 0) { + float hackGameMsec = 1.0f / static_cast(cvarSystem->GetCVarInteger("com_gameHz")); + char sHackMsec[64]; + sprintf(sHackMsec, "%.3f", hackGameMsec); + token = static_cast(sHackMsec); + } + else if (idStr::Icmp(define->name, "CHAINGUN_FIRE_SKIPFRAMES") == 0) { + int newSkipFramesVal = (int)idMath::Rint(cvarSystem->GetCVarFloat("com_gameHz")/ 7.0); //FIXME: Would be better to actually read the original value from CHAINGUN_FIRE_SKIPFRAMES instead of using 7.0 directly + char sHackSkipFramesVal[64]; + sprintf(sHackSkipFramesVal, "%d", newSkipFramesVal); + token = static_cast(sHackSkipFramesVal); + } + //UGLY HACK ends + + // read the defined stuff last = NULL; do @@ -1160,6 +1186,7 @@ int idParser::Directive_define( void ) { if ( last ) last->next = t; else define->tokens = t; last = t; + } while( idParser::ReadLine( &token ) ); if ( last ) { diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp index 818da194a..84866c994 100644 --- a/neo/renderer/RenderSystem_init.cpp +++ b/neo/renderer/RenderSystem_init.cpp @@ -86,7 +86,7 @@ idCVar r_znear( "r_znear", "3", CVAR_RENDERER | CVAR_FLOAT, "near Z clip plane d idCVar r_ignoreGLErrors( "r_ignoreGLErrors", "1", CVAR_RENDERER | CVAR_BOOL, "ignore GL errors" ); idCVar r_finish( "r_finish", "0", CVAR_RENDERER | CVAR_BOOL, "force a call to glFinish() every frame" ); -idCVar r_swapInterval( "r_swapInterval", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "changes the GL swap interval" ); +idCVar r_swapInterval( "r_swapInterval", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "changes the GL swap interval" ); idCVar r_gamma( "r_gamma", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 3.0f ); idCVar r_brightness( "r_brightness", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 2.0f ); diff --git a/neo/renderer/RenderWorld.cpp b/neo/renderer/RenderWorld.cpp index 227c96d26..c8fa8efd5 100644 --- a/neo/renderer/RenderWorld.cpp +++ b/neo/renderer/RenderWorld.cpp @@ -432,6 +432,12 @@ void idRenderWorldLocal::UpdateLightDef( qhandle_t lightHandle, const renderLigh light->archived = false; } + // new for BFG edition: force noShadows on spectrum lights so teleport spawns + // don't cause such a slowdown. Hell writing shouldn't be shadowed anyway... + if (light->parms.shader && light->parms.shader->Spectrum()) { + light->parms.noShadows = true; + } + if ( light->lightHasMoved ) { light->parms.prelightModel = NULL; } diff --git a/neo/renderer/tr_light.cpp b/neo/renderer/tr_light.cpp index 5a6819946..72b5970a2 100644 --- a/neo/renderer/tr_light.cpp +++ b/neo/renderer/tr_light.cpp @@ -1573,10 +1573,12 @@ R_RemoveUnecessaryViewLights ===================== */ void R_RemoveUnecessaryViewLights( void ) { - viewLight_t *vLight; + viewLight_t* vLight; + int numViewLights = 0; // go through each visible light for ( vLight = tr.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + numViewLights++; // if the light didn't have any lit surfaces visible, there is no need to // draw any of the shadows. We still keep the vLight for debugging // draws @@ -1621,4 +1623,31 @@ void R_RemoveUnecessaryViewLights( void ) { vLight->scissorRect.Intersect( surfRect ); } } + + + // BFG optimization: sort the viewLights list so the largest lights come first, which will reduce + // the chance of GPU pipeline bubbles + struct sortLight_t { + viewLight_t* vLight; + int screenArea; + static int sort(const void* a, const void* b) { + return ((sortLight_t*)a)->screenArea - ((sortLight_t*)b)->screenArea; + } + }; + sortLight_t* sortLights = (sortLight_t*)_alloca(sizeof(sortLight_t) * numViewLights); + int numSortLightsFilled = 0; + for (viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next) { + sortLights[numSortLightsFilled].vLight = vLight; + sortLights[numSortLightsFilled].screenArea = vLight->scissorRect.GetArea(); + numSortLightsFilled++; + } + + qsort(sortLights, numSortLightsFilled, sizeof(sortLights[0]), sortLight_t::sort); + + // rebuild the linked list in order + tr.viewDef->viewLights = NULL; + for (int i = 0; i < numSortLightsFilled; i++) { + sortLights[i].vLight->next = tr.viewDef->viewLights; + tr.viewDef->viewLights = sortLights[i].vLight; + } } diff --git a/neo/renderer/tr_local.h b/neo/renderer/tr_local.h index eca4bc87e..2909eb424 100644 --- a/neo/renderer/tr_local.h +++ b/neo/renderer/tr_local.h @@ -69,6 +69,9 @@ class idScreenRect { void Union( const idScreenRect &rect ); bool Equals( const idScreenRect &rect ) const; bool IsEmpty() const; + + //From BFG Edition + int GetArea() const { return (x2 - x1 + 1) * (y2 - y1 + 1); } }; idScreenRect R_ScreenRectFromViewFrustumBounds( const idBounds &bounds ); diff --git a/neo/sound/snd_emitter.cpp b/neo/sound/snd_emitter.cpp index 08c6fa387..0cd535190 100644 --- a/neo/sound/snd_emitter.cpp +++ b/neo/sound/snd_emitter.cpp @@ -478,7 +478,7 @@ void idSoundEmitterLocal::CheckForCompletion( int current44kHzTime ) { } // free decoder memory if no sound was decoded for a while - if ( chan->decoder != NULL && chan->decoder->GetLastDecodeTime() < current44kHzTime - SOUND_DECODER_FREE_DELAY ) { + if ( chan->decoder != NULL && chan->decoder->GetLastDecodeTime() < current44kHzTime - (1000 * MIXBUFFER_SAMPLES / (int)idMath::Rint(com_gameMSRate))) { chan->decoder->ClearDecoder(); } diff --git a/neo/sound/snd_local.h b/neo/sound/snd_local.h index f85f8c6dd..c5b489bb2 100644 --- a/neo/sound/snd_local.h +++ b/neo/sound/snd_local.h @@ -59,7 +59,6 @@ typedef enum { } soundDemoCommand_t; const int SOUND_MAX_CHANNELS = 8; -const int SOUND_DECODER_FREE_DELAY = 1000 * MIXBUFFER_SAMPLES / USERCMD_MSEC; // four seconds const int PRIMARYFREQ = 44100; // samples per second const float SND_EPSILON = 1.0f / 32768.0f; // if volume is below this, it will always multiply to zero diff --git a/neo/sys/osx/Doom 3.rsrc b/neo/sys/osx/Doom 3.rsrc new file mode 100644 index 000000000..14327b9d7 Binary files /dev/null and b/neo/sys/osx/Doom 3.rsrc differ diff --git a/neo/ui/Window.cpp b/neo/ui/Window.cpp index 2bdf96f1f..c581a85e1 100644 --- a/neo/ui/Window.cpp +++ b/neo/ui/Window.cpp @@ -600,7 +600,7 @@ idWindow::RunTimeEvents */ bool idWindow::RunTimeEvents(int time) { - if ( time - lastTimeRun < USERCMD_MSEC ) { + if ( time - lastTimeRun < idMath::Rint(com_gameMSRate) ) { //common->Printf("Skipping gui time events at %i\n", time); return false; }