//========= 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; iGetNumWearables(); ++i ) { CTFWearableDemoShield *pWearableShield = dynamic_cast( 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 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 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 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 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 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;iGetPlayerClass()->GetClassIndex() == TF_CLASS_SNIPER ) { CTFWeaponBase *pWpn = m_pOuter->GetActiveTFWeapon(); if ( pWpn && WeaponID_IsSniperRifle( pWpn->GetWeaponID() ) ) { CTFSniperRifle *pRifle = static_cast( 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<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( 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( 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; iGetNumWearables(); ++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 ( 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; iTeleport( &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( 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( 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( 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 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( 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( &m_PlayerStuns[m_iStunIndex] ) : NULL; #else return ( m_iStunIndex >= 0 ) ? const_cast( &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; im_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; im_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( 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(m_pOuter->GiveNamedItem( strDisguiseWeapon, iSubType, pItem, true )) ); if ( m_hDisguiseWeapon ) { m_hDisguiseWeapon->SetTouch( NULL );// no touch m_hDisguiseWeapon->SetOwner( dynamic_cast(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; iGetNumWearables(); ++i ) { CTFWearable *pWearable = dynamic_cast( 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( m_pOuter->GiveNamedItem( pScriptItem->GetStaticData()->GetItemClass(), 0, pScriptItem ) ); CTFWearable *pNewWearable = dynamic_cast( pNewItem ); Assert( pNewWearable ); if ( pNewWearable ) { pNewWearable->SetDisguiseWearable( true ); pNewWearable->GiveTo( m_pOuter ); // copy over the level for levelable items CTFWearableLevelableItem *pLevelableItem = dynamic_cast( pWearable ); CTFWearableLevelableItem *pNewLevelableItem = dynamic_cast( 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 ( ; iGetNumWearables(); ++i ) { CTFWearable *pWearable = dynamic_cast( 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;iCreateEvent( "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( 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 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(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(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 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(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 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( 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 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 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(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(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 flagAvoidReentrancy( m_bIsCalculatingMaximumSpeed ); CUtlVector 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()[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( 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( 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( Weapon_OwnsThisID( TF_WEAPON_INVIS ) ); if ( pInvisWatch ) { pInvisWatch->ActivateInvisibilityWatch(); } } } } break; case TF_CLASS_DEMOMAN: if ( !m_Shared.HasPasstimeBall() ) { CTFPipebombLauncher *pPipebombLauncher = static_cast( 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( 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; iGetAbsOrigin() - 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(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( 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( 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 ( 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 ( 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 *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 *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( 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( 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; }