Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

14162 lines
413 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "tf_gamerules.h"
#include "tf_player_shared.h"
#include "takedamageinfo.h"
#include "tf_weaponbase.h"
#include "effect_dispatch_data.h"
#include "tf_item.h"
#include "entity_capture_flag.h"
#include "tf_weapon_medigun.h"
#include "tf_weapon_pipebomblauncher.h"
#include "tf_weapon_invis.h"
#include "tf_weapon_sniperrifle.h"
#include "tf_weapon_shovel.h"
#include "tf_weapon_sword.h"
#include "tf_weapon_shotgun.h"
#include "in_buttons.h"
#include "tf_weapon_lunchbox.h"
#include "tf_weapon_flaregun.h"
#include "tf_weapon_wrench.h"
#include "econ_wearable.h"
#include "econ_item_system.h"
#include "tf_weapon_knife.h"
#include "tf_weapon_syringegun.h"
#include "tf_weapon_flamethrower.h"
#include "econ_entity_creation.h"
#include "tf_mapinfo.h"
#include "tf_dropped_weapon.h"
#include "tf_weapon_passtime_gun.h"
// Client specific.
#ifdef CLIENT_DLL
#include "c_tf_player.h"
#include "c_te_effect_dispatch.h"
#include "c_tf_fx.h"
#include "soundenvelope.h"
#include "c_tf_playerclass.h"
#include "iviewrender.h"
#include "prediction.h"
#include "achievementmgr.h"
#include "baseachievement.h"
#include "achievements_tf.h"
#include "c_tf_weapon_builder.h"
#include "dt_utlvector_recv.h"
#include "recvproxy.h"
#include "c_tf_weapon_builder.h"
#include "c_func_capture_zone.h"
#include "tf_hud_target_id.h"
#include "tempent.h"
#include "cam_thirdperson.h"
#include "vgui/IInput.h"
#define CTFPlayerClass C_TFPlayerClass
#define CCaptureZone C_CaptureZone
#define CRecipientFilter C_RecipientFilter
#include "c_tf_objective_resource.h"
#include "tf_weapon_buff_item.h"
#include "c_tf_passtime_logic.h"
// Server specific.
#else
#include "tf_player.h"
#include "te_effect_dispatch.h"
#include "tf_fx.h"
#include "util.h"
#include "tf_team.h"
#include "tf_gamestats.h"
#include "tf_playerclass.h"
#include "SpriteTrail.h"
#include "tf_weapon_builder.h"
#include "nav_mesh/tf_nav_area.h"
#include "nav_pathfind.h"
#include "tf_obj_dispenser.h"
#include "dt_utlvector_send.h"
#include "tf_item_wearable.h"
#include "NextBotManager.h"
#include "tf_weapon_builder.h"
#include "func_capture_zone.h"
#include "hl2orange.spa.h"
#include "bot/tf_bot.h"
#include "tf_objective_resource.h"
#include "halloween/tf_weapon_spellbook.h"
#include "tf_weapon_buff_item.h"
#include "tf_passtime_logic.h"
#include "tf_weapon_passtime_gun.h"
#include "entity_healthkit.h"
#include "halloween/merasmus/merasmus.h"
#include "tf_weapon_grapplinghook.h"
#include "tf_wearable_levelable_item.h"
#include "tf_obj_sentrygun.h"
#endif
#include "tf_wearable_item_demoshield.h"
#include "tf_weapon_bonesaw.h"
static ConVar tf_demoman_charge_frametime_scaling( "tf_demoman_charge_frametime_scaling", "1", FCVAR_REPLICATED | FCVAR_CHEAT, "When enabled, scale yaw limiting based on client performance (frametime)." );
static const float YAW_CAP_SCALE_MIN = 0.2f;
static const float YAW_CAP_SCALE_MAX = 2.f;
ConVar tf_halloween_kart_boost_recharge( "tf_halloween_kart_boost_recharge", "5.0f", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar tf_halloween_kart_boost_duration( "tf_halloween_kart_boost_duration", "1.5f", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar tf_scout_air_dash_count( "tf_scout_air_dash_count", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_spy_invis_time( "tf_spy_invis_time", "1.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Transition time in and out of spy invisibility", true, 0.1, true, 5.0 );
ConVar tf_spy_invis_unstealth_time( "tf_spy_invis_unstealth_time", "2.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Transition time in and out of spy invisibility", true, 0.1, true, 5.0 );
ConVar tf_spy_max_cloaked_speed( "tf_spy_max_cloaked_speed", "999", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED ); // no cap
ConVar tf_whip_speed_increase( "tf_whip_speed_increase", "105", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
ConVar tf_max_health_boost( "tf_max_health_boost", "1.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max health factor that players can be boosted to by healers.", true, 1.0, false, 0 );
ConVar tf_invuln_time( "tf_invuln_time", "1.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Time it takes for invulnerability to wear off." );
ConVar tf_player_movement_stun_time( "tf_player_movement_stun_time", "0.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
extern ConVar tf_player_movement_restart_freeze;
extern ConVar mp_tournament_readymode_countdown;
extern ConVar tf_max_charge_speed;
ConVar tf_always_loser( "tf_always_loser", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Force loserstate to true." );
ConVar tf_mvm_bot_flag_carrier_movement_penalty( "tf_mvm_bot_flag_carrier_movement_penalty", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT );
//ConVar tf_scout_dodge_move_penalty_duration( "tf_scout_dodge_move_penalty_duration", "3.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
//ConVar tf_scout_dodge_move_penalty( "tf_scout_dodge_move_penalty", "0.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED );
#ifdef GAME_DLL
ConVar tf_boost_drain_time( "tf_boost_drain_time", "15.0", FCVAR_DEVELOPMENTONLY, "Time is takes for a full health boost to drain away from a player.", true, 0.1, false, 0 );
#ifdef _DEBUG
CON_COMMAND_F( tf_add_bombhead, "Add Merasmus Bomb Head Condition", 0 )
{
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, true );
FOR_EACH_VEC ( playerVector, i )
{
float flBuffDuration = 7.0f;
playerVector[i]->m_Shared.StunPlayer( flBuffDuration, 0.f, TF_STUN_LOSER_STATE );
playerVector[i]->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, flBuffDuration );
playerVector[i]->m_Shared.AddCond( TF_COND_SPEED_BOOST, flBuffDuration );
//playerVector[i]->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, 7 );
}
}
ConVar tf_debug_bullets( "tf_debug_bullets", "0", FCVAR_DEVELOPMENTONLY, "Visualize bullet traces." );
#endif // _DEBUG
ConVar tf_damage_events_track_for( "tf_damage_events_track_for", "30", FCVAR_DEVELOPMENTONLY );
extern ConVar tf_halloween_giant_health_scale;
ConVar tf_allow_sliding_taunt( "tf_allow_sliding_taunt", "0", FCVAR_NONE, "1 - Allow player to slide for a bit after taunting" );
#endif // GAME_DLL
#ifdef STAGING_ONLY
ConVar tf_force_allow_move_during_taunt( "tf_force_allow_move_during_taunt", "0", FCVAR_REPLICATED );
#endif // STAGING_ONLY
ConVar tf_useparticletracers( "tf_useparticletracers", "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Use particle tracers instead of old style ones." );
ConVar tf_spy_cloak_consume_rate( "tf_spy_cloak_consume_rate", "10.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "cloak to use per second while cloaked, from 100 max )" ); // 10 seconds of invis
ConVar tf_spy_cloak_regen_rate( "tf_spy_cloak_regen_rate", "3.3", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "cloak to regen per second, up to 100 max" ); // 30 seconds to full charge
ConVar tf_spy_cloak_no_attack_time( "tf_spy_cloak_no_attack_time", "2.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "time after uncloaking that the spy is prohibited from attacking" );
ConVar tf_tournament_hide_domination_icons( "tf_tournament_hide_domination_icons", "0", FCVAR_REPLICATED, "Tournament mode server convar that forces clients to not display the domination icons above players dominating them." );
ConVar tf_damage_disablespread( "tf_damage_disablespread", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Toggles the random damage spread applied to all player damage." );
ConVar tf_scout_energydrink_regen_rate( "tf_scout_energydrink_regen_rate", "3.3", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "energy drink regen per second, up to 100 max" );
ConVar tf_scout_energydrink_consume_rate( "tf_scout_energydrink_consume_rate", "12.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "energy drink to use per second while boosted, from 100 max" );
ConVar tf_scout_energydrink_activation( "tf_scout_energydrink_activation", "0.0", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "how long it takes for the energy buff to become active" );
ConVar tf_demoman_charge_regen_rate( "tf_demoman_charge_regen_rate", "8.3", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "" );
ConVar tf_demoman_charge_drain_time( "tf_demoman_charge_drain_time", "1.5", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "" );
// STAGING_SPY
ConVar tf_feign_death_duration( "tf_feign_death_duration", "3.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Time that feign death buffs last." );
ConVar tf_feign_death_speed_duration( "tf_feign_death_speed_duration", "3.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Time that feign death speed boost last." );
ConVar tf_allow_taunt_switch( "tf_allow_taunt_switch", "0", FCVAR_REPLICATED, "0 - players are not allowed to switch weapons while taunting, 1 - players can switch weapons at the start of a taunt (old bug behavior), 2 - players can switch weapons at any time during a taunt." );
ConVar tf_allow_all_team_partner_taunt( "tf_allow_all_team_partner_taunt", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
#ifdef STAGING_ONLY
ConVar tf_random_item_min( "tf_random_item_min", "-1", FCVAR_REPLICATED, "Min Itemdef for random cosmetics" );
ConVar tf_random_item_max( "tf_random_item_max", "-1", FCVAR_REPLICATED, "Max Itemdef for random cosmetics" );
ConVar tf_killstreak_eyeglow( "tf_killstreak_eyeglow", "0", FCVAR_REPLICATED, "Force Kill Streak effect index for Eye Glows. -1 to disable regardless of equipped items. Stats at 2002" );
ConVar tf_killstreak_color( "tf_killstreak_color", "0", FCVAR_REPLICATED, "Force Kill Streak color." );
ConVar tf_eyeglow_wip( "tf_eyeglow_wip", "0", FCVAR_REPLICATED, "Activate to Always have specific wip eyeglow (DEV)" );
#endif // Staging only
#ifdef CLIENT_DLL
ConVar tf_colorblindassist( "tf_colorblindassist", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Setting this to 1 turns on colorblind mode." );
extern ConVar cam_idealdist;
extern ConVar cam_idealdistright;
#endif // CLIENT_DLL
extern ConVar tf_flamethrower_flametime;
extern ConVar weapon_medigun_chargerelease_rate;
#if defined( _DEBUG ) || defined( STAGING_ONLY )
extern ConVar mp_developer;
#endif // _DEBUG || STAGING_ONLY
//ConVar tf_spy_stealth_blink_time( "tf_spy_stealth_blink_time", "0.3", FCVAR_DEVELOPMENTONLY, "time after being hit the spy blinks into view" );
//ConVar tf_spy_stealth_blink_scale( "tf_spy_stealth_blink_scale", "0.85", FCVAR_DEVELOPMENTONLY, "percentage visible scalar after being hit the spy blinks into view" );
#define TF_SPY_STEALTH_BLINKTIME 0.3f
#define TF_SPY_STEALTH_BLINKSCALE 0.85f
#define TF_BUILDING_PICKUP_RANGE 150
#define TF_BUILDING_RESCUE_MIN_RANGE_SQ 62500 //250 * 250
#define TF_BUILDING_RESCUE_MAX_RANGE 5500
#define TF_PLAYER_CONDITION_CONTEXT "TFPlayerConditionContext"
#define TF_SCREEN_OVERLAY_MATERIAL_BURNING "effects/imcookin"
#define TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED "effects/invuln_overlay_red"
#define TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE "effects/invuln_overlay_blue"
#define TF_SCREEN_OVERLAY_MATERIAL_MILK "effects/milk_screen"
#define TF_SCREEN_OVERLAY_MATERIAL_URINE "effects/jarate_overlay"
#define TF_SCREEN_OVERLAY_MATERIAL_BLEED "effects/bleed_overlay"
#define TF_SCREEN_OVERLAY_MATERIAL_STEALTH "effects/stealth_overlay"
#define TF_SCREEN_OVERLAY_MATERIAL_SWIMMING_CURSE "effects/jarate_overlay"
#define TF_SCREEN_OVERLAY_MATERIAL_PHASE "effects/dodge_overlay"
#define MAX_DAMAGE_EVENTS 128
const char *g_pszBDayGibs[22] =
{
"models/effects/bday_gib01.mdl",
"models/effects/bday_gib02.mdl",
"models/effects/bday_gib03.mdl",
"models/effects/bday_gib04.mdl",
"models/player/gibs/gibs_balloon.mdl",
"models/player/gibs/gibs_burger.mdl",
"models/player/gibs/gibs_boot.mdl",
"models/player/gibs/gibs_bolt.mdl",
"models/player/gibs/gibs_can.mdl",
"models/player/gibs/gibs_clock.mdl",
"models/player/gibs/gibs_fish.mdl",
"models/player/gibs/gibs_gear1.mdl",
"models/player/gibs/gibs_gear2.mdl",
"models/player/gibs/gibs_gear3.mdl",
"models/player/gibs/gibs_gear4.mdl",
"models/player/gibs/gibs_gear5.mdl",
"models/player/gibs/gibs_hubcap.mdl",
"models/player/gibs/gibs_licenseplate.mdl",
"models/player/gibs/gibs_spring1.mdl",
"models/player/gibs/gibs_spring2.mdl",
"models/player/gibs/gibs_teeth.mdl",
"models/player/gibs/gibs_tire.mdl"
};
ETFCond g_SoldierBuffAttributeIDToConditionMap[kSoldierBuffCount + 1] =
{
TF_COND_LAST, // dummy entry to deal with attribute value of "1" being the lowest value we store in the attribute itself
TF_COND_OFFENSEBUFF,
TF_COND_DEFENSEBUFF,
TF_COND_REGENONDAMAGEBUFF,
TF_COND_NOHEALINGDAMAGEBUFF,
TF_COND_CRITBOOSTED_RAGE_BUFF,
TF_COND_SNIPERCHARGE_RAGE_BUFF
};
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void RecvProxy_BuildablesListChanged( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
RecvProxy_Int32ToInt32( pData, pStruct, pOut );
C_TFPlayer* pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer || pLocalPlayer->entindex() != pData->m_ObjectID )
return;
int index = pData->m_pRecvProp->GetOffset() / sizeof(int);
int object = pData->m_Value.m_Int;
IGameEvent *event = gameeventmanager->CreateEvent( "update_status_item" );
if ( event )
{
event->SetInt( "index", index );
event->SetInt( "object", object );
gameeventmanager->FireEventClientSide( event );
}
}
#endif
//=============================================================================
//
// Tables.
//
// Client specific.
#ifdef CLIENT_DLL
BEGIN_RECV_TABLE_NOBASE( localplayerscoring_t, DT_TFPlayerScoringDataExclusive )
RecvPropInt( RECVINFO( m_iCaptures ) ),
RecvPropInt( RECVINFO( m_iDefenses ) ),
RecvPropInt( RECVINFO( m_iKills ) ),
RecvPropInt( RECVINFO( m_iDeaths ) ),
RecvPropInt( RECVINFO( m_iSuicides ) ),
RecvPropInt( RECVINFO( m_iDominations ) ),
RecvPropInt( RECVINFO( m_iRevenge ) ),
RecvPropInt( RECVINFO( m_iBuildingsBuilt ) ),
RecvPropInt( RECVINFO( m_iBuildingsDestroyed ) ),
RecvPropInt( RECVINFO( m_iHeadshots ) ),
RecvPropInt( RECVINFO( m_iBackstabs ) ),
RecvPropInt( RECVINFO( m_iHealPoints ) ),
RecvPropInt( RECVINFO( m_iInvulns ) ),
RecvPropInt( RECVINFO( m_iTeleports ) ),
RecvPropInt( RECVINFO( m_iResupplyPoints ) ),
RecvPropInt( RECVINFO( m_iKillAssists ) ),
RecvPropInt( RECVINFO( m_iPoints ) ),
RecvPropInt( RECVINFO( m_iBonusPoints ) ),
RecvPropInt( RECVINFO( m_iDamageDone ) ),
RecvPropInt( RECVINFO( m_iCrits ) ),
END_RECV_TABLE()
EXTERN_RECV_TABLE(DT_TFPlayerConditionListExclusive);
BEGIN_RECV_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerSharedLocal )
RecvPropInt( RECVINFO( m_nDesiredDisguiseTeam ) ),
RecvPropInt( RECVINFO( m_nDesiredDisguiseClass ) ),
RecvPropTime( RECVINFO( m_flStealthNoAttackExpire ) ),
RecvPropTime( RECVINFO( m_flStealthNextChangeTime ) ),
RecvPropBool( RECVINFO( m_bLastDisguisedAsOwnTeam ) ),
RecvPropFloat( RECVINFO( m_flRageMeter ) ),
RecvPropBool( RECVINFO( m_bRageDraining ) ),
RecvPropTime( RECVINFO( m_flNextRageEarnTime ) ),
RecvPropBool( RECVINFO( m_bInUpgradeZone ) ),
RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominated ), RecvPropBool( RECVINFO( m_bPlayerDominated[0] ) ) ),
RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominatingMe ), RecvPropBool( RECVINFO( m_bPlayerDominatingMe[0] ) ) ),
RecvPropDataTable( RECVINFO_DT(m_ScoreData),0, &REFERENCE_RECV_TABLE(DT_TFPlayerScoringDataExclusive) ),
RecvPropDataTable( RECVINFO_DT(m_RoundScoreData),0, &REFERENCE_RECV_TABLE(DT_TFPlayerScoringDataExclusive) ),
END_RECV_TABLE()
BEGIN_RECV_TABLE_NOBASE( condition_source_t, DT_TFPlayerConditionSource )
//RecvPropInt( RECVINFO( m_nPreventedDamageFromCondition ) ),
//RecvPropFloat( RECVINFO( m_flExpireTime ) ),
RecvPropEHandle( RECVINFO( m_pProvider ) ),
//RecvPropBool( RECVINFO( m_bPrevActive ) ),
END_RECV_TABLE()
BEGIN_RECV_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerShared )
RecvPropInt( RECVINFO( m_nPlayerCond ) ),
RecvPropInt( RECVINFO( m_bJumping) ),
RecvPropInt( RECVINFO( m_nNumHealers ) ),
RecvPropInt( RECVINFO( m_iCritMult ) ),
RecvPropInt( RECVINFO( m_iAirDash ) ),
RecvPropInt( RECVINFO( m_nAirDucked ) ),
RecvPropFloat( RECVINFO( m_flDuckTimer ) ),
RecvPropInt( RECVINFO( m_nPlayerState ) ),
RecvPropInt( RECVINFO( m_iDesiredPlayerClass ) ),
RecvPropFloat( RECVINFO( m_flMovementStunTime ) ),
RecvPropInt( RECVINFO( m_iMovementStunAmount ) ),
RecvPropInt( RECVINFO( m_iMovementStunParity ) ),
RecvPropEHandle( RECVINFO( m_hStunner ) ),
RecvPropInt( RECVINFO( m_iStunFlags ) ),
RecvPropInt( RECVINFO( m_nArenaNumChanges ) ),
RecvPropBool( RECVINFO( m_bArenaFirstBloodBoost ) ),
RecvPropInt( RECVINFO( m_iWeaponKnockbackID ) ),
RecvPropBool( RECVINFO( m_bLoadoutUnavailable ) ),
RecvPropInt( RECVINFO( m_iItemFindBonus ) ),
RecvPropBool( RECVINFO( m_bShieldEquipped ) ),
RecvPropBool( RECVINFO( m_bParachuteEquipped ) ),
RecvPropInt( RECVINFO( m_iNextMeleeCrit ) ),
RecvPropInt( RECVINFO( m_iDecapitations ) ),
RecvPropInt( RECVINFO( m_iRevengeCrits ) ),
RecvPropInt( RECVINFO( m_iDisguiseBody ) ),
RecvPropEHandle( RECVINFO( m_hCarriedObject ) ),
RecvPropBool( RECVINFO( m_bCarryingObject ) ),
RecvPropFloat( RECVINFO( m_flNextNoiseMakerTime ) ),
RecvPropInt( RECVINFO( m_iSpawnRoomTouchCount ) ),
RecvPropInt( RECVINFO( m_iKillCountSinceLastDeploy ) ),
RecvPropFloat( RECVINFO( m_flFirstPrimaryAttack ) ),
//Scout
RecvPropFloat( RECVINFO( m_flEnergyDrinkMeter) ),
RecvPropFloat( RECVINFO( m_flHypeMeter) ),
// Demoman
RecvPropFloat( RECVINFO( m_flChargeMeter) ),
// Spy.
RecvPropTime( RECVINFO( m_flInvisChangeCompleteTime ) ),
RecvPropInt( RECVINFO( m_nDisguiseTeam ) ),
RecvPropInt( RECVINFO( m_nDisguiseClass ) ),
RecvPropInt( RECVINFO( m_nDisguiseSkinOverride ) ),
RecvPropInt( RECVINFO( m_nMaskClass ) ),
RecvPropInt( RECVINFO( m_iDisguiseTargetIndex ) ),
RecvPropInt( RECVINFO( m_iDisguiseHealth ) ),
RecvPropBool( RECVINFO( m_bFeignDeathReady ) ),
RecvPropEHandle( RECVINFO( m_hDisguiseWeapon ) ),
RecvPropInt( RECVINFO( m_nTeamTeleporterUsed ) ),
RecvPropFloat( RECVINFO( m_flCloakMeter ) ),
RecvPropFloat( RECVINFO( m_flSpyTranqBuffDuration ) ),
// Local Data.
RecvPropDataTable( "tfsharedlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFPlayerSharedLocal) ),
RecvPropDataTable( RECVINFO_DT(m_ConditionList),0, &REFERENCE_RECV_TABLE(DT_TFPlayerConditionListExclusive) ),
RecvPropInt( RECVINFO( m_iTauntIndex ) ),
RecvPropInt( RECVINFO( m_iTauntConcept ) ),
RecvPropInt( RECVINFO( m_nPlayerCondEx ) ),
RecvPropInt( RECVINFO( m_iStunIndex ) ),
RecvPropInt( RECVINFO( m_nHalloweenBombHeadStage ) ),
RecvPropInt( RECVINFO( m_nPlayerCondEx2 ) ),
RecvPropInt( RECVINFO( m_nPlayerCondEx3 ) ),
RecvPropArray3( RECVINFO_ARRAY( m_nStreaks ), RecvPropInt( RECVINFO( m_nStreaks[0] ) ) ),
RecvPropInt( RECVINFO( m_unTauntSourceItemID_Low ) ),
RecvPropInt( RECVINFO( m_unTauntSourceItemID_High ) ),
RecvPropFloat( RECVINFO( m_flRuneCharge ) ),
#ifdef STAGING_ONLY
RecvPropFloat( RECVINFO( m_flSpaceJumpCharge ) ),
#endif
RecvPropBool( RECVINFO( m_bHasPasstimeBall ) ),
RecvPropBool( RECVINFO( m_bIsTargetedForPasstimePass ) ),
RecvPropEHandle( RECVINFO( m_hPasstimePassTarget ) ),
RecvPropFloat( RECVINFO( m_askForBallTime ) ),
RecvPropBool( RECVINFO( m_bKingRuneBuffActive ) ),
RecvPropUtlVectorDataTable( m_ConditionData, TF_COND_LAST, DT_TFPlayerConditionSource ),
END_RECV_TABLE()
BEGIN_PREDICTION_DATA_NO_BASE( CTFPlayerShared )
DEFINE_PRED_FIELD( m_nPlayerState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nPlayerCond, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flCloakMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flRageMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_bRageDraining, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flNextRageEarnTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flEnergyDrinkMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flHypeMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flChargeMeter, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_bJumping, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_iAirDash, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nAirDucked, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flDuckTimer, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flInvisChangeCompleteTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nDisguiseTeam, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nDisguiseClass, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nDisguiseSkinOverride, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nMaskClass, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nDesiredDisguiseTeam, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nDesiredDisguiseClass, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_bLastDisguisedAsOwnTeam, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_bFeignDeathReady, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nPlayerCondEx, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nPlayerCondEx2, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_nPlayerCondEx3, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
// DEFINE_PRED_FIELD( m_hDisguiseWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
DEFINE_FIELD( m_flDisguiseCompleteTime, FIELD_FLOAT ),
DEFINE_PRED_FIELD( m_bHasPasstimeBall, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_bIsTargetedForPasstimePass, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), // does this belong here?
DEFINE_PRED_FIELD( m_askForBallTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
// Server specific.
#else
BEGIN_SEND_TABLE_NOBASE( localplayerscoring_t, DT_TFPlayerScoringDataExclusive )
SendPropInt( SENDINFO( m_iCaptures ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDefenses ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iKills ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDeaths ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iSuicides ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDominations ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iRevenge ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iBuildingsBuilt ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iBuildingsDestroyed ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iHeadshots ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iBackstabs ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iHealPoints ), 20, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iInvulns ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iTeleports ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDamageDone ), 20, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iCrits ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iResupplyPoints ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iKillAssists ), 12, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iBonusPoints ), 10, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iPoints ), 10, SPROP_UNSIGNED ),
END_SEND_TABLE()
EXTERN_SEND_TABLE(DT_TFPlayerConditionListExclusive);
BEGIN_SEND_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerSharedLocal )
SendPropInt( SENDINFO( m_nDesiredDisguiseTeam ), 3, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nDesiredDisguiseClass ), 4, SPROP_UNSIGNED ),
SendPropBool( SENDINFO( m_bLastDisguisedAsOwnTeam ) ),
SendPropTime( SENDINFO( m_flStealthNoAttackExpire ) ),
SendPropTime( SENDINFO( m_flStealthNextChangeTime ) ),
SendPropFloat( SENDINFO( m_flRageMeter ), 0, SPROP_NOSCALE, 0.0, 100.0 ),
SendPropBool( SENDINFO( m_bRageDraining ) ),
SendPropTime( SENDINFO( m_flNextRageEarnTime ) ),
SendPropBool( SENDINFO( m_bInUpgradeZone ) ),
SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ),
SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ),
SendPropDataTable( SENDINFO_DT(m_ScoreData), &REFERENCE_SEND_TABLE(DT_TFPlayerScoringDataExclusive) ),
SendPropDataTable( SENDINFO_DT(m_RoundScoreData), &REFERENCE_SEND_TABLE(DT_TFPlayerScoringDataExclusive) ),
END_SEND_TABLE()
BEGIN_SEND_TABLE_NOBASE( condition_source_t, DT_TFPlayerConditionSource )
//SendPropInt( SENDINFO( m_nPreventedDamageFromCondition ) ),
//SendPropFloat( SENDINFO( m_flExpireTime ) ),
SendPropEHandle( SENDINFO( m_pProvider ) ),
//SendPropBool( SENDINFO( m_bPrevActive ) ),
END_SEND_TABLE()
BEGIN_SEND_TABLE_NOBASE( CTFPlayerShared, DT_TFPlayerShared )
SendPropInt( SENDINFO( m_nPlayerCond ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_bJumping ), 1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nNumHealers ), 5, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iCritMult ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iAirDash ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nAirDucked ), 2, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO( m_flDuckTimer ) ),
SendPropInt( SENDINFO( m_nPlayerState ), Q_log2( TF_STATE_COUNT )+1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDesiredPlayerClass ), Q_log2( TF_CLASS_COUNT_ALL )+1, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO( m_flMovementStunTime ) ),
SendPropInt( SENDINFO( m_iMovementStunAmount ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iMovementStunParity ), MOVEMENTSTUN_PARITY_BITS, SPROP_UNSIGNED ),
SendPropEHandle( SENDINFO( m_hStunner ) ),
SendPropInt( SENDINFO( m_iStunFlags ), 12, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nArenaNumChanges ), 5, SPROP_UNSIGNED ),
SendPropBool( SENDINFO( m_bArenaFirstBloodBoost ) ),
SendPropInt( SENDINFO( m_iWeaponKnockbackID ) ),
SendPropBool( SENDINFO( m_bLoadoutUnavailable ) ),
SendPropInt( SENDINFO( m_iItemFindBonus ) ),
SendPropBool( SENDINFO( m_bShieldEquipped ) ),
SendPropBool( SENDINFO( m_bParachuteEquipped ) ),
SendPropInt( SENDINFO( m_iNextMeleeCrit ) ),
SendPropInt( SENDINFO( m_iDecapitations ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iRevengeCrits ), 7, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDisguiseBody ) ),
SendPropEHandle( SENDINFO( m_hCarriedObject ) ),
SendPropBool( SENDINFO( m_bCarryingObject ) ),
SendPropFloat( SENDINFO( m_flNextNoiseMakerTime ) ),
SendPropInt( SENDINFO( m_iSpawnRoomTouchCount ) ),
SendPropInt( SENDINFO( m_iKillCountSinceLastDeploy ) ),
SendPropFloat( SENDINFO( m_flFirstPrimaryAttack ) ),
//Scout
SendPropFloat( SENDINFO( m_flEnergyDrinkMeter ), 0, SPROP_NOSCALE, 0.0, 100.0 ),
SendPropFloat( SENDINFO( m_flHypeMeter ), 0, SPROP_NOSCALE, 0.0, 100.0 ),
// Demoman
SendPropFloat( SENDINFO( m_flChargeMeter ), 8, SPROP_NOSCALE, 0.0, 100.0 ),
// Spy
SendPropTime( SENDINFO( m_flInvisChangeCompleteTime ) ),
SendPropInt( SENDINFO( m_nDisguiseTeam ), 3, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nDisguiseClass ), 4, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nDisguiseSkinOverride ), 1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nMaskClass ), 4, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDisguiseTargetIndex ), 7, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iDisguiseHealth ), -1, SPROP_VARINT ),
SendPropBool( SENDINFO( m_bFeignDeathReady ) ),
SendPropEHandle( SENDINFO( m_hDisguiseWeapon ) ),
SendPropInt( SENDINFO( m_nTeamTeleporterUsed ), 3, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO( m_flCloakMeter ), 16, SPROP_NOSCALE, 0.0, 100.0 ),
SendPropFloat( SENDINFO( m_flSpyTranqBuffDuration ), 16, SPROP_NOSCALE, 0.0, 100.0 ),
// Local Data.
SendPropDataTable( "tfsharedlocaldata", 0, &REFERENCE_SEND_TABLE( DT_TFPlayerSharedLocal ), SendProxy_SendLocalDataTable ),
SendPropDataTable( SENDINFO_DT(m_ConditionList), &REFERENCE_SEND_TABLE(DT_TFPlayerConditionListExclusive) ),
SendPropInt( SENDINFO( m_iTauntIndex ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iTauntConcept ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nPlayerCondEx ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iStunIndex ), 8 ),
SendPropInt( SENDINFO( m_nHalloweenBombHeadStage ), 2, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nPlayerCondEx2 ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nPlayerCondEx3 ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
SendPropArray3( SENDINFO_ARRAY3( m_nStreaks ), SendPropInt( SENDINFO_ARRAY( m_nStreaks ) ) ),
SendPropInt( SENDINFO( m_unTauntSourceItemID_Low ), -1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_unTauntSourceItemID_High ), -1, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO( m_flRuneCharge ), 8, 0, 0.0, 100.0 ),
#ifdef STAGING_ONLY
SendPropFloat( SENDINFO( m_flSpaceJumpCharge ), 8, 0, 0.0, 100.0 ),
#endif
SendPropBool( SENDINFO( m_bHasPasstimeBall ) ),
SendPropBool( SENDINFO( m_bIsTargetedForPasstimePass ) ),
SendPropEHandle( SENDINFO( m_hPasstimePassTarget ) ),
SendPropFloat( SENDINFO( m_askForBallTime ) ),
SendPropBool( SENDINFO( m_bKingRuneBuffActive ) ),
SendPropUtlVectorDataTable( m_ConditionData, TF_COND_LAST, DT_TFPlayerConditionSource ),
END_SEND_TABLE()
#endif
extern void HandleRageGain( CTFPlayer *pPlayer, unsigned int iRequiredBuffFlags, float flDamage, float fInverseRageGainScale );
CTFWearableDemoShield* GetEquippedDemoShield( CTFPlayer * pPlayer )
{
// Loop through our wearables in search of a shield
for ( int i=0; i<pPlayer->GetNumWearables(); ++i )
{
CTFWearableDemoShield *pWearableShield = dynamic_cast<CTFWearableDemoShield*>( pPlayer->GetWearable( i ) );
if ( pWearableShield )
{
return pWearableShield;
}
}
return NULL;
}
CTFPlayer *GetRuneCarrier( RuneTypes_t type, int iTeam = TEAM_ANY )
{
for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
{
CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
if ( !pPlayer )
continue;
if ( iTeam != TEAM_ANY && pPlayer->GetTeamNumber() != iTeam )
continue;
if ( pPlayer->m_Shared.GetCarryingRuneType() == type )
{
return pPlayer;
}
}
return NULL;
}
// --------------------------------------------------------------------------------------------------- //
// Shared CTFPlayer implementation.
// --------------------------------------------------------------------------------------------------- //
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::HasCampaignMedal( int iMedal )
{
return ( ( m_iCampaignMedals & iMedal ) != 0 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::IsAllowedToTaunt( void )
{
if ( !IsAlive() )
return false;
// Check to see if we can taunt again!
if ( m_Shared.InCond( TF_COND_TAUNTING ) )
return false;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return false;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
return false;
// Can't taunt while charging.
if ( m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
return false;
if ( m_Shared.InCond( TF_COND_COMPETITIVE_LOSER ) )
return false;
if ( IsLerpingFOV() )
return false;
// Check for things that prevent taunting
if ( ShouldStopTaunting() )
return false;
// Check to see if we are on the ground.
if ( GetGroundEntity() == NULL && !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return false;
CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon();
if ( pActiveWeapon )
{
if ( !pActiveWeapon->OwnerCanTaunt() )
return false;
// ignore taunt key if one of these if active weapon
if ( pActiveWeapon->GetWeaponID() == TF_WEAPON_PDA_ENGINEER_BUILD
|| pActiveWeapon->GetWeaponID() == TF_WEAPON_PDA_ENGINEER_DESTROY )
return false;
}
// can't taunt while carrying an object
if ( m_Shared.IsCarryingObject() )
return false;
// Can't taunt if hooked into a player
if ( m_Shared.InCond( TF_COND_GRAPPLED_TO_PLAYER ) )
return false;
if ( IsPlayerClass( TF_CLASS_SCOUT ) )
{
if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX )
{
//Scouts can't drink while they're already phasing.
if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) || m_Shared.InCond( TF_COND_PHASE ) )
return false;
// Or if their energy drink meter isn't refilled
if ( m_Shared.GetScoutEnergyDrinkMeter() < 100 )
return false;
//They can't drink the default (phase) item while carrying a flag
pActiveWeapon = m_Shared.GetActiveTFWeapon();
if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX )
{
CTFLunchBox *pLunchbox = (CTFLunchBox*)pActiveWeapon;
if ( ( pLunchbox->GetLunchboxType() == LUNCHBOX_STANDARD ) || ( pLunchbox->GetLunchboxType() == LUNCHBOX_STANDARD_ROBO ) )
{
if ( !TFGameRules()->IsMannVsMachineMode() && HasItem() )
return false;
}
}
}
}
if ( IsPlayerClass( TF_CLASS_SPY ) )
{
if ( m_Shared.IsStealthed() || m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) )
{
return false;
}
}
return true;
}
// --------------------------------------------------------------------------------------------------- //
// CTFPlayerShared implementation.
// --------------------------------------------------------------------------------------------------- //
CTFPlayerShared::CTFPlayerShared()
{
// If you hit this assert, CONGRATULATIONS! You've added a condition that has gone
// beyond the amount of bits we network for conditions. Take a look at the pattern
// of m_nPlayerCond, m_nPlayerCondEx, m_nPlayerCondEx2, and m_nPlayerCondEx3 to get more bits.
// This pattern is as such to preserve replays.
// Don't forget to add an m_nOldCond* and m_nForceCond*
COMPILE_TIME_ASSERT( TF_COND_LAST < (32 + 32 + 32 + 32) );
m_nPlayerState.Set( TF_STATE_WELCOME );
m_bJumping = false;
m_iAirDash = 0;
m_nAirDucked = 0;
m_flDuckTimer = 0.0f;
m_flStealthNoAttackExpire = 0.0f;
m_flStealthNextChangeTime = 0.0f;
m_iCritMult = 0;
m_flInvisibility = 0.0f;
m_flPrevInvisibility = 0.f;
m_flTmpDamageBonusAmount = 1.0f;
m_bFeignDeathReady = false;
m_fCloakConsumeRate = tf_spy_cloak_consume_rate.GetFloat();
m_fCloakRegenRate = tf_spy_cloak_regen_rate.GetFloat();
m_fEnergyDrinkConsumeRate = tf_scout_energydrink_consume_rate.GetFloat();
m_fEnergyDrinkRegenRate = tf_scout_energydrink_regen_rate.GetFloat();
m_bMotionCloak = false;
m_hStunner = NULL;
m_iStunFlags = 0;
m_hAssist = NULL;
m_bLastDisguisedAsOwnTeam = false;
m_bRageDraining = false;
m_bInUpgradeZone = false;
m_bPhaseFXOn = false;
ResetRageBuffs();
m_iPhaseDamage = 0;
Q_memset(m_pPhaseTrail, 0, sizeof(m_pPhaseTrail));
m_iWeaponKnockbackID = -1;
m_bLoadoutUnavailable = false;
m_nMaskClass = 0;
m_iItemFindBonus = 0;
m_nTeamTeleporterUsed = TEAM_UNASSIGNED;
m_bShieldEquipped = false;
m_bPostShieldCharge = false;
m_iNextMeleeCrit = 0;
m_bParachuteEquipped = false;
m_iDecapitations = m_iOldDecapitations = 0;
m_iOldKillStreak = 0;
m_iOldKillStreakWepSlot = 0;
m_flNextNoiseMakerTime = 0;
m_iSpawnRoomTouchCount = 0;
m_iKillCountSinceLastDeploy = 0;
m_flFirstPrimaryAttack = 0.0f;
#ifdef GAME_DLL
m_flBestOverhealDecayMult = -1;
m_hPeeAttacker = NULL;
m_flHealedPerSecondTimer = -1000;
m_bPulseRadiusHeal = false;
m_flRadiusCurrencyCollectionTime = 0;
m_flRadiusSpyScanTime = 0;
m_flCloakStartTime = -1.0f;
ListenForGameEvent( "player_disconnect" );
#else
m_pWheelEffect = NULL;
m_angVehicleMoveAngles = QAngle( 0.f, 0.f, 0.f );
m_angVehicleMovePitchLast = 0.0f;
// Save Prediction value
m_bPreKartPredictionState = cl_predict->GetBool();
m_hKartParachuteEntity = NULL;
#endif
m_nForceConditions = 0;
m_nForceConditionsEx = 0;
m_nForceConditionsEx2 = 0;
m_nForceConditionsEx3 = 0;
m_flChargeEndTime = -1000;
m_flLastChargeTime = -1000;
m_flLastNoChargeTime = 0;
m_bChargeGlowing = false;
m_bChargeOffSounded = false;
m_bBiteEffectWasApplied = false;
m_flLastMovementStunChange = 0;
m_bStunNeedsFadeOut = false;
m_flChargeMeter = 100;
m_flEnergyDrinkMeter = 0;
m_flHypeMeter = 0;
m_bCarryingObject = false;
m_hCarriedObject = NULL;
m_iStunIndex = -1;
m_flLastNoMovementTime = -1.f;
m_flRuneCharge = 0.f;
#ifdef STAGING_ONLY
m_flSpaceJumpCharge = 100.0f;
m_flSpyTranqBuffDuration = 0.0f;
#endif
m_iPasstimeThrowAnimState = PASSTIME_THROW_ANIM_NONE;
m_bHasPasstimeBall = false;
m_bIsTargetedForPasstimePass = false;
m_askForBallTime = 0.0f;
// make sure we have all conditions in the list
m_ConditionData.EnsureCount( TF_COND_LAST );
}
void CTFPlayerShared::Init( CTFPlayer *pPlayer )
{
m_pOuter = pPlayer;
m_flNextBurningSound = 0;
m_bArenaFirstBloodBoost = false;
m_iStunAnimState = STUN_ANIM_NONE;
m_iPhaseDamage = 0;
m_iWeaponKnockbackID = -1;
m_hStunner = NULL;
m_iPasstimeThrowAnimState = PASSTIME_THROW_ANIM_NONE;
m_bHasPasstimeBall = false;
m_bIsTargetedForPasstimePass = false;
m_askForBallTime = 0.0f;
m_bMotionCloak = false;
m_bShieldEquipped = false;
m_bPostShieldCharge = false;
m_iNextMeleeCrit = 0;
m_bParachuteEquipped = false;
m_iDecapitations = m_iOldDecapitations = 0;
m_iOldKillStreak = 0;
m_iOldKillStreakWepSlot = 0;
SetJumping( false );
SetAssist( NULL );
m_flInvulnerabilityRemoveTime = -1;
SetNextMeleeCrit( MELEE_NOCRIT );
Spawn();
}
void CTFPlayerShared::ResetRageBuffs( void )
{
for ( int i = 0; i < kBuffSlot_MAX; i++ )
{
m_RageBuffSlots[i].m_iBuffTypeActive = 0;
m_RageBuffSlots[i].m_iBuffPulseCount = 0;
m_RageBuffSlots[i].m_flNextBuffPulseTime = 0.0f;
}
}
void CTFPlayerShared::Spawn( void )
{
#ifdef GAME_DLL
m_hPeeAttacker = NULL;
if ( m_bCarryingObject )
{
CBaseObject* pObj = GetCarriedObject();
if ( pObj )
{
pObj->DetonateObject();
}
}
m_bCarryingObject = false;
m_hCarriedObject = NULL;
m_flRadiusHealCheckTime = 0;
m_flKingRuneBuffCheckTime = 0.f;
m_bBiteEffectWasApplied = false;
m_flNextRocketPackTime = 0.f;
m_iSpawnRoomTouchCount = 0;
SetRevengeCrits( 0 );
m_PlayerStuns.RemoveAll();
m_iStunIndex = -1;
m_iPasstimeThrowAnimState = PASSTIME_THROW_ANIM_NONE;
m_bHasPasstimeBall = false;
m_bIsTargetedForPasstimePass = false;
m_askForBallTime = 0.0f;
#else
m_bSyncingConditions = false;
#endif
m_bKingRuneBuffActive = false;
// Reset our assist here incase something happens before we get killed
// again that checks this (getting slapped with a fish)
SetAssist( NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
template < typename tIntType >
class CConditionVars
{
public:
CConditionVars( tIntType& nPlayerCond, tIntType& nPlayerCondEx, tIntType& nPlayerCondEx2, tIntType& nPlayerCondEx3, ETFCond eCond )
{
if ( eCond >= 96 )
{
Assert( eCond < 96 + 32 );
m_pnCondVar = &nPlayerCondEx3;
m_nCondBit = eCond - 96;
}
else if( eCond >= 64 )
{
Assert( eCond < (64 + 32) );
m_pnCondVar = &nPlayerCondEx2;
m_nCondBit = eCond - 64;
}
else if ( eCond >= 32 )
{
Assert( eCond < (32 + 32) );
m_pnCondVar = &nPlayerCondEx;
m_nCondBit = eCond - 32;
}
else
{
m_pnCondVar = &nPlayerCond;
m_nCondBit = eCond;
}
}
tIntType& CondVar() const
{
return *m_pnCondVar;
}
int CondBit() const
{
return 1 << m_nCondBit;
}
private:
tIntType *m_pnCondVar;
int m_nCondBit;
};
//-----------------------------------------------------------------------------
// Purpose: Add a condition and duration
// duration of PERMANENT_CONDITION means infinite duration
//-----------------------------------------------------------------------------
void CTFPlayerShared::AddCond( ETFCond eCond, float flDuration /* = PERMANENT_CONDITION */, CBaseEntity *pProvider /*= NULL */)
{
Assert( eCond >= 0 && eCond < TF_COND_LAST );
Assert( eCond < m_ConditionData.Count() );
// If we're dead, don't take on any new conditions
if( !m_pOuter || !m_pOuter->IsAlive() )
{
return;
}
#ifdef CLEINT_DLL
if ( m_pOuter->IsDormant() )
{
return;
}
#endif
// sanity check to prevent servers from adding these conditions when they shouldn't
if ( ( eCond == TF_COND_COMPETITIVE_WINNER ) || ( eCond == TF_COND_COMPETITIVE_LOSER ) )
{
if ( TFGameRules() && !TFGameRules()->ShowMatchSummary() )
return;
}
// Which bitfield are we tracking this condition variable in? Which bit within
// that variable will we track it as?
CConditionVars<int> cPlayerCond( m_nPlayerCond.m_Value, m_nPlayerCondEx.m_Value, m_nPlayerCondEx2.m_Value, m_nPlayerCondEx3.m_Value, eCond );
// See if there is an object representation of the condition.
bool bAddedToExternalConditionList = m_ConditionList.Add( eCond, flDuration, m_pOuter, pProvider );
if ( !bAddedToExternalConditionList )
{
// Set the condition bit for this condition.
cPlayerCond.CondVar() |= cPlayerCond.CondBit();
// Flag for gamecode to query
m_ConditionData[eCond].m_bPrevActive = ( m_ConditionData[eCond].m_flExpireTime != 0.f ) ? true : false;
if ( flDuration != PERMANENT_CONDITION )
{
// if our current condition is permanent or we're trying to set a new
// time that's less our current time remaining, use our current time instead
if ( ( m_ConditionData[eCond].m_flExpireTime == PERMANENT_CONDITION ) ||
( flDuration < m_ConditionData[eCond].m_flExpireTime ) )
{
flDuration = m_ConditionData[eCond].m_flExpireTime;
}
}
m_ConditionData[eCond].m_flExpireTime = flDuration;
m_ConditionData[eCond].m_pProvider = pProvider;
m_ConditionData[ eCond ].m_nPreventedDamageFromCondition = 0;
OnConditionAdded( eCond );
}
}
//-----------------------------------------------------------------------------
// Purpose: Forcibly remove a condition
//-----------------------------------------------------------------------------
void CTFPlayerShared::RemoveCond( ETFCond eCond, bool ignore_duration )
{
Assert( eCond >= 0 && eCond < TF_COND_LAST );
Assert( eCond < m_ConditionData.Count() );
if ( !InCond( eCond ) )
return;
CConditionVars<int> cPlayerCond( m_nPlayerCond.m_Value, m_nPlayerCondEx.m_Value, m_nPlayerCondEx2.m_Value, m_nPlayerCondEx3.m_Value, eCond );
// If this variable is handled by the condition list, abort before doing the
// work for the condition flags.
if ( m_ConditionList.Remove( eCond, ignore_duration ) )
return;
cPlayerCond.CondVar() &= ~cPlayerCond.CondBit();
OnConditionRemoved( eCond );
if ( m_ConditionData[ eCond ].m_nPreventedDamageFromCondition )
{
IGameEvent *pEvent = gameeventmanager->CreateEvent( "damage_prevented" );
if ( pEvent )
{
pEvent->SetInt( "preventor", m_ConditionData[eCond].m_pProvider ? m_ConditionData[eCond].m_pProvider->entindex() : m_pOuter->entindex() );
pEvent->SetInt( "victim", m_pOuter->entindex() );
pEvent->SetInt( "amount", m_ConditionData[ eCond ].m_nPreventedDamageFromCondition );
pEvent->SetInt( "condition", eCond );
gameeventmanager->FireEvent( pEvent, true );
}
m_ConditionData[ eCond ].m_nPreventedDamageFromCondition = 0;
}
m_ConditionData[eCond].m_flExpireTime = 0;
m_ConditionData[eCond].m_pProvider = NULL;
m_ConditionData[eCond].m_bPrevActive = false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::InCond( ETFCond eCond ) const
{
Assert( eCond >= 0 && eCond < TF_COND_LAST );
// Old condition system, only used for the first 32 conditions
if ( eCond < 32 && m_ConditionList.InCond( eCond ) )
return true;
CConditionVars<const int> cPlayerCond( m_nPlayerCond.m_Value, m_nPlayerCondEx.m_Value, m_nPlayerCondEx2.m_Value, m_nPlayerCondEx3.m_Value, eCond );
return (cPlayerCond.CondVar() & cPlayerCond.CondBit()) != 0;
}
//-----------------------------------------------------------------------------
// Purpose: Return whether or not we were in this condition before.
//-----------------------------------------------------------------------------
bool CTFPlayerShared::WasInCond( ETFCond eCond ) const
{
// I don't know if this actually works for conditions < 32, because we definitely cannot peak into m_ConditionList (back in time).
// But others think that m_ConditionList is propogated into m_nOldConditions, so just check if you hit the assert. (And then remove the
// assert. And this comment).
Assert( eCond >= 32 && eCond < TF_COND_LAST );
CConditionVars<const int> cPlayerCond( m_nOldConditions, m_nOldConditionsEx, m_nOldConditionsEx2, m_nOldConditionsEx3, eCond );
return (cPlayerCond.CondVar() & cPlayerCond.CondBit()) != 0;
}
//-----------------------------------------------------------------------------
// Purpose: Set a bit to force this condition off and then back on next time we sync bits from the server.
//-----------------------------------------------------------------------------
void CTFPlayerShared::ForceRecondNextSync( ETFCond eCond )
{
// I don't know if this actually works for conditions < 32. We may need to set this bit in m_ConditionList, too.
// Please check if you hit the assert. (And then remove the assert. And this comment).
Assert(eCond >= 32 && eCond < TF_COND_LAST);
CConditionVars<int> playerCond( m_nForceConditions, m_nForceConditionsEx, m_nForceConditionsEx2, m_nForceConditionsEx3, eCond );
playerCond.CondVar() |= playerCond.CondBit();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayerShared::GetConditionDuration( ETFCond eCond ) const
{
Assert( eCond >= 0 && eCond < TF_COND_LAST );
Assert( eCond < m_ConditionData.Count() );
if ( InCond( eCond ) )
{
return m_ConditionData[eCond].m_flExpireTime;
}
return 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the entity that provided the passed in condition
//-----------------------------------------------------------------------------
CBaseEntity *CTFPlayerShared::GetConditionProvider( ETFCond eCond ) const
{
Assert( eCond >= 0 && eCond < TF_COND_LAST );
Assert( eCond < m_ConditionData.Count() );
CBaseEntity *pProvider = NULL;
if ( InCond( eCond ) )
{
if ( eCond == TF_COND_CRITBOOSTED )
{
pProvider = m_ConditionList.GetProvider( eCond );
}
else
{
pProvider = m_ConditionData[eCond].m_pProvider;
}
}
return pProvider;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the entity that applied this condition to us - for granting an assist when we die
//-----------------------------------------------------------------------------
CBaseEntity *CTFPlayerShared::GetConditionAssistFromVictim( void )
{
// We only give an assist to one person. That means this list is order
// sensitive, so consider how "powerful" an effect is when adding it here.
static const ETFCond nTrackedConditions[] =
{
TF_COND_URINE,
TF_COND_MAD_MILK,
TF_COND_MARKEDFORDEATH,
};
CBaseEntity *pProvider = NULL;
for ( int i = 0; i < ARRAYSIZE( nTrackedConditions ); i++ )
{
if ( InCond( nTrackedConditions[i] ) )
{
pProvider = GetConditionProvider( nTrackedConditions[i] );
break;
}
}
return pProvider;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the entity that applied this condition to us - for granting an assist when we kill someone
//-----------------------------------------------------------------------------
CBaseEntity *CTFPlayerShared::GetConditionAssistFromAttacker( void )
{
// We only give an assist to one person. That means this list is order
// sensitive, so consider how "powerful" an effect is when adding it here.
static const ETFCond nTrackedConditions[] =
{
TF_COND_OFFENSEBUFF, // Highest priority
TF_COND_DEFENSEBUFF,
TF_COND_REGENONDAMAGEBUFF,
TF_COND_NOHEALINGDAMAGEBUFF, // Lowest priority
};
CBaseEntity *pProvider = NULL;
for ( int i = 0; i < ARRAYSIZE( nTrackedConditions ); i++ )
{
if ( InCond( nTrackedConditions[i] ) )
{
CBaseEntity* pPotentialProvider = GetConditionProvider( nTrackedConditions[i] );
// Check to make sure we're not providing the condition to ourselves
if( pPotentialProvider != m_pOuter )
{
pProvider = pPotentialProvider;
break;
}
}
}
return pProvider;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::DebugPrintConditions( void )
{
#ifndef CLIENT_DLL
const char *szDll = "Server";
#else
const char *szDll = "Client";
#endif
Msg( "( %s ) Conditions for player ( %d )\n", szDll, m_pOuter->entindex() );
int i;
int iNumFound = 0;
for ( i=0;i<TF_COND_LAST;i++ )
{
if ( InCond( (ETFCond)i ) )
{
if ( m_ConditionData[i].m_flExpireTime == PERMANENT_CONDITION )
{
Msg( "( %s ) Condition %d - ( permanent cond )\n", szDll, i );
}
else
{
Msg( "( %s ) Condition %d - ( %.1f left )\n", szDll, i, m_ConditionData[i].m_flExpireTime );
}
iNumFound++;
}
}
if ( iNumFound == 0 )
{
Msg( "( %s ) No active conditions\n", szDll );
}
}
void CTFPlayerShared::InstantlySniperUnzoom( void )
{
// Unzoom if we are a sniper zoomed!
if ( m_pOuter->GetPlayerClass()->GetClassIndex() == TF_CLASS_SNIPER )
{
CTFWeaponBase *pWpn = m_pOuter->GetActiveTFWeapon();
if ( pWpn && WeaponID_IsSniperRifle( pWpn->GetWeaponID() ) )
{
CTFSniperRifle *pRifle = static_cast<CTFSniperRifle*>( pWpn );
if ( pRifle->IsZoomed() )
{
// Let the rifle clean up conditions and state
pRifle->ToggleZoom();
// Slam the FOV right now
m_pOuter->SetFOV( m_pOuter, 0, 0.0f );
}
}
}
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnPreDataChanged( void )
{
m_ConditionList.OnPreDataChanged();
m_nOldConditions = m_nPlayerCond;
m_nOldConditionsEx = m_nPlayerCondEx;
m_nOldConditionsEx2 = m_nPlayerCondEx2;
m_nOldConditionsEx3 = m_nPlayerCondEx3;
m_nOldDisguiseClass = GetDisguiseClass();
m_nOldDisguiseTeam = GetDisguiseTeam();
m_iOldMovementStunParity = m_iMovementStunParity;
InvisibilityThink();
ConditionThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnDataChanged( void )
{
m_ConditionList.OnDataChanged( m_pOuter );
if ( m_iOldMovementStunParity != m_iMovementStunParity )
{
m_flStunFade = gpGlobals->curtime + m_flMovementStunTime;
m_flStunEnd = m_flStunFade;
if ( IsControlStunned() && (m_iStunAnimState == STUN_ANIM_NONE) )
{
m_flStunEnd += CONTROL_STUN_ANIM_TIME;
}
UpdateLegacyStunSystem();
}
// Update conditions from last network change
SyncConditions( m_nOldConditions, m_nPlayerCond, m_nForceConditions, 0 );
SyncConditions( m_nOldConditionsEx, m_nPlayerCondEx, m_nForceConditionsEx, 32 );
SyncConditions( m_nOldConditionsEx2, m_nPlayerCondEx2, m_nForceConditionsEx2, 64 );
SyncConditions( m_nOldConditionsEx3, m_nPlayerCondEx3, m_nForceConditionsEx3, 96 );
// Make sure these items are present
m_nPlayerCond |= m_nForceConditions;
m_nPlayerCondEx |= m_nForceConditionsEx;
m_nPlayerCondEx2 |= m_nForceConditionsEx2;
m_nPlayerCondEx3 |= m_nForceConditionsEx3;
// Clear our force bits now that we've used them.
m_nForceConditions = 0;
m_nForceConditionsEx = 0;
m_nForceConditionsEx2 = 0;
m_nForceConditionsEx3 = 0;
if ( m_nOldDisguiseClass != GetDisguiseClass() || m_nOldDisguiseTeam != GetDisguiseTeam() )
{
OnDisguiseChanged();
}
if ( m_hDisguiseWeapon )
{
m_hDisguiseWeapon->UpdateVisibility();
m_hDisguiseWeapon->UpdateParticleSystems();
}
if ( ( IsLoser() || InCond( TF_COND_COMPETITIVE_LOSER ) ) && GetActiveTFWeapon() && !GetActiveTFWeapon()->IsEffectActive( EF_NODRAW ) )
{
GetActiveTFWeapon()->SetWeaponVisible( false );
}
}
//-----------------------------------------------------------------------------
// Purpose: check the newly networked conditions for changes
//-----------------------------------------------------------------------------
void CTFPlayerShared::SyncConditions( int nPreviousConditions, int nNewConditions, int nForceConditions, int nBaseCondBit )
{
if ( nPreviousConditions == nNewConditions )
return;
int nCondChanged = nNewConditions ^ nPreviousConditions;
int nCondAdded = nCondChanged & nNewConditions;
int nCondRemoved = nCondChanged & nPreviousConditions;
m_bSyncingConditions = true;
for ( int i=0;i<32;i++ )
{
const int testBit = 1<<i;
if ( nForceConditions & testBit )
{
if ( nPreviousConditions & testBit )
{
OnConditionRemoved((ETFCond)(nBaseCondBit + i));
}
OnConditionAdded((ETFCond)(nBaseCondBit + i));
}
else
{
if ( nCondAdded & testBit )
{
OnConditionAdded( (ETFCond)(nBaseCondBit + i) );
}
else if ( nCondRemoved & testBit )
{
OnConditionRemoved( (ETFCond)(nBaseCondBit + i) );
}
}
}
m_bSyncingConditions = false;
}
#endif // CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Remove any conditions affecting players
//-----------------------------------------------------------------------------
void CTFPlayerShared::RemoveAllCond()
{
m_ConditionList.RemoveAll();
int i;
for ( i=0;i<TF_COND_LAST;i++ )
{
if ( InCond( (ETFCond)i ) )
{
RemoveCond( (ETFCond)i );
}
}
// Now remove all the rest
m_nPlayerCond = 0;
m_nPlayerCondEx = 0;
m_nPlayerCondEx2 = 0;
m_nPlayerCondEx3 = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Called on both client and server. Server when we add the bit,
// and client when it receives the new cond bits and finds one added
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnConditionAdded( ETFCond eCond )
{
switch( eCond )
{
case TF_COND_ZOOMED:
OnAddZoomed();
break;
case TF_COND_HEALTH_BUFF:
#ifdef GAME_DLL
m_flHealFraction = 0;
m_flDisguiseHealFraction = 0;
m_flHealedPerSecondTimer = gpGlobals->curtime + 1.0f;
#endif
break;
case TF_COND_HEALTH_OVERHEALED:
OnAddOverhealed();
break;
case TF_COND_FEIGN_DEATH:
OnAddFeignDeath();
break;
case TF_COND_STEALTHED:
case TF_COND_STEALTHED_USER_BUFF:
OnAddStealthed();
break;
case TF_COND_INVULNERABLE:
case TF_COND_INVULNERABLE_USER_BUFF:
case TF_COND_INVULNERABLE_CARD_EFFECT:
OnAddInvulnerable();
break;
case TF_COND_TELEPORTED:
OnAddTeleported();
break;
case TF_COND_BURNING:
OnAddBurning();
break;
case TF_COND_CRITBOOSTED:
Assert( !"TF_COND_CRITBOOSTED should be handled by the condition list!" );
break;
case TF_COND_CRITBOOSTED_DEMO_CHARGE:
OnAddDemoCharge();
break;
// First blood falls through on purpose.
case TF_COND_CRITBOOSTED_FIRST_BLOOD:
SetFirstBloodBoosted( true );
case TF_COND_CRITBOOSTED_PUMPKIN:
case TF_COND_CRITBOOSTED_USER_BUFF:
case TF_COND_CRITBOOSTED_BONUS_TIME:
case TF_COND_CRITBOOSTED_CTF_CAPTURE:
case TF_COND_CRITBOOSTED_ON_KILL:
case TF_COND_CRITBOOSTED_RAGE_BUFF:
case TF_COND_SNIPERCHARGE_RAGE_BUFF:
case TF_COND_CRITBOOSTED_CARD_EFFECT:
case TF_COND_CRITBOOSTED_RUNE_TEMP:
OnAddCritBoost();
break;
case TF_COND_SODAPOPPER_HYPE:
OnAddSodaPopperHype();
break;
case TF_COND_DISGUISING:
OnAddDisguising();
break;
case TF_COND_DISGUISED:
OnAddDisguised();
break;
case TF_COND_URINE:
OnAddUrine();
break;
case TF_COND_MARKEDFORDEATH:
OnAddMarkedForDeath();
break;
case TF_COND_BLEEDING:
OnAddBleeding();
break;
case TF_COND_TAUNTING:
OnAddTaunting();
break;
case TF_COND_STUNNED:
OnAddStunned();
break;
case TF_COND_PHASE:
OnAddPhase();
break;
case TF_COND_OFFENSEBUFF:
OnAddOffenseBuff();
break;
case TF_COND_DEFENSEBUFF:
case TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK:
case TF_COND_DEFENSEBUFF_HIGH:
OnAddDefenseBuff();
break;
case TF_COND_REGENONDAMAGEBUFF:
OnAddOffenseHealthRegenBuff();
break;
case TF_COND_NOHEALINGDAMAGEBUFF:
OnAddNoHealingDamageBuff();
break;
case TF_COND_SHIELD_CHARGE:
OnAddShieldCharge();
break;
case TF_COND_DEMO_BUFF:
OnAddDemoBuff();
break;
case TF_COND_ENERGY_BUFF:
OnAddEnergyDrinkBuff();
break;
case TF_COND_RADIUSHEAL:
OnAddRadiusHeal();
break;
case TF_COND_MEGAHEAL:
OnAddMegaHeal();
break;
case TF_COND_MAD_MILK:
OnAddMadMilk();
break;
case TF_COND_SPEED_BOOST: OnAddSpeedBoost( false ); break;
#ifdef STAGING_ONLY //STAGING_ENGY
case TF_COND_NO_COMBAT_SPEED_BOOST: OnAddSpeedBoost( true ); break;
#endif
case TF_COND_SAPPED:
OnAddSapped();
break;
case TF_COND_REPROGRAMMED:
OnAddReprogrammed();
break;
case TF_COND_PASSTIME_PENALTY_DEBUFF:
case TF_COND_MARKEDFORDEATH_SILENT:
OnAddMarkedForDeathSilent();
break;
case TF_COND_DISGUISED_AS_DISPENSER:
OnAddDisguisedAsDispenser();
break;
case TF_COND_HALLOWEEN_BOMB_HEAD:
OnAddHalloweenBombHead();
break;
case TF_COND_HALLOWEEN_THRILLER:
OnAddHalloweenThriller();
break;
case TF_COND_RADIUSHEAL_ON_DAMAGE:
OnAddRadiusHealOnDamage();
break;
case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
OnAddMedEffectUberBulletResist();
break;
case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
OnAddMedEffectUberBlastResist();
break;
case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
OnAddMedEffectUberFireResist();
break;
case TF_COND_MEDIGUN_SMALL_BULLET_RESIST:
OnAddMedEffectSmallBulletResist();
break;
case TF_COND_MEDIGUN_SMALL_BLAST_RESIST:
OnAddMedEffectSmallBlastResist();
break;
case TF_COND_MEDIGUN_SMALL_FIRE_RESIST:
OnAddMedEffectSmallFireResist();
break;
case TF_COND_STEALTHED_USER_BUFF_FADING:
OnAddStealthedUserBuffFade();
break;
case TF_COND_BULLET_IMMUNE:
OnAddBulletImmune();
break;
case TF_COND_BLAST_IMMUNE:
OnAddBlastImmune();
break;
case TF_COND_FIRE_IMMUNE:
OnAddFireImmune();
break;
case TF_COND_MVM_BOT_STUN_RADIOWAVE:
OnAddMVMBotRadiowave();
break;
case TF_COND_HALLOWEEN_SPEED_BOOST:
OnAddHalloweenSpeedBoost();
break;
case TF_COND_HALLOWEEN_QUICK_HEAL:
OnAddHalloweenQuickHeal();
break;
case TF_COND_HALLOWEEN_GIANT:
OnAddHalloweenGiant();
break;
case TF_COND_HALLOWEEN_TINY:
OnAddHalloweenTiny();
break;
case TF_COND_HALLOWEEN_GHOST_MODE:
OnAddHalloweenGhostMode();
break;
case TF_COND_PARACHUTE_DEPLOYED:
OnAddCondParachute();
break;
case TF_COND_HALLOWEEN_KART_DASH:
OnAddHalloweenKartDash();
break;
case TF_COND_HALLOWEEN_KART:
OnAddHalloweenKart();
break;
case TF_COND_BALLOON_HEAD:
OnAddBalloonHead();
break;
case TF_COND_MELEE_ONLY:
OnAddMeleeOnly();
break;
case TF_COND_SWIMMING_CURSE:
OnAddSwimmingCurse();
break;
case TF_COND_HALLOWEEN_KART_CAGE:
OnAddHalloweenKartCage();
break;
case TF_COND_RUNE_RESIST:
OnAddRuneResist();
break;
case TF_COND_GRAPPLINGHOOK_LATCHED:
OnAddGrapplingHookLatched();
break;
case TF_COND_PASSTIME_INTERCEPTION:
OnAddPasstimeInterception();
break;
case TF_COND_RUNE_PLAGUE:
OnAddRunePlague();
break;
case TF_COND_PLAGUE:
OnAddPlague();
break;
case TF_COND_PURGATORY:
OnAddInPurgatory();
break;
case TF_COND_COMPETITIVE_WINNER:
OnAddCompetitiveWinner();
break;
case TF_COND_COMPETITIVE_LOSER:
OnAddCompetitiveLoser();
break;
#ifdef STAGING_ONLY
case TF_COND_SPY_CLASS_STEAL:
OnAddCondSpyClassSteal();
break;
case TF_COND_TRANQ_MARKED:
OnAddTranqMark();
break;
/*
case TF_COND_SPACE_GRAVITY:
OnAddSpaceGravity();
break;
case TF_COND_SELF_CONC:
OnAddSelfConc();
break;
*/
case TF_COND_ROCKETPACK:
OnAddRocketPack();
break;
case TF_COND_STEALTHED_PHASE:
OnAddStealthedPhase();
break;
case TF_COND_CLIP_OVERLOAD:
OnAddClipOverload();
break;
case TF_COND_KING_BUFFED:
OnAddKingBuff();
break;
#endif // STAGING_ONLY
default:
break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Called on both client and server. Server when we remove the bit,
// and client when it receives the new cond bits and finds one removed
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnConditionRemoved( ETFCond eCond )
{
switch( eCond )
{
case TF_COND_ZOOMED:
OnRemoveZoomed();
break;
case TF_COND_BURNING:
OnRemoveBurning();
break;
case TF_COND_CRITBOOSTED:
Assert( !"TF_COND_CRITBOOSTED should be handled by the condition list!" );
break;
case TF_COND_CRITBOOSTED_DEMO_CHARGE:
OnRemoveDemoCharge();
break;
// First blood falls through on purpose.
case TF_COND_CRITBOOSTED_FIRST_BLOOD:
SetFirstBloodBoosted( false );
case TF_COND_CRITBOOSTED_PUMPKIN:
case TF_COND_CRITBOOSTED_USER_BUFF:
case TF_COND_CRITBOOSTED_BONUS_TIME:
case TF_COND_CRITBOOSTED_CTF_CAPTURE:
case TF_COND_CRITBOOSTED_ON_KILL:
case TF_COND_CRITBOOSTED_RAGE_BUFF:
case TF_COND_SNIPERCHARGE_RAGE_BUFF:
case TF_COND_CRITBOOSTED_CARD_EFFECT:
case TF_COND_CRITBOOSTED_RUNE_TEMP:
OnRemoveCritBoost();
break;
case TF_COND_SODAPOPPER_HYPE:
OnRemoveSodaPopperHype();
break;
case TF_COND_TMPDAMAGEBONUS:
OnRemoveTmpDamageBonus();
break;
case TF_COND_HEALTH_BUFF:
#ifdef GAME_DLL
m_flHealFraction = 0;
m_flDisguiseHealFraction = 0;
#endif
break;
case TF_COND_HEALTH_OVERHEALED:
OnRemoveOverhealed();
break;
case TF_COND_FEIGN_DEATH:
OnRemoveFeignDeath();
break;
case TF_COND_STEALTHED:
case TF_COND_STEALTHED_USER_BUFF:
OnRemoveStealthed();
break;
case TF_COND_DISGUISED:
OnRemoveDisguised();
break;
case TF_COND_DISGUISING:
OnRemoveDisguising();
break;
case TF_COND_INVULNERABLE:
case TF_COND_INVULNERABLE_USER_BUFF:
case TF_COND_INVULNERABLE_CARD_EFFECT:
OnRemoveInvulnerable();
break;
case TF_COND_TELEPORTED:
OnRemoveTeleported();
break;
case TF_COND_STUNNED:
OnRemoveStunned();
break;
case TF_COND_PHASE:
OnRemovePhase();
break;
case TF_COND_URINE:
OnRemoveUrine();
break;
case TF_COND_MARKEDFORDEATH:
OnRemoveMarkedForDeath();
break;
case TF_COND_BLEEDING:
OnRemoveBleeding();
break;
case TF_COND_INVULNERABLE_WEARINGOFF:
OnRemoveInvulnerableWearingOff();
break;
case TF_COND_OFFENSEBUFF:
OnRemoveOffenseBuff();
break;
case TF_COND_DEFENSEBUFF:
case TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK:
case TF_COND_DEFENSEBUFF_HIGH:
OnRemoveDefenseBuff();
break;
case TF_COND_REGENONDAMAGEBUFF:
OnRemoveOffenseHealthRegenBuff();
break;
case TF_COND_NOHEALINGDAMAGEBUFF:
OnRemoveNoHealingDamageBuff();
break;
case TF_COND_SHIELD_CHARGE:
OnRemoveShieldCharge();
break;
case TF_COND_DEMO_BUFF:
OnRemoveDemoBuff();
break;
case TF_COND_ENERGY_BUFF:
OnRemoveEnergyDrinkBuff();
break;
case TF_COND_RADIUSHEAL:
OnRemoveRadiusHeal();
break;
case TF_COND_MEGAHEAL:
OnRemoveMegaHeal();
break;
case TF_COND_MAD_MILK:
OnRemoveMadMilk();
break;
case TF_COND_TAUNTING:
OnRemoveTaunting();
break;
case TF_COND_SPEED_BOOST: OnRemoveSpeedBoost( false ); break;
#ifdef STAGING_ONLY
case TF_COND_NO_COMBAT_SPEED_BOOST: OnRemoveSpeedBoost( true ); break;
#endif
case TF_COND_SAPPED:
OnRemoveSapped();
break;
case TF_COND_REPROGRAMMED:
OnRemoveReprogrammed();
break;
case TF_COND_PASSTIME_PENALTY_DEBUFF:
case TF_COND_MARKEDFORDEATH_SILENT:
OnRemoveMarkedForDeathSilent();
break;
case TF_COND_DISGUISED_AS_DISPENSER:
OnRemoveDisguisedAsDispenser();
break;
case TF_COND_HALLOWEEN_BOMB_HEAD:
OnRemoveHalloweenBombHead();
break;
case TF_COND_HALLOWEEN_THRILLER:
OnRemoveHalloweenThriller();
break;
case TF_COND_RADIUSHEAL_ON_DAMAGE:
OnRemoveRadiusHealOnDamage();
break;
case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
OnRemoveMedEffectUberBulletResist();
break;
case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
OnRemoveMedEffectUberBlastResist();
break;
case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
OnRemoveMedEffectUberFireResist();
break;
case TF_COND_MEDIGUN_SMALL_BULLET_RESIST:
OnRemoveMedEffectSmallBulletResist();
break;
case TF_COND_MEDIGUN_SMALL_BLAST_RESIST:
OnRemoveMedEffectSmallBlastResist();
break;
case TF_COND_MEDIGUN_SMALL_FIRE_RESIST:
OnRemoveMedEffectSmallFireResist();
break;
case TF_COND_STEALTHED_USER_BUFF_FADING:
OnRemoveStealthedUserBuffFade();
break;
case TF_COND_BULLET_IMMUNE:
OnRemoveBulletImmune();
break;
case TF_COND_BLAST_IMMUNE:
OnRemoveBlastImmune();
break;
case TF_COND_FIRE_IMMUNE:
OnRemoveFireImmune();
break;
case TF_COND_MVM_BOT_STUN_RADIOWAVE:
OnRemoveMVMBotRadiowave();
break;
case TF_COND_HALLOWEEN_SPEED_BOOST:
OnRemoveHalloweenSpeedBoost();
break;
case TF_COND_HALLOWEEN_QUICK_HEAL:
OnRemoveHalloweenQuickHeal();
break;
case TF_COND_HALLOWEEN_GIANT:
OnRemoveHalloweenGiant();
break;
case TF_COND_HALLOWEEN_TINY:
OnRemoveHalloweenTiny();
break;
case TF_COND_HALLOWEEN_GHOST_MODE:
OnRemoveHalloweenGhostMode();
break;
case TF_COND_PARACHUTE_DEPLOYED:
OnRemoveCondParachute();
break;
case TF_COND_HALLOWEEN_KART_DASH:
OnRemoveHalloweenKartDash();
break;
case TF_COND_HALLOWEEN_KART:
OnRemoveHalloweenKart();
break;
case TF_COND_BALLOON_HEAD:
OnRemoveBalloonHead();
break;
case TF_COND_MELEE_ONLY:
OnRemoveMeleeOnly();
break;
case TF_COND_SWIMMING_CURSE:
OnRemoveSwimmingCurse();
case TF_COND_HALLOWEEN_KART_CAGE:
OnRemoveHalloweenKartCage();
break;
case TF_COND_RUNE_RESIST:
OnRemoveRuneResist();
break;
case TF_COND_GRAPPLINGHOOK_LATCHED:
OnRemoveGrapplingHookLatched();
break;
case TF_COND_PASSTIME_INTERCEPTION:
OnRemovePasstimeInterception();
break;
case TF_COND_RUNE_PLAGUE:
OnRemoveRunePlague();
break;
case TF_COND_PLAGUE:
OnRemovePlague();
break;
case TF_COND_PURGATORY:
OnRemoveInPurgatory();
break;
case TF_COND_RUNE_KING:
OnRemoveRuneKing();
break;
case TF_COND_KING_BUFFED:
OnRemoveKingBuff();
break;
case TF_COND_RUNE_SUPERNOVA:
OnRemoveRuneSupernova();
break;
case TF_COND_COMPETITIVE_WINNER:
OnRemoveCompetitiveWinner();
break;
case TF_COND_COMPETITIVE_LOSER:
OnRemoveCompetitiveLoser();
break;
#ifdef STAGING_ONLY
case TF_COND_SPY_CLASS_STEAL:
OnRemoveCondSpyClassSteal();
break;
case TF_COND_TRANQ_MARKED:
OnRemoveTranqMark();
break;
/*
case TF_COND_SPACE_GRAVITY:
OnRemoveSpaceGravity();
break;
case TF_COND_SELF_CONC:
OnRemoveSelfConc();
break;
*/
case TF_COND_ROCKETPACK:
OnRemoveRocketPack();
break;
case TF_COND_STEALTHED_PHASE:
OnRemoveStealthedPhase();
break;
case TF_COND_CLIP_OVERLOAD:
OnRemoveClipOverload();
break;
#endif // STAGING_ONLY
default:
break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns the overheal bonus the specified healer is capable of buffing to
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetMaxBuffedHealth( bool bIgnoreAttributes /*= false*/, bool bIgnoreHealthOverMax /*= false*/ )
{
// Find the healer we have who's providing the most overheal
float flBoostMax = m_pOuter->GetMaxHealthForBuffing() * tf_max_health_boost.GetFloat();
#ifdef GAME_DLL
if ( !bIgnoreAttributes )
{
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
float flOverheal = m_pOuter->GetMaxHealthForBuffing() * m_aHealers[i].flOverhealBonus;
if ( flOverheal > flBoostMax )
{
flBoostMax = flOverheal;
}
}
}
#endif
int iRoundDown = floor( flBoostMax / 5 );
iRoundDown = iRoundDown * 5;
if ( !bIgnoreHealthOverMax )
{
// Don't allow overheal total to be less than the buffable + unbuffable max health or the current health
int nBoostMin = MAX( m_pOuter->GetMaxHealth(), m_pOuter->GetHealth() );
if ( iRoundDown < nBoostMin )
{
iRoundDown = nBoostMin;
}
}
return iRoundDown;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetDisguiseMaxBuffedHealth( bool bIgnoreAttributes /*= false*/, bool bIgnoreHealthOverMax /*= false*/ )
{
// Find the healer we have who's providing the most overheal
float flBoostMax = GetDisguiseMaxHealth() * tf_max_health_boost.GetFloat();
#ifdef GAME_DLL
if ( !bIgnoreAttributes )
{
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
float flOverheal = GetDisguiseMaxHealth() * m_aHealers[i].flOverhealBonus;
if ( flOverheal > flBoostMax )
{
flBoostMax = flOverheal;
}
}
}
#endif
int iRoundDown = floor( flBoostMax / 5 );
iRoundDown = iRoundDown * 5;
if ( !bIgnoreHealthOverMax )
{
// Don't allow overheal total to be less than the buffable + unbuffable max health or the current health
int nBoostMin = MAX(GetDisguiseMaxHealth(), GetDisguiseHealth() );
if ( iRoundDown < nBoostMin )
{
iRoundDown = nBoostMin;
}
}
return iRoundDown;
}
//-----------------------------------------------------------------------------
bool ShouldRemoveConditionOnTimeout( ETFCond eCond )
{
switch( eCond )
{
case TF_COND_HALLOWEEN_GHOST_MODE:
case TF_COND_HALLOWEEN_IN_HELL:
case TF_COND_HALLOWEEN_KART:
break;
return !TFGameRules()->ArePlayersInHell();
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Runs SERVER SIDE only Condition Think
// If a player needs something to be updated no matter what do it here (invul, etc).
//-----------------------------------------------------------------------------
void CTFPlayerShared::ConditionGameRulesThink( void )
{
#ifdef GAME_DLL
m_ConditionList.ServerThink();
if ( m_flNextCritUpdate < gpGlobals->curtime )
{
UpdateCritMult();
m_flNextCritUpdate = gpGlobals->curtime + 0.5;
}
for ( int i=0; i < TF_COND_LAST; ++i )
{
// if we're in this condition and it's not already being handled by the condition list
if ( InCond( (ETFCond)i ) && ((i >= 32) || !m_ConditionList.InCond( (ETFCond)i )) )
{
// Ignore permanent conditions
if ( m_ConditionData[i].m_flExpireTime != PERMANENT_CONDITION )
{
float flReduction = gpGlobals->frametime;
// If we're being healed, we reduce bad conditions faster
if ( ConditionExpiresFast( (ETFCond)i) && m_aHealers.Count() > 0 )
{
if ( i == TF_COND_URINE )
{
flReduction += (m_aHealers.Count() * flReduction);
}
else
{
flReduction += (m_aHealers.Count() * flReduction * 4);
}
}
m_ConditionData[i].m_flExpireTime = MAX( m_ConditionData[i].m_flExpireTime - flReduction, 0 );
if ( m_ConditionData[i].m_flExpireTime == 0 )
{
RemoveCond( (ETFCond)i );
}
}
else
{
#if !defined( DEBUG )
// Prevent hacked usercommand exploits
if ( m_pOuter->GetTimeSinceLastUserCommand() > 5.f || m_pOuter->GetTimeSinceLastThink() > 5.f )
{
ETFCond eCond = (ETFCond)i;
if ( GetCarryingRuneType() != RUNE_NONE )
{
m_pOuter->DropRune();
}
if ( ShouldRemoveConditionOnTimeout( eCond ) )
{
RemoveCond( eCond );
// Reset active weapon to prevent stale-state bugs
CTFWeaponBase *pTFWeapon = m_pOuter->GetActiveTFWeapon();
if ( pTFWeapon )
{
pTFWeapon->WeaponReset();
}
m_pOuter->TeamFortress_SetSpeed();
}
}
#endif
}
}
}
// Our health will only decay ( from being medic buffed ) if we are not being healed by a medic
// Dispensers can give us the TF_COND_HEALTH_BUFF, but will not maintain or give us health above 100%s
bool bDecayHealth = true;
bool bDecayDisguiseHealth = true;
// If we're being healed, heal ourselves
if ( InCond( TF_COND_HEALTH_BUFF ) )
{
// Heal faster if we haven't been in combat for a while
float flTimeSinceDamage = gpGlobals->curtime - m_pOuter->GetLastDamageReceivedTime();
float flScale = RemapValClamped( flTimeSinceDamage, 10, 15, 1.0, 3.0 );
float flAttribModScale = 1.0;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flAttribModScale, mult_health_fromhealers );
float flCurOverheal = (float)m_pOuter->GetHealth() / (float)m_pOuter->GetMaxHealth();
if ( flCurOverheal > 1.0f )
{
// If they're over their max health the overheal calculation is relative to the max buffable amount scale
float flMaxHealthForBuffing = m_pOuter->GetMaxHealthForBuffing();
float flBuffableRangeHealth = m_pOuter->GetHealth() - ( m_pOuter->GetMaxHealth() - flMaxHealthForBuffing );
flCurOverheal = flBuffableRangeHealth / flMaxHealthForBuffing;
}
float flCurDisguiseOverheal = ( GetDisguiseMaxHealth() != 0 ) ? ( (float)GetDisguiseHealth() / (float)GetDisguiseMaxHealth() ) : ( flCurOverheal );
float fTotalHealAmount = 0.0f;
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
Assert( m_aHealers[i].pHealer );
float flPerHealerAttribModScale = 1.f;
// Check if the healer has an attribute that modifies their overheal rate
if( flCurOverheal > 1.f && !m_aHealers[i].bDispenserHeal )
{
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_aHealers[i].pHealer, flPerHealerAttribModScale, overheal_fill_rate );
}
bool bHealDisguise = InCond( TF_COND_DISGUISED );
bool bHealActual = true;
// dispensers heal cloak
if ( m_aHealers[i].bDispenserHeal )
{
AddToSpyCloakMeter( gpGlobals->frametime * m_aHealers[i].flAmount );
}
// Don't heal over the healer's overheal bonus
if ( flCurOverheal >= m_aHealers[i].flOverhealBonus )
{
bHealActual = false;
}
// Same overheal check, but for fake health
if ( InCond( TF_COND_DISGUISED ) && flCurDisguiseOverheal >= m_aHealers[i].flOverhealBonus )
{
// Fake over-heal
bHealDisguise = false;
}
CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer );
if ( !bHealActual && !bHealDisguise )
{
if ( pTFHealer )
{
// Quick fix never lets health decay, even when they're at or above max overheal
CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( pTFHealer->GetActiveTFWeapon() );
if ( pMedigun && pMedigun->GetMedigunType() == MEDIGUN_QUICKFIX )
{
bDecayHealth = false;
bDecayDisguiseHealth = false;
}
}
continue;
}
// Being healed by a medigun, don't decay our health
if ( bHealActual )
{
bDecayHealth = false;
}
if ( bHealDisguise )
{
bDecayDisguiseHealth = false;
}
// What we multiply the heal amount by (can be changed by conditions or items).
float flHealAmountMult = 1.0f;
// Quick-Fix uber
if ( InCond( TF_COND_MEGAHEAL ) )
{
flHealAmountMult = 3.0f;
}
flScale *= flHealAmountMult;
// Dispensers heal at a constant rate
if ( m_aHealers[i].bDispenserHeal )
{
// Dispensers heal at a slower rate, but ignore flScale
if ( bHealActual )
{
float flDispenserFraction = gpGlobals->frametime * m_aHealers[i].flAmount * flAttribModScale;
m_flHealFraction += flDispenserFraction;
// track how much this healer has actually done so far
m_aHealers[i].flHealAccum += clamp( flDispenserFraction, 0.f, (float) GetMaxBuffedHealth() - m_pOuter->GetHealth() );
}
if ( bHealDisguise )
{
m_flDisguiseHealFraction += gpGlobals->frametime * m_aHealers[i].flAmount * flAttribModScale;
}
}
else // player heals are affected by the last damage time
{
if ( bHealActual )
{
// Scale this if needed
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flScale, mult_healing_from_medics );
m_flHealFraction += gpGlobals->frametime * m_aHealers[i].flAmount * flScale * flAttribModScale * flPerHealerAttribModScale;
}
if ( bHealDisguise )
{
m_flDisguiseHealFraction += gpGlobals->frametime * m_aHealers[i].flAmount * flScale * flAttribModScale * flPerHealerAttribModScale;
}
}
fTotalHealAmount += m_aHealers[i].flAmount;
// Keep our decay multiplier uptodate
if ( m_flBestOverhealDecayMult == -1 || m_aHealers[i].flOverhealDecayMult < m_flBestOverhealDecayMult )
{
m_flBestOverhealDecayMult = m_aHealers[i].flOverhealDecayMult;
}
}
if ( InCond( TF_COND_HEALING_DEBUFF ) )
{
m_flHealFraction *= 0.75f;
}
int nHealthToAdd = (int)m_flHealFraction;
int nDisguiseHealthToAdd = (int)m_flDisguiseHealFraction;
if ( nHealthToAdd > 0 || nDisguiseHealthToAdd > 0 )
{
if ( nHealthToAdd > 0 )
{
m_flHealFraction -= nHealthToAdd;
}
if ( nDisguiseHealthToAdd > 0 )
{
m_flDisguiseHealFraction -= nDisguiseHealthToAdd;
}
int iBoostMax = GetMaxBuffedHealth();
if ( InCond( TF_COND_DISGUISED ) )
{
// Separate cap for disguised health
int nFakeHealthToAdd = clamp( nDisguiseHealthToAdd, 0, GetDisguiseMaxBuffedHealth() - m_iDisguiseHealth );
m_iDisguiseHealth += nFakeHealthToAdd;
}
// Track health prior to healing
int nPrevHealth = m_pOuter->GetHealth();
// Cap it to the max we'll boost a player's health
nHealthToAdd = clamp( nHealthToAdd, 0, iBoostMax - m_pOuter->GetHealth() );
m_pOuter->TakeHealth( nHealthToAdd, DMG_IGNORE_MAXHEALTH | DMG_IGNORE_DEBUFFS );
m_pOuter->AdjustDrownDmg( -1.0 * nHealthToAdd ); // subtract this from the drowndmg in case they're drowning and being healed at the same time
// split up total healing based on the amount each healer contributes
if ( fTotalHealAmount > 0 )
{
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
Assert( m_aHealers[i].pHealScorer );
Assert( m_aHealers[i].pHealer );
if ( m_aHealers[i].pHealScorer.IsValid() && m_aHealers[i].pHealer.IsValid() )
{
CBaseEntity *pHealer = m_aHealers[i].pHealer;
float flHealAmount = nHealthToAdd * ( m_aHealers[i].flAmount / fTotalHealAmount );
if ( pHealer && IsAlly( pHealer ) )
{
CTFPlayer *pHealScorer = ToTFPlayer( m_aHealers[i].pHealScorer );
if ( pHealScorer )
{
// Don't report healing when we're close to the buff cap and haven't taken damage recently.
// This avoids sending bogus heal stats while maintaining our max overheal. Ideally we
// wouldn't decay in this scenario, but that would be a risky change.
if ( iBoostMax - nPrevHealth > 1 || gpGlobals->curtime - m_pOuter->GetLastDamageReceivedTime() <= 1.f )
{
CTF_GameStats.Event_PlayerHealedOther( pHealScorer, flHealAmount );
}
// Add this to the one-second-healing counter
m_aHealers[i].flHealedLastSecond += flHealAmount;
HandleRageGain( m_pOuter, kRageBuffFlag_OnMedicHealingReceived, flHealAmount / 2.f, 1.0f );
float flRage = flHealAmount;
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() &&
TFObjectiveResource() && TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
{
flRage = Max( flHealAmount, 10.f );
}
HandleRageGain( pHealScorer, kRageBuffFlag_OnHeal, flRage, 1.0f );
// If it's been one second, or we know healing beyond this point will be overheal, generate an event
if ( ( m_flHealedPerSecondTimer <= gpGlobals->curtime || m_pOuter->GetHealth() >= m_pOuter->GetMaxHealth() )
&& m_aHealers[i].flHealedLastSecond > 1 )
{
// Make sure this isn't pure overheal
if ( m_pOuter->GetHealth() - m_aHealers[i].flHealedLastSecond < m_pOuter->GetMaxHealth() )
{
float flOverHeal = m_pOuter->GetHealth() - m_pOuter->GetMaxHealth();
if ( flOverHeal > 0 )
{
m_aHealers[i].flHealedLastSecond -= flOverHeal;
}
// TEST THIS
// Give the medic some uber if it is from their (AoE heal) which has no overheal
if ( m_aHealers[i].flOverhealBonus <= 1.0f )
{
// Give a litte bit of uber based on actual healing
// Give them a little bit of Uber
CWeaponMedigun *pMedigun = static_cast<CWeaponMedigun *>( pHealScorer->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
if ( pMedigun )
{
// On Mediguns, per frame, the amount of uber added is based on
// Default heal rate is 24per second, we scale based on that and frametime
pMedigun->AddCharge( ( m_aHealers[i].flHealedLastSecond / 24.0f ) * gpGlobals->frametime * 0.33f );
}
}
IGameEvent * event = gameeventmanager->CreateEvent( "player_healed" );
if ( event )
{
// HLTV event priority, not transmitted
event->SetInt( "priority", 1 );
// Healed by another player.
event->SetInt( "patient", m_pOuter->GetUserID() );
event->SetInt( "healer", pHealScorer->GetUserID() );
event->SetInt( "amount", m_aHealers[i].flHealedLastSecond );
gameeventmanager->FireEvent( event );
}
// Can we figure out which item is doing this healing?
if ( pHealScorer )
{
// Can be Mediguns or anything that gives off 'heal' buff like amputator aoe heal
EconEntity_OnOwnerKillEaterEvent_Batched( pHealScorer->GetActiveTFWeapon(), pHealScorer, m_pOuter, kKillEaterEvent_AllyHealingDone, m_aHealers[i].flHealedLastSecond );
}
}
m_aHealers[i].flHealedLastSecond = 0;
m_flHealedPerSecondTimer = gpGlobals->curtime + 1.0f;
}
}
}
else
{
CTF_GameStats.Event_PlayerLeachedHealth( m_pOuter, m_aHealers[i].bDispenserHeal, flHealAmount );
}
}
}
}
}
if ( InCond( TF_COND_BURNING ) )
{
// Reduce the duration of this burn
float flReduction = 2; // ( flReduction + 1 ) x faster reduction
m_flFlameRemoveTime -= flReduction * gpGlobals->frametime;
}
if ( InCond( TF_COND_BLEEDING ) )
{
// Reduce the duration of this bleeding
float flReduction = 2; // ( flReduction + 1 ) x faster reduction
FOR_EACH_VEC( m_PlayerBleeds, i )
{
m_PlayerBleeds[i].flBleedingRemoveTime -= flReduction * gpGlobals->frametime;
}
}
}
if ( !InCond( TF_COND_HEALTH_OVERHEALED ) && m_pOuter->GetHealth() > ( m_pOuter->GetMaxHealth() - m_pOuter->GetRuneHealthBonus() ) )
{
AddCond( TF_COND_HEALTH_OVERHEALED, PERMANENT_CONDITION );
}
else if ( InCond( TF_COND_HEALTH_OVERHEALED ) && m_pOuter->GetHealth() <= ( m_pOuter->GetMaxHealth() - m_pOuter->GetRuneHealthBonus() ) )
{
RemoveCond( TF_COND_HEALTH_OVERHEALED );
}
if ( bDecayHealth )
{
float flOverheal = GetMaxBuffedHealth( false, true );
// If we're not being buffed, our health drains back to our max
if ( m_pOuter->GetHealth() > m_pOuter->GetMaxHealth() )
{
// Items exist that get us over max health, without ever being healed, in which case our m_flBestOverhealDecayMult will still be -1.
float flDrainMult = (m_flBestOverhealDecayMult == -1) ? 1.0 : m_flBestOverhealDecayMult;
float flBoostMaxAmount = flOverheal - m_pOuter->GetMaxHealth();
float flDrain = flBoostMaxAmount / (tf_boost_drain_time.GetFloat() * flDrainMult);
m_flHealFraction += (gpGlobals->frametime * flDrain);
int nHealthToDrain = (int)m_flHealFraction;
if ( nHealthToDrain > 0 )
{
m_flHealFraction -= nHealthToDrain;
// Manually subtract the health so we don't generate pain sounds / etc
m_pOuter->m_iHealth -= nHealthToDrain;
}
}
else if ( m_flBestOverhealDecayMult != -1 )
{
m_flBestOverhealDecayMult = -1;
}
}
if ( bDecayDisguiseHealth )
{
float flOverheal = GetDisguiseMaxBuffedHealth( false, true );
if ( InCond( TF_COND_DISGUISED ) && (GetDisguiseHealth() > GetDisguiseMaxHealth()) )
{
// Items exist that get us over max health, without ever being healed, in which case our m_flBestOverhealDecayMult will still be -1.
float flDrainMult = (m_flBestOverhealDecayMult == -1) ? 1.0 : m_flBestOverhealDecayMult;
float flBoostMaxAmount = flOverheal - GetDisguiseMaxHealth();
float flDrain = (flBoostMaxAmount / tf_boost_drain_time.GetFloat()) * flDrainMult;
m_flDisguiseHealFraction += (gpGlobals->frametime * flDrain);
int nHealthToDrain = (int)m_flDisguiseHealFraction;
if ( nHealthToDrain > 0 )
{
m_flDisguiseHealFraction -= nHealthToDrain;
// Reduce our fake disguised health by roughly the same amount
m_iDisguiseHealth -= nHealthToDrain;
}
}
}
// Taunt
if ( InCond( TF_COND_TAUNTING ) )
{
if ( m_pOuter->IsAllowedToRemoveTaunt() && gpGlobals->curtime > m_pOuter->GetTauntRemoveTime() )
{
RemoveCond( TF_COND_TAUNTING );
}
}
if ( InCond( TF_COND_BURNING ) && !m_pOuter->m_bInPowerPlay )
{
if ( TFGameRules() && TFGameRules()->IsTruceActive() && m_hBurnAttacker && m_hBurnAttacker->IsTruceValidForEnt() )
{
RemoveCond( TF_COND_BURNING );
}
else if ( gpGlobals->curtime > m_flFlameRemoveTime || m_pOuter->GetWaterLevel() >= WL_Waist )
{
// If we're underwater, put the fire out
if ( m_pOuter->GetWaterLevel() >= WL_Waist )
{
// General achievement for jumping into water while you're on fire
m_pOuter->AwardAchievement( ACHIEVEMENT_TF_FIRE_WATERJUMP );
// Pyro achievement for forcing players into water
if ( m_hBurnAttacker )
{
m_hBurnAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_FORCE_WATERJUMP );
}
}
RemoveCond( TF_COND_BURNING );
if ( InCond( TF_COND_HEALTH_BUFF ) )
{
// one or more players is healing us, send a "player_extinguished" event. We
// need to send one for each player who's healing us.
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
Assert( m_aHealers[i].pHealer );
if ( m_aHealers[i].bDispenserHeal )
{
CObjectDispenser *pDispenser = dynamic_cast<CObjectDispenser*>( m_aHealers[i].pHealer.Get() );
if ( pDispenser )
{
CTFPlayer *pTFPlayer = pDispenser->GetBuilder();
if ( pTFPlayer )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"dispenser\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n",
pTFPlayer->GetPlayerName(),
pTFPlayer->GetUserID(),
pTFPlayer->GetNetworkIDString(),
pTFPlayer->GetTeam()->GetName(),
m_pOuter->GetPlayerName(),
m_pOuter->GetUserID(),
m_pOuter->GetNetworkIDString(),
m_pOuter->GetTeam()->GetName(),
(int)m_aHealers[i].pHealer->GetAbsOrigin().x,
(int)m_aHealers[i].pHealer->GetAbsOrigin().y,
(int)m_aHealers[i].pHealer->GetAbsOrigin().z,
(int)m_pOuter->GetAbsOrigin().x,
(int)m_pOuter->GetAbsOrigin().y,
(int)m_pOuter->GetAbsOrigin().z );
}
}
// continue;
}
EHANDLE pHealer = m_aHealers[i].pHealer;
if ( m_aHealers[i].bDispenserHeal || !pHealer || !pHealer->IsPlayer() )
pHealer = m_aHealers[i].pHealScorer;
if ( !pHealer )
continue;
CTFPlayer *pTFPlayer = ToTFPlayer( pHealer );
if ( pTFPlayer && !m_aHealers[i].bDispenserHeal )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n",
pTFPlayer->GetPlayerName(),
pTFPlayer->GetUserID(),
pTFPlayer->GetNetworkIDString(),
pTFPlayer->GetTeam()->GetName(),
m_pOuter->GetPlayerName(),
m_pOuter->GetUserID(),
m_pOuter->GetNetworkIDString(),
m_pOuter->GetTeam()->GetName(),
( pTFPlayer->GetActiveTFWeapon() ) ? pTFPlayer->GetActiveTFWeapon()->GetName() : "tf_weapon_medigun",
(int)pTFPlayer->GetAbsOrigin().x,
(int)pTFPlayer->GetAbsOrigin().y,
(int)pTFPlayer->GetAbsOrigin().z,
(int)m_pOuter->GetAbsOrigin().x,
(int)m_pOuter->GetAbsOrigin().y,
(int)m_pOuter->GetAbsOrigin().z );
}
// Tell the clients involved
CRecipientFilter involved_filter;
CBasePlayer *pBasePlayerHealer = ToBasePlayer( pHealer );
if ( pBasePlayerHealer )
{
involved_filter.AddRecipient( pBasePlayerHealer );
}
involved_filter.AddRecipient( m_pOuter );
UserMessageBegin( involved_filter, "PlayerExtinguished" );
WRITE_BYTE( pHealer->entindex() );
WRITE_BYTE( m_pOuter->entindex() );
MessageEnd();
IGameEvent *event = gameeventmanager->CreateEvent( "player_extinguished" );
if ( event )
{
event->SetInt( "victim", m_pOuter->entindex() );
event->SetInt( "healer", pHealer->entindex() );
gameeventmanager->FireEvent( event, true );
}
}
}
}
else if ( ( gpGlobals->curtime >= m_flFlameBurnTime ) && ( TF_CLASS_PYRO != m_pOuter->GetPlayerClass()->GetClassIndex() ) )
{
// Burn the player (if not pyro, who does not take persistent burning damage)
float flBurnDamage = TF_BURNING_DMG;
int nKillType = TF_DMG_CUSTOM_BURNING;
if ( m_hBurnWeapon )
{
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hBurnWeapon, flBurnDamage, mult_wpn_burndmg );
if ( m_hBurnWeapon.Get()->GetWeaponID() == TF_WEAPON_FLAREGUN )
{
nKillType = TF_DMG_CUSTOM_BURNING_FLARE;
}
else if ( m_hBurnWeapon.Get()->GetWeaponID() == TF_WEAPON_COMPOUND_BOW )
{
nKillType = TF_DMG_CUSTOM_BURNING_ARROW;
}
}
// Halloween Spell
if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
{
int iHalloweenSpell = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_hBurnWeapon, iHalloweenSpell, halloween_green_flames );
if ( iHalloweenSpell > 0 )
{
const char *pEffectName = "halloween_burningplayer_flyingbits";
// Extra Halloween Particles
DispatchParticleEffect( pEffectName, PATTACH_ABSORIGIN_FOLLOW, m_pOuter, 0, false );
}
}
CTakeDamageInfo info( m_hBurnAttacker, m_hBurnAttacker, m_hBurnWeapon, flBurnDamage, DMG_BURN | DMG_PREVENT_PHYSICS_FORCE, nKillType );
m_pOuter->TakeDamage( info );
// Give health to attacker if they are carrying the Vampire Powerup.
if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
{
CTFPlayer *pTFAttacker = ToTFPlayer( GetConditionProvider( TF_COND_BURNING ) );
if ( pTFAttacker && pTFAttacker != m_pOuter )
{
if ( pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE )
{
pTFAttacker->TakeHealth( flBurnDamage, DMG_GENERIC );
}
}
}
m_flFlameBurnTime = gpGlobals->curtime + TF_BURNING_FREQUENCY;
}
if ( m_flNextBurningSound < gpGlobals->curtime )
{
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_ONFIRE );
m_flNextBurningSound = gpGlobals->curtime + 2.5;
}
}
// Stops the drain hack.
if ( m_pOuter->IsPlayerClass( TF_CLASS_MEDIC ) )
{
CWeaponMedigun *pWeapon = ( CWeaponMedigun* )m_pOuter->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN );
if ( pWeapon && pWeapon->IsReleasingCharge() )
{
pWeapon->DrainCharge();
}
}
TestAndExpireChargeEffect( MEDIGUN_CHARGE_INVULN );
TestAndExpireChargeEffect( MEDIGUN_CHARGE_CRITICALBOOST );
TestAndExpireChargeEffect( MEDIGUN_CHARGE_MEGAHEAL );
//TestAndExpireChargeEffect( MEDIGUN_CHARGE_BULLET_RESIST );
//TestAndExpireChargeEffect( MEDIGUN_CHARGE_BLAST_RESIST );
//TestAndExpireChargeEffect( MEDIGUN_CHARGE_FIRE_RESIST );
if ( InCond( TF_COND_STEALTHED_BLINK ) )
{
float flBlinkTime = TF_SPY_STEALTH_BLINKTIME;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flBlinkTime, cloak_blink_time_penalty );
if ( flBlinkTime < ( gpGlobals->curtime - m_flLastStealthExposeTime ) )
{
RemoveCond( TF_COND_STEALTHED_BLINK );
}
}
if ( InCond( TF_COND_FEIGN_DEATH ) )
{
if ( m_flFeignDeathEnd < gpGlobals->curtime )
{
RemoveCond( TF_COND_FEIGN_DEATH );
}
}
if ( m_pOuter->GetWaterLevel() >= WL_Waist )
{
if ( InCond( TF_COND_URINE ) )
{
// If we're underwater, wash off the urine.
RemoveCond( TF_COND_URINE );
}
if ( InCond( TF_COND_MAD_MILK ) )
{
// If we're underwater, wash off the Mad Milk.
RemoveCond( TF_COND_MAD_MILK );
}
}
if ( !InCond( TF_COND_DISGUISED ) )
{
// Remove our disguise weapon if we are ever not disguised and we have one.
RemoveDisguiseWeapon();
// also clear the disguise weapon list
m_pOuter->ClearDisguiseWeaponList();
}
if ( InCond( TF_COND_BLEEDING ) )
{
FOR_EACH_VEC_BACK( m_PlayerBleeds, i )
{
bleed_struct_t& bleed = m_PlayerBleeds[i];
if ( TFGameRules() && TFGameRules()->IsTruceActive() && bleed.hBleedingAttacker && ( bleed.hBleedingAttacker != m_pOuter ) && bleed.hBleedingAttacker->IsTruceValidForEnt() )
{
m_PlayerBleeds.FastRemove( i );
}
else if ( gpGlobals->curtime >= bleed.flBleedingRemoveTime && !bleed.bPermanentBleeding )
{
m_PlayerBleeds.FastRemove( i );
}
else if ( ( gpGlobals->curtime >= bleed.flBleedingTime ) )
{
bleed.flBleedingTime = gpGlobals->curtime + TF_BLEEDING_FREQUENCY;
CTakeDamageInfo info( bleed.hBleedingAttacker, bleed.hBleedingAttacker, bleed.hBleedingWeapon, bleed.nBleedDmg, DMG_SLASH, TF_DMG_CUSTOM_BLEEDING );
m_pOuter->TakeDamage( info );
// It's very possible we died from the take damage, which clears all our conditions
// and nukes m_PlayerBleeds. If that happens, bust out of this loop.
if( m_PlayerBleeds.Count() == 0 )
break;
}
}
if ( !m_PlayerBleeds.Count() )
{
RemoveCond( TF_COND_BLEEDING );
}
}
#ifdef STAGING_ONLY
if ( InCond( TF_COND_TRANQ_SPY_BOOST ) )
{
m_flSpyTranqBuffDuration = GetConditionDuration( TF_COND_TRANQ_SPY_BOOST );
}
else
#endif // STAGING_ONLY
{
m_flSpyTranqBuffDuration = 0;
}
if ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsPlayingRobotDestructionMode() )
{
RadiusCurrencyCollectionCheck();
}
if ( TFGameRules()->IsMannVsMachineMode() && m_pOuter->IsPlayerClass( TF_CLASS_SPY) )
{
// In MvM, Spies reveal other spies in a radius around them
RadiusSpyScan();
}
if ( GetCarryingRuneType() == RUNE_PLAGUE )
{
RadiusHealthkitCollectionCheck();
}
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// Purpose: Do CLIENT/SERVER SHARED condition thinks.
//-----------------------------------------------------------------------------
void CTFPlayerShared::ConditionThink( void )
{
// Client Only Updates Meters for Local Only
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
#endif
{
UpdateCloakMeter();
UpdateRageBuffsAndRage();
UpdateEnergyDrinkMeter();
UpdateChargeMeter();
DemoShieldChargeThink();
#ifdef STAGING_ONLY
UpdateRocketPack();
#endif // STAGING_ONLY
}
VehicleThink();
if ( m_pOuter->GetFlags() & FL_ONGROUND && InCond( TF_COND_PARACHUTE_DEPLOYED ))
{
RemoveCond( TF_COND_PARACHUTE_DEPLOYED );
}
// See if we should be pulsing our radius heal
PulseMedicRadiusHeal();
PulseKingRuneBuff();
m_ConditionList.Think();
if ( InCond( TF_COND_STUNNED ) )
{
#ifdef GAME_DLL
if ( IsControlStunned() )
{
m_pOuter->SetAbsAngles( m_pOuter->m_angTauntCamera );
m_pOuter->SetLocalAngles( m_pOuter->m_angTauntCamera );
}
#endif
if ( GetActiveStunInfo() && gpGlobals->curtime > GetActiveStunInfo()->flExpireTime )
{
#ifdef GAME_DLL
m_PlayerStuns.Remove( m_iStunIndex );
m_iStunIndex = -1;
// Apply our next stun
if ( m_PlayerStuns.Count() )
{
int iStrongestIdx = 0;
for ( int i = 1; i < m_PlayerStuns.Count(); i++ )
{
if ( m_PlayerStuns[i].flStunAmount > m_PlayerStuns[iStrongestIdx].flStunAmount )
{
iStrongestIdx = i;
}
}
m_iStunIndex = iStrongestIdx;
AddCond( TF_COND_STUNNED, -1.f, m_PlayerStuns[m_iStunIndex].hPlayer );
m_iMovementStunParity = ( m_iMovementStunParity + 1 ) & ( ( 1 << MOVEMENTSTUN_PARITY_BITS ) - 1 );
Assert( GetActiveStunInfo() );
}
else
{
RemoveCond( TF_COND_STUNNED );
}
#endif // GAME_DLL
UpdateLegacyStunSystem();
}
else if ( IsControlStunned() && GetActiveStunInfo() && ( gpGlobals->curtime > GetActiveStunInfo()->flStartFadeTime ) )
{
// Control stuns have a final anim to play.
ControlStunFading();
}
#ifdef CLIENT_DLL
// turn off stun effect that gets turned on when incomplete stun msg is received on the client
if ( GetActiveStunInfo() && GetActiveStunInfo()->iStunFlags & TF_STUN_NO_EFFECTS )
{
if ( m_pOuter->m_pStunnedEffect )
{
// Remove stun stars if they are still around.
// They might be if we died, etc.
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pStunnedEffect );
m_pOuter->m_pStunnedEffect = NULL;
}
}
#endif
}
if ( InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
{
#ifdef GAME_DLL
static struct
{
float flTimeLeft;
int nStage;
} s_vecBombStages[] = { { 8.0f, 0 }, { 3.0f, 1 }, { 0.0f, 2 } };
for ( int i = 0; i < ARRAYSIZE( s_vecBombStages ); ++i )
{
if ( m_ConditionData[TF_COND_HALLOWEEN_BOMB_HEAD].m_flExpireTime >= s_vecBombStages[i].flTimeLeft )
{
m_nHalloweenBombHeadStage = s_vecBombStages[i].nStage;
break;
}
}
if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) )
{
if ( m_pOuter->IsAlive() )
{
Vector vToBoss = m_pOuter->EyePosition() - TFGameRules()->GetActiveBoss()->WorldSpaceCenter();
if ( vToBoss.IsLengthLessThan( 100.f ) )
{
CMerasmus* pMerasmus = assert_cast< CMerasmus* >( TFGameRules()->GetActiveBoss() );
if ( pMerasmus )
{
pMerasmus->AddStun( m_pOuter );
}
}
}
}
#else
m_pOuter->HalloweenBombHeadUpdate();
#endif
}
else
{
#ifdef GAME_DLL
m_nHalloweenBombHeadStage = 0;
#endif
}
#ifdef GAME_DLL
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_VIADUCT ) && InCond( TF_COND_PURGATORY ) )
{
// escalating injury multiplier while in purgatory
if ( m_pOuter->m_purgatoryPainMultiplierTimer.IsElapsed() )
{
++m_pOuter->m_purgatoryPainMultiplier;
// injury multiplies rapidly after initial period
m_pOuter->m_purgatoryPainMultiplierTimer.Start( 10.0f );
}
}
#endif
CheckDisguiseTimer();
#ifdef CLIENT_DLL
if ( InCond( TF_COND_TAUNTING ) && m_flTauntParticleRefireTime > 0.0f && gpGlobals->curtime >= m_flTauntParticleRefireTime )
{
FireClientTauntParticleEffects();
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::CheckDisguiseTimer( void )
{
if ( InCond( TF_COND_DISGUISING ) && GetDisguiseCompleteTime() > 0 )
{
if ( gpGlobals->curtime > GetDisguiseCompleteTime() )
{
CompleteDisguise();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddZoomed( void )
{
#ifdef CLIENT_DLL
// hide cosmetic while zoom in thirdperson
if ( m_pOuter == C_TFPlayer::GetLocalTFPlayer() && ::input->CAM_IsThirdPerson() )
{
m_pOuter->UpdateWearables();
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveZoomed( void )
{
#ifdef GAME_DLL
m_pOuter->SetFOV( m_pOuter, 0, 0.1f );
#endif // GAME_DLL
#ifdef CLIENT_DLL
// unhide cosmetic after zoom in thirdperson
if ( m_pOuter == C_TFPlayer::GetLocalTFPlayer() && ::input->CAM_IsThirdPerson() )
{
m_pOuter->UpdateWearables();
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddDisguising( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) )
return;
if ( m_pOuter->m_pDisguisingEffect )
{
// m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pDisguisingEffect );
}
if ( !m_pOuter->IsLocalPlayer() && ( !IsStealthed() || !m_pOuter->IsEnemyPlayer() ) )
{
const char *pEffectName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "spy_start_disguise_red" : "spy_start_disguise_blue";
m_pOuter->m_pDisguisingEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
m_pOuter->m_flDisguiseEffectStartTime = gpGlobals->curtime;
}
m_pOuter->EmitSound( "Player.Spy_Disguise" );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: set up effects for when player finished disguising
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddDisguised( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pDisguisingEffect )
{
// turn off disguising particles
// m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pDisguisingEffect );
m_pOuter->m_pDisguisingEffect = NULL;
}
m_pOuter->m_flDisguiseEndEffectStartTime = gpGlobals->curtime;
UpdateCritBoostEffect( kCritBoost_ForceRefresh );
m_pOuter->UpdateSpyStateChange();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddDemoCharge( void )
{
#ifdef CLIENT_DLL
m_pOuter->StopSound( "DemoCharge.ChargeCritOn" );
m_pOuter->EmitSound( "DemoCharge.ChargeCritOn" );
UpdateCritBoostEffect();
#endif // CLIENT_DLL
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Return the team that the spy is displayed as.
// Not disguised: His own team
// Disguised: The team he is disguised as
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetDisplayedTeam( void ) const
{
int iVisibleTeam = m_pOuter->GetTeamNumber();
// if this player is disguised and on the other team, use disguise team
if ( InCond( TF_COND_DISGUISED ) && m_pOuter->IsEnemyPlayer() )
{
iVisibleTeam = GetDisguiseTeam();
}
return iVisibleTeam;
}
//-----------------------------------------------------------------------------
// Purpose: start, end, and changing disguise classes
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnDisguiseChanged( void )
{
// recalc disguise model index
//RecalcDisguiseWeapon( true );
m_pOuter->UpdateSpyStateChange();
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddInvulnerable( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
const char *pEffectName = NULL;
switch( m_pOuter->GetTeamNumber() )
{
case TF_TEAM_BLUE:
default:
pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE;
break;
case TF_TEAM_RED:
pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED;
break;
}
IMaterial *pMaterial = materials->FindMaterial( pEffectName, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
if ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
{
g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_HEAVY_RECEIVE_UBER_GRIND );
}
}
#else
// remove any persistent damaging conditions
if ( InCond( TF_COND_BURNING ) )
{
RemoveCond( TF_COND_BURNING );
}
if ( InCond( TF_COND_URINE ) )
{
RemoveCond( TF_COND_URINE );
}
if ( InCond( TF_COND_BLEEDING ) )
{
RemoveCond( TF_COND_BLEEDING );
}
if ( InCond( TF_COND_MAD_MILK ) )
{
RemoveCond( TF_COND_MAD_MILK );
}
if ( InCond( TF_COND_PLAGUE ) )
{
RemoveCond( TF_COND_PLAGUE );
}
#ifdef STAGING_ONLY
if ( InCond( TF_COND_TRANQ_MARKED ) )
{
RemoveCond( TF_COND_TRANQ_MARKED );
}
#endif // STAGING_ONLY
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveInvulnerable( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
// only remove the overlay if it is an invuln material
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial &&
( FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE ) ||
FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED ) ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveInvulnerableWearingOff( void )
{
#ifdef CLIENT_DLL
m_flInvulnerabilityRemoveTime = gpGlobals->curtime;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddPhase( void )
{
UpdatePhaseEffects();
#ifdef CLIENT_DLL
#else
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:1" );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemovePhase( void )
{
RemovePhaseEffects();
#ifdef CLIENT_DLL
#else
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:0" );
// Tell this player how much damage they dodged.
CSingleUserRecipientFilter user( m_pOuter );
UserMessageBegin( user, "DamageDodged" );
WRITE_SHORT( clamp( m_iPhaseDamage, 0, 10000 ) );
MessageEnd();
m_iPhaseDamage = 0;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddUrine( void )
{
#ifdef CLIENT_DLL
if ( tf_colorblindassist.GetBool() )
{
m_pOuter->AddOverheadEffect( "peejar_icon" );
}
if ( !m_pOuter->m_pUrineEffect )
{
m_pOuter->m_pUrineEffect = m_pOuter->ParticleProp()->Create( "peejar_drips", PATTACH_ABSORIGIN_FOLLOW ); // pEffect! Kek!
}
if ( m_pOuter->m_pUrineEffect )
{
m_pOuter->ParticleProp()->AddControlPoint( m_pOuter->m_pUrineEffect, 1, m_pOuter, PATTACH_ABSORIGIN_FOLLOW );
}
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_URINE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#else
// m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "urine_on" );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveUrine( void )
{
#ifdef CLIENT_DLL
m_pOuter->RemoveOverheadEffect( "peejar_icon", true );
if ( m_pOuter->m_pUrineEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pUrineEffect );
m_pOuter->m_pUrineEffect = NULL;
}
if ( m_pOuter->IsLocalPlayer() )
{
// only remove the overlay if it is urine
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_URINE ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#else
// m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "urine_off" );
if ( m_hPeeAttacker )
{
// Tell the clients involved in the jarate
CRecipientFilter involved_filter;
involved_filter.AddRecipient( m_pOuter );
involved_filter.AddRecipient( m_hPeeAttacker );
UserMessageBegin( involved_filter, "PlayerJaratedFade" );
WRITE_BYTE( m_hPeeAttacker->entindex() );
WRITE_BYTE( m_pOuter->entindex() );
MessageEnd();
}
m_hPeeAttacker = NULL;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddMarkedForDeath( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdatedMarkedForDeathEffect();
if ( m_pOuter->IsLocalPlayer() )
{
m_pOuter->EmitSound( "Weapon_Marked_for_Death.Indicator" );
}
else if ( !InCond( TF_COND_DISGUISED ) && !IsStealthed() )
{
m_pOuter->EmitSound( "Weapon_Marked_for_Death.Initial" );
}
/*
// Do we want to have a screen overlay effect?
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_URINE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
*/
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveMarkedForDeath( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdatedMarkedForDeathEffect();
/*
if ( m_pOuter->IsLocalPlayer() )
{
// only remove the overlay if it is urine
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_URINE ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
*/
#endif
}
#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddTranqMark( void )
{
#ifdef CLIENT_DLL
//if ( !InCond( TF_COND_DISGUISED ) && !IsStealthed() )
//{
m_pOuter->UpdateTranqMark( true );
//}
//if ( m_pOuter->IsLocalPlayer() )
//{
// m_pOuter->EmitSound( "Weapon_Marked_for_Death.Indicator" );
//}
//else if ( !InCond( TF_COND_DISGUISED ) && !IsStealthed() )
//{
// m_pOuter->EmitSound( "Weapon_Marked_for_Death.Initial" );
//}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveTranqMark( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateTranqMark( false );
#endif
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddCondSpyClassSteal( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateSpyClassStealParticle( true );
#endif
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveCondSpyClassSteal( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateSpyClassStealParticle( false );
#endif
}
#endif // STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose: PARACHUTE
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddCondParachute( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->GetPredictable() && ( prediction->IsFirstTimePredicted() && !m_bSyncingConditions ) )
{
m_pOuter->EmitSound( "Parachute_open" );
}
if ( InCond( TF_COND_HALLOWEEN_KART ) )
{
if ( !m_hKartParachuteEntity )
{
C_BaseAnimating* pBanner = new C_BaseAnimating;
//if ( pBanner )
// return;
pBanner->m_nSkin = 0;
pBanner->InitializeAsClientEntity( "models/workshop/weapons/c_models/c_paratooper_pack/c_paratrooper_parachute.mdl", RENDER_GROUP_OPAQUE_ENTITY );
pBanner->ForceClientSideAnimationOn();
int iSpine = m_pOuter->LookupBone( "bip_spine_3" );
Assert( iSpine != -1 );
if ( iSpine != -1 )
{
pBanner->AttachEntityToBone( m_pOuter, iSpine );
}
int sequence = pBanner->SelectWeightedSequence( ACT_PARACHUTE_DEPLOY_IDLE );
pBanner->ResetSequence( sequence );
m_hKartParachuteEntity.Set( pBanner );
}
}
else
{
IGameEvent *event = gameeventmanager->CreateEvent( "parachute_deploy" );
if ( event )
{
event->SetInt( "index", m_pOuter->entindex() );
gameeventmanager->FireEventClientSide( event );
}
}
#endif
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveCondParachute( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->GetPredictable() && ( prediction->IsFirstTimePredicted() && !m_bSyncingConditions ) )
{
m_pOuter->EmitSound( "Parachute_close" );
}
if ( m_hKartParachuteEntity )
{
m_hKartParachuteEntity->Release();
m_hKartParachuteEntity = NULL;
}
if ( !InCond( TF_COND_HALLOWEEN_KART ) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "parachute_holster" );
if ( event )
{
event->SetInt( "index", m_pOuter->entindex() );
gameeventmanager->FireEventClientSide( event );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddMadMilk( void )
{
#ifdef GAME_DLL
Assert( InCond( TF_COND_MAD_MILK ) );
// Check for the attribute that extends duration on successive hits
if ( m_ConditionData[TF_COND_MAD_MILK].m_bPrevActive )
{
CBaseEntity *pProvider = GetConditionProvider( TF_COND_MAD_MILK );
if ( pProvider )
{
int iMadMilkSyringes = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pProvider, iMadMilkSyringes, mad_milk_syringes );
if ( iMadMilkSyringes )
{
float flDuration = GetConditionDuration( TF_COND_MAD_MILK ) + 0.5f;
SetConditionDuration( TF_COND_MAD_MILK, Min( flDuration , 4.f ) );
}
}
}
#else
if ( !m_pOuter->m_pMilkEffect )
{
m_pOuter->m_pMilkEffect = m_pOuter->ParticleProp()->Create( "peejar_drips_milk", PATTACH_ABSORIGIN_FOLLOW );
}
m_pOuter->ParticleProp()->AddControlPoint( m_pOuter->m_pMilkEffect, 1, m_pOuter, PATTACH_ABSORIGIN_FOLLOW );
// if ( m_pOuter->IsLocalPlayer() )
// {
// IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_MILK, TEXTURE_GROUP_CLIENT_EFFECTS, false );
// if ( !IsErrorMaterial( pMaterial ) )
// {
// view->SetScreenOverlayMaterial( pMaterial );
// }
// }
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveMadMilk( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pMilkEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pMilkEffect );
m_pOuter->m_pMilkEffect = NULL;
}
if ( m_pOuter->IsLocalPlayer() )
{
// IMaterial *pMaterial = view->GetScreenOverlayMaterial();
// if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_MILK ) )
// {
// view->SetScreenOverlayMaterial( NULL );
// }
}
#endif
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFPlayerShared::taunt_particle_state_t CTFPlayerShared::GetClientTauntParticleDesiredState() const
{
const itemid_t unTauntSourceItemID = GetTauntSourceItemID();
if ( unTauntSourceItemID != INVALID_ITEM_ID )
{
CSteamID steamIDForPlayer;
m_pOuter->GetSteamID( &steamIDForPlayer );
CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( steamIDForPlayer.GetAccountID() );
CEconItemView *pTauntItem = pInventory ? pInventory->GetInventoryItemByItemID( unTauntSourceItemID ) : NULL;
if ( pTauntItem )
{
// do community_sparkle effect if this is a community item?
const int iQualityParticleType = pTauntItem->GetQualityParticleType();
if ( iQualityParticleType > 0 )
{
const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType );
if ( pParticleSystem )
{
return taunt_particle_state_t( pParticleSystem->pszSystemName, pParticleSystem->fRefireTime );
}
}
static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" );
uint32 unUnusualEffectIndex = 0;
if ( pTauntItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
{
const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
if ( pParticleSystem )
{
// TF Team Color Particles
if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ) )
{
static char pBlue[256];
V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
}
else if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_blue" ) )
{
// Guard against accidentally giving out the blue team color (support tool)
static char pRed[256];
V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pRed );
}
return taunt_particle_state_t( pParticleSystem->pszSystemName, pParticleSystem->fRefireTime );
}
}
for ( int i=0; i<m_pOuter->GetNumWearables(); ++i )
{
C_EconWearable *pWearable = m_pOuter->GetWearable( i );
CEconItemView *pItem = pWearable && pWearable->GetAttributeContainer() && pWearable->GetAttributeContainer()->GetItem() ? pWearable->GetAttributeContainer()->GetItem() : NULL;
// check for Unusual Cap def index (1173)
if ( pItem && pItem->GetItemDefIndex() == 1173 && pItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
{
const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
if ( pParticleSystem )
{
// TF Team Color Particles
if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ) )
{
static char pBlue[256];
V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
}
else if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_blue" ) )
{
// Guard against accidentally giving out the blue team color (support tool)
static char pRed[256];
V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pRed );
}
return taunt_particle_state_t( pParticleSystem->pszSystemName, pParticleSystem->fRefireTime );
}
}
}
}
}
return taunt_particle_state_t( NULL, 0.0f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::FireClientTauntParticleEffects()
{
taunt_particle_state_t TauntParticleState = GetClientTauntParticleDesiredState();
if ( TauntParticleState.first )
{
if ( m_pOuter->m_pTauntEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pTauntEffect );
}
if ( !m_pOuter->GetTauntEconItemView() )
return;
if ( !m_pOuter->GetTauntEconItemView()->GetStaticData() )
return;
if ( !m_pOuter->GetTauntEconItemView()->GetStaticData()->GetTauntData() )
return;
const char *pszAttachment = m_pOuter->GetTauntEconItemView()->GetStaticData()->GetTauntData()->GetParticleAttachment();
int iAttachment = pszAttachment ? m_pOuter->LookupAttachment( pszAttachment ) : INVALID_PARTICLE_ATTACHMENT;
m_pOuter->m_pTauntEffect = m_pOuter->ParticleProp()->Create( TauntParticleState.first, iAttachment != INVALID_PARTICLE_ATTACHMENT ? PATTACH_POINT_FOLLOW : PATTACH_ABSORIGIN_FOLLOW, iAttachment, vec3_origin );
if ( TauntParticleState.second > 0.0f )
{
m_flTauntParticleRefireTime = gpGlobals->curtime + TauntParticleState.second;
}
}
}
#endif // CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddTaunting( void )
{
CTFWeaponBase *pWpn = m_pOuter->GetActiveTFWeapon();
if ( pWpn )
{
// cancel any reload in progress.
pWpn->AbortReload();
// Check for taunt healing.
if ( GetTauntIndex() == TAUNT_BASE_WEAPON )
{
int iAOEHeal = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWpn, iAOEHeal, enables_aoe_heal );
if ( iAOEHeal == 1 )
{
Heal_Radius( true );
}
}
}
// Unzoom if we are a sniper zoomed!
InstantlySniperUnzoom();
if ( ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) ) && GetTauntIndex() == TAUNT_BASE_WEAPON )
{
CTFLunchBox *pLunchBox = dynamic_cast <CTFLunchBox *> ( pWpn );
if ( pLunchBox )
{
pLunchBox->DrainAmmo();
}
}
#ifdef GAME_DLL
m_pOuter->PlayWearableAnimsForPlaybackEvent( WAP_START_TAUNTING );
#else
FireClientTauntParticleEffects();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveTaunting( void )
{
#ifdef GAME_DLL
#ifdef STAGING_ONLY
if ( !m_pOuter->m_hTauntScene.Get() )
{
Warning( "Why do we not have taunt scene?\n" );
}
#endif
m_pOuter->StopTaunt();
if ( IsControlStunned() )
{
m_pOuter->SetAbsAngles( m_pOuter->m_angTauntCamera );
m_pOuter->SetLocalAngles( m_pOuter->m_angTauntCamera );
}
#endif // GAME_DLL
// Stop aoe healing if it's active.
Heal_Radius( false );
// We're done taunting, our weapons are not being repurposed anymore
for ( int i = 0; i < m_pOuter->WeaponCount(); i++)
{
CTFWeaponBase *pWpn = ( CTFWeaponBase *) m_pOuter->GetWeapon(i);
if ( !pWpn )
continue;
pWpn->SetIsBeingRepurposedForTaunt( false );
}
#ifdef GAME_DLL
// Switch to our melee weapon, if we are at the end of a type 2 lunchbox taunt.
if ( m_bBiteEffectWasApplied && InCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ) )
{
CBaseCombatWeapon *pWpn = m_pOuter->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( pWpn )
{
m_pOuter->Weapon_Switch( pWpn );
}
else
{
// Safety net
RemoveCond( TF_COND_ENERGY_BUFF );
RemoveCond( TF_COND_CANNOT_SWITCH_FROM_MELEE );
}
}
m_bBiteEffectWasApplied = false;
if ( m_pOuter->m_hTauntItem != NULL )
{
// destroy the item we were showing off
UTIL_Remove( m_pOuter->m_hTauntItem );
m_pOuter->m_hTauntItem = NULL;
}
m_pOuter->ClearTauntAttack();
m_pOuter->PlayWearableAnimsForPlaybackEvent( WAP_STOP_TAUNTING );
m_pOuter->HandleWeaponSlotAfterTaunt();
#else
CSteamID steamIDForPlayer;
m_pOuter->GetSteamID( &steamIDForPlayer );
int nMapDonationAmount = MapInfo_GetDonationAmount( steamIDForPlayer.GetAccountID(), engine->GetLevelName() );
m_pOuter->SetFootStamps( nMapDonationAmount );
if ( m_pOuter->m_pTauntEffect )
{
m_pOuter->ParticleProp()->StopEmissionAndDestroyImmediately( m_pOuter->m_pTauntEffect );
m_pOuter->m_pTauntEffect = NULL;
}
m_flTauntParticleRefireTime = 0.0f;
#endif
m_pOuter->m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
// when we stop taunting, make sure active weapon is visible
if ( m_pOuter->GetActiveWeapon() )
{
m_pOuter->GetActiveWeapon()->SetWeaponVisible( true );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddBleeding( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_BLEED, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#else
// We should have at least one bleed entry
Assert( m_PlayerBleeds.Count() );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveBleeding( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_BLEED ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#else
m_PlayerBleeds.RemoveAll();
#endif
}
const char* CTFPlayerShared::GetSoldierBuffEffectName( void )
{
if ( TFGameRules()->IsMannVsMachineMode() )
{
if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE )
{
// MVM robot version has fewer particles. Helps keep the framerate up.
return "soldierbuff_mvm";
}
else
{
return "soldierbuff_red_soldier";
}
}
else
{
if ( m_pOuter->GetTeamNumber() == TF_TEAM_BLUE )
{
return "soldierbuff_blue_soldier";
}
else
{
return "soldierbuff_red_soldier";
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddSoldierOffensiveBuff( void )
{
#ifdef CLIENT_DLL
const char* strBuffName = GetSoldierBuffEffectName();
if ( !m_pOuter->m_pSoldierOffensiveBuffEffect )
{
m_pOuter->m_pSoldierOffensiveBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
}
#endif
}
void CTFPlayerShared::OnRemoveSoldierOffensiveBuff( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pSoldierOffensiveBuffEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierOffensiveBuffEffect );
m_pOuter->m_pSoldierOffensiveBuffEffect = NULL;
}
#endif
}
void CTFPlayerShared::OnAddSoldierDefensiveBuff( void )
{
#ifdef CLIENT_DLL
const char* strBuffName = GetSoldierBuffEffectName();
if ( !m_pOuter->m_pSoldierDefensiveBuffEffect )
{
m_pOuter->m_pSoldierDefensiveBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
}
#endif
}
void CTFPlayerShared::OnRemoveSoldierDefensiveBuff( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pSoldierDefensiveBuffEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierDefensiveBuffEffect );
m_pOuter->m_pSoldierDefensiveBuffEffect = NULL;
}
#endif
}
void CTFPlayerShared::OnAddSoldierOffensiveHealthRegenBuff( void )
{
#ifdef GAME_DLL
AddCond( TF_COND_SPEED_BOOST );
#else
const char* strBuffName = GetSoldierBuffEffectName();
if ( !m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect )
{
m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
}
#endif
}
void CTFPlayerShared::OnRemoveSoldierOffensiveHealthRegenBuff( void )
{
#ifdef GAME_DLL
RemoveCond( TF_COND_SPEED_BOOST );
#else
if ( m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect );
m_pOuter->m_pSoldierOffensiveHealthRegenBuffEffect = NULL;
}
#endif
}
void CTFPlayerShared::OnAddSoldierNoHealingDamageBuff( void )
{
#ifdef CLIENT_DLL
const char* strBuffName = GetSoldierBuffEffectName();
if ( !m_pOuter->m_pSoldierNoHealingDamageBuffEffect )
{
m_pOuter->m_pSoldierNoHealingDamageBuffEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
}
#endif
}
void CTFPlayerShared::OnRemoveSoldierNoHealingDamageBuff( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pSoldierNoHealingDamageBuffEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSoldierNoHealingDamageBuffEffect );
m_pOuter->m_pSoldierNoHealingDamageBuffEffect = NULL;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddOffenseBuff( void )
{
OnAddSoldierOffensiveBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveOffenseBuff( void )
{
OnRemoveSoldierOffensiveBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddDefenseBuff( void )
{
OnAddSoldierDefensiveBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveDefenseBuff( void )
{
OnRemoveSoldierDefensiveBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddOffenseHealthRegenBuff( void )
{
OnAddSoldierOffensiveHealthRegenBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveOffenseHealthRegenBuff( void )
{
OnRemoveSoldierOffensiveHealthRegenBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddNoHealingDamageBuff( void )
{
OnAddSoldierNoHealingDamageBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveNoHealingDamageBuff( void )
{
OnRemoveSoldierNoHealingDamageBuff();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddSpeedBoost( bool IsNonCombat )
{
#ifdef CLIENT_DLL
const char* strBuffName = "speed_boost_trail";
if ( !m_pOuter->m_pSpeedBoostEffect )
{
// No speedlines at all for stealth or feign death
if ( !InCond( TF_COND_STEALTHED ) && !InCond(TF_COND_FEIGN_DEATH) )
{
m_pOuter->m_pSpeedBoostEffect = m_pOuter->ParticleProp()->Create( strBuffName, PATTACH_ABSORIGIN_FOLLOW );
}
}
// InCombat is played on the teleporter for all players to here
// "Building_Speedpad.BoostStart"
if ( !IsNonCombat && m_pOuter->IsLocalPlayer())
{
m_pOuter->EmitSound( "DisciplineDevice.PowerUp" );
}
#else // !CLIENT_DLL
m_pOuter->TeamFortress_SetSpeed();
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveSpeedBoost( bool IsNonCombat )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pSpeedBoostEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSpeedBoostEffect );
m_pOuter->m_pSpeedBoostEffect = NULL;
}
if ( !IsNonCombat && m_pOuter->IsLocalPlayer() )
{
m_pOuter->EmitSound( "DisciplineDevice.PowerDown" );
}
else
{
m_pOuter->EmitSound( "Building_Speedpad.BoostStop" );
}
#else // !CLIENT_DLL
m_pOuter->TeamFortress_SetSpeed();
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose: Applied to bots
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddSapped( void )
{
#ifdef CLIENT_DLL
if ( !m_pOuter->m_pSappedPlayerEffect )
{
const char* szParticle = "sapper_sentry1_fx";
m_pOuter->m_pSappedPlayerEffect = m_pOuter->ParticleProp()->Create( szParticle, PATTACH_POINT_FOLLOW, "head" );
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveSapped( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pSappedPlayerEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSappedPlayerEffect );
m_pOuter->m_pSappedPlayerEffect = NULL;
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose: Applied to bots
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddReprogrammed( void )
{
#ifdef STAGING_ONLY
#ifdef GAME_DLL
CTFBot *pBot = ToTFBot( m_pOuter );
if ( pBot )
{
pBot->ChangeTeam( GetEnemyTeam( pBot->GetTeamNumber() ), false, true );
pBot->SetMission( CTFBot::MISSION_REPROGRAMMED );
pBot->Update();
pBot->MarkAsMissionEnemy();
}
#endif // GAME_DLL
#endif // STAGING_ONLY
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveReprogrammed( void )
{
}
void CTFPlayerShared::OnAddDisguisedAsDispenser( void )
{
m_pOuter->TeamFortress_SetSpeed();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddHalloweenBombHead( void )
{
#ifdef CLIENT_DLL
m_pOuter->HalloweenBombHeadUpdate();
m_pOuter->CreateBombonomiconHint();
#else
if ( InCond( TF_COND_HALLOWEEN_KART ) )
{
RemoveAttributeFromPlayer( "head scale" );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveHalloweenBombHead( void )
{
#ifdef CLIENT_DLL
m_pOuter->HalloweenBombHeadUpdate();
m_pOuter->DestroyBombonomiconHint();
#else
if ( InCond( TF_COND_HALLOWEEN_KART ) )
{
ApplyAttributeToPlayer( "head scale", 3.f );
}
if ( m_pOuter->IsAlive() )
{
m_pOuter->MerasmusPlayerBombExplode( false );
Vector vecOrigin = m_pOuter->GetAbsOrigin();
// explode has a small force but we want to increase it
if ( InCond ( TF_COND_HALLOWEEN_KART ) )
{
if ( !m_pOuter->GetKartBombHeadTarget() )
{
m_pOuter->AddHalloweenKartPushEvent( m_pOuter, NULL, NULL, Vector( 0, 0, 100 ), 50 );
}
m_pOuter->SetKartBombHeadTarget( NULL );
}
else if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
{
TFGameRules()->PushAllPlayersAway( vecOrigin, 150, 400, TEAM_ANY );
}
// Particle
CPVSFilter filter( vecOrigin );
TE_TFParticleEffect( filter, 0.0, "bombinomicon_burningdebris", vecOrigin, vec3_angle );
}
#endif // GAME_DLL
}
void CTFPlayerShared::OnAddHalloweenThriller( void )
{
#ifdef CLIENT_DLL
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
if ( pLocalPlayer == m_pOuter )
{
m_pOuter->EmitSound( "Halloween.dance_howl" );
m_pOuter->EmitSound( "Halloween.dance_loop" );
}
}
#endif
}
void CTFPlayerShared::OnRemoveHalloweenThriller( void )
{
#ifdef CLIENT_DLL
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
if ( pLocalPlayer == m_pOuter )
{
m_pOuter->StopSound( "Halloween.dance_loop" );
}
}
#else
// If this is hightower, players will be healing themselves while dancing
StopHealing( m_pOuter );
#endif
}
void CTFPlayerShared::OnAddRadiusHealOnDamage( void )
{
Heal_Radius( true );
}
void CTFPlayerShared::OnRemoveRadiusHealOnDamage( void )
{
Heal_Radius( false );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddMarkedForDeathSilent( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdatedMarkedForDeathEffect();
#endif
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveMarkedForDeathSilent( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdatedMarkedForDeathEffect();
#endif
}
void CTFPlayerShared::OnRemoveDisguisedAsDispenser( void )
{
m_pOuter->TeamFortress_SetSpeed();
}
#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddRocketPack( void )
{
#ifdef CLIENT_DLL
if ( !m_pOuter->m_pRocketPackEffect )
{
const char* szParticle = "rocketbackblast";
m_pOuter->m_pRocketPackEffect = m_pOuter->ParticleProp()->Create( szParticle, PATTACH_POINT_FOLLOW, "flag" );
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveRocketPack( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pRocketPackEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRocketPackEffect );
m_pOuter->m_pRocketPackEffect = NULL;
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateRocketPack( void )
{
if ( !m_pOuter->IsPlayerClass( TF_CLASS_PYRO ) )
return;
if ( InCond( TF_COND_ROCKETPACK ) )
{
#ifdef GAME_DLL
// Check for landing
if ( m_pOuter->GetFlags() & FL_ONGROUND )
{
RemoveCond( TF_COND_ROCKETPACK );
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ApplyRocketPackStun( float flStunDuration )
{
if ( flStunDuration < 0.1f )
return;
#ifdef GAME_DLL
const int nMaxEnts = 24;
CBaseEntity *pObjects[ nMaxEnts ];
int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, m_pOuter->GetAbsOrigin(), 192.f, FL_CLIENT );
for ( int i = 0; i < nCount; i++ )
{
if ( !pObjects[i] )
continue;
if ( !pObjects[i]->IsAlive() )
continue;
if ( m_pOuter->InSameTeam( pObjects[i] ) )
continue;
if ( !m_pOuter->FVisible( pObjects[i], MASK_OPAQUE ) )
continue;
CTFPlayer *pTFPlayer = static_cast< CTFPlayer* >( pObjects[i] );
if ( !pTFPlayer )
continue;
pTFPlayer->m_Shared.StunPlayer( flStunDuration, 0.75f, TF_STUN_CONTROLS );
}
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::CanBuildSpyTraps( void )
{
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
return false;
if ( m_pOuter->IsBot() )
return false;
if ( TFGameRules()->IsMannVsMachineMode() )
return true;
int iTraps = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iTraps, ability_spy_traps );
return ( iTraps > 0 );
}
#endif // STAGING_ONLY
#ifdef CLIENT_DLL
static void AddUberScreenEffect( const CTFPlayer* pPlayer )
{
// Add the uber effect onto the local player's screen
if ( pPlayer && pPlayer->IsLocalPlayer() )
{
const char *pEffectName = NULL;
if ( pPlayer->GetTeamNumber() == TF_TEAM_RED )
{
pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED;
}
else
{
pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE;
}
IMaterial *pMaterial = materials->FindMaterial( pEffectName, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
}
static void RemoveUberScreenEffect( const CTFPlayer* pPlayer )
{
if ( pPlayer && pPlayer->IsLocalPlayer() )
{
// only remove the overlay if it is an invuln material
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial &&
( FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE ) ||
FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED ) ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
}
static const char* s_pszRedResistOverheadEffectName[] =
{
"vaccinator_red_buff1",
"vaccinator_red_buff2",
"vaccinator_red_buff3",
};
static const char* s_pszBlueResistOverheadEffectName[] =
{
"vaccinator_blue_buff1",
"vaccinator_blue_buff2",
"vaccinator_blue_buff3",
};
COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszRedResistOverheadEffectName ) == MEDIGUN_NUM_RESISTS && ARRAYSIZE( s_pszBlueResistOverheadEffectName ) == MEDIGUN_NUM_RESISTS );
static void AddResistParticle( CTFPlayer* pPlayer, medigun_resist_types_t nResistType, ETFCond eYeildToCond = TF_COND_LAST )
{
// Don't spawn it over the local player's head
if ( !pPlayer || pPlayer->IsLocalPlayer() )
return;
// do not add if stealthed
if ( pPlayer->m_Shared.IsStealthed() )
return;
// Don't add this effect if the yield effect is passed in
if( eYeildToCond != TF_COND_LAST && pPlayer->m_Shared.InCond( eYeildToCond ) )
return;
if ( pPlayer->m_Shared.GetDisplayedTeam() == TF_TEAM_RED )
{
pPlayer->AddOverheadEffect( s_pszRedResistOverheadEffectName[ nResistType ] );
}
else
{
pPlayer->AddOverheadEffect( s_pszBlueResistOverheadEffectName[ nResistType ] );
}
}
static void RemoveResistParticle( CTFPlayer* pPlayer, medigun_resist_types_t nResistType )
{
if ( !pPlayer || pPlayer->IsLocalPlayer() )
return;
bool bKeep = false;
switch ( nResistType )
{
case MEDIGUN_BULLET_RESIST:
bKeep = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST );
break;
case MEDIGUN_BLAST_RESIST:
bKeep = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST );
break;
case MEDIGUN_FIRE_RESIST:
bKeep = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST );
break;
default:
AssertMsg( 0, "Invalid medigun resist type" );
break;
}
// don't remove overhead effect if the uber's still active
if ( bKeep )
return;
if ( pPlayer->m_Shared.GetDisplayedTeam() == TF_TEAM_RED )
{
pPlayer->RemoveOverheadEffect( s_pszRedResistOverheadEffectName[ nResistType ], true );
}
else
{
pPlayer->RemoveOverheadEffect( s_pszBlueResistOverheadEffectName[ nResistType ], true );
}
}
static int GetResistShieldSkinForResistType( ETFCond eCond )
{
switch( eCond )
{
case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
return 2;
case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
return 3;
case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
return 4;
default:
AssertMsg( 0, "Invalid condition passed into AddResistShield" );
return 0;
}
}
static void AddResistShield( C_LocalTempEntity** pShield, CTFPlayer* pPlayer, ETFCond eCond )
{
if( CBasePlayer::GetLocalPlayer() == pPlayer )
return;
// do not add if stealthed
if ( pPlayer->m_Shared.IsStealthed() )
return;
// Don't create a new shield if we already have one
if( *pShield )
return;
model_t *pModel = (model_t*) engine->LoadModel( "models/effects/resist_shield/resist_shield.mdl" );
(*pShield) = tempents->SpawnTempModel( pModel, pPlayer->GetAbsOrigin(), pPlayer->GetAbsAngles(), Vector(0, 0, 0), 1, FTENT_NEVERDIE | FTENT_PLYRATTACHMENT );
if ( *pShield )
{
(*pShield)->ChangeTeam( pPlayer->m_Shared.GetDisplayedTeam() );
if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
{
(*pShield)->m_nSkin = GetResistShieldSkinForResistType( eCond );
}
else
{
(*pShield)->m_nSkin = ( pPlayer->m_Shared.GetDisplayedTeam() == TF_TEAM_RED ) ? 0 : 1;
}
(*pShield)->clientIndex = pPlayer->entindex();
(*pShield)->SetModelScale( pPlayer->GetModelScale() );
}
}
static void RemoveResistShield( C_LocalTempEntity** pShield, CTFPlayer* pPlayer )
{
if ( *pShield )
{
ETFCond eCond = TF_COND_INVALID;
// Check if we still have one of the other resist types on us
eCond = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST ) ? TF_COND_MEDIGUN_UBER_BULLET_RESIST : eCond;
eCond = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST ) ? TF_COND_MEDIGUN_UBER_BLAST_RESIST : eCond;
eCond = pPlayer->m_Shared.InCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST ) ? TF_COND_MEDIGUN_UBER_FIRE_RESIST : eCond;
eCond = ( pPlayer->m_Shared.InCond( TF_COND_RUNE_RESIST ) && !pPlayer->m_Shared.IsStealthed() ) ? TF_COND_RUNE_RESIST : eCond;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
eCond = ( pPlayer->IsEnemyPlayer() && pPlayer->m_Shared.InCond( TF_COND_RUNE_PLAGUE ) && pLocalPlayer->m_Shared.InCond( TF_COND_PLAGUE ) ) ? TF_COND_RUNE_PLAGUE : eCond;
}
// Still have one, don't remove the shield
if( eCond != TF_COND_INVALID )
{
// If we're in MvM, and we're one of the bots, change the shield color
if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
{
(*pShield)->m_nSkin = GetResistShieldSkinForResistType( eCond );
}
return;
}
else // No more bubble
{
(*pShield)->flags = FTENT_FADEOUT | FTENT_PLYRATTACHMENT;
(*pShield)->die = gpGlobals->curtime;
(*pShield)->fadeSpeed = 1.0f;
(*pShield) = NULL;
}
}
}
#endif // CLIENT_DLL
void CTFPlayerShared::OnAddMedEffectUberBulletResist( void )
{
#ifdef CLIENT_DLL
AddResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
AddUberScreenEffect( m_pOuter );
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BULLET_RESIST );
#endif
}
void CTFPlayerShared::OnRemoveMedEffectUberBulletResist( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
RemoveUberScreenEffect( m_pOuter );
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
OnAddMedEffectSmallBulletResist();
#endif
}
void CTFPlayerShared::OnAddMedEffectUberBlastResist( void )
{
#ifdef CLIENT_DLL
AddResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
AddUberScreenEffect( m_pOuter );
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BLAST_RESIST );
#endif
}
void CTFPlayerShared::OnRemoveMedEffectUberBlastResist( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
RemoveUberScreenEffect( m_pOuter );
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
OnAddMedEffectSmallBlastResist();
#endif
}
void CTFPlayerShared::OnAddMedEffectUberFireResist( void )
{
#ifdef CLIENT_DLL
AddResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
AddUberScreenEffect( m_pOuter );
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_FIRE_RESIST );
#endif
}
void CTFPlayerShared::OnRemoveMedEffectUberFireResist( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
RemoveUberScreenEffect( m_pOuter );
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
OnAddMedEffectSmallFireResist();
#endif
}
void CTFPlayerShared::OnAddMedEffectSmallBulletResist( void )
{
#ifdef CLIENT_DLL
if( InCond( TF_COND_MEDIGUN_SMALL_BULLET_RESIST ) )
{
AddResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST, TF_COND_MEDIGUN_UBER_BULLET_RESIST );
}
#endif
}
void CTFPlayerShared::OnRemoveMedEffectSmallBulletResist( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
#endif
}
void CTFPlayerShared::OnAddMedEffectSmallBlastResist( void )
{
#ifdef CLIENT_DLL
if( InCond( TF_COND_MEDIGUN_SMALL_BLAST_RESIST ) )
{
AddResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST, TF_COND_MEDIGUN_UBER_BLAST_RESIST );
}
#endif
}
void CTFPlayerShared::OnRemoveMedEffectSmallBlastResist( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
#endif
}
void CTFPlayerShared::OnAddMedEffectSmallFireResist( void )
{
#ifdef CLIENT_DLL
if( InCond( TF_COND_MEDIGUN_SMALL_FIRE_RESIST ) )
{
AddResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST, TF_COND_MEDIGUN_UBER_FIRE_RESIST );
}
#endif
}
void CTFPlayerShared::OnRemoveMedEffectSmallFireResist( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
#endif
}
void CTFPlayerShared::OnAddRuneResist( void )
{
#ifdef CLIENT_DLL
// Do use the condition bit here, it's passed along and is expected to be a cond.
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_RUNE_RESIST );
#endif
}
void CTFPlayerShared::OnRemoveRuneResist( void )
{
#ifdef CLIENT_DLL
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
#endif
}
void CTFPlayerShared::OnRemoveRuneKing( void )
{
#ifdef CLIENT_DLL
EndKingBuffRadiusEffect();
#endif
}
void CTFPlayerShared::OnAddGrapplingHookLatched( void )
{
m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_GRAPPLE_PULL_START );
}
void CTFPlayerShared::OnRemoveGrapplingHookLatched( void )
{
// DO NOTHING
}
void CTFPlayerShared::OnAddBulletImmune( void )
{
#ifdef CLIENT_DLL
AddResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
AddUberScreenEffect( m_pOuter );
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BULLET_RESIST );
#endif
}
void CTFPlayerShared::OnRemoveBulletImmune( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_BULLET_RESIST );
RemoveUberScreenEffect( m_pOuter );
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
#endif
}
void CTFPlayerShared::OnAddBlastImmune( void )
{
#ifdef CLIENT_DLL
AddResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
AddUberScreenEffect( m_pOuter );
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_BLAST_RESIST );
#endif
}
void CTFPlayerShared::OnRemoveBlastImmune( void )
{
#ifdef CLIENT_DLL
RemoveResistParticle( m_pOuter, MEDIGUN_BLAST_RESIST );
RemoveUberScreenEffect( m_pOuter );
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
#endif
}
void CTFPlayerShared::OnAddFireImmune( void )
{
#ifdef CLIENT_DLL
AddUberScreenEffect( m_pOuter );
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
{
AddResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_MEDIGUN_UBER_FIRE_RESIST );
}
#endif
}
void CTFPlayerShared::OnRemoveFireImmune( void )
{
#ifdef CLIENT_DLL
RemoveUberScreenEffect( m_pOuter );
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
{
RemoveResistParticle( m_pOuter, MEDIGUN_FIRE_RESIST );
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
}
#endif
}
void CTFPlayerShared::OnAddMVMBotRadiowave( void )
{
#ifdef CLIENT_DLL
if ( !m_pOuter->IsABot() )
return;
if ( !m_pOuter->m_pMVMBotRadiowave )
{
m_pOuter->m_pMVMBotRadiowave = m_pOuter->ParticleProp()->Create( "bot_radio_waves", PATTACH_POINT_FOLLOW, "head" );
}
#else
if ( !m_pOuter->IsBot() )
return;
StunPlayer( GetConditionDuration( TF_COND_MVM_BOT_STUN_RADIOWAVE ), 1.0, TF_STUN_BOTH | TF_STUN_NO_EFFECTS );
#endif
}
void CTFPlayerShared::OnRemoveMVMBotRadiowave( void )
{
#ifdef CLIENT_DLL
if ( !m_pOuter->IsABot() )
return;
if ( m_pOuter->m_pMVMBotRadiowave )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pMVMBotRadiowave );
m_pOuter->m_pMVMBotRadiowave = NULL;
}
#endif
}
void CTFPlayerShared::OnAddHalloweenSpeedBoost( void )
{
#ifdef GAME_DLL
AddCond( TF_COND_SPEED_BOOST );
ApplyAttributeToPlayer( "halloween reload time decreased", 0.5f );
ApplyAttributeToPlayer( "halloween fire rate bonus", 0.5f );
ApplyAttributeToPlayer( "halloween increased jump height", 1.5f );
//
#endif
}
void CTFPlayerShared::OnRemoveHalloweenSpeedBoost( void )
{
#ifdef GAME_DLL
RemoveCond( TF_COND_SPEED_BOOST );
RemoveAttributeFromPlayer( "halloween reload time decreased" );
RemoveAttributeFromPlayer( "halloween fire rate bonus" );
RemoveAttributeFromPlayer( "halloween increased jump height" );
#endif
}
void CTFPlayerShared::OnAddHalloweenQuickHeal( void )
{
#ifdef GAME_DLL
AddCond( TF_COND_MEGAHEAL );
Heal( m_pOuter, 30.0f, 2.0f, 1.0f );
#endif
}
void CTFPlayerShared::OnRemoveHalloweenQuickHeal( void )
{
#ifdef GAME_DLL
RemoveCond( TF_COND_MEGAHEAL );
StopHealing( m_pOuter );
#endif
}
void CTFPlayerShared::OnAddHalloweenGiant( void )
{
#ifdef GAME_DLL
m_pOuter->SetModelScale( 2.f );
int nNewHP = tf_halloween_giant_health_scale.GetFloat() * m_pOuter->GetPlayerClass()->GetMaxHealth();
m_pOuter->SetHealth( nNewHP );
m_pOuter->SetMaxHealth( nNewHP );
#else
cam_idealdist.SetValue( 300.f );
cam_idealdistright.SetValue( 40.f );
#endif
}
void CTFPlayerShared::OnRemoveHalloweenGiant( void )
{
#ifdef GAME_DLL
m_pOuter->SetModelScale( 1.f );
int nNewHP = m_pOuter->GetPlayerClass()->GetMaxHealth();
m_pOuter->SetHealth( nNewHP );
m_pOuter->SetMaxHealth( nNewHP );
#else
cam_idealdist.SetValue( cam_idealdist.GetDefault() );
cam_idealdistright.SetValue( cam_idealdistright.GetDefault() );
#endif
}
void CTFPlayerShared::OnAddHalloweenTiny( void )
{
#ifdef GAME_DLL
m_pOuter->SetModelScale( 0.5f );
ApplyAttributeToPlayer( "voice pitch scale", 1.3f );
ApplyAttributeToPlayer( "head scale", 3.f );
#endif
}
void CTFPlayerShared::OnAddHalloweenGhostMode( void )
{
m_pOuter->SetGroundEntity( NULL );
m_pOuter->SetSolid( SOLID_NONE );
m_pOuter->SetSolidFlags( FSOLID_NOT_SOLID );
m_pOuter->AddFlag( FL_NOTARGET );
#ifdef GAME_DLL
CSingleUserRecipientFilter filter( m_pOuter );
if ( TFGameRules() )
{
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_HOW_TO_CONTROL_GHOST );
}
else
{
TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_HOW_TO_CONTROL_GHOST_NO_RESPAWN );
}
}
// The game rules listens for this event
IGameEvent *event = gameeventmanager->CreateEvent( "player_turned_to_ghost" );
if ( event )
{
event->SetInt( "userid", m_pOuter->GetUserID() );
gameeventmanager->FireEvent( event );
}
// Push them up a little bit
Vector vecNewVel( 0, 0, 40 );
m_pOuter->Teleport( NULL, NULL, &vecNewVel );
if ( m_pOuter->GetActiveWeapon() )
{
m_pOuter->GetActiveWeapon()->SendViewModelAnim( ACT_IDLE );
m_pOuter->GetActiveWeapon()->Holster();
}
m_pOuter->SetActiveWeapon( NULL );
CBaseObject * pCarriedObj = GetCarriedObject();
if ( pCarriedObj )
{
pCarriedObj->DetonateObject();
}
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
if ( pSpellBook )
{
pSpellBook->ClearSpell();
}
#else
// Go thirdperson
SetAppropriateCamera( m_pOuter );
Color color;
m_pOuter->GetTeamColor( color );
m_pOuter->SetRenderColor( color.r(), color.g(), color.b() );
#endif
}
void CTFPlayerShared::OnAddHalloweenKartDash()
{
m_pOuter->SetFOV( m_pOuter, 110.f, 1.f, 0.f );
m_pOuter->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_KART_ACTION_DASH );
#ifdef CLIENT_DLL
#else // CLIENT_DLL
m_pOuter->EmitSound( "BumperCar.SpeedBoostStart" );
#endif // GAME_DLL
}
void CTFPlayerShared::OnRemoveHalloweenKartDash()
{
m_pOuter->SetFOV( m_pOuter, 0.f, 1.f, 0.f );
#ifdef CLIENT_DLL
if ( m_pOuter->m_pSpeedBoostEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pSpeedBoostEffect );
m_pOuter->m_pSpeedBoostEffect = NULL;
}
#else // CLIENT_DLL
m_pOuter->EmitSound( "BumperCar.SpeedBoostStop" );
#endif // GAME_DLL
}
void CTFPlayerShared::OnRemoveHalloweenTiny( void )
{
#ifdef GAME_DLL
m_pOuter->SetModelScale( 1.f );
RemoveAttributeFromPlayer( "voice pitch scale" );
RemoveAttributeFromPlayer( "head scale" );
const Vector& vOrigin = m_pOuter->GetAbsOrigin();
const QAngle& qAngle = m_pOuter->GetAbsAngles();
const Vector& vHullMins = m_pOuter->GetFlags() & FL_DUCKING ? VEC_DUCK_HULL_MIN : VEC_HULL_MIN;
const Vector& vHullMaxs = m_pOuter->GetFlags() & FL_DUCKING ? VEC_DUCK_HULL_MAX : VEC_HULL_MAX;
trace_t result;
CTraceFilterIgnoreTeammates filter( m_pOuter, COLLISION_GROUP_NONE, m_pOuter->GetTeamNumber() );
UTIL_TraceHull( vOrigin, vOrigin, vHullMins, vHullMaxs, MASK_PLAYERSOLID, &filter, &result );
// am I stuck? try to resolve it
if ( result.DidHit() )
{
float flPlayerHeight = vHullMaxs.z - vHullMins.z;
float flExtraHeight = 10;
static Vector vTest[] =
{
Vector( 32, 32, flExtraHeight ),
Vector( -32, -32, flExtraHeight ),
Vector( -32, 32, flExtraHeight ),
Vector( 32, -32, flExtraHeight ),
Vector( 0, 0, flPlayerHeight + flExtraHeight ),
Vector( 0, 0, -flPlayerHeight - flExtraHeight )
};
for ( int i=0; i<ARRAYSIZE( vTest ); ++i )
{
Vector vTestPos = vOrigin + vTest[i];
UTIL_TraceHull( vOrigin, vTestPos, vHullMins, vHullMaxs, MASK_PLAYERSOLID, &filter, &result );
if ( !result.DidHit() )
{
//NDebugOverlay::Box( vTestPos, vHullMins, vHullMaxs, 0, 255, 0, 0, 5.f );
m_pOuter->Teleport( &vTestPos, &qAngle, NULL );
return;
}
else
{
//NDebugOverlay::Box( vTestPos, vHullMins, vHullMaxs, 255, 0, 0, 0, 5.f );
}
}
// just kill the player if we can't resolve getting stuck
m_pOuter->CommitSuicide( false, true );
}
#endif
}
void CTFPlayerShared::OnRemoveHalloweenGhostMode( void )
{
#ifdef CLIENT_DLL
m_pOuter->ParticleProp()->StopEmission();
m_pOuter->SetRenderColor( 255, 255, 255 );
m_pOuter->UpdateWearables();
#else
// We don't do the rest if we're a spectator
if ( m_pOuter->GetTeamNumber() == TEAM_SPECTATOR )
return;
m_pOuter->RemoveFlag( FL_NOTARGET );
// Restore solid
m_pOuter->SetSolid( SOLID_BBOX );
m_pOuter->SetSolidFlags( FSOLID_NOT_STANDABLE );
m_pOuter->SetCollisionGroup( COLLISION_GROUP_PLAYER );
// Bring their gun back
m_pOuter->SetActiveWeapon( m_pOuter->GetLastWeapon() );
if ( m_pOuter->GetActiveWeapon() )
{
m_pOuter->GetActiveWeapon()->Deploy();
}
#endif
}
#ifdef STAGING_ONLY
void CTFPlayerShared::OnAddSpaceGravity()
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
m_pOuter->EmitSound( "RD.SpaceGravityTransition" );
}
#endif
}
void CTFPlayerShared::OnRemoveSpaceGravity()
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
m_pOuter->EmitSound( "RD.SpaceGravityTransition" );
}
#endif
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddSelfConc()
{
UpdatePhaseEffects();
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveSelfConc()
{
RemovePhaseEffects();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddStealthedPhase( void )
{
#ifdef GAME_DLL
AddCond( TF_COND_SPEED_BOOST );
m_pOuter->m_takedamage = DAMAGE_NO;
#else
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_STEALTH, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveStealthedPhase( void )
{
#ifdef GAME_DLL
RemoveCond( TF_COND_SPEED_BOOST );
m_pOuter->m_takedamage = DAMAGE_YES;
// See if the spy is inside another player or object
Vector vecPos = m_pOuter->GetAbsOrigin();
trace_t trace;
UTIL_TraceHull( vecPos, vecPos, VEC_HULL_MIN_SCALED( m_pOuter ), VEC_HULL_MAX_SCALED( m_pOuter ), ( MASK_SOLID | CONTENTS_PLAYERCLIP ), m_pOuter, COLLISION_GROUP_NONE, &trace );
if ( trace.fraction < 1.f )
{
// Telefrag!
m_pOuter->TakeDamage( CTakeDamageInfo( m_pOuter, m_pOuter, 1000, DMG_CRUSH, TF_DMG_CUSTOM_TELEFRAG ) );
}
#else
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_STEALTH ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#endif
}
void CTFPlayerShared::OnAddClipOverload( void )
{
#ifdef GAME_DLL
m_pOuter->GiveAmmo( 1000, TF_AMMO_PRIMARY );
m_pOuter->GiveAmmo( 1000, TF_AMMO_SECONDARY );
m_pOuter->GiveAmmo( 1000, TF_AMMO_METAL );
m_pOuter->GiveAmmo( 1000, TF_AMMO_GRENADES1 );
m_pOuter->GiveAmmo( 1000, TF_AMMO_GRENADES2 );
m_pOuter->GiveAmmo( 1000, TF_AMMO_GRENADES3 );
// Refills weapon clips, too
for ( int i = 0; i < MAX_WEAPONS; i++ )
{
CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( m_pOuter->GetWeapon( i ) );
if ( !pWeapon )
continue;
float flPrevClipScale = pWeapon->GetClipScale();
if ( m_pOuter->GetActiveTFWeapon() == pWeapon )
{
pWeapon->AbortReload(); // Abort reload or else their reload will "fix" their clip size
pWeapon->SendWeaponAnim( ACT_VM_IDLE );
}
pWeapon->SetClipScale( 3.f );
pWeapon->GiveDefaultAmmo();
pWeapon->SetClipScale( flPrevClipScale );
if ( pWeapon->IsEnergyWeapon() )
{
pWeapon->WeaponRegenerate();
}
}
#endif
}
void CTFPlayerShared::OnRemoveClipOverload( void )
{
// Nothing required
}
#endif // STAGING_ONLY
void CTFPlayerShared::OnAddHalloweenKart( void )
{
#ifdef GAME_DLL
CSingleUserRecipientFilter filter( m_pOuter );
TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_HOW_TO_CONTROL_KART );
ApplyAttributeToPlayer( "head scale", 3.f );
//ResetKartDamage
m_pOuter->ResetKartDamage();
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
if ( pSpellBook )
{
pSpellBook->ClearSpell();
}
m_pOuter->m_flKartNextAvailableBoost = gpGlobals->curtime + 3.0f;
// Switch to melee to make sure Spies and Engies don't have build menus open
CTFWeaponBase *pMeleeWeapon = dynamic_cast<CTFWeaponBase*>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) );
Assert( pMeleeWeapon );
if ( pMeleeWeapon )
{
m_pOuter->Weapon_Switch( pMeleeWeapon );
}
m_pOuter->m_flNextBonusDucksVOAllowedTime = gpGlobals->curtime + 17.f; // The longest Merasmus line + 1 second
#else
extern ConVar tf_halloween_kart_cam_dist;
m_pOuter->SetTauntCameraTargets( tf_halloween_kart_cam_dist.GetFloat(), 0.0f );
m_pOuter->CreateKart();
// Set vehicle angles to be our current angles so we don't spin around
// when we get in the car
//$ This is handled in the ForcePlayerViewAngles user message.
//$ m_angVehicleMoveAngles = m_pOuter->GetAbsAngles();
if ( m_pOuter->GetActiveWeapon() )
{
m_pOuter->GetActiveWeapon()->SetWeaponVisible( false );
}
#endif
}
void CTFPlayerShared::OnRemoveHalloweenKart( void )
{
#ifdef GAME_DLL
RemoveAttributeFromPlayer( "head scale" );
//ResetKartDamage
m_pOuter->ResetKartDamage();
CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
if ( pSpellBook )
{
pSpellBook->ClearSpell();
}
#else
// When we have every taunt cam use this system, we should clean up after ourselves. But for now, this causes a bad interaction
// with other systems.
// m_pOuter->SetTauntCameraTargets( 0.0f, 0.0f );
m_pOuter->RemoveKart();
// Reset any tilting now that we're out of the karts
if ( m_pOuter->m_PlayerAnimState )
{
QAngle renderAngles = m_pOuter->m_PlayerAnimState->GetRenderAngles();
renderAngles[ ROLL ] = renderAngles[ PITCH ] = 0.f;
m_pOuter->m_PlayerAnimState->SetRenderangles( renderAngles );
}
if ( m_pOuter->GetActiveWeapon() )
{
m_pOuter->GetActiveWeapon()->SetWeaponVisible( true );
}
if ( m_hKartParachuteEntity )
{
m_hKartParachuteEntity->Release();
m_hKartParachuteEntity = NULL;
}
#endif
}
void CTFPlayerShared::OnAddBalloonHead( void )
{
#ifdef GAME_DLL
ApplyAttributeToPlayer( "voice pitch scale", 0.85f );
ApplyAttributeToPlayer( "head scale", 4.f );
ApplyAttributeToPlayer( "increased jump height", 0.8f );
ApplyAttributeToPlayer( "increased air control", 0.2f );
#endif // GAME_DLL
m_pOuter->SetGravity( 0.3f );
}
void CTFPlayerShared::OnRemoveBalloonHead( void )
{
#ifdef GAME_DLL
RemoveAttributeFromPlayer( "voice pitch scale" );
RemoveAttributeFromPlayer( "head scale" );
RemoveAttributeFromPlayer( "increased jump height" );
RemoveAttributeFromPlayer( "increased air control" );
#endif // GAME_DLL
m_pOuter->SetGravity( 0.f );
}
void CTFPlayerShared::OnAddMeleeOnly( void )
{
#ifdef GAME_DLL
CTFWeaponBase *pMeleeWeapon = dynamic_cast<CTFWeaponBase*>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) );
Assert( pMeleeWeapon );
if ( pMeleeWeapon )
{
m_pOuter->Weapon_Switch( pMeleeWeapon );
}
ApplyAttributeToPlayer( "disable weapon switch", true );
ApplyAttributeToPlayer( "hand scale", 3.f );
AddCond( TF_COND_HALLOWEEN_TINY );
AddCond( TF_COND_SPEED_BOOST );
#endif // GAME_DLL
}
void CTFPlayerShared::OnRemoveMeleeOnly( void )
{
#ifdef GAME_DLL
RemoveAttributeFromPlayer( "disable weapon switch" );
RemoveAttributeFromPlayer( "hand scale" );
RemoveCond( TF_COND_HALLOWEEN_TINY );
RemoveCond( TF_COND_SPEED_BOOST );
#endif // GAME_DLL
}
void CTFPlayerShared::OnAddSwimmingCurse( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_SWIMMING_CURSE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnRemoveSwimmingCurse( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
// only remove the overlay if it is urine
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_SWIMMING_CURSE ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#else
AddCond( TF_COND_URINE, 10.0f );
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnAddHalloweenKartCage( void )
{
#ifdef CLIENT_DLL
Assert( !m_pOuter->m_hHalloweenKartCage );
if ( !m_pOuter->m_hHalloweenKartCage )
{
m_pOuter->m_hHalloweenKartCage = C_PlayerAttachedModel::Create( "models/props_halloween/bumpercar_cage.mdl", m_pOuter, 0, vec3_origin, PAM_PERMANENT, 0 );
m_pOuter->m_hHalloweenKartCage->FollowEntity( m_pOuter, true );
}
#else
AddCond( TF_COND_FREEZE_INPUT );
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnRemoveHalloweenKartCage( void )
{
#ifdef CLIENT_DLL
Assert( m_pOuter->m_hHalloweenKartCage );
if ( m_pOuter->m_hHalloweenKartCage )
{
m_pOuter->m_hHalloweenKartCage->StopFollowingEntity();
m_pOuter->m_hHalloweenKartCage->Release();
}
#else
RemoveCond( TF_COND_FREEZE_INPUT );
DispatchParticleEffect( "ghost_appearation", PATTACH_ABSORIGIN, m_pOuter );
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnAddPasstimeInterception( void )
{
#ifdef CLIENT_DLL
if ( !m_pOuter->m_pPhaseStandingEffect )
{
m_pOuter->m_pPhaseStandingEffect = m_pOuter->ParticleProp()->Create( "warp_version", PATTACH_ABSORIGIN_FOLLOW );
}
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_PHASE, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#else
if ( !m_bPhaseFXOn )
{
AddPhaseEffects();
}
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:1" );
#endif
}
void CTFPlayerShared::OnRemovePasstimeInterception( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pPhaseStandingEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect );
m_pOuter->m_pPhaseStandingEffect = NULL;
}
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_PHASE ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#else
if ( m_bPhaseFXOn )
{
RemovePhaseEffects();
}
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_DODGING, "started_dodging:0" );
#endif
}
void CTFPlayerShared::OnAddRunePlague( void )
{
#ifdef CLIENT_DLL
m_pOuter->m_pRunePlagueEffect = m_pOuter->ParticleProp()->Create( "powerup_plague_carrier", PATTACH_ABSORIGIN_FOLLOW );
// show resist effect on enemy player that has plague rune if local player is in plague cond
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && pLocalPlayer != m_pOuter && pLocalPlayer->m_Shared.InCond( TF_COND_PLAGUE ) && m_pOuter->IsEnemyPlayer() )
{
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_RUNE_PLAGUE );
}
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnRemoveRunePlague( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pRunePlagueEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRunePlagueEffect );
m_pOuter->m_pRunePlagueEffect = NULL;
}
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnAddPlague( void )
{
#ifdef CLIENT_DLL
m_pOuter->EmitSound( "Powerup.PickUpPlagueInfected" );
#endif
CTFPlayer *pProvider = ToTFPlayer( m_ConditionData[TF_COND_PLAGUE].m_pProvider );
//plague damage is a percentage of player health so everyone has the same life expectancy
float flPlagueDmg = 0.05f * m_pOuter->GetMaxHealth();
if ( pProvider )
{
MakeBleed( pProvider, NULL, 0.f, flPlagueDmg, true );
CSingleUserRecipientFilter localFilter( pProvider );
pProvider->EmitSound( localFilter, pProvider->entindex(), "Powerup.PickUpPlagueInfected" );
}
m_pOuter->EmitSound( "Powerup.PickUpPlagueInfectedLoop" );
ClientPrint( m_pOuter, HUD_PRINTCENTER, "#TF_Powerup_Contract_Plague" );
#ifdef CLIENT_DLL
// show resist effect on enemy player that has plague rune if local player is in plague cond
if ( m_pOuter->IsLocalPlayer() && pProvider && pProvider->IsEnemyPlayer() )
{
AddResistShield( &pProvider->m_pTempShield, pProvider, TF_COND_RUNE_PLAGUE );
}
#endif // CLIENT_DLL
}
void CTFPlayerShared::OnRemovePlague( void )
{
m_pOuter->StopSound( "Powerup.PickUpPlagueInfectedLoop" );
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_BLEED ) )
{
view->SetScreenOverlayMaterial( NULL );
}
// remove shield from the current plague rune carrier
int iEnemyTeam = m_pOuter->GetTeamNumber() == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED;
CTFPlayer *pCurrentRuneCarrier = GetRuneCarrier( RUNE_PLAGUE, iEnemyTeam );
if ( pCurrentRuneCarrier )
{
RemoveResistShield( &pCurrentRuneCarrier->m_pTempShield, pCurrentRuneCarrier );
}
}
#endif
// RemoveCond( TF_COND_BLEEDING );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddInPurgatory( void )
{
#ifdef GAME_DLL
// just entered
m_pOuter->m_purgatoryPainMultiplierTimer.Start( 40.0f );
m_pOuter->m_purgatoryPainMultiplier = 1;
// Set our health to full
m_pOuter->SetHealth( m_pOuter->GetMaxHealth() );
// Remove our projectiles
m_pOuter->RemoveOwnedProjectiles();
// Give us a brief period of invuln while we drop into purgatory
AddCond( TF_COND_INVULNERABLE, 1.5f );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveInPurgatory( void )
{
#ifdef GAME_DLL
if ( m_pOuter->IsAlive() )
{
// we escaped purgatory alive!
const float buffDuration = 10.0f;
AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration );
AddCond( TF_COND_SPEED_BOOST, buffDuration );
AddCond( TF_COND_INVULNERABLE, buffDuration );
m_pOuter->SetHealth( 2.0f * m_pOuter->GetMaxHealth() );
m_pOuter->m_purgatoryBuffTimer.Start( buffDuration );
TFGameRules()->BroadcastSound( 255, "Halloween.PlayerEscapedUnderworld" );
// Remove our projectiles
m_pOuter->RemoveOwnedProjectiles();
CReliableBroadcastRecipientFilter filter;
const char* pszEscapeMessage = "#TF_Halloween_Underworld";
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
{
pszEscapeMessage = "#TF_Halloween_Skull_Island_Escape";
}
UTIL_SayText2Filter( filter, m_pOuter, false, pszEscapeMessage, m_pOuter->GetPlayerName() );
IGameEvent *pEvent = gameeventmanager->CreateEvent( "escaped_loot_island" );
if ( pEvent )
{
pEvent->SetInt( "player", m_pOuter->GetUserID() );
gameeventmanager->FireEvent( pEvent, true );
}
if ( m_pOuter->GetTeam() )
{
const char* pszLogMessage = "purgatory_escaped";
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
{
pszEscapeMessage = "skull_island_escaped";
}
UTIL_LogPrintf( "HALLOWEEN: \"%s<%i><%s><%s>\" %s\n",
m_pOuter->GetPlayerName(),
m_pOuter->GetUserID(),
m_pOuter->GetNetworkIDString(),
m_pOuter->GetTeam()->GetName(),
pszLogMessage );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddCompetitiveWinner( void )
{
#ifdef GAME_DLL
#else
if ( m_pOuter->IsLocalPlayer() )
{
gHUD.LockRenderGroup( gHUD.LookupRenderGroupIndexByName( "mid" ) );
m_pOuter->UpdateVisibility();
m_pOuter->UpdateWearables();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveCompetitiveWinner( void )
{
#ifdef GAME_DLL
#else
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddCompetitiveLoser( void )
{
#ifdef GAME_DLL
#else
if ( m_pOuter->IsLocalPlayer() )
{
gHUD.LockRenderGroup( gHUD.LookupRenderGroupIndexByName( "mid" ) );
m_pOuter->UpdateVisibility();
m_pOuter->UpdateWearables();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveCompetitiveLoser( void )
{
#ifdef GAME_DLL
#else
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateChargeMeter( void )
{
if ( !m_pOuter->IsPlayerClass( TF_CLASS_DEMOMAN ) )
return;
if ( InCond( TF_COND_SHIELD_CHARGE ) )
{
// Drain the meter while we are charging.
float flChargeDrainTime = tf_demoman_charge_drain_time.GetFloat();
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flChargeDrainTime, mod_charge_time );
float flChargeDrainMod = 100.f / flChargeDrainTime;
m_flChargeMeter -= gpGlobals->frametime * flChargeDrainMod;
if ( m_flChargeMeter <= 0 )
{
m_flChargeMeter = 0;
RemoveCond( TF_COND_SHIELD_CHARGE );
}
m_flLastNoChargeTime = gpGlobals->curtime;
}
else if ( m_flChargeMeter < 100.f )
{
// Recharge the meter while we are not charging.
float flChargeRegenMod = tf_demoman_charge_regen_rate.GetFloat();
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flChargeRegenMod, charge_recharge_rate );
if ( TFGameRules() && TFGameRules()->IsPowerupMode() && GetCarryingRuneType() == RUNE_KNOCKOUT )
{
flChargeRegenMod *= 0.2f;
}
flChargeRegenMod = Max( flChargeRegenMod, 1.f );
m_flChargeMeter += gpGlobals->frametime * flChargeRegenMod;
if ( m_flChargeMeter > 100.f )
{
m_flChargeMeter = 100.f;
}
// Used for the weapon glow cooldown.
if ( !m_bChargeGlowing )
{
m_flLastNoChargeTime = gpGlobals->curtime;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::EndCharge()
{
if ( !InCond( TF_COND_SHIELD_CHARGE ) )
return;
#ifdef GAME_DLL
if ( GetDemomanChargeMeter() < 90 )
{
// Impacts drain the charge meter completely.
float flMeterAtImpact = m_flChargeMeter;
CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( m_pOuter );
if ( pWearableShield )
{
pWearableShield->ShieldBash( m_pOuter, flMeterAtImpact );
}
CalcChargeCrit();
// Removing the condition here would cause issues with prediction, so we set the
// duration to zero so that it will be removed during the next condition think.
SetConditionDuration( TF_COND_SHIELD_CHARGE, 0 );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayerShared::CalculateChargeCap( void ) const
{
float flCap = 0.45f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flCap, charge_turn_control );
// Scale yaw cap based on frametime to prevent differences in turn effectiveness due to variable framerate (between clients mainly)
if ( tf_demoman_charge_frametime_scaling.GetBool() )
{
// There's probably something better to use here as a baseline, instead of TICK_INTERVAL
float flMod = RemapValClamped( gpGlobals->frametime, ( TICK_INTERVAL * YAW_CAP_SCALE_MIN ), ( TICK_INTERVAL * YAW_CAP_SCALE_MAX ), 0.25f, 2.f );
flCap *= flMod;
}
return flCap;
}
bool CTFPlayerShared::HasDemoShieldEquipped() const
{
return GetEquippedDemoShield( m_pOuter ) != NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::CalcChargeCrit( bool bForceCrit )
{
#ifdef GAME_DLL
// Keying on TideTurner
int iDemoChargeDamagePenalty = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iDemoChargeDamagePenalty, lose_demo_charge_on_damage_when_charging );
if ( iDemoChargeDamagePenalty && GetDemomanChargeMeter() <= 75 )
{
SetNextMeleeCrit( MELEE_MINICRIT );
}
else if ( GetDemomanChargeMeter() <= 40 || bForceCrit)
{
SetNextMeleeCrit( MELEE_CRIT );
}
else if ( GetDemomanChargeMeter() <= 75 )
{
SetNextMeleeCrit( MELEE_MINICRIT );
}
m_pOuter->SetContextThink( &CTFPlayer::RemoveMeleeCrit, gpGlobals->curtime + 0.3f, "RemoveMeleeCrit" );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddShieldCharge( void )
{
UpdatePhaseEffects();
m_pOuter->TeamFortress_SetSpeed();
#ifdef CLIENT_DLL
m_pOuter->EmitSound( "DemoCharge.Charging" );
#else
m_hPlayersVisibleAtChargeStart.Purge();
// Remove debuffs
for ( int i = 0; g_aDebuffConditions[i] != TF_COND_LAST; i++ )
{
RemoveCond( g_aDebuffConditions[i] );
}
// store the players we CAN see for the TF_DEMOMAN_KILL_PLAYER_YOU_DIDNT_SEE achievement
CUtlVector<CTFPlayer *> vecPlayers;
CollectPlayers( &vecPlayers, ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED, true );
FOR_EACH_VEC( vecPlayers, i )
{
if ( !vecPlayers[i] )
continue;
if ( vecPlayers[i]->m_Shared.InCond( TF_COND_STEALTHED ) )
continue;
// can we see them?
if ( m_pOuter->FVisible( vecPlayers[i], MASK_OPAQUE ) == false )
continue;
// are they in our field of view? (might be behind us)
if ( m_pOuter->IsInFieldOfView( vecPlayers[i]) == false )
continue;
m_hPlayersVisibleAtChargeStart.AddToTail( vecPlayers[i] );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveShieldCharge( void )
{
RemovePhaseEffects();
m_pOuter->TeamFortress_SetSpeed();
m_bPostShieldCharge = true;
m_flChargeEndTime = gpGlobals->curtime;
m_flChargeMeter = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::InterruptCharge( void )
{
if ( !InCond( TF_COND_SHIELD_CHARGE ) )
return;
SetConditionDuration( TF_COND_SHIELD_CHARGE, 0 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#ifdef GAME_DLL
void CTFPlayer::RemoveMeleeCrit( void )
{
m_Shared.SetNextMeleeCrit( MELEE_NOCRIT );
m_Shared.m_bPostShieldCharge = false;
// Remove crit boost right away. DemoShieldChargeThink depends on m_bPostShieldCharge being true
// to attempt to remove crits (which we just cleared) so clear crits here as well.
if ( m_Shared.InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) )
{
m_Shared.RemoveCond( TF_COND_CRITBOOSTED_DEMO_CHARGE );
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::DemoShieldChargeThink( void )
{
if ( InCond( TF_COND_SHIELD_CHARGE ) || m_bPostShieldCharge )
{
if ( m_bPostShieldCharge && (gpGlobals->curtime - m_flChargeEndTime) >= 0.3f )
{
if ( InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) )
{
RemoveCond( TF_COND_CRITBOOSTED_DEMO_CHARGE );
}
m_bPostShieldCharge = false;
}
else if ( InCond( TF_COND_SHIELD_CHARGE ) && GetDemomanChargeMeter() < 75 )
{
if ( !InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) )
{
AddCond( TF_COND_CRITBOOSTED_DEMO_CHARGE );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddDemoBuff( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateDemomanEyeEffect( m_iDecapitations );
#else
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddEnergyDrinkBuff( void )
{
#ifdef CLIENT_DLL
UpdateCritBoostEffect();
#endif
#ifdef GAME_DLL
if ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
{
// Begin berzerker speed buff.
m_pOuter->TeamFortress_SetSpeed();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveEnergyDrinkBuff( void )
{
#ifdef CLIENT_DLL
UpdateCritBoostEffect();
#endif
#ifdef GAME_DLL
if ( m_pOuter->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
{
// End berzerker speed buff.
m_pOuter->TeamFortress_SetSpeed();
}
#endif
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::EndRadiusHealEffect( void )
{
if ( m_pOuter->m_pRadiusHealEffect )
{
m_pOuter->m_pRadiusHealEffect->StopEmission();
m_pOuter->m_pRadiusHealEffect = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::EndKingBuffRadiusEffect( void )
{
// For buffed player
if ( m_pOuter->m_pKingBuffRadiusEffect )
{
m_pOuter->m_pKingBuffRadiusEffect->StopEmission();
m_pOuter->m_pKingBuffRadiusEffect = NULL;
}
// For carrier of King Rune
if ( m_pOuter->m_pKingRuneRadiusEffect )
{
m_pOuter->m_pKingRuneRadiusEffect->StopEmission();
m_pOuter->m_pKingRuneRadiusEffect = NULL;
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddRadiusHeal( void )
{
#ifdef CLIENT_DLL
if ( InCond( TF_COND_RADIUSHEAL ) )
{
if ( IsStealthed() )
{
EndRadiusHealEffect();
return;
}
const char *pszRadiusHealEffect;
int nTeamNumber = m_pOuter->GetTeamNumber();
if ( m_pOuter->IsPlayerClass( TF_CLASS_SPY ) && InCond( TF_COND_DISGUISED ) && ( GetDisguiseTeam() == GetLocalPlayerTeam() ) )
{
nTeamNumber = GetLocalPlayerTeam();
}
if ( nTeamNumber == TF_TEAM_RED )
{
pszRadiusHealEffect = "medic_healradius_red_buffed";
}
else
{
pszRadiusHealEffect = "medic_healradius_blue_buffed";
}
if ( !m_pOuter->m_pRadiusHealEffect )
{
m_pOuter->m_pRadiusHealEffect = m_pOuter->ParticleProp()->Create( pszRadiusHealEffect, PATTACH_ABSORIGIN_FOLLOW );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveRadiusHeal( void )
{
#ifdef CLIENT_DLL
EndRadiusHealEffect();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddMegaHeal( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
const char *pEffectName = NULL;
if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
{
pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED;
}
else
{
pEffectName = TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE;
}
IMaterial *pMaterial = materials->FindMaterial( pEffectName, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
if ( InCond( TF_COND_MEGAHEAL ) )
{
const char *pszMegaHealEffectName;
if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
{
pszMegaHealEffectName = "medic_megaheal_red";
}
else
{
pszMegaHealEffectName = "medic_megaheal_blue";
}
if ( !m_pOuter->m_pMegaHealEffect )
{
m_pOuter->m_pMegaHealEffect = m_pOuter->ParticleProp()->Create( pszMegaHealEffectName, PATTACH_ABSORIGIN_FOLLOW );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveMegaHeal( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pMegaHealEffect )
{
m_pOuter->m_pMegaHealEffect->StopEmission();
m_pOuter->m_pMegaHealEffect = NULL;
}
if ( m_pOuter->IsLocalPlayer() )
{
// only remove the overlay if it is an invuln material
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial &&
( FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_BLUE ) ||
FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_INVULN_RED ) ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddKingBuff( void )
{
#ifdef CLIENT_DLL
if ( IsStealthed() )
{
EndKingBuffRadiusEffect();
return;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveKingBuff( void )
{
#ifdef CLIENT_DLL
EndKingBuffRadiusEffect();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveRuneSupernova( void )
{
SetRuneCharge( 0.f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveDemoBuff( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateDemomanEyeEffect( 0 );
#else
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
void CTFPlayerShared::ClientDemoBuffThink( void )
{
if ( m_iDecapitations > 0 )
{
if ( m_iDecapitations != m_iOldDecapitations )
{
m_iOldDecapitations = m_iDecapitations;
m_pOuter->UpdateDemomanEyeEffect( m_iDecapitations );
}
}
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::ClientKillStreakBuffThink( void )
{
int nLoadoutSlot = m_pOuter->GetActiveTFWeapon() ? m_pOuter->GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( m_pOuter->GetPlayerClass()->GetClassIndex() ) : LOADOUT_POSITION_PRIMARY;
int nKillStreak = GetStreak(kTFStreak_Kills);
if ( nKillStreak != m_iOldKillStreak || m_iOldKillStreakWepSlot != nLoadoutSlot )
{
m_pOuter->UpdateKillStreakEffects( nKillStreak, m_iOldKillStreak < nKillStreak );
m_iOldKillStreak = nKillStreak;
m_iOldKillStreakWepSlot = nLoadoutSlot;
}
else if ( !m_pOuter->IsAlive() )
{
m_pOuter->UpdateKillStreakEffects( 0, false );
}
else
{
static bool bAlternate = false;
Vector vColor = bAlternate ? m_pOuter->m_vEyeGlowColor1 : m_pOuter->m_vEyeGlowColor2;
if ( m_pOuter->m_pEyeGlowEffect[0] )
{
m_pOuter->m_pEyeGlowEffect[0]->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
}
if ( m_pOuter->m_pEyeGlowEffect[1] )
{
m_pOuter->m_pEyeGlowEffect[1]->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
}
//
bAlternate = !bAlternate;
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddTeleported( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateRecentlyTeleportedEffect();
m_flGotTeleEffectAt = gpGlobals->curtime;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveTeleported( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateRecentlyTeleportedEffect();
#endif
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::ShouldShowRecentlyTeleported( void )
{
if ( IsStealthed() )
{
return false;
}
if ( m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
{
// disguised as an enemy
if ( InCond( TF_COND_DISGUISED ) && GetDisguiseTeam() != m_pOuter->GetTeamNumber() )
{
// was this my own team's teleporter?
if ( GetTeamTeleporterUsed() == m_pOuter->GetTeamNumber() )
{
// don't show my trail
return false;
}
else
{
// okay to show the local player the trail, but not his team (might confuse them)
if ( !m_pOuter->IsLocalPlayer() && m_pOuter->GetTeamNumber() == GetLocalPlayerTeam() )
{
return false;
}
}
}
else
{
if ( GetTeamTeleporterUsed() != m_pOuter->GetTeamNumber() )
{
return false;
}
}
}
return InCond( TF_COND_TELEPORTED );
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::Burn( CTFPlayer *pAttacker, CTFWeaponBase *pWeapon, float flBurningTime /*=-1*/ )
{
#ifdef CLIENT_DLL
#else
// Don't bother igniting players who have just been killed by the fire damage.
if ( !m_pOuter->IsAlive() )
return;
//Don't ignite if I'm in phase mode.
if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_PASSTIME_INTERCEPTION ) )
return;
// pyros don't burn persistently or take persistent burning damage, but we show brief burn effect so attacker can tell they hit
bool bVictimIsPyro = ( TF_CLASS_PYRO == m_pOuter->GetPlayerClass()->GetClassIndex() );
if ( !InCond( TF_COND_BURNING ) )
{
// Start burning
AddCond( TF_COND_BURNING, -1.f, pAttacker );
m_flFlameBurnTime = gpGlobals->curtime; //asap
// let the attacker know he burned me
if ( pAttacker && !bVictimIsPyro )
{
pAttacker->OnBurnOther( m_pOuter, pWeapon );
m_hOriginalBurnAttacker = pAttacker;
}
}
int nAfterburnImmunity = 0;
// Check my weapon
CTFWeaponBase *pMyWeapon = GetActiveTFWeapon();
if ( pMyWeapon )
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pMyWeapon, nAfterburnImmunity, afterburn_immunity );
}
// STAGING_SPY
if ( InCond( TF_COND_AFTERBURN_IMMUNE ) )
{
nAfterburnImmunity = 1;
m_flFlameRemoveTime = 0;
}
// Check shield
if ( !nAfterburnImmunity && IsShieldEquipped() )
{
CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( m_pOuter );
if ( pWearableShield && !pWearableShield->IsDisguiseWearable() )
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearableShield, nAfterburnImmunity, afterburn_immunity );
}
}
bool bIsFlareGun = ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_FLAREGUN );
float flFlameLife;
if ( bVictimIsPyro || nAfterburnImmunity )
{
flFlameLife = TF_BURNING_FLAME_LIFE_PYRO;
}
else if ( flBurningTime > 0 )
{
flFlameLife = flBurningTime;
}
else if ( bIsFlareGun )
{
flFlameLife = TF_BURNING_FLAME_LIFE_FLARE;
}
else if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_PARTICLE_CANNON )
{
flFlameLife = TF_BURNING_FLAME_LIFE_PLASMA;
}
else
{
flFlameLife = TF_BURNING_FLAME_LIFE;
}
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flFlameLife, mult_wpn_burntime );
float flBurnEnd = gpGlobals->curtime + flFlameLife;
if ( flBurnEnd > m_flFlameRemoveTime )
{
m_flFlameRemoveTime = flBurnEnd;
}
m_hBurnAttacker = pAttacker;
m_hBurnWeapon = pWeapon;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: A non-TF Player burned us
// Used for Bosses, they inflict self burn
//-----------------------------------------------------------------------------
void CTFPlayerShared::SelfBurn( float flBurningTime )
{
#ifdef GAME_DLL
Burn( m_pOuter, NULL, flBurningTime );
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::MakeBleed( CTFPlayer *pPlayer, CTFWeaponBase *pWeapon, float flBleedingTime, int nBleedDmg /* = TF_BLEEDING_DMG */, bool bPermanentBleeding /*= false*/ )
{
#ifdef CLIENT_DLL
#else
// Don't bother if they are dead
if ( !m_pOuter->IsAlive() )
return;
// Required for the CTakeDamageInfo we create later
Assert( pPlayer && pWeapon );
if ( !pPlayer && !pWeapon )
return;
float flExpireTime = gpGlobals->curtime + flBleedingTime;
// See if this weapon has already applied a bleed and extend the time
FOR_EACH_VEC( m_PlayerBleeds, i )
{
if ( m_PlayerBleeds[i].hBleedingAttacker && m_PlayerBleeds[i].hBleedingAttacker == pPlayer &&
m_PlayerBleeds[i].hBleedingWeapon && m_PlayerBleeds[i].hBleedingWeapon == pWeapon )
{
if ( flExpireTime > m_PlayerBleeds[i].flBleedingRemoveTime )
{
m_PlayerBleeds[i].flBleedingRemoveTime = flExpireTime;
return;
}
}
}
// New bleed source
bleed_struct_t bleedinfo =
{
pPlayer, // hBleedingAttacker
pWeapon, // hBleedingWeapon
flBleedingTime, // flBleedingTime
flExpireTime, // flBleedingRemoveTime
nBleedDmg, // nBleedDmg
bPermanentBleeding
};
m_PlayerBleeds.AddToTail( bleedinfo );
if ( !InCond( TF_COND_BLEEDING ) )
{
AddCond( TF_COND_BLEEDING, -1.f, pPlayer );
}
#endif
}
#ifdef GAME_DLL
void CTFPlayerShared::StopBleed( CTFPlayer *pPlayer, CTFWeaponBase *pWeapon )
{
FOR_EACH_VEC_BACK( m_PlayerBleeds, i )
{
const bleed_struct_t& bleed = m_PlayerBleeds[i];
if ( bleed.hBleedingAttacker == pPlayer && bleed.hBleedingWeapon == pWeapon )
{
m_PlayerBleeds.FastRemove( i );
}
}
// remove condition right away when the list is empty
if ( !m_PlayerBleeds.Count() )
{
RemoveCond( TF_COND_BLEEDING );
}
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveBurning( void )
{
#ifdef CLIENT_DLL
m_pOuter->StopBurningSound();
if ( m_pOuter->m_nOldWaterLevel > 0 )
{
m_pOuter->ParticleProp()->Create( "water_burning_steam", PATTACH_ABSORIGIN );
}
if ( m_pOuter->m_pBurningEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pBurningEffect );
m_pOuter->m_pBurningEffect = NULL;
}
if ( m_pOuter->IsLocalPlayer() )
{
// only remove the overlay if it is the burning
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_BURNING ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
m_pOuter->m_flBurnEffectStartTime = 0;
#else
m_hBurnAttacker = NULL;
m_hOriginalBurnAttacker = NULL;
m_hBurnWeapon = NULL;
m_pOuter->ClearBurnFromBehindAttackers();
// If we were on fire and now we're not, and we're still alive, then give ourself some credit
// for surviving this fire if we have any items that track it.
if ( m_nPlayerState == TF_STATE_ACTIVE )
{
HatAndMiscEconEntities_OnOwnerKillEaterEventNoParter( m_pOuter, kKillEaterEvent_FiresSurvived );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveOverhealed( void )
{
#ifdef CLIENT_DLL
if ( !m_pOuter->IsLocalPlayer() )
{
m_pOuter->UpdateOverhealEffect();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveDemoCharge( void )
{
#ifdef CLIENT_DLL
m_pOuter->StopSound( "DemoCharge.ChargeCritOn" );
m_pOuter->EmitSound( "DemoCharge.ChargeCritOff" );
UpdateCritBoostEffect();
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveCritBoost( void )
{
#ifdef CLIENT_DLL
UpdateCritBoostEffect();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveTmpDamageBonus( void )
{
m_flTmpDamageBonusAmount = 1.0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddStealthed( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) )
return;
if ( !InCond( TF_COND_FEIGN_DEATH ) )
{
m_pOuter->EmitSound( "Player.Spy_Cloak" );
}
m_pOuter->RemoveAllDecals();
UpdateCritBoostEffect();
if ( m_pOuter->m_pTempShield && GetCarryingRuneType() == RUNE_RESIST )
{
RemoveResistShield( &m_pOuter->m_pTempShield, m_pOuter );
}
#endif
bool bSetInvisChangeTime = true;
#ifdef CLIENT_DLL
if ( !m_pOuter->IsLocalPlayer() )
{
// We only clientside predict changetime for the local player
bSetInvisChangeTime = false;
}
if ( InCond( TF_COND_STEALTHED_USER_BUFF ) && m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_STEALTH, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#endif
if ( bSetInvisChangeTime )
{
if ( !InCond( TF_COND_FEIGN_DEATH ) && !InCond( TF_COND_STEALTHED_USER_BUFF ) )
{
float flInvisTime = tf_spy_invis_time.GetFloat();
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flInvisTime, mult_cloak_rate );
m_flInvisChangeCompleteTime = gpGlobals->curtime + flInvisTime;
}
else
{
m_flInvisChangeCompleteTime = gpGlobals->curtime; // Stealth immediately if we are in feign death.
}
}
// set our offhand weapon to be the invis weapon, but only for the spy's stealth
if ( InCond( TF_COND_STEALTHED ) )
{
for (int i = 0; i < m_pOuter->WeaponCount(); i++)
{
CTFWeaponInvis *pWpn = (CTFWeaponInvis *) m_pOuter->GetWeapon(i);
if ( !pWpn )
continue;
if ( pWpn->GetWeaponID() != TF_WEAPON_INVIS )
continue;
// try to switch to this weapon
m_pOuter->SetOffHandWeapon( pWpn );
m_bMotionCloak = pWpn->HasMotionCloak();
break;
}
}
m_pOuter->TeamFortress_SetSpeed();
#ifdef CLIENT_DLL
// Remove water balloon effect if it on player
m_pOuter->ParticleProp()->StopParticlesNamed( "balloontoss_drip", true );
m_pOuter->UpdateSpyStateChange();
m_pOuter->UpdateKillStreakEffects( GetStreak( kTFStreak_Kills ) );
#endif
#ifdef GAME_DLL
m_flCloakStartTime = gpGlobals->curtime;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveStealthed( void )
{
#ifdef CLIENT_DLL
if ( !m_bSyncingConditions )
return;
CTFWeaponInvis *pWpn = (CTFWeaponInvis *) m_pOuter->Weapon_OwnsThisID( TF_WEAPON_INVIS );
int iReducedCloak = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iReducedCloak, set_quiet_unstealth );
if ( iReducedCloak == 1 )
{
m_pOuter->EmitSound( "Player.Spy_UnCloakReduced" );
}
else if ( pWpn && pWpn->HasFeignDeath() )
{
m_pOuter->EmitSound( "Player.Spy_UnCloakFeignDeath" );
}
else
{
m_pOuter->EmitSound( "Player.Spy_UnCloak" );
}
UpdateCritBoostEffect( kCritBoost_ForceRefresh );
if ( m_pOuter->IsLocalPlayer() && !InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
{
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_STEALTH ) )
{
view->SetScreenOverlayMaterial( NULL );
}
}
if ( !m_pOuter->m_pTempShield && GetCarryingRuneType() == RUNE_RESIST )
{
AddResistShield( &m_pOuter->m_pTempShield, m_pOuter, TF_COND_RUNE_RESIST );
}
#else
if ( m_flCloakStartTime > 0 )
{
// Calc a time and report every minute
float flCloaktime = ( gpGlobals->curtime - m_flCloakStartTime );
if ( flCloaktime > 0 )
{
EconEntity_OnOwnerKillEaterEventNoPartner(
dynamic_cast<CEconEntity *>( m_pOuter->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA2 ) ),
m_pOuter,
kKillEaterEvent_TimeCloaked,
(int)flCloaktime
);
}
m_flCloakStartTime = 0;
}
#endif
// End feign death if we leave stealth for some reason.
if ( InCond( TF_COND_FEIGN_DEATH ) )
{
RemoveCond( TF_COND_FEIGN_DEATH );
}
m_pOuter->HolsterOffHandWeapon();
m_pOuter->TeamFortress_SetSpeed();
m_bMotionCloak = false;
#ifdef CLIENT_DLL
m_pOuter->UpdateSpyStateChange();
m_pOuter->UpdateKillStreakEffects( GetStreak( kTFStreak_Kills ) );
#endif
#ifdef STAGING_ONLY
if ( HasPhaseCloakAbility() )
{
RemoveCond( TF_COND_STEALTHED_PHASE );
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddStealthedUserBuffFade( void )
{
#ifdef CLIENT_DLL
// If a player is firing their weapon while radius stealth hits them, we never
// get a chance to apply the screenoverlay effect, so apply it here instead.
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_STEALTH, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveStealthedUserBuffFade( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = view->GetScreenOverlayMaterial();
if ( pMaterial && FStrEq( pMaterial->GetName(), TF_SCREEN_OVERLAY_MATERIAL_STEALTH ) )
{
view->SetScreenOverlayMaterial( NULL );
return;
}
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddFeignDeath( void )
{
#ifdef CLIENT_DLL
// STAGING_SPY
AddUberScreenEffect( m_pOuter );
#else
#endif
// Go stealth w/o sound or fade out.
if ( !IsStealthed() )
{
AddCond( TF_COND_STEALTHED, -1.f, m_pOuter );
}
// STAGING_SPY
// Add a speed boost while feigned and afterburn immunity while running away
AddCond( TF_COND_SPEED_BOOST, tf_feign_death_speed_duration.GetFloat() );
AddCond( TF_COND_AFTERBURN_IMMUNE, tf_feign_death_speed_duration.GetFloat() );
SetFeignDeathReady( false );
m_flFeignDeathEnd = gpGlobals->curtime + tf_feign_death_speed_duration.GetFloat();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveFeignDeath( void )
{
#ifdef CLIENT_DLL
// STAGING_SPY
RemoveUberScreenEffect( m_pOuter );
#endif
// Previous code removed cloak meter, this has been moved to on RemoveStealth checking for steath type
// FeignDeath is the duration of cloak where speed, no shimmer and damage reduction take place
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveDisguising( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pDisguisingEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pDisguisingEffect );
m_pOuter->m_pDisguisingEffect = NULL;
}
#else
m_nDesiredDisguiseTeam = TF_SPY_UNDEFINED;
// Do not reset this value, we use the last desired disguise class for the
// 'lastdisguise' command
//m_nDesiredDisguiseClass = TF_CLASS_UNDEFINED;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveDisguised( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->GetPredictable() && ( !prediction->IsFirstTimePredicted() || m_bSyncingConditions ) )
return;
// if local player is on the other team, reset the model of this player
CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !m_pOuter->InSameTeam( pLocalPlayer ) )
{
TFPlayerClassData_t *pData = GetPlayerClassData( TF_CLASS_SPY );
int iIndex = modelinfo->GetModelIndex( pData->GetModelName() );
m_pOuter->SetModelIndex( iIndex );
}
m_pOuter->EmitSound( "Player.Spy_Disguise" );
// They may have called for medic and created a visible medic bubble
m_pOuter->StopSaveMeEffect( true );
m_pOuter->StopTauntWithMeEffect();
UpdateCritBoostEffect( kCritBoost_ForceRefresh );
m_pOuter->UpdateSpyStateChange();
#else
m_nDisguiseTeam = TF_SPY_UNDEFINED;
m_nDisguiseClass.Set( TF_CLASS_UNDEFINED );
m_nDisguiseSkinOverride = 0;
m_hDisguiseTarget.Set( NULL );
m_iDisguiseTargetIndex = TF_DISGUISE_TARGET_INDEX_NONE;
m_iDisguiseHealth = 0;
SetDisguiseBody( 0 );
m_iDisguiseAmmo = 0;
// Update the player model and skin.
m_pOuter->UpdateModel();
m_pOuter->ClearExpression();
m_pOuter->ClearDisguiseWeaponList();
RemoveDisguiseWeapon();
RemoveDisguiseWearables();
#endif
m_pOuter->TeamFortress_SetSpeed();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddBurning( void )
{
#ifdef CLIENT_DLL
// Start the burning effect
if ( !m_pOuter->m_pBurningEffect )
{
if ( !( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) && m_pOuter->IsLocalPlayer() ) )
{
const char *pEffectName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "burningplayer_red" : "burningplayer_blue";
m_pOuter->m_pBurningEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
}
m_pOuter->m_flBurnEffectStartTime = gpGlobals->curtime;
}
// set the burning screen overlay
if ( m_pOuter->IsLocalPlayer() )
{
IMaterial *pMaterial = materials->FindMaterial( TF_SCREEN_OVERLAY_MATERIAL_BURNING, TEXTURE_GROUP_CLIENT_EFFECTS, false );
if ( !IsErrorMaterial( pMaterial ) )
{
view->SetScreenOverlayMaterial( pMaterial );
}
}
#endif
// play a fire-starting sound
m_pOuter->EmitSound( "Fire.Engulf" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddOverhealed( void )
{
#ifdef CLIENT_DLL
// Start the Overheal effect
if ( !m_pOuter->IsLocalPlayer() )
{
m_pOuter->UpdateOverhealEffect();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddStunned( void )
{
if ( IsControlStunned() || IsLoserStateStunned() )
{
#ifdef CLIENT_DLL
if ( GetActiveStunInfo() )
{
if ( !m_pOuter->m_pStunnedEffect && !( GetActiveStunInfo()->iStunFlags & TF_STUN_NO_EFFECTS ) )
{
if ( ( GetActiveStunInfo()->iStunFlags & TF_STUN_BY_TRIGGER ) || InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
{
const char* pEffectName = "yikes_fx";
m_pOuter->m_pStunnedEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_POINT_FOLLOW, "head" );
}
else
{
const char* pEffectName = "conc_stars";
m_pOuter->m_pStunnedEffect = m_pOuter->ParticleProp()->Create( pEffectName, PATTACH_POINT_FOLLOW, "head" );
}
}
}
#endif
// Notify our weapon that we have been stunned.
CTFWeaponBase* pWpn = m_pOuter->GetActiveTFWeapon();
if ( pWpn )
{
pWpn->OnControlStunned();
}
if ( InCond( TF_COND_SHIELD_CHARGE ) )
{
SetDemomanChargeMeter( 0 );
RemoveCond( TF_COND_SHIELD_CHARGE );
}
m_pOuter->TeamFortress_SetSpeed();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveStunned( void )
{
m_iStunFlags = 0;
m_hStunner = NULL;
#ifdef CLIENT_DLL
if ( m_pOuter->m_pStunnedEffect )
{
// Remove stun stars if they are still around.
// They might be if we died, etc.
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pStunnedEffect );
m_pOuter->m_pStunnedEffect = NULL;
}
#else
m_iStunIndex = -1;
m_PlayerStuns.RemoveAll();
#endif
m_pOuter->TeamFortress_SetSpeed();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ControlStunFading( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pStunnedEffect )
{
// Remove stun stars early...
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pStunnedEffect );
m_pOuter->m_pStunnedEffect = NULL;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetStunExpireTime( float flTime )
{
#ifdef GAME_DLL
if ( GetActiveStunInfo() )
{
GetActiveStunInfo()->flExpireTime = flTime;
}
#else
m_flStunEnd = flTime;
#endif
UpdateLegacyStunSystem();
}
//-----------------------------------------------------------------------------
// Purpose: Mirror stun info to the old system for networking
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateLegacyStunSystem( void )
{
// What a mess.
#ifdef GAME_DLL
stun_struct_t *pStun = GetActiveStunInfo();
if ( pStun )
{
m_hStunner = pStun->hPlayer;
m_flStunFade = gpGlobals->curtime + pStun->flDuration;
m_flMovementStunTime = pStun->flDuration;
m_flStunEnd = pStun->flExpireTime;
if ( pStun->iStunFlags & TF_STUN_CONTROLS )
{
m_flStunEnd = pStun->flExpireTime;
}
m_iMovementStunAmount = pStun->flStunAmount;
m_iStunFlags = pStun->iStunFlags;
m_iMovementStunParity = ( m_iMovementStunParity + 1 ) & ( ( 1 << MOVEMENTSTUN_PARITY_BITS ) - 1 );
}
#else
m_ActiveStunInfo.hPlayer = m_hStunner;
m_ActiveStunInfo.flDuration = m_flMovementStunTime;
m_ActiveStunInfo.flExpireTime = m_flStunEnd;
m_ActiveStunInfo.flStartFadeTime = m_flStunEnd;
m_ActiveStunInfo.flStunAmount = m_iMovementStunAmount;
m_ActiveStunInfo.iStunFlags = m_iStunFlags;
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
stun_struct_t *CTFPlayerShared::GetActiveStunInfo( void ) const
{
#ifdef GAME_DLL
return ( m_PlayerStuns.IsValidIndex( m_iStunIndex ) ) ? const_cast<stun_struct_t*>( &m_PlayerStuns[m_iStunIndex] ) : NULL;
#else
return ( m_iStunIndex >= 0 ) ? const_cast<stun_struct_t*>( &m_ActiveStunInfo ) : NULL;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFPlayer *CTFPlayerShared::GetStunner( void )
{
return GetActiveStunInfo() ? GetActiveStunInfo()->hPlayer : NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddCritBoost( void )
{
#ifdef CLIENT_DLL
UpdateCritBoostEffect();
#endif
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateCritBoostEffect( ECritBoostUpdateType eUpdateType )
{
bool bShouldDisplayCritBoostEffect = IsCritBoosted()
|| InCond( TF_COND_ENERGY_BUFF )
//|| IsHypeBuffed()
|| InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF );
if ( m_pOuter->GetActiveTFWeapon() )
{
bShouldDisplayCritBoostEffect &= m_pOuter->GetActiveTFWeapon()->CanBeCritBoosted();
}
// Never show crit boost effects when stealthed
bShouldDisplayCritBoostEffect &= !IsStealthed();
// Never show crit boost effects when disguised unless we're the local player (so crits show on our viewmodel)
if ( !m_pOuter->IsLocalPlayer() )
{
bShouldDisplayCritBoostEffect &= !InCond( TF_COND_DISGUISED );
}
// Remove our current crit-boosted effect if we're forcing a refresh (in which case we'll
// regenerate an effect below) or if we aren't supposed to have an effect active.
if ( eUpdateType == kCritBoost_ForceRefresh || !bShouldDisplayCritBoostEffect )
{
if ( m_pOuter->m_pCritBoostEffect )
{
Assert( m_pOuter->m_pCritBoostEffect->IsValid() );
if ( m_pOuter->m_pCritBoostEffect->GetOwner() )
{
m_pOuter->m_pCritBoostEffect->GetOwner()->ParticleProp()->StopEmissionAndDestroyImmediately( m_pOuter->m_pCritBoostEffect );
}
else
{
m_pOuter->m_pCritBoostEffect->StopEmission();
}
m_pOuter->m_pCritBoostEffect = NULL;
}
#ifdef CLIENT_DLL
if ( m_pCritBoostSoundLoop )
{
CSoundEnvelopeController::GetController().SoundDestroy( m_pCritBoostSoundLoop );
m_pCritBoostSoundLoop = NULL;
}
#endif
}
// Should we have an active crit effect?
if ( bShouldDisplayCritBoostEffect )
{
CBaseEntity *pWeapon = NULL;
// Use GetRenderedWeaponModel() instead?
if ( m_pOuter->IsLocalPlayer() )
{
pWeapon = m_pOuter->GetViewModel(0);
}
else
{
// is this player an enemy?
if ( m_pOuter->GetTeamNumber() != GetLocalPlayerTeam() )
{
// are they a cloaked spy? or disguised as someone who almost assuredly isn't also critboosted?
if ( IsStealthed() || InCond( TF_COND_STEALTHED_BLINK ) || InCond( TF_COND_DISGUISED ) )
return;
}
pWeapon = m_pOuter->GetActiveWeapon();
}
if ( pWeapon )
{
if ( !m_pOuter->m_pCritBoostEffect )
{
if ( InCond( TF_COND_DISGUISED ) && !m_pOuter->IsLocalPlayer() && m_pOuter->GetTeamNumber() != GetLocalPlayerTeam() )
{
const char *pEffectName = ( GetDisguiseTeam() == TF_TEAM_RED ) ? "critgun_weaponmodel_red" : "critgun_weaponmodel_blu";
m_pOuter->m_pCritBoostEffect = pWeapon->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
}
else
{
const char *pEffectName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "critgun_weaponmodel_red" : "critgun_weaponmodel_blu";
m_pOuter->m_pCritBoostEffect = pWeapon->ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
}
if ( m_pOuter->IsLocalPlayer() )
{
if ( m_pOuter->m_pCritBoostEffect )
{
ClientLeafSystem()->SetRenderGroup( m_pOuter->m_pCritBoostEffect->RenderHandle(), RENDER_GROUP_VIEW_MODEL_TRANSLUCENT );
}
}
}
else
{
m_pOuter->m_pCritBoostEffect->StartEmission();
}
Assert( m_pOuter->m_pCritBoostEffect->IsValid() );
}
#ifdef CLIENT_DLL
if ( m_pOuter->GetActiveTFWeapon() && !m_pCritBoostSoundLoop )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CLocalPlayerFilter filter;
m_pCritBoostSoundLoop = controller.SoundCreate( filter, m_pOuter->entindex(), "Weapon_General.CritPower" );
controller.Play( m_pCritBoostSoundLoop, 1.0, 100 );
}
#endif
}
}
#endif
//-----------------------------------------------------------------------------
// Soda Popper Condition
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnAddSodaPopperHype( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
m_pOuter->EmitSound( "DisciplineDevice.PowerUp" );
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnRemoveSodaPopperHype( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->IsLocalPlayer() )
{
m_pOuter->EmitSound( "DisciplineDevice.PowerDown" );
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayerShared::GetStealthNoAttackExpireTime( void )
{
return m_flStealthNoAttackExpire;
}
//-----------------------------------------------------------------------------
// Purpose: Sets whether this player is dominating the specified other player
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetPlayerDominated( CTFPlayer *pPlayer, bool bDominated )
{
int iPlayerIndex = pPlayer->entindex();
m_bPlayerDominated.Set( iPlayerIndex, bDominated );
pPlayer->m_Shared.SetPlayerDominatingMe( m_pOuter, bDominated );
}
//-----------------------------------------------------------------------------
// Purpose: Sets whether this player is being dominated by the other player
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetPlayerDominatingMe( CTFPlayer *pPlayer, bool bDominated )
{
int iPlayerIndex = pPlayer->entindex();
m_bPlayerDominatingMe.Set( iPlayerIndex, bDominated );
#ifdef GAME_DLL
if ( bDominated )
{
CTFPlayer *pDominatingPlayer = ToTFPlayer( pPlayer );
if ( pDominatingPlayer && pDominatingPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
{
CBaseEntity *pHealedEntity = pPlayer->MedicGetHealTarget();
CTFPlayer *pHealedPlayer = ToTFPlayer( pHealedEntity );
if ( pHealedPlayer && pHealedPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
{
pHealedPlayer->AwardAchievement( ACHIEVEMENT_TF_HEAVY_EARN_MEDIC_DOMINATION );
}
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether this player is dominating the specified other player
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsPlayerDominated( int iPlayerIndex )
{
#ifdef CLIENT_DLL
// On the client, we only have data for the local player.
// As a result, it's only valid to ask for dominations related to the local player
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer )
return false;
Assert( m_pOuter->IsLocalPlayer() || pLocalPlayer->entindex() == iPlayerIndex );
if ( m_pOuter->IsLocalPlayer() )
return m_bPlayerDominated.Get( iPlayerIndex );
return pLocalPlayer->m_Shared.IsPlayerDominatingMe( m_pOuter->entindex() );
#else
// Server has all the data.
return m_bPlayerDominated.Get( iPlayerIndex );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsPlayerDominatingMe( int iPlayerIndex )
{
return m_bPlayerDominatingMe.Get( iPlayerIndex );
}
//-----------------------------------------------------------------------------
// Purpose: True if the given player is a spy disguised as our team.
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsSpyDisguisedAsMyTeam( CTFPlayer *pPlayer )
{
if ( !pPlayer )
return false;
if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) &&
pPlayer->GetTeamNumber() != m_pOuter->GetTeamNumber() &&
pPlayer->m_Shared.GetDisguiseTeam() == m_pOuter->GetTeamNumber() )
{
return true;
}
else
{
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::NoteLastDamageTime( int nDamage )
{
// we took damage
if ( ( nDamage > 5 || InCond( TF_COND_BLEEDING ) ) && !InCond( TF_COND_FEIGN_DEATH ) && InCond( TF_COND_STEALTHED ) )
{
m_flLastStealthExposeTime = gpGlobals->curtime;
AddCond( TF_COND_STEALTHED_BLINK );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::OnSpyTouchedByEnemy( void )
{
if ( !InCond( TF_COND_FEIGN_DEATH ) && InCond( TF_COND_STEALTHED ) )
{
m_flLastStealthExposeTime = gpGlobals->curtime;
AddCond( TF_COND_STEALTHED_BLINK );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsEnteringOrExitingFullyInvisible( void )
{
return ( ( GetPercentInvisiblePrevious() != 1.f && GetPercentInvisible() == 1.f ) ||
( GetPercentInvisiblePrevious() == 1.f && GetPercentInvisible() != 1.f ) );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::CanRuneCharge() const
{
return InCond( TF_COND_RUNE_SUPERNOVA );
}
#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::HasPhaseCloakAbility( void )
{
int iPhaseStealth = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iPhaseStealth, ability_cloak_phase );
return iPhaseStealth > 0;
}
#endif // STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::FadeInvis( float fAdditionalRateScale )
{
ETFCond nExpiringCondition = TF_COND_LAST;
if ( InCond( TF_COND_STEALTHED ) )
{
nExpiringCondition = TF_COND_STEALTHED;
RemoveCond( TF_COND_STEALTHED );
}
else if ( InCond( TF_COND_STEALTHED_USER_BUFF ) )
{
nExpiringCondition = TF_COND_STEALTHED_USER_BUFF;
RemoveCond( TF_COND_STEALTHED_USER_BUFF );
}
#ifdef GAME_DLL
// inform the bots
CTFWeaponInvis *pInvis = dynamic_cast< CTFWeaponInvis * >( m_pOuter->Weapon_OwnsThisID( TF_WEAPON_INVIS ) );
if ( pInvis )
{
TheNextBots().OnWeaponFired( m_pOuter, pInvis );
}
#endif
// If present, give our invisibility weapon a chance to override our decloak
// rate scale.
float flDecloakRateScale = 0.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, flDecloakRateScale, mult_decloak_rate );
// This comes from script, so sanity check the result.
if ( flDecloakRateScale <= 0.0f )
{
flDecloakRateScale = 1.0f;
}
float flInvisFadeTime = fAdditionalRateScale
* (tf_spy_invis_unstealth_time.GetFloat() * flDecloakRateScale);
if ( flInvisFadeTime < 0.15 )
{
// this was a force respawn, they can attack whenever
}
else if ( ( nExpiringCondition != TF_COND_STEALTHED_USER_BUFF ) && !InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
{
// next attack in some time
m_flStealthNoAttackExpire = gpGlobals->curtime + (tf_spy_cloak_no_attack_time.GetFloat() * flDecloakRateScale * fAdditionalRateScale);
}
m_flInvisChangeCompleteTime = gpGlobals->curtime + flInvisFadeTime;
}
//-----------------------------------------------------------------------------
// Purpose: Approach our desired level of invisibility
//-----------------------------------------------------------------------------
void CTFPlayerShared::InvisibilityThink( void )
{
if ( m_pOuter->GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY && InCond( TF_COND_STEALTHED ) )
{
// Shouldn't happen, but it's a safety net
m_flInvisibility = 0.0f;
if ( InCond( TF_COND_STEALTHED ) )
{
RemoveCond( TF_COND_STEALTHED );
}
return;
}
float flTargetInvis = 0.0f;
float flTargetInvisScale = 1.0f;
if ( InCond( TF_COND_STEALTHED_BLINK ) || InCond( TF_COND_URINE ) )
{
// We were bumped into or hit for some damage.
flTargetInvisScale = TF_SPY_STEALTH_BLINKSCALE;
}
// Go invisible or appear.
if ( m_flInvisChangeCompleteTime > gpGlobals->curtime )
{
if ( IsStealthed() )
{
flTargetInvis = 1.0f - ( ( m_flInvisChangeCompleteTime - gpGlobals->curtime ) );
}
else
{
flTargetInvis = ( ( m_flInvisChangeCompleteTime - gpGlobals->curtime ) * 0.5f );
}
}
else
{
if ( IsStealthed() )
{
flTargetInvis = 1.0f;
m_flLastNoMovementTime = -1.f;
if ( m_bMotionCloak )
{
if ( m_flCloakMeter == 0.f )
{
Vector vVel = m_pOuter->GetAbsVelocity();
float fSpdSqr = vVel.LengthSqr();
flTargetInvis = RemapVal( fSpdSqr, 0, m_pOuter->MaxSpeed()*m_pOuter->MaxSpeed(), 1.0f, 0.5f );
}
else
{
flTargetInvis = 1.f;
}
}
}
else
{
flTargetInvis = 0.0f;
}
}
flTargetInvis *= flTargetInvisScale;
m_flPrevInvisibility = m_flInvisibility;
m_flInvisibility = clamp( flTargetInvis, 0.0f, 1.0f );
#ifdef STAGING_ONLY
// Sniper cloak
int iSniperCloak = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iSniperCloak, ability_cloak_sniper );
if ( iSniperCloak )
{
static const float flSniperCloakDelay = 2.f;
bool bMoving = !m_pOuter->GetAbsVelocity().IsZero();
bool bAiming = IsAiming();
if ( !bMoving && !bAiming && !IsCarryingObject() )
{
if ( m_flLastNoMovementTime == -1.f )
{
m_flLastNoMovementTime = gpGlobals->curtime;
}
if ( !InCond( TF_COND_STEALTHED_USER_BUFF ) && !m_flInvisibility && m_flLastNoMovementTime > 0.f && gpGlobals->curtime - m_flLastNoMovementTime > flSniperCloakDelay )
{
AddCond( TF_COND_STEALTHED_USER_BUFF, -1.f, m_pOuter );
}
}
else
{
if ( IsStealthed() )
{
FadeInvis( 0.1f );
}
m_flLastNoMovementTime = -1.f;
}
}
#endif // STAGING_ONLY
}
//-----------------------------------------------------------------------------
// Purpose: How invisible is the player [0..1]
//-----------------------------------------------------------------------------
float CTFPlayerShared::GetPercentInvisible( void ) const
{
#ifdef STAGING_ONLY
#ifdef CLIENT_DLL
// This is a client hack to make everyone else invisible to the local player
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalTFPlayer != m_pOuter )
return 1.f;
#endif // CLIENT_DLL
#endif // STAGING_ONLY
return m_flInvisibility;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsCritBoosted( void ) const
{
bool bAllWeaponCritActive = ( InCond( TF_COND_CRITBOOSTED ) ||
InCond( TF_COND_CRITBOOSTED_PUMPKIN ) ||
InCond( TF_COND_CRITBOOSTED_USER_BUFF ) ||
#ifdef CLIENT_DLL
InCond( TF_COND_CRITBOOSTED_DEMO_CHARGE ) ||
#endif
InCond( TF_COND_CRITBOOSTED_FIRST_BLOOD ) ||
InCond( TF_COND_CRITBOOSTED_BONUS_TIME ) ||
InCond( TF_COND_CRITBOOSTED_CTF_CAPTURE ) ||
InCond( TF_COND_CRITBOOSTED_ON_KILL ) ||
InCond( TF_COND_CRITBOOSTED_CARD_EFFECT ) ||
InCond( TF_COND_CRITBOOSTED_RUNE_TEMP ) );
if ( bAllWeaponCritActive )
return true;
CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( m_pOuter->GetActiveWeapon() );
if ( pWeapon )
{
if ( InCond( TF_COND_CRITBOOSTED_RAGE_BUFF ) && pWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_PRIMARY )
{
// Only primary weapon can be crit boosted by pyro rage
return true;
}
float flCritHealthPercent = 1.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flCritHealthPercent, mult_crit_when_health_is_below_percent );
if ( flCritHealthPercent < 1.0f && m_pOuter->HealthFraction() < flCritHealthPercent )
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsInvulnerable( void ) const
{
bool bInvuln = InCond( TF_COND_INVULNERABLE ) ||
InCond( TF_COND_INVULNERABLE_USER_BUFF ) ||
InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) ||
InCond( TF_COND_INVULNERABLE_CARD_EFFECT );
return bInvuln;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsStealthed( void ) const
{
#ifdef STAGING_ONLY
#ifdef CLIENT_DLL
// This is a client hack to make everyone else invisible to the local player
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalTFPlayer != m_pOuter )
return true;
#endif // CLIENT_DLL
#endif // STAGING_ONLY
return ( InCond( TF_COND_STEALTHED ) || InCond( TF_COND_STEALTHED_USER_BUFF ) || InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) );
}
bool CTFPlayerShared::CanBeDebuffed( void ) const
{
if ( IsInvulnerable() )
return false;
if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_PASSTIME_INTERCEPTION ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Start the process of disguising
//-----------------------------------------------------------------------------
void CTFPlayerShared::Disguise( int nTeam, int nClass, CTFPlayer* pDesiredTarget, bool bOnKill )
{
int nRealTeam = m_pOuter->GetTeamNumber();
int nRealClass = m_pOuter->GetPlayerClass()->GetClassIndex();
Assert ( ( nClass >= TF_CLASS_SCOUT ) && ( nClass <= TF_CLASS_ENGINEER ) );
// we're not a spy
if ( nRealClass != TF_CLASS_SPY )
{
return;
}
if ( InCond( TF_COND_TAUNTING ) )
{
// not allowed to disguise while taunting
return;
}
// we're not disguising as anything but ourselves (so reset everything)
if ( nRealTeam == nTeam && nRealClass == nClass )
{
RemoveDisguise();
return;
}
// Ignore disguise of the same type, unless we're using 'Your Eternal Reward'
if ( nTeam == m_nDisguiseTeam && nClass == m_nDisguiseClass && !bOnKill )
{
#ifdef GAME_DLL
DetermineDisguiseWeapon( false );
#endif
return;
}
// invalid team
if ( nTeam <= TEAM_SPECTATOR || nTeam >= TF_TEAM_COUNT )
{
return;
}
// invalid class
if ( nClass <= TF_CLASS_UNDEFINED || nClass >= TF_CLASS_COUNT )
{
return;
}
// are we already in the middle of disguising as this class?
// (the lastdisguise key might get pushed multiple times before the disguise is complete)
if ( InCond( TF_COND_DISGUISING ) )
{
if ( nTeam == m_nDesiredDisguiseTeam && nClass == m_nDesiredDisguiseClass )
{
return;
}
}
m_hDesiredDisguiseTarget.Set( pDesiredTarget );
m_nDesiredDisguiseClass = nClass;
m_nDesiredDisguiseTeam = nTeam;
m_bLastDisguisedAsOwnTeam = ( m_nDesiredDisguiseTeam == m_pOuter->GetTeamNumber() );
AddCond( TF_COND_DISGUISING );
// Start the think to complete our disguise
float flTimeToDisguise = TF_TIME_TO_DISGUISE;
//CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pOuter, iTimeToDisguise, disguise_speed_penalty ); // Unused Attr
// STAGING_SPY
// Quick disguise if you already disguised
if ( InCond( TF_COND_DISGUISED ) )
{
flTimeToDisguise = TF_TIME_TO_QUICK_DISGUISE;
}
if ( pDesiredTarget )
{
flTimeToDisguise = 0;
}
m_flDisguiseCompleteTime = gpGlobals->curtime + flTimeToDisguise;
}
//-----------------------------------------------------------------------------
// Purpose: Set our target with a player we've found to emulate
//-----------------------------------------------------------------------------
#ifndef CLIENT_DLL
void CTFPlayerShared::FindDisguiseTarget( void )
{
if ( m_hDesiredDisguiseTarget )
{
m_hDisguiseTarget.Set( m_hDesiredDisguiseTarget.Get() );
m_hDesiredDisguiseTarget.Set( NULL );
}
else
{
m_hDisguiseTarget = m_pOuter->TeamFortress_GetDisguiseTarget( m_nDisguiseTeam, m_nDisguiseClass );
}
if ( m_hDisguiseTarget )
{
m_iDisguiseTargetIndex.Set( m_hDisguiseTarget.Get()->entindex() );
Assert( m_iDisguiseTargetIndex >= 1 && m_iDisguiseTargetIndex <= MAX_PLAYERS );
}
else
{
m_iDisguiseTargetIndex.Set( TF_DISGUISE_TARGET_INDEX_NONE );
}
m_pOuter->CreateDisguiseWeaponList( ToTFPlayer( m_hDisguiseTarget.Get() ) );
}
#endif
int CTFPlayerShared::GetDisguiseTeam( void ) const
{
return InCond( TF_COND_DISGUISED_AS_DISPENSER ) ? (int)( ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ) : m_nDisguiseTeam;
}
//-----------------------------------------------------------------------------
// Purpose: Complete our disguise
//-----------------------------------------------------------------------------
void CTFPlayerShared::CompleteDisguise( void )
{
AddCond( TF_COND_DISGUISED );
m_nDisguiseClass = m_nDesiredDisguiseClass;
m_nDisguiseTeam = m_nDesiredDisguiseTeam;
if ( m_nDisguiseClass == TF_CLASS_SPY )
{
m_nMaskClass = rand()%9+1;
}
RemoveCond( TF_COND_DISGUISING );
#ifdef GAME_DLL
// Update the player model and skin.
m_pOuter->UpdateModel();
m_pOuter->ClearExpression();
FindDisguiseTarget();
if ( GetDisguiseTarget() )
{
m_iDisguiseHealth = GetDisguiseTarget()->GetHealth();
if ( m_iDisguiseHealth <= 0 || !GetDisguiseTarget()->IsAlive() )
{
// If we disguised as an enemy who is currently dead, just set us to full health.
m_iDisguiseHealth = GetDisguiseMaxHealth();
}
}
else
{
int iMaxHealth = m_pOuter->GetMaxHealth();
m_iDisguiseHealth = (int)random->RandomInt( iMaxHealth / 2, iMaxHealth );
}
// In Medieval mode, don't force primary weapon because most classes just have melee weapons
DetermineDisguiseWeapon( !TFGameRules()->IsInMedievalMode() );
DetermineDisguiseWearables();
#endif
m_pOuter->TeamFortress_SetSpeed();
m_flDisguiseCompleteTime = 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetDisguiseHealth( int iDisguiseHealth )
{
m_iDisguiseHealth = iDisguiseHealth;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetDisguiseMaxHealth( void )
{
TFPlayerClassData_t *pClass = g_pTFPlayerClassDataMgr->Get( GetDisguiseClass() );
if ( pClass )
{
return pClass->m_nMaxHealth;
}
else
{
return m_pOuter->GetMaxHealth();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::RemoveDisguise( void )
{
if ( GetDisguiseTeam() != m_pOuter->GetTeamNumber() )
{
if ( InCond( TF_COND_TELEPORTED ) )
{
RemoveCond( TF_COND_TELEPORTED );
}
}
RemoveCond( TF_COND_DISGUISED );
RemoveCond( TF_COND_DISGUISING );
AddCond( TF_COND_DISGUISE_WEARINGOFF, 0.5f );
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::DetermineDisguiseWeapon( bool bForcePrimary )
{
Assert( m_pOuter->GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY );
const char* strDisguiseWeapon = NULL;
CTFPlayer *pDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() );
TFPlayerClassData_t *pData = GetPlayerClassData( m_nDisguiseClass );
if ( pDisguiseTarget && (pDisguiseTarget->GetPlayerClass()->GetClassIndex() != m_nDisguiseClass) )
{
pDisguiseTarget = NULL;
}
// Determine which slot we have active.
int iCurrentSlot = 0;
if ( m_pOuter->GetActiveTFWeapon() && !bForcePrimary )
{
iCurrentSlot = m_pOuter->GetActiveTFWeapon()->GetSlot();
if ( (iCurrentSlot == 3) && // Cig Case, so they are using the menu not a key bind to disguise.
m_pOuter->GetLastWeapon() )
{
iCurrentSlot = m_pOuter->GetLastWeapon()->GetSlot();
}
}
CTFWeaponBase *pItemWeapon = NULL;
if ( pDisguiseTarget )
{
CTFWeaponBase *pLastDisguiseWeapon = m_hDisguiseWeapon;
CTFWeaponBase *pFirstValidWeapon = NULL;
// Cycle through the target's weapons and see if we have a match.
// Note that it's possible the disguise target doesn't have a weapon in the slot we want,
// for example if they have replaced it with an unlockable that isn't a weapon (wearable).
for ( int i=0; i<m_pOuter->m_hDisguiseWeaponList.Count(); ++i )
{
CTFWeaponBase *pWeapon = m_pOuter->m_hDisguiseWeaponList[i];
if ( !pWeapon )
continue;
if ( !pFirstValidWeapon )
{
pFirstValidWeapon = pWeapon;
}
// skip passtime gun
if ( pWeapon->GetWeaponID() == TF_WEAPON_PASSTIME_GUN )
{
continue;
}
if ( pWeapon->GetSlot() == iCurrentSlot )
{
pItemWeapon = pWeapon;
break;
}
}
if ( !pItemWeapon )
{
if ( pLastDisguiseWeapon )
{
pItemWeapon = pLastDisguiseWeapon;
}
else if ( pFirstValidWeapon )
{
pItemWeapon = pFirstValidWeapon;
}
}
if ( pItemWeapon )
{
strDisguiseWeapon = pItemWeapon->GetClassname();
}
}
if ( !pItemWeapon && pData )
{
// We have not found our item yet, so cycle through the class's default weapons
// to find a match.
for ( int i=0; i<TF_PLAYER_WEAPON_COUNT; ++i )
{
if ( pData->m_aWeapons[i] == TF_WEAPON_NONE )
continue;
const char *pWpnName = WeaponIdToAlias( pData->m_aWeapons[i] );
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pWpnName );
Assert( hWpnInfo != GetInvalidWeaponInfoHandle() );
CTFWeaponInfo *pWeaponInfo = dynamic_cast<CTFWeaponInfo*>( GetFileWeaponInfoFromHandle( hWpnInfo ) );
if ( pWeaponInfo->iSlot == iCurrentSlot )
{
strDisguiseWeapon = pWeaponInfo->szClassName;
}
}
}
if ( strDisguiseWeapon )
{
// Remove the old disguise weapon, if any.
RemoveDisguiseWeapon();
CEconItemView *pItem = NULL;
if ( pItemWeapon )
{
// We are copying a generated, non-base item.
CAttributeContainer *pContainer = pItemWeapon->GetAttributeContainer();
if ( pContainer )
{
pItem = pContainer->GetItem();
}
}
// We may need a sub-type if we're a builder. Otherwise we'll always appear as a engineer's workbox.
int iSubType = 0;
if ( Q_strcmp( strDisguiseWeapon, "tf_weapon_builder" ) == 0 )
{
return; // Temporary.
}
m_hDisguiseWeapon.Set( dynamic_cast<CTFWeaponBase*>(m_pOuter->GiveNamedItem( strDisguiseWeapon, iSubType, pItem, true )) );
if ( m_hDisguiseWeapon )
{
m_hDisguiseWeapon->SetTouch( NULL );// no touch
m_hDisguiseWeapon->SetOwner( dynamic_cast<CBaseCombatCharacter*>(m_pOuter) );
m_hDisguiseWeapon->SetOwnerEntity( m_pOuter );
m_hDisguiseWeapon->SetParent( m_pOuter );
m_hDisguiseWeapon->FollowEntity( m_pOuter, true );
m_hDisguiseWeapon->m_iState = WEAPON_IS_ACTIVE;
m_hDisguiseWeapon->m_bDisguiseWeapon = true;
m_hDisguiseWeapon->SetContextThink( &CTFWeaponBase::DisguiseWeaponThink, gpGlobals->curtime + 0.5, "DisguiseWeaponThink" );
// Ammo/clip state is displayed to attached medics
m_iDisguiseAmmo = 0;
if ( !m_hDisguiseWeapon->IsMeleeWeapon() )
{
// Use the player we're disguised as if possible
if ( pDisguiseTarget )
{
CTFWeaponBase *pWeapon = pDisguiseTarget->GetActiveTFWeapon();
if ( pWeapon && pWeapon->GetWeaponID() == m_hDisguiseWeapon->GetWeaponID() )
{
m_iDisguiseAmmo = pWeapon->UsesClipsForAmmo1() ?
pWeapon->Clip1() :
pDisguiseTarget->GetAmmoCount( pWeapon->GetPrimaryAmmoType() );
}
}
// Otherwise display a faked ammo count
if ( !m_iDisguiseAmmo )
{
int nMaxCount = m_hDisguiseWeapon->UsesClipsForAmmo1() ?
m_hDisguiseWeapon->GetMaxClip1() :
m_pOuter->GetMaxAmmo( m_hDisguiseWeapon->GetPrimaryAmmoType(), m_nDisguiseClass );
m_iDisguiseAmmo = (int)random->RandomInt( 1, nMaxCount );
}
}
}
}
}
void CTFPlayerShared::DetermineDisguiseWearables()
{
CTFPlayer *pDisguiseTarget = ToTFPlayer( m_hDisguiseTarget.Get() );
if ( !pDisguiseTarget )
return;
// Remove any existing disguise wearables.
RemoveDisguiseWearables();
if ( GetDisguiseClass() != pDisguiseTarget->GetPlayerClass()->GetClassIndex() )
return;
// Equip us with copies of our disguise target's wearables.
int iPlayerSkinOverride = 0;
for ( int i=0; i<pDisguiseTarget->GetNumWearables(); ++i )
{
CTFWearable *pWearable = dynamic_cast<CTFWearable*>( pDisguiseTarget->GetWearable( i ) );
if ( pWearable )
{
if ( pWearable->IsDisguiseWearable() )
continue; // Never copy a target's disguise wearables.
CEconItemView *pScriptItem = pWearable->GetAttributeContainer()->GetItem();
if ( pScriptItem && pScriptItem->IsValid() && pScriptItem->GetStaticData()->GetItemClass() )
{
CEconEntity *pNewItem = dynamic_cast<CEconEntity*>( m_pOuter->GiveNamedItem( pScriptItem->GetStaticData()->GetItemClass(), 0, pScriptItem ) );
CTFWearable *pNewWearable = dynamic_cast<CTFWearable*>( pNewItem );
Assert( pNewWearable );
if ( pNewWearable )
{
pNewWearable->SetDisguiseWearable( true );
pNewWearable->GiveTo( m_pOuter );
// copy over the level for levelable items
CTFWearableLevelableItem *pLevelableItem = dynamic_cast<CTFWearableLevelableItem*>( pWearable );
CTFWearableLevelableItem *pNewLevelableItem = dynamic_cast<CTFWearableLevelableItem*>( pNewWearable );
if ( pLevelableItem && pNewLevelableItem )
{
int nBodyGroup = pNewLevelableItem->FindBodygroupByName( LEVELABLE_ITEM_BODYGROUP_NAME );
if ( nBodyGroup != -1 )
{
pNewLevelableItem->SetBodygroup( nBodyGroup, pLevelableItem->GetLevel() );
}
}
// find the first skin override item
if ( iPlayerSkinOverride == 0 )
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pNewWearable, iPlayerSkinOverride, player_skin_override );
}
}
}
}
}
m_nDisguiseSkinOverride = iPlayerSkinOverride;
}
void CTFPlayerShared::RemoveDisguiseWearables()
{
bool bFoundDisguiseWearable = true;
while ( bFoundDisguiseWearable )
{
int i = 0;
for ( ; i<m_pOuter->GetNumWearables(); ++i )
{
CTFWearable *pWearable = dynamic_cast<CTFWearable*>( m_pOuter->GetWearable( i ) );
if ( pWearable && pWearable->IsDisguiseWearable() )
{
// Every time we do this the list changes, so we have to loop through again.
pWearable->RemoveFrom( m_pOuter );
break;
}
}
if ( i == m_pOuter->GetNumWearables() )
{
bFoundDisguiseWearable = false;
}
}
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ProcessDisguiseImpulse( CTFPlayer *pPlayer )
{
// Get the player owning the weapon.
if ( !pPlayer )
return;
if ( pPlayer->GetImpulse() > 200 )
{
char szImpulse[6];
Q_snprintf( szImpulse, sizeof( szImpulse ), "%d", pPlayer->GetImpulse() );
char szTeam[3];
Q_snprintf( szTeam, sizeof( szTeam ), "%c", szImpulse[1] );
char szClass[3];
Q_snprintf( szClass, sizeof( szClass ), "%c", szImpulse[2] );
// 'Your Eternal Reward' handling
bool bSwitchWeaponOnly = false;
if ( pPlayer->CanDisguise_OnKill() && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
{
// Only trying to change the disguise weapon via 'lastdisguise'
if ( Q_atoi( szClass ) == pPlayer->m_Shared.GetDisguiseClass() && Q_atoi( szTeam ) == pPlayer->m_Shared.GetDisguiseTeam() )
{
bSwitchWeaponOnly = true;
}
}
if ( pPlayer->CanDisguise() || bSwitchWeaponOnly )
{
// intercepting the team value and reassigning what gets passed into Disguise()
// because the team numbers in the client menu don't match the #define values for the teams
pPlayer->m_Shared.Disguise( Q_atoi( szTeam ), Q_atoi( szClass ) );
// Switch from the PDA to our previous weapon
if ( GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_PDA_SPY )
{
pPlayer->SelectLastItem();
}
}
}
}
bool CTFPlayerShared::CanRecieveMedigunChargeEffect( medigun_charge_types eType ) const
{
bool bCanRecieve = true;
const CTFItem *pItem = m_pOuter->GetItem();
if ( pItem && pItem->GetItemID() == TF_ITEM_CAPTURE_FLAG )
{
bCanRecieve = false;
// The "flag" in Player Destruction doesn't block uber
const CCaptureFlag* pFlag = static_cast< const CCaptureFlag* >( pItem );
if ( pFlag->GetType() == TF_FLAGTYPE_PLAYER_DESTRUCTION )
{
bCanRecieve = true;
}
if ( TFGameRules()->IsMannVsMachineMode() )
{
// allow bot flag carriers to be ubered
bCanRecieve = true;
}
if ( ( eType == MEDIGUN_CHARGE_MEGAHEAL )
|| ( eType == MEDIGUN_CHARGE_BULLET_RESIST )
|| ( eType == MEDIGUN_CHARGE_BLAST_RESIST )
|| ( eType == MEDIGUN_CHARGE_FIRE_RESIST ) )
{
bCanRecieve = true;
}
}
if( TFGameRules() && TFGameRules()->IsPasstimeMode() )
{
bCanRecieve &= ! HasPasstimeBall();
}
return bCanRecieve;
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: Heal players.
// pPlayer is person who healed us
//-----------------------------------------------------------------------------
void CTFPlayerShared::Heal( CBaseEntity *pHealer, float flAmount, float flOverhealBonus, float flOverhealDecayMult, bool bDispenserHeal /* = false */, CTFPlayer *pHealScorer /* = NULL */ )
{
// If already healing, stop healing
float flHealAccum = 0;
if ( FindHealerIndex(pHealer) != m_aHealers.InvalidIndex() )
{
flHealAccum = StopHealing( pHealer );
}
healers_t newHealer;
newHealer.pHealer = pHealer;
newHealer.flAmount = flAmount;
newHealer.flHealAccum = flHealAccum;
newHealer.iKillsWhileBeingHealed = 0;
newHealer.flOverhealBonus = flOverhealBonus;
newHealer.flOverhealDecayMult = flOverhealDecayMult;
newHealer.bDispenserHeal = bDispenserHeal;
newHealer.flHealedLastSecond = 0;
if ( pHealScorer )
{
newHealer.pHealScorer = pHealScorer;
}
else
{
//Assert( pHealer->IsPlayer() );
newHealer.pHealScorer = pHealer;
}
m_aHealers.AddToTail( newHealer );
AddCond( TF_COND_HEALTH_BUFF, PERMANENT_CONDITION, pHealer );
RecalculateChargeEffects();
m_nNumHealers = m_aHealers.Count();
if ( pHealer && pHealer->IsPlayer() )
{
CTFPlayer *pPlayer = ToTFPlayer( pHealer );
Assert(pPlayer);
pPlayer->m_AchievementData.AddTargetToHistory( m_pOuter );
pPlayer->TeamFortress_SetSpeed();
}
}
//-----------------------------------------------------------------------------
// Purpose: Heal players.
// pPlayer is person who healed us
//-----------------------------------------------------------------------------
float CTFPlayerShared::StopHealing( CBaseEntity *pHealer )
{
int iIndex = FindHealerIndex(pHealer);
if ( iIndex == m_aHealers.InvalidIndex() )
return 0;
float flHealingDone = 0.f;
if ( iIndex != m_aHealers.InvalidIndex() )
{
flHealingDone = m_aHealers[iIndex].flHealAccum;
m_aHealers.Remove( iIndex );
}
if ( !m_aHealers.Count() )
{
RemoveCond( TF_COND_HEALTH_BUFF );
}
RecalculateChargeEffects();
m_nNumHealers = m_aHealers.Count();
return flHealingDone;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::RecalculateChargeEffects( bool bInstantRemove )
{
struct medic_charges_t
{
bool bActive;
CTFPlayer *pProvider;
};
medic_charges_t aCharges[MEDIGUN_NUM_CHARGE_TYPES];
for ( int i = 0; i < ARRAYSIZE( aCharges ); i++ )
{
aCharges[i].bActive = m_pOuter->m_bInPowerPlay;
aCharges[i].pProvider = NULL;
}
medigun_charge_types iMyCharge = m_pOuter->GetChargeEffectBeingProvided();
if ( iMyCharge != MEDIGUN_CHARGE_INVALID )
{
Assert( iMyCharge >= 0 && iMyCharge < MEDIGUN_NUM_CHARGE_TYPES );
aCharges[iMyCharge].bActive = true;
aCharges[iMyCharge].pProvider = m_pOuter;
}
// Loop through our medics and get all their charges
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
if ( !m_aHealers[i].pHealer )
continue;
CTFPlayer *pPlayer = ToTFPlayer( m_aHealers[i].pHealer );
if ( !pPlayer )
continue;
medigun_charge_types iCharge = pPlayer->GetChargeEffectBeingProvided();
if ( iCharge != MEDIGUN_CHARGE_INVALID )
{
Assert( iCharge >= 0 && iCharge < MEDIGUN_NUM_CHARGE_TYPES );
aCharges[iCharge].bActive = true;
aCharges[iCharge].pProvider = pPlayer;
}
}
if ( !CanRecieveMedigunChargeEffect( iMyCharge ) )
{
aCharges[MEDIGUN_CHARGE_INVULN].bActive = false;
}
SetChargeEffect( MEDIGUN_CHARGE_INVULN, aCharges[MEDIGUN_CHARGE_INVULN].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_INVULN ], tf_invuln_time.GetFloat(), aCharges[MEDIGUN_CHARGE_INVULN].pProvider );
SetChargeEffect( MEDIGUN_CHARGE_CRITICALBOOST, aCharges[MEDIGUN_CHARGE_CRITICALBOOST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_CRITICALBOOST ], 0.0f, aCharges[MEDIGUN_CHARGE_CRITICALBOOST].pProvider );
SetChargeEffect( MEDIGUN_CHARGE_MEGAHEAL, aCharges[MEDIGUN_CHARGE_MEGAHEAL].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_MEGAHEAL ], 0.0f, aCharges[MEDIGUN_CHARGE_MEGAHEAL].pProvider );
SetChargeEffect( MEDIGUN_CHARGE_BULLET_RESIST, aCharges[MEDIGUN_CHARGE_BULLET_RESIST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_BULLET_RESIST ], 0.0f, aCharges[MEDIGUN_CHARGE_BULLET_RESIST].pProvider );
SetChargeEffect( MEDIGUN_CHARGE_BLAST_RESIST, aCharges[MEDIGUN_CHARGE_BLAST_RESIST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_BLAST_RESIST ], 0.0f, aCharges[MEDIGUN_CHARGE_BLAST_RESIST].pProvider );
SetChargeEffect( MEDIGUN_CHARGE_FIRE_RESIST, aCharges[MEDIGUN_CHARGE_FIRE_RESIST].bActive, bInstantRemove, g_MedigunEffects[ MEDIGUN_CHARGE_FIRE_RESIST ], 0.0f, aCharges[MEDIGUN_CHARGE_FIRE_RESIST].pProvider );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::TestAndExpireChargeEffect( medigun_charge_types iCharge )
{
const MedigunEffects_t& effects = g_MedigunEffects[iCharge];
if ( InCond( effects.eCondition ) )
{
bool bRemoveEffect = false;
bool bGameInWinState = TFGameRules()->State_Get() == GR_STATE_TEAM_WIN;
bool bPlayerOnWinningTeam = TFGameRules()->GetWinningTeam() == m_pOuter->GetTeamNumber();
// Lose all charge effects in post-win state if we're the losing team
if ( bGameInWinState && !bPlayerOnWinningTeam )
{
bRemoveEffect = true;
}
if ( m_flChargeEffectOffTime[iCharge] )
{
if ( gpGlobals->curtime > m_flChargeEffectOffTime[iCharge] )
{
bRemoveEffect = true;
}
if (iCharge == MEDIGUN_CHARGE_CRITICALBOOST && ( bGameInWinState && bPlayerOnWinningTeam ) )
{
bRemoveEffect = false;
m_flChargeEffectOffTime[iCharge] = 0;
}
if ( GetRevengeCrits() > 0 && effects.eCondition == TF_COND_CRITBOOSTED )
{
// Don't remove while we have a weapon deployed that can consume revenge crits
CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon();
if ( pWeapon && pWeapon->CanHaveRevengeCrits() )
bRemoveEffect = false;
}
}
// Check healers for possible usercommand invuln exploit
FOR_EACH_VEC( m_aHealers, i )
{
CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer );
if ( !pTFHealer )
continue;
CTFPlayer *pTFProvider = ToTFPlayer( GetConditionProvider( effects.eCondition ) );
if ( !pTFProvider )
continue;
if ( pTFProvider == pTFHealer && pTFHealer->GetTimeSinceLastUserCommand() > weapon_medigun_chargerelease_rate.GetFloat() + 1.f )
{
// Force remove uber and detach the medigun
bRemoveEffect = true;
pTFHealer->Weapon_Switch( pTFHealer->Weapon_GetSlot( TF_WPN_TYPE_MELEE ) );
}
}
if ( bRemoveEffect )
{
m_flChargeEffectOffTime[iCharge] = 0;
RemoveCond( effects.eCondition );
if ( effects.eWearingOffCondition != TF_COND_LAST )
{
RemoveCond( effects.eWearingOffCondition );
}
}
}
else if ( m_bChargeSoundEffectsOn[iCharge] )
{
if ( effects.pszChargeOnSound[0] )
{
m_pOuter->StopSound( effects.pszChargeOnSound );
}
m_bChargeSoundEffectsOn[iCharge] = false;
}
}
//-----------------------------------------------------------------------------
// Purpose: We've started a new charge effect
//-----------------------------------------------------------------------------
void CTFPlayerShared::SendNewInvulnGameEvent( void )
{
// for each medic healing me
for ( int i=0;i<m_aHealers.Count();i++ )
{
CTFPlayer *pMedic = ToTFPlayer( GetHealerByIndex(i) );
if ( !pMedic )
continue;
// ACHIEVEMENT_TF_MEDIC_CHARGE_FRIENDS
IGameEvent *event = gameeventmanager->CreateEvent( "player_invulned" );
if ( event )
{
event->SetInt( "userid", m_pOuter->GetUserID() );
event->SetInt( "medic_userid", pMedic->GetUserID() );
gameeventmanager->FireEvent( event );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetChargeEffect( medigun_charge_types iCharge, bool bState, bool bInstant, const MedigunEffects_t& effects, float flWearOffTime, CTFPlayer *pProvider /*= NULL*/ )
{
if ( effects.eCondition == TF_COND_CRITBOOSTED )
{
// Don't remove while we have a weapon deployed that can consume revenge crits
CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon();
if ( pWeapon )
{
if ( pWeapon->CanHaveRevengeCrits() && GetRevengeCrits() > 0 )
{
return;
}
if ( pWeapon->HasLastShotCritical() )
{
return;
}
}
}
bool bCurrentState = InCond( effects.eCondition );
if ( bCurrentState == bState )
{
if ( bState && m_flChargeEffectOffTime[iCharge] )
{
m_flChargeEffectOffTime[iCharge] = 0;
if ( effects.eWearingOffCondition != TF_COND_LAST )
{
RemoveCond( effects.eWearingOffCondition );
}
SendNewInvulnGameEvent();
}
return;
}
// Avoid infinite duration, because... the internet.
float flMaxDuration = ( pProvider && pProvider->IsBot() ) ? PERMANENT_CONDITION : weapon_medigun_chargerelease_rate.GetFloat() + ( ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) ? 8.f : 1.f );
if ( bState )
{
if ( m_flChargeEffectOffTime[iCharge] )
{
m_pOuter->StopSound( effects.pszChargeOffSound );
m_flChargeEffectOffTime[iCharge] = 0;
if ( effects.eWearingOffCondition != TF_COND_LAST )
{
RemoveCond( effects.eWearingOffCondition );
}
}
// Invulnerable turning on
AddCond( effects.eCondition, flMaxDuration, pProvider );
SendNewInvulnGameEvent();
CSingleUserRecipientFilter filter( m_pOuter );
m_pOuter->EmitSound( filter, m_pOuter->entindex(), effects.pszChargeOnSound );
m_bChargeOffSounded = false;
m_bChargeSoundEffectsOn[iCharge] = true;
}
else
{
if ( m_bChargeSoundEffectsOn[iCharge] )
{
m_pOuter->StopSound( effects.pszChargeOnSound );
m_bChargeSoundEffectsOn[iCharge] = false;
}
if ( !m_flChargeEffectOffTime[iCharge] && !m_bChargeOffSounded )
{
// Make sure we don't have duplicate Off sounds playing
m_pOuter->StopSound( effects.pszChargeOffSound );
CSingleUserRecipientFilter filter( m_pOuter );
m_pOuter->EmitSound( filter, m_pOuter->entindex(), effects.pszChargeOffSound );
m_bChargeOffSounded = true;
}
if ( bInstant )
{
m_flChargeEffectOffTime[iCharge] = 0;
RemoveCond( effects.eCondition );
if ( effects.eWearingOffCondition != TF_COND_LAST )
{
RemoveCond( effects.eWearingOffCondition );
}
}
else
{
// We're already in the process of turning it off
if ( m_flChargeEffectOffTime[iCharge] )
return;
if ( effects.eWearingOffCondition != TF_COND_LAST )
{
AddCond( effects.eWearingOffCondition, PERMANENT_CONDITION, pProvider );
}
m_flChargeEffectOffTime[iCharge] = gpGlobals->curtime + flWearOffTime;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Collect currency packs in a radius around the scout
//-----------------------------------------------------------------------------
void CTFPlayerShared::RadiusCurrencyCollectionCheck( void )
{
if ( m_pOuter->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS && TFGameRules()->IsMannVsMachineMode() )
return;
if ( !m_pOuter->IsAlive() )
return;
if ( m_flRadiusCurrencyCollectionTime > gpGlobals->curtime )
return;
bool bScout = m_pOuter->GetPlayerClass()->GetClassIndex() == TF_CLASS_SCOUT;
const int nRadiusSqr = bScout ? 288 * 288 : 72 * 72;
Vector vecPos = m_pOuter->GetAbsOrigin();
// NDebugOverlay::Sphere( vecPos, nRadius, 0, 255, 0, 40, 5 );
for ( int i = 0; i < ICurrencyPackAutoList::AutoList().Count(); ++i )
{
CCurrencyPack *pCurrencyPack = static_cast< CCurrencyPack* >( ICurrencyPackAutoList::AutoList()[i] );
if ( !pCurrencyPack )
continue;
if ( !pCurrencyPack->AffectedByRadiusCollection() )
continue;
if ( ( vecPos - pCurrencyPack->GetAbsOrigin() ).LengthSqr() > nRadiusSqr )
continue;
if ( pCurrencyPack->IsClaimed() )
continue;
if ( m_pOuter->FVisible( pCurrencyPack, MASK_OPAQUE ) == false )
continue;
if ( !pCurrencyPack->ValidTouch( m_pOuter ) )
continue;
// Currencypack's seek classes with a large collection radius
if ( bScout )
{
bool bFound = false;
FOR_EACH_VEC( m_CurrencyPacks, i )
{
pulledcurrencypacks_t packinfo = m_CurrencyPacks[i];
if ( packinfo.hPack == pCurrencyPack )
bFound = true;
}
if ( !bFound )
{
// Mark as claimed to prevent other players from grabbing
pCurrencyPack->SetClaimed();
pulledcurrencypacks_t packinfo;
packinfo.hPack = pCurrencyPack;
packinfo.flTime = gpGlobals->curtime + 1.f;
m_CurrencyPacks.AddToTail( packinfo );
}
}
else
{
pCurrencyPack->Touch( m_pOuter );
}
}
FOR_EACH_VEC_BACK( m_CurrencyPacks, i )
{
if ( m_CurrencyPacks[i].hPack )
{
// If the timeout hits, force a touch
if ( m_CurrencyPacks[i].flTime <= gpGlobals->curtime )
{
m_CurrencyPacks[i].hPack->Touch( m_pOuter );
}
else
{
// Seek the player
const float flForce = 550.0f;
Vector vToPlayer = m_pOuter->GetAbsOrigin() - m_CurrencyPacks[i].hPack->GetAbsOrigin();
vToPlayer.z = 0.0f;
vToPlayer.NormalizeInPlace();
vToPlayer.z = 0.25f;
Vector vPush = flForce * vToPlayer;
m_CurrencyPacks[i].hPack->RemoveFlag( FL_ONGROUND );
m_CurrencyPacks[i].hPack->ApplyAbsVelocityImpulse( vPush );
}
}
else
{
// Automatic clean-up
m_CurrencyPacks.Remove( i );
}
}
m_flRadiusCurrencyCollectionTime = bScout ? gpGlobals->curtime + 0.15f : gpGlobals->curtime + 0.25f;
}
//-----------------------------------------------------------------------------
// Purpose: Collect objects in a radius around the player
//-----------------------------------------------------------------------------
void CTFPlayerShared::RadiusHealthkitCollectionCheck( void )
{
if ( GetCarryingRuneType() != RUNE_PLAGUE )
return;
if ( !m_pOuter->IsAlive() )
return;
if ( m_flRadiusCurrencyCollectionTime > gpGlobals->curtime )
return;
const int nRadiusSqr = 600 * 600;
const Vector& vecPos = m_pOuter->WorldSpaceCenter();
// NDebugOverlay::Sphere( vecPos, 600, 0, 255, 0, false, 2.f );
for ( int i = 0; i < IHealthKitAutoList::AutoList().Count(); ++i )
{
CHealthKit *pHealthKit = static_cast<CHealthKit*>( IHealthKitAutoList::AutoList()[i] );
if ( !pHealthKit )
continue;
if ( ( vecPos - pHealthKit->GetAbsOrigin() ).LengthSqr() > nRadiusSqr )
continue;
if ( !pHealthKit->ValidTouch( m_pOuter ) )
continue;
if ( pHealthKit->IsEffectActive( EF_NODRAW ) )
continue;
pHealthKit->ItemTouch( m_pOuter );
}
m_flRadiusCurrencyCollectionTime = gpGlobals->curtime + 0.15f;
}
//-----------------------------------------------------------------------------
// Purpose: Scan for and reveal spies in a radius around the player
//-----------------------------------------------------------------------------
void CTFPlayerShared::RadiusSpyScan( void )
{
if ( m_pOuter->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS )
return;
if ( !m_pOuter->IsAlive() )
return;
if ( m_flRadiusSpyScanTime <= gpGlobals->curtime )
{
// bool bRevealed = false;
const int iRange = 750;
CUtlVector<CTFPlayer *> vecPlayers;
CollectPlayers( &vecPlayers, TF_TEAM_PVE_INVADERS, true );
FOR_EACH_VEC( vecPlayers, i )
{
if ( !vecPlayers[i] )
continue;
if ( vecPlayers[i]->GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY )
continue;
if ( !vecPlayers[i]->m_Shared.InCond( TF_COND_STEALTHED ) )
continue;
if ( m_pOuter->FVisible( vecPlayers[i], MASK_OPAQUE ) == false )
continue;
Vector vDist = vecPlayers[i]->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
if ( vDist.LengthSqr() <= iRange * iRange )
{
vecPlayers[i]->m_Shared.OnSpyTouchedByEnemy();
// bRevealed = true;
}
}
// if ( bRevealed )
// {
// bRevealed = false;
// CSingleUserRecipientFilter filter( m_pOuter );
// m_pOuter->EmitSound( filter, m_pOuter->entindex(), "Recon.Ping" );
// }
m_flRadiusSpyScanTime = gpGlobals->curtime + 0.3f;
}
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::ApplyAttributeToPlayer( const char* pszAttribName, float flValue )
{
const CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinitionByName( pszAttribName );
m_pOuter->GetAttributeList()->SetRuntimeAttributeValue( pDef, flValue );
m_pOuter->TeamFortress_SetSpeed();
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::RemoveAttributeFromPlayer( const char* pszAttribName )
{
const CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinitionByName( pszAttribName );
m_pOuter->GetAttributeList()->RemoveAttribute( pDef );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::AddTmpDamageBonus( float flBonus, float flExpiration )
{
AddCond( TF_COND_TMPDAMAGEBONUS, flExpiration );
m_flTmpDamageBonusAmount += flBonus;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::FindHealerIndex( CBaseEntity *pHealer )
{
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
if ( m_aHealers[i].pHealer == pHealer )
return i;
}
return m_aHealers.InvalidIndex();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *CTFPlayerShared::GetHealerByIndex( int index )
{
int iNumHealers = m_aHealers.Count();
if ( index < 0 || index >= iNumHealers )
return NULL;
return m_aHealers[index].pHealer;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::HealerIsDispenser( int index )
{
int iNumHealers = m_aHealers.Count();
if ( index < 0 || index >= iNumHealers )
return false;
return m_aHealers[index].bDispenserHeal;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the first healer in the healer array. Note that this
// is an arbitrary healer.
//-----------------------------------------------------------------------------
EHANDLE CTFPlayerShared::GetFirstHealer()
{
if ( m_aHealers.Count() > 0 )
return m_aHealers.Head().pHealer;
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: External code has decided that the trigger event for an achievement
// has occurred. Go through our data and give it to the right people.
//-----------------------------------------------------------------------------
void CTFPlayerShared::CheckForAchievement( int iAchievement )
{
if ( iAchievement == ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE ||
(iAchievement == ACHIEVEMENT_TF_MEDIC_CHARGE_BLOCKER && InCond( TF_COND_INVULNERABLE ) ) )
{
// ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE : We were just saved from death by invuln. See if any medics deployed
// their charge on us recently, and if so, give them the achievement.
// ACHIEVEMENT_TF_MEDIC_CHARGE_BLOCKER: We just blocked a capture, and we're invuln. Whoever's invulning us gets the achievement.
for ( int i = 0; i < m_aHealers.Count(); i++ )
{
CTFPlayer *pPlayer = ToTFPlayer( m_aHealers[i].pHealer );
if ( !pPlayer )
continue;
if ( !pPlayer->IsPlayerClass(TF_CLASS_MEDIC) )
continue;
CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
if ( !pWpn )
continue;
CWeaponMedigun *pMedigun = dynamic_cast<CWeaponMedigun*>(pWpn);
if ( pMedigun && pMedigun->IsReleasingCharge() )
{
// Save teammate requires us to have deployed the charge within the last second
if ( iAchievement != ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE || (gpGlobals->curtime - pMedigun->GetReleaseStartedAt()) < 1.0 )
{
pPlayer->AwardAchievement( iAchievement );
}
}
}
}
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: Get all of our conditions in a nice CBitVec
//-----------------------------------------------------------------------------
void CTFPlayerShared::GetConditionsBits( CBitVec< TF_COND_LAST >& vbConditions ) const
{
vbConditions.Set( 0u, (uint32)m_nPlayerCond );
vbConditions.Set( 1u, (uint32)m_nPlayerCondEx );
vbConditions.Set( 2u, (uint32)m_nPlayerCondEx2 );
vbConditions.Set( 3u, (uint32)m_nPlayerCondEx3 );
COMPILE_TIME_ASSERT( 32 + 32 + 32 + 32 > TF_COND_LAST );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFWeaponBase *CTFPlayerShared::GetActiveTFWeapon() const
{
return m_pOuter->GetActiveTFWeapon();
}
//-----------------------------------------------------------------------------
// Purpose: Team check.
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsAlly( CBaseEntity *pEntity )
{
return ( pEntity->GetTeamNumber() == m_pOuter->GetTeamNumber() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetDesiredPlayerClassIndex( void )
{
return m_iDesiredPlayerClass;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetJumping( bool bJumping )
{
m_bJumping = bJumping;
}
void CTFPlayerShared::SetAirDash( int iAirDash )
{
m_iAirDash = iAirDash;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayerShared::GetCritMult( void )
{
float flRemapCritMul = RemapValClamped( m_iCritMult, 0, 255, 1.0, 4.0 );
/*#ifdef CLIENT_DLL
Msg("CLIENT: Crit mult %.2f - %d\n",flRemapCritMul, m_iCritMult);
#else
Msg("SERVER: Crit mult %.2f - %d\n", flRemapCritMul, m_iCritMult );
#endif*/
return flRemapCritMul;
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateCritMult( void )
{
const float flMinMult = 1.0;
const float flMaxMult = TF_DAMAGE_CRITMOD_MAXMULT;
if ( m_DamageEvents.Count() == 0 )
{
m_iCritMult = RemapValClamped( flMinMult, 1.0, 4.0, 0, 255 );
return;
}
//Msg( "Crit mult update for %s\n", m_pOuter->GetPlayerName() );
//Msg( " Entries: %d\n", m_DamageEvents.Count() );
// Go through the damage multipliers and remove expired ones, while summing damage of the others
float flTotalDamage = 0;
for ( int i = m_DamageEvents.Count() - 1; i >= 0; i-- )
{
float flDelta = gpGlobals->curtime - m_DamageEvents[i].flTime;
if ( flDelta > tf_damage_events_track_for.GetFloat() )
{
//Msg( " Discarded (%d: time %.2f, now %.2f)\n", i, m_DamageEvents[i].flTime, gpGlobals->curtime );
m_DamageEvents.Remove(i);
continue;
}
// Ignore damage we've just done. We do this so that we have time to get those damage events
// to the client in time for using them in prediction in this code.
if ( flDelta < TF_DAMAGE_CRITMOD_MINTIME )
{
//Msg( " Ignored (%d: time %.2f, now %.2f)\n", i, m_DamageEvents[i].flTime, gpGlobals->curtime );
continue;
}
if ( flDelta > TF_DAMAGE_CRITMOD_MAXTIME )
continue;
//Msg( " Added %.2f (%d: time %.2f, now %.2f)\n", m_DamageEvents[i].flDamage, i, m_DamageEvents[i].flTime, gpGlobals->curtime );
flTotalDamage += m_DamageEvents[i].flDamage * m_DamageEvents[i].flDamageCritScaleMultiplier;
}
float flMult = RemapValClamped( flTotalDamage, 0, TF_DAMAGE_CRITMOD_DAMAGE, flMinMult, flMaxMult );
// Msg( " TotalDamage: %.2f -> Mult %.2f\n", flTotalDamage, flMult );
m_iCritMult = (int)RemapValClamped( flMult, flMinMult, flMaxMult, 0, 255 );
}
#define CRIT_DAMAGE_TIME 0.1f
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::RecordDamageEvent( const CTakeDamageInfo &info, bool bKill, int nVictimPrevHealth )
{
if ( m_DamageEvents.Count() >= MAX_DAMAGE_EVENTS )
{
// Remove the oldest event
m_DamageEvents.Remove( m_DamageEvents.Count()-1 );
}
// Don't count critical damage toward the critical multiplier.
float flDamage = info.GetDamage() - info.GetDamageBonus();
float flDamageCriticalScale = info.GetDamageType() & DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE
? 0.0f
: 1.0f;
// cap the damage at our current health amount since it's going to kill us
if ( bKill && flDamage > nVictimPrevHealth )
{
flDamage = nVictimPrevHealth;
}
// Don't allow explosions to stack up damage toward the critical modifier.
bool bOverride = false;
if ( info.GetDamageType() & DMG_BLAST )
{
int nDamageCount = m_DamageEvents.Count();
for ( int iDamage = 0; iDamage < nDamageCount; ++iDamage )
{
// Was the older event I am checking against an explosion as well?
if ( m_DamageEvents[iDamage].nDamageType & DMG_BLAST )
{
// Did it happen very recently?
if ( ( gpGlobals->curtime - m_DamageEvents[iDamage].flTime ) < CRIT_DAMAGE_TIME )
{
if ( bKill )
{
m_DamageEvents[iDamage].nKills++;
if ( m_pOuter->IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
// Make sure the previous & the current are stickybombs, and go with it.
if ( m_DamageEvents[iDamage].nDamageType == info.GetDamageType() &&
m_DamageEvents[iDamage].nDamageType == g_aWeaponDamageTypes[TF_WEAPON_PIPEBOMBLAUNCHER] )
{
if ( TFGameRules()->IsMannVsMachineMode() && m_DamageEvents[iDamage].nKills >= 10 )
{
m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_DEMO_GROUP_KILL );
}
else if ( m_DamageEvents[iDamage].nKills >= 3 )
{
m_pOuter->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL3_WITH_DETONATION );
}
}
}
}
// Take the max damage done in the time frame.
if ( flDamage > m_DamageEvents[iDamage].flDamage )
{
m_DamageEvents[iDamage].flDamage = flDamage;
m_DamageEvents[iDamage].flDamageCritScaleMultiplier = flDamageCriticalScale;
m_DamageEvents[iDamage].flTime = gpGlobals->curtime;
m_DamageEvents[iDamage].nDamageType = info.GetDamageType();
// Msg( "Update Damage Event: D:%f, T:%f\n", m_DamageEvents[iDamage].flDamage, m_DamageEvents[iDamage].flTime );
}
bOverride = true;
}
}
}
}
// We overrode a value, don't add this to the list.
if ( bOverride )
return;
int iIndex = m_DamageEvents.AddToTail();
m_DamageEvents[iIndex].flDamage = flDamage;
m_DamageEvents[iIndex].flDamageCritScaleMultiplier = flDamageCriticalScale;
m_DamageEvents[iIndex].nDamageType = info.GetDamageType();
m_DamageEvents[iIndex].flTime = gpGlobals->curtime;
m_DamageEvents[iIndex].nKills = bKill;
// Msg( "Damage Event: D:%f, T:%f\n", m_DamageEvents[iIndex].flDamage, m_DamageEvents[iIndex].flTime );
if ( TFGameRules()->IsMannVsMachineMode() && m_pOuter->IsPlayerClass( TF_CLASS_SNIPER ) )
{
int nKillCount = 0;
int nDamageCount = m_DamageEvents.Count();
for ( int iDamage = 0; iDamage < nDamageCount; ++iDamage )
{
// Did it happen very recently?
if ( ( gpGlobals->curtime - m_DamageEvents[iDamage].flTime ) < CRIT_DAMAGE_TIME )
{
nKillCount += m_DamageEvents[iDamage].nKills;
}
}
if ( nKillCount >= 4 )
{
m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_SNIPER_KILL_GROUP );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::AddTempCritBonus( float flAmount )
{
if ( m_DamageEvents.Count() >= MAX_DAMAGE_EVENTS )
{
// Remove the oldest event
m_DamageEvents.Remove( m_DamageEvents.Count()-1 );
}
int iIndex = m_DamageEvents.AddToTail();
m_DamageEvents[iIndex].flDamage = RemapValClamped( flAmount, 0, 1, 0, TF_DAMAGE_CRITMOD_DAMAGE ) / (TF_DAMAGE_CRITMOD_MAXMULT - 1.0);
m_DamageEvents[iIndex].flDamageCritScaleMultiplier = 1.0f;
m_DamageEvents[iIndex].nDamageType = DMG_GENERIC;
m_DamageEvents[iIndex].flTime = gpGlobals->curtime;
m_DamageEvents[iIndex].nKills = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetNumKillsInTime( float flTime )
{
if ( tf_damage_events_track_for.GetFloat() < flTime )
{
Warning("Player asking for damage events for time %.0f, but tf_damage_events_track_for is only tracking events for %.0f\n", flTime, tf_damage_events_track_for.GetFloat() );
}
int iKills = 0;
for ( int i = m_DamageEvents.Count() - 1; i >= 0; i-- )
{
float flDelta = gpGlobals->curtime - m_DamageEvents[i].flTime;
if ( flDelta < flTime )
{
iKills += m_DamageEvents[i].nKills;
}
}
return iKills;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::AddToSpyCloakMeter( float val, bool bForce )
{
CTFWeaponInvis *pWpn = (CTFWeaponInvis *) m_pOuter->Weapon_OwnsThisID( TF_WEAPON_INVIS );
if ( !pWpn )
return false;
// STAGING_SPY
// Special cloaks only get cloak if not active and receive a smaller portion
int iNoCloakedPickup = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWpn, iNoCloakedPickup, NoCloakWhenCloaked );
if ( !bForce )
{
if ( InCond( TF_COND_STEALTHED ) && iNoCloakedPickup )
{
return false;
}
else
{
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, val, ReducedCloakFromAmmo );
}
}
bool bResult = ( val > 0 && m_flCloakMeter < 100.0f );
m_flCloakMeter = clamp( m_flCloakMeter + val, 0.0f, 100.0f );
return bResult;
}
#endif
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: Stun & Snare Application
//-----------------------------------------------------------------------------
void CTFPlayerShared::StunPlayer( float flTime, float flReductionAmount, int iStunFlags, CTFPlayer* pAttacker )
{
// Insanity prevention
if ( ( m_PlayerStuns.Count() + 1 ) >= 250 )
return;
if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_PASSTIME_INTERCEPTION ) )
return;
if ( InCond( TF_COND_MEGAHEAL ) )
return;
if ( InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) && !InCond( TF_COND_MVM_BOT_STUN_RADIOWAVE ) )
return;
if ( pAttacker && TFGameRules() && TFGameRules()->IsTruceActive() && pAttacker->IsTruceValidForEnt() )
{
if ( ( pAttacker->GetTeamNumber() == TF_TEAM_RED ) || ( pAttacker->GetTeamNumber() == TF_TEAM_BLUE ) )
return;
}
float flRemapAmount = RemapValClamped( flReductionAmount, 0.0, 1.0, 0, 255 );
int iOldStunFlags = GetStunFlags();
// Already stunned
bool bStomp = false;
if ( InCond( TF_COND_STUNNED ) )
{
if ( GetActiveStunInfo() )
{
// Is it stronger than the active?
if ( flRemapAmount > GetActiveStunInfo()->flStunAmount || iStunFlags & TF_STUN_CONTROLS || iStunFlags & TF_STUN_LOSER_STATE )
{
bStomp = true;
}
// It's weaker. Would it expire before the active?
else if ( gpGlobals->curtime + flTime < GetActiveStunInfo()->flExpireTime )
{
// Ignore
return;
}
}
}
else if ( GetActiveStunInfo() )
{
// Something yanked our TF_COND_STUNNED in an unexpected way
if ( !HushAsserts() )
Assert( !"Something yanked out TF_COND_STUNNED." );
m_PlayerStuns.RemoveAll();
return;
}
// Add it to the stack
stun_struct_t stunEvent =
{
pAttacker, // hPlayer
flTime, // flDuration
gpGlobals->curtime + flTime, // flExpireTime
gpGlobals->curtime + flTime, // flStartFadeTime
flRemapAmount, // flStunAmount
iStunFlags // iStunFlags
};
// Should this become the active stun?
if ( bStomp || !GetActiveStunInfo() )
{
// If stomping, see if the stun we're replacing has a stronger slow.
// This can happen when stuns use TF_STUN_CONTROLS or TF_STUN_LOSER_STATE.
float flOldStun = GetActiveStunInfo() ? GetActiveStunInfo()->flStunAmount : 0.f;
m_iStunIndex = m_PlayerStuns.AddToTail( stunEvent );
if ( flOldStun > flRemapAmount )
{
GetActiveStunInfo()->flStunAmount = flOldStun;
}
}
else
{
// Done for now
m_PlayerStuns.AddToTail( stunEvent );
return;
}
// Add in extra time when TF_STUN_CONTROLS
if ( GetActiveStunInfo()->iStunFlags & TF_STUN_CONTROLS )
{
if ( !InCond( TF_COND_HALLOWEEN_KART ) )
{
GetActiveStunInfo()->flExpireTime += CONTROL_STUN_ANIM_TIME;
}
}
GetActiveStunInfo()->flStartFadeTime = gpGlobals->curtime + GetActiveStunInfo()->flDuration;
// Update old system for networking
UpdateLegacyStunSystem();
if ( GetActiveStunInfo()->iStunFlags & TF_STUN_CONTROLS || GetActiveStunInfo()->iStunFlags & TF_STUN_LOSER_STATE )
{
m_pOuter->m_angTauntCamera = m_pOuter->EyeAngles();
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_STUNNED );
if ( pAttacker )
{
pAttacker->SpeakConceptIfAllowed( MP_CONCEPT_STUNNED_TARGET );
}
}
if ( !( GetActiveStunInfo()->iStunFlags & TF_STUN_NO_EFFECTS ) )
{
m_pOuter->StunSound( pAttacker, GetActiveStunInfo()->iStunFlags, iOldStunFlags );
}
// Event for achievements.
IGameEvent *event = gameeventmanager->CreateEvent( "player_stunned" );
if ( event )
{
if ( pAttacker )
{
event->SetInt( "stunner", pAttacker->GetUserID() );
}
event->SetInt( "victim", m_pOuter->GetUserID() );
event->SetBool( "victim_capping", m_pOuter->IsCapturingPoint() );
event->SetBool( "big_stun", ( GetActiveStunInfo()->iStunFlags & TF_STUN_SPECIAL_SOUND ) != 0 );
gameeventmanager->FireEvent( event );
}
// Clear off all taunts, expressions, and scenes.
if ( ( GetActiveStunInfo()->iStunFlags & TF_STUN_CONTROLS) == TF_STUN_CONTROLS || ( GetActiveStunInfo()->iStunFlags & TF_STUN_LOSER_STATE) == TF_STUN_LOSER_STATE )
{
m_pOuter->StopTaunt();
m_pOuter->ClearExpression();
m_pOuter->ClearWeaponFireScene();
}
AddCond( TF_COND_STUNNED, -1.f, pAttacker );
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: Returns the intensity of the current stun effect, if we have the type of stun indicated.
//-----------------------------------------------------------------------------
float CTFPlayerShared::GetAmountStunned( int iStunFlags )
{
if ( GetActiveStunInfo() )
{
if ( InCond( TF_COND_STUNNED ) && ( iStunFlags & GetActiveStunInfo()->iStunFlags ) && ( GetActiveStunInfo()->flExpireTime > gpGlobals->curtime ) )
return MIN( MAX( GetActiveStunInfo()->flStunAmount, 0 ), 255 ) * ( 1.f/255.f );
}
return 0.f;
}
//-----------------------------------------------------------------------------
// Purpose: Indicates that our controls are stunned.
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsControlStunned( void )
{
if ( GetActiveStunInfo() )
{
if ( InCond( TF_COND_STUNNED ) && ( m_iStunFlags & TF_STUN_CONTROLS ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Indicates that our controls are stunned.
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsLoserStateStunned( void ) const
{
if ( GetActiveStunInfo() )
{
if ( InCond( TF_COND_STUNNED ) && ( m_iStunFlags & TF_STUN_LOSER_STATE ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Indicates that our movement is slowed, but our controls are still free.
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsSnared( void )
{
if ( InCond( TF_COND_STUNNED ) && !IsControlStunned() )
return true;
else
return false;
}
//=============================================================================
//
// Shared player code that isn't CTFPlayerShared
//
//-----------------------------------------------------------------------------
struct penetrated_target_list
{
CBaseEntity *pTarget;
float flDistanceFraction;
};
//-----------------------------------------------------------------------------
class CBulletPenetrateEnum : public IEntityEnumerator
{
public:
CBulletPenetrateEnum( Ray_t *pRay, CBaseEntity *pShooter, int nCustomDamageType, bool bIgnoreTeammates = true )
{
m_pRay = pRay;
m_pShooter = pShooter;
m_nCustomDamageType = nCustomDamageType;
m_bIgnoreTeammates = bIgnoreTeammates;
}
// We need to sort the penetrated targets into order, with the closest target first
class PenetratedTargetLess
{
public:
bool Less( const penetrated_target_list &src1, const penetrated_target_list &src2, void *pCtx )
{
return src1.flDistanceFraction < src2.flDistanceFraction;
}
};
virtual bool EnumEntity( IHandleEntity *pHandleEntity )
{
trace_t tr;
CBaseEntity *pEnt = static_cast<CBaseEntity*>(pHandleEntity);
// Ignore collisions with the shooter
if ( pEnt == m_pShooter )
return true;
if ( pEnt->IsCombatCharacter() || pEnt->IsBaseObject() )
{
if ( m_bIgnoreTeammates && pEnt->GetTeam() == m_pShooter->GetTeam() )
return true;
enginetrace->ClipRayToEntity( *m_pRay, MASK_SOLID | CONTENTS_HITBOX, pHandleEntity, &tr );
if (tr.fraction < 1.0f)
{
penetrated_target_list newEntry;
newEntry.pTarget = pEnt;
newEntry.flDistanceFraction = tr.fraction;
m_Targets.Insert( newEntry );
return true;
}
}
return true;
}
public:
Ray_t *m_pRay;
int m_nCustomDamageType;
CBaseEntity *m_pShooter;
bool m_bIgnoreTeammates;
CUtlSortVector<penetrated_target_list, PenetratedTargetLess> m_Targets;
};
CTargetOnlyFilter::CTargetOnlyFilter( CBaseEntity *pShooter, CBaseEntity *pTarget )
: CTraceFilterSimple( pShooter, COLLISION_GROUP_NONE )
{
m_pShooter = pShooter;
m_pTarget = pTarget;
}
bool CTargetOnlyFilter::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
{
CBaseEntity *pEnt = static_cast<CBaseEntity*>(pHandleEntity);
if ( pEnt && pEnt == m_pTarget )
return true;
else if ( !pEnt || pEnt != m_pTarget )
{
// If we hit a solid piece of the world, we're done.
if ( pEnt->IsBSPModel() && pEnt->IsSolid() )
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
return false;
}
else
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input: info
// bDoEffects - effects (blood, etc.) should only happen client-side.
//-----------------------------------------------------------------------------
void CTFPlayer::MaybeDrawRailgunBeam( IRecipientFilter *pFilter, CTFWeaponBase *pWeapon, const Vector& vStartPos, const Vector& vEndPos )
{
#ifdef GAME_DLL
Assert( pFilter );
#else // !GAME_DLL
Assert( !pFilter );
#endif
Assert( pWeapon );
int iShouldFireTracer = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iShouldFireTracer, sniper_fires_tracer );
if ( !iShouldFireTracer )
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iShouldFireTracer, sniper_fires_tracer_HIDDEN );
}
// Check for heatmaker
if ( !iShouldFireTracer )
{
iShouldFireTracer = m_Shared.InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) && pWeapon && WeaponID_IsSniperRifle( pWeapon->GetWeaponID() );
}
if ( iShouldFireTracer )
{
const char *pParticleSystemName = pWeapon->GetTeamNumber() == TF_TEAM_BLUE ? "dxhr_sniper_rail_blue" : "dxhr_sniper_rail_red";
CTFSniperRifle *pRifle = dynamic_cast< CTFSniperRifle* >( pWeapon );
if ( pRifle && ( pRifle->GetRifleType() == RIFLE_CLASSIC ) )
{
pParticleSystemName = "tfc_sniper_distortion_trail";
}
#ifdef GAME_DLL
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, vEndPos };
TE_TFParticleEffectComplex( *pFilter, 0.0f, pParticleSystemName, vStartPos, QAngle( 0, 0, 0 ), NULL, &controlPoint, pWeapon, PATTACH_CUSTOMORIGIN );
#else // !GAME_DLL
CSmartPtr<CNewParticleEffect> pEffect = pWeapon->ParticleProp()->Create( pParticleSystemName, PATTACH_CUSTOMORIGIN, 0 );
if ( pEffect.IsValid() && pEffect->IsValid() )
{
pEffect->SetSortOrigin( vStartPos );
pEffect->SetControlPoint( 0, vStartPos );
pEffect->SetControlPoint( 1, vEndPos );
}
#endif // GAME_DLL
}
}
void CTFPlayer::GetHorriblyHackedRailgunPosition( const Vector& vStart, Vector *out_pvStartPos )
{
Assert( out_pvStartPos != NULL );
// DO NOT LOOK BEHIND THE MAGIC CURTAIN
Vector vForward, vRight, vUp;
AngleVectors( EyeAngles(), &vForward, &vRight, &vUp );
*out_pvStartPos = vStart
+ (vForward * 60.9f)
+ (vRight * 13.1f)
+ (vUp * -15.1f);
}
static bool OnOpposingTFTeams( int iTeam0, int iTeam1 )
{
// This logic is weird because we want to make sure that we're actually shooting someone on the
// other team, not just someone on a different team. This prevents weirdness where we count shooting
// the BSP as an enemy because they aren't on our team.
if ( iTeam0 == TF_TEAM_BLUE ) // if we're on the blue team...
return iTeam1 == TF_TEAM_RED; // ...and we shot someone on the red team, then we're opposing.
if ( iTeam0 == TF_TEAM_RED ) // if we're on the blue team...
return iTeam1 == TF_TEAM_BLUE; // ...and we shot someone on the red team, then we're opposing.
return iTeam0 != iTeam1; // if we're neither red nor blue, then anyone different from us is opposing
}
#ifdef GAME_DLL
extern void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::ModifyDamageInfo( CTakeDamageInfo *pInfo, const CBaseEntity *pTarget )
{
if ( pInfo && pTarget )
{
// Increased damage vs sentry's target?
if ( IsPlayerClass( TF_CLASS_ENGINEER ) )
{
float flDamageMod = 1.f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetActiveWeapon(), flDamageMod, mult_dmg_bullet_vs_sentry_target );
if ( flDamageMod > 1.f )
{
CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>( GetObjectOfType( OBJ_SENTRYGUN ) );
if ( pSentry && ( pSentry->GetTarget() == pTarget ) )
{
pInfo->SetDamage( pInfo->GetDamage() * flDamageMod );
}
}
}
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::FireBullet( CTFWeaponBase *pWpn, const FireBulletsInfo_t &info, bool bDoEffects, int nDamageType, int nCustomDamageType /*= TF_DMG_CUSTOM_NONE*/ )
{
// Fire a bullet (ignoring the shooter).
Vector vecStart = info.m_vecSrc;
Vector vecEnd = vecStart + info.m_vecDirShooting * info.m_flDistance;
trace_t trace;
ETFDmgCustom ePenetrateType = pWpn ? pWpn->GetPenetrateType() : TF_DMG_CUSTOM_NONE;
if ( ePenetrateType == TF_DMG_CUSTOM_NONE )
{
ePenetrateType = (ETFDmgCustom)nCustomDamageType;
}
Ray_t ray;
ray.Init( vecStart, vecEnd );
// Ignore teammates and their (physical) upgrade items when shooting in MvM
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
{
CTraceFilterIgnoreFriendlyCombatItems traceFilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID | CONTENTS_HITBOX, &traceFilter, &trace );
}
else
{
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID | CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &trace );
}
#ifndef CLIENT_DLL
CUtlVector<CBaseEntity *> vecTracedEntities;
bool bPenetratingShot = ( (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS) || (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM) || (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE) );
if ( bPenetratingShot && trace.m_pEnt )
{
if ( trace.m_pEnt->IsCombatCharacter() || trace.m_pEnt->IsBaseObject() )
{
// Penetrating shot: Strikes everything along the bullet's path.
CBulletPenetrateEnum bulletpenetrate( &ray, this, ePenetrateType, ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM );
enginetrace->EnumerateEntities( ray, false, &bulletpenetrate );
FOR_EACH_VEC( bulletpenetrate.m_Targets, i )
{
vecTracedEntities.AddToTail( bulletpenetrate.m_Targets[i].pTarget );
}
}
else
{
// We traced into something we don't understand (sticky bomb? pumpkin bomb?) -- just apply our
// hit logic to whatever we traced first.
vecTracedEntities.AddToTail( trace.m_pEnt );
}
}
else
#endif
{
ePenetrateType = TF_DMG_CUSTOM_NONE;
}
#ifndef CLIENT_DLL
CTakeDamageInfo dmgInfo( this, info.m_pAttacker, info.m_flDamage, nDamageType );
dmgInfo.SetWeapon( GetActiveWeapon() );
dmgInfo.SetDamageCustom( nCustomDamageType );
int iPenetratedPlayerCount = 0;
int iEnemyPlayersHit = 0;
if ( bPenetratingShot )
{
int iChargedPenetration = 0;
CALL_ATTRIB_HOOK_INT( iChargedPenetration, sniper_penetrate_players_when_charged );
int iPenetrationLimit = 0;
CALL_ATTRIB_HOOK_INT( iPenetrationLimit, projectile_penetration );
// Damage every enemy player struck by the bullet along its path.
trace_t pen_trace;
FOR_EACH_VEC( vecTracedEntities, i )
{
// Limit the number of pen targets in MvM if we're not charge-based
if ( TFGameRules()->IsMannVsMachineMode() && iChargedPenetration == 0 )
{
// For sniper class, treat iPenetrationLimit as a bool
bool bIsSniper = IsPlayerClass( TF_CLASS_SNIPER );
if ( bIsSniper && iPenetrationLimit == 0 && iPenetratedPlayerCount > 0 )
break;
if ( !bIsSniper && iPenetratedPlayerCount > iPenetrationLimit )
break;
}
CBaseEntity *pTarget = vecTracedEntities[i];
if ( !pTarget )
continue;
trace_t *pTraceToUse = &pen_trace;
if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM )
{
// Skip friendlies if we're looking for the first enemy
if ( GetTeamNumber() == pTarget->GetTeamNumber() )
continue;
pTraceToUse = &trace;
}
else if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE )
{
if ( GetTeamNumber() == pTarget->GetTeamNumber() )
{
if ( pTarget->IsPlayer() )
{
// skip friendlies that are not on burning
CTFPlayer *pTeammate = ToTFPlayer( pTarget );
if ( !pTeammate->m_Shared.InCond( TF_COND_BURNING ) )
continue;
}
}
pTraceToUse = &trace;
}
CTargetOnlyFilter penetrateFilter( this, pTarget );
UTIL_TraceLine( vecStart, vecEnd, (MASK_SOLID|CONTENTS_HITBOX), &penetrateFilter, pTraceToUse );
if ( pTraceToUse->m_pEnt == pTarget )
{
CTFPlayer *pTargetPlayer = NULL;
if ( pTarget->IsPlayer() )
pTargetPlayer = ToTFPlayer( pTarget );
// put out fire for burning teammate
if ( nCustomDamageType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE )
{
if ( pTargetPlayer && GetTeamNumber() == pTargetPlayer->GetTeamNumber() && pTargetPlayer->m_Shared.InCond( TF_COND_BURNING ) )
{
ExtinguishPlayer( GetActiveWeapon(), ToTFPlayer( GetActiveWeapon()->GetOwner() ), pTargetPlayer, GetActiveWeapon()->GetName() );
}
}
ModifyDamageInfo( &dmgInfo, pTarget );
CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, info.m_vecDirShooting, pTraceToUse->endpos, 1.0 );
dmgInfo.SetPlayerPenetrationCount( iPenetratedPlayerCount );
pTarget->DispatchTraceAttack( dmgInfo, info.m_vecDirShooting, pTraceToUse, GetActiveWeapon() ? GetActiveWeapon()->GetDmgAccumulator() : NULL );
const bool bIsPenetratingPlayer = pTargetPlayer != NULL;
if ( bIsPenetratingPlayer )
{
iPenetratedPlayerCount++;
float flPenetrationPenalty = 1.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flPenetrationPenalty, penetration_damage_penalty );
dmgInfo.SetDamage( dmgInfo.GetDamage() * flPenetrationPenalty );
}
// If we're only supposed to penetrate players and this thing isn't a player, stop here.
if ( !bIsPenetratingPlayer && (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS) )
break;
}
else
{
// We hit something solid that said we should stop tracing.
break;
}
if( pTarget->IsPlayer() && OnOpposingTFTeams( GetTeamNumber(), pTarget->GetTeamNumber() ) )
{
iEnemyPlayersHit++;
}
// If we're penetrating team mates, but we've just hit an enemy, we're done.
if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_MY_TEAM )
break;
// just hit an enemy or a burning teammate
if ( ePenetrateType == TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE )
break;
}
}
else
{
// Damage only the first entity encountered on the bullet's path.
if ( trace.m_pEnt )
{
ModifyDamageInfo( &dmgInfo, trace.m_pEnt );
CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, info.m_vecDirShooting, trace.endpos, 1.0 );
trace.m_pEnt->DispatchTraceAttack( dmgInfo, info.m_vecDirShooting, &trace );
if ( trace.m_pEnt->IsPlayer() && OnOpposingTFTeams( GetTeamNumber(), trace.m_pEnt->GetTeamNumber() ) )
{
iEnemyPlayersHit++;
}
}
}
if ( pWpn )
{
pWpn->OnBulletFire( iEnemyPlayersHit );
if ( iEnemyPlayersHit )
{ // Guarantee that the bullet that hit an enemy trumps the player viewangles
// that are locked in for the duration of the server simulation ticks
m_iLockViewanglesTickNumber = gpGlobals->tickcount;
m_qangLockViewangles = pl.v_angle;
}
}
#endif
#ifdef GAME_DLL
#ifdef _DEBUG
if ( tf_debug_bullets.GetBool() )
{
NDebugOverlay::Line( vecStart, trace.endpos, 0,255,0, true, 30 );
}
#endif // _DEBUG
#endif
if ( trace.fraction < 1.0 )
{
// Verify we have an entity at the point of impact.
Assert( trace.m_pEnt );
#ifdef GAME_DLL
// We intentionally do this logic here outside our client-side "should we do effects?" logic. We send this
// to everyone except our local owner (ourself) as we'll do our own fire effects below.
Vector vMuzzleOrigin;
if ( pWpn )
{
Vector vStartPos;
GetHorriblyHackedRailgunPosition( trace.startpos, &vStartPos );
CBroadcastNonOwnerRecipientFilter filter( this );
MaybeDrawRailgunBeam( &filter, pWpn, vStartPos, trace.endpos );
}
#endif // GAME_DLL
if ( bDoEffects )
{
// If shot starts out of water and ends in water
if ( !( enginetrace->GetPointContents( trace.startpos ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) &&
( enginetrace->GetPointContents( trace.endpos ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) )
{
// Water impact effects.
ImpactWaterTrace( trace, vecStart );
}
else
{
// Regular impact effects.
// don't decal your teammates or objects on your team
if ( trace.m_pEnt && trace.m_pEnt->GetTeamNumber() != GetTeamNumber() )
{
UTIL_ImpactTrace( &trace, nDamageType );
}
}
#ifdef CLIENT_DLL
if ( pWpn )
{
Vector vStartPos;
GetHorriblyHackedRailgunPosition( trace.startpos, &vStartPos );
MaybeDrawRailgunBeam( NULL, pWpn, vStartPos, trace.endpos );
}
static int tracerCount;
if ( ( ( info.m_iTracerFreq != 0 ) && ( tracerCount++ % info.m_iTracerFreq ) == 0 ) || (ePenetrateType == TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS) )
{
// if this is a local player, start at attachment on view model
// else start on attachment on weapon model
int iUseAttachment = TRACER_DONT_USE_ATTACHMENT;
int iAttachment = 1;
{
C_BaseCombatWeapon *pWeapon = GetActiveWeapon();
if ( pWeapon )
{
iAttachment = pWeapon->LookupAttachment( "muzzle" );
}
}
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
bool bInToolRecordingMode = clienttools->IsInRecordingMode();
// If we're using a viewmodel, override vecStart with the muzzle of that - just for the visual effect, not gameplay.
if ( ( pLocalPlayer != NULL ) && !pLocalPlayer->ShouldDrawThisPlayer() && !bInToolRecordingMode && pWpn )
{
C_BaseAnimating *pAttachEnt = pWpn->GetAppropriateWorldOrViewModel();
if ( pAttachEnt != NULL )
{
pAttachEnt->GetAttachment( iAttachment, vecStart );
}
}
else if ( !IsDormant() )
{
// fill in with third person weapon model index
C_BaseCombatWeapon *pWeapon = GetActiveWeapon();
if( pWeapon )
{
int nModelIndex = pWeapon->GetModelIndex();
int nWorldModelIndex = pWeapon->GetWorldModelIndex();
if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex )
{
pWeapon->SetModelIndex( nWorldModelIndex );
}
pWeapon->GetAttachment( iAttachment, vecStart );
if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex )
{
pWeapon->SetModelIndex( nModelIndex );
}
}
}
if ( tf_useparticletracers.GetBool() )
{
const char *pszTracerEffect = GetTracerType();
if ( pszTracerEffect && pszTracerEffect[0] )
{
char szTracerEffect[128];
if ( nDamageType & DMG_CRITICAL )
{
Q_snprintf( szTracerEffect, sizeof(szTracerEffect), "%s_crit", pszTracerEffect );
pszTracerEffect = szTracerEffect;
}
UTIL_ParticleTracer( pszTracerEffect, vecStart, trace.endpos, entindex(), iUseAttachment, true );
}
}
else
{
UTIL_Tracer( vecStart, trace.endpos, entindex(), iUseAttachment, 5000, true, GetTracerType() );
}
}
#endif
}
}
}
#ifdef CLIENT_DLL
static ConVar tf_impactwatertimeenable( "tf_impactwatertimeenable", "0", FCVAR_CHEAT, "Draw impact debris effects." );
static ConVar tf_impactwatertime( "tf_impactwatertime", "1.0f", FCVAR_CHEAT, "Draw impact debris effects." );
#endif
//-----------------------------------------------------------------------------
// Purpose: Trace from the shooter to the point of impact (another player,
// world, etc.), but this time take into account water/slime surfaces.
// Input: trace - initial trace from player to point of impact
// vecStart - starting point of the trace
//-----------------------------------------------------------------------------
void CTFPlayer::ImpactWaterTrace( trace_t &trace, const Vector &vecStart )
{
#ifdef CLIENT_DLL
if ( tf_impactwatertimeenable.GetBool() )
{
if ( m_flWaterImpactTime > gpGlobals->curtime )
return;
}
#endif
trace_t traceWater;
UTIL_TraceLine( vecStart, trace.endpos, ( MASK_SHOT | CONTENTS_WATER | CONTENTS_SLIME ),
this, COLLISION_GROUP_NONE, &traceWater );
if( traceWater.fraction < 1.0f )
{
CEffectData data;
data.m_vOrigin = traceWater.endpos;
data.m_vNormal = traceWater.plane.normal;
data.m_flScale = random->RandomFloat( 8, 12 );
if ( traceWater.contents & CONTENTS_SLIME )
{
data.m_fFlags |= FX_WATER_IN_SLIME;
}
const char *pszEffectName = "tf_gunshotsplash";
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
if ( pWeapon && ( TF_WEAPON_MINIGUN == pWeapon->GetWeaponID() ) )
{
// for the minigun, use a different, cheaper splash effect because it can create so many of them
pszEffectName = "tf_gunshotsplash_minigun";
}
DispatchEffect( pszEffectName, data );
#ifdef CLIENT_DLL
if ( tf_impactwatertimeenable.GetBool() )
{
m_flWaterImpactTime = gpGlobals->curtime + tf_impactwatertime.GetFloat();
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFWeaponBase *CTFPlayer::GetActiveTFWeapon( void ) const
{
CBaseCombatWeapon *pRet = GetActiveWeapon();
if ( pRet )
{
Assert( dynamic_cast< CTFWeaponBase* >( pRet ) != NULL );
return static_cast< CTFWeaponBase * >( pRet );
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Return true if we are currently wielding a weapon that
// matches the given item def handle.
//-----------------------------------------------------------------------------
bool CTFPlayer::IsActiveTFWeapon( CEconItemDefinition *weaponHandle ) const
{
return ( GetActiveTFWeapon() &&
GetActiveTFWeapon()->GetAttributeContainer() &&
GetActiveTFWeapon()->GetAttributeContainer()->GetItem() &&
GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetItemDefinition() == weaponHandle );
}
//-----------------------------------------------------------------------------
// Purpose: Return true if we are currently wielding a weapon that
// matches the given item def handle.
bool CTFPlayer::IsActiveTFWeapon( const CSchemaItemDefHandle &weaponHandle ) const
{
return ( GetActiveTFWeapon() &&
GetActiveTFWeapon()->GetAttributeContainer() &&
GetActiveTFWeapon()->GetAttributeContainer()->GetItem() &&
GetActiveTFWeapon()->GetAttributeContainer()->GetItem()->GetItemDefinition() == weaponHandle );
}
//-----------------------------------------------------------------------------
// Purpose: How much build resource ( metal ) does this player have
//-----------------------------------------------------------------------------
int CTFPlayer::GetBuildResources( void )
{
return GetAmmoCount( TF_AMMO_METAL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
template < typename T >
class CScopedFlag
{
public:
CScopedFlag( T& ref_ )
: ref( ref_ )
{
Assert( !ref );
ref = true;
}
~CScopedFlag()
{
Assert( ref );
ref = false;
}
private:
T& ref;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayer::GetMovementForwardPull( void ) const
{
CTFWeaponBase *pWpn = GetActiveTFWeapon();
if ( pWpn && pWpn->IsFiring() )
{
float flFiringForwardPull = 0.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flFiringForwardPull, firing_forward_pull );
return flFiringForwardPull;
}
return 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanPlayerMove() const
{
// No one can move when in a final countdown transition.
if ( TFGameRules() && TFGameRules()->BInMatchStartCountdown() )
{ return false; }
bool bFreezeOnRestart = tf_player_movement_restart_freeze.GetBool();
if ( bFreezeOnRestart )
{
#if defined( _DEBUG ) || defined( STAGING_ONLY )
if ( mp_developer.GetBool() )
bFreezeOnRestart = false;
#endif // _DEBUG || STAGING_ONLY
if ( TFGameRules() && TFGameRules()->UsePlayerReadyStatusMode() && ( TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) )
bFreezeOnRestart = false;
}
bool bInRoundRestart = TFGameRules() && TFGameRules()->InRoundRestart();
if ( bInRoundRestart && TFGameRules()->IsCompetitiveMode() )
{
if ( TFGameRules()->GetRoundsPlayed() > 0 )
{
if ( gpGlobals->curtime < TFGameRules()->GetPreroundCountdownTime() )
{
bFreezeOnRestart = true;
}
}
else
{
bFreezeOnRestart = false;
}
}
bool bNoMovement = bInRoundRestart && bFreezeOnRestart;
return !bNoMovement;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayer::TeamFortress_CalculateMaxSpeed( bool bIgnoreSpecialAbility /*= false*/ ) const
{
if ( !GameRules() )
return 0.0f;
int playerclass = GetPlayerClass()->GetClassIndex();
// Spectators can move while in Classic Observer mode
if ( IsObserver() )
{
if ( GetObserverMode() == OBS_MODE_ROAMING )
return GetPlayerClassData( TF_CLASS_SCOUT )->m_flMaxSpeed;
return 0.0f;
}
// Check for any reason why they can't move at all
if ( playerclass == TF_CLASS_UNDEFINED || !CanPlayerMove() )
return 1.0f; // this can't return 0 because other parts of the code interpret that as "use default speed" during setup
// First, get their max class speed
float default_speed = GetPlayerClassData( playerclass )->m_flMaxSpeed;
// Avoid re-entering and calculating our velocity while we're calculating our velocity.
// This can happen if we have two characters trying to match each other's velocity, for
// example if you have two medics with Quick-Fixes healing each other.
//
// In the case where we run into this, we end the recursion with someone running default
// speed.
if ( m_bIsCalculatingMaximumSpeed )
return default_speed;
CScopedFlag<char> flagAvoidReentrancy( m_bIsCalculatingMaximumSpeed );
// Slow us down if we're disguised as a slower class
// unless we're cloaked..
float maxfbspeed = default_speed;
bool bAllowSlowing = m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ? false : true;
if ( m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) && !m_Shared.IsStealthed() )
{
maxfbspeed = 0.0f;
}
else if ( m_Shared.InCond( TF_COND_DISGUISED ) && !m_Shared.IsStealthed() )
{
float flMaxDisguiseSpeed = GetPlayerClassData( m_Shared.GetDisguiseClass() )->m_flMaxSpeed;
maxfbspeed = MIN( flMaxDisguiseSpeed, maxfbspeed );
}
if ( !TFGameRules()->IsMannVsMachineMode() || !IsMiniBoss() ) // No aiming slowdown penalties for MiniBoss players in MVM
{
// if they're a sniper, and they're aiming, their speed must be 80 or less
if ( m_Shared.InCond( TF_COND_AIMING ) )
{
float flAimMax = 0;
// Heavies are allowed to move slightly faster than a sniper when spun-up
if ( playerclass == TF_CLASS_HEAVYWEAPONS )
{
{
flAimMax = 110;
}
}
else
{
if ( GetActiveTFWeapon() && (GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_COMPOUND_BOW) )
{
flAimMax = 160;
}
else
{
flAimMax = 80;
}
}
CALL_ATTRIB_HOOK_FLOAT( flAimMax, mult_player_aiming_movespeed );
maxfbspeed = MIN( maxfbspeed, flAimMax );
}
}
#ifdef GAME_DLL
#ifdef STAGING_ONLY
if ( m_Shared.InCond( TF_COND_SPEED_BOOST ) || m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) )
#else
if ( m_Shared.InCond( TF_COND_SPEED_BOOST ) )
#endif
{
// We only allow our speed boost to apply if we have a base speed to work with. If we're supposed
// to be stationary for whatever reason we don't allow a speed to allow us to move.
if ( maxfbspeed > 0.0f )
{
maxfbspeed += MIN( maxfbspeed * 0.4f, tf_whip_speed_increase.GetFloat() );
}
}
#endif
if ( m_Shared.InCond( TF_COND_STEALTHED ) )
{
if (maxfbspeed > tf_spy_max_cloaked_speed.GetFloat() )
{
maxfbspeed = tf_spy_max_cloaked_speed.GetFloat();
}
}
// if we're in bonus time because a team has won, give the winners 110% speed and the losers 90% speed
if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
{
int iWinner = TFGameRules()->GetWinningTeam();
if ( iWinner != TEAM_UNASSIGNED )
{
if ( iWinner == GetTeamNumber() )
{
maxfbspeed *= 1.1f;
}
else
{
maxfbspeed *= 0.9f;
}
}
}
CTFWeaponBase* pWeapon = GetActiveTFWeapon();
if ( pWeapon )
{
maxfbspeed *= pWeapon->GetSpeedMod();
}
if ( playerclass == TF_CLASS_DEMOMAN )
{
CTFSword *pSword = dynamic_cast<CTFSword*>(Weapon_OwnsThisID( TF_WEAPON_SWORD ));
if ( pSword )
{
maxfbspeed *= pSword->GetSwordSpeedMod();
}
if ( !bIgnoreSpecialAbility && m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
{
maxfbspeed = tf_max_charge_speed.GetFloat();
}
}
bool bCarryPenalty = true;
if ( TFGameRules()->IsMannVsMachineMode() )
{
bCarryPenalty = false;
}
if ( m_Shared.IsCarryingObject() && bCarryPenalty && bAllowSlowing )
{
#ifdef STAGING_ONLY
CBaseObject* pObject = m_Shared.GetCarriedObject();
if ( pObject && pObject->GetType() == OBJ_TELEPORTER )
{
CALL_ATTRIB_HOOK_FLOAT( maxfbspeed, teleporter_carry_speed );
}
#endif // STAGING_ONLY
// STAGING_ENGY
maxfbspeed *= 0.90f;
}
if ( m_Shared.IsLoserStateStunned() && bAllowSlowing )
{
// Yikes is not as slow, terrible gotcha
if ( m_Shared.GetActiveStunInfo()->iStunFlags & TF_STUN_BY_TRIGGER )
{
maxfbspeed *= 0.75f;
}
else
{
maxfbspeed *= 0.5f;
}
}
// If we have an item with a move speed modification, apply it to the final speed.
CALL_ATTRIB_HOOK_FLOAT( maxfbspeed, mult_player_movespeed );
if ( m_Shared.IsShieldEquipped() )
{
CALL_ATTRIB_HOOK_FLOAT( maxfbspeed, mult_player_movespeed_shieldrequired );
}
if ( playerclass == TF_CLASS_MEDIC )
{
if ( pWeapon )
{
CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( pWeapon );
if ( pMedigun )
{
// Medics match faster classes when healing them
CTFPlayer *pHealTarget = ToTFPlayer( pMedigun->GetHealTarget() );
if ( pHealTarget )
{
// The Quick-Fix attaches to charging demos
bool bCharge = ( pMedigun->GetMedigunType() == MEDIGUN_QUICKFIX && pHealTarget->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) );
const float flHealTargetMaxSpeed = ( bCharge ) ? tf_max_charge_speed.GetFloat() : pHealTarget->TeamFortress_CalculateMaxSpeed( true );
maxfbspeed = Max( maxfbspeed, flHealTargetMaxSpeed );
}
}
}
// Special bone saw
int iTakeHeads = 0;
CALL_ATTRIB_HOOK_INT( iTakeHeads, add_head_on_hit );
if ( iTakeHeads )
{
CTFBonesaw *pSaw = dynamic_cast<CTFBonesaw*>(Weapon_OwnsThisID( TF_WEAPON_HARVESTER_SAW ));
if ( pSaw )
{
maxfbspeed *= pSaw->GetBoneSawSpeedMod();
}
}
}
float flClassResourceLevelMod = 1.f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flClassResourceLevelMod, mult_player_movespeed_resource_level );
if ( flClassResourceLevelMod != 1.f )
{
// Medic Uber
if ( playerclass == TF_CLASS_MEDIC )
{
CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
if ( pMedigun )
{
maxfbspeed *= RemapValClamped( pMedigun->GetChargeLevel(), 0.f, 1.f, 1.f, flClassResourceLevelMod );
}
}
}
// If we're a heavy with berzerker mode...
if ( playerclass == TF_CLASS_HEAVYWEAPONS )
{
float heavy_max_speed = default_speed * 1.35f;
if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) )
{
maxfbspeed *= 1.35f;
if ( maxfbspeed > heavy_max_speed )
{
// Prevent other speed modifiers like GRU from making berzerker mode too fast.
maxfbspeed = heavy_max_speed;
}
}
}
if ( playerclass == TF_CLASS_SCOUT )
{
if ( Weapon_OwnsThisID( TF_WEAPON_PEP_BRAWLER_BLASTER ) )
{
// Make this change based on attrs, hardcode right now
maxfbspeed *= RemapValClamped( m_Shared.GetScoutHypeMeter(), 0.0f, 100.0f, 1.0f, 1.45f );
}
// Crit-a-Cola gives a move bonus while active
if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) )
{
maxfbspeed *= 1.25f;
}
}
// Mann Vs Machine mode has a speed penalty for carrying the flag
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
{
if ( HasTheFlag() && !IsMiniBoss() )
{
maxfbspeed *= tf_mvm_bot_flag_carrier_movement_penalty.GetFloat();
}
}
}
#ifdef STAGING_ONLY
// Overloaded circuits!
if ( m_Shared.InCond( TF_COND_REPROGRAMMED ) )
{
maxfbspeed *= 2.f;
}
#endif // STAGING_ONLY
if ( m_Shared.GetCarryingRuneType() == RUNE_HASTE )
{
maxfbspeed *= 1.3f;
}
if ( m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
{
// light classes get more benefit due to movement speed cap of 520
switch ( GetPlayerClass()->GetClassIndex() )
{
case TF_CLASS_DEMOMAN:
case TF_CLASS_SOLDIER:
case TF_CLASS_HEAVYWEAPONS:
maxfbspeed *= 1.4f;
break;
default:
maxfbspeed *= 1.5f;
break;
}
}
return maxfbspeed;
}
void CTFPlayer::TeamFortress_SetSpeed()
{
#ifdef GAME_DLL
if ( TFGameRules() && TFGameRules()->IsPasstimeMode() && g_pPasstimeLogic )
{
float flPackSpeed = g_pPasstimeLogic->GetPackSpeed( this );
if ( flPackSpeed > 0 )
{
SetMaxSpeed( flPackSpeed );
return;
}
}
#endif
const float fMaxSpeed = TeamFortress_CalculateMaxSpeed();
// Set the speed
SetMaxSpeed( fMaxSpeed );
if ( fMaxSpeed <= 0.0f )
{
SetAbsVelocity( vec3_origin );
}
#ifdef GAME_DLL
// Anyone that's watching our speed should know that our speed changed so they can
// update their own speed.
//
// We guard against re-entrancy here as well to avoid the case where two medics are
// healing each other with Quick-Fixes.
//
// This can also happen when a quickfix medic is healing a player that gets a speed
// boost. And it doesn't work because the recursive call will just return the healed
// character's default speed instead of current speed.
// TODO fix this. why not just set the medic's speed directly at this point?
//
if ( !m_bIsCalculatingMaximumSpeed )
{
CScopedFlag<char> flagAvoidReentrancy( m_bIsCalculatingMaximumSpeed );
CUtlVector<CTFPlayer *> vecSpeedWatchers;
m_Shared.GetSpeedWatchersList( &vecSpeedWatchers );
FOR_EACH_VEC( vecSpeedWatchers, i )
{
vecSpeedWatchers[i]->TeamFortress_SetSpeed();
}
}
#endif // GAME_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::HasItem( void ) const
{
return ( m_hItem != NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::SetItem( CTFItem *pItem )
{
m_hItem = pItem;
#ifndef CLIENT_DLL
if ( pItem )
{
AddGlowEffect();
}
else
{
RemoveGlowEffect();
}
if ( pItem && pItem->GetItemID() == TF_ITEM_CAPTURE_FLAG )
{
RemoveInvisibility();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFItem *CTFPlayer::GetItem( void ) const
{
return m_hItem;
}
//-----------------------------------------------------------------------------
// Purpose: Is the player carrying the flag?
//-----------------------------------------------------------------------------
bool CTFPlayer::HasTheFlag( ETFFlagType exceptionTypes[], int nNumExceptions ) const
{
if ( HasItem() && GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG )
{
CCaptureFlag* pFlag = static_cast< CCaptureFlag* >( GetItem() );
for( int i=0; i < nNumExceptions; ++i )
{
if ( exceptionTypes[ i ] == pFlag->GetType() )
return false;
}
return true;
}
return false;
}
bool CTFPlayer::IsAllowedToPickUpFlag( void ) const
{
int iCannotPickUpIntelligence = 0;
CALL_ATTRIB_HOOK_INT( iCannotPickUpIntelligence, cannot_pick_up_intelligence );
if ( iCannotPickUpIntelligence )
{
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCaptureZone *CTFPlayer::GetCaptureZoneStandingOn( void )
{
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if ( root )
{
for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
{
CBaseEntity *pTouch = link->entityTouched;
if ( pTouch && pTouch->IsSolidFlagSet( FSOLID_TRIGGER ) && pTouch->IsBSPModel() )
{
CCaptureZone *pAreaTrigger = dynamic_cast< CCaptureZone* >(pTouch);
if ( pAreaTrigger )
{
return pAreaTrigger;
}
}
}
}
return NULL;
}
CCaptureZone *CTFPlayer::GetClosestCaptureZone( void )
{
CCaptureZone *pCaptureZone = NULL;
float flClosestDistance = FLT_MAX;
for ( int i=0; i<ICaptureZoneAutoList::AutoList().Count(); ++i )
{
CCaptureZone *pTempCaptureZone = static_cast< CCaptureZone* >( ICaptureZoneAutoList::AutoList()[i] );
if ( !pTempCaptureZone->IsDisabled() && pTempCaptureZone->GetTeamNumber() == GetTeamNumber() )
{
float fCurrentDistance = GetAbsOrigin().DistTo( pTempCaptureZone->WorldSpaceCenter() );
if ( flClosestDistance > fCurrentDistance )
{
pCaptureZone = pTempCaptureZone;
flClosestDistance = fCurrentDistance;
}
}
}
return pCaptureZone;
}
//-----------------------------------------------------------------------------
// Purpose: Return true if this player's allowed to build another one of the specified object
//-----------------------------------------------------------------------------
int CTFPlayer::CanBuild( int iObjectType, int iObjectMode )
{
if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
return CB_UNKNOWN_OBJECT;
const CObjectInfo *pInfo = GetObjectInfo( iObjectType );
if ( pInfo && ((iObjectMode > pInfo->m_iNumAltModes) || (iObjectMode < 0)) )
return CB_CANNOT_BUILD;
// Does this type require a specific builder?
bool bHasSubType = false;
CTFWeaponBuilder *pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, iObjectType );
if ( pBuilder )
{
bHasSubType = true;
}
if ( TFGameRules() )
{
if ( TFGameRules()->IsTruceActive() && ( iObjectType == OBJ_ATTACHMENT_SAPPER ) )
return CB_CANNOT_BUILD;
if ( TFGameRules()->IsMannVsMachineMode() )
{
// If a human is placing a sapper
if ( !IsBot() && iObjectType == OBJ_ATTACHMENT_SAPPER )
{
// Only allow one Sapper of any kind in MvM
if ( GetNumObjects( iObjectType, BUILDING_MODE_ANY ) )
return CB_LIMIT_REACHED;
return ( ( GetAmmoCount( TF_AMMO_GRENADES2 ) > 0 ) ? CB_CAN_BUILD : CB_CANNOT_BUILD );
}
}
}
#ifndef CLIENT_DLL
CTFPlayerClass *pCls = GetPlayerClass();
if ( !bHasSubType && pCls && pCls->CanBuildObject( iObjectType ) == false )
{
return CB_CANNOT_BUILD;
}
#endif
// We can redeploy the object if we are carrying it.
CBaseObject* pObjType = GetObjectOfType( iObjectType, iObjectMode );
if ( pObjType && pObjType->IsCarried() )
{
return CB_CAN_BUILD;
}
// Special handling of "disposable" sentries
if ( TFGameRules()->GameModeUsesUpgrades() && iObjectType == OBJ_SENTRYGUN )
{
// If we have our main sentry, see if we're allowed to build disposables
if ( GetNumObjects( iObjectType, iObjectMode ) )
{
bool bHasPrimary = false;
int nDisposableCount = 0;
int nMaxDisposableCount = 0;
CALL_ATTRIB_HOOK_INT( nMaxDisposableCount, engy_disposable_sentries );
if ( nMaxDisposableCount )
{
for ( int i = GetObjectCount()-1; i >= 0; i-- )
{
CBaseObject *pObj = GetObject( i );
if ( pObj )
{
if ( !pObj->IsDisposableBuilding() )
{
bHasPrimary = true;
}
else
{
nDisposableCount++;
}
}
}
if ( bHasPrimary )
{
if ( nDisposableCount < nMaxDisposableCount )
{
return CB_CAN_BUILD;
}
else
{
return CB_LIMIT_REACHED;
}
}
}
}
}
// Allow MVM engineer bots to have multiple sentries. Currently they only need this so
// they can appear to be carrying a new building when advancing their nest rather than
// transporting an existing building.
if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && IsBot() )
{
return CB_CAN_BUILD;
}
// Make sure we haven't hit maximum number
int iObjectCount = GetNumObjects( iObjectType, iObjectMode );
if ( iObjectCount >= GetObjectInfo( iObjectType )->m_nMaxObjects && GetObjectInfo( iObjectType )->m_nMaxObjects != -1)
{
return CB_LIMIT_REACHED;
}
// Find out how much the object should cost
int iCost = m_Shared.CalculateObjectCost( this, iObjectType );
// Make sure we have enough resources
if ( GetBuildResources() < iCost )
{
return CB_NEED_RESOURCES;
}
return CB_CAN_BUILD;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsAiming( void )
{
if ( !m_pOuter )
return false;
bool bAiming = InCond( TF_COND_AIMING ) && !m_pOuter->IsPlayerClass( TF_CLASS_SOLDIER );
if ( m_pOuter->IsPlayerClass( TF_CLASS_SNIPER ) && m_pOuter->GetActiveTFWeapon() && ( m_pOuter->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_SNIPERRIFLE_CLASSIC ) )
{
bAiming = InCond( TF_COND_ZOOMED );
}
return bAiming;
}
//-----------------------------------------------------------------------------
// Purpose: Set what type of rune we are carrying--or that we are not carrying any.
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetCarryingRuneType( RuneTypes_t rt )
{
#ifdef GAME_DLL
// Stat Tracking
if ( rt != RUNE_NONE )
{
// if getting a rune, start timer
m_flRuneAcquireTime = gpGlobals->curtime;
}
else if ( IsCarryingRune() )
{
// if setting to none (death or drop) and I have a power up, report and set timer to -1
float duration = gpGlobals->curtime - m_flRuneAcquireTime;
m_flRuneAcquireTime = -1;
CTF_GameStats.Event_PowerUpRuneDuration( m_pOuter, (int)duration, GetCarryingRuneType() );
}
// clear rune charge
SetRuneCharge( 0.f );
#endif
// Not 100% sure AddCond does what I want to do if we already have that cond, so
// let's assert so we can debug it if it ever comes up.
Assert( rt != GetCarryingRuneType() );
// We are only ever allowed to carry one rune type at a time, this logic ensures that.
for ( int i = 0; i < RUNE_TYPES_MAX; ++i )
{
if ( i == rt )
{
AddCond( GetConditionFromRuneType( (RuneTypes_t) i ) );
}
else
{
RemoveCond( GetConditionFromRuneType( (RuneTypes_t) i ) );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Return the currently carried rune type, or RUNE_NONE if we are not carrying one.
//-----------------------------------------------------------------------------
RuneTypes_t CTFPlayerShared::GetCarryingRuneType( void ) const
{
RuneTypes_t retVal = RUNE_NONE;
for ( int i = 0; i < RUNE_TYPES_MAX; ++i )
{
if ( InCond( GetConditionFromRuneType( (RuneTypes_t) i ) ) )
{
// You are only allowed to have one rune type, if this hits we somehow erroneously
// have two condition bits set for different types of runes.
Assert( retVal == RUNE_NONE );
retVal = (RuneTypes_t)i;
break;
}
}
return retVal;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::CalculateObjectCost( CTFPlayer* pBuilder, int iObjectType )
{
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
#ifdef STAGING_ONLY
if ( ( TFGameRules()->InSetup() || TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() ) && iObjectType != OBJ_SPY_TRAP )
#else
if ( TFGameRules()->InSetup() || TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() )
#endif
{
return 0;
}
}
int nCost = InternalCalculateObjectCost( iObjectType );
// Mini sentires are 30 metal cheaper
CTFWrench* pWrench = dynamic_cast<CTFWrench*>( pBuilder->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) );
if ( pWrench && pWrench->IsPDQ() && ( iObjectType == OBJ_SENTRYGUN ) )
{
nCost -= 30;
}
#ifdef STAGING_ONLY
// Mini dispensers are 30 metal cheaper
int nMiniDispenserEnabled = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, nMiniDispenserEnabled, allows_building_mini_dispenser );
if ( nMiniDispenserEnabled != 0 && iObjectType == OBJ_DISPENSER )
{
nCost -= 30;
}
// Speed Pads are cheaper
int nSpeedPad = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, nSpeedPad, teleporter_is_speedpad );
if ( nSpeedPad != 0 && iObjectType == OBJ_TELEPORTER )
{
nCost -= 25;
}
#endif
if ( iObjectType == OBJ_TELEPORTER )
{
float flCostMod = 1.f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flCostMod, mod_teleporter_cost );
if ( flCostMod != 1.f )
{
nCost *= flCostMod;
}
}
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, nCost, building_cost_reduction );
return nCost;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::HealthKitPickupEffects( int iHealthGiven /*= 0*/ )
{
// Healthkits also contain a fire blanket.
if ( InCond( TF_COND_BURNING ) )
{
RemoveCond( TF_COND_BURNING );
}
// and sutures
if ( InCond( TF_COND_BLEEDING ) )
{
RemoveCond( TF_COND_BLEEDING );
}
// and cures plague
if ( InCond( TF_COND_PLAGUE ) )
{
RemoveCond( TF_COND_PLAGUE );
}
// Spawns a number on the player's health bar in the HUD, and also
// spawns a "+" particle over their head for enemies to see
if ( iHealthGiven && !IsStealthed() && m_pOuter )
{
IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" );
if ( event )
{
event->SetInt( "amount", iHealthGiven );
event->SetInt( "entindex", m_pOuter->entindex() );
event->SetInt( "weapon_def_index", INVALID_ITEM_DEF_INDEX );
gameeventmanager->FireEvent( event );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Get the number of objects of the specified type that this player has
//-----------------------------------------------------------------------------
int CTFPlayer::GetNumObjects( int iObjectType, int iObjectMode /*= 0*/ )
{
int iCount = 0;
for (int i = 0; i < GetObjectCount(); i++)
{
if ( !GetObject(i) )
continue;
if ( GetObject(i)->IsDisposableBuilding() )
continue;
if ( GetObject(i)->GetType() == iObjectType &&
( GetObject(i)->GetObjectMode() == iObjectMode || iObjectMode == BUILDING_MODE_ANY ) )
{
iCount++;
}
}
return iCount;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::ItemPostFrame()
{
if ( m_hOffHandWeapon.Get() && m_hOffHandWeapon->IsWeaponVisible() )
{
if ( gpGlobals->curtime < m_flNextAttack )
{
m_hOffHandWeapon->ItemBusyFrame();
}
else
{
#if defined( CLIENT_DLL )
// Not predicting this weapon
if ( m_hOffHandWeapon->IsPredicted() )
#endif
{
m_hOffHandWeapon->ItemPostFrame( );
}
}
}
#ifdef GAME_DLL
CTFWeaponBase *pActiveWeapon = GetActiveTFWeapon();
if ( pActiveWeapon )
{
pActiveWeapon->HandleInspect();
}
#endif // GAME_DLL
BaseClass::ItemPostFrame();
}
void CTFPlayer::SetOffHandWeapon( CTFWeaponBase *pWeapon )
{
m_hOffHandWeapon = pWeapon;
if ( m_hOffHandWeapon.Get() )
{
m_hOffHandWeapon->Deploy();
}
}
// Set to NULL at the end of the holster?
void CTFPlayer::HolsterOffHandWeapon( void )
{
if ( m_hOffHandWeapon.Get() )
{
m_hOffHandWeapon->Holster();
}
}
//-----------------------------------------------------------------------------
// Purpose: Return true if we should record our last weapon when switching between the two specified weapons
//-----------------------------------------------------------------------------
bool CTFPlayer::Weapon_ShouldSetLast( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
{
// if the weapon doesn't want to be auto-switched to, don't!
CTFWeaponBase *pTFOldWeapon = dynamic_cast< CTFWeaponBase * >( pOldWeapon );
if ( pTFOldWeapon )
{
if ( pTFOldWeapon->AllowsAutoSwitchTo() == false )
{
return false;
}
}
return BaseClass::Weapon_ShouldSetLast( pOldWeapon, pNewWeapon );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::SelectItem( const char *pstr, int iSubType /*= 0*/ )
{
// This is basically a copy from the base class with addition of Weapon_CanSwitchTo
// We're not calling BaseClass::SelectItem on purpose to prevent breaking other games
// that might rely on not calling Weapon_CanSwitchTo
if (!pstr)
return;
CBaseCombatWeapon *pItem = Weapon_OwnsThisType( pstr, iSubType );
if (!pItem)
return;
if( GetObserverMode() != OBS_MODE_NONE )
return;// Observers can't select things.
if ( !Weapon_ShouldSelectItem( pItem ) )
return;
// FIX, this needs to queue them up and delay
// Make sure the current weapon can be holstered
if ( !Weapon_CanSwitchTo( pItem ) )
return;
ResetAutoaim();
Weapon_Switch( pItem );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex )
{
// Ghosts cant switch weapons!
if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
return false;
}
// set last weapon before we switch to a new weapon to make sure that we can get the correct last weapon in Deploy/Holster
// This should be done in CBasePlayer::Weapon_Switch, but we don't want to break other games
CBaseCombatWeapon *pPreviousLastWeapon = GetLastWeapon();
CBaseCombatWeapon *pPreviousActiveWeapon = GetActiveWeapon();
// always set last for Weapon_Switch code to get attribute from the correct last item
Weapon_SetLast( GetActiveWeapon() );
bool bSwitched = BaseClass::Weapon_Switch( pWeapon, viewmodelindex );
if ( bSwitched )
{
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_ATTACK_AND_RELOAD );
// valid last weapon
if ( Weapon_ShouldSetLast( pPreviousActiveWeapon, pWeapon ) )
{
Weapon_SetLast( pPreviousActiveWeapon );
SetSecondaryLastWeapon( pPreviousLastWeapon );
}
// previous active weapon is not valid to be last weapon, but the new active weapon is
else if ( Weapon_ShouldSetLast( pWeapon, pPreviousLastWeapon ) )
{
// this will skip the logic to ignore first time block and allow weapon to check for honorbound attribute right away
CTFWeaponBase *pTFWeapon = assert_cast< CTFWeaponBase * >( pWeapon );
if ( pTFWeapon && pTFWeapon->IsHonorBound() )
{
m_Shared.m_flFirstPrimaryAttack = gpGlobals->curtime;
}
if ( pWeapon != GetSecondaryLastWeapon() )
{
Weapon_SetLast( GetSecondaryLastWeapon() );
SetSecondaryLastWeapon( pPreviousLastWeapon );
}
else
{
// new active weapon is the same as the secondary last weapon, leave the last weapon alone
Weapon_SetLast( pPreviousLastWeapon );
}
}
// both previous and new active weapons are not not valid for last weapon
else
{
Weapon_SetLast( pPreviousLastWeapon );
}
}
else
{
// restore to the previous last weapon if we failed to switch to a new weapon
Weapon_SetLast( pPreviousLastWeapon );
}
#ifdef GAME_DLL
if ( bSwitched && TFGameRules() && TFGameRules()->IsInTraining() && TFGameRules()->GetTrainingModeLogic() && IsFakeClient() == false)
{
TFGameRules()->GetTrainingModeLogic()->OnPlayerSwitchedWeapons( this );
}
#endif
#ifdef CLIENT_DLL
m_Shared.UpdateCritBoostEffect();
#endif
return bSwitched;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFWearable *CTFPlayer::GetEquippedWearableForLoadoutSlot( int iLoadoutSlot )
{
int iClass = GetPlayerClass()->GetClassIndex();
for ( int i = 0; i < GetNumWearables(); ++i )
{
CTFWearable *pWearableItem = dynamic_cast< CTFWearable * >( GetWearable( i ) );
if ( !pWearableItem )
continue;
if ( !pWearableItem->GetAttributeContainer() )
continue;
CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem();
if ( !pEconItemView )
continue;
CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
if ( !pItemDef )
continue;
if ( pItemDef->GetLoadoutSlot(iClass) == iLoadoutSlot )
return pWearableItem;
}
return NULL;
}
//-----------------------------------------------------------------------------
CBaseEntity *CTFPlayer::GetEntityForLoadoutSlot( int iLoadoutSlot )
{
CBaseEntity *pEntity = NULL;
if ( IsWearableSlot( iLoadoutSlot ) )
{
// Search Wearables first otherwise search Weapons as a fall back
pEntity = GetEquippedWearableForLoadoutSlot( iLoadoutSlot );
if ( pEntity )
{
return pEntity;
}
}
int iClass = GetPlayerClass()->GetClassIndex();
for ( int i = 0; i < MAX_WEAPONS; i++ )
{
if ( GetWeapon(i) )
{
CEconItemView *pEconItemView = GetWeapon(i)->GetAttributeContainer()->GetItem();
if ( !pEconItemView )
continue;
CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
if ( !pItemDef )
continue;
if ( pItemDef->GetLoadoutSlot( iClass ) == iLoadoutSlot )
return GetWeapon(i);
}
}
return NULL;
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::StopViewModelParticles( C_BaseEntity *pParticleEnt )
{
pParticleEnt->ParticleProp()->StopParticlesInvolving( pParticleEnt );
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::GetStepSoundVelocities( float *velwalk, float *velrun )
{
float flMaxSpeed = MaxSpeed();
if ( ( GetFlags() & FL_DUCKING ) || ( GetMoveType() == MOVETYPE_LADDER ) )
{
if ( m_Shared.IsLoser() )
{
*velwalk = 0;
*velrun = 0;
}
else
{
*velwalk = flMaxSpeed * 0.25;
*velrun = flMaxSpeed * 0.3;
}
}
else
{
*velwalk = flMaxSpeed * 0.3;
*velrun = flMaxSpeed * 0.8;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::SetStepSoundTime( stepsoundtimes_t iStepSoundTime, bool bWalking )
{
float flMaxSpeed = MaxSpeed();
switch ( iStepSoundTime )
{
case STEPSOUNDTIME_NORMAL:
case STEPSOUNDTIME_WATER_FOOT:
m_flStepSoundTime = RemapValClamped( flMaxSpeed, 200, 450, 400, 200 );
if ( bWalking )
{
m_flStepSoundTime += 100;
}
break;
case STEPSOUNDTIME_ON_LADDER:
m_flStepSoundTime = 350;
break;
case STEPSOUNDTIME_WATER_KNEE:
m_flStepSoundTime = RemapValClamped( flMaxSpeed, 200, 450, 600, 400 );
break;
default:
Assert(0);
break;
}
if ( ( GetFlags() & FL_DUCKING) || ( GetMoveType() == MOVETYPE_LADDER ) )
{
m_flStepSoundTime += 100;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CTFPlayer::GetOverrideStepSound( const char *pszBaseStepSoundName )
{
if( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS && !IsMiniBoss() && !m_Shared.InCond( TF_COND_DISGUISED ) )
{
return "MVM.BotStep";
}
Assert( pszBaseStepSoundName );
struct override_sound_entry_t { int iOverrideIndex; const char *pszBaseSoundName; const char *pszNewSoundName; };
enum
{
kFootstepSoundSet_Default = 0,
kFootstepSoundSet_SoccerCleats = 1,
kFootstepSoundSet_HeavyGiant = 2,
kFootstepSoundSet_SoldierGiant = 3,
kFootstepSoundSet_DemoGiant = 4,
kFootstepSoundSet_ScoutGiant = 5,
kFootstepSoundSet_PyroGiant = 6,
kFootstepSoundSet_SentryBuster = 7,
kFootstepSoundSet_TreasureChest = 8,
kFootstepSoundSet_Octopus = 9,
};
int iOverrideFootstepSoundSet = kFootstepSoundSet_Default;
CALL_ATTRIB_HOOK_INT( iOverrideFootstepSoundSet, override_footstep_sound_set );
if ( iOverrideFootstepSoundSet != kFootstepSoundSet_Default )
{
static const override_sound_entry_t s_ReplacementSounds[] =
{
{ kFootstepSoundSet_SoccerCleats, "Default.StepLeft", "cleats_conc.StepLeft" },
{ kFootstepSoundSet_SoccerCleats, "Default.StepRight", "cleats_conc.StepRight" },
{ kFootstepSoundSet_SoccerCleats, "Dirt.StepLeft", "cleats_dirt.StepLeft" },
{ kFootstepSoundSet_SoccerCleats, "Dirt.StepRight", "cleats_dirt.StepRight" },
{ kFootstepSoundSet_SoccerCleats, "Concrete.StepLeft", "cleats_conc.StepLeft" },
{ kFootstepSoundSet_SoccerCleats, "Concrete.StepRight", "cleats_conc.StepRight" },
//
{ kFootstepSoundSet_Octopus, "Default.StepLeft", "Octopus.StepCommon" },
{ kFootstepSoundSet_Octopus, "Default.StepRight", "Octopus.StepCommon" },
{ kFootstepSoundSet_Octopus, "Dirt.StepLeft", "Octopus.StepCommon" },
{ kFootstepSoundSet_Octopus, "Dirt.StepRight", "Octopus.StepCommon" },
{ kFootstepSoundSet_Octopus, "Concrete.StepLeft", "Octopus.StepCommon" },
{ kFootstepSoundSet_Octopus, "Concrete.StepRight", "Octopus.StepCommon" },
//
{ kFootstepSoundSet_HeavyGiant, "", "MVM.GiantHeavyStep" },
//
{ kFootstepSoundSet_SoldierGiant, "", "MVM.GiantSoldierStep" },
//
{ kFootstepSoundSet_DemoGiant, "", "MVM.GiantDemomanStep" },
//
{ kFootstepSoundSet_ScoutGiant, "", "MVM.GiantScoutStep" },
//
{ kFootstepSoundSet_PyroGiant, "", "MVM.GiantPyroStep" },
//
{ kFootstepSoundSet_SentryBuster, "", "MVM.SentryBusterStep" },
//
{ kFootstepSoundSet_TreasureChest, "", "Chest.Step" },
};
for ( int i = 0; i < ARRAYSIZE( s_ReplacementSounds ); i++ )
{
if ( iOverrideFootstepSoundSet == s_ReplacementSounds[i].iOverrideIndex )
{
if ( !s_ReplacementSounds[i].pszBaseSoundName[0] ||
!Q_stricmp( pszBaseStepSoundName, s_ReplacementSounds[i].pszBaseSoundName ) )
return s_ReplacementSounds[i].pszNewSoundName;
}
}
}
// Fallback.
return BaseClass::GetOverrideStepSound( pszBaseStepSoundName );
}
void CTFPlayer::OnEmitFootstepSound( const CSoundParameters& params, const Vector& vecOrigin, float fVolume )
{
// play jingles in addition to normal footstep sounds,
// and play them quietly to the local player so they don't go insane
int iJingle = 0;
CALL_ATTRIB_HOOK_INT( iJingle, add_jingle_to_footsteps );
if ( iJingle > 0 )
{
CRecipientFilter filter;
filter.AddRecipientsByPAS( vecOrigin );
#ifndef CLIENT_DLL
// in MP, server removes all players in the vecOrigin's PVS, these players generate the footsteps client side
if ( gpGlobals->maxClients > 1 )
{
filter.RemoveRecipientsByPVS( vecOrigin );
}
#endif
EmitSound_t ep;
ep.m_nChannel = CHAN_BODY;
ep.m_pSoundName = ( iJingle == 1 ) ? "xmas.jingle" : "xmas.jingle_higher";
#ifdef CLIENT_DLL
ep.m_flVolume = IsLocalPlayer() ? 0.3f * fVolume : fVolume; // quieter for local player
#else
ep.m_flVolume = fVolume;
#endif
ep.m_SoundLevel = params.soundlevel;
ep.m_nFlags = SND_CHANGE_VOL;
ep.m_nPitch = params.pitch;
ep.m_pOrigin = &vecOrigin;
EmitSound( filter, entindex(), ep );
}
#ifdef CLIENT_DLL
// Halloween-specific bonus footsteps for viewmodel-rendering only. Real model footsteps will happen in the real
// footstep code in response to animation events. THIS IS A HACK!
if ( !ShouldDrawThisPlayer() && !m_Shared.IsStealthed() && !m_Shared.InCond( TF_COND_DISGUISED ) )
{
int iHalloweenFootstepType = 0;
if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
{
CALL_ATTRIB_HOOK_INT( iHalloweenFootstepType, halloween_footstep_type );
}
if ( m_nFootStamps > 0 )
{
// White color!
iHalloweenFootstepType = 0xFFFFFFFF;
}
if ( iHalloweenFootstepType != 0 )
{
CNewParticleEffect *pEffect = SpawnHalloweenSpellFootsteps( PATTACH_CUSTOMORIGIN, iHalloweenFootstepType );
if ( pEffect )
{
pEffect->SetControlPoint( 0, GetAbsOrigin() );
}
}
if ( m_nFootStamps > 0 )
{
m_nFootStamps--;
}
}
#endif
}
void CTFPlayer::ModifyEmitSoundParams( EmitSound_t &params )
{
BaseClass::ModifyEmitSoundParams( params );
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->ModifyEmitSoundParams( params );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanAttack( int iCanAttackFlags )
{
CTFGameRules *pRules = TFGameRules();
Assert( pRules );
if ( m_Shared.HasPasstimeBall() )
{
// Always allow throwing the ball.
return true;
}
if ( ( m_Shared.GetStealthNoAttackExpireTime() > gpGlobals->curtime && !m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF ) ) || m_Shared.InCond( TF_COND_STEALTHED ) )
{
if ( !( iCanAttackFlags & TF_CAN_ATTACK_FLAG_GRAPPLINGHOOK ) )
{
#ifdef CLIENT_DLL
HintMessage( HINT_CANNOT_ATTACK_WHILE_CLOAKED, true, true );
#endif
return false;
}
}
if ( m_Shared.IsFeignDeathReady() )
{
#ifdef CLIENT_DLL
HintMessage( HINT_CANNOT_ATTACK_WHILE_FEIGN_ARMED, true, true );
#endif
return false;
}
if ( IsTaunting() )
return false;
if ( m_Shared.InCond( TF_COND_PHASE ) == true )
return false;
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
{
return false;
}
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Weapons can call this on secondary attack and it will link to the class
// ability
//-----------------------------------------------------------------------------
bool CTFPlayer::DoClassSpecialSkill( void )
{
if ( !IsAlive() )
return false;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return false;
bool bDoSkill = false;
// powerup charge activation has higher priority than any class special skill
if ( m_Shared.IsRuneCharged() && GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRAPPLINGHOOK )
{
#ifdef GAME_DLL
CTFGrapplingHook *pHook = static_cast<CTFGrapplingHook*>( GetActiveTFWeapon() );
if ( pHook )
{
pHook->ActivateRune();
}
#endif // GAME_DLL
return true;
}
switch( GetPlayerClass()->GetClassIndex() )
{
case TF_CLASS_SPY:
{
if ( !m_Shared.InCond( TF_COND_TAUNTING ) )
{
if ( m_Shared.m_flStealthNextChangeTime <= gpGlobals->curtime )
{
// Feign death if we have the right equipment mod.
CTFWeaponInvis* pInvisWatch = static_cast<CTFWeaponInvis*>( Weapon_OwnsThisID( TF_WEAPON_INVIS ) );
if ( pInvisWatch )
{
pInvisWatch->ActivateInvisibilityWatch();
}
}
}
}
break;
case TF_CLASS_DEMOMAN:
if ( !m_Shared.HasPasstimeBall() )
{
CTFPipebombLauncher *pPipebombLauncher = static_cast<CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
if ( pPipebombLauncher )
{
pPipebombLauncher->SecondaryAttack();
}
else
{
CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( this );
if ( pWearableShield )
{
pWearableShield->DoSpecialAction( this );
break;
}
}
}
bDoSkill = true;
break;
case TF_CLASS_ENGINEER:
if ( !m_Shared.HasPasstimeBall() )
{
bDoSkill = TryToPickupBuilding();
}
break;
default:
break;
}
return bDoSkill;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::EndClassSpecialSkill( void )
{
if ( !IsAlive() )
return false;
switch( GetPlayerClass()->GetClassIndex() )
{
case TF_CLASS_DEMOMAN:
{
CTFPipebombLauncher *pPipebombLauncher = static_cast<CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
if ( !pPipebombLauncher )
{
CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( this );
if ( pWearableShield )
{
pWearableShield->EndSpecialAction( this );
break;
}
}
}
break;
default:
break;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanPickupBuilding( CBaseObject *pPickupObject )
{
if ( !pPickupObject )
return false;
if ( pPickupObject->IsBuilding() )
return false;
if ( pPickupObject->IsUpgrading() )
return false;
if ( pPickupObject->HasSapper() )
return false;
if ( pPickupObject->IsPlasmaDisabled() )
return false;
// If we were recently carried & placed we may still be upgrading up to our old level.
if ( pPickupObject->GetUpgradeLevel() != pPickupObject->GetHighestUpgradeLevel() )
return false;
if ( m_Shared.IsCarryingObject() )
return false;
if ( m_Shared.IsLoserStateStunned() || m_Shared.IsControlStunned() )
return false;
if ( m_Shared.IsLoser() )
return false;
if ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING && TFGameRules()->State_Get() != GR_STATE_STALEMATE && TFGameRules()->State_Get() != GR_STATE_BETWEEN_RNDS )
return false;
// don't allow to pick up building while grappling hook
if ( m_Shared.InCond( TF_COND_GRAPPLINGHOOK ) )
return false;
// There's ammo in the clip... no switching away!
if ( GetActiveTFWeapon() && GetActiveTFWeapon()->AutoFiresFullClip() && GetActiveTFWeapon()->Clip1() > 0 )
return false;
// Knockout powerup restricts user to melee only, so cannot equip other items such as building pickups
if ( m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
// ClientPrint( this, HUD_PRINTCENTER, "#TF_Powerup_Pickup_Deny" );
{
ClientPrint( this, HUD_PRINTCENTER, "You can't pickup buildings while holding the KNOCKOUT powerup" );
return false;
}
// Check it's within range
int nPickUpRangeSq = TF_BUILDING_PICKUP_RANGE * TF_BUILDING_PICKUP_RANGE;
int iIncreasedRangeCost = 0;
int nSqrDist = (EyePosition() - pPickupObject->GetAbsOrigin()).LengthSqr();
// Extra range only works with primary weapon
CTFWeaponBase * pWeapon = GetActiveTFWeapon();
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iIncreasedRangeCost, building_teleporting_pickup );
if ( iIncreasedRangeCost != 0 )
{
// False on deadzone
if ( nSqrDist > nPickUpRangeSq && nSqrDist < TF_BUILDING_RESCUE_MIN_RANGE_SQ )
return false;
if ( nSqrDist >= TF_BUILDING_RESCUE_MIN_RANGE_SQ && GetAmmoCount( TF_AMMO_METAL ) < iIncreasedRangeCost )
return false;
return true;
}
else if ( nSqrDist > nPickUpRangeSq )
return false;
if ( TFGameRules()->IsInTraining() )
{
ConVarRef training_can_pickup_sentry( "training_can_pickup_sentry" );
ConVarRef training_can_pickup_dispenser( "training_can_pickup_dispenser" );
ConVarRef training_can_pickup_tele_entrance( "training_can_pickup_tele_entrance" );
ConVarRef training_can_pickup_tele_exit( "training_can_pickup_tele_exit" );
switch ( pPickupObject->GetType() )
{
case OBJ_DISPENSER:
return training_can_pickup_dispenser.GetBool();
case OBJ_TELEPORTER:
return pPickupObject->GetObjectMode() == MODE_TELEPORTER_ENTRANCE ? training_can_pickup_tele_entrance.GetBool() : training_can_pickup_tele_exit.GetBool();
case OBJ_SENTRYGUN:
return training_can_pickup_sentry.GetBool();
} // switch
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::TryToPickupBuilding()
{
if ( m_Shared.IsCarryingObject() )
return false;
if ( m_Shared.InCond( TF_COND_TAUNTING ) )
return false;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) )
return false;
if ( m_Shared.InCond( TF_COND_MELEE_ONLY ) )
return false;
if ( m_Shared.InCond( TF_COND_SWIMMING_CURSE ) )
return false;
#ifdef GAME_DLL
if ( m_bIsTeleportingUsingEurekaEffect )
return false;
int iCannotPickUpBuildings = 0;
CALL_ATTRIB_HOOK_INT( iCannotPickUpBuildings, cannot_pick_up_buildings );
if ( iCannotPickUpBuildings )
{
return false;
}
#endif
// Check to see if a building we own is in front of us.
Vector vecForward;
AngleVectors( EyeAngles(), &vecForward, NULL, NULL );
int iPickUpRange = TF_BUILDING_PICKUP_RANGE;
int iIncreasedRangeCost = 0;
CTFWeaponBase * pWeapon = GetActiveTFWeapon();
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iIncreasedRangeCost, building_teleporting_pickup );
if ( iIncreasedRangeCost != 0 )
{
iPickUpRange = TF_BUILDING_RESCUE_MAX_RANGE;
}
// Create a ray a see if any of my objects touch it
Ray_t ray;
ray.Init( EyePosition(), EyePosition() + vecForward * iPickUpRange );
CBulletPenetrateEnum ePickupPenetrate( &ray, this, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS, false );
enginetrace->EnumerateEntities( ray, false, &ePickupPenetrate );
CBaseObject *pPickupObject = NULL;
float flCurrDistanceSq = iPickUpRange * iPickUpRange;
for ( int i=0; i<GetObjectCount(); i++ )
{
CBaseObject *pObj = GetObject(i);
if ( !pObj )
continue;
float flDistToObjSq = ( pObj->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
if ( flDistToObjSq > flCurrDistanceSq )
continue;
FOR_EACH_VEC( ePickupPenetrate.m_Targets, iTarget )
{
if ( ePickupPenetrate.m_Targets[iTarget].pTarget == pObj )
{
CTargetOnlyFilter penetrateFilter( this, pObj );
trace_t pTraceToUse;
UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * iPickUpRange, ( MASK_SOLID | CONTENTS_HITBOX ), &penetrateFilter, &pTraceToUse );
if ( pTraceToUse.m_pEnt == pObj )
{
pPickupObject = pObj;
flCurrDistanceSq = flDistToObjSq;
break;
}
}
if ( ePickupPenetrate.m_Targets[iTarget].pTarget->IsWorld() )
{
break;
}
}
}
if ( !CanPickupBuilding(pPickupObject) )
{
if ( pPickupObject )
{
CSingleUserRecipientFilter filter( this );
EmitSound( filter, entindex(), "Player.UseDeny", NULL, 0.0f );
}
return false;
}
#ifdef CLIENT_DLL
return (bool) pPickupObject;
#elif GAME_DLL
if ( pPickupObject )
{
// remove rage for long range
if ( iIncreasedRangeCost )
{
int nSqrDist = (EyePosition() - pPickupObject->GetAbsOrigin()).LengthSqr();
if ( nSqrDist > TF_BUILDING_RESCUE_MIN_RANGE_SQ )
{
RemoveAmmo( iIncreasedRangeCost, TF_AMMO_METAL );
// Particles
// Spawn a railgun
Vector origin = pPickupObject->GetAbsOrigin();
CPVSFilter filter( origin );
const char *pRailParticleName = GetTeamNumber() == TF_TEAM_BLUE ? "dxhr_sniper_rail_blue" : "dxhr_sniper_rail_red";
const char *pTeleParticleName = GetTeamNumber() == TF_TEAM_BLUE ? "teleported_blue" : "teleported_red";
TE_TFParticleEffect( filter, 0.0, pTeleParticleName, origin, vec3_angle );
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, pPickupObject->GetAbsOrigin() + Vector(0,0,32) };
TE_TFParticleEffectComplex( filter, 0.0f, pRailParticleName, GetAbsOrigin() + Vector(0,0,32), QAngle( 0, 0, 0 ), NULL, &controlPoint );
// Play Sounds
pPickupObject->EmitSound( "Building_Teleporter.Send" );
EmitSound( "Building_Teleporter.Receive" );
}
}
pPickupObject->MakeCarriedObject( this );
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>(Weapon_OwnsThisID( TF_WEAPON_BUILDER ));
if ( pBuilder )
{
if ( GetActiveTFWeapon() == pBuilder )
SetActiveWeapon( NULL );
Weapon_Switch( pBuilder );
pBuilder->m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f;
}
SpeakConceptIfAllowed( MP_CONCEPT_PICKUP_BUILDING, pPickupObject->GetResponseRulesModifier() );
m_flCommentOnCarrying = gpGlobals->curtime + random->RandomFloat( 6.f, 12.f );
return true;
}
else
{
return false;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanGoInvisible( bool bAllowWhileCarryingFlag )
{
// The "flag" in Player Destruction doesn't block cloak
ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION };
if ( !bAllowWhileCarryingFlag && ( HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) || m_Shared.HasPasstimeBall() ) )
{
HintMessage( HINT_CANNOT_CLOAK_WITH_FLAG );
return false;
}
CTFGameRules *pRules = TFGameRules();
Assert( pRules );
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
{
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanStartPhase( void )
{
if ( HasTheFlag() || m_Shared.HasPasstimeBall() )
{
HintMessage( HINT_CANNOT_PHASE_WITH_FLAG );
return false;
}
CTFGameRules *pRules = TFGameRules();
Assert( pRules );
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
{
return false;
}
return true;
}
//ConVar testclassviewheight( "testclassviewheight", "0", FCVAR_DEVELOPMENTONLY );
//Vector vecTestViewHeight(0,0,0);
//-----------------------------------------------------------------------------
// Purpose: Return class-specific standing eye height
//-----------------------------------------------------------------------------
Vector CTFPlayer::GetClassEyeHeight( void )
{
CTFPlayerClass *pClass = GetPlayerClass();
if ( !pClass )
return VEC_VIEW_SCALED( this );
//if ( testclassviewheight.GetFloat() > 0 )
//{
// vecTestViewHeight.z = test.GetFloat();
// return vecTestViewHeight;
//}
int iClassIndex = pClass->GetClassIndex();
if ( iClassIndex < TF_FIRST_NORMAL_CLASS || iClassIndex > TF_LAST_NORMAL_CLASS )
return VEC_VIEW_SCALED( this );
return g_TFClassViewVectors[pClass->GetClassIndex()] * GetModelScale();
}
CTFWeaponBase *CTFPlayer::Weapon_OwnsThisID( int iWeaponID ) const
{
for (int i = 0;i < WeaponCount(); i++)
{
CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon( i );
if ( pWpn == NULL )
continue;
if ( pWpn->GetWeaponID() == iWeaponID )
{
return pWpn;
}
}
return NULL;
}
CTFWeaponBase *CTFPlayer::Weapon_GetWeaponByType( int iType )
{
for (int i = 0;i < WeaponCount(); i++)
{
CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon( i );
if ( pWpn == NULL )
continue;
int iWeaponRole = pWpn->GetTFWpnData().m_iWeaponType;
if ( iWeaponRole == iType )
{
return pWpn;
}
}
return NULL;
}
bool CTFPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
{
bool bCanSwitch = BaseClass::Weapon_CanSwitchTo( pWeapon );
if ( bCanSwitch )
{
if ( GetActiveTFWeapon() )
{
// There's ammo in the clip while auto firing... no switching away!
if ( GetActiveTFWeapon()->AutoFiresFullClip() && GetActiveTFWeapon()->Clip1() > 0 )
return false;
}
if ( m_Shared.IsCarryingObject() && (GetPlayerClass()->GetClassIndex() == TF_CLASS_ENGINEER) )
{
CTFWeaponBase *pTFWeapon = dynamic_cast<CTFWeaponBase*>( pWeapon );
if ( pTFWeapon && (pTFWeapon->GetWeaponID() != TF_WEAPON_BUILDER) )
{
return false;
}
}
// prevents script exploits, like switching to the minigun while eating a sandvich
if ( IsTaunting() && tf_allow_taunt_switch.GetInt() == 0 )
{
return false;
}
int iDisableWeaponSwitch = 0;
CALL_ATTRIB_HOOK_INT( iDisableWeaponSwitch, disable_weapon_switch );
if ( iDisableWeaponSwitch != 0 )
return false;
}
return bCanSwitch;
}
//-----------------------------------------------------------------------------
// Purpose: Gives the player an opportunity to abort a double jump.
//-----------------------------------------------------------------------------
bool CTFPlayer::CanAirDash( void ) const
{
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return false;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_SPEED_BOOST ) )
return true;
bool bScout = GetPlayerClass()->IsClass( TF_CLASS_SCOUT );
if ( !bScout )
return false;
if ( m_Shared.InCond( TF_COND_SODAPOPPER_HYPE ) )
{
if ( m_Shared.GetAirDash() < 5 )
return true;
else
return false;
}
int iDashCount = tf_scout_air_dash_count.GetInt();
CALL_ATTRIB_HOOK_INT( iDashCount, air_dash_count );
if ( m_Shared.GetAirDash() >= iDashCount )
return false;
int iNoAirDash = 0;
CALL_ATTRIB_HOOK_INT( iNoAirDash, set_scout_doublejump_disabled );
if ( 1 == iNoAirDash )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Should be immune to Jarate and Mad Milk?
//-----------------------------------------------------------------------------
bool CTFPlayer::CanGetWet( void ) const
{
int iWetImmune = 0;
CALL_ATTRIB_HOOK_INT( iWetImmune, wet_immunity );
return iWetImmune ? false : true;
}
//-----------------------------------------------------------------------------
// Purpose: Remove disguise
//-----------------------------------------------------------------------------
void CTFPlayer::RemoveDisguise( void )
{
// remove quickly
if ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) )
{
m_Shared.RemoveDisguise();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::RemoveDisguiseWeapon( void )
{
#ifdef GAME_DLL
if ( m_hDisguiseWeapon )
{
m_hDisguiseWeapon->Drop( Vector(0,0,0) );
m_hDisguiseWeapon = NULL;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanDisguise( void )
{
if ( !IsAlive() )
return false;
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY )
return false;
bool bHasFlag = false;
ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION };
if ( HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) )
{
bHasFlag = true;
}
if ( bHasFlag || m_Shared.HasPasstimeBall() )
{
HintMessage( HINT_CANNOT_DISGUISE_WITH_FLAG );
return false;
}
if ( !Weapon_GetWeaponByType( TF_WPN_TYPE_PDA ) )
return false;
int iCannotDisguise = 0;
CALL_ATTRIB_HOOK_INT( iCannotDisguise, set_cannot_disguise );
if ( iCannotDisguise == 1 )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: 'Your Eternal Reward' handling for Disguise testing
//-----------------------------------------------------------------------------
bool CTFPlayer::CanDisguise_OnKill( void )
{
if ( !IsAlive() )
return false;
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY )
return false;
CTFKnife *pKnife = dynamic_cast<CTFKnife *>( Weapon_GetWeaponByType( TF_WPN_TYPE_MELEE ) );
if ( pKnife && pKnife->GetKnifeType() != KNIFE_DISGUISE_ONKILL )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayer::GetMaxAmmo( int iAmmoIndex, int iClassIndex /*= -1*/ )
{
int iMax = ( iClassIndex == -1 ) ? m_PlayerClass.GetData()->m_aAmmoMax[iAmmoIndex] : GetPlayerClassData( iClassIndex )->m_aAmmoMax[iAmmoIndex];
if ( iAmmoIndex == TF_AMMO_PRIMARY )
{
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_primary );
}
else if ( iAmmoIndex == TF_AMMO_SECONDARY )
{
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_secondary );
}
else if ( iAmmoIndex == TF_AMMO_METAL )
{
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_metal );
}
else if ( iAmmoIndex == TF_AMMO_GRENADES1 )
{
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_grenades1 );
}
else if ( iAmmoIndex == TF_AMMO_GRENADES3 )
{
// All classes by default can carry a max of 1 "Grenade3" which is being used as ACTIONSLOT Throwables
iMax = 1;
}
// Haste Powerup Rune adds multiplier to Max Ammo
if ( m_Shared.GetCarryingRuneType() == RUNE_HASTE )
{
iMax *= 2.0f;
}
return iMax;
}
bool CTFPlayer::IsMiniBoss( void ) const
{
return m_bIsMiniBoss;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *CTFPlayer::MedicGetHealTarget( void )
{
if ( IsPlayerClass(TF_CLASS_MEDIC) )
{
CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( GetActiveWeapon() );
if ( pWeapon )
return pWeapon->GetHealTarget();
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CTFPlayer::MedicGetChargeLevel( CTFWeaponBase **pRetMedigun )
{
if ( IsPlayerClass(TF_CLASS_MEDIC) )
{
CTFWeaponBase *pWpn = ( CTFWeaponBase *)Weapon_OwnsThisID( TF_WEAPON_MEDIGUN );
if ( pWpn == NULL )
return 0;
CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pWpn );
if ( pRetMedigun )
{
*pRetMedigun = pMedigun;
}
if ( pMedigun )
return pMedigun->GetChargeLevel();
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayer::GetNumActivePipebombs( void )
{
if ( IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
CTFPipebombLauncher *pWeapon = dynamic_cast < CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
if ( pWeapon )
{
return pWeapon->GetPipeBombCount();
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Fills out the vector with the sets that are currently active on this player
//-----------------------------------------------------------------------------
void CTFPlayer::GetActiveSets( CUtlVector<const CEconItemSetDefinition *> *pItemSets )
{
pItemSets->Purge();
CSteamID steamIDForPlayer;
GetSteamID( &steamIDForPlayer );
TFInventoryManager()->GetActiveSets( pItemSets, steamIDForPlayer, GetPlayerClass()->GetClassIndex() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::CanMoveDuringTaunt()
{
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
{
if ( ( TFGameRules()->GetRoundRestartTime() > -1.f ) && ( (int)( TFGameRules()->GetRoundRestartTime() - gpGlobals->curtime ) <= mp_tournament_readymode_countdown.GetInt() ) )
return false;
if ( TFGameRules()->PlayersAreOnMatchSummaryStage() )
return false;
}
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return true;
if ( m_Shared.InCond( TF_COND_TAUNTING ) || m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) )
{
#ifdef GAME_DLL
if ( tf_allow_sliding_taunt.GetBool() )
{
return true;
}
#endif // GAME_DLL
#ifdef STAGING_ONLY
if ( tf_force_allow_move_during_taunt.GetBool() )
{
return true;
}
#endif // STAGING_ONLY
if ( m_bAllowMoveDuringTaunt )
{
return true;
}
if ( IsReadyToTauntWithPartner() || CTFPlayerSharedUtils::ConceptIsPartnerTaunt( m_Shared.m_iTauntConcept ) )
{
return false;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayer::ShouldStopTaunting()
{
// stop taunt if we're under water
return GetWaterLevel() > WL_Waist;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#if defined( CLIENT_DLL ) && defined( STAGING_ONLY )
static ConVar tf_tauntcam_dist_override( "tf_tauntcam_dist_override", "0", FCVAR_CHEAT );
static ConVar tf_tauntcam_distup_override( "tf_tauntcam_distup_override", "0", FCVAR_CHEAT );
#endif // CLIENT_DLL && STAGING_ONLY
void CTFPlayer::ParseSharedTauntDataFromEconItemView( CEconItemView *pEconItemView )
{
static CSchemaAttributeDefHandle pAttrDef_TauntForceMoveForward( "taunt force move forward" );
attrib_value_t attrTauntForceMoveForward = 0;
pEconItemView->FindAttribute( pAttrDef_TauntForceMoveForward, &attrTauntForceMoveForward );
m_bTauntForceMoveForward = attrTauntForceMoveForward != 0;
static CSchemaAttributeDefHandle pAttrDef_TauntMoveSpeed( "taunt move speed" );
attrib_value_t attrTauntMoveSpeed = 0;
pEconItemView->FindAttribute( pAttrDef_TauntMoveSpeed, &attrTauntMoveSpeed );
m_flTauntForceMoveForwardSpeed = (float&)attrTauntMoveSpeed;
static CSchemaAttributeDefHandle pAttrDef_TauntMoveAccelerationTime( "taunt move acceleration time" );
attrib_value_t attrTauntMoveAccelerationTime = 0;
pEconItemView->FindAttribute( pAttrDef_TauntMoveAccelerationTime, &attrTauntMoveAccelerationTime );
m_flTauntMoveAccelerationTime = (float&)attrTauntMoveAccelerationTime;
static CSchemaAttributeDefHandle pAttrDef_TauntTurnSpeed( "taunt turn speed" );
attrib_value_t attrTauntTurnSpeed = 0;
pEconItemView->FindAttribute( pAttrDef_TauntTurnSpeed, &attrTauntTurnSpeed );
m_flTauntTurnSpeed = (float&)attrTauntTurnSpeed;
static CSchemaAttributeDefHandle pAttrDef_TauntTurnAccelerationTime( "taunt turn acceleration time" );
attrib_value_t attrTauntTurnAccelerationTime = 0;
pEconItemView->FindAttribute( pAttrDef_TauntTurnAccelerationTime, &attrTauntTurnAccelerationTime );
m_flTauntTurnAccelerationTime = (float&)attrTauntTurnAccelerationTime;
#ifdef CLIENT_DLL
CTFTauntInfo *pTauntInfo = pEconItemView->GetStaticData()->GetTauntData();
if ( pTauntInfo )
{
if ( pTauntInfo->GetCameraDist() != 0 )
m_flTauntCamTargetDist = pTauntInfo->GetCameraDist();
if ( pTauntInfo->GetCameraDistUp() != 0 )
m_flTauntCamTargetDistUp = pTauntInfo->GetCameraDistUp();
#ifdef STAGING_ONLY
if ( tf_tauntcam_dist_override.GetFloat() != 0 )
m_flTauntCamTargetDist = tf_tauntcam_dist_override.GetFloat();
if ( tf_tauntcam_distup_override.GetFloat() != 0 )
m_flTauntCamTargetDistUp = tf_tauntcam_distup_override.GetFloat();
#endif // STAGING_ONLY
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::SetTauntYaw( float flTauntYaw )
{
m_flPrevTauntYaw = m_flTauntYaw;
m_flTauntYaw = flTauntYaw;
QAngle angle = GetLocalAngles();
angle.y = flTauntYaw;
SetLocalAngles( angle );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::StartBuildingObjectOfType( int iType, int iMode )
{
// early out if we can't build this type of object
if ( CanBuild( iType, iMode ) != CB_CAN_BUILD )
return;
// Does this type require a specific builder?
CTFWeaponBuilder *pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, iType );
if ( pBuilder )
{
#ifdef GAME_DLL
pBuilder->SetSubType( iType );
pBuilder->SetObjectMode( iMode );
if ( GetActiveTFWeapon() == pBuilder )
{
SetActiveWeapon( NULL );
}
#endif
// try to switch to this weapon
Weapon_Switch( pBuilder );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdatePhaseEffects( void )
{
bool bRunning;
float flSpeed = m_pOuter->MaxSpeed();
flSpeed *= flSpeed;
CTFPlayer *pPlayer = ToTFPlayer( m_pOuter );
if ( InCond( TF_COND_SHIELD_CHARGE ) || (pPlayer->GetAbsVelocity().LengthSqr() >= (flSpeed* 0.1f)) )
{
bRunning = true;
}
else
{
bRunning = false;
}
#ifdef CLIENT_DLL
if ( m_pOuter )
{
if ( !bRunning && !m_pOuter->m_pPhaseStandingEffect && m_flEnergyDrinkMeter < 100.0f )
{
m_pOuter->m_pPhaseStandingEffect = m_pOuter->ParticleProp()->Create( "warp_version", PATTACH_ABSORIGIN_FOLLOW );
}
else if ( bRunning && m_pOuter->m_pPhaseStandingEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect );
m_pOuter->m_pPhaseStandingEffect = NULL;
}
}
#else
// #ifdef STAGING_ONLY
// if ( !InCond( TF_COND_PHASE ) && !InCond( TF_COND_SHIELD_CHARGE ) && !InCond( TF_COND_SELF_CONC ) )
// #else
if ( !InCond( TF_COND_PHASE ) && !InCond( TF_COND_SHIELD_CHARGE ) )
//#endif // STAGING_ONLY
return;
if ( bRunning )
{
if ( !m_bPhaseFXOn )
{
AddPhaseEffects();
}
else
{
if ( m_flEnergyDrinkMeter <= 10.0f )
{
float fAlpha = ( m_flEnergyDrinkMeter / 10 ) * 255;
for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i )
{
CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pPhaseTrail[i].Get() );
if ( pTempTrail )
{
pTempTrail->SetBrightness( int(fAlpha) );
}
}
}
// #ifdef STAGING_ONLY
// else if ( InCond( TF_COND_SHIELD_CHARGE ) || InCond( TF_COND_SELF_CONC ) )
// #else
else if ( InCond( TF_COND_SHIELD_CHARGE ) )
//#endif // STAGING_ONLY
{
for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i )
{
CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pPhaseTrail[i].Get() );
if ( pTempTrail )
{
pTempTrail->SetBrightness( 0 );
}
}
}
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::AddPhaseEffects( void )
{
#ifdef CLIENT_DLL
#else
const char *pTrailTeamName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "effects/beam001_red.vmt" : "effects/beam001_blu.vmt";
CSpriteTrail *pTempTrail = NULL;
pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true );
pTempTrail->FollowEntity( m_pOuter );
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
pTempTrail->SetStartWidth( 12 );
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
pTempTrail->SetLifeTime( 1 );
pTempTrail->TurnOn();
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "back_upper" ) );
m_pPhaseTrail[0] = pTempTrail;
pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true );
pTempTrail->FollowEntity( m_pOuter );
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
pTempTrail->SetStartWidth( 16 );
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
pTempTrail->SetLifeTime( 1 );
pTempTrail->TurnOn();
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "back_lower" ) );
m_pPhaseTrail[1] = pTempTrail;
pTempTrail = CSpriteTrail::SpriteTrailCreate( "effects/beam001_white.vmt", m_pOuter->GetAbsOrigin(), true );
pTempTrail->FollowEntity( m_pOuter );
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
pTempTrail->SetStartWidth( 8 );
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
pTempTrail->SetLifeTime( 0.5f );
pTempTrail->TurnOn();
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "foot_R" ) );
m_pPhaseTrail[2] = pTempTrail;
pTempTrail = CSpriteTrail::SpriteTrailCreate( "effects/beam001_white.vmt", m_pOuter->GetAbsOrigin(), true );
pTempTrail->FollowEntity( m_pOuter );
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
pTempTrail->SetStartWidth( 8 );
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
pTempTrail->SetLifeTime( 0.5f );
pTempTrail->TurnOn();
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "foot_L" ) );
m_pPhaseTrail[3] = pTempTrail;
pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true );
pTempTrail->FollowEntity( m_pOuter );
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
pTempTrail->SetStartWidth( 8 );
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
pTempTrail->SetLifeTime( 0.5f );
pTempTrail->TurnOn();
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "hand_L" ) );
m_pPhaseTrail[4] = pTempTrail;
m_bPhaseFXOn = true;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::RemovePhaseEffects( void )
{
#ifdef CLIENT_DLL
if ( m_pOuter->m_pPhaseStandingEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect );
m_pOuter->m_pPhaseStandingEffect = NULL;
}
#else
for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i )
{
UTIL_Remove(m_pPhaseTrail[i]);
}
m_bPhaseFXOn = false;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFPlayerShared::GetSequenceForDeath( CBaseAnimating* pRagdoll, bool bBurning, int nCustomDeath )
{
if ( !pRagdoll )
return -1;
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
if ( m_pOuter && ( m_pOuter->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
return -1;
}
int iDeathSeq = -1;
// if ( bBurning )
// {
// iDeathSeq = pRagdoll->LookupSequence( "primary_death_burning" );
// }
switch ( nCustomDeath )
{
case TF_DMG_CUSTOM_HEADSHOT_DECAPITATION:
case TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING:
case TF_DMG_CUSTOM_DECAPITATION:
case TF_DMG_CUSTOM_HEADSHOT:
iDeathSeq = pRagdoll->LookupSequence( "primary_death_headshot" );
break;
case TF_DMG_CUSTOM_BACKSTAB:
iDeathSeq = pRagdoll->LookupSequence( "primary_death_backstab" );
break;
}
return iDeathSeq;
}
extern ConVar tf_halloween_kart_dash_speed;
ConVar tf_halloween_kart_slow_turn_accel_speed( "tf_halloween_kart_slow_turn_accel_speed", "200", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_fast_turn_accel_speed( "tf_halloween_kart_fast_turn_accel_speed", "400", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_return_turn_accell( "tf_halloween_kart_return_turn_accell", "200", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_slow_turn_speed( "tf_halloween_kart_slow_turn_speed", "100", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_fast_turn_speed( "tf_halloween_kart_fast_turn_speed", "60", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_turning_curve_peak_position( "tf_halloween_kart_turning_curve_peak_position", "0.5", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_stationary_turn_speed( "tf_halloween_kart_stationary_turn_speed", "50", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_reverse_turn_speed( "tf_halloween_kart_reverse_turn_speed", "50", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_air_turn_scale( "tf_halloween_kart_air_turn_scale", "1.2f", FCVAR_CHEAT | FCVAR_REPLICATED );
#ifdef CLIENT_DLL
ConVar tf_halloween_kart_pitch( "tf_halloween_kart_pitch", "10", FCVAR_ARCHIVE );
ConVar tf_halloween_kart_pitch_slow_follow_rate( "tf_halloween_kart_pitch_slow_follow_rate", "0.5", FCVAR_ARCHIVE );
ConVar tf_halloween_kart_pitch_fast_follow_rate( "tf_halloween_kart_pitch_fast_follow_rate", "2", FCVAR_ARCHIVE );
#endif // CLIENT_DLL
void CTFPlayerShared::CreateVehicleMove( float flInputSampleTime, CUserCmd *pCmd )
{
const float flSign = pCmd->sidemove == 0.f ? 0.f : Sign( pCmd->sidemove );
// Compute target turn speed
const float flVel = m_pOuter->GetAbsVelocity().Length2D();
const float flNormalizedSpeed = Clamp( flVel / tf_halloween_kart_dash_speed.GetFloat(), 0.0f, 1.0f );
float flTargetTurnSpeed;
if ( flNormalizedSpeed == 0.f )
{
flTargetTurnSpeed = flSign * tf_halloween_kart_stationary_turn_speed.GetFloat();
}
else if ( Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) < 0 )
{
flTargetTurnSpeed = Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) * flSign * tf_halloween_kart_reverse_turn_speed.GetFloat();
}
else
{
const float flSmoothCurveVal = SmoothCurve_Tweak( flNormalizedSpeed, tf_halloween_kart_turning_curve_peak_position.GetFloat() );
flTargetTurnSpeed = Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) * flSign * RemapValClamped( flSmoothCurveVal, 0.f, 1.f, tf_halloween_kart_slow_turn_speed.GetFloat(), tf_halloween_kart_fast_turn_speed.GetFloat() );
}
float flTurnAccel = 0.f;
// Compute turn accelleration
if ( flSign == Sign( m_flCurrentTauntTurnSpeed ) )
{
flTurnAccel = RemapValClamped( flNormalizedSpeed, 0.f, 1.f, tf_halloween_kart_slow_turn_accel_speed.GetFloat(), tf_halloween_kart_fast_turn_accel_speed.GetFloat() );
}
else
{ // When not trying to turn, or turning the opposite way you're already turning
// accelerate much faster
flTurnAccel = tf_halloween_kart_return_turn_accell.GetFloat();
}
// Turn faster in the air
if ( !(m_pOuter->GetFlags() & FL_ONGROUND) )
{
flTurnAccel *= tf_halloween_kart_air_turn_scale.GetFloat();
}
// Get actual turn speed
m_flCurrentTauntTurnSpeed = Approach( flTargetTurnSpeed, m_flCurrentTauntTurnSpeed, flTurnAccel * flInputSampleTime );
const float flMaxPossibleTurnSpeed = Max( tf_halloween_kart_slow_turn_speed.GetFloat(), tf_halloween_kart_fast_turn_speed.GetFloat() );
m_flCurrentTauntTurnSpeed = clamp( m_flCurrentTauntTurnSpeed, -flMaxPossibleTurnSpeed, flMaxPossibleTurnSpeed );
#ifdef DEBUG
#ifdef CLIENT_DLL
engine->Con_NPrintf( 4, "Turn: %3.2f", m_flCurrentTauntTurnSpeed );
engine->Con_NPrintf( 5, "TargetTurn: %3.2f", flTargetTurnSpeed );
engine->Con_NPrintf( 6, "TurnAccell: %3.2f", flTurnAccel );
#else
engine->Con_NPrintf( 4+3, "Turn: %3.2f", m_flCurrentTauntTurnSpeed );
engine->Con_NPrintf( 5+3, "TargetTurn: %3.2f", flTargetTurnSpeed );
engine->Con_NPrintf( 6+3, "TurnAccell: %3.2f", flTurnAccel );
#endif
#endif
#ifdef CLIENT_DLL
// Turn!
m_angVehicleMoveAngles -= QAngle( 0.f, m_flCurrentTauntTurnSpeed * flInputSampleTime, 0.f );
// We want our pitch to slowly catch up to the pitch of the player's model
const float flTargetPitch = tf_halloween_kart_pitch.GetFloat() + m_pOuter->m_PlayerAnimState->GetRenderAngles()[PITCH];
const float flStepSpeed = fabs( flTargetPitch - tf_halloween_kart_pitch.GetFloat() ) < fabs( m_angVehicleMovePitchLast - tf_halloween_kart_pitch.GetFloat() ) ? tf_halloween_kart_pitch_fast_follow_rate.GetFloat() : tf_halloween_kart_pitch_slow_follow_rate.GetFloat();
const float flPitchDiff = fabs( flTargetPitch - m_angVehicleMovePitchLast );
const float flPitchStep = flPitchDiff * flStepSpeed;
m_angVehicleMovePitchLast = Approach( flTargetPitch, m_angVehicleMovePitchLast, flPitchStep * gpGlobals->frametime );
m_angVehicleMoveAngles[PITCH] = m_angVehicleMovePitchLast;
pCmd->weaponselect = 0;
pCmd->buttons &= IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK | IN_ATTACK2 | IN_FORWARD | IN_BACK | IN_JUMP;
VectorCopy( m_angVehicleMoveAngles, pCmd->viewangles );
m_pOuter->SetLocalAngles( m_angVehicleMoveAngles );
// Fill out our kart state for the local client
m_pOuter->m_iKartState = 0;
// Hitting the gas
if ( pCmd->buttons & IN_FORWARD )
{
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Driving;
}
else if ( pCmd->buttons & IN_BACK ) // Hitting the brakes
{
// slowing down
if ( m_pOuter->GetCurrentTauntMoveSpeed() > 0 )
{
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Braking;
}
// if we are already stopped, look for new input to start going backwards
else
{
// check for new input, else do nothing
if ( ( pCmd->buttons & IN_BACK )
|| m_pOuter->GetCurrentTauntMoveSpeed() < 0
|| m_pOuter->GetVehicleReverseTime() < gpGlobals->curtime
)
{
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Reversing;
}
else
{
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Stopped;
}
}
}
#endif
}
void CTFPlayerShared::VehicleThink( void )
{
#ifdef CLIENT_DLL
m_pOuter->UpdateKartSounds();
// Ordered list of effects. Lower on the list has higher prescedence
static WheelEffect_t wheelEffects[] =
{
WheelEffect_t( 20.f, "kart_dust_trail_red", "kart_dust_trail_blue" )
};
const float flCurrentSpeed = m_pOuter->GetCurrentTauntMoveSpeed();
const WheelEffect_t* pDesiredEffect = NULL;
if ( InCond( TF_COND_HALLOWEEN_KART ) )
{
// Go through the effects, and figure out which effect to use
for( int i=0; i < ARRAYSIZE(wheelEffects); ++ i )
{
const WheelEffect_t& effect = wheelEffects[ i ];
if ( effect.m_flMinTriggerSpeed <= flCurrentSpeed )
{
pDesiredEffect = &effect;
}
}
}
// Start/stop effects if the desired effect is different
if ( pDesiredEffect != m_pWheelEffect )
{
C_BaseAnimating * pKart = m_pOuter->GetKart();
if ( !pKart )
return;
m_pWheelEffect = pDesiredEffect;
// New effect
if ( pDesiredEffect )
{
const char *pszEffectName = pDesiredEffect->m_pszParticleName[ m_pOuter->GetTeamNumber() ];
m_pOuter->CreateKartEffect( pszEffectName );
}
else // Turn off current effect
{
m_pOuter->StopKartEffect();
}
}
#endif
}
//-----------------------------------------------------------------------------
float CTFPlayer::GetKartSpeedBoost( void )
{
if ( m_flKartNextAvailableBoost < gpGlobals->curtime )
return 1.0f;
if ( m_flKartNextAvailableBoost > gpGlobals->curtime + tf_halloween_kart_boost_recharge.GetFloat() )
return 0.0f;
// Calculate time
return RemapValClamped( gpGlobals->curtime, m_flKartNextAvailableBoost - tf_halloween_kart_boost_recharge.GetFloat(), m_flKartNextAvailableBoost, 0.0f, 1.0f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPlayerShared::IsLoser( void )
{
if ( tf_always_loser.GetBool() )
return true;
if ( !TFGameRules() )
return false;
// No loser mode in competitive
if ( TFGameRules()->IsMatchTypeCompetitive() )
return false;
if ( TFGameRules()->State_Get() != GR_STATE_TEAM_WIN )
{
if ( IsLoserStateStunned() )
return true;
else
return false;
}
bool bLoser = TFGameRules()->GetWinningTeam() != m_pOuter->GetTeamNumber();
int iClass = m_pOuter->GetPlayerClass()->GetClassIndex();
// don't reveal disguised spies
if ( bLoser && iClass == TF_CLASS_SPY )
{
if ( InCond( TF_COND_DISGUISED ) && GetDisguiseTeam() == TFGameRules()->GetWinningTeam() )
{
bLoser = false;
}
}
return bLoser;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::RecalculatePlayerBodygroups( void )
{
// We have to clear the m_nBody bitfield.
// Leaving bits on from previous player classes can have weird effects
// like if we switch to a class that uses those bits for other things.
m_pOuter->m_nBody = 0;
// Update our weapon bodygroups that change state purely based on whether they're
// equipped or not.
CTFWeaponBase::UpdateWeaponBodyGroups( m_pOuter, false );
// Update our wearable bodygroups.
CEconWearable::UpdateWearableBodyGroups( m_pOuter );
// Update our weapon bodygroups for weapons that only change state when active.
CTFWeaponBase::UpdateWeaponBodyGroups( m_pOuter, true );
}
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::GetSpeedWatchersList( CUtlVector<CTFPlayer *> *out_pVecSpeedWatchers ) const
{
Assert( out_pVecSpeedWatchers != NULL );
// Are any medics healing us with the Quick-Fix?
FOR_EACH_VEC( m_aHealers, i )
{
CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer );
if ( !pTFHealer )
continue;
if ( !pTFHealer->GetActiveTFWeapon() || pTFHealer->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_MEDIGUN )
continue;
// QuickFix medics heal themselves when deploying an Uber
if ( m_aHealers[i].pHealer == m_pOuter )
continue;
out_pVecSpeedWatchers->AddToTail( pTFHealer );
}
}
#endif // GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetupRageBuffTimer( int iBuffType, int iPulseCount, ERageBuffSlot eBuffSlot )
{
m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive = iBuffType;
m_RageBuffSlots[eBuffSlot].m_iBuffPulseCount = iPulseCount;
m_RageBuffSlots[eBuffSlot].m_flNextBuffPulseTime = gpGlobals->curtime + 1.0f;
PulseRageBuff( eBuffSlot );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ActivateRageBuff( CBaseEntity *pBuffItem, int iBuffType )
{
// Sniper Focus can be activated at all times
if ( GetRageMeter() < 100.f && iBuffType != 6 )
return;
Assert( iBuffType > 0 && iBuffType < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff
if ( iBuffType < 0 || iBuffType >= ARRAYSIZE( g_RageBuffTypes ) )
{
DevMsg( "Invalid rage buff type %i for entindex %i\n", iBuffType, m_pOuter->entindex() );
ResetRageSystem();
return;
}
int nBuffPulses = g_RageBuffTypes[iBuffType].m_nMaxPulses;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffPulses, mod_buff_duration );
#ifdef GAME_DLL
switch ( iBuffType )
{
case 1:
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
break;
case 2:
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_INCOMING );
break;
case 3:
// FIXME: new sound file for samurai buff?
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
case 5:
// Pyro Rage
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
break;
case 6 :
// Sniper Focus
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
nBuffPulses *= (m_flRageMeter / 100);
break;
}
#endif
m_bRageDraining = true;
SetupRageBuffTimer( iBuffType, nBuffPulses, kBuffSlot_Rage );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateRageBuffsAndRage( void )
{
// We allow this for all classes to allow item creators and plugin authors to give rage to any class.
// If we're dead, reset both our rage and our active buffs.
if ( !m_pOuter->IsAlive() )
{
ResetRageSystem();
return;
}
// Find out whether we've run out of rage.
if ( m_bRageDraining )
{
int nBuffType = 0;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffType, set_buff_type );
Assert( nBuffType >= 0 && nBuffType < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff
if ( nBuffType < 0 || nBuffType >= ARRAYSIZE( g_RageBuffTypes ) )
{
DevMsg( "Invalid rage buff type %i for entindex %i\n", nBuffType, m_pOuter->entindex() );
ResetRageSystem();
return;
}
int nBuffPulses = g_RageBuffTypes[nBuffType].m_nMaxPulses;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffPulses, mod_buff_duration );
if ( nBuffPulses > 0 )
{
m_flRageMeter -= gpGlobals->frametime * ( 100 / nBuffPulses );
if ( m_flRageMeter <= 0.0f )
{
m_flRageMeter = 0.0f;
m_bRageDraining = false;
if ( g_SoldierBuffAttributeIDToConditionMap[ nBuffType ] == TF_COND_CRITBOOSTED_RAGE_BUFF )
{
// Pyro rage needs a cooldown so that the final crit flames
// don't significantly fill up his next rage meter
m_flNextRageEarnTime = gpGlobals->curtime + tf_flamethrower_flametime.GetFloat() + 0.1f;
}
}
}
else
{
ResetRageSystem();
}
}
#ifdef STAGING_ONLY
if ( m_flRageMeter >= 100.f )
{
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
{
if ( CanBuildSpyTraps() )
{
// Whenever a spy fills their rage meter, give them a speed boost and grenade (used by traps)
if ( m_pOuter->GetAmmoCount( TF_AMMO_GRENADES1 ) <= m_pOuter->GetMaxAmmo( TF_AMMO_GRENADES1 ) )
{
#ifdef GAME_DLL
m_pOuter->GiveAmmo( 1, TF_AMMO_GRENADES1 );
AddCond( TF_COND_SPEED_BOOST, 5.f );
#endif // GAME_DLL
m_flRageMeter = 0.f;
}
}
}
}
#endif // STAGING_ONLY
// Handle pulsing all of our active rage buffs.
for ( int i = 0; i < ARRAYSIZE( m_RageBuffSlots ); i++ )
{
RageBuff& rageBuff = m_RageBuffSlots[i];
if ( gpGlobals->curtime > rageBuff.m_flNextBuffPulseTime && rageBuff.m_iBuffPulseCount > 0 )
{
rageBuff.m_flNextBuffPulseTime += 1.0f;
--rageBuff.m_iBuffPulseCount;
PulseRageBuff( (ERageBuffSlot)i );
}
}
}
static const int k_RageBuffType_Sniper = 6;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetRageMeter( float val )
{
// Allow Sniper to gain rage on kills even when buffed
if ( !InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) && !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
{
if ( IsRageDraining() )
return;
// Can't earn rage until the time is past this delay
if ( val > m_flRageMeter && gpGlobals->curtime < m_flNextRageEarnTime )
return;
}
m_flRageMeter = MIN( val, 100.0f );
if ( InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) )
{
Assert( k_RageBuffType_Sniper > 0 && k_RageBuffType_Sniper < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff
if ( k_RageBuffType_Sniper < 0 || k_RageBuffType_Sniper >= ARRAYSIZE( g_RageBuffTypes ) )
return;
int nBuffPulses = g_RageBuffTypes[k_RageBuffType_Sniper].m_nMaxPulses;
m_bRageDraining = true;
nBuffPulses *= (m_flRageMeter / 100);
m_RageBuffSlots[kBuffSlot_Rage].m_iBuffPulseCount = nBuffPulses;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ModifyRage( float fDelta )
{
SetRageMeter( GetRageMeter() + fDelta );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ResetRageMeter( void )
{
m_flRageMeter = 0.0f;
m_flNextRageEarnTime = 0.0f;
ResetRageBuffs();
UpdateRageBuffsAndRage();
}
//-----------------------------------------------------------------------------
// Purpose: Apply the buff effect to everyone within a radius around the player.
//-----------------------------------------------------------------------------
void CTFPlayerShared::PulseRageBuff( ERageBuffSlot eBuffSlot )
{
Assert( m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive != 0 );
#ifdef CLIENT_DLL
// if this is not the local player, we don't want to do anything
if ( !m_pOuter->IsLocalPlayer() )
return;
int nBuffedFriends = 0;
#else
int nBuffedPlayers = 0;
#endif
int iSoldierBuffType = m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive;
ETFCond eBuffCond = TF_COND_LAST;
if ( iSoldierBuffType > 0 || iSoldierBuffType <= kSoldierBuffCount )
{
eBuffCond = g_SoldierBuffAttributeIDToConditionMap[iSoldierBuffType];
}
float fMaxRadius = 450.0f;
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, fMaxRadius, mod_soldier_buff_range );
const float fMaxRadiusSq = fMaxRadius * fMaxRadius;
for( int iPlayerIndex=1; iPlayerIndex<=MAX_PLAYERS; ++iPlayerIndex )
{
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
if ( !pTFPlayer || !pTFPlayer->IsAlive() )
continue;
if ( pTFPlayer->GetTeamNumber() != m_pOuter->GetTeamNumber() )
continue;
if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && (pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber()) )
continue; // For now don't give the buff to spies on our team who are disguised as enemies.
if ( pTFPlayer->m_Shared.IsStealthed() )
continue; // Don't give the buff to cloaked spies
Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
if ( vDist.LengthSqr() > fMaxRadiusSq )
continue;
#ifdef CLIENT_DLL
if ( pTFPlayer != m_pOuter )
{
if ( eBuffCond == TF_COND_CRITBOOSTED_RAGE_BUFF || eBuffCond == TF_COND_SNIPERCHARGE_RAGE_BUFF )
{
// Pyro and sniper only buffs themselves
continue;
}
// this is not the localplayer, are they a friend?
if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() )
return;
player_info_t pi;
if ( !engine->GetPlayerInfo( pTFPlayer->entindex(), &pi ) )
return;
if ( !pi.friendsID )
return;
// check and see if they're on the local player's friends list
CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
if ( steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) )
{
nBuffedFriends++;
}
}
#else
if ( pTFPlayer != m_pOuter )
{
if ( eBuffCond == TF_COND_CRITBOOSTED_RAGE_BUFF || eBuffCond == TF_COND_SNIPERCHARGE_RAGE_BUFF )
{
// Pyro and sniper only buffs themselves
continue;
}
}
if ( eBuffCond != TF_COND_LAST )
{
pTFPlayer->m_Shared.AddCond( eBuffCond, 1.2f, m_pOuter );
nBuffedPlayers++;
IGameEvent* event = gameeventmanager->CreateEvent( "player_buff" );
if ( event )
{
event->SetInt( "userid", pTFPlayer->GetUserID() );
event->SetInt( "buff_owner", m_pOuter->GetUserID() );
event->SetInt( "buff_type", iSoldierBuffType );
gameeventmanager->FireEvent( event );
}
}
#endif
}
#ifdef CLIENT_DLL
if ( nBuffedFriends >= 5 )
{
g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_SOLDIER_BUFF_FRIENDS );
}
#else
// ACHIEVEMENT_TF_MVM_SOLDIER_BUFF_TEAM
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
if ( ( m_pOuter->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) && m_pOuter->IsPlayerClass( TF_CLASS_SOLDIER ) )
{
if ( nBuffedPlayers >= 5 )
{
m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_SOLDIER_BUFF_TEAM );
}
}
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::ResetRageSystem( void )
{
m_flRageMeter = 0.f;
m_bRageDraining = false;
m_flNextRageEarnTime = 0.f;
ResetRageBuffs();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateEnergyDrinkMeter( void )
{
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
return;
bool bIsLocalPlayer = false;
#ifdef CLIENT_DLL
bIsLocalPlayer = m_pOuter->IsLocalPlayer();
#else
bIsLocalPlayer = true;
#endif
if ( bIsLocalPlayer )
{
if ( IsHypeBuffed() )
{
m_flHypeMeter -= gpGlobals->frametime * (m_fEnergyDrinkConsumeRate*0.75f);
if ( m_flHypeMeter <= 0.0f )
{
RemoveCond( TF_COND_SODAPOPPER_HYPE );
}
}
if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_ENERGY_BUFF ) )
{
// Drain the meter
m_flEnergyDrinkMeter -= gpGlobals->frametime * m_fEnergyDrinkConsumeRate;
// If we've drained the meter, remove the condition
if ( m_flEnergyDrinkMeter <= 0.f )
{
if ( InCond( TF_COND_ENERGY_BUFF ) )
{
AddCond( TF_COND_MARKEDFORDEATH_SILENT, 2.f );
}
RemoveCond( TF_COND_PHASE );
RemoveCond( TF_COND_ENERGY_BUFF );
#ifdef GAME_DLL
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_TIRED );
#endif
}
// Update the effect on phasing only
else if ( InCond( TF_COND_PHASE ) )
{
UpdatePhaseEffects();
}
}
else if ( m_flEnergyDrinkMeter < 100.0f )
{
// Regen the meter
m_flEnergyDrinkMeter += gpGlobals->frametime * m_fEnergyDrinkRegenRate;
CTFLunchBox_Drink *pDrink = static_cast< CTFLunchBox_Drink* >( m_pOuter->Weapon_OwnsThisID( TF_WEAPON_LUNCHBOX ) );
if ( pDrink )
{
// This is here in case something replenishes grenades
if ( m_flEnergyDrinkMeter < 100.0f && m_pOuter->GetAmmoCount( TF_AMMO_GRENADES2 ) == m_pOuter->GetMaxAmmo( TF_AMMO_GRENADES2 ) )
{
m_flEnergyDrinkMeter = 100.0f;
}
}
else if ( m_flEnergyDrinkMeter >= 100.0f )
{
m_flEnergyDrinkMeter = 100.0f;
}
}
}
}
void CTFPlayerShared::SetScoutHypeMeter( float val )
{
if ( IsHypeBuffed() )
return;
m_flHypeMeter = Clamp(val, 0.0f, 100.0f);
//if ( m_flHypeMeter >= 100.f )
//{
// if ( m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) )
// {
// CTFWeaponBase* pWeapon = m_pOuter->GetActiveTFWeapon();
// if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SODA_POPPER )
// {
// AddCond( TF_COND_CRITBOOSTED_HYPE );
// }
// }
//}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::UpdateCloakMeter( void )
{
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) )
return;
if ( InCond( TF_COND_STEALTHED ) )
{
if ( m_bMotionCloak )
{
// Motion cloak: drain based on our movement rate.
Vector vVel = m_pOuter->GetAbsVelocity();
float fSpdSqr = vVel.LengthSqr();
if ( fSpdSqr == 0.f )
{
if ( gpGlobals->curtime - m_flLastStealthExposeTime > 1.f )
{
m_flCloakMeter += gpGlobals->frametime * m_fCloakRegenRate;
if ( m_flCloakMeter >= 100.0f )
{
m_flCloakMeter = 100.0f;
}
}
}
else
{
float fFactor = RemapVal( fSpdSqr, 0, m_pOuter->MaxSpeed()*m_pOuter->MaxSpeed(), 0.f, 1.f );
if ( fFactor > 1.f )
{
fFactor = 1.f;
}
m_flCloakMeter -= gpGlobals->frametime * m_fCloakConsumeRate * fFactor * 1.5f;
if ( m_flCloakMeter < 0.f )
{
m_flCloakMeter = 0.f;
}
}
}
else
{
// Classic cloak: drain at a fixed rate.
m_flCloakMeter -= gpGlobals->frametime * m_fCloakConsumeRate;
}
if ( m_flCloakMeter <= 0.0f && !m_bMotionCloak)
{
FadeInvis( 1.0f );
}
// Update Debuffs
// Decrease duration if cloaked
#ifdef GAME_DLL
// staging_spy
float flReduction = gpGlobals->frametime * 0.75f;
for ( int i = 0; g_aDebuffConditions[i] != TF_COND_LAST; i++ )
{
if ( InCond( g_aDebuffConditions[i] ) )
{
if ( m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime != PERMANENT_CONDITION )
{
m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime = MAX( m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime - flReduction, 0 );
}
// Burning and Bleeding and extra timers
if ( g_aDebuffConditions[i] == TF_COND_BURNING )
{
// Reduce the duration of this burn
m_flFlameRemoveTime -= flReduction;
}
else if ( g_aDebuffConditions[i] == TF_COND_BLEEDING )
{
// Reduce the duration of this bleeding
FOR_EACH_VEC( m_PlayerBleeds, i )
{
m_PlayerBleeds[i].flBleedingRemoveTime -= flReduction;
}
}
}
}
#endif
}
else
{
m_flCloakMeter += gpGlobals->frametime * m_fCloakRegenRate;
if ( m_flCloakMeter >= 100.0f )
{
m_flCloakMeter = 100.0f;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Whether we should be doing a radius heal (does not stack with a medigun)
//-----------------------------------------------------------------------------
void CTFPlayerShared::Heal_Radius( bool bActive )
{
if ( bActive )
{
m_bPulseRadiusHeal = true;
}
else
{
m_bPulseRadiusHeal = false;
#ifdef GAME_DLL
// Stop any Radius healing
if ( m_iRadiusHealTargets.Count() > 0 )
{
for ( int iIndex = 0; iIndex < m_iRadiusHealTargets.Count(); iIndex++ )
{
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( m_iRadiusHealTargets[iIndex] ) );
if ( !pTFPlayer )
continue;
pTFPlayer->m_Shared.StopHealing( m_pOuter );
}
m_iRadiusHealTargets.RemoveAll();
}
#endif // GAME_DLL
#ifdef CLIENT_DLL
if ( m_pOuter && m_pOuter->m_pRadiusHealEffect )
{
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRadiusHealEffect );
}
m_pOuter->m_pRadiusHealEffect = NULL;
#endif // CLIENT_DLL
}
}
#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ConVar tf_rocket_pack_cooldown( "tf_rocket_pack_cooldown", "1.f", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_rocket_pack_enabled( "tf_rocket_pack_enabled", "0", FCVAR_CHEAT | FCVAR_REPLICATED );
void CTFPlayerShared::DoRocketPack()
{
if ( !tf_rocket_pack_enabled.GetBool() )
return;
if ( !( m_pOuter->m_nButtons & IN_ATTACK3 ) )
return;
if ( gpGlobals->curtime < m_flNextRocketPackTime )
return;
#ifdef GAME_DLL
// Launch
if ( !InCond( TF_COND_ROCKETPACK ) )
{
AddCond( TF_COND_ROCKETPACK );
StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT );
}
#endif
Vector vecDir;
m_pOuter->EyeVectors( &vecDir );
m_pOuter->SetAbsVelocity( vec3_origin );
Vector vecFlightDir = -vecDir;
VectorNormalize( vecFlightDir );
float flForce = 450.f;
const float flPushScale = ( m_pOuter->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f; // Greater force while airborne
const float flVertPushScale = ( m_pOuter->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f; // Less vertical force while airborne
Vector vecForce = vecFlightDir * -flForce * flPushScale;
vecForce.z += 1.f * flForce * flVertPushScale;
m_pOuter->RemoveFlag( FL_ONGROUND );
m_pOuter->ApplyAbsVelocityImpulse( vecForce );
m_pOuter->EmitSound( "Equipment.RocketPack_Activate" );
m_flNextRocketPackTime = gpGlobals->curtime + tf_rocket_pack_cooldown.GetFloat();
}
#endif // STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose: Emits an area-of-effect heal around the medic
//-----------------------------------------------------------------------------
void CTFPlayerShared::PulseMedicRadiusHeal( void )
{
if ( !m_bPulseRadiusHeal )
{
#ifdef GAME_DLL
Assert( m_iRadiusHealTargets.Count() == 0 );
if ( m_iRadiusHealTargets.Count() > 0 )
{
// We shouldn't have any heal targets if we aren't pulsing.
Heal_Radius( false );
}
#endif
return;
}
// If we're set to heal, make sure it's still valid
if ( !m_pOuter->IsAlive() || ( !m_pOuter->IsPlayerClass( TF_CLASS_MEDIC ) && !InCond( TF_COND_RADIUSHEAL_ON_DAMAGE ) ) )
{
Heal_Radius( false );
return;
}
#ifdef GAME_DLL
if ( gpGlobals->curtime >= m_flRadiusHealCheckTime )
{
for( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex )
{
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
if ( !pTFPlayer || !pTFPlayer->IsAlive() )
continue;
// Don't heal ourselves, unless this is due to radius heal on damage proc
if ( pTFPlayer == m_pOuter && !InCond( TF_COND_RADIUSHEAL_ON_DAMAGE ) )
continue;
if ( !pTFPlayer->InSameTeam( m_pOuter ) )
{
if ( !pTFPlayer->m_Shared.IsStealthed() && !pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
continue;
if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && ( pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber() ) )
continue;
}
// Don't heal players with weapon_blocks_healing
CTFWeaponBase *pTFWeapon = pTFPlayer->GetActiveTFWeapon();
if ( pTFWeapon )
{
int iBlockHealing = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iBlockHealing, weapon_blocks_healing );
if ( iBlockHealing )
continue;
}
Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
if ( vDist.LengthSqr() <= 450 * 450 )
{
// Ignore players we can't see
trace_t trace;
UTIL_TraceLine( pTFPlayer->WorldSpaceCenter(), m_pOuter->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
if ( trace.fraction < 1.0f )
continue;
// Refresh this condition, which we use to give players a particle effect
pTFPlayer->m_Shared.AddCond( TF_COND_RADIUSHEAL, 1.2f );
// Make sure we're not already healing them
if ( m_iRadiusHealTargets.Find( iPlayerIndex ) == m_iRadiusHealTargets.InvalidIndex() )
{
m_iRadiusHealTargets.AddToTail( iPlayerIndex );
pTFPlayer->m_Shared.Heal( m_pOuter, 25, 1, 1 );
}
}
else
{
if ( m_iRadiusHealTargets.Find( iPlayerIndex ) != m_iRadiusHealTargets.InvalidIndex() )
{
m_iRadiusHealTargets.FindAndRemove( iPlayerIndex );
pTFPlayer->m_Shared.StopHealing( m_pOuter );
}
}
}
m_flRadiusHealCheckTime = gpGlobals->curtime + 1.0f;
}
#endif // GAME_DLL
#ifdef CLIENT_DLL
// Radius healer gets an effect to broadcast to others what they're doing
if ( !m_pOuter->m_pRadiusHealEffect )
{
const char *pszRadiusHealEffect;
if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
{
pszRadiusHealEffect = "medic_healradius_red_buffed";
}
else
{
pszRadiusHealEffect = "medic_healradius_blue_buffed";
}
m_pOuter->m_pRadiusHealEffect = m_pOuter->ParticleProp()->Create( pszRadiusHealEffect, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector( 0, 0, 0 ) );
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose: Emits an area-of-effect buff around the King Rune carrier
//-----------------------------------------------------------------------------
void CTFPlayerShared::PulseKingRuneBuff( void )
{
// Make sure we have the King Powerup and are not invisible
if ( !m_pOuter->IsAlive() || IsStealthed() || GetCarryingRuneType() != RUNE_KING )
{
return;
}
#ifdef GAME_DLL
if ( gpGlobals->curtime >= m_flKingRuneBuffCheckTime )
{
m_bKingRuneBuffActive = false;
// Plague blocks king team buff
if ( !InCond( TF_COND_PLAGUE ) )
{
for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex )
{
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
if ( !pTFPlayer || !pTFPlayer->IsAlive() )
continue;
// Ignore players outside of the buff radius
Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin();
if ( vDist.LengthSqr() >= 768 * 768 )
continue;
// If King is the only player, there's no effect
if ( pTFPlayer == m_pOuter )
continue;
// Spies who are invisible or disguised as the King's enemy team are ignored
if ( pTFPlayer->m_Shared.IsStealthed() || ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber() ) )
continue;
// Enemies - ignore unless they are disguised as the King's team
if ( !pTFPlayer->InSameTeam( m_pOuter ) && !pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
continue;
pTFPlayer->m_Shared.AddCond( TF_COND_KING_BUFFED, 1.f );
m_bKingRuneBuffActive = true;
}
}
m_flKingRuneBuffCheckTime = gpGlobals->curtime + 0.5f;
}
#endif // GAME_DLL
#ifdef CLIENT_DLL
// King Rune carrier gets an effect to show that he's buffing someone
if ( m_bKingRuneBuffActive && !InCond( TF_COND_PLAGUE ) )
{
if ( !m_pOuter->m_pKingRuneRadiusEffect )
{
const char *pszRadiusEffect;
if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED )
{
pszRadiusEffect = "powerup_king_red";
}
else
{
pszRadiusEffect = "powerup_king_blue";
}
m_pOuter->m_pKingRuneRadiusEffect = m_pOuter->ParticleProp()->Create( pszRadiusEffect, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector( 0, 0, 0 ) );
}
}
else
{
EndKingBuffRadiusEffect();
}
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::IncrementRevengeCrits( void )
{
SetRevengeCrits( m_iRevengeCrits + 1 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetRevengeCrits( int iVal )
{
m_iRevengeCrits = clamp( iVal, 0, 35 );
CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon();
if ( ( pWeapon && pWeapon->CanHaveRevengeCrits() ) )
{
if ( m_iRevengeCrits > 0 && !InCond( TF_COND_CRITBOOSTED ) )
{
AddCond( TF_COND_CRITBOOSTED );
}
else if ( m_iRevengeCrits == 0 && InCond( TF_COND_CRITBOOSTED ) )
{
RemoveCond( TF_COND_CRITBOOSTED );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayerShared::FireGameEvent( IGameEvent *event )
{
#ifdef GAME_DLL
const char *eventName = event->GetName();
if ( !Q_strcmp( eventName, "player_disconnect" ) )
{
CBasePlayer *pPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( pPlayer )
{
int iIndex = m_iRadiusHealTargets.Find( pPlayer->entindex() );
if ( iIndex != m_iRadiusHealTargets.InvalidIndex() )
{
m_iRadiusHealTargets.FastRemove( iIndex );
}
}
}
#endif //GAME_DLL
}
//-----------------------------------------------------------------------------
void CTFPlayerShared::SetPasstimePassTarget( CTFPlayer *pEnt )
{
if ( CBaseEntity *pTarget = m_hPasstimePassTarget )
{
CTFPlayer *pPlayerTarget = ToTFPlayer( pTarget );
if ( pPlayerTarget )
pPlayerTarget->m_Shared.m_bIsTargetedForPasstimePass = false;
}
Assert( pEnt != m_pOuter );
m_hPasstimePassTarget = pEnt;
if ( CBaseEntity *pTarget = m_hPasstimePassTarget )
{
CTFPlayer *pPlayerTarget = ToTFPlayer( pTarget );
if ( pPlayerTarget )
pPlayerTarget->m_Shared.m_bIsTargetedForPasstimePass = true;
}
}
//-----------------------------------------------------------------------------
CTFPlayer *CTFPlayerShared::GetPasstimePassTarget() const { return m_hPasstimePassTarget.Get(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTraceFilterIgnoreTeammatesAndTeamObjects::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
if ( pEntity->GetTeamNumber() == m_iIgnoreTeam )
{
return false;
}
CTFPlayer *pPlayer = dynamic_cast<CTFPlayer*>( pEntity );
if ( pPlayer )
{
if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == m_iIgnoreTeam )
return false;
if ( pPlayer->m_Shared.IsStealthed() )
return false;
}
return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
}
void CTFPlayerShared::SetCarriedObject( CBaseObject* pObj )
{
m_bCarryingObject = (pObj != NULL);
m_hCarriedObject.Set( pObj );
#ifdef GAME_DLL
if ( m_pOuter )
m_pOuter->TeamFortress_SetSpeed();
#endif
}
void localplayerscoring_t::UpdateStats( RoundStats_t& roundStats, CTFPlayer *pPlayer, bool bIsRoundData )
{
m_iCaptures = roundStats.m_iStat[TFSTAT_CAPTURES];
m_iDefenses = roundStats.m_iStat[TFSTAT_DEFENSES];
m_iKills = roundStats.m_iStat[TFSTAT_KILLS];
m_iDeaths = roundStats.m_iStat[TFSTAT_DEATHS];
m_iSuicides = roundStats.m_iStat[TFSTAT_SUICIDES];
m_iKillAssists = roundStats.m_iStat[TFSTAT_KILLASSISTS];
m_iBuildingsBuilt = roundStats.m_iStat[TFSTAT_BUILDINGSBUILT];
m_iBuildingsDestroyed = roundStats.m_iStat[TFSTAT_BUILDINGSDESTROYED];
m_iHeadshots = roundStats.m_iStat[TFSTAT_HEADSHOTS];
m_iDominations = roundStats.m_iStat[TFSTAT_DOMINATIONS];
m_iRevenge = roundStats.m_iStat[TFSTAT_REVENGE];
m_iInvulns = roundStats.m_iStat[TFSTAT_INVULNS];
m_iTeleports = roundStats.m_iStat[TFSTAT_TELEPORTS];
m_iDamageDone = roundStats.m_iStat[TFSTAT_DAMAGE];
m_iCrits = roundStats.m_iStat[TFSTAT_CRITS];
m_iBackstabs = roundStats.m_iStat[TFSTAT_BACKSTABS];
int iHealthPointsHealed = (int) roundStats.m_iStat[TFSTAT_HEALING];
// send updated healing data every 10 health points, and round off what we send to nearest 10 points
int iHealPointsDelta = abs( iHealthPointsHealed - m_iHealPoints );
if ( iHealPointsDelta > 10 )
{
m_iHealPoints = ( iHealthPointsHealed / 10 ) * 10;
}
m_iBonusPoints = roundStats.m_iStat[TFSTAT_BONUS_POINTS] / TF_SCORE_BONUS_POINT_DIVISOR;
const int nPoints = TFGameRules()->CalcPlayerScore( &roundStats, pPlayer );
const int nDelta = nPoints - m_iPoints;
m_iPoints = nPoints;
if ( nDelta > 0 && !bIsRoundData )
{
IGameEvent *event = gameeventmanager->CreateEvent( "player_score_changed" );
if ( event )
{
event->SetInt( "player", pPlayer->entindex() );
event->SetInt( "delta", nDelta );
gameeventmanager->FireEvent( event );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CEconItemView *CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( CTFPlayer *pTFPlayer, int iSlot, CEconEntity **pEntity )
{
int iClass = pTFPlayer->GetPlayerClass()->GetClassIndex();
// See if it's a weapon first
for ( int i = 0; i < MAX_WEAPONS; i++ )
{
CTFWeaponBase *pWeapon = (CTFWeaponBase *)pTFPlayer->GetWeapon(i);
if ( !pWeapon )
continue;
CEconItemView *pEconItemView = pWeapon->GetAttributeContainer()->GetItem();
if ( !pEconItemView )
continue;
int iLoadoutSlot = pEconItemView->GetStaticData()->GetLoadoutSlot( iClass );
if ( iLoadoutSlot == iSlot )
{
if ( pEntity )
{
*pEntity = pWeapon;
}
return pEconItemView;
}
}
// Go through each of the actual items we have equipped right now...
for ( int i = 0; i < pTFPlayer->GetNumWearables(); ++i )
{
CTFWearable *pWearableItem = dynamic_cast<CTFWearable *>( pTFPlayer->GetWearable( i ) );
if ( !pWearableItem )
continue;
if ( !pWearableItem->GetAttributeContainer() )
continue;
CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem();
if ( !pEconItemView )
continue;
CTFItemDefinition *pItemDef = pEconItemView->GetStaticData();
if ( !pItemDef )
continue;
if ( pItemDef->GetLoadoutSlot(iClass) != iSlot )
continue;
// Yay!
if ( pEntity )
{
*pEntity = pWearableItem;
}
return pEconItemView;
}
// Nothing we currently have equipped claims to be in this slot.
if ( pEntity )
{
*pEntity = NULL;
}
return NULL;
}
bool CTFPlayerSharedUtils::ConceptIsPartnerTaunt( int iConcept )
{
return iConcept == MP_CONCEPT_HIGHFIVE_SUCCESS_FULL || iConcept == MP_CONCEPT_HIGHFIVE_SUCCESS;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFWeaponBuilder *CTFPlayerSharedUtils::GetBuilderForObjectType( CTFPlayer *pTFPlayer, int iObjectType )
{
const int OBJ_ANY = -1;
if ( !pTFPlayer )
return NULL;
for ( int i = 0; i < MAX_WEAPONS; i++ )
{
CTFWeaponBuilder *pBuilder = dynamic_cast< CTFWeaponBuilder* >( pTFPlayer->GetWeapon( i ) );
if ( !pBuilder )
continue;
// Any builder will do - return first
if ( iObjectType == OBJ_ANY )
return pBuilder;
// Requires a specific builder for this type
if ( pBuilder->CanBuildObjectType( iObjectType ) )
return pBuilder;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Can player pick up this weapon?
//-----------------------------------------------------------------------------
bool CTFPlayer::CanPickupDroppedWeapon( const CTFDroppedWeapon *pWeapon )
{
if ( !pWeapon->GetItem()->IsValid() )
return false;
int iClass = GetPlayerClass()->GetClassIndex();
if ( iClass == TF_CLASS_SPY && ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.GetPercentInvisible() > 0 ) )
return false;
if ( IsTaunting() )
return false;
if ( !IsAlive() )
return false;
// There's a rare case that the player doesn't have an active weapon. This shouldn't happen.
// If you hit this assert, figure out and fix WHY the player doesn't have a weapon.
Assert( GetActiveTFWeapon() );
if ( !GetActiveTFWeapon() || !GetActiveTFWeapon()->CanPickupOtherWeapon() )
return false;
int iItemSlot = pWeapon->GetItem()->GetStaticData()->GetLoadoutSlot( iClass );
CBaseEntity *pOwnedWeaponToDrop = GetEntityForLoadoutSlot( iItemSlot );
return pOwnedWeaponToDrop && pWeapon->GetItem()->GetStaticData()->CanBeUsedByClass( iClass ) && IsValidPickupWeaponSlot( iItemSlot );
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if player is in range to pick up this weapon
//-----------------------------------------------------------------------------
CTFDroppedWeapon* CTFPlayer::GetDroppedWeaponInRange()
{
// Check to see if a building we own is in front of us.
Vector vecForward;
AngleVectors( EyeAngles(), &vecForward );
trace_t tr;
UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * TF_WEAPON_PICKUP_RANGE, MASK_SOLID | CONTENTS_DEBRIS, this, COLLISION_GROUP_NONE, &tr );
CTFDroppedWeapon *pDroppedWeapon = dynamic_cast< CTFDroppedWeapon * >( tr.m_pEnt );
if ( !pDroppedWeapon )
return NULL;
if ( !CanPickupDroppedWeapon( pDroppedWeapon ) )
return NULL;
// too far?
if ( EyePosition().DistToSqr( pDroppedWeapon->GetAbsOrigin() ) > Square( TF_WEAPON_PICKUP_RANGE ) )
return NULL;
return pDroppedWeapon;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if player is inspecting
//-----------------------------------------------------------------------------
bool CTFPlayer::IsInspecting() const
{
return m_flInspectTime != 0.f && gpGlobals->curtime - m_flInspectTime > 0.2f;
}