Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3834d2b
Bump Version to 1.6.0pre
DanielGibson Jul 25, 2024
d8b2528
Revert "Soft particles: Disable Particle Stage "softeningRadius" keyw…
DanielGibson Jul 25, 2024
53f5ce9
Configurable FPS: Add com_gameHz, use for USERCMD_HZ and USERCMD_MSEC
DanielGibson Dec 28, 2025
934c11a
Some tweaks to prior commit (was "Make the Async Loop more precise")
DanielGibson Dec 28, 2025
c6db02b
Add hacks for replacing some #defines in scripts
DanielGibson Jun 27, 2024
339ab85
Add sys.getRawFrameTime() script event for GAME_FRAMETIME
DanielGibson Jun 27, 2024
8089706
Merge fixes from dezo2's 144Hz branch
DanielGibson Jun 27, 2024
f61d94d
Fix oxygen consumption code (idPlayer::airTics) for > 60Hz
DanielGibson Dec 29, 2025
4e10f69
Framerate-independent idPlayer::Move() and idAI::AdjustFlyingAngles()
DanielGibson Jun 27, 2024
9fd88ef
d3xp: When com_gameHz changes, scale some time values accordingly
DanielGibson Jun 27, 2024
590d6d8
Dhewm3SettingsMenu: Add setting for com_gameHz
DanielGibson Dec 29, 2025
f67dc2c
Fix cursor blinking speed
DanielGibson Dec 29, 2025
acd1280
Fix d3xp falling apart when changing com_gameHz while game is running
DanielGibson Jul 3, 2024
13c9f3c
Undo some changes to physics code for com_gameHz
DanielGibson Jul 3, 2024
fe52aa6
Merge some physics fixes from The Dark Mod
DanielGibson Jul 12, 2024
d71ee92
Fix the crane
DanielGibson Jul 13, 2024
3e4ed5d
Fix monsters walking up stairs and noclip speed
DanielGibson Jul 15, 2024
a22e4ed
Fix d3xp Grabber at high framerates
DanielGibson Jul 20, 2024
465e15c
Cosmetic changes to CalcMSec() in Game_local.cpp
DanielGibson Dec 29, 2025
1a21e58
Pressure user to not set com_gameHzVal to >250Hz
DanielGibson Dec 29, 2025
3535c4a
Apply @dezo2's fix for monsters sliding too fast
DanielGibson Jul 25, 2024
12f5f42
Apply @dezo2's ragdoll fix
DanielGibson Jul 25, 2024
41a3741
Use r_displayRefresh for real fullscreen mode (SDL2/SDL3-only)
DanielGibson Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions neo/d3xp/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,9 @@ bool idActor::StartRagdoll( void ) {
return true;
}

// dezo2, modified entity mass (must be here, mass is zeroed in StartFromCurrentPose)
float mass_mod = GetPhysics()->GetMass() * idMath::Pow( gameLocal.gameTicScale, 2.0f );

// disable the monster bounding box
GetPhysics()->DisableClip();

Expand Down Expand Up @@ -1702,6 +1705,9 @@ bool idActor::StartRagdoll( void ) {

RemoveAttachments();

// dezo2, use modified entity mass to reduce ragdoll movement at high FPS
GetPhysics()->SetMass( mass_mod );

return true;
}

Expand Down
49 changes: 43 additions & 6 deletions neo/d3xp/Game_local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ idGameLocal::idGameLocal
*/
idGameLocal::idGameLocal() {
Clear();
// DG: some sane default values until the proper values are set by SetGameHz()
msec = gameMsec = 16;
gameHz = 60;
gameTicScale = 1.0f;
}

/*
Expand Down Expand Up @@ -629,6 +633,7 @@ void idGameLocal::SaveGame( idFile *f ) {
savegame.WriteInt( time );

#ifdef _D3XP
savegame.WriteInt( gameMsec ); // DG: added with INTERNAL_SAVEGAME_VERSION 2
savegame.WriteInt( msec );
#endif

Expand Down Expand Up @@ -1538,7 +1543,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo
savegame.ReadInt( time );

#ifdef _D3XP
int savedGameMsec = 16;
if( savegame.GetInternalSavegameVersion() > 1 ) {
savegame.ReadInt( savedGameMsec );
}
float gameMsecScale = float(gameMsec) / float(savedGameMsec);

savegame.ReadInt( msec );
// DG: the saved msec must be scaled, in case com_gameHz has a different value now
msec *= gameMsecScale;
#endif

savegame.ReadInt( vacuumAreaNum );
Expand All @@ -1561,12 +1574,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo

fast.Restore( &savegame );
slow.Restore( &savegame );
fast.msec *= gameMsecScale; // DG: the saved value must be scaled, in case com_gameHz has a different value now
slow.msec *= gameMsecScale; // same here

int blah;
savegame.ReadInt( blah );
slowmoState = (slowmoState_t)blah;

savegame.ReadFloat( slowmoMsec );
slowmoMsec *= gameMsecScale; // DG: the saved value must be scaled, in case com_gameHz has a different value now
savegame.ReadBool( quickSlowmoReset );

if ( slowmoState == SLOWMO_STATE_OFF ) {
Expand Down Expand Up @@ -2449,12 +2465,12 @@ void idGameLocal::RunTimeGroup2( int msec_fast ) { // dezo2/DG: added argument f
#endif

// dezo2/DG: returns number of milliseconds for this frame, either 1000/gameHz or 1000/gameHz + 1,
// (16 or 17) so the frametimes of gameHz frames add up to 1000ms.
// (e.g. 16 or 17 for 60Hz) so the frametimes of gameHz frames add up to 1000ms.
// This prevents animations or videos from running slightly to slow or running out of sync
// with audio in cutscenes (those only worked right at 62.5fps with exactly 16ms frames,
// but now even without vsync we're enforcing 16.666ms frames for proper 60fps)
static int CalcMSec( long long framenum ) {
long long divisor = 100LL * USERCMD_HZ;
long long divisor = 100LL * gameLocal.gameHz;
return int( (framenum * 100000LL) / divisor - ((framenum-1) * 100000LL) / divisor );
}

Expand Down Expand Up @@ -4932,10 +4948,10 @@ void idGameLocal::ComputeSlowMsec() {

// do any necessary ramping
if ( slowmoState == SLOWMO_STATE_RAMPUP ) {
delta = 4 - slowmoMsec;
delta = gameMsec * 0.25f - slowmoMsec; // DG: adjust to support com_gameHz

if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) {
slowmoMsec = 4;
slowmoMsec = gameMsec * 0.25f; // DG: adjust to support com_gameHz (was 4 = 16/4)
slowmoState = SLOWMO_STATE_ON;
}
else {
Expand All @@ -4947,10 +4963,10 @@ void idGameLocal::ComputeSlowMsec() {
}
}
else if ( slowmoState == SLOWMO_STATE_RAMPDOWN ) {
delta = 16 - slowmoMsec;
delta = gameMsec - slowmoMsec; // DG: adjust to support com_gameHz

if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) {
slowmoMsec = 16;
slowmoMsec = gameMsec; // DG: adjust to support com_gameHz
slowmoState = SLOWMO_STATE_OFF;
if ( gameSoundWorld ) {
gameSoundWorld->SetSlowmo( false );
Expand Down Expand Up @@ -5062,3 +5078,24 @@ idGameLocal::GetMapLoadingGUI
===============
*/
void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { }

// DG: Added for configurable framerate
void idGameLocal::SetGameHz( float hz, float frametime, float ticScaleFactor )
{
gameHz = hz;
int oldGameMsec = gameMsec;
gameMsec = frametime;
gameTicScale = ticScaleFactor;

if ( slowmoState == SLOWMO_STATE_OFF ) {
// if slowmo is off, msec, slowmoMsec and slow/fast.msec should all be set to gameMsec
msec = slowmoMsec = slow.msec = fast.msec = gameMsec;
} else {
// otherwise the msec values must be scaled accordingly
float gameMsecScale = frametime / float(oldGameMsec);
msec *= gameMsecScale;
slowmoMsec *= gameMsecScale;
fast.msec *= gameMsecScale;
slow.msec *= gameMsecScale;
}
}
10 changes: 9 additions & 1 deletion neo/d3xp/Game_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@ class idGameLocal : public idGame {
int time; // in msec
int msec; // time since last update in milliseconds

// DG: added for configurable framerate
int gameMsec; // length of one frame/tic in milliseconds - TODO: make float?
int gameHz; // current gameHz value (tic-rate, FPS)
float gameTicScale; // gameHz/60 factor to multiply delays in tics (that assume 60fps) with

int vacuumAreaNum; // -1 if level doesn't have any outside areas

gameType_t gameType;
Expand Down Expand Up @@ -403,6 +408,9 @@ class idGameLocal : public idGame {

virtual void GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] );

// DG: Added for configurable framerate
virtual void SetGameHz( float hz, float frametime, float ticScaleFactor );

// ---------------------- Public idGameLocal Interface -------------------

void Printf( const char *fmt, ... ) const id_attribute((format(printf,2,3)));
Expand Down Expand Up @@ -518,7 +526,7 @@ class idGameLocal : public idGame {

private:
const static int INITIAL_SPAWN_COUNT = 1;
const static int INTERNAL_SAVEGAME_VERSION = 1; // DG: added this for >= 1305 savegames
const static int INTERNAL_SAVEGAME_VERSION = 2; // DG: added this for >= 1305 savegames

idStr mapFileName; // name of the map, empty string if no map loaded
idMapFile * mapFile; // will be NULL during the game unless in-game editing is used
Expand Down
38 changes: 25 additions & 13 deletions neo/d3xp/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2152,7 +2152,7 @@ void idPlayer::Save( idSaveGame *savefile ) const {
savefile->WriteInt( numProjectileHits );

savefile->WriteBool( airless );
savefile->WriteInt( airTics );
savefile->WriteInt( (int)airTics );
savefile->WriteInt( lastAirDamage );

savefile->WriteBool( gibDeath );
Expand Down Expand Up @@ -2420,7 +2420,11 @@ void idPlayer::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( numProjectileHits );

savefile->ReadBool( airless );
savefile->ReadInt( airTics );
// DG: I made made airTics float for high-fps (where we have fractions of 60Hz tics),
// but for saving ints should still suffice (and this preserves savegame compat)
int iairTics;
savefile->ReadInt( iairTics );
airTics = iairTics;
savefile->ReadInt( lastAirDamage );

savefile->ReadBool( gibDeath );
Expand Down Expand Up @@ -3238,6 +3242,9 @@ void idPlayer::DrawHUD( idUserInterface *_hud ) {
} else {
cursor->SetStateString( "grabbercursor", "0" );
cursor->SetStateString( "combatcursor", "1" );
cursor->SetStateBool("scaleto43", true); // dezo2, scaled
cursor->StateChanged(gameLocal.realClientTime); // dezo2, set state

}
// DG: update scaleto43 state if necessary
if ( cursor->GetStateBool( "scaleto43" ) != wantScaleTo43 ) {
Expand Down Expand Up @@ -3520,12 +3527,12 @@ bool idPlayer::Give( const char *statname, const char *value ) {
}

} else if ( !idStr::Icmp( statname, "air" ) ) {
if ( airTics >= pm_airTics.GetInteger() ) {
if ( airTics >= pm_airTics.GetFloat() ) { // DG: airTics are floats now for high-fps support
return false;
}
airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger();
if ( airTics > pm_airTics.GetInteger() ) {
airTics = pm_airTics.GetInteger();
airTics += atoi( value ) / 100.0 * pm_airTics.GetFloat();
if ( airTics > pm_airTics.GetFloat() ) {
airTics = pm_airTics.GetFloat();
}
#ifdef _D3XP
} else if ( !idStr::Icmp( statname, "enviroTime" ) ) {
Expand Down Expand Up @@ -6114,7 +6121,8 @@ void idPlayer::UpdateAir( void ) {
hud->HandleNamedEvent( "noAir" );
}
}
airTics--;
// DG: was airTics--, but airTics assume 60Hz tics and we support other ticrates now (com_gameHz)
airTics -= 1.0f / gameLocal.gameTicScale;
if ( airTics < 0 ) {
airTics = 0;
// check for damage
Expand All @@ -6134,16 +6142,16 @@ void idPlayer::UpdateAir( void ) {
hud->HandleNamedEvent( "Air" );
}
}
airTics+=2; // regain twice as fast as lose
if ( airTics > pm_airTics.GetInteger() ) {
airTics = pm_airTics.GetInteger();
airTics += 2.0f / gameLocal.gameTicScale; // regain twice as fast as lose - DG: scale for com_gameHz
if ( airTics > pm_airTics.GetFloat() ) {
airTics = pm_airTics.GetFloat();
}
}

airless = newAirless;

if ( hud ) {
hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() );
hud->SetStateInt( "player_air", 100 * (airTics / pm_airTics.GetFloat()) );
}
}

Expand Down Expand Up @@ -7116,8 +7124,12 @@ void idPlayer::Move( void ) {
if ( spectating ) {
SetEyeHeight( newEyeOffset );
} else {
// DG: make this framerate-independent, code suggested by tyuah8 on Github
// https://en.wikipedia.org/wiki/Exponential_smoothing#Time_constant
const float tau = -16.0f / idMath::Log( pm_crouchrate.GetFloat() );
const float a = 1.0f - idMath::Exp( -gameLocal.gameMsec / tau );
// smooth out duck height changes
SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) );
SetEyeHeight( EyeHeight() * (1.0f - a) + newEyeOffset * a );
}
}

Expand Down Expand Up @@ -7657,7 +7669,7 @@ bool idPlayer::CanGive( const char *statname, const char *value ) {
return true;

} else if ( !idStr::Icmp( statname, "air" ) ) {
if ( airTics >= pm_airTics.GetInteger() ) {
if ( airTics >= pm_airTics.GetFloat() ) {
return false;
}
return true;
Expand Down
3 changes: 2 additions & 1 deletion neo/d3xp/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,8 @@ class idPlayer : public idActor {
int numProjectileHits; // number of hits on mobs

bool airless;
int airTics; // set to pm_airTics at start, drops in vacuum
// DG: Note: airTics are tics at 60Hz, so no real tics (unless com_gameHz happens to be 60)
float airTics; // set to pm_airTics at start, drops in vacuum
int lastAirDamage;

bool gibDeath;
Expand Down
11 changes: 9 additions & 2 deletions neo/d3xp/ai/AI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2984,8 +2984,15 @@ void idAI::AdjustFlyingAngles( void ) {
}
}

fly_roll = fly_roll * 0.95f + roll * 0.05f;
fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
// DG: make this framerate-independent, code suggested by tyuah8 on Github
// https://en.wikipedia.org/wiki/Exponential_smoothing#Time_constant
static const float tau = -16.0f / idMath::Log( 0.95f );
// TODO: use gameLocal.gameMsec instead, so it's not affected by slow motion?
// enemies turning slower in slowmo seems logical, but the original code
// just increased every tic and thus was independent of slowmo
const float a = 1.0f - idMath::Exp( -gameLocal.msec / tau );
fly_roll = fly_roll * (1.0f - a) + roll * a;
fly_pitch = fly_pitch * (1.0f - a) + pitch * a;

if ( flyTiltJoint != INVALID_JOINT ) {
animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
Expand Down
2 changes: 2 additions & 0 deletions neo/d3xp/physics/Force_Drag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ If you have questions concerning this license or the applicable additional terms

#include "physics/Force_Drag.h"

#include "Game_local.h"

CLASS_DECLARATION( idForce, idForce_Drag )
END_CLASS

Expand Down
18 changes: 17 additions & 1 deletion neo/d3xp/physics/Force_Grab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,23 @@ idForce_Grab::Init
*/
void idForce_Grab::Init( float damping ) {
if ( damping >= 0.0f && damping < 1.0f ) {
this->damping = damping;
/* DG: in Evaluate(), the linear velocity (or actually momentum) of this->physics
* is multiplied with damping (0.5 by default) each frame.
* So how quickly the velocity is reduced per second depended on the framerate,
* and at higher framerates the grabbed item is slowed down too much and
* because of that sometimes even drops on the floor (gravity stronger than
* the force dragging it towards the player, or something like that).
* To fix that, damping must be adjusted depending on the framerate.
* The fixed code below is the result of this math (figuring out fixeddamping;
* note that here a^b means pow(a, b)):
* // we want velocity multiplied with damping 60 times per second to have the
* // same value as velocity multiplied with fixeddamping gameHz times per second
* velocity * damping^60 = velocity * fixeddamping^gameHz
* <=> damping^60 = fixeddamping^gameHz // divided by velocity
* <=> gameHz-th-root-of( damping^60 ) = fixeddamping // took gameHz-th root
* <=> fixeddamping = damping^( 60/gameHz ) // n-th-root-of(x^m) == x^(m/n)
*/
this->damping = idMath::Pow( damping, 60.0f/gameLocal.gameHz );
}
}

Expand Down
10 changes: 8 additions & 2 deletions neo/d3xp/physics/Physics_AF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2233,6 +2233,8 @@ bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep )
}

speed = steerAngle - angle;
// DG: steerSpeed is applied per frame, so it must be adjusted for the actual frametime
float steerSpeed = this->steerSpeed / gameLocal.gameTicScale;
if ( steerSpeed != 0.0f ) {
if ( speed > steerSpeed ) {
speed = steerSpeed;
Expand Down Expand Up @@ -5375,6 +5377,9 @@ void idPhysics_AF::Evolve( float timeStep ) {
// make absolutely sure all contact constraints are satisfied
VerifyContactConstraints();

// DG: from TDM: make friction independent of the frametime (i.e. the time between two calls of this function)
float frictionTickMul = timeStep / MS2SEC( 16 ); // USERCMD_MSEC was 16 before introducing com_gameHz

// calculate the position of the bodies for the next physics state
for ( i = 0; i < bodies.Num(); i++ ) {
body = bodies[i];
Expand All @@ -5393,8 +5398,9 @@ void idPhysics_AF::Evolve( float timeStep ) {
body->next->worldAxis.OrthoNormalizeSelf();

// linear and angular friction
body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0);
body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1);
// DG: from TDM: use frictionTicMul from above
body->next->spatialVelocity.SubVec3(0) -= frictionTickMul * body->linearFriction * body->next->spatialVelocity.SubVec3(0);
body->next->spatialVelocity.SubVec3(1) -= frictionTickMul * body->angularFriction * body->next->spatialVelocity.SubVec3(1);
}
}

Expand Down
6 changes: 5 additions & 1 deletion neo/d3xp/physics/Physics_Monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity
// try to move at the stepped up position
stepPos = tr.endpos;
stepVel = velocity;
result2 = SlideMove( stepPos, stepVel, delta );
// DG: this hack allows monsters to climb stairs at high framerates
// the tr.fraction < 1.0 check should prevent monsters from sliding faster when not
// actually on stairs (when climbing stairs it's apparently 1.0)
idVec3 fixedDelta = delta * ( tr.fraction < 1.0f ? 1.0f : gameLocal.gameTicScale );
result2 = SlideMove( stepPos, stepVel, fixedDelta );
if ( result2 == MM_BLOCKED ) {
start = noStepPos;
velocity = noStepVel;
Expand Down
3 changes: 2 additions & 1 deletion neo/d3xp/physics/Physics_Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,8 @@ void idPhysics_Player::NoclipMove( void ) {

// friction
speed = current.velocity.Length();
if ( speed < 20.0f ) {
// DG: adjust this for framerate
if ( speed < 20.0f / gameLocal.gameTicScale ) {
current.velocity = vec3_origin;
}
else {
Expand Down
Loading