Miami highfps: fix several physics & visual issues at high framerates #1205

Open
Veyrdite wants to merge 34 commits from unknown repository into miami

@ -2347,12 +2347,18 @@ PlayCruising:
SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE);
if (isMoped || accelerateState >= 150 && wheelsOnGround && brakeState <= 0 && !params.m_pVehicle->bIsHandbrakeOn if (isMoped || accelerateState >= 150 && wheelsOnGround && brakeState <= 0 && !params.m_pVehicle->bIsHandbrakeOn
&& !lostTraction && currentGear >= params.m_pTransmission->nNumberOfGears - 1) { && !lostTraction && currentGear >= params.m_pTransmission->nNumberOfGears - 1) {
#ifdef FIX_BUGS
// Prevent the fake top gear ("cruise gear") rising in pitch too quickly at high FPS.
if (CTimer::GetLogicalFramesPassed())
#endif
{
if (accelerateState >= 220 && params.m_fVelocityChange + 0.001f >= velocityChangeForAudio) { if (accelerateState >= 220 && params.m_fVelocityChange + 0.001f >= velocityChangeForAudio) {
if (nCruising < 800) if (nCruising < 800)
++nCruising; ++nCruising;
} else if (nCruising > 3) { } else if (nCruising > 3) {
--nCruising; --nCruising;
} }
}
freq = 27 * nCruising + freqModifier + 22050; freq = 27 * nCruising + freqModifier + 22050;
if (engineSoundType == SFX_BANK_TRUCK) if (engineSoundType == SFX_BANK_TRUCK)
freq /= 2; freq /= 2;

@ -3895,10 +3895,12 @@ CCam::Process_Debug(const CVector&, float, float, float)
if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f); if(Alpha > DEGTORAD(89.5f)) Alpha = DEGTORAD(89.5f);
else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f); else if(Alpha < DEGTORAD(-89.5f)) Alpha = DEGTORAD(-89.5f);
const float TimeStep = CTimer::GetTimeStepNonClipped();
if(CPad::GetPad(1)->GetSquare() || KEYDOWN('W')) if(CPad::GetPad(1)->GetSquare() || KEYDOWN('W'))
Speed += 0.1f; Speed += 0.1f * TimeStep;
else if(CPad::GetPad(1)->GetCross() || KEYDOWN('S')) else if(CPad::GetPad(1)->GetCross() || KEYDOWN('S'))
Speed -= 0.1f; Speed -= 0.1f * TimeStep;
else else
Speed = 0.0f; Speed = 0.0f;
if(Speed > 70.0f) Speed = 70.0f; if(Speed > 70.0f) Speed = 70.0f;
@ -3906,9 +3908,9 @@ CCam::Process_Debug(const CVector&, float, float, float)
if(KEYDOWN(rsRIGHT) || KEYDOWN('D')) if(KEYDOWN(rsRIGHT) || KEYDOWN('D'))
PanSpeedX += 0.1f; PanSpeedX += 0.1f * TimeStep;
else if(KEYDOWN(rsLEFT) || KEYDOWN('A')) else if(KEYDOWN(rsLEFT) || KEYDOWN('A'))
PanSpeedX -= 0.1f; PanSpeedX -= 0.1f * TimeStep;
else else
PanSpeedX = 0.0f; PanSpeedX = 0.0f;
if(PanSpeedX > 70.0f) PanSpeedX = 70.0f; if(PanSpeedX > 70.0f) PanSpeedX = 70.0f;
@ -3916,23 +3918,22 @@ CCam::Process_Debug(const CVector&, float, float, float)
if(KEYDOWN(rsUP)) if(KEYDOWN(rsUP))
PanSpeedY += 0.1f; PanSpeedY += 0.1f * TimeStep;
else if(KEYDOWN(rsDOWN)) else if(KEYDOWN(rsDOWN))
PanSpeedY -= 0.1f; PanSpeedY -= 0.1f * TimeStep;
else else
PanSpeedY = 0.0f; PanSpeedY = 0.0f;
if(PanSpeedY > 70.0f) PanSpeedY = 70.0f; if(PanSpeedY > 70.0f) PanSpeedY = 70.0f;
if(PanSpeedY < -70.0f) PanSpeedY = -70.0f; if(PanSpeedY < -70.0f) PanSpeedY = -70.0f;
Front = TargetCoors - Source; Front = TargetCoors - Source;
Front.Normalise(); Front.Normalise();
Source = Source + Front*Speed; Source = Source + Front * Speed * TimeStep;
Up = CVector{ 0.0f, 0.0f, 1.0f }; Up = CVector{ 0.0f, 0.0f, 1.0f };
CVector Right = CrossProduct(Front, Up); CVector Right = CrossProduct(Front, Up);
Up = CrossProduct(Right, Front); Up = CrossProduct(Right, Front);
Source = Source + Up*PanSpeedY + Right*PanSpeedX; Source = Source + Up * PanSpeedY * TimeStep + Right * PanSpeedX * TimeStep;
if(Source.z < -450.0f) if(Source.z < -450.0f)
Source.z = -450.0f; Source.z = -450.0f;
@ -3955,11 +3956,7 @@ CCam::Process_Debug(const CVector&, float, float, float)
Source.y += 1.0f; Source.y += 1.0f;
GetVectorsReadyForRW(); GetVectorsReadyForRW();
#ifdef FIX_BUGS
CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CAMERA); CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_CAMERA);
#else
CPad::GetPad(0)->DisablePlayerControls = PLAYERCONTROL_CAMERA;
#endif
if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn) if(CPad::GetPad(1)->GetLeftShockJustDown() && gbBigWhiteDebugLightSwitchedOn)
CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source, CShadows::StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &Source,

@ -590,7 +590,12 @@ CPhysical::ApplyAirResistance(void)
}else if(GetStatus() != STATUS_GHOST){ }else if(GetStatus() != STATUS_GHOST){
float f = Pow(1.0f/Abs(1.0f + m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr()), CTimer::GetTimeStep()); float f = Pow(1.0f/Abs(1.0f + m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr()), CTimer::GetTimeStep());
m_vecMoveSpeed *= f; m_vecMoveSpeed *= f;
#ifdef FIX_BUGS
// Fix too much friction at high FPS (evil cause of bad vehicle handling, rear tires unable to lose traction, etc!)
m_vecTurnSpeed *= Pow(0.99f, CTimer::GetTimeStepFix());
#else
m_vecTurnSpeed *= 0.99f; m_vecTurnSpeed *= 0.99f;
#endif
} }
} }
@ -1071,6 +1076,10 @@ CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
if(fOtherSpeedA > speedSum){ if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
#ifdef FIX_BUGS
impulseA *= CTimer::GetTimeStepFix();
impulseB *= CTimer::GetTimeStepFix();
#endif
impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit; if(impulseA < -impulseLimit) impulseA = -impulseLimit;
#ifdef FIX_BUGS #ifdef FIX_BUGS
@ -1107,6 +1116,10 @@ CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
if(fOtherSpeedA > speedSum){ if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * A->m_fMass; impulseA = (speedSum - fOtherSpeedA) * A->m_fMass;
impulseB = (speedSum - fOtherSpeedB) * massB; impulseB = (speedSum - fOtherSpeedB) * massB;
#ifdef FIX_BUGS
impulseA *= CTimer::GetTimeStepFix();
impulseB *= CTimer::GetTimeStepFix();
#endif
impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit; if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit; if(impulseB > impulseLimit) impulseB = impulseLimit;
@ -1140,6 +1153,10 @@ CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
if(fOtherSpeedA > speedSum){ if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * massA; impulseA = (speedSum - fOtherSpeedA) * massA;
impulseB = (speedSum - fOtherSpeedB) * B->m_fMass; impulseB = (speedSum - fOtherSpeedB) * B->m_fMass;
#ifdef FIX_BUGS
impulseA *= CTimer::GetTimeStepFix();
impulseB *= CTimer::GetTimeStepFix();
#endif
impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit; if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit; if(impulseB > impulseLimit) impulseB = impulseLimit;
@ -1174,6 +1191,10 @@ CPhysical::ApplyFriction(CPhysical *B, float adhesiveLimit, CColPoint &colpoint)
if(fOtherSpeedA > speedSum){ if(fOtherSpeedA > speedSum){
impulseA = (speedSum - fOtherSpeedA) * massA; impulseA = (speedSum - fOtherSpeedA) * massA;
impulseB = (speedSum - fOtherSpeedB) * massB; impulseB = (speedSum - fOtherSpeedB) * massB;
#ifdef FIX_BUGS
impulseA *= CTimer::GetTimeStepFix();
impulseB *= CTimer::GetTimeStepFix();
#endif
impulseLimit = adhesiveLimit*CTimer::GetTimeStep(); impulseLimit = adhesiveLimit*CTimer::GetTimeStep();
if(impulseA < -impulseLimit) impulseA = -impulseLimit; if(impulseA < -impulseLimit) impulseA = -impulseLimit;
if(impulseB > impulseLimit) impulseB = impulseLimit; if(impulseB > impulseLimit) impulseB = impulseLimit;
@ -1214,6 +1235,9 @@ CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint)
// not really impulse but speed // not really impulse but speed
// maybe use ApplyFrictionMoveForce instead? // maybe use ApplyFrictionMoveForce instead?
fImpulse = -fOtherSpeed; fImpulse = -fOtherSpeed;
#ifdef FIX_BUGS
fImpulse *= CTimer::GetTimeStepFix();
#endif
impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass; impulseLimit = adhesiveLimit*CTimer::GetTimeStep() / m_fMass;
if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
CVector vImpulse = frictionDir*fImpulse; CVector vImpulse = frictionDir*fImpulse;
@ -1235,6 +1259,9 @@ CPhysical::ApplyFriction(float adhesiveLimit, CColPoint &colpoint)
frictionDir = vOtherSpeed * (1.0f/fOtherSpeed); frictionDir = vOtherSpeed * (1.0f/fOtherSpeed);
#endif #endif
fImpulse = -fOtherSpeed * m_fMass; fImpulse = -fOtherSpeed * m_fMass;
#ifdef FIX_BUGS
fImpulse *= CTimer::GetTimeStepFix();
#endif
impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5; impulseLimit = adhesiveLimit*CTimer::GetTimeStep() * 1.5;
if(fImpulse < -impulseLimit) fImpulse = -impulseLimit; if(fImpulse < -impulseLimit) fImpulse = -impulseLimit;
ApplyFrictionMoveForce(frictionDir*fImpulse); ApplyFrictionMoveForce(frictionDir*fImpulse);
@ -1834,9 +1861,16 @@ CPhysical::ProcessCollisionSectorList(CPtrList *lists)
A->GetStatus() == STATUS_PLAYER && A->IsVehicle() && A->GetStatus() == STATUS_PLAYER && A->IsVehicle() &&
Abs(A->m_vecMoveSpeed.x) > 0.2f && Abs(A->m_vecMoveSpeed.x) > 0.2f &&
Abs(A->m_vecMoveSpeed.y) > 0.2f){ Abs(A->m_vecMoveSpeed.y) > 0.2f){
#ifdef FIX_BUGS
// Fix vehicles having lower turning circles at high FPS
A->m_vecMoveFriction.x += CTimer::GetTimeStepFix() * moveSpeed.x * -0.3f / numCollisions;
A->m_vecMoveFriction.y += CTimer::GetTimeStepFix() * moveSpeed.y * -0.3f / numCollisions;
A->m_vecTurnFriction += CTimer::GetTimeStepFix() * turnSpeed * -0.3f / numCollisions;
#else
A->m_vecMoveFriction.x += moveSpeed.x * -0.3f / numCollisions; A->m_vecMoveFriction.x += moveSpeed.x * -0.3f / numCollisions;
A->m_vecMoveFriction.y += moveSpeed.y * -0.3f / numCollisions; A->m_vecMoveFriction.y += moveSpeed.y * -0.3f / numCollisions;
A->m_vecTurnFriction += turnSpeed * -0.3f / numCollisions; A->m_vecTurnFriction += turnSpeed * -0.3f / numCollisions;
#endif
} }
if(B->IsObject() && Bobj->m_nCollisionDamageEffect && maxImpulseA > 20.0f) if(B->IsObject() && Bobj->m_nCollisionDamageEffect && maxImpulseA > 20.0f)

@ -6,6 +6,7 @@
#include "Timer.h" #include "Timer.h"
#include "rtcharse.h" #include "rtcharse.h"
#include "re3_inttypes.h" #include "re3_inttypes.h"
#include "Frontend.h"
#include "debugmenu.h" #include "debugmenu.h"
#include <new> #include <new>
@ -1017,6 +1018,10 @@ DebugMenuProcess(void)
CPad *pad = CPad::GetPad(0); CPad *pad = CPad::GetPad(0);
if(CTRLJUSTDOWN('M')) if(CTRLJUSTDOWN('M'))
menuOn = !menuOn; menuOn = !menuOn;
if (KEYJUSTDOWN(rsF4))
FrontEndMenuManager.m_PrefsFrameLimiter = !FrontEndMenuManager.m_PrefsFrameLimiter;
if(KEYJUSTDOWN(rsESC))
menuOn = false;
if(!menuOn) if(!menuOn)
return; return;

@ -429,6 +429,12 @@ CParticleObject::RemoveObject(void)
void void
CParticleObject::UpdateAll(void) CParticleObject::UpdateAll(void)
{ {
#ifdef FIX_BUGS
// Fix particle generation spam at high FPS
if (CTimer::GetLogicalFramesPassed() == 0)
return;
#endif
{ {
CParticleObject *pobj = pCloseListHead; CParticleObject *pobj = pCloseListHead;
CParticleObject *nextpobj; CParticleObject *nextpobj;

@ -1259,8 +1259,11 @@ void CParticle::Update()
} }
vecPos += vecMoveStep; vecPos += vecMoveStep;
#ifdef FIX_BUGS
if ( psystem->m_Type == PARTICLE_FIREBALL && CTimer::GetLogicalFramesPassed()) // Fix particle spam at high FPS
#else
if ( psystem->m_Type == PARTICLE_FIREBALL ) if ( psystem->m_Type == PARTICLE_FIREBALL )
#endif
{ {
AddParticle(PARTICLE_HEATHAZE, particle->m_vecPosition, CVector(0.0f, 0.0f, 0.0f), AddParticle(PARTICLE_HEATHAZE, particle->m_vecPosition, CVector(0.0f, 0.0f, 0.0f),
nil, particle->m_fSize * 5.0f); nil, particle->m_fSize * 5.0f);
@ -1326,11 +1329,18 @@ void CParticle::Update()
fDistToCam = (TheCamera.GetPosition() - vecPos).Magnitude(); fDistToCam = (TheCamera.GetPosition() - vecPos).Magnitude();
} }
#ifdef FIX_BUGS
if ( numWaterDropOnScreen < nMaxDrops && numWaterDropOnScreen < 63
&& fDistToCam < 10.0f
&& clearWaterDrop == false
&& !CGame::IsInInterior()
&& CTimer::GetLogicalFramesPassed()) // Fix waterdrop spam at high FPS
#else
if ( numWaterDropOnScreen < nMaxDrops && numWaterDropOnScreen < 63 if ( numWaterDropOnScreen < nMaxDrops && numWaterDropOnScreen < 63
&& fDistToCam < 10.0f && fDistToCam < 10.0f
&& clearWaterDrop == false && clearWaterDrop == false
&& !CGame::IsInInterior() ) && !CGame::IsInInterior() )
#endif
{ {
CVector vecWaterdropTarget CVector vecWaterdropTarget
( (
@ -1411,7 +1421,11 @@ void CParticle::Update()
} }
} }
#ifdef FIX_BUGS
if ( !(psystem->Flags & SCREEN_TRAIL) && CTimer::GetLogicalFramesPassed()) // Fix particle over-expansion at high FPS
#else
if ( !(psystem->Flags & SCREEN_TRAIL) ) if ( !(psystem->Flags & SCREEN_TRAIL) )
#endif
{ {
float size; float size;
@ -1701,6 +1715,12 @@ void CParticle::Update()
} }
} }
#ifdef FIX_BUGS
// Keep particles animating, rotating, fading etc at the right speed
// at high or low FPS.
for (uint32 i=0; i<CTimer::GetLogicalFramesPassed(); i++)
#endif
{ // -- start FPS fix ---
if ( particle->m_nFadeToBlackTimer != 0 ) if ( particle->m_nFadeToBlackTimer != 0 )
{ {
particle->m_nColorIntensity = Clamp(particle->m_nColorIntensity - particle->m_nFadeToBlackTimer, particle->m_nColorIntensity = Clamp(particle->m_nColorIntensity - particle->m_nFadeToBlackTimer,
@ -1764,6 +1784,7 @@ void CParticle::Update()
#else #else
particle->m_nRotation += particle->m_nRotationStep; particle->m_nRotation += particle->m_nRotationStep;
#endif #endif
} // -- end FPS fix --
if ( particle->m_fCurrentZRadius != 0.0f ) if ( particle->m_fCurrentZRadius != 0.0f )
{ {
@ -1773,6 +1794,16 @@ void CParticle::Update()
float fY = (Sin(nSinCosIndex) + Cos(nSinCosIndex)) * particle->m_fCurrentZRadius; float fY = (Sin(nSinCosIndex) + Cos(nSinCosIndex)) * particle->m_fCurrentZRadius;
#ifdef FIX_BUGS
// Prevent super-fast movement at high FPS
// We can use GetTimeSetpFix() here instead of GetLogicalFramesPassed()
// so the visual result looks "smooth" on player's screens. We can't
// do this with most of the above Timers because the effects are stored
// as uint16's instead of floats, which means tiny changes each frame
// are often rounded down to zero change each frame.
fX *= CTimer::GetTimeStepFix();
fY *= CTimer::GetTimeStepFix();
#endif
vecPos -= particle->m_vecParticleMovementOffset; vecPos -= particle->m_vecParticleMovementOffset;
vecPos += CVector(fX, fY, 0.0f); vecPos += CVector(fX, fY, 0.0f);

@ -81,7 +81,7 @@ enum tParticleType
PARTICLE_TEST, PARTICLE_TEST,
PARTICLE_BIRD_FRONT, PARTICLE_BIRD_FRONT,
PARTICLE_SHIP_SIDE, PARTICLE_SHIP_SIDE,
PARTICLE_BEASTIE, PARTICLE_BEASTIE, // "Beasties" look like tree leaves circling up at canopy height, but are also used in other places (eg docks)
PARTICLE_RAINDROP_2D, PARTICLE_RAINDROP_2D,
PARTICLE_HEATHAZE, PARTICLE_HEATHAZE,
PARTICLE_HEATHAZE_IN_DIST, PARTICLE_HEATHAZE_IN_DIST,

@ -921,6 +921,18 @@ CWaterLevel::RenderWater()
if ( !CTimer::GetIsPaused() ) if ( !CTimer::GetIsPaused() )
{ {
#ifdef FIX_BUGS
// Stop the movement of the ocean speeding up at high FPS.
// Should be purely aesthetic, affecting only the texture UVs.
TEXTURE_ADDU += windAddUV * CTimer::GetTimeStepFix();
TEXTURE_ADDV += windAddUV * CTimer::GetTimeStepFix();
_TEXTURE_MASK_ADDU += (Sin(fAngle) * 0.0005f + 1.1f * windAddUV) * CTimer::GetTimeStepFix();
_TEXTURE_MASK_ADDV -= (Cos(fAngle * 1.3f) * 0.0005f + 1.2f * windAddUV) * CTimer::GetTimeStepFix();
_TEXTURE_WAKE_ADDU -= (Sin(fAngle) * 0.0003f + windAddUV) * CTimer::GetTimeStepFix();
_TEXTURE_WAKE_ADDV += (Cos(fAngle * 0.7f) * 0.0003f + windAddUV) * CTimer::GetTimeStepFix();
#else
TEXTURE_ADDU += windAddUV; TEXTURE_ADDU += windAddUV;
TEXTURE_ADDV += windAddUV; TEXTURE_ADDV += windAddUV;
@ -929,8 +941,10 @@ CWaterLevel::RenderWater()
_TEXTURE_WAKE_ADDU -= Sin(fAngle) * 0.0003f + windAddUV; _TEXTURE_WAKE_ADDU -= Sin(fAngle) * 0.0003f + windAddUV;
_TEXTURE_WAKE_ADDV += Cos(fAngle * 0.7f) * 0.0003f + windAddUV; _TEXTURE_WAKE_ADDV += Cos(fAngle * 0.7f) * 0.0003f + windAddUV;
#endif
} }
// What does this code do? Are the above equations sometimes unstable and make big numbers?
if ( _TEXTURE_MASK_ADDU >= 1.0f ) if ( _TEXTURE_MASK_ADDU >= 1.0f )
_TEXTURE_MASK_ADDU = 0.0f; _TEXTURE_MASK_ADDU = 0.0f;
if ( _TEXTURE_MASK_ADDV >= 1.0f ) if ( _TEXTURE_MASK_ADDV >= 1.0f )

@ -169,7 +169,11 @@ void CWeather::Update(void)
LightningFlash = false; LightningFlash = false;
LightningBurst = false; LightningBurst = false;
} }
#ifdef FIX_BUGS
else if (CTimer::GetLogicalFramesPassed()) {
#else
else{ else{
#endif
if (LightningBurst) { if (LightningBurst) {
if ((CGeneral::GetRandomNumber() & 255) >= 32) { if ((CGeneral::GetRandomNumber() & 255) >= 32) {
// 0.875 probability // 0.875 probability
@ -303,7 +307,11 @@ void CWeather::Update(void)
TrafficLightBrightness = Max(Foggyness, TrafficLightBrightness); TrafficLightBrightness = Max(Foggyness, TrafficLightBrightness);
TrafficLightBrightness = Max(Rain, TrafficLightBrightness); TrafficLightBrightness = Max(Rain, TrafficLightBrightness);
#ifdef FIX_BUGS
if (CTimer::GetLogicalFramesPassed()) AddRain();
#else
AddRain(); AddRain();
#endif
if ((NewWeatherType == WEATHER_SUNNY || NewWeatherType == WEATHER_EXTRA_SUNNY) && if ((NewWeatherType == WEATHER_SUNNY || NewWeatherType == WEATHER_EXTRA_SUNNY) &&
!CGame::IsInInterior() && !CCutsceneMgr::IsRunning() && (CTimer::GetFrameCounter() & 7) == 0) { !CGame::IsInInterior() && !CCutsceneMgr::IsRunning() && (CTimer::GetFrameCounter() & 7) == 0) {
@ -323,6 +331,11 @@ void CWeather::Update(void)
void CWeather::AddHeatHaze() void CWeather::AddHeatHaze()
{ {
#ifdef FIX_BUGS
// Fix particle spam at high FPS
if (!CTimer::GetLogicalFramesPassed())
return;
#endif
if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED)
return; return;
@ -338,6 +351,11 @@ void CWeather::AddHeatHaze()
void CWeather::AddBeastie() void CWeather::AddBeastie()
{ {
#ifdef FIX_BUGS
// Fix particle spam at high FPS. Beasties look like tree leaves.
if (!CTimer::GetLogicalFramesPassed())
return;
#endif
if(FindPlayerVehicle() || CTimer::GetFrameCounter()%10 || (CGeneral::GetRandomNumber()&5) == 0) if(FindPlayerVehicle() || CTimer::GetFrameCounter()%10 || (CGeneral::GetRandomNumber()&5) == 0)
return; return;
CVector pos = TheCamera.GetPosition(); CVector pos = TheCamera.GetPosition();

@ -235,6 +235,12 @@ CAutomobile::CAutomobile(int32 id, uint8 CreatedBy)
bExplosionProof = true; bExplosionProof = true;
bBulletProof = true; bBulletProof = true;
} }
#ifdef FIX_BUGS
// Probably not neccesary to zero these
m_nCarHornTimer = 0;
m_fCarHornTimeButtonLastHit = 0.0f;
#endif
} }
void void
@ -857,8 +863,12 @@ CAutomobile::ProcessControl(void)
(m_aSuspensionSpringRatio[1] < 1.0f || m_aSuspensionSpringRatio[3] < 1.0f)) (m_aSuspensionSpringRatio[1] < 1.0f || m_aSuspensionSpringRatio[3] < 1.0f))
ApplyTurnForce(-GRAVITY*Min(m_fTurnMass, 2500.0f)*GetUp(), -1.0f*GetForward()); ApplyTurnForce(-GRAVITY*Min(m_fTurnMass, 2500.0f)*GetUp(), -1.0f*GetForward());
} }
#ifdef FIX_BUGS
// Keep brake non-timestepped (so the later ProcessWheel() takes only non-timestepped inputs)
brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetDefaultTimeStep();
#else
brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep(); brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep();
#endif
bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING); bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING);
float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias; float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias;
float brakeBiasRear = neutralHandling ? 1.0f : 2.0f-pHandling->fBrakeBias; // looks like a bug, but it was correct in III... float brakeBiasRear = neutralHandling ? 1.0f : 2.0f-pHandling->fBrakeBias; // looks like a bug, but it was correct in III...
@ -1058,12 +1068,7 @@ CAutomobile::ProcessControl(void)
float rearBrake = brake; float rearBrake = brake;
float rearTraction = traction; float rearTraction = traction;
if(bIsHandbrakeOn){ if(bIsHandbrakeOn){
#ifdef FIX_BUGS
// Not sure if this is needed, but brake usually has timestep as a factor
rearBrake = 20000.0f * CTimer::GetTimeStepFix();
#else
rearBrake = 20000.0f; rearBrake = 20000.0f;
#endif
if(fwdSpeed > 0.1f && pHandling->Flags & HANDLING_HANDBRAKE_TYRE){ if(fwdSpeed > 0.1f && pHandling->Flags & HANDLING_HANDBRAKE_TYRE){
m_fTireTemperature += 0.005*CTimer::GetTimeStep(); m_fTireTemperature += 0.005*CTimer::GetTimeStep();
if(m_fTireTemperature > 2.0f) if(m_fTireTemperature > 2.0f)
@ -1072,8 +1077,11 @@ CAutomobile::ProcessControl(void)
}else if(m_doingBurnout && mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)){ }else if(m_doingBurnout && mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)){
rearBrake = 0.0f; rearBrake = 0.0f;
rearTraction = 0.0f; rearTraction = 0.0f;
// BUG: missing timestep #ifdef FIX_BUGS
ApplyTurnForce(contactPoints[CARWHEEL_REAR_LEFT], -0.001f*m_fTurnMass*m_fSteerAngle*GetRight()*CTimer::GetTimeStepFix());
#else
ApplyTurnForce(contactPoints[CARWHEEL_REAR_LEFT], -0.001f*m_fTurnMass*m_fSteerAngle*GetRight()); ApplyTurnForce(contactPoints[CARWHEEL_REAR_LEFT], -0.001f*m_fTurnMass*m_fSteerAngle*GetRight());
#endif
}else if(m_fTireTemperature > 1.0f){ }else if(m_fTireTemperature > 1.0f){
rearTraction *= m_fTireTemperature; rearTraction *= m_fTireTemperature;
} }
@ -1364,18 +1372,49 @@ CAutomobile::ProcessControl(void)
ReduceHornCounter(); ReduceHornCounter();
}else{ }else{
if(UsesSiren()){ if(UsesSiren()){
#ifdef FIX_BUGS
// Allow sirens to be toggled at high FPS
const float minPressTime = 100.0f; // milli-seconds
bool currentButtonState = Pads[0].bHornHistory[(CPad::HORNHISTORY_SIZE + Pads[0].iCurrHornHistory - 0) % CPad::HORNHISTORY_SIZE];
bool lastButtonState = Pads[0].bHornHistory[(CPad::HORNHISTORY_SIZE + Pads[0].iCurrHornHistory - 1) % CPad::HORNHISTORY_SIZE]; // Extra addition of CPad::HORNHISTORY_SIZE avoids modulo of negative numbers
if (currentButtonState && !lastButtonState)
{
// Horn button has just been hit
m_fCarHornTimeButtonLastHit = CTimer::GetTimeInMilliseconds();
}
else if (currentButtonState && lastButtonState)
{
// Horn button is being held down
if (CTimer::GetTimeInMilliseconds() - m_fCarHornTimeButtonLastHit >= minPressTime)
m_nCarHornTimer = 1; // enable horn
}
else if (!currentButtonState && lastButtonState)
{
// Horn button has just been released
m_nCarHornTimer = 0;
if (CTimer::GetTimeInMilliseconds() - m_fCarHornTimeButtonLastHit < minPressTime)
m_bSirenOrAlarm = !m_bSirenOrAlarm; // Toggle siren-like-feature
}
else
{
// Nothing pressed
m_nCarHornTimer = 0; // Should not be neccesary, but may as well keep in
}
#else
if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory]){ if(Pads[0].bHornHistory[Pads[0].iCurrHornHistory]){
if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+4) % 5] && if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+4) % CPad::HORNHISTORY_SIZE] &&
Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+3) % 5]) Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+3) % CPad::HORNHISTORY_SIZE])
m_nCarHornTimer = 1; m_nCarHornTimer = 1;
else else
m_nCarHornTimer = 0; m_nCarHornTimer = 0;
}else if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+4) % 5] && }else if(Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+4) % CPad::HORNHISTORY_SIZE] &&
!Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+1) % 5]){ !Pads[0].bHornHistory[(Pads[0].iCurrHornHistory+1) % CPad::HORNHISTORY_SIZE]){
m_nCarHornTimer = 0; m_nCarHornTimer = 0;
m_bSirenOrAlarm = !m_bSirenOrAlarm; m_bSirenOrAlarm = !m_bSirenOrAlarm;
}else }else
m_nCarHornTimer = 0; m_nCarHornTimer = 0;
#endif
}else if(GetModelIndex() != MI_VOODOO && !CVehicle::bCheat3 && !carHasNitro){ }else if(GetModelIndex() != MI_VOODOO && !CVehicle::bCheat3 && !carHasNitro){
if(!IsAlarmOn()){ if(!IsAlarmOn()){
if(Pads[0].GetHorn()) if(Pads[0].GetHorn())
@ -5738,8 +5777,14 @@ CAutomobile::ShowAllComps(void)
void void
CAutomobile::ReduceHornCounter(void) CAutomobile::ReduceHornCounter(void)
{ {
#ifdef FIX_BUGS
// Make horns last longer (only used by AI drivers?)
if(m_nCarHornTimer != 0 && CTimer::GetLogicalFramesPassed())
m_nCarHornTimer--;
#else
if(m_nCarHornTimer != 0) if(m_nCarHornTimer != 0)
m_nCarHornTimer--; m_nCarHornTimer--;
#endif
} }
void void

@ -722,7 +722,12 @@ CBike::ProcessControl(void)
acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat); acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat);
acceleration /= m_fForceMultiplier; acceleration /= m_fForceMultiplier;
#ifdef FIX_BUGS
// Keep timestep out of this, it gets added properly inside ProcessWheel() later. Fixes weak brakes at high FPS.
brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetDefaultTimeStep();
#else
brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep(); brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep();
#endif
bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING); bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING);
float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias; float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias;
float brakeBiasRear = neutralHandling ? 1.0f : 2.0f*(1.0f-pHandling->fBrakeBias); float brakeBiasRear = neutralHandling ? 1.0f : 2.0f*(1.0f-pHandling->fBrakeBias);
@ -882,12 +887,7 @@ CBike::ProcessControl(void)
wheelRight.Normalise(); wheelRight.Normalise();
if(bIsHandbrakeOn){ if(bIsHandbrakeOn){
#ifdef FIX_BUGS
// Not sure if this is needed, but brake usually has timestep as a factor
rearBrake = 20000.0f * CTimer::GetTimeStepFix();
#else
rearBrake = 20000.0f; rearBrake = 20000.0f;
#endif
m_fTireTemperature = 1.0f; m_fTireTemperature = 1.0f;
}else if(m_doingBurnout){ }else if(m_doingBurnout){
rearBrake = 0.0f; rearBrake = 0.0f;
@ -2941,8 +2941,14 @@ CBike::SetupModelNodes(void)
void void
CBike::ReduceHornCounter(void) CBike::ReduceHornCounter(void)
{ {
#ifdef FIX_BUGS
// Make horns last longer (only used by AI drivers?)
if(m_nCarHornTimer != 0 && CTimer::GetLogicalFramesPassed())
m_nCarHornTimer--;
#else
if(m_nCarHornTimer != 0) if(m_nCarHornTimer != 0)
m_nCarHornTimer--; m_nCarHornTimer--;
#endif
} }
#ifdef COMPATIBLE_SAVES #ifdef COMPATIBLE_SAVES

@ -263,9 +263,17 @@ CBoat::ProcessControl(void)
} }
// Damage particles // Damage particles
#ifdef FIX_BUGS
if(m_fHealth <= 460.0f && GetStatus() != STATUS_WRECKED &&
Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f &&
Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f &&
CTimer::GetLogicalFramesPassed() ) // Fix high-FPS particle spam
{
#else
if(m_fHealth <= 460.0f && GetStatus() != STATUS_WRECKED && if(m_fHealth <= 460.0f && GetStatus() != STATUS_WRECKED &&
Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f && Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f &&
Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){ Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){
#endif
float speedSq = m_vecMoveSpeed.MagnitudeSqr(); float speedSq = m_vecMoveSpeed.MagnitudeSqr();
CVector smokeDir = 0.8f*m_vecMoveSpeed; CVector smokeDir = 0.8f*m_vecMoveSpeed;
CVector smokePos; CVector smokePos;
@ -335,6 +343,7 @@ CBoat::ProcessControl(void)
bIsDrowning = false; bIsDrowning = false;
} }
// Apply buoyancy impulse the first time (why twice?)
m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater; m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater;
m_vecBuoyancePoint = buoyancePoint; m_vecBuoyancePoint = buoyancePoint;
if(GetModelIndex() == MI_SKIMMER && GetUp().z < -0.5f && Abs(m_vecMoveSpeed.x) < 0.2f && Abs(m_vecMoveSpeed.y) < 0.2f) if(GetModelIndex() == MI_SKIMMER && GetUp().z < -0.5f && Abs(m_vecMoveSpeed.x) < 0.2f && Abs(m_vecMoveSpeed.y) < 0.2f)
@ -344,13 +353,17 @@ CBoat::ProcessControl(void)
if(bSeparateTurnForce) if(bSeparateTurnForce)
ApplyTurnForce(0.4f*buoyanceImpulse, buoyancePoint); ApplyTurnForce(0.4f*buoyanceImpulse, buoyancePoint);
// TODO: what is this?
if(GetModelIndex() == MI_SKIMMER) if(GetModelIndex() == MI_SKIMMER)
if(m_skimmerThingTimer != 0.0f || if(m_skimmerThingTimer != 0.0f ||
GetForward().z < -0.5f && GetUp().z > -0.5f && m_vecMoveSpeed.z < -0.15f && GetForward().z < -0.5f && GetUp().z > -0.5f && m_vecMoveSpeed.z < -0.15f &&
buoyanceImpulse.z > 0.01f*m_fMass * GRAVITY*CTimer::GetTimeStep() && buoyanceImpulse.z > 0.01f*m_fMass * GRAVITY*CTimer::GetTimeStep() &&
buoyanceImpulse.z < 0.4f*m_fMass * GRAVITY*CTimer::GetTimeStep()){ buoyanceImpulse.z < 0.4f*m_fMass * GRAVITY*CTimer::GetTimeStep()){
#ifdef FIX_BUGS
// buoyanceImpulse already has a factor of timestep in it
float turnImpulse = -0.00017f*GetForward().z*buoyanceImpulse.z * m_fMass*CTimer::GetDefaultTimeStep();
#else
float turnImpulse = -0.00017f*GetForward().z*buoyanceImpulse.z * m_fMass*CTimer::GetTimeStep(); float turnImpulse = -0.00017f*GetForward().z*buoyanceImpulse.z * m_fMass*CTimer::GetTimeStep();
#endif
ApplyTurnForce(turnImpulse*GetForward(), GetUp()); ApplyTurnForce(turnImpulse*GetForward(), GetUp());
bBoatInWater = false; bBoatInWater = false;
//BUG? aren't we forgetting the timestep here? //BUG? aren't we forgetting the timestep here?
@ -362,15 +375,21 @@ CBoat::ProcessControl(void)
m_skimmerThingTimer = 0.0f; m_skimmerThingTimer = 0.0f;
} }
// Apply buoyancy impulse the second time (why twice?)
if(!onLand && bBoatInWater && GetUp().z > 0.0f){ if(!onLand && bBoatInWater && GetUp().z > 0.0f){
#ifdef FIX_BUGS
// buoyanceImpulse already has a factor of timestep in it
float impulse = m_vecMoveSpeed.MagnitudeSqr()*pBoatHandling->fAqPlaneForce*buoyanceImpulse.z*CTimer::GetDefaultTimeStep()*0.5f;
#else
float impulse = m_vecMoveSpeed.MagnitudeSqr()*pBoatHandling->fAqPlaneForce*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f; float impulse = m_vecMoveSpeed.MagnitudeSqr()*pBoatHandling->fAqPlaneForce*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f;
#endif
if(GetModelIndex() == MI_SKIMMER) if(GetModelIndex() == MI_SKIMMER)
impulse *= 1.0f + m_fGasPedal; impulse *= 1.0f + m_fGasPedal;
else if(m_fGasPedal > 0.05f) else if(m_fGasPedal > 0.05f)
impulse *= m_fGasPedal; impulse *= m_fGasPedal;
else else
impulse = 0.0f; impulse = 0.0f;
impulse = Min(impulse, GRAVITY*pBoatHandling->fAqPlaneLimit*m_fMass*CTimer::GetTimeStep()); impulse = Min(impulse, GRAVITY*pBoatHandling->fAqPlaneLimit*m_fMass*CTimer::GetTimeStep()); // Both sides have a factor of TimeStep, therefore this Min() is not a problem at high FPS
ApplyMoveForce(impulse*GetUp()); ApplyMoveForce(impulse*GetUp());
ApplyTurnForce(impulse*GetUp(), buoyancePoint - pBoatHandling->fAqPlaneOffset*GetForward()); ApplyTurnForce(impulse*GetUp(), buoyancePoint - pBoatHandling->fAqPlaneOffset*GetForward());
} }
@ -378,7 +397,11 @@ CBoat::ProcessControl(void)
// Handle boat moving forward // Handle boat moving forward
float fwdSpeed = 1.0f; float fwdSpeed = 1.0f;
if(Abs(m_fGasPedal) > 0.05f || (fwdSpeed = m_vecMoveSpeed.Magnitude2D()) > 0.01f){ if(Abs(m_fGasPedal) > 0.05f || (fwdSpeed = m_vecMoveSpeed.Magnitude2D()) > 0.01f){
#ifdef FIX_BUGS
if(bBoatInWater && fwdSpeed > 0.05f && CTimer::GetLogicalFramesPassed()) // Fix super-short wake trail at high FPS
#else
if(bBoatInWater && fwdSpeed > 0.05f) if(bBoatInWater && fwdSpeed > 0.05f)
#endif
AddWakePoint(GetPosition()); AddWakePoint(GetPosition());
float steerFactor = 1.0f; float steerFactor = 1.0f;
@ -433,7 +456,11 @@ CBoat::ProcessControl(void)
// Spray some particles // Spray some particles
CVector jetDir = -0.04f * force; CVector jetDir = -0.04f * force;
#ifdef FIX_BUGS
if(m_fGasPedal > 0.0f && CTimer::GetLogicalFramesPassed()){ // Fix high-FPS particle spam
#else
if(m_fGasPedal > 0.0f){ if(m_fGasPedal > 0.0f){
#endif
if(GetStatus() == STATUS_PLAYER){ if(GetStatus() == STATUS_PLAYER){
CVector sternPos = GetColModel()->boundingBox.min; CVector sternPos = GetColModel()->boundingBox.min;
sternPos.x = 0.0f; sternPos.x = 0.0f;
@ -475,7 +502,11 @@ CBoat::ProcessControl(void)
CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f)); CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f));
ApplyMoveForce(propellerForce * CTimer::GetTimeStep()); ApplyMoveForce(propellerForce * CTimer::GetTimeStep());
ApplyTurnForce(propellerForce * CTimer::GetTimeStep(), propeller); ApplyTurnForce(propellerForce * CTimer::GetTimeStep(), propeller);
#ifdef FIX_BUGS
float rightForce = -steerSin * force * 0.75f/steerFactor * CTimer::GetTimeStep();
#else
float rightForce = -steerSin * force * 0.75f/steerFactor * Max(CTimer::GetTimeStep(), 0.01f); float rightForce = -steerSin * force * 0.75f/steerFactor * Max(CTimer::GetTimeStep(), 0.01f);
#endif
ApplyTurnForce(GetRight() * rightForce, GetUp()); ApplyTurnForce(GetRight() * rightForce, GetUp());
} }
}else }else
@ -485,7 +516,12 @@ CBoat::ProcessControl(void)
CVector right = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f)); CVector right = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f));
float rightSpeed = DotProduct(m_vecMoveSpeed, right); float rightSpeed = DotProduct(m_vecMoveSpeed, right);
float impulse = 0.1f*pHandling->fSuspensionBias * m_fMass * m_fVolumeUnderWater * rightSpeed * CTimer::GetTimeStep(); float impulse = 0.1f*pHandling->fSuspensionBias * m_fMass * m_fVolumeUnderWater * rightSpeed * CTimer::GetTimeStep();
#ifdef FIX_BUGS
// Fix boat perf at high FPS: ensure both terms have a (correct) factor of timestep before doing subtraction
ApplyMoveForce(right * CTimer::GetTimeStepFix() - impulse * 0.3f * CVector(-right.y, right.x, 0.0f));
#else
ApplyMoveForce(right - impulse * 0.3f * CVector(-right.y, right.x, 0.0f)); ApplyMoveForce(right - impulse * 0.3f * CVector(-right.y, right.x, 0.0f));
#endif
} }
if(GetStatus() == STATUS_PLAYER && CPad::GetPad(0)->GetHandBrake()){ if(GetStatus() == STATUS_PLAYER && CPad::GetPad(0)->GetHandBrake()){
@ -503,25 +539,45 @@ CBoat::ProcessControl(void)
m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MAX_Y-100.0f))*0.01f); // north m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MAX_Y-100.0f))*0.01f); // north
m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MIN_Y+100.0f))*0.01f); // south m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MIN_Y+100.0f))*0.01f); // south
// Apply water resistance to linear movement
if(!onLand && bBoatInWater && !bSeparateTurnForce) if(!onLand && bBoatInWater && !bSeparateTurnForce)
ApplyWaterResistance(); ApplyWaterResistance();
// Apply water resistance to rotation
if((GetModelIndex() != MI_SKIMMER || m_skimmerThingTimer == 0.0f) && !bSeparateTurnForce){ if((GetModelIndex() != MI_SKIMMER || m_skimmerThingTimer == 0.0f) && !bSeparateTurnForce){
// No idea what exactly is going on here besides drag in YZ #ifdef FIX_BUGS
// Rockstar's attempts to make this framerate independent are totally wrong.
// Rules of thumb:
// - use Pow(x,time) if you multiply the result into the velocity
// - use x*time if you add the result into the velocity
// We have to disable one of these Pow() methods and then add our own correction at the end.
float fxfake = Pow(pBoatHandling->vecTurnRes.x, CTimer::GetDefaultTimeStep());
float fy = Pow(pBoatHandling->vecTurnRes.y, CTimer::GetTimeStep());
float fz = Pow(pBoatHandling->vecTurnRes.z, CTimer::GetTimeStep());
m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space
float drag = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fxfake;
m_vecTurnSpeed.y *= fy;
m_vecTurnSpeed.z *= fz;
float forceUp = -(1.0f - drag) * m_vecTurnSpeed.x * m_fTurnMass;
m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world
CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
ApplyTurnForce(forceUp*GetUp() * CTimer::GetTimeStepFix(), com + GetForward());
#else
float fx = Pow(pBoatHandling->vecTurnRes.x, CTimer::GetTimeStep()); float fx = Pow(pBoatHandling->vecTurnRes.x, CTimer::GetTimeStep());
float fy = Pow(pBoatHandling->vecTurnRes.y, CTimer::GetTimeStep()); float fy = Pow(pBoatHandling->vecTurnRes.y, CTimer::GetTimeStep());
float fz = Pow(pBoatHandling->vecTurnRes.z, CTimer::GetTimeStep()); float fz = Pow(pBoatHandling->vecTurnRes.z, CTimer::GetTimeStep());
m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); // invert - to local space
// TODO: figure this out float drag = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx;
float magic = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx;
m_vecTurnSpeed.y *= fy; m_vecTurnSpeed.y *= fy;
m_vecTurnSpeed.z *= fz; m_vecTurnSpeed.z *= fz;
float forceUp = (magic - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass; float forceUp = (drag - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass;
m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed); // back to world
CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass); CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
ApplyTurnForce(forceUp*GetUp(), com + GetForward()); ApplyTurnForce(forceUp*GetUp(), com + GetForward());
#endif
} }
m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000; m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000;
// Falling into water // Falling into water
@ -539,6 +595,9 @@ CBoat::ProcessControl(void)
speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fBrakeBias; speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fBrakeBias;
CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp); CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp);
CVector splashImpulse = speed * m_fMass; CVector splashImpulse = speed * m_fMass;
#ifdef FIX_BUGS
splashImpulse *= CTimer::GetTimeStepFix();
#endif
ApplyMoveForce(splashImpulse); ApplyMoveForce(splashImpulse);
ApplyTurnForce(splashImpulse, buoyancePoint); ApplyTurnForce(splashImpulse, buoyancePoint);
} }
@ -546,8 +605,15 @@ CBoat::ProcessControl(void)
// Splashes // Splashes
float speed = m_vecMoveSpeed.Magnitude(); float speed = m_vecMoveSpeed.Magnitude();
#ifdef FIX_BUGS
if(speed > 0.05f && GetUp().x > 0.0f && !TheCamera.GetLookingForwardFirstPerson() && IsVisible() &&
(AutoPilot.m_nCarMission != MISSION_CRUISE || (CTimer::GetFrameCounter()&2) == 0) &&
CTimer::GetLogicalFramesPassed() ) // Fix particle spam at high FPS
#else
if(speed > 0.05f && GetUp().x > 0.0f && !TheCamera.GetLookingForwardFirstPerson() && IsVisible() && if(speed > 0.05f && GetUp().x > 0.0f && !TheCamera.GetLookingForwardFirstPerson() && IsVisible() &&
(AutoPilot.m_nCarMission != MISSION_CRUISE || (CTimer::GetFrameCounter()&2) == 0)){ (AutoPilot.m_nCarMission != MISSION_CRUISE || (CTimer::GetFrameCounter()&2) == 0))
#endif
{
CVector splashPos, splashDir; CVector splashPos, splashDir;
float splashSize, front, waterLevel; float splashSize, front, waterLevel;
@ -682,8 +748,14 @@ CBoat::ProcessControl(void)
} }
// Spray waterdrops on screen // Spray waterdrops on screen
#ifdef FIX_BUGS
if(TheCamera.GetLookingForwardFirstPerson() && FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() && if(TheCamera.GetLookingForwardFirstPerson() && FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() &&
m_nDeltaVolumeUnderWater > 0 && numWaterDropOnScreen < 20){ m_nDeltaVolumeUnderWater > 0 && numWaterDropOnScreen < 20 && CTimer::GetLogicalFramesPassed()) // Fix particle spam at high FPS
#else
if(TheCamera.GetLookingForwardFirstPerson() && FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() &&
m_nDeltaVolumeUnderWater > 0 && numWaterDropOnScreen < 20)
#endif
{
CVector dropPos; CVector dropPos;
CVector dropDir(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), CGeneral::GetRandomNumberInRange(1.0f, 0.75f), 0.0f); CVector dropDir(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), CGeneral::GetRandomNumberInRange(1.0f, 0.75f), 0.0f);
@ -714,7 +786,13 @@ CBoat::ProcessControl(void)
numWaterDropOnScreen++; numWaterDropOnScreen++;
} }
if(m_fPrevVolumeUnderWater == 0.0f && m_fVolumeUnderWater > 0.0f && GetModelIndex() == MI_SKIMMER){ #ifdef FIX_BUGS
if(m_fPrevVolumeUnderWater == 0.0f && m_fVolumeUnderWater > 0.0f && GetModelIndex() == MI_SKIMMER &&
CTimer::GetLogicalFramesPassed()) // Fix particle spam at high FPS
#else
if(m_fPrevVolumeUnderWater == 0.0f && m_fVolumeUnderWater > 0.0f && GetModelIndex() == MI_SKIMMER)
#endif
{
CVector splashDir(0.0f, 0.0f, 0.25f*speed); CVector splashDir(0.0f, 0.0f, 0.25f*speed);
CVector splashPos = GetPosition(); CVector splashPos = GetPosition();
float level; float level;
@ -773,6 +851,18 @@ CBoat::ProcessControlInputs(uint8 pad)
m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f; m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f;
m_fBrake = Clamp(m_fBrake, 0.0f, 1.0f); m_fBrake = Clamp(m_fBrake, 0.0f, 1.0f);
#ifdef FIX_BUGS
// Fix accelerator control and steering control ramp rates at high FPS
if(m_fBrake < 0.05f){
m_fBrake = 0.0f;
m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f*CTimer::GetTimeStepFix();
m_fAccelerate = Clamp(m_fAccelerate, 0.0f, 1.0f);
}else
m_fAccelerate = -m_fBrake*0.3f;
m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f*CTimer::GetTimeStepFix();
m_fSteeringLeftRight = Clamp(m_fSteeringLeftRight, -1.0f, 1.0f);
#else
if(m_fBrake < 0.05f){ if(m_fBrake < 0.05f){
m_fBrake = 0.0f; m_fBrake = 0.0f;
m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f; m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f;
@ -782,6 +872,7 @@ CBoat::ProcessControlInputs(uint8 pad)
m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f; m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f;
m_fSteeringLeftRight = Clamp(m_fSteeringLeftRight, -1.0f, 1.0f); m_fSteeringLeftRight = Clamp(m_fSteeringLeftRight, -1.0f, 1.0f);
#endif
float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight); float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight);
m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq); m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq);
@ -793,6 +884,82 @@ float fSeaPlaneWaterResistance = 30.0f;
void void
CBoat::ApplyWaterResistance(void) CBoat::ApplyWaterResistance(void)
{ {
#ifdef FIX_BUGS
// TODO: confirm if the explanation below makes sense
float depthResistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass;
if(GetModelIndex() == MI_SKIMMER)
depthResistance *= fSeaPlaneWaterResistance;
float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward());
// Water resistances goes up with the square of boat speed (in real life it goes up by the
// cube? close enough!). An extra 0.05f fudge factor was probably put in to make sure the
// boat still has resistance at low speeds (ie auto-brakes to standstill).
float waterResistance = (SQR(fwdSpeed) + 0.05f) * Abs(depthResistance); // Abs() used defensively, negative numbers stuff things up later
// waterResistance will now be a small number like 0.002 or 0.015
// An odd use of Abs() was in the original binary. It's possible that the developers did not
// put this in intentionally, instead the compiler may have silently added it to avoid
// Pow() having to deal with negative numbers to a fractional power (undefined) later.
// Regardless it was done badly, making assumptions like vecMoveRes never accidentally
// being negative, so the use of Abs() has changed a little bit in this FIX_BUGS. In
// real gameplay these corner cases should rarely (never?) be encountered anyway.
// Our boat has different water resistances when travelling forwards (y axis) versus
// sideways (x axis). Boats tend to find it hard to move sideways.
float rx = Abs(pBoatHandling->vecMoveRes.x/(waterResistance + 1.0f)); // Abs() used defensively, negative numbers stuff things up later
float ry = Abs(pBoatHandling->vecMoveRes.y/(waterResistance + 1.0f));
float rz = Abs(pBoatHandling->vecMoveRes.z/(waterResistance + 1.0f));
// These rx, ry, rz resistance numbers will each be something like 0.8 or 0.9 or so
// Fun fact: the above equations are _approximately_ the same as:
//
// pBoatHandling->vecMoveRes.x * (1.0f - waterResistance)
//
// If you change the equations to this then boating feels about the same in-game.
// Which version makes more sense compared to physics in real life? Probably neither :P
// This second version of the equation is a little more efficient however (no division).
// Now how do we apply these rx ry rz resistance numbers to the boat speed?
// It's not simple:
//
// - We can't multiply them into the speed once per frame, because then players with
// higher framerates will get a lot more friction when boating (lower top speed).
//
// - We can't linearly modify each r number based off frametime, as higher FPS players
// will still end up with more friction. This is for the same reason why linearly
// reducing your bank account's yearly interest into monthly amounts but then
// compounding it monthly will yield you more money than just compounding it yearly.
//
// - We could try compounding each r number based off how many fixed units of time have
// passed (eg multiply itself by itself for every 1ms elapsed this frame). This will
// work fairly regardless of framerate.
//
// We don't actually have to limit ourselves to a fixed time unit (like 1ms chunks),
// instead we can raise the resistance to some power of time using Pow().
float rrx = Pow(rx, 0.5f*CTimer::GetTimeStep()); // Why 0.5f? Taste?
float rry = Pow(ry, 0.5f*CTimer::GetTimeStep());
float rrz = Pow(rz, 0.5f*CTimer::GetTimeStep());
float rryfake = Pow(ry, 0.5f*CTimer::GetDefaultTimeStep()); // Fudge factor needed for other equations to make sense at high FPS
m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix()); // convert velocities to boat-local space (y = boat forwards, x = sideways, z = up/down)
m_vecMoveSpeed.x *= rrx;
m_vecMoveSpeed.y *= rry;
m_vecMoveSpeed.z *= rrz;
float force = (rryfake - 1.0f) * m_vecMoveSpeed.y * m_fMass;
m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed); // back to world space
// WTH is this for?
ApplyTurnForce(force*GetForward()*CTimer::GetTimeStepFix(), -GetUp());
// What the hell? Why arbitrarily compound in one more factor of rrz?
// This is framerate dependent! Bah! I suspect it has very little effect.
/*
if(m_vecMoveSpeed.z > 0.0f)
m_vecMoveSpeed.z *= rrz;
else
m_vecMoveSpeed.z *= (1.0f - rrz)*0.5f + rrz;
*/
#else
// TODO: figure out how this works // TODO: figure out how this works
float resistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass; float resistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass;
if(GetModelIndex() == MI_SKIMMER) if(GetModelIndex() == MI_SKIMMER)
@ -817,6 +984,7 @@ CBoat::ApplyWaterResistance(void)
m_vecMoveSpeed.z *= fz; m_vecMoveSpeed.z *= fz;
else else
m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz; m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz;
#endif
} }
RwObject* RwObject*

@ -109,6 +109,17 @@ cBuoyancy::ProcessBuoyancyBoat(CVehicle *veh, float buoyancy, CVector *point, CV
float volume = SimpleSumBuoyancyData(waterLevel, waterPosition); float volume = SimpleSumBuoyancyData(waterLevel, waterPosition);
float upImpulse = volume * volDiv * buoyancy * CTimer::GetTimeStep(); float upImpulse = volume * volDiv * buoyancy * CTimer::GetTimeStep();
CVector speed = veh->GetSpeed(Multiply3x3(veh->GetMatrix(), CVector(x, y, 0.0f))); CVector speed = veh->GetSpeed(Multiply3x3(veh->GetMatrix(), CVector(x, y, 0.0f)));
#ifdef FIX_BUGS
// "GetSpeed" seems to depend on framerate (bad).
// This ruins boat performance at high FPS. Approximate sequence of events:
// - "speed" goes tiny at high FPS
// - "damp" goes high as a result
// - high dampening reduces the buoyancy forces that keep the boat above the water
// - boats sit _slightly_ lower in water (you have to disable ALL waves to see this, it's a very small change)
// - all of boat handling performance changes because a different amount of the boat is underwater
// Finding this was a PITA!
speed /= CTimer::GetTimeStepFix();
#endif
float damp = 1.0f - DotProduct(speed, waterNormal)*veh->pHandling->fSuspensionDampingLevel; float damp = 1.0f - DotProduct(speed, waterNormal)*veh->pHandling->fSuspensionDampingLevel;
float finalImpulse = upImpulse*Max(damp, 0.0f); float finalImpulse = upImpulse*Max(damp, 0.0f);
impulse->z += finalImpulse; impulse->z += finalImpulse;

@ -55,6 +55,8 @@ cTransmission::CalculateGearForSimpleCar(float speed, uint8 &gear)
} }
} }
/* This function causes 'pulsing' of throttle when reversing at max speed. You
* can most easily observe this when the framelimiter is enabled.*/
float float
cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat) cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat)
{ {
@ -123,7 +125,12 @@ cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, fl
float targetVelocity = Gears[gear].fMaxVelocity*speedMul*fCheat; float targetVelocity = Gears[gear].fMaxVelocity*speedMul*fCheat;
float accel = (targetVelocity - fVelocity) * (fEngineAcceleration*accelMul) / Abs(targetVelocity); float accel = (targetVelocity - fVelocity) * (fEngineAcceleration*accelMul) / Abs(targetVelocity);
if(Abs(fVelocity) < Abs(Gears[gear].fMaxVelocity*fCheat)) if(Abs(fVelocity) < Abs(Gears[gear].fMaxVelocity*fCheat))
#ifdef FIX_BUGS
// The acceleration provided by a transmission+engine should not depend on framelength.
fAcceleration = gasPedal * accel;
#else
fAcceleration = gasPedal * accel * CTimer::GetTimeStep(); fAcceleration = gasPedal * accel * CTimer::GetTimeStep();
#endif
else else
fAcceleration = 0.0f; fAcceleration = 0.0f;
return fAcceleration; return fAcceleration;

@ -126,6 +126,9 @@ CVehicle::CVehicle(uint8 CreatedBy)
m_nCarHornTimer = 0; m_nCarHornTimer = 0;
m_nCarHornPattern = 0; m_nCarHornPattern = 0;
m_nCarHornDelay = 0; m_nCarHornDelay = 0;
#ifdef FIX_BUGS
m_fCarHornTimeButtonLastHit = 0.0f;
#endif
bPartOfConvoy = false; bPartOfConvoy = false;
bHeliMinimumTilt = false; bHeliMinimumTilt = false;
bAudioChangingGear = false; bAudioChangingGear = false;
@ -788,7 +791,14 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
bAlreadySkidding = false; bAlreadySkidding = false;
#endif #endif
// how much force we want to apply in these axes // Velocity impulses fwd and right. Units are like meters per second.
// This function was written assuming a fixed FPS, so a correction is made
// right at the end before actually applying these impulses.
//
// Note that many functions in this engine deal with "force impulses" rather
// than "velocity impulses". They are directly related: F=ma. It is
// possible that the original devs actually used force impulses here but
// an optimising compiler re-arranged their maths.
float fwd = 0.0f; float fwd = 0.0f;
float right = 0.0f; float right = 0.0f;
@ -804,7 +814,12 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
bAlreadySkidding = true; bAlreadySkidding = true;
*wheelState = WHEEL_STATE_NORMAL; *wheelState = WHEEL_STATE_NORMAL;
#ifdef FIX_BUGS
// Everything else here is timestep independent, let's stay with this theme to keep the rest of the FPS bugfixes simpler.
adhesion *= CTimer::GetDefaultTimeStep();
#else
adhesion *= CTimer::GetTimeStep(); adhesion *= CTimer::GetTimeStep();
#endif
if(bAlreadySkidding) if(bAlreadySkidding)
adhesion *= pHandling->fTractionLoss; adhesion *= pHandling->fTractionLoss;
@ -812,12 +827,6 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
if(contactSpeedRight != 0.0f){ if(contactSpeedRight != 0.0f){
// exert opposing force // exert opposing force
right = -contactSpeedRight/wheelsOnGround; right = -contactSpeedRight/wheelsOnGround;
// BUG?
// contactSpeedRight is independent of framerate but right has timestep as a factor
// so we probably have to fix this
// fixing this causes jittery cars at 15fps, and causes the car to move backwards slowly at 18fps
// at 19fps, the effects are gone ...
//right *= CTimer::GetTimeStepFix();
if(wheelStatus == WHEEL_STATUS_BURST){ if(wheelStatus == WHEEL_STATUS_BURST){
float fwdspeed = Min(contactSpeedFwd, fBurstSpeedMax); float fwdspeed = Min(contactSpeedFwd, fBurstSpeedMax);
@ -828,7 +837,7 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
if(bDriving){ if(bDriving){
fwd = thrust; fwd = thrust;
// limit sideways force (why?) // Limit sideways forces applied by the tires to the max the tires can possibly do.
if(right > 0.0f){ if(right > 0.0f){
if(right > adhesion) if(right > adhesion)
right = adhesion; right = adhesion;
@ -838,11 +847,6 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
} }
}else if(contactSpeedFwd != 0.0f){ }else if(contactSpeedFwd != 0.0f){
fwd = -contactSpeedFwd/wheelsOnGround; fwd = -contactSpeedFwd/wheelsOnGround;
#ifdef FIX_BUGS
// contactSpeedFwd is independent of framerate but fwd has timestep as a factor
// so we probably have to fix this
fwd *= CTimer::GetTimeStepFix();
#endif
if(!bBraking){ if(!bBraking){
if(m_fGasPedal < 0.01f){ if(m_fGasPedal < 0.01f){
@ -854,9 +858,6 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass; brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass;
else else
brake = mod_HandlingManager.fWheelFriction / pHandling->fMass; brake = mod_HandlingManager.fWheelFriction / pHandling->fMass;
#ifdef FIX_BUGS
brake *= CTimer::GetTimeStepFix();
#endif
} }
} }
@ -883,10 +884,10 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
*wheelState = WHEEL_STATE_SKIDDING; *wheelState = WHEEL_STATE_SKIDDING;
} }
float l = Sqrt(speedSq); float speed = Sqrt(speedSq);
float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss; float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss;
right *= adhesion * tractionLoss / l; right *= adhesion * tractionLoss / speed;
fwd *= adhesion * tractionLoss / l; fwd *= adhesion * tractionLoss / speed;
} }
if(fwd != 0.0f || right != 0.0f){ if(fwd != 0.0f || right != 0.0f){
@ -918,9 +919,19 @@ CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelCon
else else
turnDirection = direction; turnDirection = direction;
// Curious: there is a perfectly good ApplyMoveSpeed() function that
// takes "velocity impulses", but instead we use the ApplyMoveForce()
// function which takes a "force impulse" instead and then fix up the
// difference by multiplying in the vehicle mass (F=ma). Possibly
// evidence that an optimising compiler re-arranged the arithmetic?
float impulse = speed*m_fMass; float impulse = speed*m_fMass;
float turnImpulse = turnSpeed*GetMass(wheelContactPoint, turnDirection); float turnImpulse = turnSpeed*GetMass(wheelContactPoint, turnDirection);
#ifdef FIX_BUGS
impulse = impulse * CTimer::GetTimeStepFix();
turnImpulse = turnImpulse * CTimer::GetTimeStepFix();
#endif
ApplyMoveForce(impulse * direction); ApplyMoveForce(impulse * direction);
ApplyTurnForce(turnImpulse * turnDirection, wheelContactPoint); ApplyTurnForce(turnImpulse * turnDirection, wheelContactPoint);
} }
@ -944,7 +955,14 @@ CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &whee
bAlreadySkidding = false; bAlreadySkidding = false;
#endif #endif
// how much force we want to apply in these axes // Velocity impulses fwd and right. Units are like meters per second.
// This function was written assuming a fixed FPS, so a correction is made
// right at the end before actually applying these impulses.
//
// Note that many functions in this engine deal with "force impulses" rather
// than "velocity impulses". They are directly related: F=ma. It is
// possible that the original devs actually used force impulses here but
// an optimising compiler re-arranged their maths.
float fwd = 0.0f; float fwd = 0.0f;
float right = 0.0f; float right = 0.0f;
@ -961,7 +979,12 @@ CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &whee
bAlreadySkidding = true; bAlreadySkidding = true;
*wheelState = WHEEL_STATE_NORMAL; *wheelState = WHEEL_STATE_NORMAL;
#ifdef FIX_BUGS
// Everything else here is timestep independent, let's stay with this theme to keep the rest of the FPS bugfixes simpler.
adhesion *= CTimer::GetDefaultTimeStep();
#else
adhesion *= CTimer::GetTimeStep(); adhesion *= CTimer::GetTimeStep();
#endif
if(bAlreadySkidding) if(bAlreadySkidding)
adhesion *= pHandling->fTractionLoss; adhesion *= pHandling->fTractionLoss;
@ -974,11 +997,6 @@ CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &whee
if(contactSpeedRight != 0.0f){ if(contactSpeedRight != 0.0f){
// exert opposing force // exert opposing force
right = -contactSpeedRight/wheelsOnGround; right = -contactSpeedRight/wheelsOnGround;
#ifdef FIX_BUGS
// contactSpeedRight is independent of framerate but right has timestep as a factor
// so we probably have to fix this
right *= CTimer::GetTimeStepFix();
#endif
if(wheelStatus == WHEEL_STATUS_BURST){ if(wheelStatus == WHEEL_STATUS_BURST){
float fwdspeed = Min(contactSpeedFwd, fBurstBikeSpeedMax); float fwdspeed = Min(contactSpeedFwd, fBurstBikeSpeedMax);
@ -999,12 +1017,6 @@ CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &whee
} }
}else if(contactSpeedFwd != 0.0f){ }else if(contactSpeedFwd != 0.0f){
fwd = -contactSpeedFwd/wheelsOnGround; fwd = -contactSpeedFwd/wheelsOnGround;
#ifdef FIX_BUGS
// contactSpeedFwd is independent of framerate but fwd has timestep as a factor
// so we probably have to fix this
fwd *= CTimer::GetTimeStepFix();
#endif
if(!bBraking){ if(!bBraking){
if(m_fGasPedal < 0.01f){ if(m_fGasPedal < 0.01f){
if(IsBike()) if(IsBike())
@ -1015,9 +1027,6 @@ CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &whee
brake = 0.2f * mod_HandlingManager.fWheelFriction / m_fMass; brake = 0.2f * mod_HandlingManager.fWheelFriction / m_fMass;
else else
brake = mod_HandlingManager.fWheelFriction / m_fMass; brake = mod_HandlingManager.fWheelFriction / m_fMass;
#ifdef FIX_BUGS
brake *= CTimer::GetTimeStepFix();
#endif
} }
} }
@ -1068,6 +1077,10 @@ CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &whee
float impulse = speed*m_fMass; float impulse = speed*m_fMass;
float turnImpulse = speed*GetMass(wheelContactPoint, direction); float turnImpulse = speed*GetMass(wheelContactPoint, direction);
#ifdef FIX_BUGS
impulse = impulse * CTimer::GetTimeStepFix();
turnImpulse = turnImpulse * CTimer::GetTimeStepFix();
#endif
CVector vTurnImpulse = turnImpulse * direction; CVector vTurnImpulse = turnImpulse * direction;
ApplyMoveForce(impulse * direction); ApplyMoveForce(impulse * direction);

@ -273,6 +273,9 @@ public:
uint8 m_nCarHornPattern; uint8 m_nCarHornPattern;
bool m_bSirenOrAlarm; bool m_bSirenOrAlarm;
uint8 m_nCarHornDelay; uint8 m_nCarHornDelay;
#ifdef FIX_BUGS
float m_fCarHornTimeButtonLastHit;
#endif
int8 m_comedyControlState; int8 m_comedyControlState;
CStoredCollPoly m_aCollPolys[2]; // poly which is under front/rear part of car CStoredCollPoly m_aCollPolys[2]; // poly which is under front/rear part of car
float m_fSteerInput; float m_fSteerInput;