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.

1114 lines
33 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_hint.h"
  8. #include "env_headcrabcanister_shared.h"
  9. #include "explode.h"
  10. #include "beam_shared.h"
  11. #include "SpriteTrail.h"
  12. #include "ar2_explosion.h"
  13. #include "SkyCamera.h"
  14. #include "smoke_trail.h"
  15. #include "ai_basenpc.h"
  16. #include "npc_headcrab.h"
  17. #include "ai_motor.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. //-----------------------------------------------------------------------------
  21. // Models!
  22. //-----------------------------------------------------------------------------
  23. #define ENV_HEADCRABCANISTER_MODEL "models/props_combine/headcrabcannister01a.mdl"
  24. #define ENV_HEADCRABCANISTER_BROKEN_MODEL "models/props_combine/headcrabcannister01b.mdl"
  25. #define ENV_HEADCRABCANISTER_SKYBOX_MODEL "models/props_combine/headcrabcannister01a_skybox.mdl"
  26. #define ENV_HEADCRABCANISTER_INCOMING_SOUND_TIME 1.0f
  27. ConVar sk_env_headcrabcanister_shake_amplitude( "sk_env_headcrabcanister_shake_amplitude", "50" );
  28. ConVar sk_env_headcrabcanister_shake_radius( "sk_env_headcrabcanister_shake_radius", "1024" );
  29. ConVar sk_env_headcrabcanister_shake_radius_vehicle( "sk_env_headcrabcanister_shake_radius_vehicle", "2500" );
  30. #define ENV_HEADCRABCANISTER_TRAIL_TIME 3.0f
  31. //-----------------------------------------------------------------------------
  32. // Spawn flags
  33. //-----------------------------------------------------------------------------
  34. enum
  35. {
  36. SF_NO_IMPACT_SOUND = 0x1,
  37. SF_NO_LAUNCH_SOUND = 0x2,
  38. SF_START_IMPACTED = 0x1000,
  39. SF_LAND_AT_INITIAL_POSITION = 0x2000,
  40. SF_WAIT_FOR_INPUT_TO_OPEN = 0x4000,
  41. SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS = 0x8000,
  42. SF_NO_SMOKE = 0x10000,
  43. SF_NO_SHAKE = 0x20000,
  44. SF_REMOVE_ON_IMPACT = 0x40000,
  45. SF_NO_IMPACT_EFFECTS = 0x80000,
  46. };
  47. //-----------------------------------------------------------------------------
  48. // Headcrab types
  49. //-----------------------------------------------------------------------------
  50. static const char *s_pHeadcrabClass[] =
  51. {
  52. "npc_headcrab",
  53. "npc_headcrab_fast",
  54. "npc_headcrab_poison",
  55. };
  56. //-----------------------------------------------------------------------------
  57. // Context think
  58. //-----------------------------------------------------------------------------
  59. static const char *s_pOpenThinkContext = "OpenThink";
  60. static const char *s_pHeadcrabThinkContext = "HeadcrabThink";
  61. //-----------------------------------------------------------------------------
  62. // HeadcrabCanister Class
  63. //-----------------------------------------------------------------------------
  64. class CEnvHeadcrabCanister : public CBaseAnimating
  65. {
  66. DECLARE_CLASS( CEnvHeadcrabCanister, CBaseAnimating );
  67. DECLARE_DATADESC();
  68. DECLARE_SERVERCLASS();
  69. public:
  70. // Initialization
  71. CEnvHeadcrabCanister();
  72. virtual void Precache( void );
  73. virtual void Spawn( void );
  74. virtual void UpdateOnRemove();
  75. virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
  76. private:
  77. void InputFireCanister( inputdata_t &inputdata );
  78. void InputOpenCanister( inputdata_t &inputdata );
  79. void InputSpawnHeadcrabs( inputdata_t &inputdata );
  80. void InputStopSmoke( inputdata_t &inputdata );
  81. // Think(s)
  82. void HeadcrabCanisterSkyboxThink( void );
  83. void HeadcrabCanisterWorldThink( void );
  84. void HeadcrabCanisterSpawnHeadcrabThink();
  85. void HeadcrabCanisterSkyboxOnlyThink( void );
  86. void HeadcrabCanisterSkyboxRestartThink( void );
  87. void WaitForOpenSequenceThink();
  88. // Place the canister in the world
  89. CSkyCamera* PlaceCanisterInWorld();
  90. // Check for impacts
  91. void TestForCollisionsAgainstEntities( const Vector &vecEndPosition );
  92. void TestForCollisionsAgainstWorld( const Vector &vecEndPosition );
  93. // Figure out where we enter the world
  94. void ComputeWorldEntryPoint( Vector *pStartPosition, QAngle *pStartAngles, Vector *pStartDirection );
  95. // Blows up!
  96. void Detonate( void );
  97. // Landed!
  98. void SetLanded( void );
  99. void Landed( void );
  100. // Open!
  101. void OpenCanister( void );
  102. void CanisterFinishedOpening();
  103. // Set up the world model
  104. void SetupWorldModel();
  105. // Start spawning headcrabs
  106. void StartSpawningHeadcrabs( float flDelay );
  107. private:
  108. CNetworkVar( bool, m_bLanded );
  109. CNetworkVarEmbedded( CEnvHeadcrabCanisterShared, m_Shared );
  110. CHandle<CSpriteTrail> m_hTrail;
  111. CHandle<SmokeTrail> m_hSmokeTrail;
  112. int m_nHeadcrabType;
  113. int m_nHeadcrabCount;
  114. Vector m_vecImpactPosition;
  115. float m_flDamageRadius;
  116. float m_flDamage;
  117. bool m_bIncomingSoundStarted;
  118. bool m_bHasDetonated;
  119. bool m_bLaunched;
  120. bool m_bOpened;
  121. float m_flSmokeLifetime;
  122. string_t m_iszLaunchPositionName;
  123. COutputEHANDLE m_OnLaunched;
  124. COutputEvent m_OnImpacted;
  125. COutputEvent m_OnOpened;
  126. // Only for skybox only cannisters.
  127. float m_flMinRefireTime;
  128. float m_flMaxRefireTime;
  129. int m_nSkyboxCannisterCount;
  130. };
  131. //=============================================================================
  132. //
  133. // HeadcrabCanister Functions
  134. //
  135. LINK_ENTITY_TO_CLASS( env_headcrabcanister, CEnvHeadcrabCanister );
  136. BEGIN_DATADESC( CEnvHeadcrabCanister )
  137. DEFINE_FIELD( m_bLanded, FIELD_BOOLEAN ),
  138. DEFINE_EMBEDDED( m_Shared ),
  139. DEFINE_FIELD( m_hTrail, FIELD_EHANDLE ),
  140. DEFINE_FIELD( m_hSmokeTrail, FIELD_EHANDLE ),
  141. DEFINE_KEYFIELD( m_nHeadcrabType, FIELD_INTEGER, "HeadcrabType" ),
  142. DEFINE_KEYFIELD( m_nHeadcrabCount, FIELD_INTEGER, "HeadcrabCount" ),
  143. DEFINE_KEYFIELD( m_flSmokeLifetime, FIELD_FLOAT, "SmokeLifetime" ),
  144. DEFINE_KEYFIELD( m_iszLaunchPositionName, FIELD_STRING, "LaunchPositionName" ),
  145. DEFINE_FIELD( m_vecImpactPosition, FIELD_POSITION_VECTOR ),
  146. DEFINE_FIELD( m_bIncomingSoundStarted, FIELD_BOOLEAN ),
  147. DEFINE_FIELD( m_bHasDetonated, FIELD_BOOLEAN ),
  148. DEFINE_FIELD( m_bLaunched, FIELD_BOOLEAN ),
  149. DEFINE_FIELD( m_bOpened, FIELD_BOOLEAN ),
  150. DEFINE_KEYFIELD( m_flMinRefireTime, FIELD_FLOAT, "MinSkyboxRefireTime" ),
  151. DEFINE_KEYFIELD( m_flMaxRefireTime, FIELD_FLOAT, "MaxSkyboxRefireTime" ),
  152. DEFINE_KEYFIELD( m_nSkyboxCannisterCount, FIELD_INTEGER, "SkyboxCannisterCount" ),
  153. DEFINE_KEYFIELD( m_flDamageRadius, FIELD_FLOAT, "DamageRadius" ),
  154. DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ),
  155. // Function Pointers.
  156. DEFINE_FUNCTION( HeadcrabCanisterSkyboxThink ),
  157. DEFINE_FUNCTION( HeadcrabCanisterWorldThink ),
  158. DEFINE_FUNCTION( HeadcrabCanisterSpawnHeadcrabThink ),
  159. DEFINE_FUNCTION( WaitForOpenSequenceThink ),
  160. DEFINE_FUNCTION( HeadcrabCanisterSkyboxOnlyThink ),
  161. DEFINE_FUNCTION( HeadcrabCanisterSkyboxRestartThink ),
  162. // Inputs
  163. DEFINE_INPUTFUNC( FIELD_VOID, "FireCanister", InputFireCanister ),
  164. DEFINE_INPUTFUNC( FIELD_VOID, "OpenCanister", InputOpenCanister ),
  165. DEFINE_INPUTFUNC( FIELD_VOID, "SpawnHeadcrabs", InputSpawnHeadcrabs ),
  166. DEFINE_INPUTFUNC( FIELD_VOID, "StopSmoke", InputStopSmoke ),
  167. // Outputs
  168. DEFINE_OUTPUT( m_OnLaunched, "OnLaunched" ),
  169. DEFINE_OUTPUT( m_OnImpacted, "OnImpacted" ),
  170. DEFINE_OUTPUT( m_OnOpened, "OnOpened" ),
  171. END_DATADESC()
  172. EXTERN_SEND_TABLE(DT_EnvHeadcrabCanisterShared);
  173. IMPLEMENT_SERVERCLASS_ST( CEnvHeadcrabCanister, DT_EnvHeadcrabCanister )
  174. SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE(DT_EnvHeadcrabCanisterShared) ),
  175. SendPropBool( SENDINFO( m_bLanded ) ),
  176. END_SEND_TABLE()
  177. //-----------------------------------------------------------------------------
  178. // Constructor
  179. //-----------------------------------------------------------------------------
  180. CEnvHeadcrabCanister::CEnvHeadcrabCanister()
  181. {
  182. m_flMinRefireTime = -1.0f;
  183. m_flMaxRefireTime = -1.0f;
  184. }
  185. //-----------------------------------------------------------------------------
  186. // Precache!
  187. //-----------------------------------------------------------------------------
  188. void CEnvHeadcrabCanister::Precache( void )
  189. {
  190. BaseClass::Precache();
  191. PrecacheModel( ENV_HEADCRABCANISTER_MODEL );
  192. PrecacheModel( ENV_HEADCRABCANISTER_BROKEN_MODEL );
  193. PrecacheModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL );
  194. PrecacheModel("sprites/smoke.vmt");
  195. PrecacheScriptSound( "HeadcrabCanister.LaunchSound" );
  196. PrecacheScriptSound( "HeadcrabCanister.AfterLanding" );
  197. PrecacheScriptSound( "HeadcrabCanister.Explosion" );
  198. PrecacheScriptSound( "HeadcrabCanister.IncomingSound" );
  199. PrecacheScriptSound( "HeadcrabCanister.SkyboxExplosion" );
  200. PrecacheScriptSound( "HeadcrabCanister.Open" );
  201. UTIL_PrecacheOther( s_pHeadcrabClass[m_nHeadcrabType] );
  202. }
  203. //-----------------------------------------------------------------------------
  204. // Spawn!
  205. //-----------------------------------------------------------------------------
  206. void CEnvHeadcrabCanister::Spawn( void )
  207. {
  208. Precache();
  209. BaseClass::Spawn();
  210. // Do we have a position to launch from?
  211. if ( m_iszLaunchPositionName != NULL_STRING )
  212. {
  213. // It doesn't have any real presence at first.
  214. SetSolid( SOLID_NONE );
  215. m_vecImpactPosition = GetAbsOrigin();
  216. m_bIncomingSoundStarted = false;
  217. m_bLanded = false;
  218. m_bHasDetonated = false;
  219. m_bOpened = false;
  220. }
  221. else if ( !HasSpawnFlags( SF_START_IMPACTED ) )
  222. {
  223. // It doesn't have any real presence at first.
  224. SetSolid( SOLID_NONE );
  225. if ( !HasSpawnFlags( SF_LAND_AT_INITIAL_POSITION ) )
  226. {
  227. Vector vecForward;
  228. GetVectors( &vecForward, NULL, NULL );
  229. vecForward *= -1.0f;
  230. trace_t trace;
  231. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecForward * 10000, MASK_NPCWORLDSTATIC,
  232. this, COLLISION_GROUP_NONE, &trace );
  233. m_vecImpactPosition = trace.endpos;
  234. }
  235. else
  236. {
  237. m_vecImpactPosition = GetAbsOrigin();
  238. }
  239. m_bIncomingSoundStarted = false;
  240. m_bLanded = false;
  241. m_bHasDetonated = false;
  242. m_bOpened = false;
  243. }
  244. else
  245. {
  246. m_bHasDetonated = true;
  247. m_bIncomingSoundStarted = true;
  248. m_bOpened = false;
  249. m_vecImpactPosition = GetAbsOrigin();
  250. Landed();
  251. }
  252. }
  253. //-----------------------------------------------------------------------------
  254. // On remove!
  255. //-----------------------------------------------------------------------------
  256. void CEnvHeadcrabCanister::UpdateOnRemove()
  257. {
  258. BaseClass::UpdateOnRemove();
  259. StopSound( "HeadcrabCanister.AfterLanding" );
  260. if ( m_hTrail )
  261. {
  262. UTIL_Remove( m_hTrail );
  263. m_hTrail = NULL;
  264. }
  265. if ( m_hSmokeTrail )
  266. {
  267. UTIL_Remove( m_hSmokeTrail );
  268. m_hSmokeTrail = NULL;
  269. }
  270. }
  271. //-----------------------------------------------------------------------------
  272. // Set up the world model
  273. //-----------------------------------------------------------------------------
  274. void CEnvHeadcrabCanister::SetupWorldModel()
  275. {
  276. SetModel( ENV_HEADCRABCANISTER_MODEL );
  277. SetSolid( SOLID_BBOX );
  278. float flRadius = CollisionProp()->BoundingRadius();
  279. Vector vecMins( -flRadius, -flRadius, -flRadius );
  280. Vector vecMaxs( flRadius, flRadius, flRadius );
  281. SetSize( vecMins, vecMaxs );
  282. }
  283. //-----------------------------------------------------------------------------
  284. // Figure out where we enter the world
  285. //-----------------------------------------------------------------------------
  286. void CEnvHeadcrabCanister::ComputeWorldEntryPoint( Vector *pStartPosition, QAngle *pStartAngles, Vector *pStartDirection )
  287. {
  288. SetupWorldModel();
  289. Vector vecForward;
  290. GetVectors( &vecForward, NULL, NULL );
  291. // Raycast up to the place where we should start from (start raycast slightly off the ground,
  292. // since it'll be buried in the ground oftentimes)
  293. trace_t tr;
  294. CTraceFilterWorldOnly filter;
  295. UTIL_TraceLine( GetAbsOrigin() + vecForward * 100, GetAbsOrigin() + vecForward * 10000,
  296. CONTENTS_SOLID, &filter, &tr );
  297. *pStartPosition = tr.endpos;
  298. *pStartAngles = GetAbsAngles();
  299. VectorMultiply( vecForward, -1.0f, *pStartDirection );
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Place the canister in the world
  303. //-----------------------------------------------------------------------------
  304. CSkyCamera *CEnvHeadcrabCanister::PlaceCanisterInWorld()
  305. {
  306. CSkyCamera *pCamera = NULL;
  307. // Are we launching from a point? If so, use that point.
  308. if ( m_iszLaunchPositionName != NULL_STRING )
  309. {
  310. // Get the launch position entity
  311. CBaseEntity *pLaunchPos = gEntList.FindEntityByName( NULL, m_iszLaunchPositionName );
  312. if ( !pLaunchPos )
  313. {
  314. Warning("%s (%s) could not find an entity matching LaunchPositionName of '%s'\n", GetEntityName().ToCStr(), GetDebugName(), STRING(m_iszLaunchPositionName) );
  315. SUB_Remove();
  316. }
  317. else
  318. {
  319. SetupWorldModel();
  320. Vector vecForward, vecImpactDirection;
  321. GetVectors( &vecForward, NULL, NULL );
  322. VectorMultiply( vecForward, -1.0f, vecImpactDirection );
  323. m_Shared.InitInWorld( gpGlobals->curtime, pLaunchPos->GetAbsOrigin(), GetAbsAngles(),
  324. vecImpactDirection, m_vecImpactPosition, true );
  325. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink );
  326. SetNextThink( gpGlobals->curtime );
  327. }
  328. }
  329. else if ( DetectInSkybox() )
  330. {
  331. pCamera = GetEntitySkybox();
  332. SetModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL );
  333. SetSolid( SOLID_NONE );
  334. Vector vecForward;
  335. GetVectors( &vecForward, NULL, NULL );
  336. vecForward *= -1.0f;
  337. m_Shared.InitInSkybox( gpGlobals->curtime, m_vecImpactPosition, GetAbsAngles(), vecForward,
  338. m_vecImpactPosition, pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale );
  339. AddEFlags( EFL_IN_SKYBOX );
  340. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxOnlyThink );
  341. SetNextThink( gpGlobals->curtime + m_Shared.GetEnterWorldTime() + TICK_INTERVAL );
  342. }
  343. else
  344. {
  345. Vector vecStartPosition, vecDirection;
  346. QAngle vecStartAngles;
  347. ComputeWorldEntryPoint( &vecStartPosition, &vecStartAngles, &vecDirection );
  348. // Figure out which skybox to place the entity in.
  349. pCamera = GetCurrentSkyCamera();
  350. if ( pCamera )
  351. {
  352. m_Shared.InitInSkybox( gpGlobals->curtime, vecStartPosition, vecStartAngles, vecDirection,
  353. m_vecImpactPosition, pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale );
  354. if ( m_Shared.IsInSkybox() )
  355. {
  356. SetModel( ENV_HEADCRABCANISTER_SKYBOX_MODEL );
  357. SetSolid( SOLID_NONE );
  358. AddEFlags( EFL_IN_SKYBOX );
  359. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxThink );
  360. SetNextThink( gpGlobals->curtime + m_Shared.GetEnterWorldTime() );
  361. }
  362. else
  363. {
  364. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink );
  365. SetNextThink( gpGlobals->curtime );
  366. }
  367. }
  368. else
  369. {
  370. m_Shared.InitInWorld( gpGlobals->curtime, vecStartPosition, vecStartAngles,
  371. vecDirection, m_vecImpactPosition );
  372. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink );
  373. SetNextThink( gpGlobals->curtime );
  374. }
  375. }
  376. Vector vecEndPosition;
  377. QAngle vecEndAngles;
  378. m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles );
  379. SetAbsOrigin( vecEndPosition );
  380. SetAbsAngles( vecEndAngles );
  381. return pCamera;
  382. }
  383. //-----------------------------------------------------------------------------
  384. // Fires the canister!
  385. //-----------------------------------------------------------------------------
  386. void CEnvHeadcrabCanister::InputFireCanister( inputdata_t &inputdata )
  387. {
  388. if (m_bLaunched)
  389. return;
  390. m_bLaunched = true;
  391. if ( HasSpawnFlags( SF_START_IMPACTED ) )
  392. {
  393. StartSpawningHeadcrabs( 0.01f );
  394. return;
  395. }
  396. // Play a firing sound
  397. CPASAttenuationFilter filter( this, ATTN_NONE );
  398. if ( !HasSpawnFlags( SF_NO_LAUNCH_SOUND ) )
  399. {
  400. EmitSound( filter, entindex(), "HeadcrabCanister.LaunchSound" );
  401. }
  402. // Place the canister
  403. CSkyCamera *pCamera = PlaceCanisterInWorld();
  404. // Hook up a smoke trail
  405. m_hTrail = CSpriteTrail::SpriteTrailCreate( "sprites/smoke.vmt", GetAbsOrigin(), true );
  406. m_hTrail->SetTransparency( kRenderTransAdd, 224, 224, 255, 255, kRenderFxNone );
  407. m_hTrail->SetAttachment( this, 0 );
  408. m_hTrail->SetStartWidth( 32.0 );
  409. m_hTrail->SetEndWidth( 200.0 );
  410. m_hTrail->SetStartWidthVariance( 15.0f );
  411. m_hTrail->SetTextureResolution( 0.002 );
  412. m_hTrail->SetLifeTime( ENV_HEADCRABCANISTER_TRAIL_TIME );
  413. m_hTrail->SetMinFadeLength( 1000.0f );
  414. if ( pCamera && m_Shared.IsInSkybox() )
  415. {
  416. m_hTrail->SetSkybox( pCamera->m_skyboxData.origin, pCamera->m_skyboxData.scale );
  417. }
  418. // Fire that output!
  419. m_OnLaunched.Set( this, this, this );
  420. }
  421. //-----------------------------------------------------------------------------
  422. // Opens the canister!
  423. //-----------------------------------------------------------------------------
  424. void CEnvHeadcrabCanister::InputOpenCanister( inputdata_t &inputdata )
  425. {
  426. if ( m_bLanded && !m_bOpened && HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_OPEN ) )
  427. {
  428. OpenCanister();
  429. }
  430. }
  431. //-----------------------------------------------------------------------------
  432. // Spawns headcrabs
  433. //-----------------------------------------------------------------------------
  434. void CEnvHeadcrabCanister::InputSpawnHeadcrabs( inputdata_t &inputdata )
  435. {
  436. if ( m_bLanded && m_bOpened && HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS ) )
  437. {
  438. StartSpawningHeadcrabs( 0.01f );
  439. }
  440. }
  441. //-----------------------------------------------------------------------------
  442. // Purpose:
  443. // Input : &inputdata -
  444. //-----------------------------------------------------------------------------
  445. void CEnvHeadcrabCanister::InputStopSmoke( inputdata_t &inputdata )
  446. {
  447. if ( m_hSmokeTrail != NULL )
  448. {
  449. UTIL_Remove( m_hSmokeTrail );
  450. m_hSmokeTrail = NULL;
  451. }
  452. }
  453. //=============================================================================
  454. //
  455. // Enumerator for swept bbox collision.
  456. //
  457. class CCollideList : public IEntityEnumerator
  458. {
  459. public:
  460. CCollideList( Ray_t *pRay, CBaseEntity* pIgnoreEntity, int nContentsMask ) :
  461. m_Entities( 0, 32 ), m_pIgnoreEntity( pIgnoreEntity ),
  462. m_nContentsMask( nContentsMask ), m_pRay(pRay) {}
  463. virtual bool EnumEntity( IHandleEntity *pHandleEntity )
  464. {
  465. // Don't bother with the ignore entity.
  466. if ( pHandleEntity == m_pIgnoreEntity )
  467. return true;
  468. Assert( pHandleEntity );
  469. trace_t tr;
  470. enginetrace->ClipRayToEntity( *m_pRay, m_nContentsMask, pHandleEntity, &tr );
  471. if (( tr.fraction < 1.0f ) || (tr.startsolid) || (tr.allsolid))
  472. {
  473. CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
  474. m_Entities.AddToTail( pEntity );
  475. }
  476. return true;
  477. }
  478. CUtlVector<CBaseEntity*> m_Entities;
  479. private:
  480. CBaseEntity *m_pIgnoreEntity;
  481. int m_nContentsMask;
  482. Ray_t *m_pRay;
  483. };
  484. //-----------------------------------------------------------------------------
  485. // Test for impact!
  486. //-----------------------------------------------------------------------------
  487. void CEnvHeadcrabCanister::TestForCollisionsAgainstEntities( const Vector &vecEndPosition )
  488. {
  489. // Debugging!!
  490. // NDebugOverlay::Box( GetAbsOrigin(), m_vecMin * 0.5f, m_vecMax * 0.5f, 255, 255, 0, 0, 5 );
  491. // NDebugOverlay::Box( vecEndPosition, m_vecMin, m_vecMax, 255, 0, 0, 0, 5 );
  492. float flRadius = CollisionProp()->BoundingRadius();
  493. Vector vecMins( -flRadius, -flRadius, -flRadius );
  494. Vector vecMaxs( flRadius, flRadius, flRadius );
  495. Ray_t ray;
  496. ray.Init( GetAbsOrigin(), vecEndPosition, vecMins, vecMaxs );
  497. CCollideList collideList( &ray, this, MASK_SOLID );
  498. enginetrace->EnumerateEntities( ray, false, &collideList );
  499. float flDamage = m_flDamage;
  500. // Now get each entity and react accordinly!
  501. for( int iEntity = collideList.m_Entities.Count(); --iEntity >= 0; )
  502. {
  503. CBaseEntity *pEntity = collideList.m_Entities[iEntity];
  504. Vector vecForceDir = m_Shared.m_vecDirection;
  505. // Check for a physics object and apply force!
  506. IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
  507. if ( pPhysObject )
  508. {
  509. float flMass = PhysGetEntityMass( pEntity );
  510. vecForceDir *= flMass * 750;
  511. pPhysObject->ApplyForceCenter( vecForceDir );
  512. }
  513. if ( pEntity->m_takedamage && ( m_flDamage != 0.0f ) )
  514. {
  515. CTakeDamageInfo info( this, this, flDamage, DMG_BLAST );
  516. CalculateExplosiveDamageForce( &info, vecForceDir, pEntity->GetAbsOrigin() );
  517. pEntity->TakeDamage( info );
  518. }
  519. }
  520. }
  521. //-----------------------------------------------------------------------------
  522. // Test for impact!
  523. //-----------------------------------------------------------------------------
  524. #define INNER_RADIUS_FRACTION 0.25f
  525. void CEnvHeadcrabCanister::TestForCollisionsAgainstWorld( const Vector &vecEndPosition )
  526. {
  527. // Splash damage!
  528. // Iterate on all entities in the vicinity.
  529. float flDamageRadius = m_flDamageRadius;
  530. float flDamage = m_flDamage;
  531. CBaseEntity *pEntity;
  532. for ( CEntitySphereQuery sphere( vecEndPosition, flDamageRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
  533. {
  534. if ( pEntity == this )
  535. continue;
  536. if ( !pEntity->IsSolid() )
  537. continue;
  538. // Get distance to object and use it as a scale value.
  539. Vector vecSegment;
  540. VectorSubtract( pEntity->GetAbsOrigin(), vecEndPosition, vecSegment );
  541. float flDistance = VectorNormalize( vecSegment );
  542. float flFactor = 1.0f / ( flDamageRadius * (INNER_RADIUS_FRACTION - 1) );
  543. flFactor *= flFactor;
  544. float flScale = flDistance - flDamageRadius;
  545. flScale *= flScale * flFactor;
  546. if ( flScale > 1.0f )
  547. {
  548. flScale = 1.0f;
  549. }
  550. // Check for a physics object and apply force!
  551. Vector vecForceDir = vecSegment;
  552. IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
  553. if ( pPhysObject )
  554. {
  555. // Send it flying!!!
  556. float flMass = PhysGetEntityMass( pEntity );
  557. vecForceDir *= flMass * 750 * flScale;
  558. pPhysObject->ApplyForceCenter( vecForceDir );
  559. }
  560. if ( pEntity->m_takedamage && ( m_flDamage != 0.0f ) )
  561. {
  562. CTakeDamageInfo info( this, this, flDamage * flScale, DMG_BLAST );
  563. CalculateExplosiveDamageForce( &info, vecSegment, pEntity->GetAbsOrigin() );
  564. pEntity->TakeDamage( info );
  565. }
  566. if ( pEntity->IsPlayer() && !(static_cast<CBasePlayer*>(pEntity)->IsInAVehicle()) )
  567. {
  568. if (vecSegment.z < 0.1f)
  569. {
  570. vecSegment.z = 0.1f;
  571. VectorNormalize( vecSegment );
  572. }
  573. float flAmount = SimpleSplineRemapVal( flScale, 0.0f, 1.0f, 250.0f, 1000.0f );
  574. pEntity->ApplyAbsVelocityImpulse( vecSegment * flAmount );
  575. }
  576. }
  577. }
  578. //-----------------------------------------------------------------------------
  579. // Headcrab creation
  580. //-----------------------------------------------------------------------------
  581. void CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink()
  582. {
  583. Vector vecSpawnPosition;
  584. QAngle vecSpawnAngles;
  585. --m_nHeadcrabCount;
  586. int nHeadCrabAttachment = LookupAttachment( "headcrab" );
  587. if ( GetAttachment( nHeadCrabAttachment, vecSpawnPosition, vecSpawnAngles ) )
  588. {
  589. CBaseEntity *pEnt = CreateEntityByName( s_pHeadcrabClass[m_nHeadcrabType] );
  590. CBaseHeadcrab *pHeadCrab = assert_cast<CBaseHeadcrab*>(pEnt);
  591. // Necessary to get it to eject properly (don't allow the NPC
  592. // to override the spawn position specified).
  593. pHeadCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
  594. // So we don't collide with the canister
  595. // NOTE: Hierarchical attachment is necessary here to get the animations to work
  596. pHeadCrab->SetOwnerEntity( this );
  597. DispatchSpawn( pHeadCrab );
  598. pHeadCrab->SetParent( this, nHeadCrabAttachment );
  599. pHeadCrab->SetLocalOrigin( vec3_origin );
  600. pHeadCrab->SetLocalAngles( vec3_angle );
  601. pHeadCrab->CrawlFromCanister();
  602. }
  603. if ( m_nHeadcrabCount != 0 )
  604. {
  605. float flWaitTime = random->RandomFloat( 1.0f, 2.0f );
  606. SetContextThink( &CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink, gpGlobals->curtime + flWaitTime, s_pHeadcrabThinkContext );
  607. }
  608. else
  609. {
  610. SetContextThink( NULL, gpGlobals->curtime, s_pHeadcrabThinkContext );
  611. }
  612. }
  613. //-----------------------------------------------------------------------------
  614. // Start spawning headcrabs
  615. //-----------------------------------------------------------------------------
  616. void CEnvHeadcrabCanister::StartSpawningHeadcrabs( float flDelay )
  617. {
  618. if ( !m_bLanded || !m_bOpened || m_nHeadcrabCount == 0 )
  619. return;
  620. if ( m_nHeadcrabCount != 0 )
  621. {
  622. SetContextThink( &CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink, gpGlobals->curtime + flDelay, s_pHeadcrabThinkContext );
  623. }
  624. }
  625. //-----------------------------------------------------------------------------
  626. // Canister finished opening
  627. //-----------------------------------------------------------------------------
  628. void CEnvHeadcrabCanister::CanisterFinishedOpening( void )
  629. {
  630. ResetSequence( LookupSequence( "idle_open" ) );
  631. m_OnOpened.FireOutput( this, this, 0 );
  632. m_bOpened = true;
  633. SetContextThink( NULL, gpGlobals->curtime, s_pOpenThinkContext );
  634. if ( !HasSpawnFlags( SF_START_IMPACTED ) )
  635. {
  636. if ( !HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_SPAWN_HEADCRABS ) )
  637. {
  638. StartSpawningHeadcrabs( 3.0f );
  639. }
  640. }
  641. }
  642. //-----------------------------------------------------------------------------
  643. // Finish the opening sequence
  644. //-----------------------------------------------------------------------------
  645. void CEnvHeadcrabCanister::WaitForOpenSequenceThink()
  646. {
  647. StudioFrameAdvance();
  648. if ( ( GetSequence() == LookupSequence( "open" ) ) && IsSequenceFinished() )
  649. {
  650. CanisterFinishedOpening();
  651. }
  652. else
  653. {
  654. SetContextThink( &CEnvHeadcrabCanister::WaitForOpenSequenceThink, gpGlobals->curtime + 0.01f, s_pOpenThinkContext );
  655. }
  656. }
  657. //-----------------------------------------------------------------------------
  658. // Open the canister!
  659. //-----------------------------------------------------------------------------
  660. void CEnvHeadcrabCanister::OpenCanister( void )
  661. {
  662. if ( m_bOpened )
  663. return;
  664. int nOpenSequence = LookupSequence( "open" );
  665. if ( nOpenSequence != ACT_INVALID )
  666. {
  667. EmitSound( "HeadcrabCanister.Open" );
  668. ResetSequence( nOpenSequence );
  669. SetContextThink( &CEnvHeadcrabCanister::WaitForOpenSequenceThink, gpGlobals->curtime + 0.01f, s_pOpenThinkContext );
  670. }
  671. else
  672. {
  673. CanisterFinishedOpening();
  674. }
  675. }
  676. //-----------------------------------------------------------------------------
  677. // Purpose:
  678. //-----------------------------------------------------------------------------
  679. void CEnvHeadcrabCanister::SetLanded( void )
  680. {
  681. SetAbsOrigin( m_vecImpactPosition );
  682. SetModel( ENV_HEADCRABCANISTER_BROKEN_MODEL );
  683. SetMoveType( MOVETYPE_NONE );
  684. SetSolid( SOLID_VPHYSICS );
  685. VPhysicsInitStatic();
  686. IncrementInterpolationFrame();
  687. m_bLanded = true;
  688. }
  689. //-----------------------------------------------------------------------------
  690. // Landed!
  691. //-----------------------------------------------------------------------------
  692. void CEnvHeadcrabCanister::Landed( void )
  693. {
  694. EmitSound( "HeadcrabCanister.AfterLanding" );
  695. // Lock us now that we've stopped
  696. SetLanded();
  697. // Hook the follow trail to the lead of the canister (which should be buried)
  698. // to hide problems with the edge of the follow trail
  699. if (m_hTrail)
  700. {
  701. m_hTrail->SetAttachment( this, LookupAttachment("trail") );
  702. }
  703. // Start smoke, unless we don't want it
  704. if ( !HasSpawnFlags( SF_NO_SMOKE ) )
  705. {
  706. // Create the smoke trail to obscure the headcrabs
  707. m_hSmokeTrail = SmokeTrail::CreateSmokeTrail();
  708. m_hSmokeTrail->FollowEntity( this, "smoke" );
  709. m_hSmokeTrail->m_SpawnRate = 8;
  710. m_hSmokeTrail->m_ParticleLifetime = 2.0f;
  711. m_hSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
  712. m_hSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
  713. m_hSmokeTrail->m_StartSize = 32;
  714. m_hSmokeTrail->m_EndSize = 64;
  715. m_hSmokeTrail->m_SpawnRadius= 8;
  716. m_hSmokeTrail->m_MinSpeed = 0;
  717. m_hSmokeTrail->m_MaxSpeed = 8;
  718. m_hSmokeTrail->m_MinDirectedSpeed = 32;
  719. m_hSmokeTrail->m_MaxDirectedSpeed = 64;
  720. m_hSmokeTrail->m_Opacity = 0.35f;
  721. m_hSmokeTrail->SetLifetime( m_flSmokeLifetime );
  722. }
  723. SetThink( NULL );
  724. if ( !HasSpawnFlags( SF_WAIT_FOR_INPUT_TO_OPEN ) )
  725. {
  726. if ( HasSpawnFlags( SF_START_IMPACTED ) )
  727. {
  728. CanisterFinishedOpening( );
  729. }
  730. else
  731. {
  732. OpenCanister();
  733. }
  734. }
  735. }
  736. //-----------------------------------------------------------------------------
  737. // Creates the explosion effect
  738. //-----------------------------------------------------------------------------
  739. void CEnvHeadcrabCanister::Detonate( )
  740. {
  741. // Send the impact output
  742. m_OnImpacted.FireOutput( this, this, 0 );
  743. if ( !HasSpawnFlags( SF_NO_IMPACT_SOUND ) )
  744. {
  745. StopSound( "HeadcrabCanister.IncomingSound" );
  746. EmitSound( "HeadcrabCanister.Explosion" );
  747. }
  748. // If we're supposed to be removed, do that now
  749. if ( HasSpawnFlags( SF_REMOVE_ON_IMPACT ) )
  750. {
  751. SetAbsOrigin( m_vecImpactPosition );
  752. SetModel( ENV_HEADCRABCANISTER_BROKEN_MODEL );
  753. SetMoveType( MOVETYPE_NONE );
  754. IncrementInterpolationFrame();
  755. m_bLanded = true;
  756. // Become invisible so our trail can finish up
  757. AddEffects( EF_NODRAW );
  758. SetSolidFlags( FSOLID_NOT_SOLID );
  759. SetThink( &CEnvHeadcrabCanister::SUB_Remove );
  760. SetNextThink( gpGlobals->curtime + ENV_HEADCRABCANISTER_TRAIL_TIME );
  761. return;
  762. }
  763. // Test for damaging things
  764. TestForCollisionsAgainstWorld( m_vecImpactPosition );
  765. // Shake the screen unless flagged otherwise
  766. if ( !HasSpawnFlags( SF_NO_SHAKE ) )
  767. {
  768. CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
  769. // If the player is on foot, then do a more limited shake
  770. float shakeRadius = ( pPlayer && pPlayer->IsInAVehicle() ) ? sk_env_headcrabcanister_shake_radius_vehicle.GetFloat() : sk_env_headcrabcanister_shake_radius.GetFloat();
  771. UTIL_ScreenShake( m_vecImpactPosition, sk_env_headcrabcanister_shake_amplitude.GetFloat(), 150.0, 1.0, shakeRadius, SHAKE_START );
  772. }
  773. // Do explosion effects
  774. if ( !HasSpawnFlags( SF_NO_IMPACT_EFFECTS ) )
  775. {
  776. // Normal explosion
  777. ExplosionCreate( m_vecImpactPosition, GetAbsAngles(), this, 50.0f, 500.0f,
  778. SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSOUND, 1300.0f );
  779. // Dust explosion
  780. AR2Explosion *pExplosion = AR2Explosion::CreateAR2Explosion( m_vecImpactPosition );
  781. if( pExplosion )
  782. {
  783. pExplosion->SetLifetime(10);
  784. }
  785. }
  786. }
  787. //-----------------------------------------------------------------------------
  788. // Purpose: This think function simulates (moves/collides) the HeadcrabCanister while in
  789. // the world.
  790. //-----------------------------------------------------------------------------
  791. void CEnvHeadcrabCanister::HeadcrabCanisterWorldThink( void )
  792. {
  793. // Get the current time.
  794. float flTime = gpGlobals->curtime;
  795. Vector vecStartPosition = GetAbsOrigin();
  796. // Update HeadcrabCanister position for swept collision test.
  797. Vector vecEndPosition;
  798. QAngle vecEndAngles;
  799. m_Shared.GetPositionAtTime( flTime, vecEndPosition, vecEndAngles );
  800. if ( !m_bIncomingSoundStarted && !HasSpawnFlags( SF_NO_IMPACT_SOUND ) )
  801. {
  802. float flDistSq = ENV_HEADCRABCANISTER_INCOMING_SOUND_TIME * m_Shared.m_flFlightSpeed;
  803. flDistSq *= flDistSq;
  804. if ( vecEndPosition.DistToSqr(m_vecImpactPosition) <= flDistSq )
  805. {
  806. // Figure out if we're close enough to play the incoming sound
  807. EmitSound( "HeadcrabCanister.IncomingSound" );
  808. m_bIncomingSoundStarted = true;
  809. }
  810. }
  811. TestForCollisionsAgainstEntities( vecEndPosition );
  812. if ( m_Shared.DidImpact( flTime ) )
  813. {
  814. if ( !m_bHasDetonated )
  815. {
  816. Detonate();
  817. m_bHasDetonated = true;
  818. }
  819. if ( !HasSpawnFlags( SF_REMOVE_ON_IMPACT ) )
  820. {
  821. Landed();
  822. }
  823. return;
  824. }
  825. // Always move full movement.
  826. SetAbsOrigin( vecEndPosition );
  827. // Touch triggers along the way
  828. PhysicsTouchTriggers( &vecStartPosition );
  829. SetNextThink( gpGlobals->curtime + 0.2f );
  830. SetAbsAngles( vecEndAngles );
  831. if ( !m_bHasDetonated )
  832. {
  833. if ( vecEndPosition.DistToSqr( m_vecImpactPosition ) < BoundingRadius() * BoundingRadius() )
  834. {
  835. Detonate();
  836. m_bHasDetonated = true;
  837. }
  838. }
  839. }
  840. //-----------------------------------------------------------------------------
  841. // Purpose: This think function should be called at the time when the HeadcrabCanister
  842. // will be leaving the skybox and entering the world.
  843. //-----------------------------------------------------------------------------
  844. void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxThink( void )
  845. {
  846. // Use different position computation
  847. m_Shared.ConvertFromSkyboxToWorld();
  848. Vector vecEndPosition;
  849. QAngle vecEndAngles;
  850. m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles );
  851. UTIL_SetOrigin( this, vecEndPosition );
  852. SetAbsAngles( vecEndAngles );
  853. RemoveEFlags( EFL_IN_SKYBOX );
  854. // Switch to the actual-scale model
  855. SetupWorldModel();
  856. // Futz with the smoke trail to get it working across the boundary
  857. m_hTrail->SetSkybox( vec3_origin, 1.0f );
  858. // Now we start looking for collisions
  859. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterWorldThink );
  860. SetNextThink( gpGlobals->curtime + 0.01f );
  861. }
  862. //-----------------------------------------------------------------------------
  863. // Purpose: This stops its motion in the skybox
  864. //-----------------------------------------------------------------------------
  865. void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxOnlyThink( void )
  866. {
  867. Vector vecEndPosition;
  868. QAngle vecEndAngles;
  869. m_Shared.GetPositionAtTime( gpGlobals->curtime, vecEndPosition, vecEndAngles );
  870. UTIL_SetOrigin( this, vecEndPosition );
  871. SetAbsAngles( vecEndAngles );
  872. if ( !HasSpawnFlags( SF_NO_IMPACT_SOUND ) )
  873. {
  874. CPASAttenuationFilter filter( this, ATTN_NONE );
  875. EmitSound( filter, entindex(), "HeadcrabCanister.SkyboxExplosion" );
  876. }
  877. if ( m_nSkyboxCannisterCount != 0 )
  878. {
  879. if ( --m_nSkyboxCannisterCount <= 0 )
  880. {
  881. SetThink( NULL );
  882. return;
  883. }
  884. }
  885. float flRefireTime = random->RandomFloat( m_flMinRefireTime, m_flMaxRefireTime ) + ENV_HEADCRABCANISTER_TRAIL_TIME;
  886. SetThink( &CEnvHeadcrabCanister::HeadcrabCanisterSkyboxRestartThink );
  887. SetNextThink( gpGlobals->curtime + flRefireTime );
  888. }
  889. //-----------------------------------------------------------------------------
  890. // This will re-fire the headcrab cannister
  891. //-----------------------------------------------------------------------------
  892. void CEnvHeadcrabCanister::HeadcrabCanisterSkyboxRestartThink( void )
  893. {
  894. if ( m_hTrail )
  895. {
  896. UTIL_Remove( m_hTrail );
  897. m_hTrail = NULL;
  898. }
  899. m_bLaunched = false;
  900. inputdata_t data;
  901. InputFireCanister( data );
  902. }
  903. //-----------------------------------------------------------------------------
  904. // Purpose:
  905. // Input : *pInfo -
  906. // bAlways -
  907. //-----------------------------------------------------------------------------
  908. void CEnvHeadcrabCanister::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
  909. {
  910. // Are we already marked for transmission?
  911. if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
  912. return;
  913. BaseClass::SetTransmit( pInfo, bAlways );
  914. // Make our smoke trail always come with us
  915. if ( m_hSmokeTrail )
  916. {
  917. m_hSmokeTrail->SetTransmit( pInfo, bAlways );
  918. }
  919. }