diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_player_shared.cpp | |
| download | archived-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.cpp | 14162 |
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 ¶ms ) +{ + 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; +} |