From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/vehicle_baseserver.cpp | 5494 ++++++++++++++--------------- 1 file changed, 2747 insertions(+), 2747 deletions(-) (limited to 'mp/src/game/server/vehicle_baseserver.cpp') diff --git a/mp/src/game/server/vehicle_baseserver.cpp b/mp/src/game/server/vehicle_baseserver.cpp index 37b5be9c..a2fe513e 100644 --- a/mp/src/game/server/vehicle_baseserver.cpp +++ b/mp/src/game/server/vehicle_baseserver.cpp @@ -1,2747 +1,2747 @@ -//========= 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(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( 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(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(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(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(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(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(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(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(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(); -} - +//========= 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(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( 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(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(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(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(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(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(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(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(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(); +} + -- cgit v1.2.3