Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1764 lines
54 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "vehicle_jeep_episodic.h"
  8. #include "collisionutils.h"
  9. #include "npc_alyx_episodic.h"
  10. #include "particle_parse.h"
  11. #include "particle_system.h"
  12. #include "hl2_player.h"
  13. #include "in_buttons.h"
  14. #include "vphysics/friction.h"
  15. #include "vphysicsupdateai.h"
  16. #include "physics_npc_solver.h"
  17. #include "Sprite.h"
  18. #include "weapon_striderbuster.h"
  19. #include "npc_strider.h"
  20. #include "vguiscreen.h"
  21. #include "hl2_vehicle_radar.h"
  22. #include "props.h"
  23. #include "ai_dynamiclink.h"
  24. extern ConVar phys_upimpactforcescale;
  25. ConVar jalopy_blocked_exit_max_speed( "jalopy_blocked_exit_max_speed", "50" );
  26. #define JEEP_AMMOCRATE_HITGROUP 5
  27. #define JEEP_AMMO_CRATE_CLOSE_DELAY 2.0f
  28. // Bodygroups
  29. #define JEEP_RADAR_BODYGROUP 1
  30. #define JEEP_HOPPER_BODYGROUP 2
  31. #define JEEP_CARBAR_BODYGROUP 3
  32. #define RADAR_PANEL_MATERIAL "vgui/screens/radar"
  33. #define RADAR_PANEL_WRITEZ "engine/writez"
  34. static const char *s_szHazardSprite = "sprites/light_glow01.vmt";
  35. enum
  36. {
  37. RADAR_MODE_NORMAL = 0,
  38. RADAR_MODE_STICKY,
  39. };
  40. //=========================================================
  41. //=========================================================
  42. class CRadarTarget : public CPointEntity
  43. {
  44. DECLARE_CLASS( CRadarTarget, CPointEntity );
  45. public:
  46. void Spawn();
  47. bool IsDisabled() { return m_bDisabled; }
  48. int GetType() { return m_iType; }
  49. int GetMode() { return m_iMode; }
  50. void InputEnable( inputdata_t &inputdata );
  51. void InputDisable( inputdata_t &inputdata );
  52. int ObjectCaps();
  53. private:
  54. bool m_bDisabled;
  55. int m_iType;
  56. int m_iMode;
  57. public:
  58. float m_flRadius;
  59. DECLARE_DATADESC();
  60. };
  61. LINK_ENTITY_TO_CLASS( info_radar_target, CRadarTarget );
  62. BEGIN_DATADESC( CRadarTarget )
  63. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  64. DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
  65. DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ),
  66. DEFINE_KEYFIELD( m_iMode, FIELD_INTEGER, "mode" ),
  67. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  68. DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ),
  69. END_DATADESC();
  70. //-----------------------------------------------------------------------------
  71. // Purpose:
  72. //-----------------------------------------------------------------------------
  73. void CRadarTarget::Spawn()
  74. {
  75. BaseClass::Spawn();
  76. AddEffects( EF_NODRAW );
  77. SetMoveType( MOVETYPE_NONE );
  78. SetSolid( SOLID_NONE );
  79. }
  80. //-----------------------------------------------------------------------------
  81. // Purpose:
  82. //-----------------------------------------------------------------------------
  83. void CRadarTarget::InputEnable( inputdata_t &inputdata )
  84. {
  85. m_bDisabled = false;
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Purpose:
  89. //-----------------------------------------------------------------------------
  90. void CRadarTarget::InputDisable( inputdata_t &inputdata )
  91. {
  92. m_bDisabled = true;
  93. }
  94. //-----------------------------------------------------------------------------
  95. //-----------------------------------------------------------------------------
  96. int CRadarTarget::ObjectCaps()
  97. {
  98. return BaseClass::ObjectCaps() | FCAP_ACROSS_TRANSITION;
  99. }
  100. //
  101. // Trigger which detects entities placed in the cargo hold of the jalopy
  102. //
  103. class CVehicleCargoTrigger : public CBaseEntity
  104. {
  105. DECLARE_CLASS( CVehicleCargoTrigger, CBaseEntity );
  106. public:
  107. //
  108. // Creates a trigger with the specified bounds
  109. static CVehicleCargoTrigger *Create( const Vector &vecOrigin, const Vector &vecMins, const Vector &vecMaxs, CBaseEntity *pOwner )
  110. {
  111. CVehicleCargoTrigger *pTrigger = (CVehicleCargoTrigger *) CreateEntityByName( "trigger_vehicle_cargo" );
  112. if ( pTrigger == NULL )
  113. return NULL;
  114. UTIL_SetOrigin( pTrigger, vecOrigin );
  115. UTIL_SetSize( pTrigger, vecMins, vecMaxs );
  116. pTrigger->SetOwnerEntity( pOwner );
  117. pTrigger->SetParent( pOwner );
  118. pTrigger->Spawn();
  119. return pTrigger;
  120. }
  121. //
  122. // Handles the trigger touching its intended quarry
  123. void CargoTouch( CBaseEntity *pOther )
  124. {
  125. // Cannot be ignoring touches
  126. if ( ( m_hIgnoreEntity == pOther ) || ( m_flIgnoreDuration >= gpGlobals->curtime ) )
  127. return;
  128. // Make sure this object is being held by the player
  129. if ( pOther->VPhysicsGetObject() == NULL || (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) == false )
  130. return;
  131. if ( StriderBuster_NumFlechettesAttached( pOther ) > 0 )
  132. return;
  133. AddCargo( pOther );
  134. }
  135. bool AddCargo( CBaseEntity *pOther )
  136. {
  137. // For now, only bother with strider busters
  138. if ( (FClassnameIs( pOther, "weapon_striderbuster" ) == false) &&
  139. (FClassnameIs( pOther, "npc_grenade_magna" ) == false)
  140. )
  141. return false;
  142. // Must be a physics prop
  143. CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pOther);
  144. if ( pOther == NULL )
  145. return false;
  146. CPropJeepEpisodic *pJeep = dynamic_cast< CPropJeepEpisodic * >( GetOwnerEntity() );
  147. if ( pJeep == NULL )
  148. return false;
  149. // Make the player release the item
  150. Pickup_ForcePlayerToDropThisObject( pOther );
  151. // Stop colliding with things
  152. pOther->VPhysicsDestroyObject();
  153. pOther->SetSolidFlags( FSOLID_NOT_SOLID );
  154. pOther->SetMoveType( MOVETYPE_NONE );
  155. // Parent the object to our owner
  156. pOther->SetParent( GetOwnerEntity() );
  157. // The car now owns the entity
  158. pJeep->AddPropToCargoHold( pProp );
  159. // Notify the buster that it's been added to the cargo hold.
  160. StriderBuster_OnAddToCargoHold( pProp );
  161. // Stop touching this item
  162. Disable();
  163. return true;
  164. }
  165. //
  166. // Setup the entity
  167. void Spawn( void )
  168. {
  169. BaseClass::Spawn();
  170. SetSolid( SOLID_BBOX );
  171. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
  172. SetTouch( &CVehicleCargoTrigger::CargoTouch );
  173. }
  174. void Activate()
  175. {
  176. BaseClass::Activate();
  177. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); // Fixes up old savegames
  178. }
  179. //
  180. // When we've stopped touching this entity, we ignore it
  181. void EndTouch( CBaseEntity *pOther )
  182. {
  183. if ( pOther == m_hIgnoreEntity )
  184. {
  185. m_hIgnoreEntity = NULL;
  186. }
  187. BaseClass::EndTouch( pOther );
  188. }
  189. //
  190. // Disables the trigger for a set duration
  191. void IgnoreTouches( CBaseEntity *pIgnoreEntity )
  192. {
  193. m_hIgnoreEntity = pIgnoreEntity;
  194. m_flIgnoreDuration = gpGlobals->curtime + 0.5f;
  195. }
  196. void Disable( void )
  197. {
  198. SetTouch( NULL );
  199. }
  200. void Enable( void )
  201. {
  202. SetTouch( &CVehicleCargoTrigger::CargoTouch );
  203. }
  204. protected:
  205. float m_flIgnoreDuration;
  206. CHandle <CBaseEntity> m_hIgnoreEntity;
  207. DECLARE_DATADESC();
  208. };
  209. LINK_ENTITY_TO_CLASS( trigger_vehicle_cargo, CVehicleCargoTrigger );
  210. BEGIN_DATADESC( CVehicleCargoTrigger )
  211. DEFINE_FIELD( m_flIgnoreDuration, FIELD_TIME ),
  212. DEFINE_FIELD( m_hIgnoreEntity, FIELD_EHANDLE ),
  213. DEFINE_ENTITYFUNC( CargoTouch ),
  214. END_DATADESC();
  215. //
  216. // Transition reference point for the vehicle
  217. //
  218. class CInfoTargetVehicleTransition : public CPointEntity
  219. {
  220. public:
  221. DECLARE_CLASS( CInfoTargetVehicleTransition, CPointEntity );
  222. void Enable( void ) { m_bDisabled = false; }
  223. void Disable( void ) { m_bDisabled = true; }
  224. bool IsDisabled( void ) const { return m_bDisabled; }
  225. private:
  226. void InputEnable( inputdata_t &data ) { Enable(); }
  227. void InputDisable( inputdata_t &data ) { Disable(); }
  228. bool m_bDisabled;
  229. DECLARE_DATADESC();
  230. };
  231. BEGIN_DATADESC( CInfoTargetVehicleTransition )
  232. DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  233. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  234. DEFINE_INPUTFUNC( FIELD_VOID, "Disable",InputDisable ),
  235. END_DATADESC();
  236. LINK_ENTITY_TO_CLASS( info_target_vehicle_transition, CInfoTargetVehicleTransition );
  237. //
  238. // CPropJeepEpisodic
  239. //
  240. LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeepEpisodic );
  241. BEGIN_DATADESC( CPropJeepEpisodic )
  242. DEFINE_FIELD( m_bEntranceLocked, FIELD_BOOLEAN ),
  243. DEFINE_FIELD( m_bExitLocked, FIELD_BOOLEAN ),
  244. DEFINE_FIELD( m_hCargoProp, FIELD_EHANDLE ),
  245. DEFINE_FIELD( m_hCargoTrigger, FIELD_EHANDLE ),
  246. DEFINE_FIELD( m_bAddingCargo, FIELD_BOOLEAN ),
  247. DEFINE_ARRAY( m_hWheelDust, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ),
  248. DEFINE_ARRAY( m_hWheelWater, FIELD_EHANDLE, NUM_WHEEL_EFFECTS ),
  249. DEFINE_ARRAY( m_hHazardLights, FIELD_EHANDLE, NUM_HAZARD_LIGHTS ),
  250. DEFINE_FIELD( m_flCargoStartTime, FIELD_TIME ),
  251. DEFINE_FIELD( m_bBlink, FIELD_BOOLEAN ),
  252. DEFINE_FIELD( m_bRadarEnabled, FIELD_BOOLEAN ),
  253. DEFINE_FIELD( m_bRadarDetectsEnemies, FIELD_BOOLEAN ),
  254. DEFINE_FIELD( m_hRadarScreen, FIELD_EHANDLE ),
  255. DEFINE_FIELD( m_hLinkControllerFront, FIELD_EHANDLE ),
  256. DEFINE_FIELD( m_hLinkControllerRear, FIELD_EHANDLE ),
  257. DEFINE_KEYFIELD( m_bBusterHopperVisible, FIELD_BOOLEAN, "CargoVisible" ),
  258. // m_flNextAvoidBroadcastTime
  259. DEFINE_FIELD( m_flNextWaterSound, FIELD_TIME ),
  260. DEFINE_FIELD( m_flNextRadarUpdateTime, FIELD_TIME ),
  261. DEFINE_FIELD( m_iNumRadarContacts, FIELD_INTEGER ),
  262. DEFINE_ARRAY( m_vecRadarContactPos, FIELD_POSITION_VECTOR, RADAR_MAX_CONTACTS ),
  263. DEFINE_ARRAY( m_iRadarContactType, FIELD_INTEGER, RADAR_MAX_CONTACTS ),
  264. DEFINE_THINKFUNC( HazardBlinkThink ),
  265. DEFINE_OUTPUT( m_OnCompanionEnteredVehicle, "OnCompanionEnteredVehicle" ),
  266. DEFINE_OUTPUT( m_OnCompanionExitedVehicle, "OnCompanionExitedVehicle" ),
  267. DEFINE_OUTPUT( m_OnHostileEnteredVehicle, "OnHostileEnteredVehicle" ),
  268. DEFINE_OUTPUT( m_OnHostileExitedVehicle, "OnHostileExitedVehicle" ),
  269. DEFINE_INPUTFUNC( FIELD_VOID, "LockEntrance", InputLockEntrance ),
  270. DEFINE_INPUTFUNC( FIELD_VOID, "UnlockEntrance", InputUnlockEntrance ),
  271. DEFINE_INPUTFUNC( FIELD_VOID, "LockExit", InputLockExit ),
  272. DEFINE_INPUTFUNC( FIELD_VOID, "UnlockExit", InputUnlockExit ),
  273. DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadar", InputEnableRadar ),
  274. DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadar", InputDisableRadar ),
  275. DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadarDetectEnemies", InputEnableRadarDetectEnemies ),
  276. DEFINE_INPUTFUNC( FIELD_VOID, "AddBusterToCargo", InputAddBusterToCargo ),
  277. DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
  278. DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ),
  279. DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ),
  280. DEFINE_INPUTFUNC( FIELD_VOID, "CreateLinkController", InputCreateLinkController ),
  281. DEFINE_INPUTFUNC( FIELD_VOID, "DestroyLinkController", InputDestroyLinkController ),
  282. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCargoHopperVisibility", InputSetCargoVisibility ),
  283. END_DATADESC();
  284. IMPLEMENT_SERVERCLASS_ST(CPropJeepEpisodic, DT_CPropJeepEpisodic)
  285. //CNetworkVar( int, m_iNumRadarContacts );
  286. SendPropInt( SENDINFO(m_iNumRadarContacts), 8 ),
  287. //CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS );
  288. SendPropArray( SendPropVector( SENDINFO_ARRAY(m_vecRadarContactPos), -1, SPROP_COORD), m_vecRadarContactPos ),
  289. //CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS );
  290. SendPropArray( SendPropInt(SENDINFO_ARRAY(m_iRadarContactType), RADAR_CONTACT_TYPE_BITS ), m_iRadarContactType ),
  291. END_SEND_TABLE()
  292. //=============================================================================
  293. // Episodic jeep
  294. CPropJeepEpisodic::CPropJeepEpisodic( void ) :
  295. m_bEntranceLocked( false ),
  296. m_bExitLocked( false ),
  297. m_bAddingCargo( false ),
  298. m_flNextAvoidBroadcastTime( 0.0f )
  299. {
  300. m_bHasGun = false;
  301. m_bUnableToFire = true;
  302. m_bRadarDetectsEnemies = false;
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Purpose:
  306. //-----------------------------------------------------------------------------
  307. void CPropJeepEpisodic::UpdateOnRemove( void )
  308. {
  309. BaseClass::UpdateOnRemove();
  310. // Kill our wheel dust
  311. for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ )
  312. {
  313. if ( m_hWheelDust[i] != NULL )
  314. {
  315. UTIL_Remove( m_hWheelDust[i] );
  316. }
  317. if ( m_hWheelWater[i] != NULL )
  318. {
  319. UTIL_Remove( m_hWheelWater[i] );
  320. }
  321. }
  322. DestroyHazardLights();
  323. }
  324. //-----------------------------------------------------------------------------
  325. // Purpose:
  326. //-----------------------------------------------------------------------------
  327. void CPropJeepEpisodic::Precache( void )
  328. {
  329. PrecacheMaterial( RADAR_PANEL_MATERIAL );
  330. PrecacheMaterial( RADAR_PANEL_WRITEZ );
  331. PrecacheModel( s_szHazardSprite );
  332. PrecacheScriptSound( "JNK_Radar_Ping_Friendly" );
  333. PrecacheScriptSound( "Physics.WaterSplash" );
  334. PrecacheParticleSystem( "WheelDust" );
  335. PrecacheParticleSystem( "WheelSplash" );
  336. BaseClass::Precache();
  337. }
  338. //-----------------------------------------------------------------------------
  339. // Purpose:
  340. // Input : *pPlayer -
  341. //-----------------------------------------------------------------------------
  342. void CPropJeepEpisodic::EnterVehicle( CBaseCombatCharacter *pPassenger )
  343. {
  344. BaseClass::EnterVehicle( pPassenger );
  345. // Turn our hazards off!
  346. DestroyHazardLights();
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose:
  350. //-----------------------------------------------------------------------------
  351. void CPropJeepEpisodic::Spawn( void )
  352. {
  353. BaseClass::Spawn();
  354. SetBlocksLOS( false );
  355. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  356. if ( pPlayer != NULL )
  357. {
  358. pPlayer->m_Local.m_iHideHUD |= HIDEHUD_VEHICLE_CROSSHAIR;
  359. }
  360. SetBodygroup( JEEP_HOPPER_BODYGROUP, m_bBusterHopperVisible ? 1 : 0);
  361. CreateCargoTrigger();
  362. // carbar bodygroup is always on
  363. SetBodygroup( JEEP_CARBAR_BODYGROUP, 1 );
  364. m_bRadarDetectsEnemies = false;
  365. }
  366. //-----------------------------------------------------------------------------
  367. //-----------------------------------------------------------------------------
  368. void CPropJeepEpisodic::Activate()
  369. {
  370. m_iNumRadarContacts = 0; // Force first contact tone
  371. BaseClass::Activate();
  372. }
  373. //-----------------------------------------------------------------------------
  374. // Purpose:
  375. //-----------------------------------------------------------------------------
  376. void CPropJeepEpisodic::NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
  377. {
  378. // FIXME: This will be moved to the NPCs entering and exiting
  379. // Fire our outputs
  380. if ( bCompanion )
  381. {
  382. m_OnCompanionEnteredVehicle.FireOutput( this, pPassenger );
  383. }
  384. else
  385. {
  386. m_OnHostileEnteredVehicle.FireOutput( this, pPassenger );
  387. }
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose:
  391. //-----------------------------------------------------------------------------
  392. void CPropJeepEpisodic::NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
  393. {
  394. // FIXME: This will be moved to the NPCs entering and exiting
  395. // Fire our outputs
  396. if ( bCompanion )
  397. {
  398. m_OnCompanionExitedVehicle.FireOutput( this, pPassenger );
  399. }
  400. else
  401. {
  402. m_OnHostileExitedVehicle.FireOutput( this, pPassenger );
  403. }
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose:
  407. // Input : *pPassenger -
  408. // bCompanion -
  409. // Output : Returns true on success, false on failure.
  410. //-----------------------------------------------------------------------------
  411. bool CPropJeepEpisodic::NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
  412. {
  413. // Must be unlocked
  414. if ( bCompanion && m_bEntranceLocked )
  415. return false;
  416. return BaseClass::NPC_CanEnterVehicle( pPassenger, bCompanion );
  417. }
  418. //-----------------------------------------------------------------------------
  419. // Purpose:
  420. // Input : *pPassenger -
  421. // bCompanion -
  422. // Output : Returns true on success, false on failure.
  423. //-----------------------------------------------------------------------------
  424. bool CPropJeepEpisodic::NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion )
  425. {
  426. // Must be unlocked
  427. if ( bCompanion && m_bExitLocked )
  428. return false;
  429. return BaseClass::NPC_CanExitVehicle( pPassenger, bCompanion );
  430. }
  431. //-----------------------------------------------------------------------------
  432. // Purpose:
  433. //-----------------------------------------------------------------------------
  434. void CPropJeepEpisodic::InputLockEntrance( inputdata_t &data )
  435. {
  436. m_bEntranceLocked = true;
  437. }
  438. //-----------------------------------------------------------------------------
  439. // Purpose:
  440. //-----------------------------------------------------------------------------
  441. void CPropJeepEpisodic::InputUnlockEntrance( inputdata_t &data )
  442. {
  443. m_bEntranceLocked = false;
  444. }
  445. //-----------------------------------------------------------------------------
  446. // Purpose:
  447. //-----------------------------------------------------------------------------
  448. void CPropJeepEpisodic::InputLockExit( inputdata_t &data )
  449. {
  450. m_bExitLocked = true;
  451. }
  452. //-----------------------------------------------------------------------------
  453. // Purpose:
  454. //-----------------------------------------------------------------------------
  455. void CPropJeepEpisodic::InputUnlockExit( inputdata_t &data )
  456. {
  457. m_bExitLocked = false;
  458. }
  459. //-----------------------------------------------------------------------------
  460. // Purpose: Turn on the Jalopy radar device
  461. //-----------------------------------------------------------------------------
  462. void CPropJeepEpisodic::InputEnableRadar( inputdata_t &data )
  463. {
  464. if( m_bRadarEnabled )
  465. return; // Already enabled
  466. SetBodygroup( JEEP_RADAR_BODYGROUP, 1 );
  467. SpawnRadarPanel();
  468. }
  469. //-----------------------------------------------------------------------------
  470. // Purpose: Turn off the Jalopy radar device
  471. //-----------------------------------------------------------------------------
  472. void CPropJeepEpisodic::InputDisableRadar( inputdata_t &data )
  473. {
  474. if( !m_bRadarEnabled )
  475. return; // Already disabled
  476. SetBodygroup( JEEP_RADAR_BODYGROUP, 0 );
  477. DestroyRadarPanel();
  478. }
  479. //-----------------------------------------------------------------------------
  480. // Purpose: Allow the Jalopy radar to detect Hunters and Striders
  481. //-----------------------------------------------------------------------------
  482. void CPropJeepEpisodic::InputEnableRadarDetectEnemies( inputdata_t &data )
  483. {
  484. m_bRadarDetectsEnemies = true;
  485. }
  486. //-----------------------------------------------------------------------------
  487. // Purpose:
  488. //-----------------------------------------------------------------------------
  489. void CPropJeepEpisodic::InputAddBusterToCargo( inputdata_t &data )
  490. {
  491. if ( m_hCargoProp != NULL)
  492. {
  493. ReleasePropFromCargoHold();
  494. m_hCargoProp = NULL;
  495. }
  496. CBaseEntity *pNewBomb = CreateEntityByName( "weapon_striderbuster" );
  497. if ( pNewBomb )
  498. {
  499. DispatchSpawn( pNewBomb );
  500. pNewBomb->Teleport( &m_hCargoTrigger->GetAbsOrigin(), NULL, NULL );
  501. m_hCargoTrigger->AddCargo( pNewBomb );
  502. }
  503. }
  504. //-----------------------------------------------------------------------------
  505. // Purpose:
  506. // Output : Returns true on success, false on failure.
  507. //-----------------------------------------------------------------------------
  508. bool CPropJeepEpisodic::PassengerInTransition( void )
  509. {
  510. // FIXME: Big hack - we need a way to bridge this data better
  511. // TODO: Get a list of passengers we can traverse instead
  512. CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
  513. if ( pAlyx )
  514. {
  515. if ( pAlyx->GetPassengerState() == PASSENGER_STATE_ENTERING ||
  516. pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING )
  517. return true;
  518. }
  519. return false;
  520. }
  521. //-----------------------------------------------------------------------------
  522. // Purpose: Override velocity if our passenger is transitioning or we're upside-down
  523. //-----------------------------------------------------------------------------
  524. Vector CPropJeepEpisodic::PhysGunLaunchVelocity( const Vector &forward, float flMass )
  525. {
  526. // Disallow
  527. if ( PassengerInTransition() )
  528. return vec3_origin;
  529. Vector vecPuntDir = BaseClass::PhysGunLaunchVelocity( forward, flMass );
  530. vecPuntDir.z = 150.0f;
  531. vecPuntDir *= 600.0f;
  532. return vecPuntDir;
  533. }
  534. //-----------------------------------------------------------------------------
  535. // Purpose: Rolls the vehicle when its trying to upright itself from a punt
  536. //-----------------------------------------------------------------------------
  537. AngularImpulse CPropJeepEpisodic::PhysGunLaunchAngularImpulse( void )
  538. {
  539. if ( IsOverturned() )
  540. return AngularImpulse( 0, 300, 0 );
  541. // Don't spin randomly, always spin reliably
  542. return AngularImpulse( 0, 0, 0 );
  543. }
  544. //-----------------------------------------------------------------------------
  545. // Purpose: Get the upright strength based on what state we're in
  546. //-----------------------------------------------------------------------------
  547. float CPropJeepEpisodic::GetUprightStrength( void )
  548. {
  549. // Lesser if overturned
  550. if ( IsOverturned() )
  551. return 2.0f;
  552. return 0.0f;
  553. }
  554. //-----------------------------------------------------------------------------
  555. // Purpose:
  556. //-----------------------------------------------------------------------------
  557. void CPropJeepEpisodic::CreateCargoTrigger( void )
  558. {
  559. if ( m_hCargoTrigger != NULL )
  560. return;
  561. int nAttachment = LookupAttachment( "cargo" );
  562. if ( nAttachment )
  563. {
  564. Vector vecAttachOrigin;
  565. Vector vecForward, vecRight, vecUp;
  566. GetAttachment( nAttachment, vecAttachOrigin, &vecForward, &vecRight, &vecUp );
  567. // Approx size of the hold
  568. Vector vecMins( -8.0, -6.0, 0 );
  569. Vector vecMaxs( 8.0, 6.0, 4.0 );
  570. // NDebugOverlay::BoxDirection( vecAttachOrigin, vecMins, vecMaxs, vecForward, 255, 0, 0, 64, 4.0f );
  571. // Create a trigger that lives for a small amount of time
  572. m_hCargoTrigger = CVehicleCargoTrigger::Create( vecAttachOrigin, vecMins, vecMaxs, this );
  573. }
  574. }
  575. //-----------------------------------------------------------------------------
  576. // Purpose: If the player uses the jeep while at the back, he gets ammo from the crate instead
  577. //-----------------------------------------------------------------------------
  578. void CPropJeepEpisodic::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  579. {
  580. // Fall back and get in the vehicle instead, skip giving ammo
  581. BaseClass::BaseClass::Use( pActivator, pCaller, useType, value );
  582. }
  583. #define MIN_WHEEL_DUST_SPEED 5
  584. //-----------------------------------------------------------------------------
  585. // Purpose:
  586. //-----------------------------------------------------------------------------
  587. void CPropJeepEpisodic::UpdateWheelDust( void )
  588. {
  589. // See if this wheel should emit dust
  590. const vehicleparams_t *vehicleData = m_pServerVehicle->GetVehicleParams();
  591. const vehicle_operatingparams_t *carState = m_pServerVehicle->GetVehicleOperatingParams();
  592. bool bAllowDust = vehicleData->steering.dustCloud;
  593. // Car must be active
  594. bool bCarOn = m_VehiclePhysics.IsOn();
  595. // Must be moving quickly enough or skidding along the ground
  596. bool bCreateDust = ( bCarOn &&
  597. bAllowDust &&
  598. ( m_VehiclePhysics.GetSpeed() >= MIN_WHEEL_DUST_SPEED || carState->skidSpeed > DEFAULT_SKID_THRESHOLD ) );
  599. // Update our wheel dust
  600. Vector vecPos;
  601. for ( int i = 0; i < NUM_WHEEL_EFFECTS; i++ )
  602. {
  603. m_pServerVehicle->GetWheelContactPoint( i, vecPos );
  604. // Make sure the effect is created
  605. if ( m_hWheelDust[i] == NULL )
  606. {
  607. // Create the dust effect in place
  608. m_hWheelDust[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
  609. if ( m_hWheelDust[i] == NULL )
  610. continue;
  611. // Setup our basic parameters
  612. m_hWheelDust[i]->KeyValue( "start_active", "0" );
  613. m_hWheelDust[i]->KeyValue( "effect_name", "WheelDust" );
  614. m_hWheelDust[i]->SetParent( this );
  615. m_hWheelDust[i]->SetLocalOrigin( vec3_origin );
  616. DispatchSpawn( m_hWheelDust[i] );
  617. if ( gpGlobals->curtime > 0.5f )
  618. m_hWheelDust[i]->Activate();
  619. }
  620. // Make sure the effect is created
  621. if ( m_hWheelWater[i] == NULL )
  622. {
  623. // Create the dust effect in place
  624. m_hWheelWater[i] = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
  625. if ( m_hWheelWater[i] == NULL )
  626. continue;
  627. // Setup our basic parameters
  628. m_hWheelWater[i]->KeyValue( "start_active", "0" );
  629. m_hWheelWater[i]->KeyValue( "effect_name", "WheelSplash" );
  630. m_hWheelWater[i]->SetParent( this );
  631. m_hWheelWater[i]->SetLocalOrigin( vec3_origin );
  632. DispatchSpawn( m_hWheelWater[i] );
  633. if ( gpGlobals->curtime > 0.5f )
  634. m_hWheelWater[i]->Activate();
  635. }
  636. // Turn the dust on or off
  637. if ( bCreateDust )
  638. {
  639. // Angle the dust out away from the wheels
  640. Vector vecForward, vecRight, vecUp;
  641. GetVectors( &vecForward, &vecRight, &vecUp );
  642. const vehicle_controlparams_t *vehicleControls = m_pServerVehicle->GetVehicleControlParams();
  643. float flWheelDir = ( i & 1 ) ? 1.0f : -1.0f;
  644. QAngle vecAngles;
  645. vecForward += vecRight * flWheelDir;
  646. vecForward += vecRight * (vehicleControls->steering*0.5f) * flWheelDir;
  647. vecForward += vecUp;
  648. VectorAngles( vecForward, vecAngles );
  649. // NDebugOverlay::Axis( vecPos, vecAngles, 8.0f, true, 0.1f );
  650. if ( m_WaterData.m_bWheelInWater[i] )
  651. {
  652. m_hWheelDust[i]->StopParticleSystem();
  653. // Set us up in the right position
  654. m_hWheelWater[i]->StartParticleSystem();
  655. m_hWheelWater[i]->SetAbsAngles( vecAngles );
  656. m_hWheelWater[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) );
  657. if ( m_flNextWaterSound < gpGlobals->curtime )
  658. {
  659. m_flNextWaterSound = gpGlobals->curtime + random->RandomFloat( 0.25f, 1.0f );
  660. EmitSound( "Physics.WaterSplash" );
  661. }
  662. }
  663. else
  664. {
  665. m_hWheelWater[i]->StopParticleSystem();
  666. // Set us up in the right position
  667. m_hWheelDust[i]->StartParticleSystem();
  668. m_hWheelDust[i]->SetAbsAngles( vecAngles );
  669. m_hWheelDust[i]->SetAbsOrigin( vecPos + Vector( 0, 0, 8 ) );
  670. }
  671. }
  672. else
  673. {
  674. // Stop emitting
  675. m_hWheelDust[i]->StopParticleSystem();
  676. m_hWheelWater[i]->StopParticleSystem();
  677. }
  678. }
  679. }
  680. //-----------------------------------------------------------------------------
  681. //-----------------------------------------------------------------------------
  682. ConVar jalopy_radar_test_ent( "jalopy_radar_test_ent", "none" );
  683. //-----------------------------------------------------------------------------
  684. // Purpose: Search for things that the radar detects, and stick them in the
  685. // UTILVector that gets sent to the client for radar display.
  686. //-----------------------------------------------------------------------------
  687. void CPropJeepEpisodic::UpdateRadar( bool forceUpdate )
  688. {
  689. bool bDetectedDog = false;
  690. if( !m_bRadarEnabled )
  691. return;
  692. if( !forceUpdate && gpGlobals->curtime < m_flNextRadarUpdateTime )
  693. return;
  694. // Count the targets on radar. If any more targets come on the radar, we beep.
  695. int m_iNumOldRadarContacts = m_iNumRadarContacts;
  696. m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY;
  697. m_iNumRadarContacts = 0;
  698. CBaseEntity *pEnt = gEntList.FirstEnt();
  699. string_t iszRadarTarget = FindPooledString( "info_radar_target" );
  700. string_t iszStriderName = FindPooledString( "npc_strider" );
  701. string_t iszHunterName = FindPooledString( "npc_hunter" );
  702. string_t iszTestName = FindPooledString( jalopy_radar_test_ent.GetString() );
  703. Vector vecJalopyOrigin = WorldSpaceCenter();
  704. while( pEnt != NULL )
  705. {
  706. int type = RADAR_CONTACT_NONE;
  707. if( pEnt->m_iClassname == iszRadarTarget )
  708. {
  709. CRadarTarget *pTarget = dynamic_cast<CRadarTarget*>(pEnt);
  710. if( pTarget != NULL && !pTarget->IsDisabled() )
  711. {
  712. if( pTarget->m_flRadius < 0 || vecJalopyOrigin.DistToSqr(pTarget->GetAbsOrigin()) <= Square(pTarget->m_flRadius) )
  713. {
  714. // This item has been detected.
  715. type = pTarget->GetType();
  716. if( type == RADAR_CONTACT_DOG )
  717. bDetectedDog = true;// used to prevent Alyx talking about the radar (see below)
  718. if( pTarget->GetMode() == RADAR_MODE_STICKY )
  719. {
  720. // This beacon was just detected. Now change the radius to infinite
  721. // so that it will never go off the radar due to distance.
  722. pTarget->m_flRadius = -1;
  723. }
  724. }
  725. }
  726. }
  727. else if ( m_bRadarDetectsEnemies )
  728. {
  729. if ( pEnt->m_iClassname == iszStriderName )
  730. {
  731. CNPC_Strider *pStrider = dynamic_cast<CNPC_Strider*>(pEnt);
  732. if( !pStrider || !pStrider->CarriedByDropship() )
  733. {
  734. // Ignore striders which are carried by dropships.
  735. type = RADAR_CONTACT_LARGE_ENEMY;
  736. }
  737. }
  738. if ( pEnt->m_iClassname == iszHunterName )
  739. {
  740. type = RADAR_CONTACT_ENEMY;
  741. }
  742. }
  743. if( type != RADAR_CONTACT_NONE )
  744. {
  745. Vector vecPos = pEnt->WorldSpaceCenter();
  746. m_vecRadarContactPos.Set( m_iNumRadarContacts, vecPos );
  747. m_iRadarContactType.Set( m_iNumRadarContacts, type );
  748. m_iNumRadarContacts++;
  749. if( m_iNumRadarContacts == RADAR_MAX_CONTACTS )
  750. break;
  751. }
  752. pEnt = gEntList.NextEnt(pEnt);
  753. }
  754. if( m_iNumRadarContacts > m_iNumOldRadarContacts )
  755. {
  756. // Play a bleepy sound
  757. if( !bDetectedDog )
  758. {
  759. EmitSound( "JNK_Radar_Ping_Friendly" );
  760. }
  761. //Notify Alyx so she can talk about the radar contact
  762. CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
  763. if( !bDetectedDog && pAlyx != NULL && pAlyx->GetVehicle() )
  764. {
  765. pAlyx->SpeakIfAllowed( TLK_PASSENGER_NEW_RADAR_CONTACT );
  766. }
  767. }
  768. if( bDetectedDog )
  769. {
  770. // Update the radar much more frequently when dog is around.
  771. m_flNextRadarUpdateTime = gpGlobals->curtime + RADAR_UPDATE_FREQUENCY_FAST;
  772. }
  773. //Msg("Server detected %d objects\n", m_iNumRadarContacts );
  774. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  775. CSingleUserRecipientFilter filter(pPlayer);
  776. UserMessageBegin( filter, "UpdateJalopyRadar" );
  777. WRITE_BYTE( 0 ); // end marker
  778. MessageEnd(); // send message
  779. }
  780. ConVar jalopy_cargo_anim_time( "jalopy_cargo_anim_time", "1.0" );
  781. //-----------------------------------------------------------------------------
  782. // Purpose:
  783. //-----------------------------------------------------------------------------
  784. void CPropJeepEpisodic::UpdateCargoEntry( void )
  785. {
  786. // Don't bother if we have no prop to move
  787. if ( m_hCargoProp == NULL )
  788. return;
  789. // If we're past our animation point, then we're already done
  790. if ( m_flCargoStartTime + jalopy_cargo_anim_time.GetFloat() < gpGlobals->curtime )
  791. {
  792. // Close the hold immediately if we're finished
  793. if ( m_bAddingCargo )
  794. {
  795. m_flAmmoCrateCloseTime = gpGlobals->curtime;
  796. m_bAddingCargo = false;
  797. }
  798. return;
  799. }
  800. // Get our target point
  801. int nAttachment = LookupAttachment( "cargo" );
  802. Vector vecTarget, vecOut;
  803. QAngle vecAngles;
  804. GetAttachmentLocal( nAttachment, vecTarget, vecAngles );
  805. // Find where we are in the blend and bias it for a fast entry and slow ease-out
  806. float flPerc = (jalopy_cargo_anim_time.GetFloat()) ? (( gpGlobals->curtime - m_flCargoStartTime ) / jalopy_cargo_anim_time.GetFloat()) : 1.0f;
  807. flPerc = Bias( flPerc, 0.75f );
  808. VectorLerp( m_hCargoProp->GetLocalOrigin(), vecTarget, flPerc, vecOut );
  809. // Get our target orientation
  810. CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(m_hCargoProp.Get());
  811. if ( pProp == NULL )
  812. return;
  813. // Slerp our quaternions to find where we are this frame
  814. Quaternion qtTarget;
  815. QAngle qa( 0, 90, 0 );
  816. qa += pProp->PreferredCarryAngles();
  817. AngleQuaternion( qa, qtTarget ); // FIXME: Find the real offset to make this sit properly
  818. Quaternion qtCurrent;
  819. AngleQuaternion( pProp->GetLocalAngles(), qtCurrent );
  820. Quaternion qtOut;
  821. QuaternionSlerp( qtCurrent, qtTarget, flPerc, qtOut );
  822. // Put it back to angles
  823. QuaternionAngles( qtOut, vecAngles );
  824. // Finally, take these new position
  825. m_hCargoProp->SetLocalOrigin( vecOut );
  826. m_hCargoProp->SetLocalAngles( vecAngles );
  827. // Push the closing out into the future to make sure we don't try and close at the same time
  828. m_flAmmoCrateCloseTime += gpGlobals->frametime;
  829. }
  830. #define VEHICLE_AVOID_BROADCAST_RATE 0.5f
  831. //-----------------------------------------------------------------------------
  832. // Purpose: This function isn't really what we want
  833. //-----------------------------------------------------------------------------
  834. void CPropJeepEpisodic::CreateAvoidanceZone( void )
  835. {
  836. if ( m_flNextAvoidBroadcastTime > gpGlobals->curtime )
  837. return;
  838. // Only do this when we're stopped
  839. if ( m_VehiclePhysics.GetSpeed() > 5.0f )
  840. return;
  841. float flHullRadius = CollisionProp()->BoundingRadius2D();
  842. Vector vecPos;
  843. CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.33f, 0.25f ), &vecPos );
  844. CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this );
  845. // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE );
  846. CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.66f, 0.25f ), &vecPos );
  847. CSoundEnt::InsertSound( SOUND_MOVE_AWAY, vecPos, (flHullRadius*0.4f), VEHICLE_AVOID_BROADCAST_RATE, this );
  848. // NDebugOverlay::Sphere( vecPos, vec3_angle, flHullRadius*0.4f, 255, 0, 0, 0, true, VEHICLE_AVOID_BROADCAST_RATE );
  849. // Don't broadcast again until these are done
  850. m_flNextAvoidBroadcastTime = gpGlobals->curtime + VEHICLE_AVOID_BROADCAST_RATE;
  851. }
  852. //-----------------------------------------------------------------------------
  853. // Purpose:
  854. //-----------------------------------------------------------------------------
  855. void CPropJeepEpisodic::Think( void )
  856. {
  857. BaseClass::Think();
  858. // If our passenger is transitioning, then don't let the player drive off
  859. CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
  860. if ( pAlyx && pAlyx->GetPassengerState() == PASSENGER_STATE_EXITING )
  861. {
  862. m_throttleDisableTime = gpGlobals->curtime + 0.25f;
  863. }
  864. // Update our cargo entering our hold
  865. UpdateCargoEntry();
  866. // See if the wheel dust should be on or off
  867. UpdateWheelDust();
  868. // Update the radar, of course.
  869. UpdateRadar();
  870. if ( m_hCargoTrigger && !m_hCargoProp && !m_hCargoTrigger->m_pfnTouch )
  871. {
  872. m_hCargoTrigger->Enable();
  873. }
  874. CreateAvoidanceZone();
  875. }
  876. //-----------------------------------------------------------------------------
  877. // Purpose:
  878. // Input : *pEntity -
  879. //-----------------------------------------------------------------------------
  880. void CPropJeepEpisodic::AddPropToCargoHold( CPhysicsProp *pProp )
  881. {
  882. // The hold must be empty to add something to it
  883. if ( m_hCargoProp != NULL )
  884. {
  885. Assert( 0 );
  886. return;
  887. }
  888. // Take the prop as our cargo
  889. m_hCargoProp = pProp;
  890. m_flCargoStartTime = gpGlobals->curtime;
  891. m_bAddingCargo = true;
  892. }
  893. //-----------------------------------------------------------------------------
  894. // Purpose: Drops the cargo from the hold
  895. //-----------------------------------------------------------------------------
  896. void CPropJeepEpisodic::ReleasePropFromCargoHold( void )
  897. {
  898. // Pull the object free!
  899. m_hCargoProp->SetParent( NULL );
  900. m_hCargoProp->CreateVPhysics();
  901. if ( m_hCargoTrigger )
  902. {
  903. m_hCargoTrigger->Enable();
  904. m_hCargoTrigger->IgnoreTouches( m_hCargoProp );
  905. }
  906. }
  907. //-----------------------------------------------------------------------------
  908. // Purpose: If the player is trying to pull the cargo out of the hold using the physcannon, let him
  909. // Output : Returns the cargo to pick up, if all the conditions are met
  910. //-----------------------------------------------------------------------------
  911. CBaseEntity *CPropJeepEpisodic::OnFailedPhysGunPickup( Vector vPhysgunPos )
  912. {
  913. // Make sure we're available to open
  914. if ( m_hCargoProp != NULL )
  915. {
  916. // Player's forward direction
  917. Vector vecPlayerForward;
  918. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  919. if ( pPlayer == NULL )
  920. return NULL;
  921. pPlayer->EyeVectors( &vecPlayerForward );
  922. // Origin and facing of the cargo hold
  923. Vector vecCargoOrigin;
  924. Vector vecCargoForward;
  925. GetAttachment( "cargo", vecCargoOrigin, &vecCargoForward );
  926. // Direction from the cargo to the player's position
  927. Vector vecPickupDir = ( vecCargoOrigin - vPhysgunPos );
  928. float flDist = VectorNormalize( vecPickupDir );
  929. // 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
  930. bool bInCargoRange = ( (flDist < (15.0f * 12.0f)) && DotProduct( vecCargoForward, vecPickupDir ) < 0.1f );
  931. bool bFacingCargo = DotProduct( vecPlayerForward, vecPickupDir ) > 0.975f;
  932. // If we're roughly pulling at the item, pick that up
  933. if ( bInCargoRange && bFacingCargo )
  934. {
  935. // Save this for later
  936. CBaseEntity *pCargo = m_hCargoProp;
  937. // Drop the cargo
  938. ReleasePropFromCargoHold();
  939. // Forget the item but pass it back as the object to pick up
  940. m_hCargoProp = NULL;
  941. return pCargo;
  942. }
  943. }
  944. return BaseClass::OnFailedPhysGunPickup( vPhysgunPos );
  945. }
  946. // adds a collision solver for any small props that are stuck under the vehicle
  947. static void SolveBlockingProps( CPropJeepEpisodic *pVehicleEntity, IPhysicsObject *pVehiclePhysics )
  948. {
  949. CUtlVector<CBaseEntity *> solveList;
  950. float vehicleMass = pVehiclePhysics->GetMass();
  951. Vector vehicleUp;
  952. pVehicleEntity->GetVectors( NULL, NULL, &vehicleUp );
  953. IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
  954. while ( pSnapshot->IsValid() )
  955. {
  956. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  957. float otherMass = pOther->GetMass();
  958. CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
  959. Assert(pOtherEntity);
  960. if ( pOtherEntity && pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() && (otherMass*4.0f) < vehicleMass )
  961. {
  962. Vector normal;
  963. pSnapshot->GetSurfaceNormal(normal);
  964. // this points down in the car's reference frame, then it's probably trapped under the car
  965. if ( DotProduct(normal, vehicleUp) < -0.9f )
  966. {
  967. Vector point, pointLocal;
  968. pSnapshot->GetContactPoint(point);
  969. VectorITransform( point, pVehicleEntity->EntityToWorldTransform(), pointLocal );
  970. Vector bottomPoint = physcollision->CollideGetExtent( pVehiclePhysics->GetCollide(), vec3_origin, vec3_angle, Vector(0,0,-1) );
  971. // make sure it's under the bottom of the car
  972. float bottomPlane = DotProduct(bottomPoint,vehicleUp)+8; // 8 inches above bottom
  973. if ( DotProduct( pointLocal, vehicleUp ) <= bottomPlane )
  974. {
  975. //Msg("Solved %s\n", pOtherEntity->GetClassname());
  976. if ( solveList.Find(pOtherEntity) < 0 )
  977. {
  978. solveList.AddToTail(pOtherEntity);
  979. }
  980. }
  981. }
  982. }
  983. pSnapshot->NextFrictionData();
  984. }
  985. pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot );
  986. if ( solveList.Count() )
  987. {
  988. for ( int i = 0; i < solveList.Count(); i++ )
  989. {
  990. EntityPhysics_CreateSolver( pVehicleEntity, solveList[i], true, 4.0f );
  991. }
  992. pVehiclePhysics->RecheckContactPoints();
  993. }
  994. }
  995. static void SimpleCollisionResponse( Vector velocityIn, const Vector &normal, float coefficientOfRestitution, Vector *pVelocityOut )
  996. {
  997. Vector Vn = DotProduct(velocityIn,normal) * normal;
  998. Vector Vt = velocityIn - Vn;
  999. *pVelocityOut = Vt - coefficientOfRestitution * Vn;
  1000. }
  1001. static void KillBlockingEnemyNPCs( CBasePlayer *pPlayer, CBaseEntity *pVehicleEntity, IPhysicsObject *pVehiclePhysics )
  1002. {
  1003. Vector velocity;
  1004. pVehiclePhysics->GetVelocity( &velocity, NULL );
  1005. float vehicleMass = pVehiclePhysics->GetMass();
  1006. // loop through the contacts and look for enemy NPCs that we're pushing on
  1007. CUtlVector<CAI_BaseNPC *> npcList;
  1008. CUtlVector<Vector> forceList;
  1009. CUtlVector<Vector> contactList;
  1010. IPhysicsFrictionSnapshot *pSnapshot = pVehiclePhysics->CreateFrictionSnapshot();
  1011. while ( pSnapshot->IsValid() )
  1012. {
  1013. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  1014. float otherMass = pOther->GetMass();
  1015. CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
  1016. CAI_BaseNPC *pNPC = pOtherEntity ? pOtherEntity->MyNPCPointer() : NULL;
  1017. // Is this an enemy NPC with a small enough mass?
  1018. if ( pNPC && pPlayer->IRelationType(pNPC) != D_LI && ((otherMass*2.0f) < vehicleMass) )
  1019. {
  1020. // accumulate the stress force for this NPC in the lsit
  1021. float force = pSnapshot->GetNormalForce();
  1022. Vector normal;
  1023. pSnapshot->GetSurfaceNormal(normal);
  1024. normal *= force;
  1025. int index = npcList.Find(pNPC);
  1026. if ( index < 0 )
  1027. {
  1028. vphysicsupdateai_t *pUpdate = NULL;
  1029. if ( pNPC->VPhysicsGetObject() && pNPC->VPhysicsGetObject()->GetShadowController() && pNPC->GetMoveType() == MOVETYPE_STEP )
  1030. {
  1031. if ( pNPC->HasDataObjectType(VPHYSICSUPDATEAI) )
  1032. {
  1033. pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->GetDataObject(VPHYSICSUPDATEAI));
  1034. // kill this guy if I've been pushing him for more than half a second and I'm
  1035. // still pushing in his direction
  1036. if ( (gpGlobals->curtime - pUpdate->startUpdateTime) > 0.5f && DotProduct(velocity,normal) > 0)
  1037. {
  1038. index = npcList.AddToTail(pNPC);
  1039. forceList.AddToTail( normal );
  1040. Vector pos;
  1041. pSnapshot->GetContactPoint(pos);
  1042. contactList.AddToTail(pos);
  1043. }
  1044. }
  1045. else
  1046. {
  1047. pUpdate = static_cast<vphysicsupdateai_t *>(pNPC->CreateDataObject( VPHYSICSUPDATEAI ));
  1048. pUpdate->startUpdateTime = gpGlobals->curtime;
  1049. }
  1050. // update based on vphysics for the next second
  1051. // this allows the car to push the NPC
  1052. pUpdate->stopUpdateTime = gpGlobals->curtime + 1.0f;
  1053. float maxAngular;
  1054. pNPC->VPhysicsGetObject()->GetShadowController()->GetMaxSpeed( &pUpdate->savedShadowControllerMaxSpeed, &maxAngular );
  1055. pNPC->VPhysicsGetObject()->GetShadowController()->MaxSpeed( 1.0f, maxAngular );
  1056. }
  1057. }
  1058. else
  1059. {
  1060. forceList[index] += normal;
  1061. }
  1062. }
  1063. pSnapshot->NextFrictionData();
  1064. }
  1065. pVehiclePhysics->DestroyFrictionSnapshot( pSnapshot );
  1066. // now iterate the list and check each cumulative force against the threshold
  1067. if ( npcList.Count() )
  1068. {
  1069. for ( int i = npcList.Count(); --i >= 0; )
  1070. {
  1071. Vector damageForce;
  1072. npcList[i]->VPhysicsGetObject()->GetVelocity( &damageForce, NULL );
  1073. Vector vel;
  1074. pVehiclePhysics->GetVelocityAtPoint( contactList[i], &vel );
  1075. damageForce -= vel;
  1076. Vector normal = forceList[i];
  1077. VectorNormalize(normal);
  1078. SimpleCollisionResponse( damageForce, normal, 1.0, &damageForce );
  1079. damageForce += (normal * 300.0f);
  1080. damageForce *= npcList[i]->VPhysicsGetObject()->GetMass();
  1081. float len = damageForce.Length();
  1082. damageForce.z += len*phys_upimpactforcescale.GetFloat();
  1083. Vector vehicleForce = -damageForce;
  1084. CTakeDamageInfo dmgInfo( pVehicleEntity, pVehicleEntity, damageForce, contactList[i], 200.0f, DMG_CRUSH|DMG_VEHICLE );
  1085. npcList[i]->TakeDamage( dmgInfo );
  1086. pVehiclePhysics->ApplyForceOffset( vehicleForce, contactList[i] );
  1087. PhysCollisionSound( pVehicleEntity, npcList[i]->VPhysicsGetObject(), CHAN_BODY, pVehiclePhysics->GetMaterialIndex(), npcList[i]->VPhysicsGetObject()->GetMaterialIndex(), gpGlobals->frametime, 200.0f );
  1088. }
  1089. }
  1090. }
  1091. void CPropJeepEpisodic::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased )
  1092. {
  1093. /* The car headlight hurts perf, there's no timer to turn it off automatically,
  1094. and we haven't built any gameplay around it.
  1095. Furthermore, I don't think I've ever seen a playtester turn it on.
  1096. if ( ucmd->impulse == 100 )
  1097. {
  1098. if (HeadlightIsOn())
  1099. {
  1100. HeadlightTurnOff();
  1101. }
  1102. else
  1103. {
  1104. HeadlightTurnOn();
  1105. }
  1106. }*/
  1107. if ( ucmd->forwardmove != 0.0f )
  1108. {
  1109. //Msg("Push V: %.2f, %.2f, %.2f\n", ucmd->forwardmove, carState->engineRPM, carState->speed );
  1110. CBasePlayer *pPlayer = ToBasePlayer(GetDriver());
  1111. if ( pPlayer && VPhysicsGetObject() )
  1112. {
  1113. KillBlockingEnemyNPCs( pPlayer, this, VPhysicsGetObject() );
  1114. SolveBlockingProps( this, VPhysicsGetObject() );
  1115. }
  1116. }
  1117. BaseClass::DriveVehicle(flFrameTime, ucmd, iButtonsDown, iButtonsReleased);
  1118. }
  1119. //-----------------------------------------------------------------------------
  1120. // Purpose:
  1121. //-----------------------------------------------------------------------------
  1122. void CPropJeepEpisodic::CreateHazardLights( void )
  1123. {
  1124. static const char *s_szAttach[NUM_HAZARD_LIGHTS] =
  1125. {
  1126. "rearlight_r",
  1127. "rearlight_l",
  1128. "headlight_r",
  1129. "headlight_l",
  1130. };
  1131. // Turn on the hazards!
  1132. for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
  1133. {
  1134. if ( m_hHazardLights[i] == NULL )
  1135. {
  1136. m_hHazardLights[i] = CSprite::SpriteCreate( s_szHazardSprite, GetLocalOrigin(), false );
  1137. if ( m_hHazardLights[i] )
  1138. {
  1139. m_hHazardLights[i]->SetTransparency( kRenderWorldGlow, 255, 220, 40, 255, kRenderFxNoDissipation );
  1140. m_hHazardLights[i]->SetAttachment( this, LookupAttachment( s_szAttach[i] ) );
  1141. m_hHazardLights[i]->SetGlowProxySize( 2.0f );
  1142. m_hHazardLights[i]->TurnOff();
  1143. if ( i < 2 )
  1144. {
  1145. // Rear lights are red
  1146. m_hHazardLights[i]->SetColor( 255, 0, 0 );
  1147. m_hHazardLights[i]->SetScale( 1.0f );
  1148. }
  1149. else
  1150. {
  1151. // Font lights are white
  1152. m_hHazardLights[i]->SetScale( 1.0f );
  1153. }
  1154. }
  1155. }
  1156. }
  1157. // We start off
  1158. m_bBlink = false;
  1159. // Setup our blink
  1160. SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.1f, "HazardBlink" );
  1161. }
  1162. //-----------------------------------------------------------------------------
  1163. // Purpose:
  1164. //-----------------------------------------------------------------------------
  1165. void CPropJeepEpisodic::DestroyHazardLights( void )
  1166. {
  1167. for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
  1168. {
  1169. if ( m_hHazardLights[i] != NULL )
  1170. {
  1171. UTIL_Remove( m_hHazardLights[i] );
  1172. }
  1173. }
  1174. SetContextThink( NULL, gpGlobals->curtime, "HazardBlink" );
  1175. }
  1176. //-----------------------------------------------------------------------------
  1177. // Purpose:
  1178. // Input : nRole -
  1179. //-----------------------------------------------------------------------------
  1180. void CPropJeepEpisodic::ExitVehicle( int nRole )
  1181. {
  1182. BaseClass::ExitVehicle( nRole );
  1183. CreateHazardLights();
  1184. }
  1185. void CPropJeepEpisodic::SetBusterHopperVisibility(bool visible)
  1186. {
  1187. // if we're there already do nothing
  1188. if (visible == m_bBusterHopperVisible)
  1189. return;
  1190. SetBodygroup( JEEP_HOPPER_BODYGROUP, visible ? 1 : 0);
  1191. m_bBusterHopperVisible = visible;
  1192. }
  1193. void CPropJeepEpisodic::InputSetCargoVisibility( inputdata_t &data )
  1194. {
  1195. bool visible = data.value.Bool();
  1196. SetBusterHopperVisibility( visible );
  1197. }
  1198. //-----------------------------------------------------------------------------
  1199. // THIS CODE LIFTED RIGHT OUT OF TF2, to defer the pain of making vgui-on-an-entity
  1200. // code available to all CBaseAnimating.
  1201. //-----------------------------------------------------------------------------
  1202. void CPropJeepEpisodic::SpawnRadarPanel()
  1203. {
  1204. // FIXME: Deal with dynamically resizing control panels?
  1205. // If we're attached to an entity, spawn control panels on it instead of use
  1206. CBaseAnimating *pEntityToSpawnOn = this;
  1207. char *pOrgLL = "controlpanel0_ll";
  1208. char *pOrgUR = "controlpanel0_ur";
  1209. Assert( pEntityToSpawnOn );
  1210. // Lookup the attachment point...
  1211. int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgLL);
  1212. if (nLLAttachmentIndex <= 0)
  1213. {
  1214. return;
  1215. }
  1216. int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(pOrgUR);
  1217. if (nURAttachmentIndex <= 0)
  1218. {
  1219. return;
  1220. }
  1221. const char *pScreenName = "jalopy_radar_panel";
  1222. const char *pScreenClassname = "vgui_screen";
  1223. // Compute the screen size from the attachment points...
  1224. matrix3x4_t panelToWorld;
  1225. pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
  1226. matrix3x4_t worldToPanel;
  1227. MatrixInvert( panelToWorld, worldToPanel );
  1228. // Now get the lower right position + transform into panel space
  1229. Vector lr, lrlocal;
  1230. pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
  1231. MatrixGetColumn( panelToWorld, 3, lr );
  1232. VectorTransform( lr, worldToPanel, lrlocal );
  1233. float flWidth = lrlocal.x;
  1234. float flHeight = lrlocal.y;
  1235. CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
  1236. pScreen->SetActualSize( flWidth, flHeight );
  1237. pScreen->SetActive( true );
  1238. pScreen->SetOverlayMaterial( RADAR_PANEL_WRITEZ );
  1239. pScreen->SetTransparency( true );
  1240. m_hRadarScreen.Set( pScreen );
  1241. m_bRadarEnabled = true;
  1242. m_iNumRadarContacts = 0;
  1243. m_flNextRadarUpdateTime = gpGlobals->curtime - 1.0f;
  1244. }
  1245. //-----------------------------------------------------------------------------
  1246. void CPropJeepEpisodic::DestroyRadarPanel()
  1247. {
  1248. Assert( m_hRadarScreen != NULL );
  1249. m_hRadarScreen->SUB_Remove();
  1250. m_bRadarEnabled = false;
  1251. }
  1252. //-----------------------------------------------------------------------------
  1253. // Purpose:
  1254. //-----------------------------------------------------------------------------
  1255. void CPropJeepEpisodic::HazardBlinkThink( void )
  1256. {
  1257. if ( m_bBlink )
  1258. {
  1259. for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
  1260. {
  1261. if ( m_hHazardLights[i] )
  1262. {
  1263. m_hHazardLights[i]->SetBrightness( 0, 0.1f );
  1264. }
  1265. }
  1266. SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.25f, "HazardBlink" );
  1267. }
  1268. else
  1269. {
  1270. for ( int i = 0; i < NUM_HAZARD_LIGHTS; i++ )
  1271. {
  1272. if ( m_hHazardLights[i] )
  1273. {
  1274. m_hHazardLights[i]->SetBrightness( 255, 0.1f );
  1275. m_hHazardLights[i]->TurnOn();
  1276. }
  1277. }
  1278. SetContextThink( &CPropJeepEpisodic::HazardBlinkThink, gpGlobals->curtime + 0.5f, "HazardBlink" );
  1279. }
  1280. m_bBlink = !m_bBlink;
  1281. }
  1282. //-----------------------------------------------------------------------------
  1283. // Purpose:
  1284. //-----------------------------------------------------------------------------
  1285. void CPropJeepEpisodic::HandleWater( void )
  1286. {
  1287. // Only check the wheels and engine in water if we have a driver (player).
  1288. if ( !GetDriver() )
  1289. return;
  1290. // Update our internal state
  1291. CheckWater();
  1292. // Save of data from last think.
  1293. for ( int iWheel = 0; iWheel < JEEP_WHEEL_COUNT; ++iWheel )
  1294. {
  1295. m_WaterData.m_bWheelWasInWater[iWheel] = m_WaterData.m_bWheelInWater[iWheel];
  1296. }
  1297. }
  1298. //-----------------------------------------------------------------------------
  1299. // Purpose: Report our lock state
  1300. //-----------------------------------------------------------------------------
  1301. int CPropJeepEpisodic::DrawDebugTextOverlays( void )
  1302. {
  1303. int text_offset = BaseClass::DrawDebugTextOverlays();
  1304. if ( m_debugOverlays & OVERLAY_TEXT_BIT )
  1305. {
  1306. EntityText( text_offset, CFmtStr("Entrance: %s", m_bEntranceLocked ? "Locked" : "Unlocked" ), 0 );
  1307. text_offset++;
  1308. EntityText( text_offset, CFmtStr("Exit: %s", m_bExitLocked ? "Locked" : "Unlocked" ), 0 );
  1309. text_offset++;
  1310. }
  1311. return text_offset;
  1312. }
  1313. #define TRANSITION_SEARCH_RADIUS (100*12)
  1314. //-----------------------------------------------------------------------------
  1315. // Purpose: Teleport the car to a destination that will cause it to transition if it's not going to otherwise
  1316. //-----------------------------------------------------------------------------
  1317. void CPropJeepEpisodic::InputOutsideTransition( inputdata_t &inputdata )
  1318. {
  1319. // Teleport into the new map
  1320. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1321. Vector vecTeleportPos;
  1322. QAngle vecTeleportAngles;
  1323. // Get our bounds
  1324. Vector vecSurroundMins, vecSurroundMaxs;
  1325. CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
  1326. vecSurroundMins -= WorldSpaceCenter();
  1327. vecSurroundMaxs -= WorldSpaceCenter();
  1328. Vector vecBestPos;
  1329. QAngle vecBestAngles;
  1330. CInfoTargetVehicleTransition *pEntity = NULL;
  1331. bool bSucceeded = false;
  1332. // Find all entities of the correct name and try and sit where they're at
  1333. while ( ( pEntity = (CInfoTargetVehicleTransition *) gEntList.FindEntityByClassname( pEntity, "info_target_vehicle_transition" ) ) != NULL )
  1334. {
  1335. // Must be enabled
  1336. if ( pEntity->IsDisabled() )
  1337. continue;
  1338. // Must be within range
  1339. if ( ( pEntity->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() > Square( TRANSITION_SEARCH_RADIUS ) )
  1340. continue;
  1341. vecTeleportPos = pEntity->GetAbsOrigin();
  1342. vecTeleportAngles = pEntity->GetAbsAngles() + QAngle( 0, -90, 0 ); // Vehicle is always off by 90 degrees
  1343. // Rotate to face the destination angles
  1344. Vector vecMins;
  1345. Vector vecMaxs;
  1346. VectorRotate( vecSurroundMins, vecTeleportAngles, vecMins );
  1347. VectorRotate( vecSurroundMaxs, vecTeleportAngles, vecMaxs );
  1348. if ( vecMaxs.x < vecMins.x )
  1349. V_swap( vecMins.x, vecMaxs.x );
  1350. if ( vecMaxs.y < vecMins.y )
  1351. V_swap( vecMins.y, vecMaxs.y );
  1352. if ( vecMaxs.z < vecMins.z )
  1353. V_swap( vecMins.z, vecMaxs.z );
  1354. // Move up
  1355. vecTeleportPos.z += ( vecMaxs.z - vecMins.z );
  1356. trace_t tr;
  1357. UTIL_TraceHull( vecTeleportPos, vecTeleportPos - Vector( 0, 0, 128 ), vecMins, vecMaxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  1358. if ( tr.startsolid == false && tr.allsolid == false && tr.fraction < 1.0f )
  1359. {
  1360. // Store this off
  1361. vecBestPos = tr.endpos;
  1362. vecBestAngles = vecTeleportAngles;
  1363. bSucceeded = true;
  1364. // If this point isn't visible, then stop looking and use it
  1365. if ( pPlayer->FInViewCone( tr.endpos ) == false )
  1366. break;
  1367. }
  1368. }
  1369. // See if we're finished
  1370. if ( bSucceeded )
  1371. {
  1372. Teleport( &vecTeleportPos, &vecTeleportAngles, NULL );
  1373. return;
  1374. }
  1375. // TODO: We found no valid teleport points, so try to find them dynamically
  1376. Warning("No valid vehicle teleport points!\n");
  1377. }
  1378. //-----------------------------------------------------------------------------
  1379. // Purpose: Stop players punting the car around.
  1380. //-----------------------------------------------------------------------------
  1381. void CPropJeepEpisodic::InputDisablePhysGun( inputdata_t &data )
  1382. {
  1383. AddEFlags( EFL_NO_PHYSCANNON_INTERACTION );
  1384. }
  1385. //-----------------------------------------------------------------------------
  1386. // Purpose: Return to normal
  1387. //-----------------------------------------------------------------------------
  1388. void CPropJeepEpisodic::InputEnablePhysGun( inputdata_t &data )
  1389. {
  1390. RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION );
  1391. }
  1392. //-----------------------------------------------------------------------------
  1393. // Create and parent two radial node link controllers.
  1394. //-----------------------------------------------------------------------------
  1395. void CPropJeepEpisodic::InputCreateLinkController( inputdata_t &data )
  1396. {
  1397. Vector vecFront, vecRear;
  1398. Vector vecWFL, vecWFR; // Front wheels
  1399. Vector vecWRL, vecWRR; // Back wheels
  1400. GetAttachment( "wheel_fr", vecWFR );
  1401. GetAttachment( "wheel_fl", vecWFL );
  1402. GetAttachment( "wheel_rr", vecWRR );
  1403. GetAttachment( "wheel_rl", vecWRL );
  1404. vecFront = (vecWFL + vecWFR) * 0.5f;
  1405. vecRear = (vecWRL + vecWRR) * 0.5f;
  1406. float flRadius = ( (vecFront - vecRear).Length() ) * 0.6f;
  1407. CAI_RadialLinkController *pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" );
  1408. if( pLinkController != NULL && m_hLinkControllerFront.Get() == NULL )
  1409. {
  1410. pLinkController->m_flRadius = flRadius;
  1411. pLinkController->Spawn();
  1412. pLinkController->SetAbsOrigin( vecFront );
  1413. pLinkController->SetOwnerEntity( this );
  1414. pLinkController->SetParent( this );
  1415. pLinkController->Activate();
  1416. m_hLinkControllerFront.Set( pLinkController );
  1417. //NDebugOverlay::Circle( vecFront, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 );
  1418. }
  1419. pLinkController = (CAI_RadialLinkController *)CreateEntityByName( "info_radial_link_controller" );
  1420. if( pLinkController != NULL && m_hLinkControllerRear.Get() == NULL )
  1421. {
  1422. pLinkController->m_flRadius = flRadius;
  1423. pLinkController->Spawn();
  1424. pLinkController->SetAbsOrigin( vecRear );
  1425. pLinkController->SetOwnerEntity( this );
  1426. pLinkController->SetParent( this );
  1427. pLinkController->Activate();
  1428. m_hLinkControllerRear.Set( pLinkController );
  1429. //NDebugOverlay::Circle( vecRear, Vector(0,1,0), Vector(1,0,0), flRadius, 255, 255, 255, 128, false, 100 );
  1430. }
  1431. }
  1432. void CPropJeepEpisodic::InputDestroyLinkController( inputdata_t &data )
  1433. {
  1434. if( m_hLinkControllerFront.Get() != NULL )
  1435. {
  1436. CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerFront.Get());
  1437. if( pLinkController != NULL )
  1438. {
  1439. pLinkController->ModifyNodeLinks(false);
  1440. UTIL_Remove( pLinkController );
  1441. m_hLinkControllerFront.Set(NULL);
  1442. }
  1443. }
  1444. if( m_hLinkControllerRear.Get() != NULL )
  1445. {
  1446. CAI_RadialLinkController *pLinkController = dynamic_cast<CAI_RadialLinkController*>(m_hLinkControllerRear.Get());
  1447. if( pLinkController != NULL )
  1448. {
  1449. pLinkController->ModifyNodeLinks(false);
  1450. UTIL_Remove( pLinkController );
  1451. m_hLinkControllerRear.Set(NULL);
  1452. }
  1453. }
  1454. }
  1455. bool CPropJeepEpisodic::AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole )
  1456. {
  1457. // Wait until we've settled down before we resort to blocked exits.
  1458. // This keeps us from doing blocked exits in mid-jump, which can cause mayhem like
  1459. // sticking the player through player clips or into geometry.
  1460. return GetSmoothedVelocity().IsLengthLessThan( jalopy_blocked_exit_max_speed.GetFloat() );
  1461. }