@ -263,9 +263,17 @@ CBoat::ProcessControl(void)
}
// 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 & &
Abs ( GetPosition ( ) . x - TheCamera . GetPosition ( ) . x ) < 200.0f & &
Abs ( GetPosition ( ) . y - TheCamera . GetPosition ( ) . y ) < 200.0f ) {
# endif
float speedSq = m_vecMoveSpeed . MagnitudeSqr ( ) ;
CVector smokeDir = 0.8f * m_vecMoveSpeed ;
CVector smokePos ;
@ -335,6 +343,7 @@ CBoat::ProcessControl(void)
bIsDrowning = false ;
}
// Apply buoyancy impulse the first time (why twice?)
m_fVolumeUnderWater = mod_Buoyancy . m_volumeUnderWater ;
m_vecBuoyancePoint = buoyancePoint ;
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 )
ApplyTurnForce ( 0.4f * buoyanceImpulse , buoyancePoint ) ;
// TODO: what is this?
if ( GetModelIndex ( ) = = MI_SKIMMER )
if ( m_skimmerThingTimer ! = 0.0f | |
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.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 ( ) ;
# endif
ApplyTurnForce ( turnImpulse * GetForward ( ) , GetUp ( ) ) ;
bBoatInWater = false ;
//BUG? aren't we forgetting the timestep here?
@ -362,15 +375,21 @@ CBoat::ProcessControl(void)
m_skimmerThingTimer = 0.0f ;
}
// Apply buoyancy impulse the second time (why twice?)
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 ;
# endif
if ( GetModelIndex ( ) = = MI_SKIMMER )
impulse * = 1.0f + m_fGasPedal ;
else if ( m_fGasPedal > 0.05f )
impulse * = m_fGasPedal ;
else
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 ( ) ) ;
ApplyTurnForce ( impulse * GetUp ( ) , buoyancePoint - pBoatHandling - > fAqPlaneOffset * GetForward ( ) ) ;
}
@ -378,7 +397,11 @@ CBoat::ProcessControl(void)
// Handle boat moving forward
float fwdSpeed = 1.0f ;
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 )
# endif
AddWakePoint ( GetPosition ( ) ) ;
float steerFactor = 1.0f ;
@ -433,7 +456,11 @@ CBoat::ProcessControl(void)
// Spray some particles
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 ) {
# endif
if ( GetStatus ( ) = = STATUS_PLAYER ) {
CVector sternPos = GetColModel ( ) - > boundingBox . min ;
sternPos . x = 0.0f ;
@ -475,7 +502,11 @@ CBoat::ProcessControl(void)
CVector propellerForce = propellerDepth * Multiply3x3 ( GetMatrix ( ) , force * CVector ( - steerSin , 0.0f , 0.0f ) ) ;
ApplyMoveForce ( propellerForce * CTimer : : GetTimeStep ( ) ) ;
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 ) ;
# endif
ApplyTurnForce ( GetRight ( ) * rightForce , GetUp ( ) ) ;
}
} else
@ -485,7 +516,12 @@ CBoat::ProcessControl(void)
CVector right = CrossProduct ( GetForward ( ) , CVector ( 0.0f , 0.0f , 1.0f ) ) ;
float rightSpeed = DotProduct ( m_vecMoveSpeed , right ) ;
float impulse = 0.1f * pHandling - > fSuspensionBias * m_fMass * m_fVolumeUnderWater * rightSpeed * CTimer : : GetTimeStep ( ) ;
ApplyMoveForce ( right - impulse * 0.3f * CVector ( - right . y , right . x , 0.0f ) ) ;
# 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 ) ) ;
# endif
}
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 = Max ( m_vecMoveSpeed . y , - ( GetPosition ( ) . y - ( WORLD_MIN_Y + 100.0f ) ) * 0.01f ) ; // south
// Apply water resistance to linear movement
if ( ! onLand & & bBoatInWater & & ! bSeparateTurnForce )
ApplyWaterResistance ( ) ;
// Apply water resistance to rotation
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 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
// TODO: figure this out
float magic = 1.0f / ( 1000.0f * SQR ( m_vecTurnSpeed . x ) + 1.0f ) * fx ;
float drag = 1.0f / ( 1000.0f * SQR ( m_vecTurnSpeed . x ) + 1.0f ) * fx ;
m_vecTurnSpeed . y * = fy ;
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
CVector com = Multiply3x3 ( GetMatrix ( ) , m_vecCentreOfMass ) ;
ApplyTurnForce ( forceUp * GetUp ( ) , com + GetForward ( ) ) ;
# endif
}
m_nDeltaVolumeUnderWater = ( m_fVolumeUnderWater - m_fPrevVolumeUnderWater ) * 10000 ;
// Falling into water
@ -539,6 +595,9 @@ CBoat::ProcessControl(void)
speedFwd * = - m_nDeltaVolumeUnderWater * 0.01f * pHandling - > fBrakeBias ;
CVector speed = speedFwd * GetForward ( ) + CVector ( 0.0f , 0.0f , speedUp ) ;
CVector splashImpulse = speed * m_fMass ;
# ifdef FIX_BUGS
splashImpulse * = CTimer : : GetTimeStepFix ( ) ;
# endif
ApplyMoveForce ( splashImpulse ) ;
ApplyTurnForce ( splashImpulse , buoyancePoint ) ;
}
@ -546,8 +605,15 @@ CBoat::ProcessControl(void)
// Splashes
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 ) ) {
( 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 ( ) & &
( AutoPilot . m_nCarMission ! = MISSION_CRUISE | | ( CTimer : : GetFrameCounter ( ) & 2 ) = = 0 ) )
# endif
{
CVector splashPos , splashDir ;
float splashSize , front , waterLevel ;
@ -682,8 +748,14 @@ CBoat::ProcessControl(void)
}
// Spray waterdrops on screen
# ifdef FIX_BUGS
if ( TheCamera . GetLookingForwardFirstPerson ( ) & & FindPlayerVehicle ( ) & & FindPlayerVehicle ( ) - > IsBoat ( ) & &
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 ) {
m_nDeltaVolumeUnderWater > 0 & & numWaterDropOnScreen < 20 )
# endif
{
CVector dropPos ;
CVector dropDir ( CGeneral : : GetRandomNumberInRange ( - 0.25f , 0.25f ) , CGeneral : : GetRandomNumberInRange ( 1.0f , 0.75f ) , 0.0f ) ;
@ -714,7 +786,13 @@ CBoat::ProcessControl(void)
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 splashPos = GetPosition ( ) ;
float level ;
@ -773,6 +851,18 @@ CBoat::ProcessControlInputs(uint8 pad)
m_fBrake + = ( CPad : : GetPad ( pad ) - > GetBrake ( ) / 255.0f - m_fBrake ) * 0.1f ;
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 ) {
m_fBrake = 0.0f ;
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 = Clamp ( m_fSteeringLeftRight , - 1.0f , 1.0f ) ;
# endif
float steeringSq = m_fSteeringLeftRight < 0.0f ? - SQR ( m_fSteeringLeftRight ) : SQR ( m_fSteeringLeftRight ) ;
m_fSteerAngle = pHandling - > fSteeringLock * DEGTORAD ( steeringSq ) ;
@ -793,6 +884,82 @@ float fSeaPlaneWaterResistance = 30.0f;
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
float resistance = 0.001f * pHandling - > fSuspensionForceLevel * SQR ( m_fVolumeUnderWater ) * m_fMass ;
if ( GetModelIndex ( ) = = MI_SKIMMER )
@ -817,6 +984,7 @@ CBoat::ApplyWaterResistance(void)
m_vecMoveSpeed . z * = fz ;
else
m_vecMoveSpeed . z * = ( 1.0f - fz ) * 0.5f + fz ;
# endif
}
RwObject *