You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2747 lines
83 KiB
2747 lines
83 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "vcollide_parse.h" |
|
#include "vehicle_base.h" |
|
#include "npc_vehicledriver.h" |
|
#include "in_buttons.h" |
|
#include "engine/IEngineSound.h" |
|
#include "soundenvelope.h" |
|
#include "SoundEmitterSystem/isoundemittersystembase.h" |
|
#include "saverestore_utlvector.h" |
|
#include "KeyValues.h" |
|
#include "studio.h" |
|
#include "bone_setup.h" |
|
#include "collisionutils.h" |
|
#include "animation.h" |
|
#include "env_player_surface_trigger.h" |
|
#include "rumble_shared.h" |
|
|
|
#ifdef HL2_DLL |
|
#include "hl2_player.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar g_debug_vehiclesound( "g_debug_vehiclesound", "0", FCVAR_CHEAT ); |
|
ConVar g_debug_vehicleexit( "g_debug_vehicleexit", "0", FCVAR_CHEAT ); |
|
|
|
ConVar sv_vehicle_autoaim_scale("sv_vehicle_autoaim_scale", "8"); |
|
|
|
bool ShouldVehicleIgnoreEntity( CBaseEntity *pVehicle, CBaseEntity *pCollide ); |
|
|
|
#define HITBOX_SET 2 |
|
|
|
//----------------------------------------------------------------------------- |
|
// Save/load |
|
//----------------------------------------------------------------------------- |
|
BEGIN_DATADESC_NO_BASE( vehicle_gear_t ) |
|
|
|
DEFINE_FIELD( flMinSpeed, FIELD_FLOAT ), |
|
DEFINE_FIELD( flMaxSpeed, FIELD_FLOAT ), |
|
DEFINE_FIELD( flSpeedApproachFactor,FIELD_FLOAT ), |
|
|
|
END_DATADESC() |
|
|
|
BEGIN_DATADESC_NO_BASE( vehicle_crashsound_t ) |
|
DEFINE_FIELD( flMinSpeed, FIELD_FLOAT ), |
|
DEFINE_FIELD( flMinDeltaSpeed, FIELD_FLOAT ), |
|
DEFINE_FIELD( iszCrashSound, FIELD_STRING ), |
|
DEFINE_FIELD( gearLimit, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
BEGIN_DATADESC_NO_BASE( vehiclesounds_t ) |
|
|
|
DEFINE_AUTO_ARRAY( iszSound, FIELD_STRING ), |
|
DEFINE_UTLVECTOR( pGears, FIELD_EMBEDDED ), |
|
DEFINE_UTLVECTOR( crashSounds, FIELD_EMBEDDED ), |
|
DEFINE_AUTO_ARRAY( iszStateSounds, FIELD_STRING ), |
|
DEFINE_AUTO_ARRAY( minStateTime, FIELD_FLOAT ), |
|
|
|
END_DATADESC() |
|
|
|
BEGIN_SIMPLE_DATADESC( CPassengerInfo ) |
|
DEFINE_FIELD( m_hPassenger, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_strRoleName, FIELD_STRING ), |
|
DEFINE_FIELD( m_strSeatName, FIELD_STRING ), |
|
// NOT SAVED |
|
// DEFINE_FIELD( m_nRole, FIELD_INTEGER ), |
|
// DEFINE_FIELD( m_nSeat, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
BEGIN_SIMPLE_DATADESC( CBaseServerVehicle ) |
|
|
|
// These are reset every time by the constructor of the owning class |
|
// DEFINE_FIELD( m_pVehicle, FIELD_CLASSPTR ), |
|
// DEFINE_FIELD( m_pDrivableVehicle; ??? ), |
|
|
|
// Controls |
|
DEFINE_FIELD( m_nNPCButtons, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nPrevNPCButtons, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flTurnDegrees, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flVehicleVolume, FIELD_FLOAT ), |
|
|
|
// We're going to reparse this data from file in Precache |
|
DEFINE_EMBEDDED( m_vehicleSounds ), |
|
|
|
DEFINE_FIELD( m_iSoundGear, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flSpeedPercentage, FIELD_FLOAT ), |
|
DEFINE_SOUNDPATCH( m_pStateSound ), |
|
DEFINE_SOUNDPATCH( m_pStateSoundFade ), |
|
DEFINE_FIELD( m_soundState, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_soundStateStartTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_lastSpeed, FIELD_FLOAT ), |
|
|
|
// NOT SAVED |
|
// DEFINE_FIELD( m_EntryAnimations, CUtlVector ), |
|
// DEFINE_FIELD( m_ExitAnimations, CUtlVector ), |
|
// DEFINE_FIELD( m_bParsedAnimations, FIELD_BOOLEAN ), |
|
// DEFINE_UTLVECTOR( m_PassengerRoles, FIELD_EMBEDDED ), |
|
|
|
DEFINE_FIELD( m_iCurrentExitAnim, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_vecCurrentExitEndPoint, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_chPreviousTextureType, FIELD_CHARACTER ), |
|
|
|
DEFINE_FIELD( m_savedViewOffset, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_hExitBlocker, FIELD_EHANDLE ), |
|
|
|
DEFINE_UTLVECTOR( m_PassengerInfo, FIELD_EMBEDDED ), |
|
|
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Base class for drivable vehicle handling. Contain it in your |
|
// drivable vehicle. |
|
//----------------------------------------------------------------------------- |
|
CBaseServerVehicle::CBaseServerVehicle( void ) |
|
{ |
|
m_pVehicle = NULL; |
|
m_pDrivableVehicle = NULL; |
|
m_nNPCButtons = 0; |
|
m_nPrevNPCButtons = 0; |
|
m_flTurnDegrees = 0; |
|
|
|
m_bParsedAnimations = false; |
|
m_iCurrentExitAnim = 0; |
|
m_vecCurrentExitEndPoint = vec3_origin; |
|
|
|
m_flVehicleVolume = 0.5; |
|
m_iSoundGear = 0; |
|
m_pStateSound = NULL; |
|
m_pStateSoundFade = NULL; |
|
m_soundState = SS_NONE; |
|
m_flSpeedPercentage = 0; |
|
m_bUseLegacyExitChecks = false; |
|
|
|
m_vehicleSounds.Init(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseServerVehicle::~CBaseServerVehicle( void ) |
|
{ |
|
SoundShutdown(0); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::Precache( void ) |
|
{ |
|
int i; |
|
// Precache our other sounds |
|
for ( i = 0; i < VS_NUM_SOUNDS; i++ ) |
|
{ |
|
if ( m_vehicleSounds.iszSound[i] != NULL_STRING ) |
|
{ |
|
CBaseEntity::PrecacheScriptSound( STRING(m_vehicleSounds.iszSound[i]) ); |
|
} |
|
} |
|
for ( i = 0; i < m_vehicleSounds.crashSounds.Count(); i++ ) |
|
{ |
|
if ( m_vehicleSounds.crashSounds[i].iszCrashSound != NULL_STRING ) |
|
{ |
|
CBaseEntity::PrecacheScriptSound( STRING(m_vehicleSounds.crashSounds[i].iszCrashSound) ); |
|
} |
|
} |
|
|
|
for ( i = 0; i < SS_NUM_STATES; i++ ) |
|
{ |
|
if ( m_vehicleSounds.iszStateSounds[i] != NULL_STRING ) |
|
{ |
|
CBaseEntity::PrecacheScriptSound( STRING(m_vehicleSounds.iszStateSounds[i]) ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parses the vehicle's script for the vehicle sounds |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::Initialize( const char *pScriptName ) |
|
{ |
|
// Attempt to parse our vehicle script |
|
if ( PhysFindOrAddVehicleScript( pScriptName, NULL, &m_vehicleSounds ) == false ) |
|
return false; |
|
|
|
Precache(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::SetVehicle( CBaseEntity *pVehicle ) |
|
{ |
|
m_pVehicle = pVehicle; |
|
m_pDrivableVehicle = dynamic_cast<IDrivableVehicle*>(m_pVehicle); |
|
Assert( m_pDrivableVehicle ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
IDrivableVehicle *CBaseServerVehicle::GetDrivableVehicle( void ) |
|
{ |
|
Assert( m_pDrivableVehicle ); |
|
return m_pDrivableVehicle; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the driver. Unlike GetPassenger(VEHICLE_ROLE_DRIVER), it will return |
|
// the NPC driver if it has one. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CBaseServerVehicle::GetDriver( void ) |
|
{ |
|
return GetPassenger( VEHICLE_ROLE_DRIVER ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseCombatCharacter *CBaseServerVehicle::GetPassenger( int nRole ) |
|
{ |
|
Assert( nRole == VEHICLE_ROLE_DRIVER ); |
|
CBaseEntity *pDriver = GetDrivableVehicle()->GetDriver(); |
|
|
|
if ( pDriver == NULL ) |
|
return NULL; |
|
|
|
return pDriver->MyCombatCharacterPointer(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::GetPassengerRole( CBaseCombatCharacter *pPassenger ) |
|
{ |
|
if ( pPassenger == GetDrivableVehicle()->GetDriver() ) |
|
return VEHICLE_ROLE_DRIVER; |
|
|
|
return VEHICLE_ROLE_NONE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds a passenger to the vehicle |
|
// Input : nSeat - seat to sit in |
|
// *pPassenger - character to enter |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::NPC_AddPassenger( CBaseCombatCharacter *pPassenger, string_t strRoleName, int nSeat ) |
|
{ |
|
// Players cannot yet use this code! - jdw |
|
Assert( pPassenger != NULL && pPassenger->IsPlayer() == false ); |
|
if ( pPassenger == NULL || pPassenger->IsPlayer() ) |
|
return false; |
|
|
|
// Find our role |
|
int nRole = FindRoleIndexByName( strRoleName ); |
|
if ( nRole == -1 ) |
|
return false; |
|
|
|
// Cannot evict a passenger already in this position |
|
CBaseCombatCharacter *pCurrentPassenger = NPC_GetPassengerInSeat( nRole, nSeat ); |
|
if ( pCurrentPassenger == pPassenger ) |
|
return true; |
|
|
|
// If we weren't the same passenger, we need to be empty |
|
if ( pCurrentPassenger != NULL ) |
|
return false; |
|
|
|
// Find the seat |
|
for ( int i = 0; i < m_PassengerInfo.Count(); i++ ) |
|
{ |
|
if ( m_PassengerInfo[i].GetSeat() == nSeat && m_PassengerInfo[i].GetRole() == nRole ) |
|
{ |
|
m_PassengerInfo[i].m_hPassenger = pPassenger; |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes a passenger from the vehicle |
|
// Input : *pPassenger - Passenger to remove |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::NPC_RemovePassenger( CBaseCombatCharacter *pPassenger ) |
|
{ |
|
// Players cannot yet use this code! - jdw |
|
Assert( pPassenger != NULL && pPassenger->IsPlayer() == false ); |
|
if ( pPassenger == NULL || pPassenger->IsPlayer() ) |
|
return false; |
|
|
|
// Find the seat |
|
for ( int i = 0; i < m_PassengerInfo.Count(); i++ ) |
|
{ |
|
if ( m_PassengerInfo[i].m_hPassenger == pPassenger ) |
|
{ |
|
m_PassengerInfo[i].m_hPassenger = NULL; |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the attachment point index for the passenger's seat |
|
// Input : *pPassenger - Passenger in the seat |
|
// Output : int - Attachment point index for the vehicle |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::NPC_GetPassengerSeatAttachment( CBaseCombatCharacter *pPassenger ) |
|
{ |
|
// Get the role and seat the the supplied passenger |
|
for ( int i = 0; i < m_PassengerInfo.Count(); i++ ) |
|
{ |
|
// If this is the passenger, get the attachment it'll be at |
|
if ( m_PassengerInfo[i].m_hPassenger == pPassenger ) |
|
{ |
|
// The seat is the attachment point |
|
int nSeat = m_PassengerInfo[i].GetSeat(); |
|
int nRole = m_PassengerInfo[i].GetRole(); |
|
|
|
return m_PassengerRoles[nRole].m_PassengerSeats[nSeat].GetAttachmentID(); |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the worldspace position and angles of the specified seat |
|
// Input : *pPassenger - Passenger's seat to use |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::NPC_GetPassengerSeatPosition( CBaseCombatCharacter *pPassenger, Vector *vecResultPos, QAngle *vecResultAngles ) |
|
{ |
|
// Get our attachment point |
|
int nSeatAttachment = NPC_GetPassengerSeatAttachment( pPassenger ); |
|
if ( nSeatAttachment == -1 ) |
|
return false; |
|
|
|
// Figure out which entrypoint hitbox the player is in |
|
CBaseAnimating *pAnimating = dynamic_cast< CBaseAnimating * >( m_pVehicle ); |
|
if ( pAnimating == NULL ) |
|
return false; |
|
|
|
Vector vecPos; |
|
QAngle vecAngles; |
|
pAnimating->GetAttachment( nSeatAttachment, vecPos, vecAngles ); |
|
|
|
if ( vecResultPos != NULL ) |
|
{ |
|
*vecResultPos = vecPos; |
|
} |
|
|
|
if ( vecResultAngles != NULL ) |
|
{ |
|
*vecResultAngles = vecAngles; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the localspace position and angles of the specified seat |
|
// Input : *pPassenger - Passenger's seat to use |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::NPC_GetPassengerSeatPositionLocal( CBaseCombatCharacter *pPassenger, Vector *vecResultPos, QAngle *vecResultAngles ) |
|
{ |
|
// Get our attachment point |
|
int nSeatAttachment = NPC_GetPassengerSeatAttachment( pPassenger ); |
|
if ( nSeatAttachment == -1 ) |
|
return false; |
|
|
|
// Figure out which entrypoint hitbox the player is in |
|
CBaseAnimating *pAnimating = m_pVehicle->GetBaseAnimating(); |
|
if ( pAnimating == NULL ) |
|
return false; |
|
|
|
Vector vecPos; |
|
QAngle vecAngles; |
|
pAnimating->InvalidateBoneCache(); // NOTE: We're moving with velocity, so we're almost always out of date |
|
pAnimating->GetAttachmentLocal( nSeatAttachment, vecPos, vecAngles ); |
|
|
|
if ( vecResultPos != NULL ) |
|
{ |
|
*vecResultPos = vecPos; |
|
} |
|
|
|
if ( vecResultAngles != NULL ) |
|
{ |
|
*vecResultAngles = vecAngles; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retrieves a list of animations used to enter/exit the seat occupied by the passenger |
|
// Input : *pPassenger - Passenger who's seat anims to retrieve |
|
// nType - which set of animations to retrieve |
|
//----------------------------------------------------------------------------- |
|
const PassengerSeatAnims_t *CBaseServerVehicle::NPC_GetPassengerSeatAnims( CBaseCombatCharacter *pPassenger, PassengerSeatAnimType_t nType ) |
|
{ |
|
// Get the role and seat the the supplied passenger |
|
for ( int i = 0; i < m_PassengerInfo.Count(); i++ ) |
|
{ |
|
if ( m_PassengerInfo[i].m_hPassenger == pPassenger ) |
|
{ |
|
int nSeat = m_PassengerInfo[i].GetSeat(); |
|
int nRole = m_PassengerInfo[i].GetRole(); |
|
switch( nType ) |
|
{ |
|
case PASSENGER_SEAT_ENTRY: |
|
return &m_PassengerRoles[nRole].m_PassengerSeats[nSeat].m_EntryTransitions; |
|
break; |
|
|
|
case PASSENGER_SEAT_EXIT: |
|
return &m_PassengerRoles[nRole].m_PassengerSeats[nSeat].m_ExitTransitions; |
|
break; |
|
|
|
default: |
|
return NULL; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get and set the current driver. Use PassengerRole_t enum in shareddefs.h for adding passengers |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::SetPassenger( int nRole, CBaseCombatCharacter *pPassenger ) |
|
{ |
|
// Baseclass only handles vehicles with a single passenger |
|
Assert( nRole == VEHICLE_ROLE_DRIVER ); |
|
|
|
if ( pPassenger != NULL && pPassenger->IsPlayer() == false ) |
|
{ |
|
// Use NPC_AddPassenger() for NPCs at the moment, these will all be collapsed into one system -- jdw |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
// Getting in? or out? |
|
if ( pPassenger != NULL ) |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); |
|
if ( pPlayer != NULL ) |
|
{ |
|
m_savedViewOffset = pPlayer->GetViewOffset(); |
|
pPlayer->SetViewOffset( vec3_origin ); |
|
pPlayer->ShowCrosshair( false ); |
|
|
|
GetDrivableVehicle()->EnterVehicle( pPassenger ); |
|
|
|
#ifdef HL2_DLL |
|
// Stop the player sprint and flashlight. |
|
CHL2_Player *pHL2Player = dynamic_cast<CHL2_Player*>( pPlayer ); |
|
if ( pHL2Player ) |
|
{ |
|
if ( pHL2Player->IsSprinting() ) |
|
{ |
|
pHL2Player->StopSprinting(); |
|
} |
|
|
|
if ( pHL2Player->FlashlightIsOn() ) |
|
{ |
|
pHL2Player->FlashlightTurnOff(); |
|
} |
|
} |
|
#endif |
|
} |
|
} |
|
else |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( GetDriver() ); |
|
if ( pPlayer ) |
|
{ |
|
// Restore the exiting player's view offset |
|
pPlayer->SetViewOffset( m_savedViewOffset ); |
|
pPlayer->ShowCrosshair( true ); |
|
} |
|
|
|
GetDrivableVehicle()->ExitVehicle( nRole ); |
|
GetDrivableVehicle()->SetVehicleEntryAnim( false ); |
|
UTIL_Remove( m_hExitBlocker ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get a position in *world space* inside the vehicle for the player to start at |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::GetPassengerSeatPoint( int nRole, Vector *pPoint, QAngle *pAngles ) |
|
{ |
|
Assert( nRole == VEHICLE_ROLE_DRIVER ); |
|
|
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
if ( pAnimating ) |
|
{ |
|
char pAttachmentName[32]; |
|
Q_snprintf( pAttachmentName, sizeof( pAttachmentName ), "vehicle_feet_passenger%d", nRole ); |
|
int nFeetAttachmentIndex = pAnimating->LookupAttachment(pAttachmentName); |
|
int nIdleSequence = pAnimating->SelectWeightedSequence( ACT_IDLE ); |
|
if ( nFeetAttachmentIndex > 0 && nIdleSequence != -1 ) |
|
{ |
|
// FIXME: This really wants to be a faster query than this implementation! |
|
Vector vecOrigin; |
|
QAngle vecAngles; |
|
if ( GetLocalAttachmentAtTime( nIdleSequence, nFeetAttachmentIndex, 0.0f, &vecOrigin, &vecAngles ) ) |
|
{ |
|
UTIL_ParentToWorldSpace( pAnimating, vecOrigin, vecAngles ); |
|
if ( pPoint ) |
|
{ |
|
*pPoint = vecOrigin; |
|
} |
|
|
|
if ( pAngles ) |
|
{ |
|
*pAngles = vecAngles; |
|
} |
|
|
|
return; |
|
} |
|
} |
|
} |
|
|
|
// Couldn't find the attachment point, so just use the origin |
|
if ( pPoint ) |
|
{ |
|
*pPoint = m_pVehicle->GetAbsOrigin(); |
|
} |
|
|
|
if ( pAngles ) |
|
{ |
|
*pAngles = m_pVehicle->GetAbsAngles(); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------------------------------- |
|
// Check Exit Point for leaving vehicle. |
|
// |
|
// Input: yaw/roll from vehicle angle to check for exit |
|
// distance from origin to drop player (allows for different shaped vehicles |
|
// Output: returns true if valid location, pEndPoint |
|
// updated with actual exit point |
|
//--------------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::CheckExitPoint( float yaw, int distance, Vector *pEndPoint ) |
|
{ |
|
QAngle vehicleAngles = m_pVehicle->GetLocalAngles(); |
|
Vector vecStart = m_pVehicle->GetAbsOrigin(); |
|
Vector vecDir; |
|
|
|
vecStart.z += 12; // always 12" from ground |
|
vehicleAngles[YAW] += yaw; |
|
AngleVectors( vehicleAngles, NULL, &vecDir, NULL ); |
|
// Vehicles are oriented along the Y axis |
|
vecDir *= -1; |
|
*pEndPoint = vecStart + vecDir * distance; |
|
|
|
trace_t tr; |
|
UTIL_TraceHull( vecStart, *pEndPoint, VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, m_pVehicle, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction < 1.0 ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Where does this passenger exit the vehicle? |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::GetPassengerExitPoint( int nRole, Vector *pExitPoint, QAngle *pAngles ) |
|
{ |
|
Assert( nRole == VEHICLE_ROLE_DRIVER ); |
|
|
|
// First, see if we've got an attachment point |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
if ( pAnimating ) |
|
{ |
|
Vector vehicleExitOrigin; |
|
QAngle vehicleExitAngles; |
|
if ( pAnimating->GetAttachment( "vehicle_driver_exit", vehicleExitOrigin, vehicleExitAngles ) ) |
|
{ |
|
// Make sure it's clear |
|
trace_t tr; |
|
UTIL_TraceHull( vehicleExitOrigin + Vector(0, 0, 12), vehicleExitOrigin, VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, m_pVehicle, COLLISION_GROUP_NONE, &tr ); |
|
if ( !tr.startsolid ) |
|
{ |
|
*pAngles = vehicleExitAngles; |
|
*pExitPoint = tr.endpos; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
// left side |
|
if( CheckExitPoint( 90, 90, pExitPoint ) ) // angle from car, distance from origin, actual exit point |
|
return true; |
|
|
|
// right side |
|
if( CheckExitPoint( -90, 90, pExitPoint ) ) |
|
return true; |
|
|
|
// front |
|
if( CheckExitPoint( 0, 100, pExitPoint ) ) |
|
return true; |
|
|
|
// back |
|
if( CheckExitPoint( 180, 170, pExitPoint ) ) |
|
return true; |
|
|
|
// All else failed, try popping them out the top. |
|
Vector vecWorldMins, vecWorldMaxs; |
|
m_pVehicle->CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs ); |
|
pExitPoint->x = (vecWorldMins.x + vecWorldMaxs.x) * 0.5f; |
|
pExitPoint->y = (vecWorldMins.y + vecWorldMaxs.y) * 0.5f; |
|
pExitPoint->z = vecWorldMaxs.z + 50.0f; |
|
|
|
// Make sure it's clear |
|
trace_t tr; |
|
UTIL_TraceHull( m_pVehicle->CollisionProp()->WorldSpaceCenter(), *pExitPoint, VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, m_pVehicle, COLLISION_GROUP_NONE, &tr ); |
|
if ( !tr.startsolid ) |
|
{ |
|
return true; |
|
} |
|
|
|
// No clear exit point available! |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ParseExitAnim( KeyValues *pkvExitList, bool bEscapeExit ) |
|
{ |
|
// Look through the entry animations list |
|
KeyValues *pkvExitAnim = pkvExitList->GetFirstSubKey(); |
|
while ( pkvExitAnim ) |
|
{ |
|
// Add 'em to our list |
|
int iIndex = m_ExitAnimations.AddToTail(); |
|
Q_strncpy( m_ExitAnimations[iIndex].szAnimName, pkvExitAnim->GetName(), sizeof(m_ExitAnimations[iIndex].szAnimName) ); |
|
m_ExitAnimations[iIndex].bEscapeExit = bEscapeExit; |
|
if ( !Q_strncmp( pkvExitAnim->GetString(), "upsidedown", 10 ) ) |
|
{ |
|
m_ExitAnimations[iIndex].bUpright = false; |
|
} |
|
else |
|
{ |
|
m_ExitAnimations[iIndex].bUpright = true; |
|
} |
|
|
|
pkvExitAnim = pkvExitAnim->GetNextKey(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse the transition information |
|
// Input : *pTransitionKeyValues - key values to parse |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ParseNPCSeatTransition( KeyValues *pTransitionKeyValues, CPassengerSeatTransition *pTransition ) |
|
{ |
|
// Store it |
|
const char *lpszAnimName = pTransitionKeyValues->GetString( "animation" ); |
|
pTransition->m_strAnimationName = AllocPooledString( lpszAnimName ); |
|
pTransition->m_nPriority = pTransitionKeyValues->GetInt( "priority" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sorting function for vehicle seat animation priorities |
|
//----------------------------------------------------------------------------- |
|
typedef CPassengerSeatTransition SortSeatPriorityType; |
|
int __cdecl SeatPrioritySort( const SortSeatPriorityType *s1, const SortSeatPriorityType *s2 ) |
|
{ |
|
return ( s1->GetPriority() > s2->GetPriority() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse one set of entry/exit data |
|
// Input : *pSetKeyValues - Key values for this set |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ParseNPCPassengerSeat( KeyValues *pSetKeyValues, CPassengerSeat *pSeat ) |
|
{ |
|
CBaseAnimating *pAnimating = (CBaseAnimating *) m_pVehicle; |
|
|
|
// Get our attachment name |
|
const char *lpszAttachmentName = pSetKeyValues->GetString( "target_attachment" ); |
|
int nAttachmentID = pAnimating->LookupAttachment( lpszAttachmentName ); |
|
pSeat->m_nAttachmentID = nAttachmentID; |
|
pSeat->m_strSeatName = AllocPooledString( lpszAttachmentName ); |
|
|
|
KeyValues *pKey = pSetKeyValues->GetFirstSubKey(); |
|
while ( pKey != NULL ) |
|
{ |
|
const char *lpszName = pKey->GetName(); |
|
|
|
if ( Q_stricmp( lpszName, "entry" ) == 0 ) |
|
{ |
|
int nIndex = pSeat->m_EntryTransitions.AddToTail(); |
|
Assert( pSeat->m_EntryTransitions.IsValidIndex( nIndex ) ); |
|
|
|
ParseNPCSeatTransition( pKey, &pSeat->m_EntryTransitions[nIndex] ); |
|
} |
|
else if ( Q_stricmp( lpszName, "exit" ) == 0 ) |
|
{ |
|
int nIndex = pSeat->m_ExitTransitions.AddToTail(); |
|
Assert( pSeat->m_ExitTransitions.IsValidIndex( nIndex ) ); |
|
|
|
ParseNPCSeatTransition( pKey, &pSeat->m_ExitTransitions[nIndex] ); |
|
} |
|
|
|
// Advance |
|
pKey = pKey->GetNextKey(); |
|
} |
|
|
|
// Sort the seats based on their priority |
|
pSeat->m_EntryTransitions.Sort( SeatPrioritySort ); |
|
pSeat->m_ExitTransitions.Sort( SeatPrioritySort ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a passenger role (by name), or create a new one of that names |
|
// Input : strName - name of the role |
|
// : *nIndex - the index into the CUtlBuffer where this role resides |
|
// Output : CPassengerRole * - Role found or created |
|
//----------------------------------------------------------------------------- |
|
CPassengerRole *CBaseServerVehicle::FindOrCreatePassengerRole( string_t strName, int *nIndex ) |
|
{ |
|
// Try to find an already created container of the same name |
|
for ( int i = 0; i < m_PassengerRoles.Count(); i++ ) |
|
{ |
|
// If we match, return it |
|
if ( FStrEq( STRING( m_PassengerRoles[i].m_strName ), STRING( strName ) ) ) |
|
{ |
|
// Supply the index, if requested |
|
if ( nIndex != NULL ) |
|
{ |
|
*nIndex = i; |
|
} |
|
|
|
return &m_PassengerRoles[i]; |
|
} |
|
} |
|
|
|
// Create a new container |
|
int nNewIndex = m_PassengerRoles.AddToTail(); |
|
Assert( m_PassengerRoles.IsValidIndex( nNewIndex ) ); |
|
|
|
m_PassengerRoles[nNewIndex].m_strName = strName; |
|
|
|
// Supply the index, if requested |
|
if ( nIndex != NULL ) |
|
{ |
|
*nIndex = nNewIndex; |
|
} |
|
|
|
return &m_PassengerRoles[nNewIndex]; |
|
} |
|
|
|
ConVar g_debug_npc_vehicle_roles( "g_debug_npc_vehicle_roles", "0" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse NPC entry and exit anim data |
|
// Input : *pModelKeyValues - Key values from the vehicle model |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ParseNPCRoles( KeyValues *pkvPassengerList ) |
|
{ |
|
// Get the definition section |
|
if ( pkvPassengerList == NULL ) |
|
return; |
|
|
|
// Get our animating class |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
Assert( pAnimating != NULL ); |
|
if ( pAnimating == NULL ) |
|
return; |
|
|
|
// For attachment polling |
|
CStudioHdr *pStudioHdr = pAnimating->GetModelPtr(); |
|
Assert( pStudioHdr != NULL ); |
|
if ( pStudioHdr == NULL ) |
|
return; |
|
|
|
// Parse all subkeys |
|
int nRoleIndex; |
|
KeyValues *pkvPassengerKey = pkvPassengerList->GetFirstSubKey(); |
|
while ( pkvPassengerKey != NULL ) |
|
{ |
|
string_t strRoleName = AllocPooledString( pkvPassengerKey->GetName() ); |
|
|
|
// Find or create the container |
|
CPassengerRole *pRole = FindOrCreatePassengerRole( strRoleName, &nRoleIndex ); |
|
if ( pRole == NULL ) |
|
continue; |
|
|
|
// Add a new role |
|
int nSeatIndex = pRole->m_PassengerSeats.AddToTail(); |
|
Assert( pRole->m_PassengerSeats.IsValidIndex( nSeatIndex ) ); |
|
|
|
// Parse the information |
|
ParseNPCPassengerSeat( pkvPassengerKey, &pRole->m_PassengerSeats[nSeatIndex] ); |
|
|
|
// Add a matching entry into our passenger manifest |
|
int nPassengerIndex = m_PassengerInfo.AddToTail(); |
|
m_PassengerInfo[nPassengerIndex].m_hPassenger = NULL; |
|
m_PassengerInfo[nPassengerIndex].m_nSeat = nSeatIndex; |
|
m_PassengerInfo[nPassengerIndex].m_nRole = nRoleIndex; |
|
|
|
// The following are used for index fix-ups after save game restoration |
|
m_PassengerInfo[nPassengerIndex].m_strRoleName = strRoleName; |
|
m_PassengerInfo[nPassengerIndex].m_strSeatName = pRole->m_PassengerSeats[nSeatIndex].m_strSeatName; |
|
|
|
// Advance to the next key |
|
pkvPassengerKey = pkvPassengerKey->GetNextKey(); |
|
} |
|
|
|
// ====================================================================================================== |
|
// Debug print |
|
|
|
if ( g_debug_npc_vehicle_roles.GetBool() ) |
|
{ |
|
Msg("Passenger Roles Parsed:\t%d\n\n", m_PassengerRoles.Count() ); |
|
for ( int i = 0; i < m_PassengerRoles.Count(); i++ ) |
|
{ |
|
Msg("\tPassenger Role:\t%s (%d seats)\n", STRING(m_PassengerRoles[i].m_strName), m_PassengerRoles[i].m_PassengerSeats.Count() ); |
|
|
|
// Iterate through all information sets under this name |
|
for ( int j = 0; j < m_PassengerRoles[i].m_PassengerSeats.Count(); j++ ) |
|
{ |
|
Msg("\t\tAttachment: %d\n", m_PassengerRoles[i].m_PassengerSeats[j].m_nAttachmentID ); |
|
|
|
// Entries |
|
Msg("\t\tEntries:\t%d\n", m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions.Count() ); |
|
Msg("\t\t=====================\n" ); |
|
|
|
for ( int nEntry = 0; nEntry < m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions.Count(); nEntry++ ) |
|
{ |
|
Msg("\t\t\tAnimation:\t%s\t(Priority %d)\n", STRING(m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions[nEntry].m_strAnimationName), |
|
m_PassengerRoles[i].m_PassengerSeats[j].m_EntryTransitions[nEntry].m_nPriority ); |
|
} |
|
|
|
Msg("\n"); |
|
|
|
// Exits |
|
Msg("\t\tExits:\t%d\n", m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions.Count() ); |
|
Msg("\t\t=====================\n" ); |
|
|
|
for ( int nExits = 0; nExits < m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions.Count(); nExits++ ) |
|
{ |
|
Msg("\t\t\tAnimation:\t%s\t(Priority %d)\n", STRING(m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions[nExits].m_strAnimationName), |
|
m_PassengerRoles[i].m_PassengerSeats[j].m_ExitTransitions[nExits].m_nPriority ); |
|
} |
|
} |
|
|
|
Msg("\n"); |
|
} |
|
} |
|
|
|
// ====================================================================================================== |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get an attachment point at a specified time in its cycle (note: not exactly a speedy query, use sparingly!) |
|
// Input : nSequence - sequence to test |
|
// nAttachmentIndex - attachment to test |
|
// flCyclePoint - 0.0 - 1.0 |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::GetLocalAttachmentAtTime( int nQuerySequence, int nAttachmentIndex, float flCyclePoint, Vector *vecOriginOut, QAngle *vecAnglesOut ) |
|
{ |
|
CBaseAnimating *pAnimating = m_pVehicle->GetBaseAnimating(); |
|
if ( pAnimating == NULL ) |
|
return false; |
|
|
|
// TODO: It's annoying to stomp and restore this off each time when we're just going to stomp it again later, but the function |
|
// should really leave the car in an acceptable state to run this query -- jdw |
|
|
|
// Store this off for restoration later |
|
int nOldSequence = pAnimating->GetSequence(); |
|
float flOldCycle = pAnimating->GetCycle(); |
|
|
|
// Setup the model for the query |
|
pAnimating->SetSequence( nQuerySequence ); |
|
pAnimating->SetCycle( flCyclePoint ); |
|
pAnimating->InvalidateBoneCache(); |
|
|
|
// Query for the point |
|
Vector vecOrigin; |
|
QAngle vecAngles; |
|
pAnimating->GetAttachmentLocal( nAttachmentIndex, vecOrigin, vecAngles ); |
|
|
|
if ( vecOriginOut != NULL ) |
|
{ |
|
*vecOriginOut = vecOrigin; |
|
} |
|
|
|
if ( vecAnglesOut != NULL ) |
|
{ |
|
*vecAnglesOut = vecAngles; |
|
} |
|
|
|
// Restore the model after the query |
|
pAnimating->SetSequence( nOldSequence ); |
|
pAnimating->SetCycle( flOldCycle ); |
|
pAnimating->InvalidateBoneCache(); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get an attachment point at a specified time in its cycle (note: not exactly a speedy query, use sparingly!) |
|
// Input : lpszAnimName - name of the sequence to test |
|
// nAttachmentIndex - attachment to test |
|
// flCyclePoint - 0.0 - 1.0 |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::GetLocalAttachmentAtTime( const char *lpszAnimName, int nAttachmentIndex, float flCyclePoint, Vector *vecOriginOut, QAngle *vecAnglesOut ) |
|
{ |
|
CBaseAnimating *pAnimating = m_pVehicle->GetBaseAnimating(); |
|
if ( pAnimating == NULL ) |
|
return false; |
|
|
|
int nQuerySequence = pAnimating->LookupSequence( lpszAnimName ); |
|
if ( nQuerySequence < 0 ) |
|
return false; |
|
|
|
return GetLocalAttachmentAtTime( nQuerySequence, nAttachmentIndex, flCyclePoint, vecOriginOut, vecAnglesOut ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::CacheEntryExitPoints( void ) |
|
{ |
|
CBaseAnimating *pAnimating = m_pVehicle->GetBaseAnimating(); |
|
if ( pAnimating == NULL ) |
|
return; |
|
|
|
int nAttachment = pAnimating->LookupAttachment( "vehicle_driver_eyes" ); |
|
|
|
// For each exit animation, determine where the end point is and cache it |
|
for ( int i = 0; i < m_ExitAnimations.Count(); i++ ) |
|
{ |
|
if ( GetLocalAttachmentAtTime( m_ExitAnimations[i].szAnimName, nAttachment, 1.0f, &m_ExitAnimations[i].vecExitPointLocal, &m_ExitAnimations[i].vecExitAnglesLocal ) == false ) |
|
{ |
|
Warning("Exit animation %s failed to cache target points properly!\n", m_ExitAnimations[i].szAnimName ); |
|
} |
|
|
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
Vector vecExitPoint = m_ExitAnimations[i].vecExitPointLocal; |
|
QAngle vecExitAngles = m_ExitAnimations[i].vecExitAnglesLocal; |
|
UTIL_ParentToWorldSpace( pAnimating, vecExitPoint, vecExitAngles ); |
|
|
|
NDebugOverlay::Box( vecExitPoint, -Vector(8,8,8), Vector(8,8,8), 0, 255, 0, 0, 20.0f ); |
|
NDebugOverlay::Axis( vecExitPoint, vecExitAngles, 8.0f, true, 20.0f ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ParseEntryExitAnims( void ) |
|
{ |
|
// Try and find the right animation to play in the model's keyvalues |
|
KeyValues *modelKeyValues = new KeyValues(""); |
|
if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( m_pVehicle->GetModel() ), modelinfo->GetModelKeyValueText( m_pVehicle->GetModel() ) ) ) |
|
{ |
|
// Do we have an entry section? |
|
KeyValues *pkvEntryList = modelKeyValues->FindKey("vehicle_entry"); |
|
if ( pkvEntryList ) |
|
{ |
|
// Look through the entry animations list |
|
KeyValues *pkvEntryAnim = pkvEntryList->GetFirstSubKey(); |
|
while ( pkvEntryAnim ) |
|
{ |
|
// Add 'em to our list |
|
int iIndex = m_EntryAnimations.AddToTail(); |
|
Q_strncpy( m_EntryAnimations[iIndex].szAnimName, pkvEntryAnim->GetName(), sizeof(m_EntryAnimations[iIndex].szAnimName) ); |
|
m_EntryAnimations[iIndex].iHitboxGroup = pkvEntryAnim->GetInt(); |
|
|
|
pkvEntryAnim = pkvEntryAnim->GetNextKey(); |
|
} |
|
} |
|
|
|
// Do we have an exit section? |
|
KeyValues *pkvExitList = modelKeyValues->FindKey("vehicle_exit"); |
|
if ( pkvExitList ) |
|
{ |
|
ParseExitAnim( pkvExitList, false ); |
|
} |
|
|
|
// Do we have an exit section? |
|
pkvExitList = modelKeyValues->FindKey("vehicle_escape_exit"); |
|
if ( pkvExitList ) |
|
{ |
|
ParseExitAnim( pkvExitList, true ); |
|
} |
|
|
|
// Parse the NPC vehicle roles as well |
|
KeyValues *pkvPassengerList = modelKeyValues->FindKey( "vehicle_npc_passengers" ); |
|
if ( pkvPassengerList ) |
|
{ |
|
ParseNPCRoles( pkvPassengerList ); |
|
} |
|
} |
|
|
|
modelKeyValues->deleteThis(); |
|
|
|
// Determine the entry and exit points for the |
|
CacheEntryExitPoints(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::HandlePassengerEntry( CBaseCombatCharacter *pPassenger, bool bAllowEntryOutsideZone ) |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); |
|
if ( pPlayer != NULL ) |
|
{ |
|
// Find out which hitbox the player's eyepoint is within |
|
int iEntryAnim = GetEntryAnimForPoint( pPlayer->EyePosition() ); |
|
|
|
// Get this interface for animation queries |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
if ( !pAnimating ) |
|
return; |
|
|
|
// Are we in an entrypoint zone? |
|
if ( iEntryAnim == ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
// Normal get in refuses to allow entry |
|
if ( !bAllowEntryOutsideZone ) |
|
return; |
|
|
|
// We failed to find a valid entry anim, but we've got to get back in because the player's |
|
// got stuck exiting the vehicle. For now, just use the first get in anim |
|
// UNDONE: We need a better solution for this. |
|
iEntryAnim = pAnimating->LookupSequence( m_EntryAnimations[0].szAnimName ); |
|
} |
|
|
|
// Check to see if this vehicle can be controlled or if it's locked |
|
if ( GetDrivableVehicle()->CanEnterVehicle( pPlayer ) ) |
|
{ |
|
// Make sure the passenger can get in as well |
|
if ( pPlayer->CanEnterVehicle( this, VEHICLE_ROLE_DRIVER ) ) |
|
{ |
|
// Setup the "enter" vehicle sequence and skip the animation if it isn't present. |
|
pAnimating->SetCycle( 0 ); |
|
pAnimating->m_flAnimTime = gpGlobals->curtime; |
|
pAnimating->ResetSequence( iEntryAnim ); |
|
pAnimating->ResetClientsideFrame(); |
|
pAnimating->InvalidateBoneCache(); // This is necessary because we need to query attachment points this frame for blending! |
|
GetDrivableVehicle()->SetVehicleEntryAnim( true ); |
|
|
|
pPlayer->GetInVehicle( this, VEHICLE_ROLE_DRIVER ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// NPCs handle transitioning themselves, they should NOT call this function |
|
Assert( 0 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::HandlePassengerExit( CBaseCombatCharacter *pPassenger ) |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); |
|
if ( pPlayer != NULL ) |
|
{ |
|
// Clear hud hints |
|
UTIL_HudHintText( pPlayer, "" ); |
|
|
|
vbs_sound_update_t params; |
|
InitSoundParams(params); |
|
params.bExitVehicle = true; |
|
SoundState_Update( params ); |
|
|
|
// Find the right exit anim to use based on available exit points. |
|
Vector vecExitPoint; |
|
bool bAllPointsBlocked; |
|
int iSequence = GetExitAnimToUse( vecExitPoint, bAllPointsBlocked ); |
|
|
|
// If all exit points were blocked and this vehicle doesn't allow exiting in |
|
// these cases, bail. |
|
Vector vecNewPos = pPlayer->GetAbsOrigin(); |
|
QAngle angNewAngles = pPlayer->GetAbsAngles(); |
|
|
|
int nRole = GetPassengerRole( pPlayer ); |
|
if ( ( bAllPointsBlocked ) || ( iSequence == ACTIVITY_NOT_AVAILABLE ) ) |
|
{ |
|
// Animation-driven exit points are all blocked, or we have none. Fall back to the more simple static exit points. |
|
if ( !GetPassengerExitPoint( nRole, &vecNewPos, &angNewAngles ) && !GetDrivableVehicle()->AllowBlockedExit( pPlayer, nRole ) ) |
|
return false; |
|
|
|
// At this point, the player has exited the vehicle but did so without playing an animation. We need to give the vehicle a |
|
// chance to do any post-animation clean-up it may need to perform. |
|
HandleEntryExitFinish( false, true ); |
|
} |
|
|
|
// Now we either have an exit sequence to play, a valid static exit position, or we don't care |
|
// whether we're blocked or not. We're getting out, one way or another. |
|
GetDrivableVehicle()->PreExitVehicle( pPlayer, nRole ); |
|
|
|
if ( iSequence > ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
if ( pAnimating ) |
|
{ |
|
pAnimating->SetCycle( 0 ); |
|
pAnimating->m_flAnimTime = gpGlobals->curtime; |
|
pAnimating->ResetSequence( iSequence ); |
|
pAnimating->ResetClientsideFrame(); |
|
GetDrivableVehicle()->SetVehicleExitAnim( true, vecExitPoint ); |
|
|
|
// Re-deploy our weapon |
|
if ( pPlayer && pPlayer->IsAlive() ) |
|
{ |
|
if ( pPlayer->GetActiveWeapon() ) |
|
{ |
|
pPlayer->GetActiveWeapon()->Deploy(); |
|
pPlayer->ShowCrosshair( true ); |
|
} |
|
} |
|
|
|
// To prevent anything moving into the volume the player's going to occupy at the end of the exit |
|
// NOTE: Set the player as the blocker's owner so the player is allowed to intersect it |
|
Vector vecExitFeetPoint = vecExitPoint - VEC_VIEW; |
|
m_hExitBlocker = CEntityBlocker::Create( vecExitFeetPoint, VEC_HULL_MIN, VEC_HULL_MAX, pPlayer, true ); |
|
|
|
// We may as well stand where we're going to get out at and stop being parented |
|
pPlayer->SetAbsOrigin( vecExitFeetPoint ); |
|
pPlayer->SetParent( NULL ); |
|
|
|
return true; |
|
} |
|
} |
|
|
|
// Couldn't find an animation, so exit immediately |
|
pPlayer->LeaveVehicle( vecNewPos, angNewAngles ); |
|
return true; |
|
} |
|
else |
|
{ |
|
// NPCs handle transitioning themselves, they should NOT call this function |
|
Assert( 0 ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::GetEntryAnimForPoint( const Vector &vecEyePoint ) |
|
{ |
|
// Parse the vehicle animations the first time they get in the vehicle |
|
if ( !m_bParsedAnimations ) |
|
{ |
|
// Load the entry/exit animations from the vehicle |
|
ParseEntryExitAnims(); |
|
m_bParsedAnimations = true; |
|
} |
|
|
|
// No entry anims? Vehicles with no entry anims are always enterable. |
|
if ( !m_EntryAnimations.Count() ) |
|
return 0; |
|
|
|
// Figure out which entrypoint hitbox the player is in |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
if ( !pAnimating ) |
|
return 0; |
|
|
|
CStudioHdr *pStudioHdr = pAnimating->GetModelPtr(); |
|
if (!pStudioHdr) |
|
return 0; |
|
int iHitboxSet = FindHitboxSetByName( pStudioHdr, "entryboxes" ); |
|
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( iHitboxSet ); |
|
if ( !set || !set->numhitboxes ) |
|
return 0; |
|
|
|
// Loop through the hitboxes and find out which one we're in |
|
for ( int i = 0; i < set->numhitboxes; i++ ) |
|
{ |
|
mstudiobbox_t *pbox = set->pHitbox( i ); |
|
|
|
Vector vecPosition; |
|
QAngle vecAngles; |
|
pAnimating->GetBonePosition( pbox->bone, vecPosition, vecAngles ); |
|
|
|
// Build a rotation matrix from orientation |
|
matrix3x4_t fRotateMatrix; |
|
AngleMatrix( vecAngles, vecPosition, fRotateMatrix); |
|
|
|
Vector localEyePoint; |
|
VectorITransform( vecEyePoint, fRotateMatrix, localEyePoint ); |
|
if ( IsPointInBox( localEyePoint, pbox->bbmin, pbox->bbmax ) ) |
|
{ |
|
// Find the entry animation for this hitbox |
|
int iCount = m_EntryAnimations.Count(); |
|
for ( int entry = 0; entry < iCount; entry++ ) |
|
{ |
|
if ( m_EntryAnimations[entry].iHitboxGroup == pbox->group ) |
|
{ |
|
// Get the sequence for the animation |
|
return pAnimating->LookupSequence( m_EntryAnimations[entry].szAnimName ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Fail |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find an exit animation that'll get the player to a valid position |
|
// Input : vecEyeExitEndpoint - Returns with the final eye position after exiting. |
|
// bAllPointsBlocked - Returns whether all exit points were found to be blocked. |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::GetExitAnimToUse( Vector &vecEyeExitEndpoint, bool &bAllPointsBlocked ) |
|
{ |
|
bAllPointsBlocked = false; |
|
|
|
// Parse the vehicle animations the first time they get in the vehicle |
|
if ( !m_bParsedAnimations ) |
|
{ |
|
// Load the entry/exit animations from the vehicle |
|
ParseEntryExitAnims(); |
|
m_bParsedAnimations = true; |
|
} |
|
|
|
// No exit anims? |
|
if ( !m_ExitAnimations.Count() ) |
|
return ACTIVITY_NOT_AVAILABLE; |
|
|
|
// Figure out which entrypoint hitbox the player is in |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(m_pVehicle); |
|
if ( !pAnimating ) |
|
return ACTIVITY_NOT_AVAILABLE; |
|
|
|
CStudioHdr *pStudioHdr = pAnimating->GetModelPtr(); |
|
if (!pStudioHdr) |
|
return ACTIVITY_NOT_AVAILABLE; |
|
|
|
bool bUpright = IsVehicleUpright(); |
|
|
|
// Loop through the exit animations and find one that ends in a clear position |
|
// Also attempt to choose the animation which brings you closest to your view direction. |
|
CBasePlayer *pPlayer = ToBasePlayer( GetDriver() ); |
|
if ( pPlayer == NULL ) |
|
return ACTIVITY_NOT_AVAILABLE; |
|
|
|
int nRole = GetPassengerRole( pPlayer ); |
|
|
|
int nBestExitAnim = -1; |
|
bool bBestExitIsEscapePoint = true; |
|
Vector vecViewDirection, vecViewOrigin, vecBestExitPoint( 0, 0, 0 ); |
|
vecViewOrigin = pPlayer->EyePosition(); |
|
pPlayer->EyeVectors( &vecViewDirection ); |
|
vecViewDirection.z = 0.0f; |
|
VectorNormalize( vecViewDirection ); |
|
|
|
float flMaxCosAngleDelta = -2.0f; |
|
|
|
int iCount = m_ExitAnimations.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
if ( m_ExitAnimations[i].bUpright != bUpright ) |
|
continue; |
|
|
|
// Don't use an escape point if we found a non-escape point already |
|
if ( !bBestExitIsEscapePoint && m_ExitAnimations[i].bEscapeExit ) |
|
continue; |
|
|
|
Vector vehicleExitOrigin; |
|
QAngle vehicleExitAngles; |
|
|
|
// NOTE: HL2 and Ep1 used a method that relied on the animators to place attachment points in the model which marked where |
|
// the player would exit to. This was rendered unnecessary in Ep2, but the choreo vehicles of these older products |
|
// did not have proper exit animations and relied on the exact queries that were happening before. For choreo vehicles, |
|
// we now just allow them to perform those older queries to keep those products happy. - jdw |
|
|
|
// Get the position we think we're going to end up at |
|
if ( m_bUseLegacyExitChecks ) |
|
{ |
|
pAnimating->GetAttachment( m_ExitAnimations[i].szAnimName, vehicleExitOrigin, vehicleExitAngles ); |
|
} |
|
else |
|
{ |
|
vehicleExitOrigin = m_ExitAnimations[i].vecExitPointLocal; |
|
vehicleExitAngles = m_ExitAnimations[i].vecExitAnglesLocal; |
|
UTIL_ParentToWorldSpace( pAnimating, vehicleExitOrigin, vehicleExitAngles ); |
|
} |
|
|
|
// Don't bother checking points which are farther from our view direction. |
|
Vector vecDelta; |
|
VectorSubtract( vehicleExitOrigin, vecViewOrigin, vecDelta ); |
|
vecDelta.z = 0.0f; |
|
VectorNormalize( vecDelta ); |
|
float flCosAngleDelta = DotProduct( vecDelta, vecViewDirection ); |
|
|
|
// But always check non-escape exits if our current best exit is an escape exit. |
|
if ( !bBestExitIsEscapePoint || m_ExitAnimations[i].bEscapeExit ) |
|
{ |
|
if ( flCosAngleDelta < flMaxCosAngleDelta ) |
|
continue; |
|
} |
|
|
|
// The attachment points are where the driver's eyes will end up, so we subtract the view offset |
|
// to get the actual exit position. |
|
vehicleExitOrigin -= VEC_VIEW; |
|
|
|
Vector vecMove(0,0,64); |
|
Vector vecStart = vehicleExitOrigin + vecMove; |
|
Vector vecEnd = vehicleExitOrigin - vecMove; |
|
|
|
// Starting at the exit point, trace a flat plane down until we hit ground |
|
// NOTE: The hull has no vertical span because we want to test the lateral constraints against the ground, not height (yet) |
|
trace_t tr; |
|
UTIL_TraceHull( vecStart, vecEnd, VEC_HULL_MIN, Vector( VEC_HULL_MAX.x, VEC_HULL_MAX.y, VEC_HULL_MIN.z ), MASK_PLAYERSOLID, NULL, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::SweptBox( vecStart, vecEnd, VEC_HULL_MIN, Vector( VEC_HULL_MAX.x, VEC_HULL_MAX.y, VEC_HULL_MIN.y ), vec3_angle, 255, 255, 255, 8.0f, 20.0f ); |
|
} |
|
|
|
if ( tr.fraction < 1.0f ) |
|
{ |
|
// If we hit the ground, try to now "stand up" at that point to see if we'll fit |
|
UTIL_TraceHull( tr.endpos, tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, NULL, COLLISION_GROUP_NONE, &tr ); |
|
|
|
// See if we're unable to stand at this space |
|
if ( tr.startsolid ) |
|
{ |
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::Box( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 8.0f, 20.0f ); |
|
} |
|
continue; |
|
} |
|
|
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::Box( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 8.0f, 20.0f ); |
|
} |
|
} |
|
else if ( tr.allsolid || ( ( tr.fraction == 1.0 ) && !GetDrivableVehicle()->AllowMidairExit( pPlayer, nRole ) ) ) |
|
{ |
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::Box( tr.endpos, VEC_HULL_MIN, VEC_HULL_MAX, 255,0,0, 64, 10 ); |
|
} |
|
continue; |
|
} |
|
|
|
// Calculate the exit endpoint & viewpoint |
|
Vector vecExitEndPoint = tr.endpos; |
|
|
|
// Make sure we can trace to the center of the exit point |
|
UTIL_TraceLine( vecViewOrigin, vecExitEndPoint, MASK_PLAYERSOLID, pAnimating, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
#ifdef HL2_EPISODIC |
|
if ( ShouldVehicleIgnoreEntity( GetVehicleEnt(), tr.m_pEnt ) == false ) |
|
#endif //HL2_EPISODIC |
|
{ |
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::Line( vecViewOrigin, vecExitEndPoint, 255,0,0, true, 10 ); |
|
} |
|
continue; |
|
} |
|
} |
|
|
|
bBestExitIsEscapePoint = m_ExitAnimations[i].bEscapeExit; |
|
vecBestExitPoint = vecExitEndPoint; |
|
nBestExitAnim = i; |
|
flMaxCosAngleDelta = flCosAngleDelta; |
|
} |
|
|
|
if ( nBestExitAnim >= 0 ) |
|
{ |
|
m_vecCurrentExitEndPoint = vecBestExitPoint; |
|
|
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::Cross3D( m_vecCurrentExitEndPoint, 16, 0, 255, 0, true, 10 ); |
|
NDebugOverlay::Box( m_vecCurrentExitEndPoint, VEC_HULL_MIN, VEC_HULL_MAX, 255,255,255, 8, 10 ); |
|
} |
|
|
|
vecEyeExitEndpoint = vecBestExitPoint + VEC_VIEW; |
|
m_iCurrentExitAnim = nBestExitAnim; |
|
return pAnimating->LookupSequence( m_ExitAnimations[m_iCurrentExitAnim].szAnimName ); |
|
} |
|
|
|
// Fail, all exit points were blocked. |
|
bAllPointsBlocked = true; |
|
return ACTIVITY_NOT_AVAILABLE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::HandleEntryExitFinish( bool bExitAnimOn, bool bResetAnim ) |
|
{ |
|
// Parse the vehicle animations. This is needed because they may have |
|
// saved, and loaded during exit anim, which would clear the exit anim. |
|
if ( !m_bParsedAnimations ) |
|
{ |
|
// Load the entry/exit animations from the vehicle |
|
ParseEntryExitAnims(); |
|
m_bParsedAnimations = true; |
|
} |
|
|
|
// Figure out which entrypoint hitbox the player is in |
|
CBaseAnimating *pAnimating = m_pVehicle->GetBaseAnimating(); |
|
if ( !pAnimating ) |
|
return; |
|
|
|
// Did the entry anim just finish? |
|
if ( bExitAnimOn ) |
|
{ |
|
// The exit animation just finished |
|
CBasePlayer *pPlayer = ToBasePlayer( GetDriver() ); |
|
if ( pPlayer != NULL ) |
|
{ |
|
Vector vecEyes; |
|
QAngle vecEyeAng; |
|
if ( m_iCurrentExitAnim >= 0 && m_iCurrentExitAnim < m_ExitAnimations.Count() ) |
|
{ |
|
// Convert our offset points to worldspace ones |
|
vecEyes = m_ExitAnimations[m_iCurrentExitAnim].vecExitPointLocal; |
|
vecEyeAng = m_ExitAnimations[m_iCurrentExitAnim].vecExitAnglesLocal; |
|
UTIL_ParentToWorldSpace( pAnimating, vecEyes, vecEyeAng ); |
|
|
|
// Use the endpoint we figured out when we exited |
|
vecEyes = m_vecCurrentExitEndPoint; |
|
} |
|
else |
|
{ |
|
pAnimating->GetAttachment( "vehicle_driver_eyes", vecEyes, vecEyeAng ); |
|
} |
|
|
|
if ( g_debug_vehicleexit.GetBool() ) |
|
{ |
|
NDebugOverlay::Box( vecEyes, -Vector(2,2,2), Vector(2,2,2), 255,0,0, 64, 10.0 ); |
|
} |
|
|
|
// If the end point isn't clear, get back in the vehicle |
|
/* |
|
trace_t tr; |
|
UTIL_TraceHull( vecEyes, vecEyes, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.startsolid && tr.fraction < 1.0 ) |
|
{ |
|
pPlayer->LeaveVehicle( vecEyes, vecEyeAng ); |
|
m_pVehicle->Use( pPlayer, pPlayer, USE_TOGGLE, 1 ); |
|
return; |
|
} |
|
*/ |
|
|
|
pPlayer->LeaveVehicle( vecEyes, vecEyeAng ); |
|
} |
|
} |
|
|
|
// Only reset the animation if we're told to |
|
if ( bResetAnim ) |
|
{ |
|
// Start the vehicle idling again |
|
int iSequence = pAnimating->SelectWeightedSequence( ACT_IDLE ); |
|
if ( iSequence > ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
pAnimating->SetCycle( 0 ); |
|
pAnimating->m_flAnimTime = gpGlobals->curtime; |
|
pAnimating->ResetSequence( iSequence ); |
|
pAnimating->ResetClientsideFrame(); |
|
} |
|
} |
|
|
|
GetDrivableVehicle()->SetVehicleEntryAnim( false ); |
|
GetDrivableVehicle()->SetVehicleExitAnim( false, vec3_origin ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Where does the passenger see from? |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles, float *pFOV /*= NULL*/ ) |
|
{ |
|
Assert( nRole == VEHICLE_ROLE_DRIVER ); |
|
CBaseCombatCharacter *pPassenger = GetPassenger( VEHICLE_ROLE_DRIVER ); |
|
Assert( pPassenger ); |
|
|
|
CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); |
|
if ( pPlayer != NULL ) |
|
{ |
|
// Call through the player to resolve the actual position (if available) |
|
if ( pAbsOrigin != NULL ) |
|
{ |
|
*pAbsOrigin = pPlayer->EyePosition(); |
|
} |
|
|
|
if ( pAbsAngles != NULL ) |
|
{ |
|
*pAbsAngles = pPlayer->EyeAngles(); |
|
} |
|
|
|
if ( pFOV ) |
|
{ |
|
*pFOV = pPlayer->GetFOV(); |
|
} |
|
} |
|
else |
|
{ |
|
// NPCs are not supported |
|
Assert( 0 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) |
|
{ |
|
GetDrivableVehicle()->SetupMove( player, ucmd, pHelper, move ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ) |
|
{ |
|
GetDrivableVehicle()->ProcessMovement( pPlayer, pMoveData ); |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin() - Vector( 0, 0, 256 ), MASK_PLAYERSOLID, GetVehicleEnt(), COLLISION_GROUP_NONE, &tr ); |
|
|
|
// If our gamematerial has changed, tell any player surface triggers that are watching |
|
IPhysicsSurfaceProps *physprops = MoveHelper()->GetSurfaceProps(); |
|
const surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( tr.surface.surfaceProps ); |
|
char cCurrGameMaterial = pSurfaceProp->game.material; |
|
|
|
// Changed? |
|
if ( m_chPreviousTextureType != cCurrGameMaterial ) |
|
{ |
|
CEnvPlayerSurfaceTrigger::SetPlayerSurface( pPlayer, cCurrGameMaterial ); |
|
} |
|
|
|
m_chPreviousTextureType = cCurrGameMaterial; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) |
|
{ |
|
GetDrivableVehicle()->FinishMove( player, ucmd, move ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::ItemPostFrame( CBasePlayer *player ) |
|
{ |
|
Assert( player == GetDriver() ); |
|
|
|
GetDrivableVehicle()->ItemPostFrame( player ); |
|
|
|
if ( player->m_afButtonPressed & IN_USE ) |
|
{ |
|
if ( GetDrivableVehicle()->CanExitVehicle(player) ) |
|
{ |
|
if ( !HandlePassengerExit( player ) && ( player != NULL ) ) |
|
{ |
|
player->PlayUseDenySound(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_ThrottleForward( void ) |
|
{ |
|
m_nNPCButtons |= IN_FORWARD; |
|
m_nNPCButtons &= ~IN_BACK; |
|
m_nNPCButtons &= ~IN_JUMP; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_ThrottleReverse( void ) |
|
{ |
|
m_nNPCButtons |= IN_BACK; |
|
m_nNPCButtons &= ~IN_FORWARD; |
|
m_nNPCButtons &= ~IN_JUMP; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_ThrottleCenter( void ) |
|
{ |
|
m_nNPCButtons &= ~IN_FORWARD; |
|
m_nNPCButtons &= ~IN_BACK; |
|
m_nNPCButtons &= ~IN_JUMP; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_Brake( void ) |
|
{ |
|
m_nNPCButtons &= ~IN_FORWARD; |
|
m_nNPCButtons &= ~IN_BACK; |
|
m_nNPCButtons |= IN_JUMP; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_TurnLeft( float flDegrees ) |
|
{ |
|
m_nNPCButtons |= IN_MOVELEFT; |
|
m_nNPCButtons &= ~IN_MOVERIGHT; |
|
m_flTurnDegrees = -flDegrees; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_TurnRight( float flDegrees ) |
|
{ |
|
m_nNPCButtons |= IN_MOVERIGHT; |
|
m_nNPCButtons &= ~IN_MOVELEFT; |
|
m_flTurnDegrees = flDegrees; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_TurnCenter( void ) |
|
{ |
|
m_nNPCButtons &= ~IN_MOVERIGHT; |
|
m_nNPCButtons &= ~IN_MOVELEFT; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_PrimaryFire( void ) |
|
{ |
|
m_nNPCButtons |= IN_ATTACK; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::NPC_SecondaryFire( void ) |
|
{ |
|
m_nNPCButtons |= IN_ATTACK2; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::Weapon_PrimaryRanges( float *flMinRange, float *flMaxRange ) |
|
{ |
|
*flMinRange = 64; |
|
*flMaxRange = 1024; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::Weapon_SecondaryRanges( float *flMinRange, float *flMaxRange ) |
|
{ |
|
*flMinRange = 64; |
|
*flMaxRange = 1024; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the time at which this vehicle's primary weapon can fire again |
|
//----------------------------------------------------------------------------- |
|
float CBaseServerVehicle::Weapon_PrimaryCanFireAt( void ) |
|
{ |
|
return gpGlobals->curtime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the time at which this vehicle's secondary weapon can fire again |
|
//----------------------------------------------------------------------------- |
|
float CBaseServerVehicle::Weapon_SecondaryCanFireAt( void ) |
|
{ |
|
return gpGlobals->curtime; |
|
} |
|
|
|
const char *pSoundStateNames[] = |
|
{ |
|
"SS_NONE", |
|
"SS_SHUTDOWN", |
|
"SS_SHUTDOWN_WATER", |
|
"SS_START_WATER", |
|
"SS_START_IDLE", |
|
"SS_IDLE", |
|
"SS_GEAR_0", |
|
"SS_GEAR_1", |
|
"SS_GEAR_2", |
|
"SS_GEAR_3", |
|
"SS_GEAR_4", |
|
"SS_SLOWDOWN", |
|
"SS_SLOWDOWN_HIGHSPEED", |
|
"SS_GEAR_0_RESUME", |
|
"SS_GEAR_1_RESUME", |
|
"SS_GEAR_2_RESUME", |
|
"SS_GEAR_3_RESUME", |
|
"SS_GEAR_4_RESUME", |
|
"SS_TURBO", |
|
"SS_REVERSE", |
|
}; |
|
|
|
|
|
static int SoundStateIndexFromName( const char *pName ) |
|
{ |
|
for ( int i = 0; i < SS_NUM_STATES; i++ ) |
|
{ |
|
Assert( i < ARRAYSIZE(pSoundStateNames) ); |
|
if ( !strcmpi( pSoundStateNames[i], pName ) ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
static const char *SoundStateNameFromIndex( int index ) |
|
{ |
|
index = clamp(index, 0, SS_NUM_STATES-1 ); |
|
return pSoundStateNames[index]; |
|
} |
|
|
|
void CBaseServerVehicle::PlaySound( const char *pSound ) |
|
{ |
|
if ( !pSound || !pSound[0] ) |
|
return; |
|
|
|
if ( g_debug_vehiclesound.GetInt() ) |
|
{ |
|
Msg("Playing non-looping vehicle sound: %s\n", pSound ); |
|
} |
|
m_pVehicle->EmitSound( pSound ); |
|
} |
|
|
|
void CBaseServerVehicle::StopLoopingSound( float fadeTime ) |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
if ( m_pStateSoundFade ) |
|
{ |
|
controller.SoundDestroy( m_pStateSoundFade ); |
|
m_pStateSoundFade = NULL; |
|
} |
|
if ( m_pStateSound ) |
|
{ |
|
m_pStateSoundFade = m_pStateSound; |
|
m_pStateSound = NULL; |
|
controller.SoundFadeOut( m_pStateSoundFade, fadeTime, false ); |
|
} |
|
} |
|
|
|
void CBaseServerVehicle::PlayLoopingSound( const char *pSoundName ) |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
CPASAttenuationFilter filter( m_pVehicle ); |
|
CSoundPatch *pNewSound = NULL; |
|
if ( pSoundName && pSoundName[0] ) |
|
{ |
|
pNewSound = controller.SoundCreate( filter, m_pVehicle->entindex(), CHAN_STATIC, pSoundName, ATTN_NORM ); |
|
} |
|
|
|
if ( m_pStateSound && pNewSound && controller.SoundGetName( pNewSound ) == controller.SoundGetName( m_pStateSound ) ) |
|
{ |
|
// if the sound is the same, don't play this, just re-use the old one |
|
controller.SoundDestroy( pNewSound ); |
|
pNewSound = m_pStateSound; |
|
controller.SoundChangeVolume( pNewSound, 1.0f, 0.0f ); |
|
m_pStateSound = NULL; |
|
} |
|
else if ( g_debug_vehiclesound.GetInt() ) |
|
{ |
|
const char *pStopSound = m_pStateSound ? controller.SoundGetName( m_pStateSound ).ToCStr() : "NULL"; |
|
const char *pStartSound = pNewSound ? controller.SoundGetName( pNewSound ).ToCStr() : "NULL"; |
|
Msg("Stop %s, start %s\n", pStopSound, pStartSound ); |
|
} |
|
|
|
StopLoopingSound(); |
|
m_pStateSound = pNewSound; |
|
if ( m_pStateSound ) |
|
{ |
|
controller.Play( m_pStateSound, 1.0f, 100 ); |
|
} |
|
} |
|
|
|
static sound_states MapGearToState( vbs_sound_update_t ¶ms, int gear ) |
|
{ |
|
switch( gear ) |
|
{ |
|
case 0: return params.bReverse ? SS_REVERSE : SS_GEAR_0; |
|
case 1: return SS_GEAR_1; |
|
case 2: return SS_GEAR_2; |
|
case 3: return SS_GEAR_3; |
|
default:case 4: return SS_GEAR_4; |
|
} |
|
} |
|
|
|
static sound_states MapGearToMidState( vbs_sound_update_t ¶ms, int gear ) |
|
{ |
|
switch( gear ) |
|
{ |
|
case 0: return params.bReverse ? SS_REVERSE : SS_GEAR_0_RESUME; |
|
case 1: return SS_GEAR_1_RESUME; |
|
case 2: return SS_GEAR_2_RESUME; |
|
case 3: return SS_GEAR_3_RESUME; |
|
default:case 4: return SS_GEAR_4_RESUME; |
|
} |
|
} |
|
|
|
bool CBaseServerVehicle::PlayCrashSound( float speed ) |
|
{ |
|
int i; |
|
float delta = 0; |
|
float absSpeed = fabs(speed); |
|
float absLastSpeed = fabs(m_lastSpeed); |
|
if ( absLastSpeed > absSpeed ) |
|
{ |
|
delta = fabs(m_lastSpeed - speed); |
|
} |
|
|
|
float rumble = delta / 8.0f; |
|
|
|
if( rumble > 60.0f ) |
|
rumble = 60.0f; |
|
|
|
if( rumble > 5.0f ) |
|
{ |
|
if ( GetDriver() ) |
|
{ |
|
UTIL_ScreenShake( GetDriver()->GetAbsOrigin(), rumble, 150.0f, 1.0f, 240.0f, SHAKE_START_RUMBLEONLY, true ); |
|
} |
|
} |
|
|
|
for ( i = 0; i < m_vehicleSounds.crashSounds.Count(); i++ ) |
|
{ |
|
const vehicle_crashsound_t &crash = m_vehicleSounds.crashSounds[i]; |
|
if ( !crash.gearLimit ) |
|
continue; |
|
|
|
if ( m_iSoundGear <= crash.gearLimit ) |
|
{ |
|
if ( delta > crash.flMinDeltaSpeed && absLastSpeed > crash.flMinSpeed ) |
|
{ |
|
PlaySound( crash.iszCrashSound.ToCStr() ); |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
for ( i = m_vehicleSounds.crashSounds.Count()-1; i >= 0; --i ) |
|
{ |
|
const vehicle_crashsound_t &crash = m_vehicleSounds.crashSounds[i]; |
|
if ( delta > crash.flMinDeltaSpeed && absLastSpeed > crash.flMinSpeed ) |
|
{ |
|
PlaySound( crash.iszCrashSound.ToCStr() ); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
bool CBaseServerVehicle::CheckCrash( vbs_sound_update_t ¶ms ) |
|
{ |
|
if ( params.bVehicleInWater ) |
|
return false; |
|
|
|
bool bCrashed = PlayCrashSound( params.flWorldSpaceSpeed ); |
|
if ( bCrashed ) |
|
{ |
|
if ( g_debug_vehiclesound.GetInt() ) |
|
{ |
|
Msg("Crashed!: speed %.2f, lastSpeed %.2f\n", params.flWorldSpaceSpeed, m_lastSpeed ); |
|
} |
|
} |
|
m_lastSpeed = params.flWorldSpaceSpeed; |
|
return bCrashed; |
|
} |
|
|
|
|
|
sound_states CBaseServerVehicle::SoundState_ChooseState( vbs_sound_update_t ¶ms ) |
|
{ |
|
float timeInState = gpGlobals->curtime - m_soundStateStartTime; |
|
bool bInStateForMinTime = timeInState > m_vehicleSounds.minStateTime[m_soundState] ? true : false; |
|
|
|
sound_states stateOut = m_soundState; |
|
|
|
// exit overrides everything else |
|
if ( params.bExitVehicle ) |
|
{ |
|
switch ( m_soundState ) |
|
{ |
|
case SS_NONE: |
|
case SS_SHUTDOWN: |
|
case SS_SHUTDOWN_WATER: |
|
return m_soundState; |
|
} |
|
return SS_SHUTDOWN; |
|
} |
|
|
|
// check global state in states that don't mask them |
|
switch( m_soundState ) |
|
{ |
|
// global states masked for these states. |
|
case SS_NONE: |
|
case SS_START_IDLE: |
|
case SS_SHUTDOWN: |
|
break; |
|
case SS_START_WATER: |
|
case SS_SHUTDOWN_WATER: |
|
if ( !params.bVehicleInWater ) |
|
return SS_START_IDLE; |
|
break; |
|
|
|
case SS_TURBO: |
|
if ( params.bVehicleInWater ) |
|
return SS_SHUTDOWN_WATER; |
|
if ( CheckCrash(params) ) |
|
return SS_IDLE; |
|
break; |
|
|
|
case SS_IDLE: |
|
if ( params.bVehicleInWater ) |
|
return SS_SHUTDOWN_WATER; |
|
break; |
|
|
|
case SS_REVERSE: |
|
case SS_GEAR_0: |
|
case SS_GEAR_1: |
|
case SS_GEAR_2: |
|
case SS_GEAR_3: |
|
case SS_GEAR_4: |
|
case SS_SLOWDOWN: |
|
case SS_SLOWDOWN_HIGHSPEED: |
|
case SS_GEAR_0_RESUME: |
|
case SS_GEAR_1_RESUME: |
|
case SS_GEAR_2_RESUME: |
|
case SS_GEAR_3_RESUME: |
|
case SS_GEAR_4_RESUME: |
|
if ( params.bVehicleInWater ) |
|
{ |
|
return SS_SHUTDOWN_WATER; |
|
} |
|
if ( params.bTurbo ) |
|
{ |
|
return SS_TURBO; |
|
} |
|
if ( CheckCrash(params) ) |
|
return SS_IDLE; |
|
break; |
|
} |
|
|
|
switch( m_soundState ) |
|
{ |
|
case SS_START_IDLE: |
|
if ( bInStateForMinTime || params.bThrottleDown ) |
|
return SS_IDLE; |
|
break; |
|
case SS_IDLE: |
|
if ( bInStateForMinTime && params.bThrottleDown ) |
|
{ |
|
if ( params.bTurbo ) |
|
return SS_TURBO; |
|
return params.bReverse ? SS_REVERSE : SS_GEAR_0; |
|
} |
|
break; |
|
|
|
case SS_GEAR_0_RESUME: |
|
case SS_GEAR_0: |
|
if ( (bInStateForMinTime && !params.bThrottleDown) || params.bReverse ) |
|
{ |
|
return SS_IDLE; |
|
} |
|
if ( m_iSoundGear > 0 ) |
|
{ |
|
return SS_GEAR_1; |
|
} |
|
break; |
|
case SS_GEAR_1_RESUME: |
|
case SS_GEAR_1: |
|
if ( bInStateForMinTime ) |
|
{ |
|
if ( !params.bThrottleDown ) |
|
return SS_SLOWDOWN; |
|
} |
|
if ( m_iSoundGear != 1 ) |
|
return MapGearToState( params, m_iSoundGear); |
|
break; |
|
|
|
case SS_GEAR_2_RESUME: |
|
case SS_GEAR_2: |
|
if ( bInStateForMinTime ) |
|
{ |
|
if ( !params.bThrottleDown ) |
|
return SS_SLOWDOWN; |
|
else if ( m_iSoundGear != 2 ) |
|
return MapGearToState(params, m_iSoundGear); |
|
} |
|
break; |
|
|
|
case SS_GEAR_3_RESUME: |
|
case SS_GEAR_3: |
|
if ( bInStateForMinTime ) |
|
{ |
|
if ( !params.bThrottleDown ) |
|
return SS_SLOWDOWN; |
|
else if ( m_iSoundGear != 3 ) |
|
return MapGearToState(params, m_iSoundGear); |
|
} |
|
break; |
|
|
|
case SS_GEAR_4_RESUME: |
|
case SS_GEAR_4: |
|
if ( bInStateForMinTime && !params.bThrottleDown ) |
|
{ |
|
return SS_SLOWDOWN; |
|
} |
|
if ( m_iSoundGear != 4 ) |
|
{ |
|
return MapGearToMidState(params, m_iSoundGear); |
|
} |
|
break; |
|
case SS_REVERSE: |
|
if ( bInStateForMinTime && !params.bReverse ) |
|
{ |
|
return SS_SLOWDOWN; |
|
} |
|
break; |
|
|
|
case SS_SLOWDOWN_HIGHSPEED: |
|
case SS_SLOWDOWN: |
|
if ( params.bThrottleDown ) |
|
{ |
|
// map gears |
|
return MapGearToMidState(params, m_iSoundGear); |
|
} |
|
if ( m_iSoundGear == 0 ) |
|
{ |
|
return SS_IDLE; |
|
} |
|
break; |
|
|
|
case SS_NONE: |
|
stateOut = params.bVehicleInWater ? SS_START_WATER : SS_START_IDLE; |
|
break; |
|
case SS_TURBO: |
|
if ( bInStateForMinTime && !params.bTurbo ) |
|
{ |
|
return MapGearToMidState(params, m_iSoundGear); |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return stateOut; |
|
} |
|
|
|
const char *CBaseServerVehicle::StateSoundName( sound_states state ) |
|
{ |
|
return m_vehicleSounds.iszStateSounds[state].ToCStr(); |
|
} |
|
|
|
void CBaseServerVehicle::SoundState_OnNewState( sound_states lastState ) |
|
{ |
|
if ( g_debug_vehiclesound.GetInt() ) |
|
{ |
|
int index = m_soundState; |
|
Msg("Switched to state: %d (%s)\n", m_soundState, SoundStateNameFromIndex(index) ); |
|
} |
|
|
|
switch ( m_soundState ) |
|
{ |
|
case SS_SHUTDOWN: |
|
case SS_SHUTDOWN_WATER: |
|
case SS_START_WATER: |
|
StopLoopingSound(); |
|
PlaySound( StateSoundName(m_soundState) ); |
|
break; |
|
case SS_IDLE: |
|
m_lastSpeed = -1; |
|
PlayLoopingSound( StateSoundName(m_soundState) ); |
|
break; |
|
case SS_START_IDLE: |
|
case SS_REVERSE: |
|
case SS_GEAR_0: |
|
case SS_GEAR_0_RESUME: |
|
case SS_GEAR_1: |
|
case SS_GEAR_1_RESUME: |
|
case SS_GEAR_2: |
|
case SS_GEAR_2_RESUME: |
|
case SS_GEAR_3: |
|
case SS_GEAR_3_RESUME: |
|
case SS_GEAR_4: |
|
case SS_GEAR_4_RESUME: |
|
case SS_TURBO: |
|
PlayLoopingSound( StateSoundName(m_soundState) ); |
|
break; |
|
|
|
case SS_SLOWDOWN_HIGHSPEED: |
|
case SS_SLOWDOWN: |
|
if ( m_iSoundGear < 2 ) |
|
{ |
|
PlayLoopingSound( StateSoundName( SS_SLOWDOWN ) ); |
|
} |
|
else |
|
{ |
|
PlayLoopingSound( StateSoundName( SS_SLOWDOWN_HIGHSPEED ) ); |
|
} |
|
break; |
|
|
|
default:break; |
|
} |
|
|
|
m_soundStateStartTime = gpGlobals->curtime; |
|
} |
|
|
|
|
|
void CBaseServerVehicle::SoundState_Update( vbs_sound_update_t ¶ms ) |
|
{ |
|
sound_states newState = SoundState_ChooseState( params ); |
|
if ( newState != m_soundState ) |
|
{ |
|
sound_states lastState = m_soundState; |
|
m_soundState = newState; |
|
SoundState_OnNewState( lastState ); |
|
} |
|
|
|
switch( m_soundState ) |
|
{ |
|
case SS_SHUTDOWN: |
|
case SS_SHUTDOWN_WATER: |
|
case SS_START_WATER: |
|
case SS_START_IDLE: |
|
case SS_IDLE: |
|
case SS_REVERSE: |
|
case SS_GEAR_0: |
|
case SS_GEAR_4: |
|
case SS_SLOWDOWN_HIGHSPEED: |
|
case SS_SLOWDOWN: |
|
case SS_GEAR_0_RESUME: |
|
case SS_GEAR_4_RESUME: |
|
break; |
|
default:break; |
|
} |
|
} |
|
|
|
void CBaseServerVehicle::InitSoundParams( vbs_sound_update_t ¶ms ) |
|
{ |
|
params.Defaults(); |
|
params.bVehicleInWater = IsVehicleBodyInWater(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Vehicle Sound Start |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::SoundStart() |
|
{ |
|
StartEngineRumble(); |
|
|
|
m_soundState = SS_NONE; |
|
vbs_sound_update_t params; |
|
InitSoundParams(params); |
|
|
|
SoundState_Update( params ); |
|
} |
|
|
|
// vehicle is starting up disabled, but in some cases you still want to play a sound |
|
// HACK: handle those here. |
|
void CBaseServerVehicle::SoundStartDisabled() |
|
{ |
|
m_soundState = SS_NONE; |
|
vbs_sound_update_t params; |
|
InitSoundParams(params); |
|
sound_states newState = SoundState_ChooseState( params ); |
|
|
|
switch( newState ) |
|
{ |
|
case SS_START_WATER: |
|
PlaySound( StateSoundName(newState) ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::SoundShutdown( float flFadeTime ) |
|
{ |
|
StopEngineRumble(); |
|
|
|
// Stop any looping sounds that may be running, as the following stop sound may not exist |
|
// and thus leave a looping sound playing after the user gets out. |
|
for ( int i = 0; i < NUM_SOUNDS_TO_STOP_ON_EXIT; i++ ) |
|
{ |
|
StopSound( g_iSoundsToStopOnExit[i] ); |
|
} |
|
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
if ( m_pStateSoundFade ) |
|
{ |
|
controller.SoundFadeOut( m_pStateSoundFade, flFadeTime, true ); |
|
m_pStateSoundFade = NULL; |
|
} |
|
if ( m_pStateSound ) |
|
{ |
|
controller.SoundFadeOut( m_pStateSound, flFadeTime, true ); |
|
m_pStateSound = NULL; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::SoundUpdate( vbs_sound_update_t ¶ms ) |
|
{ |
|
if ( g_debug_vehiclesound.GetInt() > 1 ) |
|
{ |
|
Msg("Throttle: %s, Reverse: %s\n", params.bThrottleDown?"on":"off", params.bReverse?"on":"off" ); |
|
} |
|
|
|
float flCurrentSpeed = params.flCurrentSpeedFraction; |
|
if ( g_debug_vehiclesound.GetInt() > 1 ) |
|
{ |
|
Msg("CurrentSpeed: %.3f ", flCurrentSpeed ); |
|
} |
|
|
|
// Figure out our speed for the purposes of sound playing. |
|
// We slow the transition down a little to make the gear changes slower. |
|
if ( m_vehicleSounds.pGears.Count() > 0 ) |
|
{ |
|
if ( flCurrentSpeed > m_flSpeedPercentage ) |
|
{ |
|
// don't accelerate when the throttle isn't down |
|
if ( !params.bThrottleDown ) |
|
{ |
|
flCurrentSpeed = m_flSpeedPercentage; |
|
} |
|
flCurrentSpeed = Approach( flCurrentSpeed, m_flSpeedPercentage, params.flFrameTime * m_vehicleSounds.pGears[m_iSoundGear].flSpeedApproachFactor ); |
|
} |
|
} |
|
m_flSpeedPercentage = clamp( flCurrentSpeed, 0.0f, 1.0f ); |
|
|
|
if ( g_debug_vehiclesound.GetInt() > 1 ) |
|
{ |
|
Msg("Sound Speed: %.3f\n", m_flSpeedPercentage ); |
|
} |
|
|
|
// Only do gear changes when the throttle's down |
|
RecalculateSoundGear( params ); |
|
|
|
SoundState_Update( params ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a non-gear based vehicle sound |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::PlaySound( vehiclesound iSound ) |
|
{ |
|
if ( m_vehicleSounds.iszSound[iSound] != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( m_pVehicle ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_VOICE; |
|
ep.m_pSoundName = STRING(m_vehicleSounds.iszSound[iSound]); |
|
ep.m_flVolume = m_flVehicleVolume; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
|
|
CBaseEntity::EmitSound( filter, m_pVehicle->entindex(), ep ); |
|
if ( g_debug_vehiclesound.GetInt() ) |
|
{ |
|
Msg("Playing vehicle sound: %s\n", ep.m_pSoundName ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stop a non-gear based vehicle sound |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::StopSound( vehiclesound iSound ) |
|
{ |
|
if ( m_vehicleSounds.iszSound[iSound] != NULL_STRING ) |
|
{ |
|
CBaseEntity::StopSound( m_pVehicle->entindex(), CHAN_VOICE, STRING(m_vehicleSounds.iszSound[iSound]) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Calculate the gear we should be in based upon the vehicle's current speed |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::RecalculateSoundGear( vbs_sound_update_t ¶ms ) |
|
{ |
|
int iNumGears = m_vehicleSounds.pGears.Count(); |
|
for ( int i = (iNumGears-1); i >= 0; i-- ) |
|
{ |
|
if ( m_flSpeedPercentage > m_vehicleSounds.pGears[i].flMinSpeed ) |
|
{ |
|
m_iSoundGear = i; |
|
break; |
|
} |
|
} |
|
|
|
// If we're going in reverse, we want to stay in first gear |
|
if ( params.bReverse ) |
|
{ |
|
m_iSoundGear = 0; |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CBaseServerVehicle::StartEngineRumble() |
|
{ |
|
return; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CBaseServerVehicle::StopEngineRumble() |
|
{ |
|
return; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the passenger in the given seat of the vehicle |
|
// Input : nSeatID - seat ID to check |
|
// Output : CBaseCombatCharacter - character in the seat |
|
//----------------------------------------------------------------------------- |
|
CBaseCombatCharacter *CBaseServerVehicle::NPC_GetPassengerInSeat( int nRoleID, int nSeatID ) |
|
{ |
|
// Search all passengers in the vehicle |
|
for ( int i = 0; i < m_PassengerInfo.Count(); i++ ) |
|
{ |
|
// If the seat ID matches, return the entity in that seat |
|
if ( m_PassengerInfo[i].GetSeat() == nSeatID && m_PassengerInfo[i].GetRole() == nRoleID ) |
|
return m_PassengerInfo[i].m_hPassenger; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the first available seat (ranked by priority) |
|
// Input : nRoleID - Role index |
|
// Output : int - Seat by index |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::NPC_GetAvailableSeat_Any( CBaseCombatCharacter *pPassenger, int nRoleID ) |
|
{ |
|
// Look through all available seats |
|
for ( int i = 0; i < m_PassengerRoles[nRoleID].m_PassengerSeats.Count(); i++ ) |
|
{ |
|
// See if anyone is already in this seat |
|
CBaseCombatCharacter *pCurrentPassenger = NPC_GetPassengerInSeat( nRoleID, i ); |
|
if ( pCurrentPassenger != NULL && pCurrentPassenger != pPassenger ) |
|
continue; |
|
|
|
// This seat is open |
|
return i; |
|
} |
|
|
|
// Nothing found |
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the seat with the nearest entry point to the querier |
|
// Input : *pPassenger - Terget to be nearest to |
|
// nRoleID - Role index |
|
// Output : int - Seat by index |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::NPC_GetAvailableSeat_Nearest( CBaseCombatCharacter *pPassenger, int nRoleID ) |
|
{ |
|
// Not yet implemented |
|
Assert( 0 ); |
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get a seat in the vehicle based on our role and criteria |
|
// Input : *pPassenger - Entity attempting to find a seat |
|
// strRoleName - Role the seat must serve |
|
// nQueryType - Method for choosing the best seat (if multiple) |
|
// Output : int - Seat by unique ID |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::NPC_GetAvailableSeat( CBaseCombatCharacter *pPassenger, string_t strRoleName, VehicleSeatQuery_e nQueryType ) |
|
{ |
|
// Parse the vehicle animations the first time they get in the vehicle |
|
if ( m_bParsedAnimations == false ) |
|
{ |
|
// Load the entry/exit animations from the vehicle |
|
ParseEntryExitAnims(); |
|
m_bParsedAnimations = true; |
|
} |
|
|
|
// Get the role index |
|
int nRole = FindRoleIndexByName( strRoleName ); |
|
if ( m_PassengerRoles.IsValidIndex( nRole ) == false ) |
|
return -1; |
|
|
|
switch( nQueryType ) |
|
{ |
|
case VEHICLE_SEAT_ANY: |
|
return NPC_GetAvailableSeat_Any( pPassenger, nRole ); |
|
break; |
|
|
|
case VEHICLE_SEAT_NEAREST: |
|
return NPC_GetAvailableSeat_Nearest( pPassenger, nRole ); |
|
break; |
|
|
|
default: |
|
Assert( 0 ); |
|
break; |
|
}; |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determine if there's an available seat of a given role name |
|
// Input : strRoleName - name of the role |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::NPC_HasAvailableSeat( string_t strRoleName ) |
|
{ |
|
return ( NPC_GetAvailableSeat( NULL, strRoleName, VEHICLE_SEAT_ANY ) != -1 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a role index by name |
|
// Input : strRoleName - name of the role |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::FindRoleIndexByName( string_t strRoleName ) |
|
{ |
|
// Search through all our known roles |
|
for ( int i = 0; i < m_PassengerRoles.Count(); i++ ) |
|
{ |
|
// Return the index if the name matches |
|
if ( FStrEq( STRING( m_PassengerRoles[i].GetName() ), STRING( strRoleName ) ) ) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a seat index by its name |
|
// Input : strSeatName - name of the seat |
|
//----------------------------------------------------------------------------- |
|
int CBaseServerVehicle::FindSeatIndexByName( int nRoleIndex, string_t strSeatName ) |
|
{ |
|
// Role must be valid |
|
if ( m_PassengerRoles.IsValidIndex( nRoleIndex ) == false ) |
|
return -1; |
|
|
|
// Used for attachment polling |
|
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(GetVehicleEnt()); |
|
if ( pAnimating == NULL ) |
|
return -1; |
|
|
|
// Get the index of the named attachment in the model |
|
int nAttachmentID = pAnimating->LookupAttachment( STRING( strSeatName ) ); |
|
|
|
// Look through the roles for this seat attachment ID |
|
for ( int i = 0; i < m_PassengerRoles[nRoleIndex].m_PassengerSeats.Count(); i++ ) |
|
{ |
|
// Return that index if found |
|
if ( m_PassengerRoles[nRoleIndex].m_PassengerSeats[i].GetAttachmentID() == nAttachmentID ) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called after loading a saved game |
|
//----------------------------------------------------------------------------- |
|
void CBaseServerVehicle::RestorePassengerInfo( void ) |
|
{ |
|
// If there is passenger information, then we have passengers in the vehicle |
|
if ( m_PassengerInfo.Count() != 0 && m_bParsedAnimations == false ) |
|
{ |
|
// Load the entry/exit animations from the vehicle |
|
ParseEntryExitAnims(); |
|
m_bParsedAnimations = true; |
|
} |
|
|
|
// FIXME: If a passenger cannot fix-up its indices, it must be removed from the vehicle! |
|
|
|
// Fix-up every passenger with updated indices |
|
for ( int i = 0; i < m_PassengerInfo.Count(); i++ ) |
|
{ |
|
// Fix up the role first |
|
int nRoleIndex = FindRoleIndexByName( m_PassengerInfo[i].m_strRoleName ); |
|
if ( m_PassengerRoles.IsValidIndex( nRoleIndex ) ) |
|
{ |
|
// New role index |
|
m_PassengerInfo[i].m_nRole = nRoleIndex; |
|
|
|
// Then fix up the seat via attachment name |
|
int nSeatIndex = FindSeatIndexByName( nRoleIndex, m_PassengerInfo[i].m_strSeatName ); |
|
if ( m_PassengerRoles[nRoleIndex].m_PassengerSeats.IsValidIndex( nSeatIndex ) ) |
|
{ |
|
// New seat index |
|
m_PassengerInfo[i].m_nSeat = nSeatIndex; |
|
} |
|
else |
|
{ |
|
// The seat attachment was not found. This most likely means that the seat attachment name has changed |
|
// in the target vehicle and the NPC passenger is now stranded! |
|
Assert( 0 ); |
|
} |
|
} |
|
else |
|
{ |
|
// The role was not found. This most likely means that the role names have changed |
|
// in the target vehicle and the NPC passenger is now stranded! |
|
Assert( 0 ); |
|
} |
|
} |
|
} |
|
|
|
void CBaseServerVehicle::ReloadScript() |
|
{ |
|
if ( m_pDrivableVehicle ) |
|
{ |
|
string_t script = m_pDrivableVehicle->GetVehicleScriptName(); |
|
IPhysicsVehicleController *pController = GetVehicleController(); |
|
vehicleparams_t *pVehicleParams = pController ? &(pController->GetVehicleParamsForChange()) : NULL; |
|
PhysFindOrAddVehicleScript( script.ToCStr(), pVehicleParams, &m_vehicleSounds ); |
|
if ( pController ) |
|
{ |
|
pController->VehicleDataReload(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Passes this call down into the server vehicle where the tests are done |
|
//----------------------------------------------------------------------------- |
|
bool CBaseServerVehicle::PassengerShouldReceiveDamage( CTakeDamageInfo &info ) |
|
{ |
|
if ( GetDrivableVehicle() ) |
|
return GetDrivableVehicle()->PassengerShouldReceiveDamage( info ); |
|
|
|
return true; |
|
} |
|
|
|
//=========================================================================================================== |
|
// Vehicle Sounds |
|
//=========================================================================================================== |
|
|
|
// These are sounds that are to be automatically stopped whenever the vehicle's driver leaves it |
|
vehiclesound g_iSoundsToStopOnExit[] = |
|
{ |
|
VS_ENGINE2_START, |
|
VS_ENGINE2_STOP, |
|
}; |
|
|
|
const char *vehiclesound_parsenames[VS_NUM_SOUNDS] = |
|
{ |
|
"skid_lowfriction", |
|
"skid_normalfriction", |
|
"skid_highfriction", |
|
"engine2_start", |
|
"engine2_stop", |
|
"misc1", |
|
"misc2", |
|
"misc3", |
|
"misc4", |
|
}; |
|
|
|
CVehicleSoundsParser::CVehicleSoundsParser( void ) |
|
{ |
|
// UNDONE: Revisit this pattern - move sub-block processing ideas into the parser architecture |
|
m_iCurrentGear = -1; |
|
m_iCurrentState = -1; |
|
m_iCurrentCrashSound = -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CVehicleSoundsParser::ParseKeyValue( void *pData, const char *pKey, const char *pValue ) |
|
{ |
|
vehiclesounds_t *pSounds = (vehiclesounds_t *)pData; |
|
// New gear? |
|
if ( !strcmpi( pKey, "gear" ) ) |
|
{ |
|
// Create, initialize, and add a new gear to our list |
|
int iNewGear = pSounds->pGears.AddToTail(); |
|
pSounds->pGears[iNewGear].flMaxSpeed = 0; |
|
pSounds->pGears[iNewGear].flSpeedApproachFactor = 1.0; |
|
|
|
// Set our min speed to the previous gear's max |
|
if ( iNewGear == 0 ) |
|
{ |
|
// First gear, so our minspeed is 0 |
|
pSounds->pGears[iNewGear].flMinSpeed = 0; |
|
} |
|
else |
|
{ |
|
pSounds->pGears[iNewGear].flMinSpeed = pSounds->pGears[iNewGear-1].flMaxSpeed; |
|
} |
|
|
|
// Remember which gear we're reading data from |
|
m_iCurrentGear = iNewGear; |
|
} |
|
else if ( !strcmpi( pKey, "state" ) ) |
|
{ |
|
m_iCurrentState = 0; |
|
} |
|
else if ( !strcmpi( pKey, "crashsound" ) ) |
|
{ |
|
m_iCurrentCrashSound = pSounds->crashSounds.AddToTail(); |
|
pSounds->crashSounds[m_iCurrentCrashSound].flMinSpeed = 0; |
|
pSounds->crashSounds[m_iCurrentCrashSound].flMinDeltaSpeed = 0; |
|
pSounds->crashSounds[m_iCurrentCrashSound].iszCrashSound = NULL_STRING; |
|
} |
|
else |
|
{ |
|
int i; |
|
|
|
// Are we currently in a gear block? |
|
if ( m_iCurrentGear >= 0 ) |
|
{ |
|
Assert( m_iCurrentGear < pSounds->pGears.Count() ); |
|
|
|
// Check gear keys |
|
if ( !strcmpi( pKey, "max_speed" ) ) |
|
{ |
|
pSounds->pGears[m_iCurrentGear].flMaxSpeed = atof(pValue); |
|
return; |
|
} |
|
if ( !strcmpi( pKey, "speed_approach_factor" ) ) |
|
{ |
|
pSounds->pGears[m_iCurrentGear].flSpeedApproachFactor = atof(pValue); |
|
return; |
|
} |
|
} |
|
// We're done reading a gear, so stop checking them. |
|
m_iCurrentGear = -1; |
|
|
|
if ( m_iCurrentState >= 0 ) |
|
{ |
|
if ( !strcmpi( pKey, "name" ) ) |
|
{ |
|
m_iCurrentState = SoundStateIndexFromName( pValue ); |
|
pSounds->iszStateSounds[m_iCurrentState] = NULL_STRING; |
|
pSounds->minStateTime[m_iCurrentState] = 0.0f; |
|
return; |
|
} |
|
else if ( !strcmpi( pKey, "sound" ) ) |
|
{ |
|
pSounds->iszStateSounds[m_iCurrentState] = AllocPooledString(pValue); |
|
return; |
|
} |
|
else if ( !strcmpi( pKey, "min_time" ) ) |
|
{ |
|
pSounds->minStateTime[m_iCurrentState] = atof(pValue); |
|
return; |
|
} |
|
} |
|
// |
|
m_iCurrentState = -1; |
|
|
|
if ( m_iCurrentCrashSound >= 0 ) |
|
{ |
|
if ( !strcmpi( pKey, "min_speed" ) ) |
|
{ |
|
pSounds->crashSounds[m_iCurrentCrashSound].flMinSpeed = atof(pValue); |
|
return; |
|
} |
|
else if ( !strcmpi( pKey, "sound" ) ) |
|
{ |
|
pSounds->crashSounds[m_iCurrentCrashSound].iszCrashSound = AllocPooledString(pValue); |
|
return; |
|
} |
|
else if ( !strcmpi( pKey, "min_speed_change" ) ) |
|
{ |
|
pSounds->crashSounds[m_iCurrentCrashSound].flMinDeltaSpeed = atof(pValue); |
|
return; |
|
} |
|
else if ( !strcmpi( pKey, "gear_limit" ) ) |
|
{ |
|
pSounds->crashSounds[m_iCurrentCrashSound].gearLimit = atoi(pValue); |
|
return; |
|
} |
|
} |
|
m_iCurrentCrashSound = -1; |
|
|
|
for ( i = 0; i < VS_NUM_SOUNDS; i++ ) |
|
{ |
|
if ( !strcmpi( pKey, vehiclesound_parsenames[i] ) ) |
|
{ |
|
pSounds->iszSound[i] = AllocPooledString(pValue); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CVehicleSoundsParser::SetDefaults( void *pData ) |
|
{ |
|
vehiclesounds_t *pSounds = (vehiclesounds_t *)pData; |
|
pSounds->Init(); |
|
} |
|
|
|
|