You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3001 lines
86 KiB
3001 lines
86 KiB
//========= 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 |
|
|
|
|
|
|