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.

1294 lines
36 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_weapon_bat.h"
  8. #include "decals.h"
  9. // Client specific.
  10. #ifdef CLIENT_DLL
  11. #include "c_basedoor.h"
  12. #include "c_tf_player.h"
  13. #include "IEffects.h"
  14. #include "bone_setup.h"
  15. #include "c_tf_gamestats.h"
  16. // Server specific.
  17. #else
  18. #include "doors.h"
  19. #include "tf_player.h"
  20. #include "tf_ammo_pack.h"
  21. #include "tf_gamestats.h"
  22. #include "ilagcompensationmanager.h"
  23. #include "collisionutils.h"
  24. #include "particle_parse.h"
  25. #include "tf_projectile_base.h"
  26. #include "tf_gamerules.h"
  27. #endif
  28. const float DEFAULT_ORNAMENT_EXPLODE_RADIUS = 50.0f;
  29. const float DEFAULT_ORNAMENT_EXPLODE_DAMAGE_MULT = 0.9f;
  30. //=============================================================================
  31. //
  32. // Weapon Bat tables.
  33. //
  34. // TFBat --
  35. IMPLEMENT_NETWORKCLASS_ALIASED( TFBat, DT_TFWeaponBat )
  36. BEGIN_NETWORK_TABLE( CTFBat, DT_TFWeaponBat )
  37. END_NETWORK_TABLE()
  38. BEGIN_PREDICTION_DATA( CTFBat )
  39. END_PREDICTION_DATA()
  40. LINK_ENTITY_TO_CLASS( tf_weapon_bat, CTFBat );
  41. PRECACHE_WEAPON_REGISTER( tf_weapon_bat );
  42. // -- TFBat
  43. // TFBat_Fish --
  44. IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Fish, DT_TFWeaponBat_Fish )
  45. BEGIN_NETWORK_TABLE( CTFBat_Fish, DT_TFWeaponBat_Fish )
  46. END_NETWORK_TABLE()
  47. BEGIN_PREDICTION_DATA( CTFBat_Fish )
  48. END_PREDICTION_DATA()
  49. LINK_ENTITY_TO_CLASS( tf_weapon_bat_fish, CTFBat_Fish );
  50. PRECACHE_WEAPON_REGISTER( tf_weapon_bat_fish );
  51. // -- TFBat_Fish
  52. // TFBat_Wood --
  53. IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Wood, DT_TFWeaponBat_Wood )
  54. BEGIN_NETWORK_TABLE( CTFBat_Wood, DT_TFWeaponBat_Wood )
  55. END_NETWORK_TABLE()
  56. BEGIN_PREDICTION_DATA( CTFBat_Wood )
  57. END_PREDICTION_DATA()
  58. LINK_ENTITY_TO_CLASS( tf_weapon_bat_wood, CTFBat_Wood );
  59. PRECACHE_WEAPON_REGISTER( tf_weapon_bat_wood );
  60. // -- TFBat_Wood
  61. // CTFBat_Giftwrap --
  62. IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Giftwrap, DT_TFWeaponBat_Giftwrap )
  63. BEGIN_NETWORK_TABLE( CTFBat_Giftwrap, DT_TFWeaponBat_Giftwrap )
  64. END_NETWORK_TABLE()
  65. BEGIN_PREDICTION_DATA( CTFBat_Giftwrap )
  66. END_PREDICTION_DATA()
  67. LINK_ENTITY_TO_CLASS( tf_weapon_bat_giftwrap, CTFBat_Giftwrap );
  68. PRECACHE_WEAPON_REGISTER( tf_weapon_bat_giftwrap );
  69. // -- CTFBat_Giftwrap
  70. // TFStunBall --
  71. IMPLEMENT_NETWORKCLASS_ALIASED( TFStunBall, DT_TFProjectile_StunBall )
  72. BEGIN_NETWORK_TABLE( CTFStunBall, DT_TFProjectile_StunBall )
  73. END_NETWORK_TABLE()
  74. LINK_ENTITY_TO_CLASS( tf_projectile_stun_ball, CTFStunBall );
  75. PRECACHE_WEAPON_REGISTER( tf_projectile_stun_ball );
  76. #define TF_WEAPON_STUNBALL_VM_MODEL "models/weapons/v_models/v_baseball.mdl"
  77. #define TF_WEAPON_STUNBALL_MODEL "models/weapons/w_models/w_baseball.mdl"
  78. #if defined( GAME_DLL )
  79. ConVar tf_scout_stunball_base_duration( "tf_scout_stunball_base_duration", "6.0", FCVAR_DEVELOPMENTONLY );
  80. ConVar tf_scout_stunball_base_speed( "tf_scout_stunball_base_speed", "3000", FCVAR_DEVELOPMENTONLY );
  81. ConVar sv_proj_stunball_damage( "sv_proj_stunball_damage", "15", FCVAR_DEVELOPMENTONLY );
  82. #endif
  83. // -- TFStunBall
  84. // CTFBall_Ornament --
  85. IMPLEMENT_NETWORKCLASS_ALIASED( TFBall_Ornament, DT_TFProjectileBall_Ornament )
  86. BEGIN_NETWORK_TABLE( CTFBall_Ornament, DT_TFProjectileBall_Ornament )
  87. END_NETWORK_TABLE()
  88. LINK_ENTITY_TO_CLASS( tf_projectile_ball_ornament, CTFBall_Ornament );
  89. PRECACHE_WEAPON_REGISTER( tf_projectile_ball_ornament );
  90. #define TF_WEAPON_BALL_ORNAMENT_VM_MODEL "models/weapons/c_models/c_xms_festive_ornament.mdl"
  91. #define TF_WEAPON_BALL_ORNAMENT_MODEL "models/weapons/c_models/c_xms_festive_ornament.mdl"
  92. #if defined( GAME_DLL )
  93. //ConVar tf_scout_stunball_base_duration( "tf_scout_stunball_base_duration", "6.0", FCVAR_DEVELOPMENTONLY );
  94. #endif
  95. // -- CTFBall_Ornament
  96. static string_t s_iszTrainName;
  97. //=============================================================================
  98. #define STUNBALL_TRAIL_ALPHA 128
  99. //=============================================================================
  100. //
  101. // CTFBat
  102. //
  103. //-----------------------------------------------------------------------------
  104. // Purpose:
  105. //-----------------------------------------------------------------------------
  106. CTFBat::CTFBat()
  107. {
  108. }
  109. //-----------------------------------------------------------------------------
  110. // Purpose:
  111. //-----------------------------------------------------------------------------
  112. void CTFBat::Smack( void )
  113. {
  114. BaseClass::Smack();
  115. #ifdef GAME_DLL
  116. if ( BatDeflects() )
  117. {
  118. #ifdef TF_RAID_MODE
  119. if ( TFGameRules()->IsRaidMode() )
  120. {
  121. }
  122. else
  123. #endif // TF_RAID_MODE
  124. {
  125. DeflectProjectiles();
  126. }
  127. }
  128. #endif
  129. }
  130. //-----------------------------------------------------------------------------
  131. // Purpose:
  132. //-----------------------------------------------------------------------------
  133. void CTFBat::PlayDeflectionSound( bool bPlayer )
  134. {
  135. WeaponSound( MELEE_HIT_WORLD );
  136. }
  137. //=============================================================================
  138. //
  139. // CTFBat_Wood
  140. //
  141. CTFBat_Wood::CTFBat_Wood()
  142. {
  143. m_iEnemyBallID = 0;
  144. #ifdef CLIENT_DLL
  145. m_hStunBallVM = NULL;
  146. #endif
  147. }
  148. //-----------------------------------------------------------------------------
  149. // Purpose:
  150. //-----------------------------------------------------------------------------
  151. ConVar tf_scout_bat_launch_delay( "tf_scout_bat_launch_delay", "0.1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  152. //-----------------------------------------------------------------------------
  153. // Purpose:
  154. //-----------------------------------------------------------------------------
  155. void CTFBat_Wood::LaunchBallThink( void )
  156. {
  157. CTFPlayer *pPlayer = GetTFPlayerOwner();
  158. if ( !pPlayer )
  159. return;
  160. LaunchBall();
  161. #ifdef GAME_DLL
  162. pPlayer->SpeakWeaponFire( MP_CONCEPT_BAT_BALL );
  163. CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  164. #endif
  165. #ifdef CLIENT_DLL
  166. C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  167. #endif
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Purpose:
  171. //-----------------------------------------------------------------------------
  172. void CTFBat_Wood::SecondaryAttackAnim( CTFPlayer *pPlayer )
  173. {
  174. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );
  175. }
  176. // SERVER ONLY --
  177. #ifdef GAME_DLL
  178. //-----------------------------------------------------------------------------
  179. // Purpose: Calculate the ball's initial position, angle, and velocity.
  180. //-----------------------------------------------------------------------------
  181. void CTFBat_Wood::GetBallDynamics( Vector& vecLoc, QAngle& vecAngles, Vector& vecVelocity, AngularImpulse& angImpulse, CTFPlayer* pPlayer )
  182. {
  183. Vector vecForward, vecUp;
  184. AngleVectors( pPlayer->EyeAngles(), &vecForward, NULL, &vecUp );
  185. vecLoc = pPlayer->GetAbsOrigin() + pPlayer->GetModelScale() * ( Vector( 0, 0, 50 ) + vecForward * 32.f );
  186. vecAngles = pPlayer->GetAbsAngles();
  187. // Calculate the initial impulse on the item.
  188. vecVelocity = Vector( 0.0f, 0.0f, 0.0f );
  189. vecVelocity += vecForward * 10;
  190. vecVelocity += vecUp * 1;
  191. VectorNormalize( vecVelocity );
  192. vecVelocity *= tf_scout_stunball_base_speed.GetInt();
  193. angImpulse = AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 );
  194. }
  195. // -- SERVER ONLY
  196. #endif
  197. //-----------------------------------------------------------------------------
  198. // Purpose:
  199. //-----------------------------------------------------------------------------
  200. void CTFBat_Wood::SecondaryAttack( void )
  201. {
  202. CTFPlayer *pPlayer = GetTFPlayerOwner();
  203. if ( !pPlayer )
  204. return;
  205. if ( !CanAttack() )
  206. return;
  207. if ( m_flNextPrimaryAttack > gpGlobals->curtime )
  208. return;
  209. // Do we have any balls? If so, use them.
  210. int iBallCount = pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 );
  211. if ( (iBallCount > 0) && CanCreateBall( pPlayer ) )
  212. {
  213. SecondaryAttackAnim( pPlayer );
  214. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  215. SetContextThink( &CTFBat_Wood::LaunchBallThink, gpGlobals->curtime + tf_scout_bat_launch_delay.GetFloat(), "LAUNCH_BALL_THINK" );
  216. m_flNextPrimaryAttack = gpGlobals->curtime + 0.25;
  217. #ifdef GAME_DLL
  218. if ( pPlayer->m_Shared.IsStealthed() )
  219. {
  220. pPlayer->RemoveInvisibility();
  221. }
  222. #endif // GAME_DLL
  223. }
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Purpose: Client Only. Show the stunball view model if necessary.
  227. //-----------------------------------------------------------------------------
  228. #ifdef CLIENT_DLL
  229. void CTFBat_Wood::SetWeaponVisible( bool visible )
  230. {
  231. BaseClass::SetWeaponVisible( visible );
  232. if ( !m_hStunBallVM )
  233. return;
  234. if ( visible )
  235. {
  236. m_hStunBallVM->RemoveEffects( EF_NODRAW );
  237. }
  238. else
  239. {
  240. m_hStunBallVM->AddEffects( EF_NODRAW );
  241. }
  242. }
  243. #endif
  244. #ifdef CLIENT_DLL
  245. //-----------------------------------------------------------------------------
  246. // Purpose:
  247. //-----------------------------------------------------------------------------
  248. void CTFBat_Wood::OnDataChanged( DataUpdateType_t updateType )
  249. {
  250. BaseClass::OnDataChanged( updateType );
  251. bool bLocalPlayerAmmo = true;
  252. if ( GetPlayerOwner() == C_BasePlayer::GetLocalPlayer() )
  253. {
  254. bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( TF_AMMO_GRENADES1 ) > 0;
  255. }
  256. if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true )
  257. {
  258. AddBallChild();
  259. }
  260. else
  261. {
  262. RemoveBallChild();
  263. }
  264. }
  265. //-----------------------------------------------------------------------------
  266. // Purpose: Client Only. Show the stunball view model if necessary.
  267. //-----------------------------------------------------------------------------
  268. void CTFBat_Wood::AddBallChild( void )
  269. {
  270. CTFPlayer *pPlayer = GetTFPlayerOwner();
  271. if ( !pPlayer )
  272. return;
  273. if ( !pPlayer->IsLocalPlayer() )
  274. return;
  275. if ( !pPlayer->GetViewModel() )
  276. return;
  277. if ( m_hStunBallVM )
  278. return;
  279. CTFViewModel* pBall = new class CTFViewModel();
  280. if ( pBall != NULL )
  281. {
  282. pBall->InitializeAsClientEntity( GetBallViewModelName(), RENDER_GROUP_OPAQUE_ENTITY );
  283. pBall->SetAbsOrigin( pPlayer->GetViewModel()->GetAbsOrigin() );
  284. pBall->SetModel( GetBallViewModelName() );
  285. pBall->m_nSkin = ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
  286. CStudioHdr *pStudioHdr = pPlayer->GetViewModel()->GetModelPtr();
  287. if ( pStudioHdr )
  288. {
  289. int iAttachment = Studio_FindAttachment( pStudioHdr, "weapon_bone_L" ) + 1;
  290. pBall->SetParent( pPlayer->GetViewModel(), iAttachment );
  291. }
  292. pBall->AddEffects( EF_BONEMERGE );
  293. pBall->SetMoveType( MOVETYPE_NONE );
  294. pBall->AddSolidFlags( FSOLID_NOT_SOLID );
  295. m_hStunBallVM.Set( pBall );
  296. }
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Purpose:
  300. //-----------------------------------------------------------------------------
  301. void CTFBat_Wood::Drop( const Vector &vecVelocity )
  302. {
  303. BaseClass::Drop( vecVelocity );
  304. RemoveBallChild();
  305. }
  306. //-----------------------------------------------------------------------------
  307. // Purpose:
  308. //-----------------------------------------------------------------------------
  309. void CTFBat_Wood::WeaponReset( void )
  310. {
  311. RemoveBallChild();
  312. BaseClass::WeaponReset();
  313. }
  314. //-----------------------------------------------------------------------------
  315. // Purpose:
  316. //-----------------------------------------------------------------------------
  317. void CTFBat_Wood::UpdateOnRemove( void )
  318. {
  319. RemoveBallChild();
  320. BaseClass::UpdateOnRemove();
  321. }
  322. //-----------------------------------------------------------------------------
  323. // Purpose:
  324. //-----------------------------------------------------------------------------
  325. void CTFBat_Wood::RemoveBallChild()
  326. {
  327. if ( m_hStunBallVM )
  328. {
  329. m_hStunBallVM->Remove();
  330. m_hStunBallVM = NULL;
  331. }
  332. }
  333. #endif
  334. //-----------------------------------------------------------------------------
  335. // Purpose: Determines if there is space to create a ball.
  336. //-----------------------------------------------------------------------------
  337. bool CTFBat_Wood::CanCreateBall( CTFPlayer* pPlayer )
  338. {
  339. int iWeaponMod = 0;
  340. CALL_ATTRIB_HOOK_INT( iWeaponMod, set_weapon_mode );
  341. if ( iWeaponMod == 0 )
  342. return false;
  343. if ( pPlayer->GetWaterLevel() == WL_Eyes )
  344. return false;
  345. Vector vecForward, vecUp;
  346. AngleVectors( pPlayer->EyeAngles(), &vecForward, NULL, &vecUp );
  347. Vector vecBallStart = pPlayer->GetAbsOrigin() + Vector( 0, 0, 50 );
  348. Vector vecBallEnd = vecBallStart + vecForward * 32.f;
  349. // Trace out and see if we hit a wall.
  350. trace_t trace;
  351. CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
  352. UTIL_TraceHull( vecBallStart, vecBallEnd, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
  353. if ( trace.DidHitWorld() || trace.startsolid )
  354. return false;
  355. else
  356. {
  357. if ( trace.m_pEnt )
  358. {
  359. // Don't let the player bat through doors.
  360. CBaseDoor *pDoor = dynamic_cast<CBaseDoor*>( trace.m_pEnt );
  361. if ( pDoor )
  362. return false;
  363. }
  364. return true;
  365. }
  366. }
  367. //-----------------------------------------------------------------------------
  368. // Purpose:
  369. //-----------------------------------------------------------------------------
  370. void CTFBat_Wood::LaunchBall( void )
  371. {
  372. CTFPlayer *pPlayer = GetTFPlayerOwner();
  373. if ( !pPlayer )
  374. return;
  375. #if GAME_DLL
  376. // Make a ball.
  377. CBaseEntity* pBall = CreateBall();
  378. if ( !pBall )
  379. return;
  380. if ( IsCurrentAttackACrit() )
  381. {
  382. WeaponSound( BURST );
  383. }
  384. WeaponSound( SPECIAL2 );
  385. pPlayer->RemoveAmmo( 1, TF_AMMO_GRENADES1 );
  386. #endif
  387. StartEffectBarRegen();
  388. }
  389. // SERVER ONLY --
  390. #ifdef GAME_DLL
  391. //-----------------------------------------------------------------------------
  392. // Purpose: The wooden bat creates a baseball that stuns whomever it hits.
  393. //-----------------------------------------------------------------------------
  394. CBaseEntity* CTFBat_Wood::CreateBall( void )
  395. {
  396. CTFPlayer *pPlayer = GetTFPlayerOwner();
  397. if ( !pPlayer )
  398. return NULL;
  399. // Do another check here, as the player may have moved to an invalid position
  400. // since the first check (0.1 seconds ago). This fixes the ball sometimes
  401. // going through thin geometry, such as windows and spawn blockers.
  402. if ( !CanCreateBall( pPlayer ) )
  403. return NULL;
  404. // Determine the ball's initial location, angles, and velocity.
  405. Vector vecLocation, vecVelocity;
  406. QAngle vecAngles;
  407. AngularImpulse angImpulse;
  408. GetBallDynamics( vecLocation, vecAngles, vecVelocity, angImpulse, pPlayer );
  409. // Create a stun ball.
  410. CTFStunBall* pBall = CTFStunBall::Create( vecLocation, vecAngles, pPlayer );
  411. Assert( pBall );
  412. if ( !pBall )
  413. return NULL;
  414. CalcIsAttackCritical();
  415. pBall->m_iOriginalOwnerID = m_iEnemyBallID;
  416. m_iEnemyBallID = 0;
  417. pBall->SetCritical( IsCurrentAttackACrit() );
  418. pBall->InitGrenade( vecVelocity, angImpulse, pPlayer, GetTFWpnData() );
  419. pBall->SetLauncher( this );
  420. pBall->SetOwnerEntity( pPlayer );
  421. pBall->SetInitialSpeed( tf_scout_stunball_base_speed.GetInt() );
  422. return pBall;
  423. }
  424. // -- SERVER ONLY
  425. #endif
  426. //-----------------------------------------------------------------------------
  427. // Purpose: Play pickup anim when we grab a new ball.
  428. //-----------------------------------------------------------------------------
  429. void CTFBat_Wood::PickedUpBall( void )
  430. {
  431. if ( WeaponState() == WEAPON_IS_ACTIVE )
  432. {
  433. SendWeaponAnim( ACT_VM_PULLBACK_SPECIAL );
  434. }
  435. }
  436. //-----------------------------------------------------------------------------
  437. // Purpose: Play animation appropriate to ball status.
  438. //-----------------------------------------------------------------------------
  439. bool CTFBat_Wood::SendWeaponAnim( int iActivity )
  440. {
  441. CTFPlayer *pPlayer = GetTFPlayerOwner();
  442. if ( !pPlayer )
  443. return BaseClass::SendWeaponAnim( iActivity );
  444. if ( pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ) > 0 )
  445. {
  446. switch ( iActivity )
  447. {
  448. case ACT_VM_DRAW:
  449. iActivity = ACT_VM_DRAW_SPECIAL;
  450. break;
  451. case ACT_VM_HOLSTER:
  452. iActivity = ACT_VM_HOLSTER_SPECIAL;
  453. break;
  454. case ACT_VM_IDLE:
  455. iActivity = ACT_VM_IDLE_SPECIAL;
  456. break;
  457. case ACT_VM_PULLBACK:
  458. iActivity = ACT_VM_PULLBACK_SPECIAL;
  459. break;
  460. case ACT_VM_PRIMARYATTACK:
  461. iActivity = ACT_VM_PRIMARYATTACK_SPECIAL;
  462. break;
  463. case ACT_VM_SECONDARYATTACK:
  464. iActivity = ACT_VM_PRIMARYATTACK_SPECIAL;
  465. break;
  466. case ACT_VM_HITCENTER:
  467. iActivity = ACT_VM_HITCENTER_SPECIAL;
  468. break;
  469. case ACT_VM_SWINGHARD:
  470. iActivity = ACT_VM_SWINGHARD_SPECIAL;
  471. break;
  472. case ACT_VM_IDLE_TO_LOWERED:
  473. iActivity = ACT_VM_IDLE_TO_LOWERED_SPECIAL;
  474. break;
  475. case ACT_VM_IDLE_LOWERED:
  476. iActivity = ACT_VM_IDLE_LOWERED_SPECIAL;
  477. break;
  478. case ACT_VM_LOWERED_TO_IDLE:
  479. iActivity = ACT_VM_LOWERED_TO_IDLE_SPECIAL;
  480. break;
  481. default:
  482. break;
  483. }
  484. }
  485. return BaseClass::SendWeaponAnim( iActivity );
  486. }
  487. //=============================================================================
  488. //
  489. // CTFStunBall
  490. //
  491. // SERVER ONLY --
  492. #ifdef GAME_DLL
  493. CTFStunBall::CTFStunBall()
  494. {
  495. s_iszTrainName = AllocPooledString( "models/props_vehicles/train_enginecar.mdl" );
  496. m_iOriginalOwnerID = 0;
  497. m_pBallTrail = NULL;
  498. m_flBallTrailLife = 1.0f;
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Purpose: Static entity factory.
  502. //-----------------------------------------------------------------------------
  503. CTFStunBall* CTFStunBall::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
  504. {
  505. CTFStunBall* pBall = static_cast<CTFStunBall*>( CBaseAnimating::CreateNoSpawn( "tf_projectile_stun_ball", vecOrigin, vecAngles, pOwner ) );
  506. if ( pBall )
  507. {
  508. DispatchSpawn( pBall );
  509. }
  510. return pBall;
  511. }
  512. //-----------------------------------------------------------------------------
  513. // Purpose:
  514. //-----------------------------------------------------------------------------
  515. void CTFStunBall::Precache( void )
  516. {
  517. PrecacheModel( GetBallModelName() );
  518. PrecacheModel( GetBallViewModelName() );
  519. PrecacheModel( "effects/baseballtrail_red.vmt" );
  520. PrecacheModel( "effects/baseballtrail_blu.vmt" );
  521. BaseClass::Precache();
  522. }
  523. //-----------------------------------------------------------------------------
  524. const char *CTFStunBall::GetBallModelName( void ) const
  525. {
  526. return TF_WEAPON_STUNBALL_MODEL;
  527. }
  528. //-----------------------------------------------------------------------------
  529. const char *CTFStunBall::GetBallViewModelName( void ) const
  530. {
  531. return TF_WEAPON_STUNBALL_VM_MODEL;
  532. }
  533. //-----------------------------------------------------------------------------
  534. // Purpose: Sets up initial properties.
  535. //-----------------------------------------------------------------------------
  536. void CTFStunBall::Spawn( void )
  537. {
  538. BaseClass::Spawn();
  539. SetModel( GetBallModelName() );
  540. VPhysicsDestroyObject();
  541. VPhysicsInitNormal( SOLID_BBOX, 0, false );
  542. AddSolidFlags( FSOLID_TRIGGER );
  543. AddFlag( FL_GRENADE );
  544. SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
  545. m_takedamage = DAMAGE_NO;
  546. SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 15, "DieContext" );
  547. // Draw the trail for the Baseball on spawn
  548. if ( !m_pBallTrail )
  549. {
  550. const char *pTrailTeamName = ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/baseballtrail_red.vmt" : "effects/baseballtrail_blu.vmt";
  551. CSpriteTrail *pTempTrail = NULL;
  552. pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, GetAbsOrigin(), true );
  553. pTempTrail->FollowEntity( this );
  554. pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, STUNBALL_TRAIL_ALPHA, kRenderFxNone );
  555. pTempTrail->SetStartWidth( 9 );
  556. pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
  557. pTempTrail->SetLifeTime( 0.4 );
  558. pTempTrail->TurnOn();
  559. pTempTrail->SetAttachment( this, 0 );
  560. m_pBallTrail = pTempTrail;
  561. SetContextThink( &CTFStunBall::RemoveBallTrail, gpGlobals->curtime + 3, "FadeBallTrail");
  562. }
  563. }
  564. //-----------------------------------------------------------------------------
  565. // Purpose:
  566. //-----------------------------------------------------------------------------
  567. void CTFStunBall::Explode( trace_t *pTrace, int bitsDamageType )
  568. {
  569. if ( !IsAllowedToExplode() )
  570. return;
  571. BaseClass::Explode( pTrace, bitsDamageType );
  572. }
  573. //-----------------------------------------------------------------------------
  574. // Purpose: Stun the person we smashed into.
  575. //-----------------------------------------------------------------------------
  576. #define FLIGHT_TIME_TO_MAX_STUN 1.f
  577. void CTFStunBall::ApplyBallImpactEffectOnVictim( CBaseEntity *pOther )
  578. {
  579. if ( !pOther || !pOther->IsPlayer() )
  580. return;
  581. CTFPlayer* pPlayer = ToTFPlayer( pOther );
  582. if ( !pPlayer )
  583. return;
  584. CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
  585. if ( !pOwner )
  586. return;
  587. if ( m_bTouched )
  588. return;
  589. // Can't stun an invul player.
  590. if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
  591. return;
  592. // We have a more intense stun based on our travel time.
  593. float flLifeTime = MIN( gpGlobals->curtime - m_flCreationTime, FLIGHT_TIME_TO_MAX_STUN );
  594. float flLifeTimeRatio = flLifeTime / FLIGHT_TIME_TO_MAX_STUN;
  595. if ( flLifeTimeRatio > 0.1f )
  596. {
  597. float flStun = 0.5f;
  598. float flStunDuration = tf_scout_stunball_base_duration.GetFloat() * flLifeTimeRatio;
  599. if ( IsCritical() )
  600. flStunDuration += 2.0; // Extra two seconds of effect time if we're a critical hit.
  601. int iStunFlags = TF_STUN_LOSER_STATE | TF_STUN_MOVEMENT;
  602. if ( flLifeTimeRatio >= 1.f )
  603. {
  604. flStunDuration += 1.0;
  605. iStunFlags = TF_STUN_CONTROLS;
  606. iStunFlags |= TF_STUN_SPECIAL_SOUND;
  607. CTF_GameStats.Event_PlayerStunBall( pOwner, true );
  608. }
  609. else
  610. {
  611. CTF_GameStats.Event_PlayerStunBall( pOwner, false );
  612. }
  613. // Adjust stun amount and flags if we're hitting a boss or scaled enemy
  614. if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && ( pPlayer->IsMiniBoss() || pPlayer->GetModelScale() > 1.0f ) )
  615. {
  616. // If max range, freeze them in place - otherwise adjust it based on distance
  617. flStun = flLifeTimeRatio >= 1.f ? 1.f : RemapValClamped( flLifeTimeRatio, 0.1f, 0.99f, 0.5f, 0.75 );
  618. iStunFlags = flLifeTimeRatio >= 1.f ? ( TF_STUN_SPECIAL_SOUND | TF_STUN_MOVEMENT ) : TF_STUN_MOVEMENT;
  619. }
  620. if ( pPlayer->GetWaterLevel() != WL_Eyes )
  621. {
  622. pPlayer->m_Shared.StunPlayer( flStunDuration, flStun, iStunFlags, pOwner );
  623. if ( pPlayer->GetUserID() == m_iOriginalOwnerID )
  624. {
  625. // Holy crap! We just stunned a scout with their own ball.
  626. // Give the player an achievement for this.
  627. if ( pOwner->IsPlayerClass( TF_CLASS_SCOUT ) )
  628. {
  629. pOwner->AwardAchievement( ACHIEVEMENT_TF_SCOUT_STUN_SCOUT_WITH_THEIR_BALL );
  630. }
  631. }
  632. }
  633. }
  634. // Give 'em a love tap.
  635. const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
  636. trace_t *pNewTrace = const_cast<trace_t*>( pTrace );
  637. CBaseEntity *pInflictor = GetLauncher();
  638. CTakeDamageInfo info;
  639. info.SetAttacker( GetOwnerEntity() );
  640. info.SetInflictor( pInflictor );
  641. info.SetWeapon( pInflictor );
  642. info.SetDamage( GetDamage() );
  643. info.SetDamageCustom( TF_DMG_CUSTOM_BASEBALL );
  644. info.SetDamageForce( GetDamageForce() );
  645. info.SetDamagePosition( GetAbsOrigin() );
  646. int iDamageType = GetDamageType();
  647. if ( IsCritical() )
  648. iDamageType |= DMG_CRITICAL;
  649. info.SetDamageType( iDamageType );
  650. // Hurt 'em.
  651. Vector dir;
  652. AngleVectors( GetAbsAngles(), &dir );
  653. pPlayer->DispatchTraceAttack( info, dir, pNewTrace );
  654. ApplyMultiDamage();
  655. // Make this ball fade faster now that it's hit something.
  656. SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 4, "DieContext" );
  657. m_bTouched = true;
  658. }
  659. float CTFStunBall::GetDamage( void )
  660. {
  661. return sv_proj_stunball_damage.GetFloat();
  662. }
  663. Vector CTFStunBall::GetDamageForce( void )
  664. {
  665. Vector vecVelocity = GetAbsVelocity();
  666. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  667. if ( pPhysicsObject )
  668. {
  669. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  670. VectorNormalize( vecVelocity );
  671. }
  672. return (vecVelocity * GetDamage());
  673. }
  674. //-----------------------------------------------------------------------------
  675. // Purpose: We hit something.
  676. //-----------------------------------------------------------------------------
  677. void CTFStunBall::PipebombTouch( CBaseEntity *pOther )
  678. {
  679. CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
  680. if ( !pOwner )
  681. return;
  682. if ( !pOther || !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
  683. {
  684. pOwner->SpeakConceptIfAllowed( MP_CONCEPT_BALL_MISSED );
  685. return;
  686. }
  687. // Go away if we're hit by a moving train.
  688. if ( pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) )
  689. {
  690. UTIL_Remove( this );
  691. return;
  692. }
  693. // Go away if we hit the skybox.
  694. trace_t pTrace;
  695. Vector velDir = GetAbsVelocity();
  696. VectorNormalize( velDir );
  697. Vector vecSpot = GetAbsOrigin() - velDir * 32;
  698. UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );
  699. if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY )
  700. {
  701. UTIL_Remove( this );
  702. return;
  703. }
  704. // Ignore things that aren't players.
  705. if ( !pOther->IsPlayer() )
  706. return;
  707. // If we hit a scout, pickup as ammo
  708. if ( m_bTouched )
  709. {
  710. CTFPlayer* pPlayer = ToTFPlayer( pOther );
  711. if ( pPlayer && pPlayer->IsPlayerClass( TF_CLASS_SCOUT ) &&
  712. (pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ) < pPlayer->GetMaxAmmo( TF_AMMO_GRENADES1 )) )
  713. {
  714. pPlayer->GiveAmmo( 1, TF_AMMO_GRENADES1 );
  715. RemoveBallTrail();
  716. UTIL_Remove( this );
  717. CTFBat_Wood *pBat = (CTFBat_Wood *) pPlayer->Weapon_OwnsThisID( TF_WEAPON_BAT_WOOD );
  718. if ( pBat )
  719. {
  720. // If this ball came from an enemy scout, remember who they were...
  721. if ( pPlayer->GetTeamNumber() != GetTeamNumber() )
  722. {
  723. if ( pOwner )
  724. {
  725. pBat->m_iEnemyBallID = pOwner->GetUserID();
  726. }
  727. }
  728. // If we have the bat up, we need to play the correct anim.
  729. pBat->PickedUpBall();
  730. }
  731. // Say something.
  732. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_GRAB_BALL, (pOther->GetTeamNumber() == GetTeamNumber()) ? "my_team:1" : "my_team:0" );
  733. }
  734. return;
  735. }
  736. if ( pOther == GetThrower() )
  737. return;
  738. if ( !InSameTeam( pOther ) && pOther->m_takedamage != DAMAGE_NO )
  739. {
  740. ApplyBallImpactEffectOnVictim( pOther );
  741. }
  742. }
  743. //-----------------------------------------------------------------------------
  744. // Purpose: We hit something.
  745. //-----------------------------------------------------------------------------
  746. void CTFStunBall::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  747. {
  748. CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
  749. bool bWasTouched = m_bTouched;
  750. BaseClass::VPhysicsCollision( index, pEvent );
  751. if ( pOwner && !bWasTouched && m_bTouched )
  752. {
  753. pOwner->SpeakConceptIfAllowed( MP_CONCEPT_BALL_MISSED );
  754. }
  755. }
  756. //-----------------------------------------------------------------------------
  757. // Purpose: Fade and kill the trail
  758. //-----------------------------------------------------------------------------
  759. void CTFStunBall::RemoveBallTrail( void )
  760. {
  761. if (!m_pBallTrail)
  762. return;
  763. if (m_pBallTrail)
  764. {
  765. if (m_flBallTrailLife <= 0)
  766. {
  767. UTIL_Remove( m_pBallTrail);
  768. m_flBallTrailLife = 1.0f;
  769. }
  770. else
  771. {
  772. float fAlpha = STUNBALL_TRAIL_ALPHA * m_flBallTrailLife;
  773. CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pBallTrail.Get() );
  774. if ( pTempTrail )
  775. {
  776. pTempTrail->SetBrightness( int(fAlpha) );
  777. }
  778. m_flBallTrailLife = m_flBallTrailLife - 0.1f;
  779. SetContextThink( &CTFStunBall::RemoveBallTrail, gpGlobals->curtime + 0.05, "FadeBallTrail");
  780. }
  781. }
  782. }
  783. // -- SERVER ONLY
  784. #endif
  785. #ifdef CLIENT_DLL
  786. //-----------------------------------------------------------------------------
  787. // Purpose:
  788. //-----------------------------------------------------------------------------
  789. const char *CTFStunBall::GetTrailParticleName( void )
  790. {
  791. int iTeamNumber = GetTeamNumber();
  792. if ( GetDeflected() )
  793. {
  794. CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() );
  795. if ( pOwner )
  796. {
  797. iTeamNumber = pOwner->GetTeamNumber();
  798. }
  799. }
  800. if ( iTeamNumber == TF_TEAM_BLUE )
  801. {
  802. return "stunballtrail_blue";
  803. }
  804. else
  805. {
  806. return "stunballtrail_red";
  807. }
  808. }
  809. //-----------------------------------------------------------------------------
  810. // Purpose:
  811. //-----------------------------------------------------------------------------
  812. void CTFStunBall::CreateTrailParticles( void )
  813. {
  814. if ( pEffectTrail )
  815. {
  816. ParticleProp()->StopEmission( pEffectTrail );
  817. }
  818. if ( pEffectCrit )
  819. {
  820. ParticleProp()->StopEmission( pEffectCrit );
  821. }
  822. pEffectTrail = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW );
  823. int iTeamNumber = GetTeamNumber();
  824. if ( GetDeflected() )
  825. {
  826. CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() );
  827. if ( pOwner )
  828. {
  829. iTeamNumber = pOwner->GetTeamNumber();
  830. }
  831. }
  832. if ( m_bCritical )
  833. {
  834. if ( iTeamNumber == TF_TEAM_BLUE )
  835. {
  836. pEffectCrit = ParticleProp()->Create( "stunballtrail_blue_crit", PATTACH_ABSORIGIN_FOLLOW );
  837. }
  838. else
  839. {
  840. pEffectCrit = ParticleProp()->Create( "stunballtrail_red_crit", PATTACH_ABSORIGIN_FOLLOW );
  841. }
  842. }
  843. }
  844. #endif
  845. //-----------------------------------------------------------------------------
  846. // Purpose:
  847. //-----------------------------------------------------------------------------
  848. void CTFBat_Giftwrap::Spawn( void )
  849. {
  850. BaseClass::Spawn();
  851. m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
  852. }
  853. #ifdef GAME_DLL
  854. //-----------------------------------------------------------------------------
  855. // Purpose:
  856. //-----------------------------------------------------------------------------
  857. CBaseEntity *CTFBat_Giftwrap::CreateBall( void )
  858. {
  859. CTFPlayer *pPlayer = GetTFPlayerOwner();
  860. if ( !pPlayer )
  861. return NULL;
  862. // Do another check here, as the player may have moved to an invalid position
  863. // since the first check (0.1 seconds ago). This fixes the ball sometimes
  864. // going through thin geometry, such as windows and spawn blockers.
  865. if ( !CanCreateBall( pPlayer ) )
  866. return NULL;
  867. // Determine the ball's initial location, angles, and velocity.
  868. Vector vecLocation, vecVelocity;
  869. QAngle vecAngles;
  870. AngularImpulse angImpulse;
  871. GetBallDynamics( vecLocation, vecAngles, vecVelocity, angImpulse, pPlayer );
  872. // Create the ornament ball.
  873. CTFBall_Ornament *pBall = CTFBall_Ornament::Create( vecLocation, vecAngles, pPlayer );
  874. Assert( pBall );
  875. if ( !pBall )
  876. return NULL;
  877. CalcIsAttackCritical();
  878. pBall->m_iOriginalOwnerID = m_iEnemyBallID;
  879. m_iEnemyBallID = 0;
  880. pBall->SetCritical( IsCurrentAttackACrit() );
  881. pBall->InitGrenade( vecVelocity, angImpulse, pPlayer, GetTFWpnData() );
  882. pBall->SetLauncher( this );
  883. pBall->SetOwnerEntity( pPlayer );
  884. pBall->SetInitialSpeed( tf_scout_stunball_base_speed.GetInt() );
  885. pBall->m_nSkin = ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
  886. return pBall;
  887. }
  888. //-----------------------------------------------------------------------------
  889. void CTFBall_Ornament::Precache( void )
  890. {
  891. PrecacheScriptSound( "BallBuster.OrnamentImpactRange" );
  892. PrecacheScriptSound( "BallBuster.OrnamentImpact" );
  893. PrecacheScriptSound( "BallBuster.HitBall" );
  894. PrecacheScriptSound( "BallBuster.HitFlesh" );
  895. PrecacheScriptSound( "BallBuster.HitWorld" );
  896. PrecacheScriptSound( "BallBuster.DrawCatch" );
  897. PrecacheScriptSound( "BallBuster.Ornament_DrawCatch" );
  898. PrecacheScriptSound( "BallBuster.Ball_HitWorld" );
  899. BaseClass::Precache();
  900. }
  901. //-----------------------------------------------------------------------------
  902. CTFBall_Ornament *CTFBall_Ornament::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
  903. {
  904. CTFBall_Ornament* pBall = static_cast< CTFBall_Ornament * >( CBaseAnimating::CreateNoSpawn( "tf_projectile_ball_ornament", vecOrigin, vecAngles, pOwner ) );
  905. if ( pBall )
  906. {
  907. DispatchSpawn( pBall );
  908. }
  909. return pBall;
  910. }
  911. //-----------------------------------------------------------------------------
  912. const char *CTFBall_Ornament::GetBallModelName( void ) const
  913. {
  914. return TF_WEAPON_BALL_ORNAMENT_MODEL;
  915. }
  916. //-----------------------------------------------------------------------------
  917. const char *CTFBall_Ornament::GetBallViewModelName( void ) const
  918. {
  919. return TF_WEAPON_BALL_ORNAMENT_VM_MODEL;
  920. }
  921. //-----------------------------------------------------------------------------
  922. // Purpose:
  923. //-----------------------------------------------------------------------------
  924. void CTFBall_Ornament::ApplyBallImpactEffectOnVictim( CBaseEntity *pOther )
  925. {
  926. if ( !pOther || !pOther->IsPlayer() )
  927. return;
  928. CTFPlayer* pPlayer = ToTFPlayer( pOther );
  929. if ( !pPlayer )
  930. return;
  931. CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
  932. if ( !pOwner )
  933. return;
  934. if ( m_bTouched )
  935. return;
  936. // Can't bleed an invul player.
  937. if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
  938. return;
  939. bool bIsCriticalHit = IsCritical();
  940. float flBleedTime = 5.0f;
  941. bool bIsLongRangeHit = false;
  942. // long distance hit is always a crit
  943. float flLifeTime = gpGlobals->curtime - m_flCreationTime;
  944. if ( flLifeTime >= FLIGHT_TIME_TO_MAX_STUN )
  945. {
  946. bIsCriticalHit = true;
  947. bIsLongRangeHit = true;
  948. }
  949. // just do the bleed effect directly since the bleed
  950. // attribute comes from the inflictor, which is the bat.
  951. pPlayer->m_Shared.MakeBleed( pOwner, (CTFBat_Giftwrap *)GetLauncher(), flBleedTime );
  952. // Apply particle effect to victim (the remaining effects happen inside Explode)
  953. DispatchParticleEffect( "xms_ornament_glitter", PATTACH_POINT_FOLLOW, pPlayer, "head" );
  954. // Give 'em a love tap.
  955. const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
  956. trace_t *pNewTrace = const_cast<trace_t*>( pTrace );
  957. CBaseEntity *pInflictor = GetLauncher();
  958. CTakeDamageInfo info;
  959. info.SetAttacker( GetOwnerEntity() );
  960. info.SetInflictor( pInflictor );
  961. info.SetWeapon( pInflictor );
  962. info.SetDamage( GetDamage() );
  963. info.SetDamageCustom( TF_DMG_CUSTOM_BASEBALL );
  964. info.SetDamageForce( GetDamageForce() );
  965. info.SetDamagePosition( GetAbsOrigin() );
  966. int iDamageType = GetDamageType();
  967. if ( bIsCriticalHit )
  968. iDamageType |= DMG_CRITICAL;
  969. info.SetDamageType( iDamageType );
  970. // Hurt 'em.
  971. Vector dir;
  972. AngleVectors( GetAbsAngles(), &dir );
  973. pPlayer->DispatchTraceAttack( info, dir, pNewTrace );
  974. ApplyMultiDamage();
  975. // the ball shatters
  976. UTIL_Remove( this );
  977. m_bTouched = true;
  978. }
  979. void CTFBall_Ornament::PipebombTouch( CBaseEntity *pOther )
  980. {
  981. CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
  982. if ( !pOwner )
  983. return;
  984. // Go away if we're hit by a moving train.
  985. if ( pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) )
  986. {
  987. UTIL_Remove( this );
  988. return;
  989. }
  990. // Go away if we hit the skybox.
  991. trace_t pTrace;
  992. Vector velDir = GetAbsVelocity();
  993. VectorNormalize( velDir );
  994. Vector vecSpot = GetAbsOrigin() - velDir * 32;
  995. UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );
  996. if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY )
  997. {
  998. UTIL_Remove( this );
  999. return;
  1000. }
  1001. if ( pOther == GetThrower() )
  1002. return;
  1003. // Explode (does radius damage, triggers particles and sound effects).
  1004. Explode( &pTrace, DMG_BLAST|DMG_PREVENT_PHYSICS_FORCE );
  1005. if ( !InSameTeam( pOther ) && pOther->m_takedamage != DAMAGE_NO )
  1006. {
  1007. ApplyBallImpactEffectOnVictim( pOther );
  1008. }
  1009. }
  1010. void CTFBall_Ornament::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  1011. {
  1012. BaseClass::VPhysicsCollision( index, pEvent );
  1013. int otherIndex = !index;
  1014. CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
  1015. if ( !pHitEntity )
  1016. return;
  1017. // Break if we hit the world.
  1018. if ( pHitEntity->IsWorld() )
  1019. {
  1020. // Explode immediately next frame. (Can't explode in the collision callback.)
  1021. m_vCollisionVelocity = pEvent->preVelocity[index];
  1022. SetContextThink( &CTFBall_Ornament::VPhysicsCollisionThink, gpGlobals->curtime, "OrnamentCollisionThink" );
  1023. }
  1024. }
  1025. void CTFBall_Ornament::VPhysicsCollisionThink( void )
  1026. {
  1027. trace_t pTrace;
  1028. Vector velDir = m_vCollisionVelocity;
  1029. VectorNormalize( velDir );
  1030. Vector vecSpot = GetAbsOrigin() - velDir * 16;
  1031. UTIL_TraceLine( vecSpot, vecSpot + velDir * 32, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );
  1032. Explode( &pTrace, DMG_BLAST|DMG_PREVENT_PHYSICS_FORCE );
  1033. }
  1034. void CTFBall_Ornament::Explode( trace_t *pTrace, int bitsDamageType )
  1035. {
  1036. // Create smashed glass particles when we explode
  1037. CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
  1038. if ( pOwner && pOwner->GetTeamNumber() == TF_TEAM_RED )
  1039. {
  1040. DispatchParticleEffect( "xms_ornament_smash_red", GetAbsOrigin(), GetAbsAngles() );
  1041. }
  1042. else
  1043. {
  1044. DispatchParticleEffect( "xms_ornament_smash_blue", GetAbsOrigin(), GetAbsAngles() );
  1045. }
  1046. Vector vecOrigin = GetAbsOrigin();
  1047. // sound effects
  1048. EmitSound_t params;
  1049. params.m_flSoundTime = 0;
  1050. params.m_pflSoundDuration = 0;
  1051. params.m_pSoundName = "BallBuster.OrnamentImpact";
  1052. CPASFilter filter( vecOrigin );
  1053. filter.RemoveRecipient( pOwner );
  1054. EmitSound( filter, entindex(), params );
  1055. CSingleUserRecipientFilter attackerFilter( pOwner );
  1056. EmitSound( attackerFilter, pOwner->entindex(), params );
  1057. // Explosion damage is some fraction of our base damage
  1058. float flExplodeDamage = GetDamage() * DEFAULT_ORNAMENT_EXPLODE_DAMAGE_MULT;
  1059. // Do radius damage
  1060. Vector vecBlastForce(0.0f, 0.0f, 0.0f);
  1061. CTakeDamageInfo info( this, GetThrower(), m_hLauncher, vecBlastForce, GetAbsOrigin(), flExplodeDamage, bitsDamageType, TF_DMG_CUSTOM_BASEBALL, &vecOrigin );
  1062. CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, DEFAULT_ORNAMENT_EXPLODE_RADIUS, nullptr, 0.0f, 0.0f );
  1063. TFGameRules()->RadiusDamage( radiusinfo );
  1064. UTIL_Remove( this );
  1065. }
  1066. #endif