//========= 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( 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( 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( 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( 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(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( 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(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( 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( 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; } }