mirror of
https://github.com/nillerusr/source-engine.git
synced 2025-04-06 00:25:02 +00:00
1170 lines
31 KiB
C++
1170 lines
31 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: The Medic's Medikit weapon
|
|
//
|
|
//
|
|
// $Workfile: $
|
|
// $Date: $
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "cbase.h"
|
|
#include "in_buttons.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "tf_gamerules.h"
|
|
|
|
#if defined( CLIENT_DLL )
|
|
#include <vgui_controls/Panel.h>
|
|
#include <vgui/ISurface.h>
|
|
#include "particles_simple.h"
|
|
#include "c_tf_player.h"
|
|
#include "soundenvelope.h"
|
|
#else
|
|
#include "ndebugoverlay.h"
|
|
#include "tf_player.h"
|
|
#include "tf_team.h"
|
|
#include "tf_gamestats.h"
|
|
#include "ilagcompensationmanager.h"
|
|
#endif
|
|
|
|
#include "tf_weapon_medigun.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// Buff ranges
|
|
ConVar weapon_medigun_damage_modifier( "weapon_medigun_damage_modifier", "1.5", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Scales the damage a player does while being healed with the medigun." );
|
|
ConVar weapon_medigun_construction_rate( "weapon_medigun_construction_rate", "10", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Constructing object health healed per second by the medigun." );
|
|
ConVar weapon_medigun_charge_rate( "weapon_medigun_charge_rate", "40", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time healing it takes to fully charge the medigun." );
|
|
ConVar weapon_medigun_chargerelease_rate( "weapon_medigun_chargerelease_rate", "8", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time it takes the a full charge of the medigun to be released." );
|
|
|
|
#if defined (CLIENT_DLL)
|
|
ConVar tf_medigun_autoheal( "tf_medigun_autoheal", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "Setting this to 1 will cause the Medigun's primary attack to be a toggle instead of needing to be held down." );
|
|
#endif
|
|
|
|
#if !defined (CLIENT_DLL)
|
|
ConVar tf_medigun_lagcomp( "tf_medigun_lagcomp", "1", FCVAR_DEVELOPMENTONLY );
|
|
#endif
|
|
|
|
static const char *s_pszMedigunHealTargetThink = "MedigunHealTargetThink";
|
|
|
|
#ifdef CLIENT_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void RecvProxy_HealingTarget( const CRecvProxyData *pData, void *pStruct, void *pOut )
|
|
{
|
|
CWeaponMedigun *pMedigun = ((CWeaponMedigun*)(pStruct));
|
|
if ( pMedigun != NULL )
|
|
{
|
|
pMedigun->ForceHealingTargetUpdate();
|
|
}
|
|
|
|
RecvProxy_IntToEHandle( pData, pStruct, pOut );
|
|
}
|
|
#endif
|
|
|
|
LINK_ENTITY_TO_CLASS( tf_weapon_medigun, CWeaponMedigun );
|
|
PRECACHE_WEAPON_REGISTER( tf_weapon_medigun );
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponMedigun, DT_WeaponMedigun )
|
|
|
|
BEGIN_NETWORK_TABLE( CWeaponMedigun, DT_WeaponMedigun )
|
|
#if !defined( CLIENT_DLL )
|
|
SendPropFloat( SENDINFO(m_flChargeLevel), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ),
|
|
SendPropEHandle( SENDINFO( m_hHealingTarget ) ),
|
|
SendPropBool( SENDINFO( m_bHealing ) ),
|
|
SendPropBool( SENDINFO( m_bAttacking ) ),
|
|
SendPropBool( SENDINFO( m_bChargeRelease ) ),
|
|
SendPropBool( SENDINFO( m_bHolstered ) ),
|
|
#else
|
|
RecvPropFloat( RECVINFO(m_flChargeLevel) ),
|
|
RecvPropEHandle( RECVINFO( m_hHealingTarget ), RecvProxy_HealingTarget ),
|
|
RecvPropBool( RECVINFO( m_bHealing ) ),
|
|
RecvPropBool( RECVINFO( m_bAttacking ) ),
|
|
RecvPropBool( RECVINFO( m_bChargeRelease ) ),
|
|
RecvPropBool( RECVINFO( m_bHolstered ) ),
|
|
#endif
|
|
END_NETWORK_TABLE()
|
|
|
|
#ifdef CLIENT_DLL
|
|
BEGIN_PREDICTION_DATA( CWeaponMedigun )
|
|
|
|
DEFINE_PRED_FIELD( m_bHealing, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bAttacking, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bHolstered, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_hHealingTarget, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
|
|
|
|
DEFINE_FIELD( m_flHealEffectLifetime, FIELD_FLOAT ),
|
|
|
|
DEFINE_PRED_FIELD( m_flChargeLevel, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bChargeRelease, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
|
|
// DEFINE_PRED_FIELD( m_bPlayingSound, FIELD_BOOLEAN ),
|
|
// DEFINE_PRED_FIELD( m_bUpdateHealingTargets, FIELD_BOOLEAN ),
|
|
|
|
END_PREDICTION_DATA()
|
|
#endif
|
|
|
|
#define PARTICLE_PATH_VEL 140.0
|
|
#define NUM_PATH_PARTICLES_PER_SEC 300.0f
|
|
#define NUM_MEDIGUN_PATH_POINTS 8
|
|
|
|
extern ConVar tf_max_health_boost;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CWeaponMedigun::CWeaponMedigun( void )
|
|
{
|
|
WeaponReset();
|
|
|
|
SetPredictionEligible( true );
|
|
}
|
|
|
|
CWeaponMedigun::~CWeaponMedigun()
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
if ( m_pChargedSound )
|
|
{
|
|
CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::WeaponReset( void )
|
|
{
|
|
BaseClass::WeaponReset();
|
|
|
|
m_flHealEffectLifetime = 0;
|
|
|
|
m_bHealing = false;
|
|
m_bAttacking = false;
|
|
m_bHolstered = true;
|
|
m_bChargeRelease = false;
|
|
|
|
m_bCanChangeTarget = true;
|
|
|
|
m_flNextBuzzTime = 0;
|
|
m_flReleaseStartedAt = 0;
|
|
m_flChargeLevel = 0.0f;
|
|
|
|
RemoveHealingTarget( true );
|
|
|
|
#if defined( CLIENT_DLL )
|
|
m_bPlayingSound = false;
|
|
m_bUpdateHealingTargets = false;
|
|
m_bOldChargeRelease = false;
|
|
|
|
UpdateEffects();
|
|
ManageChargeEffect();
|
|
|
|
m_pChargeEffect = NULL;
|
|
m_pChargedSound = NULL;
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::Precache()
|
|
{
|
|
BaseClass::Precache();
|
|
PrecacheScriptSound( "WeaponMedigun.NoTarget" );
|
|
PrecacheScriptSound( "WeaponMedigun.Healing" );
|
|
PrecacheScriptSound( "WeaponMedigun.Charged" );
|
|
PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_blue" );
|
|
PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_red" );
|
|
PrecacheParticleSystem( "medicgun_beam_red_invun" );
|
|
PrecacheParticleSystem( "medicgun_beam_red" );
|
|
PrecacheParticleSystem( "medicgun_beam_blue_invun" );
|
|
PrecacheParticleSystem( "medicgun_beam_blue" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::Deploy( void )
|
|
{
|
|
if ( BaseClass::Deploy() )
|
|
{
|
|
m_bHolstered = false;
|
|
|
|
#ifdef GAME_DLL
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( m_bChargeRelease && pOwner )
|
|
{
|
|
pOwner->m_Shared.RecalculateInvuln();
|
|
}
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
ManageChargeEffect();
|
|
#endif
|
|
|
|
m_flNextTargetCheckTime = gpGlobals->curtime;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::Holster( CBaseCombatWeapon *pSwitchingTo )
|
|
{
|
|
RemoveHealingTarget( true );
|
|
m_bAttacking = false;
|
|
m_bHolstered = true;
|
|
|
|
|
|
|
|
#ifdef GAME_DLL
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( pOwner )
|
|
{
|
|
pOwner->m_Shared.RecalculateInvuln( true );
|
|
}
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
UpdateEffects();
|
|
ManageChargeEffect();
|
|
#endif
|
|
|
|
return BaseClass::Holster( pSwitchingTo );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::UpdateOnRemove( void )
|
|
{
|
|
RemoveHealingTarget( true );
|
|
m_bAttacking = false;
|
|
|
|
#ifdef CLIENT_DLL
|
|
if ( m_bPlayingSound )
|
|
{
|
|
m_bPlayingSound = false;
|
|
StopHealSound();
|
|
}
|
|
|
|
UpdateEffects();
|
|
#endif
|
|
|
|
|
|
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CWeaponMedigun::GetTargetRange( void )
|
|
{
|
|
return (float)m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flRange;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CWeaponMedigun::GetStickRange( void )
|
|
{
|
|
return (GetTargetRange() * 1.2);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CWeaponMedigun::GetHealRate( void )
|
|
{
|
|
return (float)m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::HealingTarget( CBaseEntity *pTarget )
|
|
{
|
|
if ( pTarget == m_hHealingTarget )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::AllowedToHealTarget( CBaseEntity *pTarget )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return false;
|
|
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
|
|
if ( !pTFPlayer )
|
|
return false;
|
|
|
|
bool bStealthed = pTFPlayer->m_Shared.InCond( TF_COND_STEALTHED );
|
|
bool bDisguised = pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED );
|
|
|
|
// We can heal teammates and enemies that are disguised as teammates
|
|
if ( !bStealthed &&
|
|
( pTFPlayer->InSameTeam( pOwner ) ||
|
|
( bDisguised && pTFPlayer->m_Shared.GetDisguiseTeam() == pOwner->GetTeamNumber() ) ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::CouldHealTarget( CBaseEntity *pTarget )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return false;
|
|
|
|
if ( pTarget->IsPlayer() && pTarget->IsAlive() && !HealingTarget(pTarget) )
|
|
return AllowedToHealTarget( pTarget );
|
|
|
|
return false;
|
|
}
|
|
|
|
// Now make sure there isn't something other than team players in the way.
|
|
class CMedigunFilter : public CTraceFilterSimple
|
|
{
|
|
public:
|
|
CMedigunFilter( CBaseEntity *pShooter ) : CTraceFilterSimple( pShooter, COLLISION_GROUP_WEAPON )
|
|
{
|
|
m_pShooter = pShooter;
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
|
|
{
|
|
// If it hit an edict the isn't the target and is on our team, then the ray is blocked.
|
|
CBaseEntity *pEnt = static_cast<CBaseEntity*>(pHandleEntity);
|
|
|
|
// Ignore collisions with the shooter
|
|
if ( pEnt == m_pShooter )
|
|
return false;
|
|
|
|
if ( pEnt->GetTeam() == m_pShooter->GetTeam() )
|
|
return false;
|
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
|
|
}
|
|
|
|
CBaseEntity *m_pShooter;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::MaintainTargetInSlot()
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
CBaseEntity *pTarget = m_hHealingTarget;
|
|
Assert( pTarget );
|
|
|
|
// Make sure the guy didn't go out of range.
|
|
bool bLostTarget = true;
|
|
Vector vecSrc = pOwner->Weapon_ShootPosition( );
|
|
Vector vecTargetPoint = pTarget->WorldSpaceCenter();
|
|
Vector vecPoint;
|
|
|
|
// If it's brush built, use absmins/absmaxs
|
|
pTarget->CollisionProp()->CalcNearestPoint( vecSrc, &vecPoint );
|
|
|
|
float flDistance = (vecPoint - vecSrc).Length();
|
|
if ( flDistance < GetStickRange() )
|
|
{
|
|
if ( m_flNextTargetCheckTime > gpGlobals->curtime )
|
|
return;
|
|
|
|
m_flNextTargetCheckTime = gpGlobals->curtime + 1.0f;
|
|
|
|
trace_t tr;
|
|
CMedigunFilter drainFilter( pOwner );
|
|
|
|
Vector vecAiming;
|
|
pOwner->EyeVectors( &vecAiming );
|
|
|
|
Vector vecEnd = vecSrc + vecAiming * GetTargetRange();
|
|
UTIL_TraceLine( vecSrc, vecEnd, (MASK_SHOT & ~CONTENTS_HITBOX), pOwner, DMG_GENERIC, &tr );
|
|
|
|
// Still visible?
|
|
if ( tr.m_pEnt == pTarget )
|
|
return;
|
|
|
|
UTIL_TraceLine( vecSrc, vecTargetPoint, MASK_SHOT, &drainFilter, &tr );
|
|
|
|
// Still visible?
|
|
if (( tr.fraction == 1.0f) || (tr.m_pEnt == pTarget))
|
|
return;
|
|
|
|
// If we failed, try the target's eye point as well
|
|
UTIL_TraceLine( vecSrc, pTarget->EyePosition(), MASK_SHOT, &drainFilter, &tr );
|
|
if (( tr.fraction == 1.0f) || (tr.m_pEnt == pTarget))
|
|
return;
|
|
}
|
|
|
|
// We've lost this guy
|
|
if ( bLostTarget )
|
|
{
|
|
RemoveHealingTarget();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::FindNewTargetForSlot()
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
Vector vecSrc = pOwner->Weapon_ShootPosition( );
|
|
if ( m_hHealingTarget )
|
|
{
|
|
RemoveHealingTarget();
|
|
}
|
|
|
|
// In Normal mode, we heal players under our crosshair
|
|
Vector vecAiming;
|
|
pOwner->EyeVectors( &vecAiming );
|
|
|
|
// Find a player in range of this player, and make sure they're healable.
|
|
Vector vecEnd = vecSrc + vecAiming * GetTargetRange();
|
|
trace_t tr;
|
|
|
|
UTIL_TraceLine( vecSrc, vecEnd, (MASK_SHOT & ~CONTENTS_HITBOX), pOwner, DMG_GENERIC, &tr );
|
|
if ( tr.fraction != 1.0 && tr.m_pEnt )
|
|
{
|
|
if ( CouldHealTarget( tr.m_pEnt ) )
|
|
{
|
|
#ifdef GAME_DLL
|
|
pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_STARTEDHEALING );
|
|
if ( tr.m_pEnt->IsPlayer() )
|
|
{
|
|
CTFPlayer *pTarget = ToTFPlayer( tr.m_pEnt );
|
|
pTarget->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_STARTEDHEALING );
|
|
}
|
|
|
|
// Start the heal target thinking.
|
|
SetContextThink( &CWeaponMedigun::HealTargetThink, gpGlobals->curtime, s_pszMedigunHealTargetThink );
|
|
#endif
|
|
|
|
m_hHealingTarget.Set( tr.m_pEnt );
|
|
m_flNextTargetCheckTime = gpGlobals->curtime + 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::HealTargetThink( void )
|
|
{
|
|
// Verify that we still have a valid heal target.
|
|
CBaseEntity *pTarget = m_hHealingTarget;
|
|
if ( !pTarget || !pTarget->IsAlive() )
|
|
{
|
|
SetContextThink( NULL, 0, s_pszMedigunHealTargetThink );
|
|
return;
|
|
}
|
|
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
float flTime = gpGlobals->curtime - pOwner->GetTimeBase();
|
|
if ( flTime > 5.0f || !AllowedToHealTarget(pTarget) )
|
|
{
|
|
RemoveHealingTarget( true );
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.2f, s_pszMedigunHealTargetThink );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns a pointer to a healable target
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::FindAndHealTargets( void )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return false;
|
|
|
|
bool bFound = false;
|
|
|
|
// Maintaining beam to existing target?
|
|
CBaseEntity *pTarget = m_hHealingTarget;
|
|
if ( pTarget && pTarget->IsAlive() )
|
|
{
|
|
MaintainTargetInSlot();
|
|
}
|
|
else
|
|
{
|
|
FindNewTargetForSlot();
|
|
}
|
|
|
|
CBaseEntity *pNewTarget = m_hHealingTarget;
|
|
if ( pNewTarget && pNewTarget->IsAlive() )
|
|
{
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( pNewTarget );
|
|
|
|
#ifdef GAME_DLL
|
|
// HACK: For now, just deal with players
|
|
if ( pTFPlayer )
|
|
{
|
|
if ( pTarget != pNewTarget && pNewTarget->IsPlayer() )
|
|
{
|
|
pTFPlayer->m_Shared.Heal( pOwner, GetHealRate() );
|
|
}
|
|
|
|
pTFPlayer->m_Shared.RecalculateInvuln( false );
|
|
}
|
|
|
|
if ( m_flReleaseStartedAt && m_flReleaseStartedAt < (gpGlobals->curtime + 0.2) )
|
|
{
|
|
// When we start the release, everyone we heal rockets to full health
|
|
pNewTarget->TakeHealth( pNewTarget->GetMaxHealth(), DMG_GENERIC );
|
|
}
|
|
#endif
|
|
|
|
bFound = true;
|
|
|
|
// Charge up our power if we're not releasing it, and our target
|
|
// isn't receiving any benefit from our healing.
|
|
if ( !m_bChargeRelease )
|
|
{
|
|
if ( pTFPlayer )
|
|
{
|
|
int iBoostMax = floor( pTFPlayer->m_Shared.GetMaxBuffedHealth() * 0.95);
|
|
|
|
if ( weapon_medigun_charge_rate.GetFloat() )
|
|
{
|
|
float flChargeAmount = gpGlobals->frametime / weapon_medigun_charge_rate.GetFloat();
|
|
|
|
// Reduced charge for healing fully healed guys
|
|
if ( pNewTarget->GetHealth() >= iBoostMax && ( TFGameRules() && !TFGameRules()->InSetup() ) )
|
|
{
|
|
flChargeAmount *= 0.5;
|
|
}
|
|
|
|
int iTotalHealers = pTFPlayer->m_Shared.GetNumHealers();
|
|
if ( iTotalHealers > 1 )
|
|
{
|
|
flChargeAmount /= (float)iTotalHealers;
|
|
}
|
|
|
|
float flNewLevel = min( m_flChargeLevel + flChargeAmount, 1.0 );
|
|
#ifdef GAME_DLL
|
|
if ( flNewLevel >= 1.0 && m_flChargeLevel < 1.0 )
|
|
{
|
|
pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_CHARGEREADY );
|
|
|
|
if ( pTFPlayer )
|
|
{
|
|
pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_CHARGEREADY );
|
|
}
|
|
}
|
|
#endif
|
|
m_flChargeLevel = flNewLevel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::ItemHolsterFrame( void )
|
|
{
|
|
BaseClass::ItemHolsterFrame();
|
|
|
|
DrainCharge();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::DrainCharge( void )
|
|
{
|
|
// If we're in charge release mode, drain our charge
|
|
if ( m_bChargeRelease )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
float flChargeAmount = gpGlobals->frametime / weapon_medigun_chargerelease_rate.GetFloat();
|
|
m_flChargeLevel = max( m_flChargeLevel - flChargeAmount, 0.0 );
|
|
if ( !m_flChargeLevel )
|
|
{
|
|
m_bChargeRelease = false;
|
|
m_flReleaseStartedAt = 0;
|
|
|
|
#ifdef GAME_DLL
|
|
/*
|
|
if ( m_bHealingSelf )
|
|
{
|
|
m_bHealingSelf = false;
|
|
pOwner->m_Shared.StopHealing( pOwner );
|
|
}
|
|
*/
|
|
|
|
pOwner->m_Shared.RecalculateInvuln();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Overloaded to handle the hold-down healing
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::ItemPostFrame( void )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
// If we're lowered, we're not allowed to fire
|
|
if ( CanAttack() == false )
|
|
{
|
|
RemoveHealingTarget( true );
|
|
return;
|
|
}
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
if ( AppliesModifier() )
|
|
{
|
|
m_DamageModifier.SetModifier( weapon_medigun_damage_modifier.GetFloat() );
|
|
}
|
|
#endif
|
|
|
|
// Try to start healing
|
|
m_bAttacking = false;
|
|
if ( pOwner->GetMedigunAutoHeal() )
|
|
{
|
|
if ( pOwner->m_nButtons & IN_ATTACK )
|
|
{
|
|
if ( m_bCanChangeTarget )
|
|
{
|
|
RemoveHealingTarget();
|
|
#if defined( CLIENT_DLL )
|
|
m_bPlayingSound = false;
|
|
StopHealSound();
|
|
#endif
|
|
// can't change again until we release the attack button
|
|
m_bCanChangeTarget = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_bCanChangeTarget = true;
|
|
}
|
|
|
|
if ( m_bHealing || ( pOwner->m_nButtons & IN_ATTACK ) )
|
|
{
|
|
PrimaryAttack();
|
|
m_bAttacking = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( /*m_bChargeRelease || */ pOwner->m_nButtons & IN_ATTACK )
|
|
{
|
|
PrimaryAttack();
|
|
m_bAttacking = true;
|
|
}
|
|
else if ( m_bHealing )
|
|
{
|
|
// Detach from the player if they release the attack button.
|
|
RemoveHealingTarget();
|
|
}
|
|
}
|
|
|
|
if ( pOwner->m_nButtons & IN_ATTACK2 )
|
|
{
|
|
SecondaryAttack();
|
|
}
|
|
|
|
WeaponIdle();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponMedigun::Lower( void )
|
|
{
|
|
// Stop healing if we are
|
|
if ( m_bHealing )
|
|
{
|
|
RemoveHealingTarget( true );
|
|
m_bAttacking = false;
|
|
|
|
#ifdef CLIENT_DLL
|
|
UpdateEffects();
|
|
#endif
|
|
}
|
|
|
|
return BaseClass::Lower();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::RemoveHealingTarget( bool bStopHealingSelf )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
#ifdef GAME_DLL
|
|
if ( m_hHealingTarget )
|
|
{
|
|
// HACK: For now, just deal with players
|
|
if ( m_hHealingTarget->IsPlayer() )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( m_hHealingTarget );
|
|
pTFPlayer->m_Shared.StopHealing( pOwner );
|
|
pTFPlayer->m_Shared.RecalculateInvuln( false );
|
|
|
|
pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_STOPPEDHEALING, pTFPlayer->IsAlive() ? "healtarget:alive" : "healtarget:dead" );
|
|
pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_STOPPEDHEALING );
|
|
}
|
|
}
|
|
|
|
// Stop thinking - we no longer have a heal target.
|
|
SetContextThink( NULL, 0, s_pszMedigunHealTargetThink );
|
|
#endif
|
|
|
|
m_hHealingTarget.Set( NULL );
|
|
|
|
// Stop the welding animation
|
|
if ( m_bHealing )
|
|
{
|
|
SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
|
|
pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST );
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
m_DamageModifier.RemoveModifier();
|
|
#endif
|
|
m_bHealing = false;
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Attempt to heal any player within range of the medikit
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::PrimaryAttack( void )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
|
|
if ( !CanAttack() )
|
|
return;
|
|
|
|
#ifdef GAME_DLL
|
|
/*
|
|
// Start boosting ourself if we're not
|
|
if ( m_bChargeRelease && !m_bHealingSelf )
|
|
{
|
|
pOwner->m_Shared.Heal( pOwner, GetHealRate() * 2 );
|
|
m_bHealingSelf = true;
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
#if !defined (CLIENT_DLL)
|
|
if ( tf_medigun_lagcomp.GetBool() )
|
|
lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
|
|
#endif
|
|
|
|
if ( FindAndHealTargets() )
|
|
{
|
|
// Start the animation
|
|
if ( !m_bHealing )
|
|
{
|
|
#ifdef GAME_DLL
|
|
pOwner->SpeakWeaponFire();
|
|
#endif
|
|
|
|
SendWeaponAnim( ACT_MP_ATTACK_STAND_PREFIRE );
|
|
pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE );
|
|
}
|
|
|
|
m_bHealing = true;
|
|
}
|
|
else
|
|
{
|
|
RemoveHealingTarget();
|
|
}
|
|
|
|
#if !defined (CLIENT_DLL)
|
|
if ( tf_medigun_lagcomp.GetBool() )
|
|
lagcompensation->FinishLagCompensation( pOwner );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Burn charge level to generate invulnerability
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::SecondaryAttack( void )
|
|
{
|
|
CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
if ( !CanAttack() )
|
|
return;
|
|
|
|
// Ensure they have a full charge and are not already in charge release mode
|
|
if ( m_flChargeLevel < 1.0 || m_bChargeRelease )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
// Deny, flash
|
|
if ( !m_bChargeRelease && m_flFlashCharge <= 0 )
|
|
{
|
|
m_flFlashCharge = 10;
|
|
pOwner->EmitSound( "Player.DenyWeaponSelection" );
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if ( pOwner->HasTheFlag() )
|
|
{
|
|
#ifdef GAME_DLL
|
|
CSingleUserRecipientFilter filter( pOwner );
|
|
TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_NO_INVULN_WITH_FLAG );
|
|
#endif
|
|
pOwner->EmitSound( "Player.DenyWeaponSelection" );
|
|
return;
|
|
}
|
|
|
|
// Start super charge
|
|
m_bChargeRelease = true;
|
|
m_flReleaseStartedAt = 0;//gpGlobals->curtime;
|
|
|
|
#ifdef GAME_DLL
|
|
CTF_GameStats.Event_PlayerInvulnerable( pOwner );
|
|
pOwner->m_Shared.RecalculateInvuln();
|
|
|
|
pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_CHARGEDEPLOYED );
|
|
|
|
if ( m_hHealingTarget && m_hHealingTarget->IsPlayer() )
|
|
{
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( m_hHealingTarget );
|
|
pTFPlayer->m_Shared.RecalculateInvuln();
|
|
pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_CHARGEDEPLOYED );
|
|
}
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_chargedeployed" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", pOwner->GetUserID() );
|
|
|
|
gameeventmanager->FireEvent( event, true ); // don't send to clients
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Idle tests to see if we're facing a valid target for the medikit
|
|
// If so, move into the "heal-able" animation.
|
|
// Otherwise, move into the "not-heal-able" animation.
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::WeaponIdle( void )
|
|
{
|
|
if ( HasWeaponIdleTimeElapsed() )
|
|
{
|
|
// Loop the welding animation
|
|
if ( m_bHealing )
|
|
{
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
|
|
return;
|
|
}
|
|
|
|
return BaseClass::WeaponIdle();
|
|
}
|
|
}
|
|
|
|
#if defined( CLIENT_DLL )
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::StopHealSound( bool bStopHealingSound, bool bStopNoTargetSound )
|
|
{
|
|
if ( bStopHealingSound )
|
|
{
|
|
StopSound( "WeaponMedigun.Healing" );
|
|
}
|
|
|
|
if ( bStopNoTargetSound )
|
|
{
|
|
StopSound( "WeaponMedigun.NoTarget" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::ManageChargeEffect( void )
|
|
{
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
|
C_BaseEntity *pEffectOwner = this;
|
|
|
|
if ( pLocalPlayer == NULL )
|
|
return;
|
|
|
|
if ( pLocalPlayer == GetTFPlayerOwner() )
|
|
{
|
|
pEffectOwner = pLocalPlayer->GetViewModel();
|
|
if ( !pEffectOwner )
|
|
return;
|
|
}
|
|
|
|
bool bOwnerTaunting = false;
|
|
|
|
if ( GetTFPlayerOwner() && GetTFPlayerOwner()->m_Shared.InCond( TF_COND_TAUNTING ) == true )
|
|
{
|
|
bOwnerTaunting = true;
|
|
}
|
|
|
|
if ( GetTFPlayerOwner() && bOwnerTaunting == false && m_bHolstered == false && ( m_flChargeLevel >= 1.0f || m_bChargeRelease == true ) )
|
|
{
|
|
if ( m_pChargeEffect == NULL )
|
|
{
|
|
char *pszEffectName = NULL;
|
|
|
|
switch( GetTFPlayerOwner()->GetTeamNumber() )
|
|
{
|
|
case TF_TEAM_BLUE:
|
|
pszEffectName = "medicgun_invulnstatus_fullcharge_blue";
|
|
break;
|
|
case TF_TEAM_RED:
|
|
pszEffectName = "medicgun_invulnstatus_fullcharge_red";
|
|
break;
|
|
default:
|
|
pszEffectName = "";
|
|
break;
|
|
}
|
|
|
|
m_pChargeEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
|
|
}
|
|
|
|
if ( m_pChargedSound == NULL )
|
|
{
|
|
CLocalPlayerFilter filter;
|
|
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
|
|
m_pChargedSound = controller.SoundCreate( filter, entindex(), "WeaponMedigun.Charged" );
|
|
controller.Play( m_pChargedSound, 1.0, 100 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_pChargeEffect != NULL )
|
|
{
|
|
pEffectOwner->ParticleProp()->StopEmission( m_pChargeEffect );
|
|
m_pChargeEffect = NULL;
|
|
}
|
|
|
|
if ( m_pChargedSound != NULL )
|
|
{
|
|
CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
|
|
m_pChargedSound = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : updateType -
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::OnDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
BaseClass::OnDataChanged( updateType );
|
|
|
|
if ( m_bUpdateHealingTargets )
|
|
{
|
|
UpdateEffects();
|
|
m_bUpdateHealingTargets = false;
|
|
}
|
|
|
|
// Think?
|
|
if ( m_bHealing )
|
|
{
|
|
ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS );
|
|
}
|
|
else
|
|
{
|
|
ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_NEVER );
|
|
m_bPlayingSound = false;
|
|
StopHealSound( true, false );
|
|
|
|
// Are they holding the attack button but not healing anyone? Give feedback.
|
|
if ( IsActiveByLocalPlayer() && GetOwner() && GetOwner()->IsAlive() && m_bAttacking && GetOwner() == C_BasePlayer::GetLocalPlayer() && CanAttack() == true )
|
|
{
|
|
if ( gpGlobals->curtime >= m_flNextBuzzTime )
|
|
{
|
|
CLocalPlayerFilter filter;
|
|
EmitSound( filter, entindex(), "WeaponMedigun.NoTarget" );
|
|
m_flNextBuzzTime = gpGlobals->curtime + 0.5f; // only buzz every so often.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StopHealSound( false, true ); // Stop the "no target" sound.
|
|
}
|
|
}
|
|
|
|
ManageChargeEffect();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::ClientThink()
|
|
{
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
|
if ( !pLocalPlayer )
|
|
return;
|
|
|
|
// Don't show it while the player is dead. Ideally, we'd respond to m_bHealing in OnDataChanged,
|
|
// but it stops sending the weapon when it's holstered, and it gets holstered when the player dies.
|
|
CTFPlayer *pFiringPlayer = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pFiringPlayer || pFiringPlayer->IsPlayerDead() || pFiringPlayer->IsDormant() )
|
|
{
|
|
ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_NEVER );
|
|
m_bPlayingSound = false;
|
|
StopHealSound();
|
|
return;
|
|
}
|
|
|
|
// If the local player is the guy getting healed, let him know
|
|
// who's healing him, and their charge level.
|
|
if( m_hHealingTarget != NULL )
|
|
{
|
|
if ( pLocalPlayer == m_hHealingTarget )
|
|
{
|
|
pLocalPlayer->SetHealer( pFiringPlayer, m_flChargeLevel );
|
|
}
|
|
|
|
if ( !m_bPlayingSound )
|
|
{
|
|
m_bPlayingSound = true;
|
|
CLocalPlayerFilter filter;
|
|
EmitSound( filter, entindex(), "WeaponMedigun.Healing" );
|
|
}
|
|
}
|
|
|
|
if ( m_bOldChargeRelease != m_bChargeRelease )
|
|
{
|
|
m_bOldChargeRelease = m_bChargeRelease;
|
|
ForceHealingTargetUpdate();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponMedigun::UpdateEffects( void )
|
|
{
|
|
CTFPlayer *pFiringPlayer = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pFiringPlayer )
|
|
return;
|
|
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
|
C_BaseEntity *pEffectOwner = this;
|
|
if ( pLocalPlayer == pFiringPlayer )
|
|
{
|
|
pEffectOwner = pLocalPlayer->GetViewModel();
|
|
if ( !pEffectOwner )
|
|
return;
|
|
}
|
|
|
|
// Remove all the effects
|
|
if ( pEffectOwner )
|
|
{
|
|
pEffectOwner->ParticleProp()->StopEmission( m_hHealingTargetEffect.pEffect );
|
|
}
|
|
else
|
|
{
|
|
m_hHealingTargetEffect.pEffect->StopEmission();
|
|
}
|
|
m_hHealingTargetEffect.pTarget = NULL;
|
|
m_hHealingTargetEffect.pEffect = NULL;
|
|
|
|
// Don't add targets if the medic is dead
|
|
if ( !pEffectOwner || pFiringPlayer->IsPlayerDead() || !pFiringPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
|
|
return;
|
|
|
|
// Add our targets
|
|
// Loops through the healing targets, and make sure we have an effect for each of them
|
|
if ( m_hHealingTarget )
|
|
{
|
|
if ( m_hHealingTargetEffect.pTarget == m_hHealingTarget )
|
|
return;
|
|
|
|
const char *pszEffectName;
|
|
if ( pFiringPlayer->GetTeamNumber() == TF_TEAM_RED )
|
|
{
|
|
if ( m_bChargeRelease )
|
|
{
|
|
pszEffectName = "medicgun_beam_red_invun";
|
|
}
|
|
else
|
|
{
|
|
pszEffectName = "medicgun_beam_red";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_bChargeRelease )
|
|
{
|
|
pszEffectName = "medicgun_beam_blue_invun";
|
|
}
|
|
else
|
|
{
|
|
pszEffectName = "medicgun_beam_blue";
|
|
}
|
|
}
|
|
|
|
CNewParticleEffect *pEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
|
|
pEffectOwner->ParticleProp()->AddControlPoint( pEffect, 1, m_hHealingTarget, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector(0,0,50) );
|
|
|
|
m_hHealingTargetEffect.pTarget = m_hHealingTarget;
|
|
m_hHealingTargetEffect.pEffect = pEffect;
|
|
}
|
|
}
|
|
#endif
|