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