diff options
Diffstat (limited to 'game/server/episodic/vehicle_jeep_episodic.cpp')
| -rw-r--r-- | game/server/episodic/vehicle_jeep_episodic.cpp | 1764 |
1 files changed, 1764 insertions, 0 deletions
diff --git a/game/server/episodic/vehicle_jeep_episodic.cpp b/game/server/episodic/vehicle_jeep_episodic.cpp new file mode 100644 index 0000000..368f1b9 --- /dev/null +++ b/game/server/episodic/vehicle_jeep_episodic.cpp @@ -0,0 +1,1764 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= + +#include "cbase.h" +#include "vehicle_jeep_episodic.h" +#include "collisionutils.h" +#include "npc_alyx_episodic.h" +#include "particle_parse.h" +#include "particle_system.h" +#include "hl2_player.h" +#include "in_buttons.h" +#include "vphysics/friction.h" +#include "vphysicsupdateai.h" +#include "physics_npc_solver.h" +#include "Sprite.h" +#include "weapon_striderbuster.h" +#include "npc_strider.h" +#include "vguiscreen.h" +#include "hl2_vehicle_radar.h" +#include "props.h" +#include "ai_dynamiclink.h" + +extern ConVar phys_upimpactforcescale; + +ConVar jalopy_blocked_exit_max_speed( "jalopy_blocked_exit_max_speed", "50" ); + +#define JEEP_AMMOCRATE_HITGROUP 5 +#define JEEP_AMMO_CRATE_CLOSE_DELAY 2.0f + +// Bodygroups +#define JEEP_RADAR_BODYGROUP 1 +#define JEEP_HOPPER_BODYGROUP 2 +#define JEEP_CARBAR_BODYGROUP 3 + +#define RADAR_PANEL_MATERIAL "vgui/screens/radar" +#define RADAR_PANEL_WRITEZ "engine/writez" + +static const char *s_szHazardSprite = "sprites/light_glow01.vmt"; + +enum +{ + RADAR_MODE_NORMAL = 0, + RADAR_MODE_STICKY, +}; + +//========================================================= +//========================================================= +class CRadarTarget : public CPointEntity +{ + DECLARE_CLASS( CRadarTarget, CPointEntity ); + +public: + void Spawn(); + + bool IsDisabled() { return m_bDisabled; } + int GetType() { return m_iType; } + int GetMode() { return m_iMode; } + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + int ObjectCaps(); + +private: + bool m_bDisabled; + int m_iType; + int m_iMode; + +public: + float m_flRadius; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( info_radar_target, CRadarTarget ); + +BEGIN_DATADESC( CRadarTarget ) + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), + DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ), + DEFINE_KEYFIELD( m_iMode, FIELD_INTEGER, "mode" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ), +END_DATADESC(); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRadarTarget::Spawn() +{ + BaseClass::Spawn(); + + AddEffects( EF_NODRAW ); + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_NONE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRadarTarget::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRadarTarget::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CRadarTarget::ObjectCaps() +{ + return BaseClass::ObjectCaps() | FCAP_ACROSS_TRANSITION; +} + + + + +// +// Trigger which detects entities placed in the cargo hold of the jalopy +// + +class CVehicleCargoTrigger : public CBaseEntity +{ + DECLARE_CLASS( CVehicleCargoTrigger, CBaseEntity ); + +public: + + // + // Creates a trigger with the specified bounds + + static CVehicleCargoTrigger *Create( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner ) + { + CVehicleCargoTrigger *pTrigger = (CVehicleCargoTrigger *) CreateEntityByName( "trigger_vehicle_cargo" ); + if ( pTrigger == NULL ) + return NULL; + + UTIL_SetOrigin( pTrigger, vecOrigin ); + UTIL_SetSize( pTrigger, vecMins, vecMaxs ); + pTrigger->SetOwnerEntity( pOwner ); + pTrigger->SetParent( pOwner ); + + pTrigger->Spawn(); + + return pTrigger; + } + + // + // Handles the trigger touching its intended quarry + + void CargoTouch( CBaseEntity *pOther ) + { + // Cannot be ignoring touches + if ( ( m_hIgnoreEntity == pOther ) || ( m_flIgnoreDuration >= gpGlobals->curtime ) ) + return; + + // Make sure this object is being held by the player + if ( pOther->VPhysicsGetObject() == NULL || (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) == false ) + return; + + if ( StriderBuster_NumFlechettesAttached( pOther ) > 0 ) + return; + + AddCargo( pOther ); + } + + bool AddCargo( CBaseEntity *pOther ) + { + // For now, only bother with strider busters + if ( (FClassnameIs( pOther, "weapon_striderbuster" ) == false) && + (FClassnameIs( pOther, "npc_grenade_magna" ) == false) + ) + return false; + + // Must be a physics prop + CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pOther); + if ( pOther == NULL ) + return false; + + CPropJeepEpisodic *pJeep = dynamic_cast< CPropJeepEpisodic * >( GetOwnerEntity() ); + if ( pJeep == NULL ) + return false; + + // Make the player release the item + Pickup_ForcePlayerToDropThisObject( pOther ); + + // Stop colliding with things + pOther->VPhysicsDestroyObject(); + pOther->SetSolidFlags( FSOLID_NOT_SOLID ); + pOther->SetMoveType( MOVETYPE_NONE ); + + // Parent the object to our owner + pOther->SetParent( GetOwnerEntity() ); + + // The car now owns the entity + pJeep->AddPropToCargoHold( pProp ); + + // Notify the buster that it's been added to the cargo hold. + StriderBuster_OnAddToCargoHold( pProp ); + + // Stop touching this item + Disable(); + + return true; + } + + // + // Setup the entity + + void Spawn( void ) + { + BaseClass::Spawn(); + + SetSolid( SOLID_BBOX ); + SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); + + SetTouch( &CVehicleCargoTrigger::CargoTouch ); + } + + void Activate() + { + BaseClass::Activate(); + SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); // Fixes up old savegames + } + + // + // When we've stopped touching this entity, we ignore it + + void EndTouch( CBaseEntity *pOther ) + { + if ( pOther == m_hIgnoreEntity ) + { + m_hIgnoreEntity = NULL; + } + + BaseClass::EndTouch( pOther ); + } + + // + // Disables the trigger for a set duration + + void IgnoreTouches( CBaseEntity *pIgnoreEntity ) + { + m_hIgnoreEntity = pIgnoreEntity; + m_flIgnoreDuration = gpGlobals->curtime + 0.5f; + } + + void Disable( void ) + { + SetTouch( NULL ); + } + + void Enable( void ) + { + SetTouch( &CVehicleCargoTrigger::CargoTouch ); + } + +protected: + + float m_flIgnoreDuration; + CHandle <CBaseEntity> m_hIgnoreEntity; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( trigger_vehicle_cargo, CVehicleCargoTrigger ); + +BEGIN_DATADESC( CVehicleCargoTrigger ) + DEFINE_FIELD( m_flIgnoreDuration, FIELD_TIME ), + DEFINE_FIELD( m_hIgnoreEntity, FIELD_EHANDLE ), + DEFINE_ENTITYFUNC( CargoTouch ), +END_DATADESC(); + +// +// Transition reference point for the vehicle +// + +class CInfoTargetVehicleTransition : public CPointEntity +{ +public: + DECLARE_CLASS( CInfoTargetVehicleTransition, CPointEntity ); + + void Enable( void ) { m_bDisabled = false; } + void Disable( void ) { m_bDisabled = true; } + + bool IsDisabled( void ) const { return m_bDisabled; } + +private: + + void InputEnable( inputdata_t &data ) { Enable(); } + void InputDisable( inputdata_t &data ) { Disable(); } + + bool m_bDisabled; + + DECLARE_DATADESC(); +}; + +BEGIN_DATADESC( CInfoTargetVehicleTransition ) + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ), +END_DATADESC(); + +LINK_ENTITY_TO_CLASS( info_target_vehicle_transition, CInfoTargetVehicleTransition ); + +// +// CPropJeepEpisodic +// + +LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeepEpisodic ); + +BEGIN_DATADESC( CPropJeepEpisodic ) + + DEFINE_FIELD( m_bEntranceLocked, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bExitLocked, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hCargoProp, FIELD_EHANDLE ), + DEFINE_FIELD( m_hCargoTrigger, FIELD_EHANDLE ), + DEFINE_FIELD( m_bAddingCargo, FIELD_BOOLEAN ), + DEFINE_ARRAY( m_hWheelDust, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ), + DEFINE_ARRAY( m_hWheelWater, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ), + DEFINE_ARRAY( m_hHazardLights, FIELD_EHANDLE, NUM_HAZARD_LIGHTS ), + DEFINE_FIELD( m_flCargoStartTime, FIELD_TIME ), + DEFINE_FIELD( m_bBlink, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bRadarEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bRadarDetectsEnemies, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hRadarScreen, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLinkControllerFront, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLinkControllerRear, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_bBusterHopperVisible, FIELD_BOOLEAN, "CargoVisible" ), + // m_flNextAvoidBroadcastTime + DEFINE_FIELD( m_flNextWaterSound, FIELD_TIME ), + DEFINE_FIELD( m_flNextRadarUpdateTime, FIELD_TIME ), + DEFINE_FIELD( m_iNumRadarContacts, FIELD_INTEGER ), + DEFINE_ARRAY( m_vecRadarContactPos, FIELD_POSITION_VECTOR, RADAR_MAX_CONTACTS ), + DEFINE_ARRAY( m_iRadarContactType, FIELD_INTEGER, RADAR_MAX_CONTACTS ), + + DEFINE_THINKFUNC( HazardBlinkThink ), + + DEFINE_OUTPUT( m_OnCompanionEnteredVehicle, "OnCompanionEnteredVehicle" ), + DEFINE_OUTPUT( m_OnCompanionExitedVehicle, "OnCompanionExitedVehicle" ), + DEFINE_OUTPUT( m_OnHostileEnteredVehicle, "OnHostileEnteredVehicle" ), + DEFINE_OUTPUT( m_OnHostileExitedVehicle, "OnHostileExitedVehicle" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "LockEntrance", InputLockEntrance ), + DEFINE_INPUTFUNC( FIELD_VOID, "UnlockEntrance", InputUnlockEntrance ), + DEFINE_INPUTFUNC( FIELD_VOID, "LockExit", InputLockExit ), + DEFINE_INPUTFUNC( FIELD_VOID, "UnlockExit", InputUnlockExit ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadar", InputEnableRadar ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadar", InputDisableRadar ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadarDetectEnemies", InputEnableRadarDetectEnemies ), + DEFINE_INPUTFUNC( FIELD_VOID, "AddBusterToCargo", InputAddBusterToCargo ), + DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ), + DEFINE_INPUTFUNC( FIELD_VOID, "CreateLinkController", InputCreateLinkController ), + DEFINE_INPUTFUNC( FIELD_VOID, "DestroyLinkController", InputDestroyLinkController ), + + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCargoHopperVisibility", InputSetCargoVisibility ), + +END_DATADESC(); + +IMPLEMENT_SERVERCLASS_ST(CPropJeepEpisodic, DT_CPropJeepEpisodic) + //CNetworkVar( int, m_iNumRadarContacts ); + SendPropInt( SENDINFO(m_iNumRadarContacts), 8 ), + + //CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS ); + SendPropArray( SendPropVector( SENDINFO_ARRAY(m_vecRadarContactPos), -1, SPROP_COORD), m_vecRadarContactPos ), + + //CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS ); + SendPropArray( SendPropInt(SENDINFO_ARRAY(m_iRadarContactType), RADAR_CONTACT_TYPE_BITS ), m_iRadarContactType ), +END_SEND_TABLE() + + +//============================================================================= +// Episodic jeep + +CPropJeepEpisodic::CPropJeepEpisodic( void ) : +m_bEntranceLocked( false ), +m_bExitLocked( false ), +m_bAddingCargo( false ), +m_flNextAvoidBroadcastTime( 0.0f ) +{ + m_bHasGun = false; + m_bUnableToFire = true; + m_bRadarDetectsEnemies = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::UpdateOnRemove( void ) +{ + BaseClass::UpdateOnRemove(); + + // Kill our wheel dust + for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ ) + { + if ( m_hWheelDust[i] != NULL ) + { + UTIL_Remove( m_hWheelDust[i] ); + } + + if ( m_hWheelWater[i] != NULL ) + { + UTIL_Remove( m_hWheelWater[i] ); + } + } + + DestroyHazardLights(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::Precache( void ) +{ + PrecacheMaterial( RADAR_PANEL_MATERIAL ); + PrecacheMaterial( RADAR_PANEL_WRITEZ ); + PrecacheModel( s_szHazardSprite ); + PrecacheScriptSound( "JNK_Radar_Ping_Friendly" ); + PrecacheScriptSound( "Physics.WaterSplash" ); + + PrecacheParticleSystem( "WheelDust" ); + PrecacheParticleSystem( "WheelSplash" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::EnterVehicle( CBaseCombatCharacter *pPassenger ) +{ + BaseClass::EnterVehicle( pPassenger ); + + // Turn our hazards off! + DestroyHazardLights(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::Spawn( void ) +{ + BaseClass::Spawn(); + + SetBlocksLOS( false ); + + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer != NULL ) + { + pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR; + } + + + SetBodygroup( JEEP_HOPPER_BODYGROUP, m_bBusterHopperVisible ? 1 : 0); + CreateCargoTrigger(); + + // carbar bodygroup is always on + SetBodygroup( JEEP_CARBAR_BODYGROUP, 1 ); + + m_bRadarDetectsEnemies = false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::Activate() +{ + m_iNumRadarContacts = 0; // Force first contact tone + BaseClass::Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) +{ + // FIXME: This will be moved to the NPCs entering and exiting + // Fire our outputs + if ( bCompanion ) + { + m_OnCompanionEnteredVehicle.FireOutput( this, pPassenger ); + } + else + { + m_OnHostileEnteredVehicle.FireOutput( this, pPassenger ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) +{ + // FIXME: This will be moved to the NPCs entering and exiting + // Fire our outputs + if ( bCompanion ) + { + m_OnCompanionExitedVehicle.FireOutput( this, pPassenger ); + } + else + { + m_OnHostileExitedVehicle.FireOutput( this, pPassenger ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPassenger - +// bCompanion - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPropJeepEpisodic::NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) +{ + // Must be unlocked + if ( bCompanion && m_bEntranceLocked ) + return false; + + return BaseClass::NPC_CanEnterVehicle( pPassenger, bCompanion ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPassenger - +// bCompanion - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPropJeepEpisodic::NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion ) +{ + // Must be unlocked + if ( bCompanion && m_bExitLocked ) + return false; + + return BaseClass::NPC_CanExitVehicle( pPassenger, bCompanion ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputLockEntrance( inputdata_t &data ) +{ + m_bEntranceLocked = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputUnlockEntrance( inputdata_t &data ) +{ + m_bEntranceLocked = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputLockExit( inputdata_t &data ) +{ + m_bExitLocked = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputUnlockExit( inputdata_t &data ) +{ + m_bExitLocked = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Turn on the Jalopy radar device +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputEnableRadar( inputdata_t &data ) +{ + if( m_bRadarEnabled ) + return; // Already enabled + + SetBodygroup( JEEP_RADAR_BODYGROUP, 1 ); + + SpawnRadarPanel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Turn off the Jalopy radar device +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputDisableRadar( inputdata_t &data ) +{ + if( !m_bRadarEnabled ) + return; // Already disabled + + SetBodygroup( JEEP_RADAR_BODYGROUP, 0 ); + + DestroyRadarPanel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Allow the Jalopy radar to detect Hunters and Striders +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputEnableRadarDetectEnemies( inputdata_t &data ) +{ + m_bRadarDetectsEnemies = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputAddBusterToCargo( inputdata_t &data ) +{ + if ( m_hCargoProp != NULL) + { + ReleasePropFromCargoHold(); + m_hCargoProp = NULL; + } + + CBaseEntity *pNewBomb = CreateEntityByName( "weapon_striderbuster" ); + if ( pNewBomb ) + { + DispatchSpawn( pNewBomb ); + pNewBomb->Teleport( &m_hCargoTrigger->GetAbsOrigin(), NULL, NULL ); + m_hCargoTrigger->AddCargo( pNewBomb ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPropJeepEpisodic::PassengerInTransition( void ) +{ + // FIXME: Big hack - we need a way to bridge this data better + // TODO: Get a list of passengers we can traverse instead + CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); + if ( pAlyx ) + { + if ( pAlyx->GetPassengerState() == PASSENGER_STATE_ENTERING || + pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Override velocity if our passenger is transitioning or we're upside-down +//----------------------------------------------------------------------------- +Vector CPropJeepEpisodic::PhysGunLaunchVelocity( const Vector &forward, float flMass ) +{ + // Disallow + if ( PassengerInTransition() ) + return vec3_origin; + + Vector vecPuntDir = BaseClass::PhysGunLaunchVelocity( forward, flMass ); + vecPuntDir.z = 150.0f; + vecPuntDir *= 600.0f; + return vecPuntDir; +} + +//----------------------------------------------------------------------------- +// Purpose: Rolls the vehicle when its trying to upright itself from a punt +//----------------------------------------------------------------------------- +AngularImpulse CPropJeepEpisodic::PhysGunLaunchAngularImpulse( void ) +{ + if ( IsOverturned() ) + return AngularImpulse( 0, 300, 0 ); + + // Don't spin randomly, always spin reliably + return AngularImpulse( 0, 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the upright strength based on what state we're in +//----------------------------------------------------------------------------- +float CPropJeepEpisodic::GetUprightStrength( void ) +{ + // Lesser if overturned + if ( IsOverturned() ) + return 2.0f; + + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::CreateCargoTrigger( void ) +{ + if ( m_hCargoTrigger != NULL ) + return; + + int nAttachment = LookupAttachment( "cargo" ); + if ( nAttachment ) + { + Vector vecAttachOrigin; + Vector vecForward, vecRight, vecUp; + GetAttachment( nAttachment, vecAttachOrigin, &vecForward, &vecRight, &vecUp ); + + // Approx size of the hold + Vector vecMins( -8.0, -6.0, 0 ); + Vector vecMaxs( 8.0, 6.0, 4.0 ); + + // NDebugOverlay::BoxDirection( vecAttachOrigin, vecMins, vecMaxs, vecForward, 255, 0, 0, 64, 4.0f ); + + // Create a trigger that lives for a small amount of time + m_hCargoTrigger = CVehicleCargoTrigger::Create( vecAttachOrigin, vecMins, vecMaxs, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: If the player uses the jeep while at the back, he gets ammo from the crate instead +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Fall back and get in the vehicle instead, skip giving ammo + BaseClass::BaseClass::Use( pActivator, pCaller, useType, value ); +} + +#define MIN_WHEEL_DUST_SPEED 5 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::UpdateWheelDust( void ) +{ + // See if this wheel should emit dust + const vehicleparams_t *vehicleData = m_pServerVehicle->GetVehicleParams(); + const vehicle_operatingparams_t *carState = m_pServerVehicle->GetVehicleOperatingParams(); + bool bAllowDust = vehicleData->steering.dustCloud; + + // Car must be active + bool bCarOn = m_VehiclePhysics.IsOn(); + + // Must be moving quickly enough or skidding along the ground + bool bCreateDust = ( bCarOn && + bAllowDust && + ( m_VehiclePhysics.GetSpeed() >= MIN_WHEEL_DUST_SPEED || carState->skidSpeed > DEFAULT_SKID_THRESHOLD ) ); + + // Update our wheel dust + Vector vecPos; + for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ ) + { + m_pServerVehicle->GetWheelContactPoint( i, vecPos ); + + // Make sure the effect is created + if ( m_hWheelDust[i] == NULL ) + { + // Create the dust effect in place + m_hWheelDust[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" ); + if ( m_hWheelDust[i] == NULL ) + continue; + + // Setup our basic parameters + m_hWheelDust[i]->KeyValue( "start_active", "0" ); + m_hWheelDust[i]->KeyValue( "effect_name", "WheelDust" ); + m_hWheelDust[i]->SetParent( this ); + m_hWheelDust[i]->SetLocalOrigin( vec3_origin ); + DispatchSpawn( m_hWheelDust[i] ); + if ( gpGlobals->curtime > 0.5f ) + m_hWheelDust[i]->Activate(); + } + + // Make sure the effect is created + if ( m_hWheelWater[i] == NULL ) + { + // Create the dust effect in place + m_hWheelWater[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" ); + if ( m_hWheelWater[i] == NULL ) + continue; + + // Setup our basic parameters + m_hWheelWater[i]->KeyValue( "start_active", "0" ); + m_hWheelWater[i]->KeyValue( "effect_name", "WheelSplash" ); + m_hWheelWater[i]->SetParent( this ); + m_hWheelWater[i]->SetLocalOrigin( vec3_origin ); + DispatchSpawn( m_hWheelWater[i] ); + if ( gpGlobals->curtime > 0.5f ) + m_hWheelWater[i]->Activate(); + } + + // Turn the dust on or off + if ( bCreateDust ) + { + // Angle the dust out away from the wheels + Vector vecForward, vecRight, vecUp; + GetVectors( &vecForward, &vecRight, &vecUp ); + + const vehicle_controlparams_t *vehicleControls = m_pServerVehicle->GetVehicleControlParams(); + float flWheelDir = ( i & 1 ) ? 1.0f : -1.0f; + QAngle vecAngles; + vecForward += vecRight * flWheelDir; + vecForward += vecRight * (vehicleControls->steering*0.5f) * flWheelDir; + vecForward += vecUp; + VectorAngles( vecForward, vecAngles ); + + // NDebugOverlay::Axis( vecPos, vecAngles, 8.0f, true, 0.1f ); + + if ( m_WaterData.m_bWheelInWater[i] ) + { + m_hWheelDust[i]->StopParticleSystem(); + + // Set us up in the right position + m_hWheelWater[i]->StartParticleSystem(); + m_hWheelWater[i]->SetAbsAngles( vecAngles ); + m_hWheelWater[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) ); + + if ( m_flNextWaterSound < gpGlobals->curtime ) + { + m_flNextWaterSound = gpGlobals->curtime + random->RandomFloat( 0.25f, 1.0f ); + EmitSound( "Physics.WaterSplash" ); + } + } + else + { + m_hWheelWater[i]->StopParticleSystem(); + + // Set us up in the right position + m_hWheelDust[i]->StartParticleSystem(); + m_hWheelDust[i]->SetAbsAngles( vecAngles ); + m_hWheelDust[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) ); + } + } + else + { + // Stop emitting + m_hWheelDust[i]->StopParticleSystem(); + m_hWheelWater[i]->StopParticleSystem(); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ConVar jalopy_radar_test_ent( "jalopy_radar_test_ent", "none" ); + +//----------------------------------------------------------------------------- +// Purpose: Search for things that the radar detects, and stick them in the +// UTILVector that gets sent to the client for radar display. +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::UpdateRadar( bool forceUpdate ) +{ + bool bDetectedDog = false; + + if( !m_bRadarEnabled ) + return; + + if( !forceUpdate && gpGlobals->curtime < m_flNextRadarUpdateTime ) + return; + + // Count the targets on radar. If any more targets come on the radar, we beep. + int m_iNumOldRadarContacts = m_iNumRadarContacts; + + m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY; + m_iNumRadarContacts = 0; + + CBaseEntity *pEnt = gEntList.FirstEnt(); + string_t iszRadarTarget = FindPooledString( "info_radar_target" ); + string_t iszStriderName = FindPooledString( "npc_strider" ); + string_t iszHunterName = FindPooledString( "npc_hunter" ); + + string_t iszTestName = FindPooledString( jalopy_radar_test_ent.GetString() ); + + Vector vecJalopyOrigin = WorldSpaceCenter(); + + while( pEnt != NULL ) + { + int type = RADAR_CONTACT_NONE; + + if( pEnt->m_iClassname == iszRadarTarget ) + { + CRadarTarget *pTarget = dynamic_cast<CRadarTarget*>(pEnt); + + if( pTarget != NULL && !pTarget->IsDisabled() ) + { + if( pTarget->m_flRadius < 0 || vecJalopyOrigin.DistToSqr(pTarget->GetAbsOrigin()) <= Square(pTarget->m_flRadius) ) + { + // This item has been detected. + type = pTarget->GetType(); + + if( type == RADAR_CONTACT_DOG ) + bDetectedDog = true;// used to prevent Alyx talking about the radar (see below) + + if( pTarget->GetMode() == RADAR_MODE_STICKY ) + { + // This beacon was just detected. Now change the radius to infinite + // so that it will never go off the radar due to distance. + pTarget->m_flRadius = -1; + } + } + } + } + else if ( m_bRadarDetectsEnemies ) + { + if ( pEnt->m_iClassname == iszStriderName ) + { + CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider*>(pEnt); + + if( !pStrider || !pStrider->CarriedByDropship() ) + { + // Ignore striders which are carried by dropships. + type = RADAR_CONTACT_LARGE_ENEMY; + } + } + + if ( pEnt->m_iClassname == iszHunterName ) + { + type = RADAR_CONTACT_ENEMY; + } + } + + if( type != RADAR_CONTACT_NONE ) + { + Vector vecPos = pEnt->WorldSpaceCenter(); + + m_vecRadarContactPos.Set( m_iNumRadarContacts, vecPos ); + m_iRadarContactType.Set( m_iNumRadarContacts, type ); + m_iNumRadarContacts++; + + if( m_iNumRadarContacts == RADAR_MAX_CONTACTS ) + break; + } + + pEnt = gEntList.NextEnt(pEnt); + } + + if( m_iNumRadarContacts > m_iNumOldRadarContacts ) + { + // Play a bleepy sound + if( !bDetectedDog ) + { + EmitSound( "JNK_Radar_Ping_Friendly" ); + } + + //Notify Alyx so she can talk about the radar contact + CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); + + if( !bDetectedDog && pAlyx != NULL && pAlyx->GetVehicle() ) + { + pAlyx->SpeakIfAllowed( TLK_PASSENGER_NEW_RADAR_CONTACT ); + } + } + + if( bDetectedDog ) + { + // Update the radar much more frequently when dog is around. + m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY_FAST; + } + + //Msg("Server detected %d objects\n", m_iNumRadarContacts ); + + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + CSingleUserRecipientFilter filter(pPlayer); + UserMessageBegin( filter, "UpdateJalopyRadar" ); + WRITE_BYTE( 0 ); // end marker + MessageEnd(); // send message +} + +ConVar jalopy_cargo_anim_time( "jalopy_cargo_anim_time", "1.0" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::UpdateCargoEntry( void ) +{ + // Don't bother if we have no prop to move + if ( m_hCargoProp == NULL ) + return; + + // If we're past our animation point, then we're already done + if ( m_flCargoStartTime + jalopy_cargo_anim_time.GetFloat() < gpGlobals->curtime ) + { + // Close the hold immediately if we're finished + if ( m_bAddingCargo ) + { + m_flAmmoCrateCloseTime = gpGlobals->curtime; + m_bAddingCargo = false; + } + + return; + } + + // Get our target point + int nAttachment = LookupAttachment( "cargo" ); + Vector vecTarget, vecOut; + QAngle vecAngles; + GetAttachmentLocal( nAttachment, vecTarget, vecAngles ); + + // Find where we are in the blend and bias it for a fast entry and slow ease-out + float flPerc = (jalopy_cargo_anim_time.GetFloat()) ? (( gpGlobals->curtime - m_flCargoStartTime ) / jalopy_cargo_anim_time.GetFloat()) : 1.0f; + flPerc = Bias( flPerc, 0.75f ); + VectorLerp( m_hCargoProp->GetLocalOrigin(), vecTarget, flPerc, vecOut ); + + // Get our target orientation + CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(m_hCargoProp.Get()); + if ( pProp == NULL ) + return; + + // Slerp our quaternions to find where we are this frame + Quaternion qtTarget; + QAngle qa( 0, 90, 0 ); + qa += pProp->PreferredCarryAngles(); + AngleQuaternion( qa, qtTarget ); // FIXME: Find the real offset to make this sit properly + Quaternion qtCurrent; + AngleQuaternion( pProp->GetLocalAngles(), qtCurrent ); + + Quaternion qtOut; + QuaternionSlerp( qtCurrent, qtTarget, flPerc, qtOut ); + + // Put it back to angles + QuaternionAngles( qtOut, vecAngles ); + + // Finally, take these new position + m_hCargoProp->SetLocalOrigin( vecOut ); + m_hCargoProp->SetLocalAngles( vecAngles ); + + // Push the closing out into the future to make sure we don't try and close at the same time + m_flAmmoCrateCloseTime += gpGlobals->frametime; +} + +#define VEHICLE_AVOID_BROADCAST_RATE 0.5f + +//----------------------------------------------------------------------------- +// Purpose: This function isn't really what we want +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::CreateAvoidanceZone( void ) +{ + if ( m_flNextAvoidBroadcastTime > gpGlobals->curtime ) + return; + + // Only do this when we're stopped + if ( m_VehiclePhysics.GetSpeed() > 5.0f ) + return; + + float flHullRadius = CollisionProp()->BoundingRadius2D(); + + Vector vecPos; + CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.33f, 0.25f ), &vecPos ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this ); + // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE ); + + CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.66f, 0.25f ), &vecPos ); + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this ); + // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE ); + + // Don't broadcast again until these are done + m_flNextAvoidBroadcastTime = gpGlobals->curtime + VEHICLE_AVOID_BROADCAST_RATE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::Think( void ) +{ + BaseClass::Think(); + + // If our passenger is transitioning, then don't let the player drive off + CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx(); + if ( pAlyx && pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING ) + { + m_throttleDisableTime = gpGlobals->curtime + 0.25f; + } + + // Update our cargo entering our hold + UpdateCargoEntry(); + + // See if the wheel dust should be on or off + UpdateWheelDust(); + + // Update the radar, of course. + UpdateRadar(); + + if ( m_hCargoTrigger && !m_hCargoProp && !m_hCargoTrigger->m_pfnTouch ) + { + m_hCargoTrigger->Enable(); + } + + CreateAvoidanceZone(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEntity - +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::AddPropToCargoHold( CPhysicsProp *pProp ) +{ + // The hold must be empty to add something to it + if ( m_hCargoProp != NULL ) + { + Assert( 0 ); + return; + } + + // Take the prop as our cargo + m_hCargoProp = pProp; + m_flCargoStartTime = gpGlobals->curtime; + m_bAddingCargo = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Drops the cargo from the hold +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::ReleasePropFromCargoHold( void ) +{ + // Pull the object free! + m_hCargoProp->SetParent( NULL ); + m_hCargoProp->CreateVPhysics(); + + if ( m_hCargoTrigger ) + { + m_hCargoTrigger->Enable(); + m_hCargoTrigger->IgnoreTouches( m_hCargoProp ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: If the player is trying to pull the cargo out of the hold using the physcannon, let him +// Output : Returns the cargo to pick up, if all the conditions are met +//----------------------------------------------------------------------------- +CBaseEntity *CPropJeepEpisodic::OnFailedPhysGunPickup( Vector vPhysgunPos ) +{ + // Make sure we're available to open + if ( m_hCargoProp != NULL ) + { + // Player's forward direction + Vector vecPlayerForward; + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer == NULL ) + return NULL; + + pPlayer->EyeVectors( &vecPlayerForward ); + + // Origin and facing of the cargo hold + Vector vecCargoOrigin; + Vector vecCargoForward; + GetAttachment( "cargo", vecCargoOrigin, &vecCargoForward ); + + // Direction from the cargo to the player's position + Vector vecPickupDir = ( vecCargoOrigin - vPhysgunPos ); + float flDist = VectorNormalize( vecPickupDir ); + + // We need to make sure the player's position is within a cone near the opening and that they're also facing the right way + bool bInCargoRange = ( (flDist < (15.0f * 12.0f)) && DotProduct( vecCargoForward, vecPickupDir ) < 0.1f ); + bool bFacingCargo = DotProduct( vecPlayerForward, vecPickupDir ) > 0.975f; + + // If we're roughly pulling at the item, pick that up + if ( bInCargoRange && bFacingCargo ) + { + // Save this for later + CBaseEntity *pCargo = m_hCargoProp; + + // Drop the cargo + ReleasePropFromCargoHold(); + + // Forget the item but pass it back as the object to pick up + m_hCargoProp = NULL; + return pCargo; + } + } + + return BaseClass::OnFailedPhysGunPickup( vPhysgunPos ); +} + +// adds a collision solver for any small props that are stuck under the vehicle +static void SolveBlockingProps( CPropJeepEpisodic *pVehicleEntity, IPhysicsObject *pVehiclePhysics ) +{ + CUtlVector<CBaseEntity *> solveList; + float vehicleMass = pVehiclePhysics->GetMass(); + Vector vehicleUp; + pVehicleEntity->GetVectors( NULL, NULL, &vehicleUp ); + IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot(); + while ( pSnapshot->IsValid() ) + { + IPhysicsObject *pOther = pSnapshot->GetObject(1); + float otherMass = pOther->GetMass(); + CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData()); + Assert(pOtherEntity); + if ( pOtherEntity && pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && (otherMass*4.0f) < vehicleMass ) + { + Vector normal; + pSnapshot->GetSurfaceNormal(normal); + // this points down in the car's reference frame, then it's probably trapped under the car + if ( DotProduct(normal, vehicleUp) < -0.9f ) + { + Vector point, pointLocal; + pSnapshot->GetContactPoint(point); + VectorITransform( point, pVehicleEntity->EntityToWorldTransform(), pointLocal ); + Vector bottomPoint = physcollision->CollideGetExtent( pVehiclePhysics->GetCollide(), vec3_origin, vec3_angle, Vector(0,0,-1) ); + // make sure it's under the bottom of the car + float bottomPlane = DotProduct(bottomPoint,vehicleUp)+8; // 8 inches above bottom + if ( DotProduct( pointLocal, vehicleUp ) <= bottomPlane ) + { + //Msg("Solved %s\n", pOtherEntity->GetClassname()); + if ( solveList.Find(pOtherEntity) < 0 ) + { + solveList.AddToTail(pOtherEntity); + } + } + } + } + pSnapshot->NextFrictionData(); + } + pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot ); + if ( solveList.Count() ) + { + for ( int i = 0; i < solveList.Count(); i++ ) + { + EntityPhysics_CreateSolver( pVehicleEntity, solveList[i], true, 4.0f ); + } + pVehiclePhysics->RecheckContactPoints(); + } +} + +static void SimpleCollisionResponse( Vector velocityIn, const Vector &normal, float coefficientOfRestitution, Vector *pVelocityOut ) +{ + Vector Vn = DotProduct(velocityIn,normal) * normal; + Vector Vt = velocityIn - Vn; + *pVelocityOut = Vt - coefficientOfRestitution * Vn; +} + +static void KillBlockingEnemyNPCs( CBasePlayer *pPlayer, CBaseEntity *pVehicleEntity, IPhysicsObject *pVehiclePhysics ) +{ + Vector velocity; + pVehiclePhysics->GetVelocity( &velocity, NULL ); + float vehicleMass = pVehiclePhysics->GetMass(); + + // loop through the contacts and look for enemy NPCs that we're pushing on + CUtlVector<CAI_BaseNPC *> npcList; + CUtlVector<Vector> forceList; + CUtlVector<Vector> contactList; + IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot(); + while ( pSnapshot->IsValid() ) + { + IPhysicsObject *pOther = pSnapshot->GetObject(1); + float otherMass = pOther->GetMass(); + CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData()); + CAI_BaseNPC *pNPC = pOtherEntity ? pOtherEntity->MyNPCPointer() : NULL; + // Is this an enemy NPC with a small enough mass? + if ( pNPC && pPlayer->IRelationType(pNPC) != D_LI && ((otherMass*2.0f) < vehicleMass) ) + { + // accumulate the stress force for this NPC in the lsit + float force = pSnapshot->GetNormalForce(); + Vector normal; + pSnapshot->GetSurfaceNormal(normal); + normal *= force; + int index = npcList.Find(pNPC); + if ( index < 0 ) + { + vphysicsupdateai_t *pUpdate = NULL; + if ( pNPC->VPhysicsGetObject() && pNPC->VPhysicsGetObject()->GetShadowController() && pNPC->GetMoveType() == MOVETYPE_STEP ) + { + if ( pNPC->HasDataObjectType(VPHYSICSUPDATEAI) ) + { + pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->GetDataObject(VPHYSICSUPDATEAI)); + // kill this guy if I've been pushing him for more than half a second and I'm + // still pushing in his direction + if ( (gpGlobals->curtime - pUpdate->startUpdateTime) > 0.5f && DotProduct(velocity,normal) > 0) + { + index = npcList.AddToTail(pNPC); + forceList.AddToTail( normal ); + Vector pos; + pSnapshot->GetContactPoint(pos); + contactList.AddToTail(pos); + } + } + else + { + pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->CreateDataObject( VPHYSICSUPDATEAI )); + pUpdate->startUpdateTime = gpGlobals->curtime; + } + // update based on vphysics for the next second + // this allows the car to push the NPC + pUpdate->stopUpdateTime = gpGlobals->curtime + 1.0f; + float maxAngular; + pNPC->VPhysicsGetObject()->GetShadowController()->GetMaxSpeed( &pUpdate->savedShadowControllerMaxSpeed, &maxAngular ); + pNPC->VPhysicsGetObject()->GetShadowController()->MaxSpeed( 1.0f, maxAngular ); + } + } + else + { + forceList[index] += normal; + } + } + pSnapshot->NextFrictionData(); + } + pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot ); + // now iterate the list and check each cumulative force against the threshold + if ( npcList.Count() ) + { + for ( int i = npcList.Count(); --i >= 0; ) + { + Vector damageForce; + npcList[i]->VPhysicsGetObject()->GetVelocity( &damageForce, NULL ); + Vector vel; + pVehiclePhysics->GetVelocityAtPoint( contactList[i], &vel ); + damageForce -= vel; + Vector normal = forceList[i]; + VectorNormalize(normal); + SimpleCollisionResponse( damageForce, normal, 1.0, &damageForce ); + damageForce += (normal * 300.0f); + damageForce *= npcList[i]->VPhysicsGetObject()->GetMass(); + float len = damageForce.Length(); + damageForce.z += len*phys_upimpactforcescale.GetFloat(); + Vector vehicleForce = -damageForce; + + CTakeDamageInfo dmgInfo( pVehicleEntity, pVehicleEntity, damageForce, contactList[i], 200.0f, DMG_CRUSH|DMG_VEHICLE ); + npcList[i]->TakeDamage( dmgInfo ); + pVehiclePhysics->ApplyForceOffset( vehicleForce, contactList[i] ); + PhysCollisionSound( pVehicleEntity, npcList[i]->VPhysicsGetObject(), CHAN_BODY, pVehiclePhysics->GetMaterialIndex(), npcList[i]->VPhysicsGetObject()->GetMaterialIndex(), gpGlobals->frametime, 200.0f ); + } + } +} + +void CPropJeepEpisodic::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased ) +{ + /* The car headlight hurts perf, there's no timer to turn it off automatically, + and we haven't built any gameplay around it. + + Furthermore, I don't think I've ever seen a playtester turn it on. + + if ( ucmd->impulse == 100 ) + { + if (HeadlightIsOn()) + { + HeadlightTurnOff(); + } + else + { + HeadlightTurnOn(); + } + }*/ + + if ( ucmd->forwardmove != 0.0f ) + { + //Msg("Push V: %.2f, %.2f, %.2f\n", ucmd->forwardmove, carState->engineRPM, carState->speed ); + CBasePlayer *pPlayer = ToBasePlayer(GetDriver()); + + if ( pPlayer && VPhysicsGetObject() ) + { + KillBlockingEnemyNPCs( pPlayer, this, VPhysicsGetObject() ); + SolveBlockingProps( this, VPhysicsGetObject() ); + } + } + BaseClass::DriveVehicle(flFrameTime, ucmd, iButtonsDown, iButtonsReleased); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::CreateHazardLights( void ) +{ + static const char *s_szAttach[NUM_HAZARD_LIGHTS] = + { + "rearlight_r", + "rearlight_l", + "headlight_r", + "headlight_l", + }; + + // Turn on the hazards! + for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) + { + if ( m_hHazardLights[i] == NULL ) + { + m_hHazardLights[i] = CSprite::SpriteCreate( s_szHazardSprite, GetLocalOrigin(), false ); + if ( m_hHazardLights[i] ) + { + m_hHazardLights[i]->SetTransparency( kRenderWorldGlow, 255, 220, 40, 255, kRenderFxNoDissipation ); + m_hHazardLights[i]->SetAttachment( this, LookupAttachment( s_szAttach[i] ) ); + m_hHazardLights[i]->SetGlowProxySize( 2.0f ); + m_hHazardLights[i]->TurnOff(); + if ( i < 2 ) + { + // Rear lights are red + m_hHazardLights[i]->SetColor( 255, 0, 0 ); + m_hHazardLights[i]->SetScale( 1.0f ); + } + else + { + // Font lights are white + m_hHazardLights[i]->SetScale( 1.0f ); + } + } + } + } + + // We start off + m_bBlink = false; + + // Setup our blink + SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.1f, "HazardBlink" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::DestroyHazardLights( void ) +{ + for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) + { + if ( m_hHazardLights[i] != NULL ) + { + UTIL_Remove( m_hHazardLights[i] ); + } + } + + SetContextThink( NULL, gpGlobals->curtime, "HazardBlink" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : nRole - +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::ExitVehicle( int nRole ) +{ + BaseClass::ExitVehicle( nRole ); + + CreateHazardLights(); +} + +void CPropJeepEpisodic::SetBusterHopperVisibility(bool visible) +{ + // if we're there already do nothing + if (visible == m_bBusterHopperVisible) + return; + + SetBodygroup( JEEP_HOPPER_BODYGROUP, visible ? 1 : 0); + m_bBusterHopperVisible = visible; +} + + +void CPropJeepEpisodic::InputSetCargoVisibility( inputdata_t &data ) +{ + bool visible = data.value.Bool(); + + SetBusterHopperVisibility( visible ); +} + +//----------------------------------------------------------------------------- +// THIS CODE LIFTED RIGHT OUT OF TF2, to defer the pain of making vgui-on-an-entity +// code available to all CBaseAnimating. +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::SpawnRadarPanel() +{ + // FIXME: Deal with dynamically resizing control panels? + + // If we're attached to an entity, spawn control panels on it instead of use + CBaseAnimating *pEntityToSpawnOn = this; + char *pOrgLL = "controlpanel0_ll"; + char *pOrgUR = "controlpanel0_ur"; + + Assert( pEntityToSpawnOn ); + + // Lookup the attachment point... + int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgLL); + + if (nLLAttachmentIndex <= 0) + { + return; + } + + int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgUR); + if (nURAttachmentIndex <= 0) + { + return; + } + + const char *pScreenName = "jalopy_radar_panel"; + const char *pScreenClassname = "vgui_screen"; + + // Compute the screen size from the attachment points... + matrix3x4_t panelToWorld; + pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld ); + + matrix3x4_t worldToPanel; + MatrixInvert( panelToWorld, worldToPanel ); + + // Now get the lower right position + transform into panel space + Vector lr, lrlocal; + pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld ); + MatrixGetColumn( panelToWorld, 3, lr ); + VectorTransform( lr, worldToPanel, lrlocal ); + + float flWidth = lrlocal.x; + float flHeight = lrlocal.y; + + CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex ); + pScreen->SetActualSize( flWidth, flHeight ); + pScreen->SetActive( true ); + pScreen->SetOverlayMaterial( RADAR_PANEL_WRITEZ ); + pScreen->SetTransparency( true ); + + m_hRadarScreen.Set( pScreen ); + + m_bRadarEnabled = true; + m_iNumRadarContacts = 0; + m_flNextRadarUpdateTime = gpGlobals->curtime - 1.0f; +} + +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::DestroyRadarPanel() +{ + Assert( m_hRadarScreen != NULL ); + m_hRadarScreen->SUB_Remove(); + m_bRadarEnabled = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::HazardBlinkThink( void ) +{ + if ( m_bBlink ) + { + for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) + { + if ( m_hHazardLights[i] ) + { + m_hHazardLights[i]->SetBrightness( 0, 0.1f ); + } + } + + SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.25f, "HazardBlink" ); + } + else + { + for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ ) + { + if ( m_hHazardLights[i] ) + { + m_hHazardLights[i]->SetBrightness( 255, 0.1f ); + m_hHazardLights[i]->TurnOn(); + } + } + + SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.5f, "HazardBlink" ); + } + + m_bBlink = !m_bBlink; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::HandleWater( void ) +{ + // Only check the wheels and engine in water if we have a driver (player). + if ( !GetDriver() ) + return; + + // Update our internal state + CheckWater(); + + // Save of data from last think. + for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel ) + { + m_WaterData.m_bWheelWasInWater[iWheel] = m_WaterData.m_bWheelInWater[iWheel]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Report our lock state +//----------------------------------------------------------------------------- +int CPropJeepEpisodic::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if ( m_debugOverlays & OVERLAY_TEXT_BIT ) + { + EntityText( text_offset, CFmtStr("Entrance: %s", m_bEntranceLocked ? "Locked" : "Unlocked" ), 0 ); + text_offset++; + + EntityText( text_offset, CFmtStr("Exit: %s", m_bExitLocked ? "Locked" : "Unlocked" ), 0 ); + text_offset++; + } + + return text_offset; +} + +#define TRANSITION_SEARCH_RADIUS (100*12) + +//----------------------------------------------------------------------------- +// Purpose: Teleport the car to a destination that will cause it to transition if it's not going to otherwise +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputOutsideTransition( inputdata_t &inputdata ) +{ + // Teleport into the new map + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + Vector vecTeleportPos; + QAngle vecTeleportAngles; + + // Get our bounds + Vector vecSurroundMins, vecSurroundMaxs; + CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs ); + vecSurroundMins -= WorldSpaceCenter(); + vecSurroundMaxs -= WorldSpaceCenter(); + + Vector vecBestPos; + QAngle vecBestAngles; + + CInfoTargetVehicleTransition *pEntity = NULL; + bool bSucceeded = false; + + // Find all entities of the correct name and try and sit where they're at + while ( ( pEntity = (CInfoTargetVehicleTransition *) gEntList.FindEntityByClassname( pEntity, "info_target_vehicle_transition" ) ) != NULL ) + { + // Must be enabled + if ( pEntity->IsDisabled() ) + continue; + + // Must be within range + if ( ( pEntity->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() > Square( TRANSITION_SEARCH_RADIUS ) ) + continue; + + vecTeleportPos = pEntity->GetAbsOrigin(); + vecTeleportAngles = pEntity->GetAbsAngles() + QAngle( 0, -90, 0 ); // Vehicle is always off by 90 degrees + + // Rotate to face the destination angles + Vector vecMins; + Vector vecMaxs; + VectorRotate( vecSurroundMins, vecTeleportAngles, vecMins ); + VectorRotate( vecSurroundMaxs, vecTeleportAngles, vecMaxs ); + + if ( vecMaxs.x < vecMins.x ) + V_swap( vecMins.x, vecMaxs.x ); + + if ( vecMaxs.y < vecMins.y ) + V_swap( vecMins.y, vecMaxs.y ); + + if ( vecMaxs.z < vecMins.z ) + V_swap( vecMins.z, vecMaxs.z ); + + // Move up + vecTeleportPos.z += ( vecMaxs.z - vecMins.z ); + + trace_t tr; + UTIL_TraceHull( vecTeleportPos, vecTeleportPos - Vector( 0, 0, 128 ), vecMins, vecMaxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + if ( tr.startsolid == false && tr.allsolid == false && tr.fraction < 1.0f ) + { + // Store this off + vecBestPos = tr.endpos; + vecBestAngles = vecTeleportAngles; + bSucceeded = true; + + // If this point isn't visible, then stop looking and use it + if ( pPlayer->FInViewCone( tr.endpos ) == false ) + break; + } + } + + // See if we're finished + if ( bSucceeded ) + { + Teleport( &vecTeleportPos, &vecTeleportAngles, NULL ); + return; + } + + // TODO: We found no valid teleport points, so try to find them dynamically + Warning("No valid vehicle teleport points!\n"); +} + +//----------------------------------------------------------------------------- +// Purpose: Stop players punting the car around. +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputDisablePhysGun( inputdata_t &data ) +{ + AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ); +} +//----------------------------------------------------------------------------- +// Purpose: Return to normal +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputEnablePhysGun( inputdata_t &data ) +{ + RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION ); +} + +//----------------------------------------------------------------------------- +// Create and parent two radial node link controllers. +//----------------------------------------------------------------------------- +void CPropJeepEpisodic::InputCreateLinkController( inputdata_t &data ) +{ + Vector vecFront, vecRear; + Vector vecWFL, vecWFR; // Front wheels + Vector vecWRL, vecWRR; // Back wheels + + GetAttachment( "wheel_fr", vecWFR ); + GetAttachment( "wheel_fl", vecWFL ); + + GetAttachment( "wheel_rr", vecWRR ); + GetAttachment( "wheel_rl", vecWRL ); + + vecFront = (vecWFL + vecWFR) * 0.5f; + vecRear = (vecWRL + vecWRR) * 0.5f; + + float flRadius = ( (vecFront - vecRear).Length() ) * 0.6f; + + CAI_RadialLinkController *pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" ); + if( pLinkController != NULL && m_hLinkControllerFront.Get() == NULL ) + { + pLinkController->m_flRadius = flRadius; + pLinkController->Spawn(); + pLinkController->SetAbsOrigin( vecFront ); + pLinkController->SetOwnerEntity( this ); + pLinkController->SetParent( this ); + pLinkController->Activate(); + m_hLinkControllerFront.Set( pLinkController ); + + //NDebugOverlay::Circle( vecFront, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 ); + } + + pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" ); + if( pLinkController != NULL && m_hLinkControllerRear.Get() == NULL ) + { + pLinkController->m_flRadius = flRadius; + pLinkController->Spawn(); + pLinkController->SetAbsOrigin( vecRear ); + pLinkController->SetOwnerEntity( this ); + pLinkController->SetParent( this ); + pLinkController->Activate(); + m_hLinkControllerRear.Set( pLinkController ); + + //NDebugOverlay::Circle( vecRear, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 ); + } +} + +void CPropJeepEpisodic::InputDestroyLinkController( inputdata_t &data ) +{ + if( m_hLinkControllerFront.Get() != NULL ) + { + CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerFront.Get()); + if( pLinkController != NULL ) + { + pLinkController->ModifyNodeLinks(false); + UTIL_Remove( pLinkController ); + m_hLinkControllerFront.Set(NULL); + } + } + + if( m_hLinkControllerRear.Get() != NULL ) + { + CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerRear.Get()); + if( pLinkController != NULL ) + { + pLinkController->ModifyNodeLinks(false); + UTIL_Remove( pLinkController ); + m_hLinkControllerRear.Set(NULL); + } + } +} + + +bool CPropJeepEpisodic::AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole ) +{ + // Wait until we've settled down before we resort to blocked exits. + // This keeps us from doing blocked exits in mid-jump, which can cause mayhem like + // sticking the player through player clips or into geometry. + return GetSmoothedVelocity().IsLengthLessThan( jalopy_blocked_exit_max_speed.GetFloat() ); +} + |