summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_medigun.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_weapon_medigun.cpp')
-rw-r--r--game/shared/tf/tf_weapon_medigun.cpp3001
1 files changed, 3001 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_medigun.cpp b/game/shared/tf/tf_weapon_medigun.cpp
new file mode 100644
index 0000000..a93aa8a
--- /dev/null
+++ b/game/shared/tf/tf_weapon_medigun.cpp
@@ -0,0 +1,3001 @@
+//========= Copyright 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"
+#include "tf_item.h"
+#include "entity_capture_flag.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"
+#include "tf_hud_mediccallers.h"
+#include "c_tf_playerresource.h"
+#include "prediction.h"
+#else
+#include "ndebugoverlay.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_gamestats.h"
+#include "ilagcompensationmanager.h"
+#include "tf_obj.h"
+#include "inetchannel.h"
+#include "IEffects.h"
+#include "baseprojectile.h"
+#include "soundenvelope.h"
+#include "effect_dispatch_data.h"
+#include "func_respawnroom.h"
+#endif
+
+#include "tf_revive.h"
+#include "tf_weapon_medigun.h"
+#include "tf_weapon_shovel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+MedigunEffects_t g_MedigunEffects[MEDIGUN_NUM_CHARGE_TYPES] =
+{
+ { TF_COND_INVULNERABLE, TF_COND_INVULNERABLE_WEARINGOFF, "TFPlayer.InvulnerableOn", "TFPlayer.InvulnerableOff" }, // MEDIGUN_CHARGE_INVULN = 0,
+ { TF_COND_CRITBOOSTED, TF_COND_LAST, "TFPlayer.CritBoostOn", "TFPlayer.CritBoostOff" }, // MEDIGUN_CHARGE_CRITICALBOOST,
+ { TF_COND_MEGAHEAL, TF_COND_LAST, "TFPlayer.QuickFixInvulnerableOn", "TFPlayer.MegaHealOff" }, // MEDIGUN_CHARGE_MEGAHEAL,
+ { TF_COND_MEDIGUN_UBER_BULLET_RESIST, TF_COND_LAST, "WeaponMedigun_Vaccinator.InvulnerableOn", "WeaponMedigun_Vaccinator.InvulnerableOff" }, // TF_COND_MEDIGUN_UBER_BULLET_RESIST,
+ { TF_COND_MEDIGUN_UBER_BLAST_RESIST, TF_COND_LAST, "WeaponMedigun_Vaccinator.InvulnerableOn", "WeaponMedigun_Vaccinator.InvulnerableOff" }, // TF_COND_MEDIGUN_UBER_BLAST_RESIST,
+ { TF_COND_MEDIGUN_UBER_FIRE_RESIST, TF_COND_LAST, "WeaponMedigun_Vaccinator.InvulnerableOn", "WeaponMedigun_Vaccinator.InvulnerableOff" }, // TF_COND_MEDIGUN_UBER_FIRE_RESIST,
+};
+
+struct MedigunResistConditions_t
+{
+ medigun_resist_types_t eResistType;
+ ETFCond passiveCond;
+ ETFCond uberCond;
+};
+
+MedigunResistConditions_t g_MedigunResistConditions[MEDIGUN_NUM_RESISTS] =
+{
+ { MEDIGUN_BULLET_RESIST, TF_COND_MEDIGUN_SMALL_BULLET_RESIST, TF_COND_MEDIGUN_UBER_BULLET_RESIST },
+ { MEDIGUN_BLAST_RESIST, TF_COND_MEDIGUN_SMALL_BLAST_RESIST, TF_COND_MEDIGUN_UBER_BLAST_RESIST },
+ { MEDIGUN_FIRE_RESIST, TF_COND_MEDIGUN_SMALL_FIRE_RESIST, TF_COND_MEDIGUN_UBER_FIRE_RESIST }
+};
+
+// 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." );
+ConVar weapon_medigun_resist_num_chunks( "weapon_medigun_resist_num_chunks", "4", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "How many uber bar chunks the vaccinator has." );
+ConVar tf_vaccinator_uber_charge_rate_modifier( "tf_vaccinator_uber_charge_rate_modifier", "1.0", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY , "Vaccinator uber charge rate." );
+
+#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";
+
+extern ConVar tf_invuln_time;
+
+#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 )
+
+#ifdef GAME_DLL
+void* SendProxy_SendActiveLocalWeaponDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID );
+void* SendProxy_SendNonLocalWeaponDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID );
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Only sent when a player's holding it.
+//-----------------------------------------------------------------------------
+BEGIN_NETWORK_TABLE_NOBASE( CWeaponMedigun, DT_LocalTFWeaponMedigunData )
+#if defined( CLIENT_DLL )
+RecvPropFloat( RECVINFO(m_flChargeLevel) ),
+#else
+SendPropFloat( SENDINFO(m_flChargeLevel), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ),
+#endif
+END_NETWORK_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose: Variables sent at low precision to non-holding observers.
+//-----------------------------------------------------------------------------
+BEGIN_NETWORK_TABLE_NOBASE( CWeaponMedigun, DT_TFWeaponMedigunDataNonLocal )
+#if defined( CLIENT_DLL )
+RecvPropFloat( RECVINFO(m_flChargeLevel) ),
+#else
+SendPropFloat( SENDINFO(m_flChargeLevel), 12, SPROP_NOSCALE | SPROP_CHANGES_OFTEN, 0.0, 100.0f ),
+#endif
+END_NETWORK_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose: Variables always sent
+//-----------------------------------------------------------------------------
+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 ) ),
+ SendPropInt( SENDINFO( m_nChargeResistType ) ),
+ SendPropDataTable("LocalTFWeaponMedigunData", 0, &REFERENCE_SEND_TABLE(DT_LocalTFWeaponMedigunData), SendProxy_SendLocalWeaponDataTable ),
+ SendPropDataTable("NonLocalTFWeaponMedigunData", 0, &REFERENCE_SEND_TABLE(DT_TFWeaponMedigunDataNonLocal), SendProxy_SendNonLocalWeaponDataTable ),
+#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 ) ),
+ RecvPropInt( RECVINFO( m_nChargeResistType ) ),
+ RecvPropDataTable("LocalTFWeaponMedigunData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalTFWeaponMedigunData)),
+ RecvPropDataTable("NonLocalTFWeaponMedigunData", 0, 0, &REFERENCE_RECV_TABLE(DT_TFWeaponMedigunDataNonLocal)),
+#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_bCanChangeTarget, FIELD_BOOLEAN ),
+ 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: For HUD auto medic callers
+//-----------------------------------------------------------------------------
+#ifdef CLIENT_DLL
+ConVar hud_medicautocallers( "hud_medicautocallers", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
+ConVar hud_medicautocallersthreshold( "hud_medicautocallersthreshold", "75", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
+ConVar hud_medichealtargetmarker ( "hud_medichealtargetmarker", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
+#endif
+
+const char *g_pszMedigunHealSounds[MEDIGUN_NUM_CHARGE_TYPES] =
+{
+ "WeaponMedigun.Healing", // MEDIGUN_CHARGE_INVULN = 0,
+ "WeaponMedigun.Healing", // MEDIGUN_CHARGE_CRITICALBOOST,
+ "Weapon_Quick_Fix.Healing", // MEDIGUN_CHARGE_MEGAHEAL,
+ "WeaponMedigun_Vaccinator.Healing", // MEDIGUN_CHARGE_RESIST,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CWeaponMedigun::CWeaponMedigun( void )
+{
+ WeaponReset();
+
+ SetPredictionEligible( true );
+}
+
+CWeaponMedigun::~CWeaponMedigun()
+{
+#ifdef CLIENT_DLL
+ StopChargeEffect( true );
+
+ if ( m_pChargedSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
+ }
+
+ if ( m_pDisruptSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pDisruptSound );
+ }
+
+ m_flAutoCallerCheckTime = 0.0f;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::WeaponReset( void )
+{
+ BaseClass::WeaponReset();
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( m_bHealing && pOwner && pOwner->m_Shared.InState( TF_STATE_DYING ) )
+ {
+ m_bWasHealingBeforeDeath = true;
+ }
+ else
+ {
+ m_bWasHealingBeforeDeath = false;
+ }
+
+ m_flHealEffectLifetime = 0;
+
+ m_bHealing = false;
+ m_bAttacking = false;
+ m_bChargeRelease = false;
+ m_DetachedTargets.Purge();
+ m_flEndResistCharge = 0.f;
+
+ m_bCanChangeTarget = true;
+
+ m_flNextBuzzTime = 0;
+ m_flReleaseStartedAt = 0;
+
+ int iPreserveUber = 0;
+ if ( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iPreserveUber, preserve_ubercharge );
+ m_flChargeLevel = MIN( m_flChargeLevel, iPreserveUber / 100.f );
+ }
+ else
+ {
+ m_flChargeLevel = 0;
+ }
+
+ RemoveHealingTarget( true );
+
+ m_bAttack2Down = false;
+ m_bAttack3Down = false;
+ m_bReloadDown = false;
+
+ m_nChargeResistType = 0;
+
+#if defined( GAME_DLL )
+ StopHealingOwner();
+ m_hLastHealingTarget = NULL;
+ RecalcEffectOnTarget( ToTFPlayer( GetOwnerEntity() ), true );
+ m_nHealTargetClass = 0;
+ m_nChargesReleased = 0;
+#endif
+
+#if defined( CLIENT_DLL )
+ m_nOldChargeResistType = 0;
+ m_bPlayingSound = false;
+ m_bUpdateHealingTargets = false;
+ m_bOldChargeRelease = false;
+
+ UpdateEffects();
+ StopChargeEffect( true );
+
+ m_pChargeEffectOwner = NULL;
+ m_pChargeEffect = NULL;
+ m_pChargedSound = NULL;
+ m_pDisruptSound = NULL;
+ m_flDenySecondary = 0.f;
+#endif
+
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::Precache()
+{
+ BaseClass::Precache();
+ PrecacheModel( "models/weapons/c_models/c_proto_backpack/c_proto_backpack.mdl" );
+ PrecacheScriptSound( "WeaponMedigun.NoTarget" );
+ PrecacheScriptSound( "WeaponMedigun.Healing" );
+ PrecacheScriptSound( "Weapon_Quick_Fix.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_red_targeted" );
+ PrecacheParticleSystem( "medicgun_beam_blue_invun" );
+ PrecacheParticleSystem( "medicgun_beam_blue" );
+ PrecacheParticleSystem( "medicgun_beam_blue_targeted" );
+ PrecacheParticleSystem( "vaccinator_red_buff1" );
+ PrecacheParticleSystem( "vaccinator_red_buff2" );
+ PrecacheParticleSystem( "vaccinator_red_buff3" );
+ PrecacheParticleSystem( "vaccinator_blue_buff1" );
+ PrecacheParticleSystem( "vaccinator_blue_buff2" );
+ PrecacheParticleSystem( "vaccinator_blue_buff3" );
+ PrecacheParticleSystem( "drain_effect" );
+ PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_01");
+ PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_02");
+ PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_03");
+ PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_04");
+ PrecacheScriptSound( "WeaponMedigun.HealingDisrupt" );
+ // PrecacheParticleSystem( "medicgun_beam_machinery" );
+
+ for( int i=0; i<ARRAYSIZE(g_MedigunEffects); ++i )
+ {
+ if( g_MedigunEffects[i].pszChargeOnSound[0] )
+ PrecacheScriptSound( g_MedigunEffects[i].pszChargeOnSound );
+
+ if( g_MedigunEffects[i].pszChargeOffSound[0] )
+ PrecacheScriptSound( g_MedigunEffects[i].pszChargeOffSound );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponMedigun::Deploy( void )
+{
+ if ( BaseClass::Deploy() )
+ {
+ m_bHolstered = false;
+
+ m_bWasHealingBeforeDeath = false;
+
+#ifdef GAME_DLL
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( m_bChargeRelease )
+ {
+ RecalcEffectOnTarget( pOwner );
+ }
+
+ if ( pOwner && pOwner->m_Shared.IsRageDraining() )
+ {
+ CreateMedigunShield();
+ }
+
+ // Resume healing self for Quick-Fix if we're still ubering and switch back to the Quick-Fix.
+ if ( ( GetMedigunType() == MEDIGUN_QUICKFIX ) && m_bChargeRelease && !m_bHealingSelf )
+ {
+ StartHealingTarget( pOwner );
+ m_bHealingSelf = true;
+ }
+#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
+ RecalcEffectOnTarget( ToTFPlayer( GetOwnerEntity() ), true );
+ StopHealingOwner();
+#endif
+
+ RemoveMedigunShield();
+
+#ifdef CLIENT_DLL
+ UpdateEffects();
+ ManageChargeEffect();
+#endif
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::UpdateOnRemove( void )
+{
+ RemoveHealingTarget( true );
+ m_bAttacking = false;
+ m_bChargeRelease = false;
+
+#ifdef GAME_DLL
+ RecalcEffectOnTarget( ToTFPlayer( GetOwnerEntity() ), true );
+ StopHealingOwner();
+#endif
+
+#ifdef CLIENT_DLL
+ if ( m_bPlayingSound )
+ {
+ m_bPlayingSound = false;
+ StopHealSound();
+ }
+
+ UpdateEffects();
+#endif
+
+ RemoveMedigunShield();
+
+ 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 )
+{
+ float flHealRate = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), flHealRate, mult_medigun_healrate );
+
+ // This attribute represents a bucket of attributes.
+ int iHealingMastery = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iHealingMastery, healing_mastery );
+ if ( iHealingMastery )
+ {
+ float flPerc = RemapValClamped( (float)iHealingMastery, 1.f, 4.f, 1.25f, 2.f );
+ flHealRate *= flPerc;
+ }
+
+ if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
+ {
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+
+ if ( pOwner && pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
+ {
+ flHealRate *= 2.f;
+ }
+ else if ( pOwner && ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_KING || pOwner->m_Shared.InCond( TF_COND_KING_BUFFED ) ) )
+ {
+ flHealRate *= 1.5f;
+ }
+
+ }
+
+ return flHealRate;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+
+ if ( !pTarget )
+ return false;
+
+ if ( pTarget->IsPlayer() )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
+ if ( !pTFPlayer )
+ return false;
+
+ if ( !pTFPlayer->IsAlive() )
+ return false;
+
+ // We cannot heal teammates who are using the Equalizer.
+ CTFWeaponBase *pTFWeapon = pTFPlayer->GetActiveTFWeapon();
+ int iWeaponBlocksHealing = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iWeaponBlocksHealing, weapon_blocks_healing );
+ if ( iWeaponBlocksHealing == 1 )
+ {
+ return false;
+ }
+
+ bool bStealthed = pTFPlayer->m_Shared.IsStealthed() && !pOwner->m_Shared.IsStealthed(); // Allow stealthed medics to heal stealthed targets
+ 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;
+ }
+ }
+ else
+ {
+ if ( !pTarget->InSameTeam( pOwner ) )
+ return false;
+
+#ifdef STAGING_ONLY
+ if ( pTarget->IsBaseObject() && IsAllowedToTargetBuildings() )
+ return true;
+#else
+ if ( pTarget->IsBaseObject() )
+ return false;
+#endif // STAGING_ONLY
+
+ CTFReviveMarker *pReviveMarker = dynamic_cast< CTFReviveMarker* >( pTarget );
+ if ( pReviveMarker )
+ {
+ m_hReviveMarker = pReviveMarker; // Store last marker we touched
+ return true;
+ }
+ }
+
+ 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 that 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;
+
+ CheckAchievementsOnHealTarget();
+
+ 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 ( !HealingTarget( tr.m_pEnt ) && AllowedToHealTarget( 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 );
+
+ m_hLastHealingTarget = tr.m_pEnt;
+#endif
+
+ m_hHealingTarget.Set( tr.m_pEnt );
+ m_flNextTargetCheckTime = gpGlobals->curtime + 1.0f;
+ }
+ }
+}
+
+bool CWeaponMedigun::IsReleasingCharge( void ) const
+{
+ return (m_bChargeRelease && !m_bHolstered);
+}
+
+
+int CWeaponMedigun::GetMedigunType( void ) const
+{
+ int iMode = 0;
+ CALL_ATTRIB_HOOK_INT( iMode, set_weapon_mode );
+ return iMode;
+}
+
+
+float CWeaponMedigun::GetMinChargeAmount( void ) const
+{
+ if( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ return 1.f / weapon_medigun_resist_num_chunks.GetInt();
+ }
+ else
+ {
+ return 1.f;
+ }
+}
+
+medigun_charge_types CWeaponMedigun::GetChargeType( void ) const
+{
+ int iTmp = MEDIGUN_CHARGE_INVULN;
+ CALL_ATTRIB_HOOK_INT( iTmp, set_charge_type );
+
+ if( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ // If this is a resist-medigun, then the charge type needs to be within the resist types
+ Assert( iTmp >= MEDIGUN_CHARGE_BULLET_RESIST && iTmp <= MEDIGUN_CHARGE_FIRE_RESIST );
+ Assert( m_nChargeResistType < MEDIGUN_NUM_RESISTS );
+
+ iTmp += m_nChargeResistType;
+ }
+
+ return (medigun_charge_types)iTmp;
+}
+
+void CWeaponMedigun::CycleResistType()
+{
+ // Resist medigun only!
+ if( GetMedigunType() != MEDIGUN_RESIST )
+ return;
+
+ if( IsReleasingCharge() )
+ return;
+
+#ifdef GAME_DLL
+ // When cycling resist we have to remove the current resist, then add the new resist.
+ CTFPlayer *pTFHealingTarget = ToTFPlayer( m_hHealingTarget );
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ ETFCond cond = g_MedigunResistConditions[ GetResistType() ].passiveCond;
+ // Remove from out healing target
+ if( pTFHealingTarget)
+ {
+ CBaseEntity* pProvider = pTFHealingTarget->m_Shared.GetConditionProvider( cond );
+
+ // Remove from our healing target if we're the provider
+ if( pProvider == pOwner || pProvider == NULL )
+ {
+ pTFHealingTarget->m_Shared.RemoveCond( cond );
+ }
+ }
+ // Remove from ourselves
+ if( pOwner )
+ {
+ // Remove from ourselves if we're the provider
+ CBaseEntity* pProvider = pOwner->m_Shared.GetConditionProvider( cond );
+ if( pProvider == pOwner || pProvider == NULL )
+ {
+ pOwner->m_Shared.RemoveCond( cond );
+ }
+ }
+
+#endif
+
+ m_nChargeResistType += 1;
+ m_nChargeResistType = m_nChargeResistType % MEDIGUN_NUM_RESISTS;
+
+
+#ifdef GAME_DLL
+ CTFPlayer *pTFOwner = ToTFPlayer( GetOwnerEntity() );
+
+ if( pTFOwner )
+ {
+ RecalcEffectOnTarget( pTFOwner );
+ }
+
+
+ if( pTFHealingTarget )
+ {
+ // Now add the new resist
+ RecalcEffectOnTarget( pTFHealingTarget );
+ pTFHealingTarget->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pTFOwner );
+ pTFOwner->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pTFOwner );
+ }
+#else
+ // Updates our particles
+ ForceHealingTargetUpdate();
+
+#endif
+}
+
+
+medigun_resist_types_t CWeaponMedigun::GetResistType() const
+{
+ Assert( GetMedigunType() == MEDIGUN_RESIST );
+
+ int nCurrentActiveResist = ( GetChargeType() - MEDIGUN_CHARGE_BULLET_RESIST );
+ Assert( nCurrentActiveResist >= 0 && nCurrentActiveResist < MEDIGUN_NUM_RESISTS );
+ nCurrentActiveResist = nCurrentActiveResist % MEDIGUN_NUM_RESISTS;
+
+ return medigun_resist_types_t(nCurrentActiveResist);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponMedigun::IsAllowedToTargetBuildings( void )
+{
+#ifdef STAGING_ONLY
+ if ( !TFGameRules() || !TFGameRules()->GameModeUsesUpgrades() )
+ return false;
+
+ // See if we have the upgrade to heal buildings
+ int iHealBuildings = 0;
+ CALL_ATTRIB_HOOK_INT( iHealBuildings, medic_machinery_beam );
+
+ return iHealBuildings ? true : false;
+#else
+ return false;
+#endif // STAGING_ONLY
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponMedigun::IsAttachedToBuilding( void )
+{
+ if ( !m_hHealingTarget )
+ return false;
+
+ return m_hHealingTarget->IsBaseObject();
+}
+
+#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 );
+ }
+
+ // Make sure our heal target hasn't changed classes while being healed
+ CTFPlayer *pTFTarget = ToTFPlayer( pTarget );
+ if ( pTFTarget )
+ {
+ int nPrevClass = m_nHealTargetClass;
+ m_nHealTargetClass = pTFTarget->GetPlayerClass()->GetClassIndex();
+ if ( m_nHealTargetClass != nPrevClass )
+ {
+ pOwner->TeamFortress_SetSpeed();
+ }
+ }
+
+ if ( !pTarget->IsPlayer() )
+ {
+#ifdef STAGING_ONLY
+ if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
+ {
+ if ( IsAttachedToBuilding() )
+ {
+ // Heal building
+ if ( m_hHealingTarget->GetHealth() < m_hHealingTarget->GetMaxHealth() )
+ {
+ CBaseEntity *pEntity = m_hHealingTarget;
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>( pEntity );
+ if ( pObject )
+ {
+ pObject->SetHealth( m_hHealingTarget->GetHealth() + ( GetHealRate() / 10.f ) );
+ }
+ }
+ }
+ }
+#endif // STAGING_ONLY
+
+ CTFReviveMarker *pReviveMarker = dynamic_cast< CTFReviveMarker* >( pTarget );
+ if ( pReviveMarker )
+ {
+ CTFPlayer *pDeadPlayer = pReviveMarker->GetOwner();
+ if ( pDeadPlayer )
+ {
+ pReviveMarker->SetReviver( pOwner );
+
+ // Instantly revive players when deploying uber
+ float flHealRate = GetHealRate();
+ float flReviveRate = m_bChargeRelease ? flHealRate / 2.f : flHealRate / 8.f;
+ pReviveMarker->AddMarkerHealth( flReviveRate );
+
+ // Set observer target to reviver so they know they're being revived
+ if ( pDeadPlayer->GetObserverMode() > OBS_MODE_FREEZECAM )
+ {
+ if ( pReviveMarker->GetReviver() && pDeadPlayer->GetObserverTarget() != pReviveMarker->GetReviver() )
+ {
+ pDeadPlayer->SetObserverTarget( pReviveMarker->GetReviver() );
+ }
+ }
+
+ if ( !pReviveMarker->HasOwnerBeenPrompted() )
+ {
+ // This will give them a messagebox that has a Cancel button
+ pReviveMarker->PromptOwner();
+ }
+ }
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.2f, s_pszMedigunHealTargetThink );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::StartHealingTarget( CBaseEntity *pTarget )
+{
+ CTFPlayer *pTFTarget = ToTFPlayer( pTarget );
+ if ( !pTFTarget )
+ return;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ // Handle bonuses as additive, penalties as percentage...
+ float flOverhealBonus = tf_max_health_boost.GetFloat() - 1.0f;
+ float flMod = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flMod, mult_medigun_overheal_amount );
+ if ( flMod >= 1.0f )
+ {
+ flOverhealBonus += flMod;
+ }
+ else if ( flMod < 1.0f && flOverhealBonus > 0.0f )
+ {
+ flOverhealBonus *= flMod;
+ flOverhealBonus += 1.0f;
+ }
+
+ // Safety net
+ if ( flOverhealBonus <= 1.0f )
+ {
+ flOverhealBonus = 1.0f;
+ }
+
+ float flOverhealDecayMult = 1.0;
+ CALL_ATTRIB_HOOK_FLOAT( flOverhealDecayMult, mult_medigun_overheal_decay );
+
+ float flOverhealExpert = 0.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flOverhealExpert, overheal_expert );
+ flOverhealBonus = Max( flOverhealBonus, flOverhealBonus + ( flOverhealExpert / 4 ) );
+ flOverhealDecayMult = Max( flOverhealDecayMult, flOverhealDecayMult + ( flOverhealExpert / 2 ) );
+
+ pTFTarget->m_Shared.Heal( pOwner, GetHealRate(), flOverhealBonus, flOverhealDecayMult );
+
+ // If target is grappling, set ourselves as grappling them
+ if ( pTFTarget->GetGrapplingHookTarget() )
+ {
+ pOwner->SetGrapplingHookTarget( pTFTarget );
+ }
+
+ // Add on the small passive resist when we attach onto a target
+ if( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ pTFTarget->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pOwner );
+ pOwner->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pOwner );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: QuickFix uber heals the target and medic
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::StopHealingOwner( void )
+{
+ if ( !m_bHealingSelf )
+ return;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ pOwner->m_Shared.StopHealing( pOwner );
+ m_bHealingSelf = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::AddCharge( float flPercentage )
+{
+ m_flChargeLevel = MIN( m_flChargeLevel + flPercentage, 1.0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::SubtractCharge( float flPercentage )
+{
+ float flSubtractAmount = Max( flPercentage, 0.0f );
+ SubtractChargeAndUpdateDeployState( flSubtractAmount, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::RecalcEffectOnTarget( CTFPlayer *pPlayer, bool bInstantRemove )
+{
+ if ( !pPlayer )
+ return;
+
+ pPlayer->m_Shared.RecalculateChargeEffects( bInstantRemove );
+}
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CWeaponMedigun::GetHealSound( void ) const
+{
+ COMPILE_TIME_ASSERT( ARRAYSIZE(g_pszMedigunHealSounds) == MEDIGUN_NUM_CHARGE_TYPES );
+ return g_pszMedigunHealSounds[ GetMedigunType() ];
+}
+
+#ifdef GAME_DLL
+void CWeaponMedigun::UberchargeChunkDeployed()
+{
+ m_nChargesReleased++;
+ if( m_nChargesReleased % weapon_medigun_resist_num_chunks.GetInt() == 0 )
+ {
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ CTF_GameStats.Event_PlayerInvulnerable( pOwner );
+ EconEntity_OnOwnerKillEaterEvent( this, pOwner, ToTFPlayer( m_hHealingTarget ), kKillEaterEvent_UberActivated );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::CreateMedigunShield( void )
+{
+#ifdef GAME_DLL
+ if ( m_hMedigunShield )
+ return;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+#ifdef STAGING_ONLY
+ bool bHasPermanentShield = HasPermanentShield();
+ if ( !bHasPermanentShield )
+#endif // STAGING_ONLY
+ {
+ // check if we can activate the shield
+ if ( ( pOwner->m_Shared.GetRageMeter() < 100.f ) && !pOwner->m_Shared.IsRageDraining() )
+ return;
+ }
+
+ pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_HEAL_SHIELD );
+ m_hMedigunShield = CTFMedigunShield::Create( pOwner );
+ if ( m_hMedigunShield )
+ {
+ pOwner->m_Shared.StartRageDrain();
+
+#ifdef STAGING_ONLY
+ m_hMedigunShield->SetPermanentShield( bHasPermanentShield );
+#endif // STAGING_ONLY
+ }
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::RemoveMedigunShield( void )
+{
+#ifdef GAME_DLL
+ if ( m_hMedigunShield )
+ {
+ m_hMedigunShield->RemoveShield();
+ m_hMedigunShield.Set( NULL );
+ }
+#endif // GAME_DLL
+}
+
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponMedigun::HasPermanentShield() const
+{
+ if ( !TFGameRules()->IsMannVsMachineMode() )
+ return false;
+
+ int iPermanentShield = 0;
+ CALL_ATTRIB_HOOK_INT( iPermanentShield, permanent_medic_shield );
+ return iPermanentShield != 0;
+}
+#endif // STAGING_ONLY
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a pointer to a healable target
+//-----------------------------------------------------------------------------
+bool CWeaponMedigun::FindAndHealTargets( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return false;
+
+#ifdef GAME_DLL
+ if ( !pOwner->IsBot() )
+ {
+ INetChannelInfo *pNetChanInfo = engine->GetPlayerNetInfo( pOwner->entindex() );
+ if ( !pNetChanInfo || pNetChanInfo->IsTimingOut() )
+ return false;
+ }
+#endif // GAME_DLL
+
+ 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 )
+ {
+ StartHealingTarget( pNewTarget );
+ }
+
+ RecalcEffectOnTarget( pTFPlayer );
+ }
+#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 )
+ {
+ float flChargeRate = weapon_medigun_charge_rate.GetFloat();
+ float flChargeAmount = gpGlobals->frametime / flChargeRate;
+
+ if ( pTFPlayer && weapon_medigun_charge_rate.GetFloat() )
+ {
+#ifdef GAME_DLL
+ int iBoostMax = floor( pTFPlayer->m_Shared.GetMaxBuffedHealth() * 0.95);
+ float flChargeModifier = 1.f;
+
+ // Reduced charge for healing fully healed guys
+ if ( pNewTarget->GetHealth() >= iBoostMax && ( TFGameRules() && !(TFGameRules()->InSetup() && TFGameRules()->GetActiveRoundTimer() ) ) )
+ {
+ flChargeModifier *= 0.5;
+ }
+
+ int iTotalHealers = pTFPlayer->m_Shared.GetNumHealers();
+ if ( iTotalHealers > 1 )
+ {
+ flChargeModifier /= (float)iTotalHealers;
+ }
+
+ // The resist medigun has a uber charge rate
+ flChargeAmount *= flChargeModifier;
+
+ if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
+ {
+ flChargeAmount *= 2.f;
+ }
+ else if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_KING || pOwner->m_Shared.InCond( TF_COND_KING_BUFFED ) )
+ {
+ flChargeAmount *= 1.5f;
+ }
+
+ if ( pNewTarget->GetHealth() >= pNewTarget->GetMaxHealth() && ( TFGameRules() && !(TFGameRules()->InSetup() && TFGameRules()->GetActiveRoundTimer() ) ) )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flChargeAmount, mult_medigun_overheal_uberchargerate );
+ }
+
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flChargeAmount, mult_medigun_uberchargerate );
+
+
+ // Apply any bonus our target gives us.
+ if ( pTarget )
+ {
+ bool bInRespawnRoom =
+ PointInRespawnRoom( pTarget, WorldSpaceCenter() ) ||
+ PointInRespawnRoom( pOwner, WorldSpaceCenter() );
+
+ if ( !bInRespawnRoom )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTarget, flChargeAmount, mult_uberchargerate_for_healer );
+ }
+ }
+ if ( TFGameRules() )
+ {
+ if ( TFGameRules()->IsQuickBuildTime() )
+ {
+ flChargeAmount *= 4.f;
+ }
+ else if ( TFGameRules()->InSetup() && TFGameRules()->GetActiveRoundTimer() )
+ {
+ flChargeAmount *= 3.f;
+ }
+ }
+#endif
+
+ float flNewLevel = MIN( m_flChargeLevel + flChargeAmount, 1.0 );
+
+ float flMinChargeAmount = GetMinChargeAmount();
+
+ if ( flNewLevel >= flMinChargeAmount && m_flChargeLevel < flMinChargeAmount )
+ {
+#ifdef GAME_DLL
+ pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_CHARGEREADY );
+ pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_CHARGEREADY );
+#else
+ // send a message that we've got charge
+ // if you change this from being a client-side only event, you have to
+ // fix ACHIEVEMENT_TF_MEDIC_KILL_WHILE_CHARGED to check the medic userid.
+ IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_chargeready" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+#endif
+ }
+
+#ifdef CLIENT_DLL
+ if ( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ // Play a sound when we tick over to a new charge level
+ int nChargeLevel = int(floor(flNewLevel/flMinChargeAmount));
+ float flNextChargeLevelAmount = nChargeLevel * flMinChargeAmount;
+ if( flNewLevel >= flNextChargeLevelAmount && m_flChargeLevel < flNextChargeLevelAmount )
+ {
+ const char* pzsSoundName = CFmtStr( "WeaponMedigun_Vaccinator.Charged_tier_0%d", nChargeLevel );
+
+ if ( nChargeLevel == 1 )
+ {
+ if ( m_pChargedSound != NULL )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
+ m_pChargedSound = NULL;
+ }
+
+ CLocalPlayerFilter filter;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ m_pChargedSound = controller.SoundCreate( filter, entindex(), pzsSoundName );
+ controller.Play( m_pChargedSound, 1.0, 100 );
+ }
+ else
+ {
+ pOwner->EmitSound( pzsSoundName );
+ }
+ }
+ }
+#endif
+ SetChargeLevel( flNewLevel );
+ }
+ else if ( IsAttachedToBuilding() )
+ {
+ m_flChargeLevel = MIN( m_flChargeLevel + flChargeAmount, 1.0 );
+ }
+ }
+ }
+
+ 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;
+
+ int flUberTime = weapon_medigun_chargerelease_rate.GetFloat();
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flUberTime, add_uber_time );
+
+ float flChargeAmount = gpGlobals->frametime / flUberTime;
+ float flExtraPlayerCost = flChargeAmount * 0.5;
+
+ // Drain faster the more targets we're applying to. Extra targets count for 50% drain to still reward juggling somewhat.
+ for ( int i = m_DetachedTargets.Count()-1; i >= 0; i-- )
+ {
+ if ( m_DetachedTargets[i].hTarget == NULL || m_DetachedTargets[i].hTarget.Get() == m_hHealingTarget.Get() ||
+ !m_DetachedTargets[i].hTarget->IsAlive() || m_DetachedTargets[i].flTime < (gpGlobals->curtime - tf_invuln_time.GetFloat()) )
+ {
+ m_DetachedTargets.Remove(i);
+ }
+ else
+ {
+ flChargeAmount += flExtraPlayerCost;
+ }
+ }
+
+ SubtractChargeAndUpdateDeployState( flChargeAmount, false );
+ }
+}
+
+
+void CWeaponMedigun::SubtractChargeAndUpdateDeployState( float flSubtractAmount, bool bForceDrain )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ float flNewCharge = Max( m_flChargeLevel - flSubtractAmount, 0.0f );
+
+ if( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ if( flNewCharge <= m_flEndResistCharge )
+ {
+ // If the player is holding down ATTACK2 and they have a bar of Uber left,
+ // let them burn straight into the next bar
+ if( (m_flEndResistCharge > 0) && m_bAttack2Down )
+ {
+ float flChunkSize = GetMinChargeAmount();
+ int nCurrentChunk = floor(m_flChargeLevel / flChunkSize);
+ m_flEndResistCharge = flChunkSize * Max( 0, (nCurrentChunk - 1) );
+#ifdef GAME_DLL
+ UberchargeChunkDeployed();
+#endif
+ }
+ else
+ {
+
+ // Make sure we don't cross over too far if this is a natural drain
+ if( !bForceDrain )
+ {
+ flNewCharge = m_flEndResistCharge;
+ }
+ m_flEndResistCharge = 0.f;
+ // Stop deploying
+ m_bChargeRelease = false;
+ m_flReleaseStartedAt = 0;
+ m_DetachedTargets.Purge();
+
+#ifdef GAME_DLL
+ pOwner->ClearPunchVictims();
+ RecalcEffectOnTarget( pOwner );
+#endif
+ }
+ }
+ }
+
+ m_flChargeLevel = flNewCharge;
+
+ if ( !m_flChargeLevel )
+ {
+ m_bChargeRelease = false;
+ m_flReleaseStartedAt = 0;
+ m_DetachedTargets.Purge();
+
+#ifdef GAME_DLL
+ pOwner->ClearPunchVictims();
+ RecalcEffectOnTarget( pOwner );
+ StopHealingOwner(); // QuickFix uber heals the target and medic
+#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 )
+ if (prediction->IsFirstTimePredicted() ) {
+ 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 && ( m_iState != WEAPON_IS_ACTIVE || pOwner->IsTaunting() ) )
+ {
+ RemoveHealingTarget();
+ }
+ else 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();
+ }
+ else
+ {
+ m_bAttack2Down = false;
+ }
+
+ if( (pOwner->m_nButtons & IN_ATTACK3) && !m_bAttack3Down )
+ {
+ CreateMedigunShield();
+ m_bAttack3Down = true;
+ }
+ else if( !(pOwner->m_nButtons & IN_ATTACK3) && m_bAttack3Down )
+ {
+ m_bAttack3Down = false;
+ }
+
+ if ( pOwner->m_nButtons & IN_RELOAD && !m_bReloadDown )
+ {
+#ifdef GAME_DLL
+ CycleResistType();
+#endif
+ m_bReloadDown = true;
+ }
+ else if ( !(pOwner->m_nButtons & IN_RELOAD) && m_bReloadDown )
+ {
+ m_bReloadDown = false;
+ }
+
+ 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;
+
+
+ // If this guy is already in our detached target list, update the time. Otherwise, add him.
+ if ( m_bChargeRelease )
+ {
+ int i = 0;
+ for ( i = 0; i < m_DetachedTargets.Count(); i++ )
+ {
+ if ( m_DetachedTargets[i].hTarget == m_hHealingTarget )
+ {
+ m_DetachedTargets[i].flTime = gpGlobals->curtime;
+ break;
+ }
+ }
+ if ( i == m_DetachedTargets.Count() )
+ {
+ int iIdx = m_DetachedTargets.AddToTail();
+ m_DetachedTargets[iIdx].hTarget = m_hHealingTarget;
+ m_DetachedTargets[iIdx].flTime = gpGlobals->curtime;
+ }
+ }
+
+#ifdef GAME_DLL
+ int nMedigunType = GetMedigunType();
+
+ if ( m_hHealingTarget )
+ {
+ // HACK: For now, just deal with players
+ if ( m_hHealingTarget->IsPlayer() )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( m_hHealingTarget );
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( pTFPlayer && pOwner )
+ {
+ pTFPlayer->m_Shared.StopHealing( pOwner );
+
+ pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_STOPPEDHEALING, pTFPlayer->IsAlive() ? "healtarget:alive" : "healtarget:dead" );
+ pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_STOPPEDHEALING );
+
+ // If we're grappled to this player, drop
+ if ( pOwner->GetGrapplingHookTarget() == pTFPlayer )
+ {
+ pOwner->SetGrapplingHookTarget( NULL );
+ }
+
+ // Remove our passive resist
+ if( nMedigunType == MEDIGUN_RESIST )
+ {
+ ETFCond cond = g_MedigunResistConditions[ GetResistType() ].passiveCond;
+ CBaseEntity* pProvider = pTFPlayer->m_Shared.GetConditionProvider( cond );
+
+ // Remove from our healing target if we're the provider
+ if( pProvider == pOwner || pProvider == NULL )
+ {
+ pTFPlayer->m_Shared.RemoveCond( cond );
+ }
+
+ // Remove from ourselves if we're the provider
+ pProvider = pOwner->m_Shared.GetConditionProvider( cond );
+ if( pProvider == pOwner || pProvider == NULL )
+ {
+ pOwner->m_Shared.RemoveCond( cond );
+ }
+ }
+ }
+ }
+ }
+
+ // Stop thinking - we no longer have a heal target.
+ SetContextThink( NULL, 0, s_pszMedigunHealTargetThink );
+#endif
+
+ m_hHealingTarget.Set( NULL );
+
+#ifdef GAME_DLL
+ // See if we have The QuickFix, which adjusts our move speed based on heal target
+ pOwner->TeamFortress_SetSpeed();
+
+#endif
+
+ // 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;
+
+ CTFPlayer *pTFPlayerPatient = NULL;
+ if ( m_hHealingTarget && m_hHealingTarget->IsPlayer() )
+ {
+ pTFPlayerPatient = ToTFPlayer( m_hHealingTarget );
+ }
+
+ // STAGING_MEDIC
+ // Resist gun early outs if the patient and medic both have the condition (or medic with no patient has condition)
+ if ( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ ETFCond uberCond = g_MedigunResistConditions[GetResistType()].uberCond;
+ if ( pOwner->m_Shared.InCond( uberCond ) )
+ {
+ if ( !pTFPlayerPatient || pTFPlayerPatient->m_Shared.InCond( uberCond ) )
+ {
+ return;
+ }
+ }
+ }
+
+ m_bAttack2Down = true;
+
+ // If using standard-uber-model-medigun, ensure they have a full charge and are not already in charge release mode
+ bool bDenyUse = GetMedigunType() != MEDIGUN_RESIST && (m_flChargeLevel < 1.0);
+ // If using the resist-medigun, they can shoot sooner
+ float flChunkSize = GetMinChargeAmount();
+ bDenyUse |= GetMedigunType() == MEDIGUN_RESIST && m_flChargeLevel < flChunkSize;
+
+ if ( bDenyUse || m_bChargeRelease )
+ {
+#ifdef CLIENT_DLL
+ // Deny, flash
+ if ( !m_bChargeRelease && gpGlobals->curtime >= m_flDenySecondary )
+ {
+ m_flDenySecondary = gpGlobals->curtime + 0.5f;
+ pOwner->EmitSound( "Player.DenyWeaponSelection" );
+ }
+#endif
+ return;
+ }
+
+ if ( !pOwner->m_Shared.CanRecieveMedigunChargeEffect( GetChargeType() ) )
+ {
+ if ( pOwner->m_afButtonPressed & IN_ATTACK2
+#ifdef CLIENT_DLL
+ && prediction->IsFirstTimePredicted()
+#endif
+ )
+ {
+#ifdef GAME_DLL
+ CSingleUserRecipientFilter filter( pOwner );
+ TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_NO_INVULN_WITH_FLAG );
+#else
+ pOwner->EmitSound( "Player.DenyWeaponSelection" );
+#endif
+ }
+ return;
+ }
+
+
+ // Toggle super charge state
+ m_bChargeRelease = true;
+ m_flReleaseStartedAt = gpGlobals->curtime;
+
+#ifdef GAME_DLL
+ if( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ // We dont want to give the user a point every time they deploy an uber with the resist medigun.
+ // Instead we give them a point for every 4 deploys
+ UberchargeChunkDeployed();
+
+ int nCurrentChunk = floor( m_flChargeLevel / flChunkSize );
+ Assert( nCurrentChunk >= 1 );
+
+ CPVSFilter filter( pOwner->WorldSpaceCenter() );
+ pOwner->EmitSound( filter, pOwner->entindex(), CFmtStr( "WeaponMedigun_Vaccinator.Charged_tier_0%d", nCurrentChunk ) );
+ pOwner->EmitSound( filter, pOwner->entindex(), g_MedigunEffects[MEDIGUN_CHARGE_BULLET_RESIST].pszChargeOnSound );
+ }
+ else
+ {
+ // Award assist point
+ CTF_GameStats.Event_PlayerInvulnerable( pOwner );
+ // Award strange assist score
+ EconEntity_OnOwnerKillEaterEvent( this, pOwner, ToTFPlayer( m_hHealingTarget ), kKillEaterEvent_UberActivated );
+ }
+
+ // STAGING_MEDIC
+ if ( GetMedigunType() != MEDIGUN_RESIST )
+ {
+ RecalcEffectOnTarget( pOwner );
+ }
+ pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_CHARGEDEPLOYED );
+
+ if ( pTFPlayerPatient )
+ {
+ // STAGING_MEDIC
+ if ( GetMedigunType() != MEDIGUN_RESIST )
+ {
+ RecalcEffectOnTarget( pTFPlayerPatient );
+ }
+
+ pTFPlayerPatient->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_CHARGEDEPLOYED );
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_chargedeployed" );
+ if ( event )
+ {
+ event->SetInt( "userid", pOwner->GetUserID() );
+ if ( m_hHealingTarget && m_hHealingTarget->IsPlayer() )
+ {
+ event->SetInt( "targetid", ToTFPlayer(m_hHealingTarget)->GetUserID() );
+ }
+ gameeventmanager->FireEvent( event );
+ }
+
+ // Check for achievements
+ // Simultaneous uber charge with teammates.
+ CTeam *pTeam = pOwner->GetTeam();
+ if ( pTeam )
+ {
+ CUtlVector<CTFPlayer*> aChargingMedics;
+ aChargingMedics.AddToTail( pOwner );
+ for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
+ {
+ CTFPlayer *pTeamPlayer = ToTFPlayer( pTeam->GetPlayer(i) );
+ if ( pTeamPlayer && pTeamPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && pTeamPlayer != pOwner )
+ {
+ CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( pTeamPlayer->GetActiveWeapon() );
+ if ( pWeapon && pWeapon->IsReleasingCharge() )
+ {
+ aChargingMedics.AddToTail( pTeamPlayer );
+ }
+ }
+ }
+
+ if ( aChargingMedics.Count() >= 3 )
+ {
+ // Give the achievement to all the Medics
+ for ( int i = 0; i < aChargingMedics.Count(); i++ )
+ {
+ if ( aChargingMedics[i] )
+ {
+ aChargingMedics[i]->AwardAchievement( ACHIEVEMENT_TF_MEDIC_SIMUL_CHARGE );
+ }
+ }
+ }
+ }
+
+ // reset this count
+ pOwner->HandleAchievement_Medic_AssistHeavy( NULL );
+
+ // If using the QuickFix, heal the medic
+ if ( GetMedigunType() == MEDIGUN_QUICKFIX )
+ {
+ if ( IsReleasingCharge() && !m_bHealingSelf )
+ {
+ StartHealingTarget( pOwner );
+ m_bHealingSelf = true;
+ }
+ }
+#endif // GAME_DLL
+
+ // STAGING_MEDIC
+ if ( GetMedigunType() == MEDIGUN_RESIST )
+ {
+ // Remove charge immediately and just give target and yourself the conditions
+ m_bChargeRelease = false;
+#ifdef GAME_DLL
+ int iResistDuration = 3.0f;
+ pOwner->m_Shared.AddCond( g_MedigunResistConditions[GetResistType()].uberCond, iResistDuration, pOwner );
+ m_flChargeLevel -= flChunkSize;
+ if ( pTFPlayerPatient )
+ {
+ pTFPlayerPatient->m_Shared.AddCond( g_MedigunResistConditions[GetResistType()].uberCond, iResistDuration, pOwner );
+ }
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ CBaseEntity *pTarget = m_hHealingTarget;
+ CTFReviveMarker *pReviveMarker = dynamic_cast<CTFReviveMarker*>( pTarget );
+ if ( pReviveMarker )
+ {
+ CTFPlayer *pDeadPlayer = pReviveMarker->GetOwner();
+ if ( pDeadPlayer )
+ {
+ pReviveMarker->SetReviver( pOwner );
+ // fill almost to max, give a small time period so patient has time to see notifications from regular revive code
+ pReviveMarker->AddMarkerHealth( pReviveMarker->GetMaxHealth() * 0.9f );
+ }
+ }
+ }
+
+#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( GetHealSound() );
+ }
+
+ if ( bStopNoTargetSound )
+ {
+ StopSound( "WeaponMedigun.NoTarget" );
+ }
+
+ if ( m_pDisruptSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pDisruptSound );
+ m_pDisruptSound = NULL;
+ }
+}
+
+void CWeaponMedigun::StopChargeEffect( bool bImmediately )
+{
+ // Either these should both be NULL or neither NULL
+ Assert( ( m_pChargeEffect != NULL && m_pChargeEffectOwner != NULL ) || ( m_pChargeEffect == NULL && m_pChargeEffectOwner == NULL ) );
+
+ if ( m_pChargeEffect != NULL && m_pChargeEffectOwner != NULL )
+ {
+ if( bImmediately )
+ {
+ m_pChargeEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pChargeEffect );
+ }
+ else
+ {
+ m_pChargeEffectOwner->ParticleProp()->StopEmission( m_pChargeEffect );
+ }
+ m_pChargeEffect = NULL;
+ m_pChargeEffectOwner = NULL;
+ }
+
+ if ( m_pChargedSound != NULL )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
+ m_pChargedSound = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::ManageChargeEffect( void )
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ C_BaseEntity *pEffectOwner = this;
+
+ if ( pLocalPlayer == NULL )
+ return;
+
+ if ( pLocalPlayer == GetTFPlayerOwner() )
+ {
+ pEffectOwner = pLocalPlayer->GetRenderedWeaponModel();
+ if ( !pEffectOwner )
+ {
+ return;
+ }
+ }
+
+ bool bOwnerTaunting = false;
+
+ if ( GetTFPlayerOwner() && GetTFPlayerOwner()->m_Shared.InCond( TF_COND_TAUNTING ) == true )
+ {
+ bOwnerTaunting = true;
+ }
+
+ float flMinChargeToDeploy = GetMinChargeAmount();
+
+ if ( GetTFPlayerOwner() && bOwnerTaunting == false && m_bHolstered == false && ( m_flChargeLevel >= flMinChargeToDeploy || m_bChargeRelease == true ) )
+ {
+ // Did we switch from 1st to 3rd or 3rd to 1st? Taunting does this.
+ if( pEffectOwner != m_pChargeEffectOwner )
+ {
+ // Stop the current effect so we can make a new one
+ StopChargeEffect( m_bHolstered );
+ }
+
+ if ( m_pChargeEffect == NULL )
+ {
+ const 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" );
+ m_pChargeEffectOwner = pEffectOwner;
+ }
+
+ 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
+ {
+ StopChargeEffect( m_bHolstered );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : updateType -
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::OnDataChanged( DataUpdateType_t updateType )
+{
+ BaseClass::OnDataChanged( updateType );
+
+ if ( m_bUpdateHealingTargets )
+ {
+ UpdateEffects();
+ m_bUpdateHealingTargets = false;
+ }
+
+ if ( m_nOldChargeResistType != m_nChargeResistType )
+ {
+ m_nOldChargeResistType = m_nChargeResistType;
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if( GetOwner() == pLocalPlayer && pLocalPlayer )
+ {
+ // Sound effect
+ pLocalPlayer->EmitSound( "WeaponMedigun_Vaccinator.Toggle" );
+ }
+ }
+
+ if ( m_bHealing )
+ {
+ CTFPlayer *pTarget = ToTFPlayer( m_hHealingTarget );
+ if ( !m_pDisruptSound && pTarget && pTarget->m_Shared.InCond( TF_COND_HEALING_DEBUFF ) )
+ {
+ CLocalPlayerFilter filter;
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ m_pDisruptSound = controller.SoundCreate( filter, entindex(), "WeaponMedigun.HealingDisrupt" );
+ controller.Play( m_pDisruptSound, 1.f, 100.f );
+ }
+ else if ( m_pDisruptSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pDisruptSound );
+ m_pDisruptSound = NULL;
+ }
+ }
+ 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.
+ }
+ }
+
+ // Think?
+ if ( m_bHealing || IsCarriedByLocalPlayer() )
+ {
+ ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS );
+ }
+
+ ManageChargeEffect();
+
+ // Find teammates that need healing
+ if ( IsCarriedByLocalPlayer() )
+ {
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalPlayer || !pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
+ {
+ return;
+ }
+
+ if ( pLocalPlayer == GetOwner() && hud_medicautocallers.GetBool() )
+ {
+ UpdateMedicAutoCallers();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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 );
+ }
+
+ // Setup whether we were last healed by the local player or by someone else (used by replay system)
+ // since GetHealer() gets cleared out every frame before player_death events get fired. See tf_replay.cpp.
+ C_BaseEntity *pHealingTargetEnt = m_hHealingTarget;
+ if ( pHealingTargetEnt && pHealingTargetEnt->IsPlayer() )
+ {
+ C_TFPlayer *pHealingTargetPlayer = ToTFPlayer( pHealingTargetEnt );
+ pHealingTargetPlayer->SetWasHealedByLocalPlayer( pFiringPlayer == pLocalPlayer );
+ }
+
+ if ( !m_bPlayingSound )
+ {
+ m_bPlayingSound = true;
+ CLocalPlayerFilter filter;
+ EmitSound( filter, entindex(), GetHealSound() );
+ }
+ }
+
+ if ( m_bOldChargeRelease != m_bChargeRelease )
+ {
+ m_bOldChargeRelease = m_bChargeRelease;
+ ForceHealingTargetUpdate();
+ }
+
+ // If the rendered weapon has changed, we need to update our particles
+ if ( m_hHealingTargetEffect.pOwner && pFiringPlayer->GetRenderedWeaponModel() != m_hHealingTargetEffect.pOwner )
+ {
+ ForceHealingTargetUpdate();
+ }
+
+ if ( pFiringPlayer->m_Shared.IsEnteringOrExitingFullyInvisible() )
+ {
+ UpdateEffects();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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->GetRenderedWeaponModel();
+ }
+
+ // If we're still healing and our owner changed, then we did something
+ // like changed
+ bool bImmediate = pEffectOwner != m_hHealingTargetEffect.pOwner && m_bHealing;
+
+ // Remove all the effects
+ if ( m_hHealingTargetEffect.pOwner )
+ {
+ if ( m_hHealingTargetEffect.pEffect )
+ {
+ bImmediate ? m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_hHealingTargetEffect.pEffect )
+ : m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmission( m_hHealingTargetEffect.pEffect );
+ }
+ if ( m_hHealingTargetEffect.pCustomEffect )
+ {
+ bImmediate ? m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_hHealingTargetEffect.pCustomEffect )
+ : m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmission( m_hHealingTargetEffect.pCustomEffect );
+ }
+ }
+ else
+ {
+ if ( m_hHealingTargetEffect.pEffect )
+ {
+ m_hHealingTargetEffect.pEffect->StopEmission();
+ }
+ if ( m_hHealingTargetEffect.pCustomEffect )
+ {
+ m_hHealingTargetEffect.pCustomEffect->StopEmission();
+ }
+ }
+ m_hHealingTargetEffect.pOwner = NULL;
+ m_hHealingTargetEffect.pTarget = NULL;
+ m_hHealingTargetEffect.pEffect = NULL;
+ m_hHealingTargetEffect.pCustomEffect = NULL;
+
+ // Don't add targets if the medic is dead
+ if ( !pEffectOwner || pFiringPlayer->IsPlayerDead() || !pFiringPlayer->IsPlayerClass( TF_CLASS_MEDIC ) || pFiringPlayer->m_Shared.IsFullyInvisible() )
+ 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;
+
+ bool bReviveMarker = m_hReviveMarker && m_hReviveMarker == m_hHealingTarget; // Hack to avoid another dynamic_cast here
+ bool bHealTargetMarker = hud_medichealtargetmarker.GetBool() && !bReviveMarker;
+
+ const char *pszEffectName;
+ if ( IsAttachedToBuilding() )
+ {
+ pszEffectName = "medicgun_beam_machinery";
+ }
+ else if ( pFiringPlayer->GetTeamNumber() == TF_TEAM_RED )
+ {
+ if ( m_bChargeRelease )
+ {
+ pszEffectName = "medicgun_beam_red_invun";
+ }
+ else
+ {
+ if ( bHealTargetMarker && pFiringPlayer == pLocalPlayer )
+ {
+ pszEffectName = "medicgun_beam_red_targeted";
+ }
+ else
+ {
+ pszEffectName = "medicgun_beam_red";
+ }
+ }
+ }
+ else
+ {
+ if ( m_bChargeRelease )
+ {
+ pszEffectName = "medicgun_beam_blue_invun";
+ }
+ else
+ {
+ if ( bHealTargetMarker && pFiringPlayer == pLocalPlayer )
+ {
+ pszEffectName = "medicgun_beam_blue_targeted";
+ }
+ else
+ {
+ pszEffectName = "medicgun_beam_blue";
+ }
+ }
+ }
+
+ // Attach differently if targeting a revive marker
+ float flVecHeightOffset = bReviveMarker ? 0.f : 50.f;
+ ParticleAttachment_t attachType = bReviveMarker ? PATTACH_POINT_FOLLOW : PATTACH_ABSORIGIN_FOLLOW;
+ const char *pszAttachName = bReviveMarker ? "healbeam" : NULL;
+
+ CNewParticleEffect *pEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
+ pEffectOwner->ParticleProp()->AddControlPoint( pEffect, 1, m_hHealingTarget, attachType, pszAttachName, Vector(0.f,0.f,flVecHeightOffset) );
+
+ m_hHealingTargetEffect.pTarget = m_hHealingTarget;
+ m_hHealingTargetEffect.pEffect = pEffect;
+ m_hHealingTargetEffect.pOwner = pEffectOwner;
+
+ // See if we have a custom particle effect that wants to add to the beam
+ CEconItemView *pItem = m_AttributeManager.GetItem();
+ int iSystems = pItem->GetStaticData()->GetNumAttachedParticles( GetTeamNumber() );
+ for ( int i = 0; i < iSystems; i++ )
+ {
+ attachedparticlesystem_t *pSystem = pItem->GetStaticData()->GetAttachedParticleData( GetTeamNumber(),i );
+ if ( pSystem->iCustomType == 1 )
+ {
+ pEffect = pEffectOwner->ParticleProp()->Create( pSystem->pszSystemName, PATTACH_POINT_FOLLOW, "muzzle" );
+ pEffectOwner->ParticleProp()->AddControlPoint( pEffect, 1, m_hHealingTarget, attachType, pszAttachName, Vector(0.f,0.f,flVecHeightOffset) );
+ m_hHealingTargetEffect.pCustomEffect = pEffect;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look for teammates that need healing
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::UpdateMedicAutoCallers( void )
+{
+ // Find teammates that need healing
+ if ( gpGlobals->curtime > m_flAutoCallerCheckTime )
+ {
+ if ( !g_TF_PR )
+ {
+ return;
+ }
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
+ {
+ if ( g_TF_PR->GetTeam( playerIndex ) == GetLocalPlayerTeam() )
+ {
+ C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
+
+ // Don't do this for the local player
+ if ( pPlayer == NULL || pPlayer == pLocalPlayer )
+ continue;
+
+ if( m_hHealingTarget != NULL )
+ {
+ // Don't do this for players the medic is healing
+ if ( pPlayer == m_hHealingTarget )
+ continue;
+ }
+
+ if ( pPlayer->IsAlive() )
+ {
+ int iHealth = float( pPlayer->GetHealth() ) / float( pPlayer->GetMaxHealth() ) * 100;
+ int iHealthThreshold = hud_medicautocallersthreshold.GetInt();
+
+ // If it's a healthy teammate....
+ if ( iHealth > iHealthThreshold )
+ {
+ // Make sure we don't have them in our list if previously hurt
+ if ( m_iAutoCallers.Find( playerIndex ) != m_iAutoCallers.InvalidIndex() )
+ {
+ m_iAutoCallers.FindAndRemove( playerIndex );
+ continue;
+ }
+ }
+
+ // If it's a hurt teammate....
+ if ( iHealth <= iHealthThreshold )
+ {
+
+ // Make sure we're not already tracking this
+ if ( m_iAutoCallers.Find( playerIndex ) != m_iAutoCallers.InvalidIndex())
+ continue;
+
+ // Distance check
+ float flDistSq = pPlayer->GetAbsOrigin().DistToSqr( pLocalPlayer->GetAbsOrigin() );
+ if ( flDistSq >= 1000000 )
+ {
+ continue;
+ }
+
+ // Now add auto-caller
+ pPlayer->CreateSaveMeEffect( CALLER_TYPE_AUTO );
+
+ // And track the player so we don't re-add them
+ m_iAutoCallers.AddToTail( playerIndex );
+ }
+ }
+ }
+ }
+
+ // Throttle this check
+ m_flAutoCallerCheckTime = gpGlobals->curtime + 0.25f;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::CheckAchievementsOnHealTarget( void )
+{
+ CTFPlayer *pTFPlayer = ToTFPlayer( m_hHealingTarget );
+ if ( !pTFPlayer )
+ return;
+
+#ifdef GAME_DLL
+
+ // Check for "target under fire" achievement
+ if ( pTFPlayer->m_AchievementData.CountDamagersWithinTime(3.0) >= 4 )
+ {
+ if ( GetTFPlayerOwner() )
+ {
+ GetTFPlayerOwner()->AwardAchievement( ACHIEVEMENT_TF_MEDIC_HEAL_UNDER_FIRE );
+ }
+ }
+
+ // Check for "Engineer repairing sentrygun" achievement
+ if ( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ // Has Engineer worked on his sentrygun recently?
+ CBaseObject *pSentry = pTFPlayer->GetObjectOfType( OBJ_SENTRYGUN );
+ if ( pSentry && pTFPlayer->m_AchievementData.IsTargetInHistory( pSentry, 4.0 ) )
+ {
+ if ( pSentry->m_AchievementData.CountDamagersWithinTime(3.0) > 0 )
+ {
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( pOwner )
+ {
+ // give to medic
+ pOwner->AwardAchievement( ACHIEVEMENT_TF_MEDIC_HEAL_ENGINEER );
+
+ // give to the engineer!
+ pTFPlayer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_REPAIR_SENTRY_W_MEDIC );
+ }
+ }
+ }
+ }
+#else
+
+ // check for ACHIEVEMENT_TF_MEDIC_HEAL_CALLERS
+ if ( pTFPlayer->m_flSaveMeExpireTime > gpGlobals->curtime )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_healedmediccall" );
+ if ( event )
+ {
+ event->SetInt( "userid", pTFPlayer->GetUserID() );
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Our owner has become stunned.
+//-----------------------------------------------------------------------------
+void CWeaponMedigun::OnControlStunned( void )
+{
+ BaseClass::OnControlStunned();
+
+ // Interrupt auto healing.
+ RemoveHealingTarget( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponMedigun::EffectMeterShouldFlash( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return false;
+
+ if ( pPlayer && ( pPlayer->m_Shared.GetRageMeter() >= 100.0f || pPlayer->m_Shared.IsRageDraining() ) )
+ return true;
+ else
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: UI Progress
+//-----------------------------------------------------------------------------
+float CWeaponMedigun::GetProgress( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return 0.f;
+
+ return pPlayer->m_Shared.GetRageMeter() / 100.0f;
+}
+
+//-----------------------------------------------------------------------------
+// entity_medigun_shield
+//-----------------------------------------------------------------------------
+#define MEDIGUNSHIELD_THINK_CONTEXT "CTFMedigunShield_ShieldThink"
+
+LINK_ENTITY_TO_CLASS( entity_medigun_shield, CTFMedigunShield );
+PRECACHE_WEAPON_REGISTER( entity_medigun_shield );
+
+// Network
+IMPLEMENT_NETWORKCLASS_ALIASED( TFMedigunShield, DT_TFMedigunShield )
+
+BEGIN_NETWORK_TABLE( CTFMedigunShield, DT_TFMedigunShield )
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFMedigunShield )
+END_PREDICTION_DATA()
+
+// Data
+BEGIN_DATADESC( CTFMedigunShield )
+#ifdef GAME_DLL
+DEFINE_FUNCTION( ShieldTouch ),
+DEFINE_THINKFUNC( ShieldThink ),
+#endif // GAME_DLL
+END_DATADESC()
+
+#ifdef GAME_DLL
+static const float PROJECTILE_SHIELD_ALPHA_BASE = 150.f;
+static const float PROJECTILE_SHIELD_ENERGY_REFILL_PULSE = 10.f;
+static const float PROJECTILE_SHIELD_ENERGY_MAX = 1000.f;
+#endif // GAME_DLL
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CTFMedigunShield::CTFMedigunShield()
+{
+ m_nBlinkCount = 0;
+#ifdef GAME_DLL
+ m_pTouchLoop = NULL;
+
+#ifdef STAGING_ONLY
+ m_bPermanentShield = false;
+#endif // STAGING_ONLY
+
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CTFMedigunShield::~CTFMedigunShield()
+{
+#ifdef GAME_DLL
+ if ( m_pTouchLoop )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pTouchLoop );
+ m_pTouchLoop = NULL;
+ }
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::Spawn()
+{
+ BaseClass::Spawn();
+
+ SetModel( "models/props_mvm/mvm_player_shield.mdl" );
+ SetSolid( SOLID_VPHYSICS );
+ SetSolidFlags( FSOLID_TRIGGER );
+ SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
+ SetBlocksLOS( false );
+ AddEffects( EF_NOSHADOW );
+
+ m_takedamage = DAMAGE_EVENTS_ONLY;
+
+#ifdef GAME_DLL
+ m_flShieldEnergyLevel = PROJECTILE_SHIELD_ENERGY_MAX;
+
+ SetTouch( &CTFMedigunShield::ShieldTouch );
+ SetContextThink( &CTFMedigunShield::ShieldThink, gpGlobals->curtime, MEDIGUNSHIELD_THINK_CONTEXT );
+#else
+ SetNextClientThink( CLIENT_THINK_ALWAYS );
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::Precache()
+{
+ PrecacheScriptSound( "WeaponMedi_Shield.Deploy" );
+ PrecacheScriptSound( "WeaponMedi_Shield.Protection" );
+ PrecacheScriptSound( "WeaponMedi_Shield.Retract" );
+ PrecacheScriptSound( "WeaponMedi_Shield.Burn" );
+ PrecacheScriptSound( "WeaponMedi_Shield.Burn_lp" );
+
+ PrecacheModel( "models/props_mvm/mvm_player_shield.mdl" );
+ PrecacheModel( "models/props_mvm/mvm_player_shield2.mdl" );
+
+ BaseClass::Precache();
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::ClientThink()
+{
+ UpdateShieldPosition();
+}
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::UpdateShieldPosition( void )
+{
+ CBaseEntity *pOwner = GetOwnerEntity();
+ if ( !pOwner )
+ return;
+
+ // Positioning logic
+ Vector vecForward;
+ AngleVectors( pOwner->EyeAngles(), &vecForward );
+ vecForward.z = 0.f;
+ Vector vecShieldOrigin = pOwner->GetAbsOrigin() + vecForward * 145.f;
+ SetAbsOrigin( vecShieldOrigin );
+ SetAbsAngles( QAngle( 0.f, pOwner->EyeAngles().y, 0.f ) ); // Ignore pitch
+}
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMedigunShield *CTFMedigunShield::Create( CTFPlayer *pOwner )
+{
+ if ( pOwner )
+ {
+ CTFMedigunShield *pShield = static_cast< CTFMedigunShield* >( CBaseEntity::Create( "entity_medigun_shield", pOwner->GetAbsOrigin() + Vector( 0, 0, 60 ), pOwner->GetAbsAngles() ) );
+ if ( pShield )
+ {
+ pShield->SetOwnerEntity( pOwner );
+ pShield->ChangeTeam( pOwner->GetTeamNumber() );
+
+ int nShieldLevel = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, nShieldLevel, generate_rage_on_heal );
+ if ( nShieldLevel > 1 )
+ {
+ pShield->SetModel( "models/props_mvm/mvm_player_shield2.mdl" );
+ }
+
+ pShield->m_nSkin = pShield->GetTeamNumber() == TF_TEAM_RED ? 0 : 1;
+
+ CPVSFilter filter( pOwner->WorldSpaceCenter() );
+ pOwner->EmitSound( filter, pOwner->entindex(), "WeaponMedi_Shield.Deploy" );
+
+ return pShield;
+ }
+ }
+
+ return NULL;
+}
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+// bool CTFMedigunShield::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
+// {
+// switch( GetTeamNumber() )
+// {
+// case TF_TEAM_RED:
+// if ( ( mask & CONTENTS_REDTEAM ) )
+// return false;
+// break;
+//
+// case TF_TEAM_BLUE:
+// if ( ( mask & CONTENTS_BLUETEAM ) )
+// return false;
+// break;
+// }
+//
+// vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() );
+//
+// UTIL_ClearTrace( trace );
+//
+// physcollision->TraceBox( ray, pCollide->solids[0], GetAbsOrigin(), GetAbsAngles(), &trace );
+//
+// if ( trace.fraction >= 1 )
+// return false;
+//
+// // return owner as trace hit
+// trace.m_pEnt = this;
+// return true;
+// }
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFMedigunShield::ShouldCollide( int collisionGroup, int contentsMask ) const
+{
+ if ( collisionGroup == COLLISION_GROUP_PROJECTILE ||
+ collisionGroup == TFCOLLISION_GROUP_ROCKETS ||
+ collisionGroup == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS )
+ {
+ switch( GetTeamNumber() )
+ {
+ case TF_TEAM_RED:
+ if ( ( contentsMask & CONTENTS_BLUETEAM ) )
+ return false;
+ break;
+
+ case TF_TEAM_BLUE:
+ if ( ( contentsMask & CONTENTS_REDTEAM ) )
+ return false;
+ break;
+ }
+ }
+
+ return BaseClass::ShouldCollide( collisionGroup, contentsMask );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::StartTouch( CBaseEntity *pOther )
+{
+ BaseClass::StartTouch( pOther );
+
+ CBaseEntity *pOwner = GetOwnerEntity();
+ if ( !pOwner )
+ return;
+
+ if ( !InSameTeam( pOther ) )
+ {
+ CPVSFilter filter( WorldSpaceCenter() );
+ pOwner->EmitSound( filter, entindex(), "WeaponMedi_Shield.Burn" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::ShieldTouch( CBaseEntity *pOther )
+{
+ if ( !pOther )
+ return;
+
+ if ( !InSameTeam( pOther ) )
+ {
+ // Drip pan for unknown projectiles
+ if ( !pOther->IsDeflectable() && pOther->GetCollisionGroup() == COLLISION_GROUP_PROJECTILE )
+ {
+ UTIL_Remove( pOther );
+ return;
+ }
+
+ CTFPlayer *pTFOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pTFOwner )
+ return;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( pTFOwner->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
+ return;
+
+ int nShieldLevel = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFOwner, nShieldLevel, generate_rage_on_heal );
+
+ // Damage it
+ float flDamage = ( nShieldLevel > 1 ) ? 2.f : 1.f;
+ CTakeDamageInfo info;
+ info.SetAttacker( pTFOwner );
+ info.SetInflictor( this );
+ info.SetWeapon( pTFOwner->GetActiveTFWeapon() );
+ info.SetDamage( flDamage );
+ info.SetDamageType( DMG_ENERGYBEAM );
+ info.SetDamageCustom( TF_DMG_CUSTOM_PLASMA );
+ info.SetDamagePosition( pOther->EyePosition() );
+ pOther->TakeDamage( info );
+
+ // Slow enemy players
+ if ( pOther->IsPlayer() )
+ {
+ CTFPlayer *pTFVictim = ToTFPlayer( pOther );
+ if ( !pTFVictim )
+ return;
+
+ float flStun = ( nShieldLevel > 1 ) ? 0.5f : 0.3f;
+ pTFVictim->m_Shared.StunPlayer( 0.5f, flStun, TF_STUN_MOVEMENT, pTFOwner );
+ }
+
+ // Sound
+ if ( !m_pTouchLoop )
+ {
+ CPVSFilter filter( GetAbsOrigin() );
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ m_pTouchLoop = controller.SoundCreate( filter, entindex(), "WeaponMedi_Shield.Burn_lp" );
+ controller.Play( m_pTouchLoop, 1.0, 100 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::EndTouch( CBaseEntity *pOther )
+{
+ BaseClass::EndTouch( pOther );
+
+ if ( m_pTouchLoop )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pTouchLoop );
+ m_pTouchLoop = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::ShieldThink( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+#ifdef STAGING_ONLY
+ if ( !pOwner || ( !pOwner->m_Shared.IsRageDraining() && !m_bPermanentShield ) )
+#else
+ if ( !pOwner || !pOwner->m_Shared.IsRageDraining() )
+#endif
+ {
+ RemoveShield();
+ return;
+ }
+
+ UpdateShieldPosition();
+
+ // Regen shield
+ if ( m_flShieldEnergyLevel < PROJECTILE_SHIELD_ENERGY_MAX )
+ {
+ m_flShieldEnergyLevel += PROJECTILE_SHIELD_ENERGY_REFILL_PULSE;
+ m_flShieldEnergyLevel = Min( m_flShieldEnergyLevel, PROJECTILE_SHIELD_ENERGY_MAX );
+ }
+
+ // Visuals
+ SetRenderMode( kRenderTransAlpha );
+ int nShieldOpacity = PROJECTILE_SHIELD_ALPHA_BASE;
+
+ // Determine alpha
+ float flFadePoint = PROJECTILE_SHIELD_ENERGY_MAX / 2.f;
+ if ( m_flShieldEnergyLevel <= flFadePoint )
+ {
+ nShieldOpacity = (int)RemapValClamped( m_flShieldEnergyLevel, 0.f, flFadePoint, 255.f, PROJECTILE_SHIELD_ALPHA_BASE );
+ }
+
+ // Blink when we're about to expire
+#ifdef STAGING_ONLY
+ if ( pOwner->m_Shared.GetRageMeter() <= 25.f && !m_bPermanentShield )
+#else
+ if ( pOwner->m_Shared.GetRageMeter() <= 25.f )
+#endif
+ {
+ ++m_nBlinkCount;
+
+ if ( m_nBlinkCount & 0x2 )
+ {
+ SetRenderColorA( PROJECTILE_SHIELD_ALPHA_BASE - 100 );
+ }
+ else
+ {
+ SetRenderColorA( nShieldOpacity );
+ }
+
+ // Play a retract sound
+ if ( m_nBlinkCount == 1 )
+ {
+ CPVSFilter filter( pOwner->WorldSpaceCenter() );
+ pOwner->EmitSound( filter, entindex(), "WeaponMedi_Shield.Retract" );
+ }
+ }
+ else
+ {
+ SetRenderColorA( nShieldOpacity );
+ }
+
+ SetContextThink( &CTFMedigunShield::ShieldThink, gpGlobals->curtime + 0.1f, MEDIGUNSHIELD_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::RemoveShield( void )
+{
+ SetTouch( NULL );
+ AddEffects( EF_NODRAW );
+
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFMedigunShield::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ if ( !InSameTeam( info.GetAttacker() ) )
+ {
+ CEffectData data;
+ data.m_vOrigin = ptr->endpos - (vecDir * 8);
+ data.m_vNormal = -vecDir;
+ data.m_flMagnitude = RemapValClamped( info.GetDamage(), 1.f, 50.f, 0.5f, 2.f );
+
+ CPVSFilter filter( GetAbsOrigin() );
+ te->DispatchEffect( filter, 0.f, data.m_vOrigin, "EnergyShieldImpact", data );
+
+ // g_pEffects->EnergySplash( ptr->endpos - (vecDir * 8), -vecDir, false );
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int CTFMedigunShield::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if ( !InSameTeam( info.GetAttacker() ) )
+ {
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( pOwner )
+ {
+ float flDamage = info.GetDamage();
+ m_flShieldEnergyLevel -= flDamage;
+ m_flShieldEnergyLevel = Max( m_flShieldEnergyLevel, 0.f );
+
+ // Add to blocked damage stat
+ CTF_GameStats.Event_PlayerBlockedDamage( pOwner, flDamage );
+
+// CPVSFilter filter( pOwner->WorldSpaceCenter() );
+// pOwner->EmitSound( filter, entindex(), "WeaponMedi_Shield.Protection" );
+
+ pOwner->PlayDamageResistSound( 100.f, RandomFloat( 0.85f, 1.f ) );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "medigun_shield_blocked_damage" );
+ if ( event )
+ {
+ event->SetInt( "userid", pOwner->GetUserID() );
+ event->SetFloat( "damage", flDamage );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ }
+
+ return 0;
+}
+#endif // GAME_DLL
+
+