//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: UNDONE: Rename this to prop_vehicle.cpp !!! // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "vcollide_parse.h" #include "vehicle_base.h" #include "ndebugoverlay.h" #include "igamemovement.h" #include "soundenvelope.h" #include "in_buttons.h" #include "npc_vehicledriver.h" #include "physics_saverestore.h" #include "saverestore_utlvector.h" #include "func_break.h" #include "physics_impact_damage.h" #include "entityblocker.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define SF_PROP_VEHICLE_ALWAYSTHINK 0x00000001 ConVar g_debug_vehiclebase( "g_debug_vehiclebase", "0", FCVAR_CHEAT ); extern ConVar g_debug_vehicledriver; // CFourWheelServerVehicle BEGIN_SIMPLE_DATADESC_( CFourWheelServerVehicle, CBaseServerVehicle ) DEFINE_EMBEDDED( m_ViewSmoothing ), END_DATADESC() // CPropVehicle BEGIN_DATADESC( CPropVehicle ) DEFINE_EMBEDDED( m_VehiclePhysics ), // These are necessary to save here because the 'owner' of these fields must be the prop_vehicle DEFINE_PHYSPTR( m_VehiclePhysics.m_pVehicle ), DEFINE_PHYSPTR_ARRAY( m_VehiclePhysics.m_pWheels ), DEFINE_FIELD( m_nVehicleType, FIELD_INTEGER ), // Physics Influence DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), #ifdef HL2_EPISODIC DEFINE_UTLVECTOR( m_hPhysicsChildren, FIELD_EHANDLE ), #endif // HL2_EPISODIC // Keys DEFINE_KEYFIELD( m_vehicleScript, FIELD_STRING, "VehicleScript" ), DEFINE_FIELD( m_vecSmoothedVelocity, FIELD_VECTOR ), // Inputs DEFINE_INPUTFUNC( FIELD_FLOAT, "Throttle", InputThrottle ), DEFINE_INPUTFUNC( FIELD_FLOAT, "Steer", InputSteering ), DEFINE_INPUTFUNC( FIELD_FLOAT, "Action", InputAction ), DEFINE_INPUTFUNC( FIELD_VOID, "HandBrakeOn", InputHandBrakeOn ), DEFINE_INPUTFUNC( FIELD_VOID, "HandBrakeOff", InputHandBrakeOff ), END_DATADESC() LINK_ENTITY_TO_CLASS( prop_vehicle, CPropVehicle ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #pragma warning (disable:4355) CPropVehicle::CPropVehicle() : m_VehiclePhysics( this ) { SetVehicleType( VEHICLE_TYPE_CAR_WHEELS ); } #pragma warning (default:4355) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPropVehicle::~CPropVehicle () { } //----------------------------------------------------------------------------- // Precache //----------------------------------------------------------------------------- void CPropVehicle::Precache() { BaseClass::Precache(); GetPhysics()->Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::Spawn( ) { CFourWheelServerVehicle *pServerVehicle = dynamic_cast(GetServerVehicle()); m_VehiclePhysics.SetOuter( this, pServerVehicle ); // NOTE: The model has to be set before we can spawn vehicle physics BaseClass::Spawn(); SetCollisionGroup( COLLISION_GROUP_VEHICLE ); m_VehiclePhysics.Spawn(); if (!m_VehiclePhysics.Initialize( STRING(m_vehicleScript), m_nVehicleType )) return; SetNextThink( gpGlobals->curtime ); m_vecSmoothedVelocity.Init(); } // this allows reloading the script variables from disk over an existing vehicle state // This is useful for tuning vehicles or updating old saved game formats CON_COMMAND(vehicle_flushscript, "Flush and reload all vehicle scripts") { PhysFlushVehicleScripts(); for ( CBaseEntity *pEnt = gEntList.FirstEnt(); pEnt != NULL; pEnt = gEntList.NextEnt(pEnt) ) { IServerVehicle *pServerVehicle = pEnt->GetServerVehicle(); if ( pServerVehicle ) { pServerVehicle->ReloadScript(); } } } //----------------------------------------------------------------------------- // Purpose: Restore //----------------------------------------------------------------------------- int CPropVehicle::Restore( IRestore &restore ) { CFourWheelServerVehicle *pServerVehicle = dynamic_cast(GetServerVehicle()); m_VehiclePhysics.SetOuter( this, pServerVehicle ); return BaseClass::Restore( restore ); } //----------------------------------------------------------------------------- // Purpose: Tell the vehicle physics system whenever we teleport, so it can fixup the wheels. //----------------------------------------------------------------------------- void CPropVehicle::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) { matrix3x4_t startMatrixInv; MatrixInvert( EntityToWorldTransform(), startMatrixInv ); BaseClass::Teleport( newPosition, newAngles, newVelocity ); // Calculate the relative transform of the teleport matrix3x4_t xform; ConcatTransforms( EntityToWorldTransform(), startMatrixInv, xform ); m_VehiclePhysics.Teleport( xform ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::DrawDebugGeometryOverlays() { if (m_debugOverlays & OVERLAY_BBOX_BIT) { m_VehiclePhysics.DrawDebugGeometryOverlays(); } BaseClass::DrawDebugGeometryOverlays(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CPropVehicle::DrawDebugTextOverlays() { int nOffset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { nOffset = m_VehiclePhysics.DrawDebugTextOverlays( nOffset ); } return nOffset; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBasePlayer *CPropVehicle::HasPhysicsAttacker( float dt ) { if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) { return m_hPhysicsAttacker; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Keep track of physgun influence //----------------------------------------------------------------------------- void CPropVehicle::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) { m_hPhysicsAttacker = pPhysGunUser; m_flLastPhysicsInfluenceTime = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::InputThrottle( inputdata_t &inputdata ) { m_VehiclePhysics.SetThrottle( inputdata.value.Float() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::InputSteering( inputdata_t &inputdata ) { m_VehiclePhysics.SetSteering( inputdata.value.Float(), 2*gpGlobals->frametime ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::InputAction( inputdata_t &inputdata ) { m_VehiclePhysics.SetAction( inputdata.value.Float() ); } void CPropVehicle::InputHandBrakeOn( inputdata_t &inputdata ) { m_VehiclePhysics.SetHandbrake( true ); } void CPropVehicle::InputHandBrakeOff( inputdata_t &inputdata ) { m_VehiclePhysics.ReleaseHandbrake(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::Think() { m_VehiclePhysics.Think(); // Derived classes of CPropVehicle have their own code to determine how frequently to think. // But the prop_vehicle entity native to this class will only think one time, so this flag // was added to allow prop_vehicle to always think without affecting the derived classes. if( HasSpawnFlags(SF_PROP_VEHICLE_ALWAYSTHINK) ) { SetNextThink(gpGlobals->curtime); } } #define SMOOTHING_FACTOR 0.9 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicle::VPhysicsUpdate( IPhysicsObject *pPhysics ) { if ( IsMarkedForDeletion() ) return; Vector velocity; VPhysicsGetObject()->GetVelocity( &velocity, NULL ); //Update our smoothed velocity m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + velocity * ( 1 - SMOOTHING_FACTOR ); // must be a wheel if (!m_VehiclePhysics.VPhysicsUpdate( pPhysics )) return; BaseClass::VPhysicsUpdate( pPhysics ); } //----------------------------------------------------------------------------- // Purpose: // Output : const Vector //----------------------------------------------------------------------------- Vector CPropVehicle::GetSmoothedVelocity( void ) { return m_vecSmoothedVelocity; } //============================================================================= #ifdef HL2_EPISODIC //----------------------------------------------------------------------------- // Purpose: Add an entity to a list which receives physics callbacks from the vehicle //----------------------------------------------------------------------------- void CPropVehicle::AddPhysicsChild( CBaseEntity *pChild ) { // Don't add something we already have if ( m_hPhysicsChildren.Find( pChild ) != m_hPhysicsChildren.InvalidIndex() ) return ; m_hPhysicsChildren.AddToTail( pChild ); } //----------------------------------------------------------------------------- // Purpose: Removes entity from physics callback list //----------------------------------------------------------------------------- void CPropVehicle::RemovePhysicsChild( CBaseEntity *pChild ) { int elemID = m_hPhysicsChildren.Find( pChild ); if ( m_hPhysicsChildren.IsValidIndex( elemID ) ) { m_hPhysicsChildren.Remove( elemID ); } } #endif //HL2_EPISODIC //============================================================================= //----------------------------------------------------------------------------- // Purpose: Player driveable vehicle class //----------------------------------------------------------------------------- IMPLEMENT_SERVERCLASS_ST(CPropVehicleDriveable, DT_PropVehicleDriveable) SendPropEHandle(SENDINFO(m_hPlayer)), // SendPropFloat(SENDINFO_DT_NAME(m_controls.throttle, m_throttle), 8, SPROP_ROUNDUP, 0.0f, 1.0f), SendPropInt(SENDINFO(m_nSpeed), 8), SendPropInt(SENDINFO(m_nRPM), 13), SendPropFloat(SENDINFO(m_flThrottle), 0, SPROP_NOSCALE ), SendPropInt(SENDINFO(m_nBoostTimeLeft), 8), SendPropInt(SENDINFO(m_nHasBoost), 1, SPROP_UNSIGNED), SendPropInt(SENDINFO(m_nScannerDisabledWeapons), 1, SPROP_UNSIGNED), SendPropInt(SENDINFO(m_nScannerDisabledVehicle), 1, SPROP_UNSIGNED), SendPropInt(SENDINFO(m_bEnterAnimOn), 1, SPROP_UNSIGNED ), SendPropInt(SENDINFO(m_bExitAnimOn), 1, SPROP_UNSIGNED ), SendPropInt(SENDINFO(m_bUnableToFire), 1, SPROP_UNSIGNED ), SendPropVector(SENDINFO(m_vecEyeExitEndpoint), -1, SPROP_COORD), SendPropBool(SENDINFO(m_bHasGun)), SendPropVector(SENDINFO(m_vecGunCrosshair), -1, SPROP_COORD), END_SEND_TABLE(); BEGIN_DATADESC( CPropVehicleDriveable ) // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), DEFINE_INPUT( m_bHasGun, FIELD_BOOLEAN, "EnableGun" ), // Outputs DEFINE_OUTPUT( m_playerOn, "PlayerOn" ), DEFINE_OUTPUT( m_playerOff, "PlayerOff" ), DEFINE_OUTPUT( m_pressedAttack, "PressedAttack" ), DEFINE_OUTPUT( m_pressedAttack2, "PressedAttack2" ), DEFINE_OUTPUT( m_attackaxis, "AttackAxis" ), DEFINE_OUTPUT( m_attack2axis, "Attack2Axis" ), DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), DEFINE_EMBEDDEDBYREF( m_pServerVehicle ), DEFINE_FIELD( m_nSpeed, FIELD_INTEGER ), DEFINE_FIELD( m_nRPM, FIELD_INTEGER ), DEFINE_FIELD( m_flThrottle, FIELD_FLOAT ), DEFINE_FIELD( m_nBoostTimeLeft, FIELD_INTEGER ), DEFINE_FIELD( m_nHasBoost, FIELD_INTEGER ), DEFINE_FIELD( m_nScannerDisabledWeapons, FIELD_BOOLEAN ), DEFINE_FIELD( m_nScannerDisabledVehicle, FIELD_BOOLEAN ), DEFINE_FIELD( m_bUnableToFire, FIELD_BOOLEAN ), DEFINE_FIELD( m_vecEyeExitEndpoint, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecGunCrosshair, FIELD_VECTOR ), DEFINE_FIELD( m_bEngineLocked, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_bLocked, FIELD_BOOLEAN, "VehicleLocked" ), DEFINE_FIELD( m_flMinimumSpeedToEnterExit, FIELD_FLOAT ), DEFINE_FIELD( m_bEnterAnimOn, FIELD_BOOLEAN ), DEFINE_FIELD( m_bExitAnimOn, FIELD_BOOLEAN ), DEFINE_FIELD( m_flTurnOffKeepUpright, FIELD_TIME ), //DEFINE_FIELD( m_flNoImpactDamageTime, FIELD_TIME ), DEFINE_FIELD( m_hNPCDriver, FIELD_EHANDLE ), DEFINE_FIELD( m_hKeepUpright, FIELD_EHANDLE ), END_DATADESC() LINK_ENTITY_TO_CLASS( prop_vehicle_driveable, CPropVehicleDriveable ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPropVehicleDriveable::CPropVehicleDriveable( void ) : m_pServerVehicle( NULL ), m_hKeepUpright( NULL ), m_flTurnOffKeepUpright( 0 ), m_flNoImpactDamageTime( 0 ) { m_vecEyeExitEndpoint.Init(); m_vecGunCrosshair.Init(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPropVehicleDriveable::~CPropVehicleDriveable( void ) { DestroyServerVehicle(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::CreateServerVehicle( void ) { // Create our server vehicle m_pServerVehicle = new CFourWheelServerVehicle(); m_pServerVehicle->SetVehicle( this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::DestroyServerVehicle() { if ( m_pServerVehicle ) { delete m_pServerVehicle; m_pServerVehicle = NULL; } } //------------------------------------------------ // Precache //------------------------------------------------ void CPropVehicleDriveable::Precache( void ) { BaseClass::Precache(); // This step is needed because if we're precaching from a templated instance, we'll miss our vehicle // script sounds unless we do the parse below. This instance of the vehicle will be nuked when we're actually created. if ( m_pServerVehicle == NULL ) { CreateServerVehicle(); } // Load the script file and precache our assets if ( m_pServerVehicle ) { m_pServerVehicle->Initialize( STRING( m_vehicleScript ) ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::Spawn( void ) { // Has to be created before Spawn is called (since that causes Precache to be called) DestroyServerVehicle(); CreateServerVehicle(); // Initialize our vehicle via script if ( m_pServerVehicle->Initialize( STRING(m_vehicleScript) ) == false ) { Warning( "Vehicle (%s) unable to properly initialize due to script error in (%s)!\n", GetEntityName().ToCStr(), STRING( m_vehicleScript ) ); SetThink( &CBaseEntity::SUB_Remove ); SetNextThink( gpGlobals->curtime + 0.1f ); return; } BaseClass::Spawn(); m_flMinimumSpeedToEnterExit = 0; m_takedamage = DAMAGE_EVENTS_ONLY; m_bEngineLocked = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CPropVehicleDriveable::Restore( IRestore &restore ) { // Has to be created before we can restore // and we can't create it in the constructor because it could be // overridden by a derived class. DestroyServerVehicle(); CreateServerVehicle(); int nRetVal = BaseClass::Restore( restore ); return nRetVal; } //----------------------------------------------------------------------------- // Purpose: Do extra fix-up after restore //----------------------------------------------------------------------------- void CPropVehicleDriveable::OnRestore( void ) { BaseClass::OnRestore(); // NOTE: This is necessary to prevent overflow of datatables on level transition // since the last exit eyepoint in the last level will have been fixed up // based on the level landmarks, resulting in a position that lies outside // typical map coordinates. If we're not in the middle of an exit anim, the // eye exit endpoint field isn't being used at all. if ( !m_bExitAnimOn ) { m_vecEyeExitEndpoint = GetAbsOrigin(); } m_flNoImpactDamageTime = gpGlobals->curtime + 5.0f; IServerVehicle *pServerVehicle = GetServerVehicle(); if ( pServerVehicle != NULL ) { // Restore the passenger information we're holding on to pServerVehicle->RestorePassengerInfo(); } } //----------------------------------------------------------------------------- // Purpose: Vehicles are permanently oriented off angle for vphysics. //----------------------------------------------------------------------------- void CPropVehicleDriveable::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const { // This call is necessary to cause m_rgflCoordinateFrame to be recomputed const matrix3x4_t &entityToWorld = EntityToWorldTransform(); if (pForward != NULL) { MatrixGetColumn( entityToWorld, 1, *pForward ); } if (pRight != NULL) { MatrixGetColumn( entityToWorld, 0, *pRight ); } if (pUp != NULL) { MatrixGetColumn( entityToWorld, 2, *pUp ); } } //----------------------------------------------------------------------------- // Purpose: AngleVectors equivalent that accounts for the hacked 90 degree rotation of vehicles // BUGBUG: VPhysics is hardcoded so that vehicles must face down Y instead of X like everything else //----------------------------------------------------------------------------- void CPropVehicleDriveable::VehicleAngleVectors( const QAngle &angles, Vector *pForward, Vector *pRight, Vector *pUp ) { AngleVectors( angles, pRight, pForward, pUp ); if ( pForward ) { *pForward *= -1; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { CBasePlayer *pPlayer = ToBasePlayer( pActivator ); if ( !pPlayer ) return; ResetUseKey( pPlayer ); m_pServerVehicle->HandlePassengerEntry( pPlayer, (value>0) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *CPropVehicleDriveable::GetDriver( void ) { if ( m_hNPCDriver ) return m_hNPCDriver; return m_hPlayer; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::EnterVehicle( CBaseCombatCharacter *pPassenger ) { if ( pPassenger == NULL ) return; CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); if ( pPlayer != NULL ) { // Remove any player who may be in the vehicle at the moment if ( m_hPlayer ) { ExitVehicle( VEHICLE_ROLE_DRIVER ); } m_hPlayer = pPlayer; m_playerOn.FireOutput( pPlayer, this, 0 ); // Don't start the engine if the player's using an entry animation, // because we want to start the engine once the animation is done. if ( !m_bEnterAnimOn ) { StartEngine(); } // Start Thinking SetNextThink( gpGlobals->curtime ); Vector vecViewOffset = m_pServerVehicle->GetSavedViewOffset(); // Clear our state m_pServerVehicle->InitViewSmoothing( pPlayer->GetAbsOrigin() + vecViewOffset, pPlayer->EyeAngles() ); m_VehiclePhysics.GetVehicle()->OnVehicleEnter(); } else { // NPCs are not yet supported - jdw Assert( 0 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::ExitVehicle( int nRole ) { CBasePlayer *pPlayer = m_hPlayer; if ( !pPlayer ) return; m_hPlayer = NULL; ResetUseKey( pPlayer ); m_playerOff.FireOutput( pPlayer, this, 0 ); // clear out the fire buttons m_attackaxis.Set( 0, pPlayer, this ); m_attack2axis.Set( 0, pPlayer, this ); m_nSpeed = 0; m_flThrottle = 0.0f; StopEngine(); m_VehiclePhysics.GetVehicle()->OnVehicleExit(); // Clear our state m_pServerVehicle->InitViewSmoothing( vec3_origin, vec3_angle ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::ResetUseKey( CBasePlayer *pPlayer ) { pPlayer->m_afButtonPressed &= ~IN_USE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::DriveVehicle( CBasePlayer *pPlayer, CUserCmd *ucmd ) { //Lose control when the player dies if ( pPlayer->IsAlive() == false ) return; DriveVehicle( TICK_INTERVAL, ucmd, pPlayer->m_afButtonPressed, pPlayer->m_afButtonReleased ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased ) { int iButtons = ucmd->buttons; m_VehiclePhysics.UpdateDriverControls( ucmd, flFrameTime ); m_nSpeed = m_VehiclePhysics.GetSpeed(); //send speed to client m_nRPM = clamp( m_VehiclePhysics.GetRPM(), 0, 4095 ); m_nBoostTimeLeft = m_VehiclePhysics.BoostTimeLeft(); m_nHasBoost = m_VehiclePhysics.HasBoost(); m_flThrottle = m_VehiclePhysics.GetThrottle(); m_nScannerDisabledWeapons = false; // off for now, change once we have scanners m_nScannerDisabledVehicle = false; // off for now, change once we have scanners // // Fire the appropriate outputs based on button pressed events. // // BUGBUG: m_afButtonPressed is broken - check the player.cpp code!!! float attack = 0, attack2 = 0; if ( iButtonsDown & IN_ATTACK ) { m_pressedAttack.FireOutput( this, this, 0 ); } if ( iButtonsDown & IN_ATTACK2 ) { m_pressedAttack2.FireOutput( this, this, 0 ); } if ( iButtons & IN_ATTACK ) { attack = 1; } if ( iButtons & IN_ATTACK2 ) { attack2 = 1; } m_attackaxis.Set( attack, this, this ); m_attack2axis.Set( attack2, this, this ); } //----------------------------------------------------------------------------- // Purpose: Tells whether or not the car has been overturned // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropVehicleDriveable::IsOverturned( void ) { Vector vUp; VehicleAngleVectors( GetAbsAngles(), NULL, NULL, &vUp ); float upDot = DotProduct( Vector(0,0,1), vUp ); // Tweak this number to adjust what's considered "overturned" if ( upDot < 0.0f ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::Think() { BaseClass::Think(); if ( ShouldThink() ) { SetNextThink( gpGlobals->curtime ); } // If we have an NPC Driver, tell him to drive if ( m_hNPCDriver ) { GetServerVehicle()->NPC_DriveVehicle(); } // Keep thinking while we're waiting to turn off the keep upright if ( m_flTurnOffKeepUpright ) { SetNextThink( gpGlobals->curtime ); // Time up? if ( m_hKeepUpright != NULL && m_flTurnOffKeepUpright < gpGlobals->curtime ) { variant_t emptyVariant; m_hKeepUpright->AcceptInput( "TurnOff", this, this, emptyVariant, USE_TOGGLE ); m_flTurnOffKeepUpright = 0; UTIL_Remove( m_hKeepUpright ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) { // If the engine's not active, prevent driving if ( !IsEngineOn() || m_bEngineLocked ) return; // If the player's entering/exiting the vehicle, prevent movement if ( m_bEnterAnimOn || m_bExitAnimOn ) return; DriveVehicle( player, ucmd ); } //----------------------------------------------------------------------------- // Purpose: Prevent the player from entering / exiting the vehicle //----------------------------------------------------------------------------- void CPropVehicleDriveable::InputLock( inputdata_t &inputdata ) { m_bLocked = true; } //----------------------------------------------------------------------------- // Purpose: Allow the player to enter / exit the vehicle //----------------------------------------------------------------------------- void CPropVehicleDriveable::InputUnlock( inputdata_t &inputdata ) { m_bLocked = false; } //----------------------------------------------------------------------------- // Purpose: Return true of the player's allowed to enter the vehicle //----------------------------------------------------------------------------- bool CPropVehicleDriveable::CanEnterVehicle( CBaseEntity *pEntity ) { // Only drivers are supported Assert( pEntity && pEntity->IsPlayer() ); // Prevent entering if the vehicle's being driven by an NPC if ( GetDriver() && GetDriver() != pEntity ) return false; // Can't enter if we're upside-down if ( IsOverturned() ) return false; // Prevent entering if the vehicle's locked, or if it's moving too fast. return ( !m_bLocked && (m_nSpeed <= m_flMinimumSpeedToEnterExit) ); } //----------------------------------------------------------------------------- // Purpose: Return true of the player's allowed to exit the vehicle //----------------------------------------------------------------------------- bool CPropVehicleDriveable::CanExitVehicle( CBaseEntity *pEntity ) { // Prevent exiting if the vehicle's locked, or if it's moving too fast. return ( !m_bEnterAnimOn && !m_bExitAnimOn && !m_bLocked && (m_nSpeed <= m_flMinimumSpeedToEnterExit) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::InputTurnOn( inputdata_t &inputdata ) { m_bEngineLocked = false; StartEngine(); m_VehiclePhysics.SetDisableEngine( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::InputTurnOff( inputdata_t &inputdata ) { m_bEngineLocked = true; StopEngine(); m_VehiclePhysics.SetDisableEngine( true ); } //----------------------------------------------------------------------------- // Purpose: Check to see if the engine is on. //----------------------------------------------------------------------------- bool CPropVehicleDriveable::IsEngineOn( void ) { return m_VehiclePhysics.IsOn(); } //----------------------------------------------------------------------------- // Purpose: Turn on the engine, but only if we're allowed to //----------------------------------------------------------------------------- void CPropVehicleDriveable::StartEngine( void ) { if ( m_bEngineLocked ) { m_VehiclePhysics.SetHandbrake( true ); return; } m_VehiclePhysics.TurnOn(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPropVehicleDriveable::StopEngine( void ) { m_VehiclePhysics.TurnOff(); } //----------------------------------------------------------------------------- // Purpose: // The player takes damage if he hits something going fast enough //----------------------------------------------------------------------------- void CPropVehicleDriveable::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { //============================================================================= #ifdef HL2_EPISODIC // Notify all children for ( int i = 0; i < m_hPhysicsChildren.Count(); i++ ) { if ( m_hPhysicsChildren[i] == NULL ) continue; m_hPhysicsChildren[i]->VPhysicsCollision( index, pEvent ); } #endif // HL2_EPISODIC //============================================================================= // Don't care if we don't have a driver CBaseCombatCharacter *pDriver = GetDriver() ? GetDriver()->MyCombatCharacterPointer() : NULL; if ( !pDriver ) return; // Make sure we don't keep hitting the same entity int otherIndex = !index; CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) ) return; BaseClass::VPhysicsCollision( index, pEvent ); // if this is a bone follower, promote to the owner entity if ( pHitEntity->GetOwnerEntity() && (pHitEntity->GetEffects() & EF_NODRAW) ) { CBaseEntity *pOwner = pHitEntity->GetOwnerEntity(); // no friendly bone follower damage // this allows strider legs to damage the player on impact but not d0g for example if ( pDriver->IRelationType( pOwner ) == D_LI ) return; } // If we hit hard enough, damage the player // Don't take damage from ramming bad guys if ( pHitEntity->MyNPCPointer() ) { return; } // Don't take damage from ramming ragdolls if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) return; // Ignore func_breakables CBreakable *pBreakable = dynamic_cast(pHitEntity); if ( pBreakable ) { // ROBIN: Do we want to only do this on func_breakables that are about to die? //if ( pBreakable->HasSpawnFlags( SF_PHYSICS_BREAK_IMMEDIATELY ) ) return; } // Over our skill's minimum crash level? int damageType = 0; float flDamage = CalculatePhysicsImpactDamage( index, pEvent, gDefaultPlayerVehicleImpactDamageTable, 1.0, true, damageType ); if ( flDamage > 0 && m_flNoImpactDamageTime < gpGlobals->curtime ) { Vector damagePos; pEvent->pInternalData->GetContactPoint( damagePos ); Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); CTakeDamageInfo info( this, GetDriver(), damageForce, damagePos, flDamage, (damageType|DMG_VEHICLE) ); GetDriver()->TakeDamage( info ); } } int CPropVehicleDriveable::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) { return GetPhysics()->VPhysicsGetObjectList( pList, listMax ); } //----------------------------------------------------------------------------- // Purpose: Handle trace attacks from the physcannon //----------------------------------------------------------------------------- void CPropVehicleDriveable::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) { // If we've just been zapped by the physcannon, try and right ourselves if ( info.GetDamageType() & DMG_PHYSGUN ) { float flUprightStrength = GetUprightStrength(); if ( flUprightStrength ) { // Update our strength value if we already have an upright controller if ( m_hKeepUpright ) { variant_t limitVariant; limitVariant.SetFloat( flUprightStrength ); m_hKeepUpright->AcceptInput( "SetAngularLimit", this, this, limitVariant, USE_TOGGLE ); } else { // If we don't have one, create an upright controller for us m_hKeepUpright = CreateKeepUpright( GetAbsOrigin(), vec3_angle, this, GetUprightStrength(), false ); } Assert( m_hKeepUpright ); variant_t emptyVariant; m_hKeepUpright->AcceptInput( "TurnOn", this, this, emptyVariant, USE_TOGGLE ); // Turn off the keepupright after a short time m_flTurnOffKeepUpright = gpGlobals->curtime + GetUprightTime(); SetNextThink( gpGlobals->curtime ); } #ifdef HL2_EPISODIC // Notify all children for ( int i = 0; i < m_hPhysicsChildren.Count(); i++ ) { if ( m_hPhysicsChildren[i] == NULL ) continue; variant_t emptyVariant; m_hPhysicsChildren[i]->AcceptInput( "VehiclePunted", info.GetAttacker(), this, emptyVariant, USE_TOGGLE ); } #endif // HL2_EPISODIC } BaseClass::TraceAttack( info, vecDir, ptr ); } //============================================================================= // Passenger carrier //----------------------------------------------------------------------------- // Purpose: // Input : *pPassenger - // bCompanion - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropVehicleDriveable::NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) { // Always allowed unless a leaf class says otherwise return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPassenger - // bCompanion - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropVehicleDriveable::NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) { // Always allowed unless a leaf class says otherwise return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPassenger - // bCompanion - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPropVehicleDriveable::NPC_AddPassenger( CAI_BaseNPC *pPassenger, string_t strRoleName, int nSeatID ) { // Must be allowed to enter if ( NPC_CanEnterVehicle( pPassenger, true /*FIXME*/ ) == false ) return false; IServerVehicle *pVehicleServer = GetServerVehicle(); if ( pVehicleServer != NULL ) return pVehicleServer->NPC_AddPassenger( pPassenger, strRoleName, nSeatID ); return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPassenger - // bCompanion - //----------------------------------------------------------------------------- bool CPropVehicleDriveable::NPC_RemovePassenger( CAI_BaseNPC *pPassenger ) { // Must be allowed to exit if ( NPC_CanExitVehicle( pPassenger, true /*FIXME*/ ) == false ) return false; IServerVehicle *pVehicleServer = GetServerVehicle(); if ( pVehicleServer != NULL ) return pVehicleServer->NPC_RemovePassenger( pPassenger ); return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pVictim - // &info - //----------------------------------------------------------------------------- void CPropVehicleDriveable::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { CBaseEntity *pDriver = GetDriver(); if ( pDriver != NULL ) { pDriver->Event_KilledOther( pVictim, info ); } BaseClass::Event_KilledOther( pVictim, info ); } //======================================================================================================================================== // FOUR WHEEL PHYSICS VEHICLE SERVER VEHICLE //======================================================================================================================================== CFourWheelServerVehicle::CFourWheelServerVehicle( void ) { // Setup our smoothing data memset( &m_ViewSmoothing, 0, sizeof( m_ViewSmoothing ) ); m_ViewSmoothing.bClampEyeAngles = true; m_ViewSmoothing.bDampenEyePosition = true; m_ViewSmoothing.flPitchCurveZero = PITCH_CURVE_ZERO; m_ViewSmoothing.flPitchCurveLinear = PITCH_CURVE_LINEAR; m_ViewSmoothing.flRollCurveZero = ROLL_CURVE_ZERO; m_ViewSmoothing.flRollCurveLinear = ROLL_CURVE_LINEAR; } #ifdef HL2_EPISODIC ConVar r_JeepFOV( "r_JeepFOV", "82", FCVAR_CHEAT | FCVAR_REPLICATED ); #else ConVar r_JeepFOV( "r_JeepFOV", "90", FCVAR_CHEAT | FCVAR_REPLICATED ); #endif // HL2_EPISODIC //----------------------------------------------------------------------------- // Purpose: Setup our view smoothing information //----------------------------------------------------------------------------- void CFourWheelServerVehicle::InitViewSmoothing( const Vector &vecOrigin, const QAngle &vecAngles ) { m_ViewSmoothing.bWasRunningAnim = false; m_ViewSmoothing.vecOriginSaved = vecOrigin; m_ViewSmoothing.vecAnglesSaved = vecAngles; m_ViewSmoothing.flFOV = r_JeepFOV.GetFloat(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFourWheelServerVehicle::SetVehicle( CBaseEntity *pVehicle ) { ASSERT( dynamic_cast(pVehicle) ); BaseClass::SetVehicle( pVehicle ); // Save this for view smoothing if ( pVehicle != NULL ) { m_ViewSmoothing.pVehicle = pVehicle->GetBaseAnimating(); } } //----------------------------------------------------------------------------- // Purpose: Modify the player view/camera while in a vehicle //----------------------------------------------------------------------------- void CFourWheelServerVehicle::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles, float *pFOV /*= NULL*/ ) { CBaseEntity *pDriver = GetPassenger( nRole ); if ( pDriver && pDriver->IsPlayer()) { CBasePlayer *pPlayerDriver = ToBasePlayer( pDriver ); CPropVehicleDriveable *pVehicle = GetFourWheelVehicle(); SharedVehicleViewSmoothing( pPlayerDriver, pAbsOrigin, pAbsAngles, pVehicle->IsEnterAnimOn(), pVehicle->IsExitAnimOn(), pVehicle->GetEyeExitEndpoint(), &m_ViewSmoothing, pFOV ); } else { // NPCs are not supported Assert( 0 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const vehicleparams_t *CFourWheelServerVehicle::GetVehicleParams( void ) { return &GetFourWheelVehiclePhysics()->GetVehicleParams(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const vehicle_operatingparams_t *CFourWheelServerVehicle::GetVehicleOperatingParams( void ) { return &GetFourWheelVehiclePhysics()->GetVehicleOperatingParams(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const vehicle_controlparams_t *CFourWheelServerVehicle::GetVehicleControlParams( void ) { return &GetFourWheelVehiclePhysics()->GetVehicleControls(); } IPhysicsVehicleController *CFourWheelServerVehicle::GetVehicleController() { return GetFourWheelVehiclePhysics()->GetVehicleController(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPropVehicleDriveable *CFourWheelServerVehicle::GetFourWheelVehicle( void ) { return (CPropVehicleDriveable *)m_pVehicle; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CFourWheelVehiclePhysics *CFourWheelServerVehicle::GetFourWheelVehiclePhysics( void ) { CPropVehicleDriveable *pVehicle = GetFourWheelVehicle(); return pVehicle->GetPhysics(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CFourWheelServerVehicle::IsVehicleUpright( void ) { return (GetFourWheelVehicle()->IsOverturned() == false); } bool CFourWheelServerVehicle::IsVehicleBodyInWater() { return GetFourWheelVehicle()->IsVehicleBodyInWater(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CFourWheelServerVehicle::IsPassengerEntering( void ) { return GetFourWheelVehicle()->IsEnterAnimOn(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CFourWheelServerVehicle::IsPassengerExiting( void ) { return GetFourWheelVehicle()->IsExitAnimOn(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFourWheelServerVehicle::NPC_SetDriver( CNPC_VehicleDriver *pDriver ) { if ( pDriver ) { m_nNPCButtons = 0; GetFourWheelVehicle()->m_hNPCDriver = pDriver; GetFourWheelVehicle()->StartEngine(); SetVehicleVolume( 1.0 ); // Vehicles driven by NPCs are louder // Set our owner entity to be the NPC, so it can path check without hitting us GetFourWheelVehicle()->SetOwnerEntity( pDriver ); // Start Thinking GetFourWheelVehicle()->SetNextThink( gpGlobals->curtime ); } else { GetFourWheelVehicle()->m_hNPCDriver = NULL; GetFourWheelVehicle()->StopEngine(); GetFourWheelVehicle()->SetOwnerEntity( NULL ); SetVehicleVolume( 0.5 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFourWheelServerVehicle::NPC_DriveVehicle( void ) { #ifdef HL2_DLL if ( g_debug_vehicledriver.GetInt() ) { if ( m_nNPCButtons ) { Vector vecForward, vecRight; GetFourWheelVehicle()->GetVectors( &vecForward, &vecRight, NULL ); if ( m_nNPCButtons & IN_FORWARD ) { NDebugOverlay::Line( GetFourWheelVehicle()->GetAbsOrigin(), GetFourWheelVehicle()->GetAbsOrigin() + vecForward * 200, 0,255,0, true, 0.1 ); } if ( m_nNPCButtons & IN_BACK ) { NDebugOverlay::Line( GetFourWheelVehicle()->GetAbsOrigin(), GetFourWheelVehicle()->GetAbsOrigin() - vecForward * 200, 0,255,0, true, 0.1 ); } if ( m_nNPCButtons & IN_MOVELEFT ) { NDebugOverlay::Line( GetFourWheelVehicle()->GetAbsOrigin(), GetFourWheelVehicle()->GetAbsOrigin() - vecRight * 200 * -m_flTurnDegrees, 0,255,0, true, 0.1 ); } if ( m_nNPCButtons & IN_MOVERIGHT ) { NDebugOverlay::Line( GetFourWheelVehicle()->GetAbsOrigin(), GetFourWheelVehicle()->GetAbsOrigin() + vecRight * 200 * m_flTurnDegrees, 0,255,0, true, 0.1 ); } if ( m_nNPCButtons & IN_JUMP ) { NDebugOverlay::Box( GetFourWheelVehicle()->GetAbsOrigin(), -Vector(20,20,20), Vector(20,20,20), 0,255,0, true, 0.1 ); } } } #endif int buttonsChanged = m_nPrevNPCButtons ^ m_nNPCButtons; int afButtonPressed = buttonsChanged & m_nNPCButtons; // The changed ones still down are "pressed" int afButtonReleased = buttonsChanged & (~m_nNPCButtons); // The ones not down are "released" CUserCmd fakeCmd; fakeCmd.Reset(); fakeCmd.buttons = m_nNPCButtons; fakeCmd.forwardmove += 200.0f * ( m_nNPCButtons & IN_FORWARD ); fakeCmd.forwardmove -= 200.0f * ( m_nNPCButtons & IN_BACK ); fakeCmd.sidemove -= 200.0f * ( m_nNPCButtons & IN_MOVELEFT ); fakeCmd.sidemove += 200.0f * ( m_nNPCButtons & IN_MOVERIGHT ); GetFourWheelVehicle()->DriveVehicle( gpGlobals->frametime, &fakeCmd, afButtonPressed, afButtonReleased ); m_nPrevNPCButtons = m_nNPCButtons; // NPC's cheat by using analog steering. GetFourWheelVehiclePhysics()->SetSteering( m_flTurnDegrees, 0 ); // Clear out attack buttons each frame m_nNPCButtons &= ~IN_ATTACK; m_nNPCButtons &= ~IN_ATTACK2; } //----------------------------------------------------------------------------- // Purpose: // Input : nWheelIndex - // &vecPos - //----------------------------------------------------------------------------- bool CFourWheelServerVehicle::GetWheelContactPoint( int nWheelIndex, Vector &vecPos ) { // Dig through a couple layers to get to our data CFourWheelVehiclePhysics *pVehiclePhysics = GetFourWheelVehiclePhysics(); if ( pVehiclePhysics ) { IPhysicsVehicleController *pVehicleController = pVehiclePhysics->GetVehicle(); if ( pVehicleController ) { return pVehicleController->GetWheelContactPoint( nWheelIndex, &vecPos, NULL ); } } return false; }