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.

1573 lines
48 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // TF Arrow
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_projectile_arrow.h"
  8. #include "soundent.h"
  9. #include "tf_fx.h"
  10. #include "props.h"
  11. #include "baseobject_shared.h"
  12. #include "SpriteTrail.h"
  13. #include "IEffects.h"
  14. #include "te_effect_dispatch.h"
  15. #include "collisionutils.h"
  16. #include "bone_setup.h"
  17. #include "decals.h"
  18. #include "tf_player.h"
  19. #include "tf_gamestats.h"
  20. #include "tf_pumpkin_bomb.h"
  21. #include "tf_weapon_shovel.h"
  22. #include "player_vs_environment/tf_tank_boss.h"
  23. #include "halloween/halloween_base_boss.h"
  24. #include "halloween/merasmus/merasmus_trick_or_treat_prop.h"
  25. #include "tf_logic_robot_destruction.h"
  26. #include "tf_gamerules.h"
  27. #include "bot/tf_bot.h"
  28. #include "tf_weapon_medigun.h"
  29. #include "soundenvelope.h"
  30. //=============================================================================
  31. //
  32. // TF Arrow Projectile functions (Server specific).
  33. //
  34. #define ARROW_MODEL_GIB1 "models/weapons/w_models/w_arrow_gib1.mdl"
  35. #define ARROW_MODEL_GIB2 "models/weapons/w_models/w_arrow_gib2.mdl"
  36. #define ARROW_GRAVITY 0.3f
  37. #define ARROW_THINK_CONTEXT "CTFProjectile_ArrowThink"
  38. #define CLAW_TRAIL_RED "effects/repair_claw_trail_red.vmt"
  39. #define CLAW_TRAIL_BLU "effects/repair_claw_trail_blue.vmt"
  40. #define CLAW_GIB1 "models/weapons/w_models/w_repair_claw_gib1.mdl"
  41. #define CLAW_GIB2 "models/weapons/w_models/w_repair_claw_gib2.mdl"
  42. #define CLAW_REPAIR_EFFECT_BLU "repair_claw_heal_blue"
  43. #define CLAW_REPAIR_EFFECT_RED "repair_claw_heal_red"
  44. //-----------------------------------------------------------------------------
  45. LINK_ENTITY_TO_CLASS( tf_projectile_arrow, CTFProjectile_Arrow );
  46. PRECACHE_WEAPON_REGISTER( tf_projectile_arrow );
  47. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Arrow, DT_TFProjectile_Arrow )
  48. BEGIN_NETWORK_TABLE( CTFProjectile_Arrow, DT_TFProjectile_Arrow )
  49. SendPropBool( SENDINFO( m_bArrowAlight ) ),
  50. SendPropBool( SENDINFO( m_bCritical ) ),
  51. SendPropInt( SENDINFO( m_iProjectileType ) ),
  52. END_NETWORK_TABLE()
  53. BEGIN_DATADESC( CTFProjectile_Arrow )
  54. DEFINE_THINKFUNC( ImpactThink ),
  55. END_DATADESC()
  56. //-----------------------------------------------------------------------------
  57. LINK_ENTITY_TO_CLASS( tf_projectile_healing_bolt, CTFProjectile_HealingBolt );
  58. PRECACHE_WEAPON_REGISTER( tf_projectile_healing_bolt );
  59. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_HealingBolt, DT_TFProjectile_HealingBolt )
  60. BEGIN_NETWORK_TABLE( CTFProjectile_HealingBolt, DT_TFProjectile_HealingBolt )
  61. END_NETWORK_TABLE()
  62. BEGIN_DATADESC( CTFProjectile_HealingBolt )
  63. END_DATADESC()
  64. //-----------------------------------------------------------------------------
  65. LINK_ENTITY_TO_CLASS( tf_projectile_grapplinghook, CTFProjectile_GrapplingHook );
  66. PRECACHE_WEAPON_REGISTER( tf_projectile_grapplinghook );
  67. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook )
  68. BEGIN_NETWORK_TABLE( CTFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook )
  69. END_NETWORK_TABLE()
  70. BEGIN_DATADESC( CTFProjectile_GrapplingHook )
  71. END_DATADESC()
  72. //-----------------------------------------------------------------------------
  73. // Purpose: Helper to set a grappling hook target on all healers of this player
  74. //-----------------------------------------------------------------------------
  75. static void SetMedicsGrapplingHookTarget( CTFPlayer *pTFPlayer, CBaseEntity *pGrappleTarget )
  76. {
  77. int i;
  78. int iNumHealers = pTFPlayer->m_Shared.GetNumHealers();
  79. for ( i = 0 ; i < iNumHealers ; i++ )
  80. {
  81. CTFPlayer *pMedic = ToTFPlayer( pTFPlayer->m_Shared.GetHealerByIndex( i ) );
  82. // Only want medics who are directly healing us with their medigun, not e.g. AoE healers.
  83. if ( pMedic && ToTFPlayer ( pMedic->MedicGetHealTarget() ) == pTFPlayer )
  84. {
  85. pMedic->SetGrapplingHookTarget( pGrappleTarget );
  86. }
  87. }
  88. }
  89. //-----------------------------------------------------------------------------
  90. // Purpose:
  91. //-----------------------------------------------------------------------------
  92. CTFProjectile_Arrow::CTFProjectile_Arrow()
  93. {
  94. m_flImpactTime = 0.0f;
  95. m_flTrailLife = 0.f;
  96. m_pTrail = NULL;
  97. m_bStruckEnemy = false;
  98. m_bArrowAlight = false;
  99. m_iDeflected = 0;
  100. m_bCritical = false;
  101. m_flInitTime = 0;
  102. m_bPenetrate = false;
  103. m_iProjectileType = TF_PROJECTILE_ARROW;
  104. m_iWeaponId = TF_WEAPON_COMPOUND_BOW;
  105. }
  106. //-----------------------------------------------------------------------------
  107. // Purpose:
  108. //-----------------------------------------------------------------------------
  109. CTFProjectile_Arrow::~CTFProjectile_Arrow()
  110. {
  111. m_HitEntities.Purge();
  112. }
  113. static const char* GetArrowEntityName( ProjectileType_t projectileType )
  114. {
  115. switch ( projectileType )
  116. {
  117. case TF_PROJECTILE_HEALING_BOLT:
  118. case TF_PROJECTILE_FESTIVE_HEALING_BOLT:
  119. #ifdef STAGING_ONLY
  120. case TF_PROJECTILE_MILK_BOLT:
  121. #endif
  122. return "tf_projectile_healing_bolt";
  123. case TF_PROJECTILE_GRAPPLINGHOOK:
  124. return "tf_projectile_grapplinghook";
  125. default:
  126. return "tf_projectile_arrow";
  127. }
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Purpose:
  131. //-----------------------------------------------------------------------------
  132. CTFProjectile_Arrow *CTFProjectile_Arrow::Create( const Vector &vecOrigin, const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
  133. {
  134. const char* pszArrowEntityName = GetArrowEntityName( projectileType );
  135. CTFProjectile_Arrow *pArrow = static_cast<CTFProjectile_Arrow*>( CBaseEntity::Create( pszArrowEntityName, vecOrigin, vecAngles, pOwner ) );
  136. if ( pArrow )
  137. {
  138. pArrow->InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer );
  139. }
  140. return pArrow;
  141. }
  142. //-----------------------------------------------------------------------------
  143. // Purpose:
  144. //-----------------------------------------------------------------------------
  145. void CTFProjectile_Arrow::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
  146. {
  147. // Initialize the owner.
  148. SetOwnerEntity( pOwner );
  149. // Set team.
  150. ChangeTeam( pOwner->GetTeamNumber() );
  151. // must override projectile type before Spawn for proper model
  152. m_iProjectileType = projectileType;
  153. // Spawn.
  154. Spawn();
  155. SetGravity( fGravity );
  156. SetCritical( true );
  157. // Setup the initial velocity.
  158. Vector vecForward, vecRight, vecUp;
  159. AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
  160. Vector vecVelocity = vecForward * fSpeed;
  161. SetAbsVelocity( vecVelocity );
  162. SetupInitialTransmittedGrenadeVelocity( vecVelocity );
  163. // Setup the initial angles.
  164. QAngle angles;
  165. VectorAngles( vecVelocity, angles );
  166. SetAbsAngles( angles );
  167. // Save the scoring player.
  168. SetScorer( pScorer );
  169. // Create a trail.
  170. CreateTrail();
  171. // Add ourselves to the hit entities list so we dont shoot ourselves
  172. m_HitEntities.AddToTail( pOwner->entindex() );
  173. m_flInitTime = gpGlobals->curtime;
  174. #ifdef STAGING_ONLY
  175. if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET )
  176. {
  177. CTFPlayer* pTFOwner = ToTFPlayer( pOwner );
  178. m_bFiredWhileZoomed = ( pTFOwner && pTFOwner->m_Shared.InCond( TF_COND_ZOOMED ) );
  179. }
  180. else
  181. #endif // STAGING_ONLY
  182. {
  183. m_bFiredWhileZoomed = false;
  184. }
  185. }
  186. //-----------------------------------------------------------------------------
  187. // Purpose:
  188. //-----------------------------------------------------------------------------
  189. void CTFProjectile_Arrow::Spawn()
  190. {
  191. if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT )
  192. {
  193. SetModel( g_pszArrowModels[MODEL_ARROW_BUILDING_REPAIR] );
  194. m_iWeaponId = TF_WEAPON_SHOTGUN_BUILDING_RESCUE;
  195. }
  196. else if ( m_iProjectileType == TF_PROJECTILE_FESTIVE_ARROW )
  197. {
  198. SetModel( g_pszArrowModels[MODEL_FESTIVE_ARROW_REGULAR] );
  199. }
  200. else if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT
  201. #ifdef STAGING_ONLY
  202. || m_iProjectileType == TF_PROJECTILE_MILK_BOLT
  203. #endif
  204. ) {
  205. SetModel( g_pszArrowModels[MODEL_SYRINGE] );
  206. SetModelScale( 3.0f );
  207. }
  208. else if ( m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT )
  209. {
  210. SetModel( g_pszArrowModels[MODEL_FESTIVE_HEALING_BOLT] );
  211. SetModelScale( 3.0f );
  212. }
  213. #ifdef STAGING_ONLY
  214. else if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET )
  215. {
  216. SetModel( g_pszArrowModels[MODEL_SYRINGE] );
  217. //SetModelScale( 3.0f );
  218. }
  219. #endif // STAGING_ONLY
  220. else if ( m_iProjectileType == TF_PROJECTILE_GRAPPLINGHOOK )
  221. {
  222. SetModel( g_pszArrowModels[MODEL_GRAPPLINGHOOK] );
  223. }
  224. else
  225. {
  226. SetModel( g_pszArrowModels[MODEL_ARROW_REGULAR] );
  227. }
  228. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
  229. UTIL_SetSize( this, Vector( -1.0f, -1.0f, -1.0f ), Vector( 1.0f, 1.0f, 1.0f ) );
  230. SetSolid( SOLID_BBOX );
  231. SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS );
  232. AddEffects( EF_NOSHADOW );
  233. AddFlag( FL_GRENADE );
  234. SetTouch( &CTFProjectile_Arrow::ArrowTouch );
  235. // Set team.
  236. m_nSkin = GetArrowSkin();
  237. }
  238. //-----------------------------------------------------------------------------
  239. // Purpose:
  240. //-----------------------------------------------------------------------------
  241. void CTFProjectile_Arrow::Precache()
  242. {
  243. int arrow_model = PrecacheModel( g_pszArrowModels[MODEL_ARROW_REGULAR] );
  244. int claw_model = PrecacheModel( g_pszArrowModels[MODEL_ARROW_BUILDING_REPAIR] );
  245. int festive_arrow_model = PrecacheModel( g_pszArrowModels[MODEL_FESTIVE_ARROW_REGULAR] );
  246. PrecacheModel( g_pszArrowModels[MODEL_FESTIVE_HEALING_BOLT] );
  247. PrecacheGibsForModel( arrow_model );
  248. PrecacheGibsForModel( claw_model );
  249. PrecacheGibsForModel( festive_arrow_model );
  250. //PrecacheGibsForModel( festive_healing_arrow_model );
  251. PrecacheModel( "effects/arrowtrail_red.vmt" );
  252. PrecacheModel( "effects/arrowtrail_blu.vmt" );
  253. PrecacheModel( "effects/healingtrail_red.vmt" );
  254. PrecacheModel( "effects/healingtrail_blu.vmt" );
  255. PrecacheModel( CLAW_TRAIL_RED );
  256. PrecacheModel( CLAW_TRAIL_BLU );
  257. PrecacheParticleSystem( CLAW_REPAIR_EFFECT_BLU );
  258. PrecacheParticleSystem( CLAW_REPAIR_EFFECT_RED );
  259. PrecacheScriptSound( "Weapon_Arrow.ImpactFlesh" );
  260. PrecacheScriptSound( "Weapon_Arrow.ImpactMetal" );
  261. PrecacheScriptSound( "Weapon_Arrow.ImpactWood" );
  262. PrecacheScriptSound( "Weapon_Arrow.ImpactConcrete" );
  263. PrecacheScriptSound( "Weapon_Arrow.Nearmiss" );
  264. PrecacheScriptSound( "Weapon_Arrow.ImpactFleshCrossbowHeal" );
  265. BaseClass::Precache();
  266. }
  267. //-----------------------------------------------------------------------------
  268. // Purpose:
  269. //-----------------------------------------------------------------------------
  270. void CTFProjectile_Arrow::SetScorer( CBaseEntity *pScorer )
  271. {
  272. m_Scorer = pScorer;
  273. }
  274. //-----------------------------------------------------------------------------
  275. // Purpose:
  276. //-----------------------------------------------------------------------------
  277. CBasePlayer *CTFProjectile_Arrow::GetScorer( void )
  278. {
  279. return dynamic_cast<CBasePlayer *>( m_Scorer.Get() );
  280. }
  281. //-----------------------------------------------------------------------------
  282. bool CTFProjectile_Arrow::CanHeadshot()
  283. {
  284. CBaseEntity *pOwner = GetScorer();
  285. if ( pOwner == NULL )
  286. return false;
  287. if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT
  288. || m_iProjectileType == TF_PROJECTILE_HEALING_BOLT
  289. || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT
  290. #ifdef STAGING_ONLY
  291. || m_iProjectileType == TF_PROJECTILE_MILK_BOLT
  292. #endif
  293. ) {
  294. return false;
  295. }
  296. #ifdef STAGING_ONLY
  297. if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET )
  298. {
  299. return m_bFiredWhileZoomed;
  300. }
  301. #endif // STAGING_ONLY
  302. return true;
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Purpose: Healing bolt damage.
  306. //-----------------------------------------------------------------------------
  307. float CTFProjectile_Arrow::GetDamage()
  308. {
  309. if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT
  310. || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT
  311. #ifdef STAGING_ONLY
  312. || m_iProjectileType == TF_PROJECTILE_MILK_BOLT
  313. #endif
  314. ) {
  315. float lifeTimeScale = RemapValClamped( gpGlobals->curtime - m_flInitTime, 0.0f, 0.6f, 0.5f, 1.0f );
  316. return m_flDamage * lifeTimeScale;
  317. }
  318. return BaseClass::GetDamage();
  319. }
  320. //-----------------------------------------------------------------------------
  321. // Purpose: Moves the arrow to a particular bbox.
  322. //-----------------------------------------------------------------------------
  323. bool CTFProjectile_Arrow::PositionArrowOnBone( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim )
  324. {
  325. CStudioHdr *pStudioHdr = pOtherAnim->GetModelPtr();
  326. if ( !pStudioHdr )
  327. return false;
  328. mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pOtherAnim->GetHitboxSet() );
  329. if ( !set )
  330. return false;
  331. if ( !set->numhitboxes ) // Target must have hit boxes.
  332. return false;
  333. if ( pBox->bone < 0 || pBox->bone >= pStudioHdr->numbones() ) // Bone index must be valid.
  334. return false;
  335. CBoneCache *pCache = pOtherAnim->GetBoneCache();
  336. if ( !pCache )
  337. return false;
  338. matrix3x4_t *bone_matrix = pCache->GetCachedBone( pBox->bone );
  339. if ( !bone_matrix )
  340. return false;
  341. Vector vecBoxAbsMins, vecBoxAbsMaxs;
  342. TransformAABB( *bone_matrix, pBox->bbmin, pBox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs );
  343. // Adjust the arrow so it isn't exactly in the center of the box.
  344. Vector position;
  345. Vector vecDelta = vecBoxAbsMaxs - vecBoxAbsMins;
  346. float frand = (float) rand() / VALVE_RAND_MAX;
  347. position.x = vecBoxAbsMins.x + vecDelta.x*0.6f - vecDelta.x*frand*0.2f;
  348. frand = (float) rand() / VALVE_RAND_MAX;
  349. position.y = vecBoxAbsMins.y + vecDelta.y*0.6f - vecDelta.y*frand*0.2f;
  350. frand = (float) rand() / VALVE_RAND_MAX;
  351. position.z = vecBoxAbsMins.z + vecDelta.z*0.6f - vecDelta.z*frand*0.2f;
  352. SetAbsOrigin( position );
  353. return true;
  354. }
  355. //-----------------------------------------------------------------------------
  356. // Purpose: This was written after PositionArrowOnBone, but the two might be mergable?
  357. //-----------------------------------------------------------------------------
  358. void CTFProjectile_Arrow::GetBoneAttachmentInfo( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim, Vector &bonePosition, QAngle &boneAngles, int &boneIndexAttached, int &physicsBoneIndex )
  359. {
  360. // Find a bone to stick to.
  361. matrix3x4_t arrowWorldSpace;
  362. MatrixCopy( EntityToWorldTransform(), arrowWorldSpace );
  363. // Get the bone info so we can follow the bone.
  364. boneIndexAttached = pBox->bone;
  365. physicsBoneIndex = pOtherAnim->GetPhysicsBone( boneIndexAttached );
  366. matrix3x4_t boneToWorld;
  367. pOtherAnim->GetBoneTransform( boneIndexAttached, boneToWorld );
  368. Vector attachedBonePos;
  369. QAngle attachedBoneAngles;
  370. pOtherAnim->GetBonePosition( boneIndexAttached, attachedBonePos, attachedBoneAngles );
  371. // Transform my current position/orientation into the hit bone's space.
  372. matrix3x4_t worldToBone, localMatrix;
  373. MatrixInvert( boneToWorld, worldToBone );
  374. ConcatTransforms( worldToBone, arrowWorldSpace, localMatrix );
  375. MatrixAngles( localMatrix, boneAngles, bonePosition );
  376. }
  377. //-----------------------------------------------------------------------------
  378. int CTFProjectile_Arrow::GetProjectileType ( void ) const
  379. {
  380. return m_iProjectileType;
  381. }
  382. //-----------------------------------------------------------------------------
  383. // Purpose:
  384. //-----------------------------------------------------------------------------
  385. bool CTFProjectile_Arrow::StrikeTarget( mstudiobbox_t *pBox, CBaseEntity *pOther )
  386. {
  387. if ( !pOther )
  388. return false;
  389. // Different path for arrows that heal friendly buildings.
  390. if ( pOther->IsBaseObject() )
  391. {
  392. if ( OnArrowImpactObject( pOther ) )
  393. {
  394. return false;
  395. }
  396. }
  397. // Block and break on invulnerable players
  398. CTFPlayer *pTFPlayerOther = ToTFPlayer( pOther );
  399. if ( pTFPlayerOther && pTFPlayerOther->m_Shared.IsInvulnerable() )
  400. return false;
  401. CBaseAnimating *pOtherAnim = dynamic_cast< CBaseAnimating* >(pOther);
  402. if ( !pOtherAnim )
  403. return false;
  404. bool bBreakArrow = IsBreakable() && ( ( dynamic_cast< CTFTankBoss* >( pOther ) != NULL ) || ( dynamic_cast< CHalloweenBaseBoss* >( pOther ) != NULL ) );
  405. // Position the arrow so its on the bone, within a reasonable region defined by the bbox.
  406. if ( !m_bPenetrate && !bBreakArrow )
  407. {
  408. if ( !PositionArrowOnBone( pBox, pOtherAnim ) )
  409. {
  410. return false;
  411. }
  412. }
  413. //
  414. const Vector &vecOrigin = GetAbsOrigin();
  415. Vector vecVelocity = GetAbsVelocity();
  416. int nDamageCustom = 0;
  417. bool bApplyEffect = true;
  418. int nDamageType = GetDamageType();
  419. // Are we a headshot?
  420. bool bHeadshot = false;
  421. if ( pBox->group == HITGROUP_HEAD && CanHeadshot() )
  422. {
  423. bHeadshot = true;
  424. }
  425. // Damage the entity we struck.
  426. CBaseEntity *pAttacker = GetScorer();
  427. if ( !pAttacker )
  428. {
  429. // likely not launched by a player
  430. pAttacker = GetOwnerEntity();
  431. }
  432. if ( pAttacker )
  433. {
  434. // Check if we have the penetrate attribute. We don't want
  435. // to strike the same target multiple times.
  436. if ( m_bPenetrate )
  437. {
  438. // Don't strike the same target again
  439. if ( m_HitEntities.Find( pOther->entindex() ) != m_HitEntities.InvalidIndex() )
  440. {
  441. bApplyEffect = false;
  442. }
  443. else
  444. {
  445. m_HitEntities.AddToTail( pOther->entindex() );
  446. }
  447. }
  448. if ( !InSameTeam( pOther ) )
  449. {
  450. IScorer *pScorerInterface = dynamic_cast<IScorer*>( pAttacker );
  451. if ( pScorerInterface )
  452. {
  453. pAttacker = pScorerInterface->GetScorer();
  454. }
  455. if ( m_bArrowAlight )
  456. {
  457. nDamageType |= DMG_IGNITE;
  458. nDamageCustom = TF_DMG_CUSTOM_FLYINGBURN;
  459. }
  460. if ( bHeadshot )
  461. {
  462. nDamageType |= DMG_CRITICAL;
  463. nDamageCustom = TF_DMG_CUSTOM_HEADSHOT;
  464. }
  465. if ( m_bCritical )
  466. {
  467. nDamageType |= DMG_CRITICAL;
  468. }
  469. #ifdef GAME_DLL
  470. if ( TFGameRules()->IsPVEModeControlled( pAttacker ) )
  471. {
  472. // scenario bots cant crit (unless they always do)
  473. CTFBot *bot = ToTFBot( pAttacker );
  474. if ( !bot || !bot->HasAttribute( CTFBot::ALWAYS_CRIT ) )
  475. {
  476. nDamageType &= ~DMG_CRITICAL;
  477. }
  478. }
  479. #endif
  480. // Damage
  481. if ( bApplyEffect )
  482. {
  483. // Apply Milk First so we can get health from this
  484. if ( m_bApplyMilkOnHit && pOther->IsPlayer() )
  485. {
  486. CTFPlayer *pVictim = ToTFPlayer( pOther );
  487. if ( pVictim && pVictim->m_Shared.CanBeDebuffed() && pVictim->CanGetWet() )
  488. {
  489. // duration is based on damage
  490. float flDuration = RemapValClamped( GetDamage(), 25.0f, 75.0f, 6.0f, 10.0f );
  491. pVictim->m_Shared.AddCond( TF_COND_MAD_MILK, flDuration, pAttacker );
  492. pVictim->m_Shared.SetPeeAttacker( ToTFPlayer( pAttacker ) );
  493. pVictim->SpeakConceptIfAllowed( MP_CONCEPT_JARATE_HIT );
  494. }
  495. }
  496. CTakeDamageInfo info( this, pAttacker, m_hLauncher, vecVelocity, vecOrigin, GetDamage(), nDamageType, nDamageCustom );
  497. pOther->TakeDamage( info );
  498. // Play an impact sound.
  499. ImpactSound( "Weapon_Arrow.ImpactFlesh", true );
  500. }
  501. }
  502. else if ( pOther->IsPlayer() ) // Hit a team-mate.
  503. {
  504. // Heal
  505. if ( bApplyEffect )
  506. {
  507. ImpactTeamPlayer( dynamic_cast<CTFPlayer*>( pOther ) );
  508. }
  509. }
  510. }
  511. if ( !m_bPenetrate && !bBreakArrow )
  512. {
  513. OnArrowImpact( pBox, pOther, pAttacker );
  514. }
  515. // Perform a blood mesh decal trace.
  516. trace_t tr;
  517. Vector start = vecOrigin - vecVelocity * gpGlobals->frametime;
  518. Vector end = vecOrigin + vecVelocity * gpGlobals->frametime;
  519. CTraceFilterCollisionArrows filter( this, GetOwnerEntity() );
  520. UTIL_TraceLine( start, end, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
  521. UTIL_ImpactTrace( &tr, 0 );
  522. // Break it?
  523. if ( bBreakArrow )
  524. {
  525. return false;
  526. }
  527. return true;
  528. }
  529. //-----------------------------------------------------------------------------
  530. // Purpose:
  531. //-----------------------------------------------------------------------------
  532. void CTFProjectile_Arrow::OnArrowImpact( mstudiobbox_t *pBox, CBaseEntity *pOther, CBaseEntity *pAttacker )
  533. {
  534. CBaseAnimating *pOtherAnim = dynamic_cast< CBaseAnimating* >(pOther);
  535. if ( !pOtherAnim )
  536. return;
  537. const Vector &vecOrigin = GetAbsOrigin();
  538. Vector vecVelocity = GetAbsVelocity();
  539. Vector bonePosition = vec3_origin;
  540. QAngle boneAngles = QAngle(0,0,0);
  541. int boneIndexAttached = -1;
  542. int physicsBoneIndex = -1;
  543. GetBoneAttachmentInfo( pBox, pOtherAnim, bonePosition, boneAngles, boneIndexAttached, physicsBoneIndex );
  544. bool bSendImpactMessage = true;
  545. // Did we kill the target?
  546. if ( !pOther->IsAlive() && pOther->IsPlayer() )
  547. {
  548. CTFPlayer *pTFPlayerOther = dynamic_cast<CTFPlayer*>(pOther);
  549. if ( pTFPlayerOther && pTFPlayerOther->m_hRagdoll )
  550. {
  551. VectorNormalize( vecVelocity );
  552. if ( CheckRagdollPinned( vecOrigin, vecVelocity, boneIndexAttached, physicsBoneIndex, pTFPlayerOther->m_hRagdoll, pBox->group, pTFPlayerOther->entindex() ) )
  553. {
  554. pTFPlayerOther->StopRagdollDeathAnim();
  555. bSendImpactMessage = false;
  556. }
  557. }
  558. }
  559. // Notify relevant clients of an arrow impact.
  560. if ( bSendImpactMessage )
  561. {
  562. IGameEvent * event = gameeventmanager->CreateEvent( "arrow_impact" );
  563. if ( event )
  564. {
  565. event->SetInt( "attachedEntity", pOther->entindex() );
  566. event->SetInt( "shooter", pAttacker ? pAttacker->entindex() : 0 );
  567. event->SetInt( "attachedEntity", pOther->entindex() );
  568. event->SetInt( "boneIndexAttached", boneIndexAttached );
  569. event->SetFloat( "bonePositionX", bonePosition.x );
  570. event->SetFloat( "bonePositionY", bonePosition.y );
  571. event->SetFloat( "bonePositionZ", bonePosition.z );
  572. event->SetFloat( "boneAnglesX", boneAngles.x );
  573. event->SetFloat( "boneAnglesY", boneAngles.y );
  574. event->SetFloat( "boneAnglesZ", boneAngles.z );
  575. event->SetInt( "projectileType", GetProjectileType() );
  576. gameeventmanager->FireEvent( event );
  577. }
  578. }
  579. FadeOut( 3.0 );
  580. }
  581. //-----------------------------------------------------------------------------
  582. // Purpose:
  583. //-----------------------------------------------------------------------------
  584. bool CTFProjectile_Arrow::OnArrowImpactObject( CBaseEntity *pOther )
  585. {
  586. if ( InSameTeam( pOther ) )
  587. {
  588. BuildingHealingArrow( pOther );
  589. }
  590. return false;
  591. }
  592. //-----------------------------------------------------------------------------
  593. // Purpose:
  594. //-----------------------------------------------------------------------------
  595. void CTFProjectile_Arrow::ImpactThink( void )
  596. {
  597. }
  598. //-----------------------------------------------------------------------------
  599. void CTFProjectile_Arrow::BuildingHealingArrow( CBaseEntity *pOther )
  600. {
  601. // This arrow impacted a building
  602. // If its a building on our team, heal it
  603. if ( !pOther->IsBaseObject() )
  604. return;
  605. CBaseEntity *pAttacker = GetScorer();
  606. if ( pAttacker == NULL )
  607. return;
  608. // if not on our team, forget about it
  609. if ( GetTeamNumber() != pOther->GetTeamNumber() )
  610. return;
  611. int iArrowsHealBuildings = 0;
  612. CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iArrowsHealBuildings, arrow_heals_buildings );
  613. if ( iArrowsHealBuildings == 0 )
  614. return;
  615. CBaseObject *pBuilding = dynamic_cast< CBaseObject * >( pOther );
  616. if ( !pBuilding || !pBuilding->CanBeRepaired() || pBuilding->HasSapper() || pBuilding->IsPlasmaDisabled() || pBuilding->IsBuilding() || pBuilding->IsPlacing() )
  617. return;
  618. // if building is sheilded, reduce health gain
  619. if ( pBuilding->GetShieldLevel() == SHIELD_NORMAL )
  620. {
  621. iArrowsHealBuildings *= SHIELD_NORMAL_VALUE;
  622. }
  623. float flNewHealth = MIN( pBuilding->GetMaxHealth(), (int)pBuilding->GetHealth() + iArrowsHealBuildings );
  624. int iHealthAdded = (int)(flNewHealth - pBuilding->GetHealth());
  625. if ( iHealthAdded > 0 )
  626. {
  627. pBuilding->SetHealth( flNewHealth );
  628. IGameEvent * event = gameeventmanager->CreateEvent( "building_healed" );
  629. if ( event )
  630. {
  631. // HLTV event priority, not transmitted
  632. event->SetInt( "priority", 1 );
  633. // Healed by another player.
  634. event->SetInt( "building", pBuilding->entindex() );
  635. event->SetInt( "healer", pAttacker->entindex() );
  636. event->SetInt( "amount", iHealthAdded );
  637. gameeventmanager->FireEvent( event );
  638. }
  639. const char *pParticleName = GetTeamNumber() == TF_TEAM_BLUE ? CLAW_REPAIR_EFFECT_BLU : CLAW_REPAIR_EFFECT_RED;
  640. CPVSFilter filter( GetAbsOrigin() );
  641. TE_TFParticleEffect( filter, 0.0, pParticleName, GetAbsOrigin(), vec3_angle );
  642. }
  643. }
  644. //-----------------------------------------------------------------------------
  645. // Purpose:
  646. //-----------------------------------------------------------------------------
  647. int CTFProjectile_Arrow::GetArrowSkin() const
  648. {
  649. int nTeam = GetTeamNumber();
  650. if ( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() )
  651. {
  652. CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
  653. if ( pOwner && pOwner->IsPlayerClass( TF_CLASS_SPY ) && pOwner->m_Shared.InCond( TF_COND_DISGUISED ) )
  654. {
  655. nTeam = pOwner->m_Shared.GetDisguiseTeam();
  656. }
  657. }
  658. return ( nTeam == TF_TEAM_BLUE ) ? 1 : 0;
  659. }
  660. //-----------------------------------------------------------------------------
  661. // Purpose:
  662. //-----------------------------------------------------------------------------
  663. void CTFProjectile_Arrow::OnArrowMissAllPlayers()
  664. {
  665. CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
  666. if( pOwner && pOwner->IsPlayerClass( TF_CLASS_SNIPER ) )
  667. {
  668. EconEntity_OnOwnerKillEaterEventNoPartner( assert_cast<CEconEntity *>( m_hLauncher.Get() ), pOwner, kKillEaterEvent_NEGATIVE_SniperShotsMissed );
  669. }
  670. }
  671. //-----------------------------------------------------------------------------
  672. // Purpose:
  673. //-----------------------------------------------------------------------------
  674. void CTFProjectile_Arrow::ArrowTouch( CBaseEntity *pOther )
  675. {
  676. // Safety net hack:
  677. // We routinely introduce new entity types, and arrows
  678. // are repeat-offenders at not getting along with them.
  679. // If enough time goes by, just remove the arrow.
  680. float flAliveTime = gpGlobals->curtime - m_flInitTime;
  681. if ( flAliveTime >= 10.f )
  682. {
  683. Warning( "Arrow alive for %f3.2\n seconds", flAliveTime );
  684. UTIL_Remove( this );
  685. }
  686. if ( m_bStruckEnemy || (GetMoveType() == MOVETYPE_NONE) )
  687. return;
  688. if ( !pOther )
  689. return;
  690. bool bShield = pOther->IsCombatItem() && !InSameTeam( pOther );
  691. CTFPumpkinBomb *pPumpkinBomb = dynamic_cast< CTFPumpkinBomb * >( pOther );
  692. if ( pOther->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) && !pPumpkinBomb && !bShield )
  693. return;
  694. // test against combat characters, which include players, engineer buildings, and NPCs
  695. CBaseCombatCharacter *pOtherCombatCharacter = dynamic_cast< CBaseCombatCharacter * >( pOther );
  696. if ( !pOtherCombatCharacter )
  697. {
  698. // It might be a track train with boss parented
  699. pOtherCombatCharacter = dynamic_cast< CBaseCombatCharacter * >( pOther->FirstMoveChild() );
  700. if ( pOtherCombatCharacter )
  701. {
  702. pOther = pOtherCombatCharacter;
  703. }
  704. }
  705. CTFMerasmusTrickOrTreatProp *pMerasmusProp = dynamic_cast< CTFMerasmusTrickOrTreatProp* >( pOther );
  706. CTFRobotDestruction_Robot *pRobot = dynamic_cast< CTFRobotDestruction_Robot* >( pOther );
  707. if ( pOther->IsWorld() || ( !pOtherCombatCharacter && !pPumpkinBomb && !pMerasmusProp && !bShield && !pRobot ) )
  708. {
  709. // Check to see if we struck the skybox.
  710. CheckSkyboxImpact( pOther );
  711. // If we've only got 1 entity in the hit list (the attacker by default) and we've not been deflected
  712. // then we can consider this arrow to have completely missed all players.
  713. if( m_HitEntities.Count() == 1 && GetDeflected() == 0 )
  714. {
  715. OnArrowMissAllPlayers();
  716. }
  717. return;
  718. }
  719. CBaseAnimating *pAnimOther = dynamic_cast<CBaseAnimating*>(pOther);
  720. CStudioHdr *pStudioHdr = NULL;
  721. mstudiohitboxset_t *set = NULL;
  722. if ( pAnimOther )
  723. {
  724. pStudioHdr = pAnimOther->GetModelPtr();
  725. if ( pStudioHdr )
  726. {
  727. set = pStudioHdr->pHitboxSet( pAnimOther->GetHitboxSet() );
  728. }
  729. }
  730. if ( !pAnimOther || !pStudioHdr || !set )
  731. {
  732. // Whatever we hit doesn't have hitboxes. Ignore it.
  733. UTIL_Remove( this );
  734. return;
  735. }
  736. // We struck the collision box of a player or a buildable object.
  737. // Trace forward to see if we struck a hitbox.
  738. CTraceFilterCollisionArrows filter( this, GetOwnerEntity() );
  739. Vector start = GetAbsOrigin();
  740. Vector vel = GetAbsVelocity();
  741. trace_t tr;
  742. UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
  743. // If we hit a hitbox, stop tracing.
  744. mstudiobbox_t *closest_box = NULL;
  745. if ( tr.m_pEnt && tr.m_pEnt->GetTeamNumber() != GetTeamNumber() )
  746. {
  747. // This means the arrow was true and was flying directly at a hitbox on the target.
  748. // We'll attach to that hitbox.
  749. closest_box = set->pHitbox( tr.hitbox );
  750. }
  751. if ( !closest_box )
  752. {
  753. // Locate the hitbox closest to our point of impact on the collision box.
  754. Vector position, start, forward;
  755. QAngle angles;
  756. float closest_dist = 99999;
  757. // Intense, but extremely accurate:
  758. AngleVectors( GetAbsAngles(), &forward );
  759. start = GetAbsOrigin() + forward*16;
  760. for ( int i = 0; i < set->numhitboxes; i++ )
  761. {
  762. mstudiobbox_t *pbox = set->pHitbox( i );
  763. pAnimOther->GetBonePosition( pbox->bone, position, angles );
  764. Ray_t ray;
  765. ray.Init( start, position );
  766. trace_t tr;
  767. IntersectRayWithBox( ray, position+pbox->bbmin, position+pbox->bbmax, 0.f, &tr );
  768. float dist = tr.endpos.DistTo( start );
  769. if ( dist < closest_dist )
  770. {
  771. closest_dist = dist;
  772. closest_box = pbox;
  773. }
  774. }
  775. }
  776. if ( closest_box )
  777. {
  778. // See if we're supposed to stick in the target.
  779. bool bStrike = StrikeTarget( closest_box, pOther );
  780. if ( bStrike && !m_bPenetrate)
  781. {
  782. // If we're here, it means StrikeTarget() called FadeOut( 3.0 )
  783. SetAbsOrigin( start );
  784. }
  785. if ( !bStrike || bShield )
  786. {
  787. BreakArrow();
  788. }
  789. // Slightly confusing. If we're here, the arrow stopped at the
  790. // target and will fade or break. Setting this prevents the
  791. // touch code from re-running during the delay.
  792. if ( !m_bPenetrate )
  793. {
  794. m_bStruckEnemy = true;
  795. }
  796. }
  797. }
  798. //-----------------------------------------------------------------------------
  799. // Purpose:
  800. //-----------------------------------------------------------------------------
  801. void CTFProjectile_Arrow::CheckSkyboxImpact( CBaseEntity *pOther )
  802. {
  803. trace_t tr;
  804. Vector velDir = GetAbsVelocity();
  805. VectorNormalize( velDir );
  806. Vector vecSpot = GetAbsOrigin() - velDir * 32;
  807. UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr );
  808. if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY )
  809. {
  810. // We hit the skybox, go away soon.
  811. FadeOut( 3.f );
  812. return;
  813. }
  814. if ( !pOther->IsWorld() )
  815. {
  816. BreakArrow();
  817. }
  818. else
  819. {
  820. CEffectData data;
  821. data.m_vOrigin = tr.endpos;
  822. data.m_vNormal = velDir;
  823. data.m_nEntIndex = 0;/*tr.fraction != 1.0f;*/
  824. data.m_nAttachmentIndex = 0;
  825. data.m_nMaterial = 0;
  826. data.m_fFlags = GetProjectileType();
  827. data.m_nColor = GetArrowSkin();
  828. DispatchEffect( "TFBoltImpact", data );
  829. FadeOut( 3.f );
  830. // Play an impact sound.
  831. const char* pszSoundName = "Weapon_Arrow.ImpactMetal";
  832. surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
  833. if ( psurf )
  834. {
  835. switch ( psurf->game.material )
  836. {
  837. case CHAR_TEX_GRATE:
  838. case CHAR_TEX_METAL:
  839. pszSoundName = "Weapon_Arrow.ImpactMetal";
  840. break;
  841. case CHAR_TEX_CONCRETE:
  842. pszSoundName = "Weapon_Arrow.ImpactConcrete";
  843. break;
  844. case CHAR_TEX_WOOD:
  845. pszSoundName = "Weapon_Arrow.ImpactWood";
  846. break;
  847. }
  848. }
  849. ImpactSound( pszSoundName );
  850. }
  851. }
  852. //-----------------------------------------------------------------------------
  853. // Purpose: Plays an impact sound. Louder for the attacker.
  854. //-----------------------------------------------------------------------------
  855. void CTFProjectile_Arrow::ImpactSound( const char *pszSoundName, bool bLoudForAttacker )
  856. {
  857. CTFPlayer *pAttacker = ToTFPlayer( GetScorer() );
  858. if ( !pAttacker )
  859. return;
  860. if ( bLoudForAttacker )
  861. {
  862. float soundlen = 0;
  863. EmitSound_t params;
  864. params.m_flSoundTime = 0;
  865. params.m_pSoundName = pszSoundName;
  866. params.m_pflSoundDuration = &soundlen;
  867. CPASFilter filter( GetAbsOrigin() );
  868. filter.RemoveRecipient( ToTFPlayer(pAttacker) );
  869. EmitSound( filter, entindex(), params );
  870. CSingleUserRecipientFilter attackerFilter( ToTFPlayer(pAttacker) );
  871. EmitSound( attackerFilter, pAttacker->entindex(), params );
  872. }
  873. else
  874. {
  875. EmitSound( pszSoundName );
  876. }
  877. }
  878. //-----------------------------------------------------------------------------
  879. // Purpose:
  880. //-----------------------------------------------------------------------------
  881. void CTFProjectile_Arrow::BreakArrow()
  882. {
  883. FadeOut( 3.f );
  884. CPVSFilter filter( GetAbsOrigin() );
  885. UserMessageBegin( filter, "BreakModel" );
  886. WRITE_SHORT( GetModelIndex() );
  887. WRITE_VEC3COORD( GetAbsOrigin() );
  888. WRITE_ANGLES( GetAbsAngles() );
  889. WRITE_SHORT( m_nSkin );
  890. MessageEnd();
  891. }
  892. //-----------------------------------------------------------------------------
  893. // Purpose:
  894. //-----------------------------------------------------------------------------
  895. bool CTFProjectile_Arrow::CheckRagdollPinned( const Vector &start, const Vector &vel, int boneIndexAttached, int physicsBoneIndex, CBaseEntity *pOther, int iHitGroup, int iVictim )
  896. {
  897. // Pin to the wall.
  898. trace_t tr;
  899. UTIL_TraceLine( start, start + vel * 125, MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
  900. if ( tr.fraction != 1.0f && tr.DidHitWorld() )
  901. {
  902. CEffectData data;
  903. data.m_vOrigin = tr.endpos;
  904. data.m_vNormal = vel;
  905. data.m_nEntIndex = pOther->entindex();
  906. data.m_nAttachmentIndex = boneIndexAttached;
  907. data.m_nMaterial = physicsBoneIndex;
  908. data.m_nDamageType = iHitGroup;
  909. data.m_nSurfaceProp = iVictim;
  910. data.m_fFlags = GetProjectileType();
  911. data.m_nColor = GetArrowSkin();
  912. if ( GetScorer() )
  913. {
  914. data.m_nHitBox = GetScorer()->entindex();
  915. }
  916. DispatchEffect( "TFBoltImpact", data );
  917. return true;
  918. }
  919. return false;
  920. }
  921. //-----------------------------------------------------------------------------
  922. // Purpose:
  923. //-----------------------------------------------------------------------------
  924. void CTFProjectile_Arrow::FadeOut( int iTime )
  925. {
  926. SetMoveType( MOVETYPE_NONE );
  927. SetAbsVelocity( vec3_origin );
  928. AddSolidFlags( FSOLID_NOT_SOLID );
  929. AddEffects( EF_NODRAW );
  930. // Start remove timer.
  931. SetContextThink( &CTFProjectile_Arrow::RemoveThink, gpGlobals->curtime + iTime, "ARROW_REMOVE_THINK" );
  932. }
  933. //-----------------------------------------------------------------------------
  934. // Purpose:
  935. //-----------------------------------------------------------------------------
  936. void CTFProjectile_Arrow::RemoveThink( void )
  937. {
  938. UTIL_Remove( this );
  939. }
  940. //-----------------------------------------------------------------------------
  941. const char *CTFProjectile_Arrow::GetTrailParticleName( void )
  942. {
  943. if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT )
  944. {
  945. return ( GetTeamNumber() == TF_TEAM_RED ) ? CLAW_TRAIL_RED : CLAW_TRAIL_BLU;
  946. }
  947. else if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT )
  948. {
  949. return ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/healingtrail_red.vmt" : "effects/healingtrail_blu.vmt";
  950. }
  951. return ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/arrowtrail_red.vmt" : "effects/arrowtrail_blu.vmt";
  952. }
  953. //-----------------------------------------------------------------------------
  954. // Purpose:
  955. //-----------------------------------------------------------------------------
  956. void CTFProjectile_Arrow::CreateTrail( void )
  957. {
  958. if ( IsDormant() )
  959. return;
  960. if ( !m_pTrail )
  961. {
  962. int width = 3;
  963. switch ( m_iProjectileType )
  964. {
  965. case TF_PROJECTILE_BUILDING_REPAIR_BOLT:
  966. width = 5;
  967. break;
  968. case TF_PROJECTILE_HEALING_BOLT:
  969. case TF_PROJECTILE_FESTIVE_HEALING_BOLT:
  970. case TF_PROJECTILE_GRAPPLINGHOOK:
  971. #ifdef STAGING_ONLY
  972. case TF_PROJECTILE_SNIPERBULLET:
  973. #endif // STAGING_ONLY
  974. return; // do not create arrow trail for healing bolt, use particle instead (client only)
  975. }
  976. const char *pTrailTeamName = GetTrailParticleName();
  977. CSpriteTrail *pTempTrail = NULL;
  978. pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, GetAbsOrigin(), true );
  979. pTempTrail->FollowEntity( this );
  980. pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
  981. pTempTrail->SetStartWidth( width );
  982. pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
  983. pTempTrail->SetLifeTime( 0.3 );
  984. pTempTrail->TurnOn();
  985. pTempTrail->SetAttachment( this, 0 );
  986. m_pTrail = pTempTrail;
  987. SetContextThink( &CTFProjectile_Arrow::RemoveTrail, gpGlobals->curtime + 3, "FadeTrail");
  988. }
  989. }
  990. //-----------------------------------------------------------------------------
  991. // Purpose: Fade and kill the trail
  992. //-----------------------------------------------------------------------------
  993. void CTFProjectile_Arrow::RemoveTrail( void )
  994. {
  995. if ( !m_pTrail )
  996. return;
  997. if ( m_pTrail )
  998. {
  999. if ( m_flTrailLife <= 0 )
  1000. {
  1001. UTIL_Remove( m_pTrail );
  1002. m_flTrailLife = 1.0f;
  1003. }
  1004. else
  1005. {
  1006. float fAlpha = 128 * m_flTrailLife;
  1007. CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pTrail.Get() );
  1008. if ( pTempTrail )
  1009. {
  1010. pTempTrail->SetBrightness( int(fAlpha) );
  1011. }
  1012. m_flTrailLife = m_flTrailLife - 0.1f;
  1013. SetContextThink( &CTFProjectile_Arrow::RemoveTrail, gpGlobals->curtime + 0.05, "FadeTrail");
  1014. }
  1015. }
  1016. }
  1017. //-----------------------------------------------------------------------------
  1018. // Purpose:
  1019. //-----------------------------------------------------------------------------
  1020. void CTFProjectile_Arrow::AdjustDamageDirection( const CTakeDamageInfo &info, Vector &dir, CBaseEntity *pEnt )
  1021. {
  1022. if ( pEnt )
  1023. {
  1024. dir = info.GetDamagePosition() - info.GetDamageForce() - pEnt->WorldSpaceCenter();
  1025. }
  1026. }
  1027. //-----------------------------------------------------------------------------
  1028. // Purpose: Arrow was deflected.
  1029. //-----------------------------------------------------------------------------
  1030. void CTFProjectile_Arrow::IncrementDeflected( void )
  1031. {
  1032. m_iDeflected++;
  1033. // Change trail color.
  1034. if ( m_pTrail )
  1035. {
  1036. UTIL_Remove( m_pTrail );
  1037. m_pTrail = NULL;
  1038. m_flTrailLife = 1.0f;
  1039. }
  1040. CreateTrail();
  1041. }
  1042. //-----------------------------------------------------------------------------
  1043. // Purpose: Arrow was deflected.
  1044. //-----------------------------------------------------------------------------
  1045. void CTFProjectile_Arrow::Deflected( CBaseEntity *pDeflectedBy, Vector &vecDir )
  1046. {
  1047. CTFPlayer *pTFDeflector = ToTFPlayer( pDeflectedBy );
  1048. if ( !pTFDeflector )
  1049. return;
  1050. ChangeTeam( pTFDeflector->GetTeamNumber() );
  1051. SetLauncher( pTFDeflector->GetActiveWeapon() );
  1052. CTFPlayer* pOldOwner = ToTFPlayer( GetOwnerEntity() );
  1053. SetOwnerEntity( pTFDeflector );
  1054. if ( pOldOwner )
  1055. {
  1056. pOldOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:1,victim:1" );
  1057. }
  1058. if ( pTFDeflector->m_Shared.IsCritBoosted() )
  1059. {
  1060. SetCritical( true );
  1061. }
  1062. CTFWeaponBase::SendObjectDeflectedEvent( pTFDeflector, pOldOwner, GetWeaponID(), this );
  1063. IncrementDeflected();
  1064. SetScorer( pTFDeflector );
  1065. // Purge our hit list so we can hit everyone again
  1066. m_HitEntities.Purge();
  1067. // Add ourselves so we dont hit ourselves
  1068. m_HitEntities.AddToTail( pTFDeflector->entindex() );
  1069. }
  1070. //-----------------------------------------------------------------------------
  1071. // Purpose: Setup function.
  1072. //-----------------------------------------------------------------------------
  1073. void CTFProjectile_HealingBolt::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
  1074. {
  1075. BaseClass::InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer );
  1076. //SetNextThink( gpGlobals->curtime );
  1077. }
  1078. //-----------------------------------------------------------------------------
  1079. // Purpose: Healing bolt heal.
  1080. //-----------------------------------------------------------------------------
  1081. void CTFProjectile_HealingBolt::ImpactTeamPlayer( CTFPlayer *pOther )
  1082. {
  1083. if ( !pOther )
  1084. return;
  1085. #ifdef STAGING_ONLY
  1086. // Milk Arrows only heal teammates on special shot
  1087. if ( GetProjectileType() == TF_PROJECTILE_MILK_BOLT && !m_bApplyMilkOnHit )
  1088. return;
  1089. #endif
  1090. CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
  1091. if ( !pOwner )
  1092. return;
  1093. // Don't heal players using a weapon that blocks healing
  1094. CTFWeaponBase *pWeapon = pOther->GetActiveTFWeapon();
  1095. if ( pWeapon )
  1096. {
  1097. int iBlockHealing = 0;
  1098. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iBlockHealing, weapon_blocks_healing );
  1099. if ( iBlockHealing )
  1100. return;
  1101. }
  1102. float flHealth = GetDamage() * 2.0f;
  1103. #ifdef STAGING_ONLY
  1104. // Milk Arrows give a resist bubble on hitting a teammate
  1105. if ( GetProjectileType() == TF_PROJECTILE_MILK_BOLT )
  1106. {
  1107. // use damage to scale time
  1108. float flResistDuration = RemapValClamped( flHealth, 0, 150, 1, 3 );
  1109. pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST, flResistDuration, pOwner );
  1110. pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST, flResistDuration, pOwner );
  1111. pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST, flResistDuration, pOwner );
  1112. }
  1113. #endif
  1114. // Scale this if needed
  1115. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOther, flHealth, mult_healing_from_medics );
  1116. int iActualHealed = pOther->TakeHealth( flHealth, DMG_GENERIC );
  1117. if ( iActualHealed <= 0 )
  1118. return;
  1119. // Play an impact sound.
  1120. ImpactSound( "Weapon_Arrow.ImpactFleshCrossbowHeal" );
  1121. CTF_GameStats.Event_PlayerHealedOther( pOwner, flHealth );
  1122. IGameEvent * event = gameeventmanager->CreateEvent( "player_healed" );
  1123. if ( event )
  1124. {
  1125. // HLTV event priority, not transmitted
  1126. event->SetInt( "priority", 1 );
  1127. // Healed by another player.
  1128. event->SetInt( "patient", pOther->GetUserID() );
  1129. event->SetInt( "healer", pOwner->GetUserID() );
  1130. event->SetInt( "amount", flHealth );
  1131. gameeventmanager->FireEvent( event );
  1132. }
  1133. event = gameeventmanager->CreateEvent( "player_healonhit" );
  1134. if ( event )
  1135. {
  1136. event->SetInt( "amount", flHealth );
  1137. event->SetInt( "entindex", pOther->entindex() );
  1138. item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX;
  1139. if ( pWeapon && pWeapon->GetAttributeContainer() && pWeapon->GetAttributeContainer()->GetItem() )
  1140. {
  1141. healingItemDef = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex();
  1142. }
  1143. event->SetInt( "weapon_def_index", healingItemDef );
  1144. gameeventmanager->FireEvent( event );
  1145. }
  1146. event = gameeventmanager->CreateEvent( "crossbow_heal" );
  1147. if ( event )
  1148. {
  1149. event->SetInt( "healer", pOwner->GetUserID() );
  1150. event->SetInt( "target", pOther->GetUserID() );
  1151. event->SetInt( "amount", flHealth );
  1152. gameeventmanager->FireEvent( event );
  1153. }
  1154. // Give a litte bit of uber based on actual healing
  1155. // Give them a little bit of Uber
  1156. CWeaponMedigun *pMedigun = static_cast<CWeaponMedigun *>( pOwner->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
  1157. if ( pMedigun )
  1158. {
  1159. // On Mediguns, per frame, the amount of uber added is based on
  1160. // Default heal rate is 24per second, we scale based on that and frametime
  1161. pMedigun->AddCharge( ( iActualHealed / 24.0f ) * gpGlobals->frametime );
  1162. }
  1163. pOther->m_Shared.AddCond( TF_COND_HEALTH_OVERHEALED, 1.2f );
  1164. EconEntity_OnOwnerKillEaterEvent_Batched( dynamic_cast<CEconEntity *>( GetLauncher() ), pOwner, pOther, kKillEaterEvent_AllyHealingDone, flHealth );
  1165. }
  1166. CTFProjectile_GrapplingHook::CTFProjectile_GrapplingHook()
  1167. : m_pImpactFleshSoundLoop( NULL )
  1168. {
  1169. }
  1170. //-----------------------------------------------------------------------------
  1171. // Purpose: Spawn
  1172. //-----------------------------------------------------------------------------
  1173. void CTFProjectile_GrapplingHook::Spawn()
  1174. {
  1175. BaseClass::Spawn();
  1176. SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM );
  1177. }
  1178. void CTFProjectile_GrapplingHook::Precache()
  1179. {
  1180. BaseClass::Precache();
  1181. PrecacheModel( "models/weapons/c_models/c_grapple_proj/c_grapple_proj.mdl" );
  1182. PrecacheScriptSound( "WeaponGrapplingHook.ImpactFlesh" );
  1183. PrecacheScriptSound( "WeaponGrapplingHook.ImpactDefault" );
  1184. PrecacheScriptSound( "WeaponGrapplingHook.ImpactFleshLoop" );
  1185. }
  1186. //-----------------------------------------------------------------------------
  1187. // Purpose: Spawn
  1188. //-----------------------------------------------------------------------------
  1189. void CTFProjectile_GrapplingHook::UpdateOnRemove()
  1190. {
  1191. // clear hook target
  1192. CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
  1193. if ( pTFPlayer )
  1194. {
  1195. // Clear any healers grappling with us
  1196. SetMedicsGrapplingHookTarget( pTFPlayer, NULL );
  1197. pTFPlayer->SetGrapplingHookTarget( NULL );
  1198. pTFPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK );
  1199. }
  1200. StopImpactFleshSoundLoop();
  1201. BaseClass::UpdateOnRemove();
  1202. }
  1203. //-----------------------------------------------------------------------------
  1204. // Purpose: Setup function.
  1205. //-----------------------------------------------------------------------------
  1206. void CTFProjectile_GrapplingHook::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
  1207. {
  1208. BaseClass::InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer );
  1209. CTFPlayer *pTFPlayer = ToTFPlayer( pOwner );
  1210. if ( pTFPlayer )
  1211. {
  1212. pTFPlayer->m_Shared.AddCond( TF_COND_GRAPPLINGHOOK );
  1213. }
  1214. }
  1215. //-----------------------------------------------------------------------------
  1216. // Purpose: OnArrowImpact
  1217. //-----------------------------------------------------------------------------
  1218. void CTFProjectile_GrapplingHook::OnArrowImpact( mstudiobbox_t *pBox, CBaseEntity *pOther, CBaseEntity *pAttacker )
  1219. {
  1220. HookTarget( pOther );
  1221. }
  1222. //-----------------------------------------------------------------------------
  1223. // Purpose: OnArrowImpactObject
  1224. //-----------------------------------------------------------------------------
  1225. bool CTFProjectile_GrapplingHook::OnArrowImpactObject( CBaseEntity *pOther )
  1226. {
  1227. HookTarget( pOther );
  1228. return true;
  1229. }
  1230. //-----------------------------------------------------------------------------
  1231. // Purpose: CheckSkyboxImpact
  1232. //-----------------------------------------------------------------------------
  1233. void CTFProjectile_GrapplingHook::CheckSkyboxImpact( CBaseEntity *pOther )
  1234. {
  1235. trace_t tr;
  1236. Vector velDir = GetAbsVelocity();
  1237. VectorNormalize( velDir );
  1238. Vector vecSpot = GetAbsOrigin() - velDir * 32;
  1239. UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr );
  1240. if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY )
  1241. {
  1242. // We hit the skybox, go away soon.
  1243. FadeOut( 1.f );
  1244. return;
  1245. }
  1246. if ( !pOther->IsWorld() )
  1247. {
  1248. HookTarget( pOther );
  1249. }
  1250. else
  1251. {
  1252. HookTarget( pOther );
  1253. // rotate the hook model to be perpendicular to the world surface
  1254. Vector vUp;
  1255. AngleVectors( GetAbsAngles(), NULL, NULL, &vUp );
  1256. QAngle qNewAngles;
  1257. VectorAngles( -tr.plane.normal, vUp, qNewAngles );
  1258. SetAbsAngles( qNewAngles );
  1259. SetAbsOrigin( GetAbsOrigin() + 3.f * tr.plane.normal );
  1260. }
  1261. }
  1262. //-----------------------------------------------------------------------------
  1263. // Purpose: HookTarget
  1264. //-----------------------------------------------------------------------------
  1265. void CTFProjectile_GrapplingHook::HookTarget( CBaseEntity *pOther )
  1266. {
  1267. if ( !GetOwnerEntity() || !pOther )
  1268. return;
  1269. CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
  1270. if ( !pTFPlayer || pTFPlayer->GetGrapplingHookTarget() )
  1271. return;
  1272. CBaseEntity *pTarget = pOther->IsWorld() ? this : pOther;
  1273. const char *pszSoundName = NULL;
  1274. if ( pTarget->IsPlayer() )
  1275. {
  1276. pszSoundName = "WeaponGrapplingHook.ImpactFlesh";
  1277. }
  1278. else
  1279. {
  1280. pszSoundName = "WeaponGrapplingHook.ImpactDefault";
  1281. }
  1282. ImpactSound( pszSoundName );
  1283. pTFPlayer->SetGrapplingHookTarget( pTarget, true );
  1284. // Grapple any medics to us
  1285. SetMedicsGrapplingHookTarget( pTFPlayer, pTFPlayer );
  1286. // Stop moving!
  1287. if ( pOther->IsPlayer() )
  1288. {
  1289. FollowEntity( pOther, false );
  1290. StartImpactFleshSoundLoop();
  1291. }
  1292. else
  1293. SetMoveType( MOVETYPE_NONE );
  1294. SetContextThink( &CTFProjectile_GrapplingHook::HookLatchedThink, gpGlobals->curtime + 0.1f, "HookLatchedThink" );
  1295. }
  1296. //-----------------------------------------------------------------------------
  1297. // Purpose: HookLatchedThink
  1298. //-----------------------------------------------------------------------------
  1299. void CTFProjectile_GrapplingHook::HookLatchedThink()
  1300. {
  1301. // if owner is dead, remove the hook
  1302. CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
  1303. if ( !pTFPlayer || !pTFPlayer->IsAlive() )
  1304. {
  1305. UTIL_Remove( this );
  1306. return;
  1307. }
  1308. // if the target nolonger exist or target player is dead, remove the hook
  1309. CBaseEntity *pHookTarget = pTFPlayer->GetGrapplingHookTarget();
  1310. if ( !pHookTarget || ( pHookTarget->IsPlayer() && !pHookTarget->IsAlive() ) )
  1311. {
  1312. UTIL_Remove( this );
  1313. return;
  1314. }
  1315. SetContextThink( &CTFProjectile_GrapplingHook::HookLatchedThink, gpGlobals->curtime + 0.1f, "HookLatchedThink" );
  1316. }
  1317. //-----------------------------------------------------------------------------
  1318. // Purpose:
  1319. //-----------------------------------------------------------------------------
  1320. void CTFProjectile_GrapplingHook::StartImpactFleshSoundLoop()
  1321. {
  1322. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1323. CPASAttenuationFilter filter( this );
  1324. m_pImpactFleshSoundLoop = controller.SoundCreate( filter, entindex(), "WeaponGrapplingHook.ImpactFleshLoop" );
  1325. controller.Play( m_pImpactFleshSoundLoop, 1.0, 100 );
  1326. }
  1327. //-----------------------------------------------------------------------------
  1328. // Purpose:
  1329. //-----------------------------------------------------------------------------
  1330. void CTFProjectile_GrapplingHook::StopImpactFleshSoundLoop()
  1331. {
  1332. if ( m_pImpactFleshSoundLoop )
  1333. {
  1334. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1335. controller.SoundDestroy( m_pImpactFleshSoundLoop );
  1336. m_pImpactFleshSoundLoop = NULL;
  1337. }
  1338. }