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