diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/tf_projectile_arrow.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/tf/tf_projectile_arrow.cpp')
| -rw-r--r-- | game/server/tf/tf_projectile_arrow.cpp | 1573 |
1 files changed, 1573 insertions, 0 deletions
diff --git a/game/server/tf/tf_projectile_arrow.cpp b/game/server/tf/tf_projectile_arrow.cpp new file mode 100644 index 0000000..ff59dcb --- /dev/null +++ b/game/server/tf/tf_projectile_arrow.cpp @@ -0,0 +1,1573 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// TF Arrow +// +//============================================================================= +#include "cbase.h" +#include "tf_projectile_arrow.h" +#include "soundent.h" +#include "tf_fx.h" +#include "props.h" +#include "baseobject_shared.h" +#include "SpriteTrail.h" +#include "IEffects.h" +#include "te_effect_dispatch.h" +#include "collisionutils.h" +#include "bone_setup.h" +#include "decals.h" +#include "tf_player.h" +#include "tf_gamestats.h" +#include "tf_pumpkin_bomb.h" +#include "tf_weapon_shovel.h" +#include "player_vs_environment/tf_tank_boss.h" +#include "halloween/halloween_base_boss.h" +#include "halloween/merasmus/merasmus_trick_or_treat_prop.h" +#include "tf_logic_robot_destruction.h" + +#include "tf_gamerules.h" +#include "bot/tf_bot.h" +#include "tf_weapon_medigun.h" +#include "soundenvelope.h" + + +//============================================================================= +// +// TF Arrow Projectile functions (Server specific). +// +#define ARROW_MODEL_GIB1 "models/weapons/w_models/w_arrow_gib1.mdl" +#define ARROW_MODEL_GIB2 "models/weapons/w_models/w_arrow_gib2.mdl" + +#define ARROW_GRAVITY 0.3f + +#define ARROW_THINK_CONTEXT "CTFProjectile_ArrowThink" + +#define CLAW_TRAIL_RED "effects/repair_claw_trail_red.vmt" +#define CLAW_TRAIL_BLU "effects/repair_claw_trail_blue.vmt" +#define CLAW_GIB1 "models/weapons/w_models/w_repair_claw_gib1.mdl" +#define CLAW_GIB2 "models/weapons/w_models/w_repair_claw_gib2.mdl" + +#define CLAW_REPAIR_EFFECT_BLU "repair_claw_heal_blue" +#define CLAW_REPAIR_EFFECT_RED "repair_claw_heal_red" +//----------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( tf_projectile_arrow, CTFProjectile_Arrow ); +PRECACHE_WEAPON_REGISTER( tf_projectile_arrow ); + +IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Arrow, DT_TFProjectile_Arrow ) + +BEGIN_NETWORK_TABLE( CTFProjectile_Arrow, DT_TFProjectile_Arrow ) + SendPropBool( SENDINFO( m_bArrowAlight ) ), + SendPropBool( SENDINFO( m_bCritical ) ), + SendPropInt( SENDINFO( m_iProjectileType ) ), +END_NETWORK_TABLE() + +BEGIN_DATADESC( CTFProjectile_Arrow ) +DEFINE_THINKFUNC( ImpactThink ), +END_DATADESC() + +//----------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( tf_projectile_healing_bolt, CTFProjectile_HealingBolt ); +PRECACHE_WEAPON_REGISTER( tf_projectile_healing_bolt ); + +IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_HealingBolt, DT_TFProjectile_HealingBolt ) + +BEGIN_NETWORK_TABLE( CTFProjectile_HealingBolt, DT_TFProjectile_HealingBolt ) +END_NETWORK_TABLE() + +BEGIN_DATADESC( CTFProjectile_HealingBolt ) +END_DATADESC() + +//----------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( tf_projectile_grapplinghook, CTFProjectile_GrapplingHook ); +PRECACHE_WEAPON_REGISTER( tf_projectile_grapplinghook ); + +IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook ) + +BEGIN_NETWORK_TABLE( CTFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook ) +END_NETWORK_TABLE() + +BEGIN_DATADESC( CTFProjectile_GrapplingHook ) +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Helper to set a grappling hook target on all healers of this player +//----------------------------------------------------------------------------- +static void SetMedicsGrapplingHookTarget( CTFPlayer *pTFPlayer, CBaseEntity *pGrappleTarget ) +{ + int i; + int iNumHealers = pTFPlayer->m_Shared.GetNumHealers(); + for ( i = 0 ; i < iNumHealers ; i++ ) + { + CTFPlayer *pMedic = ToTFPlayer( pTFPlayer->m_Shared.GetHealerByIndex( i ) ); + // Only want medics who are directly healing us with their medigun, not e.g. AoE healers. + if ( pMedic && ToTFPlayer ( pMedic->MedicGetHealTarget() ) == pTFPlayer ) + { + pMedic->SetGrapplingHookTarget( pGrappleTarget ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFProjectile_Arrow::CTFProjectile_Arrow() +{ + m_flImpactTime = 0.0f; + m_flTrailLife = 0.f; + m_pTrail = NULL; + m_bStruckEnemy = false; + m_bArrowAlight = false; + m_iDeflected = 0; + m_bCritical = false; + m_flInitTime = 0; + m_bPenetrate = false; + m_iProjectileType = TF_PROJECTILE_ARROW; + m_iWeaponId = TF_WEAPON_COMPOUND_BOW; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFProjectile_Arrow::~CTFProjectile_Arrow() +{ + m_HitEntities.Purge(); +} + +static const char* GetArrowEntityName( ProjectileType_t projectileType ) +{ + switch ( projectileType ) + { + case TF_PROJECTILE_HEALING_BOLT: + case TF_PROJECTILE_FESTIVE_HEALING_BOLT: +#ifdef STAGING_ONLY + case TF_PROJECTILE_MILK_BOLT: +#endif + return "tf_projectile_healing_bolt"; + case TF_PROJECTILE_GRAPPLINGHOOK: + return "tf_projectile_grapplinghook"; + + default: + return "tf_projectile_arrow"; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFProjectile_Arrow *CTFProjectile_Arrow::Create( const Vector &vecOrigin, const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer ) +{ + const char* pszArrowEntityName = GetArrowEntityName( projectileType ); + CTFProjectile_Arrow *pArrow = static_cast<CTFProjectile_Arrow*>( CBaseEntity::Create( pszArrowEntityName, vecOrigin, vecAngles, pOwner ) ); + if ( pArrow ) + { + pArrow->InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer ); + } + + return pArrow; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer ) +{ + // Initialize the owner. + SetOwnerEntity( pOwner ); + + // Set team. + ChangeTeam( pOwner->GetTeamNumber() ); + + // must override projectile type before Spawn for proper model + m_iProjectileType = projectileType; + + // Spawn. + Spawn(); + + SetGravity( fGravity ); + + SetCritical( true ); + + // Setup the initial velocity. + Vector vecForward, vecRight, vecUp; + AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp ); + + Vector vecVelocity = vecForward * fSpeed; + + SetAbsVelocity( vecVelocity ); + SetupInitialTransmittedGrenadeVelocity( vecVelocity ); + + // Setup the initial angles. + QAngle angles; + VectorAngles( vecVelocity, angles ); + SetAbsAngles( angles ); + + // Save the scoring player. + SetScorer( pScorer ); + + // Create a trail. + CreateTrail(); + + // Add ourselves to the hit entities list so we dont shoot ourselves + m_HitEntities.AddToTail( pOwner->entindex() ); + + m_flInitTime = gpGlobals->curtime; + +#ifdef STAGING_ONLY + if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET ) + { + CTFPlayer* pTFOwner = ToTFPlayer( pOwner ); + m_bFiredWhileZoomed = ( pTFOwner && pTFOwner->m_Shared.InCond( TF_COND_ZOOMED ) ); + } + else +#endif // STAGING_ONLY + { + m_bFiredWhileZoomed = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::Spawn() +{ + if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT ) + { + SetModel( g_pszArrowModels[MODEL_ARROW_BUILDING_REPAIR] ); + m_iWeaponId = TF_WEAPON_SHOTGUN_BUILDING_RESCUE; + } + else if ( m_iProjectileType == TF_PROJECTILE_FESTIVE_ARROW ) + { + SetModel( g_pszArrowModels[MODEL_FESTIVE_ARROW_REGULAR] ); + } + else if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT +#ifdef STAGING_ONLY + || m_iProjectileType == TF_PROJECTILE_MILK_BOLT +#endif + ) { + SetModel( g_pszArrowModels[MODEL_SYRINGE] ); + SetModelScale( 3.0f ); + } + else if ( m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT ) + { + SetModel( g_pszArrowModels[MODEL_FESTIVE_HEALING_BOLT] ); + SetModelScale( 3.0f ); + } +#ifdef STAGING_ONLY + else if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET ) + { + SetModel( g_pszArrowModels[MODEL_SYRINGE] ); + //SetModelScale( 3.0f ); + } +#endif // STAGING_ONLY + else if ( m_iProjectileType == TF_PROJECTILE_GRAPPLINGHOOK ) + { + SetModel( g_pszArrowModels[MODEL_GRAPPLINGHOOK] ); + } + else + { + SetModel( g_pszArrowModels[MODEL_ARROW_REGULAR] ); + } + + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); + UTIL_SetSize( this, Vector( -1.0f, -1.0f, -1.0f ), Vector( 1.0f, 1.0f, 1.0f ) ); + SetSolid( SOLID_BBOX ); + + SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); + AddEffects( EF_NOSHADOW ); + AddFlag( FL_GRENADE ); + + SetTouch( &CTFProjectile_Arrow::ArrowTouch ); + + // Set team. + m_nSkin = GetArrowSkin(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::Precache() +{ + int arrow_model = PrecacheModel( g_pszArrowModels[MODEL_ARROW_REGULAR] ); + int claw_model = PrecacheModel( g_pszArrowModels[MODEL_ARROW_BUILDING_REPAIR] ); + int festive_arrow_model = PrecacheModel( g_pszArrowModels[MODEL_FESTIVE_ARROW_REGULAR] ); + PrecacheModel( g_pszArrowModels[MODEL_FESTIVE_HEALING_BOLT] ); + + PrecacheGibsForModel( arrow_model ); + PrecacheGibsForModel( claw_model ); + PrecacheGibsForModel( festive_arrow_model ); + //PrecacheGibsForModel( festive_healing_arrow_model ); + PrecacheModel( "effects/arrowtrail_red.vmt" ); + PrecacheModel( "effects/arrowtrail_blu.vmt" ); + PrecacheModel( "effects/healingtrail_red.vmt" ); + PrecacheModel( "effects/healingtrail_blu.vmt" ); + PrecacheModel( CLAW_TRAIL_RED ); + PrecacheModel( CLAW_TRAIL_BLU ); + PrecacheParticleSystem( CLAW_REPAIR_EFFECT_BLU ); + PrecacheParticleSystem( CLAW_REPAIR_EFFECT_RED ); + PrecacheScriptSound( "Weapon_Arrow.ImpactFlesh" ); + PrecacheScriptSound( "Weapon_Arrow.ImpactMetal" ); + PrecacheScriptSound( "Weapon_Arrow.ImpactWood" ); + PrecacheScriptSound( "Weapon_Arrow.ImpactConcrete" ); + PrecacheScriptSound( "Weapon_Arrow.Nearmiss" ); + PrecacheScriptSound( "Weapon_Arrow.ImpactFleshCrossbowHeal" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::SetScorer( CBaseEntity *pScorer ) +{ + m_Scorer = pScorer; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBasePlayer *CTFProjectile_Arrow::GetScorer( void ) +{ + return dynamic_cast<CBasePlayer *>( m_Scorer.Get() ); +} + +//----------------------------------------------------------------------------- +bool CTFProjectile_Arrow::CanHeadshot() +{ + CBaseEntity *pOwner = GetScorer(); + if ( pOwner == NULL ) + return false; + + if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT + || m_iProjectileType == TF_PROJECTILE_HEALING_BOLT + || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT +#ifdef STAGING_ONLY + || m_iProjectileType == TF_PROJECTILE_MILK_BOLT +#endif + ) { + return false; + } + +#ifdef STAGING_ONLY + if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET ) + { + return m_bFiredWhileZoomed; + } +#endif // STAGING_ONLY + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Healing bolt damage. +//----------------------------------------------------------------------------- +float CTFProjectile_Arrow::GetDamage() +{ + if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT + || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT +#ifdef STAGING_ONLY + || m_iProjectileType == TF_PROJECTILE_MILK_BOLT +#endif + ) { + float lifeTimeScale = RemapValClamped( gpGlobals->curtime - m_flInitTime, 0.0f, 0.6f, 0.5f, 1.0f ); + return m_flDamage * lifeTimeScale; + } + return BaseClass::GetDamage(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Moves the arrow to a particular bbox. +//----------------------------------------------------------------------------- +bool CTFProjectile_Arrow::PositionArrowOnBone( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim ) +{ + CStudioHdr *pStudioHdr = pOtherAnim->GetModelPtr(); + if ( !pStudioHdr ) + return false; + + mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pOtherAnim->GetHitboxSet() ); + if ( !set ) + return false; + if ( !set->numhitboxes ) // Target must have hit boxes. + return false; + + if ( pBox->bone < 0 || pBox->bone >= pStudioHdr->numbones() ) // Bone index must be valid. + return false; + + CBoneCache *pCache = pOtherAnim->GetBoneCache(); + if ( !pCache ) + return false; + + matrix3x4_t *bone_matrix = pCache->GetCachedBone( pBox->bone ); + if ( !bone_matrix ) + return false; + + Vector vecBoxAbsMins, vecBoxAbsMaxs; + TransformAABB( *bone_matrix, pBox->bbmin, pBox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs ); + + // Adjust the arrow so it isn't exactly in the center of the box. + Vector position; + Vector vecDelta = vecBoxAbsMaxs - vecBoxAbsMins; + float frand = (float) rand() / VALVE_RAND_MAX; + position.x = vecBoxAbsMins.x + vecDelta.x*0.6f - vecDelta.x*frand*0.2f; + frand = (float) rand() / VALVE_RAND_MAX; + position.y = vecBoxAbsMins.y + vecDelta.y*0.6f - vecDelta.y*frand*0.2f; + frand = (float) rand() / VALVE_RAND_MAX; + position.z = vecBoxAbsMins.z + vecDelta.z*0.6f - vecDelta.z*frand*0.2f; + SetAbsOrigin( position ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: This was written after PositionArrowOnBone, but the two might be mergable? +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::GetBoneAttachmentInfo( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim, Vector &bonePosition, QAngle &boneAngles, int &boneIndexAttached, int &physicsBoneIndex ) +{ + // Find a bone to stick to. + matrix3x4_t arrowWorldSpace; + MatrixCopy( EntityToWorldTransform(), arrowWorldSpace ); + + // Get the bone info so we can follow the bone. + boneIndexAttached = pBox->bone; + physicsBoneIndex = pOtherAnim->GetPhysicsBone( boneIndexAttached ); + matrix3x4_t boneToWorld; + pOtherAnim->GetBoneTransform( boneIndexAttached, boneToWorld ); + + Vector attachedBonePos; + QAngle attachedBoneAngles; + pOtherAnim->GetBonePosition( boneIndexAttached, attachedBonePos, attachedBoneAngles ); + + // Transform my current position/orientation into the hit bone's space. + matrix3x4_t worldToBone, localMatrix; + MatrixInvert( boneToWorld, worldToBone ); + ConcatTransforms( worldToBone, arrowWorldSpace, localMatrix ); + MatrixAngles( localMatrix, boneAngles, bonePosition ); +} + +//----------------------------------------------------------------------------- +int CTFProjectile_Arrow::GetProjectileType ( void ) const +{ + return m_iProjectileType; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFProjectile_Arrow::StrikeTarget( mstudiobbox_t *pBox, CBaseEntity *pOther ) +{ + if ( !pOther ) + return false; + + // Different path for arrows that heal friendly buildings. + if ( pOther->IsBaseObject() ) + { + if ( OnArrowImpactObject( pOther ) ) + { + return false; + } + } + + // Block and break on invulnerable players + CTFPlayer *pTFPlayerOther = ToTFPlayer( pOther ); + if ( pTFPlayerOther && pTFPlayerOther->m_Shared.IsInvulnerable() ) + return false; + + CBaseAnimating *pOtherAnim = dynamic_cast< CBaseAnimating* >(pOther); + if ( !pOtherAnim ) + return false; + + bool bBreakArrow = IsBreakable() && ( ( dynamic_cast< CTFTankBoss* >( pOther ) != NULL ) || ( dynamic_cast< CHalloweenBaseBoss* >( pOther ) != NULL ) ); + + // Position the arrow so its on the bone, within a reasonable region defined by the bbox. + if ( !m_bPenetrate && !bBreakArrow ) + { + if ( !PositionArrowOnBone( pBox, pOtherAnim ) ) + { + return false; + } + } + + // + const Vector &vecOrigin = GetAbsOrigin(); + Vector vecVelocity = GetAbsVelocity(); + int nDamageCustom = 0; + bool bApplyEffect = true; + int nDamageType = GetDamageType(); + + // Are we a headshot? + bool bHeadshot = false; + if ( pBox->group == HITGROUP_HEAD && CanHeadshot() ) + { + bHeadshot = true; + } + + // Damage the entity we struck. + CBaseEntity *pAttacker = GetScorer(); + if ( !pAttacker ) + { + // likely not launched by a player + pAttacker = GetOwnerEntity(); + } + + if ( pAttacker ) + { + // Check if we have the penetrate attribute. We don't want + // to strike the same target multiple times. + if ( m_bPenetrate ) + { + // Don't strike the same target again + if ( m_HitEntities.Find( pOther->entindex() ) != m_HitEntities.InvalidIndex() ) + { + bApplyEffect = false; + } + else + { + m_HitEntities.AddToTail( pOther->entindex() ); + } + } + + if ( !InSameTeam( pOther ) ) + { + IScorer *pScorerInterface = dynamic_cast<IScorer*>( pAttacker ); + if ( pScorerInterface ) + { + pAttacker = pScorerInterface->GetScorer(); + } + + if ( m_bArrowAlight ) + { + nDamageType |= DMG_IGNITE; + nDamageCustom = TF_DMG_CUSTOM_FLYINGBURN; + } + + if ( bHeadshot ) + { + nDamageType |= DMG_CRITICAL; + nDamageCustom = TF_DMG_CUSTOM_HEADSHOT; + } + + if ( m_bCritical ) + { + nDamageType |= DMG_CRITICAL; + } + +#ifdef GAME_DLL + if ( TFGameRules()->IsPVEModeControlled( pAttacker ) ) + { + // scenario bots cant crit (unless they always do) + CTFBot *bot = ToTFBot( pAttacker ); + if ( !bot || !bot->HasAttribute( CTFBot::ALWAYS_CRIT ) ) + { + nDamageType &= ~DMG_CRITICAL; + } + } +#endif + // Damage + if ( bApplyEffect ) + { + // Apply Milk First so we can get health from this + if ( m_bApplyMilkOnHit && pOther->IsPlayer() ) + { + CTFPlayer *pVictim = ToTFPlayer( pOther ); + if ( pVictim && pVictim->m_Shared.CanBeDebuffed() && pVictim->CanGetWet() ) + { + // duration is based on damage + float flDuration = RemapValClamped( GetDamage(), 25.0f, 75.0f, 6.0f, 10.0f ); + pVictim->m_Shared.AddCond( TF_COND_MAD_MILK, flDuration, pAttacker ); + pVictim->m_Shared.SetPeeAttacker( ToTFPlayer( pAttacker ) ); + pVictim->SpeakConceptIfAllowed( MP_CONCEPT_JARATE_HIT ); + } + } + + CTakeDamageInfo info( this, pAttacker, m_hLauncher, vecVelocity, vecOrigin, GetDamage(), nDamageType, nDamageCustom ); + pOther->TakeDamage( info ); + + // Play an impact sound. + ImpactSound( "Weapon_Arrow.ImpactFlesh", true ); + } + } + else if ( pOther->IsPlayer() ) // Hit a team-mate. + { + // Heal + if ( bApplyEffect ) + { + ImpactTeamPlayer( dynamic_cast<CTFPlayer*>( pOther ) ); + } + } + } + + if ( !m_bPenetrate && !bBreakArrow ) + { + OnArrowImpact( pBox, pOther, pAttacker ); + } + + // Perform a blood mesh decal trace. + trace_t tr; + Vector start = vecOrigin - vecVelocity * gpGlobals->frametime; + Vector end = vecOrigin + vecVelocity * gpGlobals->frametime; + CTraceFilterCollisionArrows filter( this, GetOwnerEntity() ); + UTIL_TraceLine( start, end, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); + UTIL_ImpactTrace( &tr, 0 ); + + // Break it? + if ( bBreakArrow ) + { + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::OnArrowImpact( mstudiobbox_t *pBox, CBaseEntity *pOther, CBaseEntity *pAttacker ) +{ + CBaseAnimating *pOtherAnim = dynamic_cast< CBaseAnimating* >(pOther); + if ( !pOtherAnim ) + return; + + const Vector &vecOrigin = GetAbsOrigin(); + Vector vecVelocity = GetAbsVelocity(); + + Vector bonePosition = vec3_origin; + QAngle boneAngles = QAngle(0,0,0); + int boneIndexAttached = -1; + int physicsBoneIndex = -1; + GetBoneAttachmentInfo( pBox, pOtherAnim, bonePosition, boneAngles, boneIndexAttached, physicsBoneIndex ); + bool bSendImpactMessage = true; + + // Did we kill the target? + if ( !pOther->IsAlive() && pOther->IsPlayer() ) + { + CTFPlayer *pTFPlayerOther = dynamic_cast<CTFPlayer*>(pOther); + if ( pTFPlayerOther && pTFPlayerOther->m_hRagdoll ) + { + VectorNormalize( vecVelocity ); + if ( CheckRagdollPinned( vecOrigin, vecVelocity, boneIndexAttached, physicsBoneIndex, pTFPlayerOther->m_hRagdoll, pBox->group, pTFPlayerOther->entindex() ) ) + { + pTFPlayerOther->StopRagdollDeathAnim(); + bSendImpactMessage = false; + } + } + } + + // Notify relevant clients of an arrow impact. + if ( bSendImpactMessage ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "arrow_impact" ); + if ( event ) + { + event->SetInt( "attachedEntity", pOther->entindex() ); + event->SetInt( "shooter", pAttacker ? pAttacker->entindex() : 0 ); + event->SetInt( "attachedEntity", pOther->entindex() ); + event->SetInt( "boneIndexAttached", boneIndexAttached ); + event->SetFloat( "bonePositionX", bonePosition.x ); + event->SetFloat( "bonePositionY", bonePosition.y ); + event->SetFloat( "bonePositionZ", bonePosition.z ); + event->SetFloat( "boneAnglesX", boneAngles.x ); + event->SetFloat( "boneAnglesY", boneAngles.y ); + event->SetFloat( "boneAnglesZ", boneAngles.z ); + event->SetInt( "projectileType", GetProjectileType() ); + gameeventmanager->FireEvent( event ); + } + } + + FadeOut( 3.0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFProjectile_Arrow::OnArrowImpactObject( CBaseEntity *pOther ) +{ + if ( InSameTeam( pOther ) ) + { + BuildingHealingArrow( pOther ); + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::ImpactThink( void ) +{ +} + +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::BuildingHealingArrow( CBaseEntity *pOther ) +{ + // This arrow impacted a building + // If its a building on our team, heal it + if ( !pOther->IsBaseObject() ) + return; + + CBaseEntity *pAttacker = GetScorer(); + if ( pAttacker == NULL ) + return; + + // if not on our team, forget about it + if ( GetTeamNumber() != pOther->GetTeamNumber() ) + return; + + int iArrowsHealBuildings = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iArrowsHealBuildings, arrow_heals_buildings ); + if ( iArrowsHealBuildings == 0 ) + return; + + CBaseObject *pBuilding = dynamic_cast< CBaseObject * >( pOther ); + if ( !pBuilding || !pBuilding->CanBeRepaired() || pBuilding->HasSapper() || pBuilding->IsPlasmaDisabled() || pBuilding->IsBuilding() || pBuilding->IsPlacing() ) + return; + + // if building is sheilded, reduce health gain + if ( pBuilding->GetShieldLevel() == SHIELD_NORMAL ) + { + iArrowsHealBuildings *= SHIELD_NORMAL_VALUE; + } + + float flNewHealth = MIN( pBuilding->GetMaxHealth(), (int)pBuilding->GetHealth() + iArrowsHealBuildings ); + int iHealthAdded = (int)(flNewHealth - pBuilding->GetHealth()); + if ( iHealthAdded > 0 ) + { + pBuilding->SetHealth( flNewHealth ); + + IGameEvent * event = gameeventmanager->CreateEvent( "building_healed" ); + if ( event ) + { + // HLTV event priority, not transmitted + event->SetInt( "priority", 1 ); + + // Healed by another player. + event->SetInt( "building", pBuilding->entindex() ); + event->SetInt( "healer", pAttacker->entindex() ); + event->SetInt( "amount", iHealthAdded ); + gameeventmanager->FireEvent( event ); + } + + const char *pParticleName = GetTeamNumber() == TF_TEAM_BLUE ? CLAW_REPAIR_EFFECT_BLU : CLAW_REPAIR_EFFECT_RED; + CPVSFilter filter( GetAbsOrigin() ); + TE_TFParticleEffect( filter, 0.0, pParticleName, GetAbsOrigin(), vec3_angle ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFProjectile_Arrow::GetArrowSkin() const +{ + int nTeam = GetTeamNumber(); + if ( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() ) + { + CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( pOwner && pOwner->IsPlayerClass( TF_CLASS_SPY ) && pOwner->m_Shared.InCond( TF_COND_DISGUISED ) ) + { + nTeam = pOwner->m_Shared.GetDisguiseTeam(); + } + } + return ( nTeam == TF_TEAM_BLUE ) ? 1 : 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::OnArrowMissAllPlayers() +{ + CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() ); + if( pOwner && pOwner->IsPlayerClass( TF_CLASS_SNIPER ) ) + { + EconEntity_OnOwnerKillEaterEventNoPartner( assert_cast<CEconEntity *>( m_hLauncher.Get() ), pOwner, kKillEaterEvent_NEGATIVE_SniperShotsMissed ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::ArrowTouch( CBaseEntity *pOther ) +{ + // Safety net hack: + // We routinely introduce new entity types, and arrows + // are repeat-offenders at not getting along with them. + // If enough time goes by, just remove the arrow. + float flAliveTime = gpGlobals->curtime - m_flInitTime; + if ( flAliveTime >= 10.f ) + { + Warning( "Arrow alive for %f3.2\n seconds", flAliveTime ); + UTIL_Remove( this ); + } + + if ( m_bStruckEnemy || (GetMoveType() == MOVETYPE_NONE) ) + return; + + if ( !pOther ) + return; + + bool bShield = pOther->IsCombatItem() && !InSameTeam( pOther ); + CTFPumpkinBomb *pPumpkinBomb = dynamic_cast< CTFPumpkinBomb * >( pOther ); + + if ( pOther->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) && !pPumpkinBomb && !bShield ) + return; + + // test against combat characters, which include players, engineer buildings, and NPCs + CBaseCombatCharacter *pOtherCombatCharacter = dynamic_cast< CBaseCombatCharacter * >( pOther ); + + if ( !pOtherCombatCharacter ) + { + // It might be a track train with boss parented + pOtherCombatCharacter = dynamic_cast< CBaseCombatCharacter * >( pOther->FirstMoveChild() ); + if ( pOtherCombatCharacter ) + { + pOther = pOtherCombatCharacter; + } + } + + CTFMerasmusTrickOrTreatProp *pMerasmusProp = dynamic_cast< CTFMerasmusTrickOrTreatProp* >( pOther ); + CTFRobotDestruction_Robot *pRobot = dynamic_cast< CTFRobotDestruction_Robot* >( pOther ); + if ( pOther->IsWorld() || ( !pOtherCombatCharacter && !pPumpkinBomb && !pMerasmusProp && !bShield && !pRobot ) ) + { + // Check to see if we struck the skybox. + CheckSkyboxImpact( pOther ); + + // If we've only got 1 entity in the hit list (the attacker by default) and we've not been deflected + // then we can consider this arrow to have completely missed all players. + if( m_HitEntities.Count() == 1 && GetDeflected() == 0 ) + { + OnArrowMissAllPlayers(); + } + + return; + } + + CBaseAnimating *pAnimOther = dynamic_cast<CBaseAnimating*>(pOther); + CStudioHdr *pStudioHdr = NULL; + mstudiohitboxset_t *set = NULL; + if ( pAnimOther ) + { + pStudioHdr = pAnimOther->GetModelPtr(); + if ( pStudioHdr ) + { + set = pStudioHdr->pHitboxSet( pAnimOther->GetHitboxSet() ); + } + } + + if ( !pAnimOther || !pStudioHdr || !set ) + { + // Whatever we hit doesn't have hitboxes. Ignore it. + UTIL_Remove( this ); + return; + } + + // We struck the collision box of a player or a buildable object. + // Trace forward to see if we struck a hitbox. + CTraceFilterCollisionArrows filter( this, GetOwnerEntity() ); + Vector start = GetAbsOrigin(); + Vector vel = GetAbsVelocity(); + trace_t tr; + UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); + + // If we hit a hitbox, stop tracing. + mstudiobbox_t *closest_box = NULL; + if ( tr.m_pEnt && tr.m_pEnt->GetTeamNumber() != GetTeamNumber() ) + { + // This means the arrow was true and was flying directly at a hitbox on the target. + // We'll attach to that hitbox. + closest_box = set->pHitbox( tr.hitbox ); + } + + if ( !closest_box ) + { + // Locate the hitbox closest to our point of impact on the collision box. + Vector position, start, forward; + QAngle angles; + float closest_dist = 99999; + + // Intense, but extremely accurate: + AngleVectors( GetAbsAngles(), &forward ); + start = GetAbsOrigin() + forward*16; + for ( int i = 0; i < set->numhitboxes; i++ ) + { + mstudiobbox_t *pbox = set->pHitbox( i ); + + pAnimOther->GetBonePosition( pbox->bone, position, angles ); + + Ray_t ray; + ray.Init( start, position ); + trace_t tr; + IntersectRayWithBox( ray, position+pbox->bbmin, position+pbox->bbmax, 0.f, &tr ); + float dist = tr.endpos.DistTo( start ); + + if ( dist < closest_dist ) + { + closest_dist = dist; + closest_box = pbox; + } + } + } + + if ( closest_box ) + { + // See if we're supposed to stick in the target. + bool bStrike = StrikeTarget( closest_box, pOther ); + if ( bStrike && !m_bPenetrate) + { + // If we're here, it means StrikeTarget() called FadeOut( 3.0 ) + SetAbsOrigin( start ); + } + + if ( !bStrike || bShield ) + { + BreakArrow(); + } + + // Slightly confusing. If we're here, the arrow stopped at the + // target and will fade or break. Setting this prevents the + // touch code from re-running during the delay. + if ( !m_bPenetrate ) + { + m_bStruckEnemy = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::CheckSkyboxImpact( CBaseEntity *pOther ) +{ + trace_t tr; + Vector velDir = GetAbsVelocity(); + VectorNormalize( velDir ); + Vector vecSpot = GetAbsOrigin() - velDir * 32; + UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr ); + if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY ) + { + // We hit the skybox, go away soon. + FadeOut( 3.f ); + return; + } + + if ( !pOther->IsWorld() ) + { + BreakArrow(); + } + else + { + CEffectData data; + data.m_vOrigin = tr.endpos; + data.m_vNormal = velDir; + data.m_nEntIndex = 0;/*tr.fraction != 1.0f;*/ + data.m_nAttachmentIndex = 0; + data.m_nMaterial = 0; + data.m_fFlags = GetProjectileType(); + data.m_nColor = GetArrowSkin(); + + DispatchEffect( "TFBoltImpact", data ); + + FadeOut( 3.f ); + + // Play an impact sound. + const char* pszSoundName = "Weapon_Arrow.ImpactMetal"; + surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps ); + if ( psurf ) + { + switch ( psurf->game.material ) + { + case CHAR_TEX_GRATE: + case CHAR_TEX_METAL: + pszSoundName = "Weapon_Arrow.ImpactMetal"; + break; + + case CHAR_TEX_CONCRETE: + pszSoundName = "Weapon_Arrow.ImpactConcrete"; + break; + + case CHAR_TEX_WOOD: + pszSoundName = "Weapon_Arrow.ImpactWood"; + break; + } + } + ImpactSound( pszSoundName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Plays an impact sound. Louder for the attacker. +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::ImpactSound( const char *pszSoundName, bool bLoudForAttacker ) +{ + CTFPlayer *pAttacker = ToTFPlayer( GetScorer() ); + if ( !pAttacker ) + return; + + if ( bLoudForAttacker ) + { + float soundlen = 0; + EmitSound_t params; + params.m_flSoundTime = 0; + params.m_pSoundName = pszSoundName; + params.m_pflSoundDuration = &soundlen; + CPASFilter filter( GetAbsOrigin() ); + filter.RemoveRecipient( ToTFPlayer(pAttacker) ); + EmitSound( filter, entindex(), params ); + + CSingleUserRecipientFilter attackerFilter( ToTFPlayer(pAttacker) ); + EmitSound( attackerFilter, pAttacker->entindex(), params ); + } + else + { + EmitSound( pszSoundName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::BreakArrow() +{ + FadeOut( 3.f ); + CPVSFilter filter( GetAbsOrigin() ); + UserMessageBegin( filter, "BreakModel" ); + WRITE_SHORT( GetModelIndex() ); + WRITE_VEC3COORD( GetAbsOrigin() ); + WRITE_ANGLES( GetAbsAngles() ); + WRITE_SHORT( m_nSkin ); + MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFProjectile_Arrow::CheckRagdollPinned( const Vector &start, const Vector &vel, int boneIndexAttached, int physicsBoneIndex, CBaseEntity *pOther, int iHitGroup, int iVictim ) +{ + // Pin to the wall. + trace_t tr; + UTIL_TraceLine( start, start + vel * 125, MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0f && tr.DidHitWorld() ) + { + CEffectData data; + + data.m_vOrigin = tr.endpos; + data.m_vNormal = vel; + data.m_nEntIndex = pOther->entindex(); + data.m_nAttachmentIndex = boneIndexAttached; + data.m_nMaterial = physicsBoneIndex; + data.m_nDamageType = iHitGroup; + data.m_nSurfaceProp = iVictim; + data.m_fFlags = GetProjectileType(); + data.m_nColor = GetArrowSkin(); + + if ( GetScorer() ) + { + data.m_nHitBox = GetScorer()->entindex(); + } + + DispatchEffect( "TFBoltImpact", data ); + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::FadeOut( int iTime ) +{ + SetMoveType( MOVETYPE_NONE ); + SetAbsVelocity( vec3_origin ); + AddSolidFlags( FSOLID_NOT_SOLID ); + AddEffects( EF_NODRAW ); + + // Start remove timer. + SetContextThink( &CTFProjectile_Arrow::RemoveThink, gpGlobals->curtime + iTime, "ARROW_REMOVE_THINK" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::RemoveThink( void ) +{ + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +const char *CTFProjectile_Arrow::GetTrailParticleName( void ) +{ + if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT ) + { + return ( GetTeamNumber() == TF_TEAM_RED ) ? CLAW_TRAIL_RED : CLAW_TRAIL_BLU; + } + else if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT ) + { + return ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/healingtrail_red.vmt" : "effects/healingtrail_blu.vmt"; + } + + return ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/arrowtrail_red.vmt" : "effects/arrowtrail_blu.vmt"; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::CreateTrail( void ) +{ + if ( IsDormant() ) + return; + + if ( !m_pTrail ) + { + int width = 3; + switch ( m_iProjectileType ) + { + case TF_PROJECTILE_BUILDING_REPAIR_BOLT: + width = 5; + break; + case TF_PROJECTILE_HEALING_BOLT: + case TF_PROJECTILE_FESTIVE_HEALING_BOLT: + case TF_PROJECTILE_GRAPPLINGHOOK: +#ifdef STAGING_ONLY + case TF_PROJECTILE_SNIPERBULLET: +#endif // STAGING_ONLY + return; // do not create arrow trail for healing bolt, use particle instead (client only) + } + + const char *pTrailTeamName = GetTrailParticleName(); + CSpriteTrail *pTempTrail = NULL; + + pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, GetAbsOrigin(), true ); + pTempTrail->FollowEntity( this ); + pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone ); + pTempTrail->SetStartWidth( width ); + pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); + pTempTrail->SetLifeTime( 0.3 ); + pTempTrail->TurnOn(); + pTempTrail->SetAttachment( this, 0 ); + m_pTrail = pTempTrail; + SetContextThink( &CTFProjectile_Arrow::RemoveTrail, gpGlobals->curtime + 3, "FadeTrail"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fade and kill the trail +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::RemoveTrail( void ) +{ + if ( !m_pTrail ) + return; + + if ( m_pTrail ) + { + if ( m_flTrailLife <= 0 ) + { + UTIL_Remove( m_pTrail ); + m_flTrailLife = 1.0f; + } + else + { + float fAlpha = 128 * m_flTrailLife; + + CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pTrail.Get() ); + + if ( pTempTrail ) + { + pTempTrail->SetBrightness( int(fAlpha) ); + } + + m_flTrailLife = m_flTrailLife - 0.1f; + SetContextThink( &CTFProjectile_Arrow::RemoveTrail, gpGlobals->curtime + 0.05, "FadeTrail"); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::AdjustDamageDirection( const CTakeDamageInfo &info, Vector &dir, CBaseEntity *pEnt ) +{ + if ( pEnt ) + { + dir = info.GetDamagePosition() - info.GetDamageForce() - pEnt->WorldSpaceCenter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Arrow was deflected. +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::IncrementDeflected( void ) +{ + m_iDeflected++; + + // Change trail color. + if ( m_pTrail ) + { + UTIL_Remove( m_pTrail ); + m_pTrail = NULL; + m_flTrailLife = 1.0f; + } + CreateTrail(); +} + +//----------------------------------------------------------------------------- +// Purpose: Arrow was deflected. +//----------------------------------------------------------------------------- +void CTFProjectile_Arrow::Deflected( CBaseEntity *pDeflectedBy, Vector &vecDir ) +{ + CTFPlayer *pTFDeflector = ToTFPlayer( pDeflectedBy ); + if ( !pTFDeflector ) + return; + + ChangeTeam( pTFDeflector->GetTeamNumber() ); + SetLauncher( pTFDeflector->GetActiveWeapon() ); + + CTFPlayer* pOldOwner = ToTFPlayer( GetOwnerEntity() ); + SetOwnerEntity( pTFDeflector ); + + if ( pOldOwner ) + { + pOldOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:1,victim:1" ); + } + + if ( pTFDeflector->m_Shared.IsCritBoosted() ) + { + SetCritical( true ); + } + + CTFWeaponBase::SendObjectDeflectedEvent( pTFDeflector, pOldOwner, GetWeaponID(), this ); + + IncrementDeflected(); + SetScorer( pTFDeflector ); + + // Purge our hit list so we can hit everyone again + m_HitEntities.Purge(); + // Add ourselves so we dont hit ourselves + m_HitEntities.AddToTail( pTFDeflector->entindex() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup function. +//----------------------------------------------------------------------------- +void CTFProjectile_HealingBolt::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer ) +{ + BaseClass::InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer ); + + //SetNextThink( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Healing bolt heal. +//----------------------------------------------------------------------------- +void CTFProjectile_HealingBolt::ImpactTeamPlayer( CTFPlayer *pOther ) +{ + if ( !pOther ) + return; + +#ifdef STAGING_ONLY + // Milk Arrows only heal teammates on special shot + if ( GetProjectileType() == TF_PROJECTILE_MILK_BOLT && !m_bApplyMilkOnHit ) + return; +#endif + + CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); + if ( !pOwner ) + return; + + // Don't heal players using a weapon that blocks healing + CTFWeaponBase *pWeapon = pOther->GetActiveTFWeapon(); + if ( pWeapon ) + { + int iBlockHealing = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iBlockHealing, weapon_blocks_healing ); + if ( iBlockHealing ) + return; + } + + float flHealth = GetDamage() * 2.0f; + +#ifdef STAGING_ONLY + // Milk Arrows give a resist bubble on hitting a teammate + if ( GetProjectileType() == TF_PROJECTILE_MILK_BOLT ) + { + // use damage to scale time + float flResistDuration = RemapValClamped( flHealth, 0, 150, 1, 3 ); + pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST, flResistDuration, pOwner ); + pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST, flResistDuration, pOwner ); + pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST, flResistDuration, pOwner ); + } +#endif + + // Scale this if needed + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOther, flHealth, mult_healing_from_medics ); + + int iActualHealed = pOther->TakeHealth( flHealth, DMG_GENERIC ); + if ( iActualHealed <= 0 ) + return; + + // Play an impact sound. + ImpactSound( "Weapon_Arrow.ImpactFleshCrossbowHeal" ); + + CTF_GameStats.Event_PlayerHealedOther( pOwner, flHealth ); + + IGameEvent * event = gameeventmanager->CreateEvent( "player_healed" ); + if ( event ) + { + // HLTV event priority, not transmitted + event->SetInt( "priority", 1 ); + + // Healed by another player. + event->SetInt( "patient", pOther->GetUserID() ); + event->SetInt( "healer", pOwner->GetUserID() ); + event->SetInt( "amount", flHealth ); + gameeventmanager->FireEvent( event ); + } + + event = gameeventmanager->CreateEvent( "player_healonhit" ); + if ( event ) + { + event->SetInt( "amount", flHealth ); + event->SetInt( "entindex", pOther->entindex() ); + item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX; + if ( pWeapon && pWeapon->GetAttributeContainer() && pWeapon->GetAttributeContainer()->GetItem() ) + { + healingItemDef = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex(); + } + event->SetInt( "weapon_def_index", healingItemDef ); + gameeventmanager->FireEvent( event ); + } + + event = gameeventmanager->CreateEvent( "crossbow_heal" ); + if ( event ) + { + event->SetInt( "healer", pOwner->GetUserID() ); + event->SetInt( "target", pOther->GetUserID() ); + event->SetInt( "amount", flHealth ); + gameeventmanager->FireEvent( event ); + } + + // Give a litte bit of uber based on actual healing + // Give them a little bit of Uber + CWeaponMedigun *pMedigun = static_cast<CWeaponMedigun *>( pOwner->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) ); + if ( pMedigun ) + { + // On Mediguns, per frame, the amount of uber added is based on + // Default heal rate is 24per second, we scale based on that and frametime + pMedigun->AddCharge( ( iActualHealed / 24.0f ) * gpGlobals->frametime ); + } + pOther->m_Shared.AddCond( TF_COND_HEALTH_OVERHEALED, 1.2f ); + + EconEntity_OnOwnerKillEaterEvent_Batched( dynamic_cast<CEconEntity *>( GetLauncher() ), pOwner, pOther, kKillEaterEvent_AllyHealingDone, flHealth ); +} + + +CTFProjectile_GrapplingHook::CTFProjectile_GrapplingHook() + : m_pImpactFleshSoundLoop( NULL ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::Spawn() +{ + BaseClass::Spawn(); + + SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM ); +} + + +void CTFProjectile_GrapplingHook::Precache() +{ + BaseClass::Precache(); + + PrecacheModel( "models/weapons/c_models/c_grapple_proj/c_grapple_proj.mdl" ); + PrecacheScriptSound( "WeaponGrapplingHook.ImpactFlesh" ); + PrecacheScriptSound( "WeaponGrapplingHook.ImpactDefault" ); + PrecacheScriptSound( "WeaponGrapplingHook.ImpactFleshLoop" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Spawn +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::UpdateOnRemove() +{ + // clear hook target + CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pTFPlayer ) + { + // Clear any healers grappling with us + SetMedicsGrapplingHookTarget( pTFPlayer, NULL ); + pTFPlayer->SetGrapplingHookTarget( NULL ); + pTFPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK ); + } + + StopImpactFleshSoundLoop(); + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup function. +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer ) +{ + BaseClass::InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer ); + + CTFPlayer *pTFPlayer = ToTFPlayer( pOwner ); + if ( pTFPlayer ) + { + pTFPlayer->m_Shared.AddCond( TF_COND_GRAPPLINGHOOK ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: OnArrowImpact +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::OnArrowImpact( mstudiobbox_t *pBox, CBaseEntity *pOther, CBaseEntity *pAttacker ) +{ + HookTarget( pOther ); +} + + +//----------------------------------------------------------------------------- +// Purpose: OnArrowImpactObject +//----------------------------------------------------------------------------- +bool CTFProjectile_GrapplingHook::OnArrowImpactObject( CBaseEntity *pOther ) +{ + HookTarget( pOther ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: CheckSkyboxImpact +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::CheckSkyboxImpact( CBaseEntity *pOther ) +{ + trace_t tr; + Vector velDir = GetAbsVelocity(); + VectorNormalize( velDir ); + Vector vecSpot = GetAbsOrigin() - velDir * 32; + UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr ); + if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY ) + { + // We hit the skybox, go away soon. + FadeOut( 1.f ); + return; + } + + if ( !pOther->IsWorld() ) + { + HookTarget( pOther ); + } + else + { + HookTarget( pOther ); + + // rotate the hook model to be perpendicular to the world surface + Vector vUp; + AngleVectors( GetAbsAngles(), NULL, NULL, &vUp ); + QAngle qNewAngles; + VectorAngles( -tr.plane.normal, vUp, qNewAngles ); + SetAbsAngles( qNewAngles ); + SetAbsOrigin( GetAbsOrigin() + 3.f * tr.plane.normal ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: HookTarget +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::HookTarget( CBaseEntity *pOther ) +{ + if ( !GetOwnerEntity() || !pOther ) + return; + + CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( !pTFPlayer || pTFPlayer->GetGrapplingHookTarget() ) + return; + + CBaseEntity *pTarget = pOther->IsWorld() ? this : pOther; + const char *pszSoundName = NULL; + if ( pTarget->IsPlayer() ) + { + pszSoundName = "WeaponGrapplingHook.ImpactFlesh"; + } + else + { + pszSoundName = "WeaponGrapplingHook.ImpactDefault"; + } + ImpactSound( pszSoundName ); + + pTFPlayer->SetGrapplingHookTarget( pTarget, true ); + // Grapple any medics to us + SetMedicsGrapplingHookTarget( pTFPlayer, pTFPlayer ); + + // Stop moving! + if ( pOther->IsPlayer() ) + { + FollowEntity( pOther, false ); + StartImpactFleshSoundLoop(); + } + else + SetMoveType( MOVETYPE_NONE ); + + SetContextThink( &CTFProjectile_GrapplingHook::HookLatchedThink, gpGlobals->curtime + 0.1f, "HookLatchedThink" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: HookLatchedThink +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::HookLatchedThink() +{ + // if owner is dead, remove the hook + CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( !pTFPlayer || !pTFPlayer->IsAlive() ) + { + UTIL_Remove( this ); + return; + } + + // if the target nolonger exist or target player is dead, remove the hook + CBaseEntity *pHookTarget = pTFPlayer->GetGrapplingHookTarget(); + if ( !pHookTarget || ( pHookTarget->IsPlayer() && !pHookTarget->IsAlive() ) ) + { + UTIL_Remove( this ); + return; + } + + SetContextThink( &CTFProjectile_GrapplingHook::HookLatchedThink, gpGlobals->curtime + 0.1f, "HookLatchedThink" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::StartImpactFleshSoundLoop() +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + CPASAttenuationFilter filter( this ); + m_pImpactFleshSoundLoop = controller.SoundCreate( filter, entindex(), "WeaponGrapplingHook.ImpactFleshLoop" ); + controller.Play( m_pImpactFleshSoundLoop, 1.0, 100 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFProjectile_GrapplingHook::StopImpactFleshSoundLoop() +{ + if ( m_pImpactFleshSoundLoop ) + { + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundDestroy( m_pImpactFleshSoundLoop ); + m_pImpactFleshSoundLoop = NULL; + } +} |