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
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 ¶ms ) |
|
{ |
|
BaseClass::ModifyEmitSoundParams( params ); |
|
|
|
CTFWeaponBase *pWeapon = GetActiveTFWeapon(); |
|
if ( pWeapon ) |
|
{ |
|
pWeapon->ModifyEmitSoundParams( params ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanAttack( int iCanAttackFlags ) |
|
{ |
|
CTFGameRules *pRules = TFGameRules(); |
|
|
|
Assert( pRules ); |
|
|
|
if ( m_Shared.HasPasstimeBall() ) |
|
{ |
|
// Always allow throwing the ball. |
|
return true; |
|
} |
|
|
|
if ( ( m_Shared.GetStealthNoAttackExpireTime() > gpGlobals->curtime && !m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF ) ) || m_Shared.InCond( TF_COND_STEALTHED ) ) |
|
{ |
|
if ( !( iCanAttackFlags & TF_CAN_ATTACK_FLAG_GRAPPLINGHOOK ) ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
HintMessage( HINT_CANNOT_ATTACK_WHILE_CLOAKED, true, true ); |
|
#endif |
|
return false; |
|
} |
|
} |
|
|
|
if ( m_Shared.IsFeignDeathReady() ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
HintMessage( HINT_CANNOT_ATTACK_WHILE_FEIGN_ARMED, true, true ); |
|
#endif |
|
|
|
return false; |
|
} |
|
|
|
if ( IsTaunting() ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_PHASE ) == true ) |
|
return false; |
|
|
|
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Weapons can call this on secondary attack and it will link to the class |
|
// ability |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::DoClassSpecialSkill( void ) |
|
{ |
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
return false; |
|
|
|
bool bDoSkill = false; |
|
|
|
// powerup charge activation has higher priority than any class special skill |
|
if ( m_Shared.IsRuneCharged() && GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRAPPLINGHOOK ) |
|
{ |
|
#ifdef GAME_DLL |
|
CTFGrapplingHook *pHook = static_cast<CTFGrapplingHook*>( GetActiveTFWeapon() ); |
|
if ( pHook ) |
|
{ |
|
pHook->ActivateRune(); |
|
} |
|
#endif // GAME_DLL |
|
return true; |
|
} |
|
|
|
switch( GetPlayerClass()->GetClassIndex() ) |
|
{ |
|
case TF_CLASS_SPY: |
|
{ |
|
if ( !m_Shared.InCond( TF_COND_TAUNTING ) ) |
|
{ |
|
if ( m_Shared.m_flStealthNextChangeTime <= gpGlobals->curtime ) |
|
{ |
|
// Feign death if we have the right equipment mod. |
|
CTFWeaponInvis* pInvisWatch = static_cast<CTFWeaponInvis*>( Weapon_OwnsThisID( TF_WEAPON_INVIS ) ); |
|
if ( pInvisWatch ) |
|
{ |
|
pInvisWatch->ActivateInvisibilityWatch(); |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case TF_CLASS_DEMOMAN: |
|
if ( !m_Shared.HasPasstimeBall() ) |
|
{ |
|
CTFPipebombLauncher *pPipebombLauncher = static_cast<CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) ); |
|
if ( pPipebombLauncher ) |
|
{ |
|
pPipebombLauncher->SecondaryAttack(); |
|
} |
|
else |
|
{ |
|
CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( this ); |
|
if ( pWearableShield ) |
|
{ |
|
pWearableShield->DoSpecialAction( this ); |
|
break; |
|
} |
|
} |
|
} |
|
bDoSkill = true; |
|
break; |
|
case TF_CLASS_ENGINEER: |
|
if ( !m_Shared.HasPasstimeBall() ) |
|
{ |
|
bDoSkill = TryToPickupBuilding(); |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return bDoSkill; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::EndClassSpecialSkill( void ) |
|
{ |
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
switch( GetPlayerClass()->GetClassIndex() ) |
|
{ |
|
case TF_CLASS_DEMOMAN: |
|
{ |
|
CTFPipebombLauncher *pPipebombLauncher = static_cast<CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) ); |
|
if ( !pPipebombLauncher ) |
|
{ |
|
CTFWearableDemoShield *pWearableShield = GetEquippedDemoShield( this ); |
|
if ( pWearableShield ) |
|
{ |
|
pWearableShield->EndSpecialAction( this ); |
|
break; |
|
} |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanPickupBuilding( CBaseObject *pPickupObject ) |
|
{ |
|
if ( !pPickupObject ) |
|
return false; |
|
|
|
if ( pPickupObject->IsBuilding() ) |
|
return false; |
|
|
|
if ( pPickupObject->IsUpgrading() ) |
|
return false; |
|
|
|
if ( pPickupObject->HasSapper() ) |
|
return false; |
|
|
|
if ( pPickupObject->IsPlasmaDisabled() ) |
|
return false; |
|
|
|
// If we were recently carried & placed we may still be upgrading up to our old level. |
|
if ( pPickupObject->GetUpgradeLevel() != pPickupObject->GetHighestUpgradeLevel() ) |
|
return false; |
|
|
|
if ( m_Shared.IsCarryingObject() ) |
|
return false; |
|
|
|
if ( m_Shared.IsLoserStateStunned() || m_Shared.IsControlStunned() ) |
|
return false; |
|
|
|
if ( m_Shared.IsLoser() ) |
|
return false; |
|
|
|
if ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING && TFGameRules()->State_Get() != GR_STATE_STALEMATE && TFGameRules()->State_Get() != GR_STATE_BETWEEN_RNDS ) |
|
return false; |
|
|
|
// don't allow to pick up building while grappling hook |
|
if ( m_Shared.InCond( TF_COND_GRAPPLINGHOOK ) ) |
|
return false; |
|
|
|
// There's ammo in the clip... no switching away! |
|
if ( GetActiveTFWeapon() && GetActiveTFWeapon()->AutoFiresFullClip() && GetActiveTFWeapon()->Clip1() > 0 ) |
|
return false; |
|
|
|
// Knockout powerup restricts user to melee only, so cannot equip other items such as building pickups |
|
if ( m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT ) |
|
// ClientPrint( this, HUD_PRINTCENTER, "#TF_Powerup_Pickup_Deny" ); |
|
{ |
|
ClientPrint( this, HUD_PRINTCENTER, "You can't pickup buildings while holding the KNOCKOUT powerup" ); |
|
return false; |
|
} |
|
|
|
|
|
// Check it's within range |
|
int nPickUpRangeSq = TF_BUILDING_PICKUP_RANGE * TF_BUILDING_PICKUP_RANGE; |
|
int iIncreasedRangeCost = 0; |
|
int nSqrDist = (EyePosition() - pPickupObject->GetAbsOrigin()).LengthSqr(); |
|
|
|
// Extra range only works with primary weapon |
|
CTFWeaponBase * pWeapon = GetActiveTFWeapon(); |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iIncreasedRangeCost, building_teleporting_pickup ); |
|
if ( iIncreasedRangeCost != 0 ) |
|
{ |
|
// False on deadzone |
|
if ( nSqrDist > nPickUpRangeSq && nSqrDist < TF_BUILDING_RESCUE_MIN_RANGE_SQ ) |
|
return false; |
|
if ( nSqrDist >= TF_BUILDING_RESCUE_MIN_RANGE_SQ && GetAmmoCount( TF_AMMO_METAL ) < iIncreasedRangeCost ) |
|
return false; |
|
return true; |
|
} |
|
else if ( nSqrDist > nPickUpRangeSq ) |
|
return false; |
|
|
|
if ( TFGameRules()->IsInTraining() ) |
|
{ |
|
ConVarRef training_can_pickup_sentry( "training_can_pickup_sentry" ); |
|
ConVarRef training_can_pickup_dispenser( "training_can_pickup_dispenser" ); |
|
ConVarRef training_can_pickup_tele_entrance( "training_can_pickup_tele_entrance" ); |
|
ConVarRef training_can_pickup_tele_exit( "training_can_pickup_tele_exit" ); |
|
switch ( pPickupObject->GetType() ) |
|
{ |
|
case OBJ_DISPENSER: |
|
return training_can_pickup_dispenser.GetBool(); |
|
case OBJ_TELEPORTER: |
|
return pPickupObject->GetObjectMode() == MODE_TELEPORTER_ENTRANCE ? training_can_pickup_tele_entrance.GetBool() : training_can_pickup_tele_exit.GetBool(); |
|
case OBJ_SENTRYGUN: |
|
return training_can_pickup_sentry.GetBool(); |
|
} // switch |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::TryToPickupBuilding() |
|
{ |
|
if ( m_Shared.IsCarryingObject() ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_TAUNTING ) ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_MELEE_ONLY ) ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_SWIMMING_CURSE ) ) |
|
return false; |
|
|
|
#ifdef GAME_DLL |
|
if ( m_bIsTeleportingUsingEurekaEffect ) |
|
return false; |
|
|
|
int iCannotPickUpBuildings = 0; |
|
CALL_ATTRIB_HOOK_INT( iCannotPickUpBuildings, cannot_pick_up_buildings ); |
|
if ( iCannotPickUpBuildings ) |
|
{ |
|
return false; |
|
} |
|
#endif |
|
|
|
// Check to see if a building we own is in front of us. |
|
Vector vecForward; |
|
AngleVectors( EyeAngles(), &vecForward, NULL, NULL ); |
|
|
|
int iPickUpRange = TF_BUILDING_PICKUP_RANGE; |
|
int iIncreasedRangeCost = 0; |
|
CTFWeaponBase * pWeapon = GetActiveTFWeapon(); |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iIncreasedRangeCost, building_teleporting_pickup ); |
|
if ( iIncreasedRangeCost != 0 ) |
|
{ |
|
iPickUpRange = TF_BUILDING_RESCUE_MAX_RANGE; |
|
} |
|
|
|
// Create a ray a see if any of my objects touch it |
|
Ray_t ray; |
|
ray.Init( EyePosition(), EyePosition() + vecForward * iPickUpRange ); |
|
|
|
CBulletPenetrateEnum ePickupPenetrate( &ray, this, TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS, false ); |
|
enginetrace->EnumerateEntities( ray, false, &ePickupPenetrate ); |
|
|
|
CBaseObject *pPickupObject = NULL; |
|
float flCurrDistanceSq = iPickUpRange * iPickUpRange; |
|
|
|
for ( int i=0; i<GetObjectCount(); i++ ) |
|
{ |
|
CBaseObject *pObj = GetObject(i); |
|
if ( !pObj ) |
|
continue; |
|
|
|
float flDistToObjSq = ( pObj->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); |
|
if ( flDistToObjSq > flCurrDistanceSq ) |
|
continue; |
|
|
|
FOR_EACH_VEC( ePickupPenetrate.m_Targets, iTarget ) |
|
{ |
|
if ( ePickupPenetrate.m_Targets[iTarget].pTarget == pObj ) |
|
{ |
|
CTargetOnlyFilter penetrateFilter( this, pObj ); |
|
trace_t pTraceToUse; |
|
UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * iPickUpRange, ( MASK_SOLID | CONTENTS_HITBOX ), &penetrateFilter, &pTraceToUse ); |
|
if ( pTraceToUse.m_pEnt == pObj ) |
|
{ |
|
pPickupObject = pObj; |
|
flCurrDistanceSq = flDistToObjSq; |
|
break; |
|
} |
|
} |
|
if ( ePickupPenetrate.m_Targets[iTarget].pTarget->IsWorld() ) |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( !CanPickupBuilding(pPickupObject) ) |
|
{ |
|
if ( pPickupObject ) |
|
{ |
|
CSingleUserRecipientFilter filter( this ); |
|
EmitSound( filter, entindex(), "Player.UseDeny", NULL, 0.0f ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
|
|
return (bool) pPickupObject; |
|
|
|
#elif GAME_DLL |
|
|
|
if ( pPickupObject ) |
|
{ |
|
// remove rage for long range |
|
if ( iIncreasedRangeCost ) |
|
{ |
|
int nSqrDist = (EyePosition() - pPickupObject->GetAbsOrigin()).LengthSqr(); |
|
if ( nSqrDist > TF_BUILDING_RESCUE_MIN_RANGE_SQ ) |
|
{ |
|
RemoveAmmo( iIncreasedRangeCost, TF_AMMO_METAL ); |
|
|
|
// Particles |
|
// Spawn a railgun |
|
Vector origin = pPickupObject->GetAbsOrigin(); |
|
CPVSFilter filter( origin ); |
|
|
|
const char *pRailParticleName = GetTeamNumber() == TF_TEAM_BLUE ? "dxhr_sniper_rail_blue" : "dxhr_sniper_rail_red"; |
|
const char *pTeleParticleName = GetTeamNumber() == TF_TEAM_BLUE ? "teleported_blue" : "teleported_red"; |
|
|
|
TE_TFParticleEffect( filter, 0.0, pTeleParticleName, origin, vec3_angle ); |
|
|
|
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, pPickupObject->GetAbsOrigin() + Vector(0,0,32) }; |
|
TE_TFParticleEffectComplex( filter, 0.0f, pRailParticleName, GetAbsOrigin() + Vector(0,0,32), QAngle( 0, 0, 0 ), NULL, &controlPoint ); |
|
|
|
// Play Sounds |
|
pPickupObject->EmitSound( "Building_Teleporter.Send" ); |
|
EmitSound( "Building_Teleporter.Receive" ); |
|
} |
|
} |
|
|
|
pPickupObject->MakeCarriedObject( this ); |
|
|
|
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>(Weapon_OwnsThisID( TF_WEAPON_BUILDER )); |
|
if ( pBuilder ) |
|
{ |
|
if ( GetActiveTFWeapon() == pBuilder ) |
|
SetActiveWeapon( NULL ); |
|
|
|
Weapon_Switch( pBuilder ); |
|
pBuilder->m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; |
|
} |
|
|
|
SpeakConceptIfAllowed( MP_CONCEPT_PICKUP_BUILDING, pPickupObject->GetResponseRulesModifier() ); |
|
|
|
m_flCommentOnCarrying = gpGlobals->curtime + random->RandomFloat( 6.f, 12.f ); |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
|
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanGoInvisible( bool bAllowWhileCarryingFlag ) |
|
{ |
|
// The "flag" in Player Destruction doesn't block cloak |
|
ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION }; |
|
if ( !bAllowWhileCarryingFlag && ( HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) || m_Shared.HasPasstimeBall() ) ) |
|
{ |
|
HintMessage( HINT_CANNOT_CLOAK_WITH_FLAG ); |
|
return false; |
|
} |
|
|
|
CTFGameRules *pRules = TFGameRules(); |
|
|
|
Assert( pRules ); |
|
|
|
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanStartPhase( void ) |
|
{ |
|
if ( HasTheFlag() || m_Shared.HasPasstimeBall() ) |
|
{ |
|
HintMessage( HINT_CANNOT_PHASE_WITH_FLAG ); |
|
return false; |
|
} |
|
|
|
CTFGameRules *pRules = TFGameRules(); |
|
|
|
Assert( pRules ); |
|
|
|
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//ConVar testclassviewheight( "testclassviewheight", "0", FCVAR_DEVELOPMENTONLY ); |
|
//Vector vecTestViewHeight(0,0,0); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return class-specific standing eye height |
|
//----------------------------------------------------------------------------- |
|
Vector CTFPlayer::GetClassEyeHeight( void ) |
|
{ |
|
CTFPlayerClass *pClass = GetPlayerClass(); |
|
|
|
if ( !pClass ) |
|
return VEC_VIEW_SCALED( this ); |
|
|
|
//if ( testclassviewheight.GetFloat() > 0 ) |
|
//{ |
|
// vecTestViewHeight.z = test.GetFloat(); |
|
// return vecTestViewHeight; |
|
//} |
|
|
|
int iClassIndex = pClass->GetClassIndex(); |
|
|
|
if ( iClassIndex < TF_FIRST_NORMAL_CLASS || iClassIndex > TF_LAST_NORMAL_CLASS ) |
|
return VEC_VIEW_SCALED( this ); |
|
|
|
return g_TFClassViewVectors[pClass->GetClassIndex()] * GetModelScale(); |
|
} |
|
|
|
|
|
CTFWeaponBase *CTFPlayer::Weapon_OwnsThisID( int iWeaponID ) const |
|
{ |
|
for (int i = 0;i < WeaponCount(); i++) |
|
{ |
|
CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon( i ); |
|
|
|
if ( pWpn == NULL ) |
|
continue; |
|
|
|
if ( pWpn->GetWeaponID() == iWeaponID ) |
|
{ |
|
return pWpn; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
CTFWeaponBase *CTFPlayer::Weapon_GetWeaponByType( int iType ) |
|
{ |
|
for (int i = 0;i < WeaponCount(); i++) |
|
{ |
|
CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon( i ); |
|
|
|
if ( pWpn == NULL ) |
|
continue; |
|
|
|
int iWeaponRole = pWpn->GetTFWpnData().m_iWeaponType; |
|
|
|
if ( iWeaponRole == iType ) |
|
{ |
|
return pWpn; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
bool CTFPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) |
|
{ |
|
bool bCanSwitch = BaseClass::Weapon_CanSwitchTo( pWeapon ); |
|
|
|
if ( bCanSwitch ) |
|
{ |
|
if ( GetActiveTFWeapon() ) |
|
{ |
|
// There's ammo in the clip while auto firing... no switching away! |
|
if ( GetActiveTFWeapon()->AutoFiresFullClip() && GetActiveTFWeapon()->Clip1() > 0 ) |
|
return false; |
|
} |
|
|
|
if ( m_Shared.IsCarryingObject() && (GetPlayerClass()->GetClassIndex() == TF_CLASS_ENGINEER) ) |
|
{ |
|
CTFWeaponBase *pTFWeapon = dynamic_cast<CTFWeaponBase*>( pWeapon ); |
|
if ( pTFWeapon && (pTFWeapon->GetWeaponID() != TF_WEAPON_BUILDER) ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
// prevents script exploits, like switching to the minigun while eating a sandvich |
|
if ( IsTaunting() && tf_allow_taunt_switch.GetInt() == 0 ) |
|
{ |
|
return false; |
|
} |
|
|
|
int iDisableWeaponSwitch = 0; |
|
CALL_ATTRIB_HOOK_INT( iDisableWeaponSwitch, disable_weapon_switch ); |
|
if ( iDisableWeaponSwitch != 0 ) |
|
return false; |
|
} |
|
|
|
return bCanSwitch; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gives the player an opportunity to abort a double jump. |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanAirDash( void ) const |
|
{ |
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_SPEED_BOOST ) ) |
|
return true; |
|
|
|
bool bScout = GetPlayerClass()->IsClass( TF_CLASS_SCOUT ); |
|
if ( !bScout ) |
|
return false; |
|
|
|
if ( m_Shared.InCond( TF_COND_SODAPOPPER_HYPE ) ) |
|
{ |
|
if ( m_Shared.GetAirDash() < 5 ) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
int iDashCount = tf_scout_air_dash_count.GetInt(); |
|
CALL_ATTRIB_HOOK_INT( iDashCount, air_dash_count ); |
|
if ( m_Shared.GetAirDash() >= iDashCount ) |
|
return false; |
|
|
|
int iNoAirDash = 0; |
|
CALL_ATTRIB_HOOK_INT( iNoAirDash, set_scout_doublejump_disabled ); |
|
if ( 1 == iNoAirDash ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Should be immune to Jarate and Mad Milk? |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanGetWet( void ) const |
|
{ |
|
int iWetImmune = 0; |
|
CALL_ATTRIB_HOOK_INT( iWetImmune, wet_immunity ); |
|
|
|
return iWetImmune ? false : true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove disguise |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayer::RemoveDisguise( void ) |
|
{ |
|
// remove quickly |
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) ) |
|
{ |
|
m_Shared.RemoveDisguise(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::RemoveDisguiseWeapon( void ) |
|
{ |
|
#ifdef GAME_DLL |
|
if ( m_hDisguiseWeapon ) |
|
{ |
|
m_hDisguiseWeapon->Drop( Vector(0,0,0) ); |
|
m_hDisguiseWeapon = NULL; |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanDisguise( void ) |
|
{ |
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY ) |
|
return false; |
|
|
|
bool bHasFlag = false; |
|
|
|
ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION }; |
|
if ( HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) ) |
|
{ |
|
bHasFlag = true; |
|
} |
|
|
|
if ( bHasFlag || m_Shared.HasPasstimeBall() ) |
|
{ |
|
HintMessage( HINT_CANNOT_DISGUISE_WITH_FLAG ); |
|
return false; |
|
} |
|
|
|
if ( !Weapon_GetWeaponByType( TF_WPN_TYPE_PDA ) ) |
|
return false; |
|
|
|
int iCannotDisguise = 0; |
|
CALL_ATTRIB_HOOK_INT( iCannotDisguise, set_cannot_disguise ); |
|
if ( iCannotDisguise == 1 ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: 'Your Eternal Reward' handling for Disguise testing |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanDisguise_OnKill( void ) |
|
{ |
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SPY ) |
|
return false; |
|
|
|
CTFKnife *pKnife = dynamic_cast<CTFKnife *>( Weapon_GetWeaponByType( TF_WPN_TYPE_MELEE ) ); |
|
if ( pKnife && pKnife->GetKnifeType() != KNIFE_DISGUISE_ONKILL ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CTFPlayer::GetMaxAmmo( int iAmmoIndex, int iClassIndex /*= -1*/ ) |
|
{ |
|
int iMax = ( iClassIndex == -1 ) ? m_PlayerClass.GetData()->m_aAmmoMax[iAmmoIndex] : GetPlayerClassData( iClassIndex )->m_aAmmoMax[iAmmoIndex]; |
|
if ( iAmmoIndex == TF_AMMO_PRIMARY ) |
|
{ |
|
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_primary ); |
|
} |
|
else if ( iAmmoIndex == TF_AMMO_SECONDARY ) |
|
{ |
|
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_secondary ); |
|
} |
|
else if ( iAmmoIndex == TF_AMMO_METAL ) |
|
{ |
|
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_metal ); |
|
} |
|
else if ( iAmmoIndex == TF_AMMO_GRENADES1 ) |
|
{ |
|
CALL_ATTRIB_HOOK_INT( iMax, mult_maxammo_grenades1 ); |
|
} |
|
else if ( iAmmoIndex == TF_AMMO_GRENADES3 ) |
|
{ |
|
// All classes by default can carry a max of 1 "Grenade3" which is being used as ACTIONSLOT Throwables |
|
iMax = 1; |
|
} |
|
|
|
// Haste Powerup Rune adds multiplier to Max Ammo |
|
if ( m_Shared.GetCarryingRuneType() == RUNE_HASTE ) |
|
{ |
|
iMax *= 2.0f; |
|
} |
|
|
|
return iMax; |
|
} |
|
|
|
bool CTFPlayer::IsMiniBoss( void ) const |
|
{ |
|
return m_bIsMiniBoss; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CTFPlayer::MedicGetHealTarget( void ) |
|
{ |
|
if ( IsPlayerClass(TF_CLASS_MEDIC) ) |
|
{ |
|
CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( GetActiveWeapon() ); |
|
|
|
if ( pWeapon ) |
|
return pWeapon->GetHealTarget(); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CTFPlayer::MedicGetChargeLevel( CTFWeaponBase **pRetMedigun ) |
|
{ |
|
if ( IsPlayerClass(TF_CLASS_MEDIC) ) |
|
{ |
|
CTFWeaponBase *pWpn = ( CTFWeaponBase *)Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ); |
|
|
|
if ( pWpn == NULL ) |
|
return 0; |
|
|
|
CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pWpn ); |
|
|
|
if ( pRetMedigun ) |
|
{ |
|
*pRetMedigun = pMedigun; |
|
} |
|
|
|
if ( pMedigun ) |
|
return pMedigun->GetChargeLevel(); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CTFPlayer::GetNumActivePipebombs( void ) |
|
{ |
|
if ( IsPlayerClass( TF_CLASS_DEMOMAN ) ) |
|
{ |
|
CTFPipebombLauncher *pWeapon = dynamic_cast < CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) ); |
|
|
|
if ( pWeapon ) |
|
{ |
|
return pWeapon->GetPipeBombCount(); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fills out the vector with the sets that are currently active on this player |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayer::GetActiveSets( CUtlVector<const CEconItemSetDefinition *> *pItemSets ) |
|
{ |
|
pItemSets->Purge(); |
|
|
|
CSteamID steamIDForPlayer; |
|
GetSteamID( &steamIDForPlayer ); |
|
|
|
TFInventoryManager()->GetActiveSets( pItemSets, steamIDForPlayer, GetPlayerClass()->GetClassIndex() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanMoveDuringTaunt() |
|
{ |
|
|
|
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() ) |
|
{ |
|
if ( ( TFGameRules()->GetRoundRestartTime() > -1.f ) && ( (int)( TFGameRules()->GetRoundRestartTime() - gpGlobals->curtime ) <= mp_tournament_readymode_countdown.GetInt() ) ) |
|
return false; |
|
|
|
if ( TFGameRules()->PlayersAreOnMatchSummaryStage() ) |
|
return false; |
|
} |
|
|
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
return true; |
|
|
|
if ( m_Shared.InCond( TF_COND_TAUNTING ) || m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) ) |
|
{ |
|
#ifdef GAME_DLL |
|
if ( tf_allow_sliding_taunt.GetBool() ) |
|
{ |
|
return true; |
|
} |
|
#endif // GAME_DLL |
|
|
|
#ifdef STAGING_ONLY |
|
if ( tf_force_allow_move_during_taunt.GetBool() ) |
|
{ |
|
return true; |
|
} |
|
#endif // STAGING_ONLY |
|
|
|
if ( m_bAllowMoveDuringTaunt ) |
|
{ |
|
return true; |
|
} |
|
|
|
if ( IsReadyToTauntWithPartner() || CTFPlayerSharedUtils::ConceptIsPartnerTaunt( m_Shared.m_iTauntConcept ) ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::ShouldStopTaunting() |
|
{ |
|
// stop taunt if we're under water |
|
return GetWaterLevel() > WL_Waist; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
#if defined( CLIENT_DLL ) && defined( STAGING_ONLY ) |
|
static ConVar tf_tauntcam_dist_override( "tf_tauntcam_dist_override", "0", FCVAR_CHEAT ); |
|
static ConVar tf_tauntcam_distup_override( "tf_tauntcam_distup_override", "0", FCVAR_CHEAT ); |
|
#endif // CLIENT_DLL && STAGING_ONLY |
|
void CTFPlayer::ParseSharedTauntDataFromEconItemView( CEconItemView *pEconItemView ) |
|
{ |
|
static CSchemaAttributeDefHandle pAttrDef_TauntForceMoveForward( "taunt force move forward" ); |
|
attrib_value_t attrTauntForceMoveForward = 0; |
|
pEconItemView->FindAttribute( pAttrDef_TauntForceMoveForward, &attrTauntForceMoveForward ); |
|
m_bTauntForceMoveForward = attrTauntForceMoveForward != 0; |
|
|
|
static CSchemaAttributeDefHandle pAttrDef_TauntMoveSpeed( "taunt move speed" ); |
|
attrib_value_t attrTauntMoveSpeed = 0; |
|
pEconItemView->FindAttribute( pAttrDef_TauntMoveSpeed, &attrTauntMoveSpeed ); |
|
m_flTauntForceMoveForwardSpeed = (float&)attrTauntMoveSpeed; |
|
|
|
static CSchemaAttributeDefHandle pAttrDef_TauntMoveAccelerationTime( "taunt move acceleration time" ); |
|
attrib_value_t attrTauntMoveAccelerationTime = 0; |
|
pEconItemView->FindAttribute( pAttrDef_TauntMoveAccelerationTime, &attrTauntMoveAccelerationTime ); |
|
m_flTauntMoveAccelerationTime = (float&)attrTauntMoveAccelerationTime; |
|
|
|
static CSchemaAttributeDefHandle pAttrDef_TauntTurnSpeed( "taunt turn speed" ); |
|
attrib_value_t attrTauntTurnSpeed = 0; |
|
pEconItemView->FindAttribute( pAttrDef_TauntTurnSpeed, &attrTauntTurnSpeed ); |
|
m_flTauntTurnSpeed = (float&)attrTauntTurnSpeed; |
|
|
|
static CSchemaAttributeDefHandle pAttrDef_TauntTurnAccelerationTime( "taunt turn acceleration time" ); |
|
attrib_value_t attrTauntTurnAccelerationTime = 0; |
|
pEconItemView->FindAttribute( pAttrDef_TauntTurnAccelerationTime, &attrTauntTurnAccelerationTime ); |
|
m_flTauntTurnAccelerationTime = (float&)attrTauntTurnAccelerationTime; |
|
|
|
#ifdef CLIENT_DLL |
|
CTFTauntInfo *pTauntInfo = pEconItemView->GetStaticData()->GetTauntData(); |
|
if ( pTauntInfo ) |
|
{ |
|
if ( pTauntInfo->GetCameraDist() != 0 ) |
|
m_flTauntCamTargetDist = pTauntInfo->GetCameraDist(); |
|
|
|
if ( pTauntInfo->GetCameraDistUp() != 0 ) |
|
m_flTauntCamTargetDistUp = pTauntInfo->GetCameraDistUp(); |
|
|
|
#ifdef STAGING_ONLY |
|
if ( tf_tauntcam_dist_override.GetFloat() != 0 ) |
|
m_flTauntCamTargetDist = tf_tauntcam_dist_override.GetFloat(); |
|
|
|
if ( tf_tauntcam_distup_override.GetFloat() != 0 ) |
|
m_flTauntCamTargetDistUp = tf_tauntcam_distup_override.GetFloat(); |
|
#endif // STAGING_ONLY |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayer::SetTauntYaw( float flTauntYaw ) |
|
{ |
|
m_flPrevTauntYaw = m_flTauntYaw; |
|
m_flTauntYaw = flTauntYaw; |
|
|
|
QAngle angle = GetLocalAngles(); |
|
angle.y = flTauntYaw; |
|
SetLocalAngles( angle ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayer::StartBuildingObjectOfType( int iType, int iMode ) |
|
{ |
|
// early out if we can't build this type of object |
|
if ( CanBuild( iType, iMode ) != CB_CAN_BUILD ) |
|
return; |
|
|
|
// Does this type require a specific builder? |
|
CTFWeaponBuilder *pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, iType ); |
|
if ( pBuilder ) |
|
{ |
|
#ifdef GAME_DLL |
|
pBuilder->SetSubType( iType ); |
|
pBuilder->SetObjectMode( iMode ); |
|
|
|
|
|
if ( GetActiveTFWeapon() == pBuilder ) |
|
{ |
|
SetActiveWeapon( NULL ); |
|
} |
|
#endif |
|
|
|
// try to switch to this weapon |
|
Weapon_Switch( pBuilder ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::UpdatePhaseEffects( void ) |
|
{ |
|
|
|
bool bRunning; |
|
float flSpeed = m_pOuter->MaxSpeed(); |
|
flSpeed *= flSpeed; |
|
|
|
CTFPlayer *pPlayer = ToTFPlayer( m_pOuter ); |
|
if ( InCond( TF_COND_SHIELD_CHARGE ) || (pPlayer->GetAbsVelocity().LengthSqr() >= (flSpeed* 0.1f)) ) |
|
{ |
|
bRunning = true; |
|
} |
|
else |
|
{ |
|
bRunning = false; |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
if ( m_pOuter ) |
|
{ |
|
if ( !bRunning && !m_pOuter->m_pPhaseStandingEffect && m_flEnergyDrinkMeter < 100.0f ) |
|
{ |
|
m_pOuter->m_pPhaseStandingEffect = m_pOuter->ParticleProp()->Create( "warp_version", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else if ( bRunning && m_pOuter->m_pPhaseStandingEffect ) |
|
{ |
|
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect ); |
|
m_pOuter->m_pPhaseStandingEffect = NULL; |
|
} |
|
} |
|
#else |
|
|
|
// #ifdef STAGING_ONLY |
|
// if ( !InCond( TF_COND_PHASE ) && !InCond( TF_COND_SHIELD_CHARGE ) && !InCond( TF_COND_SELF_CONC ) ) |
|
// #else |
|
if ( !InCond( TF_COND_PHASE ) && !InCond( TF_COND_SHIELD_CHARGE ) ) |
|
//#endif // STAGING_ONLY |
|
return; |
|
|
|
if ( bRunning ) |
|
{ |
|
if ( !m_bPhaseFXOn ) |
|
{ |
|
AddPhaseEffects(); |
|
} |
|
else |
|
{ |
|
if ( m_flEnergyDrinkMeter <= 10.0f ) |
|
{ |
|
float fAlpha = ( m_flEnergyDrinkMeter / 10 ) * 255; |
|
for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i ) |
|
{ |
|
CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pPhaseTrail[i].Get() ); |
|
|
|
if ( pTempTrail ) |
|
{ |
|
pTempTrail->SetBrightness( int(fAlpha) ); |
|
} |
|
} |
|
} |
|
// #ifdef STAGING_ONLY |
|
// else if ( InCond( TF_COND_SHIELD_CHARGE ) || InCond( TF_COND_SELF_CONC ) ) |
|
// #else |
|
else if ( InCond( TF_COND_SHIELD_CHARGE ) ) |
|
//#endif // STAGING_ONLY |
|
{ |
|
for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i ) |
|
{ |
|
CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pPhaseTrail[i].Get() ); |
|
|
|
if ( pTempTrail ) |
|
{ |
|
pTempTrail->SetBrightness( 0 ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::AddPhaseEffects( void ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
|
|
#else |
|
|
|
const char *pTrailTeamName = ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) ? "effects/beam001_red.vmt" : "effects/beam001_blu.vmt"; |
|
|
|
CSpriteTrail *pTempTrail = NULL; |
|
|
|
pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true ); |
|
pTempTrail->FollowEntity( m_pOuter ); |
|
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone ); |
|
pTempTrail->SetStartWidth( 12 ); |
|
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); |
|
pTempTrail->SetLifeTime( 1 ); |
|
pTempTrail->TurnOn(); |
|
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "back_upper" ) ); |
|
|
|
m_pPhaseTrail[0] = pTempTrail; |
|
|
|
pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true ); |
|
pTempTrail->FollowEntity( m_pOuter ); |
|
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone ); |
|
pTempTrail->SetStartWidth( 16 ); |
|
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); |
|
pTempTrail->SetLifeTime( 1 ); |
|
pTempTrail->TurnOn(); |
|
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "back_lower" ) ); |
|
|
|
m_pPhaseTrail[1] = pTempTrail; |
|
|
|
pTempTrail = CSpriteTrail::SpriteTrailCreate( "effects/beam001_white.vmt", m_pOuter->GetAbsOrigin(), true ); |
|
pTempTrail->FollowEntity( m_pOuter ); |
|
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone ); |
|
pTempTrail->SetStartWidth( 8 ); |
|
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); |
|
pTempTrail->SetLifeTime( 0.5f ); |
|
pTempTrail->TurnOn(); |
|
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "foot_R" ) ); |
|
|
|
m_pPhaseTrail[2] = pTempTrail; |
|
|
|
pTempTrail = CSpriteTrail::SpriteTrailCreate( "effects/beam001_white.vmt", m_pOuter->GetAbsOrigin(), true ); |
|
pTempTrail->FollowEntity( m_pOuter ); |
|
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone ); |
|
pTempTrail->SetStartWidth( 8 ); |
|
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); |
|
pTempTrail->SetLifeTime( 0.5f ); |
|
pTempTrail->TurnOn(); |
|
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "foot_L" ) ); |
|
|
|
m_pPhaseTrail[3] = pTempTrail; |
|
|
|
pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, m_pOuter->GetAbsOrigin(), true ); |
|
pTempTrail->FollowEntity( m_pOuter ); |
|
pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone ); |
|
pTempTrail->SetStartWidth( 8 ); |
|
pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); |
|
pTempTrail->SetLifeTime( 0.5f ); |
|
pTempTrail->TurnOn(); |
|
pTempTrail->SetAttachment( m_pOuter, m_pOuter->LookupAttachment( "hand_L" ) ); |
|
|
|
m_pPhaseTrail[4] = pTempTrail; |
|
|
|
|
|
m_bPhaseFXOn = true; |
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::RemovePhaseEffects( void ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( m_pOuter->m_pPhaseStandingEffect ) |
|
{ |
|
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pPhaseStandingEffect ); |
|
m_pOuter->m_pPhaseStandingEffect = NULL; |
|
} |
|
|
|
#else |
|
for ( int i = 0; i < TF_SCOUT_NUMBEROFPHASEATTACHMENTS; ++i ) |
|
{ |
|
UTIL_Remove(m_pPhaseTrail[i]); |
|
} |
|
m_bPhaseFXOn = false; |
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CTFPlayerShared::GetSequenceForDeath( CBaseAnimating* pRagdoll, bool bBurning, int nCustomDeath ) |
|
{ |
|
if ( !pRagdoll ) |
|
return -1; |
|
|
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) |
|
{ |
|
if ( m_pOuter && ( m_pOuter->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) |
|
return -1; |
|
} |
|
|
|
int iDeathSeq = -1; |
|
// if ( bBurning ) |
|
// { |
|
// iDeathSeq = pRagdoll->LookupSequence( "primary_death_burning" ); |
|
// } |
|
|
|
switch ( nCustomDeath ) |
|
{ |
|
case TF_DMG_CUSTOM_HEADSHOT_DECAPITATION: |
|
case TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING: |
|
case TF_DMG_CUSTOM_DECAPITATION: |
|
case TF_DMG_CUSTOM_HEADSHOT: |
|
iDeathSeq = pRagdoll->LookupSequence( "primary_death_headshot" ); |
|
break; |
|
case TF_DMG_CUSTOM_BACKSTAB: |
|
iDeathSeq = pRagdoll->LookupSequence( "primary_death_backstab" ); |
|
break; |
|
} |
|
|
|
return iDeathSeq; |
|
} |
|
|
|
extern ConVar tf_halloween_kart_dash_speed; |
|
ConVar tf_halloween_kart_slow_turn_accel_speed( "tf_halloween_kart_slow_turn_accel_speed", "200", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_fast_turn_accel_speed( "tf_halloween_kart_fast_turn_accel_speed", "400", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_return_turn_accell( "tf_halloween_kart_return_turn_accell", "200", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_slow_turn_speed( "tf_halloween_kart_slow_turn_speed", "100", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_fast_turn_speed( "tf_halloween_kart_fast_turn_speed", "60", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_turning_curve_peak_position( "tf_halloween_kart_turning_curve_peak_position", "0.5", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_stationary_turn_speed( "tf_halloween_kart_stationary_turn_speed", "50", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_reverse_turn_speed( "tf_halloween_kart_reverse_turn_speed", "50", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_air_turn_scale( "tf_halloween_kart_air_turn_scale", "1.2f", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
|
|
#ifdef CLIENT_DLL |
|
ConVar tf_halloween_kart_pitch( "tf_halloween_kart_pitch", "10", FCVAR_ARCHIVE ); |
|
ConVar tf_halloween_kart_pitch_slow_follow_rate( "tf_halloween_kart_pitch_slow_follow_rate", "0.5", FCVAR_ARCHIVE ); |
|
ConVar tf_halloween_kart_pitch_fast_follow_rate( "tf_halloween_kart_pitch_fast_follow_rate", "2", FCVAR_ARCHIVE ); |
|
#endif // CLIENT_DLL |
|
|
|
void CTFPlayerShared::CreateVehicleMove( float flInputSampleTime, CUserCmd *pCmd ) |
|
{ |
|
const float flSign = pCmd->sidemove == 0.f ? 0.f : Sign( pCmd->sidemove ); |
|
|
|
// Compute target turn speed |
|
const float flVel = m_pOuter->GetAbsVelocity().Length2D(); |
|
const float flNormalizedSpeed = Clamp( flVel / tf_halloween_kart_dash_speed.GetFloat(), 0.0f, 1.0f ); |
|
float flTargetTurnSpeed; |
|
if ( flNormalizedSpeed == 0.f ) |
|
{ |
|
flTargetTurnSpeed = flSign * tf_halloween_kart_stationary_turn_speed.GetFloat(); |
|
} |
|
else if ( Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) < 0 ) |
|
{ |
|
flTargetTurnSpeed = Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) * flSign * tf_halloween_kart_reverse_turn_speed.GetFloat(); |
|
} |
|
else |
|
{ |
|
const float flSmoothCurveVal = SmoothCurve_Tweak( flNormalizedSpeed, tf_halloween_kart_turning_curve_peak_position.GetFloat() ); |
|
flTargetTurnSpeed = Sign( m_pOuter->GetCurrentTauntMoveSpeed() ) * flSign * RemapValClamped( flSmoothCurveVal, 0.f, 1.f, tf_halloween_kart_slow_turn_speed.GetFloat(), tf_halloween_kart_fast_turn_speed.GetFloat() ); |
|
} |
|
|
|
float flTurnAccel = 0.f; |
|
// Compute turn accelleration |
|
if ( flSign == Sign( m_flCurrentTauntTurnSpeed ) ) |
|
{ |
|
flTurnAccel = RemapValClamped( flNormalizedSpeed, 0.f, 1.f, tf_halloween_kart_slow_turn_accel_speed.GetFloat(), tf_halloween_kart_fast_turn_accel_speed.GetFloat() ); |
|
} |
|
else |
|
{ // When not trying to turn, or turning the opposite way you're already turning |
|
// accelerate much faster |
|
flTurnAccel = tf_halloween_kart_return_turn_accell.GetFloat(); |
|
} |
|
|
|
// Turn faster in the air |
|
if ( !(m_pOuter->GetFlags() & FL_ONGROUND) ) |
|
{ |
|
flTurnAccel *= tf_halloween_kart_air_turn_scale.GetFloat(); |
|
} |
|
|
|
// Get actual turn speed |
|
m_flCurrentTauntTurnSpeed = Approach( flTargetTurnSpeed, m_flCurrentTauntTurnSpeed, flTurnAccel * flInputSampleTime ); |
|
|
|
const float flMaxPossibleTurnSpeed = Max( tf_halloween_kart_slow_turn_speed.GetFloat(), tf_halloween_kart_fast_turn_speed.GetFloat() ); |
|
m_flCurrentTauntTurnSpeed = clamp( m_flCurrentTauntTurnSpeed, -flMaxPossibleTurnSpeed, flMaxPossibleTurnSpeed ); |
|
|
|
#ifdef DEBUG |
|
#ifdef CLIENT_DLL |
|
engine->Con_NPrintf( 4, "Turn: %3.2f", m_flCurrentTauntTurnSpeed ); |
|
engine->Con_NPrintf( 5, "TargetTurn: %3.2f", flTargetTurnSpeed ); |
|
engine->Con_NPrintf( 6, "TurnAccell: %3.2f", flTurnAccel ); |
|
#else |
|
engine->Con_NPrintf( 4+3, "Turn: %3.2f", m_flCurrentTauntTurnSpeed ); |
|
engine->Con_NPrintf( 5+3, "TargetTurn: %3.2f", flTargetTurnSpeed ); |
|
engine->Con_NPrintf( 6+3, "TurnAccell: %3.2f", flTurnAccel ); |
|
#endif |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
// Turn! |
|
m_angVehicleMoveAngles -= QAngle( 0.f, m_flCurrentTauntTurnSpeed * flInputSampleTime, 0.f ); |
|
|
|
// We want our pitch to slowly catch up to the pitch of the player's model |
|
const float flTargetPitch = tf_halloween_kart_pitch.GetFloat() + m_pOuter->m_PlayerAnimState->GetRenderAngles()[PITCH]; |
|
const float flStepSpeed = fabs( flTargetPitch - tf_halloween_kart_pitch.GetFloat() ) < fabs( m_angVehicleMovePitchLast - tf_halloween_kart_pitch.GetFloat() ) ? tf_halloween_kart_pitch_fast_follow_rate.GetFloat() : tf_halloween_kart_pitch_slow_follow_rate.GetFloat(); |
|
const float flPitchDiff = fabs( flTargetPitch - m_angVehicleMovePitchLast ); |
|
const float flPitchStep = flPitchDiff * flStepSpeed; |
|
|
|
m_angVehicleMovePitchLast = Approach( flTargetPitch, m_angVehicleMovePitchLast, flPitchStep * gpGlobals->frametime ); |
|
|
|
m_angVehicleMoveAngles[PITCH] = m_angVehicleMovePitchLast; |
|
pCmd->weaponselect = 0; |
|
pCmd->buttons &= IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK | IN_ATTACK2 | IN_FORWARD | IN_BACK | IN_JUMP; |
|
VectorCopy( m_angVehicleMoveAngles, pCmd->viewangles ); |
|
|
|
m_pOuter->SetLocalAngles( m_angVehicleMoveAngles ); |
|
|
|
// Fill out our kart state for the local client |
|
m_pOuter->m_iKartState = 0; |
|
|
|
// Hitting the gas |
|
if ( pCmd->buttons & IN_FORWARD ) |
|
{ |
|
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Driving; |
|
} |
|
else if ( pCmd->buttons & IN_BACK ) // Hitting the brakes |
|
{ |
|
// slowing down |
|
if ( m_pOuter->GetCurrentTauntMoveSpeed() > 0 ) |
|
{ |
|
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Braking; |
|
} |
|
// if we are already stopped, look for new input to start going backwards |
|
else |
|
{ |
|
// check for new input, else do nothing |
|
if ( ( pCmd->buttons & IN_BACK ) |
|
|| m_pOuter->GetCurrentTauntMoveSpeed() < 0 |
|
|| m_pOuter->GetVehicleReverseTime() < gpGlobals->curtime |
|
) |
|
{ |
|
|
|
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Reversing; |
|
} |
|
else |
|
{ |
|
m_pOuter->m_iKartState |= CTFPlayerShared::kKartState_Stopped; |
|
} |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
void CTFPlayerShared::VehicleThink( void ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
m_pOuter->UpdateKartSounds(); |
|
|
|
// Ordered list of effects. Lower on the list has higher prescedence |
|
static WheelEffect_t wheelEffects[] = |
|
{ |
|
WheelEffect_t( 20.f, "kart_dust_trail_red", "kart_dust_trail_blue" ) |
|
}; |
|
|
|
const float flCurrentSpeed = m_pOuter->GetCurrentTauntMoveSpeed(); |
|
const WheelEffect_t* pDesiredEffect = NULL; |
|
|
|
if ( InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
// Go through the effects, and figure out which effect to use |
|
for( int i=0; i < ARRAYSIZE(wheelEffects); ++ i ) |
|
{ |
|
const WheelEffect_t& effect = wheelEffects[ i ]; |
|
if ( effect.m_flMinTriggerSpeed <= flCurrentSpeed ) |
|
{ |
|
pDesiredEffect = &effect; |
|
} |
|
} |
|
} |
|
|
|
|
|
// Start/stop effects if the desired effect is different |
|
if ( pDesiredEffect != m_pWheelEffect ) |
|
{ |
|
C_BaseAnimating * pKart = m_pOuter->GetKart(); |
|
if ( !pKart ) |
|
return; |
|
|
|
m_pWheelEffect = pDesiredEffect; |
|
|
|
// New effect |
|
if ( pDesiredEffect ) |
|
{ |
|
const char *pszEffectName = pDesiredEffect->m_pszParticleName[ m_pOuter->GetTeamNumber() ]; |
|
m_pOuter->CreateKartEffect( pszEffectName ); |
|
} |
|
else // Turn off current effect |
|
{ |
|
m_pOuter->StopKartEffect(); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
float CTFPlayer::GetKartSpeedBoost( void ) |
|
{ |
|
if ( m_flKartNextAvailableBoost < gpGlobals->curtime ) |
|
return 1.0f; |
|
|
|
if ( m_flKartNextAvailableBoost > gpGlobals->curtime + tf_halloween_kart_boost_recharge.GetFloat() ) |
|
return 0.0f; |
|
|
|
// Calculate time |
|
return RemapValClamped( gpGlobals->curtime, m_flKartNextAvailableBoost - tf_halloween_kart_boost_recharge.GetFloat(), m_flKartNextAvailableBoost, 0.0f, 1.0f ); |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayerShared::IsLoser( void ) |
|
{ |
|
if ( tf_always_loser.GetBool() ) |
|
return true; |
|
|
|
if ( !TFGameRules() ) |
|
return false; |
|
|
|
// No loser mode in competitive |
|
if ( TFGameRules()->IsMatchTypeCompetitive() ) |
|
return false; |
|
|
|
if ( TFGameRules()->State_Get() != GR_STATE_TEAM_WIN ) |
|
{ |
|
if ( IsLoserStateStunned() ) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
bool bLoser = TFGameRules()->GetWinningTeam() != m_pOuter->GetTeamNumber(); |
|
|
|
int iClass = m_pOuter->GetPlayerClass()->GetClassIndex(); |
|
|
|
// don't reveal disguised spies |
|
if ( bLoser && iClass == TF_CLASS_SPY ) |
|
{ |
|
if ( InCond( TF_COND_DISGUISED ) && GetDisguiseTeam() == TFGameRules()->GetWinningTeam() ) |
|
{ |
|
bLoser = false; |
|
} |
|
} |
|
|
|
return bLoser; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::RecalculatePlayerBodygroups( void ) |
|
{ |
|
// We have to clear the m_nBody bitfield. |
|
// Leaving bits on from previous player classes can have weird effects |
|
// like if we switch to a class that uses those bits for other things. |
|
m_pOuter->m_nBody = 0; |
|
|
|
// Update our weapon bodygroups that change state purely based on whether they're |
|
// equipped or not. |
|
CTFWeaponBase::UpdateWeaponBodyGroups( m_pOuter, false ); |
|
|
|
// Update our wearable bodygroups. |
|
CEconWearable::UpdateWearableBodyGroups( m_pOuter ); |
|
|
|
// Update our weapon bodygroups for weapons that only change state when active. |
|
CTFWeaponBase::UpdateWeaponBodyGroups( m_pOuter, true ); |
|
} |
|
|
|
#ifdef GAME_DLL |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::GetSpeedWatchersList( CUtlVector<CTFPlayer *> *out_pVecSpeedWatchers ) const |
|
{ |
|
Assert( out_pVecSpeedWatchers != NULL ); |
|
|
|
// Are any medics healing us with the Quick-Fix? |
|
FOR_EACH_VEC( m_aHealers, i ) |
|
{ |
|
CTFPlayer *pTFHealer = ToTFPlayer( m_aHealers[i].pHealer ); |
|
if ( !pTFHealer ) |
|
continue; |
|
|
|
if ( !pTFHealer->GetActiveTFWeapon() || pTFHealer->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_MEDIGUN ) |
|
continue; |
|
|
|
// QuickFix medics heal themselves when deploying an Uber |
|
if ( m_aHealers[i].pHealer == m_pOuter ) |
|
continue; |
|
|
|
out_pVecSpeedWatchers->AddToTail( pTFHealer ); |
|
} |
|
} |
|
#endif // GAME_DLL |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::SetupRageBuffTimer( int iBuffType, int iPulseCount, ERageBuffSlot eBuffSlot ) |
|
{ |
|
m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive = iBuffType; |
|
m_RageBuffSlots[eBuffSlot].m_iBuffPulseCount = iPulseCount; |
|
m_RageBuffSlots[eBuffSlot].m_flNextBuffPulseTime = gpGlobals->curtime + 1.0f; |
|
|
|
PulseRageBuff( eBuffSlot ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::ActivateRageBuff( CBaseEntity *pBuffItem, int iBuffType ) |
|
{ |
|
// Sniper Focus can be activated at all times |
|
if ( GetRageMeter() < 100.f && iBuffType != 6 ) |
|
return; |
|
|
|
Assert( iBuffType > 0 && iBuffType < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff |
|
if ( iBuffType < 0 || iBuffType >= ARRAYSIZE( g_RageBuffTypes ) ) |
|
{ |
|
DevMsg( "Invalid rage buff type %i for entindex %i\n", iBuffType, m_pOuter->entindex() ); |
|
ResetRageSystem(); |
|
return; |
|
} |
|
|
|
int nBuffPulses = g_RageBuffTypes[iBuffType].m_nMaxPulses; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffPulses, mod_buff_duration ); |
|
|
|
#ifdef GAME_DLL |
|
switch ( iBuffType ) |
|
{ |
|
case 1: |
|
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); |
|
break; |
|
case 2: |
|
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_INCOMING ); |
|
break; |
|
case 3: |
|
// FIXME: new sound file for samurai buff? |
|
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); |
|
case 5: |
|
// Pyro Rage |
|
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); |
|
break; |
|
case 6 : |
|
// Sniper Focus |
|
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); |
|
nBuffPulses *= (m_flRageMeter / 100); |
|
break; |
|
} |
|
#endif |
|
|
|
m_bRageDraining = true; |
|
SetupRageBuffTimer( iBuffType, nBuffPulses, kBuffSlot_Rage ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::UpdateRageBuffsAndRage( void ) |
|
{ |
|
// We allow this for all classes to allow item creators and plugin authors to give rage to any class. |
|
|
|
// If we're dead, reset both our rage and our active buffs. |
|
if ( !m_pOuter->IsAlive() ) |
|
{ |
|
ResetRageSystem(); |
|
return; |
|
} |
|
|
|
// Find out whether we've run out of rage. |
|
if ( m_bRageDraining ) |
|
{ |
|
int nBuffType = 0; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffType, set_buff_type ); |
|
|
|
Assert( nBuffType >= 0 && nBuffType < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff |
|
if ( nBuffType < 0 || nBuffType >= ARRAYSIZE( g_RageBuffTypes ) ) |
|
{ |
|
DevMsg( "Invalid rage buff type %i for entindex %i\n", nBuffType, m_pOuter->entindex() ); |
|
ResetRageSystem(); |
|
return; |
|
} |
|
|
|
int nBuffPulses = g_RageBuffTypes[nBuffType].m_nMaxPulses; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, nBuffPulses, mod_buff_duration ); |
|
if ( nBuffPulses > 0 ) |
|
{ |
|
m_flRageMeter -= gpGlobals->frametime * ( 100 / nBuffPulses ); |
|
if ( m_flRageMeter <= 0.0f ) |
|
{ |
|
m_flRageMeter = 0.0f; |
|
m_bRageDraining = false; |
|
|
|
if ( g_SoldierBuffAttributeIDToConditionMap[ nBuffType ] == TF_COND_CRITBOOSTED_RAGE_BUFF ) |
|
{ |
|
// Pyro rage needs a cooldown so that the final crit flames |
|
// don't significantly fill up his next rage meter |
|
m_flNextRageEarnTime = gpGlobals->curtime + tf_flamethrower_flametime.GetFloat() + 0.1f; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
ResetRageSystem(); |
|
} |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
if ( m_flRageMeter >= 100.f ) |
|
{ |
|
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) |
|
{ |
|
if ( CanBuildSpyTraps() ) |
|
{ |
|
// Whenever a spy fills their rage meter, give them a speed boost and grenade (used by traps) |
|
if ( m_pOuter->GetAmmoCount( TF_AMMO_GRENADES1 ) <= m_pOuter->GetMaxAmmo( TF_AMMO_GRENADES1 ) ) |
|
{ |
|
#ifdef GAME_DLL |
|
m_pOuter->GiveAmmo( 1, TF_AMMO_GRENADES1 ); |
|
AddCond( TF_COND_SPEED_BOOST, 5.f ); |
|
#endif // GAME_DLL |
|
m_flRageMeter = 0.f; |
|
} |
|
} |
|
} |
|
} |
|
#endif // STAGING_ONLY |
|
|
|
// Handle pulsing all of our active rage buffs. |
|
for ( int i = 0; i < ARRAYSIZE( m_RageBuffSlots ); i++ ) |
|
{ |
|
RageBuff& rageBuff = m_RageBuffSlots[i]; |
|
|
|
if ( gpGlobals->curtime > rageBuff.m_flNextBuffPulseTime && rageBuff.m_iBuffPulseCount > 0 ) |
|
{ |
|
rageBuff.m_flNextBuffPulseTime += 1.0f; |
|
--rageBuff.m_iBuffPulseCount; |
|
PulseRageBuff( (ERageBuffSlot)i ); |
|
} |
|
} |
|
} |
|
|
|
static const int k_RageBuffType_Sniper = 6; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::SetRageMeter( float val ) |
|
{ |
|
// Allow Sniper to gain rage on kills even when buffed |
|
if ( !InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) && !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) ) |
|
{ |
|
if ( IsRageDraining() ) |
|
return; |
|
|
|
// Can't earn rage until the time is past this delay |
|
if ( val > m_flRageMeter && gpGlobals->curtime < m_flNextRageEarnTime ) |
|
return; |
|
} |
|
|
|
m_flRageMeter = MIN( val, 100.0f ); |
|
|
|
if ( InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) ) |
|
{ |
|
Assert( k_RageBuffType_Sniper > 0 && k_RageBuffType_Sniper < ARRAYSIZE( g_RageBuffTypes ) ); // 0 is valid in the array, but an invalid buff |
|
if ( k_RageBuffType_Sniper < 0 || k_RageBuffType_Sniper >= ARRAYSIZE( g_RageBuffTypes ) ) |
|
return; |
|
|
|
int nBuffPulses = g_RageBuffTypes[k_RageBuffType_Sniper].m_nMaxPulses; |
|
m_bRageDraining = true; |
|
|
|
nBuffPulses *= (m_flRageMeter / 100); |
|
|
|
m_RageBuffSlots[kBuffSlot_Rage].m_iBuffPulseCount = nBuffPulses; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::ModifyRage( float fDelta ) |
|
{ |
|
SetRageMeter( GetRageMeter() + fDelta ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::ResetRageMeter( void ) |
|
{ |
|
m_flRageMeter = 0.0f; |
|
m_flNextRageEarnTime = 0.0f; |
|
|
|
ResetRageBuffs(); |
|
UpdateRageBuffsAndRage(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Apply the buff effect to everyone within a radius around the player. |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::PulseRageBuff( ERageBuffSlot eBuffSlot ) |
|
{ |
|
Assert( m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive != 0 ); |
|
|
|
#ifdef CLIENT_DLL |
|
// if this is not the local player, we don't want to do anything |
|
if ( !m_pOuter->IsLocalPlayer() ) |
|
return; |
|
|
|
int nBuffedFriends = 0; |
|
#else |
|
int nBuffedPlayers = 0; |
|
#endif |
|
|
|
int iSoldierBuffType = m_RageBuffSlots[eBuffSlot].m_iBuffTypeActive; |
|
ETFCond eBuffCond = TF_COND_LAST; |
|
if ( iSoldierBuffType > 0 || iSoldierBuffType <= kSoldierBuffCount ) |
|
{ |
|
eBuffCond = g_SoldierBuffAttributeIDToConditionMap[iSoldierBuffType]; |
|
} |
|
|
|
float fMaxRadius = 450.0f; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pOuter, fMaxRadius, mod_soldier_buff_range ); |
|
const float fMaxRadiusSq = fMaxRadius * fMaxRadius; |
|
|
|
for( int iPlayerIndex=1; iPlayerIndex<=MAX_PLAYERS; ++iPlayerIndex ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); |
|
if ( !pTFPlayer || !pTFPlayer->IsAlive() ) |
|
continue; |
|
|
|
if ( pTFPlayer->GetTeamNumber() != m_pOuter->GetTeamNumber() ) |
|
continue; |
|
|
|
if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && (pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber()) ) |
|
continue; // For now don't give the buff to spies on our team who are disguised as enemies. |
|
|
|
if ( pTFPlayer->m_Shared.IsStealthed() ) |
|
continue; // Don't give the buff to cloaked spies |
|
|
|
Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin(); |
|
if ( vDist.LengthSqr() > fMaxRadiusSq ) |
|
continue; |
|
|
|
#ifdef CLIENT_DLL |
|
if ( pTFPlayer != m_pOuter ) |
|
{ |
|
if ( eBuffCond == TF_COND_CRITBOOSTED_RAGE_BUFF || eBuffCond == TF_COND_SNIPERCHARGE_RAGE_BUFF ) |
|
{ |
|
// Pyro and sniper only buffs themselves |
|
continue; |
|
} |
|
|
|
// this is not the localplayer, are they a friend? |
|
if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() ) |
|
return; |
|
|
|
player_info_t pi; |
|
if ( !engine->GetPlayerInfo( pTFPlayer->entindex(), &pi ) ) |
|
return; |
|
|
|
if ( !pi.friendsID ) |
|
return; |
|
|
|
// check and see if they're on the local player's friends list |
|
CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); |
|
if ( steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) ) |
|
{ |
|
nBuffedFriends++; |
|
} |
|
} |
|
#else |
|
if ( pTFPlayer != m_pOuter ) |
|
{ |
|
if ( eBuffCond == TF_COND_CRITBOOSTED_RAGE_BUFF || eBuffCond == TF_COND_SNIPERCHARGE_RAGE_BUFF ) |
|
{ |
|
// Pyro and sniper only buffs themselves |
|
continue; |
|
} |
|
} |
|
|
|
if ( eBuffCond != TF_COND_LAST ) |
|
{ |
|
pTFPlayer->m_Shared.AddCond( eBuffCond, 1.2f, m_pOuter ); |
|
|
|
nBuffedPlayers++; |
|
|
|
IGameEvent* event = gameeventmanager->CreateEvent( "player_buff" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pTFPlayer->GetUserID() ); |
|
event->SetInt( "buff_owner", m_pOuter->GetUserID() ); |
|
event->SetInt( "buff_type", iSoldierBuffType ); |
|
gameeventmanager->FireEvent( event ); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
if ( nBuffedFriends >= 5 ) |
|
{ |
|
g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_SOLDIER_BUFF_FRIENDS ); |
|
} |
|
#else |
|
// ACHIEVEMENT_TF_MVM_SOLDIER_BUFF_TEAM |
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) |
|
{ |
|
if ( ( m_pOuter->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) && m_pOuter->IsPlayerClass( TF_CLASS_SOLDIER ) ) |
|
{ |
|
if ( nBuffedPlayers >= 5 ) |
|
{ |
|
m_pOuter->AwardAchievement( ACHIEVEMENT_TF_MVM_SOLDIER_BUFF_TEAM ); |
|
} |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::ResetRageSystem( void ) |
|
{ |
|
m_flRageMeter = 0.f; |
|
m_bRageDraining = false; |
|
m_flNextRageEarnTime = 0.f; |
|
|
|
ResetRageBuffs(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::UpdateEnergyDrinkMeter( void ) |
|
{ |
|
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) ) |
|
return; |
|
|
|
bool bIsLocalPlayer = false; |
|
#ifdef CLIENT_DLL |
|
bIsLocalPlayer = m_pOuter->IsLocalPlayer(); |
|
#else |
|
bIsLocalPlayer = true; |
|
#endif |
|
|
|
if ( bIsLocalPlayer ) |
|
{ |
|
if ( IsHypeBuffed() ) |
|
{ |
|
m_flHypeMeter -= gpGlobals->frametime * (m_fEnergyDrinkConsumeRate*0.75f); |
|
if ( m_flHypeMeter <= 0.0f ) |
|
{ |
|
RemoveCond( TF_COND_SODAPOPPER_HYPE ); |
|
} |
|
} |
|
|
|
if ( InCond( TF_COND_PHASE ) || InCond( TF_COND_ENERGY_BUFF ) ) |
|
{ |
|
// Drain the meter |
|
m_flEnergyDrinkMeter -= gpGlobals->frametime * m_fEnergyDrinkConsumeRate; |
|
|
|
// If we've drained the meter, remove the condition |
|
if ( m_flEnergyDrinkMeter <= 0.f ) |
|
{ |
|
if ( InCond( TF_COND_ENERGY_BUFF ) ) |
|
{ |
|
AddCond( TF_COND_MARKEDFORDEATH_SILENT, 2.f ); |
|
} |
|
|
|
RemoveCond( TF_COND_PHASE ); |
|
RemoveCond( TF_COND_ENERGY_BUFF ); |
|
|
|
#ifdef GAME_DLL |
|
m_pOuter->SpeakConceptIfAllowed( MP_CONCEPT_TIRED ); |
|
#endif |
|
} |
|
// Update the effect on phasing only |
|
else if ( InCond( TF_COND_PHASE ) ) |
|
{ |
|
UpdatePhaseEffects(); |
|
} |
|
} |
|
else if ( m_flEnergyDrinkMeter < 100.0f ) |
|
{ |
|
// Regen the meter |
|
m_flEnergyDrinkMeter += gpGlobals->frametime * m_fEnergyDrinkRegenRate; |
|
|
|
CTFLunchBox_Drink *pDrink = static_cast< CTFLunchBox_Drink* >( m_pOuter->Weapon_OwnsThisID( TF_WEAPON_LUNCHBOX ) ); |
|
|
|
if ( pDrink ) |
|
{ |
|
// This is here in case something replenishes grenades |
|
if ( m_flEnergyDrinkMeter < 100.0f && m_pOuter->GetAmmoCount( TF_AMMO_GRENADES2 ) == m_pOuter->GetMaxAmmo( TF_AMMO_GRENADES2 ) ) |
|
{ |
|
m_flEnergyDrinkMeter = 100.0f; |
|
} |
|
} |
|
else if ( m_flEnergyDrinkMeter >= 100.0f ) |
|
{ |
|
m_flEnergyDrinkMeter = 100.0f; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CTFPlayerShared::SetScoutHypeMeter( float val ) |
|
{ |
|
if ( IsHypeBuffed() ) |
|
return; |
|
|
|
m_flHypeMeter = Clamp(val, 0.0f, 100.0f); |
|
//if ( m_flHypeMeter >= 100.f ) |
|
//{ |
|
// if ( m_pOuter->IsPlayerClass( TF_CLASS_SCOUT ) ) |
|
// { |
|
// CTFWeaponBase* pWeapon = m_pOuter->GetActiveTFWeapon(); |
|
// if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SODA_POPPER ) |
|
// { |
|
// AddCond( TF_COND_CRITBOOSTED_HYPE ); |
|
// } |
|
// } |
|
//} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::UpdateCloakMeter( void ) |
|
{ |
|
if ( !m_pOuter->IsPlayerClass( TF_CLASS_SPY ) ) |
|
return; |
|
|
|
if ( InCond( TF_COND_STEALTHED ) ) |
|
{ |
|
if ( m_bMotionCloak ) |
|
{ |
|
// Motion cloak: drain based on our movement rate. |
|
Vector vVel = m_pOuter->GetAbsVelocity(); |
|
float fSpdSqr = vVel.LengthSqr(); |
|
if ( fSpdSqr == 0.f ) |
|
{ |
|
if ( gpGlobals->curtime - m_flLastStealthExposeTime > 1.f ) |
|
{ |
|
m_flCloakMeter += gpGlobals->frametime * m_fCloakRegenRate; |
|
if ( m_flCloakMeter >= 100.0f ) |
|
{ |
|
m_flCloakMeter = 100.0f; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
float fFactor = RemapVal( fSpdSqr, 0, m_pOuter->MaxSpeed()*m_pOuter->MaxSpeed(), 0.f, 1.f ); |
|
if ( fFactor > 1.f ) |
|
{ |
|
fFactor = 1.f; |
|
} |
|
m_flCloakMeter -= gpGlobals->frametime * m_fCloakConsumeRate * fFactor * 1.5f; |
|
if ( m_flCloakMeter < 0.f ) |
|
{ |
|
m_flCloakMeter = 0.f; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Classic cloak: drain at a fixed rate. |
|
m_flCloakMeter -= gpGlobals->frametime * m_fCloakConsumeRate; |
|
} |
|
|
|
if ( m_flCloakMeter <= 0.0f && !m_bMotionCloak) |
|
{ |
|
FadeInvis( 1.0f ); |
|
} |
|
|
|
// Update Debuffs |
|
// Decrease duration if cloaked |
|
#ifdef GAME_DLL |
|
// staging_spy |
|
float flReduction = gpGlobals->frametime * 0.75f; |
|
for ( int i = 0; g_aDebuffConditions[i] != TF_COND_LAST; i++ ) |
|
{ |
|
if ( InCond( g_aDebuffConditions[i] ) ) |
|
{ |
|
if ( m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime != PERMANENT_CONDITION ) |
|
{ |
|
m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime = MAX( m_ConditionData[g_aDebuffConditions[i]].m_flExpireTime - flReduction, 0 ); |
|
} |
|
// Burning and Bleeding and extra timers |
|
if ( g_aDebuffConditions[i] == TF_COND_BURNING ) |
|
{ |
|
// Reduce the duration of this burn |
|
m_flFlameRemoveTime -= flReduction; |
|
} |
|
else if ( g_aDebuffConditions[i] == TF_COND_BLEEDING ) |
|
{ |
|
// Reduce the duration of this bleeding |
|
FOR_EACH_VEC( m_PlayerBleeds, i ) |
|
{ |
|
m_PlayerBleeds[i].flBleedingRemoveTime -= flReduction; |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
} |
|
else |
|
{ |
|
m_flCloakMeter += gpGlobals->frametime * m_fCloakRegenRate; |
|
|
|
if ( m_flCloakMeter >= 100.0f ) |
|
{ |
|
m_flCloakMeter = 100.0f; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Whether we should be doing a radius heal (does not stack with a medigun) |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::Heal_Radius( bool bActive ) |
|
{ |
|
if ( bActive ) |
|
{ |
|
m_bPulseRadiusHeal = true; |
|
} |
|
else |
|
{ |
|
m_bPulseRadiusHeal = false; |
|
|
|
#ifdef GAME_DLL |
|
// Stop any Radius healing |
|
if ( m_iRadiusHealTargets.Count() > 0 ) |
|
{ |
|
for ( int iIndex = 0; iIndex < m_iRadiusHealTargets.Count(); iIndex++ ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( m_iRadiusHealTargets[iIndex] ) ); |
|
if ( !pTFPlayer ) |
|
continue; |
|
|
|
pTFPlayer->m_Shared.StopHealing( m_pOuter ); |
|
} |
|
m_iRadiusHealTargets.RemoveAll(); |
|
} |
|
#endif // GAME_DLL |
|
|
|
#ifdef CLIENT_DLL |
|
if ( m_pOuter && m_pOuter->m_pRadiusHealEffect ) |
|
{ |
|
m_pOuter->ParticleProp()->StopEmission( m_pOuter->m_pRadiusHealEffect ); |
|
} |
|
m_pOuter->m_pRadiusHealEffect = NULL; |
|
#endif // CLIENT_DLL |
|
} |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
ConVar tf_rocket_pack_cooldown( "tf_rocket_pack_cooldown", "1.f", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_rocket_pack_enabled( "tf_rocket_pack_enabled", "0", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
void CTFPlayerShared::DoRocketPack() |
|
{ |
|
if ( !tf_rocket_pack_enabled.GetBool() ) |
|
return; |
|
|
|
if ( !( m_pOuter->m_nButtons & IN_ATTACK3 ) ) |
|
return; |
|
|
|
if ( gpGlobals->curtime < m_flNextRocketPackTime ) |
|
return; |
|
|
|
#ifdef GAME_DLL |
|
// Launch |
|
if ( !InCond( TF_COND_ROCKETPACK ) ) |
|
{ |
|
AddCond( TF_COND_ROCKETPACK ); |
|
StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT ); |
|
} |
|
#endif |
|
|
|
Vector vecDir; |
|
m_pOuter->EyeVectors( &vecDir ); |
|
m_pOuter->SetAbsVelocity( vec3_origin ); |
|
Vector vecFlightDir = -vecDir; |
|
VectorNormalize( vecFlightDir ); |
|
float flForce = 450.f; |
|
|
|
const float flPushScale = ( m_pOuter->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f; // Greater force while airborne |
|
const float flVertPushScale = ( m_pOuter->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f; // Less vertical force while airborne |
|
Vector vecForce = vecFlightDir * -flForce * flPushScale; |
|
vecForce.z += 1.f * flForce * flVertPushScale; |
|
m_pOuter->RemoveFlag( FL_ONGROUND ); |
|
m_pOuter->ApplyAbsVelocityImpulse( vecForce ); |
|
|
|
m_pOuter->EmitSound( "Equipment.RocketPack_Activate" ); |
|
|
|
m_flNextRocketPackTime = gpGlobals->curtime + tf_rocket_pack_cooldown.GetFloat(); |
|
} |
|
#endif // STAGING_ONLY |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Emits an area-of-effect heal around the medic |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::PulseMedicRadiusHeal( void ) |
|
{ |
|
if ( !m_bPulseRadiusHeal ) |
|
{ |
|
#ifdef GAME_DLL |
|
Assert( m_iRadiusHealTargets.Count() == 0 ); |
|
if ( m_iRadiusHealTargets.Count() > 0 ) |
|
{ |
|
// We shouldn't have any heal targets if we aren't pulsing. |
|
Heal_Radius( false ); |
|
} |
|
#endif |
|
return; |
|
} |
|
|
|
// If we're set to heal, make sure it's still valid |
|
if ( !m_pOuter->IsAlive() || ( !m_pOuter->IsPlayerClass( TF_CLASS_MEDIC ) && !InCond( TF_COND_RADIUSHEAL_ON_DAMAGE ) ) ) |
|
{ |
|
Heal_Radius( false ); |
|
return; |
|
} |
|
|
|
#ifdef GAME_DLL |
|
if ( gpGlobals->curtime >= m_flRadiusHealCheckTime ) |
|
{ |
|
for( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); |
|
if ( !pTFPlayer || !pTFPlayer->IsAlive() ) |
|
continue; |
|
|
|
// Don't heal ourselves, unless this is due to radius heal on damage proc |
|
if ( pTFPlayer == m_pOuter && !InCond( TF_COND_RADIUSHEAL_ON_DAMAGE ) ) |
|
continue; |
|
|
|
if ( !pTFPlayer->InSameTeam( m_pOuter ) ) |
|
{ |
|
if ( !pTFPlayer->m_Shared.IsStealthed() && !pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) |
|
continue; |
|
|
|
if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && ( pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber() ) ) |
|
continue; |
|
} |
|
|
|
// Don't heal players with weapon_blocks_healing |
|
CTFWeaponBase *pTFWeapon = pTFPlayer->GetActiveTFWeapon(); |
|
if ( pTFWeapon ) |
|
{ |
|
int iBlockHealing = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iBlockHealing, weapon_blocks_healing ); |
|
if ( iBlockHealing ) |
|
continue; |
|
} |
|
|
|
Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin(); |
|
if ( vDist.LengthSqr() <= 450 * 450 ) |
|
{ |
|
// Ignore players we can't see |
|
trace_t trace; |
|
UTIL_TraceLine( pTFPlayer->WorldSpaceCenter(), m_pOuter->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace ); |
|
|
|
if ( trace.fraction < 1.0f ) |
|
continue; |
|
|
|
// Refresh this condition, which we use to give players a particle effect |
|
pTFPlayer->m_Shared.AddCond( TF_COND_RADIUSHEAL, 1.2f ); |
|
|
|
// Make sure we're not already healing them |
|
if ( m_iRadiusHealTargets.Find( iPlayerIndex ) == m_iRadiusHealTargets.InvalidIndex() ) |
|
{ |
|
m_iRadiusHealTargets.AddToTail( iPlayerIndex ); |
|
pTFPlayer->m_Shared.Heal( m_pOuter, 25, 1, 1 ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_iRadiusHealTargets.Find( iPlayerIndex ) != m_iRadiusHealTargets.InvalidIndex() ) |
|
{ |
|
m_iRadiusHealTargets.FindAndRemove( iPlayerIndex ); |
|
pTFPlayer->m_Shared.StopHealing( m_pOuter ); |
|
} |
|
} |
|
} |
|
|
|
m_flRadiusHealCheckTime = gpGlobals->curtime + 1.0f; |
|
} |
|
#endif // GAME_DLL |
|
|
|
#ifdef CLIENT_DLL |
|
// Radius healer gets an effect to broadcast to others what they're doing |
|
if ( !m_pOuter->m_pRadiusHealEffect ) |
|
{ |
|
const char *pszRadiusHealEffect; |
|
if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) |
|
{ |
|
pszRadiusHealEffect = "medic_healradius_red_buffed"; |
|
} |
|
else |
|
{ |
|
pszRadiusHealEffect = "medic_healradius_blue_buffed"; |
|
} |
|
m_pOuter->m_pRadiusHealEffect = m_pOuter->ParticleProp()->Create( pszRadiusHealEffect, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector( 0, 0, 0 ) ); |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Emits an area-of-effect buff around the King Rune carrier |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::PulseKingRuneBuff( void ) |
|
{ |
|
// Make sure we have the King Powerup and are not invisible |
|
if ( !m_pOuter->IsAlive() || IsStealthed() || GetCarryingRuneType() != RUNE_KING ) |
|
{ |
|
return; |
|
} |
|
|
|
#ifdef GAME_DLL |
|
if ( gpGlobals->curtime >= m_flKingRuneBuffCheckTime ) |
|
{ |
|
m_bKingRuneBuffActive = false; |
|
|
|
// Plague blocks king team buff |
|
if ( !InCond( TF_COND_PLAGUE ) ) |
|
{ |
|
for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex ) |
|
{ |
|
CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); |
|
if ( !pTFPlayer || !pTFPlayer->IsAlive() ) |
|
continue; |
|
|
|
// Ignore players outside of the buff radius |
|
Vector vDist = pTFPlayer->GetAbsOrigin() - m_pOuter->GetAbsOrigin(); |
|
if ( vDist.LengthSqr() >= 768 * 768 ) |
|
continue; |
|
|
|
// If King is the only player, there's no effect |
|
if ( pTFPlayer == m_pOuter ) |
|
continue; |
|
|
|
// Spies who are invisible or disguised as the King's enemy team are ignored |
|
if ( pTFPlayer->m_Shared.IsStealthed() || ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->m_Shared.GetDisguiseTeam() != m_pOuter->GetTeamNumber() ) ) |
|
continue; |
|
|
|
// Enemies - ignore unless they are disguised as the King's team |
|
if ( !pTFPlayer->InSameTeam( m_pOuter ) && !pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) |
|
continue; |
|
|
|
pTFPlayer->m_Shared.AddCond( TF_COND_KING_BUFFED, 1.f ); |
|
m_bKingRuneBuffActive = true; |
|
} |
|
} |
|
|
|
m_flKingRuneBuffCheckTime = gpGlobals->curtime + 0.5f; |
|
} |
|
#endif // GAME_DLL |
|
|
|
#ifdef CLIENT_DLL |
|
// King Rune carrier gets an effect to show that he's buffing someone |
|
if ( m_bKingRuneBuffActive && !InCond( TF_COND_PLAGUE ) ) |
|
{ |
|
if ( !m_pOuter->m_pKingRuneRadiusEffect ) |
|
{ |
|
const char *pszRadiusEffect; |
|
if ( m_pOuter->GetTeamNumber() == TF_TEAM_RED ) |
|
{ |
|
pszRadiusEffect = "powerup_king_red"; |
|
} |
|
else |
|
{ |
|
pszRadiusEffect = "powerup_king_blue"; |
|
} |
|
m_pOuter->m_pKingRuneRadiusEffect = m_pOuter->ParticleProp()->Create( pszRadiusEffect, PATTACH_ABSORIGIN_FOLLOW, NULL, Vector( 0, 0, 0 ) ); |
|
} |
|
} |
|
else |
|
{ |
|
EndKingBuffRadiusEffect(); |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::IncrementRevengeCrits( void ) |
|
{ |
|
SetRevengeCrits( m_iRevengeCrits + 1 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::SetRevengeCrits( int iVal ) |
|
{ |
|
m_iRevengeCrits = clamp( iVal, 0, 35 ); |
|
|
|
CTFWeaponBase *pWeapon = m_pOuter->GetActiveTFWeapon(); |
|
if ( ( pWeapon && pWeapon->CanHaveRevengeCrits() ) ) |
|
{ |
|
if ( m_iRevengeCrits > 0 && !InCond( TF_COND_CRITBOOSTED ) ) |
|
{ |
|
AddCond( TF_COND_CRITBOOSTED ); |
|
} |
|
else if ( m_iRevengeCrits == 0 && InCond( TF_COND_CRITBOOSTED ) ) |
|
{ |
|
RemoveCond( TF_COND_CRITBOOSTED ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::FireGameEvent( IGameEvent *event ) |
|
{ |
|
#ifdef GAME_DLL |
|
const char *eventName = event->GetName(); |
|
|
|
if ( !Q_strcmp( eventName, "player_disconnect" ) ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) ); |
|
if ( pPlayer ) |
|
{ |
|
int iIndex = m_iRadiusHealTargets.Find( pPlayer->entindex() ); |
|
if ( iIndex != m_iRadiusHealTargets.InvalidIndex() ) |
|
{ |
|
m_iRadiusHealTargets.FastRemove( iIndex ); |
|
} |
|
} |
|
} |
|
#endif //GAME_DLL |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFPlayerShared::SetPasstimePassTarget( CTFPlayer *pEnt ) |
|
{ |
|
if ( CBaseEntity *pTarget = m_hPasstimePassTarget ) |
|
{ |
|
CTFPlayer *pPlayerTarget = ToTFPlayer( pTarget ); |
|
if ( pPlayerTarget ) |
|
pPlayerTarget->m_Shared.m_bIsTargetedForPasstimePass = false; |
|
} |
|
|
|
Assert( pEnt != m_pOuter ); |
|
m_hPasstimePassTarget = pEnt; |
|
|
|
if ( CBaseEntity *pTarget = m_hPasstimePassTarget ) |
|
{ |
|
CTFPlayer *pPlayerTarget = ToTFPlayer( pTarget ); |
|
if ( pPlayerTarget ) |
|
pPlayerTarget->m_Shared.m_bIsTargetedForPasstimePass = true; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CTFPlayer *CTFPlayerShared::GetPasstimePassTarget() const { return m_hPasstimePassTarget.Get(); } |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTraceFilterIgnoreTeammatesAndTeamObjects::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); |
|
|
|
if ( pEntity->GetTeamNumber() == m_iIgnoreTeam ) |
|
{ |
|
return false; |
|
} |
|
|
|
CTFPlayer *pPlayer = dynamic_cast<CTFPlayer*>( pEntity ); |
|
if ( pPlayer ) |
|
{ |
|
if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == m_iIgnoreTeam ) |
|
return false; |
|
|
|
if ( pPlayer->m_Shared.IsStealthed() ) |
|
return false; |
|
} |
|
|
|
return BaseClass::ShouldHitEntity( pServerEntity, contentsMask ); |
|
} |
|
|
|
void CTFPlayerShared::SetCarriedObject( CBaseObject* pObj ) |
|
{ |
|
m_bCarryingObject = (pObj != NULL); |
|
m_hCarriedObject.Set( pObj ); |
|
#ifdef GAME_DLL |
|
if ( m_pOuter ) |
|
m_pOuter->TeamFortress_SetSpeed(); |
|
#endif |
|
} |
|
|
|
void localplayerscoring_t::UpdateStats( RoundStats_t& roundStats, CTFPlayer *pPlayer, bool bIsRoundData ) |
|
{ |
|
m_iCaptures = roundStats.m_iStat[TFSTAT_CAPTURES]; |
|
m_iDefenses = roundStats.m_iStat[TFSTAT_DEFENSES]; |
|
|
|
m_iKills = roundStats.m_iStat[TFSTAT_KILLS]; |
|
m_iDeaths = roundStats.m_iStat[TFSTAT_DEATHS]; |
|
m_iSuicides = roundStats.m_iStat[TFSTAT_SUICIDES]; |
|
m_iKillAssists = roundStats.m_iStat[TFSTAT_KILLASSISTS]; |
|
|
|
m_iBuildingsBuilt = roundStats.m_iStat[TFSTAT_BUILDINGSBUILT]; |
|
m_iBuildingsDestroyed = roundStats.m_iStat[TFSTAT_BUILDINGSDESTROYED]; |
|
|
|
m_iHeadshots = roundStats.m_iStat[TFSTAT_HEADSHOTS]; |
|
m_iDominations = roundStats.m_iStat[TFSTAT_DOMINATIONS]; |
|
m_iRevenge = roundStats.m_iStat[TFSTAT_REVENGE]; |
|
m_iInvulns = roundStats.m_iStat[TFSTAT_INVULNS]; |
|
m_iTeleports = roundStats.m_iStat[TFSTAT_TELEPORTS]; |
|
|
|
m_iDamageDone = roundStats.m_iStat[TFSTAT_DAMAGE]; |
|
m_iCrits = roundStats.m_iStat[TFSTAT_CRITS]; |
|
|
|
m_iBackstabs = roundStats.m_iStat[TFSTAT_BACKSTABS]; |
|
|
|
int iHealthPointsHealed = (int) roundStats.m_iStat[TFSTAT_HEALING]; |
|
// send updated healing data every 10 health points, and round off what we send to nearest 10 points |
|
int iHealPointsDelta = abs( iHealthPointsHealed - m_iHealPoints ); |
|
if ( iHealPointsDelta > 10 ) |
|
{ |
|
m_iHealPoints = ( iHealthPointsHealed / 10 ) * 10; |
|
} |
|
m_iBonusPoints = roundStats.m_iStat[TFSTAT_BONUS_POINTS] / TF_SCORE_BONUS_POINT_DIVISOR; |
|
const int nPoints = TFGameRules()->CalcPlayerScore( &roundStats, pPlayer ); |
|
const int nDelta = nPoints - m_iPoints; |
|
m_iPoints = nPoints; |
|
|
|
if ( nDelta > 0 && !bIsRoundData ) |
|
{ |
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_score_changed" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "player", pPlayer->entindex() ); |
|
event->SetInt( "delta", nDelta ); |
|
gameeventmanager->FireEvent( event ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CEconItemView *CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( CTFPlayer *pTFPlayer, int iSlot, CEconEntity **pEntity ) |
|
{ |
|
int iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); |
|
|
|
// See if it's a weapon first |
|
for ( int i = 0; i < MAX_WEAPONS; i++ ) |
|
{ |
|
CTFWeaponBase *pWeapon = (CTFWeaponBase *)pTFPlayer->GetWeapon(i); |
|
if ( !pWeapon ) |
|
continue; |
|
|
|
CEconItemView *pEconItemView = pWeapon->GetAttributeContainer()->GetItem(); |
|
if ( !pEconItemView ) |
|
continue; |
|
|
|
int iLoadoutSlot = pEconItemView->GetStaticData()->GetLoadoutSlot( iClass ); |
|
if ( iLoadoutSlot == iSlot ) |
|
{ |
|
if ( pEntity ) |
|
{ |
|
*pEntity = pWeapon; |
|
} |
|
return pEconItemView; |
|
} |
|
} |
|
|
|
// Go through each of the actual items we have equipped right now... |
|
for ( int i = 0; i < pTFPlayer->GetNumWearables(); ++i ) |
|
{ |
|
CTFWearable *pWearableItem = dynamic_cast<CTFWearable *>( pTFPlayer->GetWearable( i ) ); |
|
if ( !pWearableItem ) |
|
continue; |
|
|
|
if ( !pWearableItem->GetAttributeContainer() ) |
|
continue; |
|
|
|
CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem(); |
|
if ( !pEconItemView ) |
|
continue; |
|
|
|
CTFItemDefinition *pItemDef = pEconItemView->GetStaticData(); |
|
if ( !pItemDef ) |
|
continue; |
|
|
|
if ( pItemDef->GetLoadoutSlot(iClass) != iSlot ) |
|
continue; |
|
|
|
// Yay! |
|
if ( pEntity ) |
|
{ |
|
*pEntity = pWearableItem; |
|
} |
|
return pEconItemView; |
|
} |
|
|
|
// Nothing we currently have equipped claims to be in this slot. |
|
if ( pEntity ) |
|
{ |
|
*pEntity = NULL; |
|
} |
|
return NULL; |
|
} |
|
|
|
bool CTFPlayerSharedUtils::ConceptIsPartnerTaunt( int iConcept ) |
|
{ |
|
return iConcept == MP_CONCEPT_HIGHFIVE_SUCCESS_FULL || iConcept == MP_CONCEPT_HIGHFIVE_SUCCESS; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFWeaponBuilder *CTFPlayerSharedUtils::GetBuilderForObjectType( CTFPlayer *pTFPlayer, int iObjectType ) |
|
{ |
|
const int OBJ_ANY = -1; |
|
|
|
if ( !pTFPlayer ) |
|
return NULL; |
|
|
|
for ( int i = 0; i < MAX_WEAPONS; i++ ) |
|
{ |
|
CTFWeaponBuilder *pBuilder = dynamic_cast< CTFWeaponBuilder* >( pTFPlayer->GetWeapon( i ) ); |
|
if ( !pBuilder ) |
|
continue; |
|
|
|
// Any builder will do - return first |
|
if ( iObjectType == OBJ_ANY ) |
|
return pBuilder; |
|
|
|
// Requires a specific builder for this type |
|
if ( pBuilder->CanBuildObjectType( iObjectType ) ) |
|
return pBuilder; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Can player pick up this weapon? |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::CanPickupDroppedWeapon( const CTFDroppedWeapon *pWeapon ) |
|
{ |
|
if ( !pWeapon->GetItem()->IsValid() ) |
|
return false; |
|
|
|
int iClass = GetPlayerClass()->GetClassIndex(); |
|
if ( iClass == TF_CLASS_SPY && ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.GetPercentInvisible() > 0 ) ) |
|
return false; |
|
|
|
if ( IsTaunting() ) |
|
return false; |
|
|
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
// There's a rare case that the player doesn't have an active weapon. This shouldn't happen. |
|
// If you hit this assert, figure out and fix WHY the player doesn't have a weapon. |
|
Assert( GetActiveTFWeapon() ); |
|
if ( !GetActiveTFWeapon() || !GetActiveTFWeapon()->CanPickupOtherWeapon() ) |
|
return false; |
|
|
|
int iItemSlot = pWeapon->GetItem()->GetStaticData()->GetLoadoutSlot( iClass ); |
|
CBaseEntity *pOwnedWeaponToDrop = GetEntityForLoadoutSlot( iItemSlot ); |
|
|
|
return pOwnedWeaponToDrop && pWeapon->GetItem()->GetStaticData()->CanBeUsedByClass( iClass ) && IsValidPickupWeaponSlot( iItemSlot ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns true if player is in range to pick up this weapon |
|
//----------------------------------------------------------------------------- |
|
CTFDroppedWeapon* CTFPlayer::GetDroppedWeaponInRange() |
|
{ |
|
// Check to see if a building we own is in front of us. |
|
Vector vecForward; |
|
AngleVectors( EyeAngles(), &vecForward ); |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * TF_WEAPON_PICKUP_RANGE, MASK_SOLID | CONTENTS_DEBRIS, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
CTFDroppedWeapon *pDroppedWeapon = dynamic_cast< CTFDroppedWeapon * >( tr.m_pEnt ); |
|
if ( !pDroppedWeapon ) |
|
return NULL; |
|
|
|
if ( !CanPickupDroppedWeapon( pDroppedWeapon ) ) |
|
return NULL; |
|
|
|
// too far? |
|
if ( EyePosition().DistToSqr( pDroppedWeapon->GetAbsOrigin() ) > Square( TF_WEAPON_PICKUP_RANGE ) ) |
|
return NULL; |
|
|
|
return pDroppedWeapon; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns true if player is inspecting |
|
//----------------------------------------------------------------------------- |
|
bool CTFPlayer::IsInspecting() const |
|
{ |
|
return m_flInspectTime != 0.f && gpGlobals->curtime - m_flInspectTime > 0.2f; |
|
}
|
|
|