//----------------------------------------------------------------------------- // Copyright(c), HL Rally: Source, 2004, All rights reserved. // The contents of this file may only be used / distributed in accordance // with the conditions listed in the supplied license, or with written // permission of the respective authors listed. // // Based off c_vehicle_jeep.cpp from sourcesdk //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Questions: // // What is the purpose of bool bIs in VPhysicsGetObject()->SetPosition(.., .., bIsTeleport) // // I *believe* (but I will confirm this) that teleport means "don't derive any velocity from the change of position" in other // words, assume the object appeared there without moving across the delta position from the last simulation tick. // // What is the difference between SetVelocity and SetVelocityInstantaneous // // So SetVelocity applies the velocity as a speed change, which will be applied in the next simulation tick, while // SetVelocityInstantaneous jams the new values directly into the speed value in the simulator. // // What is the best way of setting the physics objects origin to correct the client vehicle when out of sync // - Cant cause a sudden jerk with crazy amounts of seemingly random velocity? // - I think this problem is due to the vehicle body being moved, but the wheels arent? // - Please see the attempt at Teleport() below // // Is it possible to disable "randomness" in physics calculations in the physics engine? This is needed // so that the calculations on the server exactly match those on the client. (Less out of sync problems) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Notes: // - Pose parameters are no longer networked. They are calculated only on the client in VehiclePhysics.cpp // - Velocity is now networked - may help with correction (but hopefully can remove later) // - Consideration for multiple players has not been taken into account yet // - See the set of #defines below to enable / disable the way the client vehicles are handled //----------------------------------------------------------------------------- #include "cbase.h" #include "c_EntityPropVehicle.h" #include "c_EntityPropVehicleJeep.h" #include "movevars_shared.h" #include "view.h" #include "flashlighteffect.h" #include "c_baseplayer.h" #include "c_te_effect_dispatch.h" #include "input.h" #include "in_buttons.h" #include "gamemovement.h" #include "gamestringpool.h" // Camera modes #include "HLRally2/CCameraModeABC.h" #include "HLRally2/CameraExterior.h" #include "HLRally2/CameraInterior.h" #include #include "tier0/memdbgon.h" // Move data for the local player extern CMoveData *g_pMoveData; extern ConVar default_fov; ConVar r_JeepViewBlendTo( "r_JeepViewBlendTo", "1", FCVAR_CHEAT ); ConVar r_JeepViewBlendToScale( "r_JeepViewBlendToScale", "0.03", FCVAR_CHEAT ); ConVar r_JeepViewBlendToTime( "r_JeepViewBlendToTime", "1.5", FCVAR_CHEAT ); ConVar r_JeepFOV( "r_JeepFOV", "90", FCVAR_CHEAT ); ConVar rally_correction("rally_correction", "1", FCVAR_CHEAT); ConVar rally_correction_interpolate("rally_correction_interpolate", "1", FCVAR_CHEAT); ConVar rally_correction_overlays("rally_correction_overlays", "0", FCVAR_CHEAT); #define INTERP_ORIGIN_RATE_MIN 0.01 #define INTERP_ORIGIN_RATE_DIVISOR 0.8 #define INTERP_ANGLES_RATE_MIN 40 #define INTERP_ANGLES_RATE_DIVISOR 1.0 //----------------------------------------------------------------------------- // Purpose: Receive proxy for networked velocity // // Networked velocities could potentially be removed later to reduce bandwidth // if the server's velocity turns out to not help the correction process. //----------------------------------------------------------------------------- void RecvProxy_LocalVelocityRally( const CRecvProxyData *pData, void *pStruct, void *pOut ) { Vector vecVelocity; vecVelocity.x = pData->m_Value.m_Vector[0]; vecVelocity.y = pData->m_Value.m_Vector[1]; vecVelocity.z = pData->m_Value.m_Vector[2]; ((CBaseEntity *)pStruct)->SetLocalVelocity( vecVelocity ); } // Receive table IMPLEMENT_CLIENTCLASS_DT( C_PropJeep, DT_PropJeep, CEntityPropVehicleRally ) RecvPropBool( RECVINFO( m_bHeadlightIsOn ) ), RecvPropVector( RECVINFO(m_vecVelocity), 0, RecvProxy_LocalVelocityRally), RecvPropVector( RECVINFO(m_aiAngularVelocityChassis) ), RecvPropFloat( RECVINFO(m_fServerTime) ), RecvPropInt( RECVINFO(m_iTeleportCount) ), END_RECV_TABLE() //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- C_PropJeep::C_PropJeep() : m_VehiclePhysics(this), m_iLastFrameOrigin(0), m_iLastFrameAngles(0), m_vecOriginFrame(0, 0, 0), m_vecOriginPhysics(0, 0, 0), m_angAnglesFrame(0, 0, 0), m_angAnglesPhysics(0, 0, 0), m_vecOldVelocity(0, 0, 0), m_vecVelocity(0,0,0), m_bCorrectionInterpolating(false), m_vecCorrectionOrigin(0,0,0), m_fServerTime(0.0f), m_pSimulationHead(NULL), m_pSimulationTail(NULL) { m_vecEyeSpeed.Init(); m_flViewAngleDeltaTime = 0.0f; m_pHeadlight = NULL; m_ViewSmoothingData.flFOV = r_JeepFOV.GetFloat(); AddVar( m_flPoseParameterClient, &m_iv_flPoseParameterClient, LATCH_ANIMATION_VAR | EXCLUDE_AUTO_INTERPOLATE ); m_iUpdatePacketID = 0; // Get all of the camera positions int iAttachment = LookupAttachment( "vehicle_driver_eyes" ); QAngle angAngles; GetAttachmentLocal( iAttachment, m_vecInteriorCamOrigin, angAngles ); m_iLastTeleportCount = 1; m_iTeleportCount = 1; /* // TODO // Additional camera attachments int iAttachment = LookupAttachment( "vehicle_cam_wheels" ); QAngle angEyeAngles; GetAttachment( iAttachment, m_vecWheelCamOrigin, angAngles ); int iAttachment = LookupAttachment( "vehicle_cam_bonnet" ); QAngle angEyeAngles; GetAttachment( iAttachment, m_vecBonnetCamOrigin, angAngles ); */ // Set default view to Interior m_iLastCameraMode = -1; m_pCameraMode = NULL; if (GetPassenger(0) == CBasePlayer::GetLocalPlayer() ) ChangeCameraMode(); } //----------------------------------------------------------------------------- // Purpose: Deconstructor //----------------------------------------------------------------------------- C_PropJeep::~C_PropJeep() { if ( m_pHeadlight ) { delete m_pHeadlight; } // Destroy the client physics object PhysCleanupFrictionSounds( this ); VPhysicsDestroyObject(); } //----------------------------------------------------------------------------- // Purpose: Returns the current render origin // // There is a hack in this to make sure that the origin is consistant for the // whole frame. VPhysics updates the render origin mid-frame causing horrible // jitter. // // TODO: Fix the hack - Im sure it can be made static for the whole frame - // potentially by moving the location of where SimulatePhysics is called // // m_vecOriginPhysics - The most up to date origin provided by vphysics // m_vecOriginFrame - The origin consistant for the current frame //----------------------------------------------------------------------------- const Vector &C_PropJeep::GetRenderOrigin( void ) { if(VPhysicsGetObject()) { // Consistency hack if(m_iLastFrameOrigin != gpGlobals->framecount) { m_vecOriginFrame = m_vecOriginPhysics; m_iLastFrameOrigin = gpGlobals->framecount; } // Additional smoothing hack m_vecLast = m_vecOriginFrame; m_vecOriginFrame = m_vecOriginFrame * .5 + m_vecLast * .5; m_vecLast = m_vecOriginFrame; return m_vecOriginFrame; } return BaseClass::GetRenderOrigin(); } //----------------------------------------------------------------------------- // Purpose: Returns the current render angles // // See GetRenderOrigin() for information on the consitency hack //----------------------------------------------------------------------------- const QAngle &C_PropJeep::GetRenderAngles( void ) { if(VPhysicsGetObject()) { // Consistency hack if(m_iLastFrameAngles != gpGlobals->framecount) { m_angAnglesFrame = m_angAnglesPhysics; m_iLastFrameAngles = gpGlobals->framecount; } return m_angAnglesFrame; } return BaseClass::GetRenderAngles(); } //----------------------------------------------------------------------------- // Purpose: Teleports the entity //----------------------------------------------------------------------------- void C_PropJeep::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity, const AngularImpulse *newAngularChassis ) { matrix3x4_t startMatrix; matrix3x4_t startMatrixInv; int nSolidFlags = GetSolidFlags(); AddSolidFlags( FSOLID_NOT_SOLID ); VPhysicsGetObject()->GetPositionMatrix(&startMatrix); MatrixInvert(startMatrix, startMatrixInv); VPhysicsGetObject()->SetPosition(*newPosition, *newAngles, true); // Calculate the relative transform of the teleport matrix3x4_t xform; VPhysicsGetObject()->GetPositionMatrix(&startMatrix); ConcatTransforms( startMatrix, startMatrixInv, xform ); m_VehiclePhysics.Teleport( xform ); VPhysicsGetObject()->SetVelocity(newVelocity, NULL); for(int i=0; i< 4; i++) { m_VehiclePhysics.GetWheel(i)->SetVelocity(newVelocity, NULL); } SetSolidFlags( nSolidFlags ); } int C_PropJeep::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) { return m_VehiclePhysics.VPhysicsGetObjectList( pList, listMax ); } //----------------------------------------------------------------------------- // Purpose: Sets up the client physics object // // TODO: // This function is currently called by SimulatePhysics() which makes it // completely post init. // // Need to rename and make it an override // // Can't use: // - Spawn() - The model isnt fully setup // - OnNewModel() - The physics entity isnt fully setup //----------------------------------------------------------------------------- void C_PropJeep::VehiclePhysicsClientSetup() { SetModelName( AllocPooledString( "models/buggy.mdl" ) ); SetCollisionGroup( COLLISION_GROUP_VEHICLE ); m_VehiclePhysics.SetOuter(this); m_VehiclePhysics.Spawn(); // TODO: get the vehicle script from the server if (m_VehiclePhysics.Initialize( "models/buggy/buggy.txt", VEHICLE_TYPE_CAR_WHEELS )) { // TODO: TurnOn() should be called by onEnteredVehicle() m_VehiclePhysics.TurnOn(); m_vecOriginFrame = GetAbsOrigin(); m_vecOriginPhysics = GetAbsOrigin(); m_angAnglesFrame = GetAbsAngles(); m_angAnglesPhysics = GetAbsAngles(); m_iLastFrameOrigin = gpGlobals->framecount; m_iLastFrameAngles = gpGlobals->framecount; VPhysicsGetObject()->EnableCollisions(true); } m_VehiclePhysics.GetVehicle()->OnVehicleEnter(); for(int i = 0; i < 4; i++) { m_VehiclePhysics.GetWheel(i)->EnableCollisions(true); } m_bVehiclePhysicsSetup = true; } //----------------------------------------------------------------------------- // Purpose: Updates VPhysics //----------------------------------------------------------------------------- void C_PropJeep::VPhysicsUpdate(IPhysicsObject *pPhysics) { if ( IsMarkedForDeletion() ) { return; } // Wheels if (!m_VehiclePhysics.VPhysicsUpdate( pPhysics )) { return; } BaseClass::VPhysicsUpdate(pPhysics); } void C_PropJeep::ProcessMovement(C_BasePlayer *pPlayer, CMoveData *pMoveData) { // ConVar *convar = (ConVar*) cvar->FindVar("net_fakelag"); m_VehiclePhysics.ProcessMovement(pMoveData); /* static bool bIsAlternate = true; if(bIsAlternate) { RecordSimulation(pMoveData, gpGlobals->curtime + convar->GetInt() / 1000, &m_pSimulationHead, &m_pSimulationTail); } bIsAlternate = !bIsAlternate;*/ } void C_PropJeep::RecordSimulation(CMoveData *pMoveData, float fTime, C_PropJeepSimulation **pHead, C_PropJeepSimulation **pTail) { // Update simulations list C_PropJeepSimulation *pSimulation = new C_PropJeepSimulation; VPhysicsGetObject()->GetPosition(&pSimulation->vecPosition, &pSimulation->angAngles); pSimulation->nButtons = pMoveData->m_nButtons; pSimulation->fForwardMove = pMoveData->m_flForwardMove; pSimulation->fSideMove = pMoveData->m_flSideMove; pSimulation->fTime = fTime; pSimulation->pNext = NULL; // Special case when the head simulation does not exist if(!*pHead) { *pHead = pSimulation; *pTail = pSimulation; } else { (*pTail)->pNext = pSimulation; *pTail = pSimulation; } } void C_PropJeep::Simulate( void ) { if(!VPhysicsGetObject()) { VehiclePhysicsClientSetup(); } // The dim light is the flashlight. if ( m_bHeadlightIsOn ) { if ( m_pHeadlight == NULL ) { // Turned on the headlight; create it. m_pHeadlight = new CHeadlightEffect; if ( m_pHeadlight == NULL ) return; m_pHeadlight->TurnOn(); } QAngle vAngle; Vector vVector; Vector vecForward, vecRight, vecUp; int iAttachment = LookupAttachment( "headlight" ); if ( iAttachment != -1 ) { GetAttachment( iAttachment, vVector, vAngle ); AngleVectors( vAngle, &vecForward, &vecRight, &vecUp ); m_pHeadlight->UpdateLight( vVector, vecForward, vecRight, vecUp, JEEP_HEADLIGHT_DISTANCE ); } } else if ( m_pHeadlight ) { // Turned off the flashlight; delete it. delete m_pHeadlight; m_pHeadlight = NULL; } Correction(); BaseClass::Simulate(); } int C_PropJeep::DrawModel(int flags) { Vector muzzleOrigin; QAngle muzzleAngles; SetAbsOrigin(GetRenderOrigin()); GetAttachment(LookupAttachment("gun_ref"), muzzleOrigin, muzzleAngles); C_BasePlayer *pPlayer = dynamic_cast(GetPassenger(0)); if(pPlayer && pPlayer->GetActiveWeapon()) { // pPlayer->GetActiveWeapon()->SetLocalOrigin(Vector(0, 100, 0)); // pPlayer->GetActiveWeapon()->SetLocalAngles(QAngle(0, 90, 0)); // pPlayer->GetActiveWeapon()->SetParent(this, LookupAttachment("gun_ref")); // if(pPlayer->GetActiveWeapon()->IsFollowingEntity()) // pPlayer->GetActiveWeapon()->FollowEntity(NULL); // pPlayer->GetActiveWeapon()->SetParent(this, LookupAttachment("gun_ref")); // pPlayer->GetActiveWeapon()->SetLocalOrigin(vec3_origin); // pPlayer->GetActiveWeapon()->SetAbsOrigin(muzzleOrigin); // pPlayer->GetActiveWeapon()->SetAbsAngles(muzzleAngles); // Assert(!pPlayer->GetActiveWeapon()->GetMoveParent()); } return C_PropVehicleDriveable::DrawModel(flags); } extern IPhysicsEnvironment *physenv; void C_PropJeep::Correction() { if(!VPhysicsGetObject()) { return; } if(GetPassenger(0) != C_BasePlayer::GetLocalPlayer()) { m_vecOriginPhysics = GetAbsOrigin(); m_angAnglesPhysics = GetAbsAngles(); Teleport(&m_vecOriginPhysics, &m_angAnglesPhysics, NULL, NULL); } else { VPhysicsGetObject()->GetPosition(&m_vecOriginPhysics, &m_angAnglesPhysics); Vector vecVelocity; VPhysicsGetObject()->GetVelocity(&vecVelocity, NULL); // IGameResources *gr = GameResources(); if( gpGlobals->curtime - m_fLastSendTime > 0.1f ) { m_fLastSendTime = gpGlobals->curtime; m_iUpdatePacketID++; char sz[256]; // Update origin sprintf(sz, "u %.3f %.3f %.3f %.5f %.5f %.5f %.3f %.3f %.3f %i", m_vecOriginPhysics.x, m_vecOriginPhysics.y, m_vecOriginPhysics.z, m_angAnglesPhysics.x, m_angAnglesPhysics.y, m_angAnglesPhysics.z, vecVelocity.x, vecVelocity.y, vecVelocity.z, m_iUpdatePacketID); engine->ClientCmd(sz); } } if(rally_correction_overlays.GetBool()) { // Red line is server DebugDrawLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0, 20), 255, 0, 0, true, -1.0f); // Green line is client DebugDrawLine( m_vecOriginPhysics, m_vecOriginPhysics + Vector(0,0, 20), 0, 255, 0, true, -1.0f); } } //----------------------------------------------------------------------------- // Purpose: Called when a player enters the vehicle //----------------------------------------------------------------------------- void C_PropJeep::OnEnteredVehicle( C_BaseCombatCharacter *pPlayer ) { int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); Vector vehicleEyeOrigin; QAngle vehicleEyeAngles; GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, vehicleEyeAngles ); m_vecLastEyeTarget = vehicleEyeOrigin; m_vecLastEyePos = vehicleEyeOrigin; m_vecEyeSpeed = vec3_origin; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Ignore the functions below this point // // This location downwards is to do with a client side pose parameters // and camera //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- extern void GetSequenceLinearMotion( CStudioHdr *pstudiohdr, int iSequence, const float poseParameter[], Vector *pVec ); float Studio_CPS( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, const float poseParameter[] ); extern float Studio_Duration( const CStudioHdr *pStudioHdr, int iSequence, const float poseParameter[] ); extern float Studio_SetPoseParameter( const CStudioHdr *pStudioHdr, int iParameter, float flValue, float &ctlValue ); CStudioHdr *C_PropJeep::OnNewModel( void ) { studiohdr_t *hdr = modelinfo->GetStudiomodel( GetModel() ); m_iv_flPoseParameterClient.SetMaxCount( hdr->GetNumPoseParameters() ); int i; for ( i = 0; i < hdr->GetNumPoseParameters() ; i++ ) { const mstudioposeparamdesc_t &Pose = hdr->pPoseParameter( i ); m_iv_flPoseParameterClient.SetLooping( Pose.loop != 0.0f, i ); } CStudioHdr *result = BaseClass::OnNewModel(); return result; } float C_PropJeep::GetSequenceMoveDist( CStudioHdr *pStudioHdr, int iSequence ) { Vector vecReturn; ::GetSequenceLinearMotion( pStudioHdr, iSequence, m_flPoseParameterClient, &vecReturn ); return vecReturn.Length(); } void C_PropJeep::GetSequenceLinearMotion( int iSequence, Vector *pVec ) { ::GetSequenceLinearMotion( GetModelPtr(), iSequence, m_flPoseParameterClient, pVec ); } float C_PropJeep::SequenceDuration( int iSequence ) { CStudioHdr *hdr = GetModelPtr(); if ( !hdr ) { return 0.1f; } CStudioHdr* pstudiohdr = hdr; if (iSequence >= pstudiohdr->GetNumSeq() || iSequence < 0 ) { DevWarning( 2, "C_BaseAnimating::SequenceDuration( %d ) out of range\n", iSequence ); return 0.1; } return Studio_Duration( pstudiohdr, iSequence, m_flPoseParameterClient ); } float C_PropJeep::SetPoseParameter( CStudioHdr *pStudioHdr, int iParameter, float flValue ) { if ( !pStudioHdr ) { Assert(!"C_BaseAnimating::SetPoseParameter: model missing"); return flValue; } if (iParameter >= 0) { float flNewValue; flValue = Studio_SetPoseParameter( pStudioHdr, iParameter, flValue, flNewValue ); m_flPoseParameterClient[ iParameter ] = flNewValue; } return flValue; } void C_PropJeep::GetPoseParameters( CStudioHdr *pStudioHdr, float poseParameter[MAXSTUDIOPOSEPARAM]) { if ( !pStudioHdr ) return; // interpolate pose parameters int i; for( i=0; i < pStudioHdr->GetNumPoseParameters(); i++) { poseParameter[i] = m_flPoseParameterClient[i]; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropJeep::RestrictView( float *pYawBounds, float *pPitchBounds, float *pRollBounds, QAngle &vecViewAngles ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_PropJeep::UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ) { if ( !m_pCameraMode ) return; if( !(input->CAM_IsThirdPerson()) ) { int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); QAngle angNewEyeAngles; Vector vehicleEyeOrigin; vehicleEyeOrigin = GetRenderOrigin(); GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, angNewEyeAngles ); // Restrict the view m_pCameraMode->update(angNewEyeAngles, vehicleEyeOrigin, GetRenderAngles(), pCmd); } } void C_PropJeep::GetVehicleViewPosition( int nRole, Vector *pOrigin, QAngle *pAngles ) { if( !(input->CAM_IsThirdPerson()) && m_pCameraMode ) { UpdateViewAngles(C_BasePlayer::GetLocalPlayer(), NULL); m_pCameraMode->GetViewPosition(pOrigin, pAngles); } else { int eyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); QAngle angNewEyeAngles; Vector vehicleEyeOrigin; GetAttachment( eyeAttachmentIndex, vehicleEyeOrigin, angNewEyeAngles ); *pOrigin = vehicleEyeOrigin; *pAngles = GetRenderAngles(); } } void C_PropJeep::ChangeCameraMode() { ConVar *cameramode = (ConVar*) cvar->FindVar("rally_cameramode"); m_iLastCameraMode = cameramode->GetInt(); QAngle angTempAngles; Vector vecTempOrigin; Vector vecTempCamOrigin; switch(m_iLastCameraMode) { case -1: case 0: // Interior input->CAM_ToFirstPerson(); m_iEyeAttachmentIndex = LookupAttachment( "vehicle_driver_eyes" ); vecTempCamOrigin = m_vecInteriorCamOrigin; GetAttachment(m_iEyeAttachmentIndex, vecTempCamOrigin, QAngle(0,0,0)); m_vecInteriorCamOrigin = vecTempCamOrigin; m_pCameraMode = new CCameraInterior( m_vecInteriorCamOrigin ); break; case 1: // 3rd Person/Exterior Cam if( input->CAM_IsThirdPerson() && m_pCameraMode ) { m_pCameraMode = new CCameraExterior( 200.0, 20.0, (dynamic_cast(m_pCameraMode))->m_fCurrentCameraDistance, (dynamic_cast(m_pCameraMode))->m_fCurrentCameraAngle ); } else { input->CAM_ToThirdPerson(); m_pCameraMode = new CCameraExterior( 200.0, 20.0, 100.0, 0 ); } break; case 2: // Chase Cam if( input->CAM_IsThirdPerson() && m_pCameraMode) { m_pCameraMode = new CCameraExterior( 400.0, 30.0, (dynamic_cast(m_pCameraMode))->m_fCurrentCameraDistance, (dynamic_cast(m_pCameraMode))->m_fCurrentCameraAngle ); } else { input->CAM_ToThirdPerson(); m_pCameraMode = new CCameraExterior( 400.0, 30.0, 100.0, 0 ); } break; /* case 3: // TODO: Wheel Cam CAM_ToThirdPerson(); m_vecEyeAttachmentIndex = LookupAttachment( "vehicle_cam_wheel" ); m_pCameraMode = new CCameraWheel( m_vecWheelCamOrigin ); break; case 4: // TODO: Bonnet Cam CAM_ToThirdPerson(); m_vecEyeAttachmentIndex = LookupAttachment( "vehicle_cam_bonnet" ); m_pCameraMode = new CCameraBonnet( m_vecBonnetCamOrigin ); break; case 5: // TODO: TV Cam CAM_ToThirdPerson(); m_pCameraMode = new CCameraTV(); break; */ default: break; } } CVehiclePhysics *C_PropJeep::GetPhysics() { return &m_VehiclePhysics; } void IN_FlipVehicle() { // Flip the vehicle back onto it's wheels C_PropJeep *pVehicle = (C_PropJeep *)(C_BasePlayer::GetLocalPlayer()->GetVehicle()); Vector vecOrigin; QAngle angAngles; pVehicle->VPhysicsGetObject()->GetPosition(&vecOrigin, &angAngles); angAngles.x = angAngles.z = 0.0; vecOrigin.z += 20; pVehicle->Teleport(&vecOrigin, &angAngles, NULL, NULL); //TODO: Flash the car (disable car<->car collisions?) } ConCommand rally_flipvehicle("rally_flipvehicle", IN_FlipVehicle, "rally_flipvehicle: Flips the player's vehicle back onto it's wheels");