summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_player_shared.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_player_shared.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'game/shared/tf/tf_player_shared.cpp')
-rw-r--r--game/shared/tf/tf_player_shared.cpp14162
1 files changed, 14162 insertions, 0 deletions
diff --git a/game/shared/tf/tf_player_shared.cpp b/game/shared/tf/tf_player_shared.cpp
new file mode 100644
index 0000000..27722c9
--- /dev/null
+++ b/game/shared/tf/tf_player_shared.cpp
@@ -0,0 +1,14162 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_gamerules.h"
+#include "tf_player_shared.h"
+#include "takedamageinfo.h"
+#include "tf_weaponbase.h"
+#include "effect_dispatch_data.h"
+#include "tf_item.h"
+#include "entity_capture_flag.h"
+#include "tf_weapon_medigun.h"
+#include "tf_weapon_pipebomblauncher.h"
+#include "tf_weapon_invis.h"
+#include "tf_weapon_sniperrifle.h"
+#include "tf_weapon_shovel.h"
+#include "tf_weapon_sword.h"
+#include "tf_weapon_shotgun.h"
+#include "in_buttons.h"
+#include "tf_weapon_lunchbox.h"
+#include "tf_weapon_flaregun.h"
+#include "tf_weapon_wrench.h"
+#include "econ_wearable.h"
+#include "econ_item_system.h"
+#include "tf_weapon_knife.h"
+#include "tf_weapon_syringegun.h"
+#include "tf_weapon_flamethrower.h"
+#include "econ_entity_creation.h"
+#include "tf_mapinfo.h"
+#include "tf_dropped_weapon.h"
+#include "tf_weapon_passtime_gun.h"
+
+// Client specific.
+#ifdef CLIENT_DLL
+#include "c_tf_player.h"
+#include "c_te_effect_dispatch.h"
+#include "c_tf_fx.h"
+#include "soundenvelope.h"
+#include "c_tf_playerclass.h"
+#include "iviewrender.h"
+#include "prediction.h"
+#include "achievementmgr.h"
+#include "baseachievement.h"
+#include "achievements_tf.h"
+#include "c_tf_weapon_builder.h"
+#include "dt_utlvector_recv.h"
+#include "recvproxy.h"
+#include "c_tf_weapon_builder.h"
+#include "c_func_capture_zone.h"
+#include "tf_hud_target_id.h"
+#include "tempent.h"
+#include "cam_thirdperson.h"
+#include "vgui/IInput.h"
+
+#define CTFPlayerClass C_TFPlayerClass
+#define CCaptureZone C_CaptureZone
+#define CRecipientFilter C_RecipientFilter
+
+#include "c_tf_objective_resource.h"
+#include "tf_weapon_buff_item.h"
+#include "c_tf_passtime_logic.h"
+
+// Server specific.
+#else
+#include "tf_player.h"
+#include "te_effect_dispatch.h"
+#include "tf_fx.h"
+#include "util.h"
+#include "tf_team.h"
+#include "tf_gamestats.h"
+#include "tf_playerclass.h"
+#include "SpriteTrail.h"
+#include "tf_weapon_builder.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "nav_pathfind.h"
+#include "tf_obj_dispenser.h"
+#include "dt_utlvector_send.h"
+#include "tf_item_wearable.h"
+#include "NextBotManager.h"
+#include "tf_weapon_builder.h"
+#include "func_capture_zone.h"
+#include "hl2orange.spa.h"
+#include "bot/tf_bot.h"
+#include "tf_objective_resource.h"
+#include "halloween/tf_weapon_spellbook.h"
+#include "tf_weapon_buff_item.h"
+#include "tf_passtime_logic.h"
+#include "tf_weapon_passtime_gun.h"
+#include "entity_healthkit.h"
+#include "halloween/merasmus/merasmus.h"
+#include "tf_weapon_grapplinghook.h"
+#include "tf_wearable_levelable_item.h"
+#include "tf_obj_sentrygun.h"
+#endif
+
+#include "tf_wearable_item_demoshield.h"
+#include "tf_weapon_bonesaw.h"
+
+static ConVar tf_demoman_charge_frametime_scaling( "tf_demoman_charge_frametime_scaling", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "When enabled, scale yaw limiting based on client performance (frametime)." );
+static const float YAW_CAP_SCALE_MIN = 0.2f;
+static const float YAW_CAP_SCALE_MAX = 2.f;
+
+ConVar tf_halloween_kart_boost_recharge( "tf_halloween_kart_boost_recharge", "5.0f", FCVAR_REPLICATED | FCVAR_CHEAT );
+ConVar tf_halloween_kart_boost_duration( "tf_halloween_kart_boost_duration", "1.5f", FCVAR_REPLICATED | FCVAR_CHEAT );
+
+ConVar tf_scout_air_dash_count( "tf_scout_air_dash_count", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+
+ConVar tf_spy_invis_time( "tf_spy_invis_time", "1.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Transition time in and out of spy invisibility", true, 0.1, true, 5.0 );
+ConVar tf_spy_invis_unstealth_time( "tf_spy_invis_unstealth_time", "2.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Transition time in and out of spy invisibility", true, 0.1, true, 5.0 );
+
+ConVar tf_spy_max_cloaked_speed( "tf_spy_max_cloaked_speed", "999", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED ); // no cap
+ConVar tf_whip_speed_increase( "tf_whip_speed_increase", "105", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
+ConVar tf_max_health_boost( "tf_max_health_boost", "1.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max health factor that players can be boosted to by healers.", true, 1.0, false, 0 );
+ConVar tf_invuln_time( "tf_invuln_time", "1.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Time it takes for invulnerability to wear off." );
+ConVar tf_player_movement_stun_time( "tf_player_movement_stun_time", "0.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
+extern ConVar tf_player_movement_restart_freeze;
+extern ConVar mp_tournament_readymode_countdown;
+extern ConVar tf_max_charge_speed;
+
+ConVar tf_always_loser( "tf_always_loser", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Force loserstate to true." );
+
+ConVar tf_mvm_bot_flag_carrier_movement_penalty( "tf_mvm_bot_flag_carrier_movement_penalty", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT );
+
+//ConVar tf_scout_dodge_move_penalty_duration( "tf_scout_dodge_move_penalty_duration", "3.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
+//ConVar tf_scout_dodge_move_penalty( "tf_scout_dodge_move_penalty", "0.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
+
+
+#ifdef GAME_DLL
+ConVar tf_boost_drain_time( "tf_boost_drain_time", "15.0", FCVAR_DEVELOPMENTONLY, "Time is takes for a full health boost to drain away from a player.", true, 0.1, false, 0 );
+#ifdef _DEBUG
+CON_COMMAND_F( tf_add_bombhead, "Add Merasmus Bomb Head Condition", 0 )
+{
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, true );
+
+ FOR_EACH_VEC ( playerVector, i )
+ {
+ float flBuffDuration = 7.0f;
+ playerVector[i]->m_Shared.StunPlayer( flBuffDuration, 0.f, TF_STUN_LOSER_STATE );
+ playerVector[i]->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, flBuffDuration );
+ playerVector[i]->m_Shared.AddCond( TF_COND_SPEED_BOOST, flBuffDuration );
+ //playerVector[i]->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, 7 );
+ }
+}
+
+ConVar tf_debug_bullets( "tf_debug_bullets", "0", FCVAR_DEVELOPMENTONLY, "Visualize bullet traces." );
+#endif // _DEBUG
+
+ConVar tf_damage_events_track_for( "tf_damage_events_track_for", "30", FCVAR_DEVELOPMENTONLY );
+
+extern ConVar tf_halloween_giant_health_scale;
+
+ConVar tf_allow_sliding_taunt( "tf_allow_sliding_taunt", "0", FCVAR_NONE, "1 - Allow player to slide for a bit after taunting" );
+
+#endif // GAME_DLL
+
+#ifdef STAGING_ONLY
+ConVar tf_force_allow_move_during_taunt( "tf_force_allow_move_during_taunt", "0", FCVAR_REPLICATED );
+#endif // STAGING_ONLY
+
+ConVar tf_useparticletracers( "tf_useparticletracers", "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Use particle tracers instead of old style ones." );
+ConVar tf_spy_cloak_consume_rate( "tf_spy_cloak_consume_rate", "10.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "cloak to use per second while cloaked, from 100 max )" ); // 10 seconds of invis
+ConVar tf_spy_cloak_regen_rate( "tf_spy_cloak_regen_rate", "3.3", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "cloak to regen per second, up to 100 max" ); // 30 seconds to full charge
+ConVar tf_spy_cloak_no_attack_time( "tf_spy_cloak_no_attack_time", "2.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "time after uncloaking that the spy is prohibited from attacking" );
+ConVar tf_tournament_hide_domination_icons( "tf_tournament_hide_domination_icons", "0", FCVAR_REPLICATED, "Tournament mode server convar that forces clients to not display the domination icons above players dominating them." );
+ConVar tf_damage_disablespread( "tf_damage_disablespread", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Toggles the random damage spread applied to all player damage." );
+
+ConVar tf_scout_energydrink_regen_rate( "tf_scout_energydrink_regen_rate", "3.3", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "energy drink regen per second, up to 100 max" );
+ConVar tf_scout_energydrink_consume_rate( "tf_scout_energydrink_consume_rate", "12.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "energy drink to use per second while boosted, from 100 max" );
+ConVar tf_scout_energydrink_activation( "tf_scout_energydrink_activation", "0.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "how long it takes for the energy buff to become active" );
+
+ConVar tf_demoman_charge_regen_rate( "tf_demoman_charge_regen_rate", "8.3", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "" );
+ConVar tf_demoman_charge_drain_time( "tf_demoman_charge_drain_time", "1.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "" );
+
+// STAGING_SPY
+ConVar tf_feign_death_duration( "tf_feign_death_duration", "3.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Time that feign death buffs last." );
+ConVar tf_feign_death_speed_duration( "tf_feign_death_speed_duration", "3.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Time that feign death speed boost last." );
+
+ConVar tf_allow_taunt_switch( "tf_allow_taunt_switch", "0", FCVAR_REPLICATED, "0 - players are not allowed to switch weapons while taunting, 1 - players can switch weapons at the start of a taunt (old bug behavior), 2 - players can switch weapons at any time during a taunt." );
+
+ConVar tf_allow_all_team_partner_taunt( "tf_allow_all_team_partner_taunt", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
+
+#ifdef STAGING_ONLY
+ConVar tf_random_item_min( "tf_random_item_min", "-1", FCVAR_REPLICATED, "Min Itemdef for random cosmetics" );
+ConVar tf_random_item_max( "tf_random_item_max", "-1", FCVAR_REPLICATED, "Max Itemdef for random cosmetics" );
+
+ConVar tf_killstreak_eyeglow( "tf_killstreak_eyeglow", "0", FCVAR_REPLICATED, "Force Kill Streak effect index for Eye Glows. -1 to disable regardless of equipped items. Stats at 2002" );
+ConVar tf_killstreak_color( "tf_killstreak_color", "0", FCVAR_REPLICATED, "Force Kill Streak color." );
+ConVar tf_eyeglow_wip( "tf_eyeglow_wip", "0", FCVAR_REPLICATED, "Activate to Always have specific wip eyeglow (DEV)" );
+#endif // Staging only
+
+#ifdef CLIENT_DLL
+ConVar tf_colorblindassist( "tf_colorblindassist", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Setting this to 1 turns on colorblind mode." );
+
+extern ConVar cam_idealdist;
+extern ConVar cam_idealdistright;
+
+#endif // CLIENT_DLL
+
+extern ConVar tf_flamethrower_flametime;
+extern ConVar weapon_medigun_chargerelease_rate;
+#if defined( _DEBUG ) || defined( STAGING_ONLY )
+extern ConVar mp_developer;
+#endif // _DEBUG || STAGING_ONLY
+
+//ConVar tf_spy_stealth_blink_time( "tf_spy_stealth_blink_time", "0.3", FCVAR_DEVELOPMENTONLY, "time after being hit the spy blinks into view" );
+//ConVar tf_spy_stealth_blink_scale( "tf_spy_stealth_blink_scale", "0.85", FCVAR_DEVELOPMENTONLY, "percentage visible scalar after being hit the spy blinks into view" );
+#define TF_SPY_STEALTH_BLINKTIME 0.3f
+#define TF_SPY_STEALTH_BLINKSCALE 0.85f
+
+#define TF_BUILDING_PICKUP_RANGE 150
+#define TF_BUILDING_RESCUE_MIN_RANGE_SQ 62500 //250 * 250
+#define TF_BUILDING_RESCUE_MAX_RANGE 5500
+
+#define TF_PLAYER_CONDITION_CONTEXT "TFPlayerConditionContext"
+
+#define TF_SCREEN_OVERLAY_MATERIAL_BURNING "effects/imcookin"
+#define TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED "effects/invuln_overlay_red"
+#define TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE "effects/invuln_overlay_blue"
+
+#define TF_SCREEN_OVERLAY_MATERIAL_MILK "effects/milk_screen"
+#define TF_SCREEN_OVERLAY_MATERIAL_URINE "effects/jarate_overlay"
+#define TF_SCREEN_OVERLAY_MATERIAL_BLEED "effects/bleed_overlay"
+#define TF_SCREEN_OVERLAY_MATERIAL_STEALTH "effects/stealth_overlay"
+#define TF_SCREEN_OVERLAY_MATERIAL_SWIMMING_CURSE "effects/jarate_overlay"
+
+#define TF_SCREEN_OVERLAY_MATERIAL_PHASE "effects/dodge_overlay"
+
+#define MAX_DAMAGE_EVENTS 128
+
+const char *g_pszBDayGibs[22] =
+{
+ "models/effects/bday_gib01.mdl",
+ "models/effects/bday_gib02.mdl",
+ "models/effects/bday_gib03.mdl",
+ "models/effects/bday_gib04.mdl",
+ "models/player/gibs/gibs_balloon.mdl",
+ "models/player/gibs/gibs_burger.mdl",
+ "models/player/gibs/gibs_boot.mdl",
+ "models/player/gibs/gibs_bolt.mdl",
+ "models/player/gibs/gibs_can.mdl",
+ "models/player/gibs/gibs_clock.mdl",
+ "models/player/gibs/gibs_fish.mdl",
+ "models/player/gibs/gibs_gear1.mdl",
+ "models/player/gibs/gibs_gear2.mdl",
+ "models/player/gibs/gibs_gear3.mdl",
+ "models/player/gibs/gibs_gear4.mdl",
+ "models/player/gibs/gibs_gear5.mdl",
+ "models/player/gibs/gibs_hubcap.mdl",
+ "models/player/gibs/gibs_licenseplate.mdl",
+ "models/player/gibs/gibs_spring1.mdl",
+ "models/player/gibs/gibs_spring2.mdl",
+ "models/player/gibs/gibs_teeth.mdl",
+ "models/player/gibs/gibs_tire.mdl"
+};
+
+ETFCond g_SoldierBuffAttributeIDToConditionMap[kSoldierBuffCount + 1] =
+{
+ TF_COND_LAST, // dummy entry to deal with attribute value of "1" being the lowest value we store in the attribute itself
+ TF_COND_OFFENSEBUFF,
+ TF_COND_DEFENSEBUFF,
+ TF_COND_REGENONDAMAGEBUFF,
+ TF_COND_NOHEALINGDAMAGEBUFF,
+ TF_COND_CRITBOOSTED_RAGE_BUFF,
+ TF_COND_SNIPERCHARGE_RAGE_BUFF
+};
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RecvProxy_BuildablesListChanged( const CRecvProxyData *pData, void *pStruct, void *pOut )
+{
+ RecvProxy_Int32ToInt32( pData, pStruct, pOut );
+
+ C_TFPlayer* pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalPlayer || pLocalPlayer->entindex() != pData->m_ObjectID )
+ return;
+
+ int index = pData->m_pRecvProp->GetOffset() / sizeof(int);
+ int object = pData->m_Value.m_Int;
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "update_status_item" );
+ if ( event )
+ {
+ event->SetInt( "index", index );
+ event->SetInt( "object", object );
+ gameeventmanager->FireEventClientSide( event );
+ }
+}
+#endif
+
+//=============================================================================
+//
+// Tables.
+//
+
+// Client specific.
+
+#ifdef CLIENT_DLL
+
+BEGIN_RECV_TABLE_NOBASE( localplayerscoring_t, DT_TFPlayerScoringDataExclusive )
+ RecvPropInt( RECVINFO( m_iCaptures ) ),
+ RecvPropInt( RECVINFO( m_iDefenses ) ),
+ RecvPropInt( RECVINFO( m_iKills ) ),
+ RecvPropInt( RECVINFO( m_iDeaths ) ),
+ RecvPropInt( RECVINFO( m_iSuicides ) ),
+ RecvPropInt( RECVINFO( m_iDominations ) ),
+ RecvPropInt( RECVINFO( m_iRevenge ) ),
+ RecvPropInt( RECVINFO( m_iBuildingsBuilt ) ),
+ RecvPropInt( RECVINFO( m_iBuildingsDestroyed ) ),
+ RecvPropInt( RECVINFO( m_iHeadshots ) ),
+ RecvPropInt( RECVINFO( m_iBackstabs ) ),
+ RecvPropInt( RECVINFO( m_iHealPoints ) ),
+ RecvPropInt( RECVINFO( m_iInvulns ) ),
+ RecvPropInt( RECVINFO( m_iTeleports ) ),
+ RecvPropInt( RECVINFO( m_iResupplyPoints ) ),
+ RecvPropInt( RECVINFO( m_iKillAssists ) ),
+ RecvPropInt( RECVINFO( m_iPoints ) ),
+ RecvPropInt( RECVINFO( m_iBonusPoints ) ),
+ RecvPropInt( RECVINFO( m_iDamageDone ) ),
+ RecvPropInt( RECVINFO( m_iCrits ) ),
+END_RECV_TABLE()
+
+EXTERN_RECV_TABLE(DT_TFPlayerConditionListExclusive);
+
+BEGIN_RECV_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerSharedLocal )
+ RecvPropInt( RECVINFO( m_nDesiredDisguiseTeam ) ),
+ RecvPropInt( RECVINFO( m_nDesiredDisguiseClass ) ),
+ RecvPropTime( RECVINFO( m_flStealthNoAttackExpire ) ),
+ RecvPropTime( RECVINFO( m_flStealthNextChangeTime ) ),
+ RecvPropBool( RECVINFO( m_bLastDisguisedAsOwnTeam ) ),
+ RecvPropFloat( RECVINFO( m_flRageMeter ) ),
+ RecvPropBool( RECVINFO( m_bRageDraining ) ),
+ RecvPropTime( RECVINFO( m_flNextRageEarnTime ) ),
+ RecvPropBool( RECVINFO( m_bInUpgradeZone ) ),
+ RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominated ), RecvPropBool( RECVINFO( m_bPlayerDominated[0] ) ) ),
+ RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominatingMe ), RecvPropBool( RECVINFO( m_bPlayerDominatingMe[0] ) ) ),
+ RecvPropDataTable( RECVINFO_DT(m_ScoreData),0, &REFERENCE_RECV_TABLE(DT_TFPlayerScoringDataExclusive) ),
+ RecvPropDataTable( RECVINFO_DT(m_RoundScoreData),0, &REFERENCE_RECV_TABLE(DT_TFPlayerScoringDataExclusive) ),
+END_RECV_TABLE()
+
+BEGIN_RECV_TABLE_NOBASE( condition_source_t, DT_TFPlayerConditionSource )
+ //RecvPropInt( RECVINFO( m_nPreventedDamageFromCondition ) ),
+ //RecvPropFloat( RECVINFO( m_flExpireTime ) ),
+ RecvPropEHandle( RECVINFO( m_pProvider ) ),
+ //RecvPropBool( RECVINFO( m_bPrevActive ) ),
+END_RECV_TABLE()
+
+BEGIN_RECV_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerShared )
+ RecvPropInt( RECVINFO( m_nPlayerCond ) ),
+ RecvPropInt( RECVINFO( m_bJumping) ),
+ RecvPropInt( RECVINFO( m_nNumHealers ) ),
+ RecvPropInt( RECVINFO( m_iCritMult ) ),
+ RecvPropInt( RECVINFO( m_iAirDash ) ),
+ RecvPropInt( RECVINFO( m_nAirDucked ) ),
+ RecvPropFloat( RECVINFO( m_flDuckTimer ) ),
+ RecvPropInt( RECVINFO( m_nPlayerState ) ),
+ RecvPropInt( RECVINFO( m_iDesiredPlayerClass ) ),
+ RecvPropFloat( RECVINFO( m_flMovementStunTime ) ),
+ RecvPropInt( RECVINFO( m_iMovementStunAmount ) ),
+ RecvPropInt( RECVINFO( m_iMovementStunParity ) ),
+ RecvPropEHandle( RECVINFO( m_hStunner ) ),
+ RecvPropInt( RECVINFO( m_iStunFlags ) ),
+ RecvPropInt( RECVINFO( m_nArenaNumChanges ) ),
+ RecvPropBool( RECVINFO( m_bArenaFirstBloodBoost ) ),
+ RecvPropInt( RECVINFO( m_iWeaponKnockbackID ) ),
+ RecvPropBool( RECVINFO( m_bLoadoutUnavailable ) ),
+ RecvPropInt( RECVINFO( m_iItemFindBonus ) ),
+ RecvPropBool( RECVINFO( m_bShieldEquipped ) ),
+ RecvPropBool( RECVINFO( m_bParachuteEquipped ) ),
+ RecvPropInt( RECVINFO( m_iNextMeleeCrit ) ),
+ RecvPropInt( RECVINFO( m_iDecapitations ) ),
+ RecvPropInt( RECVINFO( m_iRevengeCrits ) ),
+ RecvPropInt( RECVINFO( m_iDisguiseBody ) ),
+ RecvPropEHandle( RECVINFO( m_hCarriedObject ) ),
+ RecvPropBool( RECVINFO( m_bCarryingObject ) ),
+ RecvPropFloat( RECVINFO( m_flNextNoiseMakerTime ) ),
+ RecvPropInt( RECVINFO( m_iSpawnRoomTouchCount ) ),
+ RecvPropInt( RECVINFO( m_iKillCountSinceLastDeploy ) ),
+ RecvPropFloat( RECVINFO( m_flFirstPrimaryAttack ) ),
+
+ //Scout
+ RecvPropFloat( RECVINFO( m_flEnergyDrinkMeter) ),
+ RecvPropFloat( RECVINFO( m_flHypeMeter) ),
+
+ // Demoman
+ RecvPropFloat( RECVINFO( m_flChargeMeter) ),
+
+ // Spy.
+ RecvPropTime( RECVINFO( m_flInvisChangeCompleteTime ) ),
+ RecvPropInt( RECVINFO( m_nDisguiseTeam ) ),
+ RecvPropInt( RECVINFO( m_nDisguiseClass ) ),
+ RecvPropInt( RECVINFO( m_nDisguiseSkinOverride ) ),
+ RecvPropInt( RECVINFO( m_nMaskClass ) ),
+ RecvPropInt( RECVINFO( m_iDisguiseTargetIndex ) ),
+ RecvPropInt( RECVINFO( m_iDisguiseHealth ) ),
+ RecvPropBool( RECVINFO( m_bFeignDeathReady ) ),
+ RecvPropEHandle( RECVINFO( m_hDisguiseWeapon ) ),
+ RecvPropInt( RECVINFO( m_nTeamTeleporterUsed ) ),
+ RecvPropFloat( RECVINFO( m_flCloakMeter ) ),
+ RecvPropFloat( RECVINFO( m_flSpyTranqBuffDuration ) ),
+
+ // Local Data.
+ RecvPropDataTable( "tfsharedlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFPlayerSharedLocal) ),
+ RecvPropDataTable( RECVINFO_DT(m_ConditionList),0, &REFERENCE_RECV_TABLE(DT_TFPlayerConditionListExclusive) ),
+
+ RecvPropInt( RECVINFO( m_iTauntIndex ) ),
+ RecvPropInt( RECVINFO( m_iTauntConcept ) ),
+
+ RecvPropInt( RECVINFO( m_nPlayerCondEx ) ),
+ RecvPropInt( RECVINFO( m_iStunIndex ) ),
+
+ RecvPropInt( RECVINFO( m_nHalloweenBombHeadStage ) ),
+
+ RecvPropInt( RECVINFO( m_nPlayerCondEx2 ) ),
+ RecvPropInt( RECVINFO( m_nPlayerCondEx3 ) ),
+ RecvPropArray3( RECVINFO_ARRAY( m_nStreaks ), RecvPropInt( RECVINFO( m_nStreaks[0] ) ) ),
+ RecvPropInt( RECVINFO( m_unTauntSourceItemID_Low ) ),
+ RecvPropInt( RECVINFO( m_unTauntSourceItemID_High ) ),
+ RecvPropFloat( RECVINFO( m_flRuneCharge ) ),
+#ifdef STAGING_ONLY
+ RecvPropFloat( RECVINFO( m_flSpaceJumpCharge ) ),
+#endif
+ RecvPropBool( RECVINFO( m_bHasPasstimeBall ) ),
+ RecvPropBool( RECVINFO( m_bIsTargetedForPasstimePass ) ),
+ RecvPropEHandle( RECVINFO( m_hPasstimePassTarget ) ),
+ RecvPropFloat( RECVINFO( m_askForBallTime ) ),
+ RecvPropBool( RECVINFO( m_bKingRuneBuffActive ) ),
+
+ RecvPropUtlVectorDataTable( m_ConditionData, TF_COND_LAST, DT_TFPlayerConditionSource ),
+END_RECV_TABLE()
+
+BEGIN_PREDICTION_DATA_NO_BASE( CTFPlayerShared )
+ DEFINE_PRED_FIELD( m_nPlayerState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nPlayerCond, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flCloakMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flRageMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bRageDraining, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flNextRageEarnTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flEnergyDrinkMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flHypeMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flChargeMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bJumping, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_iAirDash, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nAirDucked, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flDuckTimer, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_flInvisChangeCompleteTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nDisguiseTeam, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nDisguiseClass, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nDisguiseSkinOverride, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nMaskClass, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nDesiredDisguiseTeam, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nDesiredDisguiseClass, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bLastDisguisedAsOwnTeam, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bFeignDeathReady, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nPlayerCondEx, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nPlayerCondEx2, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_nPlayerCondEx3, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+// DEFINE_PRED_FIELD( m_hDisguiseWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
+ DEFINE_FIELD( m_flDisguiseCompleteTime, FIELD_FLOAT ),
+ DEFINE_PRED_FIELD( m_bHasPasstimeBall, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bIsTargetedForPasstimePass, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), // does this belong here?
+ DEFINE_PRED_FIELD( m_askForBallTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
+END_PREDICTION_DATA()
+
+// Server specific.
+#else
+
+BEGIN_SEND_TABLE_NOBASE( localplayerscoring_t, DT_TFPlayerScoringDataExclusive )
+ SendPropInt( SENDINFO( m_iCaptures ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDefenses ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iKills ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDeaths ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iSuicides ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDominations ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iRevenge ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iBuildingsBuilt ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iBuildingsDestroyed ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iHeadshots ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iBackstabs ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iHealPoints ), 20, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iInvulns ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iTeleports ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDamageDone ), 20, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iCrits ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iResupplyPoints ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iKillAssists ), 12, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iBonusPoints ), 10, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iPoints ), 10, SPROP_UNSIGNED ),
+END_SEND_TABLE()
+
+EXTERN_SEND_TABLE(DT_TFPlayerConditionListExclusive);
+BEGIN_SEND_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerSharedLocal )
+ SendPropInt( SENDINFO( m_nDesiredDisguiseTeam ), 3, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nDesiredDisguiseClass ), 4, SPROP_UNSIGNED ),
+ SendPropBool( SENDINFO( m_bLastDisguisedAsOwnTeam ) ),
+ SendPropTime( SENDINFO( m_flStealthNoAttackExpire ) ),
+ SendPropTime( SENDINFO( m_flStealthNextChangeTime ) ),
+ SendPropFloat( SENDINFO( m_flRageMeter ), 0, SPROP_NOSCALE, 0.0, 100.0 ),
+ SendPropBool( SENDINFO( m_bRageDraining ) ),
+ SendPropTime( SENDINFO( m_flNextRageEarnTime ) ),
+ SendPropBool( SENDINFO( m_bInUpgradeZone ) ),
+ SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ),
+ SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ),
+ SendPropDataTable( SENDINFO_DT(m_ScoreData), &REFERENCE_SEND_TABLE(DT_TFPlayerScoringDataExclusive) ),
+ SendPropDataTable( SENDINFO_DT(m_RoundScoreData), &REFERENCE_SEND_TABLE(DT_TFPlayerScoringDataExclusive) ),
+END_SEND_TABLE()
+
+BEGIN_SEND_TABLE_NOBASE( condition_source_t, DT_TFPlayerConditionSource )
+ //SendPropInt( SENDINFO( m_nPreventedDamageFromCondition ) ),
+ //SendPropFloat( SENDINFO( m_flExpireTime ) ),
+ SendPropEHandle( SENDINFO( m_pProvider ) ),
+ //SendPropBool( SENDINFO( m_bPrevActive ) ),
+END_SEND_TABLE()
+
+BEGIN_SEND_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerShared )
+ SendPropInt( SENDINFO( m_nPlayerCond ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bJumping ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nNumHealers ), 5, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iCritMult ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iAirDash ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nAirDucked ), 2, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flDuckTimer ) ),
+ SendPropInt( SENDINFO( m_nPlayerState ), Q_log2( TF_STATE_COUNT )+1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDesiredPlayerClass ), Q_log2( TF_CLASS_COUNT_ALL )+1, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flMovementStunTime ) ),
+ SendPropInt( SENDINFO( m_iMovementStunAmount ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iMovementStunParity ), MOVEMENTSTUN_PARITY_BITS, SPROP_UNSIGNED ),
+ SendPropEHandle( SENDINFO( m_hStunner ) ),
+ SendPropInt( SENDINFO( m_iStunFlags ), 12, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nArenaNumChanges ), 5, SPROP_UNSIGNED ),
+ SendPropBool( SENDINFO( m_bArenaFirstBloodBoost ) ),
+ SendPropInt( SENDINFO( m_iWeaponKnockbackID ) ),
+ SendPropBool( SENDINFO( m_bLoadoutUnavailable ) ),
+ SendPropInt( SENDINFO( m_iItemFindBonus ) ),
+ SendPropBool( SENDINFO( m_bShieldEquipped ) ),
+ SendPropBool( SENDINFO( m_bParachuteEquipped ) ),
+ SendPropInt( SENDINFO( m_iNextMeleeCrit ) ),
+ SendPropInt( SENDINFO( m_iDecapitations ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iRevengeCrits ), 7, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDisguiseBody ) ),
+ SendPropEHandle( SENDINFO( m_hCarriedObject ) ),
+ SendPropBool( SENDINFO( m_bCarryingObject ) ),
+ SendPropFloat( SENDINFO( m_flNextNoiseMakerTime ) ),
+ SendPropInt( SENDINFO( m_iSpawnRoomTouchCount ) ),
+ SendPropInt( SENDINFO( m_iKillCountSinceLastDeploy ) ),
+ SendPropFloat( SENDINFO( m_flFirstPrimaryAttack ) ),
+
+ //Scout
+ SendPropFloat( SENDINFO( m_flEnergyDrinkMeter ), 0, SPROP_NOSCALE, 0.0, 100.0 ),
+ SendPropFloat( SENDINFO( m_flHypeMeter ), 0, SPROP_NOSCALE, 0.0, 100.0 ),
+
+ // Demoman
+ SendPropFloat( SENDINFO( m_flChargeMeter ), 8, SPROP_NOSCALE, 0.0, 100.0 ),
+
+ // Spy
+ SendPropTime( SENDINFO( m_flInvisChangeCompleteTime ) ),
+ SendPropInt( SENDINFO( m_nDisguiseTeam ), 3, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nDisguiseClass ), 4, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nDisguiseSkinOverride ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nMaskClass ), 4, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDisguiseTargetIndex ), 7, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iDisguiseHealth ), -1, SPROP_VARINT ),
+ SendPropBool( SENDINFO( m_bFeignDeathReady ) ),
+ SendPropEHandle( SENDINFO( m_hDisguiseWeapon ) ),
+ SendPropInt( SENDINFO( m_nTeamTeleporterUsed ), 3, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flCloakMeter ), 16, SPROP_NOSCALE, 0.0, 100.0 ),
+ SendPropFloat( SENDINFO( m_flSpyTranqBuffDuration ), 16, SPROP_NOSCALE, 0.0, 100.0 ),
+
+ // Local Data.
+ SendPropDataTable( "tfsharedlocaldata", 0, &REFERENCE_SEND_TABLE( DT_TFPlayerSharedLocal ), SendProxy_SendLocalDataTable ),
+ SendPropDataTable( SENDINFO_DT(m_ConditionList), &REFERENCE_SEND_TABLE(DT_TFPlayerConditionListExclusive) ),
+
+ SendPropInt( SENDINFO( m_iTauntIndex ), 8, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iTauntConcept ), 8, SPROP_UNSIGNED ),
+
+ SendPropInt( SENDINFO( m_nPlayerCondEx ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_iStunIndex ), 8 ),
+
+ SendPropInt( SENDINFO( m_nHalloweenBombHeadStage ), 2, SPROP_UNSIGNED ),
+
+ SendPropInt( SENDINFO( m_nPlayerCondEx2 ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nPlayerCondEx3 ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
+
+ SendPropArray3( SENDINFO_ARRAY3( m_nStreaks ), SendPropInt( SENDINFO_ARRAY( m_nStreaks ) ) ),
+ SendPropInt( SENDINFO( m_unTauntSourceItemID_Low ), -1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_unTauntSourceItemID_High ), -1, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flRuneCharge ), 8, 0, 0.0, 100.0 ),
+#ifdef STAGING_ONLY
+ SendPropFloat( SENDINFO( m_flSpaceJumpCharge ), 8, 0, 0.0, 100.0 ),
+#endif
+ SendPropBool( SENDINFO( m_bHasPasstimeBall ) ),
+ SendPropBool( SENDINFO( m_bIsTargetedForPasstimePass ) ),
+ SendPropEHandle( SENDINFO( m_hPasstimePassTarget ) ),
+ SendPropFloat( SENDINFO( m_askForBallTime ) ),
+ SendPropBool( SENDINFO( m_bKingRuneBuffActive ) ),
+
+ SendPropUtlVectorDataTable( m_ConditionData, TF_COND_LAST, DT_TFPlayerConditionSource ),
+END_SEND_TABLE()
+
+#endif
+
+extern void HandleRageGain( CTFPlayer *pPlayer, unsigned int iRequiredBuffFlags, float flDamage, float fInverseRageGainScale );
+
+CTFWearableDemoShield* GetEquippedDemoShield( CTFPlayer * pPlayer )
+{
+ // Loop through our wearables in search of a shield
+ for ( int i=0; i<pPlayer->GetNumWearables(); ++i )
+ {
+ CTFWearableDemoShield *pWearableShield = dynamic_cast<CTFWearableDemoShield*>( pPlayer->GetWearable( i ) );
+ if ( pWearableShield )
+ {
+ return pWearableShield;
+ }
+ }
+
+ return NULL;
+}
+
+CTFPlayer *GetRuneCarrier( RuneTypes_t type, int iTeam = TEAM_ANY )
+{
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( !pPlayer )
+ continue;
+
+ if ( iTeam != TEAM_ANY && pPlayer->GetTeamNumber() != iTeam )
+ continue;
+
+ if ( pPlayer->m_Shared.GetCarryingRuneType() == type )
+ {
+ return pPlayer;
+ }
+ }
+
+ return NULL;
+}
+
+// --------------------------------------------------------------------------------------------------- //
+// Shared CTFPlayer implementation.
+// --------------------------------------------------------------------------------------------------- //
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::HasCampaignMedal( int iMedal )
+{
+ return ( ( m_iCampaignMedals & iMedal ) != 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::IsAllowedToTaunt( void )
+{
+ if ( !IsAlive() )
+ return false;
+
+ // Check to see if we can taunt again!
+ if ( m_Shared.InCond( TF_COND_TAUNTING ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ return false;
+
+ // Can't taunt while charging.
+ if ( m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_COMPETITIVE_LOSER ) )
+ return false;
+
+ if ( IsLerpingFOV() )
+ return false;
+
+ // Check for things that prevent taunting
+ if ( ShouldStopTaunting() )
+ return false;
+
+ // Check to see if we are on the ground.
+ if ( GetGroundEntity() == NULL && !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return false;
+
+ CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon();
+ if ( pActiveWeapon )
+ {
+ if ( !pActiveWeapon->OwnerCanTaunt() )
+ return false;
+
+ // ignore taunt key if one of these if active weapon
+ if ( pActiveWeapon->GetWeaponID() == TF_WEAPON_PDA_ENGINEER_BUILD
+ || pActiveWeapon->GetWeaponID() == TF_WEAPON_PDA_ENGINEER_DESTROY )
+ return false;
+ }
+
+ // can't taunt while carrying an object
+ if ( m_Shared.IsCarryingObject() )
+ return false;
+
+ // Can't taunt if hooked into a player
+ if ( m_Shared.InCond( TF_COND_GRAPPLED_TO_PLAYER ) )
+ return false;
+
+ if ( IsPlayerClass( TF_CLASS_SCOUT ) )
+ {
+ if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX )
+ {
+ //Scouts can't drink while they're already phasing.
+ if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) || m_Shared.InCond( TF_COND_PHASE ) )
+ return false;
+
+ // Or if their energy drink meter isn't refilled
+ if ( m_Shared.GetScoutEnergyDrinkMeter() < 100 )
+ return false;
+
+ //They can't drink the default (phase) item while carrying a flag
+ pActiveWeapon = m_Shared.GetActiveTFWeapon();
+ if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX )
+ {
+ CTFLunchBox *pLunchbox = (CTFLunchBox*)pActiveWeapon;
+
+ if ( ( pLunchbox->GetLunchboxType() == LUNCHBOX_STANDARD ) || ( pLunchbox->GetLunchboxType() == LUNCHBOX_STANDARD_ROBO ) )
+ {
+ if ( !TFGameRules()->IsMannVsMachineMode() && HasItem() )
+ return false;
+ }
+ }
+ }
+ }
+
+ if ( IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ if ( m_Shared.IsStealthed() || m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
+ m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+// --------------------------------------------------------------------------------------------------- //
+// CTFPlayerShared implementation.
+// --------------------------------------------------------------------------------------------------- //
+
+CTFPlayerShared::CTFPlayerShared()
+{
+ // If you hit this assert, CONGRATULATIONS! You've added a condition that has gone
+ // beyond the amount of bits we network for conditions. Take a look at the pattern
+ // of m_nPlayerCond, m_nPlayerCondEx, m_nPlayerCondEx2, and m_nPlayerCondEx3 to get more bits.
+ // This pattern is as such to preserve replays.
+ // Don't forget to add an m_nOldCond* and m_nForceCond*
+ COMPILE_TIME_ASSERT( TF_COND_LAST < (32 + 32 + 32 + 32) );
+
+ m_nPlayerState.Set( TF_STATE_WELCOME );
+ m_bJumping = false;
+ m_iAirDash = 0;
+ m_nAirDucked = 0;
+ m_flDuckTimer = 0.0f;
+ m_flStealthNoAttackExpire = 0.0f;
+ m_flStealthNextChangeTime = 0.0f;
+ m_iCritMult = 0;
+ m_flInvisibility = 0.0f;
+ m_flPrevInvisibility = 0.f;
+ m_flTmpDamageBonusAmount = 1.0f;
+
+ m_bFeignDeathReady = false;
+
+ m_fCloakConsumeRate = tf_spy_cloak_consume_rate.GetFloat();
+ m_fCloakRegenRate = tf_spy_cloak_regen_rate.GetFloat();
+
+ m_fEnergyDrinkConsumeRate = tf_scout_energydrink_consume_rate.GetFloat();
+ m_fEnergyDrinkRegenRate = tf_scout_energydrink_regen_rate.GetFloat();
+
+ m_bMotionCloak = false;
+
+ m_hStunner = NULL;
+ m_iStunFlags = 0;
+
+ m_hAssist = NULL;
+
+ m_bLastDisguisedAsOwnTeam = false;
+
+ m_bRageDraining = false;
+ m_bInUpgradeZone = false;
+ m_bPhaseFXOn = false;
+ ResetRageBuffs();
+
+ m_iPhaseDamage = 0;
+
+ Q_memset(m_pPhaseTrail, 0, sizeof(m_pPhaseTrail));
+
+ m_iWeaponKnockbackID = -1;
+
+ m_bLoadoutUnavailable = false;
+
+ m_nMaskClass = 0;
+
+ m_iItemFindBonus = 0;
+
+ m_nTeamTeleporterUsed = TEAM_UNASSIGNED;
+
+ m_bShieldEquipped = false;
+ m_bPostShieldCharge = false;
+ m_iNextMeleeCrit = 0;
+
+ m_bParachuteEquipped = false;
+
+ m_iDecapitations = m_iOldDecapitations = 0;
+ m_iOldKillStreak = 0;
+ m_iOldKillStreakWepSlot = 0;
+
+ m_flNextNoiseMakerTime = 0;
+ m_iSpawnRoomTouchCount = 0;
+
+ m_iKillCountSinceLastDeploy = 0;
+ m_flFirstPrimaryAttack = 0.0f;
+
+#ifdef GAME_DLL
+ m_flBestOverhealDecayMult = -1;
+ m_hPeeAttacker = NULL;
+
+ m_flHealedPerSecondTimer = -1000;
+ m_bPulseRadiusHeal = false;
+
+ m_flRadiusCurrencyCollectionTime = 0;
+ m_flRadiusSpyScanTime = 0;
+
+ m_flCloakStartTime = -1.0f;
+
+ ListenForGameEvent( "player_disconnect" );
+#else
+ m_pWheelEffect = NULL;
+ m_angVehicleMoveAngles = QAngle( 0.f, 0.f, 0.f );
+ m_angVehicleMovePitchLast = 0.0f;
+ // Save Prediction value
+ m_bPreKartPredictionState = cl_predict->GetBool();
+ m_hKartParachuteEntity = NULL;
+#endif
+
+ m_nForceConditions = 0;
+ m_nForceConditionsEx = 0;
+ m_nForceConditionsEx2 = 0;
+ m_nForceConditionsEx3 = 0;
+
+ m_flChargeEndTime = -1000;
+ m_flLastChargeTime = -1000;
+ m_flLastNoChargeTime = 0;
+ m_bChargeGlowing = false;
+
+ m_bChargeOffSounded = false;
+
+ m_bBiteEffectWasApplied = false;
+
+ m_flLastMovementStunChange = 0;
+ m_bStunNeedsFadeOut = false;
+
+ m_flChargeMeter = 100;
+ m_flEnergyDrinkMeter = 0;
+ m_flHypeMeter = 0;
+
+ m_bCarryingObject = false;
+ m_hCarriedObject = NULL;
+
+ m_iStunIndex = -1;
+ m_flLastNoMovementTime = -1.f;
+ m_flRuneCharge = 0.f;
+#ifdef STAGING_ONLY
+ m_flSpaceJumpCharge = 100.0f;
+ m_flSpyTranqBuffDuration = 0.0f;
+#endif
+ m_iPasstimeThrowAnimState = PASSTIME_THROW_ANIM_NONE;
+ m_bHasPasstimeBall = false;
+ m_bIsTargetedForPasstimePass = false;
+ m_askForBallTime = 0.0f;
+
+ // make sure we have all conditions in the list
+ m_ConditionData.EnsureCount( TF_COND_LAST );
+}
+
+void CTFPlayerShared::Init( CTFPlayer *pPlayer )
+{
+ m_pOuter = pPlayer;
+
+ m_flNextBurningSound = 0;
+
+ m_bArenaFirstBloodBoost = false;
+ m_iStunAnimState = STUN_ANIM_NONE;
+ m_iPhaseDamage = 0;
+ m_iWeaponKnockbackID = -1;
+ m_hStunner = NULL;
+
+ m_iPasstimeThrowAnimState = PASSTIME_THROW_ANIM_NONE;
+ m_bHasPasstimeBall = false;
+ m_bIsTargetedForPasstimePass = false;
+ m_askForBallTime = 0.0f;
+
+ m_bMotionCloak = false;
+
+ m_bShieldEquipped = false;
+ m_bPostShieldCharge = false;
+ m_iNextMeleeCrit = 0;
+
+ m_bParachuteEquipped = false;
+
+ m_iDecapitations = m_iOldDecapitations = 0;
+ m_iOldKillStreak = 0;
+ m_iOldKillStreakWepSlot = 0;
+
+ SetJumping( false );
+ SetAssist( NULL );
+
+ m_flInvulnerabilityRemoveTime = -1;
+
+ SetNextMeleeCrit( MELEE_NOCRIT );
+
+ Spawn();
+}
+
+void CTFPlayerShared::ResetRageBuffs( void )
+{
+ for ( int i = 0; i < kBuffSlot_MAX; i++ )
+ {
+ m_RageBuffSlots[i].m_iBuffTypeActive = 0;
+ m_RageBuffSlots[i].m_iBuffPulseCount = 0;
+ m_RageBuffSlots[i].m_flNextBuffPulseTime = 0.0f;
+ }
+}
+
+void CTFPlayerShared::Spawn( void )
+{
+#ifdef GAME_DLL
+ m_hPeeAttacker = NULL;
+
+ if ( m_bCarryingObject )
+ {
+ CBaseObject* pObj = GetCarriedObject();
+ if ( pObj )
+ {
+ pObj->DetonateObject();
+ }
+ }
+
+ m_bCarryingObject = false;
+ m_hCarriedObject = NULL;
+
+ m_flRadiusHealCheckTime = 0;
+ m_flKingRuneBuffCheckTime = 0.f;
+
+ m_bBiteEffectWasApplied = false;
+
+ m_flNextRocketPackTime = 0.f;
+ m_iSpawnRoomTouchCount = 0;
+
+ SetRevengeCrits( 0 );
+
+ m_PlayerStuns.RemoveAll();
+ m_iStunIndex = -1;
+
+ m_iPasstimeThrowAnimState = PASSTIME_THROW_ANIM_NONE;
+ m_bHasPasstimeBall = false;
+ m_bIsTargetedForPasstimePass = false;
+ m_askForBallTime = 0.0f;
+#else
+ m_bSyncingConditions = false;
+#endif
+ m_bKingRuneBuffActive = false;
+
+ // Reset our assist here incase something happens before we get killed
+ // again that checks this (getting slapped with a fish)
+ SetAssist( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+template < typename tIntType >
+class CConditionVars
+{
+public:
+ CConditionVars( tIntType& nPlayerCond, tIntType& nPlayerCondEx, tIntType& nPlayerCondEx2, tIntType& nPlayerCondEx3, ETFCond eCond )
+ {
+ if ( eCond >= 96 )
+ {
+ Assert( eCond < 96 + 32 );
+ m_pnCondVar = &nPlayerCondEx3;
+ m_nCondBit = eCond - 96;
+ }
+ else if( eCond >= 64 )
+ {
+ Assert( eCond < (64 + 32) );
+ m_pnCondVar = &nPlayerCondEx2;
+ m_nCondBit = eCond - 64;
+ }
+ else if ( eCond >= 32 )
+ {
+ Assert( eCond < (32 + 32) );
+ m_pnCondVar = &nPlayerCondEx;
+ m_nCondBit = eCond - 32;
+ }
+ else
+ {
+ m_pnCondVar = &nPlayerCond;
+ m_nCondBit = eCond;
+ }
+ }
+
+ tIntType& CondVar() const
+ {
+ return *m_pnCondVar;
+ }
+
+ int CondBit() const
+ {
+ return 1 << m_nCondBit;
+ }
+
+private:
+ tIntType *m_pnCondVar;
+ int m_nCondBit;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a condition and duration
+// duration of PERMANENT_CONDITION means infinite duration
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::AddCond( ETFCond eCond, float flDuration /* = PERMANENT_CONDITION */, CBaseEntity *pProvider /*= NULL */)
+{
+ Assert( eCond >= 0 && eCond < TF_COND_LAST );
+ Assert( eCond < m_ConditionData.Count() );
+
+ // If we're dead, don't take on any new conditions
+ if( !m_pOuter || !m_pOuter->IsAlive() )
+ {
+ return;
+ }
+
+#ifdef CLEINT_DLL
+ if ( m_pOuter->IsDormant() )
+ {
+ return;
+ }
+#endif
+
+ // sanity check to prevent servers from adding these conditions when they shouldn't
+ if ( ( eCond == TF_COND_COMPETITIVE_WINNER ) || ( eCond == TF_COND_COMPETITIVE_LOSER ) )
+ {
+ if ( TFGameRules() && !TFGameRules()->ShowMatchSummary() )
+ return;
+ }
+
+ // Which bitfield are we tracking this condition variable in? Which bit within
+ // that variable will we track it as?
+ CConditionVars<int> cPlayerCond( m_nPlayerCond.m_Value, m_nPlayerCondEx.m_Value, m_nPlayerCondEx2.m_Value, m_nPlayerCondEx3.m_Value, eCond );
+
+ // See if there is an object representation of the condition.
+ bool bAddedToExternalConditionList = m_ConditionList.Add( eCond, flDuration, m_pOuter, pProvider );
+ if ( !bAddedToExternalConditionList )
+ {
+ // Set the condition bit for this condition.
+ cPlayerCond.CondVar() |= cPlayerCond.CondBit();
+
+ // Flag for gamecode to query
+ m_ConditionData[eCond].m_bPrevActive = ( m_ConditionData[eCond].m_flExpireTime != 0.f ) ? true : false;
+
+ if ( flDuration != PERMANENT_CONDITION )
+ {
+ // if our current condition is permanent or we're trying to set a new
+ // time that's less our current time remaining, use our current time instead
+ if ( ( m_ConditionData[eCond].m_flExpireTime == PERMANENT_CONDITION ) ||
+ ( flDuration < m_ConditionData[eCond].m_flExpireTime ) )
+ {
+ flDuration = m_ConditionData[eCond].m_flExpireTime;
+ }
+ }
+
+ m_ConditionData[eCond].m_flExpireTime = flDuration;
+ m_ConditionData[eCond].m_pProvider = pProvider;
+ m_ConditionData[ eCond ].m_nPreventedDamageFromCondition = 0;
+
+ OnConditionAdded( eCond );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Forcibly remove a condition
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RemoveCond( ETFCond eCond, bool ignore_duration )
+{
+ Assert( eCond >= 0 && eCond < TF_COND_LAST );
+ Assert( eCond < m_ConditionData.Count() );
+
+ if ( !InCond( eCond ) )
+ return;
+
+ CConditionVars<int> cPlayerCond( m_nPlayerCond.m_Value, m_nPlayerCondEx.m_Value, m_nPlayerCondEx2.m_Value, m_nPlayerCondEx3.m_Value, eCond );
+
+ // If this variable is handled by the condition list, abort before doing the
+ // work for the condition flags.
+ if ( m_ConditionList.Remove( eCond, ignore_duration ) )
+ return;
+
+ cPlayerCond.CondVar() &= ~cPlayerCond.CondBit();
+ OnConditionRemoved( eCond );
+
+ if ( m_ConditionData[ eCond ].m_nPreventedDamageFromCondition )
+ {
+ IGameEvent *pEvent = gameeventmanager->CreateEvent( "damage_prevented" );
+ if ( pEvent )
+ {
+ pEvent->SetInt( "preventor", m_ConditionData[eCond].m_pProvider ? m_ConditionData[eCond].m_pProvider->entindex() : m_pOuter->entindex() );
+ pEvent->SetInt( "victim", m_pOuter->entindex() );
+ pEvent->SetInt( "amount", m_ConditionData[ eCond ].m_nPreventedDamageFromCondition );
+ pEvent->SetInt( "condition", eCond );
+
+ gameeventmanager->FireEvent( pEvent, true );
+ }
+
+ m_ConditionData[ eCond ].m_nPreventedDamageFromCondition = 0;
+ }
+
+ m_ConditionData[eCond].m_flExpireTime = 0;
+ m_ConditionData[eCond].m_pProvider = NULL;
+ m_ConditionData[eCond].m_bPrevActive = false;
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::InCond( ETFCond eCond ) const
+{
+ Assert( eCond >= 0 && eCond < TF_COND_LAST );
+
+ // Old condition system, only used for the first 32 conditions
+ if ( eCond < 32 && m_ConditionList.InCond( eCond ) )
+ return true;
+
+ CConditionVars<const int> cPlayerCond( m_nPlayerCond.m_Value, m_nPlayerCondEx.m_Value, m_nPlayerCondEx2.m_Value, m_nPlayerCondEx3.m_Value, eCond );
+ return (cPlayerCond.CondVar() & cPlayerCond.CondBit()) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return whether or not we were in this condition before.
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::WasInCond( ETFCond eCond ) const
+{
+ // I don't know if this actually works for conditions < 32, because we definitely cannot peak into m_ConditionList (back in time).
+ // But others think that m_ConditionList is propogated into m_nOldConditions, so just check if you hit the assert. (And then remove the
+ // assert. And this comment).
+ Assert( eCond >= 32 && eCond < TF_COND_LAST );
+
+ CConditionVars<const int> cPlayerCond( m_nOldConditions, m_nOldConditionsEx, m_nOldConditionsEx2, m_nOldConditionsEx3, eCond );
+ return (cPlayerCond.CondVar() & cPlayerCond.CondBit()) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set a bit to force this condition off and then back on next time we sync bits from the server.
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ForceRecondNextSync( ETFCond eCond )
+{
+ // I don't know if this actually works for conditions < 32. We may need to set this bit in m_ConditionList, too.
+ // Please check if you hit the assert. (And then remove the assert. And this comment).
+ Assert(eCond >= 32 && eCond < TF_COND_LAST);
+
+ CConditionVars<int> playerCond( m_nForceConditions, m_nForceConditionsEx, m_nForceConditionsEx2, m_nForceConditionsEx3, eCond );
+ playerCond.CondVar() |= playerCond.CondBit();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::GetConditionDuration( ETFCond eCond ) const
+{
+ Assert( eCond >= 0 && eCond < TF_COND_LAST );
+ Assert( eCond < m_ConditionData.Count() );
+
+ if ( InCond( eCond ) )
+ {
+ return m_ConditionData[eCond].m_flExpireTime;
+ }
+
+ return 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the entity that provided the passed in condition
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFPlayerShared::GetConditionProvider( ETFCond eCond ) const
+{
+ Assert( eCond >= 0 && eCond < TF_COND_LAST );
+ Assert( eCond < m_ConditionData.Count() );
+
+ CBaseEntity *pProvider = NULL;
+ if ( InCond( eCond ) )
+ {
+ if ( eCond == TF_COND_CRITBOOSTED )
+ {
+ pProvider = m_ConditionList.GetProvider( eCond );
+ }
+ else
+ {
+ pProvider = m_ConditionData[eCond].m_pProvider;
+ }
+ }
+
+ return pProvider;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the entity that applied this condition to us - for granting an assist when we die
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFPlayerShared::GetConditionAssistFromVictim( void )
+{
+ // We only give an assist to one person. That means this list is order
+ // sensitive, so consider how "powerful" an effect is when adding it here.
+ static const ETFCond nTrackedConditions[] =
+ {
+ TF_COND_URINE,
+ TF_COND_MAD_MILK,
+ TF_COND_MARKEDFORDEATH,
+ };
+
+ CBaseEntity *pProvider = NULL;
+ for ( int i = 0; i < ARRAYSIZE( nTrackedConditions ); i++ )
+ {
+ if ( InCond( nTrackedConditions[i] ) )
+ {
+ pProvider = GetConditionProvider( nTrackedConditions[i] );
+ break;
+ }
+ }
+
+ return pProvider;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the entity that applied this condition to us - for granting an assist when we kill someone
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFPlayerShared::GetConditionAssistFromAttacker( void )
+{
+ // We only give an assist to one person. That means this list is order
+ // sensitive, so consider how "powerful" an effect is when adding it here.
+ static const ETFCond nTrackedConditions[] =
+ {
+ TF_COND_OFFENSEBUFF, // Highest priority
+ TF_COND_DEFENSEBUFF,
+ TF_COND_REGENONDAMAGEBUFF,
+ TF_COND_NOHEALINGDAMAGEBUFF, // Lowest priority
+ };
+
+ CBaseEntity *pProvider = NULL;
+ for ( int i = 0; i < ARRAYSIZE( nTrackedConditions ); i++ )
+ {
+ if ( InCond( nTrackedConditions[i] ) )
+ {
+ CBaseEntity* pPotentialProvider = GetConditionProvider( nTrackedConditions[i] );
+ // Check to make sure we're not providing the condition to ourselves
+ if( pPotentialProvider != m_pOuter )
+ {
+ pProvider = pPotentialProvider;
+ break;
+ }
+ }
+ }
+
+ return pProvider;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::DebugPrintConditions( void )
+{
+#ifndef CLIENT_DLL
+ const char *szDll = "Server";
+#else
+ const char *szDll = "Client";
+#endif
+
+ Msg( "( %s ) Conditions for player ( %d )\n", szDll, m_pOuter->entindex() );
+
+ int i;
+ int iNumFound = 0;
+ for ( i=0;i<TF_COND_LAST;i++ )
+ {
+ if ( InCond( (ETFCond)i ) )
+ {
+ if ( m_ConditionData[i].m_flExpireTime == PERMANENT_CONDITION )
+ {
+ Msg( "( %s ) Condition %d - ( permanent cond )\n", szDll, i );
+ }
+ else
+ {
+ Msg( "( %s ) Condition %d - ( %.1f left )\n", szDll, i, m_ConditionData[i].m_flExpireTime );
+ }
+
+ iNumFound++;
+ }
+ }
+
+ if ( iNumFound == 0 )
+ {
+ Msg( "( %s ) No active conditions\n", szDll );
+ }
+}
+
+void CTFPlayerShared::InstantlySniperUnzoom( void )
+{
+ // Unzoom if we are a sniper zoomed!
+ if ( m_pOuter->GetPlayerClass()->GetClassIndex() == TF_CLASS_SNIPER )
+ {
+ CTFWeaponBase *pWpn = m_pOuter->GetActiveTFWeapon();
+
+ if ( pWpn && WeaponID_IsSniperRifle( pWpn->GetWeaponID() ) )
+ {
+ CTFSniperRifle *pRifle = static_cast<CTFSniperRifle*>( pWpn );
+ if ( pRifle->IsZoomed() )
+ {
+ // Let the rifle clean up conditions and state
+ pRifle->ToggleZoom();
+ // Slam the FOV right now
+ m_pOuter->SetFOV( m_pOuter, 0, 0.0f );
+ }
+ }
+ }
+}
+
+#ifdef CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnPreDataChanged( void )
+{
+ m_ConditionList.OnPreDataChanged();
+
+ m_nOldConditions = m_nPlayerCond;
+ m_nOldConditionsEx = m_nPlayerCondEx;
+ m_nOldConditionsEx2 = m_nPlayerCondEx2;
+ m_nOldConditionsEx3 = m_nPlayerCondEx3;
+ m_nOldDisguiseClass = GetDisguiseClass();
+ m_nOldDisguiseTeam = GetDisguiseTeam();
+ m_iOldMovementStunParity = m_iMovementStunParity;
+
+ InvisibilityThink();
+ ConditionThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnDataChanged( void )
+{
+ m_ConditionList.OnDataChanged( m_pOuter );
+
+ if ( m_iOldMovementStunParity != m_iMovementStunParity )
+ {
+ m_flStunFade = gpGlobals->curtime + m_flMovementStunTime;
+ m_flStunEnd = m_flStunFade;
+ if ( IsControlStunned() && (m_iStunAnimState == STUN_ANIM_NONE) )
+ {
+ m_flStunEnd += CONTROL_STUN_ANIM_TIME;
+ }
+
+ UpdateLegacyStunSystem();
+ }
+
+ // Update conditions from last network change
+ SyncConditions( m_nOldConditions, m_nPlayerCond, m_nForceConditions, 0 );
+ SyncConditions( m_nOldConditionsEx, m_nPlayerCondEx, m_nForceConditionsEx, 32 );
+ SyncConditions( m_nOldConditionsEx2, m_nPlayerCondEx2, m_nForceConditionsEx2, 64 );
+ SyncConditions( m_nOldConditionsEx3, m_nPlayerCondEx3, m_nForceConditionsEx3, 96 );
+
+ // Make sure these items are present
+ m_nPlayerCond |= m_nForceConditions;
+ m_nPlayerCondEx |= m_nForceConditionsEx;
+ m_nPlayerCondEx2 |= m_nForceConditionsEx2;
+ m_nPlayerCondEx3 |= m_nForceConditionsEx3;
+
+ // Clear our force bits now that we've used them.
+ m_nForceConditions = 0;
+ m_nForceConditionsEx = 0;
+ m_nForceConditionsEx2 = 0;
+ m_nForceConditionsEx3 = 0;
+
+ if ( m_nOldDisguiseClass != GetDisguiseClass() || m_nOldDisguiseTeam != GetDisguiseTeam() )
+ {
+ OnDisguiseChanged();
+ }
+
+ if ( m_hDisguiseWeapon )
+ {
+ m_hDisguiseWeapon->UpdateVisibility();
+ m_hDisguiseWeapon->UpdateParticleSystems();
+ }
+
+ if ( ( IsLoser() || InCond( TF_COND_COMPETITIVE_LOSER ) ) && GetActiveTFWeapon() && !GetActiveTFWeapon()->IsEffectActive( EF_NODRAW ) )
+ {
+ GetActiveTFWeapon()->SetWeaponVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: check the newly networked conditions for changes
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SyncConditions( int nPreviousConditions, int nNewConditions, int nForceConditions, int nBaseCondBit )
+{
+ if ( nPreviousConditions == nNewConditions )
+ return;
+
+ int nCondChanged = nNewConditions ^ nPreviousConditions;
+ int nCondAdded = nCondChanged & nNewConditions;
+ int nCondRemoved = nCondChanged & nPreviousConditions;
+ m_bSyncingConditions = true;
+
+ for ( int i=0;i<32;i++ )
+ {
+ const int testBit = 1<<i;
+ if ( nForceConditions & testBit )
+ {
+ if ( nPreviousConditions & testBit )
+ {
+ OnConditionRemoved((ETFCond)(nBaseCondBit + i));
+ }
+ OnConditionAdded((ETFCond)(nBaseCondBit + i));
+ }
+ else
+ {
+ if ( nCondAdded & testBit )
+ {
+ OnConditionAdded( (ETFCond)(nBaseCondBit + i) );
+ }
+ else if ( nCondRemoved & testBit )
+ {
+ OnConditionRemoved( (ETFCond)(nBaseCondBit + i) );
+ }
+ }
+ }
+ m_bSyncingConditions = false;
+}
+
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove any conditions affecting players
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RemoveAllCond()
+{
+ m_ConditionList.RemoveAll();
+
+ int i;
+ for ( i=0;i<TF_COND_LAST;i++ )
+ {
+ if ( InCond( (ETFCond)i ) )
+ {
+ RemoveCond( (ETFCond)i );
+ }
+ }
+
+ // Now remove all the rest
+ m_nPlayerCond = 0;
+ m_nPlayerCondEx = 0;
+ m_nPlayerCondEx2 = 0;
+ m_nPlayerCondEx3 = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called on both client and server. Server when we add the bit,
+// and client when it receives the new cond bits and finds one added
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnConditionAdded( ETFCond eCond )
+{
+ switch( eCond )
+ {
+ case TF_COND_ZOOMED:
+ OnAddZoomed();
+ break;
+ case TF_COND_HEALTH_BUFF:
+#ifdef GAME_DLL
+ m_flHealFraction = 0;
+ m_flDisguiseHealFraction = 0;
+
+ m_flHealedPerSecondTimer = gpGlobals->curtime + 1.0f;
+#endif
+ break;
+
+ case TF_COND_HEALTH_OVERHEALED:
+ OnAddOverhealed();
+ break;
+
+ case TF_COND_FEIGN_DEATH:
+ OnAddFeignDeath();
+ break;
+
+ case TF_COND_STEALTHED:
+ case TF_COND_STEALTHED_USER_BUFF:
+ OnAddStealthed();
+ break;
+
+ case TF_COND_INVULNERABLE:
+ case TF_COND_INVULNERABLE_USER_BUFF:
+ case TF_COND_INVULNERABLE_CARD_EFFECT:
+ OnAddInvulnerable();
+ break;
+
+ case TF_COND_TELEPORTED:
+ OnAddTeleported();
+ break;
+
+ case TF_COND_BURNING:
+ OnAddBurning();
+ break;
+
+ case TF_COND_CRITBOOSTED:
+ Assert( !"TF_COND_CRITBOOSTED should be handled by the condition list!" );
+ break;
+
+ case TF_COND_CRITBOOSTED_DEMO_CHARGE:
+ OnAddDemoCharge();
+ break;
+
+ // First blood falls through on purpose.
+ case TF_COND_CRITBOOSTED_FIRST_BLOOD:
+ SetFirstBloodBoosted( true );
+ case TF_COND_CRITBOOSTED_PUMPKIN:
+ case TF_COND_CRITBOOSTED_USER_BUFF:
+ case TF_COND_CRITBOOSTED_BONUS_TIME:
+ case TF_COND_CRITBOOSTED_CTF_CAPTURE:
+ case TF_COND_CRITBOOSTED_ON_KILL:
+ case TF_COND_CRITBOOSTED_RAGE_BUFF:
+ case TF_COND_SNIPERCHARGE_RAGE_BUFF:
+ case TF_COND_CRITBOOSTED_CARD_EFFECT:
+ case TF_COND_CRITBOOSTED_RUNE_TEMP:
+ OnAddCritBoost();
+ break;
+
+ case TF_COND_SODAPOPPER_HYPE:
+ OnAddSodaPopperHype();
+ break;
+
+ case TF_COND_DISGUISING:
+ OnAddDisguising();
+ break;
+
+ case TF_COND_DISGUISED:
+ OnAddDisguised();
+ break;
+
+ case TF_COND_URINE:
+ OnAddUrine();
+ break;
+
+ case TF_COND_MARKEDFORDEATH:
+ OnAddMarkedForDeath();
+ break;
+
+ case TF_COND_BLEEDING:
+ OnAddBleeding();
+ break;
+
+ case TF_COND_TAUNTING:
+ OnAddTaunting();
+ break;
+
+ case TF_COND_STUNNED:
+ OnAddStunned();
+ break;
+
+ case TF_COND_PHASE:
+ OnAddPhase();
+ break;
+
+ case TF_COND_OFFENSEBUFF:
+ OnAddOffenseBuff();
+ break;
+
+ case TF_COND_DEFENSEBUFF:
+ case TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK:
+ case TF_COND_DEFENSEBUFF_HIGH:
+ OnAddDefenseBuff();
+ break;
+
+ case TF_COND_REGENONDAMAGEBUFF:
+ OnAddOffenseHealthRegenBuff();
+ break;
+
+ case TF_COND_NOHEALINGDAMAGEBUFF:
+ OnAddNoHealingDamageBuff();
+ break;
+
+ case TF_COND_SHIELD_CHARGE:
+ OnAddShieldCharge();
+ break;
+
+ case TF_COND_DEMO_BUFF:
+ OnAddDemoBuff();
+ break;
+
+ case TF_COND_ENERGY_BUFF:
+ OnAddEnergyDrinkBuff();
+ break;
+
+ case TF_COND_RADIUSHEAL:
+ OnAddRadiusHeal();
+ break;
+
+ case TF_COND_MEGAHEAL:
+ OnAddMegaHeal();
+ break;
+
+ case TF_COND_MAD_MILK:
+ OnAddMadMilk();
+ break;
+
+ case TF_COND_SPEED_BOOST: OnAddSpeedBoost( false ); break;
+#ifdef STAGING_ONLY //STAGING_ENGY
+ case TF_COND_NO_COMBAT_SPEED_BOOST: OnAddSpeedBoost( true ); break;
+#endif
+
+ case TF_COND_SAPPED:
+ OnAddSapped();
+ break;
+
+ case TF_COND_REPROGRAMMED:
+ OnAddReprogrammed();
+ break;
+
+ case TF_COND_PASSTIME_PENALTY_DEBUFF:
+ case TF_COND_MARKEDFORDEATH_SILENT:
+ OnAddMarkedForDeathSilent();
+ break;
+
+ case TF_COND_DISGUISED_AS_DISPENSER:
+ OnAddDisguisedAsDispenser();
+ break;
+
+ case TF_COND_HALLOWEEN_BOMB_HEAD:
+ OnAddHalloweenBombHead();
+ break;
+
+ case TF_COND_HALLOWEEN_THRILLER:
+ OnAddHalloweenThriller();
+ break;
+
+ case TF_COND_RADIUSHEAL_ON_DAMAGE:
+ OnAddRadiusHealOnDamage();
+ break;
+
+ case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
+ OnAddMedEffectUberBulletResist();
+ break;
+
+ case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
+ OnAddMedEffectUberBlastResist();
+ break;
+
+ case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
+ OnAddMedEffectUberFireResist();
+ break;
+
+ case TF_COND_MEDIGUN_SMALL_BULLET_RESIST:
+ OnAddMedEffectSmallBulletResist();
+ break;
+
+ case TF_COND_MEDIGUN_SMALL_BLAST_RESIST:
+ OnAddMedEffectSmallBlastResist();
+ break;
+
+ case TF_COND_MEDIGUN_SMALL_FIRE_RESIST:
+ OnAddMedEffectSmallFireResist();
+ break;
+
+ case TF_COND_STEALTHED_USER_BUFF_FADING:
+ OnAddStealthedUserBuffFade();
+ break;
+
+ case TF_COND_BULLET_IMMUNE:
+ OnAddBulletImmune();
+ break;
+
+ case TF_COND_BLAST_IMMUNE:
+ OnAddBlastImmune();
+ break;
+
+ case TF_COND_FIRE_IMMUNE:
+ OnAddFireImmune();
+ break;
+
+ case TF_COND_MVM_BOT_STUN_RADIOWAVE:
+ OnAddMVMBotRadiowave();
+ break;
+
+ case TF_COND_HALLOWEEN_SPEED_BOOST:
+ OnAddHalloweenSpeedBoost();
+ break;
+
+ case TF_COND_HALLOWEEN_QUICK_HEAL:
+ OnAddHalloweenQuickHeal();
+ break;
+
+ case TF_COND_HALLOWEEN_GIANT:
+ OnAddHalloweenGiant();
+ break;
+
+ case TF_COND_HALLOWEEN_TINY:
+ OnAddHalloweenTiny();
+ break;
+
+ case TF_COND_HALLOWEEN_GHOST_MODE:
+ OnAddHalloweenGhostMode();
+ break;
+
+ case TF_COND_PARACHUTE_DEPLOYED:
+ OnAddCondParachute();
+ break;
+
+ case TF_COND_HALLOWEEN_KART_DASH:
+ OnAddHalloweenKartDash();
+ break;
+
+ case TF_COND_HALLOWEEN_KART:
+ OnAddHalloweenKart();
+ break;
+
+ case TF_COND_BALLOON_HEAD:
+ OnAddBalloonHead();
+ break;
+
+ case TF_COND_MELEE_ONLY:
+ OnAddMeleeOnly();
+ break;
+
+ case TF_COND_SWIMMING_CURSE:
+ OnAddSwimmingCurse();
+ break;
+
+ case TF_COND_HALLOWEEN_KART_CAGE:
+ OnAddHalloweenKartCage();
+ break;
+
+ case TF_COND_RUNE_RESIST:
+ OnAddRuneResist();
+ break;
+
+ case TF_COND_GRAPPLINGHOOK_LATCHED:
+ OnAddGrapplingHookLatched();
+ break;
+
+ case TF_COND_PASSTIME_INTERCEPTION:
+ OnAddPasstimeInterception();
+ break;
+
+ case TF_COND_RUNE_PLAGUE:
+ OnAddRunePlague();
+ break;
+
+ case TF_COND_PLAGUE:
+ OnAddPlague();
+ break;
+
+ case TF_COND_PURGATORY:
+ OnAddInPurgatory();
+ break;
+
+ case TF_COND_COMPETITIVE_WINNER:
+ OnAddCompetitiveWinner();
+ break;
+
+ case TF_COND_COMPETITIVE_LOSER:
+ OnAddCompetitiveLoser();
+ break;
+
+#ifdef STAGING_ONLY
+ case TF_COND_SPY_CLASS_STEAL:
+ OnAddCondSpyClassSteal();
+ break;
+
+ case TF_COND_TRANQ_MARKED:
+ OnAddTranqMark();
+ break;
+/*
+ case TF_COND_SPACE_GRAVITY:
+ OnAddSpaceGravity();
+ break;
+ case TF_COND_SELF_CONC:
+ OnAddSelfConc();
+ break;
+*/
+ case TF_COND_ROCKETPACK:
+ OnAddRocketPack();
+ break;
+
+ case TF_COND_STEALTHED_PHASE:
+ OnAddStealthedPhase();
+ break;
+
+ case TF_COND_CLIP_OVERLOAD:
+ OnAddClipOverload();
+ break;
+
+ case TF_COND_KING_BUFFED:
+ OnAddKingBuff();
+ break;
+
+
+#endif // STAGING_ONLY
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called on both client and server. Server when we remove the bit,
+// and client when it receives the new cond bits and finds one removed
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnConditionRemoved( ETFCond eCond )
+{
+ switch( eCond )
+ {
+ case TF_COND_ZOOMED:
+ OnRemoveZoomed();
+ break;
+
+ case TF_COND_BURNING:
+ OnRemoveBurning();
+ break;
+
+ case TF_COND_CRITBOOSTED:
+ Assert( !"TF_COND_CRITBOOSTED should be handled by the condition list!" );
+ break;
+
+ case TF_COND_CRITBOOSTED_DEMO_CHARGE:
+ OnRemoveDemoCharge();
+ break;
+
+ // First blood falls through on purpose.
+ case TF_COND_CRITBOOSTED_FIRST_BLOOD:
+ SetFirstBloodBoosted( false );
+ case TF_COND_CRITBOOSTED_PUMPKIN:
+ case TF_COND_CRITBOOSTED_USER_BUFF:
+ case TF_COND_CRITBOOSTED_BONUS_TIME:
+ case TF_COND_CRITBOOSTED_CTF_CAPTURE:
+ case TF_COND_CRITBOOSTED_ON_KILL:
+ case TF_COND_CRITBOOSTED_RAGE_BUFF:
+ case TF_COND_SNIPERCHARGE_RAGE_BUFF:
+ case TF_COND_CRITBOOSTED_CARD_EFFECT:
+ case TF_COND_CRITBOOSTED_RUNE_TEMP:
+ OnRemoveCritBoost();
+ break;
+
+ case TF_COND_SODAPOPPER_HYPE:
+ OnRemoveSodaPopperHype();
+ break;
+
+ case TF_COND_TMPDAMAGEBONUS:
+ OnRemoveTmpDamageBonus();
+ break;
+
+ case TF_COND_HEALTH_BUFF:
+#ifdef GAME_DLL
+ m_flHealFraction = 0;
+ m_flDisguiseHealFraction = 0;
+#endif
+ break;
+
+ case TF_COND_HEALTH_OVERHEALED:
+ OnRemoveOverhealed();
+ break;
+
+ case TF_COND_FEIGN_DEATH:
+ OnRemoveFeignDeath();
+ break;
+
+ case TF_COND_STEALTHED:
+ case TF_COND_STEALTHED_USER_BUFF:
+ OnRemoveStealthed();
+ break;
+
+ case TF_COND_DISGUISED:
+ OnRemoveDisguised();
+ break;
+
+ case TF_COND_DISGUISING:
+ OnRemoveDisguising();
+ break;
+
+ case TF_COND_INVULNERABLE:
+ case TF_COND_INVULNERABLE_USER_BUFF:
+ case TF_COND_INVULNERABLE_CARD_EFFECT:
+ OnRemoveInvulnerable();
+ break;
+
+ case TF_COND_TELEPORTED:
+ OnRemoveTeleported();
+ break;
+
+ case TF_COND_STUNNED:
+ OnRemoveStunned();
+ break;
+
+ case TF_COND_PHASE:
+ OnRemovePhase();
+ break;
+
+ case TF_COND_URINE:
+ OnRemoveUrine();
+ break;
+
+ case TF_COND_MARKEDFORDEATH:
+ OnRemoveMarkedForDeath();
+ break;
+
+ case TF_COND_BLEEDING:
+ OnRemoveBleeding();
+ break;
+
+ case TF_COND_INVULNERABLE_WEARINGOFF:
+ OnRemoveInvulnerableWearingOff();
+ break;
+
+ case TF_COND_OFFENSEBUFF:
+ OnRemoveOffenseBuff();
+ break;
+
+ case TF_COND_DEFENSEBUFF:
+ case TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK:
+ case TF_COND_DEFENSEBUFF_HIGH:
+ OnRemoveDefenseBuff();
+ break;
+
+ case TF_COND_REGENONDAMAGEBUFF:
+ OnRemoveOffenseHealthRegenBuff();
+ break;
+
+ case TF_COND_NOHEALINGDAMAGEBUFF:
+ OnRemoveNoHealingDamageBuff();
+ break;
+
+ case TF_COND_SHIELD_CHARGE:
+ OnRemoveShieldCharge();
+ break;
+
+ case TF_COND_DEMO_BUFF:
+ OnRemoveDemoBuff();
+ break;
+
+ case TF_COND_ENERGY_BUFF:
+ OnRemoveEnergyDrinkBuff();
+ break;
+
+ case TF_COND_RADIUSHEAL:
+ OnRemoveRadiusHeal();
+ break;
+
+ case TF_COND_MEGAHEAL:
+ OnRemoveMegaHeal();
+ break;
+
+ case TF_COND_MAD_MILK:
+ OnRemoveMadMilk();
+ break;
+
+ case TF_COND_TAUNTING:
+ OnRemoveTaunting();
+ break;
+
+ case TF_COND_SPEED_BOOST: OnRemoveSpeedBoost( false ); break;
+#ifdef STAGING_ONLY
+ case TF_COND_NO_COMBAT_SPEED_BOOST: OnRemoveSpeedBoost( true ); break;
+#endif
+
+
+ case TF_COND_SAPPED:
+ OnRemoveSapped();
+ break;
+
+ case TF_COND_REPROGRAMMED:
+ OnRemoveReprogrammed();
+ break;
+
+ case TF_COND_PASSTIME_PENALTY_DEBUFF:
+ case TF_COND_MARKEDFORDEATH_SILENT:
+ OnRemoveMarkedForDeathSilent();
+ break;
+
+ case TF_COND_DISGUISED_AS_DISPENSER:
+ OnRemoveDisguisedAsDispenser();
+ break;
+
+ case TF_COND_HALLOWEEN_BOMB_HEAD:
+ OnRemoveHalloweenBombHead();
+ break;
+
+ case TF_COND_HALLOWEEN_THRILLER:
+ OnRemoveHalloweenThriller();
+ break;
+
+ case TF_COND_RADIUSHEAL_ON_DAMAGE:
+ OnRemoveRadiusHealOnDamage();
+ break;
+
+ case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
+ OnRemoveMedEffectUberBulletResist();
+ break;
+
+ case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
+ OnRemoveMedEffectUberBlastResist();
+ break;
+
+ case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
+ OnRemoveMedEffectUberFireResist();
+ break;
+
+ case TF_COND_MEDIGUN_SMALL_BULLET_RESIST:
+ OnRemoveMedEffectSmallBulletResist();
+ break;
+
+ case TF_COND_MEDIGUN_SMALL_BLAST_RESIST:
+ OnRemoveMedEffectSmallBlastResist();
+ break;
+
+ case TF_COND_MEDIGUN_SMALL_FIRE_RESIST:
+ OnRemoveMedEffectSmallFireResist();
+ break;
+
+ case TF_COND_STEALTHED_USER_BUFF_FADING:
+ OnRemoveStealthedUserBuffFade();
+ break;
+
+ case TF_COND_BULLET_IMMUNE:
+ OnRemoveBulletImmune();
+ break;
+
+ case TF_COND_BLAST_IMMUNE:
+ OnRemoveBlastImmune();
+ break;
+
+ case TF_COND_FIRE_IMMUNE:
+ OnRemoveFireImmune();
+ break;
+
+ case TF_COND_MVM_BOT_STUN_RADIOWAVE:
+ OnRemoveMVMBotRadiowave();
+ break;
+
+ case TF_COND_HALLOWEEN_SPEED_BOOST:
+ OnRemoveHalloweenSpeedBoost();
+ break;
+
+ case TF_COND_HALLOWEEN_QUICK_HEAL:
+ OnRemoveHalloweenQuickHeal();
+ break;
+
+ case TF_COND_HALLOWEEN_GIANT:
+ OnRemoveHalloweenGiant();
+ break;
+
+ case TF_COND_HALLOWEEN_TINY:
+ OnRemoveHalloweenTiny();
+ break;
+
+ case TF_COND_HALLOWEEN_GHOST_MODE:
+ OnRemoveHalloweenGhostMode();
+ break;
+
+ case TF_COND_PARACHUTE_DEPLOYED:
+ OnRemoveCondParachute();
+ break;
+
+ case TF_COND_HALLOWEEN_KART_DASH:
+ OnRemoveHalloweenKartDash();
+ break;
+
+ case TF_COND_HALLOWEEN_KART:
+ OnRemoveHalloweenKart();
+ break;
+
+ case TF_COND_BALLOON_HEAD:
+ OnRemoveBalloonHead();
+ break;
+
+ case TF_COND_MELEE_ONLY:
+ OnRemoveMeleeOnly();
+ break;
+
+ case TF_COND_SWIMMING_CURSE:
+ OnRemoveSwimmingCurse();
+
+ case TF_COND_HALLOWEEN_KART_CAGE:
+ OnRemoveHalloweenKartCage();
+ break;
+
+ case TF_COND_RUNE_RESIST:
+ OnRemoveRuneResist();
+ break;
+
+ case TF_COND_GRAPPLINGHOOK_LATCHED:
+ OnRemoveGrapplingHookLatched();
+ break;
+
+ case TF_COND_PASSTIME_INTERCEPTION:
+ OnRemovePasstimeInterception();
+ break;
+
+ case TF_COND_RUNE_PLAGUE:
+ OnRemoveRunePlague();
+ break;
+
+ case TF_COND_PLAGUE:
+ OnRemovePlague();
+ break;
+
+ case TF_COND_PURGATORY:
+ OnRemoveInPurgatory();
+ break;
+
+ case TF_COND_RUNE_KING:
+ OnRemoveRuneKing();
+ break;
+
+ case TF_COND_KING_BUFFED:
+ OnRemoveKingBuff();
+ break;
+
+ case TF_COND_RUNE_SUPERNOVA:
+ OnRemoveRuneSupernova();
+ break;
+
+ case TF_COND_COMPETITIVE_WINNER:
+ OnRemoveCompetitiveWinner();
+ break;
+
+ case TF_COND_COMPETITIVE_LOSER:
+ OnRemoveCompetitiveLoser();
+ break;
+
+#ifdef STAGING_ONLY
+ case TF_COND_SPY_CLASS_STEAL:
+ OnRemoveCondSpyClassSteal();
+ break;
+
+ case TF_COND_TRANQ_MARKED:
+ OnRemoveTranqMark();
+ break;
+/*
+ case TF_COND_SPACE_GRAVITY:
+ OnRemoveSpaceGravity();
+ break;
+ case TF_COND_SELF_CONC:
+ OnRemoveSelfConc();
+ break;
+*/
+ case TF_COND_ROCKETPACK:
+ OnRemoveRocketPack();
+ break;
+
+ case TF_COND_STEALTHED_PHASE:
+ OnRemoveStealthedPhase();
+ break;
+
+ case TF_COND_CLIP_OVERLOAD:
+ OnRemoveClipOverload();
+ break;
+
+#endif // STAGING_ONLY
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the overheal bonus the specified healer is capable of buffing to
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetMaxBuffedHealth( bool bIgnoreAttributes /*= false*/, bool bIgnoreHealthOverMax /*= false*/ )
+{
+ // Find the healer we have who's providing the most overheal
+ float flBoostMax = m_pOuter->GetMaxHealthForBuffing() * tf_max_health_boost.GetFloat();
+#ifdef GAME_DLL
+ if ( !bIgnoreAttributes )
+ {
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ float flOverheal = m_pOuter->GetMaxHealthForBuffing() * m_aHealers[i].flOverhealBonus;
+ if ( flOverheal > flBoostMax )
+ {
+ flBoostMax = flOverheal;
+ }
+ }
+ }
+#endif
+
+ int iRoundDown = floor( flBoostMax / 5 );
+ iRoundDown = iRoundDown * 5;
+
+ if ( !bIgnoreHealthOverMax )
+ {
+ // Don't allow overheal total to be less than the buffable + unbuffable max health or the current health
+ int nBoostMin = MAX( m_pOuter->GetMaxHealth(), m_pOuter->GetHealth() );
+ if ( iRoundDown < nBoostMin )
+ {
+ iRoundDown = nBoostMin;
+ }
+ }
+
+ return iRoundDown;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetDisguiseMaxBuffedHealth( bool bIgnoreAttributes /*= false*/, bool bIgnoreHealthOverMax /*= false*/ )
+{
+ // Find the healer we have who's providing the most overheal
+ float flBoostMax = GetDisguiseMaxHealth() * tf_max_health_boost.GetFloat();
+#ifdef GAME_DLL
+ if ( !bIgnoreAttributes )
+ {
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ float flOverheal = GetDisguiseMaxHealth() * m_aHealers[i].flOverhealBonus;
+ if ( flOverheal > flBoostMax )
+ {
+ flBoostMax = flOverheal;
+ }
+ }
+ }
+#endif
+
+ int iRoundDown = floor( flBoostMax / 5 );
+ iRoundDown = iRoundDown * 5;
+
+ if ( !bIgnoreHealthOverMax )
+ {
+ // Don't allow overheal total to be less than the buffable + unbuffable max health or the current health
+ int nBoostMin = MAX(GetDisguiseMaxHealth(), GetDisguiseHealth() );
+ if ( iRoundDown < nBoostMin )
+ {
+ iRoundDown = nBoostMin;
+ }
+ }
+
+ return iRoundDown;
+}
+
+//-----------------------------------------------------------------------------
+bool ShouldRemoveConditionOnTimeout( ETFCond eCond )
+{
+ switch( eCond )
+ {
+ case TF_COND_HALLOWEEN_GHOST_MODE:
+ case TF_COND_HALLOWEEN_IN_HELL:
+ case TF_COND_HALLOWEEN_KART:
+ break;
+ return !TFGameRules()->ArePlayersInHell();
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Runs SERVER SIDE only Condition Think
+// If a player needs something to be updated no matter what do it here (invul, etc).
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ConditionGameRulesThink( void )
+{
+#ifdef GAME_DLL
+
+ m_ConditionList.ServerThink();
+
+ if ( m_flNextCritUpdate < gpGlobals->curtime )
+ {
+ UpdateCritMult();
+ m_flNextCritUpdate = gpGlobals->curtime + 0.5;
+ }
+
+ for ( int i=0; i < TF_COND_LAST; ++i )
+ {
+ // if we're in this condition and it's not already being handled by the condition list
+ if ( InCond( (ETFCond)i ) && ((i >= 32) || !m_ConditionList.InCond( (ETFCond)i )) )
+ {
+ // Ignore permanent conditions
+ if ( m_ConditionData[i].m_flExpireTime != PERMANENT_CONDITION )
+ {
+ float flReduction = gpGlobals->frametime;
+
+ // If we're being healed, we reduce bad conditions faster
+ if ( ConditionExpiresFast( (ETFCond)i) && m_aHealers.Count() > 0 )
+ {
+ if ( i == TF_COND_URINE )
+ {
+ flReduction += (m_aHealers.Count() * flReduction);
+ }
+ else
+ {
+ flReduction += (m_aHealers.Count() * flReduction * 4);
+ }
+ }
+
+ m_ConditionData[i].m_flExpireTime = MAX( m_ConditionData[i].m_flExpireTime - flReduction, 0 );
+
+ if ( m_ConditionData[i].m_flExpireTime == 0 )
+ {
+ RemoveCond( (ETFCond)i );
+ }
+ }
+ else
+ {
+#if !defined( DEBUG )
+ // Prevent hacked usercommand exploits
+ if ( m_pOuter->GetTimeSinceLastUserCommand() > 5.f || m_pOuter->GetTimeSinceLastThink() > 5.f )
+ {
+ ETFCond eCond = (ETFCond)i;
+
+ if ( GetCarryingRuneType() != RUNE_NONE )
+ {
+ m_pOuter->DropRune();
+ }
+
+ if ( ShouldRemoveConditionOnTimeout( eCond ) )
+ {
+ RemoveCond( eCond );
+
+ // Reset active weapon to prevent stale-state bugs
+ CTFWeaponBase *pTFWeapon = m_pOuter->GetActiveTFWeapon();
+ if ( pTFWeapon )
+ {
+ pTFWeapon->WeaponReset();
+ }
+
+ m_pOuter->TeamFortress_SetSpeed();
+ }
+ }
+#endif
+ }
+ }
+ }
+
+ // Our health will only decay ( from being medic buffed ) if we are not being healed by a medic
+ // Dispensers can give us the TF_COND_HEALTH_BUFF, but will not maintain or give us health above 100%s
+ bool bDecayHealth = true;
+ bool bDecayDisguiseHealth = true;
+
+ // If we're being healed, heal ourselves
+ if ( InCond( TF_COND_HEALTH_BUFF ) )
+ {
+ // Heal faster if we haven't been in combat for a while
+ float flTimeSinceDamage = gpGlobals->curtime - m_pOuter->GetLastDamageReceivedTime();
+ float flScale = RemapValClamped( flTimeSinceDamage, 10, 15, 1.0, 3.0 );
+ float flAttribModScale = 1.0;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flAttribModScale, mult_health_fromhealers );
+
+ float flCurOverheal = (float)m_pOuter->GetHealth() / (float)m_pOuter->GetMaxHealth();
+
+ if ( flCurOverheal > 1.0f )
+ {
+ // If they're over their max health the overheal calculation is relative to the max buffable amount scale
+ float flMaxHealthForBuffing = m_pOuter->GetMaxHealthForBuffing();
+ float flBuffableRangeHealth = m_pOuter->GetHealth() - ( m_pOuter->GetMaxHealth() - flMaxHealthForBuffing );
+ flCurOverheal = flBuffableRangeHealth / flMaxHealthForBuffing;
+ }
+
+ float flCurDisguiseOverheal = ( GetDisguiseMaxHealth() != 0 ) ? ( (float)GetDisguiseHealth() / (float)GetDisguiseMaxHealth() ) : ( flCurOverheal );
+
+ float fTotalHealAmount = 0.0f;
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ Assert( m_aHealers[i].pHealer );
+
+ float flPerHealerAttribModScale = 1.f;
+ // Check if the healer has an attribute that modifies their overheal rate
+ if( flCurOverheal > 1.f && !m_aHealers[i].bDispenserHeal )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_aHealers[i].pHealer, flPerHealerAttribModScale, overheal_fill_rate );
+ }
+
+ bool bHealDisguise = InCond( TF_COND_DISGUISED );
+ bool bHealActual = true;
+
+ // dispensers heal cloak
+ if ( m_aHealers[i].bDispenserHeal )
+ {
+ AddToSpyCloakMeter( gpGlobals->frametime * m_aHealers[i].flAmount );
+ }
+
+ // Don't heal over the healer's overheal bonus
+ if ( flCurOverheal >= m_aHealers[i].flOverhealBonus )
+ {
+ bHealActual = false;
+ }
+
+ // Same overheal check, but for fake health
+ if ( InCond( TF_COND_DISGUISED ) && flCurDisguiseOverheal >= m_aHealers[i].flOverhealBonus )
+ {
+ // Fake over-heal
+ bHealDisguise = false;
+ }
+
+ CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer );
+ if ( !bHealActual && !bHealDisguise )
+ {
+ if ( pTFHealer )
+ {
+ // Quick fix never lets health decay, even when they're at or above max overheal
+ CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( pTFHealer->GetActiveTFWeapon() );
+ if ( pMedigun && pMedigun->GetMedigunType() == MEDIGUN_QUICKFIX )
+ {
+ bDecayHealth = false;
+ bDecayDisguiseHealth = false;
+ }
+ }
+
+ continue;
+ }
+
+ // Being healed by a medigun, don't decay our health
+ if ( bHealActual )
+ {
+ bDecayHealth = false;
+ }
+
+ if ( bHealDisguise )
+ {
+ bDecayDisguiseHealth = false;
+ }
+
+ // What we multiply the heal amount by (can be changed by conditions or items).
+ float flHealAmountMult = 1.0f;
+
+ // Quick-Fix uber
+ if ( InCond( TF_COND_MEGAHEAL ) )
+ {
+ flHealAmountMult = 3.0f;
+ }
+
+ flScale *= flHealAmountMult;
+
+ // Dispensers heal at a constant rate
+ if ( m_aHealers[i].bDispenserHeal )
+ {
+ // Dispensers heal at a slower rate, but ignore flScale
+ if ( bHealActual )
+ {
+ float flDispenserFraction = gpGlobals->frametime * m_aHealers[i].flAmount * flAttribModScale;
+ m_flHealFraction += flDispenserFraction;
+
+ // track how much this healer has actually done so far
+ m_aHealers[i].flHealAccum += clamp( flDispenserFraction, 0.f, (float) GetMaxBuffedHealth() - m_pOuter->GetHealth() );
+ }
+ if ( bHealDisguise )
+ {
+ m_flDisguiseHealFraction += gpGlobals->frametime * m_aHealers[i].flAmount * flAttribModScale;
+ }
+ }
+ else // player heals are affected by the last damage time
+ {
+ if ( bHealActual )
+ {
+ // Scale this if needed
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flScale, mult_healing_from_medics );
+ m_flHealFraction += gpGlobals->frametime * m_aHealers[i].flAmount * flScale * flAttribModScale * flPerHealerAttribModScale;
+ }
+ if ( bHealDisguise )
+ {
+ m_flDisguiseHealFraction += gpGlobals->frametime * m_aHealers[i].flAmount * flScale * flAttribModScale * flPerHealerAttribModScale;
+ }
+ }
+
+ fTotalHealAmount += m_aHealers[i].flAmount;
+
+ // Keep our decay multiplier uptodate
+ if ( m_flBestOverhealDecayMult == -1 || m_aHealers[i].flOverhealDecayMult < m_flBestOverhealDecayMult )
+ {
+ m_flBestOverhealDecayMult = m_aHealers[i].flOverhealDecayMult;
+ }
+ }
+
+ if ( InCond( TF_COND_HEALING_DEBUFF ) )
+ {
+ m_flHealFraction *= 0.75f;
+ }
+
+ int nHealthToAdd = (int)m_flHealFraction;
+ int nDisguiseHealthToAdd = (int)m_flDisguiseHealFraction;
+ if ( nHealthToAdd > 0 || nDisguiseHealthToAdd > 0 )
+ {
+ if ( nHealthToAdd > 0 )
+ {
+ m_flHealFraction -= nHealthToAdd;
+ }
+
+ if ( nDisguiseHealthToAdd > 0 )
+ {
+ m_flDisguiseHealFraction -= nDisguiseHealthToAdd;
+ }
+
+ int iBoostMax = GetMaxBuffedHealth();
+
+ if ( InCond( TF_COND_DISGUISED ) )
+ {
+ // Separate cap for disguised health
+ int nFakeHealthToAdd = clamp( nDisguiseHealthToAdd, 0, GetDisguiseMaxBuffedHealth() - m_iDisguiseHealth );
+ m_iDisguiseHealth += nFakeHealthToAdd;
+ }
+
+ // Track health prior to healing
+ int nPrevHealth = m_pOuter->GetHealth();
+
+ // Cap it to the max we'll boost a player's health
+ nHealthToAdd = clamp( nHealthToAdd, 0, iBoostMax - m_pOuter->GetHealth() );
+
+ m_pOuter->TakeHealth( nHealthToAdd, DMG_IGNORE_MAXHEALTH | DMG_IGNORE_DEBUFFS );
+
+ m_pOuter->AdjustDrownDmg( -1.0 * nHealthToAdd ); // subtract this from the drowndmg in case they're drowning and being healed at the same time
+
+ // split up total healing based on the amount each healer contributes
+ if ( fTotalHealAmount > 0 )
+ {
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ Assert( m_aHealers[i].pHealScorer );
+ Assert( m_aHealers[i].pHealer );
+ if ( m_aHealers[i].pHealScorer.IsValid() && m_aHealers[i].pHealer.IsValid() )
+ {
+ CBaseEntity *pHealer = m_aHealers[i].pHealer;
+ float flHealAmount = nHealthToAdd * ( m_aHealers[i].flAmount / fTotalHealAmount );
+
+ if ( pHealer && IsAlly( pHealer ) )
+ {
+ CTFPlayer *pHealScorer = ToTFPlayer( m_aHealers[i].pHealScorer );
+ if ( pHealScorer )
+ {
+ // Don't report healing when we're close to the buff cap and haven't taken damage recently.
+ // This avoids sending bogus heal stats while maintaining our max overheal. Ideally we
+ // wouldn't decay in this scenario, but that would be a risky change.
+ if ( iBoostMax - nPrevHealth > 1 || gpGlobals->curtime - m_pOuter->GetLastDamageReceivedTime() <= 1.f )
+ {
+ CTF_GameStats.Event_PlayerHealedOther( pHealScorer, flHealAmount );
+ }
+
+ // Add this to the one-second-healing counter
+ m_aHealers[i].flHealedLastSecond += flHealAmount;
+
+ HandleRageGain( m_pOuter, kRageBuffFlag_OnMedicHealingReceived, flHealAmount / 2.f, 1.0f );
+
+ float flRage = flHealAmount;
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() &&
+ TFObjectiveResource() && TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
+ {
+ flRage = Max( flHealAmount, 10.f );
+ }
+ HandleRageGain( pHealScorer, kRageBuffFlag_OnHeal, flRage, 1.0f );
+
+ // If it's been one second, or we know healing beyond this point will be overheal, generate an event
+ if ( ( m_flHealedPerSecondTimer <= gpGlobals->curtime || m_pOuter->GetHealth() >= m_pOuter->GetMaxHealth() )
+ && m_aHealers[i].flHealedLastSecond > 1 )
+ {
+ // Make sure this isn't pure overheal
+ if ( m_pOuter->GetHealth() - m_aHealers[i].flHealedLastSecond < m_pOuter->GetMaxHealth() )
+ {
+ float flOverHeal = m_pOuter->GetHealth() - m_pOuter->GetMaxHealth();
+ if ( flOverHeal > 0 )
+ {
+ m_aHealers[i].flHealedLastSecond -= flOverHeal;
+ }
+
+ // TEST THIS
+ // Give the medic some uber if it is from their (AoE heal) which has no overheal
+ if ( m_aHealers[i].flOverhealBonus <= 1.0f )
+ {
+ // Give a litte bit of uber based on actual healing
+ // Give them a little bit of Uber
+ CWeaponMedigun *pMedigun = static_cast<CWeaponMedigun *>( pHealScorer->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
+ if ( pMedigun )
+ {
+ // On Mediguns, per frame, the amount of uber added is based on
+ // Default heal rate is 24per second, we scale based on that and frametime
+ pMedigun->AddCharge( ( m_aHealers[i].flHealedLastSecond / 24.0f ) * gpGlobals->frametime * 0.33f );
+ }
+ }
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_healed" );
+ if ( event )
+ {
+ // HLTV event priority, not transmitted
+ event->SetInt( "priority", 1 );
+
+ // Healed by another player.
+ event->SetInt( "patient", m_pOuter->GetUserID() );
+ event->SetInt( "healer", pHealScorer->GetUserID() );
+ event->SetInt( "amount", m_aHealers[i].flHealedLastSecond );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // Can we figure out which item is doing this healing?
+ if ( pHealScorer )
+ {
+ // Can be Mediguns or anything that gives off 'heal' buff like amputator aoe heal
+ EconEntity_OnOwnerKillEaterEvent_Batched( pHealScorer->GetActiveTFWeapon(), pHealScorer, m_pOuter, kKillEaterEvent_AllyHealingDone, m_aHealers[i].flHealedLastSecond );
+ }
+ }
+
+ m_aHealers[i].flHealedLastSecond = 0;
+ m_flHealedPerSecondTimer = gpGlobals->curtime + 1.0f;
+ }
+ }
+ }
+ else
+ {
+ CTF_GameStats.Event_PlayerLeachedHealth( m_pOuter, m_aHealers[i].bDispenserHeal, flHealAmount );
+ }
+ }
+ }
+ }
+ }
+
+ if ( InCond( TF_COND_BURNING ) )
+ {
+ // Reduce the duration of this burn
+ float flReduction = 2; // ( flReduction + 1 ) x faster reduction
+ m_flFlameRemoveTime -= flReduction * gpGlobals->frametime;
+ }
+ if ( InCond( TF_COND_BLEEDING ) )
+ {
+ // Reduce the duration of this bleeding
+ float flReduction = 2; // ( flReduction + 1 ) x faster reduction
+ FOR_EACH_VEC( m_PlayerBleeds, i )
+ {
+ m_PlayerBleeds[i].flBleedingRemoveTime -= flReduction * gpGlobals->frametime;
+ }
+ }
+ }
+
+ if ( !InCond( TF_COND_HEALTH_OVERHEALED ) && m_pOuter->GetHealth() > ( m_pOuter->GetMaxHealth() - m_pOuter->GetRuneHealthBonus() ) )
+ {
+ AddCond( TF_COND_HEALTH_OVERHEALED, PERMANENT_CONDITION );
+ }
+ else if ( InCond( TF_COND_HEALTH_OVERHEALED ) && m_pOuter->GetHealth() <= ( m_pOuter->GetMaxHealth() - m_pOuter->GetRuneHealthBonus() ) )
+ {
+ RemoveCond( TF_COND_HEALTH_OVERHEALED );
+ }
+
+ if ( bDecayHealth )
+ {
+ float flOverheal = GetMaxBuffedHealth( false, true );
+
+ // If we're not being buffed, our health drains back to our max
+ if ( m_pOuter->GetHealth() > m_pOuter->GetMaxHealth() )
+ {
+ // Items exist that get us over max health, without ever being healed, in which case our m_flBestOverhealDecayMult will still be -1.
+ float flDrainMult = (m_flBestOverhealDecayMult == -1) ? 1.0 : m_flBestOverhealDecayMult;
+ float flBoostMaxAmount = flOverheal - m_pOuter->GetMaxHealth();
+ float flDrain = flBoostMaxAmount / (tf_boost_drain_time.GetFloat() * flDrainMult);
+ m_flHealFraction += (gpGlobals->frametime * flDrain);
+
+ int nHealthToDrain = (int)m_flHealFraction;
+ if ( nHealthToDrain > 0 )
+ {
+ m_flHealFraction -= nHealthToDrain;
+
+ // Manually subtract the health so we don't generate pain sounds / etc
+ m_pOuter->m_iHealth -= nHealthToDrain;
+ }
+ }
+ else if ( m_flBestOverhealDecayMult != -1 )
+ {
+ m_flBestOverhealDecayMult = -1;
+ }
+
+ }
+
+ if ( bDecayDisguiseHealth )
+ {
+ float flOverheal = GetDisguiseMaxBuffedHealth( false, true );
+
+ if ( InCond( TF_COND_DISGUISED ) && (GetDisguiseHealth() > GetDisguiseMaxHealth()) )
+ {
+ // Items exist that get us over max health, without ever being healed, in which case our m_flBestOverhealDecayMult will still be -1.
+ float flDrainMult = (m_flBestOverhealDecayMult == -1) ? 1.0 : m_flBestOverhealDecayMult;
+ float flBoostMaxAmount = flOverheal - GetDisguiseMaxHealth();
+ float flDrain = (flBoostMaxAmount / tf_boost_drain_time.GetFloat()) * flDrainMult;
+ m_flDisguiseHealFraction += (gpGlobals->frametime * flDrain);
+
+ int nHealthToDrain = (int)m_flDisguiseHealFraction;
+ if ( nHealthToDrain > 0 )
+ {
+ m_flDisguiseHealFraction -= nHealthToDrain;
+
+ // Reduce our fake disguised health by roughly the same amount
+ m_iDisguiseHealth -= nHealthToDrain;
+ }
+ }
+ }
+
+ // Taunt
+ if ( InCond( TF_COND_TAUNTING ) )
+ {
+ if ( m_pOuter->IsAllowedToRemoveTaunt() && gpGlobals->curtime > m_pOuter->GetTauntRemoveTime() )
+ {
+ RemoveCond( TF_COND_TAUNTING );
+ }
+ }
+
+ if ( InCond( TF_COND_BURNING ) && !m_pOuter->m_bInPowerPlay )
+ {
+ if ( TFGameRules() && TFGameRules()->IsTruceActive() && m_hBurnAttacker && m_hBurnAttacker->IsTruceValidForEnt() )
+ {
+ RemoveCond( TF_COND_BURNING );
+ }
+ else if ( gpGlobals->curtime > m_flFlameRemoveTime || m_pOuter->GetWaterLevel() >= WL_Waist )
+ {
+ // If we're underwater, put the fire out
+ if ( m_pOuter->GetWaterLevel() >= WL_Waist )
+ {
+ // General achievement for jumping into water while you're on fire
+ m_pOuter->AwardAchievement( ACHIEVEMENT_TF_FIRE_WATERJUMP );
+
+ // Pyro achievement for forcing players into water
+ if ( m_hBurnAttacker )
+ {
+ m_hBurnAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_FORCE_WATERJUMP );
+ }
+ }
+
+ RemoveCond( TF_COND_BURNING );
+
+ if ( InCond( TF_COND_HEALTH_BUFF ) )
+ {
+ // one or more players is healing us, send a "player_extinguished" event. We
+ // need to send one for each player who's healing us.
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ Assert( m_aHealers[i].pHealer );
+
+ if ( m_aHealers[i].bDispenserHeal )
+ {
+ CObjectDispenser *pDispenser = dynamic_cast<CObjectDispenser*>( m_aHealers[i].pHealer.Get() );
+ if ( pDispenser )
+ {
+ CTFPlayer *pTFPlayer = pDispenser->GetBuilder();
+ if ( pTFPlayer )
+ {
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"dispenser\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n",
+ pTFPlayer->GetPlayerName(),
+ pTFPlayer->GetUserID(),
+ pTFPlayer->GetNetworkIDString(),
+ pTFPlayer->GetTeam()->GetName(),
+ m_pOuter->GetPlayerName(),
+ m_pOuter->GetUserID(),
+ m_pOuter->GetNetworkIDString(),
+ m_pOuter->GetTeam()->GetName(),
+ (int)m_aHealers[i].pHealer->GetAbsOrigin().x,
+ (int)m_aHealers[i].pHealer->GetAbsOrigin().y,
+ (int)m_aHealers[i].pHealer->GetAbsOrigin().z,
+ (int)m_pOuter->GetAbsOrigin().x,
+ (int)m_pOuter->GetAbsOrigin().y,
+ (int)m_pOuter->GetAbsOrigin().z );
+ }
+ }
+
+// continue;
+ }
+
+ EHANDLE pHealer = m_aHealers[i].pHealer;
+ if ( m_aHealers[i].bDispenserHeal || !pHealer || !pHealer->IsPlayer() )
+ pHealer = m_aHealers[i].pHealScorer;
+
+ if ( !pHealer )
+ continue;
+
+ CTFPlayer *pTFPlayer = ToTFPlayer( pHealer );
+ if ( pTFPlayer && !m_aHealers[i].bDispenserHeal )
+ {
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n",
+ pTFPlayer->GetPlayerName(),
+ pTFPlayer->GetUserID(),
+ pTFPlayer->GetNetworkIDString(),
+ pTFPlayer->GetTeam()->GetName(),
+ m_pOuter->GetPlayerName(),
+ m_pOuter->GetUserID(),
+ m_pOuter->GetNetworkIDString(),
+ m_pOuter->GetTeam()->GetName(),
+ ( pTFPlayer->GetActiveTFWeapon() ) ? pTFPlayer->GetActiveTFWeapon()->GetName() : "tf_weapon_medigun",
+ (int)pTFPlayer->GetAbsOrigin().x,
+ (int)pTFPlayer->GetAbsOrigin().y,
+ (int)pTFPlayer->GetAbsOrigin().z,
+ (int)m_pOuter->GetAbsOrigin().x,
+ (int)m_pOuter->GetAbsOrigin().y,
+ (int)m_pOuter->GetAbsOrigin().z );
+ }
+
+ // Tell the clients involved
+ CRecipientFilter involved_filter;
+ CBasePlayer *pBasePlayerHealer = ToBasePlayer( pHealer );
+ if ( pBasePlayerHealer )
+ {
+ involved_filter.AddRecipient( pBasePlayerHealer );
+ }
+ involved_filter.AddRecipient( m_pOuter );
+ UserMessageBegin( involved_filter, "PlayerExtinguished" );
+ WRITE_BYTE( pHealer->entindex() );
+ WRITE_BYTE( m_pOuter->entindex() );
+ MessageEnd();
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_extinguished" );
+ if ( event )
+ {
+ event->SetInt( "victim", m_pOuter->entindex() );
+ event->SetInt( "healer", pHealer->entindex() );
+
+ gameeventmanager->FireEvent( event, true );
+ }
+ }
+ }
+ }
+ else if ( ( gpGlobals->curtime >= m_flFlameBurnTime ) && ( TF_CLASS_PYRO != m_pOuter->GetPlayerClass()->GetClassIndex() ) )
+ {
+ // Burn the player (if not pyro, who does not take persistent burning damage)
+
+ float flBurnDamage = TF_BURNING_DMG;
+ int nKillType = TF_DMG_CUSTOM_BURNING;
+
+ if ( m_hBurnWeapon )
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hBurnWeapon, flBurnDamage, mult_wpn_burndmg );
+
+ if ( m_hBurnWeapon.Get()->GetWeaponID() == TF_WEAPON_FLAREGUN )
+ {
+ nKillType = TF_DMG_CUSTOM_BURNING_FLARE;
+ }
+ else if ( m_hBurnWeapon.Get()->GetWeaponID() == TF_WEAPON_COMPOUND_BOW )
+ {
+ nKillType = TF_DMG_CUSTOM_BURNING_ARROW;
+ }
+ }
+
+ // Halloween Spell
+ if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
+ {
+ int iHalloweenSpell = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_hBurnWeapon, iHalloweenSpell, halloween_green_flames );
+ if ( iHalloweenSpell > 0 )
+ {
+ const char *pEffectName = "halloween_burningplayer_flyingbits";
+ // Extra Halloween Particles
+ DispatchParticleEffect( pEffectName, PATTACH_ABSORIGIN_FOLLOW, m_pOuter, 0, false );
+ }
+ }
+
+ CTakeDamageInfo info( m_hBurnAttacker, m_hBurnAttacker, m_hBurnWeapon, flBurnDamage, DMG_BURN | DMG_PREVENT_PHYSICS_FORCE, nKillType );
+ m_pOuter->TakeDamage( info );
+
+ // Give health to attacker if they are carrying the Vampire Powerup.
+ if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
+ {
+ CTFPlayer *pTFAttacker = ToTFPlayer( GetConditionProvider( TF_COND_BURNING ) );
+
+ if ( pTFAttacker && pTFAttacker != m_pOuter )
+ {
+ if ( pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE )
+ {
+ pTFAttacker->TakeHealth( flBurnDamage, DMG_GENERIC );
+ }
+ }
+ }
+
+ m_flFlameBurnTime = gpGlobals->curtime + TF_BURNING_FREQUENCY;
+ }
+
+ if ( m_flNextBurningSound < gpGlobals->curtime )
+ {
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_ONFIRE );
+ m_flNextBurningSound = gpGlobals->curtime + 2.5;
+ }
+ }
+
+
+ // Stops the drain hack.
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_MEDIC ) )
+ {
+ CWeaponMedigun *pWeapon = ( CWeaponMedigun* )m_pOuter->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN );
+ if ( pWeapon && pWeapon->IsReleasingCharge() )
+ {
+ pWeapon->DrainCharge();
+ }
+ }
+
+ TestAndExpireChargeEffect( MEDIGUN_CHARGE_INVULN );
+ TestAndExpireChargeEffect( MEDIGUN_CHARGE_CRITICALBOOST );
+ TestAndExpireChargeEffect( MEDIGUN_CHARGE_MEGAHEAL );
+ //TestAndExpireChargeEffect( MEDIGUN_CHARGE_BULLET_RESIST );
+ //TestAndExpireChargeEffect( MEDIGUN_CHARGE_BLAST_RESIST );
+ //TestAndExpireChargeEffect( MEDIGUN_CHARGE_FIRE_RESIST );
+
+ if ( InCond( TF_COND_STEALTHED_BLINK ) )
+ {
+ float flBlinkTime = TF_SPY_STEALTH_BLINKTIME;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flBlinkTime, cloak_blink_time_penalty );
+ if ( flBlinkTime < ( gpGlobals->curtime - m_flLastStealthExposeTime ) )
+ {
+ RemoveCond( TF_COND_STEALTHED_BLINK );
+ }
+ }
+
+ if ( InCond( TF_COND_FEIGN_DEATH ) )
+ {
+ if ( m_flFeignDeathEnd < gpGlobals->curtime )
+ {
+ RemoveCond( TF_COND_FEIGN_DEATH );
+ }
+ }
+
+ if ( m_pOuter->GetWaterLevel() >= WL_Waist )
+ {
+ if ( InCond( TF_COND_URINE ) )
+ {
+ // If we're underwater, wash off the urine.
+ RemoveCond( TF_COND_URINE );
+ }
+
+ if ( InCond( TF_COND_MAD_MILK ) )
+ {
+ // If we're underwater, wash off the Mad Milk.
+ RemoveCond( TF_COND_MAD_MILK );
+ }
+ }
+
+ if ( !InCond( TF_COND_DISGUISED ) )
+ {
+ // Remove our disguise weapon if we are ever not disguised and we have one.
+ RemoveDisguiseWeapon();
+
+ // also clear the disguise weapon list
+ m_pOuter->ClearDisguiseWeaponList();
+ }
+
+ if ( InCond( TF_COND_BLEEDING ) )
+ {
+ FOR_EACH_VEC_BACK( m_PlayerBleeds, i )
+ {
+ bleed_struct_t& bleed = m_PlayerBleeds[i];
+ if ( TFGameRules() && TFGameRules()->IsTruceActive() && bleed.hBleedingAttacker && ( bleed.hBleedingAttacker != m_pOuter ) && bleed.hBleedingAttacker->IsTruceValidForEnt() )
+ {
+ m_PlayerBleeds.FastRemove( i );
+ }
+ else if ( gpGlobals->curtime >= bleed.flBleedingRemoveTime && !bleed.bPermanentBleeding )
+ {
+ m_PlayerBleeds.FastRemove( i );
+ }
+ else if ( ( gpGlobals->curtime >= bleed.flBleedingTime ) )
+ {
+ bleed.flBleedingTime = gpGlobals->curtime + TF_BLEEDING_FREQUENCY;
+
+ CTakeDamageInfo info( bleed.hBleedingAttacker, bleed.hBleedingAttacker, bleed.hBleedingWeapon, bleed.nBleedDmg, DMG_SLASH, TF_DMG_CUSTOM_BLEEDING );
+ m_pOuter->TakeDamage( info );
+
+ // It's very possible we died from the take damage, which clears all our conditions
+ // and nukes m_PlayerBleeds. If that happens, bust out of this loop.
+ if( m_PlayerBleeds.Count() == 0 )
+ break;
+ }
+ }
+
+ if ( !m_PlayerBleeds.Count() )
+ {
+ RemoveCond( TF_COND_BLEEDING );
+ }
+ }
+
+#ifdef STAGING_ONLY
+ if ( InCond( TF_COND_TRANQ_SPY_BOOST ) )
+ {
+ m_flSpyTranqBuffDuration = GetConditionDuration( TF_COND_TRANQ_SPY_BOOST );
+ }
+ else
+#endif // STAGING_ONLY
+ {
+ m_flSpyTranqBuffDuration = 0;
+ }
+
+ if ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsPlayingRobotDestructionMode() )
+ {
+ RadiusCurrencyCollectionCheck();
+ }
+
+ if ( TFGameRules()->IsMannVsMachineMode() && m_pOuter->IsPlayerClass( TF_CLASS_SPY) )
+ {
+ // In MvM, Spies reveal other spies in a radius around them
+ RadiusSpyScan();
+ }
+ if ( GetCarryingRuneType() == RUNE_PLAGUE )
+ {
+ RadiusHealthkitCollectionCheck();
+ }
+
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Do CLIENT/SERVER SHARED condition thinks.
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ConditionThink( void )
+{
+ // Client Only Updates Meters for Local Only
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+#endif
+ {
+ UpdateCloakMeter();
+ UpdateRageBuffsAndRage();
+ UpdateEnergyDrinkMeter();
+ UpdateChargeMeter();
+ DemoShieldChargeThink();
+
+#ifdef STAGING_ONLY
+ UpdateRocketPack();
+#endif // STAGING_ONLY
+ }
+
+ VehicleThink();
+
+ if ( m_pOuter->GetFlags() & FL_ONGROUND && InCond( TF_COND_PARACHUTE_DEPLOYED ))
+ {
+ RemoveCond( TF_COND_PARACHUTE_DEPLOYED );
+ }
+
+ // See if we should be pulsing our radius heal
+ PulseMedicRadiusHeal();
+ PulseKingRuneBuff();
+
+ m_ConditionList.Think();
+
+ if ( InCond( TF_COND_STUNNED ) )
+ {
+#ifdef GAME_DLL
+ if ( IsControlStunned() )
+ {
+ m_pOuter->SetAbsAngles( m_pOuter->m_angTauntCamera );
+ m_pOuter->SetLocalAngles( m_pOuter->m_angTauntCamera );
+ }
+#endif
+ if ( GetActiveStunInfo() && gpGlobals->curtime > GetActiveStunInfo()->flExpireTime )
+ {
+#ifdef GAME_DLL
+ m_PlayerStuns.Remove( m_iStunIndex );
+ m_iStunIndex = -1;
+
+ // Apply our next stun
+ if ( m_PlayerStuns.Count() )
+ {
+ int iStrongestIdx = 0;
+ for ( int i = 1; i < m_PlayerStuns.Count(); i++ )
+ {
+ if ( m_PlayerStuns[i].flStunAmount > m_PlayerStuns[iStrongestIdx].flStunAmount )
+ {
+ iStrongestIdx = i;
+ }
+ }
+ m_iStunIndex = iStrongestIdx;
+
+ AddCond( TF_COND_STUNNED, -1.f, m_PlayerStuns[m_iStunIndex].hPlayer );
+ m_iMovementStunParity = ( m_iMovementStunParity + 1 ) & ( ( 1 << MOVEMENTSTUN_PARITY_BITS ) - 1 );
+
+ Assert( GetActiveStunInfo() );
+ }
+ else
+ {
+ RemoveCond( TF_COND_STUNNED );
+ }
+#endif // GAME_DLL
+
+ UpdateLegacyStunSystem();
+ }
+ else if ( IsControlStunned() && GetActiveStunInfo() && ( gpGlobals->curtime > GetActiveStunInfo()->flStartFadeTime ) )
+ {
+ // Control stuns have a final anim to play.
+ ControlStunFading();
+ }
+
+#ifdef CLIENT_DLL
+ // turn off stun effect that gets turned on when incomplete stun msg is received on the client
+ if ( GetActiveStunInfo() && GetActiveStunInfo()->iStunFlags & TF_STUN_NO_EFFECTS )
+ {
+ if ( m_pOuter->m_pStunnedEffect )
+ {
+ // Remove stun stars if they are still around.
+ // They might be if we died, etc.
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pStunnedEffect );
+ m_pOuter->m_pStunnedEffect = NULL;
+ }
+ }
+#endif
+ }
+
+ if ( InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
+ {
+#ifdef GAME_DLL
+ static struct
+ {
+ float flTimeLeft;
+ int nStage;
+ } s_vecBombStages[] = { { 8.0f, 0 }, { 3.0f, 1 }, { 0.0f, 2 } };
+
+ for ( int i = 0; i < ARRAYSIZE( s_vecBombStages ); ++i )
+ {
+ if ( m_ConditionData[TF_COND_HALLOWEEN_BOMB_HEAD].m_flExpireTime >= s_vecBombStages[i].flTimeLeft )
+ {
+ m_nHalloweenBombHeadStage = s_vecBombStages[i].nStage;
+ break;
+ }
+ }
+
+ if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) )
+ {
+ if ( m_pOuter->IsAlive() )
+ {
+ Vector vToBoss = m_pOuter->EyePosition() - TFGameRules()->GetActiveBoss()->WorldSpaceCenter();
+ if ( vToBoss.IsLengthLessThan( 100.f ) )
+ {
+ CMerasmus* pMerasmus = assert_cast< CMerasmus* >( TFGameRules()->GetActiveBoss() );
+ if ( pMerasmus )
+ {
+ pMerasmus->AddStun( m_pOuter );
+ }
+ }
+ }
+ }
+#else
+ m_pOuter->HalloweenBombHeadUpdate();
+#endif
+ }
+ else
+ {
+#ifdef GAME_DLL
+ m_nHalloweenBombHeadStage = 0;
+#endif
+ }
+
+#ifdef GAME_DLL
+ if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_VIADUCT ) && InCond( TF_COND_PURGATORY ) )
+ {
+ // escalating injury multiplier while in purgatory
+ if ( m_pOuter->m_purgatoryPainMultiplierTimer.IsElapsed() )
+ {
+ ++m_pOuter->m_purgatoryPainMultiplier;
+
+ // injury multiplies rapidly after initial period
+ m_pOuter->m_purgatoryPainMultiplierTimer.Start( 10.0f );
+ }
+ }
+#endif
+
+ CheckDisguiseTimer();
+
+#ifdef CLIENT_DLL
+ if ( InCond( TF_COND_TAUNTING ) && m_flTauntParticleRefireTime > 0.0f && gpGlobals->curtime >= m_flTauntParticleRefireTime )
+ {
+ FireClientTauntParticleEffects();
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::CheckDisguiseTimer( void )
+{
+ if ( InCond( TF_COND_DISGUISING ) && GetDisguiseCompleteTime() > 0 )
+ {
+ if ( gpGlobals->curtime > GetDisguiseCompleteTime() )
+ {
+ CompleteDisguise();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddZoomed( void )
+{
+#ifdef CLIENT_DLL
+ // hide cosmetic while zoom in thirdperson
+ if ( m_pOuter == C_TFPlayer::GetLocalTFPlayer() && ::input->CAM_IsThirdPerson() )
+ {
+ m_pOuter->UpdateWearables();
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveZoomed( void )
+{
+#ifdef GAME_DLL
+ m_pOuter->SetFOV( m_pOuter, 0, 0.1f );
+#endif // GAME_DLL
+
+#ifdef CLIENT_DLL
+ // unhide cosmetic after zoom in thirdperson
+ if ( m_pOuter == C_TFPlayer::GetLocalTFPlayer() && ::input->CAM_IsThirdPerson() )
+ {
+ m_pOuter->UpdateWearables();
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddDisguising( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) )
+ return;
+
+ if ( m_pOuter->m_pDisguisingEffect )
+ {
+// m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pDisguisingEffect );
+ }
+
+ if ( !m_pOuter->IsLocalPlayer() && ( !IsStealthed() || !m_pOuter->IsEnemyPlayer() ) )
+ {
+ const char *pEffectName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "spy_start_disguise_red" : "spy_start_disguise_blue";
+ m_pOuter->m_pDisguisingEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
+ m_pOuter->m_flDisguiseEffectStartTime = gpGlobals->curtime;
+ }
+
+ m_pOuter->EmitSound( "Player.Spy_Disguise" );
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: set up effects for when player finished disguising
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddDisguised( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pDisguisingEffect )
+ {
+ // turn off disguising particles
+// m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pDisguisingEffect );
+ m_pOuter->m_pDisguisingEffect = NULL;
+ }
+ m_pOuter->m_flDisguiseEndEffectStartTime = gpGlobals->curtime;
+
+ UpdateCritBoostEffect( kCritBoost_ForceRefresh );
+
+ m_pOuter->UpdateSpyStateChange();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddDemoCharge( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->StopSound( "DemoCharge.ChargeCritOn" );
+ m_pOuter->EmitSound( "DemoCharge.ChargeCritOn" );
+ UpdateCritBoostEffect();
+#endif // CLIENT_DLL
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Return the team that the spy is displayed as.
+// Not disguised: His own team
+// Disguised: The team he is disguised as
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetDisplayedTeam( void ) const
+{
+ int iVisibleTeam = m_pOuter->GetTeamNumber();
+ // if this player is disguised and on the other team, use disguise team
+ if ( InCond( TF_COND_DISGUISED ) && m_pOuter->IsEnemyPlayer() )
+ {
+ iVisibleTeam = GetDisguiseTeam();
+ }
+
+ return iVisibleTeam;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: start, end, and changing disguise classes
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnDisguiseChanged( void )
+{
+ // recalc disguise model index
+ //RecalcDisguiseWeapon( true );
+ m_pOuter->UpdateSpyStateChange();
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddInvulnerable( void )
+{
+#ifdef CLIENT_DLL
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ const char *pEffectName = NULL;
+
+ switch( m_pOuter->GetTeamNumber() )
+ {
+ case TF_TEAM_BLUE:
+ default:
+ pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE;
+ break;
+ case TF_TEAM_RED:
+ pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED;
+ break;
+ }
+
+ IMaterial *pMaterial = materials->FindMaterial( pEffectName, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
+ {
+ g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_HEAVY_RECEIVE_UBER_GRIND );
+ }
+ }
+#else
+ // remove any persistent damaging conditions
+ if ( InCond( TF_COND_BURNING ) )
+ {
+ RemoveCond( TF_COND_BURNING );
+ }
+
+ if ( InCond( TF_COND_URINE ) )
+ {
+ RemoveCond( TF_COND_URINE );
+ }
+
+ if ( InCond( TF_COND_BLEEDING ) )
+ {
+ RemoveCond( TF_COND_BLEEDING );
+ }
+
+ if ( InCond( TF_COND_MAD_MILK ) )
+ {
+ RemoveCond( TF_COND_MAD_MILK );
+ }
+ if ( InCond( TF_COND_PLAGUE ) )
+ {
+ RemoveCond( TF_COND_PLAGUE );
+ }
+#ifdef STAGING_ONLY
+ if ( InCond( TF_COND_TRANQ_MARKED ) )
+ {
+ RemoveCond( TF_COND_TRANQ_MARKED );
+ }
+#endif // STAGING_ONLY
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveInvulnerable( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is an invuln material
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial &&
+ ( FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE ) ||
+ FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED ) ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveInvulnerableWearingOff( void )
+{
+#ifdef CLIENT_DLL
+ m_flInvulnerabilityRemoveTime = gpGlobals->curtime;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddPhase( void )
+{
+ UpdatePhaseEffects();
+
+#ifdef CLIENT_DLL
+
+#else
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:1" );
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemovePhase( void )
+{
+ RemovePhaseEffects();
+
+#ifdef CLIENT_DLL
+
+#else
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:0" );
+
+ // Tell this player how much damage they dodged.
+ CSingleUserRecipientFilter user( m_pOuter );
+ UserMessageBegin( user, "DamageDodged" );
+ WRITE_SHORT( clamp( m_iPhaseDamage, 0, 10000 ) );
+ MessageEnd();
+ m_iPhaseDamage = 0;
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddUrine( void )
+{
+#ifdef CLIENT_DLL
+ if ( tf_colorblindassist.GetBool() )
+ {
+ m_pOuter->AddOverheadEffect( "peejar_icon" );
+ }
+
+ if ( !m_pOuter->m_pUrineEffect )
+ {
+ m_pOuter->m_pUrineEffect = m_pOuter->ParticleProp()->Create( "peejar_drips", PATTACH_ABSORIGIN_FOLLOW ); // pEffect! Kek!
+ }
+
+ if ( m_pOuter->m_pUrineEffect )
+ {
+ m_pOuter->ParticleProp()->AddControlPoint( m_pOuter->m_pUrineEffect, 1, m_pOuter, PATTACH_ABSORIGIN_FOLLOW );
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_URINE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#else
+
+// m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "urine_on" );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveUrine( void )
+{
+
+#ifdef CLIENT_DLL
+ m_pOuter->RemoveOverheadEffect( "peejar_icon", true );
+
+ if ( m_pOuter->m_pUrineEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pUrineEffect );
+ m_pOuter->m_pUrineEffect = NULL;
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is urine
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_URINE ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#else
+// m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "urine_off" );
+
+ if ( m_hPeeAttacker )
+ {
+ // Tell the clients involved in the jarate
+ CRecipientFilter involved_filter;
+ involved_filter.AddRecipient( m_pOuter );
+ involved_filter.AddRecipient( m_hPeeAttacker );
+ UserMessageBegin( involved_filter, "PlayerJaratedFade" );
+ WRITE_BYTE( m_hPeeAttacker->entindex() );
+ WRITE_BYTE( m_pOuter->entindex() );
+ MessageEnd();
+ }
+
+ m_hPeeAttacker = NULL;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddMarkedForDeath( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdatedMarkedForDeathEffect();
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->EmitSound( "Weapon_Marked_for_Death.Indicator" );
+ }
+ else if ( !InCond( TF_COND_DISGUISED ) && !IsStealthed() )
+ {
+ m_pOuter->EmitSound( "Weapon_Marked_for_Death.Initial" );
+ }
+
+ /*
+ // Do we want to have a screen overlay effect?
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_URINE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+ */
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveMarkedForDeath( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdatedMarkedForDeathEffect();
+
+ /*
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is urine
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_URINE ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+ */
+#endif
+}
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddTranqMark( void )
+{
+#ifdef CLIENT_DLL
+ //if ( !InCond( TF_COND_DISGUISED ) && !IsStealthed() )
+ //{
+ m_pOuter->UpdateTranqMark( true );
+ //}
+
+ //if ( m_pOuter->IsLocalPlayer() )
+ //{
+ // m_pOuter->EmitSound( "Weapon_Marked_for_Death.Indicator" );
+ //}
+ //else if ( !InCond( TF_COND_DISGUISED ) && !IsStealthed() )
+ //{
+ // m_pOuter->EmitSound( "Weapon_Marked_for_Death.Initial" );
+ //}
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveTranqMark( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateTranqMark( false );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddCondSpyClassSteal( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateSpyClassStealParticle( true );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveCondSpyClassSteal( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateSpyClassStealParticle( false );
+#endif
+}
+
+#endif // STAGING_ONLY
+
+//-----------------------------------------------------------------------------
+// Purpose: PARACHUTE
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddCondParachute( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->GetPredictable() && ( prediction->IsFirstTimePredicted() && !m_bSyncingConditions ) )
+ {
+ m_pOuter->EmitSound( "Parachute_open" );
+ }
+
+ if ( InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ if ( !m_hKartParachuteEntity )
+ {
+ C_BaseAnimating* pBanner = new C_BaseAnimating;
+ //if ( pBanner )
+ // return;
+
+ pBanner->m_nSkin = 0;
+ pBanner->InitializeAsClientEntity( "models/workshop/weapons/c_models/c_paratooper_pack/c_paratrooper_parachute.mdl", RENDER_GROUP_OPAQUE_ENTITY );
+ pBanner->ForceClientSideAnimationOn();
+ int iSpine = m_pOuter->LookupBone( "bip_spine_3" );
+ Assert( iSpine != -1 );
+ if ( iSpine != -1 )
+ {
+ pBanner->AttachEntityToBone( m_pOuter, iSpine );
+ }
+
+ int sequence = pBanner->SelectWeightedSequence( ACT_PARACHUTE_DEPLOY_IDLE );
+ pBanner->ResetSequence( sequence );
+ m_hKartParachuteEntity.Set( pBanner );
+ }
+ }
+ else
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "parachute_deploy" );
+ if ( event )
+ {
+ event->SetInt( "index", m_pOuter->entindex() );
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+#endif
+}
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveCondParachute( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->GetPredictable() && ( prediction->IsFirstTimePredicted() && !m_bSyncingConditions ) )
+ {
+ m_pOuter->EmitSound( "Parachute_close" );
+ }
+
+ if ( m_hKartParachuteEntity )
+ {
+ m_hKartParachuteEntity->Release();
+ m_hKartParachuteEntity = NULL;
+ }
+
+ if ( !InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "parachute_holster" );
+ if ( event )
+ {
+ event->SetInt( "index", m_pOuter->entindex() );
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddMadMilk( void )
+{
+#ifdef GAME_DLL
+ Assert( InCond( TF_COND_MAD_MILK ) );
+
+ // Check for the attribute that extends duration on successive hits
+ if ( m_ConditionData[TF_COND_MAD_MILK].m_bPrevActive )
+ {
+ CBaseEntity *pProvider = GetConditionProvider( TF_COND_MAD_MILK );
+ if ( pProvider )
+ {
+ int iMadMilkSyringes = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pProvider, iMadMilkSyringes, mad_milk_syringes );
+ if ( iMadMilkSyringes )
+ {
+ float flDuration = GetConditionDuration( TF_COND_MAD_MILK ) + 0.5f;
+ SetConditionDuration( TF_COND_MAD_MILK, Min( flDuration , 4.f ) );
+ }
+ }
+ }
+#else
+ if ( !m_pOuter->m_pMilkEffect )
+ {
+ m_pOuter->m_pMilkEffect = m_pOuter->ParticleProp()->Create( "peejar_drips_milk", PATTACH_ABSORIGIN_FOLLOW );
+ }
+
+ m_pOuter->ParticleProp()->AddControlPoint( m_pOuter->m_pMilkEffect, 1, m_pOuter, PATTACH_ABSORIGIN_FOLLOW );
+
+// if ( m_pOuter->IsLocalPlayer() )
+// {
+// IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_MILK, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+// if ( !IsErrorMaterial( pMaterial ) )
+// {
+// view->SetScreenOverlayMaterial( pMaterial );
+// }
+// }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveMadMilk( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pMilkEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pMilkEffect );
+ m_pOuter->m_pMilkEffect = NULL;
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+// IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+// if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_MILK ) )
+// {
+// view->SetScreenOverlayMaterial( NULL );
+// }
+ }
+#endif
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerShared::taunt_particle_state_t CTFPlayerShared::GetClientTauntParticleDesiredState() const
+{
+ const itemid_t unTauntSourceItemID = GetTauntSourceItemID();
+ if ( unTauntSourceItemID != INVALID_ITEM_ID )
+ {
+ CSteamID steamIDForPlayer;
+ m_pOuter->GetSteamID( &steamIDForPlayer );
+
+ CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( steamIDForPlayer.GetAccountID() );
+ CEconItemView *pTauntItem = pInventory ? pInventory->GetInventoryItemByItemID( unTauntSourceItemID ) : NULL;
+
+ if ( pTauntItem )
+ {
+ // do community_sparkle effect if this is a community item?
+ const int iQualityParticleType = pTauntItem->GetQualityParticleType();
+ if ( iQualityParticleType > 0 )
+ {
+ const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType );
+ if ( pParticleSystem )
+ {
+ return taunt_particle_state_t( pParticleSystem->pszSystemName, pParticleSystem->fRefireTime );
+ }
+ }
+
+ static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" );
+ uint32 unUnusualEffectIndex = 0;
+ if ( pTauntItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
+ {
+ const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
+ if ( pParticleSystem )
+ {
+ // TF Team Color Particles
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ) )
+ {
+ static char pBlue[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
+ }
+ else if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_blue" ) )
+ {
+ // Guard against accidentally giving out the blue team color (support tool)
+ static char pRed[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pRed );
+ }
+
+ return taunt_particle_state_t( pParticleSystem->pszSystemName, pParticleSystem->fRefireTime );
+ }
+ }
+
+ for ( int i=0; i<m_pOuter->GetNumWearables(); ++i )
+ {
+ C_EconWearable *pWearable = m_pOuter->GetWearable( i );
+ CEconItemView *pItem = pWearable && pWearable->GetAttributeContainer() && pWearable->GetAttributeContainer()->GetItem() ? pWearable->GetAttributeContainer()->GetItem() : NULL;
+
+ // check for Unusual Cap def index (1173)
+ if ( pItem && pItem->GetItemDefIndex() == 1173 && pItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
+ {
+ const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
+ if ( pParticleSystem )
+ {
+ // TF Team Color Particles
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ) )
+ {
+ static char pBlue[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
+ }
+ else if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_blue" ) )
+ {
+ // Guard against accidentally giving out the blue team color (support tool)
+ static char pRed[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pRed );
+ }
+
+ return taunt_particle_state_t( pParticleSystem->pszSystemName, pParticleSystem->fRefireTime );
+ }
+ }
+ }
+ }
+ }
+
+ return taunt_particle_state_t( NULL, 0.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::FireClientTauntParticleEffects()
+{
+ taunt_particle_state_t TauntParticleState = GetClientTauntParticleDesiredState();
+ if ( TauntParticleState.first )
+ {
+ if ( m_pOuter->m_pTauntEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pTauntEffect );
+ }
+
+ if ( !m_pOuter->GetTauntEconItemView() )
+ return;
+
+ if ( !m_pOuter->GetTauntEconItemView()->GetStaticData() )
+ return;
+
+ if ( !m_pOuter->GetTauntEconItemView()->GetStaticData()->GetTauntData() )
+ return;
+
+ const char *pszAttachment = m_pOuter->GetTauntEconItemView()->GetStaticData()->GetTauntData()->GetParticleAttachment();
+ int iAttachment = pszAttachment ? m_pOuter->LookupAttachment( pszAttachment ) : INVALID_PARTICLE_ATTACHMENT;
+ m_pOuter->m_pTauntEffect = m_pOuter->ParticleProp()->Create( TauntParticleState.first, iAttachment != INVALID_PARTICLE_ATTACHMENT ? PATTACH_POINT_FOLLOW : PATTACH_ABSORIGIN_FOLLOW, iAttachment, vec3_origin );
+
+ if ( TauntParticleState.second > 0.0f )
+ {
+ m_flTauntParticleRefireTime = gpGlobals->curtime + TauntParticleState.second;
+ }
+ }
+}
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddTaunting( void )
+{
+ CTFWeaponBase *pWpn = m_pOuter->GetActiveTFWeapon();
+ if ( pWpn )
+ {
+ // cancel any reload in progress.
+ pWpn->AbortReload();
+
+ // Check for taunt healing.
+ if ( GetTauntIndex() == TAUNT_BASE_WEAPON )
+ {
+ int iAOEHeal = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWpn, iAOEHeal, enables_aoe_heal );
+ if ( iAOEHeal == 1 )
+ {
+ Heal_Radius( true );
+ }
+ }
+ }
+
+ // Unzoom if we are a sniper zoomed!
+ InstantlySniperUnzoom();
+
+ if ( ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) ) && GetTauntIndex() == TAUNT_BASE_WEAPON )
+ {
+ CTFLunchBox *pLunchBox = dynamic_cast <CTFLunchBox *> ( pWpn );
+ if ( pLunchBox )
+ {
+ pLunchBox->DrainAmmo();
+ }
+ }
+
+#ifdef GAME_DLL
+ m_pOuter->PlayWearableAnimsForPlaybackEvent( WAP_START_TAUNTING );
+#else
+ FireClientTauntParticleEffects();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveTaunting( void )
+{
+#ifdef GAME_DLL
+#ifdef STAGING_ONLY
+ if ( !m_pOuter->m_hTauntScene.Get() )
+ {
+ Warning( "Why do we not have taunt scene?\n" );
+ }
+#endif
+
+ m_pOuter->StopTaunt();
+
+ if ( IsControlStunned() )
+ {
+ m_pOuter->SetAbsAngles( m_pOuter->m_angTauntCamera );
+ m_pOuter->SetLocalAngles( m_pOuter->m_angTauntCamera );
+ }
+#endif // GAME_DLL
+
+ // Stop aoe healing if it's active.
+ Heal_Radius( false );
+
+ // We're done taunting, our weapons are not being repurposed anymore
+ for ( int i = 0; i < m_pOuter->WeaponCount(); i++)
+ {
+ CTFWeaponBase *pWpn = ( CTFWeaponBase *) m_pOuter->GetWeapon(i);
+ if ( !pWpn )
+ continue;
+
+ pWpn->SetIsBeingRepurposedForTaunt( false );
+ }
+
+#ifdef GAME_DLL
+ // Switch to our melee weapon, if we are at the end of a type 2 lunchbox taunt.
+ if ( m_bBiteEffectWasApplied && InCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ) )
+ {
+ CBaseCombatWeapon *pWpn = m_pOuter->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
+ if ( pWpn )
+ {
+ m_pOuter->Weapon_Switch( pWpn );
+ }
+ else
+ {
+ // Safety net
+ RemoveCond( TF_COND_ENERGY_BUFF );
+ RemoveCond( TF_COND_CANNOT_SWITCH_FROM_MELEE );
+ }
+ }
+
+ m_bBiteEffectWasApplied = false;
+
+ if ( m_pOuter->m_hTauntItem != NULL )
+ {
+ // destroy the item we were showing off
+ UTIL_Remove( m_pOuter->m_hTauntItem );
+ m_pOuter->m_hTauntItem = NULL;
+ }
+
+ m_pOuter->ClearTauntAttack();
+
+ m_pOuter->PlayWearableAnimsForPlaybackEvent( WAP_STOP_TAUNTING );
+
+ m_pOuter->HandleWeaponSlotAfterTaunt();
+#else
+ CSteamID steamIDForPlayer;
+ m_pOuter->GetSteamID( &steamIDForPlayer );
+
+ int nMapDonationAmount = MapInfo_GetDonationAmount( steamIDForPlayer.GetAccountID(), engine->GetLevelName() );
+ m_pOuter->SetFootStamps( nMapDonationAmount );
+
+ if ( m_pOuter->m_pTauntEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmissionAndDestroyImmediately( m_pOuter->m_pTauntEffect );
+ m_pOuter->m_pTauntEffect = NULL;
+ }
+
+ m_flTauntParticleRefireTime = 0.0f;
+#endif
+
+ m_pOuter->m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
+
+ // when we stop taunting, make sure active weapon is visible
+ if ( m_pOuter->GetActiveWeapon() )
+ {
+ m_pOuter->GetActiveWeapon()->SetWeaponVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddBleeding( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_BLEED, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#else
+ // We should have at least one bleed entry
+ Assert( m_PlayerBleeds.Count() );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveBleeding( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_BLEED ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#else
+ m_PlayerBleeds.RemoveAll();
+#endif
+}
+
+const char* CTFPlayerShared::GetSoldierBuffEffectName( void )
+{
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ // MVM robot version has fewer particles. Helps keep the framerate up.
+ return "soldierbuff_mvm";
+ }
+ else
+ {
+ return "soldierbuff_red_soldier";
+ }
+ }
+ else
+ {
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ return "soldierbuff_blue_soldier";
+ }
+ else
+ {
+ return "soldierbuff_red_soldier";
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddSoldierOffensiveBuff( void )
+{
+#ifdef CLIENT_DLL
+ const char* strBuffName = GetSoldierBuffEffectName();
+
+ if ( !m_pOuter->m_pSoldierOffensiveBuffEffect )
+ {
+ m_pOuter->m_pSoldierOffensiveBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveSoldierOffensiveBuff( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pSoldierOffensiveBuffEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierOffensiveBuffEffect );
+ m_pOuter->m_pSoldierOffensiveBuffEffect = NULL;
+ }
+#endif
+}
+
+void CTFPlayerShared::OnAddSoldierDefensiveBuff( void )
+{
+#ifdef CLIENT_DLL
+ const char* strBuffName = GetSoldierBuffEffectName();
+
+ if ( !m_pOuter->m_pSoldierDefensiveBuffEffect )
+ {
+ m_pOuter->m_pSoldierDefensiveBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveSoldierDefensiveBuff( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pSoldierDefensiveBuffEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierDefensiveBuffEffect );
+ m_pOuter->m_pSoldierDefensiveBuffEffect = NULL;
+ }
+#endif
+}
+
+void CTFPlayerShared::OnAddSoldierOffensiveHealthRegenBuff( void )
+{
+#ifdef GAME_DLL
+ AddCond( TF_COND_SPEED_BOOST );
+#else
+ const char* strBuffName = GetSoldierBuffEffectName();
+
+ if ( !m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect )
+ {
+ m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveSoldierOffensiveHealthRegenBuff( void )
+{
+#ifdef GAME_DLL
+ RemoveCond( TF_COND_SPEED_BOOST );
+#else
+ if ( m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect );
+ m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect = NULL;
+ }
+#endif
+}
+
+void CTFPlayerShared::OnAddSoldierNoHealingDamageBuff( void )
+{
+#ifdef CLIENT_DLL
+ const char* strBuffName = GetSoldierBuffEffectName();
+
+ if ( !m_pOuter->m_pSoldierNoHealingDamageBuffEffect )
+ {
+ m_pOuter->m_pSoldierNoHealingDamageBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveSoldierNoHealingDamageBuff( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pSoldierNoHealingDamageBuffEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierNoHealingDamageBuffEffect );
+ m_pOuter->m_pSoldierNoHealingDamageBuffEffect = NULL;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddOffenseBuff( void )
+{
+ OnAddSoldierOffensiveBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveOffenseBuff( void )
+{
+ OnRemoveSoldierOffensiveBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddDefenseBuff( void )
+{
+ OnAddSoldierDefensiveBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveDefenseBuff( void )
+{
+ OnRemoveSoldierDefensiveBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddOffenseHealthRegenBuff( void )
+{
+ OnAddSoldierOffensiveHealthRegenBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveOffenseHealthRegenBuff( void )
+{
+ OnRemoveSoldierOffensiveHealthRegenBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddNoHealingDamageBuff( void )
+{
+ OnAddSoldierNoHealingDamageBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveNoHealingDamageBuff( void )
+{
+ OnRemoveSoldierNoHealingDamageBuff();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddSpeedBoost( bool IsNonCombat )
+{
+#ifdef CLIENT_DLL
+ const char* strBuffName = "speed_boost_trail";
+
+ if ( !m_pOuter->m_pSpeedBoostEffect )
+ {
+ // No speedlines at all for stealth or feign death
+ if ( !InCond( TF_COND_STEALTHED ) && !InCond(TF_COND_FEIGN_DEATH) )
+ {
+ m_pOuter->m_pSpeedBoostEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+ }
+
+ // InCombat is played on the teleporter for all players to here
+ // "Building_Speedpad.BoostStart"
+ if ( !IsNonCombat && m_pOuter->IsLocalPlayer())
+ {
+ m_pOuter->EmitSound( "DisciplineDevice.PowerUp" );
+ }
+#else // !CLIENT_DLL
+ m_pOuter->TeamFortress_SetSpeed();
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveSpeedBoost( bool IsNonCombat )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pSpeedBoostEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSpeedBoostEffect );
+ m_pOuter->m_pSpeedBoostEffect = NULL;
+ }
+
+ if ( !IsNonCombat && m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->EmitSound( "DisciplineDevice.PowerDown" );
+ }
+ else
+ {
+ m_pOuter->EmitSound( "Building_Speedpad.BoostStop" );
+ }
+#else // !CLIENT_DLL
+ m_pOuter->TeamFortress_SetSpeed();
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Applied to bots
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddSapped( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->m_pSappedPlayerEffect )
+ {
+ const char* szParticle = "sapper_sentry1_fx";
+ m_pOuter->m_pSappedPlayerEffect = m_pOuter->ParticleProp()->Create( szParticle, PATTACH_POINT_FOLLOW, "head" );
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveSapped( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pSappedPlayerEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSappedPlayerEffect );
+ m_pOuter->m_pSappedPlayerEffect = NULL;
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Applied to bots
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddReprogrammed( void )
+{
+#ifdef STAGING_ONLY
+#ifdef GAME_DLL
+ CTFBot *pBot = ToTFBot( m_pOuter );
+ if ( pBot )
+ {
+ pBot->ChangeTeam( GetEnemyTeam( pBot->GetTeamNumber() ), false, true );
+ pBot->SetMission( CTFBot::MISSION_REPROGRAMMED );
+ pBot->Update();
+ pBot->MarkAsMissionEnemy();
+ }
+#endif // GAME_DLL
+#endif // STAGING_ONLY
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveReprogrammed( void )
+{
+}
+
+void CTFPlayerShared::OnAddDisguisedAsDispenser( void )
+{
+ m_pOuter->TeamFortress_SetSpeed();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddHalloweenBombHead( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->HalloweenBombHeadUpdate();
+ m_pOuter->CreateBombonomiconHint();
+#else
+ if ( InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ RemoveAttributeFromPlayer( "head scale" );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveHalloweenBombHead( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->HalloweenBombHeadUpdate();
+ m_pOuter->DestroyBombonomiconHint();
+#else
+ if ( InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ ApplyAttributeToPlayer( "head scale", 3.f );
+ }
+
+ if ( m_pOuter->IsAlive() )
+ {
+ m_pOuter->MerasmusPlayerBombExplode( false );
+
+ Vector vecOrigin = m_pOuter->GetAbsOrigin();
+ // explode has a small force but we want to increase it
+ if ( InCond ( TF_COND_HALLOWEEN_KART ) )
+ {
+ if ( !m_pOuter->GetKartBombHeadTarget() )
+ {
+ m_pOuter->AddHalloweenKartPushEvent( m_pOuter, NULL, NULL, Vector( 0, 0, 100 ), 50 );
+ }
+ m_pOuter->SetKartBombHeadTarget( NULL );
+ }
+ else if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
+ {
+ TFGameRules()->PushAllPlayersAway( vecOrigin, 150, 400, TEAM_ANY );
+ }
+
+ // Particle
+ CPVSFilter filter( vecOrigin );
+ TE_TFParticleEffect( filter, 0.0, "bombinomicon_burningdebris", vecOrigin, vec3_angle );
+ }
+#endif // GAME_DLL
+}
+
+void CTFPlayerShared::OnAddHalloweenThriller( void )
+{
+#ifdef CLIENT_DLL
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ if ( pLocalPlayer == m_pOuter )
+ {
+ m_pOuter->EmitSound( "Halloween.dance_howl" );
+ m_pOuter->EmitSound( "Halloween.dance_loop" );
+ }
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveHalloweenThriller( void )
+{
+#ifdef CLIENT_DLL
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ if ( pLocalPlayer == m_pOuter )
+ {
+ m_pOuter->StopSound( "Halloween.dance_loop" );
+ }
+ }
+#else
+ // If this is hightower, players will be healing themselves while dancing
+ StopHealing( m_pOuter );
+#endif
+}
+
+void CTFPlayerShared::OnAddRadiusHealOnDamage( void )
+{
+ Heal_Radius( true );
+}
+
+void CTFPlayerShared::OnRemoveRadiusHealOnDamage( void )
+{
+ Heal_Radius( false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddMarkedForDeathSilent( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdatedMarkedForDeathEffect();
+#endif
+}
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveMarkedForDeathSilent( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdatedMarkedForDeathEffect();
+#endif
+}
+
+void CTFPlayerShared::OnRemoveDisguisedAsDispenser( void )
+{
+ m_pOuter->TeamFortress_SetSpeed();
+}
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddRocketPack( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->m_pRocketPackEffect )
+ {
+ const char* szParticle = "rocketbackblast";
+ m_pOuter->m_pRocketPackEffect = m_pOuter->ParticleProp()->Create( szParticle, PATTACH_POINT_FOLLOW, "flag" );
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveRocketPack( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pRocketPackEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRocketPackEffect );
+ m_pOuter->m_pRocketPackEffect = NULL;
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateRocketPack( void )
+{
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_PYRO ) )
+ return;
+
+ if ( InCond( TF_COND_ROCKETPACK ) )
+ {
+#ifdef GAME_DLL
+ // Check for landing
+ if ( m_pOuter->GetFlags() & FL_ONGROUND )
+ {
+ RemoveCond( TF_COND_ROCKETPACK );
+ }
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ApplyRocketPackStun( float flStunDuration )
+{
+ if ( flStunDuration < 0.1f )
+ return;
+
+#ifdef GAME_DLL
+ const int nMaxEnts = 24;
+ CBaseEntity *pObjects[ nMaxEnts ];
+ int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, m_pOuter->GetAbsOrigin(), 192.f, FL_CLIENT );
+ for ( int i = 0; i < nCount; i++ )
+ {
+ if ( !pObjects[i] )
+ continue;
+
+ if ( !pObjects[i]->IsAlive() )
+ continue;
+
+ if ( m_pOuter->InSameTeam( pObjects[i] ) )
+ continue;
+
+ if ( !m_pOuter->FVisible( pObjects[i], MASK_OPAQUE ) )
+ continue;
+
+ CTFPlayer *pTFPlayer = static_cast< CTFPlayer* >( pObjects[i] );
+ if ( !pTFPlayer )
+ continue;
+
+ pTFPlayer->m_Shared.StunPlayer( flStunDuration, 0.75f, TF_STUN_CONTROLS );
+ }
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::CanBuildSpyTraps( void )
+{
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
+ return false;
+
+ if ( m_pOuter->IsBot() )
+ return false;
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ return true;
+
+ int iTraps = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iTraps, ability_spy_traps );
+
+ return ( iTraps > 0 );
+}
+#endif // STAGING_ONLY
+
+#ifdef CLIENT_DLL
+static void AddUberScreenEffect( const CTFPlayer* pPlayer )
+{
+ // Add the uber effect onto the local player's screen
+ if ( pPlayer && pPlayer->IsLocalPlayer() )
+ {
+ const char *pEffectName = NULL;
+ if ( pPlayer->GetTeamNumber() == TF_TEAM_RED )
+ {
+ pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED;
+ }
+ else
+ {
+ pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE;
+ }
+
+ IMaterial *pMaterial = materials->FindMaterial( pEffectName, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+}
+
+static void RemoveUberScreenEffect( const CTFPlayer* pPlayer )
+{
+ if ( pPlayer && pPlayer->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is an invuln material
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial &&
+ ( FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE ) ||
+ FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED ) ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+}
+
+static const char* s_pszRedResistOverheadEffectName[] =
+{
+ "vaccinator_red_buff1",
+ "vaccinator_red_buff2",
+ "vaccinator_red_buff3",
+};
+static const char* s_pszBlueResistOverheadEffectName[] =
+{
+ "vaccinator_blue_buff1",
+ "vaccinator_blue_buff2",
+ "vaccinator_blue_buff3",
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszRedResistOverheadEffectName ) == MEDIGUN_NUM_RESISTS && ARRAYSIZE( s_pszBlueResistOverheadEffectName ) == MEDIGUN_NUM_RESISTS );
+
+static void AddResistParticle( CTFPlayer* pPlayer, medigun_resist_types_t nResistType, ETFCond eYeildToCond = TF_COND_LAST )
+{
+ // Don't spawn it over the local player's head
+ if ( !pPlayer || pPlayer->IsLocalPlayer() )
+ return;
+
+ // do not add if stealthed
+ if ( pPlayer->m_Shared.IsStealthed() )
+ return;
+
+ // Don't add this effect if the yield effect is passed in
+ if( eYeildToCond != TF_COND_LAST && pPlayer->m_Shared.InCond( eYeildToCond ) )
+ return;
+
+ if ( pPlayer->m_Shared.GetDisplayedTeam() == TF_TEAM_RED )
+ {
+ pPlayer->AddOverheadEffect( s_pszRedResistOverheadEffectName[ nResistType ] );
+ }
+ else
+ {
+ pPlayer->AddOverheadEffect( s_pszBlueResistOverheadEffectName[ nResistType ] );
+ }
+}
+
+static void RemoveResistParticle( CTFPlayer* pPlayer, medigun_resist_types_t nResistType )
+{
+ if ( !pPlayer || pPlayer->IsLocalPlayer() )
+ return;
+
+ bool bKeep = false;
+ switch ( nResistType )
+ {
+ case MEDIGUN_BULLET_RESIST:
+ bKeep = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST );
+ break;
+ case MEDIGUN_BLAST_RESIST:
+ bKeep = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST );
+ break;
+ case MEDIGUN_FIRE_RESIST:
+ bKeep = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST );
+ break;
+ default:
+ AssertMsg( 0, "Invalid medigun resist type" );
+ break;
+ }
+
+ // don't remove overhead effect if the uber's still active
+ if ( bKeep )
+ return;
+
+ if ( pPlayer->m_Shared.GetDisplayedTeam() == TF_TEAM_RED )
+ {
+ pPlayer->RemoveOverheadEffect( s_pszRedResistOverheadEffectName[ nResistType ], true );
+ }
+ else
+ {
+ pPlayer->RemoveOverheadEffect( s_pszBlueResistOverheadEffectName[ nResistType ], true );
+ }
+}
+
+static int GetResistShieldSkinForResistType( ETFCond eCond )
+{
+ switch( eCond )
+ {
+ case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
+ return 2;
+
+ case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
+ return 3;
+
+ case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
+ return 4;
+
+ default:
+ AssertMsg( 0, "Invalid condition passed into AddResistShield" );
+ return 0;
+ }
+}
+
+static void AddResistShield( C_LocalTempEntity** pShield, CTFPlayer* pPlayer, ETFCond eCond )
+{
+ if( CBasePlayer::GetLocalPlayer() == pPlayer )
+ return;
+
+ // do not add if stealthed
+ if ( pPlayer->m_Shared.IsStealthed() )
+ return;
+
+ // Don't create a new shield if we already have one
+ if( *pShield )
+ return;
+
+ model_t *pModel = (model_t*) engine->LoadModel( "models/effects/resist_shield/resist_shield.mdl" );
+ (*pShield) = tempents->SpawnTempModel( pModel, pPlayer->GetAbsOrigin(), pPlayer->GetAbsAngles(), Vector(0, 0, 0), 1, FTENT_NEVERDIE | FTENT_PLYRATTACHMENT );
+ if ( *pShield )
+ {
+ (*pShield)->ChangeTeam( pPlayer->m_Shared.GetDisplayedTeam() );
+ if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ (*pShield)->m_nSkin = GetResistShieldSkinForResistType( eCond );
+ }
+ else
+ {
+ (*pShield)->m_nSkin = ( pPlayer->m_Shared.GetDisplayedTeam() == TF_TEAM_RED ) ? 0 : 1;
+ }
+ (*pShield)->clientIndex = pPlayer->entindex();
+ (*pShield)->SetModelScale( pPlayer->GetModelScale() );
+ }
+}
+
+static void RemoveResistShield( C_LocalTempEntity** pShield, CTFPlayer* pPlayer )
+{
+ if ( *pShield )
+ {
+ ETFCond eCond = TF_COND_INVALID;
+ // Check if we still have one of the other resist types on us
+ eCond = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST ) ? TF_COND_MEDIGUN_UBER_BULLET_RESIST : eCond;
+ eCond = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST ) ? TF_COND_MEDIGUN_UBER_BLAST_RESIST : eCond;
+ eCond = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST ) ? TF_COND_MEDIGUN_UBER_FIRE_RESIST : eCond;
+ eCond = ( pPlayer->m_Shared.InCond( TF_COND_RUNE_RESIST ) && !pPlayer->m_Shared.IsStealthed() ) ? TF_COND_RUNE_RESIST : eCond;
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer )
+ {
+ eCond = ( pPlayer->IsEnemyPlayer() && pPlayer->m_Shared.InCond( TF_COND_RUNE_PLAGUE ) && pLocalPlayer->m_Shared.InCond( TF_COND_PLAGUE ) ) ? TF_COND_RUNE_PLAGUE : eCond;
+ }
+
+ // Still have one, don't remove the shield
+ if( eCond != TF_COND_INVALID )
+ {
+ // If we're in MvM, and we're one of the bots, change the shield color
+ if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ (*pShield)->m_nSkin = GetResistShieldSkinForResistType( eCond );
+ }
+
+ return;
+ }
+ else // No more bubble
+ {
+ (*pShield)->flags = FTENT_FADEOUT | FTENT_PLYRATTACHMENT;
+ (*pShield)->die = gpGlobals->curtime;
+ (*pShield)->fadeSpeed = 1.0f;
+ (*pShield) = NULL;
+ }
+ }
+}
+#endif // CLIENT_DLL
+
+void CTFPlayerShared::OnAddMedEffectUberBulletResist( void )
+{
+#ifdef CLIENT_DLL
+ AddResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
+ AddUberScreenEffect( m_pOuter );
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BULLET_RESIST );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveMedEffectUberBulletResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
+ RemoveUberScreenEffect( m_pOuter );
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+ OnAddMedEffectSmallBulletResist();
+#endif
+}
+
+
+void CTFPlayerShared::OnAddMedEffectUberBlastResist( void )
+{
+#ifdef CLIENT_DLL
+ AddResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
+ AddUberScreenEffect( m_pOuter );
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BLAST_RESIST );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveMedEffectUberBlastResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
+ RemoveUberScreenEffect( m_pOuter );
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+ OnAddMedEffectSmallBlastResist();
+#endif
+}
+
+void CTFPlayerShared::OnAddMedEffectUberFireResist( void )
+{
+#ifdef CLIENT_DLL
+ AddResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
+ AddUberScreenEffect( m_pOuter );
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_FIRE_RESIST );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveMedEffectUberFireResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
+ RemoveUberScreenEffect( m_pOuter );
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+ OnAddMedEffectSmallFireResist();
+#endif
+}
+
+void CTFPlayerShared::OnAddMedEffectSmallBulletResist( void )
+{
+#ifdef CLIENT_DLL
+ if( InCond( TF_COND_MEDIGUN_SMALL_BULLET_RESIST ) )
+ {
+ AddResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST, TF_COND_MEDIGUN_UBER_BULLET_RESIST );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveMedEffectSmallBulletResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
+#endif
+}
+
+
+void CTFPlayerShared::OnAddMedEffectSmallBlastResist( void )
+{
+#ifdef CLIENT_DLL
+ if( InCond( TF_COND_MEDIGUN_SMALL_BLAST_RESIST ) )
+ {
+ AddResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST, TF_COND_MEDIGUN_UBER_BLAST_RESIST );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveMedEffectSmallBlastResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
+#endif
+}
+
+void CTFPlayerShared::OnAddMedEffectSmallFireResist( void )
+{
+#ifdef CLIENT_DLL
+ if( InCond( TF_COND_MEDIGUN_SMALL_FIRE_RESIST ) )
+ {
+ AddResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST, TF_COND_MEDIGUN_UBER_FIRE_RESIST );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveMedEffectSmallFireResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
+#endif
+}
+
+void CTFPlayerShared::OnAddRuneResist( void )
+{
+#ifdef CLIENT_DLL
+ // Do use the condition bit here, it's passed along and is expected to be a cond.
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_RUNE_RESIST );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveRuneResist( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveRuneKing( void )
+{
+#ifdef CLIENT_DLL
+ EndKingBuffRadiusEffect();
+#endif
+}
+
+void CTFPlayerShared::OnAddGrapplingHookLatched( void )
+{
+ m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_GRAPPLE_PULL_START );
+}
+
+
+void CTFPlayerShared::OnRemoveGrapplingHookLatched( void )
+{
+ // DO NOTHING
+}
+
+
+void CTFPlayerShared::OnAddBulletImmune( void )
+{
+#ifdef CLIENT_DLL
+ AddResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
+ AddUberScreenEffect( m_pOuter );
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BULLET_RESIST );
+#endif
+}
+
+
+void CTFPlayerShared::OnRemoveBulletImmune( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
+ RemoveUberScreenEffect( m_pOuter );
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+#endif
+}
+
+void CTFPlayerShared::OnAddBlastImmune( void )
+{
+#ifdef CLIENT_DLL
+ AddResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
+ AddUberScreenEffect( m_pOuter );
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BLAST_RESIST );
+#endif
+}
+
+
+void CTFPlayerShared::OnRemoveBlastImmune( void )
+{
+#ifdef CLIENT_DLL
+ RemoveResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
+ RemoveUberScreenEffect( m_pOuter );
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+#endif
+}
+
+void CTFPlayerShared::OnAddFireImmune( void )
+{
+#ifdef CLIENT_DLL
+ AddUberScreenEffect( m_pOuter );
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ AddResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_FIRE_RESIST );
+ }
+#endif
+}
+
+
+void CTFPlayerShared::OnRemoveFireImmune( void )
+{
+#ifdef CLIENT_DLL
+ RemoveUberScreenEffect( m_pOuter );
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ RemoveResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+ }
+#endif
+}
+
+
+void CTFPlayerShared::OnAddMVMBotRadiowave( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->IsABot() )
+ return;
+
+ if ( !m_pOuter->m_pMVMBotRadiowave )
+ {
+ m_pOuter->m_pMVMBotRadiowave = m_pOuter->ParticleProp()->Create( "bot_radio_waves", PATTACH_POINT_FOLLOW, "head" );
+ }
+#else
+ if ( !m_pOuter->IsBot() )
+ return;
+
+ StunPlayer( GetConditionDuration( TF_COND_MVM_BOT_STUN_RADIOWAVE ), 1.0, TF_STUN_BOTH | TF_STUN_NO_EFFECTS );
+#endif
+}
+
+
+void CTFPlayerShared::OnRemoveMVMBotRadiowave( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->IsABot() )
+ return;
+
+ if ( m_pOuter->m_pMVMBotRadiowave )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pMVMBotRadiowave );
+ m_pOuter->m_pMVMBotRadiowave = NULL;
+ }
+#endif
+}
+
+
+void CTFPlayerShared::OnAddHalloweenSpeedBoost( void )
+{
+#ifdef GAME_DLL
+ AddCond( TF_COND_SPEED_BOOST );
+ ApplyAttributeToPlayer( "halloween reload time decreased", 0.5f );
+ ApplyAttributeToPlayer( "halloween fire rate bonus", 0.5f );
+ ApplyAttributeToPlayer( "halloween increased jump height", 1.5f );
+ //
+#endif
+}
+
+void CTFPlayerShared::OnRemoveHalloweenSpeedBoost( void )
+{
+#ifdef GAME_DLL
+ RemoveCond( TF_COND_SPEED_BOOST );
+ RemoveAttributeFromPlayer( "halloween reload time decreased" );
+ RemoveAttributeFromPlayer( "halloween fire rate bonus" );
+ RemoveAttributeFromPlayer( "halloween increased jump height" );
+#endif
+}
+
+void CTFPlayerShared::OnAddHalloweenQuickHeal( void )
+{
+#ifdef GAME_DLL
+ AddCond( TF_COND_MEGAHEAL );
+ Heal( m_pOuter, 30.0f, 2.0f, 1.0f );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveHalloweenQuickHeal( void )
+{
+#ifdef GAME_DLL
+ RemoveCond( TF_COND_MEGAHEAL );
+ StopHealing( m_pOuter );
+#endif
+}
+
+void CTFPlayerShared::OnAddHalloweenGiant( void )
+{
+#ifdef GAME_DLL
+ m_pOuter->SetModelScale( 2.f );
+ int nNewHP = tf_halloween_giant_health_scale.GetFloat() * m_pOuter->GetPlayerClass()->GetMaxHealth();
+ m_pOuter->SetHealth( nNewHP );
+ m_pOuter->SetMaxHealth( nNewHP );
+#else
+ cam_idealdist.SetValue( 300.f );
+ cam_idealdistright.SetValue( 40.f );
+#endif
+}
+
+void CTFPlayerShared::OnRemoveHalloweenGiant( void )
+{
+#ifdef GAME_DLL
+ m_pOuter->SetModelScale( 1.f );
+ int nNewHP = m_pOuter->GetPlayerClass()->GetMaxHealth();
+ m_pOuter->SetHealth( nNewHP );
+ m_pOuter->SetMaxHealth( nNewHP );
+#else
+ cam_idealdist.SetValue( cam_idealdist.GetDefault() );
+ cam_idealdistright.SetValue( cam_idealdistright.GetDefault() );
+#endif
+}
+
+void CTFPlayerShared::OnAddHalloweenTiny( void )
+{
+#ifdef GAME_DLL
+ m_pOuter->SetModelScale( 0.5f );
+ ApplyAttributeToPlayer( "voice pitch scale", 1.3f );
+ ApplyAttributeToPlayer( "head scale", 3.f );
+#endif
+}
+
+void CTFPlayerShared::OnAddHalloweenGhostMode( void )
+{
+ m_pOuter->SetGroundEntity( NULL );
+ m_pOuter->SetSolid( SOLID_NONE );
+ m_pOuter->SetSolidFlags( FSOLID_NOT_SOLID );
+ m_pOuter->AddFlag( FL_NOTARGET );
+
+#ifdef GAME_DLL
+
+ CSingleUserRecipientFilter filter( m_pOuter );
+ if ( TFGameRules() )
+ {
+ if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_HOW_TO_CONTROL_GHOST );
+ }
+ else
+ {
+ TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_HOW_TO_CONTROL_GHOST_NO_RESPAWN );
+ }
+ }
+
+ // The game rules listens for this event
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_turned_to_ghost" );
+
+ if ( event )
+ {
+ event->SetInt( "userid", m_pOuter->GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // Push them up a little bit
+ Vector vecNewVel( 0, 0, 40 );
+ m_pOuter->Teleport( NULL, NULL, &vecNewVel );
+
+ if ( m_pOuter->GetActiveWeapon() )
+ {
+ m_pOuter->GetActiveWeapon()->SendViewModelAnim( ACT_IDLE );
+ m_pOuter->GetActiveWeapon()->Holster();
+ }
+ m_pOuter->SetActiveWeapon( NULL );
+
+ CBaseObject * pCarriedObj = GetCarriedObject();
+ if ( pCarriedObj )
+ {
+ pCarriedObj->DetonateObject();
+ }
+
+ CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
+ if ( pSpellBook )
+ {
+ pSpellBook->ClearSpell();
+ }
+#else
+ // Go thirdperson
+ SetAppropriateCamera( m_pOuter );
+
+ Color color;
+ m_pOuter->GetTeamColor( color );
+ m_pOuter->SetRenderColor( color.r(), color.g(), color.b() );
+#endif
+}
+
+void CTFPlayerShared::OnAddHalloweenKartDash()
+{
+ m_pOuter->SetFOV( m_pOuter, 110.f, 1.f, 0.f );
+ m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_KART_ACTION_DASH );
+
+#ifdef CLIENT_DLL
+
+#else // CLIENT_DLL
+ m_pOuter->EmitSound( "BumperCar.SpeedBoostStart" );
+#endif // GAME_DLL
+}
+
+void CTFPlayerShared::OnRemoveHalloweenKartDash()
+{
+ m_pOuter->SetFOV( m_pOuter, 0.f, 1.f, 0.f );
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pSpeedBoostEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSpeedBoostEffect );
+ m_pOuter->m_pSpeedBoostEffect = NULL;
+ }
+#else // CLIENT_DLL
+ m_pOuter->EmitSound( "BumperCar.SpeedBoostStop" );
+#endif // GAME_DLL
+}
+
+void CTFPlayerShared::OnRemoveHalloweenTiny( void )
+{
+#ifdef GAME_DLL
+ m_pOuter->SetModelScale( 1.f );
+
+ RemoveAttributeFromPlayer( "voice pitch scale" );
+ RemoveAttributeFromPlayer( "head scale" );
+
+ const Vector& vOrigin = m_pOuter->GetAbsOrigin();
+ const QAngle& qAngle = m_pOuter->GetAbsAngles();
+ const Vector& vHullMins = m_pOuter->GetFlags() & FL_DUCKING ? VEC_DUCK_HULL_MIN : VEC_HULL_MIN;
+ const Vector& vHullMaxs = m_pOuter->GetFlags() & FL_DUCKING ? VEC_DUCK_HULL_MAX : VEC_HULL_MAX;
+
+ trace_t result;
+ CTraceFilterIgnoreTeammates filter( m_pOuter, COLLISION_GROUP_NONE, m_pOuter->GetTeamNumber() );
+ UTIL_TraceHull( vOrigin, vOrigin, vHullMins, vHullMaxs, MASK_PLAYERSOLID, &filter, &result );
+ // am I stuck? try to resolve it
+ if ( result.DidHit() )
+ {
+ float flPlayerHeight = vHullMaxs.z - vHullMins.z;
+ float flExtraHeight = 10;
+ static Vector vTest[] =
+ {
+ Vector( 32, 32, flExtraHeight ),
+ Vector( -32, -32, flExtraHeight ),
+ Vector( -32, 32, flExtraHeight ),
+ Vector( 32, -32, flExtraHeight ),
+ Vector( 0, 0, flPlayerHeight + flExtraHeight ),
+ Vector( 0, 0, -flPlayerHeight - flExtraHeight )
+ };
+ for ( int i=0; i<ARRAYSIZE( vTest ); ++i )
+ {
+ Vector vTestPos = vOrigin + vTest[i];
+ UTIL_TraceHull( vOrigin, vTestPos, vHullMins, vHullMaxs, MASK_PLAYERSOLID, &filter, &result );
+ if ( !result.DidHit() )
+ {
+ //NDebugOverlay::Box( vTestPos, vHullMins, vHullMaxs, 0, 255, 0, 0, 5.f );
+ m_pOuter->Teleport( &vTestPos, &qAngle, NULL );
+ return;
+ }
+ else
+ {
+ //NDebugOverlay::Box( vTestPos, vHullMins, vHullMaxs, 255, 0, 0, 0, 5.f );
+ }
+ }
+
+ // just kill the player if we can't resolve getting stuck
+ m_pOuter->CommitSuicide( false, true );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveHalloweenGhostMode( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->ParticleProp()->StopEmission();
+ m_pOuter->SetRenderColor( 255, 255, 255 );
+ m_pOuter->UpdateWearables();
+#else
+
+ // We don't do the rest if we're a spectator
+ if ( m_pOuter->GetTeamNumber() == TEAM_SPECTATOR )
+ return;
+
+ m_pOuter->RemoveFlag( FL_NOTARGET );
+
+
+ // Restore solid
+ m_pOuter->SetSolid( SOLID_BBOX );
+ m_pOuter->SetSolidFlags( FSOLID_NOT_STANDABLE );
+ m_pOuter->SetCollisionGroup( COLLISION_GROUP_PLAYER );
+
+ // Bring their gun back
+ m_pOuter->SetActiveWeapon( m_pOuter->GetLastWeapon() );
+ if ( m_pOuter->GetActiveWeapon() )
+ {
+ m_pOuter->GetActiveWeapon()->Deploy();
+ }
+#endif
+}
+
+#ifdef STAGING_ONLY
+void CTFPlayerShared::OnAddSpaceGravity()
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->EmitSound( "RD.SpaceGravityTransition" );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveSpaceGravity()
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->EmitSound( "RD.SpaceGravityTransition" );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddSelfConc()
+{
+ UpdatePhaseEffects();
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveSelfConc()
+{
+ RemovePhaseEffects();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddStealthedPhase( void )
+{
+#ifdef GAME_DLL
+ AddCond( TF_COND_SPEED_BOOST );
+ m_pOuter->m_takedamage = DAMAGE_NO;
+#else
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_STEALTH, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveStealthedPhase( void )
+{
+#ifdef GAME_DLL
+ RemoveCond( TF_COND_SPEED_BOOST );
+ m_pOuter->m_takedamage = DAMAGE_YES;
+
+ // See if the spy is inside another player or object
+ Vector vecPos = m_pOuter->GetAbsOrigin();
+ trace_t trace;
+ UTIL_TraceHull( vecPos, vecPos, VEC_HULL_MIN_SCALED( m_pOuter ), VEC_HULL_MAX_SCALED( m_pOuter ), ( MASK_SOLID | CONTENTS_PLAYERCLIP ), m_pOuter, COLLISION_GROUP_NONE, &trace );
+ if ( trace.fraction < 1.f )
+ {
+ // Telefrag!
+ m_pOuter->TakeDamage( CTakeDamageInfo( m_pOuter, m_pOuter, 1000, DMG_CRUSH, TF_DMG_CUSTOM_TELEFRAG ) );
+ }
+#else
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_STEALTH ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#endif
+}
+
+void CTFPlayerShared::OnAddClipOverload( void )
+{
+#ifdef GAME_DLL
+ m_pOuter->GiveAmmo( 1000, TF_AMMO_PRIMARY );
+ m_pOuter->GiveAmmo( 1000, TF_AMMO_SECONDARY );
+ m_pOuter->GiveAmmo( 1000, TF_AMMO_METAL );
+ m_pOuter->GiveAmmo( 1000, TF_AMMO_GRENADES1 );
+ m_pOuter->GiveAmmo( 1000, TF_AMMO_GRENADES2 );
+ m_pOuter->GiveAmmo( 1000, TF_AMMO_GRENADES3 );
+
+ // Refills weapon clips, too
+ for ( int i = 0; i < MAX_WEAPONS; i++ )
+ {
+ CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( m_pOuter->GetWeapon( i ) );
+ if ( !pWeapon )
+ continue;
+
+ float flPrevClipScale = pWeapon->GetClipScale();
+ if ( m_pOuter->GetActiveTFWeapon() == pWeapon )
+ {
+ pWeapon->AbortReload(); // Abort reload or else their reload will "fix" their clip size
+ pWeapon->SendWeaponAnim( ACT_VM_IDLE );
+ }
+ pWeapon->SetClipScale( 3.f );
+ pWeapon->GiveDefaultAmmo();
+ pWeapon->SetClipScale( flPrevClipScale );
+
+ if ( pWeapon->IsEnergyWeapon() )
+ {
+ pWeapon->WeaponRegenerate();
+ }
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveClipOverload( void )
+{
+ // Nothing required
+}
+#endif // STAGING_ONLY
+
+void CTFPlayerShared::OnAddHalloweenKart( void )
+{
+#ifdef GAME_DLL
+ CSingleUserRecipientFilter filter( m_pOuter );
+ TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_HOW_TO_CONTROL_KART );
+
+ ApplyAttributeToPlayer( "head scale", 3.f );
+
+ //ResetKartDamage
+ m_pOuter->ResetKartDamage();
+
+ CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
+ if ( pSpellBook )
+ {
+ pSpellBook->ClearSpell();
+ }
+
+ m_pOuter->m_flKartNextAvailableBoost = gpGlobals->curtime + 3.0f;
+
+ // Switch to melee to make sure Spies and Engies don't have build menus open
+ CTFWeaponBase *pMeleeWeapon = dynamic_cast<CTFWeaponBase*>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) );
+ Assert( pMeleeWeapon );
+ if ( pMeleeWeapon )
+ {
+ m_pOuter->Weapon_Switch( pMeleeWeapon );
+ }
+
+ m_pOuter->m_flNextBonusDucksVOAllowedTime = gpGlobals->curtime + 17.f; // The longest Merasmus line + 1 second
+#else
+ extern ConVar tf_halloween_kart_cam_dist;
+ m_pOuter->SetTauntCameraTargets( tf_halloween_kart_cam_dist.GetFloat(), 0.0f );
+
+ m_pOuter->CreateKart();
+ // Set vehicle angles to be our current angles so we don't spin around
+ // when we get in the car
+ //$ This is handled in the ForcePlayerViewAngles user message.
+ //$ m_angVehicleMoveAngles = m_pOuter->GetAbsAngles();
+
+ if ( m_pOuter->GetActiveWeapon() )
+ {
+ m_pOuter->GetActiveWeapon()->SetWeaponVisible( false );
+ }
+#endif
+}
+
+void CTFPlayerShared::OnRemoveHalloweenKart( void )
+{
+#ifdef GAME_DLL
+ RemoveAttributeFromPlayer( "head scale" );
+ //ResetKartDamage
+ m_pOuter->ResetKartDamage();
+
+ CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
+ if ( pSpellBook )
+ {
+ pSpellBook->ClearSpell();
+ }
+#else
+ // When we have every taunt cam use this system, we should clean up after ourselves. But for now, this causes a bad interaction
+ // with other systems.
+ // m_pOuter->SetTauntCameraTargets( 0.0f, 0.0f );
+
+ m_pOuter->RemoveKart();
+
+ // Reset any tilting now that we're out of the karts
+ if ( m_pOuter->m_PlayerAnimState )
+ {
+ QAngle renderAngles = m_pOuter->m_PlayerAnimState->GetRenderAngles();
+ renderAngles[ ROLL ] = renderAngles[ PITCH ] = 0.f;
+ m_pOuter->m_PlayerAnimState->SetRenderangles( renderAngles );
+ }
+
+ if ( m_pOuter->GetActiveWeapon() )
+ {
+ m_pOuter->GetActiveWeapon()->SetWeaponVisible( true );
+ }
+
+ if ( m_hKartParachuteEntity )
+ {
+ m_hKartParachuteEntity->Release();
+ m_hKartParachuteEntity = NULL;
+ }
+#endif
+}
+
+void CTFPlayerShared::OnAddBalloonHead( void )
+{
+#ifdef GAME_DLL
+ ApplyAttributeToPlayer( "voice pitch scale", 0.85f );
+ ApplyAttributeToPlayer( "head scale", 4.f );
+ ApplyAttributeToPlayer( "increased jump height", 0.8f );
+ ApplyAttributeToPlayer( "increased air control", 0.2f );
+#endif // GAME_DLL
+ m_pOuter->SetGravity( 0.3f );
+}
+
+
+void CTFPlayerShared::OnRemoveBalloonHead( void )
+{
+#ifdef GAME_DLL
+ RemoveAttributeFromPlayer( "voice pitch scale" );
+ RemoveAttributeFromPlayer( "head scale" );
+ RemoveAttributeFromPlayer( "increased jump height" );
+ RemoveAttributeFromPlayer( "increased air control" );
+#endif // GAME_DLL
+ m_pOuter->SetGravity( 0.f );
+}
+
+
+void CTFPlayerShared::OnAddMeleeOnly( void )
+{
+#ifdef GAME_DLL
+ CTFWeaponBase *pMeleeWeapon = dynamic_cast<CTFWeaponBase*>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) );
+ Assert( pMeleeWeapon );
+ if ( pMeleeWeapon )
+ {
+ m_pOuter->Weapon_Switch( pMeleeWeapon );
+ }
+
+ ApplyAttributeToPlayer( "disable weapon switch", true );
+ ApplyAttributeToPlayer( "hand scale", 3.f );
+ AddCond( TF_COND_HALLOWEEN_TINY );
+ AddCond( TF_COND_SPEED_BOOST );
+#endif // GAME_DLL
+}
+
+
+void CTFPlayerShared::OnRemoveMeleeOnly( void )
+{
+#ifdef GAME_DLL
+ RemoveAttributeFromPlayer( "disable weapon switch" );
+ RemoveAttributeFromPlayer( "hand scale" );
+ RemoveCond( TF_COND_HALLOWEEN_TINY );
+ RemoveCond( TF_COND_SPEED_BOOST );
+#endif // GAME_DLL
+}
+
+
+void CTFPlayerShared::OnAddSwimmingCurse( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_SWIMMING_CURSE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#endif // CLIENT_DLL
+}
+
+void CTFPlayerShared::OnRemoveSwimmingCurse( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is urine
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_SWIMMING_CURSE ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#else
+ AddCond( TF_COND_URINE, 10.0f );
+#endif // CLIENT_DLL
+}
+
+void CTFPlayerShared::OnAddHalloweenKartCage( void )
+{
+#ifdef CLIENT_DLL
+ Assert( !m_pOuter->m_hHalloweenKartCage );
+ if ( !m_pOuter->m_hHalloweenKartCage )
+ {
+ m_pOuter->m_hHalloweenKartCage = C_PlayerAttachedModel::Create( "models/props_halloween/bumpercar_cage.mdl", m_pOuter, 0, vec3_origin, PAM_PERMANENT, 0 );
+ m_pOuter->m_hHalloweenKartCage->FollowEntity( m_pOuter, true );
+ }
+#else
+ AddCond( TF_COND_FREEZE_INPUT );
+#endif // CLIENT_DLL
+}
+
+void CTFPlayerShared::OnRemoveHalloweenKartCage( void )
+{
+#ifdef CLIENT_DLL
+ Assert( m_pOuter->m_hHalloweenKartCage );
+ if ( m_pOuter->m_hHalloweenKartCage )
+ {
+ m_pOuter->m_hHalloweenKartCage->StopFollowingEntity();
+ m_pOuter->m_hHalloweenKartCage->Release();
+ }
+#else
+ RemoveCond( TF_COND_FREEZE_INPUT );
+ DispatchParticleEffect( "ghost_appearation", PATTACH_ABSORIGIN, m_pOuter );
+#endif // CLIENT_DLL
+}
+
+void CTFPlayerShared::OnAddPasstimeInterception( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->m_pPhaseStandingEffect )
+ {
+ m_pOuter->m_pPhaseStandingEffect = m_pOuter->ParticleProp()->Create( "warp_version", PATTACH_ABSORIGIN_FOLLOW );
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_PHASE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#else
+ if ( !m_bPhaseFXOn )
+ {
+ AddPhaseEffects();
+ }
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:1" );
+#endif
+}
+
+void CTFPlayerShared::OnRemovePasstimeInterception( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pPhaseStandingEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect );
+ m_pOuter->m_pPhaseStandingEffect = NULL;
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_PHASE ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#else
+ if ( m_bPhaseFXOn )
+ {
+ RemovePhaseEffects();
+ }
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:0" );
+#endif
+}
+
+
+void CTFPlayerShared::OnAddRunePlague( void )
+{
+#ifdef CLIENT_DLL
+
+ m_pOuter->m_pRunePlagueEffect = m_pOuter->ParticleProp()->Create( "powerup_plague_carrier", PATTACH_ABSORIGIN_FOLLOW );
+
+ // show resist effect on enemy player that has plague rune if local player is in plague cond
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalPlayer && pLocalPlayer != m_pOuter && pLocalPlayer->m_Shared.InCond( TF_COND_PLAGUE ) && m_pOuter->IsEnemyPlayer() )
+ {
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_RUNE_PLAGUE );
+ }
+#endif // CLIENT_DLL
+}
+
+
+void CTFPlayerShared::OnRemoveRunePlague( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pRunePlagueEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRunePlagueEffect );
+ m_pOuter->m_pRunePlagueEffect = NULL;
+ }
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+#endif // CLIENT_DLL
+}
+
+
+void CTFPlayerShared::OnAddPlague( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->EmitSound( "Powerup.PickUpPlagueInfected" );
+
+#endif
+ CTFPlayer *pProvider = ToTFPlayer( m_ConditionData[TF_COND_PLAGUE].m_pProvider );
+
+ //plague damage is a percentage of player health so everyone has the same life expectancy
+ float flPlagueDmg = 0.05f * m_pOuter->GetMaxHealth();
+
+ if ( pProvider )
+ {
+ MakeBleed( pProvider, NULL, 0.f, flPlagueDmg, true );
+ CSingleUserRecipientFilter localFilter( pProvider );
+ pProvider->EmitSound( localFilter, pProvider->entindex(), "Powerup.PickUpPlagueInfected" );
+ }
+
+ m_pOuter->EmitSound( "Powerup.PickUpPlagueInfectedLoop" );
+ ClientPrint( m_pOuter, HUD_PRINTCENTER, "#TF_Powerup_Contract_Plague" );
+
+#ifdef CLIENT_DLL
+ // show resist effect on enemy player that has plague rune if local player is in plague cond
+ if ( m_pOuter->IsLocalPlayer() && pProvider && pProvider->IsEnemyPlayer() )
+ {
+ AddResistShield( &pProvider->m_pTempShield, pProvider, TF_COND_RUNE_PLAGUE );
+ }
+#endif // CLIENT_DLL
+}
+
+void CTFPlayerShared::OnRemovePlague( void )
+{
+ m_pOuter->StopSound( "Powerup.PickUpPlagueInfectedLoop" );
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_BLEED ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+
+ // remove shield from the current plague rune carrier
+ int iEnemyTeam = m_pOuter->GetTeamNumber() == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED;
+ CTFPlayer *pCurrentRuneCarrier = GetRuneCarrier( RUNE_PLAGUE, iEnemyTeam );
+ if ( pCurrentRuneCarrier )
+ {
+ RemoveResistShield( &pCurrentRuneCarrier->m_pTempShield, pCurrentRuneCarrier );
+ }
+ }
+#endif
+
+// RemoveCond( TF_COND_BLEEDING );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddInPurgatory( void )
+{
+#ifdef GAME_DLL
+ // just entered
+ m_pOuter->m_purgatoryPainMultiplierTimer.Start( 40.0f );
+ m_pOuter->m_purgatoryPainMultiplier = 1;
+
+ // Set our health to full
+ m_pOuter->SetHealth( m_pOuter->GetMaxHealth() );
+
+ // Remove our projectiles
+ m_pOuter->RemoveOwnedProjectiles();
+
+ // Give us a brief period of invuln while we drop into purgatory
+ AddCond( TF_COND_INVULNERABLE, 1.5f );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveInPurgatory( void )
+{
+#ifdef GAME_DLL
+ if ( m_pOuter->IsAlive() )
+ {
+ // we escaped purgatory alive!
+ const float buffDuration = 10.0f;
+ AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration );
+ AddCond( TF_COND_SPEED_BOOST, buffDuration );
+ AddCond( TF_COND_INVULNERABLE, buffDuration );
+
+ m_pOuter->SetHealth( 2.0f * m_pOuter->GetMaxHealth() );
+
+ m_pOuter->m_purgatoryBuffTimer.Start( buffDuration );
+
+ TFGameRules()->BroadcastSound( 255, "Halloween.PlayerEscapedUnderworld" );
+
+ // Remove our projectiles
+ m_pOuter->RemoveOwnedProjectiles();
+
+ CReliableBroadcastRecipientFilter filter;
+ const char* pszEscapeMessage = "#TF_Halloween_Underworld";
+ if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
+ {
+ pszEscapeMessage = "#TF_Halloween_Skull_Island_Escape";
+ }
+
+ UTIL_SayText2Filter( filter, m_pOuter, false, pszEscapeMessage, m_pOuter->GetPlayerName() );
+
+ IGameEvent *pEvent = gameeventmanager->CreateEvent( "escaped_loot_island" );
+ if ( pEvent )
+ {
+ pEvent->SetInt( "player", m_pOuter->GetUserID() );
+ gameeventmanager->FireEvent( pEvent, true );
+ }
+
+ if ( m_pOuter->GetTeam() )
+ {
+ const char* pszLogMessage = "purgatory_escaped";
+ if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
+ {
+ pszEscapeMessage = "skull_island_escaped";
+ }
+
+ UTIL_LogPrintf( "HALLOWEEN: \"%s<%i><%s><%s>\" %s\n",
+ m_pOuter->GetPlayerName(),
+ m_pOuter->GetUserID(),
+ m_pOuter->GetNetworkIDString(),
+ m_pOuter->GetTeam()->GetName(),
+ pszLogMessage );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddCompetitiveWinner( void )
+{
+#ifdef GAME_DLL
+
+#else
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ gHUD.LockRenderGroup( gHUD.LookupRenderGroupIndexByName( "mid" ) );
+ m_pOuter->UpdateVisibility();
+ m_pOuter->UpdateWearables();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveCompetitiveWinner( void )
+{
+#ifdef GAME_DLL
+
+
+#else
+
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddCompetitiveLoser( void )
+{
+#ifdef GAME_DLL
+
+
+#else
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ gHUD.LockRenderGroup( gHUD.LookupRenderGroupIndexByName( "mid" ) );
+ m_pOuter->UpdateVisibility();
+ m_pOuter->UpdateWearables();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveCompetitiveLoser( void )
+{
+#ifdef GAME_DLL
+
+
+#else
+
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateChargeMeter( void )
+{
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_DEMOMAN ) )
+ return;
+
+ if ( InCond( TF_COND_SHIELD_CHARGE ) )
+ {
+ // Drain the meter while we are charging.
+ float flChargeDrainTime = tf_demoman_charge_drain_time.GetFloat();
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flChargeDrainTime, mod_charge_time );
+ float flChargeDrainMod = 100.f / flChargeDrainTime;
+ m_flChargeMeter -= gpGlobals->frametime * flChargeDrainMod;
+ if ( m_flChargeMeter <= 0 )
+ {
+ m_flChargeMeter = 0;
+ RemoveCond( TF_COND_SHIELD_CHARGE );
+ }
+
+ m_flLastNoChargeTime = gpGlobals->curtime;
+ }
+ else if ( m_flChargeMeter < 100.f )
+ {
+ // Recharge the meter while we are not charging.
+ float flChargeRegenMod = tf_demoman_charge_regen_rate.GetFloat();
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flChargeRegenMod, charge_recharge_rate );
+ if ( TFGameRules() && TFGameRules()->IsPowerupMode() && GetCarryingRuneType() == RUNE_KNOCKOUT )
+ {
+ flChargeRegenMod *= 0.2f;
+ }
+
+ flChargeRegenMod = Max( flChargeRegenMod, 1.f );
+ m_flChargeMeter += gpGlobals->frametime * flChargeRegenMod;
+ if ( m_flChargeMeter > 100.f )
+ {
+ m_flChargeMeter = 100.f;
+ }
+
+ // Used for the weapon glow cooldown.
+ if ( !m_bChargeGlowing )
+ {
+ m_flLastNoChargeTime = gpGlobals->curtime;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::EndCharge()
+{
+ if ( !InCond( TF_COND_SHIELD_CHARGE ) )
+ return;
+
+#ifdef GAME_DLL
+ if ( GetDemomanChargeMeter() < 90 )
+ {
+ // Impacts drain the charge meter completely.
+ float flMeterAtImpact = m_flChargeMeter;
+
+ CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( m_pOuter );
+ if ( pWearableShield )
+ {
+ pWearableShield->ShieldBash( m_pOuter, flMeterAtImpact );
+ }
+
+ CalcChargeCrit();
+
+ // Removing the condition here would cause issues with prediction, so we set the
+ // duration to zero so that it will be removed during the next condition think.
+ SetConditionDuration( TF_COND_SHIELD_CHARGE, 0 );
+ }
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::CalculateChargeCap( void ) const
+{
+ float flCap = 0.45f;
+
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flCap, charge_turn_control );
+
+ // Scale yaw cap based on frametime to prevent differences in turn effectiveness due to variable framerate (between clients mainly)
+ if ( tf_demoman_charge_frametime_scaling.GetBool() )
+ {
+ // There's probably something better to use here as a baseline, instead of TICK_INTERVAL
+ float flMod = RemapValClamped( gpGlobals->frametime, ( TICK_INTERVAL * YAW_CAP_SCALE_MIN ), ( TICK_INTERVAL * YAW_CAP_SCALE_MAX ), 0.25f, 2.f );
+ flCap *= flMod;
+ }
+
+ return flCap;
+}
+
+bool CTFPlayerShared::HasDemoShieldEquipped() const
+{
+ return GetEquippedDemoShield( m_pOuter ) != NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::CalcChargeCrit( bool bForceCrit )
+{
+#ifdef GAME_DLL
+ // Keying on TideTurner
+ int iDemoChargeDamagePenalty = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iDemoChargeDamagePenalty, lose_demo_charge_on_damage_when_charging );
+ if ( iDemoChargeDamagePenalty && GetDemomanChargeMeter() <= 75 )
+ {
+ SetNextMeleeCrit( MELEE_MINICRIT );
+ }
+ else if ( GetDemomanChargeMeter() <= 40 || bForceCrit)
+ {
+ SetNextMeleeCrit( MELEE_CRIT );
+ }
+ else if ( GetDemomanChargeMeter() <= 75 )
+ {
+ SetNextMeleeCrit( MELEE_MINICRIT );
+ }
+
+ m_pOuter->SetContextThink( &CTFPlayer::RemoveMeleeCrit, gpGlobals->curtime + 0.3f, "RemoveMeleeCrit" );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddShieldCharge( void )
+{
+ UpdatePhaseEffects();
+
+ m_pOuter->TeamFortress_SetSpeed();
+
+#ifdef CLIENT_DLL
+ m_pOuter->EmitSound( "DemoCharge.Charging" );
+#else
+ m_hPlayersVisibleAtChargeStart.Purge();
+
+ // Remove debuffs
+ for ( int i = 0; g_aDebuffConditions[i] != TF_COND_LAST; i++ )
+ {
+ RemoveCond( g_aDebuffConditions[i] );
+ }
+
+ // store the players we CAN see for the TF_DEMOMAN_KILL_PLAYER_YOU_DIDNT_SEE achievement
+ CUtlVector<CTFPlayer *> vecPlayers;
+ CollectPlayers( &vecPlayers, ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED, true );
+ FOR_EACH_VEC( vecPlayers, i )
+ {
+ if ( !vecPlayers[i] )
+ continue;
+
+ if ( vecPlayers[i]->m_Shared.InCond( TF_COND_STEALTHED ) )
+ continue;
+
+ // can we see them?
+ if ( m_pOuter->FVisible( vecPlayers[i], MASK_OPAQUE ) == false )
+ continue;
+
+ // are they in our field of view? (might be behind us)
+ if ( m_pOuter->IsInFieldOfView( vecPlayers[i]) == false )
+ continue;
+
+ m_hPlayersVisibleAtChargeStart.AddToTail( vecPlayers[i] );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveShieldCharge( void )
+{
+ RemovePhaseEffects();
+
+ m_pOuter->TeamFortress_SetSpeed();
+
+ m_bPostShieldCharge = true;
+ m_flChargeEndTime = gpGlobals->curtime;
+ m_flChargeMeter = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::InterruptCharge( void )
+{
+ if ( !InCond( TF_COND_SHIELD_CHARGE ) )
+ return;
+
+ SetConditionDuration( TF_COND_SHIELD_CHARGE, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+#ifdef GAME_DLL
+void CTFPlayer::RemoveMeleeCrit( void )
+{
+ m_Shared.SetNextMeleeCrit( MELEE_NOCRIT );
+ m_Shared.m_bPostShieldCharge = false;
+ // Remove crit boost right away. DemoShieldChargeThink depends on m_bPostShieldCharge being true
+ // to attempt to remove crits (which we just cleared) so clear crits here as well.
+ if ( m_Shared.InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) )
+ {
+ m_Shared.RemoveCond( TF_COND_CRITBOOSTED_DEMO_CHARGE );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::DemoShieldChargeThink( void )
+{
+ if ( InCond( TF_COND_SHIELD_CHARGE ) || m_bPostShieldCharge )
+ {
+ if ( m_bPostShieldCharge && (gpGlobals->curtime - m_flChargeEndTime) >= 0.3f )
+ {
+ if ( InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) )
+ {
+ RemoveCond( TF_COND_CRITBOOSTED_DEMO_CHARGE );
+ }
+ m_bPostShieldCharge = false;
+ }
+ else if ( InCond( TF_COND_SHIELD_CHARGE ) && GetDemomanChargeMeter() < 75 )
+ {
+ if ( !InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) )
+ {
+ AddCond( TF_COND_CRITBOOSTED_DEMO_CHARGE );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddDemoBuff( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateDemomanEyeEffect( m_iDecapitations );
+#else
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddEnergyDrinkBuff( void )
+{
+#ifdef CLIENT_DLL
+ UpdateCritBoostEffect();
+#endif
+
+#ifdef GAME_DLL
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
+ {
+ // Begin berzerker speed buff.
+ m_pOuter->TeamFortress_SetSpeed();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveEnergyDrinkBuff( void )
+{
+#ifdef CLIENT_DLL
+ UpdateCritBoostEffect();
+#endif
+
+#ifdef GAME_DLL
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
+ {
+ // End berzerker speed buff.
+ m_pOuter->TeamFortress_SetSpeed();
+ }
+#endif
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::EndRadiusHealEffect( void )
+{
+ if ( m_pOuter->m_pRadiusHealEffect )
+ {
+ m_pOuter->m_pRadiusHealEffect->StopEmission();
+ m_pOuter->m_pRadiusHealEffect = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::EndKingBuffRadiusEffect( void )
+{
+ // For buffed player
+ if ( m_pOuter->m_pKingBuffRadiusEffect )
+ {
+ m_pOuter->m_pKingBuffRadiusEffect->StopEmission();
+ m_pOuter->m_pKingBuffRadiusEffect = NULL;
+ }
+ // For carrier of King Rune
+ if ( m_pOuter->m_pKingRuneRadiusEffect )
+ {
+ m_pOuter->m_pKingRuneRadiusEffect->StopEmission();
+ m_pOuter->m_pKingRuneRadiusEffect = NULL;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddRadiusHeal( void )
+{
+#ifdef CLIENT_DLL
+ if ( InCond( TF_COND_RADIUSHEAL ) )
+ {
+ if ( IsStealthed() )
+ {
+ EndRadiusHealEffect();
+ return;
+ }
+
+ const char *pszRadiusHealEffect;
+ int nTeamNumber = m_pOuter->GetTeamNumber();
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_SPY ) && InCond( TF_COND_DISGUISED ) && ( GetDisguiseTeam() == GetLocalPlayerTeam() ) )
+ {
+ nTeamNumber = GetLocalPlayerTeam();
+ }
+
+ if ( nTeamNumber == TF_TEAM_RED )
+ {
+ pszRadiusHealEffect = "medic_healradius_red_buffed";
+ }
+ else
+ {
+ pszRadiusHealEffect = "medic_healradius_blue_buffed";
+ }
+
+ if ( !m_pOuter->m_pRadiusHealEffect )
+ {
+ m_pOuter->m_pRadiusHealEffect = m_pOuter->ParticleProp()->Create( pszRadiusHealEffect, PATTACH_ABSORIGIN_FOLLOW );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveRadiusHeal( void )
+{
+#ifdef CLIENT_DLL
+ EndRadiusHealEffect();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddMegaHeal( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ const char *pEffectName = NULL;
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
+ {
+ pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED;
+ }
+ else
+ {
+ pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE;
+ }
+
+ IMaterial *pMaterial = materials->FindMaterial( pEffectName, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+
+ if ( InCond( TF_COND_MEGAHEAL ) )
+ {
+ const char *pszMegaHealEffectName;
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
+ {
+ pszMegaHealEffectName = "medic_megaheal_red";
+ }
+ else
+ {
+ pszMegaHealEffectName = "medic_megaheal_blue";
+ }
+
+ if ( !m_pOuter->m_pMegaHealEffect )
+ {
+ m_pOuter->m_pMegaHealEffect = m_pOuter->ParticleProp()->Create( pszMegaHealEffectName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveMegaHeal( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pMegaHealEffect )
+ {
+ m_pOuter->m_pMegaHealEffect->StopEmission();
+ m_pOuter->m_pMegaHealEffect = NULL;
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is an invuln material
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial &&
+ ( FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE ) ||
+ FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED ) ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddKingBuff( void )
+{
+#ifdef CLIENT_DLL
+ if ( IsStealthed() )
+ {
+ EndKingBuffRadiusEffect();
+ return;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveKingBuff( void )
+{
+#ifdef CLIENT_DLL
+ EndKingBuffRadiusEffect();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveRuneSupernova( void )
+{
+ SetRuneCharge( 0.f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveDemoBuff( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateDemomanEyeEffect( 0 );
+#else
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+#ifdef CLIENT_DLL
+void CTFPlayerShared::ClientDemoBuffThink( void )
+{
+ if ( m_iDecapitations > 0 )
+ {
+ if ( m_iDecapitations != m_iOldDecapitations )
+ {
+ m_iOldDecapitations = m_iDecapitations;
+ m_pOuter->UpdateDemomanEyeEffect( m_iDecapitations );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ClientKillStreakBuffThink( void )
+{
+ int nLoadoutSlot = m_pOuter->GetActiveTFWeapon() ? m_pOuter->GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( m_pOuter->GetPlayerClass()->GetClassIndex() ) : LOADOUT_POSITION_PRIMARY;
+ int nKillStreak = GetStreak(kTFStreak_Kills);
+ if ( nKillStreak != m_iOldKillStreak || m_iOldKillStreakWepSlot != nLoadoutSlot )
+ {
+ m_pOuter->UpdateKillStreakEffects( nKillStreak, m_iOldKillStreak < nKillStreak );
+ m_iOldKillStreak = nKillStreak;
+ m_iOldKillStreakWepSlot = nLoadoutSlot;
+ }
+ else if ( !m_pOuter->IsAlive() )
+ {
+ m_pOuter->UpdateKillStreakEffects( 0, false );
+ }
+ else
+ {
+ static bool bAlternate = false;
+ Vector vColor = bAlternate ? m_pOuter->m_vEyeGlowColor1 : m_pOuter->m_vEyeGlowColor2;
+
+ if ( m_pOuter->m_pEyeGlowEffect[0] )
+ {
+ m_pOuter->m_pEyeGlowEffect[0]->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+ if ( m_pOuter->m_pEyeGlowEffect[1] )
+ {
+ m_pOuter->m_pEyeGlowEffect[1]->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+ //
+ bAlternate = !bAlternate;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddTeleported( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateRecentlyTeleportedEffect();
+ m_flGotTeleEffectAt = gpGlobals->curtime;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveTeleported( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateRecentlyTeleportedEffect();
+#endif
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::ShouldShowRecentlyTeleported( void )
+{
+ if ( IsStealthed() )
+ {
+ return false;
+ }
+
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ // disguised as an enemy
+ if ( InCond( TF_COND_DISGUISED ) && GetDisguiseTeam() != m_pOuter->GetTeamNumber() )
+ {
+ // was this my own team's teleporter?
+ if ( GetTeamTeleporterUsed() == m_pOuter->GetTeamNumber() )
+ {
+ // don't show my trail
+ return false;
+ }
+ else
+ {
+ // okay to show the local player the trail, but not his team (might confuse them)
+ if ( !m_pOuter->IsLocalPlayer() && m_pOuter->GetTeamNumber() == GetLocalPlayerTeam() )
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if ( GetTeamTeleporterUsed() != m_pOuter->GetTeamNumber() )
+ {
+ return false;
+ }
+ }
+ }
+
+ return InCond( TF_COND_TELEPORTED );
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::Burn( CTFPlayer *pAttacker, CTFWeaponBase *pWeapon, float flBurningTime /*=-1*/ )
+{
+#ifdef CLIENT_DLL
+
+#else
+ // Don't bother igniting players who have just been killed by the fire damage.
+ if ( !m_pOuter->IsAlive() )
+ return;
+
+ //Don't ignite if I'm in phase mode.
+ if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_PASSTIME_INTERCEPTION ) )
+ return;
+
+ // pyros don't burn persistently or take persistent burning damage, but we show brief burn effect so attacker can tell they hit
+ bool bVictimIsPyro = ( TF_CLASS_PYRO == m_pOuter->GetPlayerClass()->GetClassIndex() );
+
+ if ( !InCond( TF_COND_BURNING ) )
+ {
+ // Start burning
+ AddCond( TF_COND_BURNING, -1.f, pAttacker );
+ m_flFlameBurnTime = gpGlobals->curtime; //asap
+ // let the attacker know he burned me
+ if ( pAttacker && !bVictimIsPyro )
+ {
+ pAttacker->OnBurnOther( m_pOuter, pWeapon );
+
+ m_hOriginalBurnAttacker = pAttacker;
+ }
+ }
+
+ int nAfterburnImmunity = 0;
+
+ // Check my weapon
+ CTFWeaponBase *pMyWeapon = GetActiveTFWeapon();
+ if ( pMyWeapon )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pMyWeapon, nAfterburnImmunity, afterburn_immunity );
+ }
+
+ // STAGING_SPY
+ if ( InCond( TF_COND_AFTERBURN_IMMUNE ) )
+ {
+ nAfterburnImmunity = 1;
+ m_flFlameRemoveTime = 0;
+ }
+
+ // Check shield
+ if ( !nAfterburnImmunity && IsShieldEquipped() )
+ {
+ CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( m_pOuter );
+ if ( pWearableShield && !pWearableShield->IsDisguiseWearable() )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearableShield, nAfterburnImmunity, afterburn_immunity );
+ }
+ }
+
+ bool bIsFlareGun = ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_FLAREGUN );
+ float flFlameLife;
+ if ( bVictimIsPyro || nAfterburnImmunity )
+ {
+ flFlameLife = TF_BURNING_FLAME_LIFE_PYRO;
+ }
+ else if ( flBurningTime > 0 )
+ {
+ flFlameLife = flBurningTime;
+ }
+ else if ( bIsFlareGun )
+ {
+ flFlameLife = TF_BURNING_FLAME_LIFE_FLARE;
+ }
+ else if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_PARTICLE_CANNON )
+ {
+ flFlameLife = TF_BURNING_FLAME_LIFE_PLASMA;
+ }
+ else
+ {
+ flFlameLife = TF_BURNING_FLAME_LIFE;
+ }
+
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flFlameLife, mult_wpn_burntime );
+
+ float flBurnEnd = gpGlobals->curtime + flFlameLife;
+ if ( flBurnEnd > m_flFlameRemoveTime )
+ {
+ m_flFlameRemoveTime = flBurnEnd;
+ }
+
+ m_hBurnAttacker = pAttacker;
+ m_hBurnWeapon = pWeapon;
+
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A non-TF Player burned us
+// Used for Bosses, they inflict self burn
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SelfBurn( float flBurningTime )
+{
+#ifdef GAME_DLL
+ Burn( m_pOuter, NULL, flBurningTime );
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::MakeBleed( CTFPlayer *pPlayer, CTFWeaponBase *pWeapon, float flBleedingTime, int nBleedDmg /* = TF_BLEEDING_DMG */, bool bPermanentBleeding /*= false*/ )
+{
+#ifdef CLIENT_DLL
+
+#else
+ // Don't bother if they are dead
+ if ( !m_pOuter->IsAlive() )
+ return;
+
+ // Required for the CTakeDamageInfo we create later
+ Assert( pPlayer && pWeapon );
+ if ( !pPlayer && !pWeapon )
+ return;
+
+ float flExpireTime = gpGlobals->curtime + flBleedingTime;
+
+ // See if this weapon has already applied a bleed and extend the time
+ FOR_EACH_VEC( m_PlayerBleeds, i )
+ {
+ if ( m_PlayerBleeds[i].hBleedingAttacker && m_PlayerBleeds[i].hBleedingAttacker == pPlayer &&
+ m_PlayerBleeds[i].hBleedingWeapon && m_PlayerBleeds[i].hBleedingWeapon == pWeapon )
+ {
+ if ( flExpireTime > m_PlayerBleeds[i].flBleedingRemoveTime )
+ {
+ m_PlayerBleeds[i].flBleedingRemoveTime = flExpireTime;
+ return;
+ }
+ }
+ }
+
+ // New bleed source
+ bleed_struct_t bleedinfo =
+ {
+ pPlayer, // hBleedingAttacker
+ pWeapon, // hBleedingWeapon
+ flBleedingTime, // flBleedingTime
+ flExpireTime, // flBleedingRemoveTime
+ nBleedDmg, // nBleedDmg
+ bPermanentBleeding
+ };
+ m_PlayerBleeds.AddToTail( bleedinfo );
+
+ if ( !InCond( TF_COND_BLEEDING ) )
+ {
+ AddCond( TF_COND_BLEEDING, -1.f, pPlayer );
+ }
+#endif
+}
+
+
+#ifdef GAME_DLL
+void CTFPlayerShared::StopBleed( CTFPlayer *pPlayer, CTFWeaponBase *pWeapon )
+{
+ FOR_EACH_VEC_BACK( m_PlayerBleeds, i )
+ {
+ const bleed_struct_t& bleed = m_PlayerBleeds[i];
+ if ( bleed.hBleedingAttacker == pPlayer && bleed.hBleedingWeapon == pWeapon )
+ {
+ m_PlayerBleeds.FastRemove( i );
+ }
+ }
+
+ // remove condition right away when the list is empty
+ if ( !m_PlayerBleeds.Count() )
+ {
+ RemoveCond( TF_COND_BLEEDING );
+ }
+}
+#endif // GAME_DLL
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveBurning( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->StopBurningSound();
+
+ if ( m_pOuter->m_nOldWaterLevel > 0 )
+ {
+ m_pOuter->ParticleProp()->Create( "water_burning_steam", PATTACH_ABSORIGIN );
+ }
+
+ if ( m_pOuter->m_pBurningEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pBurningEffect );
+ m_pOuter->m_pBurningEffect = NULL;
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ // only remove the overlay if it is the burning
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_BURNING ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+
+ m_pOuter->m_flBurnEffectStartTime = 0;
+#else
+ m_hBurnAttacker = NULL;
+ m_hOriginalBurnAttacker = NULL;
+ m_hBurnWeapon = NULL;
+
+ m_pOuter->ClearBurnFromBehindAttackers();
+
+ // If we were on fire and now we're not, and we're still alive, then give ourself some credit
+ // for surviving this fire if we have any items that track it.
+ if ( m_nPlayerState == TF_STATE_ACTIVE )
+ {
+ HatAndMiscEconEntities_OnOwnerKillEaterEventNoParter( m_pOuter, kKillEaterEvent_FiresSurvived );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveOverhealed( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->UpdateOverhealEffect();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveDemoCharge( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->StopSound( "DemoCharge.ChargeCritOn" );
+ m_pOuter->EmitSound( "DemoCharge.ChargeCritOff" );
+ UpdateCritBoostEffect();
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveCritBoost( void )
+{
+#ifdef CLIENT_DLL
+ UpdateCritBoostEffect();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveTmpDamageBonus( void )
+{
+ m_flTmpDamageBonusAmount = 1.0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddStealthed( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) )
+ return;
+
+ if ( !InCond( TF_COND_FEIGN_DEATH ) )
+ {
+ m_pOuter->EmitSound( "Player.Spy_Cloak" );
+ }
+ m_pOuter->RemoveAllDecals();
+ UpdateCritBoostEffect();
+
+ if ( m_pOuter->m_pTempShield && GetCarryingRuneType() == RUNE_RESIST )
+ {
+ RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
+ }
+#endif
+
+ bool bSetInvisChangeTime = true;
+#ifdef CLIENT_DLL
+ if ( !m_pOuter->IsLocalPlayer() )
+ {
+ // We only clientside predict changetime for the local player
+ bSetInvisChangeTime = false;
+ }
+
+ if ( InCond( TF_COND_STEALTHED_USER_BUFF ) && m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_STEALTH, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#endif
+
+ if ( bSetInvisChangeTime )
+ {
+ if ( !InCond( TF_COND_FEIGN_DEATH ) && !InCond( TF_COND_STEALTHED_USER_BUFF ) )
+ {
+ float flInvisTime = tf_spy_invis_time.GetFloat();
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flInvisTime, mult_cloak_rate );
+ m_flInvisChangeCompleteTime = gpGlobals->curtime + flInvisTime;
+ }
+ else
+ {
+ m_flInvisChangeCompleteTime = gpGlobals->curtime; // Stealth immediately if we are in feign death.
+ }
+ }
+
+ // set our offhand weapon to be the invis weapon, but only for the spy's stealth
+ if ( InCond( TF_COND_STEALTHED ) )
+ {
+ for (int i = 0; i < m_pOuter->WeaponCount(); i++)
+ {
+ CTFWeaponInvis *pWpn = (CTFWeaponInvis *) m_pOuter->GetWeapon(i);
+ if ( !pWpn )
+ continue;
+
+ if ( pWpn->GetWeaponID() != TF_WEAPON_INVIS )
+ continue;
+
+ // try to switch to this weapon
+ m_pOuter->SetOffHandWeapon( pWpn );
+
+ m_bMotionCloak = pWpn->HasMotionCloak();
+ break;
+ }
+ }
+
+ m_pOuter->TeamFortress_SetSpeed();
+
+#ifdef CLIENT_DLL
+ // Remove water balloon effect if it on player
+ m_pOuter->ParticleProp()->StopParticlesNamed( "balloontoss_drip", true );
+
+ m_pOuter->UpdateSpyStateChange();
+ m_pOuter->UpdateKillStreakEffects( GetStreak( kTFStreak_Kills ) );
+#endif
+
+#ifdef GAME_DLL
+ m_flCloakStartTime = gpGlobals->curtime;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveStealthed( void )
+{
+#ifdef CLIENT_DLL
+ if ( !m_bSyncingConditions )
+ return;
+
+ CTFWeaponInvis *pWpn = (CTFWeaponInvis *) m_pOuter->Weapon_OwnsThisID( TF_WEAPON_INVIS );
+
+ int iReducedCloak = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iReducedCloak, set_quiet_unstealth );
+ if ( iReducedCloak == 1 )
+ {
+ m_pOuter->EmitSound( "Player.Spy_UnCloakReduced" );
+ }
+ else if ( pWpn && pWpn->HasFeignDeath() )
+ {
+ m_pOuter->EmitSound( "Player.Spy_UnCloakFeignDeath" );
+ }
+ else
+ {
+ m_pOuter->EmitSound( "Player.Spy_UnCloak" );
+ }
+ UpdateCritBoostEffect( kCritBoost_ForceRefresh );
+
+ if ( m_pOuter->IsLocalPlayer() && !InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
+ {
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_STEALTH ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ }
+ }
+
+ if ( !m_pOuter->m_pTempShield && GetCarryingRuneType() == RUNE_RESIST )
+ {
+ AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_RUNE_RESIST );
+ }
+#else
+ if ( m_flCloakStartTime > 0 )
+ {
+ // Calc a time and report every minute
+ float flCloaktime = ( gpGlobals->curtime - m_flCloakStartTime );
+ if ( flCloaktime > 0 )
+ {
+ EconEntity_OnOwnerKillEaterEventNoPartner(
+ dynamic_cast<CEconEntity *>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA2 ) ),
+ m_pOuter,
+ kKillEaterEvent_TimeCloaked,
+ (int)flCloaktime
+ );
+ }
+ m_flCloakStartTime = 0;
+ }
+
+#endif
+
+ // End feign death if we leave stealth for some reason.
+ if ( InCond( TF_COND_FEIGN_DEATH ) )
+ {
+ RemoveCond( TF_COND_FEIGN_DEATH );
+ }
+
+ m_pOuter->HolsterOffHandWeapon();
+
+ m_pOuter->TeamFortress_SetSpeed();
+
+ m_bMotionCloak = false;
+
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateSpyStateChange();
+ m_pOuter->UpdateKillStreakEffects( GetStreak( kTFStreak_Kills ) );
+#endif
+
+#ifdef STAGING_ONLY
+ if ( HasPhaseCloakAbility() )
+ {
+ RemoveCond( TF_COND_STEALTHED_PHASE );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddStealthedUserBuffFade( void )
+{
+#ifdef CLIENT_DLL
+ // If a player is firing their weapon while radius stealth hits them, we never
+ // get a chance to apply the screenoverlay effect, so apply it here instead.
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_STEALTH, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveStealthedUserBuffFade( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = view->GetScreenOverlayMaterial();
+ if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_STEALTH ) )
+ {
+ view->SetScreenOverlayMaterial( NULL );
+ return;
+ }
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddFeignDeath( void )
+{
+#ifdef CLIENT_DLL
+ // STAGING_SPY
+ AddUberScreenEffect( m_pOuter );
+#else
+#endif
+ // Go stealth w/o sound or fade out.
+ if ( !IsStealthed() )
+ {
+ AddCond( TF_COND_STEALTHED, -1.f, m_pOuter );
+ }
+
+ // STAGING_SPY
+ // Add a speed boost while feigned and afterburn immunity while running away
+ AddCond( TF_COND_SPEED_BOOST, tf_feign_death_speed_duration.GetFloat() );
+ AddCond( TF_COND_AFTERBURN_IMMUNE, tf_feign_death_speed_duration.GetFloat() );
+
+ SetFeignDeathReady( false );
+
+ m_flFeignDeathEnd = gpGlobals->curtime + tf_feign_death_speed_duration.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveFeignDeath( void )
+{
+#ifdef CLIENT_DLL
+ // STAGING_SPY
+ RemoveUberScreenEffect( m_pOuter );
+#endif
+ // Previous code removed cloak meter, this has been moved to on RemoveStealth checking for steath type
+ // FeignDeath is the duration of cloak where speed, no shimmer and damage reduction take place
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveDisguising( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pDisguisingEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pDisguisingEffect );
+ m_pOuter->m_pDisguisingEffect = NULL;
+ }
+#else
+ m_nDesiredDisguiseTeam = TF_SPY_UNDEFINED;
+
+ // Do not reset this value, we use the last desired disguise class for the
+ // 'lastdisguise' command
+
+ //m_nDesiredDisguiseClass = TF_CLASS_UNDEFINED;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveDisguised( void )
+{
+#ifdef CLIENT_DLL
+
+ if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) )
+ return;
+
+ // if local player is on the other team, reset the model of this player
+ CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ if ( !m_pOuter->InSameTeam( pLocalPlayer ) )
+ {
+ TFPlayerClassData_t *pData = GetPlayerClassData( TF_CLASS_SPY );
+ int iIndex = modelinfo->GetModelIndex( pData->GetModelName() );
+
+ m_pOuter->SetModelIndex( iIndex );
+ }
+
+ m_pOuter->EmitSound( "Player.Spy_Disguise" );
+
+ // They may have called for medic and created a visible medic bubble
+ m_pOuter->StopSaveMeEffect( true );
+ m_pOuter->StopTauntWithMeEffect();
+
+ UpdateCritBoostEffect( kCritBoost_ForceRefresh );
+ m_pOuter->UpdateSpyStateChange();
+
+#else
+ m_nDisguiseTeam = TF_SPY_UNDEFINED;
+ m_nDisguiseClass.Set( TF_CLASS_UNDEFINED );
+ m_nDisguiseSkinOverride = 0;
+ m_hDisguiseTarget.Set( NULL );
+ m_iDisguiseTargetIndex = TF_DISGUISE_TARGET_INDEX_NONE;
+ m_iDisguiseHealth = 0;
+ SetDisguiseBody( 0 );
+ m_iDisguiseAmmo = 0;
+
+ // Update the player model and skin.
+ m_pOuter->UpdateModel();
+
+ m_pOuter->ClearExpression();
+
+ m_pOuter->ClearDisguiseWeaponList();
+
+ RemoveDisguiseWeapon();
+
+ RemoveDisguiseWearables();
+
+#endif
+
+ m_pOuter->TeamFortress_SetSpeed();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddBurning( void )
+{
+#ifdef CLIENT_DLL
+ // Start the burning effect
+ if ( !m_pOuter->m_pBurningEffect )
+ {
+ if ( !( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) && m_pOuter->IsLocalPlayer() ) )
+ {
+ const char *pEffectName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "burningplayer_red" : "burningplayer_blue";
+ m_pOuter->m_pBurningEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+
+ m_pOuter->m_flBurnEffectStartTime = gpGlobals->curtime;
+ }
+ // set the burning screen overlay
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_BURNING, TEXTURE_GROUP_CLIENT_EFFECTS, false );
+ if ( !IsErrorMaterial( pMaterial ) )
+ {
+ view->SetScreenOverlayMaterial( pMaterial );
+ }
+ }
+#endif
+
+ // play a fire-starting sound
+ m_pOuter->EmitSound( "Fire.Engulf" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddOverhealed( void )
+{
+#ifdef CLIENT_DLL
+ // Start the Overheal effect
+
+ if ( !m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->UpdateOverhealEffect();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddStunned( void )
+{
+ if ( IsControlStunned() || IsLoserStateStunned() )
+ {
+#ifdef CLIENT_DLL
+ if ( GetActiveStunInfo() )
+ {
+ if ( !m_pOuter->m_pStunnedEffect && !( GetActiveStunInfo()->iStunFlags & TF_STUN_NO_EFFECTS ) )
+ {
+ if ( ( GetActiveStunInfo()->iStunFlags & TF_STUN_BY_TRIGGER ) || InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
+ {
+ const char* pEffectName = "yikes_fx";
+ m_pOuter->m_pStunnedEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_POINT_FOLLOW, "head" );
+ }
+ else
+ {
+ const char* pEffectName = "conc_stars";
+ m_pOuter->m_pStunnedEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_POINT_FOLLOW, "head" );
+ }
+ }
+ }
+#endif
+
+ // Notify our weapon that we have been stunned.
+ CTFWeaponBase* pWpn = m_pOuter->GetActiveTFWeapon();
+ if ( pWpn )
+ {
+ pWpn->OnControlStunned();
+ }
+
+ if ( InCond( TF_COND_SHIELD_CHARGE ) )
+ {
+ SetDemomanChargeMeter( 0 );
+ RemoveCond( TF_COND_SHIELD_CHARGE );
+ }
+
+ m_pOuter->TeamFortress_SetSpeed();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveStunned( void )
+{
+ m_iStunFlags = 0;
+ m_hStunner = NULL;
+
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pStunnedEffect )
+ {
+ // Remove stun stars if they are still around.
+ // They might be if we died, etc.
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pStunnedEffect );
+ m_pOuter->m_pStunnedEffect = NULL;
+ }
+#else
+ m_iStunIndex = -1;
+ m_PlayerStuns.RemoveAll();
+#endif
+
+ m_pOuter->TeamFortress_SetSpeed();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ControlStunFading( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pStunnedEffect )
+ {
+ // Remove stun stars early...
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pStunnedEffect );
+ m_pOuter->m_pStunnedEffect = NULL;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetStunExpireTime( float flTime )
+{
+#ifdef GAME_DLL
+ if ( GetActiveStunInfo() )
+ {
+ GetActiveStunInfo()->flExpireTime = flTime;
+ }
+#else
+ m_flStunEnd = flTime;
+#endif
+
+ UpdateLegacyStunSystem();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Mirror stun info to the old system for networking
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateLegacyStunSystem( void )
+{
+ // What a mess.
+#ifdef GAME_DLL
+ stun_struct_t *pStun = GetActiveStunInfo();
+ if ( pStun )
+ {
+ m_hStunner = pStun->hPlayer;
+ m_flStunFade = gpGlobals->curtime + pStun->flDuration;
+ m_flMovementStunTime = pStun->flDuration;
+ m_flStunEnd = pStun->flExpireTime;
+ if ( pStun->iStunFlags & TF_STUN_CONTROLS )
+ {
+ m_flStunEnd = pStun->flExpireTime;
+ }
+ m_iMovementStunAmount = pStun->flStunAmount;
+ m_iStunFlags = pStun->iStunFlags;
+
+ m_iMovementStunParity = ( m_iMovementStunParity + 1 ) & ( ( 1 << MOVEMENTSTUN_PARITY_BITS ) - 1 );
+ }
+#else
+ m_ActiveStunInfo.hPlayer = m_hStunner;
+ m_ActiveStunInfo.flDuration = m_flMovementStunTime;
+ m_ActiveStunInfo.flExpireTime = m_flStunEnd;
+ m_ActiveStunInfo.flStartFadeTime = m_flStunEnd;
+ m_ActiveStunInfo.flStunAmount = m_iMovementStunAmount;
+ m_ActiveStunInfo.iStunFlags = m_iStunFlags;
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+stun_struct_t *CTFPlayerShared::GetActiveStunInfo( void ) const
+{
+#ifdef GAME_DLL
+ return ( m_PlayerStuns.IsValidIndex( m_iStunIndex ) ) ? const_cast<stun_struct_t*>( &m_PlayerStuns[m_iStunIndex] ) : NULL;
+#else
+ return ( m_iStunIndex >= 0 ) ? const_cast<stun_struct_t*>( &m_ActiveStunInfo ) : NULL;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayer *CTFPlayerShared::GetStunner( void )
+{
+ return GetActiveStunInfo() ? GetActiveStunInfo()->hPlayer : NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddCritBoost( void )
+{
+#ifdef CLIENT_DLL
+ UpdateCritBoostEffect();
+#endif
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateCritBoostEffect( ECritBoostUpdateType eUpdateType )
+{
+ bool bShouldDisplayCritBoostEffect = IsCritBoosted()
+ || InCond( TF_COND_ENERGY_BUFF )
+ //|| IsHypeBuffed()
+ || InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF );
+
+ if ( m_pOuter->GetActiveTFWeapon() )
+ {
+ bShouldDisplayCritBoostEffect &= m_pOuter->GetActiveTFWeapon()->CanBeCritBoosted();
+ }
+
+ // Never show crit boost effects when stealthed
+ bShouldDisplayCritBoostEffect &= !IsStealthed();
+
+ // Never show crit boost effects when disguised unless we're the local player (so crits show on our viewmodel)
+ if ( !m_pOuter->IsLocalPlayer() )
+ {
+ bShouldDisplayCritBoostEffect &= !InCond( TF_COND_DISGUISED );
+ }
+
+ // Remove our current crit-boosted effect if we're forcing a refresh (in which case we'll
+ // regenerate an effect below) or if we aren't supposed to have an effect active.
+ if ( eUpdateType == kCritBoost_ForceRefresh || !bShouldDisplayCritBoostEffect )
+ {
+ if ( m_pOuter->m_pCritBoostEffect )
+ {
+ Assert( m_pOuter->m_pCritBoostEffect->IsValid() );
+ if ( m_pOuter->m_pCritBoostEffect->GetOwner() )
+ {
+ m_pOuter->m_pCritBoostEffect->GetOwner()->ParticleProp()->StopEmissionAndDestroyImmediately( m_pOuter->m_pCritBoostEffect );
+ }
+ else
+ {
+ m_pOuter->m_pCritBoostEffect->StopEmission();
+ }
+
+ m_pOuter->m_pCritBoostEffect = NULL;
+ }
+
+#ifdef CLIENT_DLL
+ if ( m_pCritBoostSoundLoop )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pCritBoostSoundLoop );
+ m_pCritBoostSoundLoop = NULL;
+ }
+#endif
+ }
+
+ // Should we have an active crit effect?
+ if ( bShouldDisplayCritBoostEffect )
+ {
+ CBaseEntity *pWeapon = NULL;
+ // Use GetRenderedWeaponModel() instead?
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ pWeapon = m_pOuter->GetViewModel(0);
+ }
+ else
+ {
+ // is this player an enemy?
+ if ( m_pOuter->GetTeamNumber() != GetLocalPlayerTeam() )
+ {
+ // are they a cloaked spy? or disguised as someone who almost assuredly isn't also critboosted?
+ if ( IsStealthed() || InCond( TF_COND_STEALTHED_BLINK ) || InCond( TF_COND_DISGUISED ) )
+ return;
+ }
+
+ pWeapon = m_pOuter->GetActiveWeapon();
+ }
+
+ if ( pWeapon )
+ {
+ if ( !m_pOuter->m_pCritBoostEffect )
+ {
+ if ( InCond( TF_COND_DISGUISED ) && !m_pOuter->IsLocalPlayer() && m_pOuter->GetTeamNumber() != GetLocalPlayerTeam() )
+ {
+ const char *pEffectName = ( GetDisguiseTeam() == TF_TEAM_RED ) ? "critgun_weaponmodel_red" : "critgun_weaponmodel_blu";
+ m_pOuter->m_pCritBoostEffect = pWeapon->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+ else
+ {
+ const char *pEffectName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "critgun_weaponmodel_red" : "critgun_weaponmodel_blu";
+ m_pOuter->m_pCritBoostEffect = pWeapon->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
+ }
+
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ if ( m_pOuter->m_pCritBoostEffect )
+ {
+ ClientLeafSystem()->SetRenderGroup( m_pOuter->m_pCritBoostEffect->RenderHandle(), RENDER_GROUP_VIEW_MODEL_TRANSLUCENT );
+ }
+ }
+ }
+ else
+ {
+ m_pOuter->m_pCritBoostEffect->StartEmission();
+ }
+
+ Assert( m_pOuter->m_pCritBoostEffect->IsValid() );
+ }
+
+#ifdef CLIENT_DLL
+ if ( m_pOuter->GetActiveTFWeapon() && !m_pCritBoostSoundLoop )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ CLocalPlayerFilter filter;
+ m_pCritBoostSoundLoop = controller.SoundCreate( filter, m_pOuter->entindex(), "Weapon_General.CritPower" );
+ controller.Play( m_pCritBoostSoundLoop, 1.0, 100 );
+ }
+#endif
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Soda Popper Condition
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnAddSodaPopperHype( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->EmitSound( "DisciplineDevice.PowerUp" );
+ }
+#endif // CLIENT_DLL
+}
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnRemoveSodaPopperHype( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->IsLocalPlayer() )
+ {
+ m_pOuter->EmitSound( "DisciplineDevice.PowerDown" );
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::GetStealthNoAttackExpireTime( void )
+{
+ return m_flStealthNoAttackExpire;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets whether this player is dominating the specified other player
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetPlayerDominated( CTFPlayer *pPlayer, bool bDominated )
+{
+ int iPlayerIndex = pPlayer->entindex();
+ m_bPlayerDominated.Set( iPlayerIndex, bDominated );
+ pPlayer->m_Shared.SetPlayerDominatingMe( m_pOuter, bDominated );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets whether this player is being dominated by the other player
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetPlayerDominatingMe( CTFPlayer *pPlayer, bool bDominated )
+{
+ int iPlayerIndex = pPlayer->entindex();
+ m_bPlayerDominatingMe.Set( iPlayerIndex, bDominated );
+
+#ifdef GAME_DLL
+ if ( bDominated )
+ {
+ CTFPlayer *pDominatingPlayer = ToTFPlayer( pPlayer );
+ if ( pDominatingPlayer && pDominatingPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
+ {
+ CBaseEntity *pHealedEntity = pPlayer->MedicGetHealTarget();
+ CTFPlayer *pHealedPlayer = ToTFPlayer( pHealedEntity );
+
+ if ( pHealedPlayer && pHealedPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
+ {
+ pHealedPlayer->AwardAchievement( ACHIEVEMENT_TF_HEAVY_EARN_MEDIC_DOMINATION );
+ }
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether this player is dominating the specified other player
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsPlayerDominated( int iPlayerIndex )
+{
+#ifdef CLIENT_DLL
+ // On the client, we only have data for the local player.
+ // As a result, it's only valid to ask for dominations related to the local player
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalPlayer )
+ return false;
+
+ Assert( m_pOuter->IsLocalPlayer() || pLocalPlayer->entindex() == iPlayerIndex );
+
+ if ( m_pOuter->IsLocalPlayer() )
+ return m_bPlayerDominated.Get( iPlayerIndex );
+
+ return pLocalPlayer->m_Shared.IsPlayerDominatingMe( m_pOuter->entindex() );
+#else
+ // Server has all the data.
+ return m_bPlayerDominated.Get( iPlayerIndex );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsPlayerDominatingMe( int iPlayerIndex )
+{
+ return m_bPlayerDominatingMe.Get( iPlayerIndex );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: True if the given player is a spy disguised as our team.
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsSpyDisguisedAsMyTeam( CTFPlayer *pPlayer )
+{
+ if ( !pPlayer )
+ return false;
+
+ if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) &&
+ pPlayer->GetTeamNumber() != m_pOuter->GetTeamNumber() &&
+ pPlayer->m_Shared.GetDisguiseTeam() == m_pOuter->GetTeamNumber() )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::NoteLastDamageTime( int nDamage )
+{
+ // we took damage
+ if ( ( nDamage > 5 || InCond( TF_COND_BLEEDING ) ) && !InCond( TF_COND_FEIGN_DEATH ) && InCond( TF_COND_STEALTHED ) )
+ {
+ m_flLastStealthExposeTime = gpGlobals->curtime;
+ AddCond( TF_COND_STEALTHED_BLINK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::OnSpyTouchedByEnemy( void )
+{
+ if ( !InCond( TF_COND_FEIGN_DEATH ) && InCond( TF_COND_STEALTHED ) )
+ {
+ m_flLastStealthExposeTime = gpGlobals->curtime;
+ AddCond( TF_COND_STEALTHED_BLINK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsEnteringOrExitingFullyInvisible( void )
+{
+ return ( ( GetPercentInvisiblePrevious() != 1.f && GetPercentInvisible() == 1.f ) ||
+ ( GetPercentInvisiblePrevious() == 1.f && GetPercentInvisible() != 1.f ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::CanRuneCharge() const
+{
+ return InCond( TF_COND_RUNE_SUPERNOVA );
+}
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::HasPhaseCloakAbility( void )
+{
+ int iPhaseStealth = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iPhaseStealth, ability_cloak_phase );
+ return iPhaseStealth > 0;
+}
+
+#endif // STAGING_ONLY
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::FadeInvis( float fAdditionalRateScale )
+{
+ ETFCond nExpiringCondition = TF_COND_LAST;
+ if ( InCond( TF_COND_STEALTHED ) )
+ {
+ nExpiringCondition = TF_COND_STEALTHED;
+ RemoveCond( TF_COND_STEALTHED );
+ }
+ else if ( InCond( TF_COND_STEALTHED_USER_BUFF ) )
+ {
+ nExpiringCondition = TF_COND_STEALTHED_USER_BUFF;
+ RemoveCond( TF_COND_STEALTHED_USER_BUFF );
+ }
+
+#ifdef GAME_DLL
+ // inform the bots
+ CTFWeaponInvis *pInvis = dynamic_cast< CTFWeaponInvis * >( m_pOuter->Weapon_OwnsThisID( TF_WEAPON_INVIS ) );
+ if ( pInvis )
+ {
+ TheNextBots().OnWeaponFired( m_pOuter, pInvis );
+ }
+#endif
+
+ // If present, give our invisibility weapon a chance to override our decloak
+ // rate scale.
+ float flDecloakRateScale = 0.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flDecloakRateScale, mult_decloak_rate );
+
+ // This comes from script, so sanity check the result.
+ if ( flDecloakRateScale <= 0.0f )
+ {
+ flDecloakRateScale = 1.0f;
+ }
+
+ float flInvisFadeTime = fAdditionalRateScale
+ * (tf_spy_invis_unstealth_time.GetFloat() * flDecloakRateScale);
+
+ if ( flInvisFadeTime < 0.15 )
+ {
+ // this was a force respawn, they can attack whenever
+ }
+ else if ( ( nExpiringCondition != TF_COND_STEALTHED_USER_BUFF ) && !InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
+ {
+ // next attack in some time
+ m_flStealthNoAttackExpire = gpGlobals->curtime + (tf_spy_cloak_no_attack_time.GetFloat() * flDecloakRateScale * fAdditionalRateScale);
+ }
+
+ m_flInvisChangeCompleteTime = gpGlobals->curtime + flInvisFadeTime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Approach our desired level of invisibility
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::InvisibilityThink( void )
+{
+ if ( m_pOuter->GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY && InCond( TF_COND_STEALTHED ) )
+ {
+ // Shouldn't happen, but it's a safety net
+ m_flInvisibility = 0.0f;
+ if ( InCond( TF_COND_STEALTHED ) )
+ {
+ RemoveCond( TF_COND_STEALTHED );
+ }
+ return;
+ }
+
+ float flTargetInvis = 0.0f;
+ float flTargetInvisScale = 1.0f;
+ if ( InCond( TF_COND_STEALTHED_BLINK ) || InCond( TF_COND_URINE ) )
+ {
+ // We were bumped into or hit for some damage.
+ flTargetInvisScale = TF_SPY_STEALTH_BLINKSCALE;
+ }
+
+ // Go invisible or appear.
+ if ( m_flInvisChangeCompleteTime > gpGlobals->curtime )
+ {
+ if ( IsStealthed() )
+ {
+ flTargetInvis = 1.0f - ( ( m_flInvisChangeCompleteTime - gpGlobals->curtime ) );
+ }
+ else
+ {
+ flTargetInvis = ( ( m_flInvisChangeCompleteTime - gpGlobals->curtime ) * 0.5f );
+ }
+ }
+ else
+ {
+ if ( IsStealthed() )
+ {
+ flTargetInvis = 1.0f;
+ m_flLastNoMovementTime = -1.f;
+
+ if ( m_bMotionCloak )
+ {
+ if ( m_flCloakMeter == 0.f )
+ {
+ Vector vVel = m_pOuter->GetAbsVelocity();
+ float fSpdSqr = vVel.LengthSqr();
+ flTargetInvis = RemapVal( fSpdSqr, 0, m_pOuter->MaxSpeed()*m_pOuter->MaxSpeed(), 1.0f, 0.5f );
+ }
+ else
+ {
+ flTargetInvis = 1.f;
+ }
+ }
+ }
+ else
+ {
+ flTargetInvis = 0.0f;
+ }
+ }
+
+ flTargetInvis *= flTargetInvisScale;
+ m_flPrevInvisibility = m_flInvisibility;
+ m_flInvisibility = clamp( flTargetInvis, 0.0f, 1.0f );
+
+#ifdef STAGING_ONLY
+ // Sniper cloak
+ int iSniperCloak = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iSniperCloak, ability_cloak_sniper );
+ if ( iSniperCloak )
+ {
+ static const float flSniperCloakDelay = 2.f;
+ bool bMoving = !m_pOuter->GetAbsVelocity().IsZero();
+ bool bAiming = IsAiming();
+
+ if ( !bMoving && !bAiming && !IsCarryingObject() )
+ {
+ if ( m_flLastNoMovementTime == -1.f )
+ {
+ m_flLastNoMovementTime = gpGlobals->curtime;
+ }
+
+ if ( !InCond( TF_COND_STEALTHED_USER_BUFF ) && !m_flInvisibility && m_flLastNoMovementTime > 0.f && gpGlobals->curtime - m_flLastNoMovementTime > flSniperCloakDelay )
+ {
+ AddCond( TF_COND_STEALTHED_USER_BUFF, -1.f, m_pOuter );
+ }
+ }
+ else
+ {
+ if ( IsStealthed() )
+ {
+ FadeInvis( 0.1f );
+ }
+ m_flLastNoMovementTime = -1.f;
+ }
+ }
+#endif // STAGING_ONLY
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: How invisible is the player [0..1]
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::GetPercentInvisible( void ) const
+{
+#ifdef STAGING_ONLY
+#ifdef CLIENT_DLL
+ // This is a client hack to make everyone else invisible to the local player
+ C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalTFPlayer != m_pOuter )
+ return 1.f;
+#endif // CLIENT_DLL
+#endif // STAGING_ONLY
+
+ return m_flInvisibility;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsCritBoosted( void ) const
+{
+ bool bAllWeaponCritActive = ( InCond( TF_COND_CRITBOOSTED ) ||
+ InCond( TF_COND_CRITBOOSTED_PUMPKIN ) ||
+ InCond( TF_COND_CRITBOOSTED_USER_BUFF ) ||
+#ifdef CLIENT_DLL
+ InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) ||
+#endif
+ InCond( TF_COND_CRITBOOSTED_FIRST_BLOOD ) ||
+ InCond( TF_COND_CRITBOOSTED_BONUS_TIME ) ||
+ InCond( TF_COND_CRITBOOSTED_CTF_CAPTURE ) ||
+ InCond( TF_COND_CRITBOOSTED_ON_KILL ) ||
+ InCond( TF_COND_CRITBOOSTED_CARD_EFFECT ) ||
+ InCond( TF_COND_CRITBOOSTED_RUNE_TEMP ) );
+
+ if ( bAllWeaponCritActive )
+ return true;
+
+
+ CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( m_pOuter->GetActiveWeapon() );
+ if ( pWeapon )
+ {
+ if ( InCond( TF_COND_CRITBOOSTED_RAGE_BUFF ) && pWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_PRIMARY )
+ {
+ // Only primary weapon can be crit boosted by pyro rage
+ return true;
+ }
+
+ float flCritHealthPercent = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flCritHealthPercent, mult_crit_when_health_is_below_percent );
+
+ if ( flCritHealthPercent < 1.0f && m_pOuter->HealthFraction() < flCritHealthPercent )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsInvulnerable( void ) const
+{
+ bool bInvuln = InCond( TF_COND_INVULNERABLE ) ||
+ InCond( TF_COND_INVULNERABLE_USER_BUFF ) ||
+ InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) ||
+ InCond( TF_COND_INVULNERABLE_CARD_EFFECT );
+
+ return bInvuln;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsStealthed( void ) const
+{
+#ifdef STAGING_ONLY
+#ifdef CLIENT_DLL
+ // This is a client hack to make everyone else invisible to the local player
+ C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalTFPlayer != m_pOuter )
+ return true;
+#endif // CLIENT_DLL
+#endif // STAGING_ONLY
+
+ return ( InCond( TF_COND_STEALTHED ) || InCond( TF_COND_STEALTHED_USER_BUFF ) || InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) );
+}
+
+bool CTFPlayerShared::CanBeDebuffed( void ) const
+{
+ if ( IsInvulnerable() )
+ return false;
+
+ if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_PASSTIME_INTERCEPTION ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start the process of disguising
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::Disguise( int nTeam, int nClass, CTFPlayer* pDesiredTarget, bool bOnKill )
+{
+ int nRealTeam = m_pOuter->GetTeamNumber();
+ int nRealClass = m_pOuter->GetPlayerClass()->GetClassIndex();
+
+ Assert ( ( nClass >= TF_CLASS_SCOUT ) && ( nClass <= TF_CLASS_ENGINEER ) );
+
+ // we're not a spy
+ if ( nRealClass != TF_CLASS_SPY )
+ {
+ return;
+ }
+
+ if ( InCond( TF_COND_TAUNTING ) )
+ {
+ // not allowed to disguise while taunting
+ return;
+ }
+
+ // we're not disguising as anything but ourselves (so reset everything)
+ if ( nRealTeam == nTeam && nRealClass == nClass )
+ {
+ RemoveDisguise();
+ return;
+ }
+
+ // Ignore disguise of the same type, unless we're using 'Your Eternal Reward'
+ if ( nTeam == m_nDisguiseTeam && nClass == m_nDisguiseClass && !bOnKill )
+ {
+#ifdef GAME_DLL
+ DetermineDisguiseWeapon( false );
+#endif
+ return;
+ }
+
+ // invalid team
+ if ( nTeam <= TEAM_SPECTATOR || nTeam >= TF_TEAM_COUNT )
+ {
+ return;
+ }
+
+ // invalid class
+ if ( nClass <= TF_CLASS_UNDEFINED || nClass >= TF_CLASS_COUNT )
+ {
+ return;
+ }
+
+ // are we already in the middle of disguising as this class?
+ // (the lastdisguise key might get pushed multiple times before the disguise is complete)
+ if ( InCond( TF_COND_DISGUISING ) )
+ {
+ if ( nTeam == m_nDesiredDisguiseTeam && nClass == m_nDesiredDisguiseClass )
+ {
+ return;
+ }
+ }
+
+ m_hDesiredDisguiseTarget.Set( pDesiredTarget );
+ m_nDesiredDisguiseClass = nClass;
+ m_nDesiredDisguiseTeam = nTeam;
+
+ m_bLastDisguisedAsOwnTeam = ( m_nDesiredDisguiseTeam == m_pOuter->GetTeamNumber() );
+
+ AddCond( TF_COND_DISGUISING );
+
+ // Start the think to complete our disguise
+ float flTimeToDisguise = TF_TIME_TO_DISGUISE;
+ //CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iTimeToDisguise, disguise_speed_penalty ); // Unused Attr
+
+ // STAGING_SPY
+ // Quick disguise if you already disguised
+ if ( InCond( TF_COND_DISGUISED ) )
+ {
+ flTimeToDisguise = TF_TIME_TO_QUICK_DISGUISE;
+ }
+
+ if ( pDesiredTarget )
+ {
+ flTimeToDisguise = 0;
+ }
+ m_flDisguiseCompleteTime = gpGlobals->curtime + flTimeToDisguise;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set our target with a player we've found to emulate
+//-----------------------------------------------------------------------------
+#ifndef CLIENT_DLL
+void CTFPlayerShared::FindDisguiseTarget( void )
+{
+ if ( m_hDesiredDisguiseTarget )
+ {
+ m_hDisguiseTarget.Set( m_hDesiredDisguiseTarget.Get() );
+ m_hDesiredDisguiseTarget.Set( NULL );
+ }
+ else
+ {
+ m_hDisguiseTarget = m_pOuter->TeamFortress_GetDisguiseTarget( m_nDisguiseTeam, m_nDisguiseClass );
+ }
+
+ if ( m_hDisguiseTarget )
+ {
+ m_iDisguiseTargetIndex.Set( m_hDisguiseTarget.Get()->entindex() );
+ Assert( m_iDisguiseTargetIndex >= 1 && m_iDisguiseTargetIndex <= MAX_PLAYERS );
+ }
+ else
+ {
+ m_iDisguiseTargetIndex.Set( TF_DISGUISE_TARGET_INDEX_NONE );
+ }
+
+ m_pOuter->CreateDisguiseWeaponList( ToTFPlayer( m_hDisguiseTarget.Get() ) );
+}
+
+#endif
+
+int CTFPlayerShared::GetDisguiseTeam( void ) const
+{
+ return InCond( TF_COND_DISGUISED_AS_DISPENSER ) ? (int)( ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ) : m_nDisguiseTeam;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Complete our disguise
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::CompleteDisguise( void )
+{
+ AddCond( TF_COND_DISGUISED );
+
+ m_nDisguiseClass = m_nDesiredDisguiseClass;
+ m_nDisguiseTeam = m_nDesiredDisguiseTeam;
+
+ if ( m_nDisguiseClass == TF_CLASS_SPY )
+ {
+ m_nMaskClass = rand()%9+1;
+ }
+
+ RemoveCond( TF_COND_DISGUISING );
+
+#ifdef GAME_DLL
+ // Update the player model and skin.
+ m_pOuter->UpdateModel();
+ m_pOuter->ClearExpression();
+
+ FindDisguiseTarget();
+
+ if ( GetDisguiseTarget() )
+ {
+ m_iDisguiseHealth = GetDisguiseTarget()->GetHealth();
+ if ( m_iDisguiseHealth <= 0 || !GetDisguiseTarget()->IsAlive() )
+ {
+ // If we disguised as an enemy who is currently dead, just set us to full health.
+ m_iDisguiseHealth = GetDisguiseMaxHealth();
+ }
+ }
+ else
+ {
+ int iMaxHealth = m_pOuter->GetMaxHealth();
+ m_iDisguiseHealth = (int)random->RandomInt( iMaxHealth / 2, iMaxHealth );
+ }
+
+ // In Medieval mode, don't force primary weapon because most classes just have melee weapons
+ DetermineDisguiseWeapon( !TFGameRules()->IsInMedievalMode() );
+ DetermineDisguiseWearables();
+#endif
+
+ m_pOuter->TeamFortress_SetSpeed();
+
+ m_flDisguiseCompleteTime = 0.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetDisguiseHealth( int iDisguiseHealth )
+{
+ m_iDisguiseHealth = iDisguiseHealth;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetDisguiseMaxHealth( void )
+{
+ TFPlayerClassData_t *pClass = g_pTFPlayerClassDataMgr->Get( GetDisguiseClass() );
+ if ( pClass )
+ {
+ return pClass->m_nMaxHealth;
+ }
+ else
+ {
+ return m_pOuter->GetMaxHealth();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RemoveDisguise( void )
+{
+ if ( GetDisguiseTeam() != m_pOuter->GetTeamNumber() )
+ {
+ if ( InCond( TF_COND_TELEPORTED ) )
+ {
+ RemoveCond( TF_COND_TELEPORTED );
+ }
+ }
+
+ RemoveCond( TF_COND_DISGUISED );
+ RemoveCond( TF_COND_DISGUISING );
+
+ AddCond( TF_COND_DISGUISE_WEARINGOFF, 0.5f );
+}
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::DetermineDisguiseWeapon( bool bForcePrimary )
+{
+ Assert( m_pOuter->GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY );
+
+ const char* strDisguiseWeapon = NULL;
+
+ CTFPlayer *pDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() );
+ TFPlayerClassData_t *pData = GetPlayerClassData( m_nDisguiseClass );
+ if ( pDisguiseTarget && (pDisguiseTarget->GetPlayerClass()->GetClassIndex() != m_nDisguiseClass) )
+ {
+ pDisguiseTarget = NULL;
+ }
+
+ // Determine which slot we have active.
+ int iCurrentSlot = 0;
+ if ( m_pOuter->GetActiveTFWeapon() && !bForcePrimary )
+ {
+ iCurrentSlot = m_pOuter->GetActiveTFWeapon()->GetSlot();
+ if ( (iCurrentSlot == 3) && // Cig Case, so they are using the menu not a key bind to disguise.
+ m_pOuter->GetLastWeapon() )
+ {
+ iCurrentSlot = m_pOuter->GetLastWeapon()->GetSlot();
+ }
+ }
+
+ CTFWeaponBase *pItemWeapon = NULL;
+ if ( pDisguiseTarget )
+ {
+ CTFWeaponBase *pLastDisguiseWeapon = m_hDisguiseWeapon;
+ CTFWeaponBase *pFirstValidWeapon = NULL;
+ // Cycle through the target's weapons and see if we have a match.
+ // Note that it's possible the disguise target doesn't have a weapon in the slot we want,
+ // for example if they have replaced it with an unlockable that isn't a weapon (wearable).
+ for ( int i=0; i<m_pOuter->m_hDisguiseWeaponList.Count(); ++i )
+ {
+ CTFWeaponBase *pWeapon = m_pOuter->m_hDisguiseWeaponList[i];
+
+ if ( !pWeapon )
+ continue;
+
+ if ( !pFirstValidWeapon )
+ {
+ pFirstValidWeapon = pWeapon;
+ }
+
+ // skip passtime gun
+ if ( pWeapon->GetWeaponID() == TF_WEAPON_PASSTIME_GUN )
+ {
+ continue;
+ }
+
+ if ( pWeapon->GetSlot() == iCurrentSlot )
+ {
+ pItemWeapon = pWeapon;
+ break;
+ }
+ }
+
+ if ( !pItemWeapon )
+ {
+ if ( pLastDisguiseWeapon )
+ {
+ pItemWeapon = pLastDisguiseWeapon;
+ }
+ else if ( pFirstValidWeapon )
+ {
+ pItemWeapon = pFirstValidWeapon;
+ }
+ }
+
+ if ( pItemWeapon )
+ {
+ strDisguiseWeapon = pItemWeapon->GetClassname();
+ }
+ }
+
+ if ( !pItemWeapon && pData )
+ {
+ // We have not found our item yet, so cycle through the class's default weapons
+ // to find a match.
+ for ( int i=0; i<TF_PLAYER_WEAPON_COUNT; ++i )
+ {
+ if ( pData->m_aWeapons[i] == TF_WEAPON_NONE )
+ continue;
+ const char *pWpnName = WeaponIdToAlias( pData->m_aWeapons[i] );
+ WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pWpnName );
+ Assert( hWpnInfo != GetInvalidWeaponInfoHandle() );
+ CTFWeaponInfo *pWeaponInfo = dynamic_cast<CTFWeaponInfo*>( GetFileWeaponInfoFromHandle( hWpnInfo ) );
+ if ( pWeaponInfo->iSlot == iCurrentSlot )
+ {
+ strDisguiseWeapon = pWeaponInfo->szClassName;
+ }
+ }
+ }
+
+ if ( strDisguiseWeapon )
+ {
+ // Remove the old disguise weapon, if any.
+ RemoveDisguiseWeapon();
+
+ CEconItemView *pItem = NULL;
+ if ( pItemWeapon )
+ {
+ // We are copying a generated, non-base item.
+ CAttributeContainer *pContainer = pItemWeapon->GetAttributeContainer();
+ if ( pContainer )
+ {
+ pItem = pContainer->GetItem();
+ }
+ }
+
+ // We may need a sub-type if we're a builder. Otherwise we'll always appear as a engineer's workbox.
+ int iSubType = 0;
+ if ( Q_strcmp( strDisguiseWeapon, "tf_weapon_builder" ) == 0 )
+ {
+ return; // Temporary.
+ }
+
+ m_hDisguiseWeapon.Set( dynamic_cast<CTFWeaponBase*>(m_pOuter->GiveNamedItem( strDisguiseWeapon, iSubType, pItem, true )) );
+ if ( m_hDisguiseWeapon )
+ {
+ m_hDisguiseWeapon->SetTouch( NULL );// no touch
+ m_hDisguiseWeapon->SetOwner( dynamic_cast<CBaseCombatCharacter*>(m_pOuter) );
+ m_hDisguiseWeapon->SetOwnerEntity( m_pOuter );
+ m_hDisguiseWeapon->SetParent( m_pOuter );
+ m_hDisguiseWeapon->FollowEntity( m_pOuter, true );
+ m_hDisguiseWeapon->m_iState = WEAPON_IS_ACTIVE;
+ m_hDisguiseWeapon->m_bDisguiseWeapon = true;
+ m_hDisguiseWeapon->SetContextThink( &CTFWeaponBase::DisguiseWeaponThink, gpGlobals->curtime + 0.5, "DisguiseWeaponThink" );
+
+
+ // Ammo/clip state is displayed to attached medics
+ m_iDisguiseAmmo = 0;
+ if ( !m_hDisguiseWeapon->IsMeleeWeapon() )
+ {
+ // Use the player we're disguised as if possible
+ if ( pDisguiseTarget )
+ {
+ CTFWeaponBase *pWeapon = pDisguiseTarget->GetActiveTFWeapon();
+ if ( pWeapon && pWeapon->GetWeaponID() == m_hDisguiseWeapon->GetWeaponID() )
+ {
+ m_iDisguiseAmmo = pWeapon->UsesClipsForAmmo1() ?
+ pWeapon->Clip1() :
+ pDisguiseTarget->GetAmmoCount( pWeapon->GetPrimaryAmmoType() );
+ }
+ }
+
+ // Otherwise display a faked ammo count
+ if ( !m_iDisguiseAmmo )
+ {
+ int nMaxCount = m_hDisguiseWeapon->UsesClipsForAmmo1() ?
+ m_hDisguiseWeapon->GetMaxClip1() :
+ m_pOuter->GetMaxAmmo( m_hDisguiseWeapon->GetPrimaryAmmoType(), m_nDisguiseClass );
+
+ m_iDisguiseAmmo = (int)random->RandomInt( 1, nMaxCount );
+ }
+ }
+ }
+ }
+}
+
+void CTFPlayerShared::DetermineDisguiseWearables()
+{
+ CTFPlayer *pDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() );
+ if ( !pDisguiseTarget )
+ return;
+
+ // Remove any existing disguise wearables.
+ RemoveDisguiseWearables();
+
+ if ( GetDisguiseClass() != pDisguiseTarget->GetPlayerClass()->GetClassIndex() )
+ return;
+
+ // Equip us with copies of our disguise target's wearables.
+ int iPlayerSkinOverride = 0;
+ for ( int i=0; i<pDisguiseTarget->GetNumWearables(); ++i )
+ {
+ CTFWearable *pWearable = dynamic_cast<CTFWearable*>( pDisguiseTarget->GetWearable( i ) );
+ if ( pWearable )
+ {
+ if ( pWearable->IsDisguiseWearable() )
+ continue; // Never copy a target's disguise wearables.
+ CEconItemView *pScriptItem = pWearable->GetAttributeContainer()->GetItem();
+ if ( pScriptItem && pScriptItem->IsValid() && pScriptItem->GetStaticData()->GetItemClass() )
+ {
+ CEconEntity *pNewItem = dynamic_cast<CEconEntity*>( m_pOuter->GiveNamedItem( pScriptItem->GetStaticData()->GetItemClass(), 0, pScriptItem ) );
+ CTFWearable *pNewWearable = dynamic_cast<CTFWearable*>( pNewItem );
+ Assert( pNewWearable );
+ if ( pNewWearable )
+ {
+ pNewWearable->SetDisguiseWearable( true );
+ pNewWearable->GiveTo( m_pOuter );
+
+ // copy over the level for levelable items
+ CTFWearableLevelableItem *pLevelableItem = dynamic_cast<CTFWearableLevelableItem*>( pWearable );
+ CTFWearableLevelableItem *pNewLevelableItem = dynamic_cast<CTFWearableLevelableItem*>( pNewWearable );
+ if ( pLevelableItem && pNewLevelableItem )
+ {
+ int nBodyGroup = pNewLevelableItem->FindBodygroupByName( LEVELABLE_ITEM_BODYGROUP_NAME );
+ if ( nBodyGroup != -1 )
+ {
+ pNewLevelableItem->SetBodygroup( nBodyGroup, pLevelableItem->GetLevel() );
+ }
+ }
+
+ // find the first skin override item
+ if ( iPlayerSkinOverride == 0 )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pNewWearable, iPlayerSkinOverride, player_skin_override );
+ }
+ }
+ }
+ }
+ }
+
+ m_nDisguiseSkinOverride = iPlayerSkinOverride;
+}
+
+void CTFPlayerShared::RemoveDisguiseWearables()
+{
+ bool bFoundDisguiseWearable = true;
+ while ( bFoundDisguiseWearable )
+ {
+ int i = 0;
+ for ( ; i<m_pOuter->GetNumWearables(); ++i )
+ {
+ CTFWearable *pWearable = dynamic_cast<CTFWearable*>( m_pOuter->GetWearable( i ) );
+ if ( pWearable && pWearable->IsDisguiseWearable() )
+ {
+ // Every time we do this the list changes, so we have to loop through again.
+ pWearable->RemoveFrom( m_pOuter );
+ break;
+ }
+ }
+ if ( i == m_pOuter->GetNumWearables() )
+ {
+ bFoundDisguiseWearable = false;
+ }
+ }
+
+}
+
+#endif // GAME_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ProcessDisguiseImpulse( CTFPlayer *pPlayer )
+{
+ // Get the player owning the weapon.
+ if ( !pPlayer )
+ return;
+
+ if ( pPlayer->GetImpulse() > 200 )
+ {
+ char szImpulse[6];
+ Q_snprintf( szImpulse, sizeof( szImpulse ), "%d", pPlayer->GetImpulse() );
+
+ char szTeam[3];
+ Q_snprintf( szTeam, sizeof( szTeam ), "%c", szImpulse[1] );
+
+ char szClass[3];
+ Q_snprintf( szClass, sizeof( szClass ), "%c", szImpulse[2] );
+
+ // 'Your Eternal Reward' handling
+ bool bSwitchWeaponOnly = false;
+ if ( pPlayer->CanDisguise_OnKill() && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ // Only trying to change the disguise weapon via 'lastdisguise'
+ if ( Q_atoi( szClass ) == pPlayer->m_Shared.GetDisguiseClass() && Q_atoi( szTeam ) == pPlayer->m_Shared.GetDisguiseTeam() )
+ {
+ bSwitchWeaponOnly = true;
+ }
+ }
+
+ if ( pPlayer->CanDisguise() || bSwitchWeaponOnly )
+ {
+ // intercepting the team value and reassigning what gets passed into Disguise()
+ // because the team numbers in the client menu don't match the #define values for the teams
+ pPlayer->m_Shared.Disguise( Q_atoi( szTeam ), Q_atoi( szClass ) );
+
+ // Switch from the PDA to our previous weapon
+ if ( GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_PDA_SPY )
+ {
+ pPlayer->SelectLastItem();
+ }
+ }
+ }
+}
+
+bool CTFPlayerShared::CanRecieveMedigunChargeEffect( medigun_charge_types eType ) const
+{
+ bool bCanRecieve = true;
+
+ const CTFItem *pItem = m_pOuter->GetItem();
+ if ( pItem && pItem->GetItemID() == TF_ITEM_CAPTURE_FLAG )
+ {
+ bCanRecieve = false;
+
+ // The "flag" in Player Destruction doesn't block uber
+ const CCaptureFlag* pFlag = static_cast< const CCaptureFlag* >( pItem );
+ if ( pFlag->GetType() == TF_FLAGTYPE_PLAYER_DESTRUCTION )
+ {
+ bCanRecieve = true;
+ }
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ // allow bot flag carriers to be ubered
+ bCanRecieve = true;
+ }
+
+ if ( ( eType == MEDIGUN_CHARGE_MEGAHEAL )
+ || ( eType == MEDIGUN_CHARGE_BULLET_RESIST )
+ || ( eType == MEDIGUN_CHARGE_BLAST_RESIST )
+ || ( eType == MEDIGUN_CHARGE_FIRE_RESIST ) )
+ {
+ bCanRecieve = true;
+ }
+ }
+
+
+ if( TFGameRules() && TFGameRules()->IsPasstimeMode() )
+ {
+ bCanRecieve &= ! HasPasstimeBall();
+ }
+
+ return bCanRecieve;
+}
+
+#ifdef GAME_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose: Heal players.
+// pPlayer is person who healed us
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::Heal( CBaseEntity *pHealer, float flAmount, float flOverhealBonus, float flOverhealDecayMult, bool bDispenserHeal /* = false */, CTFPlayer *pHealScorer /* = NULL */ )
+{
+ // If already healing, stop healing
+ float flHealAccum = 0;
+ if ( FindHealerIndex(pHealer) != m_aHealers.InvalidIndex() )
+ {
+ flHealAccum = StopHealing( pHealer );
+ }
+
+ healers_t newHealer;
+ newHealer.pHealer = pHealer;
+ newHealer.flAmount = flAmount;
+ newHealer.flHealAccum = flHealAccum;
+ newHealer.iKillsWhileBeingHealed = 0;
+ newHealer.flOverhealBonus = flOverhealBonus;
+ newHealer.flOverhealDecayMult = flOverhealDecayMult;
+ newHealer.bDispenserHeal = bDispenserHeal;
+ newHealer.flHealedLastSecond = 0;
+
+ if ( pHealScorer )
+ {
+ newHealer.pHealScorer = pHealScorer;
+ }
+ else
+ {
+ //Assert( pHealer->IsPlayer() );
+ newHealer.pHealScorer = pHealer;
+ }
+
+ m_aHealers.AddToTail( newHealer );
+
+ AddCond( TF_COND_HEALTH_BUFF, PERMANENT_CONDITION, pHealer );
+
+ RecalculateChargeEffects();
+
+ m_nNumHealers = m_aHealers.Count();
+
+ if ( pHealer && pHealer->IsPlayer() )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( pHealer );
+ Assert(pPlayer);
+ pPlayer->m_AchievementData.AddTargetToHistory( m_pOuter );
+ pPlayer->TeamFortress_SetSpeed();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Heal players.
+// pPlayer is person who healed us
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::StopHealing( CBaseEntity *pHealer )
+{
+ int iIndex = FindHealerIndex(pHealer);
+ if ( iIndex == m_aHealers.InvalidIndex() )
+ return 0;
+
+ float flHealingDone = 0.f;
+
+ if ( iIndex != m_aHealers.InvalidIndex() )
+ {
+ flHealingDone = m_aHealers[iIndex].flHealAccum;
+ m_aHealers.Remove( iIndex );
+ }
+
+ if ( !m_aHealers.Count() )
+ {
+ RemoveCond( TF_COND_HEALTH_BUFF );
+ }
+
+ RecalculateChargeEffects();
+
+ m_nNumHealers = m_aHealers.Count();
+
+ return flHealingDone;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RecalculateChargeEffects( bool bInstantRemove )
+{
+ struct medic_charges_t
+ {
+ bool bActive;
+ CTFPlayer *pProvider;
+ };
+
+ medic_charges_t aCharges[MEDIGUN_NUM_CHARGE_TYPES];
+
+ for ( int i = 0; i < ARRAYSIZE( aCharges ); i++ )
+ {
+ aCharges[i].bActive = m_pOuter->m_bInPowerPlay;
+ aCharges[i].pProvider = NULL;
+ }
+
+ medigun_charge_types iMyCharge = m_pOuter->GetChargeEffectBeingProvided();
+
+ if ( iMyCharge != MEDIGUN_CHARGE_INVALID )
+ {
+ Assert( iMyCharge >= 0 && iMyCharge < MEDIGUN_NUM_CHARGE_TYPES );
+ aCharges[iMyCharge].bActive = true;
+ aCharges[iMyCharge].pProvider = m_pOuter;
+ }
+
+ // Loop through our medics and get all their charges
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ if ( !m_aHealers[i].pHealer )
+ continue;
+
+ CTFPlayer *pPlayer = ToTFPlayer( m_aHealers[i].pHealer );
+ if ( !pPlayer )
+ continue;
+
+ medigun_charge_types iCharge = pPlayer->GetChargeEffectBeingProvided();
+
+ if ( iCharge != MEDIGUN_CHARGE_INVALID )
+ {
+ Assert( iCharge >= 0 && iCharge < MEDIGUN_NUM_CHARGE_TYPES );
+ aCharges[iCharge].bActive = true;
+ aCharges[iCharge].pProvider = pPlayer;
+ }
+ }
+
+ if ( !CanRecieveMedigunChargeEffect( iMyCharge ) )
+ {
+ aCharges[MEDIGUN_CHARGE_INVULN].bActive = false;
+ }
+
+ SetChargeEffect( MEDIGUN_CHARGE_INVULN, aCharges[MEDIGUN_CHARGE_INVULN].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_INVULN ], tf_invuln_time.GetFloat(), aCharges[MEDIGUN_CHARGE_INVULN].pProvider );
+ SetChargeEffect( MEDIGUN_CHARGE_CRITICALBOOST, aCharges[MEDIGUN_CHARGE_CRITICALBOOST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_CRITICALBOOST ], 0.0f, aCharges[MEDIGUN_CHARGE_CRITICALBOOST].pProvider );
+ SetChargeEffect( MEDIGUN_CHARGE_MEGAHEAL, aCharges[MEDIGUN_CHARGE_MEGAHEAL].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_MEGAHEAL ], 0.0f, aCharges[MEDIGUN_CHARGE_MEGAHEAL].pProvider );
+ SetChargeEffect( MEDIGUN_CHARGE_BULLET_RESIST, aCharges[MEDIGUN_CHARGE_BULLET_RESIST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_BULLET_RESIST ], 0.0f, aCharges[MEDIGUN_CHARGE_BULLET_RESIST].pProvider );
+ SetChargeEffect( MEDIGUN_CHARGE_BLAST_RESIST, aCharges[MEDIGUN_CHARGE_BLAST_RESIST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_BLAST_RESIST ], 0.0f, aCharges[MEDIGUN_CHARGE_BLAST_RESIST].pProvider );
+ SetChargeEffect( MEDIGUN_CHARGE_FIRE_RESIST, aCharges[MEDIGUN_CHARGE_FIRE_RESIST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_FIRE_RESIST ], 0.0f, aCharges[MEDIGUN_CHARGE_FIRE_RESIST].pProvider );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::TestAndExpireChargeEffect( medigun_charge_types iCharge )
+{
+ const MedigunEffects_t& effects = g_MedigunEffects[iCharge];
+
+ if ( InCond( effects.eCondition ) )
+ {
+ bool bRemoveEffect = false;
+ bool bGameInWinState = TFGameRules()->State_Get() == GR_STATE_TEAM_WIN;
+ bool bPlayerOnWinningTeam = TFGameRules()->GetWinningTeam() == m_pOuter->GetTeamNumber();
+
+ // Lose all charge effects in post-win state if we're the losing team
+ if ( bGameInWinState && !bPlayerOnWinningTeam )
+ {
+ bRemoveEffect = true;
+ }
+
+ if ( m_flChargeEffectOffTime[iCharge] )
+ {
+ if ( gpGlobals->curtime > m_flChargeEffectOffTime[iCharge] )
+ {
+ bRemoveEffect = true;
+ }
+ if (iCharge == MEDIGUN_CHARGE_CRITICALBOOST && ( bGameInWinState && bPlayerOnWinningTeam ) )
+ {
+ bRemoveEffect = false;
+ m_flChargeEffectOffTime[iCharge] = 0;
+ }
+ if ( GetRevengeCrits() > 0 && effects.eCondition == TF_COND_CRITBOOSTED )
+ {
+ // Don't remove while we have a weapon deployed that can consume revenge crits
+ CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon();
+ if ( pWeapon && pWeapon->CanHaveRevengeCrits() )
+ bRemoveEffect = false;
+ }
+ }
+
+ // Check healers for possible usercommand invuln exploit
+ FOR_EACH_VEC( m_aHealers, i )
+ {
+ CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer );
+ if ( !pTFHealer )
+ continue;
+
+ CTFPlayer *pTFProvider = ToTFPlayer( GetConditionProvider( effects.eCondition ) );
+ if ( !pTFProvider )
+ continue;
+
+ if ( pTFProvider == pTFHealer && pTFHealer->GetTimeSinceLastUserCommand() > weapon_medigun_chargerelease_rate.GetFloat() + 1.f )
+ {
+ // Force remove uber and detach the medigun
+ bRemoveEffect = true;
+ pTFHealer->Weapon_Switch( pTFHealer->Weapon_GetSlot( TF_WPN_TYPE_MELEE ) );
+ }
+ }
+
+ if ( bRemoveEffect )
+ {
+ m_flChargeEffectOffTime[iCharge] = 0;
+ RemoveCond( effects.eCondition );
+ if ( effects.eWearingOffCondition != TF_COND_LAST )
+ {
+ RemoveCond( effects.eWearingOffCondition );
+ }
+ }
+ }
+ else if ( m_bChargeSoundEffectsOn[iCharge] )
+ {
+ if ( effects.pszChargeOnSound[0] )
+ {
+ m_pOuter->StopSound( effects.pszChargeOnSound );
+ }
+ m_bChargeSoundEffectsOn[iCharge] = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We've started a new charge effect
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SendNewInvulnGameEvent( void )
+{
+ // for each medic healing me
+ for ( int i=0;i<m_aHealers.Count();i++ )
+ {
+ CTFPlayer *pMedic = ToTFPlayer( GetHealerByIndex(i) );
+ if ( !pMedic )
+ continue;
+
+ // ACHIEVEMENT_TF_MEDIC_CHARGE_FRIENDS
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_invulned" );
+
+ if ( event )
+ {
+ event->SetInt( "userid", m_pOuter->GetUserID() );
+ event->SetInt( "medic_userid", pMedic->GetUserID() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetChargeEffect( medigun_charge_types iCharge, bool bState, bool bInstant, const MedigunEffects_t& effects, float flWearOffTime, CTFPlayer *pProvider /*= NULL*/ )
+{
+ if ( effects.eCondition == TF_COND_CRITBOOSTED )
+ {
+ // Don't remove while we have a weapon deployed that can consume revenge crits
+ CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon();
+ if ( pWeapon )
+ {
+ if ( pWeapon->CanHaveRevengeCrits() && GetRevengeCrits() > 0 )
+ {
+ return;
+ }
+
+ if ( pWeapon->HasLastShotCritical() )
+ {
+ return;
+ }
+ }
+ }
+
+ bool bCurrentState = InCond( effects.eCondition );
+ if ( bCurrentState == bState )
+ {
+ if ( bState && m_flChargeEffectOffTime[iCharge] )
+ {
+ m_flChargeEffectOffTime[iCharge] = 0;
+ if ( effects.eWearingOffCondition != TF_COND_LAST )
+ {
+ RemoveCond( effects.eWearingOffCondition );
+ }
+
+ SendNewInvulnGameEvent();
+ }
+ return;
+ }
+
+ // Avoid infinite duration, because... the internet.
+ float flMaxDuration = ( pProvider && pProvider->IsBot() ) ? PERMANENT_CONDITION : weapon_medigun_chargerelease_rate.GetFloat() + ( ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) ? 8.f : 1.f );
+
+ if ( bState )
+ {
+ if ( m_flChargeEffectOffTime[iCharge] )
+ {
+ m_pOuter->StopSound( effects.pszChargeOffSound );
+
+ m_flChargeEffectOffTime[iCharge] = 0;
+ if ( effects.eWearingOffCondition != TF_COND_LAST )
+ {
+ RemoveCond( effects.eWearingOffCondition );
+ }
+ }
+
+ // Invulnerable turning on
+ AddCond( effects.eCondition, flMaxDuration, pProvider );
+
+ SendNewInvulnGameEvent();
+
+ CSingleUserRecipientFilter filter( m_pOuter );
+ m_pOuter->EmitSound( filter, m_pOuter->entindex(), effects.pszChargeOnSound );
+ m_bChargeOffSounded = false;
+ m_bChargeSoundEffectsOn[iCharge] = true;
+ }
+ else
+ {
+ if ( m_bChargeSoundEffectsOn[iCharge] )
+ {
+ m_pOuter->StopSound( effects.pszChargeOnSound );
+ m_bChargeSoundEffectsOn[iCharge] = false;
+ }
+
+ if ( !m_flChargeEffectOffTime[iCharge] && !m_bChargeOffSounded )
+ {
+ // Make sure we don't have duplicate Off sounds playing
+ m_pOuter->StopSound( effects.pszChargeOffSound );
+
+ CSingleUserRecipientFilter filter( m_pOuter );
+ m_pOuter->EmitSound( filter, m_pOuter->entindex(), effects.pszChargeOffSound );
+ m_bChargeOffSounded = true;
+ }
+
+ if ( bInstant )
+ {
+ m_flChargeEffectOffTime[iCharge] = 0;
+ RemoveCond( effects.eCondition );
+ if ( effects.eWearingOffCondition != TF_COND_LAST )
+ {
+ RemoveCond( effects.eWearingOffCondition );
+ }
+ }
+ else
+ {
+ // We're already in the process of turning it off
+ if ( m_flChargeEffectOffTime[iCharge] )
+ return;
+
+ if ( effects.eWearingOffCondition != TF_COND_LAST )
+ {
+ AddCond( effects.eWearingOffCondition, PERMANENT_CONDITION, pProvider );
+ }
+ m_flChargeEffectOffTime[iCharge] = gpGlobals->curtime + flWearOffTime;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Collect currency packs in a radius around the scout
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RadiusCurrencyCollectionCheck( void )
+{
+ if ( m_pOuter->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS && TFGameRules()->IsMannVsMachineMode() )
+ return;
+
+ if ( !m_pOuter->IsAlive() )
+ return;
+
+ if ( m_flRadiusCurrencyCollectionTime > gpGlobals->curtime )
+ return;
+
+ bool bScout = m_pOuter->GetPlayerClass()->GetClassIndex() == TF_CLASS_SCOUT;
+ const int nRadiusSqr = bScout ? 288 * 288 : 72 * 72;
+ Vector vecPos = m_pOuter->GetAbsOrigin();
+
+ // NDebugOverlay::Sphere( vecPos, nRadius, 0, 255, 0, 40, 5 );
+
+ for ( int i = 0; i < ICurrencyPackAutoList::AutoList().Count(); ++i )
+ {
+ CCurrencyPack *pCurrencyPack = static_cast< CCurrencyPack* >( ICurrencyPackAutoList::AutoList()[i] );
+ if ( !pCurrencyPack )
+ continue;
+
+ if ( !pCurrencyPack->AffectedByRadiusCollection() )
+ continue;
+
+ if ( ( vecPos - pCurrencyPack->GetAbsOrigin() ).LengthSqr() > nRadiusSqr )
+ continue;
+
+ if ( pCurrencyPack->IsClaimed() )
+ continue;
+
+ if ( m_pOuter->FVisible( pCurrencyPack, MASK_OPAQUE ) == false )
+ continue;
+
+ if ( !pCurrencyPack->ValidTouch( m_pOuter ) )
+ continue;
+
+ // Currencypack's seek classes with a large collection radius
+ if ( bScout )
+ {
+ bool bFound = false;
+ FOR_EACH_VEC( m_CurrencyPacks, i )
+ {
+ pulledcurrencypacks_t packinfo = m_CurrencyPacks[i];
+ if ( packinfo.hPack == pCurrencyPack )
+ bFound = true;
+ }
+
+ if ( !bFound )
+ {
+ // Mark as claimed to prevent other players from grabbing
+ pCurrencyPack->SetClaimed();
+ pulledcurrencypacks_t packinfo;
+ packinfo.hPack = pCurrencyPack;
+ packinfo.flTime = gpGlobals->curtime + 1.f;
+ m_CurrencyPacks.AddToTail( packinfo );
+ }
+ }
+ else
+ {
+ pCurrencyPack->Touch( m_pOuter );
+ }
+ }
+
+ FOR_EACH_VEC_BACK( m_CurrencyPacks, i )
+ {
+ if ( m_CurrencyPacks[i].hPack )
+ {
+ // If the timeout hits, force a touch
+ if ( m_CurrencyPacks[i].flTime <= gpGlobals->curtime )
+ {
+ m_CurrencyPacks[i].hPack->Touch( m_pOuter );
+ }
+ else
+ {
+ // Seek the player
+ const float flForce = 550.0f;
+
+ Vector vToPlayer = m_pOuter->GetAbsOrigin() - m_CurrencyPacks[i].hPack->GetAbsOrigin();
+
+ vToPlayer.z = 0.0f;
+ vToPlayer.NormalizeInPlace();
+ vToPlayer.z = 0.25f;
+
+ Vector vPush = flForce * vToPlayer;
+
+ m_CurrencyPacks[i].hPack->RemoveFlag( FL_ONGROUND );
+ m_CurrencyPacks[i].hPack->ApplyAbsVelocityImpulse( vPush );
+ }
+ }
+ else
+ {
+ // Automatic clean-up
+ m_CurrencyPacks.Remove( i );
+ }
+ }
+
+ m_flRadiusCurrencyCollectionTime = bScout ? gpGlobals->curtime + 0.15f : gpGlobals->curtime + 0.25f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Collect objects in a radius around the player
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RadiusHealthkitCollectionCheck( void )
+{
+ if ( GetCarryingRuneType() != RUNE_PLAGUE )
+ return;
+
+ if ( !m_pOuter->IsAlive() )
+ return;
+
+ if ( m_flRadiusCurrencyCollectionTime > gpGlobals->curtime )
+ return;
+
+ const int nRadiusSqr = 600 * 600;
+ const Vector& vecPos = m_pOuter->WorldSpaceCenter();
+
+// NDebugOverlay::Sphere( vecPos, 600, 0, 255, 0, false, 2.f );
+
+ for ( int i = 0; i < IHealthKitAutoList::AutoList().Count(); ++i )
+ {
+ CHealthKit *pHealthKit = static_cast<CHealthKit*>( IHealthKitAutoList::AutoList()[i] );
+ if ( !pHealthKit )
+ continue;
+
+ if ( ( vecPos - pHealthKit->GetAbsOrigin() ).LengthSqr() > nRadiusSqr )
+ continue;
+
+ if ( !pHealthKit->ValidTouch( m_pOuter ) )
+ continue;
+
+ if ( pHealthKit->IsEffectActive( EF_NODRAW ) )
+ continue;
+
+ pHealthKit->ItemTouch( m_pOuter );
+ }
+
+ m_flRadiusCurrencyCollectionTime = gpGlobals->curtime + 0.15f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scan for and reveal spies in a radius around the player
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RadiusSpyScan( void )
+{
+ if ( m_pOuter->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS )
+ return;
+
+ if ( !m_pOuter->IsAlive() )
+ return;
+
+ if ( m_flRadiusSpyScanTime <= gpGlobals->curtime )
+ {
+// bool bRevealed = false;
+ const int iRange = 750;
+
+ CUtlVector<CTFPlayer *> vecPlayers;
+ CollectPlayers( &vecPlayers, TF_TEAM_PVE_INVADERS, true );
+ FOR_EACH_VEC( vecPlayers, i )
+ {
+
+ if ( !vecPlayers[i] )
+ continue;
+
+ if ( vecPlayers[i]->GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY )
+ continue;
+
+ if ( !vecPlayers[i]->m_Shared.InCond( TF_COND_STEALTHED ) )
+ continue;
+
+ if ( m_pOuter->FVisible( vecPlayers[i], MASK_OPAQUE ) == false )
+ continue;
+
+ Vector vDist = vecPlayers[i]->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
+ if ( vDist.LengthSqr() <= iRange * iRange )
+ {
+ vecPlayers[i]->m_Shared.OnSpyTouchedByEnemy();
+// bRevealed = true;
+ }
+ }
+
+// if ( bRevealed )
+// {
+// bRevealed = false;
+// CSingleUserRecipientFilter filter( m_pOuter );
+// m_pOuter->EmitSound( filter, m_pOuter->entindex(), "Recon.Ping" );
+// }
+
+ m_flRadiusSpyScanTime = gpGlobals->curtime + 0.3f;
+ }
+}
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ApplyAttributeToPlayer( const char* pszAttribName, float flValue )
+{
+ const CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinitionByName( pszAttribName );
+
+ m_pOuter->GetAttributeList()->SetRuntimeAttributeValue( pDef, flValue );
+ m_pOuter->TeamFortress_SetSpeed();
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RemoveAttributeFromPlayer( const char* pszAttribName )
+{
+ const CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinitionByName( pszAttribName );
+ m_pOuter->GetAttributeList()->RemoveAttribute( pDef );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::AddTmpDamageBonus( float flBonus, float flExpiration )
+{
+ AddCond( TF_COND_TMPDAMAGEBONUS, flExpiration );
+ m_flTmpDamageBonusAmount += flBonus;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::FindHealerIndex( CBaseEntity *pHealer )
+{
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ if ( m_aHealers[i].pHealer == pHealer )
+ return i;
+ }
+
+ return m_aHealers.InvalidIndex();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFPlayerShared::GetHealerByIndex( int index )
+{
+ int iNumHealers = m_aHealers.Count();
+
+ if ( index < 0 || index >= iNumHealers )
+ return NULL;
+
+ return m_aHealers[index].pHealer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::HealerIsDispenser( int index )
+{
+ int iNumHealers = m_aHealers.Count();
+
+ if ( index < 0 || index >= iNumHealers )
+ return false;
+
+ return m_aHealers[index].bDispenserHeal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the first healer in the healer array. Note that this
+// is an arbitrary healer.
+//-----------------------------------------------------------------------------
+EHANDLE CTFPlayerShared::GetFirstHealer()
+{
+ if ( m_aHealers.Count() > 0 )
+ return m_aHealers.Head().pHealer;
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: External code has decided that the trigger event for an achievement
+// has occurred. Go through our data and give it to the right people.
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::CheckForAchievement( int iAchievement )
+{
+ if ( iAchievement == ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE ||
+ (iAchievement == ACHIEVEMENT_TF_MEDIC_CHARGE_BLOCKER && InCond( TF_COND_INVULNERABLE ) ) )
+ {
+ // ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE : We were just saved from death by invuln. See if any medics deployed
+ // their charge on us recently, and if so, give them the achievement.
+
+ // ACHIEVEMENT_TF_MEDIC_CHARGE_BLOCKER: We just blocked a capture, and we're invuln. Whoever's invulning us gets the achievement.
+
+ for ( int i = 0; i < m_aHealers.Count(); i++ )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( m_aHealers[i].pHealer );
+ if ( !pPlayer )
+ continue;
+
+ if ( !pPlayer->IsPlayerClass(TF_CLASS_MEDIC) )
+ continue;
+
+ CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
+ if ( !pWpn )
+ continue;
+
+ CWeaponMedigun *pMedigun = dynamic_cast<CWeaponMedigun*>(pWpn);
+ if ( pMedigun && pMedigun->IsReleasingCharge() )
+ {
+ // Save teammate requires us to have deployed the charge within the last second
+ if ( iAchievement != ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE || (gpGlobals->curtime - pMedigun->GetReleaseStartedAt()) < 1.0 )
+ {
+ pPlayer->AwardAchievement( iAchievement );
+ }
+ }
+ }
+ }
+}
+
+#endif // GAME_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose: Get all of our conditions in a nice CBitVec
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::GetConditionsBits( CBitVec< TF_COND_LAST >& vbConditions ) const
+{
+ vbConditions.Set( 0u, (uint32)m_nPlayerCond );
+ vbConditions.Set( 1u, (uint32)m_nPlayerCondEx );
+ vbConditions.Set( 2u, (uint32)m_nPlayerCondEx2 );
+ vbConditions.Set( 3u, (uint32)m_nPlayerCondEx3 );
+ COMPILE_TIME_ASSERT( 32 + 32 + 32 + 32 > TF_COND_LAST );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFWeaponBase *CTFPlayerShared::GetActiveTFWeapon() const
+{
+ return m_pOuter->GetActiveTFWeapon();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Team check.
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsAlly( CBaseEntity *pEntity )
+{
+ return ( pEntity->GetTeamNumber() == m_pOuter->GetTeamNumber() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetDesiredPlayerClassIndex( void )
+{
+ return m_iDesiredPlayerClass;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetJumping( bool bJumping )
+{
+ m_bJumping = bJumping;
+}
+
+void CTFPlayerShared::SetAirDash( int iAirDash )
+{
+ m_iAirDash = iAirDash;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::GetCritMult( void )
+{
+ float flRemapCritMul = RemapValClamped( m_iCritMult, 0, 255, 1.0, 4.0 );
+/*#ifdef CLIENT_DLL
+ Msg("CLIENT: Crit mult %.2f - %d\n",flRemapCritMul, m_iCritMult);
+#else
+ Msg("SERVER: Crit mult %.2f - %d\n", flRemapCritMul, m_iCritMult );
+#endif*/
+
+ return flRemapCritMul;
+}
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateCritMult( void )
+{
+ const float flMinMult = 1.0;
+ const float flMaxMult = TF_DAMAGE_CRITMOD_MAXMULT;
+
+ if ( m_DamageEvents.Count() == 0 )
+ {
+ m_iCritMult = RemapValClamped( flMinMult, 1.0, 4.0, 0, 255 );
+ return;
+ }
+
+ //Msg( "Crit mult update for %s\n", m_pOuter->GetPlayerName() );
+ //Msg( " Entries: %d\n", m_DamageEvents.Count() );
+
+ // Go through the damage multipliers and remove expired ones, while summing damage of the others
+ float flTotalDamage = 0;
+ for ( int i = m_DamageEvents.Count() - 1; i >= 0; i-- )
+ {
+ float flDelta = gpGlobals->curtime - m_DamageEvents[i].flTime;
+ if ( flDelta > tf_damage_events_track_for.GetFloat() )
+ {
+ //Msg( " Discarded (%d: time %.2f, now %.2f)\n", i, m_DamageEvents[i].flTime, gpGlobals->curtime );
+ m_DamageEvents.Remove(i);
+ continue;
+ }
+
+ // Ignore damage we've just done. We do this so that we have time to get those damage events
+ // to the client in time for using them in prediction in this code.
+ if ( flDelta < TF_DAMAGE_CRITMOD_MINTIME )
+ {
+ //Msg( " Ignored (%d: time %.2f, now %.2f)\n", i, m_DamageEvents[i].flTime, gpGlobals->curtime );
+ continue;
+ }
+
+ if ( flDelta > TF_DAMAGE_CRITMOD_MAXTIME )
+ continue;
+
+ //Msg( " Added %.2f (%d: time %.2f, now %.2f)\n", m_DamageEvents[i].flDamage, i, m_DamageEvents[i].flTime, gpGlobals->curtime );
+
+ flTotalDamage += m_DamageEvents[i].flDamage * m_DamageEvents[i].flDamageCritScaleMultiplier;
+ }
+
+ float flMult = RemapValClamped( flTotalDamage, 0, TF_DAMAGE_CRITMOD_DAMAGE, flMinMult, flMaxMult );
+
+// Msg( " TotalDamage: %.2f -> Mult %.2f\n", flTotalDamage, flMult );
+
+ m_iCritMult = (int)RemapValClamped( flMult, flMinMult, flMaxMult, 0, 255 );
+}
+
+#define CRIT_DAMAGE_TIME 0.1f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RecordDamageEvent( const CTakeDamageInfo &info, bool bKill, int nVictimPrevHealth )
+{
+ if ( m_DamageEvents.Count() >= MAX_DAMAGE_EVENTS )
+ {
+ // Remove the oldest event
+ m_DamageEvents.Remove( m_DamageEvents.Count()-1 );
+ }
+
+ // Don't count critical damage toward the critical multiplier.
+ float flDamage = info.GetDamage() - info.GetDamageBonus();
+
+ float flDamageCriticalScale = info.GetDamageType() & DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE
+ ? 0.0f
+ : 1.0f;
+
+ // cap the damage at our current health amount since it's going to kill us
+ if ( bKill && flDamage > nVictimPrevHealth )
+ {
+ flDamage = nVictimPrevHealth;
+ }
+
+ // Don't allow explosions to stack up damage toward the critical modifier.
+ bool bOverride = false;
+ if ( info.GetDamageType() & DMG_BLAST )
+ {
+ int nDamageCount = m_DamageEvents.Count();
+ for ( int iDamage = 0; iDamage < nDamageCount; ++iDamage )
+ {
+ // Was the older event I am checking against an explosion as well?
+ if ( m_DamageEvents[iDamage].nDamageType & DMG_BLAST )
+ {
+ // Did it happen very recently?
+ if ( ( gpGlobals->curtime - m_DamageEvents[iDamage].flTime ) < CRIT_DAMAGE_TIME )
+ {
+ if ( bKill )
+ {
+ m_DamageEvents[iDamage].nKills++;
+
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_DEMOMAN ) )
+ {
+ // Make sure the previous & the current are stickybombs, and go with it.
+ if ( m_DamageEvents[iDamage].nDamageType == info.GetDamageType() &&
+ m_DamageEvents[iDamage].nDamageType == g_aWeaponDamageTypes[TF_WEAPON_PIPEBOMBLAUNCHER] )
+ {
+ if ( TFGameRules()->IsMannVsMachineMode() && m_DamageEvents[iDamage].nKills >= 10 )
+ {
+ m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_DEMO_GROUP_KILL );
+ }
+ else if ( m_DamageEvents[iDamage].nKills >= 3 )
+ {
+ m_pOuter->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL3_WITH_DETONATION );
+ }
+ }
+ }
+ }
+
+ // Take the max damage done in the time frame.
+ if ( flDamage > m_DamageEvents[iDamage].flDamage )
+ {
+ m_DamageEvents[iDamage].flDamage = flDamage;
+ m_DamageEvents[iDamage].flDamageCritScaleMultiplier = flDamageCriticalScale;
+ m_DamageEvents[iDamage].flTime = gpGlobals->curtime;
+ m_DamageEvents[iDamage].nDamageType = info.GetDamageType();
+
+// Msg( "Update Damage Event: D:%f, T:%f\n", m_DamageEvents[iDamage].flDamage, m_DamageEvents[iDamage].flTime );
+ }
+
+ bOverride = true;
+ }
+ }
+ }
+ }
+
+ // We overrode a value, don't add this to the list.
+ if ( bOverride )
+ return;
+
+ int iIndex = m_DamageEvents.AddToTail();
+ m_DamageEvents[iIndex].flDamage = flDamage;
+ m_DamageEvents[iIndex].flDamageCritScaleMultiplier = flDamageCriticalScale;
+ m_DamageEvents[iIndex].nDamageType = info.GetDamageType();
+ m_DamageEvents[iIndex].flTime = gpGlobals->curtime;
+ m_DamageEvents[iIndex].nKills = bKill;
+
+// Msg( "Damage Event: D:%f, T:%f\n", m_DamageEvents[iIndex].flDamage, m_DamageEvents[iIndex].flTime );
+
+ if ( TFGameRules()->IsMannVsMachineMode() && m_pOuter->IsPlayerClass( TF_CLASS_SNIPER ) )
+ {
+ int nKillCount = 0;
+ int nDamageCount = m_DamageEvents.Count();
+ for ( int iDamage = 0; iDamage < nDamageCount; ++iDamage )
+ {
+ // Did it happen very recently?
+ if ( ( gpGlobals->curtime - m_DamageEvents[iDamage].flTime ) < CRIT_DAMAGE_TIME )
+ {
+ nKillCount += m_DamageEvents[iDamage].nKills;
+ }
+ }
+
+ if ( nKillCount >= 4 )
+ {
+ m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_SNIPER_KILL_GROUP );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::AddTempCritBonus( float flAmount )
+{
+ if ( m_DamageEvents.Count() >= MAX_DAMAGE_EVENTS )
+ {
+ // Remove the oldest event
+ m_DamageEvents.Remove( m_DamageEvents.Count()-1 );
+ }
+
+ int iIndex = m_DamageEvents.AddToTail();
+ m_DamageEvents[iIndex].flDamage = RemapValClamped( flAmount, 0, 1, 0, TF_DAMAGE_CRITMOD_DAMAGE ) / (TF_DAMAGE_CRITMOD_MAXMULT - 1.0);
+ m_DamageEvents[iIndex].flDamageCritScaleMultiplier = 1.0f;
+ m_DamageEvents[iIndex].nDamageType = DMG_GENERIC;
+ m_DamageEvents[iIndex].flTime = gpGlobals->curtime;
+ m_DamageEvents[iIndex].nKills = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetNumKillsInTime( float flTime )
+{
+ if ( tf_damage_events_track_for.GetFloat() < flTime )
+ {
+ Warning("Player asking for damage events for time %.0f, but tf_damage_events_track_for is only tracking events for %.0f\n", flTime, tf_damage_events_track_for.GetFloat() );
+ }
+
+ int iKills = 0;
+ for ( int i = m_DamageEvents.Count() - 1; i >= 0; i-- )
+ {
+ float flDelta = gpGlobals->curtime - m_DamageEvents[i].flTime;
+ if ( flDelta < flTime )
+ {
+ iKills += m_DamageEvents[i].nKills;
+ }
+ }
+
+ return iKills;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::AddToSpyCloakMeter( float val, bool bForce )
+{
+ CTFWeaponInvis *pWpn = (CTFWeaponInvis *) m_pOuter->Weapon_OwnsThisID( TF_WEAPON_INVIS );
+ if ( !pWpn )
+ return false;
+
+ // STAGING_SPY
+ // Special cloaks only get cloak if not active and receive a smaller portion
+ int iNoCloakedPickup = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWpn, iNoCloakedPickup, NoCloakWhenCloaked );
+ if ( !bForce )
+ {
+ if ( InCond( TF_COND_STEALTHED ) && iNoCloakedPickup )
+ {
+ return false;
+ }
+ else
+ {
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, val, ReducedCloakFromAmmo );
+ }
+ }
+
+ bool bResult = ( val > 0 && m_flCloakMeter < 100.0f );
+
+ m_flCloakMeter = clamp( m_flCloakMeter + val, 0.0f, 100.0f );
+
+ return bResult;
+}
+
+
+#endif
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Stun & Snare Application
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::StunPlayer( float flTime, float flReductionAmount, int iStunFlags, CTFPlayer* pAttacker )
+{
+ // Insanity prevention
+ if ( ( m_PlayerStuns.Count() + 1 ) >= 250 )
+ return;
+
+ if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_PASSTIME_INTERCEPTION ) )
+ return;
+
+ if ( InCond( TF_COND_MEGAHEAL ) )
+ return;
+
+ if ( InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) && !InCond( TF_COND_MVM_BOT_STUN_RADIOWAVE ) )
+ return;
+
+ if ( pAttacker && TFGameRules() && TFGameRules()->IsTruceActive() && pAttacker->IsTruceValidForEnt() )
+ {
+ if ( ( pAttacker->GetTeamNumber() == TF_TEAM_RED ) || ( pAttacker->GetTeamNumber() == TF_TEAM_BLUE ) )
+ return;
+ }
+
+ float flRemapAmount = RemapValClamped( flReductionAmount, 0.0, 1.0, 0, 255 );
+
+ int iOldStunFlags = GetStunFlags();
+
+ // Already stunned
+ bool bStomp = false;
+ if ( InCond( TF_COND_STUNNED ) )
+ {
+ if ( GetActiveStunInfo() )
+ {
+ // Is it stronger than the active?
+ if ( flRemapAmount > GetActiveStunInfo()->flStunAmount || iStunFlags & TF_STUN_CONTROLS || iStunFlags & TF_STUN_LOSER_STATE )
+ {
+ bStomp = true;
+ }
+ // It's weaker. Would it expire before the active?
+ else if ( gpGlobals->curtime + flTime < GetActiveStunInfo()->flExpireTime )
+ {
+ // Ignore
+ return;
+ }
+ }
+ }
+ else if ( GetActiveStunInfo() )
+ {
+ // Something yanked our TF_COND_STUNNED in an unexpected way
+ if ( !HushAsserts() )
+ Assert( !"Something yanked out TF_COND_STUNNED." );
+ m_PlayerStuns.RemoveAll();
+ return;
+ }
+
+ // Add it to the stack
+ stun_struct_t stunEvent =
+ {
+ pAttacker, // hPlayer
+ flTime, // flDuration
+ gpGlobals->curtime + flTime, // flExpireTime
+ gpGlobals->curtime + flTime, // flStartFadeTime
+ flRemapAmount, // flStunAmount
+ iStunFlags // iStunFlags
+ };
+
+ // Should this become the active stun?
+ if ( bStomp || !GetActiveStunInfo() )
+ {
+ // If stomping, see if the stun we're replacing has a stronger slow.
+ // This can happen when stuns use TF_STUN_CONTROLS or TF_STUN_LOSER_STATE.
+ float flOldStun = GetActiveStunInfo() ? GetActiveStunInfo()->flStunAmount : 0.f;
+
+ m_iStunIndex = m_PlayerStuns.AddToTail( stunEvent );
+
+ if ( flOldStun > flRemapAmount )
+ {
+ GetActiveStunInfo()->flStunAmount = flOldStun;
+ }
+ }
+ else
+ {
+ // Done for now
+ m_PlayerStuns.AddToTail( stunEvent );
+ return;
+ }
+
+ // Add in extra time when TF_STUN_CONTROLS
+ if ( GetActiveStunInfo()->iStunFlags & TF_STUN_CONTROLS )
+ {
+ if ( !InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ GetActiveStunInfo()->flExpireTime += CONTROL_STUN_ANIM_TIME;
+ }
+ }
+
+ GetActiveStunInfo()->flStartFadeTime = gpGlobals->curtime + GetActiveStunInfo()->flDuration;
+
+ // Update old system for networking
+ UpdateLegacyStunSystem();
+
+ if ( GetActiveStunInfo()->iStunFlags & TF_STUN_CONTROLS || GetActiveStunInfo()->iStunFlags & TF_STUN_LOSER_STATE )
+ {
+ m_pOuter->m_angTauntCamera = m_pOuter->EyeAngles();
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_STUNNED );
+ if ( pAttacker )
+ {
+ pAttacker->SpeakConceptIfAllowed( MP_CONCEPT_STUNNED_TARGET );
+ }
+ }
+
+ if ( !( GetActiveStunInfo()->iStunFlags & TF_STUN_NO_EFFECTS ) )
+ {
+ m_pOuter->StunSound( pAttacker, GetActiveStunInfo()->iStunFlags, iOldStunFlags );
+ }
+
+ // Event for achievements.
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_stunned" );
+ if ( event )
+ {
+ if ( pAttacker )
+ {
+ event->SetInt( "stunner", pAttacker->GetUserID() );
+ }
+ event->SetInt( "victim", m_pOuter->GetUserID() );
+ event->SetBool( "victim_capping", m_pOuter->IsCapturingPoint() );
+ event->SetBool( "big_stun", ( GetActiveStunInfo()->iStunFlags & TF_STUN_SPECIAL_SOUND ) != 0 );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // Clear off all taunts, expressions, and scenes.
+ if ( ( GetActiveStunInfo()->iStunFlags & TF_STUN_CONTROLS) == TF_STUN_CONTROLS || ( GetActiveStunInfo()->iStunFlags & TF_STUN_LOSER_STATE) == TF_STUN_LOSER_STATE )
+ {
+ m_pOuter->StopTaunt();
+ m_pOuter->ClearExpression();
+ m_pOuter->ClearWeaponFireScene();
+ }
+
+ AddCond( TF_COND_STUNNED, -1.f, pAttacker );
+}
+#endif // GAME_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the intensity of the current stun effect, if we have the type of stun indicated.
+//-----------------------------------------------------------------------------
+float CTFPlayerShared::GetAmountStunned( int iStunFlags )
+{
+ if ( GetActiveStunInfo() )
+ {
+ if ( InCond( TF_COND_STUNNED ) && ( iStunFlags & GetActiveStunInfo()->iStunFlags ) && ( GetActiveStunInfo()->flExpireTime > gpGlobals->curtime ) )
+ return MIN( MAX( GetActiveStunInfo()->flStunAmount, 0 ), 255 ) * ( 1.f/255.f );
+ }
+
+ return 0.f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates that our controls are stunned.
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsControlStunned( void )
+{
+ if ( GetActiveStunInfo() )
+ {
+ if ( InCond( TF_COND_STUNNED ) && ( m_iStunFlags & TF_STUN_CONTROLS ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates that our controls are stunned.
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsLoserStateStunned( void ) const
+{
+ if ( GetActiveStunInfo() )
+ {
+ if ( InCond( TF_COND_STUNNED ) && ( m_iStunFlags & TF_STUN_LOSER_STATE ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates that our movement is slowed, but our controls are still free.
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsSnared( void )
+{
+ if ( InCond( TF_COND_STUNNED ) && !IsControlStunned() )
+ return true;
+ else
+ return false;
+}
+
+//=============================================================================
+//
+// Shared player code that isn't CTFPlayerShared
+//
+//-----------------------------------------------------------------------------
+struct penetrated_target_list
+{
+ CBaseEntity *pTarget;
+ float flDistanceFraction;
+};
+
+//-----------------------------------------------------------------------------
+class CBulletPenetrateEnum : public IEntityEnumerator
+{
+public:
+ CBulletPenetrateEnum( Ray_t *pRay, CBaseEntity *pShooter, int nCustomDamageType, bool bIgnoreTeammates = true )
+ {
+ m_pRay = pRay;
+ m_pShooter = pShooter;
+ m_nCustomDamageType = nCustomDamageType;
+ m_bIgnoreTeammates = bIgnoreTeammates;
+ }
+
+ // We need to sort the penetrated targets into order, with the closest target first
+ class PenetratedTargetLess
+ {
+ public:
+ bool Less( const penetrated_target_list &src1, const penetrated_target_list &src2, void *pCtx )
+ {
+ return src1.flDistanceFraction < src2.flDistanceFraction;
+ }
+ };
+
+ virtual bool EnumEntity( IHandleEntity *pHandleEntity )
+ {
+ trace_t tr;
+
+ CBaseEntity *pEnt = static_cast<CBaseEntity*>(pHandleEntity);
+
+ // Ignore collisions with the shooter
+ if ( pEnt == m_pShooter )
+ return true;
+
+ if ( pEnt->IsCombatCharacter() || pEnt->IsBaseObject() )
+ {
+ if ( m_bIgnoreTeammates && pEnt->GetTeam() == m_pShooter->GetTeam() )
+ return true;
+
+ enginetrace->ClipRayToEntity( *m_pRay, MASK_SOLID | CONTENTS_HITBOX, pHandleEntity, &tr );
+
+ if (tr.fraction < 1.0f)
+ {
+ penetrated_target_list newEntry;
+ newEntry.pTarget = pEnt;
+ newEntry.flDistanceFraction = tr.fraction;
+ m_Targets.Insert( newEntry );
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+public:
+ Ray_t *m_pRay;
+ int m_nCustomDamageType;
+ CBaseEntity *m_pShooter;
+ bool m_bIgnoreTeammates;
+ CUtlSortVector<penetrated_target_list, PenetratedTargetLess> m_Targets;
+};
+
+
+CTargetOnlyFilter::CTargetOnlyFilter( CBaseEntity *pShooter, CBaseEntity *pTarget )
+ : CTraceFilterSimple( pShooter, COLLISION_GROUP_NONE )
+{
+ m_pShooter = pShooter;
+ m_pTarget = pTarget;
+}
+
+bool CTargetOnlyFilter::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+{
+ CBaseEntity *pEnt = static_cast<CBaseEntity*>(pHandleEntity);
+
+ if ( pEnt && pEnt == m_pTarget )
+ return true;
+ else if ( !pEnt || pEnt != m_pTarget )
+ {
+ // If we hit a solid piece of the world, we're done.
+ if ( pEnt->IsBSPModel() && pEnt->IsSolid() )
+ return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
+ return false;
+ }
+ else
+ return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input: info
+// bDoEffects - effects (blood, etc.) should only happen client-side.
+//-----------------------------------------------------------------------------
+void CTFPlayer::MaybeDrawRailgunBeam( IRecipientFilter *pFilter, CTFWeaponBase *pWeapon, const Vector& vStartPos, const Vector& vEndPos )
+{
+#ifdef GAME_DLL
+ Assert( pFilter );
+#else // !GAME_DLL
+ Assert( !pFilter );
+#endif
+ Assert( pWeapon );
+
+ int iShouldFireTracer = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iShouldFireTracer, sniper_fires_tracer );
+
+ if ( !iShouldFireTracer )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iShouldFireTracer, sniper_fires_tracer_HIDDEN );
+ }
+
+ // Check for heatmaker
+ if ( !iShouldFireTracer )
+ {
+ iShouldFireTracer = m_Shared.InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) && pWeapon && WeaponID_IsSniperRifle( pWeapon->GetWeaponID() );
+ }
+
+ if ( iShouldFireTracer )
+ {
+ const char *pParticleSystemName = pWeapon->GetTeamNumber() == TF_TEAM_BLUE ? "dxhr_sniper_rail_blue" : "dxhr_sniper_rail_red";
+ CTFSniperRifle *pRifle = dynamic_cast< CTFSniperRifle* >( pWeapon );
+ if ( pRifle && ( pRifle->GetRifleType() == RIFLE_CLASSIC ) )
+ {
+ pParticleSystemName = "tfc_sniper_distortion_trail";
+ }
+
+#ifdef GAME_DLL
+ te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, vEndPos };
+
+ TE_TFParticleEffectComplex( *pFilter, 0.0f, pParticleSystemName, vStartPos, QAngle( 0, 0, 0 ), NULL, &controlPoint, pWeapon, PATTACH_CUSTOMORIGIN );
+#else // !GAME_DLL
+ CSmartPtr<CNewParticleEffect> pEffect = pWeapon->ParticleProp()->Create( pParticleSystemName, PATTACH_CUSTOMORIGIN, 0 );
+ if ( pEffect.IsValid() && pEffect->IsValid() )
+ {
+ pEffect->SetSortOrigin( vStartPos );
+ pEffect->SetControlPoint( 0, vStartPos );
+ pEffect->SetControlPoint( 1, vEndPos );
+ }
+#endif // GAME_DLL
+ }
+}
+
+void CTFPlayer::GetHorriblyHackedRailgunPosition( const Vector& vStart, Vector *out_pvStartPos )
+{
+ Assert( out_pvStartPos != NULL );
+
+ // DO NOT LOOK BEHIND THE MAGIC CURTAIN
+ Vector vForward, vRight, vUp;
+ AngleVectors( EyeAngles(), &vForward, &vRight, &vUp );
+
+ *out_pvStartPos = vStart
+ + (vForward * 60.9f)
+ + (vRight * 13.1f)
+ + (vUp * -15.1f);
+}
+
+static bool OnOpposingTFTeams( int iTeam0, int iTeam1 )
+{
+ // This logic is weird because we want to make sure that we're actually shooting someone on the
+ // other team, not just someone on a different team. This prevents weirdness where we count shooting
+ // the BSP as an enemy because they aren't on our team.
+
+ if ( iTeam0 == TF_TEAM_BLUE ) // if we're on the blue team...
+ return iTeam1 == TF_TEAM_RED; // ...and we shot someone on the red team, then we're opposing.
+
+ if ( iTeam0 == TF_TEAM_RED ) // if we're on the blue team...
+ return iTeam1 == TF_TEAM_BLUE; // ...and we shot someone on the red team, then we're opposing.
+
+ return iTeam0 != iTeam1; // if we're neither red nor blue, then anyone different from us is opposing
+}
+
+#ifdef GAME_DLL
+extern void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::ModifyDamageInfo( CTakeDamageInfo *pInfo, const CBaseEntity *pTarget )
+{
+ if ( pInfo && pTarget )
+ {
+ // Increased damage vs sentry's target?
+ if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ float flDamageMod = 1.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetActiveWeapon(), flDamageMod, mult_dmg_bullet_vs_sentry_target );
+ if ( flDamageMod > 1.f )
+ {
+ CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>( GetObjectOfType( OBJ_SENTRYGUN ) );
+ if ( pSentry && ( pSentry->GetTarget() == pTarget ) )
+ {
+ pInfo->SetDamage( pInfo->GetDamage() * flDamageMod );
+ }
+ }
+ }
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::FireBullet( CTFWeaponBase *pWpn, const FireBulletsInfo_t &info, bool bDoEffects, int nDamageType, int nCustomDamageType /*= TF_DMG_CUSTOM_NONE*/ )
+{
+ // Fire a bullet (ignoring the shooter).
+ Vector vecStart = info.m_vecSrc;
+ Vector vecEnd = vecStart + info.m_vecDirShooting * info.m_flDistance;
+ trace_t trace;
+
+ ETFDmgCustom ePenetrateType = pWpn ? pWpn->GetPenetrateType() : TF_DMG_CUSTOM_NONE;
+ if ( ePenetrateType == TF_DMG_CUSTOM_NONE )
+ {
+ ePenetrateType = (ETFDmgCustom)nCustomDamageType;
+ }
+
+ Ray_t ray;
+ ray.Init( vecStart, vecEnd );
+
+ // Ignore teammates and their (physical) upgrade items when shooting in MvM
+ if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
+ {
+ CTraceFilterIgnoreFriendlyCombatItems traceFilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID | CONTENTS_HITBOX, &traceFilter, &trace );
+ }
+ else
+ {
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID | CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &trace );
+ }
+
+#ifndef CLIENT_DLL
+ CUtlVector<CBaseEntity *> vecTracedEntities;
+ bool bPenetratingShot = ( (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS) || (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM) || (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE) );
+ if ( bPenetratingShot && trace.m_pEnt )
+ {
+ if ( trace.m_pEnt->IsCombatCharacter() || trace.m_pEnt->IsBaseObject() )
+ {
+ // Penetrating shot: Strikes everything along the bullet's path.
+ CBulletPenetrateEnum bulletpenetrate( &ray, this, ePenetrateType, ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM );
+ enginetrace->EnumerateEntities( ray, false, &bulletpenetrate );
+
+ FOR_EACH_VEC( bulletpenetrate.m_Targets, i )
+ {
+ vecTracedEntities.AddToTail( bulletpenetrate.m_Targets[i].pTarget );
+ }
+ }
+ else
+ {
+ // We traced into something we don't understand (sticky bomb? pumpkin bomb?) -- just apply our
+ // hit logic to whatever we traced first.
+ vecTracedEntities.AddToTail( trace.m_pEnt );
+ }
+ }
+ else
+#endif
+ {
+ ePenetrateType = TF_DMG_CUSTOM_NONE;
+ }
+
+#ifndef CLIENT_DLL
+ CTakeDamageInfo dmgInfo( this, info.m_pAttacker, info.m_flDamage, nDamageType );
+ dmgInfo.SetWeapon( GetActiveWeapon() );
+ dmgInfo.SetDamageCustom( nCustomDamageType );
+
+ int iPenetratedPlayerCount = 0;
+
+ int iEnemyPlayersHit = 0;
+ if ( bPenetratingShot )
+ {
+ int iChargedPenetration = 0;
+ CALL_ATTRIB_HOOK_INT( iChargedPenetration, sniper_penetrate_players_when_charged );
+ int iPenetrationLimit = 0;
+ CALL_ATTRIB_HOOK_INT( iPenetrationLimit, projectile_penetration );
+
+ // Damage every enemy player struck by the bullet along its path.
+ trace_t pen_trace;
+ FOR_EACH_VEC( vecTracedEntities, i )
+ {
+ // Limit the number of pen targets in MvM if we're not charge-based
+ if ( TFGameRules()->IsMannVsMachineMode() && iChargedPenetration == 0 )
+ {
+ // For sniper class, treat iPenetrationLimit as a bool
+ bool bIsSniper = IsPlayerClass( TF_CLASS_SNIPER );
+ if ( bIsSniper && iPenetrationLimit == 0 && iPenetratedPlayerCount > 0 )
+ break;
+
+ if ( !bIsSniper && iPenetratedPlayerCount > iPenetrationLimit )
+ break;
+ }
+
+ CBaseEntity *pTarget = vecTracedEntities[i];
+
+ if ( !pTarget )
+ continue;
+
+ trace_t *pTraceToUse = &pen_trace;
+
+ if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM )
+ {
+ // Skip friendlies if we're looking for the first enemy
+ if ( GetTeamNumber() == pTarget->GetTeamNumber() )
+ continue;
+
+ pTraceToUse = &trace;
+ }
+ else if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE )
+ {
+ if ( GetTeamNumber() == pTarget->GetTeamNumber() )
+ {
+ if ( pTarget->IsPlayer() )
+ {
+ // skip friendlies that are not on burning
+ CTFPlayer *pTeammate = ToTFPlayer( pTarget );
+ if ( !pTeammate->m_Shared.InCond( TF_COND_BURNING ) )
+ continue;
+ }
+ }
+
+ pTraceToUse = &trace;
+ }
+
+ CTargetOnlyFilter penetrateFilter( this, pTarget );
+ UTIL_TraceLine( vecStart, vecEnd, (MASK_SOLID|CONTENTS_HITBOX), &penetrateFilter, pTraceToUse );
+
+ if ( pTraceToUse->m_pEnt == pTarget )
+ {
+ CTFPlayer *pTargetPlayer = NULL;
+ if ( pTarget->IsPlayer() )
+ pTargetPlayer = ToTFPlayer( pTarget );
+
+ // put out fire for burning teammate
+ if ( nCustomDamageType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE )
+ {
+ if ( pTargetPlayer && GetTeamNumber() == pTargetPlayer->GetTeamNumber() && pTargetPlayer->m_Shared.InCond( TF_COND_BURNING ) )
+ {
+ ExtinguishPlayer( GetActiveWeapon(), ToTFPlayer( GetActiveWeapon()->GetOwner() ), pTargetPlayer, GetActiveWeapon()->GetName() );
+ }
+ }
+
+ ModifyDamageInfo( &dmgInfo, pTarget );
+ CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, info.m_vecDirShooting, pTraceToUse->endpos, 1.0 );
+ dmgInfo.SetPlayerPenetrationCount( iPenetratedPlayerCount );
+ pTarget->DispatchTraceAttack( dmgInfo, info.m_vecDirShooting, pTraceToUse, GetActiveWeapon() ? GetActiveWeapon()->GetDmgAccumulator() : NULL );
+
+ const bool bIsPenetratingPlayer = pTargetPlayer != NULL;
+ if ( bIsPenetratingPlayer )
+ {
+ iPenetratedPlayerCount++;
+ float flPenetrationPenalty = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flPenetrationPenalty, penetration_damage_penalty );
+ dmgInfo.SetDamage( dmgInfo.GetDamage() * flPenetrationPenalty );
+ }
+
+ // If we're only supposed to penetrate players and this thing isn't a player, stop here.
+ if ( !bIsPenetratingPlayer && (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS) )
+ break;
+ }
+ else
+ {
+ // We hit something solid that said we should stop tracing.
+ break;
+ }
+
+ if( pTarget->IsPlayer() && OnOpposingTFTeams( GetTeamNumber(), pTarget->GetTeamNumber() ) )
+ {
+ iEnemyPlayersHit++;
+ }
+
+ // If we're penetrating team mates, but we've just hit an enemy, we're done.
+ if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM )
+ break;
+
+ // just hit an enemy or a burning teammate
+ if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE )
+ break;
+ }
+ }
+ else
+ {
+ // Damage only the first entity encountered on the bullet's path.
+ if ( trace.m_pEnt )
+ {
+ ModifyDamageInfo( &dmgInfo, trace.m_pEnt );
+ CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, info.m_vecDirShooting, trace.endpos, 1.0 );
+ trace.m_pEnt->DispatchTraceAttack( dmgInfo, info.m_vecDirShooting, &trace );
+ if ( trace.m_pEnt->IsPlayer() && OnOpposingTFTeams( GetTeamNumber(), trace.m_pEnt->GetTeamNumber() ) )
+ {
+ iEnemyPlayersHit++;
+ }
+ }
+ }
+ if ( pWpn )
+ {
+ pWpn->OnBulletFire( iEnemyPlayersHit );
+
+ if ( iEnemyPlayersHit )
+ { // Guarantee that the bullet that hit an enemy trumps the player viewangles
+ // that are locked in for the duration of the server simulation ticks
+ m_iLockViewanglesTickNumber = gpGlobals->tickcount;
+ m_qangLockViewangles = pl.v_angle;
+ }
+ }
+#endif
+
+#ifdef GAME_DLL
+#ifdef _DEBUG
+ if ( tf_debug_bullets.GetBool() )
+ {
+ NDebugOverlay::Line( vecStart, trace.endpos, 0,255,0, true, 30 );
+ }
+#endif // _DEBUG
+#endif
+
+ if ( trace.fraction < 1.0 )
+ {
+ // Verify we have an entity at the point of impact.
+ Assert( trace.m_pEnt );
+
+#ifdef GAME_DLL
+ // We intentionally do this logic here outside our client-side "should we do effects?" logic. We send this
+ // to everyone except our local owner (ourself) as we'll do our own fire effects below.
+ Vector vMuzzleOrigin;
+ if ( pWpn )
+ {
+ Vector vStartPos;
+ GetHorriblyHackedRailgunPosition( trace.startpos, &vStartPos );
+
+ CBroadcastNonOwnerRecipientFilter filter( this );
+ MaybeDrawRailgunBeam( &filter, pWpn, vStartPos, trace.endpos );
+ }
+#endif // GAME_DLL
+
+ if ( bDoEffects )
+ {
+ // If shot starts out of water and ends in water
+ if ( !( enginetrace->GetPointContents( trace.startpos ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) &&
+ ( enginetrace->GetPointContents( trace.endpos ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) )
+ {
+ // Water impact effects.
+ ImpactWaterTrace( trace, vecStart );
+ }
+ else
+ {
+ // Regular impact effects.
+
+ // don't decal your teammates or objects on your team
+ if ( trace.m_pEnt && trace.m_pEnt->GetTeamNumber() != GetTeamNumber() )
+ {
+ UTIL_ImpactTrace( &trace, nDamageType );
+ }
+ }
+
+#ifdef CLIENT_DLL
+ if ( pWpn )
+ {
+ Vector vStartPos;
+ GetHorriblyHackedRailgunPosition( trace.startpos, &vStartPos );
+
+ MaybeDrawRailgunBeam( NULL, pWpn, vStartPos, trace.endpos );
+ }
+
+ static int tracerCount;
+ if ( ( ( info.m_iTracerFreq != 0 ) && ( tracerCount++ % info.m_iTracerFreq ) == 0 ) || (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS) )
+ {
+ // if this is a local player, start at attachment on view model
+ // else start on attachment on weapon model
+ int iUseAttachment = TRACER_DONT_USE_ATTACHMENT;
+ int iAttachment = 1;
+
+ {
+ C_BaseCombatWeapon *pWeapon = GetActiveWeapon();
+
+ if ( pWeapon )
+ {
+ iAttachment = pWeapon->LookupAttachment( "muzzle" );
+ }
+ }
+
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+
+ bool bInToolRecordingMode = clienttools->IsInRecordingMode();
+
+ // If we're using a viewmodel, override vecStart with the muzzle of that - just for the visual effect, not gameplay.
+ if ( ( pLocalPlayer != NULL ) && !pLocalPlayer->ShouldDrawThisPlayer() && !bInToolRecordingMode && pWpn )
+ {
+ C_BaseAnimating *pAttachEnt = pWpn->GetAppropriateWorldOrViewModel();
+ if ( pAttachEnt != NULL )
+ {
+ pAttachEnt->GetAttachment( iAttachment, vecStart );
+ }
+ }
+ else if ( !IsDormant() )
+ {
+ // fill in with third person weapon model index
+ C_BaseCombatWeapon *pWeapon = GetActiveWeapon();
+
+ if( pWeapon )
+ {
+ int nModelIndex = pWeapon->GetModelIndex();
+ int nWorldModelIndex = pWeapon->GetWorldModelIndex();
+ if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex )
+ {
+ pWeapon->SetModelIndex( nWorldModelIndex );
+ }
+
+ pWeapon->GetAttachment( iAttachment, vecStart );
+
+ if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex )
+ {
+ pWeapon->SetModelIndex( nModelIndex );
+ }
+ }
+ }
+
+ if ( tf_useparticletracers.GetBool() )
+ {
+ const char *pszTracerEffect = GetTracerType();
+ if ( pszTracerEffect && pszTracerEffect[0] )
+ {
+ char szTracerEffect[128];
+ if ( nDamageType & DMG_CRITICAL )
+ {
+ Q_snprintf( szTracerEffect, sizeof(szTracerEffect), "%s_crit", pszTracerEffect );
+ pszTracerEffect = szTracerEffect;
+ }
+
+ UTIL_ParticleTracer( pszTracerEffect, vecStart, trace.endpos, entindex(), iUseAttachment, true );
+ }
+ }
+ else
+ {
+ UTIL_Tracer( vecStart, trace.endpos, entindex(), iUseAttachment, 5000, true, GetTracerType() );
+ }
+ }
+#endif
+ }
+ }
+}
+
+#ifdef CLIENT_DLL
+static ConVar tf_impactwatertimeenable( "tf_impactwatertimeenable", "0", FCVAR_CHEAT, "Draw impact debris effects." );
+static ConVar tf_impactwatertime( "tf_impactwatertime", "1.0f", FCVAR_CHEAT, "Draw impact debris effects." );
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Trace from the shooter to the point of impact (another player,
+// world, etc.), but this time take into account water/slime surfaces.
+// Input: trace - initial trace from player to point of impact
+// vecStart - starting point of the trace
+//-----------------------------------------------------------------------------
+void CTFPlayer::ImpactWaterTrace( trace_t &trace, const Vector &vecStart )
+{
+#ifdef CLIENT_DLL
+ if ( tf_impactwatertimeenable.GetBool() )
+ {
+ if ( m_flWaterImpactTime > gpGlobals->curtime )
+ return;
+ }
+#endif
+
+ trace_t traceWater;
+ UTIL_TraceLine( vecStart, trace.endpos, ( MASK_SHOT | CONTENTS_WATER | CONTENTS_SLIME ),
+ this, COLLISION_GROUP_NONE, &traceWater );
+ if( traceWater.fraction < 1.0f )
+ {
+ CEffectData data;
+ data.m_vOrigin = traceWater.endpos;
+ data.m_vNormal = traceWater.plane.normal;
+ data.m_flScale = random->RandomFloat( 8, 12 );
+ if ( traceWater.contents & CONTENTS_SLIME )
+ {
+ data.m_fFlags |= FX_WATER_IN_SLIME;
+ }
+
+ const char *pszEffectName = "tf_gunshotsplash";
+ CTFWeaponBase *pWeapon = GetActiveTFWeapon();
+ if ( pWeapon && ( TF_WEAPON_MINIGUN == pWeapon->GetWeaponID() ) )
+ {
+ // for the minigun, use a different, cheaper splash effect because it can create so many of them
+ pszEffectName = "tf_gunshotsplash_minigun";
+ }
+ DispatchEffect( pszEffectName, data );
+
+#ifdef CLIENT_DLL
+ if ( tf_impactwatertimeenable.GetBool() )
+ {
+ m_flWaterImpactTime = gpGlobals->curtime + tf_impactwatertime.GetFloat();
+ }
+#endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFWeaponBase *CTFPlayer::GetActiveTFWeapon( void ) const
+{
+ CBaseCombatWeapon *pRet = GetActiveWeapon();
+ if ( pRet )
+ {
+ Assert( dynamic_cast< CTFWeaponBase* >( pRet ) != NULL );
+ return static_cast< CTFWeaponBase * >( pRet );
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if we are currently wielding a weapon that
+// matches the given item def handle.
+//-----------------------------------------------------------------------------
+bool CTFPlayer::IsActiveTFWeapon( CEconItemDefinition *weaponHandle ) const
+{
+ return ( GetActiveTFWeapon() &&
+ GetActiveTFWeapon()->GetAttributeContainer() &&
+ GetActiveTFWeapon()->GetAttributeContainer()->GetItem() &&
+ GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetItemDefinition() == weaponHandle );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if we are currently wielding a weapon that
+// matches the given item def handle.
+bool CTFPlayer::IsActiveTFWeapon( const CSchemaItemDefHandle &weaponHandle ) const
+{
+ return ( GetActiveTFWeapon() &&
+ GetActiveTFWeapon()->GetAttributeContainer() &&
+ GetActiveTFWeapon()->GetAttributeContainer()->GetItem() &&
+ GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetItemDefinition() == weaponHandle );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: How much build resource ( metal ) does this player have
+//-----------------------------------------------------------------------------
+int CTFPlayer::GetBuildResources( void )
+{
+ return GetAmmoCount( TF_AMMO_METAL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+template < typename T >
+class CScopedFlag
+{
+public:
+ CScopedFlag( T& ref_ )
+ : ref( ref_ )
+ {
+ Assert( !ref );
+ ref = true;
+ }
+
+ ~CScopedFlag()
+ {
+ Assert( ref );
+ ref = false;
+ }
+
+private:
+ T& ref;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayer::GetMovementForwardPull( void ) const
+{
+ CTFWeaponBase *pWpn = GetActiveTFWeapon();
+ if ( pWpn && pWpn->IsFiring() )
+ {
+ float flFiringForwardPull = 0.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flFiringForwardPull, firing_forward_pull );
+
+ return flFiringForwardPull;
+ }
+
+ return 0.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanPlayerMove() const
+{
+ // No one can move when in a final countdown transition.
+ if ( TFGameRules() && TFGameRules()->BInMatchStartCountdown() )
+ { return false; }
+
+ bool bFreezeOnRestart = tf_player_movement_restart_freeze.GetBool();
+ if ( bFreezeOnRestart )
+ {
+#if defined( _DEBUG ) || defined( STAGING_ONLY )
+ if ( mp_developer.GetBool() )
+ bFreezeOnRestart = false;
+#endif // _DEBUG || STAGING_ONLY
+
+ if ( TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && ( TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) )
+ bFreezeOnRestart = false;
+ }
+
+ bool bInRoundRestart = TFGameRules() && TFGameRules()->InRoundRestart();
+ if ( bInRoundRestart && TFGameRules()->IsCompetitiveMode() )
+ {
+ if ( TFGameRules()->GetRoundsPlayed() > 0 )
+ {
+ if ( gpGlobals->curtime < TFGameRules()->GetPreroundCountdownTime() )
+ {
+ bFreezeOnRestart = true;
+ }
+ }
+ else
+ {
+ bFreezeOnRestart = false;
+ }
+ }
+
+ bool bNoMovement = bInRoundRestart && bFreezeOnRestart;
+
+ return !bNoMovement;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayer::TeamFortress_CalculateMaxSpeed( bool bIgnoreSpecialAbility /*= false*/ ) const
+{
+ if ( !GameRules() )
+ return 0.0f;
+
+ int playerclass = GetPlayerClass()->GetClassIndex();
+
+ // Spectators can move while in Classic Observer mode
+ if ( IsObserver() )
+ {
+ if ( GetObserverMode() == OBS_MODE_ROAMING )
+ return GetPlayerClassData( TF_CLASS_SCOUT )->m_flMaxSpeed;
+
+ return 0.0f;
+ }
+
+ // Check for any reason why they can't move at all
+ if ( playerclass == TF_CLASS_UNDEFINED || !CanPlayerMove() )
+ return 1.0f; // this can't return 0 because other parts of the code interpret that as "use default speed" during setup
+
+ // First, get their max class speed
+ float default_speed = GetPlayerClassData( playerclass )->m_flMaxSpeed;
+
+ // Avoid re-entering and calculating our velocity while we're calculating our velocity.
+ // This can happen if we have two characters trying to match each other's velocity, for
+ // example if you have two medics with Quick-Fixes healing each other.
+ //
+ // In the case where we run into this, we end the recursion with someone running default
+ // speed.
+ if ( m_bIsCalculatingMaximumSpeed )
+ return default_speed;
+
+ CScopedFlag<char> flagAvoidReentrancy( m_bIsCalculatingMaximumSpeed );
+
+ // Slow us down if we're disguised as a slower class
+ // unless we're cloaked..
+ float maxfbspeed = default_speed;
+
+ bool bAllowSlowing = m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ? false : true;
+
+ if ( m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) && !m_Shared.IsStealthed() )
+ {
+ maxfbspeed = 0.0f;
+ }
+ else if ( m_Shared.InCond( TF_COND_DISGUISED ) && !m_Shared.IsStealthed() )
+ {
+ float flMaxDisguiseSpeed = GetPlayerClassData( m_Shared.GetDisguiseClass() )->m_flMaxSpeed;
+ maxfbspeed = MIN( flMaxDisguiseSpeed, maxfbspeed );
+ }
+
+ if ( !TFGameRules()->IsMannVsMachineMode() || !IsMiniBoss() ) // No aiming slowdown penalties for MiniBoss players in MVM
+ {
+ // if they're a sniper, and they're aiming, their speed must be 80 or less
+ if ( m_Shared.InCond( TF_COND_AIMING ) )
+ {
+ float flAimMax = 0;
+
+ // Heavies are allowed to move slightly faster than a sniper when spun-up
+ if ( playerclass == TF_CLASS_HEAVYWEAPONS )
+ {
+ {
+ flAimMax = 110;
+ }
+ }
+ else
+ {
+ if ( GetActiveTFWeapon() && (GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_COMPOUND_BOW) )
+ {
+ flAimMax = 160;
+ }
+ else
+ {
+ flAimMax = 80;
+ }
+ }
+
+ CALL_ATTRIB_HOOK_FLOAT( flAimMax, mult_player_aiming_movespeed );
+ maxfbspeed = MIN( maxfbspeed, flAimMax );
+ }
+ }
+
+#ifdef GAME_DLL
+#ifdef STAGING_ONLY
+ if ( m_Shared.InCond( TF_COND_SPEED_BOOST ) || m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) )
+#else
+ if ( m_Shared.InCond( TF_COND_SPEED_BOOST ) )
+#endif
+ {
+ // We only allow our speed boost to apply if we have a base speed to work with. If we're supposed
+ // to be stationary for whatever reason we don't allow a speed to allow us to move.
+ if ( maxfbspeed > 0.0f )
+ {
+ maxfbspeed += MIN( maxfbspeed * 0.4f, tf_whip_speed_increase.GetFloat() );
+ }
+ }
+#endif
+
+ if ( m_Shared.InCond( TF_COND_STEALTHED ) )
+ {
+ if (maxfbspeed > tf_spy_max_cloaked_speed.GetFloat() )
+ {
+ maxfbspeed = tf_spy_max_cloaked_speed.GetFloat();
+ }
+ }
+
+ // if we're in bonus time because a team has won, give the winners 110% speed and the losers 90% speed
+ if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
+ {
+ int iWinner = TFGameRules()->GetWinningTeam();
+
+ if ( iWinner != TEAM_UNASSIGNED )
+ {
+ if ( iWinner == GetTeamNumber() )
+ {
+ maxfbspeed *= 1.1f;
+ }
+ else
+ {
+ maxfbspeed *= 0.9f;
+ }
+ }
+ }
+
+ CTFWeaponBase* pWeapon = GetActiveTFWeapon();
+ if ( pWeapon )
+ {
+ maxfbspeed *= pWeapon->GetSpeedMod();
+ }
+
+ if ( playerclass == TF_CLASS_DEMOMAN )
+ {
+ CTFSword *pSword = dynamic_cast<CTFSword*>(Weapon_OwnsThisID( TF_WEAPON_SWORD ));
+ if ( pSword )
+ {
+ maxfbspeed *= pSword->GetSwordSpeedMod();
+ }
+
+ if ( !bIgnoreSpecialAbility && m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
+ {
+ maxfbspeed = tf_max_charge_speed.GetFloat();
+ }
+ }
+
+ bool bCarryPenalty = true;
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ bCarryPenalty = false;
+ }
+
+ if ( m_Shared.IsCarryingObject() && bCarryPenalty && bAllowSlowing )
+ {
+#ifdef STAGING_ONLY
+ CBaseObject* pObject = m_Shared.GetCarriedObject();
+ if ( pObject && pObject->GetType() == OBJ_TELEPORTER )
+ {
+ CALL_ATTRIB_HOOK_FLOAT( maxfbspeed, teleporter_carry_speed );
+ }
+#endif // STAGING_ONLY
+ // STAGING_ENGY
+ maxfbspeed *= 0.90f;
+ }
+
+ if ( m_Shared.IsLoserStateStunned() && bAllowSlowing )
+ {
+ // Yikes is not as slow, terrible gotcha
+ if ( m_Shared.GetActiveStunInfo()->iStunFlags & TF_STUN_BY_TRIGGER )
+ {
+ maxfbspeed *= 0.75f;
+ }
+ else
+ {
+ maxfbspeed *= 0.5f;
+ }
+ }
+
+ // If we have an item with a move speed modification, apply it to the final speed.
+ CALL_ATTRIB_HOOK_FLOAT( maxfbspeed, mult_player_movespeed );
+
+ if ( m_Shared.IsShieldEquipped() )
+ {
+ CALL_ATTRIB_HOOK_FLOAT( maxfbspeed, mult_player_movespeed_shieldrequired );
+ }
+
+ if ( playerclass == TF_CLASS_MEDIC )
+ {
+ if ( pWeapon )
+ {
+ CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( pWeapon );
+ if ( pMedigun )
+ {
+ // Medics match faster classes when healing them
+ CTFPlayer *pHealTarget = ToTFPlayer( pMedigun->GetHealTarget() );
+ if ( pHealTarget )
+ {
+ // The Quick-Fix attaches to charging demos
+ bool bCharge = ( pMedigun->GetMedigunType() == MEDIGUN_QUICKFIX && pHealTarget->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) );
+
+ const float flHealTargetMaxSpeed = ( bCharge ) ? tf_max_charge_speed.GetFloat() : pHealTarget->TeamFortress_CalculateMaxSpeed( true );
+ maxfbspeed = Max( maxfbspeed, flHealTargetMaxSpeed );
+ }
+ }
+ }
+
+ // Special bone saw
+ int iTakeHeads = 0;
+ CALL_ATTRIB_HOOK_INT( iTakeHeads, add_head_on_hit );
+ if ( iTakeHeads )
+ {
+ CTFBonesaw *pSaw = dynamic_cast<CTFBonesaw*>(Weapon_OwnsThisID( TF_WEAPON_HARVESTER_SAW ));
+ if ( pSaw )
+ {
+ maxfbspeed *= pSaw->GetBoneSawSpeedMod();
+ }
+ }
+ }
+
+ float flClassResourceLevelMod = 1.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flClassResourceLevelMod, mult_player_movespeed_resource_level );
+ if ( flClassResourceLevelMod != 1.f )
+ {
+ // Medic Uber
+ if ( playerclass == TF_CLASS_MEDIC )
+ {
+ CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
+ if ( pMedigun )
+ {
+ maxfbspeed *= RemapValClamped( pMedigun->GetChargeLevel(), 0.f, 1.f, 1.f, flClassResourceLevelMod );
+ }
+ }
+ }
+
+ // If we're a heavy with berzerker mode...
+ if ( playerclass == TF_CLASS_HEAVYWEAPONS )
+ {
+ float heavy_max_speed = default_speed * 1.35f;
+ if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) )
+ {
+ maxfbspeed *= 1.35f;
+ if ( maxfbspeed > heavy_max_speed )
+ {
+ // Prevent other speed modifiers like GRU from making berzerker mode too fast.
+ maxfbspeed = heavy_max_speed;
+ }
+ }
+ }
+
+ if ( playerclass == TF_CLASS_SCOUT )
+ {
+ if ( Weapon_OwnsThisID( TF_WEAPON_PEP_BRAWLER_BLASTER ) )
+ {
+ // Make this change based on attrs, hardcode right now
+ maxfbspeed *= RemapValClamped( m_Shared.GetScoutHypeMeter(), 0.0f, 100.0f, 1.0f, 1.45f );
+ }
+ // Crit-a-Cola gives a move bonus while active
+ if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) )
+ {
+ maxfbspeed *= 1.25f;
+ }
+ }
+
+ // Mann Vs Machine mode has a speed penalty for carrying the flag
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
+ {
+ if ( HasTheFlag() && !IsMiniBoss() )
+ {
+ maxfbspeed *= tf_mvm_bot_flag_carrier_movement_penalty.GetFloat();
+ }
+ }
+ }
+
+#ifdef STAGING_ONLY
+ // Overloaded circuits!
+ if ( m_Shared.InCond( TF_COND_REPROGRAMMED ) )
+ {
+ maxfbspeed *= 2.f;
+ }
+#endif // STAGING_ONLY
+
+ if ( m_Shared.GetCarryingRuneType() == RUNE_HASTE )
+ {
+ maxfbspeed *= 1.3f;
+ }
+ if ( m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
+ {
+ // light classes get more benefit due to movement speed cap of 520
+ switch ( GetPlayerClass()->GetClassIndex() )
+ {
+ case TF_CLASS_DEMOMAN:
+ case TF_CLASS_SOLDIER:
+ case TF_CLASS_HEAVYWEAPONS:
+ maxfbspeed *= 1.4f;
+ break;
+ default:
+ maxfbspeed *= 1.5f;
+ break;
+ }
+ }
+
+ return maxfbspeed;
+}
+
+void CTFPlayer::TeamFortress_SetSpeed()
+{
+#ifdef GAME_DLL
+ if ( TFGameRules() && TFGameRules()->IsPasstimeMode() && g_pPasstimeLogic )
+ {
+ float flPackSpeed = g_pPasstimeLogic->GetPackSpeed( this );
+ if ( flPackSpeed > 0 )
+ {
+ SetMaxSpeed( flPackSpeed );
+ return;
+ }
+ }
+#endif
+
+ const float fMaxSpeed = TeamFortress_CalculateMaxSpeed();
+
+ // Set the speed
+ SetMaxSpeed( fMaxSpeed );
+
+ if ( fMaxSpeed <= 0.0f )
+ {
+ SetAbsVelocity( vec3_origin );
+ }
+
+#ifdef GAME_DLL
+ // Anyone that's watching our speed should know that our speed changed so they can
+ // update their own speed.
+ //
+ // We guard against re-entrancy here as well to avoid the case where two medics are
+ // healing each other with Quick-Fixes.
+ //
+ // This can also happen when a quickfix medic is healing a player that gets a speed
+ // boost. And it doesn't work because the recursive call will just return the healed
+ // character's default speed instead of current speed.
+ // TODO fix this. why not just set the medic's speed directly at this point?
+ //
+ if ( !m_bIsCalculatingMaximumSpeed )
+ {
+ CScopedFlag<char> flagAvoidReentrancy( m_bIsCalculatingMaximumSpeed );
+
+ CUtlVector<CTFPlayer *> vecSpeedWatchers;
+ m_Shared.GetSpeedWatchersList( &vecSpeedWatchers );
+ FOR_EACH_VEC( vecSpeedWatchers, i )
+ {
+ vecSpeedWatchers[i]->TeamFortress_SetSpeed();
+ }
+ }
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::HasItem( void ) const
+{
+ return ( m_hItem != NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::SetItem( CTFItem *pItem )
+{
+ m_hItem = pItem;
+
+#ifndef CLIENT_DLL
+ if ( pItem )
+ {
+ AddGlowEffect();
+ }
+ else
+ {
+ RemoveGlowEffect();
+ }
+
+ if ( pItem && pItem->GetItemID() == TF_ITEM_CAPTURE_FLAG )
+ {
+ RemoveInvisibility();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFItem *CTFPlayer::GetItem( void ) const
+{
+ return m_hItem;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Is the player carrying the flag?
+//-----------------------------------------------------------------------------
+bool CTFPlayer::HasTheFlag( ETFFlagType exceptionTypes[], int nNumExceptions ) const
+{
+ if ( HasItem() && GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG )
+ {
+ CCaptureFlag* pFlag = static_cast< CCaptureFlag* >( GetItem() );
+
+ for( int i=0; i < nNumExceptions; ++i )
+ {
+ if ( exceptionTypes[ i ] == pFlag->GetType() )
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CTFPlayer::IsAllowedToPickUpFlag( void ) const
+{
+ int iCannotPickUpIntelligence = 0;
+ CALL_ATTRIB_HOOK_INT( iCannotPickUpIntelligence, cannot_pick_up_intelligence );
+ if ( iCannotPickUpIntelligence )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCaptureZone *CTFPlayer::GetCaptureZoneStandingOn( void )
+{
+ touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
+ if ( root )
+ {
+ for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
+ {
+ CBaseEntity *pTouch = link->entityTouched;
+ if ( pTouch && pTouch->IsSolidFlagSet( FSOLID_TRIGGER ) && pTouch->IsBSPModel() )
+ {
+ CCaptureZone *pAreaTrigger = dynamic_cast< CCaptureZone* >(pTouch);
+ if ( pAreaTrigger )
+ {
+ return pAreaTrigger;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+CCaptureZone *CTFPlayer::GetClosestCaptureZone( void )
+{
+ CCaptureZone *pCaptureZone = NULL;
+ float flClosestDistance = FLT_MAX;
+
+ for ( int i=0; i<ICaptureZoneAutoList::AutoList().Count(); ++i )
+ {
+ CCaptureZone *pTempCaptureZone = static_cast< CCaptureZone* >( ICaptureZoneAutoList::AutoList()[i] );
+ if ( !pTempCaptureZone->IsDisabled() && pTempCaptureZone->GetTeamNumber() == GetTeamNumber() )
+ {
+ float fCurrentDistance = GetAbsOrigin().DistTo( pTempCaptureZone->WorldSpaceCenter() );
+ if ( flClosestDistance > fCurrentDistance )
+ {
+ pCaptureZone = pTempCaptureZone;
+ flClosestDistance = fCurrentDistance;
+ }
+ }
+ }
+
+ return pCaptureZone;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this player's allowed to build another one of the specified object
+//-----------------------------------------------------------------------------
+int CTFPlayer::CanBuild( int iObjectType, int iObjectMode )
+{
+ if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
+ return CB_UNKNOWN_OBJECT;
+
+ const CObjectInfo *pInfo = GetObjectInfo( iObjectType );
+ if ( pInfo && ((iObjectMode > pInfo->m_iNumAltModes) || (iObjectMode < 0)) )
+ return CB_CANNOT_BUILD;
+
+ // Does this type require a specific builder?
+ bool bHasSubType = false;
+ CTFWeaponBuilder *pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, iObjectType );
+ if ( pBuilder )
+ {
+ bHasSubType = true;
+ }
+
+ if ( TFGameRules() )
+ {
+ if ( TFGameRules()->IsTruceActive() && ( iObjectType == OBJ_ATTACHMENT_SAPPER ) )
+ return CB_CANNOT_BUILD;
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ // If a human is placing a sapper
+ if ( !IsBot() && iObjectType == OBJ_ATTACHMENT_SAPPER )
+ {
+ // Only allow one Sapper of any kind in MvM
+ if ( GetNumObjects( iObjectType, BUILDING_MODE_ANY ) )
+ return CB_LIMIT_REACHED;
+
+ return ( ( GetAmmoCount( TF_AMMO_GRENADES2 ) > 0 ) ? CB_CAN_BUILD : CB_CANNOT_BUILD );
+ }
+ }
+ }
+
+#ifndef CLIENT_DLL
+ CTFPlayerClass *pCls = GetPlayerClass();
+
+ if ( !bHasSubType && pCls && pCls->CanBuildObject( iObjectType ) == false )
+ {
+ return CB_CANNOT_BUILD;
+ }
+#endif
+
+ // We can redeploy the object if we are carrying it.
+ CBaseObject* pObjType = GetObjectOfType( iObjectType, iObjectMode );
+ if ( pObjType && pObjType->IsCarried() )
+ {
+ return CB_CAN_BUILD;
+ }
+
+ // Special handling of "disposable" sentries
+ if ( TFGameRules()->GameModeUsesUpgrades() && iObjectType == OBJ_SENTRYGUN )
+ {
+ // If we have our main sentry, see if we're allowed to build disposables
+ if ( GetNumObjects( iObjectType, iObjectMode ) )
+ {
+ bool bHasPrimary = false;
+ int nDisposableCount = 0;
+ int nMaxDisposableCount = 0;
+ CALL_ATTRIB_HOOK_INT( nMaxDisposableCount, engy_disposable_sentries );
+ if ( nMaxDisposableCount )
+ {
+
+ for ( int i = GetObjectCount()-1; i >= 0; i-- )
+ {
+ CBaseObject *pObj = GetObject( i );
+ if ( pObj )
+ {
+ if ( !pObj->IsDisposableBuilding() )
+ {
+ bHasPrimary = true;
+ }
+ else
+ {
+ nDisposableCount++;
+ }
+ }
+ }
+
+ if ( bHasPrimary )
+ {
+ if ( nDisposableCount < nMaxDisposableCount )
+ {
+ return CB_CAN_BUILD;
+ }
+ else
+ {
+ return CB_LIMIT_REACHED;
+ }
+ }
+ }
+ }
+ }
+
+ // Allow MVM engineer bots to have multiple sentries. Currently they only need this so
+ // they can appear to be carrying a new building when advancing their nest rather than
+ // transporting an existing building.
+ if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && IsBot() )
+ {
+ return CB_CAN_BUILD;
+ }
+
+ // Make sure we haven't hit maximum number
+ int iObjectCount = GetNumObjects( iObjectType, iObjectMode );
+ if ( iObjectCount >= GetObjectInfo( iObjectType )->m_nMaxObjects && GetObjectInfo( iObjectType )->m_nMaxObjects != -1)
+ {
+ return CB_LIMIT_REACHED;
+ }
+
+ // Find out how much the object should cost
+ int iCost = m_Shared.CalculateObjectCost( this, iObjectType );
+
+ // Make sure we have enough resources
+ if ( GetBuildResources() < iCost )
+ {
+ return CB_NEED_RESOURCES;
+ }
+
+ return CB_CAN_BUILD;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsAiming( void )
+{
+ if ( !m_pOuter )
+ return false;
+
+ bool bAiming = InCond( TF_COND_AIMING ) && !m_pOuter->IsPlayerClass( TF_CLASS_SOLDIER );
+ if ( m_pOuter->IsPlayerClass( TF_CLASS_SNIPER ) && m_pOuter->GetActiveTFWeapon() && ( m_pOuter->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_SNIPERRIFLE_CLASSIC ) )
+ {
+ bAiming = InCond( TF_COND_ZOOMED );
+ }
+
+ return bAiming;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set what type of rune we are carrying--or that we are not carrying any.
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetCarryingRuneType( RuneTypes_t rt )
+{
+#ifdef GAME_DLL
+ // Stat Tracking
+ if ( rt != RUNE_NONE )
+ {
+ // if getting a rune, start timer
+ m_flRuneAcquireTime = gpGlobals->curtime;
+ }
+ else if ( IsCarryingRune() )
+ {
+ // if setting to none (death or drop) and I have a power up, report and set timer to -1
+ float duration = gpGlobals->curtime - m_flRuneAcquireTime;
+ m_flRuneAcquireTime = -1;
+
+ CTF_GameStats.Event_PowerUpRuneDuration( m_pOuter, (int)duration, GetCarryingRuneType() );
+ }
+
+ // clear rune charge
+ SetRuneCharge( 0.f );
+#endif
+
+ // Not 100% sure AddCond does what I want to do if we already have that cond, so
+ // let's assert so we can debug it if it ever comes up.
+ Assert( rt != GetCarryingRuneType() );
+
+ // We are only ever allowed to carry one rune type at a time, this logic ensures that.
+ for ( int i = 0; i < RUNE_TYPES_MAX; ++i )
+ {
+ if ( i == rt )
+ {
+ AddCond( GetConditionFromRuneType( (RuneTypes_t) i ) );
+ }
+ else
+ {
+ RemoveCond( GetConditionFromRuneType( (RuneTypes_t) i ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the currently carried rune type, or RUNE_NONE if we are not carrying one.
+//-----------------------------------------------------------------------------
+RuneTypes_t CTFPlayerShared::GetCarryingRuneType( void ) const
+{
+ RuneTypes_t retVal = RUNE_NONE;
+ for ( int i = 0; i < RUNE_TYPES_MAX; ++i )
+ {
+ if ( InCond( GetConditionFromRuneType( (RuneTypes_t) i ) ) )
+ {
+ // You are only allowed to have one rune type, if this hits we somehow erroneously
+ // have two condition bits set for different types of runes.
+ Assert( retVal == RUNE_NONE );
+ retVal = (RuneTypes_t)i;
+ break;
+ }
+ }
+
+ return retVal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::CalculateObjectCost( CTFPlayer* pBuilder, int iObjectType )
+{
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+#ifdef STAGING_ONLY
+ if ( ( TFGameRules()->InSetup() || TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() ) && iObjectType != OBJ_SPY_TRAP )
+#else
+ if ( TFGameRules()->InSetup() || TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
+#endif
+ {
+ return 0;
+ }
+ }
+
+ int nCost = InternalCalculateObjectCost( iObjectType );
+
+ // Mini sentires are 30 metal cheaper
+ CTFWrench* pWrench = dynamic_cast<CTFWrench*>( pBuilder->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) );
+ if ( pWrench && pWrench->IsPDQ() && ( iObjectType == OBJ_SENTRYGUN ) )
+ {
+ nCost -= 30;
+ }
+
+#ifdef STAGING_ONLY
+ // Mini dispensers are 30 metal cheaper
+ int nMiniDispenserEnabled = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, nMiniDispenserEnabled, allows_building_mini_dispenser );
+ if ( nMiniDispenserEnabled != 0 && iObjectType == OBJ_DISPENSER )
+ {
+ nCost -= 30;
+ }
+
+ // Speed Pads are cheaper
+ int nSpeedPad = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, nSpeedPad, teleporter_is_speedpad );
+ if ( nSpeedPad != 0 && iObjectType == OBJ_TELEPORTER )
+ {
+ nCost -= 25;
+ }
+#endif
+
+ if ( iObjectType == OBJ_TELEPORTER )
+ {
+ float flCostMod = 1.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flCostMod, mod_teleporter_cost );
+ if ( flCostMod != 1.f )
+ {
+ nCost *= flCostMod;
+ }
+ }
+
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, nCost, building_cost_reduction );
+
+ return nCost;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::HealthKitPickupEffects( int iHealthGiven /*= 0*/ )
+{
+ // Healthkits also contain a fire blanket.
+ if ( InCond( TF_COND_BURNING ) )
+ {
+ RemoveCond( TF_COND_BURNING );
+ }
+ // and sutures
+ if ( InCond( TF_COND_BLEEDING ) )
+ {
+ RemoveCond( TF_COND_BLEEDING );
+ }
+ // and cures plague
+ if ( InCond( TF_COND_PLAGUE ) )
+ {
+ RemoveCond( TF_COND_PLAGUE );
+ }
+
+ // Spawns a number on the player's health bar in the HUD, and also
+ // spawns a "+" particle over their head for enemies to see
+ if ( iHealthGiven && !IsStealthed() && m_pOuter )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" );
+ if ( event )
+ {
+ event->SetInt( "amount", iHealthGiven );
+ event->SetInt( "entindex", m_pOuter->entindex() );
+ event->SetInt( "weapon_def_index", INVALID_ITEM_DEF_INDEX );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of objects of the specified type that this player has
+//-----------------------------------------------------------------------------
+int CTFPlayer::GetNumObjects( int iObjectType, int iObjectMode /*= 0*/ )
+{
+ int iCount = 0;
+ for (int i = 0; i < GetObjectCount(); i++)
+ {
+ if ( !GetObject(i) )
+ continue;
+
+ if ( GetObject(i)->IsDisposableBuilding() )
+ continue;
+
+ if ( GetObject(i)->GetType() == iObjectType &&
+ ( GetObject(i)->GetObjectMode() == iObjectMode || iObjectMode == BUILDING_MODE_ANY ) )
+ {
+ iCount++;
+ }
+ }
+
+ return iCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::ItemPostFrame()
+{
+ if ( m_hOffHandWeapon.Get() && m_hOffHandWeapon->IsWeaponVisible() )
+ {
+ if ( gpGlobals->curtime < m_flNextAttack )
+ {
+ m_hOffHandWeapon->ItemBusyFrame();
+ }
+ else
+ {
+#if defined( CLIENT_DLL )
+ // Not predicting this weapon
+ if ( m_hOffHandWeapon->IsPredicted() )
+#endif
+ {
+ m_hOffHandWeapon->ItemPostFrame( );
+ }
+ }
+ }
+
+#ifdef GAME_DLL
+ CTFWeaponBase *pActiveWeapon = GetActiveTFWeapon();
+ if ( pActiveWeapon )
+ {
+ pActiveWeapon->HandleInspect();
+ }
+#endif // GAME_DLL
+
+ BaseClass::ItemPostFrame();
+}
+
+void CTFPlayer::SetOffHandWeapon( CTFWeaponBase *pWeapon )
+{
+ m_hOffHandWeapon = pWeapon;
+ if ( m_hOffHandWeapon.Get() )
+ {
+ m_hOffHandWeapon->Deploy();
+ }
+}
+
+// Set to NULL at the end of the holster?
+void CTFPlayer::HolsterOffHandWeapon( void )
+{
+ if ( m_hOffHandWeapon.Get() )
+ {
+ m_hOffHandWeapon->Holster();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if we should record our last weapon when switching between the two specified weapons
+//-----------------------------------------------------------------------------
+bool CTFPlayer::Weapon_ShouldSetLast( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
+{
+ // if the weapon doesn't want to be auto-switched to, don't!
+ CTFWeaponBase *pTFOldWeapon = dynamic_cast< CTFWeaponBase * >( pOldWeapon );
+ if ( pTFOldWeapon )
+ {
+ if ( pTFOldWeapon->AllowsAutoSwitchTo() == false )
+ {
+ return false;
+ }
+ }
+
+ return BaseClass::Weapon_ShouldSetLast( pOldWeapon, pNewWeapon );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::SelectItem( const char *pstr, int iSubType /*= 0*/ )
+{
+ // This is basically a copy from the base class with addition of Weapon_CanSwitchTo
+ // We're not calling BaseClass::SelectItem on purpose to prevent breaking other games
+ // that might rely on not calling Weapon_CanSwitchTo
+
+ if (!pstr)
+ return;
+
+ CBaseCombatWeapon *pItem = Weapon_OwnsThisType( pstr, iSubType );
+
+ if (!pItem)
+ return;
+
+ if( GetObserverMode() != OBS_MODE_NONE )
+ return;// Observers can't select things.
+
+ if ( !Weapon_ShouldSelectItem( pItem ) )
+ return;
+
+ // FIX, this needs to queue them up and delay
+ // Make sure the current weapon can be holstered
+ if ( !Weapon_CanSwitchTo( pItem ) )
+ return;
+
+ ResetAutoaim();
+
+ Weapon_Switch( pItem );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex )
+{
+ // Ghosts cant switch weapons!
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ {
+ return false;
+ }
+
+ // set last weapon before we switch to a new weapon to make sure that we can get the correct last weapon in Deploy/Holster
+ // This should be done in CBasePlayer::Weapon_Switch, but we don't want to break other games
+ CBaseCombatWeapon *pPreviousLastWeapon = GetLastWeapon();
+ CBaseCombatWeapon *pPreviousActiveWeapon = GetActiveWeapon();
+
+ // always set last for Weapon_Switch code to get attribute from the correct last item
+ Weapon_SetLast( GetActiveWeapon() );
+
+ bool bSwitched = BaseClass::Weapon_Switch( pWeapon, viewmodelindex );
+ if ( bSwitched )
+ {
+ m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_ATTACK_AND_RELOAD );
+
+ // valid last weapon
+ if ( Weapon_ShouldSetLast( pPreviousActiveWeapon, pWeapon ) )
+ {
+ Weapon_SetLast( pPreviousActiveWeapon );
+ SetSecondaryLastWeapon( pPreviousLastWeapon );
+ }
+ // previous active weapon is not valid to be last weapon, but the new active weapon is
+ else if ( Weapon_ShouldSetLast( pWeapon, pPreviousLastWeapon ) )
+ {
+ // this will skip the logic to ignore first time block and allow weapon to check for honorbound attribute right away
+ CTFWeaponBase *pTFWeapon = assert_cast< CTFWeaponBase * >( pWeapon );
+ if ( pTFWeapon && pTFWeapon->IsHonorBound() )
+ {
+ m_Shared.m_flFirstPrimaryAttack = gpGlobals->curtime;
+ }
+
+ if ( pWeapon != GetSecondaryLastWeapon() )
+ {
+ Weapon_SetLast( GetSecondaryLastWeapon() );
+ SetSecondaryLastWeapon( pPreviousLastWeapon );
+ }
+ else
+ {
+ // new active weapon is the same as the secondary last weapon, leave the last weapon alone
+ Weapon_SetLast( pPreviousLastWeapon );
+ }
+ }
+ // both previous and new active weapons are not not valid for last weapon
+ else
+ {
+ Weapon_SetLast( pPreviousLastWeapon );
+ }
+ }
+ else
+ {
+ // restore to the previous last weapon if we failed to switch to a new weapon
+ Weapon_SetLast( pPreviousLastWeapon );
+ }
+
+#ifdef GAME_DLL
+ if ( bSwitched && TFGameRules() && TFGameRules()->IsInTraining() && TFGameRules()->GetTrainingModeLogic() && IsFakeClient() == false)
+ {
+ TFGameRules()->GetTrainingModeLogic()->OnPlayerSwitchedWeapons( this );
+ }
+#endif
+
+#ifdef CLIENT_DLL
+ m_Shared.UpdateCritBoostEffect();
+#endif
+
+ return bSwitched;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFWearable *CTFPlayer::GetEquippedWearableForLoadoutSlot( int iLoadoutSlot )
+{
+ int iClass = GetPlayerClass()->GetClassIndex();
+
+ for ( int i = 0; i < GetNumWearables(); ++i )
+ {
+ CTFWearable *pWearableItem = dynamic_cast< CTFWearable * >( GetWearable( i ) );
+ if ( !pWearableItem )
+ continue;
+
+ if ( !pWearableItem->GetAttributeContainer() )
+ continue;
+
+ CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem();
+ if ( !pEconItemView )
+ continue;
+
+ CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
+ if ( !pItemDef )
+ continue;
+
+ if ( pItemDef->GetLoadoutSlot(iClass) == iLoadoutSlot )
+ return pWearableItem;
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFPlayer::GetEntityForLoadoutSlot( int iLoadoutSlot )
+{
+ CBaseEntity *pEntity = NULL;
+ if ( IsWearableSlot( iLoadoutSlot ) )
+ {
+ // Search Wearables first otherwise search Weapons as a fall back
+ pEntity = GetEquippedWearableForLoadoutSlot( iLoadoutSlot );
+ if ( pEntity )
+ {
+ return pEntity;
+ }
+ }
+
+ int iClass = GetPlayerClass()->GetClassIndex();
+ for ( int i = 0; i < MAX_WEAPONS; i++ )
+ {
+ if ( GetWeapon(i) )
+ {
+ CEconItemView *pEconItemView = GetWeapon(i)->GetAttributeContainer()->GetItem();
+ if ( !pEconItemView )
+ continue;
+
+ CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
+ if ( !pItemDef )
+ continue;
+
+ if ( pItemDef->GetLoadoutSlot( iClass ) == iLoadoutSlot )
+ return GetWeapon(i);
+ }
+ }
+ return NULL;
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::StopViewModelParticles( C_BaseEntity *pParticleEnt )
+{
+ pParticleEnt->ParticleProp()->StopParticlesInvolving( pParticleEnt );
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::GetStepSoundVelocities( float *velwalk, float *velrun )
+{
+ float flMaxSpeed = MaxSpeed();
+
+ if ( ( GetFlags() & FL_DUCKING ) || ( GetMoveType() == MOVETYPE_LADDER ) )
+ {
+ if ( m_Shared.IsLoser() )
+ {
+ *velwalk = 0;
+ *velrun = 0;
+ }
+ else
+ {
+ *velwalk = flMaxSpeed * 0.25;
+ *velrun = flMaxSpeed * 0.3;
+ }
+ }
+ else
+ {
+ *velwalk = flMaxSpeed * 0.3;
+ *velrun = flMaxSpeed * 0.8;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::SetStepSoundTime( stepsoundtimes_t iStepSoundTime, bool bWalking )
+{
+ float flMaxSpeed = MaxSpeed();
+
+ switch ( iStepSoundTime )
+ {
+ case STEPSOUNDTIME_NORMAL:
+ case STEPSOUNDTIME_WATER_FOOT:
+ m_flStepSoundTime = RemapValClamped( flMaxSpeed, 200, 450, 400, 200 );
+ if ( bWalking )
+ {
+ m_flStepSoundTime += 100;
+ }
+ break;
+
+ case STEPSOUNDTIME_ON_LADDER:
+ m_flStepSoundTime = 350;
+ break;
+
+ case STEPSOUNDTIME_WATER_KNEE:
+ m_flStepSoundTime = RemapValClamped( flMaxSpeed, 200, 450, 600, 400 );
+ break;
+
+ default:
+ Assert(0);
+ break;
+ }
+
+ if ( ( GetFlags() & FL_DUCKING) || ( GetMoveType() == MOVETYPE_LADDER ) )
+ {
+ m_flStepSoundTime += 100;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayer::GetOverrideStepSound( const char *pszBaseStepSoundName )
+{
+
+ if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS && !IsMiniBoss() && !m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ return "MVM.BotStep";
+ }
+
+ Assert( pszBaseStepSoundName );
+
+ struct override_sound_entry_t { int iOverrideIndex; const char *pszBaseSoundName; const char *pszNewSoundName; };
+
+ enum
+ {
+ kFootstepSoundSet_Default = 0,
+ kFootstepSoundSet_SoccerCleats = 1,
+ kFootstepSoundSet_HeavyGiant = 2,
+ kFootstepSoundSet_SoldierGiant = 3,
+ kFootstepSoundSet_DemoGiant = 4,
+ kFootstepSoundSet_ScoutGiant = 5,
+ kFootstepSoundSet_PyroGiant = 6,
+ kFootstepSoundSet_SentryBuster = 7,
+ kFootstepSoundSet_TreasureChest = 8,
+ kFootstepSoundSet_Octopus = 9,
+ };
+
+ int iOverrideFootstepSoundSet = kFootstepSoundSet_Default;
+ CALL_ATTRIB_HOOK_INT( iOverrideFootstepSoundSet, override_footstep_sound_set );
+
+ if ( iOverrideFootstepSoundSet != kFootstepSoundSet_Default )
+ {
+ static const override_sound_entry_t s_ReplacementSounds[] =
+ {
+ { kFootstepSoundSet_SoccerCleats, "Default.StepLeft", "cleats_conc.StepLeft" },
+ { kFootstepSoundSet_SoccerCleats, "Default.StepRight", "cleats_conc.StepRight" },
+ { kFootstepSoundSet_SoccerCleats, "Dirt.StepLeft", "cleats_dirt.StepLeft" },
+ { kFootstepSoundSet_SoccerCleats, "Dirt.StepRight", "cleats_dirt.StepRight" },
+ { kFootstepSoundSet_SoccerCleats, "Concrete.StepLeft", "cleats_conc.StepLeft" },
+ { kFootstepSoundSet_SoccerCleats, "Concrete.StepRight", "cleats_conc.StepRight" },
+
+ //
+ { kFootstepSoundSet_Octopus, "Default.StepLeft", "Octopus.StepCommon" },
+ { kFootstepSoundSet_Octopus, "Default.StepRight", "Octopus.StepCommon" },
+ { kFootstepSoundSet_Octopus, "Dirt.StepLeft", "Octopus.StepCommon" },
+ { kFootstepSoundSet_Octopus, "Dirt.StepRight", "Octopus.StepCommon" },
+ { kFootstepSoundSet_Octopus, "Concrete.StepLeft", "Octopus.StepCommon" },
+ { kFootstepSoundSet_Octopus, "Concrete.StepRight", "Octopus.StepCommon" },
+
+ //
+ { kFootstepSoundSet_HeavyGiant, "", "MVM.GiantHeavyStep" },
+
+ //
+ { kFootstepSoundSet_SoldierGiant, "", "MVM.GiantSoldierStep" },
+
+ //
+ { kFootstepSoundSet_DemoGiant, "", "MVM.GiantDemomanStep" },
+
+ //
+ { kFootstepSoundSet_ScoutGiant, "", "MVM.GiantScoutStep" },
+
+ //
+ { kFootstepSoundSet_PyroGiant, "", "MVM.GiantPyroStep" },
+
+ //
+ { kFootstepSoundSet_SentryBuster, "", "MVM.SentryBusterStep" },
+
+ //
+ { kFootstepSoundSet_TreasureChest, "", "Chest.Step" },
+ };
+
+ for ( int i = 0; i < ARRAYSIZE( s_ReplacementSounds ); i++ )
+ {
+ if ( iOverrideFootstepSoundSet == s_ReplacementSounds[i].iOverrideIndex )
+ {
+ if ( !s_ReplacementSounds[i].pszBaseSoundName[0] ||
+ !Q_stricmp( pszBaseStepSoundName, s_ReplacementSounds[i].pszBaseSoundName ) )
+ return s_ReplacementSounds[i].pszNewSoundName;
+ }
+ }
+ }
+
+ // Fallback.
+ return BaseClass::GetOverrideStepSound( pszBaseStepSoundName );
+}
+
+void CTFPlayer::OnEmitFootstepSound( const CSoundParameters& params, const Vector& vecOrigin, float fVolume )
+{
+ // play jingles in addition to normal footstep sounds,
+ // and play them quietly to the local player so they don't go insane
+ int iJingle = 0;
+ CALL_ATTRIB_HOOK_INT( iJingle, add_jingle_to_footsteps );
+ if ( iJingle > 0 )
+ {
+ CRecipientFilter filter;
+ filter.AddRecipientsByPAS( vecOrigin );
+
+#ifndef CLIENT_DLL
+ // in MP, server removes all players in the vecOrigin's PVS, these players generate the footsteps client side
+ if ( gpGlobals->maxClients > 1 )
+ {
+ filter.RemoveRecipientsByPVS( vecOrigin );
+ }
+#endif
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_BODY;
+ ep.m_pSoundName = ( iJingle == 1 ) ? "xmas.jingle" : "xmas.jingle_higher";
+#ifdef CLIENT_DLL
+ ep.m_flVolume = IsLocalPlayer() ? 0.3f * fVolume : fVolume; // quieter for local player
+#else
+ ep.m_flVolume = fVolume;
+#endif
+ ep.m_SoundLevel = params.soundlevel;
+ ep.m_nFlags = SND_CHANGE_VOL;
+ ep.m_nPitch = params.pitch;
+ ep.m_pOrigin = &vecOrigin;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+#ifdef CLIENT_DLL
+ // Halloween-specific bonus footsteps for viewmodel-rendering only. Real model footsteps will happen in the real
+ // footstep code in response to animation events. THIS IS A HACK!
+ if ( !ShouldDrawThisPlayer() && !m_Shared.IsStealthed() && !m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ int iHalloweenFootstepType = 0;
+ if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
+ {
+ CALL_ATTRIB_HOOK_INT( iHalloweenFootstepType, halloween_footstep_type );
+ }
+
+ if ( m_nFootStamps > 0 )
+ {
+ // White color!
+ iHalloweenFootstepType = 0xFFFFFFFF;
+ }
+
+ if ( iHalloweenFootstepType != 0 )
+ {
+ CNewParticleEffect *pEffect = SpawnHalloweenSpellFootsteps( PATTACH_CUSTOMORIGIN, iHalloweenFootstepType );
+ if ( pEffect )
+ {
+ pEffect->SetControlPoint( 0, GetAbsOrigin() );
+ }
+ }
+
+ if ( m_nFootStamps > 0 )
+ {
+ m_nFootStamps--;
+ }
+ }
+#endif
+}
+
+void CTFPlayer::ModifyEmitSoundParams( EmitSound_t &params )
+{
+ BaseClass::ModifyEmitSoundParams( params );
+
+ CTFWeaponBase *pWeapon = GetActiveTFWeapon();
+ if ( pWeapon )
+ {
+ pWeapon->ModifyEmitSoundParams( params );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanAttack( int iCanAttackFlags )
+{
+ CTFGameRules *pRules = TFGameRules();
+
+ Assert( pRules );
+
+ if ( m_Shared.HasPasstimeBall() )
+ {
+ // Always allow throwing the ball.
+ return true;
+ }
+
+ if ( ( m_Shared.GetStealthNoAttackExpireTime() > gpGlobals->curtime && !m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF ) ) || m_Shared.InCond( TF_COND_STEALTHED ) )
+ {
+ if ( !( iCanAttackFlags & TF_CAN_ATTACK_FLAG_GRAPPLINGHOOK ) )
+ {
+#ifdef CLIENT_DLL
+ HintMessage( HINT_CANNOT_ATTACK_WHILE_CLOAKED, true, true );
+#endif
+ return false;
+ }
+ }
+
+ if ( m_Shared.IsFeignDeathReady() )
+ {
+#ifdef CLIENT_DLL
+ HintMessage( HINT_CANNOT_ATTACK_WHILE_FEIGN_ARMED, true, true );
+#endif
+
+ return false;
+ }
+
+ if ( IsTaunting() )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_PHASE ) == true )
+ return false;
+
+ if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
+ {
+ return false;
+ }
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Weapons can call this on secondary attack and it will link to the class
+// ability
+//-----------------------------------------------------------------------------
+bool CTFPlayer::DoClassSpecialSkill( void )
+{
+ if ( !IsAlive() )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return false;
+
+ bool bDoSkill = false;
+
+ // powerup charge activation has higher priority than any class special skill
+ if ( m_Shared.IsRuneCharged() && GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRAPPLINGHOOK )
+ {
+#ifdef GAME_DLL
+ CTFGrapplingHook *pHook = static_cast<CTFGrapplingHook*>( GetActiveTFWeapon() );
+ if ( pHook )
+ {
+ pHook->ActivateRune();
+ }
+#endif // GAME_DLL
+ return true;
+ }
+
+ switch( GetPlayerClass()->GetClassIndex() )
+ {
+ case TF_CLASS_SPY:
+ {
+ if ( !m_Shared.InCond( TF_COND_TAUNTING ) )
+ {
+ if ( m_Shared.m_flStealthNextChangeTime <= gpGlobals->curtime )
+ {
+ // Feign death if we have the right equipment mod.
+ CTFWeaponInvis* pInvisWatch = static_cast<CTFWeaponInvis*>( Weapon_OwnsThisID( TF_WEAPON_INVIS ) );
+ if ( pInvisWatch )
+ {
+ pInvisWatch->ActivateInvisibilityWatch();
+ }
+ }
+ }
+ }
+ break;
+
+ case TF_CLASS_DEMOMAN:
+ if ( !m_Shared.HasPasstimeBall() )
+ {
+ CTFPipebombLauncher *pPipebombLauncher = static_cast<CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
+ if ( pPipebombLauncher )
+ {
+ pPipebombLauncher->SecondaryAttack();
+ }
+ else
+ {
+ CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( this );
+ if ( pWearableShield )
+ {
+ pWearableShield->DoSpecialAction( this );
+ break;
+ }
+ }
+ }
+ bDoSkill = true;
+ break;
+ case TF_CLASS_ENGINEER:
+ if ( !m_Shared.HasPasstimeBall() )
+ {
+ bDoSkill = TryToPickupBuilding();
+ }
+ break;
+ default:
+ break;
+ }
+
+ return bDoSkill;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::EndClassSpecialSkill( void )
+{
+ if ( !IsAlive() )
+ return false;
+
+ switch( GetPlayerClass()->GetClassIndex() )
+ {
+ case TF_CLASS_DEMOMAN:
+ {
+ CTFPipebombLauncher *pPipebombLauncher = static_cast<CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
+ if ( !pPipebombLauncher )
+ {
+ CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( this );
+ if ( pWearableShield )
+ {
+ pWearableShield->EndSpecialAction( this );
+ break;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanPickupBuilding( CBaseObject *pPickupObject )
+{
+ if ( !pPickupObject )
+ return false;
+
+ if ( pPickupObject->IsBuilding() )
+ return false;
+
+ if ( pPickupObject->IsUpgrading() )
+ return false;
+
+ if ( pPickupObject->HasSapper() )
+ return false;
+
+ if ( pPickupObject->IsPlasmaDisabled() )
+ return false;
+
+ // If we were recently carried & placed we may still be upgrading up to our old level.
+ if ( pPickupObject->GetUpgradeLevel() != pPickupObject->GetHighestUpgradeLevel() )
+ return false;
+
+ if ( m_Shared.IsCarryingObject() )
+ return false;
+
+ if ( m_Shared.IsLoserStateStunned() || m_Shared.IsControlStunned() )
+ return false;
+
+ if ( m_Shared.IsLoser() )
+ return false;
+
+ if ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING && TFGameRules()->State_Get() != GR_STATE_STALEMATE && TFGameRules()->State_Get() != GR_STATE_BETWEEN_RNDS )
+ return false;
+
+ // don't allow to pick up building while grappling hook
+ if ( m_Shared.InCond( TF_COND_GRAPPLINGHOOK ) )
+ return false;
+
+ // There's ammo in the clip... no switching away!
+ if ( GetActiveTFWeapon() && GetActiveTFWeapon()->AutoFiresFullClip() && GetActiveTFWeapon()->Clip1() > 0 )
+ return false;
+
+ // Knockout powerup restricts user to melee only, so cannot equip other items such as building pickups
+ if ( m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
+// ClientPrint( this, HUD_PRINTCENTER, "#TF_Powerup_Pickup_Deny" );
+ {
+ ClientPrint( this, HUD_PRINTCENTER, "You can't pickup buildings while holding the KNOCKOUT powerup" );
+ return false;
+ }
+
+
+ // Check it's within range
+ int nPickUpRangeSq = TF_BUILDING_PICKUP_RANGE * TF_BUILDING_PICKUP_RANGE;
+ int iIncreasedRangeCost = 0;
+ int nSqrDist = (EyePosition() - pPickupObject->GetAbsOrigin()).LengthSqr();
+
+ // Extra range only works with primary weapon
+ CTFWeaponBase * pWeapon = GetActiveTFWeapon();
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iIncreasedRangeCost, building_teleporting_pickup );
+ if ( iIncreasedRangeCost != 0 )
+ {
+ // False on deadzone
+ if ( nSqrDist > nPickUpRangeSq && nSqrDist < TF_BUILDING_RESCUE_MIN_RANGE_SQ )
+ return false;
+ if ( nSqrDist >= TF_BUILDING_RESCUE_MIN_RANGE_SQ && GetAmmoCount( TF_AMMO_METAL ) < iIncreasedRangeCost )
+ return false;
+ return true;
+ }
+ else if ( nSqrDist > nPickUpRangeSq )
+ return false;
+
+ if ( TFGameRules()->IsInTraining() )
+ {
+ ConVarRef training_can_pickup_sentry( "training_can_pickup_sentry" );
+ ConVarRef training_can_pickup_dispenser( "training_can_pickup_dispenser" );
+ ConVarRef training_can_pickup_tele_entrance( "training_can_pickup_tele_entrance" );
+ ConVarRef training_can_pickup_tele_exit( "training_can_pickup_tele_exit" );
+ switch ( pPickupObject->GetType() )
+ {
+ case OBJ_DISPENSER:
+ return training_can_pickup_dispenser.GetBool();
+ case OBJ_TELEPORTER:
+ return pPickupObject->GetObjectMode() == MODE_TELEPORTER_ENTRANCE ? training_can_pickup_tele_entrance.GetBool() : training_can_pickup_tele_exit.GetBool();
+ case OBJ_SENTRYGUN:
+ return training_can_pickup_sentry.GetBool();
+ } // switch
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::TryToPickupBuilding()
+{
+ if ( m_Shared.IsCarryingObject() )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_TAUNTING ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_MELEE_ONLY ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_SWIMMING_CURSE ) )
+ return false;
+
+#ifdef GAME_DLL
+ if ( m_bIsTeleportingUsingEurekaEffect )
+ return false;
+
+ int iCannotPickUpBuildings = 0;
+ CALL_ATTRIB_HOOK_INT( iCannotPickUpBuildings, cannot_pick_up_buildings );
+ if ( iCannotPickUpBuildings )
+ {
+ return false;
+ }
+#endif
+
+ // Check to see if a building we own is in front of us.
+ Vector vecForward;
+ AngleVectors( EyeAngles(), &vecForward, NULL, NULL );
+
+ int iPickUpRange = TF_BUILDING_PICKUP_RANGE;
+ int iIncreasedRangeCost = 0;
+ CTFWeaponBase * pWeapon = GetActiveTFWeapon();
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iIncreasedRangeCost, building_teleporting_pickup );
+ if ( iIncreasedRangeCost != 0 )
+ {
+ iPickUpRange = TF_BUILDING_RESCUE_MAX_RANGE;
+ }
+
+ // Create a ray a see if any of my objects touch it
+ Ray_t ray;
+ ray.Init( EyePosition(), EyePosition() + vecForward * iPickUpRange );
+
+ CBulletPenetrateEnum ePickupPenetrate( &ray, this, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS, false );
+ enginetrace->EnumerateEntities( ray, false, &ePickupPenetrate );
+
+ CBaseObject *pPickupObject = NULL;
+ float flCurrDistanceSq = iPickUpRange * iPickUpRange;
+
+ for ( int i=0; i<GetObjectCount(); i++ )
+ {
+ CBaseObject *pObj = GetObject(i);
+ if ( !pObj )
+ continue;
+
+ float flDistToObjSq = ( pObj->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ if ( flDistToObjSq > flCurrDistanceSq )
+ continue;
+
+ FOR_EACH_VEC( ePickupPenetrate.m_Targets, iTarget )
+ {
+ if ( ePickupPenetrate.m_Targets[iTarget].pTarget == pObj )
+ {
+ CTargetOnlyFilter penetrateFilter( this, pObj );
+ trace_t pTraceToUse;
+ UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * iPickUpRange, ( MASK_SOLID | CONTENTS_HITBOX ), &penetrateFilter, &pTraceToUse );
+ if ( pTraceToUse.m_pEnt == pObj )
+ {
+ pPickupObject = pObj;
+ flCurrDistanceSq = flDistToObjSq;
+ break;
+ }
+ }
+ if ( ePickupPenetrate.m_Targets[iTarget].pTarget->IsWorld() )
+ {
+ break;
+ }
+ }
+ }
+
+ if ( !CanPickupBuilding(pPickupObject) )
+ {
+ if ( pPickupObject )
+ {
+ CSingleUserRecipientFilter filter( this );
+ EmitSound( filter, entindex(), "Player.UseDeny", NULL, 0.0f );
+ }
+
+ return false;
+ }
+
+#ifdef CLIENT_DLL
+
+ return (bool) pPickupObject;
+
+#elif GAME_DLL
+
+ if ( pPickupObject )
+ {
+ // remove rage for long range
+ if ( iIncreasedRangeCost )
+ {
+ int nSqrDist = (EyePosition() - pPickupObject->GetAbsOrigin()).LengthSqr();
+ if ( nSqrDist > TF_BUILDING_RESCUE_MIN_RANGE_SQ )
+ {
+ RemoveAmmo( iIncreasedRangeCost, TF_AMMO_METAL );
+
+ // Particles
+ // Spawn a railgun
+ Vector origin = pPickupObject->GetAbsOrigin();
+ CPVSFilter filter( origin );
+
+ const char *pRailParticleName = GetTeamNumber() == TF_TEAM_BLUE ? "dxhr_sniper_rail_blue" : "dxhr_sniper_rail_red";
+ const char *pTeleParticleName = GetTeamNumber() == TF_TEAM_BLUE ? "teleported_blue" : "teleported_red";
+
+ TE_TFParticleEffect( filter, 0.0, pTeleParticleName, origin, vec3_angle );
+
+ te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, pPickupObject->GetAbsOrigin() + Vector(0,0,32) };
+ TE_TFParticleEffectComplex( filter, 0.0f, pRailParticleName, GetAbsOrigin() + Vector(0,0,32), QAngle( 0, 0, 0 ), NULL, &controlPoint );
+
+ // Play Sounds
+ pPickupObject->EmitSound( "Building_Teleporter.Send" );
+ EmitSound( "Building_Teleporter.Receive" );
+ }
+ }
+
+ pPickupObject->MakeCarriedObject( this );
+
+ CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>(Weapon_OwnsThisID( TF_WEAPON_BUILDER ));
+ if ( pBuilder )
+ {
+ if ( GetActiveTFWeapon() == pBuilder )
+ SetActiveWeapon( NULL );
+
+ Weapon_Switch( pBuilder );
+ pBuilder->m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f;
+ }
+
+ SpeakConceptIfAllowed( MP_CONCEPT_PICKUP_BUILDING, pPickupObject->GetResponseRulesModifier() );
+
+ m_flCommentOnCarrying = gpGlobals->curtime + random->RandomFloat( 6.f, 12.f );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanGoInvisible( bool bAllowWhileCarryingFlag )
+{
+ // The "flag" in Player Destruction doesn't block cloak
+ ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION };
+ if ( !bAllowWhileCarryingFlag && ( HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) || m_Shared.HasPasstimeBall() ) )
+ {
+ HintMessage( HINT_CANNOT_CLOAK_WITH_FLAG );
+ return false;
+ }
+
+ CTFGameRules *pRules = TFGameRules();
+
+ Assert( pRules );
+
+ if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanStartPhase( void )
+{
+ if ( HasTheFlag() || m_Shared.HasPasstimeBall() )
+ {
+ HintMessage( HINT_CANNOT_PHASE_WITH_FLAG );
+ return false;
+ }
+
+ CTFGameRules *pRules = TFGameRules();
+
+ Assert( pRules );
+
+ if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//ConVar testclassviewheight( "testclassviewheight", "0", FCVAR_DEVELOPMENTONLY );
+//Vector vecTestViewHeight(0,0,0);
+
+//-----------------------------------------------------------------------------
+// Purpose: Return class-specific standing eye height
+//-----------------------------------------------------------------------------
+Vector CTFPlayer::GetClassEyeHeight( void )
+{
+ CTFPlayerClass *pClass = GetPlayerClass();
+
+ if ( !pClass )
+ return VEC_VIEW_SCALED( this );
+
+ //if ( testclassviewheight.GetFloat() > 0 )
+ //{
+ // vecTestViewHeight.z = test.GetFloat();
+ // return vecTestViewHeight;
+ //}
+
+ int iClassIndex = pClass->GetClassIndex();
+
+ if ( iClassIndex < TF_FIRST_NORMAL_CLASS || iClassIndex > TF_LAST_NORMAL_CLASS )
+ return VEC_VIEW_SCALED( this );
+
+ return g_TFClassViewVectors[pClass->GetClassIndex()] * GetModelScale();
+}
+
+
+CTFWeaponBase *CTFPlayer::Weapon_OwnsThisID( int iWeaponID ) const
+{
+ for (int i = 0;i < WeaponCount(); i++)
+ {
+ CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon( i );
+
+ if ( pWpn == NULL )
+ continue;
+
+ if ( pWpn->GetWeaponID() == iWeaponID )
+ {
+ return pWpn;
+ }
+ }
+
+ return NULL;
+}
+
+CTFWeaponBase *CTFPlayer::Weapon_GetWeaponByType( int iType )
+{
+ for (int i = 0;i < WeaponCount(); i++)
+ {
+ CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon( i );
+
+ if ( pWpn == NULL )
+ continue;
+
+ int iWeaponRole = pWpn->GetTFWpnData().m_iWeaponType;
+
+ if ( iWeaponRole == iType )
+ {
+ return pWpn;
+ }
+ }
+
+ return NULL;
+}
+
+bool CTFPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
+{
+ bool bCanSwitch = BaseClass::Weapon_CanSwitchTo( pWeapon );
+
+ if ( bCanSwitch )
+ {
+ if ( GetActiveTFWeapon() )
+ {
+ // There's ammo in the clip while auto firing... no switching away!
+ if ( GetActiveTFWeapon()->AutoFiresFullClip() && GetActiveTFWeapon()->Clip1() > 0 )
+ return false;
+ }
+
+ if ( m_Shared.IsCarryingObject() && (GetPlayerClass()->GetClassIndex() == TF_CLASS_ENGINEER) )
+ {
+ CTFWeaponBase *pTFWeapon = dynamic_cast<CTFWeaponBase*>( pWeapon );
+ if ( pTFWeapon && (pTFWeapon->GetWeaponID() != TF_WEAPON_BUILDER) )
+ {
+ return false;
+ }
+ }
+
+ // prevents script exploits, like switching to the minigun while eating a sandvich
+ if ( IsTaunting() && tf_allow_taunt_switch.GetInt() == 0 )
+ {
+ return false;
+ }
+
+ int iDisableWeaponSwitch = 0;
+ CALL_ATTRIB_HOOK_INT( iDisableWeaponSwitch, disable_weapon_switch );
+ if ( iDisableWeaponSwitch != 0 )
+ return false;
+ }
+
+ return bCanSwitch;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gives the player an opportunity to abort a double jump.
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanAirDash( void ) const
+{
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_SPEED_BOOST ) )
+ return true;
+
+ bool bScout = GetPlayerClass()->IsClass( TF_CLASS_SCOUT );
+ if ( !bScout )
+ return false;
+
+ if ( m_Shared.InCond( TF_COND_SODAPOPPER_HYPE ) )
+ {
+ if ( m_Shared.GetAirDash() < 5 )
+ return true;
+ else
+ return false;
+ }
+
+ int iDashCount = tf_scout_air_dash_count.GetInt();
+ CALL_ATTRIB_HOOK_INT( iDashCount, air_dash_count );
+ if ( m_Shared.GetAirDash() >= iDashCount )
+ return false;
+
+ int iNoAirDash = 0;
+ CALL_ATTRIB_HOOK_INT( iNoAirDash, set_scout_doublejump_disabled );
+ if ( 1 == iNoAirDash )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Should be immune to Jarate and Mad Milk?
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanGetWet( void ) const
+{
+ int iWetImmune = 0;
+ CALL_ATTRIB_HOOK_INT( iWetImmune, wet_immunity );
+
+ return iWetImmune ? false : true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove disguise
+//-----------------------------------------------------------------------------
+void CTFPlayer::RemoveDisguise( void )
+{
+ // remove quickly
+ if ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) )
+ {
+ m_Shared.RemoveDisguise();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RemoveDisguiseWeapon( void )
+{
+#ifdef GAME_DLL
+ if ( m_hDisguiseWeapon )
+ {
+ m_hDisguiseWeapon->Drop( Vector(0,0,0) );
+ m_hDisguiseWeapon = NULL;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanDisguise( void )
+{
+ if ( !IsAlive() )
+ return false;
+
+ if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY )
+ return false;
+
+ bool bHasFlag = false;
+
+ ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION };
+ if ( HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) )
+ {
+ bHasFlag = true;
+ }
+
+ if ( bHasFlag || m_Shared.HasPasstimeBall() )
+ {
+ HintMessage( HINT_CANNOT_DISGUISE_WITH_FLAG );
+ return false;
+ }
+
+ if ( !Weapon_GetWeaponByType( TF_WPN_TYPE_PDA ) )
+ return false;
+
+ int iCannotDisguise = 0;
+ CALL_ATTRIB_HOOK_INT( iCannotDisguise, set_cannot_disguise );
+ if ( iCannotDisguise == 1 )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: 'Your Eternal Reward' handling for Disguise testing
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanDisguise_OnKill( void )
+{
+ if ( !IsAlive() )
+ return false;
+
+ if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY )
+ return false;
+
+ CTFKnife *pKnife = dynamic_cast<CTFKnife *>( Weapon_GetWeaponByType( TF_WPN_TYPE_MELEE ) );
+ if ( pKnife && pKnife->GetKnifeType() != KNIFE_DISGUISE_ONKILL )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayer::GetMaxAmmo( int iAmmoIndex, int iClassIndex /*= -1*/ )
+{
+ int iMax = ( iClassIndex == -1 ) ? m_PlayerClass.GetData()->m_aAmmoMax[iAmmoIndex] : GetPlayerClassData( iClassIndex )->m_aAmmoMax[iAmmoIndex];
+ if ( iAmmoIndex == TF_AMMO_PRIMARY )
+ {
+ CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_primary );
+ }
+ else if ( iAmmoIndex == TF_AMMO_SECONDARY )
+ {
+ CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_secondary );
+ }
+ else if ( iAmmoIndex == TF_AMMO_METAL )
+ {
+ CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_metal );
+ }
+ else if ( iAmmoIndex == TF_AMMO_GRENADES1 )
+ {
+ CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_grenades1 );
+ }
+ else if ( iAmmoIndex == TF_AMMO_GRENADES3 )
+ {
+ // All classes by default can carry a max of 1 "Grenade3" which is being used as ACTIONSLOT Throwables
+ iMax = 1;
+ }
+
+ // Haste Powerup Rune adds multiplier to Max Ammo
+ if ( m_Shared.GetCarryingRuneType() == RUNE_HASTE )
+ {
+ iMax *= 2.0f;
+ }
+
+ return iMax;
+}
+
+bool CTFPlayer::IsMiniBoss( void ) const
+{
+ return m_bIsMiniBoss;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFPlayer::MedicGetHealTarget( void )
+{
+ if ( IsPlayerClass(TF_CLASS_MEDIC) )
+ {
+ CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( GetActiveWeapon() );
+
+ if ( pWeapon )
+ return pWeapon->GetHealTarget();
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayer::MedicGetChargeLevel( CTFWeaponBase **pRetMedigun )
+{
+ if ( IsPlayerClass(TF_CLASS_MEDIC) )
+ {
+ CTFWeaponBase *pWpn = ( CTFWeaponBase *)Weapon_OwnsThisID( TF_WEAPON_MEDIGUN );
+
+ if ( pWpn == NULL )
+ return 0;
+
+ CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pWpn );
+
+ if ( pRetMedigun )
+ {
+ *pRetMedigun = pMedigun;
+ }
+
+ if ( pMedigun )
+ return pMedigun->GetChargeLevel();
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayer::GetNumActivePipebombs( void )
+{
+ if ( IsPlayerClass( TF_CLASS_DEMOMAN ) )
+ {
+ CTFPipebombLauncher *pWeapon = dynamic_cast < CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
+
+ if ( pWeapon )
+ {
+ return pWeapon->GetPipeBombCount();
+ }
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills out the vector with the sets that are currently active on this player
+//-----------------------------------------------------------------------------
+void CTFPlayer::GetActiveSets( CUtlVector<const CEconItemSetDefinition *> *pItemSets )
+{
+ pItemSets->Purge();
+
+ CSteamID steamIDForPlayer;
+ GetSteamID( &steamIDForPlayer );
+
+ TFInventoryManager()->GetActiveSets( pItemSets, steamIDForPlayer, GetPlayerClass()->GetClassIndex() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanMoveDuringTaunt()
+{
+
+ if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
+ {
+ if ( ( TFGameRules()->GetRoundRestartTime() > -1.f ) && ( (int)( TFGameRules()->GetRoundRestartTime() - gpGlobals->curtime ) <= mp_tournament_readymode_countdown.GetInt() ) )
+ return false;
+
+ if ( TFGameRules()->PlayersAreOnMatchSummaryStage() )
+ return false;
+ }
+
+ if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ return true;
+
+ if ( m_Shared.InCond( TF_COND_TAUNTING ) || m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) )
+ {
+#ifdef GAME_DLL
+ if ( tf_allow_sliding_taunt.GetBool() )
+ {
+ return true;
+ }
+#endif // GAME_DLL
+
+#ifdef STAGING_ONLY
+ if ( tf_force_allow_move_during_taunt.GetBool() )
+ {
+ return true;
+ }
+#endif // STAGING_ONLY
+
+ if ( m_bAllowMoveDuringTaunt )
+ {
+ return true;
+ }
+
+ if ( IsReadyToTauntWithPartner() || CTFPlayerSharedUtils::ConceptIsPartnerTaunt( m_Shared.m_iTauntConcept ) )
+ {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayer::ShouldStopTaunting()
+{
+ // stop taunt if we're under water
+ return GetWaterLevel() > WL_Waist;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+#if defined( CLIENT_DLL ) && defined( STAGING_ONLY )
+static ConVar tf_tauntcam_dist_override( "tf_tauntcam_dist_override", "0", FCVAR_CHEAT );
+static ConVar tf_tauntcam_distup_override( "tf_tauntcam_distup_override", "0", FCVAR_CHEAT );
+#endif // CLIENT_DLL && STAGING_ONLY
+void CTFPlayer::ParseSharedTauntDataFromEconItemView( CEconItemView *pEconItemView )
+{
+ static CSchemaAttributeDefHandle pAttrDef_TauntForceMoveForward( "taunt force move forward" );
+ attrib_value_t attrTauntForceMoveForward = 0;
+ pEconItemView->FindAttribute( pAttrDef_TauntForceMoveForward, &attrTauntForceMoveForward );
+ m_bTauntForceMoveForward = attrTauntForceMoveForward != 0;
+
+ static CSchemaAttributeDefHandle pAttrDef_TauntMoveSpeed( "taunt move speed" );
+ attrib_value_t attrTauntMoveSpeed = 0;
+ pEconItemView->FindAttribute( pAttrDef_TauntMoveSpeed, &attrTauntMoveSpeed );
+ m_flTauntForceMoveForwardSpeed = (float&)attrTauntMoveSpeed;
+
+ static CSchemaAttributeDefHandle pAttrDef_TauntMoveAccelerationTime( "taunt move acceleration time" );
+ attrib_value_t attrTauntMoveAccelerationTime = 0;
+ pEconItemView->FindAttribute( pAttrDef_TauntMoveAccelerationTime, &attrTauntMoveAccelerationTime );
+ m_flTauntMoveAccelerationTime = (float&)attrTauntMoveAccelerationTime;
+
+ static CSchemaAttributeDefHandle pAttrDef_TauntTurnSpeed( "taunt turn speed" );
+ attrib_value_t attrTauntTurnSpeed = 0;
+ pEconItemView->FindAttribute( pAttrDef_TauntTurnSpeed, &attrTauntTurnSpeed );
+ m_flTauntTurnSpeed = (float&)attrTauntTurnSpeed;
+
+ static CSchemaAttributeDefHandle pAttrDef_TauntTurnAccelerationTime( "taunt turn acceleration time" );
+ attrib_value_t attrTauntTurnAccelerationTime = 0;
+ pEconItemView->FindAttribute( pAttrDef_TauntTurnAccelerationTime, &attrTauntTurnAccelerationTime );
+ m_flTauntTurnAccelerationTime = (float&)attrTauntTurnAccelerationTime;
+
+#ifdef CLIENT_DLL
+ CTFTauntInfo *pTauntInfo = pEconItemView->GetStaticData()->GetTauntData();
+ if ( pTauntInfo )
+ {
+ if ( pTauntInfo->GetCameraDist() != 0 )
+ m_flTauntCamTargetDist = pTauntInfo->GetCameraDist();
+
+ if ( pTauntInfo->GetCameraDistUp() != 0 )
+ m_flTauntCamTargetDistUp = pTauntInfo->GetCameraDistUp();
+
+#ifdef STAGING_ONLY
+ if ( tf_tauntcam_dist_override.GetFloat() != 0 )
+ m_flTauntCamTargetDist = tf_tauntcam_dist_override.GetFloat();
+
+ if ( tf_tauntcam_distup_override.GetFloat() != 0 )
+ m_flTauntCamTargetDistUp = tf_tauntcam_distup_override.GetFloat();
+#endif // STAGING_ONLY
+ }
+#endif // CLIENT_DLL
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::SetTauntYaw( float flTauntYaw )
+{
+ m_flPrevTauntYaw = m_flTauntYaw;
+ m_flTauntYaw = flTauntYaw;
+
+ QAngle angle = GetLocalAngles();
+ angle.y = flTauntYaw;
+ SetLocalAngles( angle );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayer::StartBuildingObjectOfType( int iType, int iMode )
+{
+ // early out if we can't build this type of object
+ if ( CanBuild( iType, iMode ) != CB_CAN_BUILD )
+ return;
+
+ // Does this type require a specific builder?
+ CTFWeaponBuilder *pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, iType );
+ if ( pBuilder )
+ {
+#ifdef GAME_DLL
+ pBuilder->SetSubType( iType );
+ pBuilder->SetObjectMode( iMode );
+
+
+ if ( GetActiveTFWeapon() == pBuilder )
+ {
+ SetActiveWeapon( NULL );
+ }
+#endif
+
+ // try to switch to this weapon
+ Weapon_Switch( pBuilder );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdatePhaseEffects( void )
+{
+
+ bool bRunning;
+ float flSpeed = m_pOuter->MaxSpeed();
+ flSpeed *= flSpeed;
+
+ CTFPlayer *pPlayer = ToTFPlayer( m_pOuter );
+ if ( InCond( TF_COND_SHIELD_CHARGE ) || (pPlayer->GetAbsVelocity().LengthSqr() >= (flSpeed* 0.1f)) )
+ {
+ bRunning = true;
+ }
+ else
+ {
+ bRunning = false;
+ }
+
+#ifdef CLIENT_DLL
+ if ( m_pOuter )
+ {
+ if ( !bRunning && !m_pOuter->m_pPhaseStandingEffect && m_flEnergyDrinkMeter < 100.0f )
+ {
+ m_pOuter->m_pPhaseStandingEffect = m_pOuter->ParticleProp()->Create( "warp_version", PATTACH_ABSORIGIN_FOLLOW );
+ }
+ else if ( bRunning && m_pOuter->m_pPhaseStandingEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect );
+ m_pOuter->m_pPhaseStandingEffect = NULL;
+ }
+ }
+#else
+
+// #ifdef STAGING_ONLY
+// if ( !InCond( TF_COND_PHASE ) && !InCond( TF_COND_SHIELD_CHARGE ) && !InCond( TF_COND_SELF_CONC ) )
+// #else
+ if ( !InCond( TF_COND_PHASE ) && !InCond( TF_COND_SHIELD_CHARGE ) )
+//#endif // STAGING_ONLY
+ return;
+
+ if ( bRunning )
+ {
+ if ( !m_bPhaseFXOn )
+ {
+ AddPhaseEffects();
+ }
+ else
+ {
+ if ( m_flEnergyDrinkMeter <= 10.0f )
+ {
+ float fAlpha = ( m_flEnergyDrinkMeter / 10 ) * 255;
+ for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i )
+ {
+ CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pPhaseTrail[i].Get() );
+
+ if ( pTempTrail )
+ {
+ pTempTrail->SetBrightness( int(fAlpha) );
+ }
+ }
+ }
+// #ifdef STAGING_ONLY
+// else if ( InCond( TF_COND_SHIELD_CHARGE ) || InCond( TF_COND_SELF_CONC ) )
+// #else
+ else if ( InCond( TF_COND_SHIELD_CHARGE ) )
+//#endif // STAGING_ONLY
+ {
+ for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i )
+ {
+ CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pPhaseTrail[i].Get() );
+
+ if ( pTempTrail )
+ {
+ pTempTrail->SetBrightness( 0 );
+ }
+ }
+ }
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::AddPhaseEffects( void )
+{
+#ifdef CLIENT_DLL
+
+#else
+
+ const char *pTrailTeamName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "effects/beam001_red.vmt" : "effects/beam001_blu.vmt";
+
+ CSpriteTrail *pTempTrail = NULL;
+
+ pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true );
+ pTempTrail->FollowEntity( m_pOuter );
+ pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
+ pTempTrail->SetStartWidth( 12 );
+ pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
+ pTempTrail->SetLifeTime( 1 );
+ pTempTrail->TurnOn();
+ pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "back_upper" ) );
+
+ m_pPhaseTrail[0] = pTempTrail;
+
+ pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true );
+ pTempTrail->FollowEntity( m_pOuter );
+ pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
+ pTempTrail->SetStartWidth( 16 );
+ pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
+ pTempTrail->SetLifeTime( 1 );
+ pTempTrail->TurnOn();
+ pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "back_lower" ) );
+
+ m_pPhaseTrail[1] = pTempTrail;
+
+ pTempTrail = CSpriteTrail::SpriteTrailCreate( "effects/beam001_white.vmt", m_pOuter->GetAbsOrigin(), true );
+ pTempTrail->FollowEntity( m_pOuter );
+ pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
+ pTempTrail->SetStartWidth( 8 );
+ pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
+ pTempTrail->SetLifeTime( 0.5f );
+ pTempTrail->TurnOn();
+ pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "foot_R" ) );
+
+ m_pPhaseTrail[2] = pTempTrail;
+
+ pTempTrail = CSpriteTrail::SpriteTrailCreate( "effects/beam001_white.vmt", m_pOuter->GetAbsOrigin(), true );
+ pTempTrail->FollowEntity( m_pOuter );
+ pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
+ pTempTrail->SetStartWidth( 8 );
+ pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
+ pTempTrail->SetLifeTime( 0.5f );
+ pTempTrail->TurnOn();
+ pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "foot_L" ) );
+
+ m_pPhaseTrail[3] = pTempTrail;
+
+ pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true );
+ pTempTrail->FollowEntity( m_pOuter );
+ pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
+ pTempTrail->SetStartWidth( 8 );
+ pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
+ pTempTrail->SetLifeTime( 0.5f );
+ pTempTrail->TurnOn();
+ pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "hand_L" ) );
+
+ m_pPhaseTrail[4] = pTempTrail;
+
+
+ m_bPhaseFXOn = true;
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RemovePhaseEffects( void )
+{
+#ifdef CLIENT_DLL
+ if ( m_pOuter->m_pPhaseStandingEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect );
+ m_pOuter->m_pPhaseStandingEffect = NULL;
+ }
+
+#else
+ for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i )
+ {
+ UTIL_Remove(m_pPhaseTrail[i]);
+ }
+ m_bPhaseFXOn = false;
+
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerShared::GetSequenceForDeath( CBaseAnimating* pRagdoll, bool bBurning, int nCustomDeath )
+{
+ if ( !pRagdoll )
+ return -1;
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( m_pOuter && ( m_pOuter->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
+ return -1;
+ }
+
+ int iDeathSeq = -1;
+// if ( bBurning )
+// {
+// iDeathSeq = pRagdoll->LookupSequence( "primary_death_burning" );
+// }
+
+ switch ( nCustomDeath )
+ {
+ case TF_DMG_CUSTOM_HEADSHOT_DECAPITATION:
+ case TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING:
+ case TF_DMG_CUSTOM_DECAPITATION:
+ case TF_DMG_CUSTOM_HEADSHOT:
+ iDeathSeq = pRagdoll->LookupSequence( "primary_death_headshot" );
+ break;
+ case TF_DMG_CUSTOM_BACKSTAB:
+ iDeathSeq = pRagdoll->LookupSequence( "primary_death_backstab" );
+ break;
+ }
+
+ return iDeathSeq;
+}
+
+extern ConVar tf_halloween_kart_dash_speed;
+ConVar tf_halloween_kart_slow_turn_accel_speed( "tf_halloween_kart_slow_turn_accel_speed", "200", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_fast_turn_accel_speed( "tf_halloween_kart_fast_turn_accel_speed", "400", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_return_turn_accell( "tf_halloween_kart_return_turn_accell", "200", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_slow_turn_speed( "tf_halloween_kart_slow_turn_speed", "100", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_fast_turn_speed( "tf_halloween_kart_fast_turn_speed", "60", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_turning_curve_peak_position( "tf_halloween_kart_turning_curve_peak_position", "0.5", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_stationary_turn_speed( "tf_halloween_kart_stationary_turn_speed", "50", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_reverse_turn_speed( "tf_halloween_kart_reverse_turn_speed", "50", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_halloween_kart_air_turn_scale( "tf_halloween_kart_air_turn_scale", "1.2f", FCVAR_CHEAT | FCVAR_REPLICATED );
+
+#ifdef CLIENT_DLL
+ConVar tf_halloween_kart_pitch( "tf_halloween_kart_pitch", "10", FCVAR_ARCHIVE );
+ConVar tf_halloween_kart_pitch_slow_follow_rate( "tf_halloween_kart_pitch_slow_follow_rate", "0.5", FCVAR_ARCHIVE );
+ConVar tf_halloween_kart_pitch_fast_follow_rate( "tf_halloween_kart_pitch_fast_follow_rate", "2", FCVAR_ARCHIVE );
+#endif // CLIENT_DLL
+
+void CTFPlayerShared::CreateVehicleMove( float flInputSampleTime, CUserCmd *pCmd )
+{
+ const float flSign = pCmd->sidemove == 0.f ? 0.f : Sign( pCmd->sidemove );
+
+ // Compute target turn speed
+ const float flVel = m_pOuter->GetAbsVelocity().Length2D();
+ const float flNormalizedSpeed = Clamp( flVel / tf_halloween_kart_dash_speed.GetFloat(), 0.0f, 1.0f );
+ float flTargetTurnSpeed;
+ if ( flNormalizedSpeed == 0.f )
+ {
+ flTargetTurnSpeed = flSign * tf_halloween_kart_stationary_turn_speed.GetFloat();
+ }
+ else if ( Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) < 0 )
+ {
+ flTargetTurnSpeed = Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) * flSign * tf_halloween_kart_reverse_turn_speed.GetFloat();
+ }
+ else
+ {
+ const float flSmoothCurveVal = SmoothCurve_Tweak( flNormalizedSpeed, tf_halloween_kart_turning_curve_peak_position.GetFloat() );
+ flTargetTurnSpeed = Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) * flSign * RemapValClamped( flSmoothCurveVal, 0.f, 1.f, tf_halloween_kart_slow_turn_speed.GetFloat(), tf_halloween_kart_fast_turn_speed.GetFloat() );
+ }
+
+ float flTurnAccel = 0.f;
+ // Compute turn accelleration
+ if ( flSign == Sign( m_flCurrentTauntTurnSpeed ) )
+ {
+ flTurnAccel = RemapValClamped( flNormalizedSpeed, 0.f, 1.f, tf_halloween_kart_slow_turn_accel_speed.GetFloat(), tf_halloween_kart_fast_turn_accel_speed.GetFloat() );
+ }
+ else
+ { // When not trying to turn, or turning the opposite way you're already turning
+ // accelerate much faster
+ flTurnAccel = tf_halloween_kart_return_turn_accell.GetFloat();
+ }
+
+ // Turn faster in the air
+ if ( !(m_pOuter->GetFlags() & FL_ONGROUND) )
+ {
+ flTurnAccel *= tf_halloween_kart_air_turn_scale.GetFloat();
+ }
+
+ // Get actual turn speed
+ m_flCurrentTauntTurnSpeed = Approach( flTargetTurnSpeed, m_flCurrentTauntTurnSpeed, flTurnAccel * flInputSampleTime );
+
+ const float flMaxPossibleTurnSpeed = Max( tf_halloween_kart_slow_turn_speed.GetFloat(), tf_halloween_kart_fast_turn_speed.GetFloat() );
+ m_flCurrentTauntTurnSpeed = clamp( m_flCurrentTauntTurnSpeed, -flMaxPossibleTurnSpeed, flMaxPossibleTurnSpeed );
+
+#ifdef DEBUG
+ #ifdef CLIENT_DLL
+ engine->Con_NPrintf( 4, "Turn: %3.2f", m_flCurrentTauntTurnSpeed );
+ engine->Con_NPrintf( 5, "TargetTurn: %3.2f", flTargetTurnSpeed );
+ engine->Con_NPrintf( 6, "TurnAccell: %3.2f", flTurnAccel );
+ #else
+ engine->Con_NPrintf( 4+3, "Turn: %3.2f", m_flCurrentTauntTurnSpeed );
+ engine->Con_NPrintf( 5+3, "TargetTurn: %3.2f", flTargetTurnSpeed );
+ engine->Con_NPrintf( 6+3, "TurnAccell: %3.2f", flTurnAccel );
+ #endif
+#endif
+
+#ifdef CLIENT_DLL
+ // Turn!
+ m_angVehicleMoveAngles -= QAngle( 0.f, m_flCurrentTauntTurnSpeed * flInputSampleTime, 0.f );
+
+ // We want our pitch to slowly catch up to the pitch of the player's model
+ const float flTargetPitch = tf_halloween_kart_pitch.GetFloat() + m_pOuter->m_PlayerAnimState->GetRenderAngles()[PITCH];
+ const float flStepSpeed = fabs( flTargetPitch - tf_halloween_kart_pitch.GetFloat() ) < fabs( m_angVehicleMovePitchLast - tf_halloween_kart_pitch.GetFloat() ) ? tf_halloween_kart_pitch_fast_follow_rate.GetFloat() : tf_halloween_kart_pitch_slow_follow_rate.GetFloat();
+ const float flPitchDiff = fabs( flTargetPitch - m_angVehicleMovePitchLast );
+ const float flPitchStep = flPitchDiff * flStepSpeed;
+
+ m_angVehicleMovePitchLast = Approach( flTargetPitch, m_angVehicleMovePitchLast, flPitchStep * gpGlobals->frametime );
+
+ m_angVehicleMoveAngles[PITCH] = m_angVehicleMovePitchLast;
+ pCmd->weaponselect = 0;
+ pCmd->buttons &= IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK | IN_ATTACK2 | IN_FORWARD | IN_BACK | IN_JUMP;
+ VectorCopy( m_angVehicleMoveAngles, pCmd->viewangles );
+
+ m_pOuter->SetLocalAngles( m_angVehicleMoveAngles );
+
+ // Fill out our kart state for the local client
+ m_pOuter->m_iKartState = 0;
+
+ // Hitting the gas
+ if ( pCmd->buttons & IN_FORWARD )
+ {
+ m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Driving;
+ }
+ else if ( pCmd->buttons & IN_BACK ) // Hitting the brakes
+ {
+ // slowing down
+ if ( m_pOuter->GetCurrentTauntMoveSpeed() > 0 )
+ {
+ m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Braking;
+ }
+ // if we are already stopped, look for new input to start going backwards
+ else
+ {
+ // check for new input, else do nothing
+ if ( ( pCmd->buttons & IN_BACK )
+ || m_pOuter->GetCurrentTauntMoveSpeed() < 0
+ || m_pOuter->GetVehicleReverseTime() < gpGlobals->curtime
+ )
+ {
+
+ m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Reversing;
+ }
+ else
+ {
+ m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Stopped;
+ }
+ }
+ }
+#endif
+}
+
+void CTFPlayerShared::VehicleThink( void )
+{
+#ifdef CLIENT_DLL
+ m_pOuter->UpdateKartSounds();
+
+ // Ordered list of effects. Lower on the list has higher prescedence
+ static WheelEffect_t wheelEffects[] =
+ {
+ WheelEffect_t( 20.f, "kart_dust_trail_red", "kart_dust_trail_blue" )
+ };
+
+ const float flCurrentSpeed = m_pOuter->GetCurrentTauntMoveSpeed();
+ const WheelEffect_t* pDesiredEffect = NULL;
+
+ if ( InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ // Go through the effects, and figure out which effect to use
+ for( int i=0; i < ARRAYSIZE(wheelEffects); ++ i )
+ {
+ const WheelEffect_t& effect = wheelEffects[ i ];
+ if ( effect.m_flMinTriggerSpeed <= flCurrentSpeed )
+ {
+ pDesiredEffect = &effect;
+ }
+ }
+ }
+
+
+ // Start/stop effects if the desired effect is different
+ if ( pDesiredEffect != m_pWheelEffect )
+ {
+ C_BaseAnimating * pKart = m_pOuter->GetKart();
+ if ( !pKart )
+ return;
+
+ m_pWheelEffect = pDesiredEffect;
+
+ // New effect
+ if ( pDesiredEffect )
+ {
+ const char *pszEffectName = pDesiredEffect->m_pszParticleName[ m_pOuter->GetTeamNumber() ];
+ m_pOuter->CreateKartEffect( pszEffectName );
+ }
+ else // Turn off current effect
+ {
+ m_pOuter->StopKartEffect();
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+float CTFPlayer::GetKartSpeedBoost( void )
+{
+ if ( m_flKartNextAvailableBoost < gpGlobals->curtime )
+ return 1.0f;
+
+ if ( m_flKartNextAvailableBoost > gpGlobals->curtime + tf_halloween_kart_boost_recharge.GetFloat() )
+ return 0.0f;
+
+ // Calculate time
+ return RemapValClamped( gpGlobals->curtime, m_flKartNextAvailableBoost - tf_halloween_kart_boost_recharge.GetFloat(), m_flKartNextAvailableBoost, 0.0f, 1.0f );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerShared::IsLoser( void )
+{
+ if ( tf_always_loser.GetBool() )
+ return true;
+
+ if ( !TFGameRules() )
+ return false;
+
+ // No loser mode in competitive
+ if ( TFGameRules()->IsMatchTypeCompetitive() )
+ return false;
+
+ if ( TFGameRules()->State_Get() != GR_STATE_TEAM_WIN )
+ {
+ if ( IsLoserStateStunned() )
+ return true;
+ else
+ return false;
+ }
+
+ bool bLoser = TFGameRules()->GetWinningTeam() != m_pOuter->GetTeamNumber();
+
+ int iClass = m_pOuter->GetPlayerClass()->GetClassIndex();
+
+ // don't reveal disguised spies
+ if ( bLoser && iClass == TF_CLASS_SPY )
+ {
+ if ( InCond( TF_COND_DISGUISED ) && GetDisguiseTeam() == TFGameRules()->GetWinningTeam() )
+ {
+ bLoser = false;
+ }
+ }
+
+ return bLoser;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::RecalculatePlayerBodygroups( void )
+{
+ // We have to clear the m_nBody bitfield.
+ // Leaving bits on from previous player classes can have weird effects
+ // like if we switch to a class that uses those bits for other things.
+ m_pOuter->m_nBody = 0;
+
+ // Update our weapon bodygroups that change state purely based on whether they're
+ // equipped or not.
+ CTFWeaponBase::UpdateWeaponBodyGroups( m_pOuter, false );
+
+ // Update our wearable bodygroups.
+ CEconWearable::UpdateWearableBodyGroups( m_pOuter );
+
+ // Update our weapon bodygroups for weapons that only change state when active.
+ CTFWeaponBase::UpdateWeaponBodyGroups( m_pOuter, true );
+}
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::GetSpeedWatchersList( CUtlVector<CTFPlayer *> *out_pVecSpeedWatchers ) const
+{
+ Assert( out_pVecSpeedWatchers != NULL );
+
+ // Are any medics healing us with the Quick-Fix?
+ FOR_EACH_VEC( m_aHealers, i )
+ {
+ CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer );
+ if ( !pTFHealer )
+ continue;
+
+ if ( !pTFHealer->GetActiveTFWeapon() || pTFHealer->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_MEDIGUN )
+ continue;
+
+ // QuickFix medics heal themselves when deploying an Uber
+ if ( m_aHealers[i].pHealer == m_pOuter )
+ continue;
+
+ out_pVecSpeedWatchers->AddToTail( pTFHealer );
+ }
+}
+#endif // GAME_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetupRageBuffTimer( int iBuffType, int iPulseCount, ERageBuffSlot eBuffSlot )
+{
+ m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive = iBuffType;
+ m_RageBuffSlots[eBuffSlot].m_iBuffPulseCount = iPulseCount;
+ m_RageBuffSlots[eBuffSlot].m_flNextBuffPulseTime = gpGlobals->curtime + 1.0f;
+
+ PulseRageBuff( eBuffSlot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ActivateRageBuff( CBaseEntity *pBuffItem, int iBuffType )
+{
+ // Sniper Focus can be activated at all times
+ if ( GetRageMeter() < 100.f && iBuffType != 6 )
+ return;
+
+ Assert( iBuffType > 0 && iBuffType < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff
+ if ( iBuffType < 0 || iBuffType >= ARRAYSIZE( g_RageBuffTypes ) )
+ {
+ DevMsg( "Invalid rage buff type %i for entindex %i\n", iBuffType, m_pOuter->entindex() );
+ ResetRageSystem();
+ return;
+ }
+
+ int nBuffPulses = g_RageBuffTypes[iBuffType].m_nMaxPulses;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffPulses, mod_buff_duration );
+
+#ifdef GAME_DLL
+ switch ( iBuffType )
+ {
+ case 1:
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
+ break;
+ case 2:
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_INCOMING );
+ break;
+ case 3:
+ // FIXME: new sound file for samurai buff?
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
+ case 5:
+ // Pyro Rage
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
+ break;
+ case 6 :
+ // Sniper Focus
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
+ nBuffPulses *= (m_flRageMeter / 100);
+ break;
+ }
+#endif
+
+ m_bRageDraining = true;
+ SetupRageBuffTimer( iBuffType, nBuffPulses, kBuffSlot_Rage );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateRageBuffsAndRage( void )
+{
+ // We allow this for all classes to allow item creators and plugin authors to give rage to any class.
+
+ // If we're dead, reset both our rage and our active buffs.
+ if ( !m_pOuter->IsAlive() )
+ {
+ ResetRageSystem();
+ return;
+ }
+
+ // Find out whether we've run out of rage.
+ if ( m_bRageDraining )
+ {
+ int nBuffType = 0;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffType, set_buff_type );
+
+ Assert( nBuffType >= 0 && nBuffType < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff
+ if ( nBuffType < 0 || nBuffType >= ARRAYSIZE( g_RageBuffTypes ) )
+ {
+ DevMsg( "Invalid rage buff type %i for entindex %i\n", nBuffType, m_pOuter->entindex() );
+ ResetRageSystem();
+ return;
+ }
+
+ int nBuffPulses = g_RageBuffTypes[nBuffType].m_nMaxPulses;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffPulses, mod_buff_duration );
+ if ( nBuffPulses > 0 )
+ {
+ m_flRageMeter -= gpGlobals->frametime * ( 100 / nBuffPulses );
+ if ( m_flRageMeter <= 0.0f )
+ {
+ m_flRageMeter = 0.0f;
+ m_bRageDraining = false;
+
+ if ( g_SoldierBuffAttributeIDToConditionMap[ nBuffType ] == TF_COND_CRITBOOSTED_RAGE_BUFF )
+ {
+ // Pyro rage needs a cooldown so that the final crit flames
+ // don't significantly fill up his next rage meter
+ m_flNextRageEarnTime = gpGlobals->curtime + tf_flamethrower_flametime.GetFloat() + 0.1f;
+ }
+ }
+ }
+ else
+ {
+ ResetRageSystem();
+ }
+ }
+
+#ifdef STAGING_ONLY
+ if ( m_flRageMeter >= 100.f )
+ {
+ if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
+ {
+ if ( CanBuildSpyTraps() )
+ {
+ // Whenever a spy fills their rage meter, give them a speed boost and grenade (used by traps)
+ if ( m_pOuter->GetAmmoCount( TF_AMMO_GRENADES1 ) <= m_pOuter->GetMaxAmmo( TF_AMMO_GRENADES1 ) )
+ {
+#ifdef GAME_DLL
+ m_pOuter->GiveAmmo( 1, TF_AMMO_GRENADES1 );
+ AddCond( TF_COND_SPEED_BOOST, 5.f );
+#endif // GAME_DLL
+ m_flRageMeter = 0.f;
+ }
+ }
+ }
+ }
+#endif // STAGING_ONLY
+
+ // Handle pulsing all of our active rage buffs.
+ for ( int i = 0; i < ARRAYSIZE( m_RageBuffSlots ); i++ )
+ {
+ RageBuff& rageBuff = m_RageBuffSlots[i];
+
+ if ( gpGlobals->curtime > rageBuff.m_flNextBuffPulseTime && rageBuff.m_iBuffPulseCount > 0 )
+ {
+ rageBuff.m_flNextBuffPulseTime += 1.0f;
+ --rageBuff.m_iBuffPulseCount;
+ PulseRageBuff( (ERageBuffSlot)i );
+ }
+ }
+}
+
+static const int k_RageBuffType_Sniper = 6;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetRageMeter( float val )
+{
+ // Allow Sniper to gain rage on kills even when buffed
+ if ( !InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) && !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ if ( IsRageDraining() )
+ return;
+
+ // Can't earn rage until the time is past this delay
+ if ( val > m_flRageMeter && gpGlobals->curtime < m_flNextRageEarnTime )
+ return;
+ }
+
+ m_flRageMeter = MIN( val, 100.0f );
+
+ if ( InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) )
+ {
+ Assert( k_RageBuffType_Sniper > 0 && k_RageBuffType_Sniper < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff
+ if ( k_RageBuffType_Sniper < 0 || k_RageBuffType_Sniper >= ARRAYSIZE( g_RageBuffTypes ) )
+ return;
+
+ int nBuffPulses = g_RageBuffTypes[k_RageBuffType_Sniper].m_nMaxPulses;
+ m_bRageDraining = true;
+
+ nBuffPulses *= (m_flRageMeter / 100);
+
+ m_RageBuffSlots[kBuffSlot_Rage].m_iBuffPulseCount = nBuffPulses;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ModifyRage( float fDelta )
+{
+ SetRageMeter( GetRageMeter() + fDelta );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ResetRageMeter( void )
+{
+ m_flRageMeter = 0.0f;
+ m_flNextRageEarnTime = 0.0f;
+
+ ResetRageBuffs();
+ UpdateRageBuffsAndRage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Apply the buff effect to everyone within a radius around the player.
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::PulseRageBuff( ERageBuffSlot eBuffSlot )
+{
+ Assert( m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive != 0 );
+
+#ifdef CLIENT_DLL
+ // if this is not the local player, we don't want to do anything
+ if ( !m_pOuter->IsLocalPlayer() )
+ return;
+
+ int nBuffedFriends = 0;
+#else
+ int nBuffedPlayers = 0;
+#endif
+
+ int iSoldierBuffType = m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive;
+ ETFCond eBuffCond = TF_COND_LAST;
+ if ( iSoldierBuffType > 0 || iSoldierBuffType <= kSoldierBuffCount )
+ {
+ eBuffCond = g_SoldierBuffAttributeIDToConditionMap[iSoldierBuffType];
+ }
+
+ float fMaxRadius = 450.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, fMaxRadius, mod_soldier_buff_range );
+ const float fMaxRadiusSq = fMaxRadius * fMaxRadius;
+
+ for( int iPlayerIndex=1; iPlayerIndex<=MAX_PLAYERS; ++iPlayerIndex )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( !pTFPlayer || !pTFPlayer->IsAlive() )
+ continue;
+
+ if ( pTFPlayer->GetTeamNumber() != m_pOuter->GetTeamNumber() )
+ continue;
+
+ if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && (pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber()) )
+ continue; // For now don't give the buff to spies on our team who are disguised as enemies.
+
+ if ( pTFPlayer->m_Shared.IsStealthed() )
+ continue; // Don't give the buff to cloaked spies
+
+ Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
+ if ( vDist.LengthSqr() > fMaxRadiusSq )
+ continue;
+
+#ifdef CLIENT_DLL
+ if ( pTFPlayer != m_pOuter )
+ {
+ if ( eBuffCond == TF_COND_CRITBOOSTED_RAGE_BUFF || eBuffCond == TF_COND_SNIPERCHARGE_RAGE_BUFF )
+ {
+ // Pyro and sniper only buffs themselves
+ continue;
+ }
+
+ // this is not the localplayer, are they a friend?
+ if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() )
+ return;
+
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( pTFPlayer->entindex(), &pi ) )
+ return;
+
+ if ( !pi.friendsID )
+ return;
+
+ // check and see if they're on the local player's friends list
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+ if ( steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) )
+ {
+ nBuffedFriends++;
+ }
+ }
+#else
+ if ( pTFPlayer != m_pOuter )
+ {
+ if ( eBuffCond == TF_COND_CRITBOOSTED_RAGE_BUFF || eBuffCond == TF_COND_SNIPERCHARGE_RAGE_BUFF )
+ {
+ // Pyro and sniper only buffs themselves
+ continue;
+ }
+ }
+
+ if ( eBuffCond != TF_COND_LAST )
+ {
+ pTFPlayer->m_Shared.AddCond( eBuffCond, 1.2f, m_pOuter );
+
+ nBuffedPlayers++;
+
+ IGameEvent* event = gameeventmanager->CreateEvent( "player_buff" );
+ if ( event )
+ {
+ event->SetInt( "userid", pTFPlayer->GetUserID() );
+ event->SetInt( "buff_owner", m_pOuter->GetUserID() );
+ event->SetInt( "buff_type", iSoldierBuffType );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+#endif
+ }
+
+#ifdef CLIENT_DLL
+ if ( nBuffedFriends >= 5 )
+ {
+ g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_SOLDIER_BUFF_FRIENDS );
+ }
+#else
+ // ACHIEVEMENT_TF_MVM_SOLDIER_BUFF_TEAM
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( ( m_pOuter->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) && m_pOuter->IsPlayerClass( TF_CLASS_SOLDIER ) )
+ {
+ if ( nBuffedPlayers >= 5 )
+ {
+ m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_SOLDIER_BUFF_TEAM );
+ }
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::ResetRageSystem( void )
+{
+ m_flRageMeter = 0.f;
+ m_bRageDraining = false;
+ m_flNextRageEarnTime = 0.f;
+
+ ResetRageBuffs();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateEnergyDrinkMeter( void )
+{
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
+ return;
+
+ bool bIsLocalPlayer = false;
+#ifdef CLIENT_DLL
+ bIsLocalPlayer = m_pOuter->IsLocalPlayer();
+#else
+ bIsLocalPlayer = true;
+#endif
+
+ if ( bIsLocalPlayer )
+ {
+ if ( IsHypeBuffed() )
+ {
+ m_flHypeMeter -= gpGlobals->frametime * (m_fEnergyDrinkConsumeRate*0.75f);
+ if ( m_flHypeMeter <= 0.0f )
+ {
+ RemoveCond( TF_COND_SODAPOPPER_HYPE );
+ }
+ }
+
+ if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_ENERGY_BUFF ) )
+ {
+ // Drain the meter
+ m_flEnergyDrinkMeter -= gpGlobals->frametime * m_fEnergyDrinkConsumeRate;
+
+ // If we've drained the meter, remove the condition
+ if ( m_flEnergyDrinkMeter <= 0.f )
+ {
+ if ( InCond( TF_COND_ENERGY_BUFF ) )
+ {
+ AddCond( TF_COND_MARKEDFORDEATH_SILENT, 2.f );
+ }
+
+ RemoveCond( TF_COND_PHASE );
+ RemoveCond( TF_COND_ENERGY_BUFF );
+
+#ifdef GAME_DLL
+ m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_TIRED );
+#endif
+ }
+ // Update the effect on phasing only
+ else if ( InCond( TF_COND_PHASE ) )
+ {
+ UpdatePhaseEffects();
+ }
+ }
+ else if ( m_flEnergyDrinkMeter < 100.0f )
+ {
+ // Regen the meter
+ m_flEnergyDrinkMeter += gpGlobals->frametime * m_fEnergyDrinkRegenRate;
+
+ CTFLunchBox_Drink *pDrink = static_cast< CTFLunchBox_Drink* >( m_pOuter->Weapon_OwnsThisID( TF_WEAPON_LUNCHBOX ) );
+
+ if ( pDrink )
+ {
+ // This is here in case something replenishes grenades
+ if ( m_flEnergyDrinkMeter < 100.0f && m_pOuter->GetAmmoCount( TF_AMMO_GRENADES2 ) == m_pOuter->GetMaxAmmo( TF_AMMO_GRENADES2 ) )
+ {
+ m_flEnergyDrinkMeter = 100.0f;
+ }
+ }
+ else if ( m_flEnergyDrinkMeter >= 100.0f )
+ {
+ m_flEnergyDrinkMeter = 100.0f;
+ }
+ }
+ }
+}
+
+void CTFPlayerShared::SetScoutHypeMeter( float val )
+{
+ if ( IsHypeBuffed() )
+ return;
+
+ m_flHypeMeter = Clamp(val, 0.0f, 100.0f);
+ //if ( m_flHypeMeter >= 100.f )
+ //{
+ // if ( m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
+ // {
+ // CTFWeaponBase* pWeapon = m_pOuter->GetActiveTFWeapon();
+ // if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SODA_POPPER )
+ // {
+ // AddCond( TF_COND_CRITBOOSTED_HYPE );
+ // }
+ // }
+ //}
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::UpdateCloakMeter( void )
+{
+ if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
+ return;
+
+ if ( InCond( TF_COND_STEALTHED ) )
+ {
+ if ( m_bMotionCloak )
+ {
+ // Motion cloak: drain based on our movement rate.
+ Vector vVel = m_pOuter->GetAbsVelocity();
+ float fSpdSqr = vVel.LengthSqr();
+ if ( fSpdSqr == 0.f )
+ {
+ if ( gpGlobals->curtime - m_flLastStealthExposeTime > 1.f )
+ {
+ m_flCloakMeter += gpGlobals->frametime * m_fCloakRegenRate;
+ if ( m_flCloakMeter >= 100.0f )
+ {
+ m_flCloakMeter = 100.0f;
+ }
+ }
+ }
+ else
+ {
+ float fFactor = RemapVal( fSpdSqr, 0, m_pOuter->MaxSpeed()*m_pOuter->MaxSpeed(), 0.f, 1.f );
+ if ( fFactor > 1.f )
+ {
+ fFactor = 1.f;
+ }
+ m_flCloakMeter -= gpGlobals->frametime * m_fCloakConsumeRate * fFactor * 1.5f;
+ if ( m_flCloakMeter < 0.f )
+ {
+ m_flCloakMeter = 0.f;
+ }
+ }
+ }
+ else
+ {
+ // Classic cloak: drain at a fixed rate.
+ m_flCloakMeter -= gpGlobals->frametime * m_fCloakConsumeRate;
+ }
+
+ if ( m_flCloakMeter <= 0.0f && !m_bMotionCloak)
+ {
+ FadeInvis( 1.0f );
+ }
+
+ // Update Debuffs
+ // Decrease duration if cloaked
+#ifdef GAME_DLL
+ // staging_spy
+ float flReduction = gpGlobals->frametime * 0.75f;
+ for ( int i = 0; g_aDebuffConditions[i] != TF_COND_LAST; i++ )
+ {
+ if ( InCond( g_aDebuffConditions[i] ) )
+ {
+ if ( m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime != PERMANENT_CONDITION )
+ {
+ m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime = MAX( m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime - flReduction, 0 );
+ }
+ // Burning and Bleeding and extra timers
+ if ( g_aDebuffConditions[i] == TF_COND_BURNING )
+ {
+ // Reduce the duration of this burn
+ m_flFlameRemoveTime -= flReduction;
+ }
+ else if ( g_aDebuffConditions[i] == TF_COND_BLEEDING )
+ {
+ // Reduce the duration of this bleeding
+ FOR_EACH_VEC( m_PlayerBleeds, i )
+ {
+ m_PlayerBleeds[i].flBleedingRemoveTime -= flReduction;
+ }
+ }
+ }
+ }
+#endif
+ }
+ else
+ {
+ m_flCloakMeter += gpGlobals->frametime * m_fCloakRegenRate;
+
+ if ( m_flCloakMeter >= 100.0f )
+ {
+ m_flCloakMeter = 100.0f;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether we should be doing a radius heal (does not stack with a medigun)
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::Heal_Radius( bool bActive )
+{
+ if ( bActive )
+ {
+ m_bPulseRadiusHeal = true;
+ }
+ else
+ {
+ m_bPulseRadiusHeal = false;
+
+#ifdef GAME_DLL
+ // Stop any Radius healing
+ if ( m_iRadiusHealTargets.Count() > 0 )
+ {
+ for ( int iIndex = 0; iIndex < m_iRadiusHealTargets.Count(); iIndex++ )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( m_iRadiusHealTargets[iIndex] ) );
+ if ( !pTFPlayer )
+ continue;
+
+ pTFPlayer->m_Shared.StopHealing( m_pOuter );
+ }
+ m_iRadiusHealTargets.RemoveAll();
+ }
+#endif // GAME_DLL
+
+#ifdef CLIENT_DLL
+ if ( m_pOuter && m_pOuter->m_pRadiusHealEffect )
+ {
+ m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRadiusHealEffect );
+ }
+ m_pOuter->m_pRadiusHealEffect = NULL;
+#endif // CLIENT_DLL
+ }
+}
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar tf_rocket_pack_cooldown( "tf_rocket_pack_cooldown", "1.f", FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar tf_rocket_pack_enabled( "tf_rocket_pack_enabled", "0", FCVAR_CHEAT | FCVAR_REPLICATED );
+void CTFPlayerShared::DoRocketPack()
+{
+ if ( !tf_rocket_pack_enabled.GetBool() )
+ return;
+
+ if ( !( m_pOuter->m_nButtons & IN_ATTACK3 ) )
+ return;
+
+ if ( gpGlobals->curtime < m_flNextRocketPackTime )
+ return;
+
+#ifdef GAME_DLL
+ // Launch
+ if ( !InCond( TF_COND_ROCKETPACK ) )
+ {
+ AddCond( TF_COND_ROCKETPACK );
+ StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT );
+ }
+#endif
+
+ Vector vecDir;
+ m_pOuter->EyeVectors( &vecDir );
+ m_pOuter->SetAbsVelocity( vec3_origin );
+ Vector vecFlightDir = -vecDir;
+ VectorNormalize( vecFlightDir );
+ float flForce = 450.f;
+
+ const float flPushScale = ( m_pOuter->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f; // Greater force while airborne
+ const float flVertPushScale = ( m_pOuter->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f; // Less vertical force while airborne
+ Vector vecForce = vecFlightDir * -flForce * flPushScale;
+ vecForce.z += 1.f * flForce * flVertPushScale;
+ m_pOuter->RemoveFlag( FL_ONGROUND );
+ m_pOuter->ApplyAbsVelocityImpulse( vecForce );
+
+ m_pOuter->EmitSound( "Equipment.RocketPack_Activate" );
+
+ m_flNextRocketPackTime = gpGlobals->curtime + tf_rocket_pack_cooldown.GetFloat();
+}
+#endif // STAGING_ONLY
+
+//-----------------------------------------------------------------------------
+// Purpose: Emits an area-of-effect heal around the medic
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::PulseMedicRadiusHeal( void )
+{
+ if ( !m_bPulseRadiusHeal )
+ {
+#ifdef GAME_DLL
+ Assert( m_iRadiusHealTargets.Count() == 0 );
+ if ( m_iRadiusHealTargets.Count() > 0 )
+ {
+ // We shouldn't have any heal targets if we aren't pulsing.
+ Heal_Radius( false );
+ }
+#endif
+ return;
+ }
+
+ // If we're set to heal, make sure it's still valid
+ if ( !m_pOuter->IsAlive() || ( !m_pOuter->IsPlayerClass( TF_CLASS_MEDIC ) && !InCond( TF_COND_RADIUSHEAL_ON_DAMAGE ) ) )
+ {
+ Heal_Radius( false );
+ return;
+ }
+
+#ifdef GAME_DLL
+ if ( gpGlobals->curtime >= m_flRadiusHealCheckTime )
+ {
+ for( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( !pTFPlayer || !pTFPlayer->IsAlive() )
+ continue;
+
+ // Don't heal ourselves, unless this is due to radius heal on damage proc
+ if ( pTFPlayer == m_pOuter && !InCond( TF_COND_RADIUSHEAL_ON_DAMAGE ) )
+ continue;
+
+ if ( !pTFPlayer->InSameTeam( m_pOuter ) )
+ {
+ if ( !pTFPlayer->m_Shared.IsStealthed() && !pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ continue;
+
+ if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && ( pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber() ) )
+ continue;
+ }
+
+ // Don't heal players with weapon_blocks_healing
+ CTFWeaponBase *pTFWeapon = pTFPlayer->GetActiveTFWeapon();
+ if ( pTFWeapon )
+ {
+ int iBlockHealing = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iBlockHealing, weapon_blocks_healing );
+ if ( iBlockHealing )
+ continue;
+ }
+
+ Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
+ if ( vDist.LengthSqr() <= 450 * 450 )
+ {
+ // Ignore players we can't see
+ trace_t trace;
+ UTIL_TraceLine( pTFPlayer->WorldSpaceCenter(), m_pOuter->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
+
+ if ( trace.fraction < 1.0f )
+ continue;
+
+ // Refresh this condition, which we use to give players a particle effect
+ pTFPlayer->m_Shared.AddCond( TF_COND_RADIUSHEAL, 1.2f );
+
+ // Make sure we're not already healing them
+ if ( m_iRadiusHealTargets.Find( iPlayerIndex ) == m_iRadiusHealTargets.InvalidIndex() )
+ {
+ m_iRadiusHealTargets.AddToTail( iPlayerIndex );
+ pTFPlayer->m_Shared.Heal( m_pOuter, 25, 1, 1 );
+ }
+ }
+ else
+ {
+ if ( m_iRadiusHealTargets.Find( iPlayerIndex ) != m_iRadiusHealTargets.InvalidIndex() )
+ {
+ m_iRadiusHealTargets.FindAndRemove( iPlayerIndex );
+ pTFPlayer->m_Shared.StopHealing( m_pOuter );
+ }
+ }
+ }
+
+ m_flRadiusHealCheckTime = gpGlobals->curtime + 1.0f;
+ }
+#endif // GAME_DLL
+
+#ifdef CLIENT_DLL
+ // Radius healer gets an effect to broadcast to others what they're doing
+ if ( !m_pOuter->m_pRadiusHealEffect )
+ {
+ const char *pszRadiusHealEffect;
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
+ {
+ pszRadiusHealEffect = "medic_healradius_red_buffed";
+ }
+ else
+ {
+ pszRadiusHealEffect = "medic_healradius_blue_buffed";
+ }
+ m_pOuter->m_pRadiusHealEffect = m_pOuter->ParticleProp()->Create( pszRadiusHealEffect, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector( 0, 0, 0 ) );
+ }
+#endif // CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Emits an area-of-effect buff around the King Rune carrier
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::PulseKingRuneBuff( void )
+{
+ // Make sure we have the King Powerup and are not invisible
+ if ( !m_pOuter->IsAlive() || IsStealthed() || GetCarryingRuneType() != RUNE_KING )
+ {
+ return;
+ }
+
+#ifdef GAME_DLL
+ if ( gpGlobals->curtime >= m_flKingRuneBuffCheckTime )
+ {
+ m_bKingRuneBuffActive = false;
+
+ // Plague blocks king team buff
+ if ( !InCond( TF_COND_PLAGUE ) )
+ {
+ for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
+ if ( !pTFPlayer || !pTFPlayer->IsAlive() )
+ continue;
+
+ // Ignore players outside of the buff radius
+ Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
+ if ( vDist.LengthSqr() >= 768 * 768 )
+ continue;
+
+ // If King is the only player, there's no effect
+ if ( pTFPlayer == m_pOuter )
+ continue;
+
+ // Spies who are invisible or disguised as the King's enemy team are ignored
+ if ( pTFPlayer->m_Shared.IsStealthed() || ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber() ) )
+ continue;
+
+ // Enemies - ignore unless they are disguised as the King's team
+ if ( !pTFPlayer->InSameTeam( m_pOuter ) && !pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ continue;
+
+ pTFPlayer->m_Shared.AddCond( TF_COND_KING_BUFFED, 1.f );
+ m_bKingRuneBuffActive = true;
+ }
+ }
+
+ m_flKingRuneBuffCheckTime = gpGlobals->curtime + 0.5f;
+ }
+#endif // GAME_DLL
+
+#ifdef CLIENT_DLL
+ // King Rune carrier gets an effect to show that he's buffing someone
+ if ( m_bKingRuneBuffActive && !InCond( TF_COND_PLAGUE ) )
+ {
+ if ( !m_pOuter->m_pKingRuneRadiusEffect )
+ {
+ const char *pszRadiusEffect;
+ if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
+ {
+ pszRadiusEffect = "powerup_king_red";
+ }
+ else
+ {
+ pszRadiusEffect = "powerup_king_blue";
+ }
+ m_pOuter->m_pKingRuneRadiusEffect = m_pOuter->ParticleProp()->Create( pszRadiusEffect, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector( 0, 0, 0 ) );
+ }
+ }
+ else
+ {
+ EndKingBuffRadiusEffect();
+ }
+#endif // CLIENT_DLL
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::IncrementRevengeCrits( void )
+{
+ SetRevengeCrits( m_iRevengeCrits + 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetRevengeCrits( int iVal )
+{
+ m_iRevengeCrits = clamp( iVal, 0, 35 );
+
+ CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon();
+ if ( ( pWeapon && pWeapon->CanHaveRevengeCrits() ) )
+ {
+ if ( m_iRevengeCrits > 0 && !InCond( TF_COND_CRITBOOSTED ) )
+ {
+ AddCond( TF_COND_CRITBOOSTED );
+ }
+ else if ( m_iRevengeCrits == 0 && InCond( TF_COND_CRITBOOSTED ) )
+ {
+ RemoveCond( TF_COND_CRITBOOSTED );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::FireGameEvent( IGameEvent *event )
+{
+#ifdef GAME_DLL
+ const char *eventName = event->GetName();
+
+ if ( !Q_strcmp( eventName, "player_disconnect" ) )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+ if ( pPlayer )
+ {
+ int iIndex = m_iRadiusHealTargets.Find( pPlayer->entindex() );
+ if ( iIndex != m_iRadiusHealTargets.InvalidIndex() )
+ {
+ m_iRadiusHealTargets.FastRemove( iIndex );
+ }
+ }
+ }
+#endif //GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerShared::SetPasstimePassTarget( CTFPlayer *pEnt )
+{
+ if ( CBaseEntity *pTarget = m_hPasstimePassTarget )
+ {
+ CTFPlayer *pPlayerTarget = ToTFPlayer( pTarget );
+ if ( pPlayerTarget )
+ pPlayerTarget->m_Shared.m_bIsTargetedForPasstimePass = false;
+ }
+
+ Assert( pEnt != m_pOuter );
+ m_hPasstimePassTarget = pEnt;
+
+ if ( CBaseEntity *pTarget = m_hPasstimePassTarget )
+ {
+ CTFPlayer *pPlayerTarget = ToTFPlayer( pTarget );
+ if ( pPlayerTarget )
+ pPlayerTarget->m_Shared.m_bIsTargetedForPasstimePass = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+CTFPlayer *CTFPlayerShared::GetPasstimePassTarget() const { return m_hPasstimePassTarget.Get(); }
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTraceFilterIgnoreTeammatesAndTeamObjects::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
+{
+ CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
+
+ if ( pEntity->GetTeamNumber() == m_iIgnoreTeam )
+ {
+ return false;
+ }
+
+ CTFPlayer *pPlayer = dynamic_cast<CTFPlayer*>( pEntity );
+ if ( pPlayer )
+ {
+ if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == m_iIgnoreTeam )
+ return false;
+
+ if ( pPlayer->m_Shared.IsStealthed() )
+ return false;
+ }
+
+ return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
+}
+
+void CTFPlayerShared::SetCarriedObject( CBaseObject* pObj )
+{
+ m_bCarryingObject = (pObj != NULL);
+ m_hCarriedObject.Set( pObj );
+#ifdef GAME_DLL
+ if ( m_pOuter )
+ m_pOuter->TeamFortress_SetSpeed();
+#endif
+}
+
+void localplayerscoring_t::UpdateStats( RoundStats_t& roundStats, CTFPlayer *pPlayer, bool bIsRoundData )
+{
+ m_iCaptures = roundStats.m_iStat[TFSTAT_CAPTURES];
+ m_iDefenses = roundStats.m_iStat[TFSTAT_DEFENSES];
+
+ m_iKills = roundStats.m_iStat[TFSTAT_KILLS];
+ m_iDeaths = roundStats.m_iStat[TFSTAT_DEATHS];
+ m_iSuicides = roundStats.m_iStat[TFSTAT_SUICIDES];
+ m_iKillAssists = roundStats.m_iStat[TFSTAT_KILLASSISTS];
+
+ m_iBuildingsBuilt = roundStats.m_iStat[TFSTAT_BUILDINGSBUILT];
+ m_iBuildingsDestroyed = roundStats.m_iStat[TFSTAT_BUILDINGSDESTROYED];
+
+ m_iHeadshots = roundStats.m_iStat[TFSTAT_HEADSHOTS];
+ m_iDominations = roundStats.m_iStat[TFSTAT_DOMINATIONS];
+ m_iRevenge = roundStats.m_iStat[TFSTAT_REVENGE];
+ m_iInvulns = roundStats.m_iStat[TFSTAT_INVULNS];
+ m_iTeleports = roundStats.m_iStat[TFSTAT_TELEPORTS];
+
+ m_iDamageDone = roundStats.m_iStat[TFSTAT_DAMAGE];
+ m_iCrits = roundStats.m_iStat[TFSTAT_CRITS];
+
+ m_iBackstabs = roundStats.m_iStat[TFSTAT_BACKSTABS];
+
+ int iHealthPointsHealed = (int) roundStats.m_iStat[TFSTAT_HEALING];
+ // send updated healing data every 10 health points, and round off what we send to nearest 10 points
+ int iHealPointsDelta = abs( iHealthPointsHealed - m_iHealPoints );
+ if ( iHealPointsDelta > 10 )
+ {
+ m_iHealPoints = ( iHealthPointsHealed / 10 ) * 10;
+ }
+ m_iBonusPoints = roundStats.m_iStat[TFSTAT_BONUS_POINTS] / TF_SCORE_BONUS_POINT_DIVISOR;
+ const int nPoints = TFGameRules()->CalcPlayerScore( &roundStats, pPlayer );
+ const int nDelta = nPoints - m_iPoints;
+ m_iPoints = nPoints;
+
+ if ( nDelta > 0 && !bIsRoundData )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_score_changed" );
+ if ( event )
+ {
+ event->SetInt( "player", pPlayer->entindex() );
+ event->SetInt( "delta", nDelta );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconItemView *CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( CTFPlayer *pTFPlayer, int iSlot, CEconEntity **pEntity )
+{
+ int iClass = pTFPlayer->GetPlayerClass()->GetClassIndex();
+
+ // See if it's a weapon first
+ for ( int i = 0; i < MAX_WEAPONS; i++ )
+ {
+ CTFWeaponBase *pWeapon = (CTFWeaponBase *)pTFPlayer->GetWeapon(i);
+ if ( !pWeapon )
+ continue;
+
+ CEconItemView *pEconItemView = pWeapon->GetAttributeContainer()->GetItem();
+ if ( !pEconItemView )
+ continue;
+
+ int iLoadoutSlot = pEconItemView->GetStaticData()->GetLoadoutSlot( iClass );
+ if ( iLoadoutSlot == iSlot )
+ {
+ if ( pEntity )
+ {
+ *pEntity = pWeapon;
+ }
+ return pEconItemView;
+ }
+ }
+
+ // Go through each of the actual items we have equipped right now...
+ for ( int i = 0; i < pTFPlayer->GetNumWearables(); ++i )
+ {
+ CTFWearable *pWearableItem = dynamic_cast<CTFWearable *>( pTFPlayer->GetWearable( i ) );
+ if ( !pWearableItem )
+ continue;
+
+ if ( !pWearableItem->GetAttributeContainer() )
+ continue;
+
+ CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem();
+ if ( !pEconItemView )
+ continue;
+
+ CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
+ if ( !pItemDef )
+ continue;
+
+ if ( pItemDef->GetLoadoutSlot(iClass) != iSlot )
+ continue;
+
+ // Yay!
+ if ( pEntity )
+ {
+ *pEntity = pWearableItem;
+ }
+ return pEconItemView;
+ }
+
+ // Nothing we currently have equipped claims to be in this slot.
+ if ( pEntity )
+ {
+ *pEntity = NULL;
+ }
+ return NULL;
+}
+
+bool CTFPlayerSharedUtils::ConceptIsPartnerTaunt( int iConcept )
+{
+ return iConcept == MP_CONCEPT_HIGHFIVE_SUCCESS_FULL || iConcept == MP_CONCEPT_HIGHFIVE_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFWeaponBuilder *CTFPlayerSharedUtils::GetBuilderForObjectType( CTFPlayer *pTFPlayer, int iObjectType )
+{
+ const int OBJ_ANY = -1;
+
+ if ( !pTFPlayer )
+ return NULL;
+
+ for ( int i = 0; i < MAX_WEAPONS; i++ )
+ {
+ CTFWeaponBuilder *pBuilder = dynamic_cast< CTFWeaponBuilder* >( pTFPlayer->GetWeapon( i ) );
+ if ( !pBuilder )
+ continue;
+
+ // Any builder will do - return first
+ if ( iObjectType == OBJ_ANY )
+ return pBuilder;
+
+ // Requires a specific builder for this type
+ if ( pBuilder->CanBuildObjectType( iObjectType ) )
+ return pBuilder;
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Can player pick up this weapon?
+//-----------------------------------------------------------------------------
+bool CTFPlayer::CanPickupDroppedWeapon( const CTFDroppedWeapon *pWeapon )
+{
+ if ( !pWeapon->GetItem()->IsValid() )
+ return false;
+
+ int iClass = GetPlayerClass()->GetClassIndex();
+ if ( iClass == TF_CLASS_SPY && ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.GetPercentInvisible() > 0 ) )
+ return false;
+
+ if ( IsTaunting() )
+ return false;
+
+ if ( !IsAlive() )
+ return false;
+
+ // There's a rare case that the player doesn't have an active weapon. This shouldn't happen.
+ // If you hit this assert, figure out and fix WHY the player doesn't have a weapon.
+ Assert( GetActiveTFWeapon() );
+ if ( !GetActiveTFWeapon() || !GetActiveTFWeapon()->CanPickupOtherWeapon() )
+ return false;
+
+ int iItemSlot = pWeapon->GetItem()->GetStaticData()->GetLoadoutSlot( iClass );
+ CBaseEntity *pOwnedWeaponToDrop = GetEntityForLoadoutSlot( iItemSlot );
+
+ return pOwnedWeaponToDrop && pWeapon->GetItem()->GetStaticData()->CanBeUsedByClass( iClass ) && IsValidPickupWeaponSlot( iItemSlot );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if player is in range to pick up this weapon
+//-----------------------------------------------------------------------------
+CTFDroppedWeapon* CTFPlayer::GetDroppedWeaponInRange()
+{
+ // Check to see if a building we own is in front of us.
+ Vector vecForward;
+ AngleVectors( EyeAngles(), &vecForward );
+
+ trace_t tr;
+ UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * TF_WEAPON_PICKUP_RANGE, MASK_SOLID | CONTENTS_DEBRIS, this, COLLISION_GROUP_NONE, &tr );
+
+ CTFDroppedWeapon *pDroppedWeapon = dynamic_cast< CTFDroppedWeapon * >( tr.m_pEnt );
+ if ( !pDroppedWeapon )
+ return NULL;
+
+ if ( !CanPickupDroppedWeapon( pDroppedWeapon ) )
+ return NULL;
+
+ // too far?
+ if ( EyePosition().DistToSqr( pDroppedWeapon->GetAbsOrigin() ) > Square( TF_WEAPON_PICKUP_RANGE ) )
+ return NULL;
+
+ return pDroppedWeapon;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if player is inspecting
+//-----------------------------------------------------------------------------
+bool CTFPlayer::IsInspecting() const
+{
+ return m_flInspectTime != 0.f && gpGlobals->curtime - m_flInspectTime > 0.2f;
+}