Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1573 lines
48 KiB

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