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.

2197 lines
63 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: combine ball - can be held by the super physcannon and launched
  4. // by the AR2's alt-fire
  5. //
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "prop_combine_ball.h"
  9. #include "props.h"
  10. #include "explode.h"
  11. #include "saverestore_utlvector.h"
  12. #include "hl2_shareddefs.h"
  13. #include "materialsystem/imaterial.h"
  14. #include "beam_flags.h"
  15. #include "physics_prop_ragdoll.h"
  16. #include "soundent.h"
  17. #include "soundenvelope.h"
  18. #include "te_effect_dispatch.h"
  19. #include "ai_basenpc.h"
  20. #include "npc_bullseye.h"
  21. #include "filters.h"
  22. #include "SpriteTrail.h"
  23. #include "decals.h"
  24. #include "hl2_player.h"
  25. #include "eventqueue.h"
  26. #include "physics_collisionevent.h"
  27. #include "gamestats.h"
  28. // memdbgon must be the last include file in a .cpp file!!!
  29. #include "tier0/memdbgon.h"
  30. #define PROP_COMBINE_BALL_MODEL "models/effects/combineball.mdl"
  31. #define PROP_COMBINE_BALL_SPRITE_TRAIL "sprites/combineball_trail_black_1.vmt"
  32. #define PROP_COMBINE_BALL_LIFETIME 4.0f // Seconds
  33. #define PROP_COMBINE_BALL_HOLD_DISSOLVE_TIME 8.0f
  34. #define SF_COMBINE_BALL_BOUNCING_IN_SPAWNER 0x10000
  35. #define MAX_COMBINEBALL_RADIUS 12
  36. ConVar sk_npc_dmg_combineball( "sk_npc_dmg_combineball","15", FCVAR_REPLICATED);
  37. ConVar sk_combineball_guidefactor( "sk_combineball_guidefactor","0.5", FCVAR_REPLICATED);
  38. ConVar sk_combine_ball_search_radius( "sk_combine_ball_search_radius", "512", FCVAR_REPLICATED);
  39. ConVar sk_combineball_seek_angle( "sk_combineball_seek_angle","15.0", FCVAR_REPLICATED);
  40. ConVar sk_combineball_seek_kill( "sk_combineball_seek_kill","0", FCVAR_REPLICATED);
  41. // For our ring explosion
  42. int s_nExplosionTexture = -1;
  43. //-----------------------------------------------------------------------------
  44. // Context think
  45. //-----------------------------------------------------------------------------
  46. static const char *s_pWhizThinkContext = "WhizThinkContext";
  47. static const char *s_pHoldDissolveContext = "HoldDissolveContext";
  48. static const char *s_pExplodeTimerContext = "ExplodeTimerContext";
  49. static const char *s_pAnimThinkContext = "AnimThinkContext";
  50. static const char *s_pCaptureContext = "CaptureContext";
  51. static const char *s_pRemoveContext = "RemoveContext";
  52. //-----------------------------------------------------------------------------
  53. // Purpose:
  54. // Input : radius -
  55. // Output : CBaseEntity
  56. //-----------------------------------------------------------------------------
  57. CBaseEntity *CreateCombineBall( const Vector &origin, const Vector &velocity, float radius, float mass, float lifetime, CBaseEntity *pOwner )
  58. {
  59. CPropCombineBall *pBall = static_cast<CPropCombineBall*>( CreateEntityByName( "prop_combine_ball" ) );
  60. pBall->SetRadius( radius );
  61. pBall->SetAbsOrigin( origin );
  62. pBall->SetOwnerEntity( pOwner );
  63. pBall->SetOriginalOwner( pOwner );
  64. pBall->SetAbsVelocity( velocity );
  65. pBall->Spawn();
  66. pBall->SetState( CPropCombineBall::STATE_THROWN );
  67. pBall->SetSpeed( velocity.Length() );
  68. pBall->EmitSound( "NPC_CombineBall.Launch" );
  69. PhysSetGameFlags( pBall->VPhysicsGetObject(), FVPHYSICS_WAS_THROWN );
  70. pBall->StartWhizSoundThink();
  71. pBall->SetMass( mass );
  72. pBall->StartLifetime( lifetime );
  73. pBall->SetWeaponLaunched( true );
  74. return pBall;
  75. }
  76. //-----------------------------------------------------------------------------
  77. // Purpose: Allows game to know if the physics object should kill allies or not
  78. //-----------------------------------------------------------------------------
  79. CBasePlayer *CPropCombineBall::HasPhysicsAttacker( float dt )
  80. {
  81. // Must have an owner
  82. if ( GetOwnerEntity() == NULL )
  83. return NULL;
  84. // Must be a player
  85. if ( GetOwnerEntity()->IsPlayer() == false )
  86. return NULL;
  87. // We don't care about the time passed in
  88. return static_cast<CBasePlayer *>(GetOwnerEntity());
  89. }
  90. //-----------------------------------------------------------------------------
  91. // Purpose: Determines whether a physics object is a combine ball or not
  92. // Input : *pObj - Object to test
  93. // Output : Returns true on success, false on failure.
  94. // Notes : This function cannot identify a combine ball that is held by
  95. // the physcannon because any object held by the physcannon is
  96. // COLLISIONGROUP_DEBRIS.
  97. //-----------------------------------------------------------------------------
  98. bool UTIL_IsCombineBall( CBaseEntity *pEntity )
  99. {
  100. // Must be the correct collision group
  101. if ( pEntity->GetCollisionGroup() != HL2COLLISION_GROUP_COMBINE_BALL )
  102. return false;
  103. //NOTENOTE: This allows ANY combine ball to pass the test
  104. /*
  105. CPropCombineBall *pBall = dynamic_cast<CPropCombineBall *>(pEntity);
  106. if ( pBall && pBall->WasWeaponLaunched() )
  107. return false;
  108. */
  109. return true;
  110. }
  111. //-----------------------------------------------------------------------------
  112. // Purpose: Determines whether a physics object is an AR2 combine ball or not
  113. // Input : *pEntity -
  114. // Output : Returns true on success, false on failure.
  115. //-----------------------------------------------------------------------------
  116. bool UTIL_IsAR2CombineBall( CBaseEntity *pEntity )
  117. {
  118. // Must be the correct collision group
  119. if ( pEntity->GetCollisionGroup() != HL2COLLISION_GROUP_COMBINE_BALL )
  120. return false;
  121. CPropCombineBall *pBall = dynamic_cast<CPropCombineBall *>(pEntity);
  122. if ( pBall && pBall->WasWeaponLaunched() )
  123. return true;
  124. return false;
  125. }
  126. //-----------------------------------------------------------------------------
  127. // Purpose: Uses a deeper casting check to determine if pEntity is a combine
  128. // ball. This function exists because the normal (much faster) check
  129. // in UTIL_IsCombineBall() can never identify a combine ball held by
  130. // the physcannon because the physcannon changes the held entity's
  131. // collision group.
  132. // Input : *pEntity - Entity to check
  133. // Output : Returns true on success, false on failure.
  134. //-----------------------------------------------------------------------------
  135. bool UTIL_IsCombineBallDefinite( CBaseEntity *pEntity )
  136. {
  137. CPropCombineBall *pBall = dynamic_cast<CPropCombineBall *>(pEntity);
  138. return pBall != NULL;
  139. }
  140. //-----------------------------------------------------------------------------
  141. //
  142. // Spawns combine balls
  143. //
  144. //-----------------------------------------------------------------------------
  145. #define SF_SPAWNER_START_DISABLED 0x1000
  146. #define SF_SPAWNER_POWER_SUPPLY 0x2000
  147. //-----------------------------------------------------------------------------
  148. // Implementation of CPropCombineBall
  149. //-----------------------------------------------------------------------------
  150. LINK_ENTITY_TO_CLASS( prop_combine_ball, CPropCombineBall );
  151. //-----------------------------------------------------------------------------
  152. // Save/load:
  153. //-----------------------------------------------------------------------------
  154. BEGIN_DATADESC( CPropCombineBall )
  155. DEFINE_FIELD( m_flLastBounceTime, FIELD_TIME ),
  156. DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
  157. DEFINE_FIELD( m_nState, FIELD_CHARACTER ),
  158. DEFINE_FIELD( m_pGlowTrail, FIELD_CLASSPTR ),
  159. DEFINE_SOUNDPATCH( m_pHoldingSound ),
  160. DEFINE_FIELD( m_bFiredGrabbedOutput, FIELD_BOOLEAN ),
  161. DEFINE_FIELD( m_bEmit, FIELD_BOOLEAN ),
  162. DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN ),
  163. DEFINE_FIELD( m_bLaunched, FIELD_BOOLEAN ),
  164. DEFINE_FIELD( m_bStruckEntity, FIELD_BOOLEAN ),
  165. DEFINE_FIELD( m_bWeaponLaunched, FIELD_BOOLEAN ),
  166. DEFINE_FIELD( m_bForward, FIELD_BOOLEAN ),
  167. DEFINE_FIELD( m_flSpeed, FIELD_FLOAT ),
  168. DEFINE_FIELD( m_flNextDamageTime, FIELD_TIME ),
  169. DEFINE_FIELD( m_flLastCaptureTime, FIELD_TIME ),
  170. DEFINE_FIELD( m_bCaptureInProgress, FIELD_BOOLEAN ),
  171. DEFINE_FIELD( m_nBounceCount, FIELD_INTEGER ),
  172. DEFINE_FIELD( m_nMaxBounces, FIELD_INTEGER ),
  173. DEFINE_FIELD( m_bBounceDie, FIELD_BOOLEAN ),
  174. DEFINE_FIELD( m_hSpawner, FIELD_EHANDLE ),
  175. DEFINE_THINKFUNC( ExplodeThink ),
  176. DEFINE_THINKFUNC( WhizSoundThink ),
  177. DEFINE_THINKFUNC( DieThink ),
  178. DEFINE_THINKFUNC( DissolveThink ),
  179. DEFINE_THINKFUNC( DissolveRampSoundThink ),
  180. DEFINE_THINKFUNC( AnimThink ),
  181. DEFINE_THINKFUNC( CaptureBySpawner ),
  182. DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ),
  183. DEFINE_INPUTFUNC( FIELD_VOID, "FadeAndRespawn", InputFadeAndRespawn ),
  184. DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ),
  185. DEFINE_INPUTFUNC( FIELD_VOID, "Socketed", InputSocketed ),
  186. END_DATADESC()
  187. IMPLEMENT_SERVERCLASS_ST( CPropCombineBall, DT_PropCombineBall )
  188. SendPropBool( SENDINFO( m_bEmit ) ),
  189. SendPropFloat( SENDINFO( m_flRadius ), 0, SPROP_NOSCALE ),
  190. SendPropBool( SENDINFO( m_bHeld ) ),
  191. SendPropBool( SENDINFO( m_bLaunched ) ),
  192. END_SEND_TABLE()
  193. //-----------------------------------------------------------------------------
  194. // Gets at the spawner
  195. //-----------------------------------------------------------------------------
  196. CFuncCombineBallSpawner *CPropCombineBall::GetSpawner()
  197. {
  198. return m_hSpawner;
  199. }
  200. //-----------------------------------------------------------------------------
  201. // Precache
  202. //-----------------------------------------------------------------------------
  203. void CPropCombineBall::Precache( void )
  204. {
  205. //NOTENOTE: We don't call into the base class because it chains multiple
  206. // precaches we don't need to incur
  207. PrecacheModel( PROP_COMBINE_BALL_MODEL );
  208. PrecacheModel( PROP_COMBINE_BALL_SPRITE_TRAIL );
  209. s_nExplosionTexture = PrecacheModel( "sprites/lgtning.vmt" );
  210. PrecacheScriptSound( "NPC_CombineBall.Launch" );
  211. PrecacheScriptSound( "NPC_CombineBall.KillImpact" );
  212. if ( hl2_episodic.GetBool() )
  213. {
  214. PrecacheScriptSound( "NPC_CombineBall_Episodic.Explosion" );
  215. PrecacheScriptSound( "NPC_CombineBall_Episodic.WhizFlyby" );
  216. PrecacheScriptSound( "NPC_CombineBall_Episodic.Impact" );
  217. }
  218. else
  219. {
  220. PrecacheScriptSound( "NPC_CombineBall.Explosion" );
  221. PrecacheScriptSound( "NPC_CombineBall.WhizFlyby" );
  222. PrecacheScriptSound( "NPC_CombineBall.Impact" );
  223. }
  224. PrecacheScriptSound( "NPC_CombineBall.HoldingInPhysCannon" );
  225. }
  226. //-----------------------------------------------------------------------------
  227. // Spherical vphysics
  228. //-----------------------------------------------------------------------------
  229. bool CPropCombineBall::OverridePropdata()
  230. {
  231. return true;
  232. }
  233. //-----------------------------------------------------------------------------
  234. // Spherical vphysics
  235. //-----------------------------------------------------------------------------
  236. void CPropCombineBall::SetState( int state )
  237. {
  238. if ( m_nState != state )
  239. {
  240. if ( m_nState == STATE_NOT_THROWN )
  241. {
  242. m_flLastCaptureTime = gpGlobals->curtime;
  243. }
  244. m_nState = state;
  245. }
  246. }
  247. bool CPropCombineBall::IsInField() const
  248. {
  249. return (m_nState == STATE_NOT_THROWN);
  250. }
  251. //-----------------------------------------------------------------------------
  252. // Sets the radius
  253. //-----------------------------------------------------------------------------
  254. void CPropCombineBall::SetRadius( float flRadius )
  255. {
  256. m_flRadius = clamp( flRadius, 1, MAX_COMBINEBALL_RADIUS );
  257. }
  258. //-----------------------------------------------------------------------------
  259. // Create vphysics
  260. //-----------------------------------------------------------------------------
  261. bool CPropCombineBall::CreateVPhysics()
  262. {
  263. SetSolid( SOLID_BBOX );
  264. float flSize = m_flRadius;
  265. SetCollisionBounds( Vector(-flSize, -flSize, -flSize), Vector(flSize, flSize, flSize) );
  266. objectparams_t params = g_PhysDefaultObjectParams;
  267. params.pGameData = static_cast<void *>(this);
  268. int nMaterialIndex = physprops->GetSurfaceIndex("metal_bouncy");
  269. IPhysicsObject *pPhysicsObject = physenv->CreateSphereObject( flSize, nMaterialIndex, GetAbsOrigin(), GetAbsAngles(), &params, false );
  270. if ( !pPhysicsObject )
  271. return false;
  272. VPhysicsSetObject( pPhysicsObject );
  273. SetMoveType( MOVETYPE_VPHYSICS );
  274. pPhysicsObject->Wake();
  275. pPhysicsObject->SetMass( 750.0f );
  276. pPhysicsObject->EnableGravity( false );
  277. pPhysicsObject->EnableDrag( false );
  278. float flDamping = 0.0f;
  279. float flAngDamping = 0.5f;
  280. pPhysicsObject->SetDamping( &flDamping, &flAngDamping );
  281. pPhysicsObject->SetInertia( Vector( 1e30, 1e30, 1e30 ) );
  282. if( WasFiredByNPC() )
  283. {
  284. // Don't do impact damage. Just touch them and do your dissolve damage and move on.
  285. PhysSetGameFlags( pPhysicsObject, FVPHYSICS_NO_NPC_IMPACT_DMG );
  286. }
  287. else
  288. {
  289. PhysSetGameFlags( pPhysicsObject, FVPHYSICS_DMG_DISSOLVE | FVPHYSICS_HEAVY_OBJECT );
  290. }
  291. return true;
  292. }
  293. //-----------------------------------------------------------------------------
  294. // Spawn:
  295. //-----------------------------------------------------------------------------
  296. void CPropCombineBall::Spawn( void )
  297. {
  298. BaseClass::Spawn();
  299. SetModel( PROP_COMBINE_BALL_MODEL );
  300. if( ShouldHitPlayer() )
  301. {
  302. // This allows the combine ball to hit the player.
  303. SetCollisionGroup( HL2COLLISION_GROUP_COMBINE_BALL_NPC );
  304. }
  305. else
  306. {
  307. SetCollisionGroup( HL2COLLISION_GROUP_COMBINE_BALL );
  308. }
  309. CreateVPhysics();
  310. Vector vecAbsVelocity = GetAbsVelocity();
  311. VPhysicsGetObject()->SetVelocity( &vecAbsVelocity, NULL );
  312. m_nState = STATE_NOT_THROWN;
  313. m_flLastBounceTime = -1.0f;
  314. m_bFiredGrabbedOutput = false;
  315. m_bForward = true;
  316. m_bCaptureInProgress = false;
  317. // No shadow!
  318. AddEffects( EF_NOSHADOW );
  319. // Start up the eye trail
  320. m_pGlowTrail = CSpriteTrail::SpriteTrailCreate( PROP_COMBINE_BALL_SPRITE_TRAIL, GetAbsOrigin(), false );
  321. if ( m_pGlowTrail != NULL )
  322. {
  323. m_pGlowTrail->FollowEntity( this );
  324. m_pGlowTrail->SetTransparency( kRenderTransAdd, 0, 0, 0, 255, kRenderFxNone );
  325. m_pGlowTrail->SetStartWidth( m_flRadius );
  326. m_pGlowTrail->SetEndWidth( 0 );
  327. m_pGlowTrail->SetLifeTime( 0.1f );
  328. m_pGlowTrail->TurnOff();
  329. }
  330. m_bEmit = true;
  331. m_bHeld = false;
  332. m_bLaunched = false;
  333. m_bStruckEntity = false;
  334. m_bWeaponLaunched = false;
  335. m_flNextDamageTime = gpGlobals->curtime;
  336. }
  337. //-----------------------------------------------------------------------------
  338. // Purpose:
  339. //-----------------------------------------------------------------------------
  340. void CPropCombineBall::StartAnimating( void )
  341. {
  342. // Start our animation cycle. Use the random to avoid everything thinking the same frame
  343. SetContextThink( &CPropCombineBall::AnimThink, gpGlobals->curtime + random->RandomFloat( 0.0f, 0.1f), s_pAnimThinkContext );
  344. int nSequence = LookupSequence( "idle" );
  345. SetCycle( 0 );
  346. m_flAnimTime = gpGlobals->curtime;
  347. ResetSequence( nSequence );
  348. ResetClientsideFrame();
  349. }
  350. //-----------------------------------------------------------------------------
  351. // Purpose:
  352. //-----------------------------------------------------------------------------
  353. void CPropCombineBall::StopAnimating( void )
  354. {
  355. SetContextThink( NULL, gpGlobals->curtime, s_pAnimThinkContext );
  356. }
  357. //-----------------------------------------------------------------------------
  358. // Put it into the spawner
  359. //-----------------------------------------------------------------------------
  360. void CPropCombineBall::CaptureBySpawner( )
  361. {
  362. m_bCaptureInProgress = true;
  363. m_bFiredGrabbedOutput = false;
  364. // Slow down the ball
  365. Vector vecVelocity;
  366. VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );
  367. float flSpeed = VectorNormalize( vecVelocity );
  368. if ( flSpeed > 25.0f )
  369. {
  370. vecVelocity *= flSpeed * 0.4f;
  371. VPhysicsGetObject()->SetVelocity( &vecVelocity, NULL );
  372. // Slow it down until we can set its velocity ok
  373. SetContextThink( &CPropCombineBall::CaptureBySpawner, gpGlobals->curtime + 0.01f, s_pCaptureContext );
  374. return;
  375. }
  376. // Ok, we're captured
  377. SetContextThink( NULL, gpGlobals->curtime, s_pCaptureContext );
  378. ReplaceInSpawner( GetSpawner()->GetBallSpeed() );
  379. m_bCaptureInProgress = false;
  380. }
  381. //-----------------------------------------------------------------------------
  382. // Put it into the spawner
  383. //-----------------------------------------------------------------------------
  384. void CPropCombineBall::ReplaceInSpawner( float flSpeed )
  385. {
  386. m_bForward = true;
  387. m_nState = STATE_NOT_THROWN;
  388. // Prevent it from exploding
  389. ClearLifetime( );
  390. // Stop whiz noises
  391. SetContextThink( NULL, gpGlobals->curtime, s_pWhizThinkContext );
  392. // Slam velocity to what the field wants
  393. Vector vecTarget, vecVelocity;
  394. GetSpawner()->GetTargetEndpoint( m_bForward, &vecTarget );
  395. VectorSubtract( vecTarget, GetAbsOrigin(), vecVelocity );
  396. VectorNormalize( vecVelocity );
  397. vecVelocity *= flSpeed;
  398. VPhysicsGetObject()->SetVelocity( &vecVelocity, NULL );
  399. // Set our desired speed to the spawner's speed. This will be
  400. // our speed on our first bounce in the field.
  401. SetSpeed( flSpeed );
  402. }
  403. float CPropCombineBall::LastCaptureTime() const
  404. {
  405. if ( IsInField() || IsBeingCaptured() )
  406. return gpGlobals->curtime;
  407. return m_flLastCaptureTime;
  408. }
  409. //-----------------------------------------------------------------------------
  410. // Purpose: Starts the lifetime countdown on the ball
  411. // Input : flDuration - number of seconds to live before exploding
  412. //-----------------------------------------------------------------------------
  413. void CPropCombineBall::StartLifetime( float flDuration )
  414. {
  415. SetContextThink( &CPropCombineBall::ExplodeThink, gpGlobals->curtime + flDuration, s_pExplodeTimerContext );
  416. }
  417. //-----------------------------------------------------------------------------
  418. // Purpose: Stops the lifetime on the ball from expiring
  419. //-----------------------------------------------------------------------------
  420. void CPropCombineBall::ClearLifetime( void )
  421. {
  422. // Prevent it from exploding
  423. SetContextThink( NULL, gpGlobals->curtime, s_pExplodeTimerContext );
  424. }
  425. //-----------------------------------------------------------------------------
  426. // Purpose:
  427. // Input : mass -
  428. //-----------------------------------------------------------------------------
  429. void CPropCombineBall::SetMass( float mass )
  430. {
  431. IPhysicsObject *pObj = VPhysicsGetObject();
  432. if ( pObj != NULL )
  433. {
  434. pObj->SetMass( mass );
  435. pObj->SetInertia( Vector( 500, 500, 500 ) );
  436. }
  437. }
  438. //-----------------------------------------------------------------------------
  439. // Purpose:
  440. //-----------------------------------------------------------------------------
  441. bool CPropCombineBall::ShouldHitPlayer() const
  442. {
  443. if ( GetOwnerEntity() )
  444. {
  445. CAI_BaseNPC *pNPC = GetOwnerEntity()->MyNPCPointer();
  446. if ( pNPC && !pNPC->IsPlayerAlly() )
  447. {
  448. return true;
  449. }
  450. }
  451. return false;
  452. }
  453. //-----------------------------------------------------------------------------
  454. // Purpose:
  455. //-----------------------------------------------------------------------------
  456. void CPropCombineBall::InputKill( inputdata_t &inputdata )
  457. {
  458. // tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
  459. CBaseEntity *pOwner = GetOwnerEntity();
  460. if ( pOwner )
  461. {
  462. pOwner->DeathNotice( this );
  463. SetOwnerEntity( NULL );
  464. }
  465. UTIL_Remove( this );
  466. NotifySpawnerOfRemoval();
  467. }
  468. //-----------------------------------------------------------------------------
  469. // Purpose:
  470. //-----------------------------------------------------------------------------
  471. void CPropCombineBall::InputSocketed( inputdata_t &inputdata )
  472. {
  473. // tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
  474. CBaseEntity *pOwner = GetOwnerEntity();
  475. if ( pOwner )
  476. {
  477. pOwner->DeathNotice( this );
  478. SetOwnerEntity( NULL );
  479. }
  480. // if our owner is a player, tell them we were socketed
  481. CHL2_Player *pPlayer = dynamic_cast<CHL2_Player *>( pOwner );
  482. if ( pPlayer )
  483. {
  484. pPlayer->CombineBallSocketed( this );
  485. }
  486. UTIL_Remove( this );
  487. NotifySpawnerOfRemoval();
  488. }
  489. //-----------------------------------------------------------------------------
  490. // Cleanup.
  491. //-----------------------------------------------------------------------------
  492. void CPropCombineBall::UpdateOnRemove()
  493. {
  494. if ( m_pGlowTrail != NULL )
  495. {
  496. UTIL_Remove( m_pGlowTrail );
  497. m_pGlowTrail = NULL;
  498. }
  499. //Sigh... this is the only place where I can get a message after the ball is done dissolving.
  500. if ( hl2_episodic.GetBool() )
  501. {
  502. if ( IsDissolving() )
  503. {
  504. if ( GetSpawner() )
  505. {
  506. GetSpawner()->BallGrabbed( this );
  507. NotifySpawnerOfRemoval();
  508. }
  509. }
  510. }
  511. BaseClass::UpdateOnRemove();
  512. }
  513. //-----------------------------------------------------------------------------
  514. // Purpose:
  515. //-----------------------------------------------------------------------------
  516. void CPropCombineBall::ExplodeThink( void )
  517. {
  518. DoExplosion();
  519. }
  520. //-----------------------------------------------------------------------------
  521. // Purpose: Tell the respawner to make a new one
  522. //-----------------------------------------------------------------------------
  523. void CPropCombineBall::NotifySpawnerOfRemoval( void )
  524. {
  525. if ( GetSpawner() )
  526. {
  527. GetSpawner()->RespawnBallPostExplosion();
  528. }
  529. }
  530. //-----------------------------------------------------------------------------
  531. // Fade out.
  532. //-----------------------------------------------------------------------------
  533. void CPropCombineBall::DieThink()
  534. {
  535. if ( GetSpawner() )
  536. {
  537. //Let the spawner know we died so it does it's thing
  538. if( hl2_episodic.GetBool() && IsInField() )
  539. {
  540. GetSpawner()->BallGrabbed( this );
  541. }
  542. GetSpawner()->RespawnBall( 0.1 );
  543. }
  544. UTIL_Remove( this );
  545. }
  546. //-----------------------------------------------------------------------------
  547. // Fade out.
  548. //-----------------------------------------------------------------------------
  549. void CPropCombineBall::FadeOut( float flDuration )
  550. {
  551. AddSolidFlags( FSOLID_NOT_SOLID );
  552. // Start up the eye trail
  553. if ( m_pGlowTrail != NULL )
  554. {
  555. m_pGlowTrail->SetBrightness( 0, flDuration );
  556. }
  557. SetThink( &CPropCombineBall::DieThink );
  558. SetNextThink( gpGlobals->curtime + flDuration );
  559. }
  560. //-----------------------------------------------------------------------------
  561. // Purpose:
  562. //-----------------------------------------------------------------------------
  563. void CPropCombineBall::StartWhizSoundThink( void )
  564. {
  565. SetContextThink( &CPropCombineBall::WhizSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pWhizThinkContext );
  566. }
  567. //-----------------------------------------------------------------------------
  568. // Danger sounds.
  569. //-----------------------------------------------------------------------------
  570. void CPropCombineBall::WhizSoundThink()
  571. {
  572. Vector vecPosition, vecVelocity;
  573. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  574. if ( pPhysicsObject == NULL )
  575. {
  576. //NOTENOTE: We should always have been created at this point
  577. Assert( 0 );
  578. SetContextThink( &CPropCombineBall::WhizSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pWhizThinkContext );
  579. return;
  580. }
  581. pPhysicsObject->GetPosition( &vecPosition, NULL );
  582. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  583. if ( gpGlobals->maxClients == 1 )
  584. {
  585. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  586. if ( pPlayer )
  587. {
  588. Vector vecDelta;
  589. VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta );
  590. VectorNormalize( vecDelta );
  591. if ( DotProduct( vecDelta, vecVelocity ) > 0.5f )
  592. {
  593. Vector vecEndPoint;
  594. VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint );
  595. float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint );
  596. if ( flDist < 200.0f )
  597. {
  598. CPASAttenuationFilter filter( vecPosition, ATTN_NORM );
  599. EmitSound_t ep;
  600. ep.m_nChannel = CHAN_STATIC;
  601. if ( hl2_episodic.GetBool() )
  602. {
  603. ep.m_pSoundName = "NPC_CombineBall_Episodic.WhizFlyby";
  604. }
  605. else
  606. {
  607. ep.m_pSoundName = "NPC_CombineBall.WhizFlyby";
  608. }
  609. ep.m_flVolume = 1.0f;
  610. ep.m_SoundLevel = SNDLVL_NORM;
  611. EmitSound( filter, entindex(), ep );
  612. SetContextThink( &CPropCombineBall::WhizSoundThink, gpGlobals->curtime + 0.5f, s_pWhizThinkContext );
  613. return;
  614. }
  615. }
  616. }
  617. }
  618. SetContextThink( &CPropCombineBall::WhizSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pWhizThinkContext );
  619. }
  620. //-----------------------------------------------------------------------------
  621. // Purpose:
  622. //-----------------------------------------------------------------------------
  623. void CPropCombineBall::SetBallAsLaunched( void )
  624. {
  625. // Give the ball a duration
  626. StartLifetime( PROP_COMBINE_BALL_LIFETIME );
  627. m_bHeld = false;
  628. m_bLaunched = true;
  629. SetState( STATE_THROWN );
  630. VPhysicsGetObject()->SetMass( 750.0f );
  631. VPhysicsGetObject()->SetInertia( Vector( 1e30, 1e30, 1e30 ) );
  632. StopLoopingSounds();
  633. EmitSound( "NPC_CombineBall.Launch" );
  634. WhizSoundThink();
  635. }
  636. //-----------------------------------------------------------------------------
  637. // Lighten the mass so it's zippy toget to the gun
  638. //-----------------------------------------------------------------------------
  639. void CPropCombineBall::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  640. {
  641. CDefaultPlayerPickupVPhysics::OnPhysGunPickup( pPhysGunUser, reason );
  642. if ( m_nMaxBounces == -1 )
  643. {
  644. m_nMaxBounces = 0;
  645. }
  646. if ( !m_bFiredGrabbedOutput )
  647. {
  648. if ( GetSpawner() )
  649. {
  650. GetSpawner()->BallGrabbed( this );
  651. }
  652. m_bFiredGrabbedOutput = true;
  653. }
  654. if ( m_pGlowTrail )
  655. {
  656. m_pGlowTrail->TurnOff();
  657. m_pGlowTrail->SetRenderColor( 0, 0, 0, 0 );
  658. }
  659. if ( reason != PUNTED_BY_CANNON )
  660. {
  661. SetState( STATE_HOLDING );
  662. CPASAttenuationFilter filter( GetAbsOrigin(), ATTN_NORM );
  663. filter.MakeReliable();
  664. EmitSound_t ep;
  665. ep.m_nChannel = CHAN_STATIC;
  666. if( hl2_episodic.GetBool() )
  667. {
  668. ep.m_pSoundName = "NPC_CombineBall_Episodic.HoldingInPhysCannon";
  669. }
  670. else
  671. {
  672. ep.m_pSoundName = "NPC_CombineBall.HoldingInPhysCannon";
  673. }
  674. ep.m_flVolume = 1.0f;
  675. ep.m_SoundLevel = SNDLVL_NORM;
  676. // Now we own this ball
  677. SetPlayerLaunched( pPhysGunUser );
  678. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  679. m_pHoldingSound = controller.SoundCreate( filter, entindex(), ep );
  680. controller.Play( m_pHoldingSound, 1.0f, 100 );
  681. // Don't collide with anything we may have to pull the ball through
  682. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  683. VPhysicsGetObject()->SetMass( 20.0f );
  684. VPhysicsGetObject()->SetInertia( Vector( 100, 100, 100 ) );
  685. // Make it not explode
  686. ClearLifetime( );
  687. m_bHeld = true;
  688. m_bLaunched = false;
  689. //Let the ball know is not being captured by one of those ball fields anymore.
  690. //
  691. m_bCaptureInProgress = false;
  692. SetContextThink( &CPropCombineBall::DissolveRampSoundThink, gpGlobals->curtime + GetBallHoldSoundRampTime(), s_pHoldDissolveContext );
  693. StartAnimating();
  694. }
  695. else
  696. {
  697. Vector vecVelocity;
  698. VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );
  699. SetSpeed( vecVelocity.Length() );
  700. // Set us as being launched by the player
  701. SetPlayerLaunched( pPhysGunUser );
  702. SetBallAsLaunched();
  703. StopAnimating();
  704. }
  705. }
  706. //-----------------------------------------------------------------------------
  707. // Purpose: Reset the ball to be deadly to NPCs after we've picked it up
  708. //-----------------------------------------------------------------------------
  709. void CPropCombineBall::SetPlayerLaunched( CBasePlayer *pOwner )
  710. {
  711. // Now we own this ball
  712. SetOwnerEntity( pOwner );
  713. SetWeaponLaunched( false );
  714. if( VPhysicsGetObject() )
  715. {
  716. PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_NO_NPC_IMPACT_DMG );
  717. PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_DMG_DISSOLVE | FVPHYSICS_HEAVY_OBJECT );
  718. }
  719. }
  720. //-----------------------------------------------------------------------------
  721. // Activate death-spin!
  722. //-----------------------------------------------------------------------------
  723. void CPropCombineBall::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  724. {
  725. CDefaultPlayerPickupVPhysics::OnPhysGunDrop( pPhysGunUser, Reason );
  726. SetState( STATE_THROWN );
  727. WhizSoundThink();
  728. m_bHeld = false;
  729. m_bLaunched = true;
  730. // Stop with the dissolving
  731. SetContextThink( NULL, gpGlobals->curtime, s_pHoldDissolveContext );
  732. // We're ready to start colliding again.
  733. SetCollisionGroup( HL2COLLISION_GROUP_COMBINE_BALL );
  734. if ( m_pGlowTrail )
  735. {
  736. m_pGlowTrail->TurnOn();
  737. m_pGlowTrail->SetRenderColor( 255, 255, 255, 255 );
  738. }
  739. // Set our desired speed to be launched at
  740. SetSpeed( 1500.0f );
  741. SetPlayerLaunched( pPhysGunUser );
  742. if ( Reason != LAUNCHED_BY_CANNON )
  743. {
  744. // Choose a random direction (forward facing)
  745. Vector vecForward;
  746. pPhysGunUser->GetVectors( &vecForward, NULL, NULL );
  747. QAngle shotAng;
  748. VectorAngles( vecForward, shotAng );
  749. // Offset by some small cone
  750. shotAng[PITCH] += random->RandomInt( -55, 55 );
  751. shotAng[YAW] += random->RandomInt( -55, 55 );
  752. AngleVectors( shotAng, &vecForward, NULL, NULL );
  753. vecForward *= GetSpeed();
  754. VPhysicsGetObject()->SetVelocity( &vecForward, &vec3_origin );
  755. }
  756. else
  757. {
  758. // This will have the consequence of making it so that the
  759. // ball is launched directly down the crosshair even if the player is moving.
  760. VPhysicsGetObject()->SetVelocity( &vec3_origin, &vec3_origin );
  761. }
  762. SetBallAsLaunched();
  763. StopAnimating();
  764. }
  765. //------------------------------------------------------------------------------
  766. // Stop looping sounds
  767. //------------------------------------------------------------------------------
  768. void CPropCombineBall::StopLoopingSounds()
  769. {
  770. if ( m_pHoldingSound )
  771. {
  772. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  773. controller.Shutdown( m_pHoldingSound );
  774. controller.SoundDestroy( m_pHoldingSound );
  775. m_pHoldingSound = NULL;
  776. }
  777. }
  778. //------------------------------------------------------------------------------
  779. // Pow!
  780. //------------------------------------------------------------------------------
  781. void CPropCombineBall::DissolveRampSoundThink( )
  782. {
  783. float dt = GetBallHoldDissolveTime() - GetBallHoldSoundRampTime();
  784. if ( m_pHoldingSound )
  785. {
  786. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  787. controller.SoundChangePitch( m_pHoldingSound, 150, dt );
  788. }
  789. SetContextThink( &CPropCombineBall::DissolveThink, gpGlobals->curtime + dt, s_pHoldDissolveContext );
  790. }
  791. //------------------------------------------------------------------------------
  792. // Pow!
  793. //------------------------------------------------------------------------------
  794. void CPropCombineBall::DissolveThink( )
  795. {
  796. DoExplosion();
  797. }
  798. //-----------------------------------------------------------------------------
  799. //-----------------------------------------------------------------------------
  800. float CPropCombineBall::GetBallHoldDissolveTime()
  801. {
  802. float flDissolveTime = PROP_COMBINE_BALL_HOLD_DISSOLVE_TIME;
  803. if( g_pGameRules->IsSkillLevel( 1 ) && hl2_episodic.GetBool() )
  804. {
  805. // Give players more time to handle/aim combine balls on Easy.
  806. flDissolveTime *= 1.5f;
  807. }
  808. return flDissolveTime;
  809. }
  810. //-----------------------------------------------------------------------------
  811. //-----------------------------------------------------------------------------
  812. float CPropCombineBall::GetBallHoldSoundRampTime()
  813. {
  814. return GetBallHoldDissolveTime() - 1.0f;
  815. }
  816. //------------------------------------------------------------------------------
  817. // Pow!
  818. //------------------------------------------------------------------------------
  819. void CPropCombineBall::DoExplosion( )
  820. {
  821. // don't do this twice
  822. if ( GetMoveType() == MOVETYPE_NONE )
  823. return;
  824. if ( PhysIsInCallback() )
  825. {
  826. g_PostSimulationQueue.QueueCall( this, &CPropCombineBall::DoExplosion );
  827. return;
  828. }
  829. // Tell the respawner to make a new one
  830. if ( GetSpawner() )
  831. {
  832. GetSpawner()->RespawnBallPostExplosion();
  833. }
  834. //Shockring
  835. CBroadcastRecipientFilter filter2;
  836. if ( OutOfBounces() == false )
  837. {
  838. if ( hl2_episodic.GetBool() )
  839. {
  840. EmitSound( "NPC_CombineBall_Episodic.Explosion" );
  841. }
  842. else
  843. {
  844. EmitSound( "NPC_CombineBall.Explosion" );
  845. }
  846. UTIL_ScreenShake( GetAbsOrigin(), 20.0f, 150.0, 1.0, 1250.0f, SHAKE_START );
  847. CEffectData data;
  848. data.m_vOrigin = GetAbsOrigin();
  849. DispatchEffect( "cball_explode", data );
  850. te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
  851. m_flRadius, //start radius
  852. 1024, //end radius
  853. s_nExplosionTexture, //texture
  854. 0, //halo index
  855. 0, //start frame
  856. 2, //framerate
  857. 0.2f, //life
  858. 64, //width
  859. 0, //spread
  860. 0, //amplitude
  861. 255, //r
  862. 255, //g
  863. 225, //b
  864. 32, //a
  865. 0, //speed
  866. FBEAM_FADEOUT
  867. );
  868. //Shockring
  869. te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
  870. m_flRadius, //start radius
  871. 1024, //end radius
  872. s_nExplosionTexture, //texture
  873. 0, //halo index
  874. 0, //start frame
  875. 2, //framerate
  876. 0.5f, //life
  877. 64, //width
  878. 0, //spread
  879. 0, //amplitude
  880. 255, //r
  881. 255, //g
  882. 225, //b
  883. 64, //a
  884. 0, //speed
  885. FBEAM_FADEOUT
  886. );
  887. }
  888. else
  889. {
  890. //Shockring
  891. te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
  892. 128, //start radius
  893. 384, //end radius
  894. s_nExplosionTexture, //texture
  895. 0, //halo index
  896. 0, //start frame
  897. 2, //framerate
  898. 0.25f, //life
  899. 48, //width
  900. 0, //spread
  901. 0, //amplitude
  902. 255, //r
  903. 255, //g
  904. 225, //b
  905. 64, //a
  906. 0, //speed
  907. FBEAM_FADEOUT
  908. );
  909. }
  910. if( hl2_episodic.GetBool() )
  911. {
  912. CSoundEnt::InsertSound( SOUND_COMBAT | SOUND_CONTEXT_EXPLOSION, WorldSpaceCenter(), 180.0f, 0.25, this );
  913. }
  914. // Turn us off and wait because we need our trails to finish up properly
  915. SetAbsVelocity( vec3_origin );
  916. SetMoveType( MOVETYPE_NONE );
  917. AddSolidFlags( FSOLID_NOT_SOLID );
  918. m_bEmit = false;
  919. if( !m_bStruckEntity && hl2_episodic.GetBool() && GetOwnerEntity() != NULL )
  920. {
  921. // Notify the player proxy that this combine ball missed so that it can fire an output.
  922. CHL2_Player *pPlayer = dynamic_cast<CHL2_Player *>( GetOwnerEntity() );
  923. if ( pPlayer )
  924. {
  925. pPlayer->MissedAR2AltFire();
  926. }
  927. }
  928. SetContextThink( &CPropCombineBall::SUB_Remove, gpGlobals->curtime + 0.5f, s_pRemoveContext );
  929. StopLoopingSounds();
  930. }
  931. //-----------------------------------------------------------------------------
  932. // Enable/disable
  933. //-----------------------------------------------------------------------------
  934. void CPropCombineBall::InputExplode( inputdata_t &inputdata )
  935. {
  936. DoExplosion();
  937. }
  938. //-----------------------------------------------------------------------------
  939. // Enable/disable
  940. //-----------------------------------------------------------------------------
  941. void CPropCombineBall::InputFadeAndRespawn( inputdata_t &inputdata )
  942. {
  943. FadeOut( 0.1f );
  944. }
  945. //-----------------------------------------------------------------------------
  946. // Purpose:
  947. //-----------------------------------------------------------------------------
  948. void CPropCombineBall::CollisionEventToTrace( int index, gamevcollisionevent_t *pEvent, trace_t &tr )
  949. {
  950. UTIL_ClearTrace( tr );
  951. pEvent->pInternalData->GetSurfaceNormal( tr.plane.normal );
  952. pEvent->pInternalData->GetContactPoint( tr.endpos );
  953. tr.plane.dist = DotProduct( tr.plane.normal, tr.endpos );
  954. VectorMA( tr.endpos, -1.0f, pEvent->preVelocity[index], tr.startpos );
  955. tr.m_pEnt = pEvent->pEntities[!index];
  956. tr.fraction = 0.01f; // spoof!
  957. }
  958. //-----------------------------------------------------------------------------
  959. //-----------------------------------------------------------------------------
  960. bool CPropCombineBall::DissolveEntity( CBaseEntity *pEntity )
  961. {
  962. if( pEntity->IsEFlagSet( EFL_NO_DISSOLVE ) )
  963. return false;
  964. #ifdef HL2MP
  965. if ( pEntity->IsPlayer() )
  966. {
  967. m_bStruckEntity = true;
  968. return false;
  969. }
  970. #endif
  971. if( !pEntity->IsNPC() && !(dynamic_cast<CRagdollProp*>(pEntity)) )
  972. return false;
  973. pEntity->GetBaseAnimating()->Dissolve( "", gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL );
  974. // Note that we've struck an entity
  975. m_bStruckEntity = true;
  976. // Force an NPC to not drop their weapon if dissolved
  977. // CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( pEntity );
  978. // if ( pBCC != NULL )
  979. // {
  980. // pEntity->AddSpawnFlags( SF_NPC_NO_WEAPON_DROP );
  981. // }
  982. return true;
  983. }
  984. //-----------------------------------------------------------------------------
  985. // Purpose:
  986. //-----------------------------------------------------------------------------
  987. void CPropCombineBall::OnHitEntity( CBaseEntity *pHitEntity, float flSpeed, int index, gamevcollisionevent_t *pEvent )
  988. {
  989. // Detonate on the strider + the bone followers in the strider
  990. if ( FClassnameIs( pHitEntity, "npc_strider" ) ||
  991. (pHitEntity->GetOwnerEntity() && FClassnameIs( pHitEntity->GetOwnerEntity(), "npc_strider" )) )
  992. {
  993. DoExplosion();
  994. return;
  995. }
  996. CTakeDamageInfo info( this, GetOwnerEntity(), GetAbsVelocity(), GetAbsOrigin(), sk_npc_dmg_combineball.GetFloat(), DMG_DISSOLVE );
  997. bool bIsDissolving = (pHitEntity->GetFlags() & FL_DISSOLVING) != 0;
  998. bool bShouldHit = pHitEntity->PassesDamageFilter( info );
  999. //One more check
  1000. //Combine soldiers are not allowed to hurt their friends with combine balls (they can still shoot and hurt each other with grenades).
  1001. CBaseCombatCharacter *pBCC = pHitEntity->MyCombatCharacterPointer();
  1002. if ( pBCC )
  1003. {
  1004. bShouldHit = pBCC->IRelationType( GetOwnerEntity() ) != D_LI;
  1005. }
  1006. if ( !bIsDissolving && bShouldHit == true )
  1007. {
  1008. if ( pHitEntity->PassesDamageFilter( info ) )
  1009. {
  1010. if( WasFiredByNPC() || m_nMaxBounces == -1 )
  1011. {
  1012. // Since Combine balls fired by NPCs do a metered dose of damage per impact, we have to ignore touches
  1013. // for a little while after we hit someone, or the ball will immediately touch them again and do more
  1014. // damage.
  1015. if( gpGlobals->curtime >= m_flNextDamageTime )
  1016. {
  1017. EmitSound( "NPC_CombineBall.KillImpact" );
  1018. if ( pHitEntity->IsNPC() && pHitEntity->Classify() != CLASS_PLAYER_ALLY_VITAL && hl2_episodic.GetBool() == true )
  1019. {
  1020. if ( pHitEntity->Classify() != CLASS_PLAYER_ALLY || ( pHitEntity->Classify() == CLASS_PLAYER_ALLY && m_bStruckEntity == false ) )
  1021. {
  1022. info.SetDamage( pHitEntity->GetMaxHealth() );
  1023. m_bStruckEntity = true;
  1024. }
  1025. }
  1026. else
  1027. {
  1028. // Ignore touches briefly.
  1029. m_flNextDamageTime = gpGlobals->curtime + 0.1f;
  1030. }
  1031. pHitEntity->TakeDamage( info );
  1032. }
  1033. }
  1034. else
  1035. {
  1036. if ( (m_nState == STATE_THROWN) && (pHitEntity->IsNPC() || dynamic_cast<CRagdollProp*>(pHitEntity) ))
  1037. {
  1038. EmitSound( "NPC_CombineBall.KillImpact" );
  1039. }
  1040. if ( (m_nState != STATE_HOLDING) )
  1041. {
  1042. CBasePlayer *pPlayer = ToBasePlayer( GetOwnerEntity() );
  1043. if ( pPlayer && UTIL_IsAR2CombineBall( this ) && ToBaseCombatCharacter( pHitEntity ) )
  1044. {
  1045. gamestats->Event_WeaponHit( pPlayer, false, "weapon_ar2", info );
  1046. }
  1047. DissolveEntity( pHitEntity );
  1048. if ( pHitEntity->ClassMatches( "npc_hunter" ) )
  1049. {
  1050. DoExplosion();
  1051. return;
  1052. }
  1053. }
  1054. }
  1055. }
  1056. }
  1057. Vector vecFinalVelocity;
  1058. if ( IsInField() )
  1059. {
  1060. // Don't deflect when in a spawner field
  1061. vecFinalVelocity = pEvent->preVelocity[index];
  1062. }
  1063. else
  1064. {
  1065. // Don't slow down when hitting other entities.
  1066. vecFinalVelocity = pEvent->postVelocity[index];
  1067. VectorNormalize( vecFinalVelocity );
  1068. vecFinalVelocity *= GetSpeed();
  1069. }
  1070. PhysCallbackSetVelocity( pEvent->pObjects[index], vecFinalVelocity );
  1071. }
  1072. //-----------------------------------------------------------------------------
  1073. // Purpose:
  1074. //-----------------------------------------------------------------------------
  1075. void CPropCombineBall::DoImpactEffect( const Vector &preVelocity, int index, gamevcollisionevent_t *pEvent )
  1076. {
  1077. // Do that crazy impact effect!
  1078. trace_t tr;
  1079. CollisionEventToTrace( !index, pEvent, tr );
  1080. CBaseEntity *pTraceEntity = pEvent->pEntities[index];
  1081. UTIL_TraceLine( tr.startpos - preVelocity * 2.0f, tr.startpos + preVelocity * 2.0f, MASK_SOLID, pTraceEntity, COLLISION_GROUP_NONE, &tr );
  1082. if ( tr.fraction < 1.0f )
  1083. {
  1084. // See if we hit the sky
  1085. if ( tr.surface.flags & SURF_SKY )
  1086. {
  1087. DoExplosion();
  1088. return;
  1089. }
  1090. // Send the effect over
  1091. CEffectData data;
  1092. data.m_flRadius = 16;
  1093. data.m_vNormal = tr.plane.normal;
  1094. data.m_vOrigin = tr.endpos + tr.plane.normal * 1.0f;
  1095. DispatchEffect( "cball_bounce", data );
  1096. }
  1097. if ( hl2_episodic.GetBool() )
  1098. {
  1099. EmitSound( "NPC_CombineBall_Episodic.Impact" );
  1100. }
  1101. else
  1102. {
  1103. EmitSound( "NPC_CombineBall.Impact" );
  1104. }
  1105. }
  1106. //-----------------------------------------------------------------------------
  1107. // Tells whether this combine ball should consider deflecting towards this entity.
  1108. //-----------------------------------------------------------------------------
  1109. bool CPropCombineBall::IsAttractiveTarget( CBaseEntity *pEntity )
  1110. {
  1111. if ( !pEntity->IsAlive() )
  1112. return false;
  1113. if ( pEntity->GetFlags() & EF_NODRAW )
  1114. return false;
  1115. // Don't guide toward striders
  1116. if ( FClassnameIs( pEntity, "npc_strider" ) )
  1117. return false;
  1118. if( WasFiredByNPC() )
  1119. {
  1120. // Fired by an NPC
  1121. if( !pEntity->IsNPC() && !pEntity->IsPlayer() )
  1122. return false;
  1123. // Don't seek entities of the same class.
  1124. if ( pEntity->m_iClassname == GetOwnerEntity()->m_iClassname )
  1125. return false;
  1126. }
  1127. else
  1128. {
  1129. #ifndef HL2MP
  1130. if ( GetOwnerEntity() )
  1131. {
  1132. // Things we check if this ball has an owner that's not an NPC.
  1133. if( GetOwnerEntity()->IsPlayer() )
  1134. {
  1135. if( pEntity->Classify() == CLASS_PLAYER ||
  1136. pEntity->Classify() == CLASS_PLAYER_ALLY ||
  1137. pEntity->Classify() == CLASS_PLAYER_ALLY_VITAL )
  1138. {
  1139. // Not attracted to other players or allies.
  1140. return false;
  1141. }
  1142. }
  1143. }
  1144. // The default case.
  1145. if ( !pEntity->IsNPC() )
  1146. return false;
  1147. if( pEntity->Classify() == CLASS_BULLSEYE )
  1148. return false;
  1149. #else
  1150. if ( pEntity->IsPlayer() == false )
  1151. return false;
  1152. if ( pEntity == GetOwnerEntity() )
  1153. return false;
  1154. //No tracking teammates in teammode!
  1155. if ( g_pGameRules->IsTeamplay() )
  1156. {
  1157. if ( g_pGameRules->PlayerRelationship( GetOwnerEntity(), pEntity ) == GR_TEAMMATE )
  1158. return false;
  1159. }
  1160. #endif
  1161. // We must be able to hit them
  1162. trace_t tr;
  1163. UTIL_TraceLine( WorldSpaceCenter(), pEntity->BodyTarget( WorldSpaceCenter() ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  1164. if ( tr.fraction < 1.0f && tr.m_pEnt != pEntity )
  1165. return false;
  1166. }
  1167. return true;
  1168. }
  1169. //-----------------------------------------------------------------------------
  1170. // Deflects the ball toward enemies in case of a collision
  1171. //-----------------------------------------------------------------------------
  1172. void CPropCombineBall::DeflectTowardEnemy( float flSpeed, int index, gamevcollisionevent_t *pEvent )
  1173. {
  1174. // Bounce toward a particular enemy; choose one that's closest to my new velocity.
  1175. Vector vecVelDir = pEvent->postVelocity[index];
  1176. VectorNormalize( vecVelDir );
  1177. CBaseEntity *pBestTarget = NULL;
  1178. Vector vecStartPoint;
  1179. pEvent->pInternalData->GetContactPoint( vecStartPoint );
  1180. float flBestDist = MAX_COORD_FLOAT;
  1181. CBaseEntity *list[1024];
  1182. Vector vecDelta;
  1183. float distance, flDot;
  1184. // If we've already hit something, get accurate
  1185. bool bSeekKill = m_bStruckEntity && (WasWeaponLaunched() || sk_combineball_seek_kill.GetInt() );
  1186. if ( bSeekKill )
  1187. {
  1188. int nCount = UTIL_EntitiesInSphere( list, 1024, GetAbsOrigin(), sk_combine_ball_search_radius.GetFloat(), FL_NPC | FL_CLIENT );
  1189. for ( int i = 0; i < nCount; i++ )
  1190. {
  1191. if ( !IsAttractiveTarget( list[i] ) )
  1192. continue;
  1193. VectorSubtract( list[i]->WorldSpaceCenter(), vecStartPoint, vecDelta );
  1194. distance = VectorNormalize( vecDelta );
  1195. if ( distance < flBestDist )
  1196. {
  1197. // Check our direction
  1198. if ( DotProduct( vecDelta, vecVelDir ) > 0.0f )
  1199. {
  1200. pBestTarget = list[i];
  1201. flBestDist = distance;
  1202. }
  1203. }
  1204. }
  1205. }
  1206. else
  1207. {
  1208. float flMaxDot = 0.966f;
  1209. if ( !WasWeaponLaunched() )
  1210. {
  1211. float flMaxDot = sk_combineball_seek_angle.GetFloat();
  1212. float flGuideFactor = sk_combineball_guidefactor.GetFloat();
  1213. for ( int i = m_nBounceCount; --i >= 0; )
  1214. {
  1215. flMaxDot *= flGuideFactor;
  1216. }
  1217. flMaxDot = cos( flMaxDot * M_PI / 180.0f );
  1218. if ( flMaxDot > 1.0f )
  1219. {
  1220. flMaxDot = 1.0f;
  1221. }
  1222. }
  1223. // Otherwise only help out a little
  1224. Vector extents = Vector(256, 256, 256);
  1225. Ray_t ray;
  1226. ray.Init( vecStartPoint, vecStartPoint + 2048 * vecVelDir, -extents, extents );
  1227. int nCount = UTIL_EntitiesAlongRay( list, 1024, ray, FL_NPC | FL_CLIENT );
  1228. for ( int i = 0; i < nCount; i++ )
  1229. {
  1230. if ( !IsAttractiveTarget( list[i] ) )
  1231. continue;
  1232. VectorSubtract( list[i]->WorldSpaceCenter(), vecStartPoint, vecDelta );
  1233. distance = VectorNormalize( vecDelta );
  1234. flDot = DotProduct( vecDelta, vecVelDir );
  1235. if ( flDot > flMaxDot )
  1236. {
  1237. if ( distance < flBestDist )
  1238. {
  1239. pBestTarget = list[i];
  1240. flBestDist = distance;
  1241. }
  1242. }
  1243. }
  1244. }
  1245. if ( pBestTarget )
  1246. {
  1247. Vector vecDelta;
  1248. VectorSubtract( pBestTarget->WorldSpaceCenter(), vecStartPoint, vecDelta );
  1249. VectorNormalize( vecDelta );
  1250. vecDelta *= GetSpeed();
  1251. PhysCallbackSetVelocity( pEvent->pObjects[index], vecDelta );
  1252. }
  1253. }
  1254. //-----------------------------------------------------------------------------
  1255. // Bounce inside the spawner:
  1256. //-----------------------------------------------------------------------------
  1257. void CPropCombineBall::BounceInSpawner( float flSpeed, int index, gamevcollisionevent_t *pEvent )
  1258. {
  1259. GetSpawner()->RegisterReflection( this, m_bForward );
  1260. m_bForward = !m_bForward;
  1261. Vector vecTarget;
  1262. GetSpawner()->GetTargetEndpoint( m_bForward, &vecTarget );
  1263. Vector vecVelocity;
  1264. VectorSubtract( vecTarget, GetAbsOrigin(), vecVelocity );
  1265. VectorNormalize( vecVelocity );
  1266. vecVelocity *= flSpeed;
  1267. PhysCallbackSetVelocity( pEvent->pObjects[index], vecVelocity );
  1268. }
  1269. //-----------------------------------------------------------------------------
  1270. // Purpose:
  1271. //-----------------------------------------------------------------------------
  1272. bool CPropCombineBall::IsHittableEntity( CBaseEntity *pHitEntity )
  1273. {
  1274. if ( pHitEntity->IsWorld() )
  1275. return false;
  1276. if ( pHitEntity->GetMoveType() == MOVETYPE_PUSH )
  1277. {
  1278. if( pHitEntity->GetOwnerEntity() && FClassnameIs(pHitEntity->GetOwnerEntity(), "npc_strider") )
  1279. {
  1280. // The Strider's Bone Followers are MOVETYPE_PUSH, and we want the combine ball to hit these.
  1281. return true;
  1282. }
  1283. // If the entity we hit can take damage, we're good
  1284. if ( pHitEntity->m_takedamage == DAMAGE_YES )
  1285. return true;
  1286. return false;
  1287. }
  1288. return true;
  1289. }
  1290. //-----------------------------------------------------------------------------
  1291. // Purpose:
  1292. //-----------------------------------------------------------------------------
  1293. void CPropCombineBall::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  1294. {
  1295. Vector preVelocity = pEvent->preVelocity[index];
  1296. float flSpeed = VectorNormalize( preVelocity );
  1297. if ( m_nMaxBounces == -1 )
  1298. {
  1299. const surfacedata_t *pHit = physprops->GetSurfaceData( pEvent->surfaceProps[!index] );
  1300. if( pHit->game.material != CHAR_TEX_FLESH || !hl2_episodic.GetBool() )
  1301. {
  1302. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  1303. if ( pHitEntity && IsHittableEntity( pHitEntity ) )
  1304. {
  1305. OnHitEntity( pHitEntity, flSpeed, index, pEvent );
  1306. }
  1307. // Remove self without affecting the object that was hit. (Unless it was flesh)
  1308. NotifySpawnerOfRemoval();
  1309. PhysCallbackRemove( this->NetworkProp() );
  1310. // disable dissolve damage so we don't kill off the player when he's the one we hit
  1311. PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_DMG_DISSOLVE );
  1312. return;
  1313. }
  1314. }
  1315. // Prevents impact sounds, effects, etc. when it's in the field
  1316. if ( !IsInField() )
  1317. {
  1318. BaseClass::VPhysicsCollision( index, pEvent );
  1319. }
  1320. if ( m_nState == STATE_HOLDING )
  1321. return;
  1322. // If we've collided going faster than our desired, then up our desired
  1323. if ( flSpeed > GetSpeed() )
  1324. {
  1325. SetSpeed( flSpeed );
  1326. }
  1327. // Make sure we don't slow down
  1328. Vector vecFinalVelocity = pEvent->postVelocity[index];
  1329. VectorNormalize( vecFinalVelocity );
  1330. vecFinalVelocity *= GetSpeed();
  1331. PhysCallbackSetVelocity( pEvent->pObjects[index], vecFinalVelocity );
  1332. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  1333. if ( pHitEntity && IsHittableEntity( pHitEntity ) )
  1334. {
  1335. OnHitEntity( pHitEntity, flSpeed, index, pEvent );
  1336. return;
  1337. }
  1338. if ( IsInField() )
  1339. {
  1340. if ( HasSpawnFlags( SF_COMBINE_BALL_BOUNCING_IN_SPAWNER ) && GetSpawner() )
  1341. {
  1342. BounceInSpawner( GetSpeed(), index, pEvent );
  1343. return;
  1344. }
  1345. PhysCallbackSetVelocity( pEvent->pObjects[index], vec3_origin );
  1346. // Delay the fade out so that we don't change our
  1347. // collision rules inside a vphysics callback.
  1348. variant_t emptyVariant;
  1349. g_EventQueue.AddEvent( this, "FadeAndRespawn", 0.01, NULL, NULL );
  1350. return;
  1351. }
  1352. if ( IsBeingCaptured() )
  1353. return;
  1354. // Do that crazy impact effect!
  1355. DoImpactEffect( preVelocity, index, pEvent );
  1356. // Only do the bounce so often
  1357. if ( gpGlobals->curtime - m_flLastBounceTime < 0.25f )
  1358. return;
  1359. // Save off our last bounce time
  1360. m_flLastBounceTime = gpGlobals->curtime;
  1361. // Reset the sound timer
  1362. SetContextThink( &CPropCombineBall::WhizSoundThink, gpGlobals->curtime + 0.01, s_pWhizThinkContext );
  1363. // Deflect towards nearby enemies
  1364. DeflectTowardEnemy( flSpeed, index, pEvent );
  1365. // Once more bounce
  1366. ++m_nBounceCount;
  1367. if ( OutOfBounces() && m_bBounceDie == false )
  1368. {
  1369. StartLifetime( 0.5 );
  1370. //Hack: Stop this from being called by doing this.
  1371. m_bBounceDie = true;
  1372. }
  1373. }
  1374. //-----------------------------------------------------------------------------
  1375. // Purpose:
  1376. //-----------------------------------------------------------------------------
  1377. void CPropCombineBall::AnimThink( void )
  1378. {
  1379. StudioFrameAdvance();
  1380. SetContextThink( &CPropCombineBall::AnimThink, gpGlobals->curtime + 0.1f, s_pAnimThinkContext );
  1381. }
  1382. //-----------------------------------------------------------------------------
  1383. //
  1384. // Implementation of CPropCombineBall
  1385. //
  1386. //-----------------------------------------------------------------------------
  1387. LINK_ENTITY_TO_CLASS( func_combine_ball_spawner, CFuncCombineBallSpawner );
  1388. //-----------------------------------------------------------------------------
  1389. // Save/load:
  1390. //-----------------------------------------------------------------------------
  1391. BEGIN_DATADESC( CFuncCombineBallSpawner )
  1392. DEFINE_KEYFIELD( m_nBallCount, FIELD_INTEGER, "ballcount" ),
  1393. DEFINE_KEYFIELD( m_flMinSpeed, FIELD_FLOAT, "minspeed" ),
  1394. DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "maxspeed" ),
  1395. DEFINE_KEYFIELD( m_flBallRadius, FIELD_FLOAT, "ballradius" ),
  1396. DEFINE_KEYFIELD( m_flBallRespawnTime, FIELD_FLOAT, "ballrespawntime" ),
  1397. DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
  1398. DEFINE_FIELD( m_nBallsRemainingInField, FIELD_INTEGER ),
  1399. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  1400. DEFINE_UTLVECTOR( m_BallRespawnTime, FIELD_TIME ),
  1401. DEFINE_FIELD( m_flDisableTime, FIELD_TIME ),
  1402. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  1403. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  1404. DEFINE_OUTPUT( m_OnBallGrabbed, "OnBallGrabbed" ),
  1405. DEFINE_OUTPUT( m_OnBallReinserted, "OnBallReinserted" ),
  1406. DEFINE_OUTPUT( m_OnBallHitTopSide, "OnBallHitTopSide" ),
  1407. DEFINE_OUTPUT( m_OnBallHitBottomSide, "OnBallHitBottomSide" ),
  1408. DEFINE_OUTPUT( m_OnLastBallGrabbed, "OnLastBallGrabbed" ),
  1409. DEFINE_OUTPUT( m_OnFirstBallReinserted, "OnFirstBallReinserted" ),
  1410. DEFINE_THINKFUNC( BallThink ),
  1411. DEFINE_ENTITYFUNC( GrabBallTouch ),
  1412. END_DATADESC()
  1413. //-----------------------------------------------------------------------------
  1414. // Purpose: Constructor
  1415. //-----------------------------------------------------------------------------
  1416. CFuncCombineBallSpawner::CFuncCombineBallSpawner()
  1417. {
  1418. m_flBallRespawnTime = 0.0f;
  1419. m_flBallRadius = 20.0f;
  1420. m_flDisableTime = 0.0f;
  1421. m_bShooter = false;
  1422. }
  1423. //-----------------------------------------------------------------------------
  1424. // Spawn a ball
  1425. //-----------------------------------------------------------------------------
  1426. void CFuncCombineBallSpawner::SpawnBall()
  1427. {
  1428. CPropCombineBall *pBall = static_cast<CPropCombineBall*>( CreateEntityByName( "prop_combine_ball" ) );
  1429. float flRadius = m_flBallRadius;
  1430. pBall->SetRadius( flRadius );
  1431. Vector vecAbsOrigin;
  1432. ChoosePointInBox( &vecAbsOrigin );
  1433. Vector zaxis;
  1434. MatrixGetColumn( EntityToWorldTransform(), 2, zaxis );
  1435. VectorMA( vecAbsOrigin, flRadius, zaxis, vecAbsOrigin );
  1436. pBall->SetAbsOrigin( vecAbsOrigin );
  1437. pBall->SetSpawner( this );
  1438. float flSpeed = random->RandomFloat( m_flMinSpeed, m_flMaxSpeed );
  1439. zaxis *= flSpeed;
  1440. pBall->SetAbsVelocity( zaxis );
  1441. if ( HasSpawnFlags( SF_SPAWNER_POWER_SUPPLY ) )
  1442. {
  1443. pBall->AddSpawnFlags( SF_COMBINE_BALL_BOUNCING_IN_SPAWNER );
  1444. }
  1445. pBall->Spawn();
  1446. }
  1447. void CFuncCombineBallSpawner::Precache()
  1448. {
  1449. BaseClass::Precache();
  1450. UTIL_PrecacheOther( "prop_combine_ball" );
  1451. }
  1452. //-----------------------------------------------------------------------------
  1453. // Spawn
  1454. //-----------------------------------------------------------------------------
  1455. void CFuncCombineBallSpawner::Spawn()
  1456. {
  1457. BaseClass::Spawn();
  1458. Precache();
  1459. AddEffects( EF_NODRAW );
  1460. SetModel( STRING( GetModelName() ) );
  1461. SetSolid( SOLID_BSP );
  1462. AddSolidFlags( FSOLID_NOT_SOLID );
  1463. m_nBallsRemainingInField = m_nBallCount;
  1464. float flWidth = CollisionProp()->OBBSize().x;
  1465. float flHeight = CollisionProp()->OBBSize().y;
  1466. m_flRadius = MIN( flWidth, flHeight ) * 0.5f;
  1467. if ( m_flRadius <= 0.0f && m_bShooter == false )
  1468. {
  1469. Warning("Zero dimension func_combine_ball_spawner! Removing...\n");
  1470. UTIL_Remove( this );
  1471. return;
  1472. }
  1473. // Compute a respawn time
  1474. float flDeltaT = 1.0f;
  1475. if ( !( m_flMinSpeed == 0 && m_flMaxSpeed == 0 ) )
  1476. {
  1477. flDeltaT = (CollisionProp()->OBBSize().z - 2 * m_flBallRadius) / ((m_flMinSpeed + m_flMaxSpeed) * 0.5f);
  1478. flDeltaT /= m_nBallCount;
  1479. }
  1480. m_BallRespawnTime.EnsureCapacity( m_nBallCount );
  1481. for ( int i = 0; i < m_nBallCount; ++i )
  1482. {
  1483. RespawnBall( (float)i * flDeltaT );
  1484. }
  1485. m_bEnabled = true;
  1486. if ( HasSpawnFlags( SF_SPAWNER_START_DISABLED ) )
  1487. {
  1488. inputdata_t inputData;
  1489. InputDisable( inputData );
  1490. }
  1491. else
  1492. {
  1493. SetThink( &CFuncCombineBallSpawner::BallThink );
  1494. SetNextThink( gpGlobals->curtime + 0.1f );
  1495. }
  1496. }
  1497. //-----------------------------------------------------------------------------
  1498. // Enable/disable
  1499. //-----------------------------------------------------------------------------
  1500. void CFuncCombineBallSpawner::InputEnable( inputdata_t &inputdata )
  1501. {
  1502. if ( m_bEnabled )
  1503. return;
  1504. m_bEnabled = true;
  1505. m_flDisableTime = 0.0f;
  1506. for ( int i = m_BallRespawnTime.Count(); --i >= 0; )
  1507. {
  1508. m_BallRespawnTime[i] += gpGlobals->curtime;
  1509. }
  1510. SetThink( &CFuncCombineBallSpawner::BallThink );
  1511. SetNextThink( gpGlobals->curtime + 0.1f );
  1512. }
  1513. void CFuncCombineBallSpawner::InputDisable( inputdata_t &inputdata )
  1514. {
  1515. if ( !m_bEnabled )
  1516. return;
  1517. m_flDisableTime = gpGlobals->curtime;
  1518. m_bEnabled = false;
  1519. for ( int i = m_BallRespawnTime.Count(); --i >= 0; )
  1520. {
  1521. m_BallRespawnTime[i] -= gpGlobals->curtime;
  1522. }
  1523. SetThink( NULL );
  1524. }
  1525. //-----------------------------------------------------------------------------
  1526. // Choose a random point inside the cylinder
  1527. //-----------------------------------------------------------------------------
  1528. void CFuncCombineBallSpawner::ChoosePointInBox( Vector *pVecPoint )
  1529. {
  1530. float flXBoundary = ( CollisionProp()->OBBSize().x != 0 ) ? m_flBallRadius / CollisionProp()->OBBSize().x : 0.0f;
  1531. float flYBoundary = ( CollisionProp()->OBBSize().y != 0 ) ? m_flBallRadius / CollisionProp()->OBBSize().y : 0.0f;
  1532. if ( flXBoundary > 0.5f )
  1533. {
  1534. flXBoundary = 0.5f;
  1535. }
  1536. if ( flYBoundary > 0.5f )
  1537. {
  1538. flYBoundary = 0.5f;
  1539. }
  1540. CollisionProp()->RandomPointInBounds(
  1541. Vector( flXBoundary, flYBoundary, 0.0f ), Vector( 1.0f - flXBoundary, 1.0f - flYBoundary, 0.0f ), pVecPoint );
  1542. }
  1543. //-----------------------------------------------------------------------------
  1544. // Choose a random point inside the cylinder
  1545. //-----------------------------------------------------------------------------
  1546. void CFuncCombineBallSpawner::ChoosePointInCylinder( Vector *pVecPoint )
  1547. {
  1548. float flXRange = m_flRadius / CollisionProp()->OBBSize().x;
  1549. float flYRange = m_flRadius / CollisionProp()->OBBSize().y;
  1550. Vector vecEndPoint1, vecEndPoint2;
  1551. CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecEndPoint1 );
  1552. CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &vecEndPoint2 );
  1553. // Choose a point inside the cylinder
  1554. float flDistSq;
  1555. do
  1556. {
  1557. CollisionProp()->RandomPointInBounds(
  1558. Vector( 0.5f - flXRange, 0.5f - flYRange, 0.0f ),
  1559. Vector( 0.5f + flXRange, 0.5f + flYRange, 0.0f ),
  1560. pVecPoint );
  1561. flDistSq = CalcDistanceSqrToLine( *pVecPoint, vecEndPoint1, vecEndPoint2 );
  1562. } while ( flDistSq > m_flRadius * m_flRadius );
  1563. }
  1564. //-----------------------------------------------------------------------------
  1565. // Register that a reflection occurred
  1566. //-----------------------------------------------------------------------------
  1567. void CFuncCombineBallSpawner::RegisterReflection( CPropCombineBall *pBall, bool bForward )
  1568. {
  1569. if ( bForward )
  1570. {
  1571. m_OnBallHitTopSide.FireOutput( pBall, this );
  1572. }
  1573. else
  1574. {
  1575. m_OnBallHitBottomSide.FireOutput( pBall, this );
  1576. }
  1577. }
  1578. //-----------------------------------------------------------------------------
  1579. // Choose a random point on the
  1580. //-----------------------------------------------------------------------------
  1581. void CFuncCombineBallSpawner::GetTargetEndpoint( bool bForward, Vector *pVecEndPoint )
  1582. {
  1583. float flZValue = bForward ? 1.0f : 0.0f;
  1584. CollisionProp()->RandomPointInBounds(
  1585. Vector( 0.0f, 0.0f, flZValue ), Vector( 1.0f, 1.0f, flZValue ), pVecEndPoint );
  1586. }
  1587. //-----------------------------------------------------------------------------
  1588. // Fire ball grabbed output
  1589. //-----------------------------------------------------------------------------
  1590. void CFuncCombineBallSpawner::BallGrabbed( CBaseEntity *pCombineBall )
  1591. {
  1592. m_OnBallGrabbed.FireOutput( pCombineBall, this );
  1593. --m_nBallsRemainingInField;
  1594. if ( m_nBallsRemainingInField == 0 )
  1595. {
  1596. m_OnLastBallGrabbed.FireOutput( pCombineBall, this );
  1597. }
  1598. // Wait for another ball to touch this to re-power it up.
  1599. if ( HasSpawnFlags( SF_SPAWNER_POWER_SUPPLY ) )
  1600. {
  1601. AddSolidFlags( FSOLID_TRIGGER );
  1602. SetTouch( &CFuncCombineBallSpawner::GrabBallTouch );
  1603. }
  1604. // Stop the ball thinking in case it was in the middle of being captured (which could re-add incorrectly)
  1605. if ( pCombineBall != NULL )
  1606. {
  1607. pCombineBall->SetContextThink( NULL, gpGlobals->curtime, s_pCaptureContext );
  1608. }
  1609. }
  1610. //-----------------------------------------------------------------------------
  1611. // Fire ball grabbed output
  1612. //-----------------------------------------------------------------------------
  1613. void CFuncCombineBallSpawner::GrabBallTouch( CBaseEntity *pOther )
  1614. {
  1615. // Safety net for two balls hitting this at once
  1616. if ( m_nBallsRemainingInField >= m_nBallCount )
  1617. return;
  1618. if ( pOther->GetCollisionGroup() != HL2COLLISION_GROUP_COMBINE_BALL )
  1619. return;
  1620. CPropCombineBall *pBall = dynamic_cast<CPropCombineBall*>( pOther );
  1621. Assert( pBall );
  1622. // Don't grab AR2 alt-fire
  1623. if ( pBall->WasWeaponLaunched() || !pBall->VPhysicsGetObject() )
  1624. return;
  1625. // Don't grab balls that are already in the field..
  1626. if ( pBall->IsInField() )
  1627. return;
  1628. // Don't grab fading out balls...
  1629. if ( !pBall->IsSolid() )
  1630. return;
  1631. // Don't capture balls that were very recently in the field (breaks punting)
  1632. if ( gpGlobals->curtime - pBall->LastCaptureTime() < 0.5f )
  1633. return;
  1634. // Now we're bouncing in this spawner
  1635. pBall->AddSpawnFlags( SF_COMBINE_BALL_BOUNCING_IN_SPAWNER );
  1636. // Tell the respawner we're no longer its ball
  1637. pBall->NotifySpawnerOfRemoval();
  1638. pBall->SetOwnerEntity( NULL );
  1639. pBall->SetSpawner( this );
  1640. pBall->CaptureBySpawner();
  1641. ++m_nBallsRemainingInField;
  1642. if ( m_nBallsRemainingInField >= m_nBallCount )
  1643. {
  1644. RemoveSolidFlags( FSOLID_TRIGGER );
  1645. SetTouch( NULL );
  1646. }
  1647. m_OnBallReinserted.FireOutput( pBall, this );
  1648. if ( m_nBallsRemainingInField == 1 )
  1649. {
  1650. m_OnFirstBallReinserted.FireOutput( pBall, this );
  1651. }
  1652. }
  1653. //-----------------------------------------------------------------------------
  1654. // Get a speed for the ball to insert
  1655. //-----------------------------------------------------------------------------
  1656. float CFuncCombineBallSpawner::GetBallSpeed( ) const
  1657. {
  1658. return random->RandomFloat( m_flMinSpeed, m_flMaxSpeed );
  1659. }
  1660. //-----------------------------------------------------------------------------
  1661. // Balls call this when they've been removed from the spawner
  1662. //-----------------------------------------------------------------------------
  1663. void CFuncCombineBallSpawner::RespawnBall( float flRespawnTime )
  1664. {
  1665. // Insert the time in sorted order,
  1666. // which by definition means to always insert at the start
  1667. m_BallRespawnTime.AddToTail( gpGlobals->curtime + flRespawnTime - m_flDisableTime );
  1668. }
  1669. //-----------------------------------------------------------------------------
  1670. //
  1671. //-----------------------------------------------------------------------------
  1672. void CFuncCombineBallSpawner::RespawnBallPostExplosion( void )
  1673. {
  1674. if ( m_flBallRespawnTime < 0 )
  1675. return;
  1676. if ( m_flBallRespawnTime == 0.0f )
  1677. {
  1678. m_BallRespawnTime.AddToTail( gpGlobals->curtime + 4.0f - m_flDisableTime );
  1679. }
  1680. else
  1681. {
  1682. m_BallRespawnTime.AddToTail( gpGlobals->curtime + m_flBallRespawnTime - m_flDisableTime );
  1683. }
  1684. }
  1685. //-----------------------------------------------------------------------------
  1686. // Ball think
  1687. //-----------------------------------------------------------------------------
  1688. void CFuncCombineBallSpawner::BallThink()
  1689. {
  1690. for ( int i = m_BallRespawnTime.Count(); --i >= 0; )
  1691. {
  1692. if ( m_BallRespawnTime[i] < gpGlobals->curtime )
  1693. {
  1694. SpawnBall();
  1695. m_BallRespawnTime.FastRemove( i );
  1696. }
  1697. }
  1698. // There are no more to respawn
  1699. SetNextThink( gpGlobals->curtime + 0.1f );
  1700. }
  1701. BEGIN_DATADESC( CPointCombineBallLauncher )
  1702. DEFINE_KEYFIELD( m_flConeDegrees, FIELD_FLOAT, "launchconenoise" ),
  1703. DEFINE_KEYFIELD( m_iszBullseyeName, FIELD_STRING, "bullseyename" ),
  1704. DEFINE_KEYFIELD( m_iBounces, FIELD_INTEGER, "maxballbounces" ),
  1705. DEFINE_INPUTFUNC( FIELD_VOID, "LaunchBall", InputLaunchBall ),
  1706. END_DATADESC()
  1707. #define SF_COMBINE_BALL_LAUNCHER_ATTACH_BULLSEYE 0x00000001
  1708. #define SF_COMBINE_BALL_LAUNCHER_COLLIDE_PLAYER 0x00000002
  1709. LINK_ENTITY_TO_CLASS( point_combine_ball_launcher, CPointCombineBallLauncher );
  1710. CPointCombineBallLauncher::CPointCombineBallLauncher()
  1711. {
  1712. m_bShooter = true;
  1713. m_flConeDegrees = 0.0f;
  1714. m_iBounces = 0;
  1715. }
  1716. void CPointCombineBallLauncher::Spawn( void )
  1717. {
  1718. m_bShooter = true;
  1719. BaseClass::Spawn();
  1720. }
  1721. void CPointCombineBallLauncher::InputLaunchBall ( inputdata_t &inputdata )
  1722. {
  1723. SpawnBall();
  1724. }
  1725. //-----------------------------------------------------------------------------
  1726. // Spawn a ball
  1727. //-----------------------------------------------------------------------------
  1728. void CPointCombineBallLauncher::SpawnBall()
  1729. {
  1730. CPropCombineBall *pBall = static_cast<CPropCombineBall*>( CreateEntityByName( "prop_combine_ball" ) );
  1731. if ( pBall == NULL )
  1732. return;
  1733. float flRadius = m_flBallRadius;
  1734. pBall->SetRadius( flRadius );
  1735. Vector vecAbsOrigin = GetAbsOrigin();
  1736. Vector zaxis;
  1737. pBall->SetAbsOrigin( vecAbsOrigin );
  1738. pBall->SetSpawner( this );
  1739. float flSpeed = random->RandomFloat( m_flMinSpeed, m_flMaxSpeed );
  1740. Vector vDirection;
  1741. QAngle qAngle = GetAbsAngles();
  1742. qAngle = qAngle + QAngle ( random->RandomFloat( -m_flConeDegrees, m_flConeDegrees ), random->RandomFloat( -m_flConeDegrees, m_flConeDegrees ), 0 );
  1743. AngleVectors( qAngle, &vDirection, NULL, NULL );
  1744. vDirection *= flSpeed;
  1745. pBall->SetAbsVelocity( vDirection );
  1746. DispatchSpawn(pBall);
  1747. pBall->Activate();
  1748. pBall->SetState( CPropCombineBall::STATE_LAUNCHED );
  1749. pBall->SetMaxBounces( m_iBounces );
  1750. if ( HasSpawnFlags( SF_COMBINE_BALL_LAUNCHER_COLLIDE_PLAYER ) )
  1751. {
  1752. pBall->SetCollisionGroup( HL2COLLISION_GROUP_COMBINE_BALL_NPC );
  1753. }
  1754. if( GetSpawnFlags() & SF_COMBINE_BALL_LAUNCHER_ATTACH_BULLSEYE )
  1755. {
  1756. CNPC_Bullseye *pBullseye = static_cast<CNPC_Bullseye*>( CreateEntityByName( "npc_bullseye" ) );
  1757. if( pBullseye )
  1758. {
  1759. pBullseye->SetAbsOrigin( pBall->GetAbsOrigin() );
  1760. pBullseye->SetAbsAngles( QAngle( 0, 0, 0 ) );
  1761. pBullseye->KeyValue( "solid", "6" );
  1762. pBullseye->KeyValue( "targetname", STRING(m_iszBullseyeName) );
  1763. pBullseye->Spawn();
  1764. DispatchSpawn(pBullseye);
  1765. pBullseye->Activate();
  1766. pBullseye->SetParent(pBall);
  1767. pBullseye->SetHealth(10);
  1768. }
  1769. }
  1770. }
  1771. // ###################################################################
  1772. // > FilterClass
  1773. // ###################################################################
  1774. class CFilterCombineBall : public CBaseFilter
  1775. {
  1776. DECLARE_CLASS( CFilterCombineBall, CBaseFilter );
  1777. DECLARE_DATADESC();
  1778. public:
  1779. int m_iBallType;
  1780. bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity )
  1781. {
  1782. CPropCombineBall *pBall = dynamic_cast<CPropCombineBall*>(pEntity );
  1783. if ( pBall )
  1784. {
  1785. //Playtest HACK: If we have an NPC owner then we were shot from an AR2.
  1786. if ( pBall->GetOwnerEntity() && pBall->GetOwnerEntity()->IsNPC() )
  1787. return false;
  1788. return pBall->GetState() == m_iBallType;
  1789. }
  1790. return false;
  1791. }
  1792. };
  1793. LINK_ENTITY_TO_CLASS( filter_combineball_type, CFilterCombineBall );
  1794. BEGIN_DATADESC( CFilterCombineBall )
  1795. // Keyfields
  1796. DEFINE_KEYFIELD( m_iBallType, FIELD_INTEGER, "balltype" ),
  1797. END_DATADESC()