//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Player for HL1. // // $NoKeywords: $ //============================================================================= #include "cbase.h" #include "tf_player.h" #include "tf_gamerules.h" #include "tf_gamestats.h" #include "KeyValues.h" #include "viewport_panel_names.h" #include "client.h" #include "team.h" #include "tf_weaponbase.h" #include "tf_client.h" #include "tf_team.h" #include "tf_viewmodel.h" #include "tf_item.h" #include "in_buttons.h" #include "entity_capture_flag.h" #include "effect_dispatch_data.h" #include "te_effect_dispatch.h" #include "game.h" #include "tf_weapon_builder.h" #include "tf_obj.h" #include "tf_ammo_pack.h" #include "datacache/imdlcache.h" #include "particle_parse.h" #include "props_shared.h" #include "filesystem.h" #include "toolframework_server.h" #include "IEffects.h" #include "func_respawnroom.h" #include "networkstringtable_gamedll.h" #include "team_control_point_master.h" #include "tf_weapon_pda.h" #include "sceneentity.h" #include "fmtstr.h" #include "tf_weapon_sniperrifle.h" #include "tf_weapon_minigun.h" #include "tf_weapon_fists.h" #include "tf_weapon_shotgun.h" #include "tf_weapon_lunchbox.h" #include "tf_weapon_knife.h" #include "tf_weapon_bottle.h" #include "tf_weapon_sword.h" #include "tf_weapon_grenade_pipebomb.h" #include "tf_weapon_buff_item.h" #include "tf_weapon_flamethrower.h" #include "tf_projectile_flare.h" #include "trigger_area_capture.h" #include "triggers.h" #include "tf_weapon_medigun.h" #include "tf_weapon_invis.h" #include "hl2orange.spa.h" #include "te_tfblood.h" #include "activitylist.h" #include "cdll_int.h" #include "econ_entity_creation.h" #include "tf_weaponbase_gun.h" #include "team_train_watcher.h" #include "vgui/ILocalize.h" #include "tier3/tier3.h" #include "serverbenchmark_base.h" #include "trains.h" #include "tf_fx.h" #include "recipientfilter.h" #include "ilagcompensationmanager.h" #include "dt_utlvector_send.h" #include "tf_item_wearable.h" #include "tf_item_powerup_bottle.h" #include "nav_mesh/tf_nav_mesh.h" #include "tier0/vprof.h" #include "econ_gcmessages.h" #include "tf_gcmessages.h" #include "tf_obj_sentrygun.h" #include "tf_weapon_shovel.h" #include "bot/tf_bot.h" #include "bot/tf_bot_manager.h" #include "NextBotUtil.h" #include "tf_wearable_item_demoshield.h" #include "tier0/icommandline.h" #include "entity_healthkit.h" #include "choreoevent.h" #include "minigames/tf_duel.h" #include "tf_bot_temp.h" #include "tf_objective_resource.h" #include "tf_weapon_pipebomblauncher.h" #include "func_achievement.h" #include "halloween/merasmus/merasmus.h" #include "inetchannel.h" #include "tf_wearable_levelable_item.h" #include "tf_weapon_jar.h" #include "halloween/tf_weapon_spellbook.h" #include "soundenvelope.h" #include "tf_triggers.h" #include "collisionutils.h" #include "tf_taunt_prop.h" #include "eventlist.h" #include "entity_rune.h" #include "entity_halloween_pickup.h" #include "tf_gc_server.h" #include "tf_logic_halloween_2014.h" #include "tf_weapon_knife.h" #include "tf_weapon_grapplinghook.h" #include "tf_dropped_weapon.h" #include "tf_passtime_logic.h" #include "tf_weapon_passtime_gun.h" #include "player_resource.h" #include "tf_player_resource.h" #include "gcsdk/gcclient_sharedobjectcache.h" #include "tf_party.h" #ifdef STAGING_ONLY #include "tf_extra_map_entity.h" #endif #ifdef TF_RAID_MODE #include "bot_npc/bot_npc_decoy.h" #include "raid/tf_raid_logic.h" #endif #include "entity_currencypack.h" #include "tf_mann_vs_machine_stats.h" #include "player_vs_environment/tf_upgrades.h" #include "player_vs_environment/tf_population_manager.h" #include "tf_revive.h" #include "tf_logic_halloween_2014.h" #include "tf_logic_player_destruction.h" // NVNT haptic utils #include "haptics/haptic_utils.h" #include "gc_clientsystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #pragma warning( disable: 4355 ) // disables ' 'this' : used in base member initializer list' ConVar sv_motd_unload_on_dismissal( "sv_motd_unload_on_dismissal", "0", 0, "If enabled, the MOTD contents will be unloaded when the player closes the MOTD." ); #define DAMAGE_FORCE_SCALE_SELF 9 #define SCOUT_ADD_BIRD_ON_GIB_CHANCE 5 #define MEDIC_RELEASE_DOVE_COUNT 10 #define JUMP_MIN_SPEED 268.3281572999747f extern bool IsInCommentaryMode( void ); extern void SpawnClientsideFlyingBird( Vector &vecSpawn ); extern ConVar sk_player_head; extern ConVar sk_player_chest; extern ConVar sk_player_stomach; extern ConVar sk_player_arm; extern ConVar sk_player_leg; extern ConVar tf_spy_invis_time; extern ConVar tf_spy_invis_unstealth_time; extern ConVar tf_stalematechangeclasstime; extern ConVar tf_gravetalk; extern ConVar tf_bot_quota_mode; extern ConVar tf_bot_quota; extern ConVar halloween_starting_souls; float GetCurrentGravity( void ); float m_flNextReflectZap = 0.f; static CTFPlayer *gs_pRecursivePlayerCheck = NULL; bool CTFPlayer::m_bTFPlayerNeedsPrecache = true; static const char g_pszIdleKickString[] = "#TF_Idle_kicked"; EHANDLE g_pLastSpawnPoints[TF_TEAM_COUNT]; EHANDLE g_hTestSub; ConVar tf_playerstatetransitions( "tf_playerstatetransitions", "-2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "tf_playerstatetransitions . Show player state transitions." ); ConVar tf_playergib( "tf_playergib", "1", FCVAR_NOTIFY, "Allow player gibbing. 0: never, 1: normal, 2: always", true, 0, true, 2 ); ConVar tf_damageforcescale_other( "tf_damageforcescale_other", "6.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_damageforcescale_self_soldier_rj( "tf_damageforcescale_self_soldier_rj", "10.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_damageforcescale_self_soldier_badrj( "tf_damageforcescale_self_soldier_badrj", "5.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_damageforcescale_pyro_jump( "tf_damageforcescale_pyro_jump", "8.5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_damagescale_self_soldier( "tf_damagescale_self_soldier", "0.60", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_damage_range( "tf_damage_range", "0.5", FCVAR_DEVELOPMENTONLY ); ConVar tf_damage_multiplier_blue( "tf_damage_multiplier_blue", "1.0", FCVAR_CHEAT, "All incoming damage to a blue player is multiplied by this value" ); ConVar tf_damage_multiplier_red( "tf_damage_multiplier_red", "1.0", FCVAR_CHEAT, "All incoming damage to a red player is multiplied by this value" ); ConVar tf_max_voice_speak_delay( "tf_max_voice_speak_delay", "1.5", FCVAR_DEVELOPMENTONLY, "Max time after a voice command until player can do another one" ); ConVar tf_allow_player_use( "tf_allow_player_use", "0", FCVAR_NOTIFY, "Allow players to execute +use while playing." ); ConVar tf_deploying_bomb_time( "tf_deploying_bomb_time", "1.90", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time to deploy bomb before the point of no return." ); ConVar tf_deploying_bomb_delay_time( "tf_deploying_bomb_delay_time", "0.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time to delay before deploying bomb." ); #ifdef TF_RAID_MODE ConVar tf_raid_team_size( "tf_raid_team_size", "5", FCVAR_NOTIFY, "Max number of Raiders" ); ConVar tf_raid_respawn_safety_time( "tf_raid_respawn_safety_time", "1.5", FCVAR_NOTIFY, "Number of seconds of invulnerability after respawning" ); ConVar tf_raid_allow_class_change( "tf_raid_allow_class_change", "1", FCVAR_NOTIFY, "If nonzero, allow invaders to change their class after leaving the safe room" ); ConVar tf_raid_use_rescue_closets( "tf_raid_use_rescue_closets", "1", FCVAR_NOTIFY ); ConVar tf_raid_drop_healthkit_chance( "tf_raid_drop_healthkit_chance", "50" ); // , FCVAR_CHEAT ); ConVar tf_boss_battle_team_size( "tf_boss_battle_team_size", "5", FCVAR_NOTIFY, "Max number of players in Boss Battle mode" ); ConVar tf_boss_battle_respawn_safety_time( "tf_boss_battle_respawn_safety_time", "3", FCVAR_NOTIFY, "Number of seconds of invulnerability after respawning" ); ConVar tf_boss_battle_respawn_on_friends( "tf_boss_battle_respawn_on_friends", "1", FCVAR_NOTIFY ); #endif ConVar tf_mvm_death_penalty( "tf_mvm_death_penalty", "0", FCVAR_NOTIFY | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "How much currency players lose when dying" ); extern ConVar tf_populator_damage_multiplier; extern ConVar tf_mvm_skill; #ifdef STAGING_ONLY ConVar tf_debug_ballistics( "tf_debug_ballistics", "0", FCVAR_CHEAT ); ConVar tf_debug_ballistic_targeting( "tf_debug_ballistic_targeting", "0", FCVAR_CHEAT ); ConVar tf_debug_ballistic_targeting_tolerance( "tf_debug_ballistic_targeting_tolerance", "5", FCVAR_CHEAT ); static Vector tf_debug_ballistic_target( 0, 0, 0 ); ConVar tf_space_thrust_scout( "tf_space_thrust_scout", "40.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_sniper( "tf_space_thrust_sniper", "34.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_spy( "tf_space_thrust_spy", "35.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_pyro( "tf_space_thrust_pyro", "35.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_soldier( "tf_space_thrust_soldier", "33.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_engy( "tf_space_thrust_engy", "35.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_medic( "tf_space_thrust_medic", "37.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_heavy( "tf_space_thrust_heavy", "33.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_demo( "tf_space_thrust_demo", "33.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much thrust is added while holding jump" ); ConVar tf_space_thrust_use_rate( "tf_space_thrust_use_rate", "2.0", FCVAR_CHEAT | FCVAR_REPLICATED, "How much fuel is used per tick" ); ConVar tf_space_thrust_recharge_rate( "tf_space_thrust_recharge_rate", "0.5", FCVAR_CHEAT | FCVAR_REPLICATED, "How much fuel is recharged per tick" ); ConVar tf_skip_intro_and_spectate( "tf_skip_intro_and_spectate", "0", FCVAR_REPLICATED, "Skip intro panels and start spectating." ); #endif #ifdef STAGING_ONLY ConVar tf_highfive_separation_forward( "tf_highfive_separation_forward", "0", FCVAR_CHEAT, "Forward distance between high five partners" ); ConVar tf_highfive_separation_right( "tf_highfive_separation_right", "0", FCVAR_CHEAT, "Right distance between high five partners" ); #else ConVar tf_highfive_separation_forward( "tf_highfive_separation_forward", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Forward distance between high five partners" ); ConVar tf_highfive_separation_right( "tf_highfive_separation_right", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Right distance between high five partners" ); #endif ConVar tf_highfive_max_range( "tf_highfive_max_range", "150", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "The farthest away a high five partner can be" ); ConVar tf_highfive_height_tolerance( "tf_highfive_height_tolerance", "12", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "The maximum height difference allowed for two high-fivers." ); ConVar tf_highfive_debug( "tf_highfive_debug", "0", FCVAR_NONE, "Turns on some console spew for debugging high five issues." ); ConVar tf_test_teleport_home_fx( "tf_test_teleport_home_fx", "0", FCVAR_CHEAT ); ConVar tf_halloween_giant_health_scale( "tf_halloween_giant_health_scale", "10", FCVAR_CHEAT ); ConVar tf_grapplinghook_los_force_detach_time( "tf_grapplinghook_los_force_detach_time", "1", FCVAR_CHEAT ); ConVar tf_powerup_max_charge_time( "tf_powerup_max_charge_time", "30", FCVAR_CHEAT ); extern ConVar tf_powerup_mode; extern ConVar tf_mvm_buybacks_method; extern ConVar tf_mvm_buybacks_per_wave; #define TF_CANNONBALL_FORCE_SCALE 80.f #define TF_CANNONBALL_FORCE_UPWARD 300.f #ifdef STAGING_ONLY void CC_tf_debug_ballistic_targeting_mark_target( const CCommand &args ) { CBasePlayer *player = UTIL_GetListenServerHost(); if ( !player ) { return; } Vector forward; AngleVectors( player->EyeAngles() + player->GetPunchAngle(), &forward ); trace_t result; UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 2000.0f * forward, MASK_SHOT, player, COLLISION_GROUP_NONE, &result ); tf_debug_ballistic_target = result.endpos; } static ConCommand tf_debug_ballistic_targeting_mark_target( "tf_debug_ballistic_targeting_mark_target", CC_tf_debug_ballistic_targeting_mark_target, "Mark a spot for testing ballistic targeting.", FCVAR_CHEAT ); ConVar tf_infinite_ammo( "tf_infinite_ammo", "0", FCVAR_CHEAT ); extern ConVar tf_bountymode_currency_starting; extern ConVar tf_bountymode_upgrades_wipeondeath; extern ConVar tf_bountymode_currency_penalty_ondeath; #endif // STAGING_ONLY ConVar tf_halloween_unlimited_spells( "tf_halloween_unlimited_spells", "0", FCVAR_CHEAT ); extern ConVar tf_halloween_kart_boost_recharge; extern ConVar tf_halloween_kart_boost_duration; ConVar tf_halloween_kart_impact_force( "tf_halloween_kart_impact_force", "0.75f", FCVAR_CHEAT, "Impact force scaler" ); ConVar tf_halloween_kart_impact_damage( "tf_halloween_kart_impact_damage", "1.0f", FCVAR_CHEAT, "Impact damage scaler" ); ConVar tf_halloween_kart_impact_rate( "tf_halloween_kart_impact_rate", "0.5f", FCVAR_CHEAT, "rate of allowing impact damage" ); ConVar tf_halloween_kart_boost_impact_force( "tf_halloween_kart_boost_impact_force", "0.75f", FCVAR_CHEAT, "Impact force scaler on boosts" ); ConVar tf_halloween_kart_impact_bounds_scale( "tf_halloween_kart_impact_bounds_scale", "1.0f", FCVAR_CHEAT ); ConVar tf_halloween_kart_impact_feedback( "tf_halloween_kart_impact_feedback", "0.25f", FCVAR_CHEAT ); ConVar tf_halloween_kart_impact_lookahead( "tf_halloween_kart_impact_lookahead", "12.0f", FCVAR_CHEAT ); ConVar tf_halloween_kart_bomb_head_damage_scale( "tf_halloween_kart_bomb_head_damage_scale", "2", FCVAR_CHEAT ); ConVar tf_halloween_kart_bomb_head_impulse_scale( "tf_halloween_kart_bomb_head_impulse_scale", "2", FCVAR_CHEAT ); ConVar tf_halloween_kart_impact_air_scale( "tf_halloween_kart_impact_air_scale", "0.75f", FCVAR_CHEAT ); ConVar tf_halloween_kart_damage_to_force( "tf_halloween_kart_damage_to_force", "300.0f", FCVAR_CHEAT ); ConVar tf_halloween_kart_stun_duration_scale( "tf_halloween_kart_stun_duration_scale", "0.70f", FCVAR_CHEAT ); ConVar tf_halloween_kart_stun_amount( "tf_halloween_kart_stun_amount", "1.0f", FCVAR_CHEAT ); ConVar tf_halloween_kart_stun_enabled( "tf_halloween_kart_stun_enabled", "1", FCVAR_CHEAT ); ConVar tf_tauntcam_fov_override( "tf_tauntcam_fov_override", "0", FCVAR_CHEAT ); ConVar tf_nav_in_combat_range( "tf_nav_in_combat_range", "1000", FCVAR_CHEAT ); ConVar tf_halloween_kart_punting_ghost_force_scale( "tf_halloween_kart_punting_ghost_force_scale", "4", FCVAR_CHEAT ); ConVar tf_halloween_allow_ghost_hit_by_kart_delay( "tf_halloween_allow_ghost_hit_by_kart_delay", "0.5", FCVAR_CHEAT ); extern ConVar tf_feign_death_duration; extern ConVar spec_freeze_time; extern ConVar spec_freeze_traveltime; extern ConVar sv_maxunlag; extern ConVar tf_allow_taunt_switch; extern ConVar weapon_medigun_chargerelease_rate; extern ConVar tf_scout_energydrink_consume_rate; extern ConVar tf_mm_trusted; extern ConVar mp_spectators_restricted; extern ConVar mp_teams_unbalance_limit; extern ConVar tf_tournament_classchange_allowed; extern ConVar tf_tournament_classchange_ready_allowed; #if defined( _DEBUG ) || defined( STAGING_ONLY ) extern ConVar mp_developer; #endif // _DEBUG || STAGING_ONLY #ifdef STAGING_ONLY extern ConVar tf_skillrating_debug_bots_allowed; #endif // STAGING_ONLY extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); extern bool CanScatterGunKnockBack( CTFWeaponBase *pWeapon, float flDamage, float flDistanceSq ); static const char *s_pszTauntRPSParticleNames[] = { "rps_rock_red", "rps_paper_red", "rps_scissors_red", "rps_rock_red_win", "rps_paper_red_win", "rps_scissors_red_win", "rps_rock_blue", "rps_paper_blue", "rps_scissors_blue", "rps_rock_blue_win", "rps_paper_blue_win", "rps_scissors_blue_win" }; // -------------------------------------------------------------------------------- // // Player animation event. Sent to the client when a player fires, jumps, reloads, etc.. // -------------------------------------------------------------------------------- // class CTEPlayerAnimEvent : public CBaseTempEntity { public: DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity ); DECLARE_SERVERCLASS(); CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name ) { m_iPlayerIndex = TF_PLAYER_INDEX_NONE; } CNetworkVar( int, m_iPlayerIndex ); CNetworkVar( int, m_iEvent ); CNetworkVar( int, m_nData ); }; IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent ) SendPropInt( SENDINFO( m_iPlayerIndex ), 7, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ), // BUGBUG: ywb we assume this is either 0 or an animation sequence #, but it could also be an activity, which should fit within this limit, but we're not guaranteed. SendPropInt( SENDINFO( m_nData ), ANIMATION_SEQUENCE_BITS ), END_SEND_TABLE() static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" ); void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData ) { Vector vecEyePos = pPlayer->EyePosition(); CPVSFilter filter( vecEyePos ); if ( !IsCustomPlayerAnimEvent( event ) && ( event != PLAYERANIMEVENT_SNAP_YAW ) && ( event != PLAYERANIMEVENT_VOICE_COMMAND_GESTURE ) ) { // if prediction is off, alway send jump if ( !( ( event == PLAYERANIMEVENT_JUMP ) && ( FStrEq(engine->GetClientConVarValue( pPlayer->entindex(), "cl_predict" ), "0" ) ) ) ) { filter.RemoveRecipient( pPlayer ); } } Assert( pPlayer->entindex() >= 1 && pPlayer->entindex() <= MAX_PLAYERS ); g_TEPlayerAnimEvent.m_iPlayerIndex = pPlayer->entindex(); g_TEPlayerAnimEvent.m_iEvent = event; Assert( nData < (1<= ActivityList_HighestIndex() ); g_TEPlayerAnimEvent.m_nData = nData; g_TEPlayerAnimEvent.Create( filter, 0 ); } //================================================================================= // // Ragdoll Entity // class CTFRagdoll : public CBaseAnimatingOverlay { public: DECLARE_CLASS( CTFRagdoll, CBaseAnimatingOverlay ); DECLARE_SERVERCLASS(); CTFRagdoll() { m_iPlayerIndex.Set( TF_PLAYER_INDEX_NONE ); m_bGib = false; m_bBurning = false; m_bElectrocuted = false; m_bFeignDeath = false; m_bWasDisguised = false; m_bBecomeAsh = false; m_bOnGround = false; m_bCloaked = false; m_iDamageCustom = 0; m_bCritOnHardHit = false; m_vecRagdollOrigin.Init(); m_vecRagdollVelocity.Init(); } ~CTFRagdoll() { // Destroy all of our attached wearables. for ( int i=0; iRemove(); } } m_hRagWearables.Purge(); } // Transmit ragdolls to everyone. virtual int UpdateTransmitState() { UseClientSideAnimation(); return SetTransmitState( FL_EDICT_ALWAYS ); } CNetworkVar( int, m_iPlayerIndex ); CNetworkVector( m_vecRagdollVelocity ); CNetworkVector( m_vecRagdollOrigin ); CNetworkVar( bool, m_bGib ); CNetworkVar( bool, m_bBurning ); CNetworkVar( bool, m_bElectrocuted ); CNetworkVar( bool, m_bFeignDeath ); CNetworkVar( bool, m_bWasDisguised ); CNetworkVar( bool, m_bBecomeAsh ); CNetworkVar( bool, m_bOnGround ); CNetworkVar( bool, m_bCloaked ); CNetworkVar( int, m_iDamageCustom ); CNetworkVar( int, m_iTeam ); CNetworkVar( int, m_iClass ); CNetworkVar( bool, m_bGoldRagdoll ); CNetworkVar( bool, m_bIceRagdoll ); CNetworkVar( bool, m_bCritOnHardHit ); CNetworkVar( float, m_flHeadScale ); CNetworkVar( float, m_flTorsoScale ); CNetworkVar( float, m_flHandScale ); CUtlVector > m_hRagWearables; }; LINK_ENTITY_TO_CLASS( tf_ragdoll, CTFRagdoll ); IMPLEMENT_SERVERCLASS_ST_NOBASE( CTFRagdoll, DT_TFRagdoll ) SendPropVector( SENDINFO( m_vecRagdollOrigin ), -1, SPROP_COORD ), SendPropInt( SENDINFO( m_iPlayerIndex ), 7, SPROP_UNSIGNED ), SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ), SendPropVector( SENDINFO( m_vecRagdollVelocity ), 13, SPROP_ROUNDDOWN, -2048.0f, 2048.0f ), SendPropInt( SENDINFO( m_nForceBone ) ), SendPropBool( SENDINFO( m_bGib ) ), SendPropBool( SENDINFO( m_bBurning ) ), SendPropBool( SENDINFO( m_bElectrocuted ) ), SendPropBool( SENDINFO( m_bFeignDeath ) ), SendPropBool( SENDINFO( m_bWasDisguised ) ), SendPropBool( SENDINFO( m_bBecomeAsh ) ), SendPropBool( SENDINFO( m_bOnGround ) ), SendPropBool( SENDINFO( m_bCloaked ) ), SendPropInt( SENDINFO( m_iDamageCustom ) ), SendPropInt( SENDINFO( m_iTeam ), 3, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_iClass ), 4, SPROP_UNSIGNED ), SendPropUtlVector( SENDINFO_UTLVECTOR( m_hRagWearables ), 8, SendPropEHandle( NULL, 0 ) ), SendPropBool( SENDINFO( m_bGoldRagdoll ) ), SendPropBool( SENDINFO( m_bIceRagdoll ) ), SendPropBool( SENDINFO( m_bCritOnHardHit ) ), SendPropFloat( SENDINFO( m_flHeadScale ) ), SendPropFloat( SENDINFO( m_flTorsoScale ) ), SendPropFloat( SENDINFO( m_flHandScale ) ), END_SEND_TABLE() // -------------------------------------------------------------------------------- // // Tables. // -------------------------------------------------------------------------------- // //----------------------------------------------------------------------------- // Purpose: Filters updates to a variable so that only non-local players see // the changes. This is so we can send a low-res origin to non-local players // while sending a hi-res one to the local player. // Input : *pVarData - // *pOut - // objectID - //----------------------------------------------------------------------------- void* SendProxy_SendNonLocalDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) { pRecipients->SetAllRecipients(); pRecipients->ClearRecipient( objectID - 1 ); return ( void * )pVarData; } REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendNonLocalDataTable ); //----------------------------------------------------------------------------- // Purpose: SendProxy that converts the UtlVector list of objects to entindexes, where it's reassembled on the client //----------------------------------------------------------------------------- void SendProxy_PlayerObjectList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) { CTFPlayer *pPlayer = (CTFPlayer*)pStruct; // If this fails, then SendProxyArrayLength_PlayerObjects didn't work. Assert( iElement < pPlayer->GetObjectCount() ); CBaseObject *pObject = pPlayer->GetObject(iElement); EHANDLE hObject; hObject = pObject; SendProxy_EHandleToInt( pProp, pStruct, &hObject, pOut, iElement, objectID ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int SendProxyArrayLength_PlayerObjects( const void *pStruct, int objectID ) { CTFPlayer *pPlayer = (CTFPlayer*)pStruct; int iObjects = pPlayer->GetObjectCount(); Assert( iObjects <= MAX_OBJECTS_PER_PLAYER ); return iObjects; } //----------------------------------------------------------------------------- // Purpose: Send to attached medics //----------------------------------------------------------------------------- void* SendProxy_SendHealersDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) { CTFPlayer *pPlayer = (CTFPlayer*)pStruct; if ( pPlayer ) { // Add attached medics for ( int i = 0; i < pPlayer->m_Shared.GetNumHealers(); i++ ) { CTFPlayer *pMedic = ToTFPlayer( pPlayer->m_Shared.GetHealerByIndex( i ) ); if ( !pMedic ) continue; pRecipients->SetRecipient( pMedic->GetClientIndex() ); return (void*)pVarData; } } return NULL; } REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendHealersDataTable ); BEGIN_DATADESC( CTFPlayer ) DEFINE_INPUTFUNC( FIELD_VOID, "IgnitePlayer", InputIgnitePlayer ), DEFINE_INPUTFUNC( FIELD_STRING, "SetCustomModel", InputSetCustomModel ), DEFINE_INPUTFUNC( FIELD_VECTOR, "SetCustomModelOffset", InputSetCustomModelOffset ), DEFINE_INPUTFUNC( FIELD_VECTOR, "SetCustomModelRotation", InputSetCustomModelRotation ), DEFINE_INPUTFUNC( FIELD_VOID, "ClearCustomModelRotation", InputClearCustomModelRotation ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCustomModelRotates", InputSetCustomModelRotates ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCustomModelVisibleToSelf", InputSetCustomModelVisibleToSelf ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetForcedTauntCam", InputSetForcedTauntCam ), DEFINE_INPUTFUNC( FIELD_VOID, "ExtinguishPlayer", InputExtinguishPlayer ), DEFINE_INPUTFUNC( FIELD_FLOAT, "BleedPlayer", InputBleedPlayer ), DEFINE_INPUTFUNC( FIELD_VOID, "TriggerLootIslandAchievement", InputTriggerLootIslandAchievement ), DEFINE_INPUTFUNC( FIELD_VOID, "TriggerLootIslandAchievement2", InputTriggerLootIslandAchievement2 ), DEFINE_INPUTFUNC( FIELD_STRING, "SpeakResponseConcept", InputSpeakResponseConcept ), DEFINE_INPUTFUNC( FIELD_VOID, "RollRareSpell", InputRollRareSpell ), DEFINE_INPUTFUNC( FIELD_VOID, "RoundSpawn", InputRoundSpawn ), END_DATADESC() EXTERN_SEND_TABLE( DT_ScriptCreatedItem ); // specific to the local player BEGIN_SEND_TABLE_NOBASE( CTFPlayer, DT_TFLocalPlayerExclusive ) // send a hi-res origin to the local player for use in prediction SendPropVectorXY(SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_OriginXY ), SendPropFloat (SENDINFO_VECTORELEM(m_vecOrigin, 2), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_OriginZ ), SendPropArray2( SendProxyArrayLength_PlayerObjects, SendPropInt("player_object_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_PlayerObjectList), MAX_OBJECTS_PER_PLAYER, 0, "player_object_array" ), SendPropFloat( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 8, SPROP_CHANGES_OFTEN, -90.0f, 90.0f ), // No longer used by the local player, could be omitted. Preserved for backwards-compat for now. // SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 10, SPROP_CHANGES_OFTEN ), SendPropBool( SENDINFO( m_bIsCoaching ) ), SendPropEHandle( SENDINFO( m_hCoach ) ), SendPropEHandle( SENDINFO( m_hStudent ) ), SendPropInt( SENDINFO( m_nCurrency ), -1, SPROP_VARINT ), SendPropInt( SENDINFO( m_nExperienceLevel ), 7, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_nExperienceLevelProgress ), 7, SPROP_UNSIGNED ), SendPropBool( SENDINFO( m_bMatchSafeToLeave ) ), END_SEND_TABLE() // all players except the local player BEGIN_SEND_TABLE_NOBASE( CTFPlayer, DT_TFNonLocalPlayerExclusive ) // send a lo-res origin to other players SendPropVectorXY(SENDINFO(m_vecOrigin), -1, SPROP_COORD_MP_LOWPRECISION|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_OriginXY ), SendPropFloat (SENDINFO_VECTORELEM(m_vecOrigin, 2), -1, SPROP_COORD_MP_LOWPRECISION|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_OriginZ ), SendPropFloat( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 8, SPROP_CHANGES_OFTEN, -90.0f, 90.0f ), SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 10, SPROP_CHANGES_OFTEN ), END_SEND_TABLE() //----------------------------------------------------------------------------- // Purpose: Sent to attached medics //----------------------------------------------------------------------------- BEGIN_SEND_TABLE_NOBASE( CTFPlayer, DT_TFSendHealersDataTable ) SendPropInt( SENDINFO( m_nActiveWpnClip ), -1, SPROP_VARINT | SPROP_UNSIGNED ), END_SEND_TABLE() //============ LINK_ENTITY_TO_CLASS( player, CTFPlayer ); PRECACHE_REGISTER(player); IMPLEMENT_SERVERCLASS_ST( CTFPlayer, DT_TFPlayer ) SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ), SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), SendPropExclude( "DT_BaseAnimating", "m_nBody" ), SendPropExclude( "DT_BaseEntity", "m_angRotation" ), SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ), SendPropExclude( "DT_BaseEntity", "m_nModelIndex" ), SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ), // cs_playeranimstate and clientside animation takes care of these on the client SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), SendPropExclude( "DT_BaseFlex", "m_flexWeight" ), SendPropExclude( "DT_BaseFlex", "m_blinktoggle" ), SendPropExclude( "DT_BaseFlex", "m_viewtarget" ), SendPropBool(SENDINFO(m_bSaveMeParity)), SendPropBool(SENDINFO(m_bIsMiniBoss)), SendPropBool(SENDINFO(m_bIsABot)), SendPropInt( SENDINFO(m_nBotSkill), 3, SPROP_UNSIGNED ), // This will create a race condition will the local player, but the data will be the same so..... SendPropInt( SENDINFO( m_nWaterLevel ), 2, SPROP_UNSIGNED ), // Ragdoll. SendPropEHandle( SENDINFO( m_hRagdoll ) ), SendPropDataTable( SENDINFO_DT( m_PlayerClass ), &REFERENCE_SEND_TABLE( DT_TFPlayerClassShared ) ), SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_TFPlayerShared ) ), SendPropEHandle(SENDINFO(m_hItem)), // Data that only gets sent to the local player SendPropDataTable( "tflocaldata", 0, &REFERENCE_SEND_TABLE(DT_TFLocalPlayerExclusive), SendProxy_SendLocalDataTable ), // Data that gets sent to all other players SendPropDataTable( "tfnonlocaldata", 0, &REFERENCE_SEND_TABLE(DT_TFNonLocalPlayerExclusive), SendProxy_SendNonLocalDataTable ), SendPropBool( SENDINFO( m_bAllowMoveDuringTaunt ) ), SendPropBool( SENDINFO( m_bIsReadyToHighFive ) ), SendPropEHandle( SENDINFO( m_hHighFivePartner ) ), SendPropInt( SENDINFO( m_nForceTauntCam ), 2, SPROP_UNSIGNED ), SendPropFloat( SENDINFO( m_flTauntYaw ), 0, SPROP_NOSCALE ), SendPropInt( SENDINFO( m_nActiveTauntSlot ) ), SendPropInt( SENDINFO( m_iTauntItemDefIndex ) ), SendPropFloat( SENDINFO( m_flCurrentTauntMoveSpeed ) ), SendPropFloat( SENDINFO( m_flVehicleReverseTime ) ), SendPropFloat( SENDINFO( m_flLastDamageTime ), 16, SPROP_ROUNDUP ), SendPropBool( SENDINFO( m_bInPowerPlay ) ), SendPropInt( SENDINFO( m_iSpawnCounter ) ), SendPropBool( SENDINFO( m_bArenaSpectator ) ), SendPropFloat( SENDINFO( m_flHeadScale ) ), SendPropFloat( SENDINFO( m_flTorsoScale ) ), SendPropFloat( SENDINFO( m_flHandScale ) ), SendPropBool( SENDINFO( m_bUseBossHealthBar ) ), SendPropBool( SENDINFO( m_bUsingVRHeadset ) ), SendPropBool( SENDINFO( m_bForcedSkin ) ), SendPropInt( SENDINFO( m_nForcedSkin ), ANIMATION_SKIN_BITS ), SendPropDataTable( SENDINFO_DT( m_AttributeManager ), &REFERENCE_SEND_TABLE(DT_AttributeManager) ), SendPropDataTable( "TFSendHealersDataTable", 0, &REFERENCE_SEND_TABLE( DT_TFSendHealersDataTable ), SendProxy_SendHealersDataTable ), SendPropFloat( SENDINFO( m_flKartNextAvailableBoost ) ), SendPropInt( SENDINFO( m_iKartHealth ) ), SendPropInt( SENDINFO( m_iKartState ) ), SendPropEHandle( SENDINFO( m_hGrapplingHookTarget ) ), SendPropEHandle( SENDINFO( m_hSecondaryLastWeapon ) ), SendPropBool( SENDINFO( m_bUsingActionSlot ) ), SendPropFloat( SENDINFO( m_flInspectTime ) ), SendPropInt( SENDINFO( m_iCampaignMedals ) ), SendPropInt( SENDINFO( m_iPlayerSkinOverride ) ), END_SEND_TABLE() // -------------------------------------------------------------------------------- // void cc_CreatePredictionError_f() { CBaseEntity *pEnt = CBaseEntity::Instance( 1 ); pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) ); } ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); // -------------------------------------------------------------------------------- // enum eCoachCommand { kCoachCommand_Look = 1, // slot1 kCoachCommand_Go, // slot2 kCoachCommand_Attack, kCoachCommand_Defend, kNumCoachCommands, }; /** * Handles a command from the coach */ static void HandleCoachCommand( CTFPlayer *pPlayer, eCoachCommand command ) { if ( pPlayer && pPlayer->IsCoaching() && pPlayer->GetStudent() && command < kNumCoachCommands ) { const float kMaxRateCoachCommands = 1.0f; float flLastCoachCommandDelta = gpGlobals->curtime - pPlayer->m_flLastCoachCommand; if ( flLastCoachCommandDelta < kMaxRateCoachCommands && flLastCoachCommandDelta > 0.0f ) { return; } pPlayer->m_flLastCoachCommand = gpGlobals->curtime; IGameEvent *pEvent = gameeventmanager->CreateEvent( "show_annotation" ); if ( pEvent ) { Vector vForward; AngleVectors( pPlayer->EyeAngles(), &vForward ); trace_t trace; CTraceFilterSimple filter( pPlayer->GetStudent(), COLLISION_GROUP_NONE ); UTIL_TraceLine( pPlayer->EyePosition(), pPlayer->EyePosition() + vForward * MAX_TRACE_LENGTH, MASK_SOLID, &filter, &trace ); CBaseEntity *pHitEntity = trace.m_pEnt && trace.m_pEnt->IsWorld() == false && trace.m_pEnt != pPlayer->GetStudent() ? trace.m_pEnt : NULL; pEvent->SetInt( "id", pPlayer->entindex() ); pEvent->SetFloat( "worldPosX", trace.endpos.x ); pEvent->SetFloat( "worldPosY", trace.endpos.y ); pEvent->SetFloat( "worldPosZ", trace.endpos.z ); pEvent->SetFloat( "worldNormalX", trace.plane.normal.x ); pEvent->SetFloat( "worldNormalY", trace.plane.normal.y ); pEvent->SetFloat( "worldNormalZ", trace.plane.normal.z ); pEvent->SetFloat( "lifetime", 10.0f ); if ( pHitEntity ) { pEvent->SetInt( "follow_entindex", pHitEntity->entindex() ); } pEvent->SetInt( "visibilityBitfield", ( 1 << pPlayer->entindex() | 1 << pPlayer->GetStudent()->entindex() ) ); pEvent->SetBool( "show_distance", true ); pEvent->SetBool( "show_effect", true ); switch ( command ) { case kCoachCommand_Attack: pEvent->SetString( "text", pHitEntity ? "#TF_Coach_AttackThis" : "#TF_Coach_AttackHere" ); pEvent->SetString( "play_sound", "coach/coach_attack_here.wav" ); break; case kCoachCommand_Defend: pEvent->SetString( "text", pHitEntity ? "#TF_Coach_DefendThis" : "#TF_Coach_DefendHere" ); pEvent->SetString( "play_sound", "coach/coach_defend_here.wav" ); break; case kCoachCommand_Look: pEvent->SetString( "text", pHitEntity ? "#TF_Coach_LookAt" : "#TF_Coach_LookHere" ); pEvent->SetString( "play_sound", "coach/coach_look_here.wav" ); break; case kCoachCommand_Go: pEvent->SetString( "text", pHitEntity ? "#TF_Coach_GoToThis" : "#TF_Coach_GoHere" ); pEvent->SetString( "play_sound", "coach/coach_go_here.wav" ); break; } gameeventmanager->FireEvent( pEvent ); } } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFPlayer::CTFPlayer() { m_pAttributes = this; m_PlayerAnimState = CreateTFPlayerAnimState( this ); SetArmorValue( 10 ); m_hItem = NULL; m_hTauntScene = NULL; m_hTauntProp = NULL; UseClientSideAnimation(); m_angEyeAngles.Init(); m_pStateInfo = NULL; m_lifeState = LIFE_DEAD; // Start "dead". m_iMaxSentryKills = 0; m_flLastCoachCommand = 0; m_flNextTimeCheck = gpGlobals->curtime; m_flSpawnTime = 0; m_flWaterExitTime = 0; SetViewOffset( TF_PLAYER_VIEW_OFFSET ); m_Shared.Init( this ); m_iLastSkin = -1; m_bHudClassAutoKill = false; m_bMedigunAutoHeal = false; m_vecLastDeathPosition = Vector( FLT_MAX, FLT_MAX, FLT_MAX ); SetDesiredPlayerClassIndex( TF_CLASS_UNDEFINED ); SetContextThink( &CTFPlayer::TFPlayerThink, gpGlobals->curtime, "TFPlayerThink" ); m_flLastAction = gpGlobals->curtime; m_flTimeInSpawn = 0; m_bInitTaunt = false; m_bSpeakingConceptAsDisguisedSpy = false; m_iPreviousteam = TEAM_UNASSIGNED; m_bArenaSpectator = false; m_bArenaIsAFK = false; m_bIsAFK = false; m_nDeployingBombState = TF_BOMB_DEPLOYING_NONE; m_flNextChangeClassTime = 0.0f; m_flNextChangeTeamTime = 0.0f; m_bScattergunJump = false; m_iOldStunFlags = 0; m_iLastWeaponSlot = 1; m_iNumberofDominations = 0; m_bFlipViewModels = false; m_iBlastJumpState = 0; m_flBlastJumpLandTime = 0; m_fMaxHealthTime = -1; m_iHealthBefore = 0; m_iTeamChanges = 0; m_iClassChanges = 0; m_hReviveMarker = NULL; // Bounty Mode m_nExperienceLevel = 1; m_nExperiencePoints = 0; m_nExperienceLevelProgress = 0; SetDefLessFunc( m_Cappers ); // Tracks victims for demo achievement //============================================================================= // HPE_BEGIN: // [msmith] Added a player type so we can distinguish between bots and humans. //============================================================================= m_playerType = HUMAN_PLAYER; //============================================================================= // HPE_END //============================================================================= m_bIsTargetDummy = false; m_bCollideWithSentry = false; m_flCommentOnCarrying = 0; m_bIsReadyToHighFive = false; m_hHighFivePartner = NULL; m_nForceTauntCam = 0; m_bAllowMoveDuringTaunt = false; m_bTauntForceMoveForward = false; m_flTauntForceMoveForwardSpeed = 0.f; m_flTauntMoveAccelerationTime = 0.f; m_flTauntTurnSpeed = 0.f; m_flTauntTurnAccelerationTime = 0.f; m_bTauntMimic = false; m_bIsTauntInitiator = false; m_TauntEconItemView.Invalidate(); m_iPreTauntWeaponSlot = -1; m_bIsCalculatingMaximumSpeed = false; m_flLastThinkTime = -1.f; m_nCurrency = 0; m_pWaveSpawnPopulator = NULL; m_flLastReadySoundTime = 0.f; m_damageRateArray = new int[ DPS_Period ]; ResetDamagePerSecond(); m_nActiveWpnClip.Set( 0 ); m_nActiveWpnClipPrev = 0; m_flNextClipSendTime = 0; m_nCanPurchaseUpgradesCount = 0; m_flHeadScale = 1.f; m_flTorsoScale = 1.f; m_flHandScale = 1.f; m_bPendingMerasmusPlayerBombExplode = false; m_fLastBombHeadTimestamp = 0.0f; m_bIsSapping = false; m_iSappingEvent = TF_SAPEVENT_NONE; m_flSapStartTime = 0.00; m_bIsMiniBoss = false; m_bUseBossHealthBar = false; m_bUsingVRHeadset = false; m_bForcedSkin = false; m_nForcedSkin = 0; SetRespawnOverride( -1.f, NULL_STRING ); m_qPreviousChargeEyeAngle.Init(); m_vHalloweenKartPush.Zero(); m_flHalloweenKartPushEventTime = 0.f; m_bCheckKartCollision = false; m_flHHHKartAttackTime = 0.f; m_flNextBonusDucksVOAllowedTime = 0.f; m_flGhostLastHitByKartTime = 0.f; m_flVehicleReverseTime = FLT_MAX; m_iCampaignMedals = 0; m_bPasstimeBallSlippery = false; m_flNextScorePointForPD = -1; m_iPlayerSkinOverride = 0; m_nPrevRoundTeamNum = TEAM_UNASSIGNED; m_flLastDamageResistSoundTime = -1.f; m_hLastDamageDoneEntity = NULL; m_mapCustomAttributes.SetLessFunc( UtlStringCaseInsensitiveLessFunc ); SetDefLessFunc( m_PlayersExtinguished ); m_flLastAutobalanceTime = 0.f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ForcePlayerViewAngles( const QAngle& qTeleportAngles ) { CSingleUserRecipientFilter filter( this ); UserMessageBegin( filter, "ForcePlayerViewAngles" ); WRITE_BYTE( 0x01 ); // Reserved space for flags. WRITE_BYTE( entindex() ); WRITE_ANGLES( qTeleportAngles ); MessageEnd(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SetGrapplingHookTarget( CBaseEntity *pTarget, bool bShouldBleed /*= false*/ ) { if ( pTarget ) { // prevent fall damage after a successful hook m_Shared.AddCond( TF_COND_GRAPPLINGHOOK_SAFEFALL ); m_Shared.AddCond( TF_COND_GRAPPLINGHOOK_LATCHED ); } else { m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK_LATCHED ); } CBaseEntity *pPreviousTarget = m_hGrapplingHookTarget; m_hGrapplingHookTarget = pTarget; if ( pTarget ) { if ( pTarget->IsPlayer() ) { CTFPlayer *pTargetPlayer = ToTFPlayer( pTarget ); m_Shared.AddCond( TF_COND_GRAPPLED_TO_PLAYER ); // make player bleed if ( bShouldBleed ) { CTFGrapplingHook *pGrapplingHook = dynamic_cast< CTFGrapplingHook* >( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pGrapplingHook ) pTargetPlayer->m_Shared.MakeBleed( this, pGrapplingHook, 0, TF_BLEEDING_DMG, true ); pTargetPlayer->m_nHookAttachedPlayers++; } if ( !pTargetPlayer->m_Shared.InCond( TF_COND_GRAPPLINGHOOK_BLEEDING ) && pTargetPlayer->m_nHookAttachedPlayers > 0 ) { pTargetPlayer->m_Shared.AddCond( TF_COND_GRAPPLINGHOOK_BLEEDING ); } if ( !pTargetPlayer->m_Shared.InCond( TF_COND_GRAPPLED_BY_PLAYER ) && pTargetPlayer->m_nHookAttachedPlayers > 0 ) { pTargetPlayer->m_Shared.AddCond( TF_COND_GRAPPLED_BY_PLAYER ); } } m_flLastSeenHookTarget = gpGlobals->curtime; } else { if ( pPreviousTarget && pPreviousTarget->IsPlayer() ) { CTFPlayer *pPreviousTargetPlayer = ToTFPlayer( pPreviousTarget ); m_Shared.RemoveCond( TF_COND_GRAPPLED_TO_PLAYER ); // try to remove bleeding from hook if there's one if ( pPreviousTargetPlayer->m_Shared.InCond( TF_COND_BLEEDING ) ) { CTFGrapplingHook *pGrapplingHook = dynamic_cast< CTFGrapplingHook* >( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pGrapplingHook ) pPreviousTargetPlayer->m_Shared.StopBleed( this, pGrapplingHook ); } pPreviousTargetPlayer->m_nHookAttachedPlayers--; Assert( pPreviousTargetPlayer->m_nHookAttachedPlayers >= 0 ); if ( pPreviousTargetPlayer->m_nHookAttachedPlayers == 0 ) { pPreviousTargetPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK_BLEEDING ); pPreviousTargetPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLED_BY_PLAYER ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::CanBeForcedToLaugh( void ) { if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && IsBot() && ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::TFPlayerThink() { if ( m_pStateInfo && m_pStateInfo->pfnThink ) { (this->*m_pStateInfo->pfnThink)(); } if ( m_flSendPickupWeaponMessageTime != -1.f && gpGlobals->curtime >= m_flSendPickupWeaponMessageTime ) { CSingleUserRecipientFilter filter( this ); filter.MakeReliable(); UserMessageBegin( filter, "PlayerPickupWeapon" ); MessageEnd(); m_flSendPickupWeaponMessageTime = -1.f; } // In doomsday event, kart can run over ghost to do stuff if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { CUtlVector< CTFPlayer * > playerVector; CollectPlayers( &playerVector, TEAM_ANY, true ); CUtlVector< CTFPlayer * > ghostVector; for ( int i=0; im_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) { if ( ( playerVector[i]->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < Square( 100 ) ) { ghostVector.AddToTail( playerVector[i] ); } } } for ( int i=0; iGetTeamNumber() == GetTeamNumber() ) { // Trace the ghosts bbox right where they are to see if they collide with enemy players trace_t trace; Ray_t ray; ray.Init( pGhost->GetAbsOrigin(), pGhost->GetAbsOrigin(), pGhost->GetPlayerMins(), pGhost->GetPlayerMaxs() ); UTIL_TraceRay( ray, PlayerSolidMask(), pGhost, COLLISION_GROUP_PLAYER, &trace ); // If our trace is clear, spawn that ghost if ( trace.fraction == 1.0f ) { // Force the players kart angles to line up with our current ghost angles. // This should put us in the kart at the same direction we are currently looking. pGhost->ForcePlayerViewAngles( pGhost->GetAbsAngles() ); pGhost->m_Shared.RemoveCond( TF_COND_HALLOWEEN_GHOST_MODE ); pGhost->m_Shared.AddCond( TF_COND_HALLOWEEN_KART ); pGhost->m_Shared.AddCond( TF_COND_HALLOWEEN_IN_HELL ); // keep you in hell to be able to respawn as ghost pGhost->m_Shared.AddCond( TF_COND_HALLOWEEN_QUICK_HEAL, 3, this ); pGhost->EmitSound( "BumperCar.SpawnFromLava" ); DispatchParticleEffect( "ghost_appearation", PATTACH_ABSORIGIN, pGhost ); if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) { // achievement for me! AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_DOOMSDAY_RESPAWN_TEAMMATES ); IGameEvent *pEvent = gameeventmanager->CreateEvent( "respawn_ghost" ); if ( pEvent ) { pEvent->SetInt( "reviver", GetUserID() ); pEvent->SetInt( "ghost", pGhost->GetUserID() ); gameeventmanager->FireEvent( pEvent, true ); } } } } else if ( tf_halloween_allow_ghost_hit_by_kart_delay.GetFloat() > 0 && gpGlobals->curtime - pGhost->m_flGhostLastHitByKartTime > tf_halloween_allow_ghost_hit_by_kart_delay.GetFloat() ) { // punt off other team ghost float flImpactForce = GetLocalVelocity().Length(); flImpactForce = MAX( 100.f, flImpactForce ); // add min force Vector vOffset = pGhost->WorldSpaceCenter() - WorldSpaceCenter(); vOffset.z = 0; Vector vPuntDir = ( vOffset ).Normalized(); vPuntDir.z = 0.5f; pGhost->ApplyAirBlastImpulse( tf_halloween_kart_punting_ghost_force_scale.GetFloat() * flImpactForce * vPuntDir ); pGhost->EmitSound( "BumperCar.HitGhost" ); pGhost->m_flGhostLastHitByKartTime = gpGlobals->curtime; } } } if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() ) { if ( IsUsingActionSlot() && GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_GRAPPLINGHOOK ) { CTFGrapplingHook *pGrapplingHook = dynamic_cast< CTFGrapplingHook* >( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pGrapplingHook ) { Weapon_Switch( pGrapplingHook ); } } CBaseEntity *pHookTarget = GetGrapplingHookTarget(); if ( pHookTarget ) { // detatch hook if the object's picked up if ( pHookTarget->IsBaseObject() ) { CBaseObject *pObj = assert_cast< CBaseObject* >( pHookTarget ); if ( pObj->IsCarried() ) { SetGrapplingHookTarget( NULL ); pHookTarget = NULL; } } // check if something is blocking the player from traveling to the hook target if ( pHookTarget ) { trace_t tr; CTraceFilterLOS filter( this, COLLISION_GROUP_PLAYER_MOVEMENT, pHookTarget ); UTIL_TraceLine( WorldSpaceCenter(), pHookTarget->WorldSpaceCenter(), MASK_PLAYERSOLID, &filter, &tr ); if ( !tr.DidHit() ) { m_flLastSeenHookTarget = gpGlobals->curtime; } else if ( gpGlobals->curtime - m_flLastSeenHookTarget > tf_grapplinghook_los_force_detach_time.GetFloat() ) { // force to detach if the hooker lost sight of the target for sometime SetGrapplingHookTarget( NULL ); } } } } UpdateCustomAttributes(); // Time to finish the current random expression? Or time to pick a new one? if ( IsAlive() && !IsReadyToTauntWithPartner() && ( m_flNextSpeakWeaponFire < gpGlobals->curtime ) && m_flNextRandomExpressionTime >= 0 && gpGlobals->curtime > m_flNextRandomExpressionTime ) { // Random expressions need to be cleared, because they don't loop. So if we // pick the same one again, we want to restart it. ClearExpression(); m_iszExpressionScene = NULL_STRING; UpdateExpression(); } if ( IsTaunting() ) { if ( !m_strTauntSoundName.IsEmpty() && m_flTauntSoundTime > 0 && m_flTauntSoundTime <= gpGlobals->curtime ) { EmitSound( m_strTauntSoundName.String() ); m_flTauntSoundTime = 0.f; } if ( !m_strTauntSoundLoopName.IsEmpty() && m_flTauntSoundLoopTime > 0 && m_flTauntSoundLoopTime <= gpGlobals->curtime ) { CReliableBroadcastRecipientFilter filter; UserMessageBegin( filter, "PlayerTauntSoundLoopStart" ); WRITE_BYTE( entindex() ); WRITE_STRING( m_strTauntSoundLoopName.String() ); MessageEnd(); m_flTauntSoundLoopTime = 0.f; } // play taunt outro if ( m_flTauntOutroTime > 0.f && m_flTauntOutroTime <= gpGlobals->curtime ) { m_bAllowedToRemoveTaunt = true; float flDuration = PlayTauntOutroScene(); m_flTauntRemoveTime = gpGlobals->curtime + flDuration; m_flTauntOutroTime = 0.f; } } // Halloween Hacks // Spell Casting on Attack1 if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { // Check if this is the spellbook so we can save off info to preserve weapon switching CTFSpellBook *pSpellBook = dynamic_cast( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pSpellBook ) { // cast Spell if ( m_nButtons & IN_ATTACK ) { if ( pSpellBook ) { pSpellBook->PrimaryAttack(); } } } // Speed Boost if ( m_nButtons & IN_ATTACK2 ) { if ( GetKartSpeedBoost() >= 1.0f ) { m_flKartNextAvailableBoost = gpGlobals->curtime + tf_halloween_kart_boost_recharge.GetFloat(); m_Shared.AddCond( TF_COND_HALLOWEEN_KART_DASH, tf_halloween_kart_boost_duration.GetFloat() ); } } } CBaseEntity *pGroundEntity = GetGroundEntity(); // We consider players "in air" if they have no ground entity and they're not in water. if ( pGroundEntity == NULL && GetWaterLevel() == WL_NotInWater ) { if ( m_iLeftGroundHealth < 0 ) { m_iLeftGroundHealth = GetHealth(); } } else { m_iLeftGroundHealth = -1; if ( GetFlags() & FL_ONGROUND ) { m_Shared.RemoveCond( TF_COND_KNOCKED_INTO_AIR ); } if ( m_iBlastJumpState ) { const char *pszEvent = NULL; if ( StickyJumped() ) { pszEvent = "sticky_jump_landed"; } else if ( RocketJumped() ) { pszEvent = "rocket_jump_landed"; } ClearBlastJumpState(); if ( pszEvent ) { IGameEvent * event = gameeventmanager->CreateEvent( pszEvent ); if ( event ) { event->SetInt( "userid", GetUserID() ); gameeventmanager->FireEvent( event ); } } } } if( IsTaunting() ) { bool bStopTaunt = false; // if I'm not supposed to move during taunt // stop taunting if I lost my ground entity or was moved at all if ( !CanMoveDuringTaunt() ) { bStopTaunt |= pGroundEntity == NULL; if ( m_TauntEconItemView.IsValid() && m_TauntEconItemView.GetStaticData()->GetTauntData()->ShouldStopTauntIfMoved() ) bStopTaunt |= m_vecTauntStartPosition.DistToSqr( GetAbsOrigin() ) > 0.1f; } if ( !bStopTaunt ) { bStopTaunt |= ShouldStopTaunting(); } if ( bStopTaunt ) { CancelTaunt(); } } if ( ( RocketJumped() || StickyJumped() ) && IsAlive() && m_bCreatedRocketJumpParticles == false ) { const char *pEffectName = "rocketjump_smoke"; DispatchParticleEffect( pEffectName, PATTACH_POINT_FOLLOW, this, "foot_L" ); DispatchParticleEffect( pEffectName, PATTACH_POINT_FOLLOW, this, "foot_R" ); m_bCreatedRocketJumpParticles = true; } if ( !m_bCollideWithSentry ) { if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) { CBaseObject *pSentry = GetObjectOfType( OBJ_SENTRYGUN ); if ( !pSentry ) { m_bCollideWithSentry = true; } else { if ( ( pSentry->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() > 2500 ) { m_bCollideWithSentry = true; } } } else { m_bCollideWithSentry = true; } } if ( gpGlobals->curtime > m_flCommentOnCarrying && (m_flCommentOnCarrying != 0.f) ) { m_flCommentOnCarrying = 0.f; CBaseObject* pObj = m_Shared.GetCarriedObject(); if ( pObj ) { SpeakConceptIfAllowed( MP_CONCEPT_CARRYING_BUILDING, pObj->GetResponseRulesModifier() ); } } #ifdef TF_RAID_MODE CTFNavArea *area = (CTFNavArea *)GetLastKnownArea(); if ( area && area->HasAttributeTF( TF_NAV_RESCUE_CLOSET ) ) { // we're standing in a rescue closet and need a friend to let us out - call for help! SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_HELP ); } #endif // Wrenchmotron taunt effect if ( m_bIsTeleportingUsingEurekaEffect ) { if ( m_teleportHomeFlashTimer.HasStarted() && m_teleportHomeFlashTimer.IsElapsed() ) { m_teleportHomeFlashTimer.Invalidate(); if ( !tf_test_teleport_home_fx.GetBool() ) { // cover up the end of the taunt with a flash color32 colorHit = { 255, 255, 255, 255 }; UTIL_ScreenFade( this, colorHit, 0.25f, 0.25f, FFADE_IN ); } Vector origin = GetAbsOrigin(); CPVSFilter filter( origin ); UserMessageBegin( filter, "PlayerTeleportHomeEffect" ); WRITE_BYTE( entindex() ); MessageEnd(); // DispatchParticleEffect( "drg_wrenchmotron_teleport", PATTACH_ABSORIGIN ); switch( GetTeamNumber() ) { case TF_TEAM_RED: TE_TFParticleEffect( filter, 0.0, "teleported_red", origin, vec3_angle ); TE_TFParticleEffect( filter, 0.0, "player_sparkles_red", origin, vec3_angle, this, PATTACH_POINT ); break; case TF_TEAM_BLUE: TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle ); TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle, this, PATTACH_POINT ); break; default: break; } } // teleport home when taunt finishes if ( !IsTaunting() ) { // drop the intel and any powerup we are carrying DropFlag(); DropRune(); EmitSound( "Building_Teleporter.Send" ); m_bIsTeleportingUsingEurekaEffect = false; CObjectTeleporter* pTeleExit = assert_cast< CObjectTeleporter* >( GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ) ); // Check if they wanted to go to their teleporter AND their teleporter can accept them if ( m_eEurekaTeleportTarget == EUREKA_TELEPORT_TELEPORTER_EXIT && pTeleExit && ( pTeleExit->GetState() != TELEPORTER_STATE_BUILDING ) ) { pTeleExit->RecieveTeleportingPlayer( this ); } else { // Default to the spawn TFGameRules()->GetPlayerSpawnSpot( this ); } } } // Send active weapon's clip state to attached medics bool bSendClipInfo = gpGlobals->curtime > m_flNextClipSendTime && m_Shared.GetNumHealers() && IsAlive(); if ( bSendClipInfo ) { CTFWeaponBase *pTFWeapon = GetActiveTFWeapon(); if ( pTFWeapon ) { int nClip = 0; if ( m_Shared.InCond( TF_COND_DISGUISED ) ) { nClip = m_Shared.GetDisguiseAmmoCount(); } else { nClip = pTFWeapon->UsesClipsForAmmo1() ? pTFWeapon->Clip1() : GetAmmoCount( pTFWeapon->GetPrimaryAmmoType() ); } if ( nClip >= 0 && nClip != m_nActiveWpnClipPrev ) { if ( nClip > 500 ) { Warning( "Heal Target: ClipSize Data Limit Exceeded: %d (max 500)\n", nClip ); nClip = MIN( nClip, 500 ); } m_nActiveWpnClip.Set( nClip ); m_nActiveWpnClipPrev = m_nActiveWpnClip; m_flNextClipSendTime = gpGlobals->curtime + 0.25f; } } } if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY && ( GetFlags() & FL_DUCKING ) && ( pGroundEntity != NULL ) ) { int nDisguiseAsDispenserOnCrouch = 0; CALL_ATTRIB_HOOK_FLOAT( nDisguiseAsDispenserOnCrouch, disguise_as_dispenser_on_crouch ); if ( nDisguiseAsDispenserOnCrouch != 0 ) { m_Shared.AddCond( TF_COND_DISGUISED_AS_DISPENSER, 0.5f ); } } // rune charge over time if ( m_Shared.CanRuneCharge() && !m_Shared.IsRuneCharged() ) { float dt = gpGlobals->curtime - m_flLastRuneChargeUpdate; float flAdd = dt * 100.f / tf_powerup_max_charge_time.GetFloat(); m_Shared.SetRuneCharge( m_Shared.GetRuneCharge() + flAdd ); if (m_Shared.GetCarryingRuneType() == RUNE_SUPERNOVA && m_Shared.IsRuneCharged() ) { ClientPrint( this, HUD_PRINTCENTER, "#TF_Powerup_Supernova_Deploy" ); } } m_flLastRuneChargeUpdate = gpGlobals->curtime; // You can't touch a hooked target, so transmit plague when you get as close as you can if ( GetGrapplingHookTarget() && GetGrapplingHookTarget()->IsPlayer() && m_Shared.GetCarryingRuneType() == RUNE_PLAGUE ) { CTFPlayer *pHookedPlayer = ToTFPlayer( GetGrapplingHookTarget() ); float flDistSqrToTarget = GetAbsOrigin().DistToSqr( pHookedPlayer->GetAbsOrigin() ); if ( flDistSqrToTarget < 8100 && !pHookedPlayer->m_Shared.InCond( TF_COND_PLAGUE ) && !m_Shared.IsAlly( pHookedPlayer ) && !pHookedPlayer->m_Shared.IsInvulnerable() && pHookedPlayer->m_Shared.GetCarryingRuneType() != RUNE_RESIST ) { pHookedPlayer->m_Shared.AddCond( TF_COND_PLAGUE, PERMANENT_CONDITION, this ); } } if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { // prevents player from standing on bot's head to block its movement. if ( pGroundEntity && pGroundEntity->IsPlayer() ) { Vector vPush = GetAbsOrigin() - pGroundEntity->GetAbsOrigin(); vPush.z = 0.f; vPush.NormalizeInPlace(); vPush.z = 1.f; vPush *= 100.f; ApplyAbsVelocityImpulse( vPush ); } } // Scale our head m_flHeadScale = Approach( GetDesiredHeadScale(), m_flHeadScale, GetHeadScaleSpeed() ); // scale our torso m_flTorsoScale = Approach( GetDesiredTorsoScale(), m_flTorsoScale, GetTorsoScaleSpeed() ); // scale our torso m_flHandScale = Approach( GetDesiredHandScale(), m_flHandScale, GetHandScaleSpeed() ); /* #ifdef STAGING_ONLY if ( m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) { // JetPack testing if ( m_nButtons & IN_JUMP && !( GetFlags() & FL_ONGROUND ) && m_Shared.GetSpaceJumpChargeMeter() > tf_space_thrust_use_rate.GetFloat() ) { //mv->m_vecVelocity[2] += 10.0f; Vector vThrust = Vector(0,0,0); switch( GetPlayerClass()->GetClassIndex() ) { case TF_CLASS_SCOUT : vThrust.z = tf_space_thrust_scout.GetFloat(); break; case TF_CLASS_SNIPER : vThrust.z = tf_space_thrust_sniper.GetFloat(); break; case TF_CLASS_SOLDIER : vThrust.z = tf_space_thrust_soldier.GetFloat(); break; case TF_CLASS_DEMOMAN : vThrust.z = tf_space_thrust_demo.GetFloat(); break; case TF_CLASS_MEDIC : vThrust.z = tf_space_thrust_medic.GetFloat(); break; case TF_CLASS_HEAVYWEAPONS : vThrust.z = tf_space_thrust_heavy.GetFloat(); break; case TF_CLASS_PYRO : vThrust.z = tf_space_thrust_pyro.GetFloat(); break; case TF_CLASS_SPY : vThrust.z = tf_space_thrust_spy.GetFloat(); break; case TF_CLASS_ENGINEER : vThrust.z = tf_space_thrust_engy.GetFloat(); break; } ApplyAbsVelocityImpulse( vThrust ); m_Shared.SetSpaceJumpChargeMeter( m_Shared.GetSpaceJumpChargeMeter() - tf_space_thrust_use_rate.GetFloat() ); } else { if ( GetFlags() & FL_ONGROUND ) { m_Shared.SetSpaceJumpChargeMeter( m_Shared.GetSpaceJumpChargeMeter() + tf_space_thrust_recharge_rate.GetFloat() ); } } } #endif */ SetContextThink( &CTFPlayer::TFPlayerThink, gpGlobals->curtime, "TFPlayerThink" ); m_flLastThinkTime = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: Returns a portion of health every think. //----------------------------------------------------------------------------- void CTFPlayer::RegenThink( void ) { if ( !IsAlive() ) return; // Queue the next think SetContextThink( &CTFPlayer::RegenThink, gpGlobals->curtime + TF_REGEN_TIME, "RegenThink" ); // if we're going in to this too often, quit out. if ( m_flLastHealthRegenAt + TF_REGEN_TIME > gpGlobals->curtime ) return; bool bShowRegen = true; // Medic has a base regen amount if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) { // Heal faster if we haven't been in combat for a while. float flTimeSinceDamage = gpGlobals->curtime - GetLastDamageReceivedTime(); float flScale = RemapValClamped( flTimeSinceDamage, 5.0f, 10.0f, 1.0f, 2.0f ); float flRegenAmt = TF_REGEN_AMOUNT; // If you are healing a hurt patient, increase your base regen CTFPlayer *pPatient = ToTFPlayer( MedicGetHealTarget() ); if ( pPatient && pPatient->GetHealth() < pPatient->GetMaxHealth() ) { // Double regen amount flRegenAmt += TF_REGEN_AMOUNT; } flRegenAmt *= flScale; // If the medic has this attribute, increase their regen. if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) { int iHealingMastery = 0; CALL_ATTRIB_HOOK_INT( iHealingMastery, healing_mastery ); if ( iHealingMastery ) { float flPerc = RemapValClamped( (float)iHealingMastery, 1.f, 4.f, 1.25f, 2.f ); flRegenAmt *= flPerc; } } m_flAccumulatedHealthRegen += flRegenAmt; bShowRegen = false; } // Other classes can be regenerated by items float flRegenAmount = 0; CALL_ATTRIB_HOOK_FLOAT( flRegenAmount, add_health_regen ); if ( flRegenAmount ) { float flTimeSinceDamage = gpGlobals->curtime - GetLastDamageReceivedTime(); float flScale = 1.0f; // Ignore Scale for MvM, always give full regen if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { flScale = 1.0f; } else if ( flTimeSinceDamage < 5.0f ) { flScale = 0.25f; } else { flScale = RemapValClamped( flTimeSinceDamage, 5.0f, 10.0f, 0.5f, 1.0f ); } flRegenAmount *= flScale; } m_flAccumulatedHealthRegen += flRegenAmount; // if ( m_Shared.InCond( TF_COND_HEALING_DEBUFF ) ) // { // m_flAccumulatedHealthRegen *= 0.75f; // } int nHealAmount = 0; if ( m_flAccumulatedHealthRegen >= 1.f ) { nHealAmount = floor( m_flAccumulatedHealthRegen ); if ( GetHealth() < GetMaxHealth() ) { int nHealedAmount = TakeHealth( nHealAmount, DMG_GENERIC | DMG_IGNORE_DEBUFFS ); if ( nHealedAmount > 0 ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_healed" ); if ( event ) { event->SetInt( "priority", 1 ); // HLTV event priority event->SetInt( "patient", GetUserID() ); event->SetInt( "healer", GetUserID() ); event->SetInt( "amount", nHealedAmount ); gameeventmanager->FireEvent( event ); } } } } else if ( m_flAccumulatedHealthRegen < -1.f ) { nHealAmount = ceil( m_flAccumulatedHealthRegen ); TakeDamage( CTakeDamageInfo( this, this, NULL, vec3_origin, WorldSpaceCenter(), nHealAmount * -1, DMG_GENERIC ) ); } if ( GetHealth() < GetMaxHealth() && nHealAmount != 0 && bShowRegen ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" ); if ( event ) { event->SetInt( "amount", nHealAmount ); event->SetInt( "entindex", entindex() ); event->SetInt( "weapon_def_index", INVALID_ITEM_DEF_INDEX ); gameeventmanager->FireEvent( event ); } } m_flAccumulatedHealthRegen -= nHealAmount; m_flLastHealthRegenAt = gpGlobals->curtime; // Regenerate ammo if ( m_flNextAmmoRegenAt < gpGlobals->curtime ) { // We regen ammo every 5 seconds m_flNextAmmoRegenAt = gpGlobals->curtime + 5.0; flRegenAmount = 0; CALL_ATTRIB_HOOK_FLOAT( flRegenAmount, addperc_ammo_regen ); if ( flRegenAmount ) { RegenAmmoInternal( TF_AMMO_PRIMARY, flRegenAmount ); RegenAmmoInternal( TF_AMMO_SECONDARY, flRegenAmount ); } // Regenerate metal int iMetal = 0; CALL_ATTRIB_HOOK_INT( iMetal, add_metal_regen ); if ( iMetal ) { GiveAmmo( iMetal, TF_AMMO_METAL, true ); } } } //----------------------------------------------------------------------------- // Purpose: Returns a portion of health every think. //----------------------------------------------------------------------------- void CTFPlayer::RuneRegenThink( void ) { if ( !IsAlive() ) return; // Queue the next think SetContextThink( &CTFPlayer::RuneRegenThink, gpGlobals->curtime + TF_REGEN_TIME_RUNE, "RuneRegenThink" ); // if we're going in to this too often, quit out. if ( m_flLastRuneHealthRegenAt + TF_REGEN_TIME_RUNE > gpGlobals->curtime ) return; int nRuneType = m_Shared.GetCarryingRuneType(); if ( nRuneType == RUNE_NONE && !HasTheFlag() ) return; // Regenerate health float flAmount = 0.f; switch ( GetPlayerClass()->GetClassIndex() ) { case TF_CLASS_SCOUT: case TF_CLASS_SPY: flAmount = 16; break; case TF_CLASS_SNIPER: case TF_CLASS_ENGINEER: flAmount = 14; break; case TF_CLASS_MEDIC: case TF_CLASS_DEMOMAN: case TF_CLASS_PYRO: flAmount = 12; break; case TF_CLASS_SOLDIER: flAmount = 10; break; case TF_CLASS_HEAVYWEAPONS: flAmount = 8; break; } if ( nRuneType == RUNE_REGEN ) { m_flAccumulatedRuneHealthRegen += flAmount; } // King and buffed team mates get some health regeneration unless they have the plague else if ( ( nRuneType == RUNE_KING || m_Shared.InCond( TF_COND_KING_BUFFED ) ) && !m_Shared.InCond( TF_COND_PLAGUE ) ) { flAmount *= 0.3; m_flAccumulatedRuneHealthRegen += flAmount; } // non powered up flag carriers get a small health regeneration else if ( HasTheFlag() && nRuneType == RUNE_NONE ) { flAmount *= 0.1; m_flAccumulatedRuneHealthRegen += flAmount; } int nHealAmount = 0; if ( m_flAccumulatedRuneHealthRegen >= 1.0 ) { nHealAmount = floor( m_flAccumulatedRuneHealthRegen ); if ( GetHealth() < GetMaxHealth() ) { TakeHealth( nHealAmount, DMG_GENERIC ); int nHealedAmount = TakeHealth( nHealAmount, DMG_GENERIC ); if ( nHealedAmount > 0 ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_healed" ); if ( event ) { event->SetInt("priority", 1); // HLTV event priority event->SetInt("patient", GetUserID()); event->SetInt( "healer", GetUserID() ); event->SetInt( "amount", nHealedAmount ); gameeventmanager->FireEvent( event ); } } } } else if ( m_flAccumulatedRuneHealthRegen < -1.0 ) { nHealAmount = ceil( m_flAccumulatedRuneHealthRegen ); TakeDamage( CTakeDamageInfo( this, this, NULL, vec3_origin, WorldSpaceCenter(), nHealAmount * -1, DMG_GENERIC ) ); } if ( GetHealth() < GetMaxHealth() && nHealAmount != 0 ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" ); if ( event ) { event->SetInt( "amount", nHealAmount ); event->SetInt( "entindex", entindex() ); event->SetInt( "weapon_def_index", INVALID_ITEM_DEF_INDEX ); gameeventmanager->FireEvent( event ); } } m_flAccumulatedRuneHealthRegen -= nHealAmount; m_flLastRuneHealthRegenAt = gpGlobals->curtime; // Regenerate ammo and metal if ( m_flNextRuneAmmoRegenAt < gpGlobals->curtime ) { m_flNextRuneAmmoRegenAt = gpGlobals->curtime + 5; if ( nRuneType == RUNE_REGEN ) { RegenAmmoInternal( TF_AMMO_PRIMARY, 0.5f ); RegenAmmoInternal( TF_AMMO_SECONDARY, 0.5f ); GiveAmmo( 200, TF_AMMO_METAL, true ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RegenAmmoInternal( int iIndex, float flRegen ) { m_flAccumulatedAmmoRegens[iIndex] += flRegen; // As soon as we have enough accumulated to regen a single unit of ammo, do it. int iMaxAmmo = GetMaxAmmo(iIndex); int iAmmo = m_flAccumulatedAmmoRegens[iIndex] * iMaxAmmo; if ( iAmmo >= 1 ) { GiveAmmo( iAmmo, iIndex, true ); m_flAccumulatedAmmoRegens[iIndex] -= ((float)iAmmo / (float)iMaxAmmo); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFPlayer::~CTFPlayer() { delete [] m_damageRateArray; DestroyRagdoll(); m_PlayerAnimState->Release(); FOR_EACH_VEC( m_ItemsToTest, i ) { int iDef = TESTITEM_DEFINITIONS_BEGIN_AT + m_ItemsToTest[i].scriptItem.GetItemDefIndex(); ItemSystem()->GetItemSchema()->ItemTesting_DiscardTestDefinition( iDef ); m_ItemsToTest[i].pKV->deleteThis(); m_ItemsToTest[i].pKV = NULL; } if ( m_hReviveMarker ) { UTIL_Remove( m_hReviveMarker ); m_hReviveMarker = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFPlayer *CTFPlayer::CreatePlayer( const char *className, edict_t *ed ) { CTFPlayer::s_PlayerEdict = ed; return (CTFPlayer*)CreateEntityByName( className ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::UpdateTimers( void ) { m_Shared.ConditionThink(); m_Shared.InvisibilityThink(); } //----------------------------------------------------------------------------- // Estimate where a projectile fired from the given weapon will initially hit (it may bounce on from there). // NOTE: We should be able to directly compute this knowing initial velocity, angle, gravity, etc, // but I have been unable to find a formula that reproduces what our physics actually // do. //----------------------------------------------------------------------------- Vector CTFPlayer::EstimateProjectileImpactPosition( CTFWeaponBaseGun *weapon ) { if ( !weapon ) { return GetAbsOrigin(); } const QAngle &angles = EyeAngles(); float initVel = weapon->IsWeapon( TF_WEAPON_PIPEBOMBLAUNCHER ) ? 900.0f : weapon->GetProjectileSpeed(); CALL_ATTRIB_HOOK_FLOAT( initVel, mult_projectile_range ); return EstimateProjectileImpactPosition( angles.x, angles.y, initVel ); } //----------------------------------------------------------------------------- // Estimate where a stickybomb projectile will hit, // using given pitch, yaw, and weapon charge (0-1) //----------------------------------------------------------------------------- Vector CTFPlayer::EstimateStickybombProjectileImpactPosition( float pitch, float yaw, float charge ) { // estimate impact spot float initVel = charge * ( TF_PIPEBOMB_MAX_CHARGE_VEL - TF_PIPEBOMB_MIN_CHARGE_VEL ) + TF_PIPEBOMB_MIN_CHARGE_VEL; CALL_ATTRIB_HOOK_FLOAT( initVel, mult_projectile_range ); return EstimateProjectileImpactPosition( pitch, yaw, initVel ); } //----------------------------------------------------------------------------- // Estimate where a projectile fired will initially hit (it may bounce on from there), // using given pitch, yaw, and initial velocity. //----------------------------------------------------------------------------- Vector CTFPlayer::EstimateProjectileImpactPosition( float pitch, float yaw, float initVel ) { // copied from CTFWeaponBaseGun::FirePipeBomb() Vector vecForward, vecRight, vecUp; QAngle angles( pitch, yaw, 0.0f ); AngleVectors( angles, &vecForward, &vecRight, &vecUp ); // we will assume bots never flip viewmodels float fRight = 8.f; Vector vecSrc = Weapon_ShootPosition(); vecSrc += vecForward * 16.0f + vecRight * fRight + vecUp * -6.0f; const float initVelScale = 0.9f; Vector vecVelocity = initVelScale * ( ( vecForward * initVel ) + ( vecUp * 200.0f ) ); const float timeStep = 0.01f; const float maxTime = 5.0f; Vector pos = vecSrc; Vector lastPos = pos; const float g = GetCurrentGravity(); // compute forward facing unit vector in horiz plane Vector alongDir = vecForward; alongDir.z = 0.0f; alongDir.NormalizeInPlace(); float alongVel = FastSqrt( vecVelocity.x * vecVelocity.x + vecVelocity.y * vecVelocity.y ); trace_t trace; NextBotTraceFilterIgnoreActors traceFilter( NULL, COLLISION_GROUP_NONE ); float t; for( t = 0.0f; t < maxTime; t += timeStep ) { float along = alongVel * t; float height = vecVelocity.z * t - 0.5f * g * t * t; pos.x = vecSrc.x + alongDir.x * along; pos.y = vecSrc.y + alongDir.y * along; pos.z = vecSrc.z + height; UTIL_TraceHull( lastPos, pos, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); if ( trace.DidHit() ) { #ifdef STAGING_ONLY if ( tf_debug_ballistics.GetBool() ) { NDebugOverlay::Cross3D( trace.endpos, 10.0f, 100, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } #endif break; } #ifdef STAGING_ONLY if ( tf_debug_ballistics.GetBool() ) { NDebugOverlay::Line( lastPos, pos, 0, 255, 0, false, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } #endif lastPos = pos; } return trace.endpos; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ) { // TF Players only process scene events on the server while running taunts if ( !IsTaunting() ) return false; // Only process sequences if ( event->GetType() != CChoreoEvent::SEQUENCE ) return false; return BaseClass::ProcessSceneEvent( info, scene, event ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PreThink() { // Update timers. UpdateTimers(); // Pass through to the base class think. BaseClass::PreThink(); // Reset bullet force accumulator, only lasts one frame, for ragdoll forces from multiple shots. m_vecTotalBulletForce = vec3_origin; CheckForIdle(); ProcessSceneEvents(); if ( TFGameRules()->IsInArenaMode() == true ) { if ( TFGameRules()->State_Get() != GR_STATE_TEAM_WIN ) { if ( GetTeamNumber() == TEAM_SPECTATOR ) { m_Local.m_iHideHUD &= ~HIDEHUD_MISCSTATUS; } } } // Hype Decreases over time if ( IsPlayerClass( TF_CLASS_SCOUT ) ) { float flHypeDecays = 0; CALL_ATTRIB_HOOK_FLOAT( flHypeDecays, hype_decays_over_time ); if ( flHypeDecays != 0 ) { // Loose hype over time float flHype = m_Shared.GetScoutHypeMeter(); flHype = flHype - flHypeDecays; m_Shared.SetScoutHypeMeter( flHype ); TeamFortress_SetSpeed(); } } #ifdef STAGING_ONLY // show ballistic path for currently equipped weapon (ie: grenades) if ( tf_debug_ballistics.GetBool() ) { CTFWeaponBaseGun *myWeapon = dynamic_cast< CTFWeaponBaseGun * >( m_Shared.GetActiveTFWeapon() ); EstimateProjectileImpactPosition( myWeapon ); } if ( tf_debug_ballistic_targeting.GetBool() ) { CTFWeaponBaseGun *myWeapon = dynamic_cast< CTFWeaponBaseGun * >( m_Shared.GetActiveTFWeapon() ); Vector hitPos = EstimateProjectileImpactPosition( myWeapon ); Vector toEye = EyePosition() - hitPos; Vector toTarget = tf_debug_ballistic_target - hitPos; float error = toTarget.NormalizeInPlace(); if ( error < 20.0f ) { // on target NDebugOverlay::Cross3D( tf_debug_ballistic_target, 10.0f, 0, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } else { if ( DotProduct( toEye, toTarget ) > 0.0f ) { // too far NDebugOverlay::Cross3D( tf_debug_ballistic_target, 10.0f, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); NDebugOverlay::VertArrow( tf_debug_ballistic_target, tf_debug_ballistic_target - Vector( 0, 0, error ), 10.0f, 0, 0, 255, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } else { // too near NDebugOverlay::Cross3D( tf_debug_ballistic_target, 10.0f, 255, 0, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); NDebugOverlay::VertArrow( tf_debug_ballistic_target, tf_debug_ballistic_target + Vector( 0, 0, error ), 10.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } } } #endif // STAGING_ONLY } ConVar mp_idledealmethod( "mp_idledealmethod", "1", FCVAR_GAMEDLL, "Deals with Idle Players. 1 = Sends them into Spectator mode then kicks them if they're still idle, 2 = Kicks them out of the game;" ); ConVar mp_idlemaxtime( "mp_idlemaxtime", "3", FCVAR_GAMEDLL, "Maximum time a player is allowed to be idle (in minutes)" ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CheckForIdle( void ) { if ( m_afButtonLast != m_nButtons ) m_flLastAction = gpGlobals->curtime; if ( mp_idledealmethod.GetInt() ) { if ( IsHLTV() || IsReplay() ) return; if ( IsFakeClient() ) return; if ( IsCoaching() && GetStudent() != NULL ) return; if ( TFGameRules() && TFGameRules()->ShowMatchSummary() ) return; if ( TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) return; //Don't mess with the host on a listen server (probably one of us debugging something) if ( engine->IsDedicatedServer() == false && entindex() == 1 ) return; if ( IsAutoKickDisabled() ) return; const bool cbMoving = ( m_nButtons & ( IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) != 0; m_bIsAFK = false; if ( !cbMoving && PointInRespawnRoom( this, WorldSpaceCenter() ) ) { m_flTimeInSpawn += TICK_INTERVAL; } else m_flTimeInSpawn = 0; if ( TFGameRules()->IsInArenaMode() && tf_arena_use_queue.GetBool() == true ) { if ( GetTeamNumber() == TEAM_SPECTATOR ) return; if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN && TFGameRules()->GetWinningTeam() != GetTeamNumber() ) { if ( m_bArenaIsAFK ) { m_bIsAFK = true; m_bArenaIsAFK = false; } } } else { // Cannot possibly get out of the spawn room in 0 seconds--so if the ConVar says 0, let's assume 30 seconds. float flIdleTime = Max( mp_idlemaxtime.GetFloat() * 60, 30.0f ); if ( TFGameRules()->InStalemate() ) { flIdleTime = mp_stalemate_timelimit.GetInt() * 0.5f; } m_bIsAFK = ( gpGlobals->curtime - m_flLastAction ) > flIdleTime || ( m_flTimeInSpawn > flIdleTime ); } if ( m_bIsAFK == true ) { bool bKickPlayer = false; ConVarRef mp_allowspectators( "mp_allowspectators" ); if ( ( mp_allowspectators.IsValid() && mp_allowspectators.GetBool() == false ) || ( TFGameRules()->IsInArenaMode() && tf_arena_use_queue.GetBool() ) ) { // just kick the player if this server doesn't allow spectators bKickPlayer = true; } else if ( mp_idledealmethod.GetInt() == 1 ) { if ( GetTeamNumber() < FIRST_GAME_TEAM ) { bKickPlayer = true; } else { //First send them into spectator mode then kick him. ForceChangeTeam( TEAM_SPECTATOR ); m_flLastAction = gpGlobals->curtime; m_flTimeInSpawn = 0; return; } } else if ( mp_idledealmethod.GetInt() == 2 ) { bKickPlayer = true; } if ( bKickPlayer == true ) { UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#game_idle_kick", GetPlayerName() ); engine->ServerCommand( UTIL_VarArgs( "kickid %d %s\n", GetUserID(), g_pszIdleKickString ) ); m_flLastAction = gpGlobals->curtime; m_flTimeInSpawn = 0; } } } } extern ConVar flashlight; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::FlashlightIsOn( void ) { return IsEffectActive( EF_DIMLIGHT ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::FlashlightTurnOn( void ) { if( flashlight.GetInt() > 0 && IsAlive() ) { AddEffects( EF_DIMLIGHT ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::FlashlightTurnOff( void ) { if( IsEffectActive(EF_DIMLIGHT) ) { RemoveEffects( EF_DIMLIGHT ); } } //----------------------------------------------------------------------------- // Purpose: Update Halloween scenario effects on players. //----------------------------------------------------------------------------- void CTFPlayer::UpdateHalloween( void ) { // This is a push force if ( !m_vHalloweenKartPush.IsZero() ) { if ( !m_Shared.InCond( TF_COND_INVULNERABLE_USER_BUFF ) ) { CPVSFilter filter( GetAbsOrigin() ); TE_TFParticleEffect( filter, 0.0, "kart_impact_sparks", GetAbsOrigin(), vec3_angle, this, PATTACH_ABSORIGIN ); float flStunDuration = m_vHalloweenKartPush.Length() / 1000.0f; if ( m_vHalloweenKartPush.LengthSqr() > 1000 * 1000 ) { TE_TFParticleEffect( filter, 0.0, "kartimpacttrail", GetAbsOrigin(), vec3_angle, this, PATTACH_ABSORIGIN_FOLLOW ); EmitSound( "BumperCar.BumpIntoAir" ); EmitSound( "BumperCar.BumpHard" ); } else { EmitSound( "BumperCar.Bump" ); } if ( tf_halloween_kart_stun_enabled.GetBool() ) { m_Shared.StunPlayer( flStunDuration * tf_halloween_kart_stun_duration_scale.GetFloat(), tf_halloween_kart_stun_amount.GetFloat(), TF_STUN_BOTH | TF_STUN_NO_EFFECTS ); } if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) ) { m_Shared.RemoveCond( TF_COND_HALLOWEEN_KART_DASH ); } ApplyAirBlastImpulse( m_vHalloweenKartPush ); } m_vHalloweenKartPush.Zero(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::AddHalloweenKartPushEvent( CTFPlayer *pOther, CBaseEntity *pInflictor, CBaseEntity *pWeapon, Vector vForce, int iDamage, int iDamageType /* = 0 */ ) { // Create a damage event so they can get credit for the kill //m_vHalloweenKartPushEventTime + 0.2 > gpGlobals->curtime && if ( m_Shared.InCond( TF_COND_INVULNERABLE_USER_BUFF ) ) return; // Ignore small forces float flForce = vForce.LengthSqr(); if ( flForce < 100.0f ) return; float flExtraMultiplier = 1.f; if ( pOther && pOther != this && pOther->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ) { iDamage *= tf_halloween_kart_bomb_head_damage_scale.GetFloat(); flExtraMultiplier = tf_halloween_kart_bomb_head_impulse_scale.GetFloat(); pOther->SetKartBombHeadTarget( pOther ); pOther->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD ); } const float flCurrentKartKnockbackMultiplier = GetKartKnockbackMultiplier( flExtraMultiplier ); if ( pOther ) { // Fake Damage IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" ); if ( event ) { int iKartDamageType = DMG_CLUB | DMG_PREVENT_PHYSICS_FORCE; if ( pOther != this && flCurrentKartKnockbackMultiplier * vForce.LengthSqr() > 1000 * 1000 ) { iKartDamageType |= DMG_CRITICAL; } event->SetInt( "userid", GetUserID() ); event->SetInt( "health", MAX( 0, m_iHealth ) ); // HLTV event priority, not transmitted event->SetInt( "priority", 5 ); event->SetInt( "damageamount", iDamage ); // Hurt by another player. event->SetInt( "attacker", pOther->GetUserID() ); event->SetInt( "custom", TF_DMG_CUSTOM_SUICIDE ); event->SetBool( "crit", ( iKartDamageType & DMG_CRITICAL ) != 0 ); event->SetBool( "allseecrit", ( iKartDamageType & DMG_CRITICAL ) != 0 ); event->SetInt( "bonuseffect", (int)kBonusEffect_None ); // gameeventmanager->FireEvent( event ); } m_AchievementData.AddDamagerToHistory( pOther ); m_AchievementData.AddPusherToHistory( pOther ); //m_Shared.SetAssist( pOther ); } if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { m_iKartHealth += iDamage; } // HHH if ( TFGameRules()->IsIT( pOther ) ) { // Tag! You're IT! TFGameRules()->SetIT( this ); } if ( iDamageType == TF_DMG_CUSTOM_DECAPITATION_BOSS ) { m_flHHHKartAttackTime = gpGlobals->curtime; } //m_vHalloweenKartPushEventTime = gpGlobals->curtime; float flImpulseScale = 1.0f; // m_flKartHealth might change by this point, calculate new knock back multiplier const float flNewKartKnockbackMultiplier = GetKartKnockbackMultiplier( flExtraMultiplier ); // push other if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { // If applying damage increase the force, otherwise its likely just a wall collision if ( iDamage > 0 ) { vForce *= flImpulseScale * flNewKartKnockbackMultiplier; vForce.z *= ( ( flNewKartKnockbackMultiplier - 1.0f ) * 0.20f ) + 1.0f; // Decrease all forces if in the air if (!(GetFlags() & FL_ONGROUND) ) { vForce *= tf_halloween_kart_impact_air_scale.GetFloat(); } } } else { // Make non-karters take damage! vForce *= ( flImpulseScale * 2.0f ); //ghostinfo.SetDamageCustom( TF_DMG_CUSTOM_KART ); // Create a damage event based on Speed float flDamage = vForce.Length() / 50.0f + RandomFloat( 3.0f, 7.0f ); CTakeDamageInfo info; info.SetAttacker( pOther ); info.SetInflictor( pOther ); info.SetDamage( flDamage ); info.SetDamageCustom( TF_DMG_CUSTOM_KART ); info.SetDamagePosition( GetAbsOrigin() ); int iKartDamageType = DMG_CLUB; if ( flDamage > 20 ) { iKartDamageType |= DMG_CRITICAL; } info.SetDamageType( iKartDamageType ); info.SetDamageForce( vForce ); TakeDamage( info ); return; } m_vHalloweenKartPush += vForce; // Dropped collection game tokens if hit hard enough and we're in the collection minigame if ( pOther && iDamage > 10 && CTFMinigameLogic::GetMinigameLogic() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame()->GetMinigameType() == CTFMiniGame::EMinigameType::MINIGAME_HALLOWEEN2014_COLLECTION ) { CUtlVector< CTFPlayer* > vecEveryone; CollectPlayers( &vecEveryone ); int nNumPlayers = vecEveryone.Count(); // Drop tokens uint32 nNumToSpawn = ( iDamage / 5 ) + 1; nNumToSpawn = RemapValClamped( nNumPlayers, 8, 16, nNumToSpawn, 1 ); if ( gpGlobals->curtime > m_flNextBonusDucksVOAllowedTime ) { // Tell this user to play a "Bonus Ducks!" line. CSingleUserRecipientFilter filter( pOther ); UserMessageBegin( filter, "BonusDucks" ); WRITE_BYTE( entindex() ); WRITE_BYTE( false ); MessageEnd(); } while( nNumToSpawn-- ) { CHalloweenPickup *pPickup = dynamic_cast< CHalloweenPickup * >( CreateEntityByName( "tf_halloween_pickup" ) ); if (pPickup) { pPickup->m_nSkin = 2; // Golden skin pPickup->Precache(); DispatchSpawn(pPickup); Vector vecRandom = RandomVector( -200.f, 200.f ); vecRandom.z = RandomFloat( 300.f, 400.f ); Vector vecDropVector = vecRandom + vForce * 0.2f; pPickup->DropSingleInstance( vecDropVector, this, 1.f, 1.f ); pPickup->SetAbsOrigin( GetAbsOrigin() + Vector( 0.f, 0.f, 40.f ) ); pPickup->Activate(); } } } //DevMsg( "Kart Impact %fx,%fy,%fz - %f Base. %f Multiplayer, %f TotalForce, %d Damage, %i Class \n", // vForce.x, vForce.y, vForce.z, vForce.Length(), flNewKartKnockbackMultiplier, vForce.Length() * flNewKartKnockbackMultiplier, iDamage, GetPlayerClass()->GetClassIndex() ); vForce.z = 0; vForce.NormalizeInPlace(); DispatchParticleEffect( "kart_impact_sparks", GetAbsOrigin() + vForce * 24, GetAbsAngles() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetKartKnockbackMultiplier( float flExtraMultiplier /*= 1.f*/ ) const { return flExtraMultiplier * ( 1.0f + (float)m_iKartHealth / tf_halloween_kart_damage_to_force.GetFloat() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ResetKartDamage() { m_iKartHealth = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CancelEurekaTeleport() { m_bIsTeleportingUsingEurekaEffect = false; m_teleportHomeFlashTimer.Invalidate(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PostThink() { BaseClass::PostThink(); QAngle angles = GetLocalAngles(); angles[PITCH] = 0; SetLocalAngles( angles ); // Store the eye angles pitch so the client can compute its animation state correctly. m_angEyeAngles = EyeAngles(); m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] ); if ( m_flTauntAttackTime && m_flTauntAttackTime < gpGlobals->curtime ) { m_flTauntAttackTime = 0; DoTauntAttack(); } // if we are coaching, then capture events for adding annotations if ( m_bIsCoaching && m_hStudent ) { if ( ( m_afButtonPressed & ( IN_ATTACK | IN_ATTACK2 ) ) != 0 ) { if ( m_afButtonPressed & IN_ATTACK ) { HandleCoachCommand( this, kCoachCommand_Attack ); } else if ( m_afButtonPressed & IN_ATTACK2 ) { HandleCoachCommand( this, kCoachCommand_Defend ); } } if ( m_hStudent->GetTeamNumber() != TEAM_SPECTATOR ) { // tether coach to student--if the coach gets too far, move them toward the student Vector vecTarget = m_hStudent->GetAbsOrigin(); Vector vecDelta = GetAbsOrigin() - vecTarget; float flDistance = vecDelta.Length(); const float kInchesToMeters = 0.0254f; const float kMetersToInches = 1.0f / kInchesToMeters; const float kMaxDistanceToStudent = 30; int distance = RoundFloatToInt( flDistance * kInchesToMeters ); if ( distance > kMaxDistanceToStudent ) { VectorNormalize( vecDelta ); SetAbsOrigin( vecTarget + vecDelta * ( kMaxDistanceToStudent * kMetersToInches ) ); } } } if ( TFGameRules()->IsMannVsMachineMode() ) { // metal is free during setup time if ( TFGameRules()->IsQuickBuildTime() ) { GiveAmmo( 1000, TF_AMMO_METAL, true ); } // clamp maximum velocity to avoid sending mini-bosses into the stratosphere if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { Vector ahead = GetAbsVelocity(); float speed = ahead.NormalizeInPlace(); const float velocityLimit = 1000.0f; if ( speed > velocityLimit ) { speed = velocityLimit; } SetAbsVelocity( speed * ahead ); } } UpdateHalloween(); #ifdef STAGING_ONLY m_Shared.DoRocketPack(); #endif // STAGING_ONLY } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PrecacheMvM() { for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) { COMPILE_TIME_ASSERT( ARRAYSIZE( g_szBotModels ) == TF_LAST_NORMAL_CLASS ); int iModelIndex = PrecacheModel( g_szBotModels[ i ] ); PrecacheGibsForModel( iModelIndex ); COMPILE_TIME_ASSERT( ARRAYSIZE( g_szBotBossModels ) == TF_LAST_NORMAL_CLASS ); iModelIndex = PrecacheModel( g_szBotBossModels[ i ] ); PrecacheGibsForModel( iModelIndex ); } int iModelIndex = PrecacheModel( g_szBotBossSentryBusterModel ); PrecacheGibsForModel( iModelIndex ); PrecacheModel( "models/items/currencypack_small.mdl" ); PrecacheModel( "models/items/currencypack_medium.mdl" ); PrecacheModel( "models/items/currencypack_large.mdl" ); PrecacheModel( "models/bots/tw2/boss_bot/twcarrier_addon.mdl" ); PrecacheParticleSystem( "bot_impact_light" ); PrecacheParticleSystem( "bot_impact_heavy" ); PrecacheParticleSystem( "bot_death" ); PrecacheParticleSystem( "bot_radio_waves" ); PrecacheScriptSound( "MVM.BotStep" ); PrecacheScriptSound( "MVM.GiantHeavyStep" ); PrecacheScriptSound( "MVM.GiantSoldierStep" ); PrecacheScriptSound( "MVM.GiantDemomanStep" ); PrecacheScriptSound( "MVM.GiantScoutStep" ); PrecacheScriptSound( "MVM.GiantPyroStep" ); PrecacheScriptSound( "MVM.GiantHeavyLoop" ); PrecacheScriptSound( "MVM.GiantSoldierLoop" ); PrecacheScriptSound( "MVM.GiantDemomanLoop" ); PrecacheScriptSound( "MVM.GiantScoutLoop" ); PrecacheScriptSound( "MVM.GiantPyroLoop" ); PrecacheScriptSound( "MVM.GiantHeavyExplodes" ); PrecacheScriptSound( "MVM.GiantCommonExplodes" ); PrecacheScriptSound( "MVM.SentryBusterExplode" ); PrecacheScriptSound( "MVM.SentryBusterLoop" ); PrecacheScriptSound( "MVM.SentryBusterIntro" ); PrecacheScriptSound( "MVM.SentryBusterStep" ); PrecacheScriptSound( "MVM.SentryBusterSpin" ); PrecacheScriptSound( "MVM.DeployBombSmall" ); PrecacheScriptSound( "MVM.DeployBombGiant" ); PrecacheScriptSound( "Weapon_Upgrade.ExplosiveHeadshot" ); PrecacheScriptSound( "Spy.MVM_Chuckle" ); PrecacheScriptSound( "MVM.Robot_Engineer_Spawn" ); PrecacheScriptSound( "MVM.Robot_Teleporter_Deliver" ); PrecacheScriptSound( "MVM.MoneyPickup" ); PrecacheMaterial( "effects/circle_nocull" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PrecacheKart() { PrecacheModel( "models/player/items/taunts/bumpercar/parts/bumpercar.mdl" ); PrecacheModel( "models/props_halloween/bumpercar_cage.mdl" ); PrecacheScriptSound( "BumperCar.Spawn" ); PrecacheScriptSound( "BumperCar.SpawnFromLava" ); PrecacheScriptSound( "BumperCar.GoLoop" ); PrecacheScriptSound( "BumperCar.Screech" ); PrecacheScriptSound( "BumperCar.HitGhost" ); PrecacheScriptSound( "BumperCar.Bump" ); PrecacheScriptSound( "BumperCar.BumpHard" ); PrecacheScriptSound( "BumperCar.BumpIntoAir" ); PrecacheScriptSound( "BumperCar.SpeedBoostStart" ); PrecacheScriptSound( "BumperCar.SpeedBoostStop" ); PrecacheScriptSound( "BumperCar.Jump" ); PrecacheScriptSound( "BumperCar.JumpLand" ); PrecacheScriptSound( "sf14.Merasmus.DuckHunt.BonusDucks" ); PrecacheParticleSystem( "kartimpacttrail" ); PrecacheParticleSystem( "kart_dust_trail_red" ); PrecacheParticleSystem( "kart_dust_trail_blue" ); PrecacheParticleSystem( "kartdamage_4"); } //----------------------------------------------------------------------------- // Purpose: Precache the player models and player model gibs. //----------------------------------------------------------------------------- void CTFPlayer::PrecachePlayerModels( void ) { int i; for ( i = 0; i < TF_CLASS_COUNT_ALL; i++ ) { const char *pszModel = GetPlayerClassData( i )->m_szModelName; if ( pszModel && pszModel[0] ) { int iModel = PrecacheModel( pszModel ); PrecacheGibsForModel( iModel ); } pszModel = GetPlayerClassData( i )->m_szHandModelName; if ( pszModel && pszModel[0] ) { PrecacheModel( pszModel ); } /* if ( !IsX360() ) { // Precache the hardware facial morphed models as well. const char *pszHWMModel = GetPlayerClassData( i )->m_szHWMModelName; if ( pszHWMModel && pszHWMModel[0] ) { PrecacheModel( pszHWMModel ); } } */ } // Always precache the silly gibs. for ( i = 4; i < ARRAYSIZE( g_pszBDayGibs ); ++i ) { PrecacheModel( g_pszBDayGibs[i] ); } if ( TFGameRules() && TFGameRules()->IsBirthday() ) { for ( i = 0; i < 4/*ARRAYSIZE(g_pszBDayGibs)*/; i++ ) { PrecacheModel( g_pszBDayGibs[i] ); } PrecacheModel( "models/effects/bday_hat.mdl" ); } if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) ) { PrecacheModel( "models/props_halloween/halloween_gift.mdl" ); PrecacheModel( "models/props_halloween/ghost_no_hat.mdl" ); PrecacheModel( "models/props_halloween/ghost_no_hat_red.mdl" ); } // Precache player class sounds for ( i = TF_FIRST_NORMAL_CLASS; i < TF_CLASS_COUNT_ALL; ++i ) { TFPlayerClassData_t *pData = GetPlayerClassData( i ); for ( int i = 0; i < ARRAYSIZE( pData->m_szDeathSound ); ++i ) { PrecacheScriptSound( pData->m_szDeathSound[ i ] ); } } #ifdef STAGING_ONLY PrecacheModel( "models/weapons/c_models/c_bread/c_bread_plainloaf.mdl" ); #endif COMPILE_TIME_ASSERT( TF_CALLING_CARD_MODEL_COUNT == ARRAYSIZE( g_pszDeathCallingCardModels ) ); // Precache, Deliberatly skipping zero for ( i = 1; i < TF_CALLING_CARD_MODEL_COUNT; i++ ) { PrecacheModel( g_pszDeathCallingCardModels[i] ); } #ifdef STAGING_ONLY COMPILE_TIME_ASSERT( ARRAYSIZE( g_szPlayerRobotModels ) == TF_LAST_NORMAL_CLASS ); for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; ++i ) { PrecacheModel( g_szPlayerRobotModels[i] ); //int iModelIndex = PrecacheModel( g_szPlayerRobotModels[i] ); //PrecacheGibsForModel( iModelIndex ); } #endif // STAGING_ONLY } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PrecacheTFPlayer() { VPROF_BUDGET( "CTFPlayer::PrecacheTFPlayer", VPROF_BUDGETGROUP_PLAYER ); if( !m_bTFPlayerNeedsPrecache ) return; m_bTFPlayerNeedsPrecache = false; // Precache the player models and gibs. PrecachePlayerModels(); // Precache the player sounds. PrecacheScriptSound( "Player.Spawn" ); PrecacheScriptSound( "TFPlayer.Pain" ); PrecacheScriptSound( "TFPlayer.CritHit" ); PrecacheScriptSound( "TFPlayer.CritHitMini" ); PrecacheScriptSound( "TFPlayer.DoubleDonk" ); PrecacheScriptSound( "TFPlayer.CritPain" ); PrecacheScriptSound( "TFPlayer.CritDeath" ); PrecacheScriptSound( "TFPlayer.FreezeCam" ); PrecacheScriptSound( "TFPlayer.Drown" ); PrecacheScriptSound( "TFPlayer.AttackerPain" ); PrecacheScriptSound( "TFPlayer.SaveMe" ); PrecacheScriptSound( "TFPlayer.CritBoostOn" ); PrecacheScriptSound( "TFPlayer.CritBoostOff" ); PrecacheScriptSound( "TFPlayer.Decapitated" ); PrecacheScriptSound( "TFPlayer.ReCharged" ); PrecacheScriptSound( "Camera.SnapShot" ); PrecacheScriptSound( "TFPlayer.Dissolve" ); PrecacheScriptSound( "Saxxy.TurnGold" ); PrecacheScriptSound( "Icicle.TurnToIce" ); PrecacheScriptSound( "Icicle.HitWorld" ); PrecacheScriptSound( "Icicle.Melt" ); PrecacheScriptSound( "DemoCharge.ChargeCritOn" ); PrecacheScriptSound( "DemoCharge.ChargeCritOff" ); PrecacheScriptSound( "DemoCharge.Charging" ); PrecacheScriptSound( "TFPlayer.StunImpactRange" ); PrecacheScriptSound( "TFPlayer.StunImpact" ); PrecacheScriptSound( "Halloween.PlayerScream" ); PrecacheScriptSound( "Halloween.PlayerEscapedUnderworld" ); PrecacheScriptSound( "Game.YourTeamLost" ); PrecacheScriptSound( "Game.YourTeamWon" ); PrecacheScriptSound( "Game.SuddenDeath" ); PrecacheScriptSound( "Game.Stalemate" ); PrecacheScriptSound( "TV.Tune" ); //This will be moved out once we do the announcer pass. PrecacheScriptSound( "Announcer.AM_FirstBloodRandom" ); PrecacheScriptSound( "Announcer.AM_CapEnabledRandom" ); PrecacheScriptSound( "Announcer.AM_RoundStartRandom" ); PrecacheScriptSound( "Announcer.AM_FirstBloodFast" ); PrecacheScriptSound( "Announcer.AM_FirstBloodFinally" ); PrecacheScriptSound( "Announcer.AM_FlawlessVictoryRandom" ); PrecacheScriptSound( "Announcer.AM_FlawlessDefeatRandom" ); PrecacheScriptSound( "Announcer.AM_FlawlessVictory01" ); PrecacheScriptSound( "Announcer.AM_TeamScrambleRandom" ); PrecacheScriptSound( "Taunt.MedicHeroic" ); PrecacheScriptSound( "Taunt.GuitarRiff" ); // Dmg absorb sound PrecacheScriptSound( "Powerup.ReducedDamage" ); // Tourney UI PrecacheScriptSound( "Tournament.PlayerReady" ); PrecacheScriptSound( "Medic.AutoCallerAnnounce" ); #ifdef STAGING_ONLY // Killstreak sounds PrecacheScriptSound( "Announcer.KillStreak_Level1" ); PrecacheScriptSound( "Announcer.KillStreak_Level2" ); PrecacheScriptSound( "Announcer.KillStreak_Level3" ); PrecacheScriptSound( "Announcer.KillStreak_Level4" ); PrecacheScriptSound( "Game.KillStreak" ); #endif // Precache particle systems PrecacheParticleSystem( "crit_text" ); PrecacheParticleSystem( "miss_text" ); PrecacheParticleSystem( "cig_smoke" ); PrecacheParticleSystem( "speech_mediccall" ); PrecacheParticleSystem( "speech_mediccall_auto" ); PrecacheParticleSystem( "speech_taunt_all" ); PrecacheParticleSystem( "speech_taunt_red" ); PrecacheParticleSystem( "speech_taunt_blue" ); PrecacheParticleSystem( "player_recent_teleport_blue" ); PrecacheParticleSystem( "player_recent_teleport_red" ); PrecacheParticleSystem( "particle_nemesis_red" ); PrecacheParticleSystem( "particle_nemesis_blue" ); PrecacheParticleSystem( "spy_start_disguise_red" ); PrecacheParticleSystem( "spy_start_disguise_blue" ); PrecacheParticleSystem( "burningplayer_red" ); PrecacheParticleSystem( "burningplayer_blue" ); PrecacheParticleSystem( "burningplayer_rainbow" ); PrecacheParticleSystem( "blood_spray_red_01" ); PrecacheParticleSystem( "blood_spray_red_01_far" ); PrecacheParticleSystem( "pyrovision_blood" ); PrecacheParticleSystem( "water_blood_impact_red_01" ); PrecacheParticleSystem( "blood_impact_red_01" ); PrecacheParticleSystem( "water_playerdive" ); PrecacheParticleSystem( "water_playeremerge" ); PrecacheParticleSystem( "healthgained_red" ); PrecacheParticleSystem( "healthgained_blu" ); PrecacheParticleSystem( "healthgained_red_large" ); PrecacheParticleSystem( "healthgained_blu_large" ); PrecacheParticleSystem( "healthgained_red_giant" ); PrecacheParticleSystem( "healthgained_blu_giant" ); PrecacheParticleSystem( "critgun_weaponmodel_red" ); PrecacheParticleSystem( "critgun_weaponmodel_blu" ); PrecacheParticleSystem( "overhealedplayer_red_pluses" ); PrecacheParticleSystem( "overhealedplayer_blue_pluses" ); PrecacheParticleSystem( "highfive_red" ); PrecacheParticleSystem( "highfive_blue" ); PrecacheParticleSystem( "god_rays" ); PrecacheParticleSystem( "bl_killtaunt" ); PrecacheParticleSystem( "birthday_player_circling" ); PrecacheParticleSystem( "drg_fiery_death" ); PrecacheParticleSystem( "drg_wrenchmotron_teleport" ); PrecacheParticleSystem( "taunt_flip_land_red" ); PrecacheParticleSystem( "taunt_flip_land_blue" ); PrecacheParticleSystem( "tfc_sniper_mist" ); PrecacheParticleSystem( "dxhr_sniper_rail_blue" ); PrecacheParticleSystem( "dxhr_sniper_rail_red" ); PrecacheParticleSystem( "tfc_sniper_distortion_trail" ); for ( int i=0; iframetime, ucmd ); } else if ( IsTaunting() || m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) ) { // For some taunts, it is critical that the player not move once they start if ( !CanMoveDuringTaunt() ) { ucmd->forwardmove = 0; ucmd->upmove = 0; ucmd->sidemove = 0; ucmd->viewangles = pl.v_angle; } if ( tf_allow_taunt_switch.GetInt() == 0 && ucmd->weaponselect != 0 ) { ucmd->weaponselect = 0; // FIXME: The client will have predicted the weapon switch and have // called Holster/Deploy which will make the wielded weapon // invisible on their end. } } BaseClass::PlayerRunCommand( ucmd, moveHelper ); // try to play taunt remap on input after updating user command if ( IsTaunting() && m_flNextAllowTauntRemapInputTime >= 0.f && m_flNextAllowTauntRemapInputTime <= gpGlobals->curtime ) { float flSceneDuration = PlayTauntRemapInputScene(); if ( flSceneDuration > 0.f ) { m_flNextAllowTauntRemapInputTime = gpGlobals->curtime + flSceneDuration; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsReadyToPlay( void ) { bool bRetVal = false; if ( GetTeamNumber() == TEAM_SPECTATOR && m_bArenaSpectator == true ) return false; //============================================================================= // HPE_BEGIN: // [msmith] We don't want to say that the player is ready if they're still // a training video. //============================================================================= if ( TFGameRules() && TFGameRules()->IsInTraining() && tf_training_client_message.GetInt() == TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE ) return false; //============================================================================= // HPE_END //============================================================================= if ( GetTeamNumber() > LAST_SHARED_TEAM ) { if ( GetDesiredPlayerClassIndex() > TF_CLASS_UNDEFINED ) { bRetVal = true; } else { if ( TFGameRules() && TFGameRules()->IsInArenaMode() && tf_arena_force_class.GetBool() == true && tf_arena_use_queue.GetBool() == true ) { bRetVal = true; } } } return bRetVal; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsReadyToSpawn( void ) { if ( IsClassMenuOpen() ) { return false; } #ifdef TF_RAID_MODE if ( GetTeamNumber() == TF_TEAM_RED ) { if ( TFGameRules()->IsRaidMode() || TFGameRules()->IsBossBattleMode() ) { // enemy bots never respawn - they are spawned by the population system return false; } } #endif // TF_RAID_MODE // Medic attached to marker - delay if ( m_hReviveMarker && m_hReviveMarker->IsReviveInProgress() && ( StateGet() != TF_STATE_DYING ) ) { return false; } // Map-makers can force players to have custom respawn times if ( GetRespawnTimeOverride() != -1.f && gpGlobals->curtime < GetDeathTime() + GetRespawnTimeOverride() ) return false; return ( StateGet() != TF_STATE_DYING ); } //----------------------------------------------------------------------------- // Purpose: Return true if this player should be allowed to instantly spawn // when they next finish picking a class. //----------------------------------------------------------------------------- bool CTFPlayer::ShouldGainInstantSpawn( void ) { return ( GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED || IsClassMenuOpen() ); } //----------------------------------------------------------------------------- // Purpose: Resets player scores //----------------------------------------------------------------------------- void CTFPlayer::ResetScores( void ) { m_Shared.ResetScores(); CTF_GameStats.ResetPlayerStats( this ); RemoveNemesisRelationships(); MannVsMachineStats_ResetPlayerEvents( this ); BaseClass::ResetScores(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InitialSpawn( void ) { BaseClass::InitialSpawn(); m_AttributeManager.InitializeAttributes( this ); m_AttributeManager.SetPlayer( this ); m_AttributeList.SetManager( &m_AttributeManager ); SetWeaponBuilder( NULL ); ResetScores(); StateEnter( TF_STATE_WELCOME ); UpdateInventory( true ); ResetAccumulatedSentryGunDamageDealt(); ResetAccumulatedSentryGunKillCount(); ResetDamagePerSecond(); IGameEvent * event = gameeventmanager->CreateEvent( "player_initial_spawn" ); if ( event ) { event->SetInt( "index", entindex() ); gameeventmanager->FireEvent( event ); } } //----------------------------------------------------------------------------- // Purpose: Request this player's inventories from the steam backend //----------------------------------------------------------------------------- void CTFPlayer::UpdateOnRemove( void ) { BaseClass::UpdateOnRemove(); #if !defined(NO_STEAM) m_Inventory.RemoveListener( this ); #endif } //----------------------------------------------------------------------------- // Purpose: Override Base ApplyAbsVelocityImpulse (BaseEntity) to apply potential item attributes //----------------------------------------------------------------------------- void CTFPlayer::ApplyAbsVelocityImpulse( const Vector &vecImpulse ) { // Check for Attributes (mult_aiming_knockback_resistance) Vector vecForce = vecImpulse; float flImpulseScale = 1.0f; if ( IsPlayerClass( TF_CLASS_SNIPER ) && m_Shared.InCond( TF_COND_AIMING ) ) { CALL_ATTRIB_HOOK_FLOAT( flImpulseScale, mult_aiming_knockback_resistance ); } if ( m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) && !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { flImpulseScale *= 2.f; } // take extra force if you have a parachute deployed in x-y directions if ( m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) ) { // don't allow parachute robot to get push in MvM float flHorizontalScale = TFGameRules()->IsMannVsMachineMode() && IsBot() ? 0.f : 1.5f; vecForce.x *= flHorizontalScale; vecForce.y *= flHorizontalScale; } CBaseMultiplayerPlayer::ApplyAbsVelocityImpulse( vecForce * flImpulseScale ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ApplyAirBlastImpulse( const Vector &vecImpulse ) { // Knockout powerup carriers are immune to airblast if ( m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT || m_Shared.InCond( TF_COND_MEGAHEAL ) ) return; Vector vForce = vecImpulse; float flScale = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flScale, airblast_vulnerability_multiplier ); vForce *= flScale; // if on the ground, require min force to boost you off it if ( ( GetFlags() & FL_ONGROUND ) && ( vForce.z < JUMP_MIN_SPEED ) ) { // Minimum value of vecForce.z vForce.z = JUMP_MIN_SPEED; } CALL_ATTRIB_HOOK_FLOAT( vForce.z, airblast_vertical_vulnerability_multiplier ); RemoveFlag( FL_ONGROUND ); m_Shared.AddCond( TF_COND_KNOCKED_INTO_AIR ); ApplyAbsVelocityImpulse( vForce ); } //----------------------------------------------------------------------------- // Purpose: Go between for Setting Local Punch Impulses. Checks item attributes // Use this instead of directly calling m_Local.m_vecPunchAngle.SetX( value ); //----------------------------------------------------------------------------- bool CTFPlayer::ApplyPunchImpulseX ( float flImpulse ) { // Check for No Aim Flinch bool bFlinch = true; if ( IsPlayerClass( TF_CLASS_SNIPER ) && m_Shared.InCond( TF_COND_AIMING ) ) { CTFWeaponBase *pWeapon = GetActiveTFWeapon(); if ( pWeapon && WeaponID_IsSniperRifle( pWeapon->GetWeaponID() ) ) { CTFSniperRifle *pRifle = static_cast< CTFSniperRifle* >( pWeapon ); if ( pRifle->IsFullyCharged() ) { int iAimingNoFlinch = 0; CALL_ATTRIB_HOOK_INT( iAimingNoFlinch, aiming_no_flinch ); if ( iAimingNoFlinch > 0 ) { bFlinch = false; } } } } if ( bFlinch ) { m_Local.m_vecPunchAngle.SetX( flImpulse ); } return bFlinch; } //----------------------------------------------------------------------------- // Purpose: Request this player's inventories from the steam backend //----------------------------------------------------------------------------- void CTFPlayer::UpdateInventory( bool bInit ) { #if !defined(NO_STEAM) if ( IsFakeClient() ) return; if ( bInit || !m_Inventory.GetSOC() ) { if ( steamgameserverapicontext->SteamGameServer() ) { CSteamID steamIDForPlayer; if ( GetSteamID( &steamIDForPlayer ) ) { TFInventoryManager()->SteamRequestInventory( &m_Inventory, steamIDForPlayer, this ); } } } // If we have an SOCache, we've got a connection to the GC bool bInvalid = true; if ( m_Inventory.GetSOC() ) { bInvalid = m_Inventory.GetSOC()->BIsInitialized() == false; } m_Shared.SetLoadoutUnavailable( bInvalid ); #endif } //----------------------------------------------------------------------------- // Purpose: Requests that the GC confirm that this player is supposed to have // an SO cache on this gameserver and send it again if so. //----------------------------------------------------------------------------- void CTFPlayer::VerifySOCache() { #if !defined(NO_STEAM) if ( IsFakeClient() || IsHLTV() || IsReplay() ) return; CSteamID steamIDForPlayer; GetSteamID( &steamIDForPlayer ); if( steamIDForPlayer.BIndividualAccount() ) { // if we didn't find an inventory ask the GC to refresh us GCSDK::CGCMsg msgVerifyCache( k_EMsgGCVerifyCacheSubscription ); msgVerifyCache.Body().m_ulSteamID = steamIDForPlayer.ConvertToUint64(); GCClientSystem()->BSendMessage( msgVerifyCache ); } else { Msg( "Cannot verify load for invalid steam ID %s\n", steamIDForPlayer.Render() ); } #endif } #ifdef DEBUG CON_COMMAND_F( verifyloadout, "Cause the server to verify the player's items on the server.", FCVAR_NONE ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_GetCommandClient() ); if ( !pPlayer ) return; pPlayer->VerifySOCache(); } #endif // DEBUG //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) { // always send information to student or client if ( pInfo->m_pClientEnt ) { if ( m_hStudent && m_hStudent == CBaseEntity::Instance( pInfo->m_pClientEnt ) ) { return FL_EDICT_ALWAYS; } else if ( m_hCoach && m_hCoach == CBaseEntity::Instance( pInfo->m_pClientEnt ) ) { return FL_EDICT_ALWAYS; } else if ( TFGameRules() && TFGameRules()->IsPasstimeMode() ) { // TODO it should be possible to restrict this further based on // the values of tf_passtime_player_reticles_friends/enemies return FL_EDICT_ALWAYS; } CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); if ( pRecipientEntity && pRecipientEntity->ShouldForceTransmitsForTeam( GetTeamNumber() ) ) return FL_EDICT_ALWAYS; } return BaseClass::ShouldTransmit( pInfo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) { // coach can only "see" what the student "sees" if ( m_bIsCoaching && m_hStudent ) { Vector org; org = m_hStudent->EyePosition(); engine->AddOriginToPVS( org ); } else { BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Spawn() { VPROF_BUDGET( "CTFPlayer::Spawn", VPROF_BUDGETGROUP_PLAYER ); MDLCACHE_CRITICAL_SECTION(); m_bIsABot = IsBot(); if ( m_bIsABot && IsBotOfType( TF_BOT_TYPE ) ) { m_nBotSkill = ToTFBot( this )->GetDifficulty(); } else { m_nBotSkill = 0; } m_flSpawnTime = gpGlobals->curtime; SetModelScale( 1.0f ); UpdateModel(); SetMoveType( MOVETYPE_WALK ); BaseClass::Spawn(); // We have to clear this early, so that the sword knows its max health in ManageRegularWeapons below m_Shared.SetDecapitations( 0 ); // Check the make sure we have our inventory each time we spawn UpdateInventory( false ); #ifndef NO_STEAM if( m_Shared.IsLoadoutUnavailable() ) { VerifySOCache(); } #endif // Create our off hand viewmodel if necessary CreateViewModel( 1 ); // Make sure it has no model set, in case it had one before GetViewModel(1)->SetModel( "" ); // Kind of lame, but CBasePlayer::Spawn resets a lot of the state that we initially want on. // So if we're in the welcome state, call its enter function to reset if ( m_Shared.InState( TF_STATE_WELCOME ) ) { StateEnterWELCOME(); } // If they were dead, then they're respawning. Put them in the active state. if ( m_Shared.InState( TF_STATE_DYING ) ) { StateTransition( TF_STATE_ACTIVE ); } // If they're spawning into the world as fresh meat, give them items and stuff. bool bMatchSummary = TFGameRules() && TFGameRules()->ShowMatchSummary(); if ( m_Shared.InState( TF_STATE_ACTIVE ) || bMatchSummary ) { // remove our disguise each time we spawn if ( m_Shared.InCond( TF_COND_DISGUISED ) ) { m_Shared.RemoveDisguise(); } if ( !bMatchSummary ) { EmitSound( "Player.Spawn" ); } InitClass(); m_Shared.RemoveAllCond(); // Remove conc'd, burning, rotting, hallucinating, etc. // add team glows for a period of time after we respawn m_Shared.AddCond( TF_COND_TEAM_GLOWS, tf_spawn_glows_duration.GetInt() ); UpdateSkin( GetTeamNumber() ); // Prevent firing for a second so players don't blow their faces off SetNextAttack( gpGlobals->curtime + 1.0 ); DoAnimationEvent( PLAYERANIMEVENT_SPAWN ); // Force a taunt off, if we are still taunting, the condition should have been cleared above. StopTaunt(); // turn on separation so players don't get stuck in each other when spawned m_Shared.SetSeparation( true ); m_Shared.SetSeparationVelocity( vec3_origin ); RemoveTeleportEffect(); //If this is true it means I respawned without dying (changing class inside the spawn room) but doesn't necessarily mean that my healers have stopped healing me //This means that medics can still be linked to me but my health would not be affected since this condition is not set. //So instead of going and forcing every healer on me to stop healing we just set this condition back on. //If the game decides I shouldn't be healed by someone (LOS, Distance, etc) they will break the link themselves like usual. if ( m_Shared.GetNumHealers() > 0 ) { m_Shared.AddCond( TF_COND_HEALTH_BUFF ); } if ( !m_bSeenRoundInfo ) { if ( TFGameRules() && TFGameRules()->IsPasstimeMode() ) { CSingleUserRecipientFilter filter( this ); TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_PASSTIME_HOWTO ); } TFGameRules()->ShowRoundInfoPanel( this ); m_bSeenRoundInfo = true; } if ( IsInCommentaryMode() && !IsFakeClient() ) { // Player is spawning in commentary mode. Tell the commentary system. CBaseEntity *pEnt = NULL; variant_t emptyVariant; while ( (pEnt = gEntList.FindEntityByClassname( pEnt, "commentary_auto" )) != NULL ) { pEnt->AcceptInput( "MultiplayerSpawned", this, this, emptyVariant, 0 ); } } } CTF_GameStats.Event_PlayerSpawned( this ); m_iSpawnCounter = !m_iSpawnCounter; m_bAllowInstantSpawn = false; m_Shared.SetSpyCloakMeter( 100.0f ); m_Shared.SetScoutEnergyDrinkMeter( 100.0f ); m_Shared.SetScoutHypeMeter( 0.0f ); m_Shared.StopScoutHypeDrain(); m_Shared.SetRageMeter( 0.0f ); m_Shared.SetDemomanChargeMeter( 100.0f ); m_Shared.ClearDamageEvents(); m_AchievementData.ClearHistories(); m_flLastDamageTime = 0.f; m_flLastDamageDoneTime = 0.f; m_iMaxSentryKills = 0; m_flNextVoiceCommandTime = gpGlobals->curtime; m_iVoiceSpamCounter = 0; ClearZoomOwner(); SetFOV( this , 0 ); SetViewOffset( GetClassEyeHeight() * GetModelScale() ); RemoveAllScenesInvolvingActor( this ); ClearExpression(); m_flNextSpeakWeaponFire = gpGlobals->curtime; m_bInPowerPlay = false; // This makes the surrounding box always the same size as the standing collision box // helps with parts of the hitboxes that extend out of the crouching hitbox, eg with the // heavyweapons guy Vector mins = VEC_HULL_MIN; Vector maxs = VEC_HULL_MAX; CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &mins, &maxs ); m_iLeftGroundHealth = -1; m_iBlastJumpState = 0; m_bGoingFeignDeath = false; m_bTakenBlastDamageSinceLastMovement = false; ClearTauntAttack(); m_hTauntItem = NULL; m_bArenaIsAFK = false; m_Shared.SetFeignDeathReady( false ); m_bScattergunJump = false; m_iOldStunFlags = 0; m_flAccumulatedHealthRegen = 0; memset( m_flAccumulatedAmmoRegens, 0, sizeof(m_flAccumulatedAmmoRegens) ); IGameEvent * event = gameeventmanager->CreateEvent( "player_spawn" ); if ( event ) { event->SetInt( "userid", GetUserID() ); event->SetInt( "team", GetTeamNumber() ); event->SetInt( "class", GetPlayerClass()->GetClassIndex() ); gameeventmanager->FireEvent( event ); } #ifdef TF_RAID_MODE if ( TFGameRules()->IsRaidMode() && GetTeamNumber() == TF_TEAM_BLUE ) { // raiders respawn invulnerable for a short time m_Shared.AddCond( TF_COND_INVULNERABLE, tf_raid_respawn_safety_time.GetFloat() ); // friends glow AddGlowEffect(); } if ( TFGameRules()->IsBossBattleMode() && GetTeamNumber() == TF_TEAM_BLUE ) { // respawn invulnerable for a short time m_Shared.AddCond( TF_COND_INVULNERABLE, tf_boss_battle_respawn_safety_time.GetFloat() ); } #endif // TF_RAID_MODE m_bIsMissionEnemy = false; m_bIsSupportEnemy = false; m_bIsLimitedSupportEnemy = false; m_Shared.Spawn(); m_bCollideWithSentry = false; m_calledForMedicTimer.Invalidate(); m_placedSapperTimer.Invalidate(); m_bIsReadyToHighFive = false; m_nForceTauntCam = 0; m_bAllowedToRemoveTaunt = true; m_purgatoryPainMultiplier = 1; m_purgatoryPainMultiplierTimer.Invalidate(); m_bIsTeleportingUsingEurekaEffect = false; m_playerMovementStuckTimer.Invalidate(); m_bIsMiniBoss = false; m_bUseBossHealthBar = false; m_hGrapplingHookTarget = NULL; m_nHookAttachedPlayers = 0; m_bUsingActionSlot = false; m_flInspectTime = 0.f; m_flSendPickupWeaponMessageTime = -1.f; SetRespawnOverride( -1.f, NULL_STRING ); // Remove all powerups and add temporary invuln on spawn if ( TFGameRules()->IsPowerupMode() ) { m_Shared.AddCond( TF_COND_INVULNERABLE_USER_BUFF, 8.f ); } if ( TFGameRules() ) { // It's halloween, and it's hell time. Force this player to spawn in hell. if ( TFGameRules()->ArePlayersInHell() ) { const char *pSpawnEntName = NULL; if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) ) { pSpawnEntName = "hell_ghost_spawn"; } else if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) && CTFMinigameLogic::GetMinigameLogic() ) { CTFMiniGame *pActiveMinigame = CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame(); if ( pActiveMinigame ) { pSpawnEntName = pActiveMinigame->GetTeamSpawnPointName( GetTeamNumber() ); } } if ( pSpawnEntName ) { TFGameRules()->SpawnPlayerInHell( this, pSpawnEntName ); } } TFGameRules()->OnPlayerSpawned( this ); } if ( m_hReviveMarker ) { UTIL_Remove( m_hReviveMarker ); m_hReviveMarker = NULL; } // make sure we clear custom attributes that we added RemoveAllCustomAttributes(); #ifdef STAGING_ONLY // in MVM, scout can see glowing cash by default if ( TFGameRules()->IsMannVsMachineMode() ) { int iClass = GetPlayerClass()->GetClassIndex(); if ( iClass == TF_CLASS_SCOUT || iClass == TF_CLASS_SPY ) { AddCustomAttribute( "mvm see cash through wall", 1.f ); } } #endif CTFPlayerResource *pResource = dynamic_cast( g_pPlayerResource ); if ( pResource ) { pResource->SetPlayerClassWhenKilled( entindex(), TF_CLASS_UNDEFINED ); } if ( TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) { const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); if ( pMatchDesc && pMatchDesc->m_params.m_bAutoReady ) { TFGameRules()->PlayerReadyStatus_UpdatePlayerState( this, true ); } } CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch(); if ( pMatch ) { CSteamID steamID; GetSteamID( &steamID ); // This client entered a running match CMatchInfo::PlayerMatchData_t *pMatchPlayer = pMatch->GetMatchDataForPlayer( steamID ); if ( pMatchPlayer && TFGameRules() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) { pMatchPlayer->bPlayed = true; } } } //----------------------------------------------------------------------------- // Purpose: Removes all nemesis relationships between this player and others //----------------------------------------------------------------------------- void CTFPlayer::RemoveNemesisRelationships() { for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CTFPlayer *pTemp = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pTemp && pTemp != this ) { bool bRemove = false; if ( TFGameRules()->IsInArenaMode() == true ) { if ( GetTeamNumber() != TEAM_SPECTATOR ) { if ( InSameTeam( pTemp ) == true ) { bRemove = true; } } if ( IsDisconnecting() == true ) { bRemove = true; } } else { bRemove = true; } if ( bRemove == true ) { // set this player to be not dominating anyone else m_Shared.SetPlayerDominated( pTemp, false ); m_iNumberofDominations = 0; // set no one else to be dominating this player bool bThisPlayerIsDominatingMe = m_Shared.IsPlayerDominatingMe( i ); pTemp->m_Shared.SetPlayerDominated( this, false ); if ( bThisPlayerIsDominatingMe ) { int iDoms = pTemp->GetNumberofDominations(); pTemp->SetNumberofDominations( iDoms - 1); } } } } if ( TFGameRules()->IsInArenaMode() == false || IsDisconnecting() == true ) { // reset the matrix of who has killed whom with respect to this player CTF_GameStats.ResetKillHistory( this ); } IGameEvent *event = gameeventmanager->CreateEvent( "remove_nemesis_relationships" ); if ( event ) { event->SetInt( "player", entindex() ); gameeventmanager->FireEvent( event ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Regenerate( bool bRefillHealthAndAmmo /*= true*/ ) { // We may have been boosted over our max health. If we have, // restore it after we reset out class values. int nOldMaxHealth = GetMaxHealth(); int nOldHealth = GetHealth(); bool bBoosted = ( nOldHealth > nOldMaxHealth || !bRefillHealthAndAmmo ) && ( nOldMaxHealth > 0 ); int nAmmo[ TF_AMMO_COUNT ]; if ( !bRefillHealthAndAmmo ) { for ( int iAmmo = 0; iAmmo < TF_AMMO_COUNT; ++iAmmo ) { nAmmo[ iAmmo ] = GetAmmoCount( iAmmo ); } } m_bRegenerating = true; // This recomputes MaxHealth InitClass(); m_bRegenerating = false; if ( bBoosted ) { SetHealth( MAX( nOldHealth, GetMaxHealth() ) ); } if ( bRefillHealthAndAmmo ) { if ( m_Shared.InCond( TF_COND_BURNING ) ) { m_Shared.RemoveCond( TF_COND_BURNING ); } if ( m_Shared.InCond( TF_COND_URINE ) ) { m_Shared.RemoveCond( TF_COND_URINE ); } if ( m_Shared.InCond( TF_COND_MAD_MILK ) ) { m_Shared.RemoveCond( TF_COND_MAD_MILK ); } if ( m_Shared.InCond( TF_COND_BLEEDING ) ) { m_Shared.RemoveCond( TF_COND_BLEEDING ); } if ( m_Shared.InCond( TF_COND_ENERGY_BUFF ) ) { m_Shared.RemoveCond( TF_COND_ENERGY_BUFF ); if ( m_Shared.InCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ) ) { m_Shared.RemoveCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ); } } if ( m_Shared.InCond( TF_COND_PHASE ) ) { m_Shared.RemoveCond( TF_COND_PHASE ); } if ( m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) ) { m_Shared.RemoveCond( TF_COND_PARACHUTE_DEPLOYED ); } if ( m_Shared.InCond( TF_COND_PLAGUE ) ) { m_Shared.RemoveCond( TF_COND_PLAGUE ); } #ifdef STAGING_ONLY if ( m_Shared.InCond( TF_COND_TRANQ_MARKED ) ) { m_Shared.RemoveCond( TF_COND_TRANQ_MARKED ); } #endif // STAGING_ONLY m_Shared.SetSpyCloakMeter( 100.0f ); m_Shared.SetScoutEnergyDrinkMeter( 100.0f ); m_Shared.SetDemomanChargeMeter( 100.0f ); } // Reset our first allowed fire time. This allows honorbound weapons to be switched away // from for a bit. m_Shared.m_flFirstPrimaryAttack = MAX( m_Shared.m_flFirstPrimaryAttack, gpGlobals->curtime + 1.0f ); if ( bRefillHealthAndAmmo ) { for ( int iAmmo = 0; iAmmo < TF_AMMO_COUNT; ++iAmmo ) { if ( GetAmmoCount( iAmmo ) > GetMaxAmmo( iAmmo ) ) { SetAmmoCount( GetMaxAmmo( iAmmo ), iAmmo ); } } } else { for ( int iAmmo = 0; iAmmo < TF_AMMO_COUNT; ++iAmmo ) { SetAmmoCount( nAmmo[ iAmmo ], iAmmo ); } } IGameEvent *event = gameeventmanager->CreateEvent( "player_regenerate" ); if ( event ) { gameeventmanager->FireEvent( event ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InitClass( void ) { SetArmorValue( GetPlayerClass()->GetMaxArmor() ); // Init the anim movement vars m_PlayerAnimState->SetRunSpeed( GetPlayerClass()->GetMaxSpeed() ); m_PlayerAnimState->SetWalkSpeed( GetPlayerClass()->GetMaxSpeed() * 0.5 ); // Give default items for class. GiveDefaultItems(); // Set initial health and armor based on class. // Do it after items have been delivered, so items can modify it SetMaxHealth( GetMaxHealth() ); SetHealth( GetMaxHealth() ); TeamFortress_SetSpeed(); #ifdef STAGING_ONLY int nForceRobotModel = 0; CALL_ATTRIB_HOOK_INT( nForceRobotModel, appear_as_mvm_robot ); if ( nForceRobotModel != 0 ) { int nClassIndex = ( GetPlayerClass() ? GetPlayerClass()->GetClassIndex() : TF_CLASS_UNDEFINED ); GetPlayerClass()->SetCustomModel( g_szPlayerRobotModels[nClassIndex], USE_CLASS_ANIMATIONS ); UpdateModel(); SetBloodColor( DONT_BLEED ); } else { GetPlayerClass()->SetCustomModel( NULL ); UpdateModel(); SetBloodColor( BLOOD_COLOR_RED ); } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CreateViewModel( int iViewModel ) { Assert( iViewModel >= 0 && iViewModel < MAX_VIEWMODELS ); if ( GetViewModel( iViewModel ) ) return; CTFViewModel *pViewModel = ( CTFViewModel * )CreateEntityByName( "tf_viewmodel" ); if ( pViewModel ) { pViewModel->SetAbsOrigin( GetAbsOrigin() ); pViewModel->SetOwner( this ); pViewModel->SetIndex( iViewModel ); DispatchSpawn( pViewModel ); pViewModel->FollowEntity( this, false ); m_hViewModel.Set( iViewModel, pViewModel ); } } //----------------------------------------------------------------------------- // Purpose: Gets the view model for the player's off hand //----------------------------------------------------------------------------- CBaseViewModel *CTFPlayer::GetOffHandViewModel() { // off hand model is slot 1 return GetViewModel( 1 ); } //----------------------------------------------------------------------------- // Purpose: Sends the specified animation activity to the off hand view model //----------------------------------------------------------------------------- void CTFPlayer::SendOffHandViewModelActivity( Activity activity ) { CBaseViewModel *pViewModel = GetOffHandViewModel(); if ( pViewModel ) { int sequence = pViewModel->SelectWeightedSequence( activity ); pViewModel->SendViewModelMatchingSequence( sequence ); } } //----------------------------------------------------------------------------- // Purpose: Set the player up with the default weapons, ammo, etc. //----------------------------------------------------------------------------- void CTFPlayer::GiveDefaultItems() { // Get the player class data. TFPlayerClassData_t *pData = m_PlayerClass.GetData(); if ( GetTeamNumber() == TEAM_SPECTATOR ) { RemoveAllWeapons(); return; } // Give weapons. ManageRegularWeapons( pData ); if ( !TFGameRules() || !TFGameRules()->IsInMedievalMode() ) { // Give a builder weapon for each object the playerclass is allowed to build ManageBuilderWeapons( pData ); } // Weapons that added greater ammo than base require us to now fill the player up to max ammo for ( int iAmmo = 0; iAmmo < TF_AMMO_COUNT; ++iAmmo ) { GiveAmmo( GetMaxAmmo(iAmmo), iAmmo, true, kAmmoSource_Resupply ); } // Clear the player's banner buffs. m_Shared.RemoveCond( TF_COND_OFFENSEBUFF ); m_Shared.RemoveCond( TF_COND_DEFENSEBUFF ); m_Shared.RemoveCond( TF_COND_REGENONDAMAGEBUFF ); m_Shared.RemoveCond( TF_COND_NOHEALINGDAMAGEBUFF ); m_Shared.RemoveCond( TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK ); m_Shared.RemoveCond( TF_COND_DEFENSEBUFF_HIGH ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ManageBuilderWeapons( TFPlayerClassData_t *pData ) { // Collect all builders and validate them against the list of objects (below) CUtlVector< CTFWeaponBuilder* > vecBuilderDestroyList; for ( int i = 0; i < MAX_WEAPONS; ++i ) { CTFWeaponBuilder *pBuilder = dynamic_cast< CTFWeaponBuilder* >( GetWeapon( i ) ); if ( !pBuilder ) continue; vecBuilderDestroyList.AddToTail( pBuilder ); } CEconItemView *pLoadoutBuilderItemView = NULL; // Go through each object and see if we need to create or remove builders for ( int i = 0; i < OBJ_LAST; ++i ) { if ( !GetPlayerClass()->CanBuildObject( i ) ) continue; // TODO: Need to add support for "n" builders, rather hard-wired for two. // Currently, the only class that uses more than one is the spy: // - BUILDER is OBJ_ATTACHMENT_SAPPER, which is invoked via weapon selection (see objects.txt). // - BUILDER2 is OBJ_SPY_TRAP, which is invoked via a build command from PDA3 (spy-specific). #ifdef STAGING_ONLY int nLoadoutPos = ( GetObjectInfo( i )->m_bRequiresOwnBuilder ) ? LOADOUT_POSITION_BUILDING2 : LOADOUT_POSITION_BUILDING; #else int nLoadoutPos = LOADOUT_POSITION_BUILDING; #endif pLoadoutBuilderItemView = GetLoadoutItem( GetPlayerClass()->GetClassIndex(), nLoadoutPos, true ); // Do we have a specific builder for this object? CTFWeaponBuilder *pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, i ); if ( pBuilder ) { // We may have a different builder back-end item now. If so, destroy and make a new one below. CEconItemView *pCurrentBuilderItemView = pBuilder->GetAttributeContainer()->GetItem(); if ( pCurrentBuilderItemView == NULL || pLoadoutBuilderItemView == NULL || !ItemsMatch( pData, pCurrentBuilderItemView, pLoadoutBuilderItemView, pBuilder ) ) { // Manually nuke the item from the weapon list here so that we don't find it vecBuilderDestroyList.FindAndRemove( pBuilder ); Weapon_Detach( pBuilder ); UTIL_Remove( pBuilder ); // Wrong builder item, so pretend we didn't find one pBuilder = NULL; } } else if ( !GetObjectInfo( i )->m_bRequiresOwnBuilder ) { // Do we have a default builder, and an object that doesn't require a specific builder? pBuilder = CTFPlayerSharedUtils::GetBuilderForObjectType( this, -1 ); if ( pBuilder ) { // Flag it as supported by this builder (ugly, but necessary for legacy system) pBuilder->SetObjectTypeAsBuildable( i ); } } // Is a new builder required? if ( !pBuilder || ( GetObjectInfo( i )->m_bRequiresOwnBuilder && !( CTFPlayerSharedUtils::GetBuilderForObjectType( this, i ) ) ) ) { pBuilder = dynamic_cast< CTFWeaponBuilder* >( GiveNamedItem( "tf_weapon_builder", i, pLoadoutBuilderItemView ) ); if ( pBuilder ) { pBuilder->DefaultTouch( this ); } } // Builder settings if ( pBuilder ) { if ( m_bRegenerating == false ) { pBuilder->WeaponReset(); } pBuilder->GiveDefaultAmmo(); pBuilder->ChangeTeam( GetTeamNumber() ); pBuilder->SetObjectTypeAsBuildable( i ); pBuilder->m_nSkin = GetTeamNumber() - 2; // color the w_model to the team // Pull it out of the "destroy" list vecBuilderDestroyList.FindAndRemove( pBuilder ); } } // Anything left should be destroyed FOR_EACH_VEC( vecBuilderDestroyList, i ) { Assert( vecBuilderDestroyList[i] ); Weapon_Detach( vecBuilderDestroyList[i] ); UTIL_Remove( vecBuilderDestroyList[i] ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ItemsMatch( TFPlayerClassData_t *pData, CEconItemView *pCurWeaponItem, CEconItemView *pNewWeaponItem, CTFWeaponBase *pWpnEntity ) { if ( !pNewWeaponItem || !pNewWeaponItem->IsValid() ) return false; // If we already have a weapon in this slot but is not the same type, nuke it (changed classes) // We don't need to do this for non-base items because they've already been verified above. bool bHasNonBaseWeapon = pNewWeaponItem ? pNewWeaponItem->GetItemQuality() != AE_NORMAL : false; if ( bHasNonBaseWeapon ) { // If the item isn't the one we're supposed to have, nuke it if ( pCurWeaponItem->GetItemID() != pNewWeaponItem->GetItemID() ) { /* Msg("Removing %s because its global index (%d) doesn't match the loadout's (%d)\n", pWeapon->GetDebugName(), pCurWeaponItem->GetItemID(), pNewWeaponItem->GetItemID() ); */ return false; } // Some items create different entities when wielded by different classes. If so, we need to say // the items don't match so the item gets recreated as the right entity. if ( pWpnEntity ) { const char *pszCurWeaponClass = pWpnEntity->GetClassname(), *pszNewWeaponTransClass = TranslateWeaponEntForClass( pNewWeaponItem->GetStaticData()->GetItemClass(), GetPlayerClass()->GetClassIndex() ); if ( !pszCurWeaponClass || !pszNewWeaponTransClass || Q_stricmp( pszCurWeaponClass, pszNewWeaponTransClass ) ) return false; } } else { if ( pCurWeaponItem->GetItemQuality() != AE_NORMAL || (pCurWeaponItem->GetItemDefIndex() != pNewWeaponItem->GetItemDefIndex()) ) { //Msg("Removing %s because it's not the right type for the class.\n", pWeapon->GetDebugName() ); return false; } CSteamID ownerSteamID; GetSteamID( &ownerSteamID ); // If the owner is not the same, then they're different as well. This catches // cases of stock items comparing if ( pCurWeaponItem->GetAccountID() != ownerSteamID.GetAccountID() ) { return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ItemIsAllowed( CEconItemView *pItem ) { if ( !pItem || !pItem->GetStaticData() ) return false; int iClass = GetPlayerClass()->GetClassIndex(); int iSlot = pItem->GetStaticData()->GetLoadoutSlot(iClass); // Passtime hack to allow passtime gun if ( V_stristr( pItem->GetItemDefinition()->GetDefinitionName(), "passtime" ) ) { return TFGameRules() && TFGameRules()->IsPasstimeMode(); } // Holiday Restriction CEconItemDefinition* pData = pItem->GetStaticData(); if ( TFGameRules() && pData && pData->GetHolidayRestriction() ) { int iHolidayRestriction = UTIL_GetHolidayForString( pData->GetHolidayRestriction() ); if ( iHolidayRestriction != kHoliday_None && !TFGameRules()->IsHolidayActive( iHolidayRestriction ) ) return false; } if ( TFGameRules()->InStalemate() && mp_stalemate_meleeonly.GetBool() ) { bool bMeleeOnlyAllowed = (iSlot == LOADOUT_POSITION_MELEE) || (iClass == TF_CLASS_SPY && (iSlot == LOADOUT_POSITION_PDA || iSlot == LOADOUT_POSITION_PDA2)); if ( !bMeleeOnlyAllowed ) return false; } if ( TFGameRules()->IsInMedievalMode() ) { bool bMedievalModeAllowed = false; // Allow all melee-class weapons, non-weapons, and the spy equipment. switch ( iSlot ) { case LOADOUT_POSITION_MELEE: case LOADOUT_POSITION_HEAD: case LOADOUT_POSITION_MISC: case LOADOUT_POSITION_MISC2: case LOADOUT_POSITION_ACTION: case LOADOUT_POSITION_TAUNT: case LOADOUT_POSITION_TAUNT2: case LOADOUT_POSITION_TAUNT3: case LOADOUT_POSITION_TAUNT4: case LOADOUT_POSITION_TAUNT5: case LOADOUT_POSITION_TAUNT6: case LOADOUT_POSITION_TAUNT7: case LOADOUT_POSITION_TAUNT8: #ifdef STAGING_ONLY case LOADOUT_POSITION_PDA3: case LOADOUT_POSITION_BUILDING2: #endif bMedievalModeAllowed = true; break; case LOADOUT_POSITION_PDA: case LOADOUT_POSITION_PDA2: if ( iClass == TF_CLASS_SPY ) bMedievalModeAllowed = true; break; } if ( !bMedievalModeAllowed ) { static CSchemaAttributeDefHandle pAttrib_AllowedInMedievalMode( "allowed in medieval mode" ); if ( !pItem->FindAttribute( pAttrib_AllowedInMedievalMode ) ) { return false; } } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ManageRegularWeapons( TFPlayerClassData_t *pData ) { // Reset ammo. RemoveAllAmmo(); // Remove our disguise weapon. m_Shared.RemoveDisguiseWeapon(); CUtlVector precacheStrings; CBaseCombatWeapon* pCurrentWeapon = m_hActiveWeapon; // Give ammo. Must be done before weapons, so weapons know the player has ammo for them. for ( int iAmmo = 0; iAmmo < TF_AMMO_COUNT; ++iAmmo ) { GiveAmmo( GetMaxAmmo(iAmmo), iAmmo, true, kAmmoSource_Resupply ); } if ( IsX360() ) { ManageRegularWeaponsLegacy( pData ); } else { // Loop through our current wearables and ensure we're supposed to have them. ValidateWearables( pData ); // Loop through all our current weapons, and ensure we're supposed to have them. ValidateWeapons( pData, true ); // Create a copy of currently equipped items, if we equip something new report player loadout bool bItemsChanged = false; // Now Loop through our inventory for the current class, and give us any items we don't have. int iClass = GetPlayerClass()->GetClassIndex(); if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_CLASS_COUNT ) { CSteamID ownerSteamID; GetSteamID( &ownerSteamID ); for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ ) { // bots don't need the action slot item for MvM (canteen) if ( ( i == LOADOUT_POSITION_ACTION ) && IsBot() && TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) continue; m_EquippedLoadoutItemIndices[i] = LOADOUT_SLOT_USE_BASE_ITEM; // use base items in training mode CEconItemView *pItem = GetLoadoutItem( iClass, i, true ); if ( !pItem || !pItem->IsValid() ) continue; if ( !ItemIsAllowed( pItem ) ) continue; // Only do this for taunts, because other items will be caught by the dynamic model loading system. if ( IsTauntSlot( i ) ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Precaching taunts, etc", __FUNCTION__ ); // This has to be done before the continue for "no_entity", because we're trying to precache taunts which // explicitly bail out there. precacheStrings.RemoveAll(); pItem->GetItemDefinition()->GeneratePrecacheModelStrings( false, &precacheStrings ); FOR_EACH_VEC( precacheStrings, iModel ) { if ( precacheStrings[iModel] && ( *precacheStrings[iModel] ) ) { PrecacheModel( precacheStrings[iModel], false ); } } } m_EquippedLoadoutItemIndices[i] = pItem->GetItemID(); Assert( pItem->GetStaticData()->GetItemClass() ); if ( pItem->GetStaticData()->GetItemClass() && FStrEq( pItem->GetStaticData()->GetItemClass(), "no_entity" ) ) continue; CTFWeaponBase *pCurrentWeaponOfType = NULL; bool bAlreadyHave = false; // Don't need to check weapons if it's a wearable-only slot if ( !IsWearableSlot(i) || pItem->GetItemDefinition()->IsActingAsAWeapon() ) { // Weapon slot. Check out weapons to see if we have it. for ( int wpn = 0; wpn < MAX_WEAPONS; wpn++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon(wpn); if ( !pWeapon ) continue; if ( ItemsMatch( pData, pWeapon->GetAttributeContainer()->GetItem(), pItem, pWeapon ) ) { pCurrentWeaponOfType = pWeapon; bAlreadyHave = true; break; } } } CEconWearable *pWearable = NULL; if ( !bAlreadyHave ) { // We couldn't find a matching weapon. See if we have a matching wearable. for ( int wbl = 0; wbl < m_hMyWearables.Count(); wbl++ ) { pWearable = m_hMyWearables[wbl]; if ( !pWearable ) continue; CEconItemView *pWearableView = pWearable->GetAttributeContainer()->GetItem(); if ( ItemsMatch( pData, pWearableView, pItem ) ) { bAlreadyHave = true; break; } } } if ( !bAlreadyHave && pItem->GetStaticData()->GetItemClass() ) { CEconEntity *pNewItem = dynamic_cast(GiveNamedItem( pItem->GetStaticData()->GetItemClass(), 0, pItem )); Assert( pNewItem ); if ( pNewItem ) { pNewItem->GetAttributeContainer()->GetItem()->SetOverrideAccountID( ownerSteamID.GetAccountID() ); CTFWeaponBuilder *pBuilder = dynamic_cast( (CBaseEntity*)pNewItem ); if ( pBuilder ) { pBuilder->SetSubType( pData->m_aBuildable[0] ); } CBaseCombatWeapon* pWeapon = dynamic_cast< CBaseCombatWeapon* >( pNewItem ); if ( pWeapon ) { pWeapon->SetSoundsEnabled( false ); } pNewItem->GiveTo( this ); if ( pWeapon ) { pWeapon->SetSoundsEnabled( true ); } } } else { if ( pCurrentWeaponOfType ) { pCurrentWeaponOfType->UpdateExtraWearables(); // We need to ensure all hands pointers are updated for all weapons. // Otherwise we could end up using animation sequences from the wrong class hands. pCurrentWeaponOfType->UpdateHands(); } } bItemsChanged |= !bAlreadyHave; } // For each item in load out } if ( bItemsChanged ) { CTF_GameStats.Event_PlayerLoadoutChanged( this, false ); } // We may have added weapons that make others invalid. Recheck. ValidateWeapons( pData, false ); if ( m_hActiveWeapon.Get() != pCurrentWeapon && m_hActiveWeapon ) { m_hActiveWeapon->WeaponSound( DEPLOY ); } CSingleUserRecipientFilter filter( this ); UserMessageBegin( filter, "PlayerLoadoutUpdated" ); WRITE_BYTE( entindex() ); MessageEnd(); } // On equip, legacy source code will autoswitch to new weapons. // Instead of refactoring, we check here to see if we are allowed to have certain weapons switched to // TF2: Not allowed to have a actionslot item as last or active on regenerate / respawn // HACK Don't allow the parachute to be an active weapon CTFWeaponBase *pCurr = GetActiveTFWeapon(); CTFWeaponBase *pPrev = dynamic_cast( GetLastWeapon() ); if ( ( pCurr && pCurr->GetAttributeContainer()->GetItem()->GetEquippedPositionForClass( GetPlayerClass()->GetClassIndex() ) == LOADOUT_POSITION_ACTION ) || ( pPrev && pPrev->GetAttributeContainer()->GetItem()->GetEquippedPositionForClass( GetPlayerClass()->GetClassIndex() ) == LOADOUT_POSITION_ACTION ) || ( pCurr && pCurr->GetWeaponID() == TF_WEAPON_PARACHUTE ) ) { m_bRegenerating = false; m_iLastWeaponSlot = 0; } if ( m_bRegenerating == false ) { bool bWepSwitched = false; if ( m_bRememberActiveWeapon && m_iActiveWeaponTypePriorToDeath ) { CTFWeaponBase *pWeapon = Weapon_OwnsThisID( m_iActiveWeaponTypePriorToDeath ); if ( pWeapon && pWeapon->GetAttributeContainer()->GetItem()->GetEquippedPositionForClass( GetPlayerClass()->GetClassIndex() ) != LOADOUT_POSITION_ACTION ) { bWepSwitched = Weapon_Switch( pWeapon ); } } if ( !bWepSwitched ) { SetActiveWeapon( NULL ); // Find a weapon to switch to, starting with primary. CTFWeaponBase *pWeapon = dynamic_cast( GetEntityForLoadoutSlot( LOADOUT_POSITION_PRIMARY ) ); if ( !pWeapon || !pWeapon->CanBeSelected() || !Weapon_Switch( pWeapon ) ) { pWeapon = dynamic_cast( GetEntityForLoadoutSlot( LOADOUT_POSITION_SECONDARY ) ); if ( !pWeapon || pWeapon->CanBeSelected() || !Weapon_Switch( pWeapon ) ) { pWeapon = dynamic_cast( GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) ); Weapon_Switch( pWeapon ); } } } if ( (m_iLastWeaponSlot == 0 || !m_bRememberLastWeapon) && !m_bRememberActiveWeapon ) { m_iLastWeaponSlot = 1; } if ( !Weapon_GetSlot( m_iLastWeaponSlot ) ) { Weapon_SetLast( Weapon_GetSlot( TF_WPN_TYPE_MELEE ) ); } else { Weapon_SetLast( Weapon_GetSlot( m_iLastWeaponSlot ) ); } } // Now make sure we don't have too much ammo. This can happen if an item has reduced our max ammo. for ( int iAmmo = 0; iAmmo < TF_AMMO_COUNT; ++iAmmo ) { int iMax = GetMaxAmmo(iAmmo); if ( iMax < GetAmmoCount(iAmmo) ) { RemoveAmmo( GetAmmoCount(iAmmo) - iMax, iAmmo ); } } // If our max health dropped below current due to item changes, drop our current health. // If we're not being buffed, clamp it to max. Otherwise, clamp it to the max buffed health int iMaxHealth = m_Shared.InCond( TF_COND_HEALTH_BUFF ) ? m_Shared.GetMaxBuffedHealth() : GetMaxHealth(); if ( m_iHealth > iMaxHealth ) { // Modify health manually to prevent showing all the "you got hurt" UI. m_iHealth = iMaxHealth; } if ( TFGameRules()->InStalemate() && mp_stalemate_meleeonly.GetBool() ) { CBaseCombatWeapon *meleeWeapon = Weapon_GetSlot( TF_WPN_TYPE_MELEE ); if ( meleeWeapon ) { Weapon_Switch( meleeWeapon ); } } // In testing mode, switch bots to the weapon being tested if ( TFGameRules()->IsInItemTestingMode() && IsFakeClient() ) { // Our first player should be the human tester CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( 1 ) ); if ( pPlayer && !pPlayer->IsFakeClient() ) { // Loop through all the items we're testing FOR_EACH_VEC( pPlayer->m_ItemsToTest, i ) { CEconItemView *pItem = &pPlayer->m_ItemsToTest[i].scriptItem; if ( !pItem ) continue; int iSlot = pItem->GetStaticData()->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ); if ( IsWearableSlot( iSlot ) ) continue; CBaseCombatWeapon *pWeapon = Weapon_GetSlot( iSlot ); if ( pWeapon ) { Weapon_Switch( pWeapon ); break; } } } } if ( TFGameRules() && TFGameRules()->IsPVEModeActive() && !IsBot() ) { if ( m_Inventory.ClassLoadoutHasChanged( GetPlayerClass()->GetClassIndex() ) || ( m_bSwitchedClass ) || ( g_pPopulationManager && g_pPopulationManager->IsRestoringCheckpoint() ) ) { ReapplyPlayerUpgrades(); } // Calculate how much money is being used on active class / items int nSpending = 0; int iClass = GetPlayerClass()->GetClassIndex(); CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( this ); if ( upgrades ) { for( int u = 0; u < upgrades->Count(); ++u ) { // Class Match, Check to see if we have this item equipped if ( iClass == upgrades->Element(u).m_iPlayerClass) { // Player upgrade if ( upgrades->Element( u ).m_itemDefIndex == INVALID_ITEM_DEF_INDEX ) { nSpending += upgrades->Element(u).m_nCost; continue; } // Item upgrade, look at equipment only not miscs or bottle for ( int itemIndex = 0; itemIndex <= LOADOUT_POSITION_PDA2; itemIndex++ ) { CEconItemView *pItem = GetLoadoutItem( iClass, itemIndex, true ); if ( upgrades->Element(u).m_itemDefIndex == pItem->GetItemDefIndex() ) { nSpending += upgrades->Element(u).m_nCost; break; } } } } } CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance(); if ( pStats ) { pStats->NotifyPlayerActiveUpgradeCosts( this, nSpending ); } } PostInventoryApplication(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CEconItemView *CTFPlayer::GetLoadoutItem( int iClass, int iSlot, bool bReportWhitelistFails ) { if ( TFGameRules()->IsInItemTestingMode() ) { CEconItemView *pItem = ItemTesting_GetTestItem( iClass, iSlot ); if ( pItem ) return pItem; } if ( TFGameRules()->IsInTraining() || TFGameRules()->IsInItemTestingMode() ) { CTFInventoryManager *pInventoryManager = TFInventoryManager(); return pInventoryManager->GetBaseItemForClass( iClass, iSlot ); } CEconItemView *pItem = m_Inventory.GetItemInLoadout( iClass, iSlot ); // Check to see if this item passes the tournament rules (in whitelist/or normal quality). // If it doesn't, we fall back to the base item for the loadout slot. if ( (pItem && pItem->IsValid()) && (pItem->GetItemQuality() != AE_NORMAL) && !pItem->GetStaticData()->IsAllowedInMatch() && TFGameRules()->IsInTournamentMode() ) { if ( bReportWhitelistFails ) { ClientPrint( this, HUD_PRINTNOTIFY, "#Item_BlacklistedInMatch", pItem->GetStaticData()->GetItemBaseName() ); } pItem = TFInventoryManager()->GetBaseItemForClass( iClass, iSlot ); } return pItem; } //----------------------------------------------------------------------------- // Purpose: Handles pressing the use action slot item key. //----------------------------------------------------------------------------- void CTFPlayer::UseActionSlotItemPressed( void ) { m_bUsingActionSlot = true; if ( TryToPickupDroppedWeapon() ) return; int iNoiseMaker = 0; CALL_ATTRIB_HOOK_INT( iNoiseMaker, enable_misc2_noisemaker ); if ( iNoiseMaker ) { DoNoiseMaker(); return; } CBaseEntity *pActionSlotEntity = GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ); if ( !pActionSlotEntity ) return; // get the equipped item and see what it is CTFPowerupBottle *pPowerupBottle = dynamic_cast< CTFPowerupBottle* >( pActionSlotEntity ); if ( pPowerupBottle ) { // @todo send event to clients so that they know what's going on pPowerupBottle->Use(); return; } // is it a throwable? CTFThrowable *pThrowable = dynamic_cast< CTFThrowable* >( pActionSlotEntity ); if ( pThrowable ) { if ( !Weapon_ShouldSelectItem( pThrowable ) ) return; if ( GetActiveWeapon() ) { if ( !GetActiveWeapon()->CanHolster() ) return; ResetAutoaim( ); } // Check if this is the spellbook so we can save off info to preserve weapon switching CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pThrowable ); if ( pSpellBook ) { if ( !pSpellBook->CanCastSpell( this ) ) { // if no spell force a roll if cheat is active if ( tf_halloween_unlimited_spells.GetBool() && !pSpellBook->HasASpellWithCharges() ) { pSpellBook->RollNewSpell( 0 ); } else if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { // if I'm in a halloween Vehicle, cast the spell immediately //pSpellBook->CastKartSpell(); pSpellBook->PrimaryAttack(); } else { EmitSound_t params; params.m_flSoundTime = 0; params.m_pflSoundDuration = 0; params.m_pSoundName = "Player.DenyWeaponSelection"; CSingleUserRecipientFilter filter( this ); EmitSound( filter, entindex(), params ); } return; } // Notify the spellbook of the current last used weapon pSpellBook->SaveLastWeapon( GetLastWeapon() ); } // Equip it Weapon_Switch( pThrowable ); return; } if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() ) { CTFGrapplingHook *pGrapplingHook = dynamic_cast< CTFGrapplingHook* >( pActionSlotEntity ); if ( pGrapplingHook ) { Weapon_Switch( pGrapplingHook ); return; } } } //----------------------------------------------------------------------------- // Purpose: Handles releasing the use action slot item key. //----------------------------------------------------------------------------- void CTFPlayer::UseActionSlotItemReleased( void ) { m_bUsingActionSlot = false; if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() ) { // if we're using the hook, switch back to the last weapon if ( GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRAPPLINGHOOK ) { CBaseCombatWeapon *pLastWeapon = GetLastWeapon(); if ( pLastWeapon && Weapon_CanSwitchTo( pLastWeapon ) ) { Weapon_Switch( pLastWeapon ); } else { // in case we failed to switch back to last weapon for some reason, just find the next best SwitchToNextBestWeapon( pLastWeapon ); } return; } } } //----------------------------------------------------------------------------- // Purpose: Handles pressing the inspect key. //----------------------------------------------------------------------------- void CTFPlayer::InspectButtonPressed() { m_flInspectTime = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: Handles releasing the inspect key. //----------------------------------------------------------------------------- void CTFPlayer::InspectButtonReleased() { m_flInspectTime = 0.f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::AddToSpyKnife( float value, bool force ) { CTFKnife *pWpn = (CTFKnife *)Weapon_OwnsThisID( TF_WEAPON_KNIFE ); if ( !pWpn ) return false; return pWpn->DecreaseRegenerationTime( value, force ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveAllItems() { // Nuke items. for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon(i); if ( !pWeapon ) continue; Weapon_Detach( pWeapon ); UTIL_Remove( pWeapon ); } // Nuke wearables. for ( int wbl = m_hMyWearables.Count()-1; wbl >= 0; wbl-- ) { CEconWearable *pWearable = m_hMyWearables[wbl]; Assert( pWearable ); if ( !pWearable ) continue; RemoveWearable( pWearable ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ValidateWeapons( TFPlayerClassData_t *pData, bool bResetWeapons ) { CSteamID steamIDForPlayer; GetSteamID( &steamIDForPlayer ); bool bFoundBuffItem = false; bool bOverrideRemoval = false; if ( bResetWeapons && m_bForceItemRemovalOnRespawn ) { bOverrideRemoval = true; m_bForceItemRemovalOnRespawn = false; } // Disable sounds for all weapons. We're about to switch weapons MANY times, // and we don't want the deploy sounds to play for any of them, since none // of the deploys are actually visible to the player for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon(i); if ( !pWeapon ) continue; pWeapon->SetSoundsEnabled( false ); } for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon(i); if ( !pWeapon ) continue; int iLoadoutSlot = pWeapon->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ); CEconItemView *pItem = GetLoadoutItem( GetPlayerClass()->GetClassIndex(), iLoadoutSlot ); // See if gamerules says this item isn't allowed right now bool bForceRemoved = bOverrideRemoval || !ItemIsAllowed( pItem ); if ( bForceRemoved || !ItemsMatch( pData, pWeapon->GetAttributeContainer()->GetItem(), pItem, pWeapon ) ) { // we can't hold this weapon anymore, switch to the next best weapon before removing it if ( GetActiveTFWeapon() == pWeapon ) { SwitchToNextBestWeapon( pWeapon ); } // drop weapon that belongs to other player, unless we're not regenerating // which happens at round restart if ( !bForceRemoved && m_bRegenerating ) { CEconItemView *pDroppedItem = pWeapon->GetAttributeContainer()->GetItem(); CSteamID steamID; GetSteamID( &steamID ); if ( pDroppedItem->GetAccountID() != steamID.GetAccountID() ) { // Find the position and angle of the weapons so the "ammo box" matches. Vector vecPackOrigin; QAngle vecPackAngles; if( !CalculateAmmoPackPositionAndAngles( pWeapon, vecPackOrigin, vecPackAngles ) ) return; CTFDroppedWeapon *pDroppedWeapon = CTFDroppedWeapon::Create( this, vecPackOrigin, vecPackAngles, pWeapon->GetWorldModel(), pDroppedItem ); if ( pDroppedWeapon ) { pDroppedWeapon->InitDroppedWeapon( this, pWeapon, false ); } } } // We shouldn't have this weapon. Remove it. Weapon_Detach( pWeapon ); UTIL_Remove( pWeapon ); } else if ( bResetWeapons ) { // We should have this weapon. Reset it. pWeapon->ChangeTeam( GetTeamNumber() ); pWeapon->GiveDefaultAmmo(); pWeapon->ClearKillComboCount(); if ( m_bRegenerating == false ) { pWeapon->WeaponReset(); } else { pWeapon->WeaponRegenerate(); } } int nBuffType = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, nBuffType, set_buff_type ); if ( pWeapon->GetWeaponID() == TF_WEAPON_BUFF_ITEM || nBuffType ) { bFoundBuffItem = true; } } // Reenable sounds for all weapons for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon(i); if ( !pWeapon ) continue; pWeapon->SetSoundsEnabled( true ); } // Prevent a rage exploit with changing items outside of a spawn room if ( ( IsPlayerClass( TF_CLASS_SOLDIER ) || IsPlayerClass( TF_CLASS_PYRO ) || IsPlayerClass( TF_CLASS_SNIPER ) ) && !bFoundBuffItem ) { m_Shared.SetRageMeter( 0.0f ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ValidateWearables( TFPlayerClassData_t *pData ) { CSteamID steamIDForPlayer; GetSteamID( &steamIDForPlayer ); bool bIsDisguisedSpy = IsPlayerClass( TF_CLASS_SPY ) && m_Shared.InCond( TF_COND_DISGUISED ); // Need to move backwards because we'll be removing them as we find them. for ( int wbl = m_hMyWearables.Count()-1; wbl >= 0; wbl-- ) { CEconWearable *pWearable = m_hMyWearables[wbl]; Assert( pWearable ); if ( !pWearable ) { // Integrity is failing, remove NULLs m_hMyWearables.Remove( wbl ); continue; } CTFWearable *pTFWearable = assert_cast< CTFWearable* >( pWearable ); if ( bIsDisguisedSpy && pTFWearable->IsDisguiseWearable() ) continue; bool itemMatch = false; // If you are an extra wearable, just make sure your associated weapon is valid instead CBaseEntity *pEntity = pTFWearable->GetWeaponAssociatedWith(); if ( pEntity ) { CTFWeaponBase *pWeapon = assert_cast< CTFWeaponBase* >( pTFWearable->GetWeaponAssociatedWith() ); int iLoadoutSlot = pWeapon->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ); if (iLoadoutSlot >= 0 ) { CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( GetPlayerClass()->GetClassIndex(), iLoadoutSlot, &steamIDForPlayer ); itemMatch |= ItemsMatch( pData, pWeapon->GetAttributeContainer()->GetItem(), pItem ); } } else { // Regular Wearable int iLoadoutSlot = pWearable->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ); if ( iLoadoutSlot >= 0 ) { CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( GetPlayerClass()->GetClassIndex(), iLoadoutSlot, &steamIDForPlayer ); itemMatch |= ItemsMatch( pData, pWearable->GetAttributeContainer()->GetItem(), pItem ); // Item says what slot it wants to be in, but Misc's and Taunts can be in multiple places, check against all bool bLoadoutMisc = iLoadoutSlot == LOADOUT_POSITION_MISC; bool bLoadoutTaunt = iLoadoutSlot == LOADOUT_POSITION_TAUNT; if ( bLoadoutMisc || bLoadoutTaunt ) { for ( int i = LOADOUT_POSITION_INVALID + 1; i < CLASS_LOADOUT_POSITION_COUNT; i++ ) { if ( ( bLoadoutMisc && IsMiscSlot( i ) ) || ( bLoadoutTaunt && IsTauntSlot( i ) ) ) { pItem = TFInventoryManager()->GetItemInLoadoutForClass( GetPlayerClass()->GetClassIndex(), i, &steamIDForPlayer ); itemMatch |= ItemsMatch( pData, pWearable->GetAttributeContainer()->GetItem(), pItem ); } } } } } if ( !itemMatch || pWearable->GetTeamNumber() != GetTeamNumber() || m_bForceItemRemovalOnRespawn || m_bSwitchedClass ) { if ( !pWearable->AlwaysAllow() ) { // We shouldn't have this wearable. Remove it. RemoveWearable( pWearable ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PostInventoryApplication( void ) { m_Shared.RecalculatePlayerBodygroups(); if ( m_Shared.InCond( TF_COND_DISGUISED ) ) { // Using weapons lockers destroys our disguise weapon, so we might need a new one. m_Shared.DetermineDisguiseWeapon( false ); } // Apply set bonuses. ApplySetBonuses(); // Remove our disguise if we can't disguise. if ( !CanDisguise() ) { RemoveDisguise(); } // Notify the client. IGameEvent *event = gameeventmanager->CreateEvent( "post_inventory_application" ); if ( event ) { event->SetInt( "userid", GetUserID() ); gameeventmanager->FireEvent( event ); } // Iterate over all of our wearables int iPlayerSkinOverride = 0; for ( int i=0; i< GetNumWearables(); ++i ) { CTFWearable *pWearable = dynamic_cast( GetWearable( i ) ); if ( pWearable == NULL || pWearable->IsDisguiseWearable() ) continue; // Check if we have an item that activates the skin override we want // find first skin override CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearable, iPlayerSkinOverride, player_skin_override ); if ( iPlayerSkinOverride != 0 ) // Zombie { break; } } m_iPlayerSkinOverride = iPlayerSkinOverride; m_Inventory.ClearClassLoadoutChangeTracking(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ManageRegularWeaponsLegacy( TFPlayerClassData_t *pData ) { CSteamID steamIDForPlayer; GetSteamID( &steamIDForPlayer ); for ( int iWeapon = 0; iWeapon < TF_PLAYER_WEAPON_COUNT; ++iWeapon ) { if ( pData->m_aWeapons[iWeapon] != TF_WEAPON_NONE ) { int iWeaponID = pData->m_aWeapons[iWeapon]; const char *pszWeaponName = WeaponIdToAlias( iWeaponID ); CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon( iWeapon ); WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pszWeaponName ); Assert( hWpnInfo != GetInvalidWeaponInfoHandle() ); CTFWeaponInfo *pWeaponInfo = dynamic_cast( GetFileWeaponInfoFromHandle( hWpnInfo ) ); int iLoadoutSlot = pWeaponInfo->m_iWeaponType; // HACK: Convert engineer's second PDA to using the second pda slot if ( iWeaponID == TF_WEAPON_PDA_ENGINEER_DESTROY || iWeaponID == TF_WEAPON_INVIS ) { iLoadoutSlot = LOADOUT_POSITION_PDA2; } #ifdef STAGING_ONLY if ( iWeaponID == TF_WEAPON_PDA_SPY_BUILD ) { iLoadoutSlot = LOADOUT_POSITION_PDA3; } #endif // Do we have a custom weapon in this slot? CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( GetPlayerClass()->GetClassIndex(), iLoadoutSlot, &steamIDForPlayer ); bool bHasNonBaseWeapon = pItem ? pItem->GetItemQuality() != AE_NORMAL : false; if ( pWeapon ) { bool bShouldRemove = false; if ( pItem ) { // If the item isn't the one we're supposed to have, nuke it if ( pWeapon->GetAttributeContainer()->GetItem()->GetItemID() != pItem->GetItemID() ) { bShouldRemove = true; /* Msg("Removing %s because its global index (%d) doesn't match the loadout's (%d)\n", pWeapon->GetDebugName(), pWeapon->GetAttributeContainer()->GetItem()->GetItemID(), pItem->GetItemID() ); */ } } else { // We should have a base item in our loadout. if ( pWeapon->GetAttributeContainer()->GetItem()->GetItemQuality() != AE_NORMAL ) { bShouldRemove = true; //Msg("Removing %s because it's a non-base item, and the loadout specifies a base item.\n", pWeapon->GetDebugName() ); } } // If we already have a weapon in this slot but is not the same type, nuke it (changed classes) // We don't do this if the weapon in this slot isn't a base item, because items like the flaregun // don't have matching weaponIDs, yet they shouldn't be removed. The inventory system has already // ensured that the weapon is valid in this slot. if ( !bShouldRemove && pWeapon->GetWeaponID() != iWeaponID && !bHasNonBaseWeapon ) { bShouldRemove = true; //Msg("Removing %s because it's not the right type for the class.\n", pWeapon->GetDebugName() ); } if ( bShouldRemove ) { Weapon_Detach( pWeapon ); UTIL_Remove( pWeapon ); pWeapon = NULL; } } if ( !bHasNonBaseWeapon ) { pWeapon = dynamic_cast(Weapon_OwnsThisID( iWeaponID )); } if ( pWeapon ) { Assert( pWeapon->GetAttributeContainer()->GetItem()->GetItemID() == ( pItem ? pItem->GetItemID() : INVALID_ITEM_ID ) ); pWeapon->ChangeTeam( GetTeamNumber() ); pWeapon->GiveDefaultAmmo(); if ( m_bRegenerating == false ) { pWeapon->WeaponReset(); } //char tempstr[1024]; //g_pVGuiLocalize->ConvertUnicodeToANSI( pWeapon->GetAttributeContainer()->GetItem()->GetItemName(), tempstr, sizeof(tempstr) ); //Msg("Updated %s for %s\n", tempstr, GetPlayerName() ); } else { CEconEntity* pNewItem = dynamic_cast(GiveNamedItem( pszWeaponName, 0, pItem )); Assert( pNewItem ); if ( pNewItem ) { //char tempstr[1024]; //g_pVGuiLocalize->ConvertUnicodeToANSI( pWeapon->GetAttributeContainer()->GetItem()->GetItemName(), tempstr, sizeof(tempstr) ); //Msg("Created %s for %s\n", tempstr, GetPlayerName() ); //pWeapon->DebugDescribe(); pNewItem->GiveTo( this ); } } } else { //I shouldn't have any weapons in this slot, so get rid of it CTFWeaponBase *pCarriedWeapon = (CTFWeaponBase *)GetWeapon( iWeapon ); //Don't nuke builders since they will be nuked if we don't need them later. if ( pCarriedWeapon && pCarriedWeapon->GetWeaponID() != TF_WEAPON_BUILDER ) { Weapon_Detach( pCarriedWeapon ); UTIL_Remove( pCarriedWeapon ); } } } // If we lack a primary or secondary weapon, start with our melee weapon ready. // This is for supporting new unlockables that take up weapon slots and leave our character with nothing to wield. int iMainWeaponCount = 0; CTFWeaponBase* pMeleeWeapon = NULL; for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase*) GetWeapon(i); if ( pWeapon == NULL ) continue; if ( pWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_PRIMARY || pWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_SECONDARY ) { ++iMainWeaponCount; } else if ( pWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_MELEE ) { pMeleeWeapon = pWeapon; } } if ( pMeleeWeapon && (iMainWeaponCount==0) ) { Weapon_Switch( pMeleeWeapon ); } } //----------------------------------------------------------------------------- // Purpose: Create and give the named item to the player. Then return it. //----------------------------------------------------------------------------- CBaseEntity *CTFPlayer::GiveNamedItem( const char *pszName, int iSubType, const CEconItemView *pScriptItem, bool bForce ) { // We need to support players putting any shotgun into a shotgun slot, pistol into a pistol slot, etc. // For legacy reasons, different classes actually spawn different entities for their shotguns/pistols/etc. // To deal with this, we translate entities into the right one for the class we're playing. if ( !bForce ) { // We don't do this if force is set, since a spy might be disguising as this character, etc. pszName = TranslateWeaponEntForClass( pszName, GetPlayerClass()->GetClassIndex() ); } if ( !pszName ) return NULL; // If I already own this type don't create one if ( Weapon_OwnsThisType(pszName, iSubType) && !bForce) { Assert(0); return NULL; } CBaseEntity *pItem = NULL; if ( pScriptItem ) { // Generate a weapon directly from that item pItem = ItemGeneration()->GenerateItemFromScriptData( pScriptItem, GetLocalOrigin(), vec3_angle, pszName ); } else { // Generate a base item of the specified type CItemSelectionCriteria criteria; criteria.SetQuality( AE_NORMAL ); criteria.BAddCondition( "name", k_EOperator_String_EQ, pszName, true ); pItem = ItemGeneration()->GenerateRandomItem( &criteria, GetAbsOrigin(), vec3_angle ); } if ( pItem == NULL ) { Msg( "Failed to generate base item: %s\n", pszName ); return NULL; } pItem->AddSpawnFlags( SF_NORESPAWN ); CBaseCombatWeapon *pWeapon = dynamic_cast( (CBaseEntity*)pItem ); if ( pWeapon ) { pWeapon->SetSubType( iSubType ); } DispatchSpawn( pItem ); if ( pItem != NULL && !(pItem->IsMarkedForDeletion()) ) { pItem->Touch( this ); } return pItem; } //----------------------------------------------------------------------------- // Purpose: Destroy all attributes on this player that match the bSetBonuses flag //----------------------------------------------------------------------------- void CTFPlayer::RemovePlayerAttributes( bool bSetBonuses ) { const int iAttribs = m_AttributeList.GetNumAttributes(); for ( int i = iAttribs-1; i >= 0; i-- ) { const CEconItemAttribute *pAttrib = m_AttributeList.GetAttribute(i); const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( pAttrib->GetAttribIndex() ); if ( !pAttrDef || (pAttrDef->BIsSetBonusAttribute() == bSetBonuses) ) { m_AttributeList.RemoveAttributeByIndex( i ); } } GetAttributeManager()->OnAttributeValuesChanged(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ApplySetBonuses( void ) { RemovePlayerAttributes( true ); CUtlVector pActiveSets; GetActiveSets( &pActiveSets ); FOR_EACH_VEC( pActiveSets, set ) { for ( int i = 0; i < pActiveSets[set]->m_iAttributes.Count(); i++ ) { const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( pActiveSets[set]->m_iAttributes[i].m_iAttribDefIndex ); if ( pAttrDef ) { Assert( pAttrDef->GetAttributeType() ); Assert( pAttrDef->GetAttributeType()->BSupportsGameplayModificationAndNetworking() ); // is an assert instead of a check because we're in client code here -- this means someone set up a set with bad data Assert( pAttrDef->BIsSetBonusAttribute() ); float flAttrValue = pActiveSets[set]->m_iAttributes[i].m_flValue; GetAttributeList()->SetRuntimeAttributeValue( pAttrDef, flAttrValue ); } } } } #ifdef TF_RAID_MODE //----------------------------------------------------------------------------- // Return true if the given entity can be used by a dead Raider // as a respawn point in Raid mode. bool IsValidRaidRespawnTarget( CBaseEntity *entity ) { if ( !entity->IsPlayer() ) { CObjectSentrygun *pSentry = dynamic_cast< CObjectSentrygun* >( entity ); if ( pSentry && pSentry->GetTeamNumber() == TF_TEAM_BLUE ) { if ( pSentry->GetOwner() && !pSentry->GetOwner()->IsBot() ) { return true; } } return false; } if ( entity->GetTeamNumber() != TF_TEAM_BLUE ) return false; CTFPlayer *player = ToTFPlayer( entity ); CTFBot *bot = ToTFBot( player ); return !bot || !bot->HasAttribute( CTFBot::IS_NPC ); } #endif // TF_RAID_MODE //----------------------------------------------------------------------------- // Purpose: Find a spawn point for the player. //----------------------------------------------------------------------------- CBaseEntity* CTFPlayer::EntSelectSpawnPoint() { CBaseEntity *pSpot = g_pLastSpawnPoints[ GetTeamNumber() ]; const char *pSpawnPointName = ""; #ifdef TF_RAID_MODE if ( TFGameRules()->IsRaidMode() ) { if ( GetTeamNumber() == TF_TEAM_BLUE ) { // only spawn next to friends if the round is not restarting if ( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) { if ( tf_raid_use_rescue_closets.GetBool() ) { // find a valid rescue closet to spawn into CBaseEntity *rescueSpawn = g_pRaidLogic->GetRescueRespawn(); if ( rescueSpawn ) { return rescueSpawn; } } else if ( tf_boss_battle_respawn_on_friends.GetBool() ) { // the raiders are in the wild - respawn next to them float timeSinceInjured = -1.0f; CBaseEntity *respawnEntity = NULL; // if we are observing a friend, spawn into them CBaseEntity *watchEntity = GetObserverTarget(); if ( watchEntity && IsValidRaidRespawnTarget( watchEntity ) ) { respawnEntity = watchEntity; } else { // spawn on the least recently damaged friend CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE ); for( int i=0; iGetNumPlayers(); ++i ) { CTFPlayer *buddy = (CTFPlayer *)raidingTeam->GetPlayer(i); // we can't use IsAlive(), because that has already been reset since // this code is mid-spawn. Use m_Shared state instead. if ( buddy != this && buddy->m_Shared.InState( TF_STATE_ACTIVE ) && IsValidRaidRespawnTarget( buddy ) ) { // pick the friend who has been hurt least recently if ( buddy->GetTimeSinceLastInjury( TF_TEAM_RED ) > timeSinceInjured ) { timeSinceInjured = buddy->GetTimeSinceLastInjury( TF_TEAM_RED ); respawnEntity = buddy; } } } } if ( respawnEntity ) { CPVSFilter filter( respawnEntity->GetAbsOrigin() ); TE_TFParticleEffect( filter, 0.0, "teleported_blue", respawnEntity->GetAbsOrigin(), vec3_angle ); TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", respawnEntity->GetAbsOrigin(), vec3_angle, this, PATTACH_POINT ); return respawnEntity; } } } } } #endif // TF_RAID_MODE bool bMatchSummary = TFGameRules() && TFGameRules()->ShowMatchSummary(); // See if the map is asking to force this player to spawn at a specific location if ( GetRespawnLocationOverride() && !bMatchSummary ) { if ( SelectSpawnSpotByName( GetRespawnLocationOverride(), pSpot ) ) { m_pSpawnPoint = dynamic_cast< CTFTeamSpawn* >( pSpot ); // Is this even used anymore? return pSpot; } // If the entity doesn't exist - or isn't valid - let the regular system handle it } switch( GetTeamNumber() ) { case TF_TEAM_RED: case TF_TEAM_BLUE: { pSpawnPointName = "info_player_teamspawn"; if ( SelectSpawnSpotByType( pSpawnPointName, pSpot ) ) { g_pLastSpawnPoints[ GetTeamNumber() ] = pSpot; } else if ( pSpot ) { int iClass = GetPlayerClass()->GetClassIndex(); if ( iClass >= 0 && iClass < ARRAYSIZE( g_aPlayerClassNames ) ) { Warning( "EntSelectSpawnPoint(): No valid spawns for class %s on team %i found, even though at least one spawn entity exists.\n", g_aPlayerClassNames[iClass], GetTeamNumber() ); } } // need to save this for later so we can apply and modifiers to the armor and grenades...after the call to InitClass() m_pSpawnPoint = dynamic_cast( pSpot ); break; } case TEAM_SPECTATOR: case TEAM_UNASSIGNED: default: { pSpot = CBaseEntity::Instance( INDEXENT(0) ); break; } } if ( !pSpot ) { Warning( "PutClientInServer: no %s on level\n", pSpawnPointName ); return CBaseEntity::Instance( INDEXENT(0) ); } return pSpot; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::SelectSpawnSpotByType( const char *pEntClassName, CBaseEntity* &pSpot ) { bool bMatchSummary = TFGameRules()->ShowMatchSummary(); CBaseEntity *pMatchSummaryFallback = NULL; // Get an initial spawn point. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); if ( !pSpot ) { // Sometimes the first spot can be NULL???? pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); } // First we try to find a spawn point that is fully clear. If that fails, // we look for a spawn point that's clear except for another players. We // don't collide with our team members, so we should be fine. bool bIgnorePlayers = false; // When dealing with a standard spawn ent, try to obey any class spawn flags bool bRestrictByClass = !V_strcmp( pEntClassName, "info_player_teamspawn" ); CBaseEntity *pFirstSpot = pSpot; do { if ( pSpot ) { // Check to see if this is a valid team spawn (player is on this team, etc.). if ( TFGameRules()->IsSpawnPointValid( pSpot, this, bIgnorePlayers ) ) { // Check for a bad spawn entity. if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) ) { goto next_spawn_point; } // SpawnFlags were only recently added to the .fgd (Feb 2016), which means older maps won't have any flags at all (they default to on). // So this means we only look for restrictions when we find flags, which a map compiled after this change would/should have. else if ( bRestrictByClass && pSpot->GetSpawnFlags() ) { int nClass = GetPlayerClass()->GetClassIndex() - 1; if ( !pSpot->HasSpawnFlags( ( 1 << nClass ) ) ) { goto next_spawn_point; } } // Found a valid spawn point. return true; } } next_spawn_point:; // Let's save off a fallback spot for competitive mode if ( bMatchSummary && !pMatchSummaryFallback ) { CTFTeamSpawn *pCTFSpawn = dynamic_cast( pSpot ); if ( pCTFSpawn ) { if ( ( pCTFSpawn->GetTeamNumber() == pCTFSpawn->GetTeamNumber() ) && ( pCTFSpawn->GetMatchSummaryType() == PlayerTeamSpawn_MatchSummary_None ) ) { pMatchSummaryFallback = pCTFSpawn; } } } // Get the next spawning point to check. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); // Exhausted the list if ( pSpot == pFirstSpot ) { // Loop through again, ignoring class restrictions (but check against players) if ( bRestrictByClass ) { bRestrictByClass = false; pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); } // Loop through again, ignoring players and classes else if ( !bRestrictByClass && !bIgnorePlayers ) { bIgnorePlayers = true; pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); } } } // Continue until a valid spawn point is found or we hit the start. while ( pSpot != pFirstSpot ); // Return a fallback spot for competitive mode if ( bMatchSummary && pMatchSummaryFallback ) { pSpot = pMatchSummaryFallback; return true; } return false; } //----------------------------------------------------------------------------- // Purpose: We're being asked to use a spawn with a specific name //----------------------------------------------------------------------------- bool CTFPlayer::SelectSpawnSpotByName( const char *pEntName, CBaseEntity* &pSpot ) { if ( pEntName && pEntName[0] ) { pSpot = gEntList.FindEntityByName( pSpot, pEntName ); while ( pSpot ) { if ( TFGameRules()->IsSpawnPointValid( pSpot, this, true, PlayerTeamSpawnMode_Triggered ) ) return true; pSpot = gEntList.FindEntityByName( pSpot, pEntName ); } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) { MDLCACHE_CRITICAL_SECTION(); m_PlayerAnimState->DoAnimationEvent( event, nData ); TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy. } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::HandleAnimEvent( animevent_t *pEvent ) { if ( pEvent->event == AE_TAUNT_ENABLE_MOVE ) { m_bAllowMoveDuringTaunt = true; } else if ( pEvent->event == AE_TAUNT_DISABLE_MOVE ) { m_bAllowMoveDuringTaunt = false; } else if ( pEvent->event == AE_WPN_HIDE ) { // does nothing for now. } else BaseClass::HandleAnimEvent( pEvent ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PhysObjectSleep() { IPhysicsObject *pObj = VPhysicsGetObject(); if ( pObj ) pObj->Sleep(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PhysObjectWake() { IPhysicsObject *pObj = VPhysicsGetObject(); if ( pObj ) pObj->Wake(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::GetAutoTeam( int nPreferedTeam /*= TF_TEAM_AUTOASSIGN*/ ) { int iTeam = TEAM_SPECTATOR; CTFTeam *pBlue = TFTeamMgr()->GetTeam( TF_TEAM_BLUE ); CTFTeam *pRed = TFTeamMgr()->GetTeam( TF_TEAM_RED ); if ( pBlue && pRed ) { if ( TFGameRules() ) { if ( TFGameRules()->IsInHighlanderMode() ) { if ( ( pBlue->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) && ( pRed->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) ) { // teams are full....join team Spectator for now return TEAM_SPECTATOR; } } bool bReturnDefenders = false; #ifdef TF_RAID_MODE if ( TFGameRules()->IsBossBattleMode() ) { bReturnDefenders = true; } #endif // TF_RAID_MODE if ( TFGameRules()->IsMannVsMachineMode() ) { bReturnDefenders = true; } if ( bReturnDefenders ) { // If joining a MVM game that's in-progress, give us the max per-player collected value if ( TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager ) { int nRoundCurrency = MannVsMachineStats_GetAcquiredCredits(); nRoundCurrency += g_pPopulationManager->GetStartingCurrency(); // Check to see if this player has an upgrade history and apply it to them // deduct any cash that has already been spent int spentCurrency = g_pPopulationManager->GetPlayerCurrencySpent( this ); if ( m_nCurrency < nRoundCurrency ) { SetCurrency( nRoundCurrency - spentCurrency ); } if ( g_pPopulationManager ) { // See if the team's earned any respec credits if ( TFGameRules()->IsMannVsMachineRespecEnabled() && !g_pPopulationManager->GetNumRespecsAvailableForPlayer( this ) ) { uint16 nRespecs = g_pPopulationManager->GetNumRespecsEarned(); if ( nRespecs ) { g_pPopulationManager->SetNumRespecsForPlayer( this, nRespecs ); } } // Set buyback credits - if they aren't reconnecting if ( !g_pPopulationManager->IsPlayerBeingTrackedForBuybacks( this ) ) { g_pPopulationManager->SetBuybackCreditsForPlayer( this, tf_mvm_buybacks_per_wave.GetInt() ); } } } return TFGameRules()->GetTeamAssignmentOverride( this, TF_TEAM_PVE_DEFENDERS ); } } CTFBot *pPlayerBot = dynamic_cast( this ); if ( FStrEq( tf_bot_quota_mode.GetString(), "fill" ) && ( tf_bot_quota.GetInt() > 0 ) && !( pPlayerBot && pPlayerBot->HasAttribute( CTFBot::QUOTA_MANANGED ) ) ) { // We're using 'tf_bot_quota_mode fill' to keep the teams even so balance based on the human players on each team int nPlayerCountRed = 0; int nPlayerCountBlue = 0; for ( int i = 1; i <= gpGlobals->maxClients; ++i ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer == NULL ) continue; if ( FNullEnt( pPlayer->edict() ) ) continue; if ( !pPlayer->IsConnected() ) continue; if ( !pPlayer->IsPlayer() ) continue; CTFBot* pBot = dynamic_cast( pPlayer ); if ( pBot && pBot->HasAttribute( CTFBot::QUOTA_MANANGED ) ) continue; if ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) { nPlayerCountRed++; } else if( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) { nPlayerCountBlue++; } } if ( nPlayerCountRed < nPlayerCountBlue ) { iTeam = TF_TEAM_RED; } else if ( nPlayerCountBlue < nPlayerCountRed ) { iTeam = TF_TEAM_BLUE; } else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT || pRed->GetRole() == TEAM_ROLE_DEFENDERS ) { // AutoTeam should give new players to the attackers on A/D maps if the teams are even iTeam = TF_TEAM_BLUE; } else { // teams have an even number of human players, pick a random team iTeam = RandomInt( 0, 1 ) ? TF_TEAM_RED : TF_TEAM_BLUE; } bool bKick = false; // Now we have a team we want to join to balance the human players, can we join it? if ( iTeam == TF_TEAM_RED ) { if ( pBlue->GetNumPlayers() < pRed->GetNumPlayers() ) { bKick = true; } } else { if ( pRed->GetNumPlayers() < pBlue->GetNumPlayers() ) { bKick = true; } } if ( !bKick || TheTFBots().RemoveBotFromTeamAndKick( iTeam ) ) { return iTeam; } // If kick needed but failed, fall through to default logic } if ( pBlue->GetNumPlayers() < pRed->GetNumPlayers() ) { iTeam = TF_TEAM_BLUE; } else if ( pRed->GetNumPlayers() < pBlue->GetNumPlayers() ) { iTeam = TF_TEAM_RED; } else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT || pRed->GetRole() == TEAM_ROLE_DEFENDERS ) { // AutoTeam should give new players to the attackers on A/D maps if the teams are even iTeam = TF_TEAM_BLUE; } else { if ( nPreferedTeam == TF_TEAM_AUTOASSIGN ) { iTeam = RandomInt( 0, 1 ) ? TF_TEAM_RED : TF_TEAM_BLUE; } else { Assert( nPreferedTeam >= FIRST_GAME_TEAM ); iTeam = nPreferedTeam; } } } return iTeam; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ShouldForceAutoTeam( void ) { if ( mp_forceautoteam.GetBool() ) return true; if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) return true; if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() ) return true; bool bForce = false; // On official servers, and in normal game modes, see if we should re-assign returning players if ( TFGameRules() && TFGameRules()->IsDefaultGameMode() ) { int nTimeSinceLast = TFGameRules()->PlayerHistory_GetTimeSinceLastSeen( this ); bForce = ( tf_mm_trusted.GetBool() && nTimeSinceLast > 0 && nTimeSinceLast < 60 ); } return bForce; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::HandleCommand_JoinTeam( const char *pTeamName ) { if ( TFGameRules()->State_Get() == GR_STATE_GAME_OVER ) return; if ( GetTeamNumber() == TF_TEAM_RED || GetTeamNumber() == TF_TEAM_BLUE ) { const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); if ( pMatchDesc && !pMatchDesc->m_params.m_bAllowTeamChange ) { ClientPrint( this, HUD_PRINTCENTER, "#TF_Ladder_NoTeamChange" ); return; } #ifdef STAGING_ONLY else if ( TFGameRules()->ArePlayersInHell() ) #else else if ( TFGameRules()->ArePlayersInHell() || TFGameRules()->IsPowerupMode() ) #endif // STAGING_ONLY { ClientPrint( this, HUD_PRINTCENTER, "#TF_CantChangeTeamNow" ); return; } } bool bAutoTeamed = false; bool bArenaSpectator = false; int iTeam = TF_TEAM_RED; if ( stricmp( pTeamName, "auto" ) == 0 ) { iTeam = GetAutoTeam(); bAutoTeamed = true; } else if ( stricmp( pTeamName, "spectate" ) == 0 ) { iTeam = TEAM_SPECTATOR; } else if ( stricmp( pTeamName, "spectatearena" ) == 0 ) { iTeam = TEAM_SPECTATOR; if ( mp_allowspectators.GetBool() == true ) { bArenaSpectator = true; } } else { for ( int i = 0; i < TF_TEAM_COUNT; ++i ) { COMPILE_TIME_ASSERT( TF_TEAM_COUNT == ARRAYSIZE( g_aTeamNames ) ); if ( stricmp( pTeamName, g_aTeamNames[i] ) == 0 ) { iTeam = i; break; } } } // now check if we're limited in our team selection (unless we want to be on the spectator team) if ( !IsBot() && iTeam != TEAM_SPECTATOR ) { int iHumanTeam = TFGameRules()->GetAssignedHumanTeam(); if ( iHumanTeam != TEAM_ANY ) { iTeam = iHumanTeam; bAutoTeamed = true; } } // invalid team selection if ( iTeam < TEAM_SPECTATOR ) { return; } if ( IsCoaching() && ( iTeam != TEAM_SPECTATOR ) ) return; #ifdef TF_RAID_MODE if ( TFGameRules()->IsRaidMode() ) { if ( !IsBot() && iTeam != TEAM_SPECTATOR ) { // human raiders can only be on the blue team CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE ); int humanCount = 0; for( int i=0; iGetNumPlayers(); ++i ) { if ( raidingTeam->GetPlayer(i)->IsBot() ) continue; ++humanCount; } if ( humanCount < tf_raid_team_size.GetInt() ) { iTeam = TF_TEAM_BLUE; } else { // no room iTeam = TEAM_SPECTATOR; } } } if ( TFGameRules()->IsBossBattleMode() ) { if ( !IsBot() && iTeam != TEAM_SPECTATOR ) { // players can only be on the blue team if ( GetGlobalTeam( TF_TEAM_BLUE )->GetNumPlayers() < tf_boss_battle_team_size.GetInt() ) { iTeam = TF_TEAM_BLUE; } else { // no room iTeam = TEAM_SPECTATOR; } } DuelMiniGame_NotifyPlayerChangedTeam( this, iTeam, true ); ChangeTeam( iTeam, true ); return; } #endif // TF_RAID_MODE // Some game modes will overrule our player-based logic iTeam = TFGameRules()->GetTeamAssignmentOverride( this, iTeam ); if ( iTeam == TEAM_SPECTATOR || ( TFGameRules()->IsInArenaMode() && tf_arena_use_queue.GetBool() && GetTeamNumber() <= LAST_SHARED_TEAM ) ) { // Prevent this is the cvar is set if ( ( mp_allowspectators.GetBool() == false ) && !IsHLTV() && !IsReplay() && TFGameRules()->IsInArenaMode() == false ) { ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); return; } // Deny spectator access if it would unbalance the teams if ( ( mp_spectators_restricted.GetBool() || tf_mm_trusted.GetBool() ) && TFGameRules() && !TFGameRules()->IsMannVsMachineMode() ) { if ( GetTeamNumber() == TF_TEAM_RED || GetTeamNumber() == TF_TEAM_BLUE ) { CTeam *pRedTeam = GetGlobalTeam( TF_TEAM_RED ); CTeam *pBlueTeam = GetGlobalTeam( TF_TEAM_BLUE ); if ( pRedTeam && pBlueTeam ) { int nRedCount = pRedTeam->GetNumPlayers(); int nBlueCount = pBlueTeam->GetNumPlayers(); int nGap = GetTeamNumber() == TF_TEAM_RED ? ( nBlueCount - nRedCount ) : ( nRedCount - nBlueCount ); if ( nGap >= mp_teams_unbalance_limit.GetInt() ) { ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator_Unbalance" ); return; } } } } if ( GetTeamNumber() != TEAM_UNASSIGNED && !IsDead() ) { CommitSuicide( false, true ); } m_bArenaSpectator = bArenaSpectator; DuelMiniGame_NotifyPlayerChangedTeam( this, TEAM_SPECTATOR, true ); ChangeTeam( TEAM_SPECTATOR ); if ( m_bArenaSpectator == true ) { SetDesiredPlayerClassIndex( TF_CLASS_UNDEFINED ); TFGameRules()->Arena_ClientDisconnect( GetPlayerName() ); TFGameRules()->RemovePlayerFromQueue( this ); } // do we have fadetoblack on? (need to fade their screen back in) if ( mp_fadetoblack.GetBool() ) { color32_s clr = { 0,0,0,255 }; UTIL_ScreenFade( this, clr, 0, 0, FFADE_IN | FFADE_PURGE ); } if ( TFGameRules()->IsInArenaMode() == true && m_bArenaSpectator == false ) { ShowViewPortPanel( PANEL_CLASS_BLUE ); } } else { if ( iTeam == GetTeamNumber() ) { return; // we wouldn't change the team } if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() ) { CTFTeam *pTeam = TFTeamMgr()->GetTeam( iTeam ); if ( pTeam ) { if ( pTeam->GetNumPlayers() >= TF_LAST_NORMAL_CLASS - 1 ) { // if this join would put too many players on the team in Highlander mode, refuse // come up with a better way to tell the player they tried to join a full team! ShowViewPortPanel( PANEL_TEAM ); return; } } } // if this join would unbalance the teams, refuse // come up with a better way to tell the player they tried to join a full team! if ( TFGameRules()->WouldChangeUnbalanceTeams( iTeam, GetTeamNumber() ) ) { ShowViewPortPanel( PANEL_TEAM ); return; } DuelMiniGame_NotifyPlayerChangedTeam( this, iTeam, true ); bool bSilent = TFGameRules() && TFGameRules()->IsPVEModeActive() && IsBot(); #ifndef _DEBUG TFGameRules()->SetPlayerReadyState( entindex(), false ); TFGameRules()->SetTeamReadyState( false, GetTeamNumber() ); #endif // _DEBUG ChangeTeam( iTeam, bAutoTeamed, bSilent ); if ( tf_arena_force_class.GetBool() == false ) { ShowViewPortPanel( ( iTeam == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); } } } //----------------------------------------------------------------------------- // Purpose: Join a team without using the game menus //----------------------------------------------------------------------------- void CTFPlayer::HandleCommand_JoinTeam_NoMenus( const char *pTeamName ) { Assert( IsX360() ); Msg( "Client command HandleCommand_JoinTeam_NoMenus: %s\n", pTeamName ); // Only expected to be used on the 360 when players leave the lobby to start a new game if ( !IsInCommentaryMode() ) { Assert( GetTeamNumber() == TEAM_UNASSIGNED ); Assert( IsX360() ); } int iTeam = TEAM_SPECTATOR; if ( Q_stricmp( pTeamName, "spectate" ) ) { for ( int i = 0; i < TF_TEAM_COUNT; ++i ) { COMPILE_TIME_ASSERT( TF_TEAM_COUNT == ARRAYSIZE( g_aTeamNames ) ); if ( stricmp( pTeamName, g_aTeamNames[i] ) == 0 ) { iTeam = i; break; } } } ForceChangeTeam( iTeam ); } //----------------------------------------------------------------------------- // Purpose: Player has been forcefully changed to another team //----------------------------------------------------------------------------- void CTFPlayer::ForceChangeTeam( int iTeamNum, bool bFullTeamSwitch ) { int iNewTeam = iTeamNum; if ( iNewTeam == TF_TEAM_AUTOASSIGN ) { iNewTeam = GetAutoTeam(); } if ( !GetGlobalTeam( iNewTeam ) ) { Warning( "CTFPlayer::ForceChangeTeam( %d ) - invalid team index.\n", iNewTeam ); return; } // Some game modes will overrule our player-based logic iNewTeam = TFGameRules()->GetTeamAssignmentOverride( this, iNewTeam ); int iOldTeam = GetTeamNumber(); // if this is our current team, just abort if ( iNewTeam == iOldTeam ) return; // can't change teams if in a duel if ( DuelMiniGame_IsInDuel( this ) ) { if ( !m_bIsCoaching ) return; DuelMiniGame_NotifyPlayerChangedTeam( this, iTeamNum, true ); } // can't change teams if coaching if ( m_bIsCoaching && m_hStudent != NULL && iTeamNum != TEAM_SPECTATOR ) return; RemoveAllOwnedEntitiesFromWorld( true ); m_iPreviousteam = iOldTeam; BaseClass::ChangeTeam( iNewTeam, false, true ); if ( !bFullTeamSwitch ) { RemoveNemesisRelationships(); if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() ) { if ( IsAlive() ) { CommitSuicide( false, true ); } ResetPlayerClass(); } } if ( iNewTeam == TEAM_UNASSIGNED ) { StateTransition( TF_STATE_OBSERVER ); } else if ( iNewTeam == TEAM_SPECTATOR ) { StateTransition( TF_STATE_OBSERVER ); RemoveAllWeapons(); DestroyViewModels(); if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true ) { TFGameRules()->AddPlayerToQueueHead( this ); } } DropFlag(); // Don't modify living players in any way } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::HandleFadeToBlack( void ) { if ( mp_fadetoblack.GetBool() ) { SetObserverMode( OBS_MODE_CHASE ); color32_s clr = { 0,0,0,255 }; UTIL_ScreenFade( this, clr, 0.75, 0, FFADE_OUT | FFADE_STAYOUT ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ChangeTeam( int iTeamNum, bool bAutoTeam, bool bSilent, bool bAutoBalance /*= false*/ ) { if ( !GetGlobalTeam( iTeamNum ) ) { Warning( "CTFPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); return; } // game rules don't allow to change team if ( TFGameRules() && !TFGameRules()->CanChangeTeam( GetTeamNumber() ) ) { return; } // Not allowed to change teams when a ghost if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) { return; } // Not allowed to change teams in bumper kart if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { return; } // can only be on TEAM_SPECTATOR when coaching if ( IsCoaching() && ( iTeamNum >= FIRST_GAME_TEAM ) ) { return; } // Some game modes will overrule our player-based logic iTeamNum = TFGameRules()->GetTeamAssignmentOverride( this, iTeamNum, bAutoBalance ); int iOldTeam = GetTeamNumber(); // if this is our current team, just abort if ( iTeamNum == iOldTeam ) return; RemoveAllOwnedEntitiesFromWorld( true ); bool bNoTeam = GetTeamNumber() == TEAM_UNASSIGNED; m_iPreviousteam = iOldTeam; CTF_GameStats.Event_TeamChange( this, iOldTeam, iTeamNum ); m_iTeamChanges++; // If joining the underdog team, make next spawn instant (autobalance, paladins) if ( TFGameRules() && TFGameRules()->IsDefaultGameMode() && GetTeamNumber() >= FIRST_GAME_TEAM ) { int nStackedTeam, nWeakTeam; if ( TFGameRules()->AreTeamsUnbalanced( nStackedTeam, nWeakTeam ) ) { if ( iTeamNum == nWeakTeam ) { AllowInstantSpawn(); } } } BaseClass::ChangeTeam( iTeamNum, bAutoTeam, bSilent, bAutoBalance ); if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() ) { if ( IsAlive() ) { CommitSuicide( false, true ); } ResetPlayerClass(); } RemoveNemesisRelationships(); if ( iTeamNum == TEAM_UNASSIGNED ) { StateTransition( TF_STATE_OBSERVER ); } else if ( iTeamNum == TEAM_SPECTATOR ) { StateTransition( TF_STATE_OBSERVER ); RemoveAllWeapons(); DestroyViewModels(); if ( TFGameRules()->IsInArenaMode() == true && bNoTeam == false && tf_arena_use_queue.GetBool() == true ) { TFGameRules()->AddPlayerToQueue( this ); } } else // active player { bool bKill = true; #ifdef STAGING_ONLY bKill = ( m_Shared.InCond( TF_COND_REPROGRAMMED ) ) ? false : true; #endif // STAGING_ONLY if ( bKill && !IsDead() && (iOldTeam == TF_TEAM_RED || iOldTeam == TF_TEAM_BLUE) ) { // Kill player if switching teams while alive CommitSuicide( false, true ); } else if ( IsDead() && iOldTeam < FIRST_GAME_TEAM ) { HandleFadeToBlack(); } // let any spies disguising as me know that I've changed teams for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CTFPlayer *pTemp = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pTemp && pTemp != this ) { if ( ( pTemp->m_Shared.GetDisguiseTarget() == this ) || // they were disguising as me and I've changed teams ( !pTemp->m_Shared.GetDisguiseTarget() && pTemp->m_Shared.GetDisguiseTeam() == iTeamNum ) ) // they don't have a disguise and I'm joining the team they're disguising as { // choose someone else... pTemp->m_Shared.FindDisguiseTarget(); } } } } m_Shared.RemoveAllCond(); DuelMiniGame_NotifyPlayerChangedTeam( this, iTeamNum, false ); #ifdef STAGING_ONLY if ( TFGameRules() && TFGameRules()->GameModeUsesExperience() ) { RefundExperiencePoints(); } #endif // STAGING_ONLY } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ResetPlayerClass( void ) { if ( GetPlayerClass() ) { GetPlayerClass()->Reset(); } SetDesiredPlayerClassIndex( TF_CLASS_UNDEFINED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::HandleCommand_JoinClass( const char *pClassName, bool bAllowSpawn /* = true */ ) { VPROF_BUDGET( "CTFPlayer::HandleCommand_JoinClass", VPROF_BUDGETGROUP_PLAYER ); if ( TFGameRules()->State_Get() == GR_STATE_GAME_OVER ) { return; } // if ( TFGameRules()->ArePlayersInHell() && ( m_Shared.m_iDesiredPlayerClass > TF_CLASS_UNDEFINED ) ) // { // ClientPrint( this, HUD_PRINTCENTER, "#TF_CantChangeClassNow" ); // return; // } if ( TFGameRules()->IsCompetitiveMode() ) { if ( !tf_tournament_classchange_allowed.GetBool() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) { ClientPrint( this, HUD_PRINTCENTER, "#TF_Ladder_NoClassChangeRound" ); return; } if ( !tf_tournament_classchange_ready_allowed.GetBool() && TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS && TFGameRules()->IsPlayerReady( entindex() ) ) { ClientPrint( this, HUD_PRINTCENTER, "#TF_Ladder_NoClassChangeReady" ); return; } } if ( IsCoaching() ) return; // can only join a class after you join a valid team if ( GetTeamNumber() <= LAST_SHARED_TEAM && TFGameRules()->IsInArenaMode() == false ) return; // In case we don't get the class menu message before the spawn timer // comes up, fake that we've closed the menu. SetClassMenuOpen( false ); if ( TFGameRules()->InStalemate() && TFGameRules()->IsInArenaMode() == false ) { if ( IsAlive() && !TFGameRules()->CanChangeClassInStalemate() ) { char szTime[6]; Q_snprintf( szTime, sizeof( szTime ), "%d", tf_stalematechangeclasstime.GetInt() ); ClientPrint( this, HUD_PRINTTALK, "#game_stalemate_cant_change_class", szTime ); return; } } if ( TFGameRules()->IsInArenaMode() == true && IsAlive() == true ) { if ( GetTeamNumber() > LAST_SHARED_TEAM && TFGameRules()->InStalemate() == true ) { ClientPrint( this, HUD_PRINTTALK, "#TF_Arena_NoClassChange" ); return; } } #ifdef TF_RAID_MODE if ( TFGameRules()->IsRaidMode() && GetTeamNumber() == TF_TEAM_BLUE && !tf_raid_allow_class_change.GetBool() ) { CTFNavArea *area = (CTFNavArea *)GetLastKnownArea(); if ( area && !area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) ) { ClientPrint( this, HUD_PRINTTALK, "No class changes after leaving the safe room" ); return; } } #endif // TF_RAID_MODE if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) { if ( m_nCanPurchaseUpgradesCount > 0 ) { ClientPrint( this, HUD_PRINTCENTER, "#TF_MVM_NoClassUpgradeUI" ); return; } if ( IsReadyToPlay() && !TFGameRules()->InSetup() && g_pPopulationManager && !g_pPopulationManager->IsInEndlessWaves() ) { ClientPrint( this, HUD_PRINTTALK, "#TF_MVM_NoClassChangeAfterSetup" ); return; } } int iClass = TF_CLASS_UNDEFINED; bool bShouldNotRespawn = false; if ( !bAllowSpawn || ( ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) && ( TFGameRules()->GetWinningTeam() != GetTeamNumber() ) ) ) { m_bAllowInstantSpawn = false; bShouldNotRespawn = true; } if ( stricmp( pClassName, "random" ) != 0 && stricmp( pClassName, "auto" ) != 0 ) { int i = 0; for ( i = TF_CLASS_SCOUT ; i < TF_CLASS_COUNT_ALL ; i++ ) { if ( stricmp( pClassName, GetPlayerClassData( i )->m_szClassName ) == 0 ) { iClass = i; break; } } bool bCivilianOkay = false; if ( !bCivilianOkay && ( i >= TF_LAST_NORMAL_CLASS ) ) { Warning( "HandleCommand_JoinClass( %s ) - invalid class name.\n", pClassName ); return; } // Check class limits if ( !TFGameRules()->CanPlayerChooseClass(this, iClass) ) { ShowViewPortPanel( ( GetTeamNumber() == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); return; } } else { int iTries = 20; // The player has selected Random class...so let's pick one for them. do{ // Don't let them be the same class twice in a row iClass = random->RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS - 1 ); // -1 to remove the civilian from the randomness iTries--; } while( iClass == GetPlayerClass()->GetClassIndex() || (iTries > 0 && !TFGameRules()->CanPlayerChooseClass(this,iClass)) ); if ( iTries <= 0 ) { // We failed to find a random class. Bring up the class menu again. ShowViewPortPanel( ( GetTeamNumber() == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); return; } } if ( TFGameRules() && TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) { // Bit field of classes played during the game CSteamID steamID; GetSteamID( &steamID ); CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch(); if ( pMatch ) { CMatchInfo::PlayerMatchData_t *pMatchPlayer = pMatch->GetMatchDataForPlayer( steamID ); if ( pMatchPlayer ) { pMatchPlayer->UpdateClassesPlayed( GetPlayerClass()->GetClassIndex() ); } } } #if defined( _DEBUG ) || defined( STAGING_ONLY ) if ( mp_developer.GetBool() && !IsBot() ) { Vector vPos = GetAbsOrigin(); QAngle qAngle = GetAbsAngles(); SetDesiredPlayerClassIndex( iClass ); ForceRespawn(); Teleport( &vPos, &qAngle, &vec3_origin ); return; } #endif // _DEBUG || STAGING_ONLY // joining the same class? if ( iClass != TF_CLASS_RANDOM && iClass == GetDesiredPlayerClassIndex() ) { // If we're dead, and we have instant spawn, respawn us immediately. Catches the case // were a player misses respawn wave because they're at the class menu, and then changes // their mind and reselects their current class. if ( m_bAllowInstantSpawn && !IsAlive() ) { ForceRespawn(); } return; } if ( TFGameRules()->IsInArenaMode() && tf_arena_use_queue.GetBool() == true && GetTeamNumber() <= LAST_SHARED_TEAM ) { TFGameRules()->AddPlayerToQueue( this ); } // @note Tom Bui: we need to restrict the UI somehow // if there's a class restriction on duels... int iDuelClass = DuelMiniGame_GetRequiredPlayerClass( this ); if ( iDuelClass >= TF_FIRST_NORMAL_CLASS && iDuelClass < TF_LAST_NORMAL_CLASS ) { iClass = iDuelClass; } SetDesiredPlayerClassIndex( iClass ); IGameEvent * event = gameeventmanager->CreateEvent( "player_changeclass" ); if ( event ) { event->SetInt( "userid", GetUserID() ); event->SetInt( "class", iClass ); gameeventmanager->FireEvent( event ); } // are they TF_CLASS_RANDOM and trying to select the class they're currently playing as (so they can stay this class)? if ( iClass == GetPlayerClass()->GetClassIndex() ) { // If we're dead, and we have instant spawn, respawn us immediately. Catches the case // were a player misses respawn wave because they're at the class menu, and then changes // their mind and reselects their current class. if ( m_bAllowInstantSpawn && !IsAlive() ) { ForceRespawn(); } return; } // We can respawn instantly if: // - We're dead, and we're past the required post-death time // - We're inside a respawn room // - We're in the stalemate grace period bool bInRespawnRoom = PointInRespawnRoom( this, WorldSpaceCenter() ); if ( bInRespawnRoom && !IsAlive() ) { // If we're not spectating ourselves, ignore respawn rooms. Otherwise we'll get instant spawns // by spectating someone inside a respawn room. bInRespawnRoom = (GetObserverTarget() == this); } bool bDeadInstantSpawn = !IsAlive(); if ( bDeadInstantSpawn && m_flDeathTime ) { // In death mode, don't allow class changes to force respawns ahead of respawn waves float flWaveTime = TFGameRules()->GetNextRespawnWave( GetTeamNumber(), this ); bDeadInstantSpawn = (gpGlobals->curtime > flWaveTime); } bool bInStalemateClassChangeTime = false; if ( TFGameRules()->InStalemate() && TFGameRules()->IsInWaitingForPlayers() == false ) { // Stalemate overrides respawn rules. Only allow spawning if we're in the class change time. bInStalemateClassChangeTime = TFGameRules()->CanChangeClassInStalemate(); bDeadInstantSpawn = false; bInRespawnRoom = false; } if ( TFGameRules()->IsInArenaMode() == true ) { if ( TFGameRules()->IsInWaitingForPlayers() == false ) { bDeadInstantSpawn = false; if ( TFGameRules()->InStalemate() == false && TFGameRules()->State_Get() != GR_STATE_TEAM_WIN ) { bInRespawnRoom = true; bShouldNotRespawn = false; } else { bShouldNotRespawn = true; if ( tf_arena_use_queue.GetBool() == false ) return; } } else if ( tf_arena_use_queue.GetBool() == false ) { return; } } if ( TFGameRules()->IsMannVsMachineMode() && TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) m_bAllowInstantSpawn = true; if ( bShouldNotRespawn == false && ( m_bAllowInstantSpawn || bDeadInstantSpawn || bInRespawnRoom || bInStalemateClassChangeTime ) ) { ForceRespawn(); #ifdef STAGING_ONLY if ( TFGameRules() && TFGameRules()->GameModeUsesExperience() ) { RefundExperiencePoints(); } #endif // STAGING_ONLY return; } if( iClass == TF_CLASS_RANDOM ) { if( IsAlive() ) { ClientPrint(this, HUD_PRINTTALK, "#game_respawn_asrandom" ); } else { ClientPrint(this, HUD_PRINTTALK, "#game_spawn_asrandom" ); } } else { if( IsAlive() ) { ClientPrint(this, HUD_PRINTTALK, "#game_respawn_as", GetPlayerClassData( iClass )->m_szLocalizableName ); } else { ClientPrint(this, HUD_PRINTTALK, "#game_spawn_as", GetPlayerClassData( iClass )->m_szLocalizableName ); } } if ( IsAlive() && ( GetHudClassAutoKill() == true ) && bShouldNotRespawn == false ) { CommitSuicide( false, true ); } #ifdef STAGING_ONLY if ( TFGameRules() && TFGameRules()->GameModeUsesExperience() ) { SetExperiencePoints( 0 ); SetCurrency( 0 ); SetExperienceLevel( 1 ); } #endif // STAGING_ONLY } //----------------------------------------------------------------------------- // Purpose: The GC has told us this player wants to respawn now that their loadout has changed. //----------------------------------------------------------------------------- void CTFPlayer::CheckInstantLoadoutRespawn( void ) { // Must be alive if ( !IsAlive() ) return; // In a respawn room if ( !PointInRespawnRoom( this, WorldSpaceCenter() ) ) return; // Not in stalemate (beyond the change class period) if ( TFGameRules()->InStalemate() && !TFGameRules()->CanChangeClassInStalemate() ) return; // Not in Arena mode if ( TFGameRules()->IsInArenaMode() == true ) return; // Not if we're on the losing team if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN && TFGameRules()->GetWinningTeam() != GetTeamNumber() ) return; // Not if our current class's loadout hasn't changed int iClass = GetPlayerClass() ? GetPlayerClass()->GetClassIndex() : TF_CLASS_UNDEFINED; if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ) { if ( m_Inventory.ClassLoadoutHasChanged( iClass ) ) { if ( m_Shared.InCond( TF_COND_AIMING ) ) { // If we are in condition TF_COND_AIMING it will be removed during the ForceRespawn() so we need to reset the weapon // (which is normally skipped while regenerating)...this only affects the Minigun and the Sniper Rifle. CTFWeaponBase *pWeapon = GetActiveTFWeapon(); if ( pWeapon ) { if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || WeaponID_IsSniperRifle( pWeapon->GetWeaponID() ) ) { pWeapon->WeaponReset(); } } } if ( IsPlayerClass( TF_CLASS_MEDIC ) ) { CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( GetActiveTFWeapon() ); if ( pMedigun ) { pMedigun->Lower(); } } // We want to use ForceRespawn() here so the player is physically moved back // into the spawn room and not just regenerated instantly in the doorway ForceRegenerateAndRespawn(); } } } class CGC_RespawnPostLoadoutChange : public GCSDK::CGCClientJob { public: CGC_RespawnPostLoadoutChange( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); CSteamID steamID = msg.Body().m_ulInitiatorSteamID; // Find the player with this steamID CSteamID tmpID; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( !pPlayer ) continue; if ( !pPlayer->GetSteamID( &tmpID ) ) continue; if ( tmpID == steamID ) { pPlayer->CheckInstantLoadoutRespawn(); break; } } return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGC_RespawnPostLoadoutChange, "CGC_RespawnPostLoadoutChange", k_EMsgGCRespawnPostLoadoutChange, GCSDK::k_EServerTypeGCClient ); #if defined (_DEBUG) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- static void DebugEconItemView( const char *pszDescStr, CEconItemView *pEconItemView ) { if ( !pEconItemView ) return; const GameItemDefinition_t *pItemDef = pEconItemView->GetItemDefinition(); Assert( pItemDef ); Warning("%s: \"%s\"\n", pszDescStr, pItemDef->GetDefinitionName() ); } #endif bool CTFPlayer::ClientCommand( const CCommand &args ) { const char *pcmd = args[0]; m_flLastAction = gpGlobals->curtime; if ( FStrEq( pcmd, "addcond" ) ) { if ( sv_cheats->GetBool() && args.ArgC() >= 2 ) { ETFCond eCond = (ETFCond)clamp( atoi( args[1] ), 0, TF_COND_LAST-1 ); CTFPlayer *pTargetPlayer = this; if ( args.ArgC() >= 4 ) { // Find the matching netname for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex(i) ); if ( pPlayer ) { if ( Q_strstr( pPlayer->GetPlayerName(), args[3] ) ) { pTargetPlayer = ToTFPlayer(pPlayer); break; } } } } if ( args.ArgC() >= 3 ) { float flDuration = atof( args[2] ); pTargetPlayer->m_Shared.AddCond( eCond, flDuration ); } else { pTargetPlayer->m_Shared.AddCond( eCond ); } } return true; } else if ( FStrEq( pcmd, "removecond" ) ) { if ( sv_cheats->GetBool() && args.ArgC() >= 2 ) { CTFPlayer *pTargetPlayer = this; if ( args.ArgC() >= 3 ) { // Find the matching netname for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex(i) ); if ( pPlayer ) { if ( Q_strstr( pPlayer->GetPlayerName(), args[2] ) ) { pTargetPlayer = ToTFPlayer(pPlayer); break; } } } } ETFCond eCond = (ETFCond)clamp( atoi( args[1] ), 0, TF_COND_LAST-1 ); pTargetPlayer->m_Shared.RemoveCond( eCond ); } return true; } #ifdef _DEBUG else if ( FStrEq( pcmd, "burn" ) ) { m_Shared.Burn( this, GetActiveTFWeapon() ); return true; } else if ( FStrEq( pcmd, "bleed" ) ) { m_Shared.MakeBleed( this, GetActiveTFWeapon(), 10.0f ); return true; } else if ( FStrEq( pcmd, "dump_damagers" ) ) { m_AchievementData.DumpDamagers(); return true; } else if ( FStrEq( pcmd, "stun" ) ) { if ( args.ArgC() >= 4 ) { m_Shared.StunPlayer( atof(args[1]), atof(args[2]), atof(args[3]) ); } return true; } // else if ( FStrEq( pcmd, "decoy" ) ) // { // CBotNPCDecoy *decoy = (CBotNPCDecoy *)CreateEntityByName( "bot_npc_decoy" ); // if ( decoy ) // { // decoy->SetOwnerEntity( this ); // DispatchSpawn( decoy ); // } // return true; // } else if ( FStrEq( pcmd, "tada" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { Taunt( TAUNT_SHOW_ITEM ); } return true; } // else if ( FStrEq( pcmd, "player_disguise" ) ) // { // CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByName( args[1] ) ); // pPlayer->m_Shared.Disguise( Q_atoi( args[2] ), Q_atoi( args[3] ) ); // return true; // } else #endif if ( FStrEq( pcmd, "jointeam" ) ) { // don't let them spam the server with changes if ( GetNextChangeTeamTime() > gpGlobals->curtime ) return true; SetNextChangeTeamTime( gpGlobals->curtime + 2.0f ); // limit to one change every 2 secs if ( args.ArgC() >= 2 ) { HandleCommand_JoinTeam( args[1] ); } return true; } else if ( FStrEq( pcmd, "jointeam_nomenus" ) ) { if ( IsX360() ) { if ( args.ArgC() >= 2 ) { HandleCommand_JoinTeam_NoMenus( args[1] ); } return true; } return false; } else if ( FStrEq( pcmd, "closedwelcomemenu" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( GetTeamNumber() == TEAM_UNASSIGNED ) { if ( ShouldForceAutoTeam() ) { ChangeTeam( GetAutoTeam(), true, false ); ShowViewPortPanel( ( GetTeamNumber() == TF_TEAM_BLUE ) ? PANEL_CLASS_BLUE : PANEL_CLASS_RED ); } else { ShowViewPortPanel( PANEL_TEAM, true ); } } else if ( IsPlayerClass( TF_CLASS_UNDEFINED ) ) { if ( tf_arena_force_class.GetBool() == false ) { switch( GetTeamNumber() ) { case TF_TEAM_RED: ShowViewPortPanel( PANEL_CLASS_RED, true ); break; case TF_TEAM_BLUE: ShowViewPortPanel( PANEL_CLASS_BLUE, true ); break; default: break; } } } } return true; } else if ( FStrEq( pcmd, "joinclass" ) ) { // don't let them spam the server with changes if ( GetNextChangeClassTime() > gpGlobals->curtime ) return true; SetNextChangeClassTime( gpGlobals->curtime + 0.5 ); // limit to one change every 0.5 secs if ( tf_arena_force_class.GetBool() == false ) { if ( args.ArgC() >= 2 ) { HandleCommand_JoinClass( args[1] ); } } return true; } else if ( FStrEq( pcmd, "resetclass" ) ) { if ( TFGameRules() && TFGameRules()->IsInHighlanderMode() && ( GetTeamNumber() > LAST_SHARED_TEAM ) ) { if ( IsAlive() ) { CommitSuicide( false, true ); } ResetPlayerClass(); ShowViewPortPanel( ( GetTeamNumber() == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); } return true; } else if ( FStrEq( pcmd, "mp_playgesture" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( args.ArgC() == 1 ) { Warning( "mp_playgesture: Gesture activity or sequence must be specified!\n" ); return true; } if ( sv_cheats->GetBool() ) { if ( !PlayGesture( args[1] ) ) { Warning( "mp_playgesture: unknown sequence or activity name \"%s\"\n", args[1] ); return true; } } } return true; } else if ( FStrEq( pcmd, "mp_playanimation" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( args.ArgC() == 1 ) { Warning( "mp_playanimation: Activity or sequence must be specified!\n" ); return true; } if ( sv_cheats->GetBool() ) { if ( !PlaySpecificSequence( args[1] ) ) { Warning( "mp_playanimation: Unknown sequence or activity name \"%s\"\n", args[1] ); return true; } } } return true; } else if ( FStrEq( pcmd, "menuopen" ) ) { SetClassMenuOpen( true ); return true; } else if ( FStrEq( pcmd, "menuclosed" ) ) { SetClassMenuOpen( false ); return true; } else if ( FStrEq( pcmd, "pda_click" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { // player clicked on the PDA, play attack animation CTFWeaponBase *pWpn = GetActiveTFWeapon(); CTFWeaponPDA *pPDA = dynamic_cast( pWpn ); if ( pPDA && !m_Shared.InCond( TF_COND_DISGUISED ) ) { DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); } } return true; } else if ( FStrEq( pcmd, "weapon_taunt" ) || FStrEq( pcmd, "taunt" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { int iTauntSlot = args.ArgC() == 2 ? atoi( args[1] ) : 0; HandleTauntCommand( iTauntSlot ); } return true; } else if ( FStrEq( pcmd, "stop_taunt" ) ) { if( m_Shared.GetTauntIndex() == TAUNT_LONG && !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { EndLongTaunt(); } return true; } else if ( FStrEq( pcmd, "-taunt" ) ) { // DO NOTHING // We changed taunt key to be press to toggle instead of press and hold to do long taunt return true; } else if ( FStrEq( pcmd, "td_buyback" ) ) { if ( TFGameRules() && TFGameRules()->IsPVEModeActive() && IsObserver() && ( GetTeamNumber() > TEAM_SPECTATOR ) ) { // Make sure we're not still in freezecam int iObsMode = GetObserverMode(); if ( iObsMode == OBS_MODE_FREEZECAM || iObsMode == OBS_MODE_DEATHCAM ) return true; float flWaveTime = TFGameRules()->GetNextRespawnWave( GetTeamNumber(), this ); bool bSuccess = false; int iRespawnWait = (flWaveTime - gpGlobals->curtime); int iCost = iRespawnWait * MVM_BUYBACK_COST_PER_SEC; // New system (finite buybacks per-wave, not currency-based) if ( tf_mvm_buybacks_method.GetBool() ) { if ( g_pPopulationManager->GetNumBuybackCreditsForPlayer( this ) ) { bSuccess = true; iCost = 1; g_pPopulationManager->RemoveBuybackCreditFromPlayer( this ); } } // Old system (currency-based) else { if ( GetCurrency() >= iCost ) { bSuccess = true; RemoveCurrency( iCost ); MannVsMachineStats_PlayerEvent_BoughtInstantRespawn( this, iCost ); } } if ( bSuccess ) { ForceRespawn(); IGameEvent *event = gameeventmanager->CreateEvent( "player_buyback" ); if ( event ) { event->SetInt( "player", entindex() ); event->SetInt( "cost", iCost ); gameeventmanager->FireEvent( event ); } } else { CSingleUserRecipientFilter filter( this ); EmitSound_t params; params.m_pSoundName = "Player.DenyWeaponSelection"; EmitSound( filter, entindex(), params ); } } return true; } else if ( FStrEq( pcmd, "build" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( TFGameRules()->InStalemate() && mp_stalemate_meleeonly.GetBool() ) return true; // can't issue a build command while carrying an object if ( m_Shared.IsCarryingObject() ) return true; if ( IsTaunting() ) return true; int iBuilding = 0; int iMode = 0; bool bArgsChecked = false; // Fixup old binds. if ( args.ArgC() == 2 ) { iBuilding = atoi( args[ 1 ] ); if ( iBuilding == 3 ) // Teleport exit is now a mode. { iBuilding = 1; iMode = 1; } bArgsChecked = true; } else if ( args.ArgC() == 3 ) { iBuilding = atoi( args[ 1 ] ); iMode = atoi( args[ 2 ] ); bArgsChecked = true; } if ( bArgsChecked ) { StartBuildingObjectOfType( iBuilding, iMode ); } else { Warning( "Usage: build \n" ); } } return true; } else if ( FStrEq( pcmd, "destroy" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) // Spies can't destroy buildings (sappers) { int iBuilding = 0; int iMode = 0; bool bArgsChecked = false; // Fixup old binds. if ( args.ArgC() == 2 ) { iBuilding = atoi( args[ 1 ] ); if ( iBuilding == 3 ) // Teleport exit is now a mode. { iBuilding = 1; iMode = 1; } bArgsChecked = true; } else if ( args.ArgC() == 3 ) { iBuilding = atoi( args[ 1 ] ); iMode = atoi( args[ 2 ] ); bArgsChecked = true; } if ( bArgsChecked ) { DetonateObjectOfType( iBuilding, iMode ); } else { Warning( "Usage: destroy \n" ); } } } return true; } else if ( FStrEq( pcmd, "eureka_teleport" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { CTFWeaponBase* pWeapon = GetActiveTFWeapon(); if ( !pWeapon ) return true; if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) return true; int iAltFireTeleportToSpawn = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iAltFireTeleportToSpawn, alt_fire_teleport_to_spawn ); if ( IsPlayerClass( TF_CLASS_ENGINEER ) && iAltFireTeleportToSpawn ) { if ( args.ArgC() == 2 ) { m_eEurekaTeleportTarget = (eEurekaTeleportTargets)atoi( args[1] ); } else { m_eEurekaTeleportTarget = EUREKA_TELEPORT_HOME; } // Do the Eureka Effect teleport taunt Taunt( TAUNT_SPECIAL, MP_CONCEPT_TAUNT_EUREKA_EFFECT_TELEPORT ); } } } else if ( FStrEq( pcmd, "arena_changeclass" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( TFGameRules() && TFGameRules()->IsInArenaMode() && ( tf_arena_force_class.GetBool() == true ) ) { if ( TFGameRules()->State_Get() == GR_STATE_PREROUND ) { if ( m_Shared.GetArenaNumChanges() < tf_arena_change_limit.GetInt() ) { CommitSuicide( true, false ); m_Shared.IncrementArenaNumChanges(); } } } } return true; } else if ( FStrEq( pcmd, "extendfreeze" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { m_flDeathTime += 2.0f; } return true; } else if ( FStrEq( pcmd, "show_motd" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( ShouldForceAutoTeam() ) { int nPreferedTeam = TF_TEAM_AUTOASSIGN; PlayerHistoryInfo_t *pPlayerInfo = ( TFGameRules() ) ? TFGameRules()->PlayerHistory_GetPlayerInfo( this ) : NULL; if ( pPlayerInfo && pPlayerInfo->nTeam >= FIRST_GAME_TEAM ) { nPreferedTeam = pPlayerInfo->nTeam; } int iTeam = GetAutoTeam( nPreferedTeam ); ChangeTeam( iTeam, true, false ); ShowViewPortPanel( ( iTeam == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); } #ifdef TF_RAID_MODE else if ( TFGameRules()->IsBossBattleMode() ) { int iTeam = GetAutoTeam(); ChangeTeam( iTeam, true ); ShowViewPortPanel( ( iTeam == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); } #endif else { ShowViewPortPanel( PANEL_TEAM, false ); } ShowViewPortPanel( PANEL_ARENA_TEAM, false ); char pszWelcome[128]; Q_snprintf( pszWelcome, sizeof(pszWelcome), "#TF_Welcome" ); if ( UTIL_GetActiveHolidayString() ) { Q_snprintf( pszWelcome, sizeof(pszWelcome), "#TF_Welcome_%s", UTIL_GetActiveHolidayString() ); } KeyValues *data = new KeyValues( "data" ); data->SetString( "title", pszWelcome ); // info panel title data->SetString( "type", "1" ); // show userdata from stringtable entry data->SetString( "msg", "motd" ); // use this stringtable entry data->SetString( "msg_fallback", "motd_text" ); // use this stringtable entry if the base is HTML, and client has disabled HTML motds data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() ); ShowViewPortPanel( PANEL_INFO, true, data ); data->deleteThis(); } return true; } else if ( FStrEq( pcmd, "show_htmlpage" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( args.ArgC() != 2 ) { Warning( "Usage: show_htmlpage \n" ); return true; } KeyValues *data = new KeyValues( "data" ); data->SetString( "title", "#TF_Welcome" ); // info panel title data->SetString( "type", "2" ); // show url data->SetString( "msg", args[1] ); data->SetString( "msg_fallback", "motd_text" ); // use this stringtable entry if the base is HTML, and client has disabled HTML motds data->SetInt( "cmd", TEXTWINDOW_CMD_CLOSED_HTMLPAGE ); // exec this command if panel closed data->SetString( "customsvr", "1" ); data->SetBool( "unload", false ); ShowViewPortPanel( PANEL_INFO, true, data ); data->deleteThis(); } return true; } else if ( FStrEq( pcmd, "closed_htmlpage" ) ) { // Does nothing, it's for server plugins to hook. return true; } else if ( FStrEq( pcmd, "condump_on" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( !PlayerHasPowerplay() ) { Msg("Console dumping on.\n"); return true; } else { if ( args.ArgC() == 2 && GetTeam() ) { for ( int i = 0; i < GetTeam()->GetNumPlayers(); i++ ) { CTFPlayer *pTeamPlayer = ToTFPlayer( GetTeam()->GetPlayer(i) ); if ( pTeamPlayer ) { pTeamPlayer->SetPowerplayEnabled( true ); } } return true; } else { if ( SetPowerplayEnabled( true ) ) return true; } } } } else if ( FStrEq( pcmd, "condump_off" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { if ( !PlayerHasPowerplay() ) { Msg("Console dumping off.\n"); return true; } else { if ( args.ArgC() == 2 && GetTeam() ) { for ( int i = 0; i < GetTeam()->GetNumPlayers(); i++ ) { CTFPlayer *pTeamPlayer = ToTFPlayer( GetTeam()->GetPlayer(i) ); if ( pTeamPlayer ) { pTeamPlayer->SetPowerplayEnabled( false ); } } return true; } else { if ( SetPowerplayEnabled( false ) ) return true; } } } } else if ( FStrEq( pcmd, "spec_next" ) ) // chase next player { if ( m_bIsCoaching ) { return true; } // if ( !ShouldRunRateLimitedCommand( args ) ) // return true; // intentionally falling through to the bottom so the baseclass version is called m_bArenaIsAFK = false; } else if ( FStrEq( pcmd, "spec_prev" ) ) // chase prev player { if ( m_bIsCoaching ) { return true; } // if ( !ShouldRunRateLimitedCommand( args ) ) // return true; // intentionally falling through to the bottom so the baseclass version is called m_bArenaIsAFK = false; } else if ( FStrEq( pcmd, "spec_mode" ) ) // set obs mode { // if ( !ShouldRunRateLimitedCommand( args ) ) // return true; // intentionally falling through to the bottom so the baseclass version is called m_bArenaIsAFK = false; } else if ( FStrEq( pcmd, "showroundinfo" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) { // don't let the player open the round info menu until they're a spectator or they're on a regular team and have picked a class if ( ( GetTeamNumber() == TEAM_SPECTATOR ) || ( ( GetTeamNumber() != TEAM_UNASSIGNED ) && ( GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED ) ) ) { if ( TFGameRules() ) { TFGameRules()->ShowRoundInfoPanel( this ); } } } return true; } #ifdef STAGING_ONLY else if ( FStrEq( pcmd, "feigndeath") ) { m_Shared.SetFeignDeathReady( true ); } #endif // STAGING_ONLY else if ( FStrEq( pcmd, "autoteam" ) ) { if ( !IsCoaching() ) { int iTeam = GetAutoTeam(); ChangeTeam( iTeam, true, false ); if ( iTeam > LAST_SHARED_TEAM ) { ShowViewPortPanel( ( iTeam == TF_TEAM_RED ) ? PANEL_CLASS_RED : PANEL_CLASS_BLUE ); } } return true; } else if ( FStrEq( pcmd, "coach_command" ) ) { if ( m_bIsCoaching && m_hStudent && args.ArgC() > 1 ) { eCoachCommand command = (eCoachCommand)atoi( args[1] ); HandleCoachCommand( this, command ); return true; } } else if ( FStrEq( pcmd, "boo" ) && m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) { if ( m_booTimer.IsElapsed() ) { m_booTimer.Start( 1.f ); EmitSound( "Halloween.GhostBoo" ); } return true; } else if ( FStrEq( pcmd, "loot_response" ) ) { // Only allowed to speak these during post-game MvM if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() || !( TFGameRules()->State_Get() == GR_STATE_GAME_OVER ) ) { return true; } if ( FStrEq( args[1], "common" ) ) { SpeakConceptIfAllowed( MP_CONCEPT_MVM_LOOT_COMMON ); return true; } else if ( FStrEq( args[1], "rare" ) ) { SpeakConceptIfAllowed( MP_CONCEPT_MVM_LOOT_RARE ); return true; } else if ( FStrEq( args[1], "ultra_rare" ) ) { SpeakConceptIfAllowed( MP_CONCEPT_MVM_LOOT_ULTRARARE ); return true; } } else if ( FStrEq( pcmd, "done_viewing_loot" ) ) { if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && g_pPopulationManager ) { g_pPopulationManager->PlayerDoneViewingLoot( this ); } return true; } else if ( FStrEq( pcmd, "spectate" ) ) { HandleCommand_JoinTeam( "spectate" ); return true; } else if ( FStrEq( pcmd, "team_ui_setup" ) ) { bool bAutoTeam = ShouldForceAutoTeam(); #ifdef TF_RAID_MODE bAutoTeam |= TFGameRules()->IsBossBattleMode(); #endif // For autoteam, display the appropriate team's CLASS selection ui if ( bAutoTeam ) { ChangeTeam( GetAutoTeam(), true, false ); ShowViewPortPanel( ( GetTeamNumber() == TF_TEAM_BLUE ) ? PANEL_CLASS_BLUE : PANEL_CLASS_RED ); } // Otherwise, show TEAM selection ui else { ShowViewPortPanel( PANEL_TEAM ); } return true; } else if ( FStrEq( "next_map_vote", pcmd ) ) { CTFGameRules::EUserNextMapVote eVoteState = (CTFGameRules::EUserNextMapVote)atoi( args[1] ); switch( eVoteState ) { case CTFGameRules::USER_NEXT_MAP_VOTE_MAP_0: case CTFGameRules::USER_NEXT_MAP_VOTE_MAP_1: case CTFGameRules::USER_NEXT_MAP_VOTE_MAP_2: // Valid break; default: // Invalid Assert( false ); return true; } // No flip flop! if ( TFGameRules()->PlayerNextMapVoteState( entindex() ) != CTFGameRules::USER_NEXT_MAP_VOTE_UNDECIDED ) return true; // Needs to do next-map voting const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); if ( !pMatchDesc || !pMatchDesc->BUsesMapVoteAfterMatchEnds() ) return true; if ( TFGameRules()->State_Get() != GR_STATE_GAME_OVER ) return true; CMatchInfo* pMatch = GTFGCClientSystem()->GetMatch(); if ( !pMatch ) return true; TFGameRules()->SetPlayerNextMapVote( entindex(), eVoteState ); DevMsg( "Settings player %d to rematch vote state %d.\n", entindex(), eVoteState ); return true; } #ifdef STAGING_ONLY else if ( FStrEq( pcmd, "reload_extra_models" ) ) { for ( int i = 0; i < CExtraMapEntity::AutoList().Count(); i++ ) { CExtraMapEntity *pEntity = static_cast( CExtraMapEntity::AutoList()[i] ); UTIL_Remove( pEntity ); } CExtraMapEntity::SpawnExtraModel(); return true; } #endif // STAGING_ONLY return BaseClass::ClientCommand( args ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SetClassMenuOpen( bool bOpen ) { m_bIsClassMenuOpen = bOpen; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsClassMenuOpen( void ) { return m_bIsClassMenuOpen; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::MerasmusPlayerBombExplode( bool bExcludeMe /*= true */ ) { float flDamage = 40.0f; // bomb head damage is 100 only for fighting Merasmus, lower for all other scenarios if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) ) { flDamage = 100.0f; } // explode! Vector vecExplosion = EyePosition(); CPVSFilter filter( vecExplosion ); TE_TFExplosion( filter, 0.0f, vecExplosion, Vector(0,0,1), NULL, entindex() ); int iDmgType = DMG_BLAST | DMG_USEDISTANCEMOD; CTakeDamageInfo info( this, this, NULL, vecExplosion, vecExplosion, flDamage, iDmgType, TF_DMG_CUSTOM_MERASMUS_PLAYER_BOMB, &vecExplosion ); CBaseEntity *pIgnoreEnt = NULL; if ( bExcludeMe ) { pIgnoreEnt = this; } CTFRadiusDamageInfo radiusinfo( &info, vecExplosion, 100.f, pIgnoreEnt ); TFGameRules()->RadiusDamage( radiusinfo ); UTIL_ScreenShake( vecExplosion, 15.0f, 5.0f, 2.f, 750.f, SHAKE_START, true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DropDeathCallingCard( CTFPlayer* pTFAttacker, CTFPlayer* pTFVictim ) { int iCallingCard = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFAttacker, iCallingCard, calling_card_on_kill ); if ( iCallingCard ) { CEffectData data; data.m_vOrigin = pTFVictim->GetAbsOrigin(); data.m_vAngles = pTFVictim->GetAbsAngles(); data.m_nAttachmentIndex = pTFVictim->entindex(); // Victim data.m_nHitBox = entindex(); // iShooter data.m_fFlags = iCallingCard; // Index to the Calling card DispatchEffect( "TFDeathCallingCard", data ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::PlayGesture( const char *pGestureName ) { Activity nActivity = (Activity)LookupActivity( pGestureName ); if ( nActivity != ACT_INVALID ) { DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, nActivity ); return true; } int nSequence = LookupSequence( pGestureName ); if ( nSequence != -1 ) { DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE_SEQUENCE, nSequence ); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::PlaySpecificSequence( const char *pAnimationName ) { Activity nActivity = (Activity)LookupActivity( pAnimationName ); if ( nActivity != ACT_INVALID ) { DoAnimationEvent( PLAYERANIMEVENT_CUSTOM, nActivity ); return true; } int nSequence = LookupSequence( pAnimationName ); if ( nSequence != -1 ) { DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_SEQUENCE, nSequence ); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DetonateObjectOfType( int iType, int iMode, bool bIgnoreSapperState ) { CBaseObject *pObj = GetObjectOfType( iType, iMode ); if( !pObj ) return; if( !bIgnoreSapperState && ( pObj->HasSapper() || pObj->IsPlasmaDisabled() ) ) return; IGameEvent *event = gameeventmanager->CreateEvent( "object_removed" ); if ( event ) { event->SetInt( "userid", GetUserID() ); // user ID of the object owner event->SetInt( "objecttype", iType ); // type of object removed event->SetInt( "index", pObj->entindex() ); // index of the object removed gameeventmanager->FireEvent( event ); } if ( TFGameRules() && TFGameRules()->GetTrainingModeLogic() && IsFakeClient() == false ) { TFGameRules()->GetTrainingModeLogic()->OnPlayerDetonateBuilding( this, pObj ); } SpeakConceptIfAllowed( MP_CONCEPT_DETONATED_OBJECT, pObj->GetResponseRulesModifier() ); pObj->DetonateObject(); const CObjectInfo *pInfo = GetObjectInfo( iType ); if ( pInfo ) { UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"killedobject\" (object \"%s\") (weapon \"%s\") (objectowner \"%s<%i><%s><%s>\") (attacker_position \"%d %d %d\")\n", GetPlayerName(), GetUserID(), GetNetworkIDString(), GetTeam()->GetName(), pInfo->m_pObjectName, "pda_engineer", GetPlayerName(), GetUserID(), GetNetworkIDString(), GetTeam()->GetName(), (int)GetAbsOrigin().x, (int)GetAbsOrigin().y, (int)GetAbsOrigin().z ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetObjectBuildSpeedMultiplier( int iObjectType, bool bIsRedeploy ) const { float flBuildRate = 1.f; // need a base value for mult switch( iObjectType ) { case OBJ_SENTRYGUN: CALL_ATTRIB_HOOK_FLOAT( flBuildRate, sentry_build_rate_multiplier ); flBuildRate += bIsRedeploy ? 2.0 : 0.0f; break; case OBJ_TELEPORTER: CALL_ATTRIB_HOOK_FLOAT( flBuildRate, teleporter_build_rate_multiplier ); flBuildRate += bIsRedeploy ? 3.0 : 0.0f; break; case OBJ_DISPENSER: CALL_ATTRIB_HOOK_FLOAT( flBuildRate, teleporter_build_rate_multiplier ); flBuildRate += bIsRedeploy ? 3.0 : 0.0f; break; #ifdef STAGING_ONLY // STAGING_ENGY case OBJ_CATAPULT: flBuildRate += 5.0f; flBuildRate += bIsRedeploy ? 3.0 : 0.0f; break; #endif } return flBuildRate - 1.0f; // sub out the initial 1 so the final result is added } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { if ( m_takedamage != DAMAGE_YES ) return; CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); if ( pAttacker ) { // Weapons that use uber ammo can transfer that uber into other medics if ( pAttacker->IsPlayerClass( TF_CLASS_MEDIC ) && IsPlayerClass( TF_CLASS_MEDIC ) ) { CTFWeaponBase *pWep = pAttacker->GetActiveTFWeapon(); if ( pWep ) { float flUberTransfer = pWep->UberChargeAmmoPerShot(); if ( flUberTransfer > 0.0f ) { float flTransferPercent = 0.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWep, flTransferPercent, ubercharge_transfer ); if ( flTransferPercent ) { flUberTransfer *= ( flTransferPercent * 0.01f ); CWeaponMedigun *pMedigun = static_cast< CWeaponMedigun * >( Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) ); if ( pMedigun ) { pMedigun->AddCharge( flUberTransfer ); } } } } } // Prevent team damage here so blood doesn't appear if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker, info ) ) { return; } } // Save this bone for the ragdoll. m_nForceBone = ptr->physicsbone; SetLastHitGroup( ptr->hitgroup ); // Ignore hitboxes for all weapons except the sniper rifle CTakeDamageInfo info_modified = info; bool bIsHeadshot = false; if ( info_modified.GetDamageType() & DMG_USE_HITLOCATIONS ) { if ( !m_Shared.InCond( TF_COND_INVULNERABLE ) && ptr->hitgroup == HITGROUP_HEAD ) { CTFWeaponBase *pWpn = pAttacker->GetActiveTFWeapon(); bool bCritical = true; bIsHeadshot = true; if ( pWpn && !pWpn->CanFireCriticalShot( true ) ) { bCritical = false; } int iBackheadshot = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetInflictor(), iBackheadshot, back_headshot ); if ( iBackheadshot ) { // only allow if hit in the back of the head Vector entForward; AngleVectors( EyeAngles(), &entForward ); Vector toEnt = GetAbsOrigin() - pAttacker->GetAbsOrigin(); toEnt.NormalizeInPlace(); // did not backshot //if ( DotProduct( toEnt, entForward ) <= 0.7071f ) // 0.7 os 45 degress from center if ( DotProduct( toEnt, entForward ) < 0.5f ) // 60 degrees from center (total of 120) { bCritical = false; bIsHeadshot = false; } } // Check for headshot damage modifiers float flHeadshotModifier = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER ( pAttacker, flHeadshotModifier, headshot_damage_modify); info_modified.ScaleDamage(flHeadshotModifier); if ( bCritical ) { info_modified.AddDamageType( DMG_CRITICAL ); int iDecapType = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER ( pAttacker, iDecapType, decapitate_type); if ( iDecapType > 0 ) { info_modified.SetDamageCustom( TF_DMG_CUSTOM_HEADSHOT_DECAPITATION ); } else { info_modified.SetDamageCustom( TF_DMG_CUSTOM_HEADSHOT ); } // play the critical shot sound to the shooter if ( pWpn ) { pWpn->WeaponSound( BURST ); } } } } if ( !bIsHeadshot && pAttacker ) { // Check for bodyshot damage modifiers CTFWeaponBase *pWpn = pAttacker->GetActiveTFWeapon(); float flBodyshotModifier = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER ( pWpn, flBodyshotModifier, bodyshot_damage_modify); info_modified.ScaleDamage( flBodyshotModifier ); } if ( GetTeamNumber() == TF_TEAM_BLUE ) { info_modified.SetDamage( info_modified.GetDamage() * tf_damage_multiplier_blue.GetFloat() ); } else if ( GetTeamNumber() == TF_TEAM_RED ) { info_modified.SetDamage( info_modified.GetDamage() * tf_damage_multiplier_red.GetFloat() ); } if ( m_Shared.InCond( TF_COND_DISGUISED ) ) { // no impact effects } else if ( m_Shared.IsInvulnerable() ) { // Make bullet impacts g_pEffects->Ricochet( ptr->endpos - (vecDir * 8), -vecDir ); } else { // Since this code only runs on the server, make sure it shows the tempents it creates. CDisablePredictionFiltering disabler; // This does smaller splotches on the guy and splats blood on the world. TraceBleed( info_modified.GetDamage(), vecDir, ptr, info_modified.GetDamageType() ); } AddMultiDamage( info_modified, this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::TakeHealth( float flHealth, int bitsDamageType ) { if ( m_Shared.InCond( TF_COND_NOHEALINGDAMAGEBUFF ) ) { return 0; // No healing while in this state! } int nResult = 0; CTFWeaponBase *pWeapon = GetActiveTFWeapon(); if ( pWeapon ) { float flHealingBonus = 1.f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flHealingBonus, mult_healing_received ); flHealth *= flHealingBonus; } // Medigun healing and player/class regen use an accumulator, so they've already factored in debuffs. // if ( m_Shared.InCond( TF_COND_HEALING_DEBUFF ) && !( bitsDamageType & DMG_IGNORE_DEBUFFS ) ) // { // flHealth *= 0.75f; // } // If the bit's set, add over the max health if ( bitsDamageType & DMG_IGNORE_MAXHEALTH ) { int iTimeBasedDamage = g_pGameRules->Damage_GetTimeBased(); m_bitsDamageType &= ~(bitsDamageType & ~iTimeBasedDamage); m_iHealth += flHealth; nResult = flHealth; } else { float flHealthToAdd = flHealth; float flMaxHealth = GetMaxHealth(); // don't want to add more than we're allowed to have if ( flHealthToAdd > flMaxHealth - m_iHealth ) { flHealthToAdd = flMaxHealth - m_iHealth; } if ( flHealthToAdd <= 0 ) { nResult = 0; } else { nResult = BaseClass::TakeHealth( flHealthToAdd, bitsDamageType ); } } return nResult; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::TFWeaponRemove( int iWeaponID ) { // find the weapon that matches the id and remove it int i; for (i = 0; i < WeaponCount(); i++) { CTFWeaponBase *pWeapon = ( CTFWeaponBase *)GetWeapon( i ); if ( !pWeapon ) continue; if ( pWeapon->GetWeaponID() != iWeaponID ) continue; RemovePlayerItem( pWeapon ); UTIL_Remove( pWeapon ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) { CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); // Can I have this weapon type? if ( !IsAllowedToPickupWeapons() ) return false; if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) { UTIL_Remove( pWeapon ); return false; } // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) if ( !pWeapon->FVisible( this, MASK_SOLID ) ) return false; // ---------------------------------------- // If I already have it just take the ammo // ---------------------------------------- if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) { UTIL_Remove( pWeapon ); return true; } else { // ------------------------- // Otherwise take the weapon // ------------------------- pWeapon->CheckRespawn(); pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); pWeapon->AddEffects( EF_NODRAW ); Weapon_Equip( pWeapon ); return true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::DropCurrentWeapon( void ) { return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DropFlag( bool bSilent /* = false */ ) { if ( HasItem() ) { CCaptureFlag *pFlag = dynamic_cast( GetItem() ); if ( pFlag ) { int nFlagTeamNumber = pFlag->GetTeamNumber(); pFlag->Drop( this, true, true, !bSilent ); IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); if ( event ) { event->SetInt( "player", entindex() ); event->SetInt( "eventtype", TF_FLAGEVENT_DROPPED ); event->SetInt( "priority", 8 ); event->SetInt( "team", nFlagTeamNumber ); gameeventmanager->FireEvent( event ); } } } } //----------------------------------------------------------------------------- // Purpose: Players can drop Powerup Runes //----------------------------------------------------------------------------- void CTFPlayer::DropRune( bool bApplyForce /* = true */, int nTeam /* = TEAM_ANY */ ) { if ( m_Shared.IsCarryingRune() ) { Vector forward; EyeVectors( &forward ); RuneTypes_t nRuneType = m_Shared.GetCarryingRuneType(); // We expect that we are actually are carrying here, so assert that we are. Assert( nRuneType >= 0 && nRuneType < RUNE_TYPES_MAX ); m_Shared.SetCarryingRuneType( RUNE_NONE ); bool bShouldRemoveMeleeOnly = !( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && m_Shared.InCond( TF_COND_ENERGY_BUFF ) ); if ( bShouldRemoveMeleeOnly ) { m_Shared.RemoveCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ); // Knockout powerup sets this to on } TeamFortress_SetSpeed(); // Need to call this or speed bonus isn't removed immediately CTFRune::CreateRune( GetAbsOrigin(), nRuneType, nTeam, true, bApplyForce, forward ); // Manually dropped powerups are always neutral } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- EHANDLE CTFPlayer::TeamFortress_GetDisguiseTarget( int nTeam, int nClass ) { if ( /*nTeam == GetTeamNumber() ||*/ nTeam == TF_SPY_UNDEFINED ) { // we're not disguised as the enemy team return NULL; } CUtlVector potentialTargets; CBaseEntity *pLastTarget = m_Shared.GetDisguiseTarget(); // don't redisguise self as this person // Find a player on the team the spy is disguised as to pretend to be CTFPlayer *pPlayer = NULL; // Loop through players and attempt to find a player as the team/class we're disguising as int i; for ( i = 1; i <= gpGlobals->maxClients; i++ ) { pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer && ( pPlayer != pLastTarget ) ) { // First, try to find a player with the same color AND skin if ( ( pPlayer->GetTeamNumber() == nTeam ) && ( pPlayer->GetPlayerClass()->GetClassIndex() == nClass ) ) { potentialTargets.AddToHead( i ); } } } // do we have any potential targets in the list? if ( potentialTargets.Count() > 0 ) { int iIndex = random->RandomInt( 0, potentialTargets.Count() - 1 ); return UTIL_PlayerByIndex( potentialTargets[iIndex] ); } // we didn't find someone with the class, so just find someone with the same team color for ( i = 1; i <= gpGlobals->maxClients; i++ ) { pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer && ( pPlayer->GetTeamNumber() == nTeam ) ) { potentialTargets.AddToHead( i ); } } if ( potentialTargets.Count() > 0 ) { int iIndex = random->RandomInt( 0, potentialTargets.Count() - 1 ); return UTIL_PlayerByIndex( potentialTargets[iIndex] ); } // we didn't find anyone return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float DamageForce( const Vector &size, float damage, float scale ) { float force = damage * ((48 * 48 * 82.0) / (size.x * size.y * size.z)) * scale; if ( force > 1000.0 ) { force = 1000.0; } return force; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SetBlastJumpState( int iState, bool bPlaySound /*= false*/ ) { m_iBlastJumpState |= iState; const char *pszEvent = NULL; if ( iState == TF_PLAYER_STICKY_JUMPED ) { pszEvent = "sticky_jump"; } else if ( iState == TF_PLAYER_ROCKET_JUMPED ) { pszEvent = "rocket_jump"; } if ( pszEvent ) { IGameEvent * event = gameeventmanager->CreateEvent( pszEvent ); if ( event ) { event->SetInt( "userid", GetUserID() ); event->SetBool( "playsound", bPlaySound ); gameeventmanager->FireEvent( event ); } } m_Shared.AddCond( TF_COND_BLASTJUMPING ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClearBlastJumpState( void ) { m_bCreatedRocketJumpParticles = false; m_iBlastJumpState = 0; m_flBlastJumpLandTime = gpGlobals->curtime; m_Shared.RemoveCond( TF_COND_BLASTJUMPING ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void HandleRageGain( CTFPlayer *pPlayer, unsigned int iRequiredBuffFlags, float flDamage, float fInverseRageGainScale ) { if ( !pPlayer ) return; if ( pPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) ) { CTFBuffItem *pBuffItem = dynamic_cast( pPlayer->Weapon_OwnsThisID( TF_WEAPON_BUFF_ITEM ) ); unsigned int iBuffId = pBuffItem ? pBuffItem->GetBuffType() : 0; if ( iBuffId < ARRAYSIZE( g_RageBuffTypes ) ) { if ( g_RageBuffTypes[iBuffId].m_iBuffFlags & iRequiredBuffFlags ) { pPlayer->m_Shared.ModifyRage( g_RageBuffTypes[iBuffId].m_fRageScale * ( flDamage / fInverseRageGainScale ) ); } } } else if ( pPlayer->IsPlayerClass( TF_CLASS_PYRO ) ) { CTFFlameThrower *pFlameThrower = dynamic_cast( pPlayer->Weapon_OwnsThisID( TF_WEAPON_FLAMETHROWER ) ); unsigned int iBuffId = pFlameThrower ? pFlameThrower->GetBuffType() : 0; if ( iBuffId < ARRAYSIZE( g_RageBuffTypes ) ) { if ( g_RageBuffTypes[iBuffId].m_iBuffFlags & iRequiredBuffFlags ) { if ( TFGameRules() && TFGameRules()->IsPowerupMode() && pPlayer->m_Shared.GetCarryingRuneType() != RUNE_NONE ) { pPlayer->m_Shared.ModifyRage(g_RageBuffTypes[iBuffId].m_fRageScale * ( ( flDamage / 10 ) / fInverseRageGainScale) ); } else { pPlayer->m_Shared.ModifyRage( g_RageBuffTypes[iBuffId].m_fRageScale * ( flDamage / fInverseRageGainScale ) ); } } } } // General int iRage = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iRage, generate_rage_on_dmg ); if ( iRage ) { if ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) && ( kRageBuffFlag_OnDamageDealt & iRequiredBuffFlags ) ) { pPlayer->m_Shared.ModifyRage( flDamage ); } else if ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && ( kRageBuffFlag_OnDamageDealt & iRequiredBuffFlags ) ) { pPlayer->m_Shared.ModifyRage( 0.22f * ( flDamage / fInverseRageGainScale ) ); } } int iHealRage = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iHealRage, generate_rage_on_heal ); // ...lol if ( iHealRage ) { if ( pPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && ( kRageBuffFlag_OnHeal & iRequiredBuffFlags ) ) { pPlayer->m_Shared.ModifyRage( 0.25f * flDamage ); } } #ifdef STAGING_ONLY if ( TFGameRules()->GameModeUsesUpgrades() && pPlayer->IsPlayerClass( TF_CLASS_SPY ) && ( kRageBuffFlag_OnDamageDealt & iRequiredBuffFlags ) ) { // Don't allow when radius cloak is in effect if ( !pPlayer->m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF ) ) { pPlayer->m_Shared.ModifyRage( 0.025f * flDamage ); } } #endif // STAGING_ONLY } // we want to ship this...do not remove ConVar tf_debug_damage( "tf_debug_damage", "0", FCVAR_CHEAT ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo ) { CTakeDamageInfo info = inputInfo; bool bIsObject = info.GetInflictor() && info.GetInflictor()->IsBaseObject(); // need to check this now, before dying bool bHadBallBeforeDamage = false; if ( TFGameRules() && TFGameRules()->IsPasstimeMode() ) { bHadBallBeforeDamage = m_Shared.HasPasstimeBall(); } // damage may not come from a weapon (ie: Bosses, etc) // The existing code below already checked for NULL pWeapon, anyways CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( inputInfo.GetWeapon() ); if ( GetFlags() & FL_GODMODE ) return 0; if ( IsInCommentaryMode() ) return 0; bool bBuddha = ( m_debugOverlays & OVERLAY_BUDDHA_MODE ) ? true : false; #if defined( _DEBUG ) || defined( STAGING_ONLY ) if ( mp_developer.GetInt() > 1 && !IsBot() ) bBuddha = true; #endif // _DEBUG || STAGING_ONLY if ( bBuddha ) { if ( ( m_iHealth - info.GetDamage() ) <= 0 ) { m_iHealth = 1; return 0; } } if ( !IsAlive() ) return 0; // Early out if there's no damage if ( !info.GetDamage() ) return 0; // Ghosts dont take damage if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) { return 0; } CBaseEntity *pAttacker = info.GetAttacker(); CTFPlayer *pTFAttacker = ToTFPlayer( pAttacker ); bool bDebug = tf_debug_damage.GetBool(); // If attacker has Strength Powerup Rune, apply damage multiplier, but not if you're a building if ( !bIsObject && pTFAttacker && pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_STRENGTH ) { info.ScaleDamage( 2.f ); } // Make sure the player can take damage from the attacking entity if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker, info ) ) { if ( bDebug ) { Warning( " ABORTED: Player can't take damage from that attacker.\n" ); } return 0; } if ( IsBot() ) { // Don't let Sentry Busters die until they've done their spin-up if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { CTFBot *bot = ToTFBot( this ); if ( bot ) { if ( bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) ) { if ( ( m_iHealth - info.GetDamage() ) <= 0 ) { m_iHealth = 1; return 0; } } // Sentry Busters hurt teammates when they explode. // Force damage value when the victim is a giant. if ( pTFAttacker && pTFAttacker->IsBot() ) { CTFBot *pTFAttackerBot = ToTFBot( pTFAttacker ); if ( pTFAttackerBot && ( pTFAttackerBot != this ) && pTFAttackerBot->GetPrevMission() == CTFBot::MISSION_DESTROY_SENTRIES && info.IsForceFriendlyFire() && InSameTeam( pTFAttackerBot ) && IsMiniBoss() ) { info.SetDamage( 600.f ); } } } } } // Halloween 2011 if ( IsInPurgatory() ) { info.SetDamage( m_purgatoryPainMultiplier * info.GetDamage() ); } m_iHealthBefore = GetHealth(); bool bIsSoldierRocketJumping = ( IsPlayerClass( TF_CLASS_SOLDIER ) && (pAttacker == this) && !(GetFlags() & FL_ONGROUND) && !(GetFlags() & FL_INWATER)) && (inputInfo.GetDamageType() & DMG_BLAST); bool bIsDemomanPipeJumping = ( IsPlayerClass( TF_CLASS_DEMOMAN) && (pAttacker == this) && !(GetFlags() & FL_ONGROUND) && !(GetFlags() & FL_INWATER)) && (inputInfo.GetDamageType() & DMG_BLAST); if ( bDebug ) { Warning( "%s taking damage from %s, via %s. Damage: %.2f\n", GetDebugName(), info.GetInflictor() ? info.GetInflictor()->GetDebugName() : "Unknown Inflictor", pAttacker ? pAttacker->GetDebugName() : "Unknown Attacker", info.GetDamage() ); } if ( pTFAttacker ) { pTFAttacker->SetLastEntityDamagedTime( gpGlobals->curtime ); pTFAttacker->SetLastEntityDamaged( this ); CTFWeaponBase *myWeapon = GetActiveTFWeapon(); CTFWeaponBase *attackerWeapon = pTFAttacker->GetActiveTFWeapon(); if ( myWeapon && attackerWeapon ) { int iStunEnemyWithSameWeapon = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( attackerWeapon, iStunEnemyWithSameWeapon, stun_enemies_wielding_same_weapon ); if ( iStunEnemyWithSameWeapon ) { CEconItemView *myItem = myWeapon->GetAttributeContainer()->GetItem(); CEconItemView *attackerItem = attackerWeapon->GetAttributeContainer()->GetItem(); if ( myItem && attackerItem && myItem->GetItemDefIndex() == attackerItem->GetItemDefIndex() ) { // we're both wielding the same weapon - stun! m_Shared.StunPlayer( 1.0f, 1.0, TF_STUN_BOTH | TF_STUN_NO_EFFECTS ); } } } } if ( ( info.GetDamageType() & DMG_FALL ) ) { bool bHitEnemy = false; // Are we transferring falling damage to someone else? // Space Gravity gives everyone manntreads effect. Mantreads just makes it higher int iHeadStomp = 0; CALL_ATTRIB_HOOK_INT( iHeadStomp, boots_falling_stomp ); //#ifdef STAGING_ONLY // if ( ( iHeadStomp || m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) && //#else if ( iHeadStomp && //#endif // STAGING_ONLY GetGroundEntity() && GetGroundEntity()->IsPlayer() ) { // Did we land on a guy from the enemy team? CTFPlayer *pOther = ToTFPlayer( GetGroundEntity() ); if ( pOther && pOther->GetTeamNumber() != GetTeamNumber() ) { float flStompDamage = info.GetDamage(); if ( iHeadStomp ) { flStompDamage = 10.0f + flStompDamage * 3.0f; } CTakeDamageInfo infoInner( this, this, GetEquippedWearableForLoadoutSlot( LOADOUT_POSITION_SECONDARY ), flStompDamage, DMG_FALL, TF_DMG_CUSTOM_BOOTS_STOMP ); pOther->TakeDamage( infoInner ); m_Local.m_flFallVelocity = 0; info.SetDamage( 0.0f ); EmitSound( "Weapon_Mantreads.Impact" ); UTIL_ScreenShake( pOther->WorldSpaceCenter(), 15.0, 150.0, 1.0, 500, SHAKE_START ); bHitEnemy = true; } } #ifdef STAGING_ONLY /* // no fall damage in space if ( m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) { info.SetDamage( 0.0f ); } */ // Apply an impact stun (intensity determined by fall damage for now) if ( TFGameRules()->GameModeUsesUpgrades() && m_Shared.InCond( TF_COND_ROCKETPACK ) ) { float flStunTime = RemapValClamped( info.GetDamage(), 0.1f, 50.f, 1.f, 3.f ); m_Shared.ApplyRocketPackStun( bHitEnemy ? 5.f : flStunTime ); info.SetDamage( 0.f ); m_Local.m_flFallVelocity = 0.f; } #endif // STAGING_ONLY } // Ignore damagers on our team, to prevent capturing rocket jumping, etc. if ( pAttacker && pAttacker->GetTeam() != GetTeam() ) { m_AchievementData.AddDamagerToHistory( pAttacker ); if ( pAttacker->IsPlayer() ) { ToTFPlayer( pAttacker )->m_AchievementData.AddTargetToHistory( this ); // add to list of damagers via sentry so that later we can check for achievement: ACHIEVEMENT_TF_ENGINEER_SHOTGUN_KILL_PREV_SENTRY_TARGET CBaseEntity *pInflictor = info.GetInflictor(); CObjectSentrygun *pSentry = dynamic_cast< CObjectSentrygun * >( pInflictor ); if ( pSentry ) { m_AchievementData.AddSentryDamager( pAttacker, pInflictor ); } } } // keep track of amount of damage last sustained m_lastDamageAmount = info.GetDamage(); m_LastDamageType = info.GetDamageType(); if ( m_LastDamageType & DMG_FALL ) { if ( ( m_lastDamageAmount > m_iLeftGroundHealth ) && ( m_lastDamageAmount < GetHealth() ) ) { // we gained health in the air, and it saved us from death. // if any medics are healing us, they get an achievement int iNumHealers = m_Shared.GetNumHealers(); for ( int i=0;iIsPlayerClass( TF_CLASS_MEDIC ) ) { pMedic->AwardAchievement( ACHIEVEMENT_TF_MEDIC_SAVE_FALLING_TEAMMATE ); } } } } // Check for Demo Achievement: // Kill a Heavy from full health with one detonation if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { if ( pTFAttacker && pTFAttacker->IsPlayerClass( TF_CLASS_DEMOMAN ) ) { if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_PIPEBOMBLAUNCHER ) { // We're at full health if ( m_iHealthBefore >= GetMaxHealth() ) { // Record the time m_fMaxHealthTime = gpGlobals->curtime; } // If we're still being hit in the same time window if ( m_fMaxHealthTime == gpGlobals->curtime ) { // Check if the damage is fatal int iDamage = info.GetDamage(); if ( m_iHealth - iDamage <= 0 ) { pTFAttacker->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_X_HEAVIES_FULLHP_ONEDET ); } } } } } if ( bIsSoldierRocketJumping || bIsDemomanPipeJumping ) { int nJumpType = 0; // If this is our own rocket, scale down the damage if we're rocket jumping if ( bIsSoldierRocketJumping ) { float flDamage = info.GetDamage() * tf_damagescale_self_soldier.GetFloat(); info.SetDamage( flDamage ); if ( m_iHealthBefore - flDamage > 0 ) { nJumpType = TF_PLAYER_ROCKET_JUMPED; } } else if ( bIsDemomanPipeJumping ) { nJumpType = TF_PLAYER_STICKY_JUMPED; } if ( nJumpType ) { bool bPlaySound = false; if ( pWeapon ) { int iNoBlastDamage = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iNoBlastDamage, no_self_blast_dmg ) bPlaySound = iNoBlastDamage ? true : false; } SetBlastJumpState( nJumpType, bPlaySound ); } } if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { // can only bounce invaders when they are on the ground if ( GetGroundEntity() == NULL ) { info.SetDamageForce( vec3_origin ); } } // Save damage force for ragdolls. m_vecTotalBulletForce = info.GetDamageForce(); m_vecTotalBulletForce.x = clamp( m_vecTotalBulletForce.x, -15000.0f, 15000.0f ); m_vecTotalBulletForce.y = clamp( m_vecTotalBulletForce.y, -15000.0f, 15000.0f ); m_vecTotalBulletForce.z = clamp( m_vecTotalBulletForce.z, -15000.0f, 15000.0f ); int bTookDamage = 0; int bitsDamage = inputInfo.GetDamageType(); bool bAllowDamage = false; // check to see if our attacker is a trigger_hurt entity (and allow it to kill us even if we're invuln) if ( pAttacker && pAttacker->IsSolidFlagSet( FSOLID_TRIGGER ) ) { CTriggerHurt *pTrigger = dynamic_cast( pAttacker ); if ( pTrigger ) { bAllowDamage = true; info.SetDamageCustom( TF_DMG_CUSTOM_TRIGGER_HURT ); } } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TELEFRAG ) { bAllowDamage = true; } if ( !TFGameRules()->ApplyOnDamageModifyRules( info, this, bAllowDamage ) ) { return 0; } // If player has Reflect Powerup, reflect damage to attacker. // We do this here, after damage modify rules to ensure distance falloff calculations have already been made before we pass that damage back to the attacker if ( pTFAttacker && m_Shared.GetCarryingRuneType() == RUNE_REFLECT && pTFAttacker != this && !pTFAttacker->m_Shared.IsInvulnerable() && pTFAttacker->IsAlive() ) { CTakeDamageInfo dmg = info; CTFProjectile_SentryRocket *sentryRocket = dynamic_cast( info.GetInflictor() ); if ( gpGlobals->curtime > m_flNextReflectZap ) // don't spam the effect for fast weapons like flamethrower and minigun { m_flNextReflectZap = gpGlobals->curtime + 0.5f; CPVSFilter filter( WorldSpaceCenter() ); Vector vEnd = pTFAttacker->WorldSpaceCenter(); Vector vStart = WorldSpaceCenter(); if ( bIsObject || sentryRocket ) { CBaseEntity *pInflictor = info.GetInflictor(); vEnd = pInflictor->WorldSpaceCenter(); } else { // Push the attacker away from the Reflect powerup holder Vector toPlayer = vEnd - vStart; toPlayer.z = 0.0f; toPlayer.NormalizeInPlace(); toPlayer.z = 1.0f; float flDamage = dmg.GetDamage(); if ( dmg.GetDamageCustom() != TF_DMG_CUSTOM_BURNING ) { float flPushForce = RemapValClamped( flDamage, 0.1f, 150.f, 300.f, 500.f ); // Scale the push force according to damage Vector vPush = flPushForce * toPlayer; pTFAttacker->ApplyAbsVelocityImpulse( vPush ); } // Play a sound and reduce the volume if damage is low CSoundParameters params; if ( CBaseEntity::GetParametersForSound( "Powerup.Reflect.Reflect", params, NULL ) ) { CPASAttenuationFilter soundFilter( pTFAttacker->GetAbsOrigin(), params.soundlevel ); EmitSound_t ep( params ); if ( flDamage < 10.f ) { ep.m_flVolume *= 0.75f; } pTFAttacker->EmitSound( soundFilter, entindex(), ep ); pTFAttacker->PainSound( dmg ); } } te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd }; TE_TFParticleEffectComplex( filter, 0.f, "dxhr_arm_muzzleflash", vStart, QAngle( 0.f, 0.f, 0.f ), NULL, &controlPoint, pTFAttacker, PATTACH_CUSTOMORIGIN ); } dmg.SetDamageCustom( TF_DMG_CUSTOM_RUNE_REFLECT ); dmg.SetDamageType( DMG_SHOCK ); dmg.SetAttacker( this ); if ( bIsObject ) { CBaseEntity *pInflictor = info.GetInflictor(); dmg.SetDamage( info.GetDamage() ); pInflictor->TakeDamage( dmg ); } // Sentry rockets are not included in bIsobject so we deal with them separately else { if ( sentryRocket ) { dmg.SetDamage( info.GetDamage() ); info.GetInflictor()->GetOwnerEntity()->TakeDamage( dmg ); } else { // Take damage unless you have Resist or Vampire (they are immune to reflect damage) if ( pTFAttacker->m_Shared.GetCarryingRuneType() != RUNE_RESIST && pTFAttacker->m_Shared.GetCarryingRuneType() != RUNE_VAMPIRE ) { dmg.SetDamage(info.GetDamage() * 0.8f); pTFAttacker->TakeDamage( dmg ); } } } } //Don't take damage while I'm phasing. if ( ( m_Shared.InCond( TF_COND_PHASE ) || m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) && bAllowDamage == false ) { SpeakConceptIfAllowed( MP_CONCEPT_DODGE_SHOT ); if ( pAttacker && pAttacker->IsPlayer() ) { CEffectData data; data.m_nHitBox = GetParticleSystemIndex( "miss_text" ); data.m_vOrigin = WorldSpaceCenter() + Vector(0,0,32); data.m_vAngles = vec3_angle; data.m_nEntIndex = 0; CSingleUserRecipientFilter filter( (CBasePlayer*)pAttacker ); te->DispatchEffect( filter, 0.0, data.m_vOrigin, "ParticleEffect", data ); } Vector vecDir = vec3_origin; if ( info.GetInflictor() ) { vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0.0f, 0.0f, 10.0f ) - WorldSpaceCenter(); VectorNormalize( vecDir ); } ApplyPushFromDamage( info, vecDir ); if ( m_Shared.InCond( TF_COND_PHASE ) ) { m_Shared.m_ConditionData[ TF_COND_PHASE ].m_nPreventedDamageFromCondition += info.GetDamage(); m_Shared.m_iPhaseDamage += info.GetDamage(); } bTookDamage = false; } else { bool bFatal = ( m_iHealth - info.GetDamage() ) <= 0; bool bTrackEvent = pTFAttacker && pTFAttacker != this && !pTFAttacker->IsBot() && !IsBot(); if ( bTrackEvent ) { float flHealthRemoved = bFatal ? m_iHealth : info.GetDamage(); if ( info.GetDamageBonus() && info.GetDamageBonusProvider() ) { // Don't deal with raw damage numbers, only health removed. // Example based on a crit rocket to a player with 120 hp: // Actual damage is 120, but potential damage is 300, where // 100 is the base, and 200 is the bonus. Apply this ratio // to actual (so, attacker did 40, and provider added 80). float flBonusMult = info.GetDamage() / abs( info.GetDamageBonus() - info.GetDamage() ); float flBonus = flHealthRemoved - ( flHealthRemoved / flBonusMult ); m_AchievementData.AddDamageEventToHistory( info.GetDamageBonusProvider(), flBonus ); flHealthRemoved -= flBonus; } m_AchievementData.AddDamageEventToHistory( pAttacker, flHealthRemoved ); } // This should kill us if ( bFatal ) { // Damage could have been modified since we started // Try to prevent death with buddha one more time if ( bBuddha ) { m_iHealth = 1; return 0; } // Check to see if we have the cheat death attribute that makes // us teleport to base rather than die float flCheatDeathChance = 0.f; CALL_ATTRIB_HOOK_FLOAT( flCheatDeathChance, teleport_instead_of_die ); if( RandomFloat() < flCheatDeathChance ) { // Send back to base ForceRespawn(); m_iHealth = 1; return 0; } // Avoid one death if ( m_Shared.InCond( TF_COND_PREVENT_DEATH ) ) { m_Shared.RemoveCond( TF_COND_PREVENT_DEATH ); m_iHealth = 1; return 0; } // Powerup-sourced reflected damage should not kill player if ( info.GetDamageCustom() == TF_DMG_CUSTOM_RUNE_REFLECT ) { m_iHealth = 1; return 0; } } // NOTE: Deliberately skip base player OnTakeDamage, because we don't want all the stuff it does re: suit voice bTookDamage = CBaseCombatCharacter::OnTakeDamage( info ); // Early out if the base class took no damage if ( !bTookDamage ) { if ( bDebug ) { Warning( " ABORTED: Player failed to take the damage.\n" ); } return 0; } // Check to see if we need to pass along the damage to other players if ( pWeapon && ( gs_pRecursivePlayerCheck == NULL ) ) { int iDamageAllConnected = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iDamageAllConnected, damage_all_connected ); if ( iDamageAllConnected > 0 ) { // Am I healing someone or being healed? CUtlVector pTempPlayerQueue; AddConnectedPlayers( pTempPlayerQueue, this ); gs_pRecursivePlayerCheck = this; for ( int iCount = 0 ; iCount < pTempPlayerQueue.Count() ; iCount++ ) { CTFPlayer *pTFPlayer = pTempPlayerQueue[iCount]; if ( pTFPlayer && ( pTFPlayer != this ) ) { pTFPlayer->TakeDamage( inputInfo ); } } gs_pRecursivePlayerCheck = NULL; } } } if ( bTookDamage == false ) return 0; if ( bDebug ) { Warning( " DEALT: Player took %.2f damage.\n", info.GetDamage() ); Warning( " HEALTH LEFT: %d\n", GetHealth() ); } // Some weapons have the ability to impart extra moment just because they feel like it. Let their attributes // do so if they're in the mood. if ( pWeapon != NULL ) { float flZScale = 0.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flZScale, apply_z_velocity_on_damage ); if ( flZScale != 0.0f ) { ApplyAbsVelocityImpulse( Vector( 0.0f, 0.0f, flZScale ) ); } float flDirScale = 0.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDirScale, apply_look_velocity_on_damage ); if ( flDirScale != 0.0f && pAttacker != NULL ) { Vector vecForward; AngleVectors( pAttacker->EyeAngles(), &vecForward ); Vector vecForwardNoDownward = Vector( vecForward.x, vecForward.y, MIN( 0.0f, vecForward.z ) ).Normalized(); ApplyAbsVelocityImpulse( vecForwardNoDownward * flDirScale ); } } // let weapons react to their owner being injured CTFWeaponBase *pMyWeapon = GetActiveTFWeapon(); if ( pMyWeapon ) { pMyWeapon->ApplyOnInjuredAttributes( this, pTFAttacker, info ); } // Send the damage message to the client for the hud damage indicator // Try and figure out where the damage is coming from Vector vecDamageOrigin = info.GetReportedPosition(); // If we didn't get an origin to use, try using the attacker's origin if ( vecDamageOrigin == vec3_origin && info.GetInflictor() ) { vecDamageOrigin = info.GetInflictor()->GetAbsOrigin(); } // Tell the player's client that he's been hurt. if ( m_iHealthBefore != GetHealth() ) { CSingleUserRecipientFilter user( this ); UserMessageBegin( user, "Damage" ); WRITE_SHORT( clamp( (int)info.GetDamage(), 0, 32000 ) ); WRITE_LONG( info.GetDamageType() ); // Tell the client whether they should show it in the indicator if ( bitsDamage != DMG_GENERIC && !(bitsDamage & (DMG_DROWN | DMG_FALL | DMG_BURN) ) ) { WRITE_BOOL( true ); WRITE_VEC3COORD( vecDamageOrigin ); } else { WRITE_BOOL( false ); } MessageEnd(); } // add to the damage total for clients, which will be sent as a single // message at the end of the frame // todo: remove after combining shotgun blasts? if ( info.GetInflictor() && info.GetInflictor()->edict() ) { m_DmgOrigin = info.GetInflictor()->GetAbsOrigin(); } m_DmgTake += (int)info.GetDamage(); // Reset damage time countdown for each type of time based damage player just sustained for (int i = 0; i < CDMG_TIMEBASED; i++) { // Make sure the damage type is really time-based. // This is kind of hacky but necessary until we setup DamageType as an enum. int iDamage = ( DMG_PARALYZE << i ); if ( ( info.GetDamageType() & iDamage ) && g_pGameRules->Damage_IsTimeBased( iDamage ) ) { m_rgbTimeBasedDamage[i] = 0; } } const char* pzsMedigunResistEffect = NULL; const char* pzsTeam = GetTeamNumber() == TF_TEAM_RED ? "red" : "blue"; // If we have one of the medigun resist buffs and get hit with the matching damage type then // spawn a particle above our head to let enemies know their damage is being resisted, and tell // the medic he's doing the right thing. bool bMedicBulletResist = m_Shared.InCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST ) || m_Shared.InCond( TF_COND_MEDIGUN_SMALL_BULLET_RESIST ); bool bMedicExplosiveResist = m_Shared.InCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST ) || m_Shared.InCond( TF_COND_MEDIGUN_SMALL_BLAST_RESIST ); bool bMedicFireResist = m_Shared.InCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST ) || m_Shared.InCond( TF_COND_MEDIGUN_SMALL_FIRE_RESIST ); if( ( bMedicBulletResist && ( bitsDamage & DMG_BULLET ) ) ) { pzsMedigunResistEffect = CFmtStr( "vaccinator_%s_buff1_burst", pzsTeam ); } else if( bMedicExplosiveResist && ( bitsDamage & DMG_BLAST ) ) { pzsMedigunResistEffect = CFmtStr( "vaccinator_%s_buff2_burst", pzsTeam ); } else if( bMedicFireResist && ( bitsDamage & DMG_BURN ) ) { pzsMedigunResistEffect = CFmtStr( "vaccinator_%s_buff3_burst", pzsTeam ); } if( pzsMedigunResistEffect != NULL ) { const Vector& vecOrigin = GetAbsOrigin(); CPVSFilter filter( vecOrigin ); TE_TFParticleEffect( filter, 0, pzsMedigunResistEffect, vecOrigin, vec3_angle ); } // Display any effect associate with this damage type DamageEffect( info.GetDamage(),bitsDamage ); m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client m_bitsHUDDamage = -1; // make sure the damage bits get reset // Flinch bool bFlinch = true; if ( bitsDamage != DMG_GENERIC ) { if ( IsPlayerClass( TF_CLASS_SNIPER ) && m_Shared.InCond( TF_COND_AIMING ) ) { if ( pTFAttacker && pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_MINIGUN ) { float flDistSqr = ( pTFAttacker->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); if ( flDistSqr > 750 * 750 ) { bFlinch = false; } } } if ( bFlinch ) { if ( ApplyPunchImpulseX( -2 ) ) { PlayFlinch( info ); } } // PASSTIME intense flinch to make it hard to throw straight while taking damage extern ConVar tf_passtime_flinch_boost; if( TFGameRules() && TFGameRules()->IsPasstimeMode() && (tf_passtime_flinch_boost.GetInt() > 0) ) { int iFlinch = tf_passtime_flinch_boost.GetInt(); CTFWeaponBase *pMyWeapon = GetActiveTFWeapon(); if( pMyWeapon && pMyWeapon->GetWeaponID() == TF_WEAPON_PASSTIME_GUN ) { QAngle punch; punch.Random( -iFlinch, iFlinch ); SetPunchAngle( punch ); } } } // Do special explosion damage effect if ( bitsDamage & DMG_BLAST ) { OnDamagedByExplosion( info ); } if ( m_iHealthBefore != GetHealth() ) { PainSound( info ); } // Detect drops below 25% health and restart expression, so that characters look worried. int iHealthBoundary = (GetMaxHealth() * 0.25); if ( GetHealth() <= iHealthBoundary && m_iHealthBefore > iHealthBoundary ) { ClearExpression(); } #ifdef _DEBUG // Report damage from the info in debug so damage against targetdummies goes // through the system, as m_iHealthBefore - GetHealth() will always be 0. CTF_GameStats.Event_PlayerDamage( this, info, info.GetDamage() ); #else CTF_GameStats.Event_PlayerDamage( this, info, m_iHealthBefore - GetHealth() ); #endif // _DEBUG // if we take damage after we leave the ground, update the health if its less if ( bTookDamage && m_iLeftGroundHealth > 0 ) { if ( GetHealth() < m_iLeftGroundHealth ) { m_iLeftGroundHealth = GetHealth(); } } if ( IsPlayerClass( TF_CLASS_SPY ) && ( inputInfo.GetDamageCustom() != TF_DMG_CUSTOM_TELEFRAG ) ) { // Trigger feign death if the player has it prepped... if ( m_Shared.IsFeignDeathReady() ) { m_Shared.SetFeignDeathReady( false ); if ( !m_Shared.InCond( TF_COND_TAUNTING ) ) { SpyDeadRingerDeath( info ); pTFAttacker->IncrementKillCountSinceLastDeploy( info ); } } else if ( !( info.GetDamageType() & DMG_FALL ) ) { m_Shared.NoteLastDamageTime( m_lastDamageAmount ); } } if ( pWeapon ) { pWeapon->ApplyPostHitEffects( inputInfo, this ); } if ( IsPlayerClass( TF_CLASS_DEMOMAN ) ) { // Reduce charge if damage is taken int iDemoChargeDamagePenalty = 0; CALL_ATTRIB_HOOK_INT( iDemoChargeDamagePenalty, lose_demo_charge_on_damage_when_charging ); // Does not apply to self or fall damage if ( iDemoChargeDamagePenalty && m_Shared.InCond( TF_COND_SHIELD_CHARGE ) && !( info.GetDamageType() & DMG_FALL ) && (pAttacker != this) ) { iDemoChargeDamagePenalty *= info.GetDamage(); m_Shared.SetDemomanChargeMeter( Max( m_Shared.GetDemomanChargeMeter() - (float)iDemoChargeDamagePenalty, 0.0f ) ); } } #ifdef STAGING_ONLY // Remove Cond if hit if ( m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) ) { m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST ); } #endif float flRageScale = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flRageScale, rage_giving_scale ); // Give the soldier/pyro some rage points for dealing/taking damage. if ( bTookDamage && pTFAttacker != this ) { // Buff flag 1: we get rage when we deal damage. Here, that means the soldier that attacked // gets rage when we take damage. HandleRageGain( pTFAttacker, kRageBuffFlag_OnDamageDealt, info.GetDamage() * flRageScale, 6.0f ); // Buff flag 2: we get rage when we take damage. if ( !( info.GetDamageType() & DMG_FALL ) ) { HandleRageGain( this, kRageBuffFlag_OnDamageReceived, info.GetDamage() * flRageScale, 3.5f ); } // Buff 5: our pyro attacker get rage when we're damaged by fire if ( ( info.GetDamageType() & DMG_BURN ) != 0 || ( info.GetDamageType() & DMG_PLASMA ) != 0 ) { float flInverseRageGainScale = TFGameRules()->IsMannVsMachineMode() ? 12.f : 3.f; HandleRageGain( pTFAttacker, kRageBuffFlag_OnBurnDamageDealt, info.GetDamage() * flRageScale, flInverseRageGainScale ); } } if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_BAT_FISH ) { bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED ) && (m_Shared.GetDisguiseTeam() == pTFAttacker->GetTeamNumber()); if ( m_iHealth <= 0 ) { info.SetDamageCustom( TF_DMG_CUSTOM_FISH_KILL ); } if ( m_iHealth <= 0 || !bDisguised ) { // Do you ever find yourself typing "fish damage override" into a million-lines-of-code project and // wondering about the world? Because I do. int iFishDamageOverride = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iFishDamageOverride, fish_damage_override ); TFGameRules()->DeathNotice( this, info, iFishDamageOverride ? "fish_notice__arm" : "fish_notice" ); } } if ( IsPlayerClass( TF_CLASS_SCOUT) ) { // Lose hype on take damage int iHypeResetsOnTakeDamage = 0; CALL_ATTRIB_HOOK_INT( iHypeResetsOnTakeDamage, lose_hype_on_take_damage ); if ( iHypeResetsOnTakeDamage != 0 ) { // Loose x hype on jump float flHype = m_Shared.GetScoutHypeMeter(); m_Shared.SetScoutHypeMeter( flHype - iHypeResetsOnTakeDamage * info.GetDamage() ); TeamFortress_SetSpeed(); } } // Add humilation Obituary here for throwable hits //if ( info.GetDamageCustom() == TF_DMG_CUSTOM_THROWABLE ) //{ // bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED ) && (m_Shared.GetDisguiseTeam() == pTFAttacker->GetTeamNumber()); // if( m_iHealth <= 0 ) // { // info.SetDamageCustom( TF_DMG_CUSTOM_THROWABLE_KILL ); // } // if ( m_iHealth <= 0 || !bDisguised ) // { // TFGameRules()->DeathNotice( this, info, "throwable_hit" ); // } //} // Let attacker react to the damage they dealt if ( pTFAttacker ) { pTFAttacker->OnDealtDamage( this, info ); } bool bIsPyroDetonateJumping = ( IsPlayerClass( TF_CLASS_PYRO ) && pAttacker == this && !(GetFlags() & FL_ONGROUND) && !(GetFlags() & FL_INWATER)); if ( bIsDemomanPipeJumping || bIsSoldierRocketJumping || bIsPyroDetonateJumping ) { // Are we being healed by any QuickFix medics? for ( int i = 0; i < m_Shared.m_nNumHealers; i++ ) { CTFPlayer *pMedic = ToTFPlayer( m_Shared.m_aHealers[i].pHealer ); if ( !pMedic ) continue; // Share blast jump with them CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun* >( pMedic->GetActiveTFWeapon() ); if ( pMedigun && pMedigun->GetMedigunType() == MEDIGUN_QUICKFIX ) { // Vector vecDir = vec3_origin; // if ( info.GetInflictor() ) // { // vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0.0f, 0.0f, 10.0f ) - WorldSpaceCenter(); // info.GetInflictor()->AdjustDamageDirection( info, vecDir, this ); // VectorNormalize( vecDir ); // } // pMedic->RemoveFlag( FL_ONGROUND ); // pMedic->ApplyPushFromDamage( info, vecDir ); float flForce = GetAbsVelocity().Length(); flForce = MIN( flForce, 900.f ); Vector vecNewVelocity = GetAbsVelocity(); VectorNormalize( vecNewVelocity ); pMedic->RemoveFlag( FL_ONGROUND ); pMedic->ApplyAbsVelocityImpulse( vecNewVelocity * flForce ); } } } if ( pTFAttacker && pTFAttacker->IsPlayerClass( TF_CLASS_SOLDIER ) ) { if ( info.GetDamageType() & DMG_BLAST ) { // Send an event whenever a soldier hits another player directly with a stun rocket CTFBaseRocket *pRocket = dynamic_cast< CTFBaseRocket* >( info.GetInflictor() ); if ( pRocket && pRocket->GetStunLevel() && pRocket->GetEnemy() && pRocket->GetEnemy() == this ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_directhit_stun" ); if ( event ) { event->SetInt( "attacker", pTFAttacker->entindex() ); event->SetInt( "victim", entindex() ); gameeventmanager->FireEvent( event ); } } } } CTFWeaponBase *pTFWeapon = GetKilleaterWeaponFromDamageInfo( &info ); if ( !pTFWeapon ) { // Check Wearable instead like demoshields or manntreads CTFWearable *pWearable = dynamic_cast< CTFWearable* >( info.GetWeapon() ); if ( pWearable ) { EconEntity_OnOwnerKillEaterEvent_Batched( pWearable, pTFAttacker, this, kKillEaterEvent_DamageDealt, info.GetDamage() ); EconEntity_OnOwnerKillEaterEvent_Batched( pWearable, pTFAttacker, this, kKillEaterEvent_PlayersHit, 1 ); } } else { EconEntity_OnOwnerKillEaterEvent_Batched( pTFWeapon, pTFAttacker, this, kKillEaterEvent_DamageDealt, info.GetDamage() ); EconEntity_OnOwnerKillEaterEvent_Batched( pTFWeapon, pTFAttacker, this, kKillEaterEvent_PlayersHit, 1 ); } // bHadBallBeforeDamage will always be false in non-passtime modes if ( bTookDamage && bHadBallBeforeDamage ) { g_pPasstimeLogic->OnBallCarrierDamaged( this, pTFAttacker, info ); } return info.GetDamage(); } //----------------------------------------------------------------------------- // Purpose: Invoked when we deal damage to another victim //----------------------------------------------------------------------------- void CTFPlayer::OnDealtDamage( CBaseCombatCharacter *pVictim, const CTakeDamageInfo &info ) { if ( pVictim ) { // which second of the window are we in int i = (int)gpGlobals->curtime; i %= DPS_Period; if ( i != m_lastDamageRateIndex ) { // a second has ticked over, start a new accumulation m_damageRateArray[ i ] = info.GetDamage(); m_lastDamageRateIndex = i; // track peak DPS for this player m_peakDamagePerSecond = 0; for( i=0; i m_peakDamagePerSecond ) { m_peakDamagePerSecond = m_damageRateArray[i]; } } } else { m_damageRateArray[ i ] += info.GetDamage(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::AddConnectedPlayers( CUtlVector &vecPlayers, CTFPlayer *pPlayerToConsider ) { if ( !pPlayerToConsider ) return; if ( vecPlayers.Find( pPlayerToConsider ) != vecPlayers.InvalidIndex() ) return; // already in the list vecPlayers.AddToTail( pPlayerToConsider ); if ( pPlayerToConsider->MedicGetHealTarget() ) { AddConnectedPlayers( vecPlayers, ToTFPlayer( pPlayerToConsider->MedicGetHealTarget() ) ); } for ( int i = 0 ; i < pPlayerToConsider->m_Shared.GetNumHealers() ; i++ ) { CTFPlayer *pMedic = ToTFPlayer( pPlayerToConsider->m_Shared.GetHealerByIndex( i ) ); if ( pMedic ) { AddConnectedPlayers( vecPlayers, pMedic ); } } } //----------------------------------------------------------------------------- // Purpose: Reduces backstab damage if we have a back shield. //----------------------------------------------------------------------------- bool CTFPlayer::CheckBlockBackstab( CTFPlayer *pTFAttacker ) { // Check all items for the attribute that blocks a backstab. // Destroy the first item that intercepts the backstab. CUtlVector itemList; int iBackStabShield = 0; CALL_ATTRIB_HOOK( int, iBackStabShield, set_blockbackstab_once, this, &itemList ); if ( iBackStabShield ) { Assert( itemList.Count() != 0 ); CBaseEntity *pEntity = itemList.Element( 0 ); if ( pEntity ) { if ( pEntity->IsBaseCombatWeapon() ) { // Remove. } if ( pEntity->IsWearable() ) { // Yay stats. EconEntity_OnOwnerKillEaterEvent( dynamic_cast( pEntity ), this, pTFAttacker, kKillEaterEvent_BackstabAbsorbed ); // Unequip. CTFWearable *pItem = dynamic_cast( pEntity ); pItem->Break(); pItem->RemoveFrom( this ); } UTIL_Remove( pEntity ); // tell the bot his Razorback just got broken CTFBot *me = ToTFBot( this ); if ( me ) { me->DelayedThreatNotice( pTFAttacker, 0.5f ); } } return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DamageEffect(float flDamage, int fDamageType) { bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED ); if (fDamageType & DMG_CRUSH) { //Red damage indicator color32 red = {128,0,0,128}; UTIL_ScreenFade( this, red, 1.0f, 0.1f, FFADE_IN ); } else if (fDamageType & DMG_DROWN) { //Red damage indicator color32 blue = {0,0,128,128}; UTIL_ScreenFade( this, blue, 1.0f, 0.1f, FFADE_IN ); } else if (fDamageType & DMG_SLASH) { if ( !bDisguised ) { // If slash damage shoot some blood SpawnBlood(EyePosition(), g_vecAttackDir, BloodColor(), flDamage); } } else if ( fDamageType & DMG_BULLET ) { if ( !bDisguised ) { EmitSound( "Flesh.BulletImpact" ); } } } //----------------------------------------------------------------------------- // Purpose: // Input : collisionGroup - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFPlayer::ShouldCollide( int collisionGroup, int contentsMask ) const { if ( ( ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) && tf_avoidteammates.GetBool() ) || collisionGroup == TFCOLLISION_GROUP_ROCKETS || collisionGroup == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) { switch( GetTeamNumber() ) { case TF_TEAM_RED: if ( !( contentsMask & CONTENTS_REDTEAM ) ) return false; break; case TF_TEAM_BLUE: if ( !( contentsMask & CONTENTS_BLUETEAM ) ) return false; break; } } return BaseClass::ShouldCollide( collisionGroup, contentsMask ); } //--------------------------------------- // Is the player the passed player class? //--------------------------------------- bool CTFPlayer::IsPlayerClass( int iClass ) const { const CTFPlayerClass *pClass = &m_PlayerClass; if ( !pClass ) return false; return ( pClass->IsClass( iClass ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CommitSuicide( bool bExplode /* = false */, bool bForce /*= false*/ ) { // Don't suicide if we haven't picked a class for the first time, or we're not in active state if ( IsPlayerClass( TF_CLASS_UNDEFINED ) || !m_Shared.InState( TF_STATE_ACTIVE ) ) return; // Don't suicide during the "bonus time" if we're not on the winning team if ( !bForce && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN && GetTeamNumber() != TFGameRules()->GetWinningTeam() ) { return; } if ( TFGameRules()->ShowMatchSummary() ) return; // No suicide while a ghost! if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) return; // No suicide while a kart if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) return; m_bSuicideExplode = bExplode; m_iSuicideCustomKillFlags = TF_DMG_CUSTOM_SUICIDE; BaseClass::CommitSuicide( bExplode, bForce ); } //----------------------------------------------------------------------------- // Purpose: // Input : &info - // Output : int //----------------------------------------------------------------------------- ConVar tf_preround_push_from_damage_enable( "tf_preround_push_from_damage_enable", "0", FCVAR_NONE, "If enabled, this will allow players using certain type of damage to move during pre-round freeze time." ); void CTFPlayer::ApplyPushFromDamage( const CTakeDamageInfo &info, Vector vecDir ) { // check if player can be moved if ( !tf_preround_push_from_damage_enable.GetBool() && !CanPlayerMove() ) return; if ( m_bIsTargetDummy ) return; Vector vecForce; vecForce.Init(); if ( info.GetAttacker() == this ) { Vector vecSize = WorldAlignSize(); Vector hullSizeCrouch = VEC_DUCK_HULL_MAX - VEC_DUCK_HULL_MIN; if ( vecSize == hullSizeCrouch ) { // Use the original hull for damage force calculation to ensure our RJ height doesn't change due to crouch hull increase // ^^ Comment above is an ancient lie, Ducking actually increases blast force, this value increases it even more 82 standing, 62 ducking, 55 modified vecSize.z = 55; } float flDamageForForce = info.GetDamageForForceCalc() ? info.GetDamageForForceCalc() : info.GetDamage(); float flSelfPushMult = 1.0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( info.GetWeapon(), flSelfPushMult, mult_dmgself_push_force ); if ( IsPlayerClass( TF_CLASS_SOLDIER ) ) { // Rocket Jump if ( (info.GetDamageType() & DMG_BLAST) ) { if ( GetFlags() & FL_ONGROUND ) { vecForce = vecDir * -DamageForce( vecSize, flDamageForForce, tf_damageforcescale_self_soldier_badrj.GetFloat() ) * flSelfPushMult; } else { vecForce = vecDir * -DamageForce( vecSize, flDamageForForce, tf_damageforcescale_self_soldier_rj.GetFloat() ) * flSelfPushMult; } SetBlastJumpState( TF_PLAYER_ROCKET_JUMPED ); // Reset duck in air on self rocket impulse. m_Shared.SetAirDucked( 0 ); } else { // Self Damage no force vecForce.Zero(); } } else { // Detonator blast jump modifier if ( IsPlayerClass( TF_CLASS_PYRO ) && info.GetDamageCustom() == TF_DMG_CUSTOM_FLARE_EXPLOSION ) { vecForce = vecDir * -DamageForce( vecSize, flDamageForForce, tf_damageforcescale_pyro_jump.GetFloat() ) * flSelfPushMult; } else { // Other Jumps (Stickies) vecForce = vecDir * -DamageForce( vecSize, flDamageForForce, DAMAGE_FORCE_SCALE_SELF ) * flSelfPushMult; } // Reset duck in air on self grenade impulse. m_Shared.SetAirDucked( 0 ); } // Precision removes self damage so we don't want push force from damage if ( m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) { vecForce.Zero(); } } else { // Don't let bot get pushed while they're in spawn area if ( m_Shared.InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) || m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT ) { return; } // Sentryguns push a lot harder if ( (info.GetDamageType() & DMG_BULLET) && info.GetInflictor() && info.GetInflictor()->IsBaseObject() ) { float flSentryPushMultiplier = 16.f; CObjectSentrygun* pSentry = dynamic_cast( info.GetInflictor() ); if ( pSentry ) { flSentryPushMultiplier = pSentry->GetPushMultiplier(); // Scale the force based on Distance, Wrangled Sentries should not push so hard at distance // get the distance between sentry and victim and lower push force if outside of attack range (wrangled) float flDistSqr = (pSentry->GetAbsOrigin() - GetAbsOrigin()).LengthSqr(); if ( flDistSqr > SENTRY_MAX_RANGE_SQRD ) { flSentryPushMultiplier *= 0.5f; } } vecForce = vecDir * -DamageForce( WorldAlignSize(), info.GetDamage(), flSentryPushMultiplier ); } else { CTFWeaponBase *pWeapon = dynamic_cast(info.GetWeapon()); if ( pWeapon && (pWeapon->GetWeaponID() == TF_WEAPON_COMPOUND_BOW) ) { vecForce = vecDir * -DamageForce( WorldAlignSize(), info.GetDamage(), tf_damageforcescale_other.GetFloat() ); vecForce.z = 0; } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED ) { vecForce = vecDir * -DamageForce( WorldAlignSize(), info.GetDamage(), tf_damageforcescale_other.GetFloat() ) * 1.25f; } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_FLARE_PELLET) { float flTimeAlive = 0.0f; CTFProjectile_Flare *pFlare = dynamic_cast< CTFProjectile_Flare* >( info.GetInflictor() ); if ( pFlare ) { flTimeAlive = pFlare->GetTimeAlive(); } vecForce = vecDir * -DamageForce( WorldAlignSize(), info.GetDamage(), TF_FLARE_PELLET_FORCE * RemapValClamped( flTimeAlive, 0.1f, 1.0f, 1.0f, TF_FLARE_PELLET_FORCE_DISTANCE_SCALE ) ); vecForce.z = ( ( GetPlayerClass()->GetClassIndex() == TF_CLASS_HEAVYWEAPONS ) ? ( TF_FLARE_PELLET_FORCE_UPWARD_HEAVY ) : ( TF_FLARE_PELLET_FORCE_UPWARD ) ); } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_KART ) { vecForce = info.GetDamageForce(); } else { vecForce = vecDir * -DamageForce( WorldAlignSize(), info.GetDamage(), tf_damageforcescale_other.GetFloat() ); } if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { // Heavies take less push from non sentryguns vecForce *= 0.5; } CBaseEntity* pInflictor = info.GetInflictor(); if ( pInflictor && CanScatterGunKnockBack(pWeapon, info.GetDamage(), (WorldSpaceCenter() - pInflictor->WorldSpaceCenter()).LengthSqr() ) ) { // Remove all Z force from these shots if they are close enough and doing enough damage if ( vecForce.z < 0 ) { vecForce.z = 0; } } int iAirBlast = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iAirBlast, damage_causes_airblast ); if ( iAirBlast ) { float force = -DamageForce( WorldAlignSize(), 100, 6 ); ApplyAirBlastImpulse( force * vecDir ); vecForce.Zero(); } } bool bBigKnockback = false; CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); if ( pAttacker && pAttacker->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && pAttacker->m_Shared.IsRageDraining() ) { // Generic Rage attribute int iRage = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iRage, generate_rage_on_dmg ); if ( iRage ) { // In MvM, Heavies can purchase a knockback+stun effect float flPushMultiplier = ( iRage + 1 ) * 24.f; vecForce = vecDir * -DamageForce( WorldAlignSize(), info.GetDamage(), flPushMultiplier ); bBigKnockback = true; // Track for achievements m_AchievementData.AddPusherToHistory( pAttacker ); } } // Airblast effect for general attacks. Scaled by range. float flImpactBlastForce = 1.f; CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), flImpactBlastForce, damage_blast_push ); if ( flImpactBlastForce != 1.f ) { CBaseEntity *pInflictor = info.GetInflictor(); if ( pInflictor ) { const float flMaxPushBackDistSqr = 700.f * 700.f; float flDistSqr = ( WorldSpaceCenter() - pInflictor->WorldSpaceCenter() ).LengthSqr(); if ( flDistSqr <= flMaxPushBackDistSqr ) { if ( vecForce.z < 0 ) { vecForce.z = 0; } m_Shared.StunPlayer( 0.3f, 1.f, TF_STUN_MOVEMENT | TF_STUN_MOVEMENT_FORWARD_ONLY, pAttacker ); flImpactBlastForce = RemapValClamped( flDistSqr, 1000.f, flMaxPushBackDistSqr, flImpactBlastForce, ( flImpactBlastForce * 0.5f ) ); float flForce = -DamageForce( WorldAlignSize(), info.GetDamage() * 2, flImpactBlastForce ); ApplyAirBlastImpulse( flForce * vecDir ); } } } if ( TFGameRules()->GameModeUsesUpgrades() ) { if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { // invading bots can't be pushed by sentry guns if ( info.GetInflictor() && info.GetInflictor()->IsBaseObject() ) { return; } } if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS && !bBigKnockback ) { if ( IsMiniBoss() ) { // Minibosses can't be pushed by anything except heavy rage and airblast (airblast is suppressed when deploying in deploy ai code) return; } else if ( m_nDeployingBombState != TF_BOMB_DEPLOYING_NONE && ( info.GetDamageType() & DMG_BLAST ) == 0 ) { // Regular robots only get pushed by blast damage when deploying the bomb return; } } } float flDamageForceReduction = 1.f; CALL_ATTRIB_HOOK_FLOAT( flDamageForceReduction, damage_force_reduction ); vecForce *= flDamageForceReduction; } ApplyAbsVelocityImpulse( vecForce ); // If we were pushed by an enemy explosion, we're now marked as being blasted by an enemy. // If we stay on the ground, next frame our player think will remove this flag. if ( info.GetAttacker() != this && info.GetDamageType() & DMG_BLAST ) { m_bTakenBlastDamageSinceLastMovement = true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PlayDamageResistSound( float flStartDamage, float flModifiedDamage ) { if ( flStartDamage <= 0.f ) return; // Spam control if ( gpGlobals->curtime - m_flLastDamageResistSoundTime <= 0.1f ) return; // Play an absorb sound based on the percentage the damage has been reduced to float flDamagePercent = flModifiedDamage / flStartDamage; if ( flDamagePercent > 0.f && flDamagePercent < 1.f ) { const char *pszSoundName = ( flDamagePercent >= 0.75f ) ? "Player.ResistanceLight" : ( flDamagePercent <= 0.25f ) ? "Player.ResistanceHeavy" : "Player.ResistanceMedium"; CSoundParameters params; if ( CBaseEntity::GetParametersForSound( pszSoundName, params, NULL ) ) { CPASAttenuationFilter filter( GetAbsOrigin(), params.soundlevel ); EmitSound_t ep( params ); ep.m_flVolume *= RemapValClamped( flStartDamage, 1.f, 70.f, 0.7f, 1.f ); EmitSound( filter, entindex(), ep ); m_flLastDamageResistSoundTime = gpGlobals->curtime; } } } //----------------------------------------------------------------------------- // Purpose: // Input : &info - // Output : int //----------------------------------------------------------------------------- int CTFPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { if ( TFGameRules()->IsInItemTestingMode() && !IsFakeClient() ) return 0; bool bUsingUpgrades = TFGameRules()->GameModeUsesUpgrades(); // Always NULL check this below CTFPlayer *pTFAttacker = ToTFPlayer( info.GetAttacker() ); CTFGameRules::DamageModifyExtras_t outParams; outParams.bIgniting = false; outParams.bSelfBlastDmg = false; outParams.bSendPreFeignDamage = false; outParams.bPlayDamageReductionSound = false; float realDamage = info.GetDamage(); int iPreFeignDamage = realDamage; if ( TFGameRules() ) { realDamage = TFGameRules()->ApplyOnDamageAliveModifyRules( info, this, outParams ); if ( realDamage == -1 ) { // Hard out requested from ApplyOnDamageModifyRules return 0; } } if ( outParams.bPlayDamageReductionSound ) { PlayDamageResistSound( info.GetDamage(), realDamage ); } // Grab the vector of the incoming attack. // (Pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). Vector vecDir = vec3_origin; if ( info.GetInflictor() ) { vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0.0f, 0.0f, 10.0f ) - WorldSpaceCenter(); info.GetInflictor()->AdjustDamageDirection( info, vecDir, this ); VectorNormalize( vecDir ); } g_vecAttackDir = vecDir; // Do the damage. m_bitsDamageType |= info.GetDamageType(); // Check to see if the Wheatley sapper item is equipped and should react if ( m_bitsDamageType & DMG_BULLET && IsPlayerClass( TF_CLASS_SPY ) ) { CBaseCombatWeapon *pRet = GetActiveWeapon(); CTFWeaponSapper *pSap = dynamic_cast< CTFWeaponSapper* >( pRet ); if ( pSap != NULL ) { if (pSap->IsWheatleySapper()) { pSap->WheatleyDamage(); } } } float flBleedingTime = 0.0f; int iPrevHealth = m_iHealth; if ( m_takedamage != DAMAGE_EVENTS_ONLY ) { if ( info.GetDamageCustom() != TF_DMG_CUSTOM_BLEEDING && !outParams.bSelfBlastDmg ) { CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( info.GetWeapon(), flBleedingTime, bleeding_duration ); } // Take damage - round to the nearest integer. int iOldHealth = m_iHealth; m_iHealth -= ( realDamage + 0.5f ); if ( IsHeadshot( info.GetDamageCustom() ) && (m_iHealth <= 0) && (iOldHealth != 1) ) { int iNoDeathFromHeadshots = 0; CALL_ATTRIB_HOOK_INT( iNoDeathFromHeadshots, no_death_from_headshots ); if ( iNoDeathFromHeadshots == 1 ) { m_iHealth = 1; } } // For lifeleech, calculate how much damage we actually inflicted. CTFPlayer *pAttackingPlayer = dynamic_cast( info.GetAttacker() ); if ( pAttackingPlayer && pAttackingPlayer->GetActiveWeapon() ) { float fLifeleechOnDamage = 0.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pAttackingPlayer->GetActiveWeapon(), fLifeleechOnDamage, lifeleech_on_damage ); if ( fLifeleechOnDamage > 0.0f ) { const float fActualDamageDealt = iOldHealth - m_iHealth; const float fHealAmount = fActualDamageDealt * fLifeleechOnDamage; if ( fHealAmount >= 0.5f ) { const int iHealthToAdd = MIN( (int)(fHealAmount + 0.5f), pAttackingPlayer->m_Shared.GetMaxBuffedHealth() - pAttackingPlayer->GetHealth() ); pAttackingPlayer->TakeHealth( iHealthToAdd, DMG_GENERIC ); } } } // track accumulated sentry gun damage dealt by players if ( pTFAttacker ) { // track amount of damage dealt by defender's sentry guns CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() ); CTFProjectile_SentryRocket *sentryRocket = dynamic_cast< CTFProjectile_SentryRocket * >( info.GetInflictor() ); if ( ( sentry && !sentry->IsDisposableBuilding() ) || sentryRocket ) { int flooredHealth = clamp( m_iHealth, 0, m_iHealth ); pTFAttacker->AccumulateSentryGunDamageDealt( iOldHealth - flooredHealth ); } } } m_flLastDamageTime = gpGlobals->curtime; // Apply a damage force. CBaseEntity *pAttacker = info.GetAttacker(); if ( !pAttacker ) return 0; if ( ( info.GetDamageType() & DMG_PREVENT_PHYSICS_FORCE ) == 0 ) { if ( info.GetInflictor() && ( GetMoveType() == MOVETYPE_WALK ) && ( !pAttacker->IsSolidFlagSet( FSOLID_TRIGGER ) ) && ( !m_Shared.InCond( TF_COND_DISGUISED ) ) ) { if ( !m_Shared.InCond( TF_COND_MEGAHEAL ) || outParams.bSelfBlastDmg ) { ApplyPushFromDamage( info, vecDir ); } } } if ( outParams.bIgniting && pTFAttacker ) { m_Shared.Burn( pTFAttacker, dynamic_cast< CTFWeaponBase * >( info.GetWeapon() ) ); } if ( flBleedingTime > 0 && pTFAttacker ) { m_Shared.MakeBleed( pTFAttacker, dynamic_cast< CTFWeaponBase * >( info.GetWeapon() ), flBleedingTime ); } // Don't recieve reflected damage if you are carrying Reflect (prevents a loop in a game with two Reflect players) if ( ( info.GetDamageType() & TF_DMG_CUSTOM_RUNE_REFLECT ) && m_Shared.GetCarryingRuneType() == RUNE_REFLECT ) { return 0; } CTFWeaponBase *pTFWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() ); if ( pTFWeapon && WeaponID_IsSniperRifle( pTFWeapon->GetWeaponID() ) ) { CTFSniperRifle *pSniper = dynamic_cast( pTFWeapon ); if ( pSniper && ( pSniper->IsZoomed() || ( pSniper->GetWeaponID() == TF_WEAPON_SNIPERRIFLE_CLASSIC ) ) ) { float flJarateTime = pSniper->GetJarateTime(); if ( flJarateTime && !m_Shared.IsInvulnerable() && !m_Shared.InCond( TF_COND_PHASE ) && !m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) { Vector vecOrigin = info.GetDamagePosition(); CPVSFilter filter( vecOrigin ); TE_TFParticleEffect( filter, 0.0, "peejar_impact_small", vecOrigin, vec3_angle ); m_Shared.AddCond( TF_COND_URINE, flJarateTime ); if ( pTFAttacker ) { UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"%s\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n", pTFAttacker->GetPlayerName(), pTFAttacker->GetUserID(), pTFAttacker->GetNetworkIDString(), pTFAttacker->GetTeam()->GetName(), "jarate_attack", GetPlayerName(), GetUserID(), GetNetworkIDString(), GetTeam()->GetName(), "sniperrifle", (int)pTFAttacker->GetAbsOrigin().x, (int)pTFAttacker->GetAbsOrigin().y, (int)pTFAttacker->GetAbsOrigin().z, (int)GetAbsOrigin().x, (int)GetAbsOrigin().y, (int)GetAbsOrigin().z ); // explosive jarate shot for a fully charged shot or headshot if ( pSniper->IsFullyCharged() || IsHeadshot( info.GetDamageCustom() ) || LastHitGroup() == HITGROUP_HEAD ) { JarExplode( entindex(), pTFAttacker, pTFWeapon, pTFWeapon, info.GetDamagePosition(), pTFAttacker->GetTeamNumber(), 100.f, TF_COND_URINE, flJarateTime, "peejar_impact" ); } } } if ( bUsingUpgrades && pTFAttacker ) { int iExplosiveShot = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER ( pTFAttacker, iExplosiveShot, explosive_sniper_shot ); if ( iExplosiveShot ) { if ( IsHeadshot( info.GetDamageCustom() ) || ( flJarateTime && LastHitGroup() == HITGROUP_HEAD ) ) { pSniper->ExplosiveHeadShot( pTFAttacker, this ); } } } } } // Prevents a sandwich ignore-ammo-while-taking-damage-and-eating alias exploit if ( m_Shared.InCond( TF_COND_TAUNTING ) && m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON ) { if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { CTFLunchBox *pLunchBox = dynamic_cast ( m_Shared.GetActiveTFWeapon() ); if ( pLunchBox ) { if ( pLunchBox->GetLunchboxType() != LUNCHBOX_ADDS_MAXHEALTH ) { pLunchBox->DrainAmmo( true ); } } } } // Fire a global game event - "player_hurt" IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" ); if ( event ) { event->SetInt( "userid", GetUserID() ); event->SetInt( "health", MAX( 0, m_iHealth ) ); // HLTV event priority, not transmitted event->SetInt( "priority", 5 ); int iDamageAmount = ( iPrevHealth - m_iHealth ); event->SetInt( "damageamount", outParams.bSendPreFeignDamage ? iPreFeignDamage : iDamageAmount ); // Hurt by another player. if ( pAttacker->IsPlayer() ) { CBasePlayer *pPlayer = ToBasePlayer( pAttacker ); event->SetInt( "attacker", pPlayer->GetUserID() ); event->SetInt( "custom", info.GetDamageCustom() ); event->SetBool( "showdisguisedcrit", m_bShowDisguisedCrit ); event->SetBool( "crit", (info.GetDamageType() & DMG_CRITICAL) != 0 ); event->SetBool( "minicrit", m_bMiniCrit ); event->SetBool( "allseecrit", m_bAllSeeCrit ); Assert( (int)m_eBonusAttackEffect < 256 ); event->SetInt( "bonuseffect", (int)m_eBonusAttackEffect ); if ( pTFAttacker && pTFAttacker->GetActiveTFWeapon() ) { event->SetInt( "weaponid", pTFAttacker->GetActiveTFWeapon()->GetWeaponID() ); } } // Hurt by world. else { event->SetInt( "attacker", 0 ); } gameeventmanager->FireEvent( event ); } if ( pTFAttacker && pTFAttacker != this ) { pTFAttacker->RecordDamageEvent( info, (m_iHealth <= 0), iPrevHealth ); } //No bleeding while invul or disguised. bool bBleed = ( ( m_Shared.InCond( TF_COND_DISGUISED ) == false || m_Shared.GetDisguiseTeam() != pAttacker->GetTeamNumber() ) && !m_Shared.IsInvulnerable() ); // No bleed effects for DMG_GENERIC if ( info.GetDamageType() == 0 ) { bBleed = false; } // Except if we are really bleeding! bBleed |= m_Shared.InCond( TF_COND_BLEEDING ); if ( bBleed && pTFAttacker ) { CTFWeaponBase *pWeapon = pTFAttacker->GetActiveTFWeapon(); if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_FLAMETHROWER ) { bBleed = false; } } if ( bBleed && ( realDamage > 0.f ) ) { Vector vDamagePos = info.GetDamagePosition(); if ( vDamagePos == vec3_origin ) { vDamagePos = WorldSpaceCenter(); } if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { if ( ( IsMiniBoss() && static_cast< float >( GetHealth() ) / GetMaxHealth() > 0.3f ) || realDamage < 50 ) { DispatchParticleEffect( "bot_impact_light", GetAbsOrigin(), vec3_angle ); } else { DispatchParticleEffect( "bot_impact_heavy", GetAbsOrigin(), vec3_angle ); } } else { CPVSFilter filter( vDamagePos ); TE_TFBlood( filter, 0.0, vDamagePos, -vecDir, entindex() ); } } if ( m_bIsTargetDummy ) { // In the case of a targetdummy bot, restore any damage so it can never die TakeHealth( ( iPrevHealth - m_iHealth ), DMG_GENERIC ); } m_vecFeignDeathVelocity = GetAbsVelocity(); if ( pTFAttacker ) { // If we're invuln, give whomever provided it rewards/credit if ( m_Shared.IsInvulnerable() && realDamage > 0.f ) { // Medigun? CBaseEntity *pProvider = m_Shared.GetConditionProvider( TF_COND_INVULNERABLE ); if ( !pProvider && bUsingUpgrades ) { // Bottle? pProvider = m_Shared.GetConditionProvider( TF_COND_INVULNERABLE_USER_BUFF ); } if ( pProvider ) { CTFPlayer *pTFProvider = ToTFPlayer( pProvider ); if ( pTFProvider ) { if ( pTFProvider != pTFAttacker && bUsingUpgrades ) { HandleRageGain( pTFProvider, kRageBuffFlag_OnHeal, ( realDamage / 2.f ), 1.f ); } CTF_GameStats.Event_PlayerBlockedDamage( pTFProvider, realDamage ); } } } // Give the attacker's medic Energy based on damage done CBaseEntity *pProvider = pTFAttacker->m_Shared.GetConditionProvider( TF_COND_HEALTH_BUFF ); if ( pProvider ) { CTFPlayer *pTFProvider = ToTFPlayer( pProvider ); if ( pTFProvider && pTFProvider->IsPlayerClass( TF_CLASS_MEDIC ) ) { // Cap to prevent insane values coming from headshots and backstabs float flAmount = Min( realDamage, 250.f ) / 10.f; HandleRageGain( ToTFPlayer( pProvider ), kRageBuffFlag_OnHeal, flAmount, 1.f ); } } } // Done. return 1; } //----------------------------------------------------------------------------- // Purpose: // Input : &info - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFPlayer::ShouldGib( const CTakeDamageInfo &info ) { // Check to see if we should allow players to gib. if ( tf_playergib.GetInt() != 1 ) { if ( tf_playergib.GetInt() < 1 ) return false; else return true; } // normal players/bots don't gib in MvM if ( TFGameRules()->IsMannVsMachineMode() ) return false; // Suicide explode always gibs. if ( m_bSuicideExplode ) { m_bSuicideExplode = false; return true; } // Are we set up to gib always on critical hits? if ( info.GetDamageType() & DMG_CRITICAL ) { int iAlwaysGibOnCrit = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iAlwaysGibOnCrit, crit_kill_will_gib ); if ( iAlwaysGibOnCrit ) return true; } int iCritOnHardHit = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iCritOnHardHit, crit_on_hard_hit ); if ( iCritOnHardHit == 0 ) { // Only blast & half falloff damage can gib. if ( ( (info.GetDamageType() & DMG_BLAST) == 0 ) && ( (info.GetDamageType() & DMG_HALF_FALLOFF) == 0 ) ) return false; } // Explosive crits always gib. if ( info.GetDamageType() & DMG_CRITICAL ) return true; // Hard hits also gib. if ( GetHealth() <= -10 ) return true; if ( m_bGoingFeignDeath ) { // The player won't actually have negative health, // but spies often gib from explosive damage so we should make that likely here. float frand = (float) rand() / VALVE_RAND_MAX; return (frand>0.15f) ? true : false; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::HasBombinomiconEffectOnDeath( void ) { int iBombinomicomEffectOnDeath = 0; CALL_ATTRIB_HOOK_INT( iBombinomicomEffectOnDeath, bombinomicon_effect_on_death ); return ( iBombinomicomEffectOnDeath != 0 ); } //----------------------------------------------------------------------------- // Purpose: Figures out if there is a special assist responsible for our death. // Must be called before conditions are cleared druing death. //----------------------------------------------------------------------------- void CTFPlayer::DetermineAssistForKill( const CTakeDamageInfo &info ) { CTFPlayer *pPlayerAttacker = ToTFPlayer( info.GetAttacker() ); if ( !pPlayerAttacker ) return; CTFPlayer *pPlayerAssist = NULL; if ( m_Shared.GetConditionAssistFromVictim() ) { // If we are covered in urine, mad milk, etc, then give the provider an assist. pPlayerAssist = ToTFPlayer( m_Shared.GetConditionAssistFromVictim() ); } if ( m_Shared.IsControlStunned() ) { // If we've been stunned, the stunner gets credit for the assist. pPlayerAssist = m_Shared.GetStunner(); } // Can't assist ourself. if ( pPlayerAttacker && (pPlayerAttacker != pPlayerAssist) ) { m_Shared.SetAssist( pPlayerAssist ); } else { m_Shared.SetAssist( NULL ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { BaseClass::Event_KilledOther( pVictim, info ); if ( pVictim->IsPlayer() ) { CTFPlayer *pTFVictim = ToTFPlayer( pVictim ); if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { if ( pTFVictim && pTFVictim->IsBot() && ( pTFVictim->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) { if ( pTFVictim->GetDeployingBombState() > TF_BOMB_DEPLOYING_NONE ) { IGameEvent *event = gameeventmanager->CreateEvent( "mvm_kill_robot_delivering_bomb" ); if ( event ) { event->SetInt( "player", entindex() ); gameeventmanager->FireEvent( event ); } } } } // Custom death handlers // TODO: Need a system here! This conditional is getting pretty big. const char *pszCustomDeath = "customdeath:none"; if ( info.GetAttacker() && info.GetAttacker()->IsBaseObject() ) { pszCustomDeath = "customdeath:sentrygun"; } else if ( info.GetInflictor() && info.GetInflictor()->IsBaseObject() ) { CBaseObject* pObj = dynamic_cast( info.GetInflictor() ); if ( pObj->IsMiniBuilding() ) { pszCustomDeath = "customdeath:minisentrygun"; } else { pszCustomDeath = "customdeath:sentrygun"; } } else if ( IsHeadshot( info.GetDamageCustom() ) ) { pszCustomDeath = "customdeath:headshot"; } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB ) { pszCustomDeath = "customdeath:backstab"; } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BURNING ) { pszCustomDeath = "customdeath:burning"; } else if ( IsTauntDmg( info.GetDamageCustom() ) ) { pszCustomDeath = "customdeath:taunt"; } else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BURNING_FLARE ) { pszCustomDeath = "customdeath:flareburn"; } // Revenge handler const char *pszDomination = "domination:none"; if ( pTFVictim->GetDeathFlags() & (TF_DEATH_REVENGE|TF_DEATH_ASSISTER_REVENGE) ) { pszDomination = "domination:revenge"; } else if ( pTFVictim->GetDeathFlags() & TF_DEATH_DOMINATION ) { pszDomination = "domination:dominated"; } const char *pszVictimStunned = "victimstunned:0"; if ( pTFVictim->m_Shared.InCond( TF_COND_STUNNED ) ) { pszVictimStunned = "victimstunned:1"; } const char *pszVictimDoubleJumping = "victimdoublejumping:0"; if ( pTFVictim->m_Shared.GetAirDash() > 0 ) { pszVictimDoubleJumping = "victimdoublejumping:1"; } CFmtStrN<128> modifiers( "%s,%s,%s,%s,victimclass:%s", pszCustomDeath, pszDomination, pszVictimStunned, pszVictimDoubleJumping, g_aPlayerClassNames_NonLocalized[ pTFVictim->GetPlayerClass()->GetClassIndex() ] ); bool bPlayspeech = true; // Don't play speech if this kill disguises the spy if ( IsPlayerClass( TF_CLASS_SPY ) ) { if ( !Q_stricmp( "customdeath:backstab", pszCustomDeath ) ) { CTFKnife *pKnife = dynamic_cast( GetActiveTFWeapon() ); if ( pKnife && pKnife->GetKnifeType() == KNIFE_DISGUISE_ONKILL ) { bPlayspeech = false; } } } if ( bPlayspeech ) { SpeakConceptIfAllowed( MP_CONCEPT_KILLED_PLAYER, modifiers ); } CTFWeaponBase *pWeapon = dynamic_cast(info.GetWeapon()); if ( pWeapon ) { pWeapon->OnPlayerKill( pTFVictim, info ); int iCritBoost = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iCritBoost, add_onkill_critboost_time ); if ( iCritBoost ) { // Perceptually, people seem to think the effect is shorter than the stated time, so we cheat by adding a tad more for that m_Shared.AddCond( TF_COND_CRITBOOSTED_ON_KILL, iCritBoost+1 ); } int iMiniCritBoost = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iMiniCritBoost, add_onkill_minicritboost_time ); if ( iMiniCritBoost ) { // Perceptually, people seem to think the effect is shorter than the stated time, so we cheat by adding a tad more for that m_Shared.AddCond( TF_COND_ENERGY_BUFF, iMiniCritBoost + 1 ); } } // Check for CP_Foundry achievements if ( FStrEq( "cp_foundry", STRING( gpGlobals->mapname ) ) ) { if ( pTFVictim && ( pTFVictim->GetTeamNumber() != GetTeamNumber() ) ) { if ( pTFVictim->IsCapturingPoint() ) { if ( info.GetDamageType() & DMG_CRITICAL ) { AwardAchievement( ACHIEVEMENT_TF_MAPS_FOUNDRY_KILL_CAPPING_ENEMY ); } } if ( InAchievementZone( pTFVictim ) ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_killed_achievement_zone" ); if ( event ) { event->SetInt( "attacker", entindex() ); event->SetInt( "victim", pTFVictim->entindex() ); gameeventmanager->FireEvent( event ); } } } } // Check for SD_Doomsday achievements if ( FStrEq( "sd_doomsday", STRING( gpGlobals->mapname ) ) ) { if ( pTFVictim && ( pTFVictim->GetTeamNumber() != GetTeamNumber() ) ) { // find the flag in the map CCaptureFlag *pFlag = NULL; for ( int i=0; i( ICaptureFlagAutoList::AutoList()[i] ); if ( !pFlag->IsDisabled() ) { break; } } // was the victim in an achievement zone? CAchievementZone *pZone = InAchievementZone( pTFVictim ); if ( pZone ) { int iZoneID = pZone->GetZoneID(); if ( iZoneID == 0 ) { if ( pFlag && pFlag->IsHome() ) { AwardAchievement( ACHIEVEMENT_TF_MAPS_DOOMSDAY_DENY_NEUTRAL_PICKUP ); } } else { IGameEvent *event = gameeventmanager->CreateEvent( "player_killed_achievement_zone" ); if ( event ) { event->SetInt( "attacker", entindex() ); event->SetInt( "victim", pTFVictim->entindex() ); event->SetInt( "zone_id", iZoneID ); gameeventmanager->FireEvent( event ); } } } // check the flag carrier to see if the victim has recently damaged them if ( pFlag && pFlag->IsStolen() ) { CTFPlayer *pFlagCarrier = ToTFPlayer( pFlag->GetOwnerEntity() ); if ( pFlagCarrier && ( pFlagCarrier->GetTeamNumber() == GetTeamNumber() ) ) { // has the victim damaged the flag carrier in the last 3 seconds? if ( pFlagCarrier->m_AchievementData.IsDamagerInHistory( pTFVictim, 3.0 ) ) { AwardAchievement( ACHIEVEMENT_TF_MAPS_DOOMSDAY_DEFEND_CARRIER ); } } } } } // Check for CP_Snakewater achievement if ( FStrEq( "cp_snakewater_final1", STRING( gpGlobals->mapname ) ) ) { if ( pTFVictim && ( pTFVictim->GetTeamNumber() != GetTeamNumber() ) ) { if ( InAchievementZone( pTFVictim ) ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_killed_achievement_zone" ); if ( event ) { event->SetInt( "attacker", entindex() ); event->SetInt( "victim", pTFVictim->entindex() ); gameeventmanager->FireEvent( event ); } } } } if ( IsPlayerClass( TF_CLASS_DEMOMAN ) ) { if ( pVictim->GetTeamNumber() != GetTeamNumber() ) { // Check if this kill should refill the charge meter CTFWeaponBase *pWeapon = dynamic_cast(info.GetWeapon()); float flRefill = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flRefill, kill_refills_meter ); if ( m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT ) // Knockout powerup restricts charge { flRefill *= 0.2; } if ( flRefill > 0 && ((info.GetDamageType() & DMG_MELEE) || ( info.GetDamageCustom() == TF_DMG_CUSTOM_CHARGE_IMPACT ) ) ) { m_Shared.SetDemomanChargeMeter( m_Shared.GetDemomanChargeMeter() + flRefill * 100.0f ); } if ( ( pWeapon && pWeapon->IsCurrentAttackDuringDemoCharge() ) || ( info.GetDamageCustom() == TF_DMG_CUSTOM_CHARGE_IMPACT ) ) { if ( flRefill > 0 ) { IGameEvent *event = gameeventmanager->CreateEvent( "kill_refills_meter" ); if ( event ) { event->SetInt( "index", entindex() ); gameeventmanager->FireEvent( event ); } } if ( pTFVictim ) { // could the attacker see this player when the charge started? if ( m_Shared.m_hPlayersVisibleAtChargeStart.Find( pTFVictim ) == m_Shared.m_hPlayersVisibleAtChargeStart.InvalidIndex() ) { AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_PLAYER_YOU_DIDNT_SEE ); } } } // Demoman achievement: Kill at least 3 players capping or pushing the cart with the same detonation CTriggerAreaCapture *pAreaTrigger = pTFVictim->GetControlPointStandingOn(); if ( pAreaTrigger ) { CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint(); if ( pCP ) { if ( pCP->GetOwner() == GetTeamNumber() ) { if ( GetActiveTFWeapon() && ( GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_PIPEBOMBLAUNCHER ) ) { // Add victim to our list int iIndex = m_Cappers.Find( pTFVictim->GetUserID() ); if ( iIndex != m_Cappers.InvalidIndex() ) { // they're already in our list m_Cappers[iIndex] = gpGlobals->curtime; } else { // we need to add them m_Cappers.Insert( pTFVictim->GetUserID(), gpGlobals->curtime ); } // Did we get three? if ( m_Cappers.Count() >= 3 ) { // Traverse the list, comparing the recorded time to curtime int iHitCount = 0; FOR_EACH_MAP_FAST ( m_Cappers, cIndex ) { // For each match, increment counter if ( gpGlobals->curtime <= m_Cappers[cIndex] + 0.1f ) { iHitCount++; } else { m_Cappers.Remove( cIndex ); } // If we hit 3, award and purge the group if ( iHitCount >= 3 ) { AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_X_CAPPING_ONEDET ); m_Cappers.RemoveAll(); } } } } } // Kill players defending "x" times else { // If we're able to cap the point... if ( TeamplayGameRules()->TeamMayCapturePoint( GetTeamNumber(), pCP->GetPointIndex() ) && TeamplayGameRules()->PlayerMayCapturePoint( this, pCP->GetPointIndex() ) ) { AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_X_DEFENDING ); } } } } } } // Sniper Kill Rage if ( IsPlayerClass( TF_CLASS_SNIPER ) ) { // Item attribute // Add Sniper Rage On Kills float flRageGain = 0; CALL_ATTRIB_HOOK_FLOAT( flRageGain, rage_on_kill ); if (flRageGain != 0) { m_Shared.ModifyRage(flRageGain); } } for ( int i=0; i= 5 && m_Shared.m_aHealers[i].bDispenserHeal ) { // We got five kills while being healed by this dispenser. Reward the engineer with an achievement! CTFPlayer *pHealScorer = ToTFPlayer( m_Shared.m_aHealers[i].pHealScorer ); if ( pHealScorer && pHealScorer->IsPlayerClass( TF_CLASS_ENGINEER ) ) { pHealScorer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_HEAVY_ASSIST ); } } } } OnKilledOther_Effects( pVictim, info ); // track accumulated sentry gun kills on owning player for Sentry Busters in MvM (so they can't clear this by rebuilding their sentry) CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() ); CTFProjectile_SentryRocket *sentryRocket = dynamic_cast< CTFProjectile_SentryRocket * >( info.GetInflictor() ); if ( ( sentry && !sentry->IsDisposableBuilding() ) || sentryRocket ) { IncrementSentryGunKillCount(); } // Halloween Death Ghosts // Check the weapon I used to kill with this player and if it has my desired attribute if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) ) { int iHalloweenDeathGhosts = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iHalloweenDeathGhosts, halloween_death_ghosts ); if ( iHalloweenDeathGhosts > 0 ) { if ( pTFVictim->GetTeam()->GetTeamNumber() == TF_TEAM_BLUE ) { DispatchParticleEffect( "halloween_player_death_blue", pTFVictim->GetAbsOrigin() + Vector( 0, 0, 32 ), vec3_angle ); } else if ( pTFVictim->GetTeam()->GetTeamNumber() == TF_TEAM_RED ) { DispatchParticleEffect( "halloween_player_death", pTFVictim->GetAbsOrigin() + Vector( 0, 0, 32 ), vec3_angle ); } } } DropDeathCallingCard( this, pTFVictim ); if ( pTFVictim != this ) { for ( int i=0; i( GetWearable(i) ); if ( pItem ) { pItem->IncrementLevel(); } } } if ( pTFVictim ) { // was the victim on a control point (includes payload carts) CTriggerAreaCapture *pAreaTrigger = pTFVictim->GetControlPointStandingOn(); if ( pAreaTrigger ) { CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint(); if ( pCP && ( pCP->GetOwner() != pTFVictim->GetTeamNumber() ) ) { if ( TeamplayGameRules()->TeamMayCapturePoint( pTFVictim->GetTeamNumber(), pCP->GetPointIndex() ) && TeamplayGameRules()->PlayerMayCapturePoint( pTFVictim, pCP->GetPointIndex() ) ) { CTFPlayer *pTFAssister = NULL; if ( TFGameRules() ) { pTFAssister = ToTFPlayer( TFGameRules()->GetAssister( pTFVictim, this, info.GetInflictor() ) ); } IGameEvent *event = gameeventmanager->CreateEvent( "killed_capping_player" ); if ( event ) { event->SetInt( "cp", pCP->GetPointIndex() ); event->SetInt( "killer", entindex() ); event->SetInt( "victim", pTFVictim->entindex() ); event->SetInt( "assister", pTFAssister ? pTFAssister->entindex() : -1 ); event->SetInt( "priority", 9 ); gameeventmanager->FireEvent( event ); } } } } } } else { if ( pVictim->IsBaseObject() ) { CBaseObject *pObject = dynamic_cast( pVictim ); SpeakConceptIfAllowed( MP_CONCEPT_KILLED_OBJECT, pObject->GetResponseRulesModifier() ); } } } //----------------------------------------------------------------------------- // Purpose: Called on kill for primary and second-highest damage dealer //----------------------------------------------------------------------------- void CTFPlayer::OnKilledOther_Effects( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { int iHealOnKill = 0; if ( IsPlayerClass( TF_CLASS_SPY ) ) { int iCloakOnKill = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( GetActiveWeapon(), iCloakOnKill, add_cloak_on_kill ); if ( iCloakOnKill > 0 ) { m_Shared.AddToSpyCloakMeter( iCloakOnKill, true ); } } CTFWeaponBase *pWeapon = dynamic_cast( info.GetWeapon() ); if ( !pWeapon ) return; int iRestoreHealthToPercentageOnKill = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iRestoreHealthToPercentageOnKill, restore_health_on_kill ); if ( iRestoreHealthToPercentageOnKill > 0 ) { // This attribute should ignore runes int iRestoreMax = GetMaxHealth() - GetRuneHealthBonus(); // We add one here to deal with a bizarre problem that comes up leaving you one health short sometimes // due to bizarre floating point rounding or something equally silly. int iTargetHealth = ( int )( ( ( float )iRestoreHealthToPercentageOnKill / 100.0f ) * ( float )iRestoreMax ) + 1; int iBaseMaxHealth = GetMaxHealth() * 1.5, iNewHealth = Min( GetHealth() + iTargetHealth, iBaseMaxHealth ), iDeltaHealth = Max(iNewHealth - GetHealth(), 0); TakeHealth( iDeltaHealth, DMG_IGNORE_MAXHEALTH ); } CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iHealOnKill, heal_on_kill ); if ( iHealOnKill != 0 ) { int iHealthToAdd = MIN( iHealOnKill, m_Shared.GetMaxBuffedHealth() - m_iHealth ); TakeHealth( iHealthToAdd, DMG_GENERIC ); //m_iHealth += iHealthToAdd; IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" ); if ( event ) { event->SetInt( "amount", iHealthToAdd ); event->SetInt( "entindex", entindex() ); item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX; if ( pWeapon->GetAttributeContainer() && pWeapon->GetAttributeContainer()->GetItem() ) { healingItemDef = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex(); } event->SetInt( "weapon_def_index", healingItemDef ); gameeventmanager->FireEvent( event ); } } int iSpeedBoostOnKill = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iSpeedBoostOnKill, speed_boost_on_kill ); if ( iSpeedBoostOnKill ) { m_Shared.AddCond( TF_COND_SPEED_BOOST, iSpeedBoostOnKill ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Event_Killed( const CTakeDamageInfo &info ) { CTFPlayer *pPlayerAttacker = NULL; if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) { pPlayerAttacker = ToTFPlayer( info.GetAttacker() ); } CTFWeaponBase *pKillerWeapon = NULL; if ( pPlayerAttacker ) { pKillerWeapon = dynamic_cast < CTFWeaponBase * > ( info.GetWeapon() ); } if ( m_Shared.InCond( TF_COND_TAUNTING ) ) { static CSchemaItemDefHandle dosidoTaunt( "Square Dance Taunt" ); static CSchemaItemDefHandle congaTaunt( "Conga Taunt" ); if ( GetTauntEconItemView() ) { if ( GetTauntEconItemView()->GetItemDefinition() == dosidoTaunt ) { if ( pKillerWeapon && ( pKillerWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_MELEE ) ) { if ( pPlayerAttacker ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_TAUNT_DOSIDO_MELLE_KILL ); } } } else if ( GetTauntEconItemView()->GetItemDefinition() == congaTaunt ) { if ( pPlayerAttacker ) { IGameEvent *event = gameeventmanager->CreateEvent( "conga_kill" ); if ( event ) { event->SetInt( "index", pPlayerAttacker->entindex() ); gameeventmanager->FireEvent( event ); } } } } StopTaunt(); } // Cheat this death! if ( m_Shared.InCond( TF_COND_HALLOWEEN_IN_HELL ) ) { // Turn into a ghost m_Shared.RemoveAllCond(); m_Shared.AddCond( TF_COND_HALLOWEEN_GHOST_MODE ); // Create a puff right where we died to mask the ghost spawning in DispatchParticleEffect( "ghost_appearation", PATTACH_ABSORIGIN, this ); // Check for achievement if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TRIGGER_HURT ) { if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 1, 5.0 ); if ( pRecentDamager ) { pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_ENVIRONMENTAL_KILLS ); } } } CTakeDamageInfo ghostinfo = info; // If we were killed by "the world", then give credit to the next damager in the list CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 1, 10.0 ); // If killed by trigger hurt, get last attacker if ( info.GetAttacker() == info.GetInflictor() && info.GetAttacker() && info.GetAttacker()->IsBSPModel() ) { if ( pRecentDamager ) { ghostinfo.SetAttacker( pRecentDamager ); if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) { ghostinfo.SetDamageCustom( TF_DMG_CUSTOM_KART ); pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_DOOMSDAY_KILL_KARTS ); HatAndMiscEconEntities_OnOwnerKillEaterEvent( pRecentDamager, this, kKillEaterEvent_Halloween_UnderworldKills ); } } // if no recent damager, check for HHH else if ( m_flHHHKartAttackTime > gpGlobals->curtime - 15.0f ) { ghostinfo.SetDamageCustom( TF_DMG_CUSTOM_DECAPITATION_BOSS ); } } if ( pRecentDamager ) { // Score the "kill". We don't want any of the other logic, so short circuit here. pRecentDamager->Event_KilledOther( this, ghostinfo ); IGameEvent *pEvent = gameeventmanager->CreateEvent( "kill_in_hell" ); if ( pEvent ) { pEvent->SetInt( "killer", pRecentDamager->GetUserID() ); pEvent->SetInt( "victim", GetUserID() ); gameeventmanager->FireEvent( pEvent, true ); } } FeignDeath( ghostinfo ); // Have 1 HP m_iHealth = 1; return; } SpeakConceptIfAllowed( MP_CONCEPT_DIED ); StateTransition( TF_STATE_DYING ); // Transition into the dying state. if ( pPlayerAttacker ) { if ( TFGameRules()->IsIT( this ) ) { // I was IT - transfer to my killer TFGameRules()->SetIT( pPlayerAttacker ); } if ( pPlayerAttacker != this ) { if ( CTFPlayerDestructionLogic::GetRobotDestructionLogic() && ( CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() == CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION ) ) { // was this the team leader? if ( CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetTeamLeader( GetTeamNumber() ) == this ) { IGameEvent * event = gameeventmanager->CreateEvent( "team_leader_killed" ); if ( event ) { event->SetInt( "killer", pPlayerAttacker->entindex() ); event->SetInt( "victim", entindex() ); gameeventmanager->FireEvent( event ); } } } } } m_bIsTeleportingUsingEurekaEffect = false; for ( int i=0; i( GetWearable(i) ); if ( pItem ) { pItem->ResetLevel(); } } /* // We're going to save this for a future date if ( pPlayerAttacker ) { if ( pPlayerAttacker != this ) { // Killed by another player if ( ( TFGameRules()->GetBirthdayPlayer() == this ) || ( TFGameRules()->GetBirthdayPlayer() == NULL ) ) { // I was the birthday player (or we don't have one) - transfer to my killer TFGameRules()->SetBirthdayPlayer( pPlayerAttacker ); } } else { // Suicide if ( TFGameRules()->GetBirthdayPlayer() == this ) { // I was the birthday player - reset for suicide TFGameRules()->SetBirthdayPlayer( NULL ); } } } */ bool bOnGround = GetFlags() & FL_ONGROUND; bool bElectrocuted = false; bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED ); // we want the rag doll to burn if the player was burning and was not a pyro (who only burns momentarily) bool bBurning = m_Shared.InCond( TF_COND_BURNING ) && ( TF_CLASS_PYRO != GetPlayerClass()->GetClassIndex() ); CTFPlayer *pOriginalBurner = m_Shared.GetOriginalBurnAttacker(); CTFPlayer *pLastBurner = m_Shared.GetBurnAttacker(); if ( m_aBurnFromBackAttackers.Count() > 0 ) { CTFWeaponBase *pWeapon = dynamic_cast(info.GetWeapon()); if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_FLAMETHROWER ) { for ( int i = 0; i < m_aBurnFromBackAttackers.Count(); i++ ) { CTFPlayer *pBurner = ToTFPlayer( m_aBurnFromBackAttackers[i].Get() ); if ( pBurner ) { pBurner->AwardAchievement( ACHIEVEMENT_TF_PYRO_KILL_FROM_BEHIND ); } } } ClearBurnFromBehindAttackers(); } if ( IsPlayerClass( TF_CLASS_MEDIC ) ) { CWeaponMedigun* pMedigun = assert_cast( Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) ); float flChargeLevel = pMedigun ? pMedigun->GetChargeLevel() : 0.f; float flMinChargeLevel = pMedigun ? pMedigun->GetMinChargeAmount() : 1.f; bool bCharged = flChargeLevel >= flMinChargeLevel; if ( bCharged ) { // Had an ubercharge ready at death? CEconEntity *pVictimEconWeapon = dynamic_cast( GetActiveTFWeapon() ); EconEntity_OnOwnerKillEaterEventNoPartner( pVictimEconWeapon, this, kKillEaterEvent_NEGATIVE_UbersDropped ); bElectrocuted = true; if ( pPlayerAttacker ) { if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_SCOUT ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SCOUT_KILL_CHARGED_MEDICS ); } else if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_SNIPER ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_KILL_CHARGED_MEDIC ); } else if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_SPY ) ) { if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SPY_BACKSTAB_MEDIC_CHARGED ); } } CTF_GameStats.Event_PlayerAwardBonusPoints( pPlayerAttacker, this, 20 ); } } // Disable radius healing m_Shared.Heal_Radius( false ); IGameEvent * event = gameeventmanager->CreateEvent( "medic_death" ); if ( event ) { int iHealing = 0; PlayerStats_t *pPlayerStats = CTF_GameStats.FindPlayerStats( this ); if ( pPlayerStats ) { iHealing = pPlayerStats->statsCurrentLife.m_iStat[TFSTAT_HEALING]; // defensive fix for the moment for bug where healing value becomes bogus sometimes: if bogus, slam it to 0 // ...copied from CTFGameRules::CalcPlayerScore() if ( iHealing < 0 || iHealing > 10000000 ) { iHealing = 0; } } event->SetInt( "userid", GetUserID() ); event->SetInt( "attacker", pPlayerAttacker ? pPlayerAttacker->GetUserID() : 0 ); event->SetInt( "healing", iHealing ); event->SetBool( "charged", bCharged ); gameeventmanager->FireEvent( event ); } } else if ( IsPlayerClass( TF_CLASS_SOLDIER ) || IsPlayerClass( TF_CLASS_DEMOMAN ) ) { if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_SNIPER ) && RocketJumped() && !GetGroundEntity() ) { if ( pKillerWeapon ) { if ( WeaponID_IsSniperRifleOrBow( pKillerWeapon->GetWeaponID() ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_KILL_RJER ); } if ( pKillerWeapon->GetWeaponID() == TF_WEAPON_SNIPERRIFLE_CLASSIC ) { if ( ( info.GetDamageCustom() == TF_DMG_CUSTOM_HEADSHOT ) && ( info.GetDamageType() & DMG_CRITICAL ) ) { if ( pPlayerAttacker->m_Shared.IsAiming() == false ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_CLASSIC_RIFLE_HEADSHOT_JUMPER ); } } } } } } else if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) { if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_SOLDIER ) ) { // Has Engineer worked on his sentrygun recently? CBaseObject *pSentry = GetObjectOfType( OBJ_SENTRYGUN ); if ( pSentry && m_AchievementData.IsTargetInHistory( pSentry, 4.0 ) ) { if ( pSentry->m_AchievementData.CountDamagersWithinTime( 3.0 ) > 0 ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_KILL_ENGY ); } } } if ( m_Shared.IsCarryingObject() ) { CTakeDamageInfo info( pPlayerAttacker, pPlayerAttacker, NULL, vec3_origin, GetAbsOrigin(), 0, DMG_GENERIC ); info.SetDamageCustom( TF_DMG_CUSTOM_CARRIED_BUILDING ); if ( m_Shared.GetCarriedObject() != NULL ) { m_Shared.GetCarriedObject()->Killed( info ); // Killeater event for being killed while carrying a building CEconEntity *pVictimEconWeapon = dynamic_cast( Weapon_OwnsThisID( TF_WEAPON_WRENCH ) ); EconEntity_OnOwnerKillEaterEventNoPartner( pVictimEconWeapon, this, kKillEaterEvent_NEGATIVE_DeathsWhileCarryingBuilding ); } } } else if ( IsPlayerClass( TF_CLASS_SNIPER ) ) { if ( pPlayerAttacker ) { if ( GetActiveTFWeapon() && ( GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_SNIPERRIFLE_CLASSIC ) ) { if ( pKillerWeapon && ( pKillerWeapon->GetTFWpnData().m_iWeaponType == TF_WPN_TYPE_MELEE ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_MELEE_KILL_CLASSIC_RIFLE_SNIPER ); } } } } if ( pPlayerAttacker ) { if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_SOLDIER ) ) { if ( pPlayerAttacker->RocketJumped() || (gpGlobals->curtime - pPlayerAttacker->m_flBlastJumpLandTime) < 1 ) { if ( pKillerWeapon && pKillerWeapon->GetWeaponID() == TF_WEAPON_SHOVEL ) { CTFShovel *pShovel = static_cast< CTFShovel* >( pKillerWeapon ); if ( pShovel && pShovel->GetShovelType() == SHOVEL_DAMAGE_BOOST ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_RJ_EQUALIZER_KILL ); } } } } else if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_SNIPER ) ) { if ( pKillerWeapon && WeaponID_IsSniperRifle( pKillerWeapon->GetWeaponID() ) && pPlayerAttacker->m_Shared.IsAiming() == false ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_KILL_UNSCOPED ); } if ( pKillerWeapon && ( pKillerWeapon->GetWeaponID() == TF_WEAPON_SNIPERRIFLE_CLASSIC ) ) { if ( ( info.GetDamageCustom() == TF_DMG_CUSTOM_HEADSHOT ) && ( info.GetDamageType() & DMG_CRITICAL ) ) { if ( pPlayerAttacker->m_Shared.IsAiming() == false ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_CLASSIC_RIFLE_NOSCOPE_HEADSHOT ); } } } } else if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_SPY ) ) { #ifdef STAGING_ONLY // Move to Killed Other // Spy Tranq Buff if ( m_Shared.InCond( TF_COND_TRANQ_MARKED ) && info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB ) { int iTranq = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayerAttacker, iTranq, override_projectile_type ); if ( iTranq == TF_PROJECTILE_TRANQ ) { // BIGGEST HACK EVER int iDesiredClass = GetPlayerClass()->GetClassIndex(); if ( iDesiredClass != TF_CLASS_SPY ) { pPlayerAttacker->GetPlayerClass()->Init( iDesiredClass ); for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)pPlayerAttacker->GetWeapon( i ); if ( pWeapon ) { pWeapon->OnOwnerClassChange(); } } pPlayerAttacker->RemoveAllItems( true ); // TODO: move this into conditions pPlayerAttacker->RemoveTeleportEffect(); // remove invisibility very quickly pPlayerAttacker->m_Shared.FadeInvis( 0.1f ); // Stop any firing that was taking place before respawn. pPlayerAttacker->m_nButtons = 0; // Possibly Save and set their health percentage here Vector vAttackerPos = pPlayerAttacker->GetAbsOrigin(); QAngle qAttackerAngle = pPlayerAttacker->GetAbsAngles(); pPlayerAttacker->StateTransition( TF_STATE_ACTIVE ); pPlayerAttacker->Spawn(); pPlayerAttacker->Teleport( &vAttackerPos, &qAttackerAngle, &vec3_origin ); pPlayerAttacker->m_Shared.AddCond( TF_COND_SPY_CLASS_STEAL ); // Overheal pPlayerAttacker->SetHealth( pPlayerAttacker->GetMaxHealth() * 1.5f ); // Steal their uber if ( IsPlayerClass( TF_CLASS_MEDIC ) ) { // Steal Enemy Uber CWeaponMedigun *pMedigun = (CWeaponMedigun *)Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ); if ( pMedigun ) { float flCharge = pMedigun->GetChargeLevel(); CWeaponMedigun *pAttackerMedigun = (CWeaponMedigun *)pPlayerAttacker->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ); if ( pAttackerMedigun ) { pAttackerMedigun->AddCharge( flCharge ); } } } // Steal Rage pPlayerAttacker->m_Shared.SetRageMeter( m_Shared.GetRageMeter() ); // Steal heads pPlayerAttacker->m_Shared.SetDecapitations( m_Shared.GetDecapitations() ); // Effects //pPlayerAttacker->EmitSound( "Player.Spy_Disguise" ); pPlayerAttacker->EmitSound( "WeaponDNAGun.Transform" ); Vector vOrigin = pPlayerAttacker->GetAbsOrigin(); CPVSFilter filter( vOrigin ); switch ( pPlayerAttacker->GetTeamNumber() ) { case TF_TEAM_RED: TE_TFParticleEffect( filter, 0.0, "teleported_red", vOrigin, vec3_angle ); TE_TFParticleEffect( filter, 0.0, "player_sparkles_red", vOrigin, vec3_angle, pPlayerAttacker, PATTACH_POINT ); break; case TF_TEAM_BLUE: TE_TFParticleEffect( filter, 0.0, "teleported_blue", vOrigin, vec3_angle ); TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", vOrigin, vec3_angle, pPlayerAttacker, PATTACH_POINT ); break; default: break; } } } } #endif // STAGING_ONLY CTriggerAreaCapture *pAreaTrigger = GetControlPointStandingOn(); if ( pAreaTrigger ) { CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint(); if ( pCP ) { if ( pCP->GetOwner() == GetTeamNumber() ) { // killed on a control point owned by my team pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SPY_KILL_CP_DEFENDERS ); } else { // killed on a control point NOT owned by my team, was it a backstab? if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB ) { // was i able to capture the control point? if ( TeamplayGameRules()->TeamMayCapturePoint( GetTeamNumber(), pCP->GetPointIndex() ) && TeamplayGameRules()->PlayerMayCapturePoint( this, pCP->GetPointIndex() ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SPY_BACKSTAB_CAPPING_ENEMIES ); } } } } } if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) { //m_AchievementData.CountTargetsWithinTime int iHistory = 0; EntityHistory_t *pHistory = m_AchievementData.GetTargetHistory( iHistory ); while ( pHistory ) { if ( pHistory->hEntity && pHistory->hEntity->IsBaseObject() && m_AchievementData.IsTargetInHistory( pHistory->hEntity, 1.0f ) ) { CBaseObject *pObject = dynamic_cast( pHistory->hEntity.Get() ); if ( pObject->ObjectType() == OBJ_SENTRYGUN ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SPY_KILL_WORKING_ENGY ); break; } } iHistory++; pHistory = m_AchievementData.GetTargetHistory( iHistory ); } } } else if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_DEMOMAN ) ) { // Kill "x" players with a direct pipebomb hit if ( pPlayerAttacker->GetActiveTFWeapon() && ( pPlayerAttacker->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRENADELAUNCHER ) ) { CBaseEntity *pInflictor = info.GetInflictor(); if ( pInflictor && pInflictor->IsPlayer() == false ) { CTFGrenadePipebombProjectile *pBaseGrenade = dynamic_cast< CTFGrenadePipebombProjectile* >( pInflictor ); if ( pBaseGrenade && pBaseGrenade->m_bTouched != true ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_X_WITH_DIRECTPIPE ); } } } } else if ( pPlayerAttacker->IsPlayerClass( TF_CLASS_ENGINEER ) ) { // give achievement for killing someone who was recently damaged by our sentry // note that we don't check to see if the sentry is still alive if ( pKillerWeapon && ( pKillerWeapon->GetWeaponID() == TF_WEAPON_SENTRY_REVENGE || pKillerWeapon->GetWeaponID() == TF_WEAPON_SHOTGUN_PRIMARY ) ) { if ( m_AchievementData.IsSentryDamagerInHistory( pPlayerAttacker, 5.0 ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_SHOTGUN_KILL_PREV_SENTRY_TARGET ); } } } // Revenge Crits for Diamondback if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB ) { pPlayerAttacker->m_Shared.IncrementRevengeCrits(); } } // Check for CP_Foundry achievement if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TRIGGER_HURT ) { if ( FStrEq( "cp_foundry", STRING( gpGlobals->mapname ) ) ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 1, 5.0 ); if ( pRecentDamager ) { pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_MAPS_FOUNDRY_PUSH_INTO_CAULDRON ); } } } // Record if we were stunned for achievement tracking. m_iOldStunFlags = m_Shared.GetStunFlags(); // Determine the optional assist for the kill. DetermineAssistForKill( info ); // put here to stop looping kritz sound from playing til respawn. if ( m_Shared.InCond( TF_COND_CRITBOOSTED ) ) { StopSound( "TFPlayer.CritBoostOn" ); } if ( m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ) { SetPendingMerasmusPlayerBombExplode(); } // check for MvM achievements if ( TFGameRules()->IsMannVsMachineMode() && IsBot() ) { if ( pPlayerAttacker && ( pPlayerAttacker->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) ) { if ( FStrEq( "mvm_mannhattan", STRING( gpGlobals->mapname ) ) ) { CTFBot *pBot = dynamic_cast< CTFBot* >( this ); if ( pBot ) { // kill gate bots if ( pBot->HasTag( "bot_gatebot" ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_MVM_MAPS_MANNHATTAN_BOMB_BOT_GRIND ); } } // kill stunned bots if ( m_Shared.InCond( TF_COND_MVM_BOT_STUN_RADIOWAVE ) ) { if ( g_pPopulationManager->IsAdvancedPopFile() ) { IGameEvent *event = gameeventmanager->CreateEvent( "mvm_adv_wave_killed_stun_radio" ); if ( event ) { gameeventmanager->FireEvent( event ); } } } } } } // Reset our model if we were disguised if ( bDisguised ) { UpdateModel(); } RemoveTeleportEffect(); // Drop a pack with their leftover ammo // Arena: Only do this if the match hasn't started yet. if ( ShouldDropAmmoPack() ) { DropAmmoPack( info, false, false ); } if ( TFGameRules()->IsInMedievalMode() ) { DropHealthPack( info, true ); } #ifdef TF_RAID_MODE // Bots sometimes drop health kits in Raid Mode if ( TFGameRules()->IsRaidMode() && GetTeamNumber() == TF_TEAM_RED ) { if ( RandomInt( 1, 100 ) <= tf_raid_drop_healthkit_chance.GetInt() ) { DropHealthPack( info, true ); } } #endif // TF_RAID_MODE // PvE mode credits/currency if ( TFGameRules()->IsMannVsMachineMode() ) { MannVsMachineStats_PlayerEvent_Died( this ); if ( IsBot() ) { m_nCurrency = 0; if ( !IsMissionEnemy() && m_pWaveSpawnPopulator ) { m_nCurrency = m_pWaveSpawnPopulator->GetCurrencyAmountPerDeath(); } // only drop currency if the map designer has specified it if ( m_nCurrency > 0 ) { // We only drop a pack when the game's accumulated enough to make it worth it int nDropAmount = TFGameRules()->CalculateCurrencyAmount_CustomPack( m_nCurrency ); if ( nDropAmount ) { bool bDropPack = true; // Give money directly to the team if a trigger killed us if ( info.GetDamageType() ) { CBaseTrigger *pTrigger = dynamic_cast< CBaseTrigger *>( info.GetInflictor() ); if ( pTrigger ) { bDropPack = false; TFGameRules()->DistributeCurrencyAmount( nDropAmount, NULL, true, true ); } } if ( bDropPack ) { CTFPlayer* pMoneyMaker = NULL; if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_SNIPER ) ) { if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BLEEDING || ( pKillerWeapon && WeaponID_IsSniperRifleOrBow( pKillerWeapon->GetWeaponID() ) ) ) { pMoneyMaker = pPlayerAttacker; if ( IsHeadshot( info.GetDamageCustom() ) || ( LastHitGroup() == HITGROUP_HEAD && pKillerWeapon && pKillerWeapon->GetJarateTime() ) ) { IGameEvent *event = gameeventmanager->CreateEvent( "mvm_sniper_headshot_currency" ); if ( event ) { event->SetInt( "userid", pPlayerAttacker->GetUserID() ); event->SetInt( "currency", nDropAmount ); gameeventmanager->FireEvent( event ); } } } } int iForceDistributeCurrency = 0; CALL_ATTRIB_HOOK_INT( iForceDistributeCurrency, force_distribute_currency_on_death ); bool bForceDistribute = iForceDistributeCurrency != 0; // if I'm force to distribute currency, just give the credit to the attacker if ( !pMoneyMaker && bForceDistribute ) { pMoneyMaker = pPlayerAttacker; } DropCurrencyPack( TF_CURRENCY_PACK_CUSTOM, nDropAmount, bForceDistribute, pMoneyMaker ); } } } if ( !m_bIsSupportEnemy ) { unsigned int iFlags = m_bIsMissionEnemy ? MVM_CLASS_FLAG_MISSION : MVM_CLASS_FLAG_NORMAL; if ( IsMiniBoss() ) { iFlags |= MVM_CLASS_FLAG_MINIBOSS; } TFObjectiveResource()->DecrementMannVsMachineWaveClassCount( GetPlayerClass()->GetClassIconName(), iFlags ); } if ( m_bIsLimitedSupportEnemy ) { TFObjectiveResource()->DecrementMannVsMachineWaveClassCount( GetPlayerClass()->GetClassIconName(), MVM_CLASS_FLAG_SUPPORT_LIMITED ); } // Electrical effect whenever a bot dies CPVSFilter filter( WorldSpaceCenter() ); TE_TFParticleEffect( filter, 0.f, "bot_death", GetAbsOrigin(), vec3_angle ); } else { // Players lose money for dying RemoveCurrency( tf_mvm_death_penalty.GetInt() ); } // tell the population manager a player died // THIS MUST HAPPEN AFTER THE CURRENCY CALCULATION (ABOVE) // NOW THAT WE'RE CALCULATING CURRENCY ON-DEATH INSTEAD OF ON-SPAWN if ( g_pPopulationManager ) { g_pPopulationManager->OnPlayerKilled( this ); } if ( IsBot() && HasTheFlag() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { int nLevel = TFObjectiveResource()->GetFlagCarrierUpgradeLevel(); IGameEvent *event = gameeventmanager->CreateEvent( "mvm_bomb_carrier_killed" ); if ( event ) { event->SetInt( "level", nLevel ); gameeventmanager->FireEvent( event ); } } if ( !IsBot() && !m_hReviveMarker ) { m_hReviveMarker = CTFReviveMarker::Create( this ); } } // This system is designed to coarsely measure a player's skill in public pvp games. // UpdateSkillRatingData(); #ifdef STAGING_ONLY if ( TFGameRules()->IsBountyMode() ) { // Lose unspent currency on death? float flPenalty = tf_bountymode_currency_penalty_ondeath.GetFloat(); if ( flPenalty ) { int nAmount = GetCurrency(); if ( nAmount ) { nAmount *= ( 1.f - flPenalty ); SetCurrency( Max( nAmount, 0 ) ); } } if ( tf_bountymode_upgrades_wipeondeath.GetInt() ) { // Remove upgrade attributes from the player and their items if ( g_hUpgradeEntity ) { g_hUpgradeEntity->GrantOrRemoveAllUpgrades( this, true, false ); } // Remove the appropriate upgrade info from upgrade histories if ( g_pPopulationManager ) { g_pPopulationManager->RemovePlayerAndItemUpgradesFromHistory( this ); } } } #endif // STAGING_ONLY if ( pPlayerAttacker ) { int iDropHealthOnKill = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayerAttacker, iDropHealthOnKill, drop_health_pack_on_kill ); if ( iDropHealthOnKill == 1 ) { DropHealthPack( info, true ); } int iKillForcesAttackerToLaugh = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayerAttacker, iKillForcesAttackerToLaugh, kill_forces_attacker_to_laugh ); if ( iKillForcesAttackerToLaugh == 1 ) { // force yourself to laugh! pPlayerAttacker->Taunt( TAUNT_MISC_ITEM, MP_CONCEPT_TAUNT_LAUGH ); } } // If the player has a capture flag and was killed by another player, award that player a defense if ( HasItem() && pPlayerAttacker && ( pPlayerAttacker != this ) ) { CCaptureFlag *pCaptureFlag = dynamic_cast( GetItem() ); if ( pCaptureFlag ) { IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); if ( event ) { event->SetInt( "player", pPlayerAttacker->entindex() ); event->SetInt( "eventtype", TF_FLAGEVENT_DEFEND ); event->SetInt( "carrier", entindex() ); event->SetInt( "priority", 8 ); event->SetInt( "team", pCaptureFlag->GetTeamNumber() ); gameeventmanager->FireEvent( event ); } CTF_GameStats.Event_PlayerDefendedPoint( pPlayerAttacker ); if ( !CTFPlayerDestructionLogic::GetRobotDestructionLogic() || ( CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() != CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION ) ) { if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_SNIPER ) ) { CTFWeaponBase *pKillerWeapon = dynamic_cast < CTFWeaponBase * > ( info.GetWeapon() ); if ( pKillerWeapon && pKillerWeapon->GetWeaponID() == TF_WEAPON_COMPOUND_BOW ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_SNIPER_BOW_KILL_FLAGCARRIER ); } } // Handle the "you killed someone with the flag" event. We can't handle this with the usual block // in PlayerKilled() because by that point we've forgotten that we had the flag. EconEntity_OnOwnerKillEaterEvent( dynamic_cast( pKillerWeapon ), pPlayerAttacker, this, kKillEaterEvent_DefenderKill ); } } } CTFWeaponBase* pActiveWeapon = GetActiveTFWeapon(); if( pActiveWeapon ) { CEconEntity *pVictimEconWeapon = dynamic_cast( pActiveWeapon ); EconEntity_OnOwnerKillEaterEventNoPartner( pVictimEconWeapon, this, kKillEaterEvent_NEGATIVE_Deaths ); // Check if we died from environmental damage CBaseTrigger *pTrigger = dynamic_cast< CBaseTrigger *>( info.GetInflictor() ); if ( pTrigger ) { EconEntity_OnOwnerKillEaterEventNoPartner( pVictimEconWeapon, this, kKillEaterEvent_NEGATIVE_DeathsFromEnvironment ); } // Check if we died from fall damage if( info.GetDamageType() == DMG_FALL ) { EconEntity_OnOwnerKillEaterEventNoPartner( pVictimEconWeapon, this, kKillEaterEvent_NEGATIVE_DeathsFromCratering ); } } ClearZoomOwner(); m_vecLastDeathPosition = GetAbsOrigin(); CTakeDamageInfo info_modified = info; // Ragdoll, gib, or death animation. bool bRagdoll = true; bool bGib = false; // See if we should gib. if ( ShouldGib( info ) ) { bGib = true; bRagdoll = false; } else // See if we should play a custom death animation. { // If this was a rocket/grenade kill that didn't gib, exaggerated the blast force if ( ( info.GetDamageType() & DMG_BLAST ) != 0 ) { Vector vForceModifier = info.GetDamageForce(); vForceModifier.x *= 2.5; vForceModifier.y *= 2.5; vForceModifier.z *= 2; info_modified.SetDamageForce( vForceModifier ); } } if ( bElectrocuted && bGib ) { const char *pEffectName = ( GetTeamNumber() == TF_TEAM_RED ) ? "electrocuted_gibbed_red" : "electrocuted_gibbed_blue"; DispatchParticleEffect( pEffectName, GetAbsOrigin(), vec3_angle ); EmitSound( "TFPlayer.MedicChargedDeath" ); } SetGibbedOnLastDeath( bGib ); bool bIsMvMRobot = TFGameRules()->IsMannVsMachineMode() && IsBot(); if ( bGib && !bIsMvMRobot && IsPlayerClass( TF_CLASS_SCOUT ) && RandomInt( 1, 100 ) <= SCOUT_ADD_BIRD_ON_GIB_CHANCE ) { Vector vecPos = WorldSpaceCenter(); SpawnClientsideFlyingBird( vecPos ); } // show killer in death cam mode // chopped down version of SetObserverTarget without the team check if( pPlayerAttacker ) { // See if we were killed by a sentrygun. If so, look at that instead of the player if ( info.GetInflictor() && info.GetInflictor()->IsBaseObject() ) { // Catches the case where we're killed directly by the sentrygun (i.e. bullets) // Look at the sentrygun m_hObserverTarget.Set( info.GetInflictor() ); } // See if we were killed by a projectile emitted from a base object. The attacker // will still be the owner of that object, but we want the deathcam to point to the // object itself. else if ( info.GetInflictor() && info.GetInflictor()->GetOwnerEntity() && info.GetInflictor()->GetOwnerEntity()->IsBaseObject() ) { m_hObserverTarget.Set( info.GetInflictor()->GetOwnerEntity() ); } else { // Look at the player if ( m_Shared.InCond( TF_COND_HALLOWEEN_IN_HELL ) ) { m_hObserverTarget.Set( pPlayerAttacker ); } else { m_hObserverTarget.Set( info.GetAttacker() ); } } // reset fov to default SetFOV( this, 0 ); } else if ( info.GetAttacker() && info.GetAttacker()->IsBaseObject() ) { // Catches the case where we're killed by entities spawned by the sentrygun (i.e. rockets) // Look at the sentrygun. m_hObserverTarget.Set( info.GetAttacker() ); } else if ( info.GetAttacker() && TFGameRules()->GetActiveBoss() && info.GetAttacker()->entindex() == TFGameRules()->GetActiveBoss()->entindex() ) { // killed by the boss - look at him m_hObserverTarget.Set( info.GetAttacker() ); } else { m_hObserverTarget.Set( NULL ); } bool bSuicide = false; if ( info_modified.GetDamageCustom() == TF_DMG_CUSTOM_SUICIDE ) { bSuicide = true; // if this was suicide, recalculate attacker to see if we want to award the kill to a recent damager info_modified.SetAttacker( TFGameRules()->GetDeathScorer( info.GetAttacker(), info.GetInflictor(), this ) ); } else if ( info.GetAttacker() == this ) { bSuicide = true; // If we killed ourselves in non-suicide fashion, and we've been hurt lately, give that guy the kill. CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 0, 5.0 ); if ( pRecentDamager ) { info_modified.SetDamageCustom( TF_DMG_CUSTOM_SUICIDE ); info_modified.SetDamageType( DMG_GENERIC ); info_modified.SetAttacker( pRecentDamager ); info_modified.SetWeapon( NULL ); info_modified.SetInflictor( NULL ); } } else if ( info.GetAttacker() == info.GetInflictor() && info.GetAttacker() && info.GetAttacker()->IsBSPModel() ) { bSuicide = true; if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { // If we were killed by "the world", then give credit to the next damager in the list CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 1, 10.0 ); if ( pRecentDamager ) { //info_modified.SetDamageCustom( TF_DMG_CUSTOM_SUICIDE ); info_modified.SetDamageType( DMG_GENERIC ); info_modified.SetAttacker( pRecentDamager ); info_modified.SetWeapon( NULL ); info_modified.SetInflictor( NULL ); } } else { // If we were killed by "the world", then give credit to the next damager in the list CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 1, 5.0 ); if ( pRecentDamager ) { info_modified.SetDamageCustom( TF_DMG_CUSTOM_SUICIDE ); info_modified.SetDamageType( DMG_GENERIC ); info_modified.SetAttacker( pRecentDamager ); info_modified.SetWeapon( NULL ); info_modified.SetInflictor( NULL ); } else if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) && ( info_modified.GetDamageType() & DMG_CLUB ) ) { info_modified.SetDamageCustom( TF_DMG_CUSTOM_GIANT_HAMMER ); info_modified.SetDamageType( info_modified.GetDamageType() | DMG_CRITICAL ); } } } if ( pPlayerAttacker && pPlayerAttacker->m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) && !pPlayerAttacker->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) { info_modified.SetDamageCustom( TF_DMG_CUSTOM_SPELL_TINY ); if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_DOOMSDAY_TINY_SMASHER ); } } if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) { // Report Kill CTF_GameStats.Event_PowerUpModeDeath( pPlayerAttacker, this ); } // Drop your powerup rune when you die if ( m_Shared.IsCarryingRune() ) { int iTeam = GetEnemyTeam( GetTeamNumber() ); // Dead players drop opposing team colored powerups CTFRune::CreateRune( GetAbsOrigin(), m_Shared.GetCarryingRuneType(), iTeam, true, false ); } // in PD, player death adds points to the flag drop if ( CTFPlayerDestructionLogic::GetRobotDestructionLogic() && CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() == CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 0, 5.0 ); int pointsOnDeath = ( !bSuicide || pRecentDamager ) ? CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->GetPointsOnPlayerDeath() : 0; CCaptureFlag *pFlag = NULL; if ( HasItem() ) { pFlag = dynamic_cast( GetItem() ); } else { if ( pointsOnDeath && !PointInRespawnRoom( this, WorldSpaceCenter() ) ) { pFlag = CCaptureFlag::Create( GetAbsOrigin(), CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->GetPropModelName(), TF_FLAGTYPE_PLAYER_DESTRUCTION ); } } if ( pFlag ) { // don't add more point to the dropping flag if the player suicided if ( pointsOnDeath ) { pFlag->AddPointValue( pointsOnDeath ); } pFlag->Drop( this, true, true, true ); } } CTFPlayerResource *pResource = dynamic_cast( g_pPlayerResource ); if ( pResource ) { pResource->SetPlayerClassWhenKilled( entindex(), GetPlayerClass()->GetClassIndex() ); } BaseClass::Event_Killed( info_modified ); if ( !m_bSwitchedClass ) { SaveLastWeaponSlot(); } // Remove all items... RemoveAllItems( true ); for ( int iWeapon = 0; iWeapon < TF_PLAYER_WEAPON_COUNT; ++iWeapon ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon( iWeapon ); if ( pWeapon ) { pWeapon->WeaponReset(); } } if ( GetActiveWeapon() ) { m_iActiveWeaponTypePriorToDeath = GetActiveTFWeapon()->GetWeaponID(); if ( m_iActiveWeaponTypePriorToDeath == TF_WEAPON_BUILDER ) m_iActiveWeaponTypePriorToDeath = 0; GetActiveWeapon()->SendViewModelAnim( ACT_IDLE ); GetActiveWeapon()->Holster(); SetActiveWeapon( NULL ); } else { m_iActiveWeaponTypePriorToDeath = 0; } int iIceRagdoll = 0; CTFPlayer *pInflictor = ToTFPlayer( info.GetInflictor() ); if ( ( IsHeadshot( info.GetDamageCustom() ) ) && pPlayerAttacker ) { CTFWeaponBase *pWpn = ( CTFWeaponBase *) info.GetWeapon(); bool bBowShot = false; if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_COMPOUND_BOW ) { bBowShot = true; } CTF_GameStats.Event_Headshot( pPlayerAttacker, bBowShot ); } else if ( ( TF_DMG_CUSTOM_BACKSTAB == info.GetDamageCustom() ) && pInflictor ) { CTF_GameStats.Event_Backstab( pInflictor ); if ( pKillerWeapon ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pKillerWeapon, iIceRagdoll, freeze_backstab_victim ); } } bool bCloakedCorpse = false; if ( pKillerWeapon && pKillerWeapon->GetWeaponID() == TF_WEAPON_KNIFE ) { CTFKnife *pKnife = dynamic_cast( pKillerWeapon ); if ( pKnife && pKnife->ShouldDisguiseOnBackstab() ) { bCloakedCorpse = true; } } int iGoldRagdoll = 0; if ( pKillerWeapon ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pKillerWeapon, iGoldRagdoll, set_turn_to_gold ); } int iRagdollsBecomeAsh = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iRagdollsBecomeAsh, ragdolls_become_ash ); } int iRagdollsPlasmaEffect = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iRagdollsPlasmaEffect, ragdolls_plasma_effect ); } int iCustomDamage = info.GetDamageCustom(); if ( iRagdollsPlasmaEffect ) { iCustomDamage = TF_DMG_CUSTOM_PLASMA; } int iCritOnHardHit = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iCritOnHardHit, crit_on_hard_hit ); } // Create the ragdoll entity. if ( bGib || bRagdoll ) { CreateRagdollEntity( bGib, bBurning, bElectrocuted, bOnGround, bCloakedCorpse, iGoldRagdoll != 0, iIceRagdoll != 0, iRagdollsBecomeAsh != 0, iCustomDamage, ( iCritOnHardHit != 0 ) ); } #ifdef STAGING_ONLY // Spy Mark removal on others when killed // Only check if I have the spy marking gun // STAGING_SPY int iTranq = 0; CALL_ATTRIB_HOOK_INT( iTranq, override_projectile_type ); if ( iTranq == TF_PROJECTILE_TRANQ ) { CUtlVector< CTFPlayer * > playerVector; CollectPlayers( &playerVector, GetTeamNumber() == TF_TEAM_BLUE ? TF_TEAM_RED : TF_TEAM_BLUE, true ); FOR_EACH_VEC ( playerVector, i ) { if ( playerVector[i]->m_Shared.GetConditionProvider( TF_COND_TRANQ_MARKED ) == this ) { playerVector[i]->m_Shared.RemoveCond( TF_COND_TRANQ_MARKED ); } } } // If I was a spy cloned, give me instant respawn if ( m_Shared.InCond( TF_COND_SPY_CLASS_STEAL ) ) { m_flRespawnTimeOverride = 2.0f; } #endif // Remove all conditions... m_Shared.RemoveAllCond(); // Don't overflow the value for this. m_iHealth = 0; // If we died in sudden death and we're an engineer, explode our buildings if ( IsPlayerClass( TF_CLASS_ENGINEER ) && TFGameRules()->InStalemate() && TFGameRules()->IsInArenaMode() == false ) { for (int i = GetObjectCount()-1; i >= 0; i--) { CBaseObject *obj = GetObject(i); Assert( obj ); if ( obj ) { obj->DetonateObject(); } } } // Achievement checks if ( pPlayerAttacker ) { // ACHIEVEMENT_TF_MEDIC_KILL_HEALED_SPY - medic kills a spy he has been healing if ( IsPlayerClass( TF_CLASS_SPY ) && pPlayerAttacker->IsPlayerClass( TF_CLASS_MEDIC ) ) { // if we were killed by a medic, see if he healed us most recently for ( int i=0;iWeaponCount();i++ ) { CTFWeaponBase *pWpn = ( CTFWeaponBase *)pPlayerAttacker->GetWeapon( i ); if ( pWpn == NULL ) continue; if ( pWpn->GetWeaponID() == TF_WEAPON_MEDIGUN ) { CWeaponMedigun *pMedigun = dynamic_cast< CWeaponMedigun * >( pWpn ); if ( pMedigun ) { if ( pMedigun->GetMostRecentHealTarget() == this ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_MEDIC_KILL_HEALED_SPY ); } } } } } if ( bBurning && pPlayerAttacker->IsPlayerClass( TF_CLASS_PYRO ) ) { // ACHIEVEMENT_TF_PYRO_KILL_MULTIWEAPONS - Pyro kills previously ignited target with other weapon CTFWeaponBase *pWeapon = dynamic_cast(info.GetWeapon()); if ( ( pOriginalBurner == pPlayerAttacker || pLastBurner == pPlayerAttacker ) && pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SHOTGUN_PYRO ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_KILL_MULTIWEAPONS ); } // ACHIEVEMENT_TF_PYRO_KILL_TEAMWORK - Pyro kills an enemy previously ignited by another Pyro if ( pOriginalBurner != pPlayerAttacker ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_KILL_TEAMWORK ); } } } if ( TFGameRules()->IsMannVsMachineMode() ) { // Have teammates announce my death if ( GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) { // have the last player on the defenders speak the last_man_standing line CUtlVector< CTFPlayer * > playerVector; CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS, true ); if ( playerVector.Count() == 1 ) { CTFPlayer *pAlivePlayer = playerVector[0]; if ( pAlivePlayer ) { pAlivePlayer->SpeakConceptIfAllowed( MP_CONCEPT_MVM_LAST_MAN_STANDING ); } } else { if ( pPlayerAttacker && pPlayerAttacker->IsMiniBoss() ) { TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_GIANT_KILLED_TEAMMATE, TF_TEAM_PVE_DEFENDERS ); } TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_DEFENDER_DIED, TF_TEAM_PVE_DEFENDERS, CFmtStr( "victimclass:%s", g_aPlayerClassNames_NonLocalized[ GetPlayerClass()->GetClassIndex() ] ).Access() ); } } else { if ( IsMiniBoss() ) { TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_GIANT_KILLED, TF_TEAM_PVE_DEFENDERS ); } } } // Reset Streaks to zero m_Shared.ResetStreaks(); for ( int i = 0; i < WeaponCount(); i++) { CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon(i); if ( !pWpn ) continue; pWpn->SetKillStreak( 0 ); } for ( int i = 0; i < GetNumWearables(); ++i ) { CTFWearable* pWearable = dynamic_cast( GetWearable(i) ); if ( !pWearable ) continue; pWearable->SetKillStreak( 0 ); } // Is the player inside a respawn time override volume? // don't do this for MvM bots if ( !TFGameRules()->IsMannVsMachineMode() || !IsBot() ) { FOR_EACH_VEC( ITriggerPlayerRespawnOverride::AutoList(), i ) { CTriggerPlayerRespawnOverride *pTriggerRespawn = static_cast< CTriggerPlayerRespawnOverride* >( ITriggerPlayerRespawnOverride::AutoList()[i] ); if ( !pTriggerRespawn->m_bDisabled && pTriggerRespawn->IsTouching( this ) ) { SetRespawnOverride( pTriggerRespawn->GetRespawnTime(), pTriggerRespawn->GetRespawnName() ); break; } else { SetRespawnOverride( -1.f, NULL_STRING ); } } } // Is this an environmental death? if ( ( info.GetAttacker() == info.GetInflictor() && info.GetAttacker() && info.GetAttacker()->IsBSPModel() ) || ( info.GetDamageCustom() == TF_DMG_CUSTOM_TRIGGER_HURT ) || ( info.GetDamageType() & DMG_VEHICLE ) ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 1, 5.0 ); if ( pRecentDamager ) { IGameEvent *event = gameeventmanager->CreateEvent( "environmental_death" ); if ( event ) { event->SetInt( "killer", pRecentDamager->entindex() ); event->SetInt( "victim", entindex() ); event->SetInt( "priority", 9 ); gameeventmanager->FireEvent( event ); } } } // make sure to remove custom attributes RemoveAllCustomAttributes(); } struct SkillRatingAttackRecord_t { CHandle< CTFPlayer > hAttacker; float flDamagePercent; }; //----------------------------------------------------------------------------- // Purpose: // Input : *pWeapon - // &vecOrigin - // &vecAngles - //----------------------------------------------------------------------------- bool CTFPlayer::CalculateAmmoPackPositionAndAngles( CTFWeaponBase *pWeapon, Vector &vecOrigin, QAngle &vecAngles ) { // Look up the hand and weapon bones. int iHandBone = LookupBone( "weapon_bone" ); if ( iHandBone == -1 ) return false; GetBonePosition( iHandBone, vecOrigin, vecAngles ); // need to fix up the z because the weapon bone position can be under the player if ( IsTaunting() ) { // put the pack at the middle of the dying player vecOrigin = WorldSpaceCenter(); } // Draw the position and angles. Vector vecDebugForward2, vecDebugRight2, vecDebugUp2; AngleVectors( vecAngles, &vecDebugForward2, &vecDebugRight2, &vecDebugUp2 ); /* NDebugOverlay::Line( vecOrigin, ( vecOrigin + vecDebugForward2 * 25.0f ), 255, 0, 0, false, 30.0f ); NDebugOverlay::Line( vecOrigin, ( vecOrigin + vecDebugRight2 * 25.0f ), 0, 255, 0, false, 30.0f ); NDebugOverlay::Line( vecOrigin, ( vecOrigin + vecDebugUp2 * 25.0f ), 0, 0, 255, false, 30.0f ); */ VectorAngles( vecDebugUp2, vecAngles ); return true; } //----------------------------------------------------------------------------- // Purpose: // NOTE: If we don't let players drop ammo boxes, we don't need this code.. //----------------------------------------------------------------------------- void CTFPlayer::AmmoPackCleanUp( void ) { // If we have more than 3 ammo packs out now, destroy the oldest one. int iNumPacks = 0; CTFAmmoPack *pOldestBox = NULL; // Cycle through all ammobox in the world and remove them CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "tf_ammo_pack" ); while ( pEnt ) { CBaseEntity *pOwner = pEnt->GetOwnerEntity(); if (pOwner == this) { CTFAmmoPack *pThisBox = dynamic_cast( pEnt ); Assert( pThisBox ); if ( pThisBox ) { iNumPacks++; // Find the oldest one if ( pOldestBox == NULL || pOldestBox->GetCreationTime() > pThisBox->GetCreationTime() ) { pOldestBox = pThisBox; } } } pEnt = gEntList.FindEntityByClassname( pEnt, "tf_ammo_pack" ); } // If they have more than 3 packs active, remove the oldest one if ( iNumPacks > 3 && pOldestBox ) { UTIL_Remove( pOldestBox ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ShouldDropAmmoPack() { if ( TFGameRules()->IsMannVsMachineMode() && IsBot() ) return false; if ( TFGameRules()->IsInArenaMode() && TFGameRules()->InStalemate() == false ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DropAmmoPack( const CTakeDamageInfo &info, bool bEmpty, bool bDisguisedWeapon ) { // We want the ammo packs to look like the player's weapon model they were carrying. // except if they are melee or building weapons CTFWeaponBase *pWeapon = NULL; CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon(); if ( !pActiveWeapon || pActiveWeapon->GetTFWpnData().m_bDontDrop ) { // Don't drop this one, find another one to drop int iWeight = -1; // find the highest weighted weapon for (int i = 0;i < WeaponCount(); i++) { CTFWeaponBase *pWpn = ( CTFWeaponBase *)GetWeapon(i); if ( !pWpn ) continue; if ( pWpn->GetTFWpnData().m_bDontDrop ) continue; int iThisWeight = pWpn->GetTFWpnData().iWeight; if ( iThisWeight > iWeight ) { iWeight = iThisWeight; pWeapon = pWpn; } } } else { pWeapon = pActiveWeapon; } // If we didn't find one, bail if ( !pWeapon ) return; // Figure out which model/skin to use for the drop. We may pull from our real weapon or // from the weapon we're disguised as. CTFWeaponBase *pDropWeaponProps = (bDisguisedWeapon && m_Shared.InCond( TF_COND_DISGUISED ) && m_Shared.GetDisguiseWeapon()) ? m_Shared.GetDisguiseWeapon() : pWeapon; const char *pszWorldModel = pDropWeaponProps->GetWorldModel(); int nSkin = pDropWeaponProps->GetDropSkinOverride(); if ( nSkin < 0 ) { nSkin = pDropWeaponProps->GetSkin(); } if ( pszWorldModel == NULL ) return; // Find the position and angle of the weapons so the "ammo box" matches. Vector vecPackOrigin; QAngle vecPackAngles; if( !CalculateAmmoPackPositionAndAngles( pWeapon, vecPackOrigin, vecPackAngles ) ) return; CEconItemView *pItem = pDropWeaponProps->GetAttributeContainer()->GetItem(); bool bIsSuicide = info.GetAttacker() ? info.GetAttacker()->GetTeamNumber() == GetTeamNumber() : false; CTFDroppedWeapon *pDroppedWeapon = CTFDroppedWeapon::Create( this, vecPackOrigin, vecPackAngles, pszWorldModel, pItem ); if ( pDroppedWeapon ) { pDroppedWeapon->InitDroppedWeapon( this, pDropWeaponProps, false, bIsSuicide ); } // Create the ammo pack. CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( vecPackOrigin, vecPackAngles, this, "models/items/ammopack_medium.mdl" ); Assert( pAmmoPack ); if ( pAmmoPack ) { pAmmoPack->InitWeaponDrop( this, pWeapon, nSkin, bEmpty, bIsSuicide ); // Clean up old ammo packs if they exist in the world AmmoPackCleanUp(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DropHealthPack( const CTakeDamageInfo &info, bool bEmpty ) { Vector vecSrc = this->WorldSpaceCenter(); CHealthKitSmall *pMedKit = assert_cast( CBaseEntity::Create( "item_healthkit_small", vecSrc, vec3_angle, this ) ); if ( pMedKit ) { Vector vecImpulse = RandomVector( -1,1 ); vecImpulse.z = 1; VectorNormalize( vecImpulse ); Vector vecVelocity = vecImpulse * 250.0; pMedKit->DropSingleInstance( vecVelocity, this, 0 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DropCurrencyPack( CurrencyRewards_t nSize /* = TF_CURRENCY_PACK_SMALL */, int nAmount /*= 0*/, bool bForceDistribute /*= false*/, CBasePlayer* pMoneyMaker /*= NULL*/ ) { // SMALL, MEDIUM, LARGE packs generate a default value on spawn // Only pass in an amount when dropping TF_CURRENCY_PACK_CUSTOM Vector vecSrc = this->WorldSpaceCenter(); CCurrencyPack *pCurrencyPack = NULL; switch ( nSize ) { case TF_CURRENCY_PACK_SMALL: pCurrencyPack = assert_cast( CBaseEntity::Create( "item_currencypack_small", vecSrc, vec3_angle, this ) ); break; case TF_CURRENCY_PACK_MEDIUM: pCurrencyPack = assert_cast( CBaseEntity::Create( "item_currencypack_medium", vecSrc, vec3_angle, this ) ); break; case TF_CURRENCY_PACK_LARGE: pCurrencyPack = assert_cast( CBaseEntity::Create( "item_currencypack_large", vecSrc, vec3_angle, this ) ); break; case TF_CURRENCY_PACK_CUSTOM: // Pop file may have said to not drop anything Assert( nAmount > 0 ); if ( nAmount == 0 ) return; // Create no spawn first so we can set the multiplier before it spawns & picks it model pCurrencyPack = assert_cast( CBaseEntity::CreateNoSpawn( "item_currencypack_custom", vecSrc, vec3_angle, this ) ); pCurrencyPack->SetAmount( nAmount ); break; }; if ( pCurrencyPack ) { Vector vecImpulse = RandomVector( -1,1 ); vecImpulse.z = 1; VectorNormalize( vecImpulse ); Vector vecVelocity = vecImpulse * 250.0; if ( pMoneyMaker || bForceDistribute ) { pCurrencyPack->DistributedBy( pMoneyMaker ); } DispatchSpawn( pCurrencyPack ); pCurrencyPack->DropSingleInstance( vecVelocity, this, 0, 0 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PlayerDeathThink( void ) { // We're doing this here to avoid getting stuck // in a recursive loop if we do it in Event_Killed if ( m_bPendingMerasmusPlayerBombExplode ) { m_bPendingMerasmusPlayerBombExplode = false; MerasmusPlayerBombExplode(); } // don't need to think again... } //----------------------------------------------------------------------------- // Purpose: Remove the tf items from the player then call into the base class // removal of items. //----------------------------------------------------------------------------- void CTFPlayer::RemoveAllItems( bool removeSuit ) { if ( TFGameRules() && TFGameRules()->IsPasstimeMode() && m_Shared.HasPasstimeBall() ) { g_pPasstimeLogic->EjectBall( this, this ); } // If the player has a capture flag, drop it. if ( HasItem() ) { int nFlagTeamNumber = GetItem()->GetTeamNumber(); GetItem()->Drop( this, true ); IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); if ( event ) { event->SetInt( "player", entindex() ); event->SetInt( "eventtype", TF_FLAGEVENT_DROPPED ); event->SetInt( "priority", 8 ); event->SetInt( "team", nFlagTeamNumber ); gameeventmanager->FireEvent( event ); } } m_Shared.Heal_Radius( false ); if ( m_hOffHandWeapon.Get() ) { HolsterOffHandWeapon(); // hide the weapon model // don't normally have to do this, unless we have a holster animation CBaseViewModel *vm = GetViewModel( 1 ); if ( vm ) { vm->SetWeaponModel( NULL, NULL ); } m_hOffHandWeapon = NULL; } Weapon_SetLast( NULL ); UpdateClientData(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClientHearVox( const char *pSentence ) { //TFTODO: implement this. } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::UpdateModel( void ) { SetModel( GetPlayerClass()->GetModelName() ); // Immediately reset our collision bounds - our collision bounds will be set to the model's bounds. SetCollisionBounds( GetPlayerMins(), GetPlayerMaxs() ); m_PlayerAnimState->OnNewModel(); } //----------------------------------------------------------------------------- // Purpose: // Input : iSkin - //----------------------------------------------------------------------------- void CTFPlayer::UpdateSkin( int iTeam ) { // The player's skin is team - 2. int iSkin = iTeam - 2; // Check to see if the skin actually changed. if ( iSkin != m_iLastSkin ) { m_nSkin = iSkin; m_iLastSkin = iSkin; } } //========================================================================= // Displays the state of the items specified by the Goal passed in void CTFPlayer::DisplayLocalItemStatus( CTFGoal *pGoal ) { #if 0 for (int i = 0; i < 4; i++) { if (pGoal->display_item_status[i] != 0) { CTFGoalItem *pItem = Finditem(pGoal->display_item_status[i]); if (pItem) DisplayItemStatus(pGoal, this, pItem); else ClientPrint( this, HUD_PRINTTALK, "#Item_missing" ); } } #endif } void CTFPlayer::SetIsCoaching( bool bIsCoaching ) { m_bIsCoaching = bIsCoaching; if ( !bIsCoaching ) { // reset our last action time so we don't get kicked for being idle while we were coaching m_flLastAction = gpGlobals->curtime; } } //========================================================================= // Called when the player disconnects from the server. void CTFPlayer::TeamFortress_ClientDisconnected( void ) { RemoveAllOwnedEntitiesFromWorld( true ); RemoveNemesisRelationships(); StopTaunt(); RemoveAllWeapons(); RemoveAllItems( true ); TFGameRules()->RemovePlayerFromQueue( this ); TFGameRules()->PlayerHistory_AddPlayer( this ); DuelMiniGame_NotifyPlayerDisconnect( this ); // cleanup coaching if ( GetCoach() ) { GetCoach()->SetIsCoaching( false ); GetCoach()->SetStudent( NULL ); } else if ( GetStudent() ) { SetIsCoaching( false ); GetStudent()->SetCoach( NULL ); } // Drop your powerup when you disconnect if ( m_Shared.IsCarryingRune() ) { CTFRune::CreateRune( GetAbsOrigin(), m_Shared.GetCarryingRuneType(), TEAM_ANY, true, false ); } } //========================================================================= // Removes everything this player has (buildings, grenades, etc.) from the world void CTFPlayer::RemoveAllOwnedEntitiesFromWorld( bool bExplodeBuildings /* = false */ ) { RemoveOwnedProjectiles(); if ( TFGameRules()->IsMannVsMachineMode() && ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) { // MvM engineer bots leave their sentries behind when they die return; } #ifdef TF_RAID_MODE if ( TFGameRules()->IsRaidMode() && ( GetTeamNumber() == TF_TEAM_RED ) ) { // for now, leave Engineer's sentrygun alive after he dies return; } #endif // TF_RAID_MODE if ( IsBotOfType( TF_BOT_TYPE ) && ToTFBot( this )->HasAttribute( CTFBot::RETAIN_BUILDINGS ) ) { // keep this bot's buildings return; } // Destroy any buildables - this should replace TeamFortress_RemoveBuildings RemoveAllObjects( bExplodeBuildings ); } //========================================================================= // Removes all rockets the player has fired into the world // (this prevents a team kill cheat where players would fire rockets // then change teams to kill their own team) void CTFPlayer::RemoveOwnedProjectiles( void ) { FOR_EACH_VEC( IBaseProjectileAutoList::AutoList(), i ) { CBaseProjectile *pProjectile = static_cast< CBaseProjectile* >( IBaseProjectileAutoList::AutoList()[i] ); // if the player owns this entity, remove it bool bOwner = ( pProjectile->GetOwnerEntity() == this ); if ( !bOwner ) { if ( pProjectile->GetBaseProjectileType() == TF_BASE_PROJECTILE_GRENADE ) { CTFWeaponBaseGrenadeProj *pGrenade = assert_cast( pProjectile ); if ( pGrenade ) { bOwner = ( pGrenade->GetThrower() == this ); } } else if ( pProjectile->GetProjectileType() == TF_PROJECTILE_SENTRY_ROCKET ) { CTFProjectile_SentryRocket *pRocket = assert_cast( pProjectile ); if ( pRocket ) { bOwner = ( pRocket->GetScorer() == this ); } } } if ( bOwner ) { pProjectile->SetTouch( NULL ); pProjectile->AddEffects( EF_NODRAW ); UTIL_Remove( pProjectile ); } } FOR_EACH_VEC( ITFFlameEntityAutoList::AutoList(), i ) { CTFFlameEntity *pFlameEnt = static_cast< CTFFlameEntity* >( ITFFlameEntityAutoList::AutoList()[i] ); if ( pFlameEnt->IsEntityAttacker( this ) ) { pFlameEnt->SetTouch( NULL ); pFlameEnt->AddEffects( EF_NODRAW ); UTIL_Remove( pFlameEnt ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::NoteWeaponFired() { Assert( m_pCurrentCommand ); if ( m_pCurrentCommand ) { m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number; } // Remember the tickcount when the weapon was fired and lock viewangles here! if ( m_iLockViewanglesTickNumber != gpGlobals->tickcount ) { m_iLockViewanglesTickNumber = gpGlobals->tickcount; m_qangLockViewangles = pl.v_angle; } } //============================================================================= // // Player state functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPlayerStateInfo *CTFPlayer::StateLookupInfo( int nState ) { // This table MUST match the static CPlayerStateInfo playerStateInfos[] = { { TF_STATE_ACTIVE, "TF_STATE_ACTIVE", &CTFPlayer::StateEnterACTIVE, NULL, NULL }, { TF_STATE_WELCOME, "TF_STATE_WELCOME", &CTFPlayer::StateEnterWELCOME, NULL, &CTFPlayer::StateThinkWELCOME }, { TF_STATE_OBSERVER, "TF_STATE_OBSERVER", &CTFPlayer::StateEnterOBSERVER, NULL, &CTFPlayer::StateThinkOBSERVER }, { TF_STATE_DYING, "TF_STATE_DYING", &CTFPlayer::StateEnterDYING, NULL, &CTFPlayer::StateThinkDYING }, }; for ( int iState = 0; iState < ARRAYSIZE( playerStateInfos ); ++iState ) { if ( playerStateInfos[iState].m_nPlayerState == nState ) return &playerStateInfos[iState]; } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateEnter( int nState ) { m_Shared.m_nPlayerState = nState; m_pStateInfo = StateLookupInfo( nState ); if ( tf_playerstatetransitions.GetInt() == -1 || tf_playerstatetransitions.GetInt() == entindex() ) { if ( m_pStateInfo ) Msg( "ShowStateTransitions: entering '%s'\n", m_pStateInfo->m_pStateName ); else Msg( "ShowStateTransitions: entering #%d\n", nState ); } // Initialize the new state. if ( m_pStateInfo && m_pStateInfo->pfnEnterState ) { (this->*m_pStateInfo->pfnEnterState)(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateLeave( void ) { if ( m_pStateInfo && m_pStateInfo->pfnLeaveState ) { (this->*m_pStateInfo->pfnLeaveState)(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateTransition( int nState ) { StateLeave(); StateEnter( nState ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateEnterWELCOME( void ) { PickWelcomeObserverPoint(); StartObserverMode( OBS_MODE_FIXED ); // Important to set MOVETYPE_NONE or our physics object will fall while we're sitting at one of the intro cameras. SetMoveType( MOVETYPE_NONE ); AddSolidFlags( FSOLID_NOT_SOLID ); AddEffects( EF_NODRAW | EF_NOSHADOW ); PhysObjectSleep(); if ( g_pServerBenchmark->IsLocalBenchmarkPlayer( this ) ) { m_bSeenRoundInfo = true; ChangeTeam( TEAM_SPECTATOR ); } else if ( gpGlobals->eLoadType == MapLoad_Background ) { m_bSeenRoundInfo = true; ChangeTeam( TEAM_SPECTATOR ); } else if ( (TFGameRules() && TFGameRules()->IsLoadingBugBaitReport()) ) { m_bSeenRoundInfo = true; ChangeTeam( TF_TEAM_BLUE ); SetDesiredPlayerClassIndex( TF_CLASS_SCOUT ); ForceRespawn(); } else if ( IsInCommentaryMode() ) { m_bSeenRoundInfo = true; } //============================================================================= // HPE_BEGIN: // [msmith] When in training, we want the option to show an intro movie. //============================================================================= else if ( TFGameRules()->IsInTraining() && IsFakeClient() == false ) { ShowViewPortPanel( PANEL_INTRO, true ); m_bSeenRoundInfo = true; } //============================================================================= // HPE_END //============================================================================= #ifdef STAGING_ONLY else if ( tf_skip_intro_and_spectate.GetBool() ) { m_bSeenRoundInfo = true; ChangeTeam( TEAM_SPECTATOR ); SetObserverMode( OBS_MODE_CHASE ); } #endif else { if ( !IsX360() ) { char pszWelcome[128]; Q_snprintf( pszWelcome, sizeof(pszWelcome), "#TF_Welcome" ); if ( UTIL_GetActiveHolidayString() ) { Q_snprintf( pszWelcome, sizeof(pszWelcome), "#TF_Welcome_%s", UTIL_GetActiveHolidayString() ); } KeyValues *data = new KeyValues( "data" ); data->SetString( "title", pszWelcome ); // info panel title data->SetString( "type", "1" ); // show userdata from stringtable entry data->SetString( "msg", "motd" ); // use this stringtable entry data->SetString( "msg_fallback", "motd_text" ); // use this stringtable entry if the base is HTML, and client has disabled HTML motds data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() ); ShowViewPortPanel( PANEL_INFO, true, data ); data->deleteThis(); } else { ShowViewPortPanel( PANEL_MAPINFO, true ); } m_bSeenRoundInfo = false; } #ifdef STAGING_ONLY if ( TFGameRules() && TFGameRules()->IsBountyMode() ) { // See if we should give starting money int nCurrency = tf_bountymode_currency_starting.GetInt(); if ( nCurrency > 0 ) { SetCurrency( nCurrency ); } } #endif // STAGING_ONLY } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateThinkWELCOME( void ) { if ( !IsFakeClient() ) { if ( IsInCommentaryMode() ) { ChangeTeam( TF_TEAM_BLUE ); SetDesiredPlayerClassIndex( TF_CLASS_SCOUT ); ForceRespawn(); } else if ( TFGameRules()->IsInTraining() ) { int iTeam = TFGameRules()->GetAssignedHumanTeam(); int iClass = TFGameRules()->GetTrainingModeLogic() ? TFGameRules()->GetTrainingModeLogic()->GetDesiredClass() : TF_CLASS_SOLDIER; ChangeTeam( iTeam != TEAM_ANY ? iTeam : TF_TEAM_BLUE ); SetDesiredPlayerClassIndex( iClass ); ForceRespawn(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateEnterACTIVE() { SetMoveType( MOVETYPE_WALK ); RemoveEffects( EF_NODRAW | EF_NOSHADOW ); RemoveSolidFlags( FSOLID_NOT_SOLID ); m_Local.m_iHideHUD = 0; PhysObjectWake(); m_flLastAction = gpGlobals->curtime; m_flLastHealthRegenAt = gpGlobals->curtime; SetContextThink( &CTFPlayer::RegenThink, gpGlobals->curtime + TF_REGEN_TIME, "RegenThink" ); if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) { SetContextThink( &CTFPlayer::RuneRegenThink, gpGlobals->curtime + TF_REGEN_TIME_RUNE, "RuneRegenThink" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::SetObserverMode(int mode) { if ( !TFGameRules() ) return false; if ( mode < OBS_MODE_NONE || mode >= NUM_OBSERVER_MODES ) return false; if ( TFGameRules()->ShowMatchSummary() ) return false; // Skip over OBS_MODE_POI if we're not in Passtime mode if ( mode == OBS_MODE_POI ) { if ( !TFGameRules()->IsPasstimeMode() ) { mode = OBS_MODE_ROAMING; } } // Skip over OBS_MODE_ROAMING for dead players if( GetTeamNumber() > TEAM_SPECTATOR ) { if ( IsDead() && ( mode > OBS_MODE_FIXED ) && mp_fadetoblack.GetBool() ) { mode = OBS_MODE_CHASE; } else if ( mode == OBS_MODE_ROAMING ) { mode = OBS_MODE_IN_EYE; } } if ( m_iObserverMode > OBS_MODE_DEATHCAM ) { // remember mode if we were really spectating before m_iObserverLastMode = m_iObserverMode; } m_iObserverMode = mode; if ( !m_bArenaIsAFK ) { m_flLastAction = gpGlobals->curtime; } // this is the old behavior, still supported for community servers bool bAllowSpecModeChange = TFGameRules()->IsInTournamentMode() ? TFGameRules()->IsMannVsMachineMode() : true; // new behavior for Valve casual, competitive, and mvm matches const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); if ( pMatchDesc ) { bAllowSpecModeChange = pMatchDesc->m_params.m_bAllowSpecModeChange; } if ( !bAllowSpecModeChange ) { if ( ( mode != OBS_MODE_DEATHCAM ) && ( mode != OBS_MODE_FREEZECAM ) && ( GetTeamNumber() > TEAM_SPECTATOR ) ) { if ( IsValidObserverTarget( GetObserverTarget() ) ) { m_iObserverMode.Set( OBS_MODE_IN_EYE ); } else { m_iObserverMode.Set( OBS_MODE_DEATHCAM ); } } } switch ( m_iObserverMode ) { case OBS_MODE_NONE: case OBS_MODE_FIXED : case OBS_MODE_DEATHCAM : SetFOV( this, 0 ); // Reset FOV SetViewOffset( vec3_origin ); SetMoveType( MOVETYPE_NONE ); break; case OBS_MODE_CHASE : case OBS_MODE_IN_EYE : // udpate FOV and viewmodels SetObserverTarget( m_hObserverTarget ); SetMoveType( MOVETYPE_OBSERVER ); break; case OBS_MODE_POI : // PASSTIME SetObserverTarget( TFGameRules()->GetObjectiveObserverTarget() ); SetMoveType( MOVETYPE_OBSERVER ); break; case OBS_MODE_ROAMING : SetFOV( this, 0 ); // Reset FOV SetObserverTarget( m_hObserverTarget ); SetViewOffset( vec3_origin ); SetMoveType( MOVETYPE_OBSERVER ); break; case OBS_MODE_FREEZECAM: SetFOV( this, 0 ); // Reset FOV SetObserverTarget( m_hObserverTarget ); SetViewOffset( vec3_origin ); SetMoveType( MOVETYPE_OBSERVER ); break; } CheckObserverSettings(); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateEnterOBSERVER( void ) { // Always start a spectator session in chase mode m_iObserverLastMode = OBS_MODE_CHASE; if( m_hObserverTarget == NULL ) { // find a new observer target CheckObserverSettings(); } if ( !m_bAbortFreezeCam ) { FindInitialObserverTarget(); } // If we haven't yet set a valid observer mode, such as when // the player aborts the freezecam and sets a mode "by hand" // force the initial mode to last mode if ( m_iObserverMode <= OBS_MODE_FREEZECAM ) { if ( TFGameRules() && TFGameRules()->IsPasstimeMode() ) { m_iObserverMode = OBS_MODE_POI; } else { m_iObserverMode = m_iObserverLastMode; } } // If we're in fixed mode, but we found an observer target, move to non fixed. if ( m_hObserverTarget.Get() != NULL && m_iObserverMode == OBS_MODE_FIXED ) { m_iObserverMode.Set( OBS_MODE_IN_EYE ); } StartObserverMode( m_iObserverMode ); PhysObjectSleep(); if ( GetTeamNumber() != TEAM_SPECTATOR ) { HandleFadeToBlack(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateThinkOBSERVER() { // Make sure nobody has changed any of our state. Assert( m_takedamage == DAMAGE_NO ); Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); // Must be dead. Assert( m_lifeState == LIFE_DEAD ); Assert( pl.deadflag ); #ifdef STAGING_ONLY if ( tf_skip_intro_and_spectate.GetInt() > 5 ) { static float s_flLastTime = gpGlobals->curtime; float curtime = gpGlobals->curtime; if ( ( curtime - s_flLastTime ) > tf_skip_intro_and_spectate.GetInt() ) { s_flLastTime = curtime; for ( int i = 1; i <= gpGlobals->maxClients; ++i ) { CBasePlayer *pl = UTIL_PlayerByIndex( i ); if ( pl && ( pl->GetTeamNumber() == TEAM_SPECTATOR ) ) { CBaseEntity * target = pl->FindNextObserverTarget( false ); if ( target ) { // Could also switch spec_mode: GetObserverMode(). pl->SetObserverTarget( target ); } } } } } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StateEnterDYING( void ) { SetMoveType( MOVETYPE_NONE ); AddSolidFlags( FSOLID_NOT_SOLID ); m_bPlayedFreezeCamSound = false; m_bAbortFreezeCam = false; if ( TFGameRules() && TFGameRules()->IsInArenaMode() ) { float flLastActionTime = gpGlobals->curtime - m_flLastAction; float flAliveThisRoundTime = gpGlobals->curtime - TFGameRules()->GetRoundStart(); if ( flAliveThisRoundTime - flLastActionTime < 0 ) { m_bArenaIsAFK = true; } } } //----------------------------------------------------------------------------- // Purpose: Move the player to observer mode once the dying process is over //----------------------------------------------------------------------------- void CTFPlayer::StateThinkDYING( void ) { // If we have a ragdoll, it's time to go to deathcam if ( !m_bAbortFreezeCam && m_hRagdoll && (m_lifeState == LIFE_DYING || m_lifeState == LIFE_DEAD) && GetObserverMode() != OBS_MODE_FREEZECAM ) { if ( GetObserverMode() != OBS_MODE_DEATHCAM ) { StartObserverMode( OBS_MODE_DEATHCAM ); // go to observer mode } RemoveEffects( EF_NODRAW | EF_NOSHADOW ); // still draw player body } float flTimeInFreeze = spec_freeze_traveltime.GetFloat() + spec_freeze_time.GetFloat(); float flFreezeEnd = (m_flDeathTime + TF_DEATH_ANIMATION_TIME + flTimeInFreeze ); if ( !m_bPlayedFreezeCamSound && GetObserverTarget() && GetObserverTarget() != this ) { // Start the sound so that it ends at the freezecam lock on time float flFreezeSoundLength = 0.3; float flFreezeSoundTime = (m_flDeathTime + TF_DEATH_ANIMATION_TIME ) + spec_freeze_traveltime.GetFloat() - flFreezeSoundLength; if ( gpGlobals->curtime >= flFreezeSoundTime ) { CSingleUserRecipientFilter filter( this ); EmitSound_t params; params.m_flSoundTime = 0; params.m_pSoundName = "TFPlayer.FreezeCam"; EmitSound( filter, entindex(), params ); m_bPlayedFreezeCamSound = true; } } if ( gpGlobals->curtime >= (m_flDeathTime + TF_DEATH_ANIMATION_TIME ) ) // allow x seconds death animation / death cam { if ( GetObserverTarget() && GetObserverTarget() != this ) { if ( !m_bAbortFreezeCam && gpGlobals->curtime < flFreezeEnd ) { if ( GetObserverMode() != OBS_MODE_FREEZECAM ) { StartObserverMode( OBS_MODE_FREEZECAM ); PhysObjectSleep(); } return; } } if ( GetObserverMode() == OBS_MODE_FREEZECAM ) { // If we're in freezecam, and we want out, abort. (only if server is not using mp_fadetoblack) if ( m_bAbortFreezeCam && !mp_fadetoblack.GetBool() ) { if ( m_hObserverTarget == NULL ) { // find a new observer target CheckObserverSettings(); } FindInitialObserverTarget(); if ( TFGameRules() && TFGameRules()->IsPasstimeMode() ) { SetObserverMode( OBS_MODE_POI ); } else { SetObserverMode( OBS_MODE_CHASE ); } ShowViewPortPanel( "specgui" , ModeWantsSpectatorGUI(OBS_MODE_CHASE) ); } } // Don't allow anyone to respawn until freeze time is over, even if they're not // in freezecam. This prevents players skipping freezecam to spawn faster. if ( gpGlobals->curtime < flFreezeEnd ) return; m_lifeState = LIFE_RESPAWNABLE; StopAnimation(); IncrementInterpolationFrame(); if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) ) SetMoveType( MOVETYPE_NONE ); StateTransition( TF_STATE_OBSERVER ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::AttemptToExitFreezeCam( void ) { float flFreezeTravelTime = (m_flDeathTime + TF_DEATH_ANIMATION_TIME ) + spec_freeze_traveltime.GetFloat() + 0.5; if ( gpGlobals->curtime < flFreezeTravelTime ) return; m_bAbortFreezeCam = true; } class CIntroViewpoint : public CPointEntity { DECLARE_CLASS( CIntroViewpoint, CPointEntity ); public: DECLARE_DATADESC(); virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); } int m_iIntroStep; float m_flStepDelay; string_t m_iszMessage; string_t m_iszGameEvent; float m_flEventDelay; int m_iGameEventData; float m_flFOV; }; BEGIN_DATADESC( CIntroViewpoint ) DEFINE_KEYFIELD( m_iIntroStep, FIELD_INTEGER, "step_number" ), DEFINE_KEYFIELD( m_flStepDelay, FIELD_FLOAT, "time_delay" ), DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "hint_message" ), DEFINE_KEYFIELD( m_iszGameEvent, FIELD_STRING, "event_to_fire" ), DEFINE_KEYFIELD( m_flEventDelay, FIELD_FLOAT, "event_delay" ), DEFINE_KEYFIELD( m_iGameEventData, FIELD_INTEGER, "event_data_int" ), DEFINE_KEYFIELD( m_flFOV, FIELD_FLOAT, "fov" ), END_DATADESC() LINK_ENTITY_TO_CLASS( game_intro_viewpoint, CIntroViewpoint ); //----------------------------------------------------------------------------- // Purpose: Give the player some ammo. // Input : iCount - Amount of ammo to give. // iAmmoIndex - Index of the ammo into the AmmoInfoArray // iMax - Max carrying capability of the player // Output : Amount of ammo actually given //----------------------------------------------------------------------------- int CTFPlayer::GiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound ) { return GiveAmmo( iCount, iAmmoIndex, bSuppressSound, kAmmoSource_Pickup ); } //----------------------------------------------------------------------------- // Purpose: Give the player some ammo. // Input : iCount - Amount of ammo to give. // iAmmoIndex - Index of the ammo into the AmmoInfoArray // iMax - Max carrying capability of the player // Output : Amount of ammo actually given //----------------------------------------------------------------------------- int CTFPlayer::GiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound, EAmmoSource eAmmoSource ) { if ( iCount <= 0 ) { return 0; } // Metal always ignores the eAmmoSource settings, which are really used only for determining // whether ammo should be converted into health or ignored or, in rare cases, treated as actual // ammo. if ( iAmmoIndex != TF_AMMO_METAL ) { //int iAmmoBecomesHealth = 0; //CALL_ATTRIB_HOOK_INT( iAmmoBecomesHealth, ammo_becomes_health ); //if ( iAmmoBecomesHealth == 1 ) //{ // // Ammo from ground pickups is converted to health. // if ( eAmmoSource == kAmmoSource_Pickup ) // { // int iTakenHealth = TakeHealth( iCount, DMG_GENERIC ); // if ( iTakenHealth > 0 ) // { // if ( !bSuppressSound ) // { // EmitSound( "BaseCombatCharacter.AmmoPickup" ); // } // m_Shared.HealthKitPickupEffects( iCount ); // } // return iTakenHealth; // } // // Ammo from the cart or engineer dispensers is flatly ignored. // if ( eAmmoSource == kAmmoSource_DispenserOrCart ) // return 0; // Assert( eAmmoSource == kAmmoSource_Resupply ); //} } else if ( iAmmoIndex == TF_AMMO_METAL ) { if ( eAmmoSource != kAmmoSource_Resupply ) { float flMultMetal = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flMultMetal, mult_metal_pickup ); iCount = (int)(flMultMetal * iCount ); } } if ( !g_pGameRules->CanHaveAmmo( this, iAmmoIndex ) ) { // game rules say I can't have any more of this ammo type. return 0; } if ( iAmmoIndex < 0 || iAmmoIndex >= MAX_AMMO_SLOTS ) { return 0; } int iAdd = MIN( iCount, GetMaxAmmo(iAmmoIndex) - GetAmmoCount(iAmmoIndex) ); if ( iAdd < 1 ) { return 0; } // Ammo pickup sound if ( !bSuppressSound ) { EmitSound( "BaseCombatCharacter.AmmoPickup" ); } CBaseCombatCharacter::GiveAmmo( iAdd, iAmmoIndex, bSuppressSound ); return iAdd; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveAmmo( int iCount, int iAmmoIndex ) { #ifdef STAGING_ONLY if ( tf_infinite_ammo.GetBool() ) { return; } #endif // STAGING_ONLY #if defined( _DEBUG ) || defined( STAGING_ONLY ) if ( mp_developer.GetInt() > 1 && !IsBot() ) return; #endif // _DEBUG || STAGING_ONLY if ( m_Shared.InCond( TF_COND_HALLOWEEN_GIANT ) ) { return; } // Infinite primary, secondary and metal in these game modes if ( TFGameRules() && iAmmoIndex < TF_AMMO_GRENADES1 ) { if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) return; #ifdef STAGING_ONLY if ( TFGameRules()->IsBountyMode() && IsMiniBoss() ) return; #endif // STAGING_ONLY } CBaseCombatCharacter::RemoveAmmo( iCount, iAmmoIndex ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveAmmo( int iCount, const char *szName ) { if ( TFGameRules() ) { if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) return; if ( TFGameRules()->GameModeUsesMiniBosses() && IsMiniBoss() ) return; } CBaseCombatCharacter::RemoveAmmo( iCount, szName ); } //----------------------------------------------------------------------------- // Purpose: Returns the amount of ammunition of a particular type owned // owned by the character // Input : Ammo Index // Output : The amount of ammo //----------------------------------------------------------------------------- int CTFPlayer::GetAmmoCount( int iAmmoIndex ) const { if ( iAmmoIndex == -1 ) return 0; if ( IsFakeClient() && TFGameRules()->IsInItemTestingMode() ) return 999; return BaseClass::GetAmmoCount( iAmmoIndex ); } //----------------------------------------------------------------------------- // Purpose: Has to be const for override, but needs to access non-const member methods. //----------------------------------------------------------------------------- int CTFPlayer::GetMaxHealth() const { int iMax = GetMaxHealthForBuffing(); // Also add the nonbuffed health bonuses CALL_ATTRIB_HOOK_INT( iMax, add_maxhealth_nonbuffed ); return MAX( iMax, 1 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::GetMaxHealthForBuffing() const { int iMax = m_PlayerClass.GetMaxHealth(); CALL_ATTRIB_HOOK_INT( iMax, add_maxhealth ); CTFWeaponBase *pWeapon = GetActiveTFWeapon(); if ( pWeapon ) { iMax += pWeapon->GetMaxHealthMod(); } if ( const_cast(this)->GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN ) { CTFSword *pSword = dynamic_cast(const_cast(this)->Weapon_OwnsThisID( TF_WEAPON_SWORD )); if ( pSword ) { iMax += pSword->GetSwordHealthMod(); } } // Some Powerup Runes increase your Max Health iMax += GetRuneHealthBonus(); if ( m_Shared.InCond( TF_COND_HALLOWEEN_GIANT ) ) { return iMax * tf_halloween_giant_health_scale.GetFloat(); } return iMax; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::GetRuneHealthBonus() const { int nRuneType = m_Shared.GetCarryingRuneType(); if ( nRuneType == RUNE_NONE ) { return 0; } if ( nRuneType == RUNE_KNOCKOUT ) { if ( IsPlayerClass( TF_CLASS_DEMOMAN ) ) { // Swords have various extra melee benefits, so we reduce Max Health bonus if ( Weapon_GetSlot( TF_WPN_TYPE_MELEE ) ) { int iDecapitateType = 0; CALL_ATTRIB_HOOK_INT( iDecapitateType, decapitate_type ); if ( iDecapitateType ) { return 20; } } // Shields have passive resistance so we reduce Max Health bonus if ( m_Shared.IsShieldEquipped() ) { return 30; } return 150; } else if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) || IsPlayerClass( TF_CLASS_PYRO ) ) { return 125; } else if ( IsPlayerClass( TF_CLASS_SOLDIER ) || IsPlayerClass( TF_CLASS_MEDIC ) ) { return 150; } else { return 175; } } else if ( nRuneType == RUNE_REFLECT ) { return ( 400 - m_PlayerClass.GetMaxHealth() ); } else if ( nRuneType == RUNE_KING ) { return 100; } else if ( nRuneType == RUNE_VAMPIRE ) { return 80; } return 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ForceRegenerateAndRespawn( void ) { m_bRegenerating = true; ForceRespawn(); m_bRegenerating = false; } //----------------------------------------------------------------------------- // Purpose: Reset player's information and force him to spawn //----------------------------------------------------------------------------- void CTFPlayer::ForceRespawn( void ) { VPROF_BUDGET( "CTFPlayer::ForceRespawn", VPROF_BUDGETGROUP_PLAYER ); CTF_GameStats.Event_PlayerForceRespawn( this ); m_flSpawnTime = gpGlobals->curtime; bool bRandom = false; // force a random class if the server requires it if ( TFGameRules() && TFGameRules()->IsInArenaMode() ) { if ( tf_arena_force_class.GetBool() == true ) { bRandom = true; if ( GetTeamNumber() > LAST_SHARED_TEAM ) { if ( !IsAlive() || ( TFGameRules() && TFGameRules()->State_Get() != GR_STATE_STALEMATE ) ) { HandleCommand_JoinClass( "random", false ); } } } if ( ( tf_arena_use_queue.GetBool() == false && TFGameRules()->IsInWaitingForPlayers() ) || TFGameRules()->State_Get() == GR_STATE_PREGAME ) { return; } } int iDesiredClass = GetDesiredPlayerClassIndex(); if ( iDesiredClass == TF_CLASS_UNDEFINED ) { return; } if ( iDesiredClass == TF_CLASS_RANDOM ) { bRandom = true; // Don't let them be the same class twice in a row do{ iDesiredClass = random->RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS ); } while( iDesiredClass == GetPlayerClass()->GetClassIndex() ); } if ( HasTheFlag() ) { DropFlag(); } if ( GetPlayerClass()->GetClassIndex() != iDesiredClass ) { // clean up any pipebombs/buildings in the world (no explosions) m_bSwitchedClass = true; RemoveAllOwnedEntitiesFromWorld(); int iOldClass = GetPlayerClass()->GetClassIndex(); GetPlayerClass()->Init( iDesiredClass ); // Don't report class changes if we're random, because it's not a player choice if ( !bRandom ) { m_iClassChanges++; CTF_GameStats.Event_PlayerChangedClass( this, iOldClass, iDesiredClass ); } } else { m_bSwitchedClass = false; } m_Shared.RemoveAllCond(); m_Shared.ResetRageMeter(); if ( m_bSwitchedClass ) { m_iLastWeaponSlot = 1; // Tell all the items we have that we've changed class. Some items need to change model. // Also reset KillStreaks for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = (CTFWeaponBase *)GetWeapon(i); if ( pWeapon ) { pWeapon->OnOwnerClassChange(); } } } else { if ( IsAlive() ) { if ( GetActiveTFWeapon() ) { m_iActiveWeaponTypePriorToDeath = GetActiveTFWeapon()->GetWeaponID(); } SaveLastWeaponSlot(); } } // Any Respawns will reset killstreaks m_Shared.ResetStreaks(); for ( int i = 0; i < WeaponCount(); i++ ) { CTFWeaponBase *pWpn = (CTFWeaponBase *)GetWeapon( i ); if ( !pWpn ) continue; pWpn->SetKillStreak( 0 ); } for ( int i = 0; i < GetNumWearables(); ++i ) { CTFWearable* pWearable = dynamic_cast( GetWearable( i ) ); if ( !pWearable ) continue; pWearable->SetKillStreak( 0 ); } RemoveAllItems( true ); // Reset ground state for airwalk animations SetGroundEntity( NULL ); // TODO: move this into conditions RemoveTeleportEffect(); // remove invisibility very quickly m_Shared.FadeInvis( 0.1f ); // Stop any firing that was taking place before respawn. m_nButtons = 0; StateTransition( TF_STATE_ACTIVE ); Spawn(); m_bSwitchedClass = false; } //----------------------------------------------------------------------------- // Purpose: Do nothing multiplayer_animstate takes care of animation. // Input : playerAnim - //----------------------------------------------------------------------------- void CTFPlayer::SetAnimation( PLAYER_ANIM playerAnim ) { return; } //----------------------------------------------------------------------------- // Purpose: Handle cheat commands // Input : iImpulse - //----------------------------------------------------------------------------- void CTFPlayer::CheatImpulseCommands( int iImpulse ) { switch( iImpulse ) { case 101: { if( sv_cheats->GetBool() ) { extern int gEvilImpulse101; gEvilImpulse101 = true; GiveAmmo( 1000, TF_AMMO_PRIMARY ); GiveAmmo( 1000, TF_AMMO_SECONDARY ); GiveAmmo( 1000, TF_AMMO_METAL ); GiveAmmo( 1000, TF_AMMO_GRENADES1 ); GiveAmmo( 1000, TF_AMMO_GRENADES2 ); GiveAmmo( 1000, TF_AMMO_GRENADES3 ); TakeHealth( 999, DMG_GENERIC ); // Refills weapon clips, too for ( int i = 0; i < MAX_WEAPONS; i++ ) { CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( GetWeapon( i ) ); if ( !pWeapon ) continue; pWeapon->GiveDefaultAmmo(); if ( pWeapon->IsEnergyWeapon() ) { pWeapon->WeaponRegenerate(); } } m_Shared.m_flRageMeter = 100.f; m_Shared.SetDemomanChargeMeter( 100.f ); gEvilImpulse101 = false; } } break; default: { BaseClass::CheatImpulseCommands( iImpulse ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SetWeaponBuilder( CTFWeaponBuilder *pBuilder ) { m_hWeaponBuilder = pBuilder; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFWeaponBuilder *CTFPlayer::GetWeaponBuilder( void ) { Assert( 0 ); return m_hWeaponBuilder; } //----------------------------------------------------------------------------- // Purpose: Returns true if this player is building something //----------------------------------------------------------------------------- bool CTFPlayer::IsBuilding( void ) { /* CTFWeaponBuilder *pBuilder = GetWeaponBuilder(); if ( pBuilder ) return pBuilder->IsBuilding(); */ return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveBuildResources( int iAmount ) { RemoveAmmo( iAmount, TF_AMMO_METAL ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::AddBuildResources( int iAmount ) { GiveAmmo( iAmount, TF_AMMO_METAL, false, kAmmoSource_Pickup ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseObject *CTFPlayer::GetObject( int index ) const { return (CBaseObject *)( m_aObjects[index].Get() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseObject *CTFPlayer::GetObjectOfType( int iObjectType, int iObjectMode ) const { int iNumObjects = GetObjectCount(); for ( int i=0; iGetType() != iObjectType ) continue; if ( pObj->GetObjectMode() != iObjectMode ) continue; if ( pObj->IsDisposableBuilding() ) continue; return pObj; } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::GetObjectCount( void ) const { return m_aObjects.Count(); } //----------------------------------------------------------------------------- // Purpose: Remove all the player's objects // If bExplodeBuildings is not set, remove all of them immediately. // Otherwise, make them all explode. //----------------------------------------------------------------------------- void CTFPlayer::RemoveAllObjects( bool bExplodeBuildings /* = false */ ) { // Remove all the player's objects for (int i = GetObjectCount()-1; i >= 0; i--) { CBaseObject *obj = GetObject(i); Assert( obj ); if ( obj ) { // this is separate from the object_destroyed event, which does // not get sent when we remove the objects from the world IGameEvent *event = gameeventmanager->CreateEvent( "object_removed" ); if ( event ) { event->SetInt( "userid", GetUserID() ); // user ID of the object owner event->SetInt( "objecttype", obj->GetType() ); // type of object removed event->SetInt( "index", obj->entindex() ); // index of the object removed gameeventmanager->FireEvent( event ); } if ( bExplodeBuildings ) { obj->DetonateObject(); } else { // This fixes a bug in Raid mode where we could spawn where our sentry was but // we didn't get the weapons because they couldn't trace to us in FVisible obj->SetSolid( SOLID_NONE ); UTIL_Remove( obj ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StopPlacement( void ) { /* // Tell our builder weapon CTFWeaponBuilder *pBuilder = GetWeaponBuilder(); if ( pBuilder ) { pBuilder->StopPlacement(); } */ } //----------------------------------------------------------------------------- // Purpose: Player has started building an object //----------------------------------------------------------------------------- int CTFPlayer::StartedBuildingObject( int iObjectType ) { // Deduct the cost of the object int iCost = m_Shared.CalculateObjectCost( this, iObjectType ); if ( iCost > GetBuildResources() ) { // Player must have lost resources since he started placing return 0; } RemoveBuildResources( iCost ); // If the object costs 0, we need to return non-0 to mean success if ( !iCost ) return 1; return iCost; } //----------------------------------------------------------------------------- // Purpose: Player has aborted building something //----------------------------------------------------------------------------- void CTFPlayer::StoppedBuilding( int iObjectType ) { /* int iCost = CalculateObjectCost( iObjectType ); AddBuildResources( iCost ); // Tell our builder weapon CTFWeaponBuilder *pBuilder = GetWeaponBuilder(); if ( pBuilder ) { pBuilder->StoppedBuilding( iObjectType ); } */ } //----------------------------------------------------------------------------- // Purpose: Object has been built by this player //----------------------------------------------------------------------------- void CTFPlayer::FinishedObject( CBaseObject *pObject ) { AddObject( pObject ); CTF_GameStats.Event_PlayerCreatedBuilding( this, pObject ); if ( TFGameRules() && TFGameRules()->IsInTraining() && TFGameRules()->GetTrainingModeLogic() && IsFakeClient() == false ) { TFGameRules()->GetTrainingModeLogic()->OnPlayerBuiltBuilding( this, pObject ); } /* // Tell our builder weapon CTFWeaponBuilder *pBuilder = GetWeaponBuilder(); if ( pBuilder ) { pBuilder->FinishedObject(); } */ } //----------------------------------------------------------------------------- // Purpose: Add the specified object to this player's object list. //----------------------------------------------------------------------------- void CTFPlayer::AddObject( CBaseObject *pObject ) { TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::AddObject adding object %p:%s to player %s\n", gpGlobals->curtime, pObject, pObject->GetClassname(), GetPlayerName() ) ); // Make a handle out of it CHandle hObject; hObject = pObject; bool alreadyInList = PlayerOwnsObject( pObject ); // Assert( !alreadyInList ); if ( !alreadyInList ) { m_aObjects.AddToTail( hObject ); } } //----------------------------------------------------------------------------- // Purpose: Object built by this player has been destroyed //----------------------------------------------------------------------------- void CTFPlayer::OwnedObjectDestroyed( CBaseObject *pObject ) { TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::OwnedObjectDestroyed player %s object %p:%s\n", gpGlobals->curtime, GetPlayerName(), pObject, pObject->GetClassname() ) ); RemoveObject( pObject ); // Tell our builder weapon so it recalculates the state of the build icons /* CTFWeaponBuilder *pBuilder = GetWeaponBuilder(); if ( pBuilder ) { pBuilder->RecalcState(); } */ } //----------------------------------------------------------------------------- // Removes an object from the player //----------------------------------------------------------------------------- void CTFPlayer::RemoveObject( CBaseObject *pObject ) { TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseTFPlayer::RemoveObject %p:%s from player %s\n", gpGlobals->curtime, pObject, pObject->GetClassname(), GetPlayerName() ) ); Assert( pObject ); int i; for ( i = m_aObjects.Count(); --i >= 0; ) { // Also, while we're at it, remove all other bogus ones too... if ( (!m_aObjects[i].Get()) || (m_aObjects[i] == pObject)) { m_aObjects.FastRemove(i); } } } //----------------------------------------------------------------------------- // See if the player owns this object //----------------------------------------------------------------------------- bool CTFPlayer::PlayerOwnsObject( CBaseObject *pObject ) { return ( m_aObjects.Find( pObject ) != -1 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PlayFlinch( const CTakeDamageInfo &info ) { // Don't play flinches if we just died. if ( !IsAlive() ) return; // No pain flinches while disguised, our man has supreme discipline if ( m_Shared.InCond( TF_COND_DISGUISED ) ) return; PlayerAnimEvent_t flinchEvent; switch ( LastHitGroup() ) { // pick a region-specific flinch case HITGROUP_HEAD: flinchEvent = PLAYERANIMEVENT_FLINCH_HEAD; break; case HITGROUP_LEFTARM: flinchEvent = PLAYERANIMEVENT_FLINCH_LEFTARM; break; case HITGROUP_RIGHTARM: flinchEvent = PLAYERANIMEVENT_FLINCH_RIGHTARM; break; case HITGROUP_LEFTLEG: flinchEvent = PLAYERANIMEVENT_FLINCH_LEFTLEG; break; case HITGROUP_RIGHTLEG: flinchEvent = PLAYERANIMEVENT_FLINCH_RIGHTLEG; break; case HITGROUP_STOMACH: case HITGROUP_CHEST: case HITGROUP_GEAR: case HITGROUP_GENERIC: default: // just get a generic flinch. flinchEvent = PLAYERANIMEVENT_FLINCH_CHEST; break; } DoAnimationEvent( flinchEvent ); } //----------------------------------------------------------------------------- // Purpose: Plays the crit sound that players that get crit hear //----------------------------------------------------------------------------- float CTFPlayer::PlayCritReceivedSound( void ) { float flCritPainLength = 0; // Play a custom pain sound to the guy taking the damage CSingleUserRecipientFilter receiverfilter( this ); EmitSound_t params; params.m_flSoundTime = 0; params.m_pSoundName = "TFPlayer.CritPain"; params.m_pflSoundDuration = &flCritPainLength; EmitSound( receiverfilter, entindex(), params ); return flCritPainLength; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PainSound( const CTakeDamageInfo &info ) { // Don't make sounds if we just died. DeathSound will handle that. if ( !IsAlive() ) return; // no pain sounds while disguised, our man has supreme discipline if ( m_Shared.InCond( TF_COND_DISGUISED ) ) return; if ( m_flNextPainSoundTime > gpGlobals->curtime ) return; // Don't play falling pain sounds, they have their own system if ( info.GetDamageType() & DMG_FALL ) return; // No sound for DMG_GENERIC if ( info.GetDamageType() == 0 || info.GetDamageType() == DMG_PREVENT_PHYSICS_FORCE ) return; if ( info.GetDamageType() & DMG_DROWN ) { EmitSound( "TFPlayer.Drown" ); return; } if ( info.GetDamageType() & DMG_BURN ) { // Looping fire pain sound is done in CTFPlayerShared::ConditionThink return; } float flPainLength = 0; bool bAttackerIsPlayer = ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ); CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); Assert( pExpresser ); pExpresser->AllowMultipleScenes(); // speak a pain concept here, send to everyone but the attacker CPASFilter filter( GetAbsOrigin() ); if ( bAttackerIsPlayer ) { filter.RemoveRecipient( ToBasePlayer( info.GetAttacker() ) ); } // play a crit sound to the victim ( us ) if ( info.GetDamageType() & DMG_CRITICAL ) { flPainLength = PlayCritReceivedSound(); // remove us from hearing our own pain sound if we hear the crit sound filter.RemoveRecipient( this ); } char szResponse[AI_Response::MAX_RESPONSE_NAME]; if ( SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_PAIN, "damagecritical:1", szResponse, AI_Response::MAX_RESPONSE_NAME, &filter ) ) { flPainLength = MAX( GetSceneDuration( szResponse ), flPainLength ); } // speak a louder pain concept to just the attacker if ( bAttackerIsPlayer ) { CSingleUserRecipientFilter attackerFilter( ToBasePlayer( info.GetAttacker() ) ); SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_ATTACKER_PAIN, "damagecritical:1", szResponse, AI_Response::MAX_RESPONSE_NAME, &attackerFilter ); } pExpresser->DisallowMultipleScenes(); m_flNextPainSoundTime = gpGlobals->curtime + flPainLength; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DeathSound( const CTakeDamageInfo &info ) { // Don't make death sounds when choosing a class if ( IsPlayerClass( TF_CLASS_UNDEFINED ) ) return; TFPlayerClassData_t *pData = GetPlayerClass()->GetData(); if ( !pData ) return; if ( m_bGoingFeignDeath ) { bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED ) && (m_Shared.GetDisguiseTeam() == GetTeamNumber()); if ( bDisguised ) { // Use our disguise class, if we have one and will drop a disguise class corpse. pData = g_pTFPlayerClassDataMgr->Get( m_Shared.GetDisguiseClass() ); if ( !pData ) return; } } CTFPlayer *pAttacker = (CTFPlayer*)ToTFPlayer( info.GetAttacker() ); if ( pAttacker ) { CTFWeaponBase *pWpn = pAttacker->GetActiveTFWeapon(); if ( pWpn && pWpn->IsSilentKiller() ) return; } int nDeathSoundOffset = DEATH_SOUND_FIRST; if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { nDeathSoundOffset = IsMiniBoss() ? DEATH_SOUND_GIANT_MVM_FIRST : DEATH_SOUND_MVM_FIRST; } if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() != TF_TEAM_PVE_INVADERS && !m_bGoingFeignDeath ) { EmitSound( "MVM.PlayerDied" ); return; } if ( m_LastDamageType & DMG_FALL ) // Did we die from falling? { // They died in the fall. Play a splat sound. EmitSound( "Player.FallGib" ); } else if ( m_LastDamageType & DMG_BLAST ) { EmitSound( pData->GetDeathSound( DEATH_SOUND_EXPLOSION + nDeathSoundOffset ) ); } else if ( m_LastDamageType & DMG_CRITICAL ) { EmitSound( pData->GetDeathSound( DEATH_SOUND_CRIT + nDeathSoundOffset ) ); PlayCritReceivedSound(); } else if ( m_LastDamageType & DMG_CLUB ) { EmitSound( pData->GetDeathSound( DEATH_SOUND_MELEE + nDeathSoundOffset ) ); } else { EmitSound( pData->GetDeathSound( DEATH_SOUND_GENERIC + nDeathSoundOffset ) ); } // Play an additional sound when we're in MvM and have a boss death if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && IsMiniBoss() ) { switch ( GetPlayerClass()->GetClassIndex() ) { case TF_CLASS_HEAVYWEAPONS: { EmitSound( "MVM.GiantHeavyExplodes" ); break; } default: { EmitSound( "MVM.GiantCommonExplodes" ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CTFPlayer::GetSceneSoundToken( void ) { if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { if ( IsMiniBoss() ) { return "M_MVM_"; } else { return "MVM_"; } } else { return ""; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StunSound( CTFPlayer* pAttacker, int iStunFlags, int iOldStunFlags ) { if ( !IsAlive() ) return; if ( !(iStunFlags & TF_STUN_CONTROLS) && !(iStunFlags & TF_STUN_LOSER_STATE) ) return; if ( (iStunFlags & TF_STUN_BY_TRIGGER) && (iOldStunFlags != 0) ) return; // Only play stun triggered sounds when not already stunned. // Play the stun sound for everyone but the attacker. CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); Assert( pExpresser ); pExpresser->AllowMultipleScenes(); float flStunSoundLength = 0; EmitSound_t params; params.m_flSoundTime = 0; if ( iStunFlags & TF_STUN_SPECIAL_SOUND ) { params.m_pSoundName = "TFPlayer.StunImpactRange"; } else if ( (iStunFlags & TF_STUN_LOSER_STATE) && !pAttacker ) { params.m_pSoundName = "Halloween.PlayerScream"; } else { params.m_pSoundName = "TFPlayer.StunImpact"; } params.m_pflSoundDuration = &flStunSoundLength; if ( pAttacker ) { CPASFilter filter( GetAbsOrigin() ); filter.RemoveRecipient( pAttacker ); EmitSound( filter, entindex(), params ); // Play a louder pain sound for the person who got the stun. CSingleUserRecipientFilter attackerFilter( pAttacker ); EmitSound( attackerFilter, pAttacker->entindex(), params ); } else { EmitSound( params.m_pSoundName ); } pExpresser->DisallowMultipleScenes(); // Suppress any pain sound that might come right after this stun sound. m_flNextPainSoundTime = gpGlobals->curtime + 2.0f; } //----------------------------------------------------------------------------- // Purpose: called when this player burns another player //----------------------------------------------------------------------------- void CTFPlayer::OnBurnOther( CTFPlayer *pTFPlayerVictim, CTFWeaponBase *pWeapon ) { #define ACHIEVEMENT_BURN_TIME_WINDOW 30.0f #define ACHIEVEMENT_BURN_VICTIMS 5 // add current time we burned another player to head of vector m_aBurnOtherTimes.AddToHead( gpGlobals->curtime ); // remove any burn times that are older than the burn window from the list float flTimeDiscard = gpGlobals->curtime - ACHIEVEMENT_BURN_TIME_WINDOW; for ( int i = 1; i < m_aBurnOtherTimes.Count(); i++ ) { if ( m_aBurnOtherTimes[i] < flTimeDiscard ) { m_aBurnOtherTimes.RemoveMultiple( i, m_aBurnOtherTimes.Count() - i ); break; } } // see if we've burned enough players in time window to satisfy achievement if ( m_aBurnOtherTimes.Count() >= ACHIEVEMENT_BURN_VICTIMS ) { AwardAchievement( ACHIEVEMENT_TF_BURN_PLAYERSINMINIMIMTIME ); } // ACHIEVEMENT_TF_PYRO_KILL_SPIES - Awarded for igniting enemy spies who have active sappers on friendly building if ( pTFPlayerVictim->IsPlayerClass(TF_CLASS_SPY)) { CBaseObject *pSapper = pTFPlayerVictim->GetObjectOfType( OBJ_ATTACHMENT_SAPPER, 0 ); if ( pSapper ) { AwardAchievement( ACHIEVEMENT_TF_PYRO_KILL_SPIES ); } } // ACHIEVEMENT_TF_PYRO_BURN_RJ_SOLDIER - Pyro ignited a rocket jumping soldier in mid-air if ( pTFPlayerVictim->IsPlayerClass(TF_CLASS_SOLDIER) ) { if ( pTFPlayerVictim->RocketJumped() && !pTFPlayerVictim->GetGroundEntity() ) { AwardAchievement( ACHIEVEMENT_TF_PYRO_BURN_RJ_SOLDIER ); } } // ACHIEVEMENT_TF_PYRO_DEFEND_POINTS - Pyro kills targets capping control points CTriggerAreaCapture *pAreaTrigger = pTFPlayerVictim->GetControlPointStandingOn(); if ( pAreaTrigger ) { CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint(); if ( pCP && pCP->GetOwner() == GetTeamNumber() ) { if ( TeamplayGameRules()->TeamMayCapturePoint( pTFPlayerVictim->GetTeamNumber(), pCP->GetPointIndex() ) && TeamplayGameRules()->PlayerMayCapturePoint( pTFPlayerVictim, pCP->GetPointIndex() ) ) { AwardAchievement( ACHIEVEMENT_TF_PYRO_DEFEND_POINTS ); } } } // ACHIEVEMENT_TF_MEDIC_ASSIST_PYRO // if we're invuln, let the medic know that we burned someone if ( m_Shared.InCond( TF_COND_INVULNERABLE ) || m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) { int i; int iNumHealers = m_Shared.GetNumHealers(); for ( i=0;iGetChargeEffectBeingProvided() == MEDIGUN_CHARGE_INVULN ) { // Tell the clients involved in the ignition CSingleUserRecipientFilter medic_filter( pMedic ); UserMessageBegin( medic_filter, "PlayerIgnitedInv" ); WRITE_BYTE( entindex() ); WRITE_BYTE( pTFPlayerVictim->entindex() ); WRITE_BYTE( pMedic->entindex() ); MessageEnd(); } } } // Tell the clients involved in the ignition CRecipientFilter involved_filter; involved_filter.AddRecipient( this ); involved_filter.AddRecipient( pTFPlayerVictim ); UserMessageBegin( involved_filter, "PlayerIgnited" ); WRITE_BYTE( entindex() ); WRITE_BYTE( pTFPlayerVictim->entindex() ); WRITE_BYTE( pWeapon ? pWeapon->GetWeaponID() : 0 ); MessageEnd(); IGameEvent *event = gameeventmanager->CreateEvent( "player_ignited" ); if ( event ) { event->SetInt( "pyro_entindex", entindex() ); event->SetInt( "victim_entindex", pTFPlayerVictim->entindex() ); event->SetInt( "weaponid", pWeapon ? pWeapon->GetWeaponID() : 0 ); gameeventmanager->FireEvent( event, true ); } } //----------------------------------------------------------------------------- // Purpose: Returns true if the player is capturing a point. //----------------------------------------------------------------------------- bool CTFPlayer::IsCapturingPoint() { CTriggerAreaCapture *pAreaTrigger = GetControlPointStandingOn(); if ( pAreaTrigger ) { CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint(); if ( pCP ) { if ( TeamplayGameRules()->TeamMayCapturePoint( GetTeamNumber(), pCP->GetPointIndex() ) && TeamplayGameRules()->PlayerMayCapturePoint( this, pCP->GetPointIndex() ) ) { // if we own this point, we're no longer "capturing" it return pCP->GetOwner() != GetTeamNumber(); } } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFTeam *CTFPlayer::GetTFTeam( void ) { CTFTeam *pTeam = dynamic_cast( GetTeam() ); Assert( pTeam ); return pTeam; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFTeam *CTFPlayer::GetOpposingTFTeam( void ) { if ( TFTeamMgr() ) { int iTeam = GetTeamNumber(); if ( iTeam == TF_TEAM_RED ) { return TFTeamMgr()->GetTeam( TF_TEAM_BLUE ); } else if ( iTeam == TF_TEAM_BLUE ) { return TFTeamMgr()->GetTeam( TF_TEAM_RED ); } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Give this player the "i just teleported" effect for 12 seconds //----------------------------------------------------------------------------- void CTFPlayer::TeleportEffect( void ) { m_Shared.AddCond( TF_COND_TELEPORTED ); float flDuration = 12.f; if ( TFGameRules()->IsMannVsMachineMode() && m_bIsABot && IsBotOfType( TF_BOT_TYPE ) ) { flDuration = 30.f; } // Also removed on death SetContextThink( &CTFPlayer::RemoveTeleportEffect, gpGlobals->curtime + flDuration, "TFPlayer_TeleportEffect" ); } //----------------------------------------------------------------------------- // Purpose: Remove the teleporter effect //----------------------------------------------------------------------------- void CTFPlayer::RemoveTeleportEffect( void ) { m_Shared.RemoveCond( TF_COND_TELEPORTED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StopRagdollDeathAnim( void ) { CTFRagdoll *pRagdoll = dynamic_cast( m_hRagdoll.Get() ); if ( pRagdoll ) { pRagdoll->m_iDamageCustom = 0; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CreateRagdollEntity( void ) { CreateRagdollEntity( false, false, false, false, false, false, false, false ); } //----------------------------------------------------------------------------- // Purpose: Create a ragdoll entity to pass to the client. //----------------------------------------------------------------------------- void CTFPlayer::CreateRagdollEntity( bool bGib, bool bBurning, bool bElectrocuted, bool bOnGround, bool bCloakedCorpse, bool bGoldRagdoll, bool bIceRagdoll, bool bBecomeAsh, int iDamageCustom, bool bCritOnHardHit ) { // If we already have a ragdoll destroy it. CTFRagdoll *pRagdoll = dynamic_cast( m_hRagdoll.Get() ); if( pRagdoll ) { UTIL_Remove( pRagdoll ); pRagdoll = NULL; } Assert( pRagdoll == NULL ); // Create a ragdoll. pRagdoll = dynamic_cast( CreateEntityByName( "tf_ragdoll" ) ); if ( pRagdoll ) { pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); pRagdoll->m_vecRagdollVelocity = GetAbsVelocity(); pRagdoll->m_vecForce = m_vecForce; pRagdoll->m_nForceBone = m_nForceBone; Assert( entindex() >= 1 && entindex() <= MAX_PLAYERS ); pRagdoll->m_iPlayerIndex.Set( entindex() ); pRagdoll->m_bGib = bGib; pRagdoll->m_bBurning = bBurning; pRagdoll->m_bElectrocuted = bElectrocuted; pRagdoll->m_bOnGround = bOnGround; pRagdoll->m_bCloaked = bCloakedCorpse; pRagdoll->m_iDamageCustom = iDamageCustom; pRagdoll->m_iTeam = GetTeamNumber(); pRagdoll->m_iClass = GetPlayerClass()->GetClassIndex(); pRagdoll->m_bGoldRagdoll = bGoldRagdoll; pRagdoll->m_bIceRagdoll = bIceRagdoll; pRagdoll->m_bBecomeAsh = bBecomeAsh; pRagdoll->m_bCritOnHardHit = bCritOnHardHit; pRagdoll->m_flHeadScale = m_flHeadScale; pRagdoll->m_flTorsoScale = m_flTorsoScale; pRagdoll->m_flHandScale = m_flHandScale; } // Turn off the player. AddSolidFlags( FSOLID_NOT_SOLID ); AddEffects( EF_NODRAW | EF_NOSHADOW ); SetMoveType( MOVETYPE_NONE ); // Add additional gib setup. if ( bGib ) { m_nRenderFX = kRenderFxRagdoll; } // Save ragdoll handle. m_hRagdoll = pRagdoll; } //----------------------------------------------------------------------------- // Purpose: Destroy's a ragdoll, called with a player is disconnecting. //----------------------------------------------------------------------------- void CTFPlayer::DestroyRagdoll( void ) { CTFRagdoll *pRagdoll = dynamic_cast( m_hRagdoll.Get() ); if( pRagdoll ) { UTIL_Remove( pRagdoll ); } // Remove the feign death ragdoll at the same time. pRagdoll = dynamic_cast( m_hFeignRagdoll.Get() ); if( pRagdoll ) { UTIL_Remove( pRagdoll ); } } //----------------------------------------------------------------------------- // Purpose: The player appears to die, creating a corpse and silently stealthing. // Occurs when a player takes damage with the dead ringer active //----------------------------------------------------------------------------- void CTFPlayer::SpyDeadRingerDeath( const CTakeDamageInfo& info ) { // Can't feign death if we're actually dead or if we're not a spy. if ( !IsAlive() || !IsPlayerClass( TF_CLASS_SPY ) ) return; // Can't feign death if we're already stealthed. if ( m_Shared.InCond( TF_COND_STEALTHED ) ) return; // Can't feign death if we aren't at full cloak energy. if ( !CanGoInvisible( true ) || ( m_Shared.GetSpyCloakMeter() < 100.0f ) ) return; m_Shared.SetSpyCloakMeter( 50.0f ); m_bGoingFeignDeath = true; FeignDeath( info ); // Go feign death. m_Shared.AddCond( TF_COND_FEIGN_DEATH, tf_feign_death_duration.GetFloat() ); m_bGoingFeignDeath = false; } //----------------------------------------------------------------------------- // Purpose: The player appears to die, creating a corpse //----------------------------------------------------------------------------- void CTFPlayer::FeignDeath( const CTakeDamageInfo& info ) { if ( HasTheFlag() ) { DropFlag(); } // Dead Ringer death removes Powerup Rune for authenticity DropRune(); // Only drop disguised ragdoll & weapon if we're disguised as a teammate. bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED ) && (m_Shared.GetDisguiseTeam() == GetTeamNumber()); // We want the ragdoll to burn if the player was burning and was not disguised as a pyro. bool bBurning = m_Shared.InCond( TF_COND_BURNING ) && (!bDisguised || (TF_CLASS_PYRO != m_Shared.GetDisguiseClass())); // Stop us from burning and other effects that would give the game away. m_Shared.RemoveCond( TF_COND_BURNING ); m_Shared.RemoveCond( TF_COND_BLEEDING ); RemoveTeleportEffect(); // Fake death audio. EmitSound( "BaseCombatCharacter.StopWeaponSounds" ); SpeakConceptIfAllowed( MP_CONCEPT_DIED ); DeathSound( info ); // Check if we should create gibs. bool bGib = ShouldGib( info ); SetGibbedOnLastDeath( bGib ); // Fake death notice. TFGameRules()->DeathNotice( this, info ); // Drop an empty ammo pack! if ( ShouldDropAmmoPack() ) { DropAmmoPack( info, true /*Empty*/, bDisguised ); } if ( TFGameRules()->IsInMedievalMode() ) { DropHealthPack( info, true ); } if ( GetActiveTFWeapon() ) { int iDropHealthOnKill = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetActiveTFWeapon(), iDropHealthOnKill, drop_health_pack_on_kill ); if ( iDropHealthOnKill == 1 ) { DropHealthPack( info, true ); } } CTFPlayer *pTFPlayer = ToTFPlayer( info.GetAttacker() ); if ( pTFPlayer ) { int iKillForcesAttackerToLaugh = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFPlayer, iKillForcesAttackerToLaugh, kill_forces_attacker_to_laugh ); if ( iKillForcesAttackerToLaugh == 1 ) { // force the attacker to laugh! pTFPlayer->Taunt( TAUNT_MISC_ITEM, MP_CONCEPT_TAUNT_LAUGH ); } CTFWeaponInvis *pWpn = (CTFWeaponInvis *)Weapon_OwnsThisID( TF_WEAPON_INVIS ); if ( pWpn && pWpn->HasFeignDeath() ) { DropDeathCallingCard( pTFPlayer, this ); } } // Create a ragdoll. CreateFeignDeathRagdoll( info, bGib, bBurning, bDisguised ); // Note that we succeeded for stats tracking. EconEntity_OnOwnerKillEaterEvent( dynamic_cast( GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA2 ) ), this, pTFPlayer, // in this case the "victim" is the person doing the damage kKillEaterEvent_DeathsFeigned ); } //----------------------------------------------------------------------------- // Purpose: Create a ragdoll entity for feign death. Does not hide the player. // Creates an entirely seperate ragdoll that isn't used for client death cam or other real death stuff. //----------------------------------------------------------------------------- void CTFPlayer::CreateFeignDeathRagdoll( const CTakeDamageInfo& info, bool bGib, bool bBurning, bool bDisguised ) { // If we already have a feigning ragdoll destroy it. CTFRagdoll *pRagdoll = dynamic_cast( m_hFeignRagdoll.Get() ); if( pRagdoll ) { UTIL_Remove( pRagdoll ); pRagdoll = NULL; } Assert( pRagdoll == NULL ); // Create a ragdoll. pRagdoll = dynamic_cast( CreateEntityByName( "tf_ragdoll" ) ); if ( pRagdoll ) { pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); pRagdoll->m_vecRagdollVelocity = m_vecFeignDeathVelocity; pRagdoll->m_vecForce = CalcDamageForceVector( info ); pRagdoll->m_nForceBone = m_nForceBone; Assert( entindex() >= 1 && entindex() <= MAX_PLAYERS ); pRagdoll->m_iPlayerIndex.Set( entindex() ); pRagdoll->m_bGib = bGib; pRagdoll->m_bBurning = bBurning; pRagdoll->m_bElectrocuted = false; pRagdoll->m_bFeignDeath = true; pRagdoll->m_bWasDisguised = bDisguised; pRagdoll->m_bBecomeAsh = false; pRagdoll->m_bOnGround = (bool) (GetFlags() & FL_ONGROUND); pRagdoll->m_iDamageCustom = info.GetDamageCustom(); pRagdoll->m_bCritOnHardHit = false; pRagdoll->m_flHeadScale = m_flHeadScale; pRagdoll->m_flTorsoScale = m_flTorsoScale; pRagdoll->m_flHandScale = m_flHandScale; { int iGoldRagdoll = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iGoldRagdoll, set_turn_to_gold ); } pRagdoll->m_bGoldRagdoll = iGoldRagdoll != 0; int iIceRagdoll = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iIceRagdoll, set_turn_to_ice ); } pRagdoll->m_bIceRagdoll = iIceRagdoll != 0; int iRagdollsBecomeAsh = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iRagdollsBecomeAsh, ragdolls_become_ash ); } pRagdoll->m_bBecomeAsh = iRagdollsBecomeAsh != 0; int iRagdollsPlasmaEffect = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iRagdollsPlasmaEffect, ragdolls_plasma_effect ); } if ( iRagdollsPlasmaEffect ) { pRagdoll->m_iDamageCustom = TF_DMG_CUSTOM_PLASMA; } int iCritOnHardHit = 0; if ( info.GetWeapon() ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iCritOnHardHit, crit_on_hard_hit ); } pRagdoll->m_bCritOnHardHit = iCritOnHardHit != 0; } // If we are disguised, make the ragdoll look like our disguise. if ( bDisguised ) { pRagdoll->m_iTeam = m_Shared.GetDisguiseTeam(); pRagdoll->m_iClass = m_Shared.GetDisguiseClass(); } else { pRagdoll->m_iTeam = GetTeamNumber(); pRagdoll->m_iClass = GetPlayerClass()->GetClassIndex(); } } // Exaggerate ragdoll velocity if recently hit by blast damage. if ( !bGib && ( info.GetDamageType() & DMG_BLAST ) ) { Vector vForceModifier = info.GetDamageForce(); vForceModifier.x *= 1.5; vForceModifier.y *= 1.5; vForceModifier.z *= 1; pRagdoll->m_vecForce = vForceModifier; } // Save ragdoll handle. m_hFeignRagdoll = pRagdoll; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Weapon_FrameUpdate( void ) { BaseClass::Weapon_FrameUpdate(); if ( m_hOffHandWeapon.Get() && m_hOffHandWeapon->IsWeaponVisible() ) { m_hOffHandWeapon->Operator_FrameUpdate( this ); } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CTFPlayer::Weapon_HandleAnimEvent( animevent_t *pEvent ) { BaseClass::Weapon_HandleAnimEvent( pEvent ); if ( m_hOffHandWeapon.Get() ) { m_hOffHandWeapon->Operator_HandleAnimEvent( pEvent, this ); } } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CTFPlayer::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget , const Vector *pVelocity ) { } //----------------------------------------------------------------------------- // Purpose: Call this when this player fires a weapon to allow other systems to react //----------------------------------------------------------------------------- void CTFPlayer::OnMyWeaponFired( CBaseCombatWeapon *weapon ) { BaseClass::OnMyWeaponFired( weapon ); // mark region as 'in combat' if ( m_inCombatThrottleTimer.IsElapsed() ) { CTFWeaponBase *tfWeapon = static_cast< CTFWeaponBase * >( weapon ); if ( !tfWeapon ) { return; } switch ( tfWeapon->GetWeaponID() ) { case TF_WEAPON_MEDIGUN: case TF_WEAPON_PDA: case TF_WEAPON_PDA_ENGINEER_BUILD: case TF_WEAPON_PDA_ENGINEER_DESTROY: case TF_WEAPON_PDA_SPY: case TF_WEAPON_BUILDER: case TF_WEAPON_DISPENSER: case TF_WEAPON_INVIS: case TF_WEAPON_LUNCHBOX: case TF_WEAPON_BUFF_ITEM: case TF_WEAPON_PUMPKIN_BOMB: case TF_WEAPON_WRENCH: // skip this so engineer building doesn't mark 'in combat' case TF_WEAPON_PDA_SPY_BUILD: // not a 'combat' weapon return; }; // important to keep this at one second, so rate cvars make sense (units/sec) m_inCombatThrottleTimer.Start( 1.0f ); // only search up/down StepHeight as a cheap substitute for line of sight CUtlVector< CNavArea * > nearbyAreaVector; CollectSurroundingAreas( &nearbyAreaVector, GetLastKnownArea(), tf_nav_in_combat_range.GetFloat(), StepHeight, StepHeight ); for( int i=0; i( nearbyAreaVector[i] )->OnCombat(); } } } //----------------------------------------------------------------------------- // Purpose: Remove invisibility, called when player attacks //----------------------------------------------------------------------------- void CTFPlayer::RemoveInvisibility( void ) { if ( !m_Shared.IsStealthed() ) return; // remove quickly CTFPlayer *pProvider = ToTFPlayer( m_Shared.GetConditionProvider( TF_COND_STEALTHED_USER_BUFF ) ); bool bAEStealth = ( m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF ) && pProvider && ( pProvider->IsPlayerClass( TF_CLASS_SPY ) ? true : false ) && ( pProvider != this ) ); if ( m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF ) ) { m_Shared.AddCond( TF_COND_STEALTHED_USER_BUFF_FADING, ( bAEStealth ) ? 4.f : 0.5f ); } m_Shared.FadeInvis( bAEStealth ? 2.f : 0.5f ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::SayAskForBall() { if ( !TFGameRules() || !TFGameRules()->IsPasstimeMode() || ( m_Shared.AskForBallTime() > gpGlobals->curtime ) ) { return false; } CPasstimeBall *pBall = g_pPasstimeLogic->GetBall(); if ( !pBall ) { return false; } CTFPlayer *pBallCarrier = pBall->GetCarrier(); if ( !pBallCarrier ) { return false; } HudNotification_t cantCarryReason; if ( !CPasstimeGun::BValidPassTarget( pBallCarrier, this, &cantCarryReason ) ) { if ( cantCarryReason ) { CSingleUserReliableRecipientFilter filter( this ); TFGameRules()->SendHudNotification( filter, cantCarryReason ); } return false; } CRecipientFilter filter; filter.AddRecipient( this ); filter.AddRecipient( pBallCarrier ); filter.MakeReliable(); EmitSound( filter, entindex(), "Passtime.AskForBall" ); ++CTF_GameStats.m_passtimeStats.summary.nTotalPassRequests; m_Shared.SetAskForBallTime( gpGlobals->curtime + 5.0f ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SaveMe( void ) { if ( !IsAlive() || IsPlayerClass( TF_CLASS_UNDEFINED ) || GetTeamNumber() < TF_TEAM_RED ) return; m_bSaveMeParity = !m_bSaveMeParity; } //----------------------------------------------------------------------------- // Purpose: drops the flag //----------------------------------------------------------------------------- void CC_DropItem( void ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_GetCommandClient() ); if ( !pPlayer ) return; if ( pPlayer->m_Shared.IsCarryingRune() ) { pPlayer->DropRune(); return; } if ( pPlayer->HasTheFlag() ) { pPlayer->DropFlag(); } } static ConCommand dropitem( "dropitem", CC_DropItem, "Drop the flag." ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CObserverPoint::CObserverPoint() { m_bMatchSummary = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CObserverPoint::Activate( void ) { BaseClass::Activate(); if ( m_bMatchSummary ) { // sanity check to make sure the competitive match summary target is disabled until we're ready for it SetDisabled( true ); } if ( m_iszAssociateTeamEntityName != NULL_STRING ) { m_hAssociatedTeamEntity = gEntList.FindEntityByName( NULL, m_iszAssociateTeamEntityName ); if ( !m_hAssociatedTeamEntity ) { Warning("info_observer_point (%s) couldn't find associated team entity named '%s'\n", GetDebugName(), STRING(m_iszAssociateTeamEntityName) ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CObserverPoint::CanUseObserverPoint( CTFPlayer *pPlayer ) { if ( m_bDisabled ) return false; // Only spectate observer points on control points in the current miniround if ( g_pObjectiveResource->PlayingMiniRounds() && m_hAssociatedTeamEntity ) { CTeamControlPoint *pPoint = dynamic_cast(m_hAssociatedTeamEntity.Get()); if ( pPoint ) { bool bInRound = g_pObjectiveResource->IsInMiniRound( pPoint->GetPointIndex() ); if ( !bInRound ) return false; } } if ( m_hAssociatedTeamEntity && mp_forcecamera.GetInt() == OBS_ALLOW_TEAM ) { // don't care about this check during a team win if ( TFGameRules() && TFGameRules()->State_Get() != GR_STATE_TEAM_WIN ) { // If we don't own the associated team entity, we can't use this point if ( m_hAssociatedTeamEntity->GetTeamNumber() != pPlayer->GetTeamNumber() && pPlayer->GetTeamNumber() >= FIRST_GAME_TEAM ) return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CObserverPoint::UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CObserverPoint::InputEnable( inputdata_t &inputdata ) { m_bDisabled = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CObserverPoint::InputDisable( inputdata_t &inputdata ) { m_bDisabled = true; } BEGIN_DATADESC( CObserverPoint ) DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), DEFINE_KEYFIELD( m_bDefaultWelcome, FIELD_BOOLEAN, "defaultwelcome" ), DEFINE_KEYFIELD( m_iszAssociateTeamEntityName, FIELD_STRING, "associated_team_entity" ), DEFINE_KEYFIELD( m_flFOV, FIELD_FLOAT, "fov" ), DEFINE_KEYFIELD( m_bMatchSummary, FIELD_BOOLEAN, "match_summary" ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), END_DATADESC() LINK_ENTITY_TO_CLASS( info_observer_point, CObserverPoint ); //----------------------------------------------------------------------------- // Purpose: Builds a list of entities that this player can observe. // Returns the index into the list of the player's current observer target. //----------------------------------------------------------------------------- int CTFPlayer::BuildObservableEntityList( void ) { m_hObservableEntities.Purge(); int iCurrentIndex = -1; // Add all the map-placed observer points CBaseEntity *pObserverPoint = gEntList.FindEntityByClassname( NULL, "info_observer_point" ); while ( pObserverPoint ) { m_hObservableEntities.AddToTail( pObserverPoint ); if ( m_hObserverTarget.Get() == pObserverPoint ) { iCurrentIndex = (m_hObservableEntities.Count()-1); } pObserverPoint = gEntList.FindEntityByClassname( pObserverPoint, "info_observer_point" ); } // Add all the players for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer ) { m_hObservableEntities.AddToTail( pPlayer ); if ( m_hObserverTarget.Get() == pPlayer ) { iCurrentIndex = (m_hObservableEntities.Count()-1); } } } // Add all my objects int iNumObjects = GetObjectCount(); for ( int i = 0; i < iNumObjects; i++ ) { CBaseObject *pObj = GetObject( i ); if ( pObj ) { m_hObservableEntities.AddToTail( pObj ); if ( m_hObserverTarget.Get() == pObj ) { iCurrentIndex = ( m_hObservableEntities.Count() - 1 ); } } } #ifdef TF_RAID_MODE // Add all of the objects for my team if we're in Raid mode if ( TFGameRules() && TFGameRules()->IsRaidMode() ) { CTFTeam *pTeam = TFTeamMgr()->GetTeam( TF_TEAM_BLUE ); if ( pTeam ) { int nTeamObjectCount = pTeam->GetNumObjects(); for ( int iObject = 0; iObject < nTeamObjectCount; ++iObject ) { CBaseObject *pObj = pTeam->GetObject( iObject ); if ( !pObj ) continue; // we've already added our own buildings in the previous loop if ( pObj->GetOwner() == this ) continue; m_hObservableEntities.AddToTail( pObj ); if ( m_hObserverTarget.Get() == pObj ) { iCurrentIndex = ( m_hObservableEntities.Count() - 1 ); } } } } #endif // TF_RAID_MODE // If there are any team_train_watchers, add the train they are linked to CTeamTrainWatcher *pWatcher = dynamic_cast( gEntList.FindEntityByClassname( NULL, "team_train_watcher" ) ); while ( pWatcher ) { if ( !pWatcher->IsDisabled() ) { CBaseEntity *pTrain = pWatcher->GetTrainEntity(); if ( pTrain ) { m_hObservableEntities.AddToTail( pTrain ); if ( m_hObserverTarget.Get() == pTrain ) { iCurrentIndex = (m_hObservableEntities.Count()-1); } } } pWatcher = dynamic_cast( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) ); } // observe active bosses if ( TFGameRules()->GetActiveBoss() ) { m_hObservableEntities.AddToTail( TFGameRules()->GetActiveBoss() ); if ( m_hObserverTarget.Get() == TFGameRules()->GetActiveBoss() ) { iCurrentIndex = ( m_hObservableEntities.Count() - 1 ); } } return iCurrentIndex; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::GetNextObserverSearchStartPoint( bool bReverse ) { int iDir = bReverse ? -1 : 1; int startIndex = BuildObservableEntityList(); int iMax = m_hObservableEntities.Count()-1; startIndex += iDir; if (startIndex > iMax) startIndex = 0; else if (startIndex < 0) startIndex = iMax; return startIndex; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *CTFPlayer::FindNextObserverTarget(bool bReverse) { int startIndex = GetNextObserverSearchStartPoint( bReverse ); int currentIndex = startIndex; int iDir = bReverse ? -1 : 1; int iMax = m_hObservableEntities.Count()-1; // Make sure the current index is within the max. Can happen if we were previously // spectating an object which has been destroyed. if ( startIndex > iMax ) { currentIndex = startIndex = 1; } do { CBaseEntity *nextTarget = m_hObservableEntities[currentIndex]; if ( IsValidObserverTarget( nextTarget ) ) return nextTarget; currentIndex += iDir; // Loop through the entities if (currentIndex > iMax) { currentIndex = 0; } else if (currentIndex < 0) { currentIndex = iMax; } } while ( currentIndex != startIndex ); return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsValidObserverTarget(CBaseEntity * target) { if ( !target || target == this ) return false; // if we are coaching, the target is always valid if ( target && m_hStudent == target && target->IsPlayer() ) { return true; } bool bAllowInTournament = false; if ( TFGameRules()->IsMannVsMachineMode() ) { bAllowInTournament = true; } if ( TFGameRules()->IsPasstimeMode() && (target == TFGameRules()->GetObjectiveObserverTarget()) ) { return true; } if ( target && !target->IsPlayer() ) { //Can only spectate players in Tournament Mode if ( TFGameRules()->IsInTournamentMode() == true && !bAllowInTournament ) return false; CObserverPoint *pObsPoint = dynamic_cast(target); if ( pObsPoint && !pObsPoint->CanUseObserverPoint( this ) ) return false; CFuncTrackTrain *pTrain = dynamic_cast(target); if ( pTrain ) { // can only spec the trains while the round is running if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) return false; } if ( GetTeamNumber() == TEAM_SPECTATOR ) return true; switch ( mp_forcecamera.GetInt() ) { case OBS_ALLOW_ALL : break; case OBS_ALLOW_TEAM : if ( target->GetTeamNumber() != TEAM_UNASSIGNED && GetTeamNumber() != target->GetTeamNumber() ) return false; break; case OBS_ALLOW_NONE : return false; } return true; } return BaseClass::IsValidObserverTarget( target ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PickWelcomeObserverPoint( void ) { //Don't just spawn at the world origin, find a nice spot to look from while we choose our team and class. CObserverPoint *pObserverPoint = (CObserverPoint *)gEntList.FindEntityByClassname( NULL, "info_observer_point" ); while ( pObserverPoint ) { if ( IsValidObserverTarget( pObserverPoint ) ) { SetObserverTarget( pObserverPoint ); } if ( pObserverPoint->IsDefaultWelcome() ) break; pObserverPoint = (CObserverPoint *)gEntList.FindEntityByClassname( pObserverPoint, "info_observer_point" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::SetObserverTarget(CBaseEntity *target) { ClearZoomOwner(); SetFOV( this, 0 ); if ( !BaseClass::SetObserverTarget(target) ) return false; CObserverPoint *pObsPoint = dynamic_cast(target); if ( pObsPoint ) { SetViewOffset( vec3_origin ); JumptoPosition( target->GetAbsOrigin(), target->EyeAngles() ); SetFOV( pObsPoint, pObsPoint->m_flFOV ); } if ( !m_bArenaIsAFK ) { m_flLastAction = gpGlobals->curtime; } return true; } //----------------------------------------------------------------------------- // Purpose: Find the nearest team member within the distance of the origin. // Favor players who are the same class. //----------------------------------------------------------------------------- CBaseEntity *CTFPlayer::FindNearestObservableTarget( Vector vecOrigin, float flMaxDist ) { CTeam *pTeam = GetTeam(); CBaseEntity *pReturnTarget = NULL; bool bFoundClass = false; float flCurDistSqr = (flMaxDist * flMaxDist); int iNumPlayers = pTeam->GetNumPlayers(); if ( pTeam->GetTeamNumber() == TEAM_SPECTATOR ) { iNumPlayers = gpGlobals->maxClients; } for ( int i = 0; i < iNumPlayers; i++ ) { CTFPlayer *pPlayer = NULL; if ( pTeam->GetTeamNumber() == TEAM_SPECTATOR ) { pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); } else { pPlayer = ToTFPlayer( pTeam->GetPlayer(i) ); } if ( !pPlayer ) continue; if ( !IsValidObserverTarget(pPlayer) ) continue; float flDistSqr = ( pPlayer->GetAbsOrigin() - vecOrigin ).LengthSqr(); if ( flDistSqr < flCurDistSqr ) { // If we've found a player matching our class already, this guy needs // to be a matching class and closer to boot. if ( !bFoundClass || pPlayer->IsPlayerClass( GetPlayerClass()->GetClassIndex() ) ) { pReturnTarget = pPlayer; flCurDistSqr = flDistSqr; if ( pPlayer->IsPlayerClass( GetPlayerClass()->GetClassIndex() ) ) { bFoundClass = true; } } } else if ( !bFoundClass ) { if ( pPlayer->IsPlayerClass( GetPlayerClass()->GetClassIndex() ) ) { pReturnTarget = pPlayer; flCurDistSqr = flDistSqr; bFoundClass = true; } } } if ( !bFoundClass && IsPlayerClass( TF_CLASS_ENGINEER ) ) { // let's spectate our sentry instead, we didn't find any other engineers to spec int iNumObjects = GetObjectCount(); for ( int i=0;iGetType() == OBJ_SENTRYGUN ) { pReturnTarget = pObj; } } } return pReturnTarget; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::FindInitialObserverTarget( void ) { // if there is a Boss active, watch him if ( TFGameRules()->GetActiveBoss() ) { m_hObserverTarget.Set( TFGameRules()->GetActiveBoss() ); } // If we're on a team (i.e. not a pure observer), try and find // a target that'll give the player the most useful information. if ( GetTeamNumber() >= FIRST_GAME_TEAM ) { CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; if ( pMaster ) { // Has our forward cap point been contested recently? int iFarthestPoint = TFGameRules()->GetFarthestOwnedControlPoint( GetTeamNumber(), false ); if ( iFarthestPoint != -1 ) { float flTime = pMaster->PointLastContestedAt( iFarthestPoint ); if ( flTime != -1 && flTime > (gpGlobals->curtime - 30) ) { // Does it have an associated viewpoint? CBaseEntity *pObserverPoint = gEntList.FindEntityByClassname( NULL, "info_observer_point" ); while ( pObserverPoint ) { CObserverPoint *pObsPoint = assert_cast(pObserverPoint); if ( pObsPoint && pObsPoint->m_hAssociatedTeamEntity == pMaster->GetControlPoint(iFarthestPoint) ) { if ( IsValidObserverTarget( pObsPoint ) ) { m_hObserverTarget.Set( pObsPoint ); return; } } pObserverPoint = gEntList.FindEntityByClassname( pObserverPoint, "info_observer_point" ); } } } // Has the point beyond our farthest been contested lately? iFarthestPoint += (ObjectiveResource()->GetBaseControlPointForTeam( GetTeamNumber() ) == 0 ? 1 : -1); if ( iFarthestPoint >= 0 && iFarthestPoint < MAX_CONTROL_POINTS ) { float flTime = pMaster->PointLastContestedAt( iFarthestPoint ); if ( flTime != -1 && flTime > (gpGlobals->curtime - 30) ) { // Try and find a player near that cap point CBaseEntity *pCapPoint = pMaster->GetControlPoint(iFarthestPoint); if ( pCapPoint ) { CBaseEntity *pTarget = FindNearestObservableTarget( pCapPoint->GetAbsOrigin(), 1500 ); if ( pTarget ) { m_hObserverTarget.Set( pTarget ); return; } } } } } } // Find the nearest guy near myself CBaseEntity *pTarget = FindNearestObservableTarget( GetAbsOrigin(), FLT_MAX ); if ( pTarget ) { m_hObserverTarget.Set( pTarget ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ValidateCurrentObserverTarget( void ) { // If our current target is a dead player who's gibbed / died, refind as if // we were finding our initial target, so we end up somewhere useful. if ( m_hObserverTarget && m_hObserverTarget->IsPlayer() ) { CBasePlayer *player = ToBasePlayer( m_hObserverTarget ); if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING ) { // if we are coaching, don't switch if ( m_hStudent == m_hObserverTarget ) { return; } // Once we're past the pause after death, find a new target if ( (player->GetDeathTime() + DEATH_ANIMATION_TIME ) < gpGlobals->curtime ) { FindInitialObserverTarget(); } return; } } if ( m_hObserverTarget && !m_hObserverTarget->IsPlayer() ) { // can only spectate players in-eye if ( m_iObserverMode == OBS_MODE_IN_EYE ) { ForceObserverMode( OBS_MODE_CHASE ); } } BaseClass::ValidateCurrentObserverTarget(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CheckObserverSettings() { // make sure we are always observing the student if ( m_hObserverTarget && m_hStudent && m_hStudent != m_hObserverTarget ) { SetObserverTarget( m_hStudent ); } else if ( TFGameRules() ) { // is there a current entity that is the required spectator target? if ( TFGameRules()->GetRequiredObserverTarget() ) { SetObserverTarget( TFGameRules()->GetRequiredObserverTarget() ); return; } if ( TFGameRules()->IsPasstimeMode() && g_pPasstimeLogic && (GetObserverMode() == OBS_MODE_POI) ) { CPasstimeBall *pBall = g_pPasstimeLogic->GetBall(); if ( !pBall || ((m_hObserverTarget.Get() == pBall) && pBall->BOutOfPlay()) ) { FindInitialObserverTarget(); } else if ( !pBall->BOutOfPlay() && (GetObserverTarget() != TFGameRules()->GetObjectiveObserverTarget()) ) { SetObserverTarget( TFGameRules()->GetObjectiveObserverTarget() ); } return; } // make sure we're not trying to spec the train during a team win // if we are, switch to spectating the last control point instead (where the train ended) if ( m_hObserverTarget && m_hObserverTarget->IsBaseTrain() && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) { // find the nearest spectator point to use instead of the train CObserverPoint *pObserverPoint = (CObserverPoint *)gEntList.FindEntityByClassname( NULL, "info_observer_point" ); CObserverPoint *pClosestPoint = NULL; float flMinDistance = -1.0f; Vector vecTrainOrigin = m_hObserverTarget->GetAbsOrigin(); while ( pObserverPoint ) { if ( IsValidObserverTarget( pObserverPoint ) ) { float flDist = pObserverPoint->GetAbsOrigin().DistTo( vecTrainOrigin ); if ( flMinDistance < 0 || flDist < flMinDistance ) { flMinDistance = flDist; pClosestPoint = pObserverPoint; } } pObserverPoint = (CObserverPoint *)gEntList.FindEntityByClassname( pObserverPoint, "info_observer_point" ); } if ( pClosestPoint ) { SetObserverTarget( pClosestPoint ); } } } BaseClass::CheckObserverSettings(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Touch( CBaseEntity *pOther ) { CTFPlayer *pVictim = ToTFPlayer( pOther ); if ( pVictim ) { // ACHIEVEMENT_TF_SPY_BUMP_CLOAKED_SPY if ( !m_Shared.IsAlly( pVictim ) ) { if ( IsPlayerClass( TF_CLASS_SPY ) && pVictim->IsPlayerClass( TF_CLASS_SPY ) ) { if ( m_Shared.InCond( TF_COND_STEALTHED ) && pVictim->m_Shared.InCond( TF_COND_STEALTHED ) ) { AwardAchievement( ACHIEVEMENT_TF_SPY_BUMP_CLOAKED_SPY ); } } } CheckUncoveringSpies( pVictim ); // ACHIEVEMENT_TF_HEAVY_BLOCK_INVULN_HEAVY if ( !m_Shared.IsAlly( pVictim ) ) { if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && pVictim->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { CTFTeam *pTeam = GetGlobalTFTeam( GetTeamNumber() ); if ( pTeam && pTeam->GetRole() == TEAM_ROLE_DEFENDERS ) { if ( m_Shared.InCond( TF_COND_INVULNERABLE ) || m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) { if ( pVictim->m_Shared.InCond( TF_COND_INVULNERABLE ) || pVictim->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) ) { float flMaxSpeed = 50.0f * 50.0f; if ( ( GetAbsVelocity().LengthSqr() < flMaxSpeed ) && ( pVictim->GetAbsVelocity().LengthSqr() < flMaxSpeed ) ) { AwardAchievement( ACHIEVEMENT_TF_HEAVY_BLOCK_INVULN_HEAVY ); } } } } } // **************************************************************************************************************** // Halloween Karts if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) && m_flHalloweenKartPushEventTime < gpGlobals->curtime ) { // calculate a force and save it off, it is used on a later frame cause it is to late to apply the force here float flImpactForce = GetLocalVelocity().Length(); if ( flImpactForce > 10.0f ) { float flForceMult = 1.0f; Vector vAim = GetLocalVelocity(); vAim.NormalizeInPlace(); Vector vOrigin = GetAbsOrigin(); // Force direction is velocity of the player in the case that this is a head on collison. // Trace trace_t pTrace; Ray_t ray; CTraceFilterOnlyNPCsAndPlayer pFilter( this, COLLISION_GROUP_NONE ); //tf_halloween_kart_impact_lookahead //tf_halloween_kart_impact_bounds_scale ray.Init( vOrigin, Vector( 0, 0, 16 ) + vOrigin + vAim * tf_halloween_kart_impact_lookahead.GetFloat(), GetPlayerMins() * tf_halloween_kart_impact_bounds_scale.GetFloat(), GetPlayerMaxs() * tf_halloween_kart_impact_bounds_scale.GetFloat() ); enginetrace->TraceRay( ray, MASK_SOLID, &pFilter, &pTrace ); Vector vecForceDirection; vecForceDirection = vAim; vecForceDirection.z += 0.60f; vecForceDirection.NormalizeInPlace(); if ( pTrace.m_pEnt == pVictim ) { if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) ) { flForceMult *= tf_halloween_kart_boost_impact_force.GetFloat(); // Stop moving SetAbsVelocity( vec3_origin ); SetCurrentTauntMoveSpeed( 0 ); m_Shared.RemoveCond( TF_COND_HALLOWEEN_KART_DASH ); EmitSound( "BumperCar.BumpHard" ); } else { SetAbsVelocity( GetAbsVelocity() * tf_halloween_kart_impact_feedback.GetFloat() ); SetCurrentTauntMoveSpeed( GetCurrentTauntMoveSpeed() * tf_halloween_kart_impact_feedback.GetFloat() ); EmitSound( "BumperCar.Bump" ); } // Invul Crash if ( m_Shared.InCond( TF_COND_INVULNERABLE_USER_BUFF ) ) { flForceMult += 0.5f; } // Apply some kart damage //Speed maxes at 800, normally at 300? we want about 10 damage a hit? 10-15? int iDamage = (int)( ( flImpactForce / 50.0f + RandomInt( 13, 19 ) ) * tf_halloween_kart_impact_damage.GetFloat() ); // Apply force to enemy vecForceDirection *= flImpactForce * flForceMult * tf_halloween_kart_impact_force.GetFloat(); pVictim->AddHalloweenKartPushEvent( this, NULL, NULL, vecForceDirection, iDamage ); } else { DevMsg( "Collision with player not in Trace, %f Force \n", flImpactForce ); } // can only give a kart push event every 0.2 seconds if ( vecForceDirection.LengthSqr() > 100.0f ) { m_flHalloweenKartPushEventTime = gpGlobals->curtime + tf_halloween_kart_impact_rate.GetFloat(); } } } if ( ( m_Shared.GetPercentInvisible() < 0.10f ) && m_Shared.GetCarryingRuneType() == RUNE_PLAGUE && !m_Shared.IsAlly( pVictim ) && !pVictim->m_Shared.IsInvulnerable() && !pVictim->m_Shared.InCond( TF_COND_PLAGUE ) && pVictim->m_Shared.GetCarryingRuneType() != RUNE_RESIST ) { pVictim->m_Shared.AddCond( TF_COND_PLAGUE, PERMANENT_CONDITION, this ); //Plague transmission event infects nearby eligible players on the same team. Only works for powerup carrier to host, not host to host. const Vector& vecPos = pVictim->WorldSpaceCenter(); for ( int i = 0; i < pVictim->GetTeam()->GetNumPlayers(); i++ ) { CTFPlayer *pTeamMate = ToTFPlayer( pVictim->GetTeam()->GetPlayer( i ) ); if ( pTeamMate && pTeamMate != pVictim && pTeamMate->IsAlive() && !pTeamMate->m_Shared.IsInvulnerable() && !pTeamMate->m_Shared.InCond( TF_COND_PLAGUE ) && pTeamMate->m_Shared.GetCarryingRuneType() != RUNE_RESIST ) { // Only nearby teammates. Check for this before the more expensive visibility trace if ( ( vecPos - pTeamMate->WorldSpaceCenter() ).LengthSqr() < ( 350 * 350 ) ) { // Doesn't go through walls if ( pVictim->FVisible( pTeamMate, MASK_SOLID ) ) { pTeamMate->m_Shared.AddCond( TF_COND_PLAGUE, PERMANENT_CONDITION, this ); CPVSFilter filter( WorldSpaceCenter() ); Vector vStart = pVictim->EyePosition(); Vector vEnd = pTeamMate->GetAbsOrigin() + Vector( 0, 0, 56 ); te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd }; TE_TFParticleEffectComplex( filter, 0.f, "plague_transmission", vStart, QAngle( 0.f, 0.f, 0.f ), NULL, &controlPoint, pTeamMate, PATTACH_CUSTOMORIGIN ); } } } } } } } BaseClass::Touch( pOther ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RefreshCollisionBounds( void ) { BaseClass::RefreshCollisionBounds(); SetViewOffset( ( IsDucked() ) ? ( VEC_DUCK_VIEW_SCALED( this ) ) : ( GetClassEyeHeight() ) ); } //----------------------------------------------------------------------------- /** * Invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL) */ void CTFPlayer::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ) { VPROF_BUDGET( "CTFPlayer::OnNavAreaChanged", "NextBot" ); if ( !IsAlive() || GetTeamNumber() == TEAM_SPECTATOR ) { return; } if ( leftArea ) { // remove us from old visible set NavAreaCollector wasVisible; leftArea->ForAllPotentiallyVisibleAreas( wasVisible ); for( int i=0; iRemovePotentiallyVisibleActor( this ); } } if ( enteredArea ) { // add us to new visible set // @todo: is it faster to only do this for the areas that changed between sets? NavAreaCollector isVisible; enteredArea->ForAllPotentiallyVisibleAreas( isVisible ); for( int i=0; iAddPotentiallyVisibleActor( this ); } } } //----------------------------------------------------------------------------------------------------- // Return true if the given threat is aiming in our direction bool CTFPlayer::IsThreatAimingTowardMe( CBaseEntity *threat, float cosTolerance ) const { CTFPlayer *player = ToTFPlayer( threat ); Vector to = GetAbsOrigin() - threat->GetAbsOrigin(); float threatRange = to.NormalizeInPlace(); Vector forward; if ( player == NULL ) { CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( threat ); if ( sentry ) { // are we in range? if ( threatRange < SENTRY_MAX_RANGE ) { // is it pointing at us? AngleVectors( sentry->GetTurretAngles(), &forward ); if ( DotProduct( to, forward ) > cosTolerance ) { return true; } } } // not a player, not a sentry, not a threat? return false; } // is the player pointing at me? player->EyeVectors( &forward ); if ( DotProduct( to, forward ) > cosTolerance ) { return true; } return false; } //----------------------------------------------------------------------------------------------------- // Return true if the given threat is aiming in our direction and firing its weapon bool CTFPlayer::IsThreatFiringAtMe( CBaseEntity *threat ) const { if ( IsThreatAimingTowardMe( threat ) ) { CTFPlayer *player = ToTFPlayer( threat ); if ( player ) { return player->IsFiringWeapon(); } CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( threat ); if ( sentry ) { return sentry->GetTimeSinceLastFired() < 1.0f; } } return false; } //----------------------------------------------------------------------------- // Purpose: Check to see if this player has seen through an enemy spy's disguise //----------------------------------------------------------------------------- void CTFPlayer::CheckUncoveringSpies( CTFPlayer *pTouchedPlayer ) { // Only uncover enemies if ( m_Shared.IsAlly( pTouchedPlayer ) ) { return; } // Only uncover if they're stealthed if ( !pTouchedPlayer->m_Shared.InCond( TF_COND_STEALTHED ) ) { return; } // pulse their invisibility pTouchedPlayer->m_Shared.OnSpyTouchedByEnemy(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DoNoiseMaker( void ) { if ( gpGlobals->curtime < m_Shared.GetNextNoiseMakerTime() ) return; CSteamID steamIDForPlayer; GetSteamID( &steamIDForPlayer ); // Check to see that we have a noise maker item equipped. We intentionally // want to check this to fix the infinite noise maker bugs. CEconItemView *pItem = GetEquippedItemForLoadoutSlot( LOADOUT_POSITION_ACTION ); if ( !pItem ) return; int iUnlimitedQuantity = 0; CALL_ATTRIB_HOOK_INT( iUnlimitedQuantity, unlimited_quantity ); if ( pItem->GetItemQuantity() <= 0 && !iUnlimitedQuantity ) return; perteamvisuals_t* vis = pItem->GetStaticData()->GetPerTeamVisual( 0 ); if ( !vis ) return; int iNumSounds = 0; for ( int i=0; ipszCustomSounds[i] ) iNumSounds++; } if ( iNumSounds == 0 ) return; int rand = RandomInt( 0, iNumSounds-1 ); float flSoundLength = 0; EmitSound_t params; params.m_flSoundTime = 0; params.m_pSoundName = vis->pszCustomSounds[rand]; params.m_pflSoundDuration = &flSoundLength; CPASFilter filter( GetAbsOrigin() ); EmitSound( filter, entindex(), params ); // Add a particle effect. const char *particleEffectName = pItem->GetStaticData()->GetParticleEffect( TEAM_UNASSIGNED ); if ( particleEffectName ) { TE_TFParticleEffect( filter, 0.0, particleEffectName, PATTACH_POINT_FOLLOW, this, "head" ); } float flDelay = 1.0f; // Duck Badge Cooldown is based on badge level. Noisemaker is more like an easter egg CSchemaAttributeDefHandle pAttr_DuckLevelBadge( "duck badge level" ); uint32 iDuckBadgeLevel = 0; if ( FindAttribute_UnsafeBitwiseCast( pItem, pAttr_DuckLevelBadge, &iDuckBadgeLevel ) ) { flDelay = 5.0f; } // Throttle the usage rate to sound duration plus some dead time. m_Shared.SetNextNoiseMakerTime( gpGlobals->curtime + flSoundLength + flDelay ); } //----------------------------------------------------------------------------- // Purpose: Finds an open space for a high five partner. flTolerance specifies the maximum amount that should be allowed underneath position. //----------------------------------------------------------------------------- bool CTFPlayer::FindOpenTauntPartnerPosition( CEconItemView *pEconItemView, Vector &position, float *flTolerance ) { if ( !pEconItemView || !pEconItemView->IsValid() ) return false; const GameItemDefinition_t *pItemDef = pEconItemView->GetItemDefinition(); if ( !pItemDef || !pItemDef->GetTauntData() ) { position = GetAbsOrigin(); *flTolerance = tf_highfive_height_tolerance.GetFloat(); return false; } const float flTauntSeparationForwardDistance = tf_highfive_separation_forward.GetFloat() != 0 ? tf_highfive_separation_forward.GetFloat() : pItemDef->GetTauntData()->GetTauntSeparationForwardDistance(); const float flTauntSeparationRightDistance = tf_highfive_separation_right.GetFloat() != 0 ? tf_highfive_separation_right.GetFloat() : pItemDef->GetTauntData()->GetTauntSeparationRightDistance(); bool ret = true; Vector forward, right; AngleVectors( GetAbsAngles(), &forward, &right, NULL ); Vector vecStart = GetAbsOrigin(); Vector vecEnd = vecStart + ( forward * flTauntSeparationForwardDistance ) + ( right * flTauntSeparationRightDistance ); *flTolerance = tf_highfive_height_tolerance.GetFloat(); trace_t result; CTraceFilterIgnoreTeammates filter( this, COLLISION_GROUP_NONE, GetAllowedTauntPartnerTeam() ); UTIL_TraceHull( vecStart, vecEnd + ( forward * 2 ), VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, &filter, &result ); if ( result.DidHit() ) { // something's directly in front of us, but let's allow for a little bit of variation since we might be standing on an uneven displacement trace_t result2; vecStart = GetAbsOrigin() + Vector( 0, 0, *flTolerance ); vecEnd = vecStart + ( forward * flTauntSeparationForwardDistance ) + ( right * flTauntSeparationRightDistance ); UTIL_TraceHull( vecStart, vecEnd + ( forward * 2 ), VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, &filter, &result2 ); // Now we can allow for twice the space underneath us. *flTolerance *= 2; if ( result2.DidHit() ) { // Not enough space in front of us. ret = false; } else { position = vecEnd; } } else { position = vecEnd; } if( ret ) { Vector vecStartCenter = WorldSpaceCenter(); // Scale up how far we test. Dont even let them get close. Vector vecEndSuperSafe = vecStartCenter + ( forward * flTauntSeparationForwardDistance * 2.f ) + ( right * flTauntSeparationRightDistance ); // Dont allow crossing through the spawn room visualizers ret = !PointsCrossRespawnRoomVisualizer( vecStartCenter, vecEndSuperSafe ); } return ret; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsAllowedToInitiateTauntWithPartner( CEconItemView *pEconItemView, char *pszErrorMessage, int cubErrorMessage ) { Vector vecEnd; float flTolerance; if ( TFGameRules() && TFGameRules()->ShowMatchSummary() ) return true; bool ret = FindOpenTauntPartnerPosition( pEconItemView, vecEnd, &flTolerance ); // Check that there isn't too much space underneath the destination. if ( ret ) { trace_t result3; CTraceFilterIgnoreTeammates filter( this, COLLISION_GROUP_NONE, GetTeamNumber() ); UTIL_TraceHull( vecEnd, vecEnd - Vector( 0, 0, flTolerance ), VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, &filter, &result3 ); if ( !result3.DidHit() ) { if ( pszErrorMessage && cubErrorMessage > 0 ) { V_strncpy( pszErrorMessage, "#TF_PartnerTaunt_TooHigh", cubErrorMessage ); } ret = false; } } else if ( pszErrorMessage && cubErrorMessage > 0 ) { V_strncpy( pszErrorMessage, "#TF_PartnerTaunt_Blocked", cubErrorMessage ); } return ret; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsWormsGearEquipped( void ) const { // If we have the Worms Gear equipped, play their custom sound static CSchemaItemDefHandle ppItemDefWearables[] = { CSchemaItemDefHandle( "Worms Gear" ) }; return HasWearablesEquipped( ppItemDefWearables, ARRAYSIZE( ppItemDefWearables ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsRobotCostumeEquipped( void ) const { if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_SOLDIER ) return false; static CSchemaItemDefHandle ppItemDefWearables[] = { CSchemaItemDefHandle( "Idiot Box" ), CSchemaItemDefHandle( "Steel Pipes" ), CSchemaItemDefHandle( "Shoestring Budget" ) }; return HasWearablesEquipped( ppItemDefWearables, ARRAYSIZE( ppItemDefWearables ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsDemowolf( void ) const { if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_DEMOMAN ) return false; static CSchemaItemDefHandle ppItemDefWearables[] = { CSchemaItemDefHandle( "Hair of the Dog" ), CSchemaItemDefHandle( "Scottish Snarl" ), CSchemaItemDefHandle( "Pickled Paws" ) }; return HasWearablesEquipped( ppItemDefWearables, ARRAYSIZE( ppItemDefWearables ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsFrankenHeavy( void ) const { if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_HEAVYWEAPONS ) return false; static CSchemaItemDefHandle ppItemDefWearables[] = { CSchemaItemDefHandle( "Can Opener" ), CSchemaItemDefHandle( "Soviet Stitch-Up" ), CSchemaItemDefHandle( "Steel-Toed Stompers" ) }; return HasWearablesEquipped( ppItemDefWearables, ARRAYSIZE( ppItemDefWearables ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsFairyHeavy( void ) const { if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_HEAVYWEAPONS ) return false; static CSchemaItemDefHandle ppItemDefWearables[] = { CSchemaItemDefHandle( "The Grand Duchess Tutu" ), CSchemaItemDefHandle( "The Grand Duchess Fairy Wings" ), CSchemaItemDefHandle( "The Grand Duchess Tiara" ) }; return HasWearablesEquipped( ppItemDefWearables, ARRAYSIZE( ppItemDefWearables ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsZombieCostumeEquipped( void ) const { int iZombie = 0; CALL_ATTRIB_HOOK_INT( iZombie, zombiezombiezombiezombie ); return iZombie != 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::HasWearablesEquipped( const CSchemaItemDefHandle *ppItemDefs, int nWearables ) const { for ( int i = 0; i < nWearables; i++ ) { const CEconItemDefinition *pItemDef = ppItemDefs[i]; // Backwards because our wearable items are probably sitting in our cosmetic slots near // the end of our list. bool bHasWearable = false; FOR_EACH_VEC_BACK( m_hMyWearables, wbl ) { CEconWearable *pWearable = m_hMyWearables[wbl]; if ( pWearable && pWearable->GetAttributeContainer()->GetItem() && pWearable->GetAttributeContainer()->GetItem()->GetItemDefinition() == pItemDef ) { bHasWearable = true; break; } } if ( !bHasWearable ) { return false; } } return true; } //----------------------------------------------------------------------------- // Purpose: Returns the current concept for press-and-hold taunts or MP_CONCEPT_NONE if none is available. //----------------------------------------------------------------------------- int CTFPlayer::GetTauntConcept( CEconItemDefinition *pItemDef ) { for ( int i=0; iGetNumAnimations( GetTeamNumber() ); ++i ) { animation_on_wearable_t* pAnim = pItemDef->GetAnimationData( GetTeamNumber(), i ); if ( pAnim && pAnim->pszActivity && !Q_stricmp( pAnim->pszActivity, "taunt_concept" ) ) { const char* pszConcept = pAnim->pszReplacement; if ( !pszConcept ) return true; return GetMPConceptIndexFromString( pszConcept ); } } return MP_CONCEPT_NONE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::PlayTauntSceneFromItem( CEconItemView *pEconItemView ) { if ( !pEconItemView ) return false; if ( !IsAllowedToTaunt() ) return false; const GameItemDefinition_t *pItemDef = pEconItemView->GetItemDefinition(); if ( !pItemDef ) return false; CTFTauntInfo *pTauntData = pItemDef->GetTauntData(); if ( !pTauntData ) return false; int iClass = GetPlayerClass()->GetClassIndex(); // If we didn't find any custom taunts, then we're done if ( pTauntData->GetIntroSceneCount( iClass ) == 0 ) { return false; } int iScene = RandomInt( 0, pTauntData->GetIntroSceneCount( iClass ) - 1 ); const char* pszScene = pTauntData->GetIntroScene( iClass, iScene ); if ( pszScene ) { int iTauntIndex = TAUNT_MISC_ITEM; int iTauntConcept = 0; // check if this is a long taunt static CSchemaAttributeDefHandle pAttrDef_TauntPressAndHold( "taunt is press and hold" ); attrib_value_t iLongTaunt = 0; if ( pEconItemView->FindAttribute( pAttrDef_TauntPressAndHold, &iLongTaunt ) && iLongTaunt != 0 ) { iTauntIndex = TAUNT_LONG; iTauntConcept = pTauntData->IsPartnerTaunt() ? MP_CONCEPT_PARTNER_TAUNT_READY : iTauntConcept; m_bIsTauntInitiator = true; ParseSharedTauntDataFromEconItemView( pEconItemView ); /*cant we just network over the "taunting item id", since client and server both know all the item defs, then they can both look at attributes and we dont need to keep networking more and more stuff?*/ // check if this taunt can be mimic by other players static CSchemaAttributeDefHandle pAttrDef_TauntMimic( "taunt mimic" ); attrib_value_t iTauntMimic = 0; pEconItemView->FindAttribute( pAttrDef_TauntMimic, &iTauntMimic ); m_bTauntMimic = iTauntMimic != 0; // check if we can initiate partner taunt (ignore mimic taunt to allow Conga initiation) char szClientError[64]; if ( !m_bTauntMimic && pTauntData->IsPartnerTaunt() && !IsAllowedToInitiateTauntWithPartner( pEconItemView, szClientError, ARRAYSIZE( szClientError ) ) ) { CSingleUserRecipientFilter filter( this ); EmitSound_t params; params.m_pSoundName = "Player.DenyWeaponSelection"; EmitSound( filter, entindex(), params ); TFGameRules()->SendHudNotification( filter, szClientError, "ico_notify_partner_taunt" ); return false; } } // Store this off so eventually we can let clients know which item ID is doing this taunt. m_iTauntItemDefIndex = pEconItemView->GetItemDefIndex(); m_TauntEconItemView = *pEconItemView; // Should we play a sound? m_strTauntSoundName = ""; m_flTauntSoundTime = 0.f; static CSchemaAttributeDefHandle pAttrDef_TauntSuccessSound( "taunt success sound" ); CAttribute_String attrTauntSuccessSound; if ( pEconItemView->FindAttribute( pAttrDef_TauntSuccessSound, &attrTauntSuccessSound ) ) { const char* pszTauntSoundName = attrTauntSuccessSound.value().c_str(); Assert( pszTauntSoundName && *pszTauntSoundName ); if ( pszTauntSoundName && *pszTauntSoundName ) { m_strTauntSoundName = pszTauntSoundName; static CSchemaAttributeDefHandle pAttrDef_TauntSuccessSoundOffset( "taunt success sound offset" ); attrib_value_t attrTauntSoundOffset = 0; pEconItemView->FindAttribute( pAttrDef_TauntSuccessSoundOffset, &attrTauntSoundOffset ); float flTauntSoundOffset = (float&)attrTauntSoundOffset; m_flTauntSoundTime = gpGlobals->curtime + flTauntSoundOffset; } } // Should we play a looping sound? m_flTauntSoundLoopTime = 0.f; Assert( m_strTauntSoundLoopName.IsEmpty() ); m_strTauntSoundLoopName = ""; static CSchemaAttributeDefHandle pAttrDef_TauntSuccessSoundLoop( "taunt success sound loop" ); CAttribute_String attrTauntSuccessSoundLoop; if ( pEconItemView->FindAttribute( pAttrDef_TauntSuccessSoundLoop, &attrTauntSuccessSoundLoop ) ) { const char* pszTauntSoundLoopName = attrTauntSuccessSoundLoop.value().c_str(); Assert( pszTauntSoundLoopName && *pszTauntSoundLoopName ); if ( pszTauntSoundLoopName && *pszTauntSoundLoopName ) { // play the looping sounds using the envelope controller m_strTauntSoundLoopName = pszTauntSoundLoopName; static CSchemaAttributeDefHandle pAttrDef_TauntSuccessSoundLoopOffset( "taunt success sound loop offset" ); attrib_value_t attrTauntSoundLoopOffset = 0; pEconItemView->FindAttribute( pAttrDef_TauntSuccessSoundLoopOffset, &attrTauntSoundLoopOffset ); float flTauntSoundLoopOffset = (float&)attrTauntSoundLoopOffset; m_flTauntSoundLoopTime = gpGlobals->curtime + flTauntSoundLoopOffset; } } m_iTauntAttack = TAUNTATK_NONE; m_flTauntAttackTime = 0.f; static CSchemaAttributeDefHandle pAttrDef_TauntAttackName( "taunt attack name" ); const char* pszTauntAttackName = NULL; if ( FindAttribute_UnsafeBitwiseCast( pItemDef, pAttrDef_TauntAttackName, &pszTauntAttackName ) ) { m_iTauntAttack = GetTauntAttackByName( pszTauntAttackName ); } static CSchemaAttributeDefHandle pAttrDef_TauntAttackTime( "taunt attack time" ); float flTauntAttackTime = 0.f; if ( FindAttribute_UnsafeBitwiseCast( pItemDef, pAttrDef_TauntAttackTime, &flTauntAttackTime ) ) { m_flTauntAttackTime = gpGlobals->curtime + flTauntAttackTime; } m_iPreTauntWeaponSlot = -1; if ( GetActiveWeapon() ) { m_iPreTauntWeaponSlot = GetActiveWeapon()->GetSlot(); } static CSchemaAttributeDefHandle pAttrDef_TauntForceWeaponSlot( "taunt force weapon slot" ); const char* pszTauntForceWeaponSlotName = NULL; if ( FindAttribute_UnsafeBitwiseCast( pItemDef, pAttrDef_TauntForceWeaponSlot, &pszTauntForceWeaponSlotName ) ) { int iForceWeaponSlot = StringFieldToInt( pszTauntForceWeaponSlotName, GetItemSchema()->GetWeaponTypeSubstrings() ); Weapon_Switch( Weapon_GetSlot( iForceWeaponSlot ) ); } m_bInitTaunt = true; // Allow voice commands, etc to be interrupted. CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); Assert( pExpresser ); pExpresser->AllowMultipleScenes(); float flSceneDuration = PlayScene( pszScene ); OnTauntSucceeded( pszScene, iTauntIndex, iTauntConcept ); m_flNextAllowTauntRemapInputTime = iTauntIndex == TAUNT_LONG ? gpGlobals->curtime + flSceneDuration : -1.f; pExpresser->DisallowMultipleScenes(); const char *pszTauntProp = pTauntData->GetProp( iClass ); if ( pszTauntProp ) { const char *pszTauntPropScene = pTauntData->GetPropIntroScene( iClass ); if ( pszTauntPropScene ) { CTFTauntProp *pProp = static_cast< CTFTauntProp * >( CreateEntityByName( "tf_taunt_prop" ) ); if ( pProp ) { pProp->SetModel( pszTauntProp ); pProp->m_nSkin = GetTeamNumber() == TF_TEAM_RED ? 0 : 1; DispatchSpawn( pProp ); pProp->SetAbsOrigin( GetAbsOrigin() ); pProp->SetAbsAngles( GetAbsAngles() ); pProp->SetEFlags( EFL_FORCE_CHECK_TRANSMIT ); pProp->PlayScene( pszTauntPropScene ); m_hTauntProp = pProp; } } else { CTFWeaponBase *pWeapon = GetActiveTFWeapon(); if ( pWeapon && pWeapon->HideAttachmentsAndShowBodygroupsWhenPerformingWeaponIndependentTaunt() ) { // If there's no prop scene, our weapon is being repurposed pWeapon->SetIsBeingRepurposedForTaunt( true ); } } } // check for achievement static CSchemaItemDefHandle congaTaunt( "Conga Taunt" ); if ( pEconItemView->GetItemDefinition() == congaTaunt ) { CUtlVector< CTFPlayer * > vecPlayers; CollectPlayers( &vecPlayers, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); CollectPlayers( &vecPlayers, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); CUtlVector< CTFPlayer * > vecCongaLine; FOR_EACH_VEC( vecPlayers, i ) { CTFPlayer *pPlayer = vecPlayers[i]; if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) { // is this player doing the Conga? if ( pPlayer->GetTauntEconItemView() && ( pPlayer->GetTauntEconItemView()->GetItemDefinition() == congaTaunt ) ) { vecCongaLine.AddToTail( pPlayer ); } } } if ( vecCongaLine.Count() >= 10 ) { FOR_EACH_VEC( vecCongaLine, i ) { CTFPlayer *pPlayer = vecCongaLine[i]; if ( pPlayer ) { pPlayer->AwardAchievement( ACHIEVEMENT_TF_TAUNT_CONGA_LINE ); } } } } // override FOV m_iPreTauntFOV = GetFOV(); if ( pTauntData->GetFOV() != 0 ) { SetFOV( this, pTauntData->GetFOV() ); } #ifdef STAGING_ONLY if ( tf_tauntcam_fov_override.GetInt() != 0 ) { SetFOV( this, tf_tauntcam_fov_override.GetInt() ); } #endif // STAGING_ONLY m_TauntStage = TAUNT_INTRO; return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::PlayTauntRemapInputScene() { CTFTauntInfo *pTaunt = m_TauntEconItemView.GetStaticData()->GetTauntData(); if ( !pTaunt ) { return -1.f; } if ( m_TauntStage != TAUNT_INTRO ) { return -1.f; } int iClass = GetPlayerClass()->GetClassIndex(); const char *pszCurrentSceneFileName = GetSceneFilename( m_hTauntScene ); const char *pszSceneName = NULL; for ( int iButtonIndex=0; iButtonIndexGetTauntInputRemapCount(); ++iButtonIndex ) { const CTFTauntInfo::TauntInputRemap_t& tauntRemap = pTaunt->GetTauntInputRemapScene( iButtonIndex ); if ( tauntRemap.m_vecButtonPressedScenes[iClass].IsEmpty() ) continue; if ( m_afButtonPressed & tauntRemap.m_iButton ) { int iRandomTaunt = RandomInt( 0, tauntRemap.m_vecButtonPressedScenes[iClass].Count() - 1 ); pszSceneName = tauntRemap.m_vecButtonPressedScenes[iClass][iRandomTaunt]; break; } const char *pszPressedScene = tauntRemap.m_vecButtonPressedScenes[iClass][0]; if ( m_nButtons & tauntRemap.m_iButton ) { // already in this scene, try again later for next state if ( FStrEq( pszCurrentSceneFileName, pszPressedScene ) ) { return 0.f; } pszSceneName = pszPressedScene; break; } else if ( FStrEq( pszCurrentSceneFileName, pszPressedScene ) && !tauntRemap.m_vecButtonReleasedScenes[iClass].IsEmpty() ) { int iRandomTaunt = RandomInt( 0, tauntRemap.m_vecButtonReleasedScenes[iClass].Count() - 1 ); pszSceneName = tauntRemap.m_vecButtonReleasedScenes[iClass][iRandomTaunt]; break; } } if ( pszSceneName ) { StopScriptedScene( this, m_hTauntScene ); m_hTauntScene = NULL; CMultiplayer_Expresser *pInitiatorExpresser = GetMultiplayerExpresser(); Assert( pInitiatorExpresser ); pInitiatorExpresser->AllowMultipleScenes(); // extend initiator's taunt duration to include actual high five m_bInitTaunt = true; float flSceneDuration = PlayScene( pszSceneName ); m_bInitTaunt = false; pInitiatorExpresser->DisallowMultipleScenes(); return flSceneDuration; } return 0.f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::OnTauntSucceeded( const char* pszSceneName, int iTauntIndex /*= 0*/, int iTauntConcept /*= 0*/ ) { float flDuration = GetSceneDuration( pszSceneName ) + 0.2f; float flDurationMod = 1; CALL_ATTRIB_HOOK_FLOAT( flDurationMod, mult_gesture_time ); // Modify by attributes. flDuration /= flDurationMod; // Set player state as taunting. m_Shared.m_iTauntIndex = iTauntIndex; m_Shared.m_iTauntConcept.Set( iTauntConcept ); m_flTauntStartTime = gpGlobals->curtime; const itemid_t unTauntSourceItemID = m_TauntEconItemView.IsValid() ? m_TauntEconItemView.GetItemID() : INVALID_ITEM_ID; m_Shared.m_unTauntSourceItemID_Low = unTauntSourceItemID & 0xffffffff; m_Shared.m_unTauntSourceItemID_High = (unTauntSourceItemID >> 32) & 0xffffffff; m_Shared.AddCond( TF_COND_TAUNTING ); if ( iTauntIndex == TAUNT_LONG ) { m_flTauntRemoveTime = gpGlobals->curtime; m_bAllowedToRemoveTaunt = false; if ( iTauntConcept == MP_CONCEPT_PARTNER_TAUNT_READY ) { GetReadyToTauntWithPartner(); } m_flTauntYaw = BodyAngles().y; } else { m_flTauntRemoveTime = gpGlobals->curtime + flDuration; m_bAllowedToRemoveTaunt = true; } m_angTauntCamera = EyeAngles(); // Slam velocity to zero. SetAbsVelocity( vec3_origin ); // play custom set taunt particle if we have a full set equipped if ( IsPlayerClass( TF_CLASS_SPY ) ) { // FIX ME: We should be using string attribute type instead of float when we add code support to it // Hand Coded for this effect which may change later int iCustomTauntParticle = 0; CALL_ATTRIB_HOOK_INT( iCustomTauntParticle, custom_taunt_particle_attr ); if ( iCustomTauntParticle ) { DispatchParticleEffect( "set_taunt_saharan_spy", PATTACH_ABSORIGIN_FOLLOW, this ); } } // set initial taunt yaw to make sure that the client anim not off because of lag SetTauntYaw( GetAbsAngles()[YAW] ); m_vecTauntStartPosition = GetAbsOrigin(); // Strange Taunts EconItemInterface_OnOwnerKillEaterEventNoPartner( &m_TauntEconItemView, this, kKillEaterEvent_TauntsPerformed ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Taunt( taunts_t iTauntIndex, int iTauntConcept ) { if ( !IsAllowedToTaunt() ) return; if ( iTauntIndex == TAUNT_LONG ) { AssertMsg( false, "Long Taunt should be using the new system which reads scene names from item definitions" ); return; } // Heavies can purchase a rage-based knockback+stun effect in MvM, // so ignore taunt and activate rage if we're at full rage if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { if ( GetActiveTFWeapon() && GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_MINIGUN ) { int iRage = 0; CALL_ATTRIB_HOOK_INT( iRage, generate_rage_on_dmg ); if ( iRage ) { if ( m_Shared.GetRageMeter() >= 100.f ) { m_Shared.m_bRageDraining = true; EmitSound( "Heavy.Battlecry03" ); return; } if ( m_Shared.IsRageDraining() ) return; } } } // Allow voice commands, etc to be interrupted. CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); Assert( pExpresser ); pExpresser->AllowMultipleScenes(); m_hTauntItem = NULL; m_bInitTaunt = true; char szResponse[AI_Response::MAX_RESPONSE_NAME]; bool bTauntSucceeded = false; switch ( iTauntIndex ) { case TAUNT_SHOW_ITEM: iTauntConcept = MP_CONCEPT_PLAYER_SHOW_ITEM_TAUNT; break; // use the concept specified for these two case TAUNT_MISC_ITEM: case TAUNT_SPECIAL: break; default: case TAUNT_BASE_WEAPON: iTauntConcept = MP_CONCEPT_PLAYER_TAUNT; break; }; bTauntSucceeded = SpeakConceptIfAllowed( iTauntConcept, NULL, szResponse, AI_Response::MAX_RESPONSE_NAME ); if ( bTauntSucceeded ) { OnTauntSucceeded( szResponse, iTauntIndex, iTauntConcept ); } else { m_bInitTaunt = false; } pExpresser->DisallowMultipleScenes(); m_flTauntAttackTime = 0; m_iTauntAttack = TAUNTATK_NONE; if ( !bTauntSucceeded ) return; // should we play a sound? CAttribute_String attrCosmeticTauntSound; CALL_ATTRIB_HOOK_STRING( attrCosmeticTauntSound, cosmetic_taunt_sound ); const char* pszTauntSoundName = attrCosmeticTauntSound.value().c_str(); if ( pszTauntSoundName && *pszTauntSoundName ) { EmitSound( pszTauntSoundName ); } if ( iTauntIndex == TAUNT_SHOW_ITEM ) { m_flTauntAttackTime = gpGlobals->curtime + 1.5; m_iTauntAttack = TAUNTATK_SHOW_ITEM; return; } CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon(); if ( iTauntIndex == TAUNT_BASE_WEAPON ) { // phlogistinator if ( IsPlayerClass( TF_CLASS_PYRO ) && m_Shared.GetRageMeter() >= 100.0f && StringHasPrefix( szResponse, "scenes/player/pyro/low/taunt01" ) ) { // Pyro Rage! CBaseCombatWeapon *pWeapon = GetActiveWeapon(); if ( pWeapon ) { int iBuffType = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iBuffType, set_buff_type ); if ( iBuffType > 0 ) { // Time for crits! m_Shared.ActivateRageBuff( this, iBuffType ); // Pyro needs high defense while he's taunting //m_Shared.AddCond( TF_COND_DEFENSEBUFF_HIGH, 3.0f ); m_Shared.AddCond( TF_COND_INVULNERABLE_USER_BUFF, 2.60f ); m_Shared.AddCond( TF_COND_MEGAHEAL, 2.60f ); } } } else if ( IsPlayerClass( TF_CLASS_SCOUT ) ) { if ( m_Shared.InCond( TF_COND_PHASE ) == false ) { if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX ) { m_flTauntAttackTime = gpGlobals->curtime + 0.9; m_iTauntAttack = TAUNTATK_SCOUT_DRINK; } } } else if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX ) { m_flTauntAttackTime = gpGlobals->curtime + 1.0; m_iTauntAttack = TAUNTATK_HEAVY_EAT; // Only count sandviches for "eat 100 sandviches" achievement CTFLunchBox *pLunchbox = (CTFLunchBox*)pActiveWeapon; if ( ( pLunchbox->GetLunchboxType() == LUNCHBOX_STANDARD ) || ( pLunchbox->GetLunchboxType() == LUNCHBOX_STANDARD_ROBO ) ) { AwardAchievement( ACHIEVEMENT_TF_HEAVY_EAT_SANDWICHES ); } } } } else if ( iTauntIndex == TAUNT_SPECIAL ) { if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) { // Wrenchmotron taunt teleport home effect if ( !Q_stricmp( szResponse, "scenes/player/engineer/low/taunt_drg_melee.vcd" ) ) { m_bIsTeleportingUsingEurekaEffect = true; m_teleportHomeFlashTimer.Start( 1.9f ); // play teleport sound at location we are leaving Vector soundOrigin = WorldSpaceCenter(); CPASAttenuationFilter filter( soundOrigin ); EmitSound_t ep; ep.m_nChannel = CHAN_STATIC; ep.m_pSoundName = "Weapon_DRG_Wrench.Teleport"; ep.m_flVolume = 1.0f; ep.m_SoundLevel = SNDLVL_150dB; ep.m_nFlags = 0; ep.m_nPitch = PITCH_NORM; ep.m_pOrigin = &soundOrigin; int worldEntIndex = 0; EmitSound( filter, worldEntIndex, ep ); } } } // Setup taunt attacks. Hacky, but a lot easier to do than getting server side anim events working. if ( IsPlayerClass(TF_CLASS_PYRO) ) { if ( !V_stricmp( szResponse, "scenes/player/pyro/low/taunt02.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 2.1f; m_iTauntAttack = TAUNTATK_PYRO_HADOUKEN; } else if ( !V_stricmp( szResponse, "scenes/player/pyro/low/taunt_bubbles.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 3.0f; m_iTauntAttack = TAUNTATK_PYRO_ARMAGEDDON; // We need to parent this to a target instead of the player because the player changing their camera view can twist the rainbow CBaseEntity *pTarget = CreateEntityByName( "info_target" ); if ( pTarget ) { DispatchSpawn( pTarget ); pTarget->SetAbsOrigin( GetAbsOrigin() ); pTarget->SetAbsAngles( GetAbsAngles() ); pTarget->SetEFlags( EFL_FORCE_CHECK_TRANSMIT ); pTarget->SetThink( &BaseClass::SUB_Remove ); pTarget->SetNextThink( gpGlobals->curtime + 8.0f ); CBaseEntity *pGround = GetGroundEntity(); if ( pGround && pGround->GetMoveType() == MOVETYPE_PUSH ) { pTarget->SetParent( pGround ); } } DispatchParticleEffect( "pyrotaunt_rainbow_norainbow", PATTACH_ABSORIGIN_FOLLOW, pTarget ); } else if ( !V_stricmp( szResponse, "scenes/player/pyro/low/taunt_scorch_shot.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 1.9f; m_iTauntAttack = TAUNTATK_PYRO_SCORCHSHOT; } } else if ( IsPlayerClass(TF_CLASS_HEAVYWEAPONS) ) { if ( !V_stricmp( szResponse, "scenes/player/heavy/low/taunt03_v1.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 1.8; m_iTauntAttack = TAUNTATK_HEAVY_HIGH_NOON; } else if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_FISTS ) { CTFFists *pFists = dynamic_cast(pActiveWeapon); if ( pFists && pFists->GetFistType() == FISTTYPE_RADIAL_BUFF ) { m_flTauntAttackTime = gpGlobals->curtime + 1.0; m_iTauntAttack = TAUNTATK_HEAVY_RADIAL_BUFF; } } } else if ( IsPlayerClass( TF_CLASS_SCOUT ) ) { if ( !V_stricmp( szResponse, "scenes/player/scout/low/taunt05_v1.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 4.03f; m_iTauntAttack = TAUNTATK_SCOUT_GRAND_SLAM; } } else if ( IsPlayerClass( TF_CLASS_MEDIC ) ) { if ( !V_stricmp( szResponse, "scenes/player/medic/low/taunt06.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 0.8f; m_flTauntInhaleTime = gpGlobals->curtime + 1.8f; const char *pszParticleEffect; pszParticleEffect = ( GetTeamNumber() == TF_TEAM_RED ? "healhuff_red" : "healhuff_blu" ); DispatchParticleEffect( pszParticleEffect, PATTACH_POINT_FOLLOW, this, "eyes" ); m_iTauntAttack = TAUNTATK_MEDIC_INHALE; } else if ( !V_stricmp( szResponse, "scenes/player/medic/low/taunt08.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 2.2f; m_iTauntAttack = TAUNTATK_MEDIC_UBERSLICE_IMPALE; } } else if ( IsPlayerClass( TF_CLASS_SPY ) ) { if ( !V_strnicmp( szResponse, "scenes/player/spy/low/taunt03", 29 ) ) // There's taunt03_v1 & taunt03_v2 { m_flTauntAttackTime = gpGlobals->curtime + 1.8f; m_iTauntAttack = TAUNTATK_SPY_FENCING_SLASH_A; } } else if ( IsPlayerClass( TF_CLASS_SNIPER ) ) { if ( !V_stricmp( szResponse, "scenes/player/sniper/low/taunt04.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 0.85f; m_iTauntAttack = TAUNTATK_SNIPER_ARROW_STAB_IMPALE; } } else if ( IsPlayerClass( TF_CLASS_SOLDIER ) ) { if ( !V_stricmp( szResponse, "scenes/player/soldier/low/taunt05.vcd" ) ) { if ( IsWormsGearEquipped() ) { m_flTauntAttackTime = gpGlobals->curtime + 1.4f; m_iTauntAttack = TAUNTATK_SOLDIER_GRENADE_KILL_WORMSIGN; return; } m_flTauntAttackTime = gpGlobals->curtime + 3.5f; m_iTauntAttack = TAUNTATK_SOLDIER_GRENADE_KILL; } } else if ( IsPlayerClass( TF_CLASS_DEMOMAN ) ) { if ( !V_stricmp( szResponse, "scenes/player/demoman/low/taunt09.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 2.55f; m_iTauntAttack = TAUNTATK_DEMOMAN_BARBARIAN_SWING; } } else if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) { if ( !V_stricmp( szResponse, "scenes/player/engineer/low/taunt07.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 3.695f; m_iTauntAttack = TAUNTATK_ENGINEER_GUITAR_SMASH; } else if ( !V_stricmp( szResponse, "scenes/player/engineer/low/taunt09.vcd" ) ) { m_flTauntAttackTime = gpGlobals->curtime + 3.2f; m_iTauntAttack = TAUNTATK_ENGINEER_ARM_IMPALE; } } } //----------------------------------------------------------------------------- // Purpose: Aborts a taunt in progress. //----------------------------------------------------------------------------- void CTFPlayer::CancelTaunt( void ) { m_bIsTeleportingUsingEurekaEffect = false; m_teleportHomeFlashTimer.Reset(); StopTaunt(); } //----------------------------------------------------------------------------- // Purpose: Stops taunting //----------------------------------------------------------------------------- void CTFPlayer::StopTaunt( void ) { if ( m_hTauntScene.Get() ) { StopScriptedScene( this, m_hTauntScene ); m_flTauntRemoveTime = 0.0f; m_bAllowedToRemoveTaunt = true; m_hTauntScene = NULL; } if ( m_hTauntProp.Get() ) { UTIL_Remove( m_hTauntProp ); m_hTauntProp = NULL; } if ( IsReadyToTauntWithPartner() ) { CancelTauntWithPartner(); } StopTauntSoundLoop(); // reset the FOV if ( m_TauntEconItemView.IsValid() ) { SetFOV( this, m_iPreTauntFOV ); } m_hHighFivePartner = NULL; m_bAllowMoveDuringTaunt = false; m_flTauntOutroTime = 0.f; m_bTauntForceMoveForward = false; m_flTauntForceMoveForwardSpeed = 0.f; m_flTauntMoveAccelerationTime = 0.f; m_flTauntTurnSpeed = 0.f; m_flTauntTurnAccelerationTime = 0.f; m_bTauntMimic = false; m_bIsTauntInitiator = false; m_TauntEconItemView.Invalidate(); m_flNextAllowTauntRemapInputTime = -1.f; m_flCurrentTauntMoveSpeed = 0.f; m_nActiveTauntSlot = LOADOUT_POSITION_INVALID; m_iTauntItemDefIndex = INVALID_ITEM_DEF_INDEX; m_TauntStage = TAUNT_NONE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::EndLongTaunt() { Assert( m_Shared.GetTauntIndex() == TAUNT_LONG ); m_bAllowedToRemoveTaunt = true; m_flTauntRemoveTime = gpGlobals->curtime; int iClass = GetPlayerClass()->GetClassIndex(); CTFTauntInfo *pTauntData = m_TauntEconItemView.GetStaticData()->GetTauntData(); if ( pTauntData ) { // Make sure press-and-hold taunts last a minimum amount of time float flMinTime = pTauntData->GetMinTauntTime(); if ( m_flTauntStartTime + flMinTime > gpGlobals->curtime ) { m_flTauntRemoveTime = m_flTauntStartTime + flMinTime; } // should we play outro? if ( pTauntData->GetOutroSceneCount( iClass ) > 0 ) { m_bAllowedToRemoveTaunt = false; m_flTauntOutroTime = m_flTauntRemoveTime; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::PlayTauntOutroScene() { m_TauntStage = TAUNT_OUTRO; float flDuration = 0.f; int iClass = GetPlayerClass()->GetClassIndex(); CTFTauntInfo *pTauntData = m_TauntEconItemView.GetStaticData()->GetTauntData(); if ( pTauntData ) { if ( pTauntData->GetOutroSceneCount( iClass ) > 0 ) { // play outro const char *pszOutroScene = pTauntData->GetOutroScene( iClass, RandomInt( 0, pTauntData->GetOutroSceneCount( iClass ) - 1 ) ); if ( m_hTauntScene.Get() ) { StopScriptedScene( this, m_hTauntScene ); m_hTauntScene = NULL; StopTauntSoundLoop(); } // Allow voice commands, etc to be interrupted. CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); Assert( pExpresser ); pExpresser->AllowMultipleScenes(); m_bInitTaunt = true; flDuration = PlayScene( pszOutroScene ); OnTauntSucceeded( pszOutroScene, TAUNT_MISC_ITEM, MP_CONCEPT_HIGHFIVE_SUCCESS ); m_bInitTaunt = false; pExpresser->DisallowMultipleScenes(); if ( m_hTauntProp != NULL ) { const char *pszPropScene = pTauntData->GetPropOutroScene( iClass ); if ( pszPropScene ) { m_hTauntProp->PlayScene( pszPropScene ); } } } } return flDuration; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::HandleTauntCommand( int iTauntSlot ) { if ( !IsAllowedToTaunt() ) return; m_nActiveTauntSlot = LOADOUT_POSITION_INVALID; if ( iTauntSlot > 0 && iTauntSlot <= 8 ) { m_nActiveTauntSlot = LOADOUT_POSITION_TAUNT + iTauntSlot - 1; CEconItemView* pItem = GetEquippedItemForLoadoutSlot( m_nActiveTauntSlot ); PlayTauntSceneFromItem( pItem ); return; } else { // Check if I should accept taunt with partner CTFPlayer *initiator = FindPartnerTauntInitiator(); if ( initiator ) { if ( initiator->m_bTauntMimic ) { MimicTauntFromPartner( initiator ); } else { AcceptTauntWithPartner( initiator ); } return; } // does this weapon prevent player from doing manual taunt? CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon(); if ( pActiveWeapon && !pActiveWeapon->AllowTaunts() ) return; Taunt( TAUNT_BASE_WEAPON ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClearTauntAttack() { m_flTauntAttackTime = 0.f; m_flTauntInhaleTime = 0.f; m_iTauntAttack = TAUNTATK_NONE; m_iTauntAttackCount = 0; m_iTauntRPSResult = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::HandleWeaponSlotAfterTaunt() { if ( m_iPreTauntWeaponSlot != -1 ) { // switch back to the active weapon before taunting Weapon_Switch( Weapon_GetSlot( m_iPreTauntWeaponSlot ) ); m_iPreTauntWeaponSlot = -1; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- static void DispatchRPSEffect( const CTFPlayer *pPlayer, const char* pszParticleName ) { CEffectData data; data.m_nHitBox = GetParticleSystemIndex( pszParticleName ); data.m_vOrigin = pPlayer->GetAbsOrigin() + Vector( 0, 0, 87.0f ); data.m_vAngles = vec3_angle; CPASFilter intiatorFilter( data.m_vOrigin ); intiatorFilter.SetIgnorePredictionCull( true ); te->DispatchEffect( intiatorFilter, 0.0, data.m_vOrigin, "ParticleEffect", data ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::DoTauntAttack( void ) { if ( !IsTaunting() || !IsAlive() || m_iTauntAttack == TAUNTATK_NONE ) { return; } int iTauntAttack = m_iTauntAttack; m_iTauntAttack = TAUNTATK_NONE; if ( iTauntAttack == TAUNTATK_PYRO_HADOUKEN || iTauntAttack == TAUNTATK_SPY_FENCING_SLASH_A || iTauntAttack == TAUNTATK_SPY_FENCING_SLASH_B || iTauntAttack == TAUNTATK_SPY_FENCING_STAB ) { // Pyro Hadouken fireball attack // Kill all enemies within a small volume in front of the player. Vector vecForward; AngleVectors( QAngle(0, m_angEyeAngles[YAW], 0), &vecForward ); Vector vecCenter = WorldSpaceCenter() + vecForward * 64; Vector vecSize = Vector(24,24,24); CBaseEntity *pList[256]; int count = UTIL_EntitiesInBox( pList, 256, vecCenter - vecSize, vecCenter + vecSize, FL_CLIENT|FL_OBJECT ); if ( count ) { // Launch them up a little AngleVectors( QAngle(-45, m_angEyeAngles[YAW], 0), &vecForward ); for ( int i = 0; i < count; i++ ) { // Team damage doesn't prevent us hurting ourself, so we do it manually here if ( pList[i] == this ) continue; if ( FVisible( pList[i], MASK_SOLID ) == false ) continue; Vector vecPos = WorldSpaceCenter(); vecPos += (pList[i]->WorldSpaceCenter() - vecPos) * 0.75; // Spy taunt does two quick slashes, followed by a killing blow if ( iTauntAttack == TAUNTATK_SPY_FENCING_SLASH_A || iTauntAttack == TAUNTATK_SPY_FENCING_SLASH_B ) { // No physics push so it doesn't push the player out of the range of the stab pList[i]->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 100, vecPos, 25, DMG_SLASH | DMG_PREVENT_PHYSICS_FORCE, TF_DMG_CUSTOM_TAUNTATK_FENCING ) ); } else if ( iTauntAttack == TAUNTATK_SPY_FENCING_STAB ) { pList[i]->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 20000, vecPos, 500.0f, DMG_SLASH, TF_DMG_CUSTOM_TAUNTATK_FENCING ) ); } else if ( iTauntAttack == TAUNTATK_PYRO_HADOUKEN ) { pList[i]->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 25000, vecPos, 500.0f, DMG_BURN | DMG_IGNITE, TF_DMG_CUSTOM_TAUNTATK_HADOUKEN ) ); } } } if ( iTauntAttack == TAUNTATK_SPY_FENCING_SLASH_A ) { m_iTauntAttack = TAUNTATK_SPY_FENCING_SLASH_B; m_flTauntAttackTime = gpGlobals->curtime + 0.47; } else if ( iTauntAttack == TAUNTATK_SPY_FENCING_SLASH_B ) { m_iTauntAttack = TAUNTATK_SPY_FENCING_STAB; m_flTauntAttackTime = gpGlobals->curtime + 1.73; } if ( tf_debug_damage.GetBool() ) { NDebugOverlay::Box( vecCenter, -vecSize, vecSize, 0, 255, 0, 40, 10 ); } } else if ( iTauntAttack == TAUNTATK_SOLDIER_GRENADE_KILL_WORMSIGN ) { EmitSound( "Taunt.WormsHHG" ); m_iTauntAttack = TAUNTATK_SOLDIER_GRENADE_KILL; m_flTauntAttackTime = gpGlobals->curtime + 2.1; } else if ( iTauntAttack == TAUNTATK_SOLDIER_GRENADE_KILL ) { matrix3x4_t worldSpace; MatrixCopy( EntityToWorldTransform(), worldSpace ); Vector bonePos; QAngle boneAngles; int iRightHand = LookupBone( "bip_hand_r" ); if ( iRightHand != -1 ) { GetBonePosition( iRightHand, bonePos, boneAngles ); CPVSFilter filter( bonePos ); TE_TFExplosion( filter, 0.0f, bonePos, Vector(0,0,1), TF_WEAPON_GRENADELAUNCHER, entindex() ); CTakeDamageInfo info( this, this, GetActiveTFWeapon(), vec3_origin, bonePos, 200.f, DMG_BLAST | DMG_USEDISTANCEMOD, TF_DMG_CUSTOM_TAUNTATK_GRENADE, &bonePos ); CTFRadiusDamageInfo radiusinfo( &info, bonePos, 100.f ); TFGameRules()->RadiusDamage( radiusinfo ); } } else if ( iTauntAttack == TAUNTATK_SNIPER_ARROW_STAB_IMPALE || iTauntAttack == TAUNTATK_SNIPER_ARROW_STAB_KILL || iTauntAttack == TAUNTATK_ENGINEER_ARM_IMPALE || iTauntAttack == TAUNTATK_ENGINEER_ARM_KILL || iTauntAttack == TAUNTATK_ENGINEER_ARM_BLEND ) { Vector vecForward; AngleVectors( EyeAngles(), &vecForward ); Vector vecEnd = EyePosition() + vecForward * 128; trace_t tr; UTIL_TraceLine( EyePosition(), vecEnd, MASK_SOLID & ~CONTENTS_HITBOX, this, COLLISION_GROUP_PLAYER, &tr ); if ( tr.fraction < 1.0 ) { CBaseEntity *pEnt = tr.m_pEnt; if ( pEnt && pEnt->IsPlayer() && pEnt->GetTeamNumber() > LAST_SHARED_TEAM && pEnt->GetTeamNumber() != GetTeamNumber() ) { CTFPlayer *pVictim = ToTFPlayer( pEnt ); switch ( iTauntAttack ) { case TAUNTATK_SNIPER_ARROW_STAB_IMPALE: case TAUNTATK_ENGINEER_ARM_IMPALE: if ( pVictim ) { // don't stun giants if ( !pVictim->IsMiniBoss() ) { pVictim->m_Shared.StunPlayer( 3.0f, 1.0, TF_STUN_BOTH | TF_STUN_NO_EFFECTS, this ); } if ( iTauntAttack == TAUNTATK_ENGINEER_ARM_IMPALE ) { pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward, pEnt->WorldSpaceCenter(), 1, DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE, TF_DMG_CUSTOM_TAUNTATK_ENGINEER_ARM_KILL ) ); } } break; case TAUNTATK_ENGINEER_ARM_BLEND: pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward, pEnt->WorldSpaceCenter(), 1, DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE, TF_DMG_CUSTOM_TAUNTATK_ENGINEER_ARM_KILL ) ); break; case TAUNTATK_SNIPER_ARROW_STAB_KILL: // Launch them up a little vecForward = (WorldSpaceCenter() - pEnt->WorldSpaceCenter()); VectorNormalize( vecForward ); pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 12000, pEnt->WorldSpaceCenter(), 500.0f, DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE, TF_DMG_CUSTOM_TAUNTATK_ARROW_STAB ) ); break; case TAUNTATK_ENGINEER_ARM_KILL: pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 12000, pEnt->WorldSpaceCenter(), 500.0f, DMG_BLAST, TF_DMG_CUSTOM_TAUNTATK_ENGINEER_ARM_KILL ) ); break; } } } if ( iTauntAttack == TAUNTATK_SNIPER_ARROW_STAB_IMPALE ) { m_iTauntAttack = TAUNTATK_SNIPER_ARROW_STAB_KILL; m_flTauntAttackTime = gpGlobals->curtime + 1.30; } else if ( iTauntAttack == TAUNTATK_ENGINEER_ARM_IMPALE ) { m_iTauntAttack = TAUNTATK_ENGINEER_ARM_BLEND; m_flTauntAttackTime = gpGlobals->curtime + 0.05; m_iTauntAttackCount = 0; } else if ( iTauntAttack == TAUNTATK_ENGINEER_ARM_BLEND ) { m_iTauntAttack = TAUNTATK_ENGINEER_ARM_BLEND; m_flTauntAttackTime = gpGlobals->curtime + 0.05; m_iTauntAttackCount++; if ( m_iTauntAttackCount == 13 ) { m_iTauntAttack = TAUNTATK_ENGINEER_ARM_KILL; } } } else if ( iTauntAttack == TAUNTATK_HEAVY_EAT ) { CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon(); if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX ) { CTFLunchBox *pLunchbox = (CTFLunchBox*)pActiveWeapon; pLunchbox->ApplyBiteEffects( this ); } // Keep eating until the taunt is over m_iTauntAttack = TAUNTATK_HEAVY_EAT; m_flTauntAttackTime = gpGlobals->curtime + 1.0; // If we're going to finish eating after this bite, say our line if ( m_flTauntRemoveTime < m_flTauntAttackTime ) { if ( IsSpeaking() ) { // The player may technically still be speaking even though the actual VO is over and just // hasn't been cleared yet. We need to force it to end so our next concept can be played. CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); if ( pExpresser ) { pExpresser->ForceNotSpeaking(); } } SpeakConceptIfAllowed( MP_CONCEPT_ATE_FOOD ); } } else if ( iTauntAttack == TAUNTATK_HEAVY_RADIAL_BUFF ) { Vector vecOrg = GetAbsOrigin(); // Find nearby team mates and give them bonus health & crit chance for ( int i = 0; i < GetTeam()->GetNumPlayers(); i++ ) { CTFPlayer *pTeamPlayer = ToTFPlayer( GetTeam()->GetPlayer(i) ); if ( pTeamPlayer && pTeamPlayer->IsAlive() ) { // If they're within the radius, give 'em the buff if ( (vecOrg - pTeamPlayer->GetAbsOrigin()).LengthSqr() < (1024*1024) ) { pTeamPlayer->TakeHealth( 50, DMG_GENERIC ); pTeamPlayer->m_Shared.AddTempCritBonus( 0.5 ); IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" ); if ( event ) { event->SetInt( "amount", 50 ); event->SetInt( "entindex", pTeamPlayer->entindex() ); event->SetInt( "weapon_def_index", INVALID_ITEM_DEF_INDEX ); gameeventmanager->FireEvent( event ); } } } } } else if ( iTauntAttack == TAUNTATK_HEAVY_HIGH_NOON ) { // Heavy "High Noon" attack Vector vecForward; AngleVectors( EyeAngles(), &vecForward ); Vector vecEnd = EyePosition() + vecForward * 500; trace_t tr; UTIL_TraceLine( EyePosition(), vecEnd, ( MASK_SOLID | CONTENTS_HITBOX ), this, COLLISION_GROUP_PLAYER, &tr ); // DebugDrawLine( EyePosition(), vecEnd, 0, 0, 255, true, 3.0f ); if ( tr.fraction < 1.0 ) { CBaseEntity *pEnt = tr.m_pEnt; if ( pEnt && pEnt->IsPlayer() && pEnt->GetTeamNumber() > LAST_SHARED_TEAM && pEnt->GetTeamNumber() != GetTeamNumber() ) { // Launch them up a little AngleVectors( QAngle(-45, m_angEyeAngles[YAW], 0), &vecForward ); pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 25000, WorldSpaceCenter(), 500.0f, DMG_BULLET, TF_DMG_CUSTOM_TAUNTATK_HIGH_NOON ) ); } } } else if ( iTauntAttack == TAUNTATK_SCOUT_DRINK ) { if ( !m_Shared.IsControlStunned() ) { // Check for CritBerry flavor CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon(); if ( pActiveWeapon && pActiveWeapon->GetWeaponID() == TF_WEAPON_LUNCHBOX ) { float flDropDeadTime = ( 100.f / tf_scout_energydrink_consume_rate.GetFloat() ) + 1.f; // Just in case. Normally over in 8 seconds. CTFLunchBox *pLunchbox = static_cast< CTFLunchBox* >( pActiveWeapon ); if ( pLunchbox && pLunchbox->GetLunchboxType() == LUNCHBOX_ADDS_MINICRITS ) { m_Shared.AddCond( TF_COND_ENERGY_BUFF, flDropDeadTime ); } else { m_Shared.AddCond( TF_COND_PHASE, flDropDeadTime ); if ( HasTheFlag() ) { bool bShouldDrop = true; // Always allow teams to hear each other in TD mode if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { bShouldDrop = false; } if ( bShouldDrop ) { DropFlag(); } } } SelectLastItem(); } } } else if ( iTauntAttack == TAUNTATK_SCOUT_GRAND_SLAM ) { // Find a player in front of us and knock 'em across the map. // Same box logic as hadouken & pyro knockback. Vector vecForward; AngleVectors( QAngle(0, m_angEyeAngles[YAW], 0), &vecForward ); Vector vecCenter = WorldSpaceCenter() + vecForward * 64; Vector vecSize = Vector(24,24,24); CBaseEntity *pObjects[256]; int count = UTIL_EntitiesInBox( pObjects, 256, vecCenter - vecSize, vecCenter + vecSize, FL_CLIENT|FL_OBJECT ); if ( count ) { for ( int i=0; iWorldSpaceCenter() - WorldSpaceCenter(); VectorNormalize( vecToTarget ); float flDot = DotProduct( vecForward, vecToTarget ); if ( flDot < 0.80 ) continue; CTFPlayer *pTarget = ToTFPlayer( pObjects[i] ); if ( !pTarget ) continue; if ( pTarget->GetTeamNumber() == GetTeamNumber() ) continue; // Do a quick trace and make sure we have LOS. trace_t tr; UTIL_TraceLine( WorldSpaceCenter(), pObjects[i]->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER, &tr ); if ( tr.fraction < 1.0 ) continue; pTarget->SetAbsVelocity( vec3_origin ); //pTarget->m_Shared.StunPlayer( 8.f, 1.f, TF_STUN_BOTH | TF_STUN_SPECIAL_SOUND ); pTarget->StunSound( this, TF_STUN_BOTH | TF_STUN_SPECIAL_SOUND ); pTarget->ApplyPunchImpulseX( RandomInt( 10, 15 ) ); AngleVectors( QAngle(-45, m_angEyeAngles[YAW], 0), &vecForward ); pTarget->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 130000, WorldSpaceCenter(), 500.0f, DMG_BULLET, TF_DMG_CUSTOM_TAUNTATK_GRAND_SLAM ) ); // Tell the achievement system we swatted someone. IGameEvent *event = gameeventmanager->CreateEvent( "scout_grand_slam" ); if ( event ) { event->SetInt( "scout_id", GetUserID() ); event->SetInt( "target_id", pTarget->GetUserID() ); gameeventmanager->FireEvent( event ); } } } } else if ( iTauntAttack == TAUNTATK_MEDIC_HEROIC_TAUNT ) { // do these later m_flTauntAttackTime = gpGlobals->curtime + 3.0f; m_iTauntAttack = TAUNTATK_MEDIC_RELEASE_DOVES; // send a reliable message to make sure the effect happens CPVSFilter filter( GetAbsOrigin() ); UserMessageBegin( filter, "PlayerGodRayEffect" ); WRITE_BYTE( entindex() ); MessageEnd(); EmitSound( "Taunt.MedicHeroic" ); } else if ( iTauntAttack == TAUNTATK_MEDIC_RELEASE_DOVES ) { // not really a taunt "attack", just a hook to release some doves at the appropriate time Vector launchSpot = ( WorldSpaceCenter() + GetAbsOrigin() ) / 2.0f; for( int i=0; i vecDamagedPlayers; const float flRadius = 100.0f; const float flRadiusSqr = flRadius * flRadius; CBaseEntity *pEntity = NULL; for ( CEntitySphereQuery sphere( origin, flRadius ); (pEntity = sphere.GetCurrentEntity()) != NULL && vecDamagedPlayers.Count() < ARRAYSIZE( nRandomPick ); sphere.NextEntity() ) { // Skip players on the same team or who are invuln CTFPlayer *pPlayer = ToTFPlayer( pEntity ); if ( !pPlayer || InSameTeam( pPlayer ) || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) ) continue; // CEntitySphereQuery actually does a box test. So we need to make sure the distance is less than the radius first. Vector vecPos; pEntity->CollisionProp()->CalcNearestPoint( origin, &vecPos ); if ( ( origin - vecPos ).LengthSqr() > flRadiusSqr ) continue; // Finally LOS test trace_t tr; Vector vecSrc = WorldSpaceCenter(); Vector vecSpot = pEntity->WorldSpaceCenter(); CTraceFilterSimple filter( this, COLLISION_GROUP_PROJECTILE ); UTIL_TraceLine( vecSrc, vecSpot, MASK_SOLID_BRUSHONLY, &filter, &tr ); // If we don't trace the whole way to the target, and we didn't hit the target entity, we're blocked if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity ) continue; vecDamagedPlayers.AddToTail( pPlayer ); } if ( vecDamagedPlayers.Count() ) { int nBurnCount = 0; float fDamage = 400.0f; for ( int i = vecDamagedPlayers.Count() - 1; i >= 0; --i ) { // Pick a random player int nRand = RandomInt( 0, i ); CTFPlayer *pPlayer = vecDamagedPlayers[ nRandomPick[ nRand ] ]; if ( pPlayer ) { bool bBurning = pPlayer->m_Shared.InCond( TF_COND_BURNING ); pPlayer->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vec3_origin, origin, fDamage, DMG_PLASMA, ( iTauntAttack == TAUNTATK_PYRO_ARMAGEDDON ) ? TF_DMG_CUSTOM_TAUNTATK_ARMAGEDDON : TF_DMG_CUSTOM_TAUNTATK_ALLCLASS_GUITAR_RIFF, &origin ) ); // If they weren't burning before but now they are, count it if ( !bBurning && pPlayer->m_Shared.InCond( TF_COND_BURNING ) ) { nBurnCount++; } // Next choice gets half that amount fDamage /= 2; // The end of the list moves overwrites the one we just picked nRandomPick[ nRand ] = nRandomPick[ i ]; } } if ( iTauntAttack == TAUNTATK_PYRO_ARMAGEDDON ) { if ( nBurnCount >= 3 ) { AwardAchievement( ACHIEVEMENT_TF_PYRO_IGNITE_WITH_RAINBOW ); } } } UTIL_ScreenShake( origin, 15.0, 150.0, 0.75f, 500.0f, SHAKE_START ); } else if ( iTauntAttack == TAUNTATK_PYRO_SCORCHSHOT ) { CTFWeaponBase *pWeapon = GetActiveTFWeapon(); if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_FLAREGUN ) { CTFWeaponBaseGun *pGun = dynamic_cast< CTFWeaponBaseGun* >( pWeapon ); if ( pGun ) { pGun->FireProjectile( this ); } } } else if ( iTauntAttack == TAUNTATK_ALLCLASS_GUITAR_RIFF ) { // We need to parent this to a target instead of the player because the player changing their camera view can twist the rainbow CBaseEntity *pTarget = CreateEntityByName( "info_target" ); if ( pTarget ) { DispatchSpawn( pTarget ); pTarget->SetAbsOrigin( GetAbsOrigin() ); pTarget->SetAbsAngles( GetAbsAngles() ); pTarget->SetEFlags( EFL_FORCE_CHECK_TRANSMIT ); pTarget->SetThink( &BaseClass::SUB_Remove ); pTarget->SetNextThink( gpGlobals->curtime + 6.0f ); CBaseEntity *pGround = GetGroundEntity(); if ( pGround && pGround->GetMoveType() == MOVETYPE_PUSH ) { pTarget->SetParent( pGround ); } } CBroadcastRecipientFilter filter; TE_TFParticleEffect( filter, 0.0, "bl_killtaunt", GetAbsOrigin(), GetAbsAngles(), pTarget, PATTACH_ABSORIGIN_FOLLOW ); EmitSound( "Taunt.GuitarRiff" ); } else if ( iTauntAttack == TAUNTATK_MEDIC_INHALE ) { int iHealed = TakeHealth( 1, DMG_GENERIC ); if ( iHealed > 0 ) { CTF_GameStats.Event_PlayerHealedOther( this, iHealed ); } // Keep eating until the taunt is over if ( m_flTauntInhaleTime > gpGlobals->curtime ) { m_iTauntAttack = TAUNTATK_MEDIC_INHALE; m_flTauntAttackTime = gpGlobals->curtime + 0.1; } } else if ( iTauntAttack == TAUNTATK_MEDIC_UBERSLICE_IMPALE || iTauntAttack == TAUNTATK_MEDIC_UBERSLICE_KILL ) { Vector vecForward; AngleVectors( EyeAngles(), &vecForward ); Vector vecEnd = EyePosition() + vecForward * 128; trace_t tr; UTIL_TraceLine( EyePosition(), vecEnd, MASK_SOLID & ~CONTENTS_HITBOX, this, COLLISION_GROUP_PLAYER, &tr ); if ( tr.fraction < 1.0 ) { CBaseEntity *pEnt = tr.m_pEnt; if ( pEnt && pEnt->IsPlayer() && pEnt->GetTeamNumber() > LAST_SHARED_TEAM && pEnt->GetTeamNumber() != GetTeamNumber() ) { CTFPlayer *pVictim = ToTFPlayer( pEnt ); if ( iTauntAttack == TAUNTATK_MEDIC_UBERSLICE_IMPALE ) { if ( pVictim ) { // don't stun giants if ( !pVictim->IsMiniBoss() ) { pVictim->m_Shared.StunPlayer( 1.5f, 1.0, TF_STUN_BOTH | TF_STUN_NO_EFFECTS, this ); } pVictim->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward, WorldSpaceCenter(), 1, DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE, TF_DMG_CUSTOM_TAUNTATK_UBERSLICE ) ); } } else { // Launch them up a little vecForward = (WorldSpaceCenter() - pVictim->WorldSpaceCenter()); VectorNormalize( vecForward ); pVictim->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 12000, WorldSpaceCenter(), 500.0f, DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE, TF_DMG_CUSTOM_TAUNTATK_UBERSLICE ) ); CWeaponMedigun *pMedigun = (CWeaponMedigun *) Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ); if ( pMedigun ) { pMedigun->AddCharge( 0.5f ); } } } } if ( iTauntAttack == TAUNTATK_MEDIC_UBERSLICE_IMPALE ) { m_iTauntAttack = TAUNTATK_MEDIC_UBERSLICE_KILL; m_flTauntAttackTime = gpGlobals->curtime + 0.75; } } else if ( iTauntAttack == TAUNTATK_DEMOMAN_BARBARIAN_SWING ) { Vector vecForward; AngleVectors( EyeAngles(), &vecForward ); Vector vecEnd = EyePosition() + vecForward * 128; trace_t tr; UTIL_TraceLine( EyePosition(), vecEnd, MASK_SOLID & ~CONTENTS_HITBOX, this, COLLISION_GROUP_PLAYER, &tr ); if ( tr.fraction < 1.0 ) { CBaseEntity *pEnt = tr.m_pEnt; if ( pEnt && pEnt->IsPlayer() && pEnt->GetTeamNumber() > LAST_SHARED_TEAM && pEnt->GetTeamNumber() != GetTeamNumber() ) { vecForward = (WorldSpaceCenter() - pEnt->WorldSpaceCenter()); VectorNormalize( vecForward ); pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 12000, WorldSpaceCenter(), 500.0f, DMG_CLUB, TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING ) ); } } } else if ( iTauntAttack == TAUNTATK_ENGINEER_GUITAR_SMASH ) { Vector vecForward; AngleVectors( EyeAngles(), &vecForward ); Vector vecEnd = EyePosition() + vecForward * 128; trace_t tr; UTIL_TraceLine( EyePosition(), vecEnd, MASK_SOLID & ~CONTENTS_HITBOX, this, COLLISION_GROUP_PLAYER, &tr ); if ( tr.fraction < 1.0 ) { CBaseEntity *pEnt = tr.m_pEnt; if ( pEnt && pEnt->IsPlayer() && pEnt->GetTeamNumber() > LAST_SHARED_TEAM && pEnt->GetTeamNumber() != GetTeamNumber() ) { vecForward = (WorldSpaceCenter() - pEnt->WorldSpaceCenter()); VectorNormalize( vecForward ); pEnt->TakeDamage( CTakeDamageInfo( this, this, GetActiveTFWeapon(), vecForward * 12, WorldSpaceCenter(), 500.0f, DMG_CLUB, TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH ) ); } } } else if ( iTauntAttack == TAUNTATK_SHOW_ITEM ) { if ( m_hTauntItem == NULL ) { int itemCount = Inventory()->GetItemCount(); CUtlVector< CEconItemView * > hatVector; for( int i=0; iGetItem( i ); int iSlot = econItemView->GetStaticData()->GetLoadoutSlot( GetPlayerClass()->GetClassIndex() ); if ( iSlot == LOADOUT_POSITION_HEAD ) { hatVector.AddToTail( econItemView ); } } if ( hatVector.Count() > 0 ) { int which = RandomInt( 0, hatVector.Count()-1 ); CEconItemView *hatView = hatVector[ which ]; int iHandBone = LookupBone( "weapon_bone" ); if ( iHandBone != -1 ) { Vector pos; QAngle angles; GetBonePosition( iHandBone, pos, angles ); pos = Vector( 0, 0, 50.0f ); m_hTauntItem = ItemGeneration()->GenerateItemFromScriptData( hatView, pos, angles, NULL ); if ( m_hTauntItem != NULL ) { m_hTauntItem->AddSolidFlags( FSOLID_NOT_SOLID ); m_hTauntItem->SetOwnerEntity( this ); } } } } } else if ( iTauntAttack == TAUNTATK_HIGHFIVE_PARTICLE ) { if ( m_hHighFivePartner.Get() ) { QAngle bodyAngles = BodyAngles(); bodyAngles.x = 0; Vector vecForward, vecRight, vecUp; AngleVectors( bodyAngles, &vecForward, &vecRight, &vecUp ); //Msg( "forward: %f %f %f right: %f %f %f up: %f %f %f\n", vecForward.x, vecForward.y, vecForward.z, // vecRight.x, vecRight.y, vecRight.z, // vecUp.x, vecUp.y, vecUp.z ); Vector vecParticle = GetAbsOrigin() + (vecForward * 30.0f) + (vecRight * -3.0f) + (vecUp * 87.0f); //Msg( "particle: %f %f %f\n", vecParticle.x, vecParticle.y, vecParticle.z ); CEffectData data; data.m_nHitBox = GetParticleSystemIndex( GetTeamNumber() == TF_TEAM_RED ? "highfive_red" : "highfive_blue" ); data.m_vOrigin = vecParticle; data.m_vAngles = vec3_angle; CPASFilter filter( data.m_vOrigin ); filter.SetIgnorePredictionCull( true ); te->DispatchEffect( filter, 0.0, data.m_vOrigin, "ParticleEffect", data ); } } else if ( iTauntAttack == TAUNTATK_RPS_PARTICLE ) { if ( m_hHighFivePartner.Get() ) { bool bInitiatorWin = ( m_iTauntRPSResult / 3 ) == 0; // figure out for RPS // 0:rock 1:paper 2:scissors int iInitiator = m_iTauntRPSResult % 3; int iReceiver = ( iInitiator + ( bInitiatorWin ? 2 : 1 ) ) % 3; // offset to get the correct particle name if ( bInitiatorWin ) { iInitiator += 3; } else { iReceiver += 3; } if ( GetTeamNumber() == TF_TEAM_BLUE ) { iInitiator += 6; } if ( m_hHighFivePartner->GetTeamNumber() == TF_TEAM_BLUE ) { iReceiver += 6; } DispatchRPSEffect( this, s_pszTauntRPSParticleNames[iInitiator] ); DispatchRPSEffect( m_hHighFivePartner.Get(), s_pszTauntRPSParticleNames[iReceiver] ); // setup time to kill the opposing team loser if ( GetTeamNumber() != m_hHighFivePartner->GetTeamNumber() ) { m_iTauntAttack = TAUNTATK_RPS_KILL; m_flTauntAttackTime = m_flTauntRemoveTime - 1.2f; } IGameEvent *event = gameeventmanager->CreateEvent( "rps_taunt_event" ); if ( event ) { int iInitiatorRPS = m_iTauntRPSResult % 3; int iReceiverRPS = ( iInitiatorRPS + ( bInitiatorWin ? 2 : 1 ) ) % 3; event->SetInt( "winner", bInitiatorWin ? entindex() : m_hHighFivePartner.Get()->entindex() ); event->SetInt( "winner_rps", bInitiatorWin ? iInitiatorRPS : iReceiverRPS ); event->SetInt( "loser", bInitiatorWin ? m_hHighFivePartner.Get()->entindex() : entindex() ); event->SetInt( "loser_rps", bInitiatorWin ? iReceiverRPS : iInitiatorRPS ); gameeventmanager->FireEvent( event ); } } } else if ( iTauntAttack == TAUNTATK_RPS_KILL ) { if ( m_hHighFivePartner.Get() ) { bool bInitiatorWin = ( m_iTauntRPSResult / 3 ) == 0; CTFPlayer *pWinner = NULL; CTFPlayer *pLoser = NULL; if ( bInitiatorWin ) { pWinner = this; pLoser = m_hHighFivePartner.Get(); } else { pWinner = m_hHighFivePartner.Get(); pLoser = this; } // gib the loser pLoser->m_bSuicideExplode = true; pLoser->TakeDamage( CTakeDamageInfo( pWinner, pWinner, NULL, 999, DMG_GENERIC, 0 ) ); } } // Particle Being played in VCD instead //else if ( iTauntAttack == TAUNTATK_FLIP_LAND_PARTICLE ) //{ // if ( m_hHighFivePartner.Get() ) // { // CEffectData data; // data.m_nHitBox = GetParticleSystemIndex( GetTeamNumber() == TF_TEAM_RED ? "taunt_flip_land_red" : "taunt_flip_land_blue" ); // data.m_vOrigin = m_hHighFivePartner.Get()->GetAbsOrigin(); // data.m_vAngles = m_hHighFivePartner.Get()->GetAbsAngles(); // CPASFilter filter( data.m_vOrigin ); // filter.SetIgnorePredictionCull( true ); // te->DispatchEffect( filter, 0.0, data.m_vOrigin, "ParticleEffect", data ); // } //} } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::GetSpecialDSP( void ) { int iSpecialDSP = 0; CALL_ATTRIB_HOOK_INT( iSpecialDSP, special_dsp ); return iSpecialDSP; } //----------------------------------------------------------------------------- // Purpose: Play a one-shot scene // Input : // Output : //----------------------------------------------------------------------------- float CTFPlayer::PlayScene( const char *pszScene, float flDelay, AI_Response *response, IRecipientFilter *filter ) { MDLCACHE_CRITICAL_SECTION(); // This is a lame way to detect a taunt! if ( m_bInitTaunt ) { m_bInitTaunt = false; return InstancedScriptedScene( this, pszScene, &m_hTauntScene, flDelay, false, response, true, filter ); } else { return InstancedScriptedScene( this, pszScene, NULL, flDelay, false, response, true, filter ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ) { BaseClass::ModifyOrAppendCriteria( criteriaSet ); // If we have 'disguiseclass' criteria, pretend that we are actually our // disguise class. That way we just look up the scene we would play as if // we were that class. int disguiseIndex = criteriaSet.FindCriterionIndex( "disguiseclass" ); if ( disguiseIndex != -1 ) { criteriaSet.AppendCriteria( "playerclass", criteriaSet.GetValue(disguiseIndex) ); } else { if ( GetPlayerClass() ) { criteriaSet.AppendCriteria( "playerclass", g_aPlayerClassNames_NonLocalized[ GetPlayerClass()->GetClassIndex() ] ); } } bool bRedTeam = ( GetTeamNumber() == TF_TEAM_RED ); if ( m_Shared.InCond( TF_COND_DISGUISED ) ) { bRedTeam = ( m_Shared.GetDisguiseTeam() == TF_TEAM_RED ); } criteriaSet.AppendCriteria( "OnRedTeam", bRedTeam ? "1" : "0" ); //============================================================================= // HPE_BEGIN: // [msmith] When in training, we kill a lot of guys... a WHOLE LOT. This was // triggering some response sounds that got very annoying after a while. //============================================================================= if ( TFGameRules()->IsInTraining() ) { criteriaSet.AppendCriteria( "recentkills", UTIL_VarArgs("%d", 0) ); } else { criteriaSet.AppendCriteria( "recentkills", UTIL_VarArgs("%d", m_Shared.GetNumKillsInTime(30.0)) ); } //============================================================================= // HPE_END //============================================================================= int iTotalKills = 0; PlayerStats_t *pStats = CTF_GameStats.FindPlayerStats( this ); if ( pStats ) { iTotalKills = pStats->statsCurrentLife.m_iStat[TFSTAT_KILLS] + pStats->statsCurrentLife.m_iStat[TFSTAT_KILLASSISTS]+ pStats->statsCurrentLife.m_iStat[TFSTAT_BUILDINGSDESTROYED]; } criteriaSet.AppendCriteria( "killsthislife", UTIL_VarArgs( "%d", iTotalKills ) ); criteriaSet.AppendCriteria( "disguised", m_Shared.InCond( TF_COND_DISGUISED ) ? "1" : "0" ); criteriaSet.AppendCriteria( "cloaked", ( m_Shared.IsStealthed() || m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ) ? "1" : "0" ); criteriaSet.AppendCriteria( "invulnerable", m_Shared.InCond( TF_COND_INVULNERABLE ) ? "1" : "0" ); criteriaSet.AppendCriteria( "beinghealed", m_Shared.InCond( TF_COND_HEALTH_BUFF ) ? "1" : "0" ); criteriaSet.AppendCriteria( "waitingforplayers", (TFGameRules()->IsInWaitingForPlayers() || TFGameRules()->IsInPreMatch()) ? "1" : "0" ); criteriaSet.AppendCriteria( "stunned", m_Shared.IsControlStunned() ? "1" : "0" ); criteriaSet.AppendCriteria( "snared", m_Shared.IsSnared() ? "1" : "0" ); criteriaSet.AppendCriteria( "dodging", (m_Shared.InCond( TF_COND_PHASE ) || m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION )) ? "1" : "0" ); criteriaSet.AppendCriteria( "doublejumping", (m_Shared.GetAirDash()>0) ? "1" : "0" ); switch ( GetTFTeam()->GetRole() ) { case TEAM_ROLE_DEFENDERS: criteriaSet.AppendCriteria( "teamrole", "defense" ); break; case TEAM_ROLE_ATTACKERS: criteriaSet.AppendCriteria( "teamrole", "offense" ); break; } // Current weapon role CTFWeaponBase *pActiveWeapon = m_Shared.GetActiveTFWeapon(); if ( pActiveWeapon ) { int iWeaponRole = pActiveWeapon->GetTFWpnData().m_iWeaponType; switch( iWeaponRole ) { case TF_WPN_TYPE_PRIMARY: default: criteriaSet.AppendCriteria( "weaponmode", "primary" ); break; case TF_WPN_TYPE_SECONDARY: criteriaSet.AppendCriteria( "weaponmode", "secondary" ); break; case TF_WPN_TYPE_MELEE: criteriaSet.AppendCriteria( "weaponmode", "melee" ); break; case TF_WPN_TYPE_BUILDING: criteriaSet.AppendCriteria( "weaponmode", "building" ); break; case TF_WPN_TYPE_PDA: criteriaSet.AppendCriteria( "weaponmode", "pda" ); break; case TF_WPN_TYPE_ITEM1: criteriaSet.AppendCriteria( "weaponmode", "item1" ); break; case TF_WPN_TYPE_ITEM2: criteriaSet.AppendCriteria( "weaponmode", "item2" ); break; } if ( WeaponID_IsSniperRifle( pActiveWeapon->GetWeaponID() ) ) { CTFSniperRifle *pRifle = dynamic_cast(pActiveWeapon); if ( pRifle && pRifle->IsZoomed() ) { criteriaSet.AppendCriteria( "sniperzoomed", "1" ); } } else if ( pActiveWeapon->GetWeaponID() == TF_WEAPON_MINIGUN ) { CTFMinigun *pMinigun = dynamic_cast(pActiveWeapon); if ( pMinigun ) { criteriaSet.AppendCriteria( "minigunfiretime", UTIL_VarArgs( "%.1f", pMinigun->GetFiringDuration() ) ); } } CEconItemView *pItem = pActiveWeapon->GetAttributeContainer()->GetItem(); if ( pItem && pItem->GetItemQuality() != AE_NORMAL ) { criteriaSet.AppendCriteria( "item_name", pItem->GetStaticData()->GetDefinitionName() ); criteriaSet.AppendCriteria( "item_type_name", pItem->GetStaticData()->GetItemTypeName() ); } } // equipped loadout items { static const char* kSlotCriteriaName[CLASS_LOADOUT_POSITION_COUNT] = { "loadout_slot_primary", // LOADOUT_POSITION_PRIMARY = 0, "loadout_slot_secondary", // LOADOUT_POSITION_SECONDARY, "loadout_slot_melee", // LOADOUT_POSITION_MELEE, "loadout_slot_utility", // LOADOUT_POSITION_UTILITY, "loadout_slot_building", // LOADOUT_POSITION_BUILDING, "loadout_slot_pda", // LOADOUT_POSITION_PDA, "loadout_slot_pda2", // LOADOUT_POSITION_PDA2, "loadout_slot_head", // LOADOUT_POSITION_HEAD, "loadout_slot_misc", // LOADOUT_POSITION_MISC, "loadout_slot_action", // LOADOUT_POSITION_ACTION, "loadout_slot_misc2", // LOADOUT_POSITION_MISC2 "loadout_slot_taunt", // LOADOUT_POSITION_TAUNT "loadout_slot_taunt2", // LOADOUT_POSITION_TAUNT2 "loadout_slot_taunt3", // LOADOUT_POSITION_TAUNT3 "loadout_slot_taunt4", // LOADOUT_POSITION_TAUNT4 "loadout_slot_taunt5", // LOADOUT_POSITION_TAUNT5 "loadout_slot_taunt6", // LOADOUT_POSITION_TAUNT6 "loadout_slot_taunt7", // LOADOUT_POSITION_TAUNT7 "loadout_slot_taunt8", // LOADOUT_POSITION_TAUNT8 #ifdef STAGING_ONLY "loadout_slot_pda3", // LOADOUT_POSITION_PDA3, //"loadout_slot_misc3", // LOADOUT_POSITION_MISC3 //"loadout_slot_misc4", // LOADOUT_POSITION_MISC4 //"loadout_slot_misc5", // LOADOUT_POSITION_MISC5 //"loadout_slot_misc6", // LOADOUT_POSITION_MISC6 //"loadout_slot_misc7", // LOADOUT_POSITION_MISC3 //"loadout_slot_misc8", // LOADOUT_POSITION_MISC4 //"loadout_slot_misc9", // LOADOUT_POSITION_MISC5 //"loadout_slot_misc10", // LOADOUT_POSITION_MISC6 "loadout_slot_building2", // LOADOUT_POSITION_BUILDING2, #endif // STAGING_ONLY }; COMPILE_TIME_ASSERT( ARRAYSIZE(kSlotCriteriaName) == CLASS_LOADOUT_POSITION_COUNT ); CEconItemView *pItem = NULL; for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; ++i ) { if ( m_EquippedLoadoutItemIndices[i] != LOADOUT_SLOT_USE_BASE_ITEM ) { pItem = m_Inventory.GetInventoryItemByItemID( m_EquippedLoadoutItemIndices[i] ); if ( pItem ) { criteriaSet.AppendCriteria( kSlotCriteriaName[i], pItem->GetStaticData()->GetDefinitionName() ); } } } } // Player under crosshair trace_t tr; Vector forward; EyeVectors( &forward ); UTIL_TraceLine( EyePosition(), EyePosition() + (forward * MAX_TRACE_LENGTH), MASK_BLOCKLOS_AND_NPCS, this, COLLISION_GROUP_NONE, &tr ); if ( !tr.startsolid && tr.DidHitNonWorldEntity() ) { CBaseEntity *pEntity = tr.m_pEnt; if ( pEntity && pEntity->IsPlayer() ) { CTFPlayer *pTFPlayer = ToTFPlayer(pEntity); if ( pTFPlayer ) { int iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); if ( !InSameTeam(pTFPlayer) ) { // Prevent spotting stealthed enemies who haven't been exposed recently if ( pTFPlayer->m_Shared.InCond( TF_COND_STEALTHED ) ) { if ( pTFPlayer->m_Shared.GetLastStealthExposedTime() < (gpGlobals->curtime - 3.0) ) { iClass = TF_CLASS_UNDEFINED; } else { iClass = TF_CLASS_SPY; } } else if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) { iClass = pTFPlayer->m_Shared.GetDisguiseClass(); } } if ( iClass > TF_CLASS_UNDEFINED && iClass <= TF_LAST_NORMAL_CLASS ) { criteriaSet.AppendCriteria( "crosshair_on", g_aPlayerClassNames_NonLocalized[iClass] ); int iVisibleTeam = pTFPlayer->GetTeamNumber(); if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) { iVisibleTeam = pTFPlayer->m_Shared.GetDisguiseTeam(); } if ( iVisibleTeam != GetTeamNumber() ) { criteriaSet.AppendCriteria( "crosshair_enemy", "yes" ); } } } } } // Previous round win bool bLoser = ( TFGameRules()->GetPreviousRoundWinners() != TEAM_UNASSIGNED && TFGameRules()->GetPreviousRoundWinners() != GetPrevRoundTeamNum() ); criteriaSet.AppendCriteria( "LostRound", bLoser ? "1" : "0" ); bool bPrevRoundTie = ( ( TFGameRules()->GetRoundsPlayed() > 0 ) && ( TFGameRules()->GetPreviousRoundWinners() == TEAM_UNASSIGNED ) ); criteriaSet.AppendCriteria( "PrevRoundWasTie", bPrevRoundTie ? "1" : "0" ); // Control points CTriggerAreaCapture *pAreaTrigger = GetControlPointStandingOn(); if ( pAreaTrigger ) { CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint(); if ( pCP ) { if ( pCP->GetOwner() == GetTeamNumber() ) { criteriaSet.AppendCriteria( "OnFriendlyControlPoint", "1" ); } else { if ( TeamplayGameRules()->TeamMayCapturePoint( GetTeamNumber(), pCP->GetPointIndex() ) && TeamplayGameRules()->PlayerMayCapturePoint( this, pCP->GetPointIndex() ) ) { criteriaSet.AppendCriteria( "OnCappableControlPoint", "1" ); } } } } bool bIsBonusTime = false; bool bGameOver = false; // Current game state criteriaSet.AppendCriteria( "GameRound", UTIL_VarArgs( "%d", TFGameRules()->State_Get() ) ); if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) { criteriaSet.AppendCriteria( "OnWinningTeam", ( TFGameRules()->GetWinningTeam() == GetTeamNumber() ) ? "1" : "0" ); bIsBonusTime = ( TFGameRules()->GetStateTransitionTime() > gpGlobals->curtime ); bGameOver = TFGameRules()->IsGameOver(); } // Number of rounds played criteriaSet.AppendCriteria( "RoundsPlayed", UTIL_VarArgs( "%d", TFGameRules()->GetRoundsPlayed() ) ); // Is this a 6v6 match? CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch(); bool bIsComp6v6 = ( pMatch && pMatch->m_eMatchGroup == k_nMatchGroup_Ladder_6v6 ); criteriaSet.AppendCriteria( "IsComp6v6", bIsComp6v6 ? "1" : "0" ); bool bIsCompWinner = m_Shared.InCond( TF_COND_COMPETITIVE_WINNER ); criteriaSet.AppendCriteria( "IsCompWinner", bIsCompWinner ? "1" : "0" ); // Holiday Taunt int iSpecialTaunt = 0; if ( pActiveWeapon ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iSpecialTaunt, special_taunt ); } // only roll random halloween taunt if the active weapon doesn't have special taunt attribute if ( TFGameRules()->IsHolidayActive( kHoliday_Halloween ) && iSpecialTaunt == 0 ) { if ( !TFGameRules()->IsMannVsMachineMode() || ( GetTeamNumber() != TF_TEAM_PVE_INVADERS ) ) { if ( pActiveWeapon ) { int iRageTaunt = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iRageTaunt, burn_damage_earns_rage ); CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iRageTaunt, generate_rage_on_dmg ); int iWeaponID = pActiveWeapon->GetWeaponID(); if ( iWeaponID != TF_WEAPON_LUNCHBOX && !( iRageTaunt && m_Shared.GetRageMeter() >= 100.f ) ) { float frand = (float) rand() / VALVE_RAND_MAX; if ( frand < 0.4f ) { criteriaSet.AppendCriteria( "IsHalloweenTaunt", "1" ); } } } } } if ( TFGameRules()->IsHolidayActive( kHoliday_AprilFools ) && iSpecialTaunt == 0 ) { if ( pActiveWeapon ) { int iRageTaunt = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iRageTaunt, burn_damage_earns_rage ); CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iRageTaunt, generate_rage_on_dmg ); int iWeaponID = pActiveWeapon->GetWeaponID(); if ( iWeaponID != TF_WEAPON_LUNCHBOX && !( iRageTaunt && m_Shared.GetRageMeter() >= 100.f ) ) { float frand = (float)rand() / VALVE_RAND_MAX; if ( frand < 0.8f ) { criteriaSet.AppendCriteria( "IsAprilFoolsTaunt", "1" ); } } } } // Force the thriller taunt if we have the thriller condition if( m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) ) { criteriaSet.AppendCriteria( "IsHalloweenTaunt", "1" ); } // Only allow these rules if in the holiday if ( TFGameRules()->IsHolidayActive( kHoliday_HalloweenOrFullMoon ) && iSpecialTaunt == 0 ) { // Halloween costume sets if ( IsRobotCostumeEquipped() ) { criteriaSet.AppendCriteria( "IsRobotCostume", "1" ); } else if ( IsDemowolf() ) { criteriaSet.AppendCriteria( "IsDemowolf", "1" ); } else if ( IsFrankenHeavy() ) { criteriaSet.AppendCriteria( "IsFrankenHeavy", "1" ); } // Single items with response rules else { static CSchemaAttributeDefHandle pAttrDef_AdditionalHalloweenResponseRule( "additional halloween response criteria name" ); FOR_EACH_VEC_BACK( m_hMyWearables, wbl ) { CEconWearable *pWearable = m_hMyWearables[wbl]; if ( pWearable && pWearable->GetAttributeContainer()->GetItem() ) { const char *pszAdditionalResponseRule = NULL; if ( FindAttribute_UnsafeBitwiseCast( pWearable->GetAttributeContainer()->GetItem(), pAttrDef_AdditionalHalloweenResponseRule, &pszAdditionalResponseRule ) ) { criteriaSet.AppendCriteria( pszAdditionalResponseRule, "1" ); } } } } // Zombie could work in addition to any of these if ( IsZombieCostumeEquipped() ) { criteriaSet.AppendCriteria( "IsZombieCostume", "1" ); } } if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) ) { CMerasmus* pMerasmus = assert_cast< CMerasmus* >( TFGameRules()->GetActiveBoss() ); if ( pMerasmus ) { if ( pMerasmus->IsHiding() ) { criteriaSet.AppendCriteria( "IsMerasmusHiding", "1" ); } } } bool bInHell = false; if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) && ( TFGameRules()->ArePlayersInHell() == true ) ) { bInHell = true; } criteriaSet.AppendCriteria( "IsInHell", bInHell ? "1" : "0" ); if ( TFGameRules()->IsHolidayActive( kHoliday_HalloweenOrFullMoonOrValentines ) ) { if ( IsFairyHeavy() ) { criteriaSet.AppendCriteria( "IsFairyHeavy", "1" ); } } if ( TFGameRules()->IsMannVsMachineMode() ) { if ( GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) { criteriaSet.AppendCriteria( "IsMvMDefender", "1" ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTriggerAreaCapture *CTFPlayer::GetControlPointStandingOn( 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() ) { CTriggerAreaCapture *pAreaTrigger = dynamic_cast(pTouch); if ( pAreaTrigger ) return pAreaTrigger; } } } return NULL; } //----------------------------------------------------------------------------- // Usable by CTFPlayers, not just CTFBots class CTFPlayertPathCost : public IPathCost { public: CTFPlayertPathCost( const CTFPlayer *me ) { m_me = me; m_stepHeight = StepHeight; m_maxJumpHeight = 72.0f; m_maxDropHeight = 200.0f; } virtual float operator()( CNavArea *baseArea, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const { VPROF_BUDGET( "CTFPlayertPathCost::operator()", "NextBot" ); CTFNavArea *area = (CTFNavArea *)baseArea; if ( fromArea == NULL ) { // first area in path, no cost return 0.0f; } else { if ( !m_me->IsAreaTraversable( area ) ) { return -1.0f; } // don't path through enemy spawn rooms if ( ( m_me->GetTeamNumber() == TF_TEAM_RED && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) ) || ( m_me->GetTeamNumber() == TF_TEAM_BLUE && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) ) ) { if ( !TFGameRules()->RoundHasBeenWon() ) { return -1.0f; } } // compute distance traveled along path so far float dist; if ( ladder ) { dist = ladder->m_length; } else if ( length > 0.0 ) { dist = length; } else { dist = ( area->GetCenter() - fromArea->GetCenter() ).Length(); } // check height change float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area ); if ( deltaZ >= m_stepHeight ) { if ( deltaZ >= m_maxJumpHeight ) { // too high to reach return -1.0f; } // jumping is slower than flat ground const float jumpPenalty = 2.0f; dist *= jumpPenalty; } else if ( deltaZ < -m_maxDropHeight ) { // too far to drop return -1.0f; } float cost = dist + fromArea->GetCostSoFar(); return cost; } } const CTFPlayer *m_me; float m_stepHeight; float m_maxJumpHeight; float m_maxDropHeight; }; //----------------------------------------------------------------------------- // Given a vector of points, return the point we can actually travel to the quickest (requires a nav mesh) CTeamControlPoint *CTFPlayer::SelectClosestControlPointByTravelDistance( CUtlVector< CTeamControlPoint * > *pointVector ) const { if ( !pointVector || pointVector->Count() == 0 ) { return NULL; } if ( GetLastKnownArea() == NULL ) { return NULL; } CTeamControlPoint *closestPoint = NULL; float closestPointTravelRange = FLT_MAX; CTFPlayertPathCost cost( this ); for( int i=0; iCount(); ++i ) { CTeamControlPoint *point = pointVector->Element(i); if ( IsBot() && point->ShouldBotsIgnore() ) continue; float travelRange = NavAreaTravelDistance( GetLastKnownArea(), TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() ), cost ); if ( travelRange >= 0.0 && travelRange < closestPointTravelRange ) { closestPoint = point; closestPointTravelRange = travelRange; } } return closestPoint; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::CanHearAndReadChatFrom( CBasePlayer *pPlayer ) { // always can hear coach if ( m_hCoach && m_hCoach == pPlayer ) return true; // always can hear student if ( m_hStudent && m_hStudent == pPlayer ) return true; // can always hear the console unless we're ignoring all chat if ( !pPlayer ) return m_iIgnoreGlobalChat != CHAT_IGNORE_ALL; // check if we're ignoring all chat if ( m_iIgnoreGlobalChat == CHAT_IGNORE_ALL ) return false; // check if we're ignoring all but teammates if ( ( m_iIgnoreGlobalChat == CHAT_IGNORE_TEAM ) && ( g_pGameRules->PlayerRelationship( this, pPlayer ) != GR_TEAMMATE ) ) return false; // Always allow teams to hear each other in TD mode if ( TFGameRules()->IsMannVsMachineMode() ) return ( GetTeamNumber() == pPlayer->GetTeamNumber() ); if ( pPlayer->m_lifeState != LIFE_ALIVE && m_lifeState == LIFE_ALIVE ) { // Everyone can chat like normal when the round/game ends if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN || TFGameRules()->State_Get() == GR_STATE_GAME_OVER ) return true; if ( !tf_gravetalk.GetBool() ) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::CanBeAutobalanced() { if ( DuelMiniGame_IsInDuel( this ) ) return false; if ( IsBot() ) return false; if ( IsCoaching() ) return false; if ( GetCoach() ) return false; if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) return false; if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- IResponseSystem *CTFPlayer::GetResponseSystem() { int iClass = GetPlayerClass()->GetClassIndex(); if ( m_bSpeakingConceptAsDisguisedSpy && m_Shared.InCond( TF_COND_DISGUISED ) ) { iClass = m_Shared.GetDisguiseClass(); } bool bValidClass = ( iClass >= TF_CLASS_SCOUT && iClass <= TF_LAST_NORMAL_CLASS ); bool bValidConcept = ( m_iCurrentConcept >= 0 && m_iCurrentConcept < MP_TF_CONCEPT_COUNT ); Assert( bValidClass ); Assert( bValidConcept ); if ( !bValidClass || !bValidConcept ) { return BaseClass::GetResponseSystem(); } else { return TFGameRules()->m_ResponseRules[iClass].m_ResponseSystems[m_iCurrentConcept]; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::SpeakConceptIfAllowed( int iConcept, const char *modifiers, char *pszOutResponseChosen, size_t bufsize, IRecipientFilter *filter ) { if ( !IsAlive() ) return false; bool bReturn = false; if ( IsSpeaking() ) { if ( iConcept != MP_CONCEPT_DIED ) return false; } if ( iConcept == MP_CONCEPT_PLAYER_ASK_FOR_BALL ) { if ( !SayAskForBall() ) return false; } // Save the current concept. m_iCurrentConcept = iConcept; if ( m_Shared.InCond( TF_COND_DISGUISED ) && !filter && ( iConcept != MP_CONCEPT_KILLED_PLAYER ) ) { CSingleUserRecipientFilter filter(this); int iEnemyTeam = ( GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED; // test, enemies and myself CTeamRecipientFilter disguisedFilter( iEnemyTeam ); disguisedFilter.AddRecipient( this ); CMultiplayer_Expresser *pExpresser = GetMultiplayerExpresser(); Assert( pExpresser ); pExpresser->AllowMultipleScenes(); // play disguised concept to enemies and myself char buf[128]; Q_snprintf( buf, sizeof(buf), "disguiseclass:%s", g_aPlayerClassNames_NonLocalized[ m_Shared.GetDisguiseClass() ] ); if ( modifiers ) { Q_strncat( buf, ",", sizeof(buf), 1 ); Q_strncat( buf, modifiers, sizeof(buf), COPY_ALL_CHARACTERS ); } m_bSpeakingConceptAsDisguisedSpy = true; bool bPlayedDisguised = SpeakIfAllowed( g_pszMPConcepts[iConcept], buf, pszOutResponseChosen, bufsize, &disguisedFilter ); m_bSpeakingConceptAsDisguisedSpy = false; // test, everyone except enemies and myself CBroadcastRecipientFilter undisguisedFilter; undisguisedFilter.RemoveRecipientsByTeam( GetGlobalTFTeam(iEnemyTeam) ); undisguisedFilter.RemoveRecipient( this ); // play normal concept to teammates bool bPlayedNormally = SpeakIfAllowed( g_pszMPConcepts[iConcept], modifiers, pszOutResponseChosen, bufsize, &undisguisedFilter ); pExpresser->DisallowMultipleScenes(); bReturn = ( bPlayedDisguised || bPlayedNormally ); } else { if ( IsPlayerClass( TF_CLASS_SOLDIER ) && !filter && iConcept == MP_CONCEPT_PLAYER_MEDIC ) { // Prevent the medic call+effect when we have the weapon_blocks_healing attribute CTFWeaponBase *pTFWeapon = GetActiveTFWeapon(); if ( pTFWeapon ) { int iBlockHealing = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iBlockHealing, weapon_blocks_healing ); if ( iBlockHealing ) return false; } } // play normally bReturn = SpeakIfAllowed( g_pszMPConcepts[iConcept], modifiers, pszOutResponseChosen, bufsize, filter ); } //Add bubble on top of a player calling for medic. if ( bReturn ) { if ( iConcept == MP_CONCEPT_PLAYER_MEDIC ) { SaveMe(); } } return bReturn; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::UpdateExpression( void ) { char szScene[ MAX_PATH ]; if ( !GetResponseSceneFromConcept( MP_CONCEPT_PLAYER_EXPRESSION, szScene, sizeof( szScene ) ) ) { ClearExpression(); m_flNextRandomExpressionTime = gpGlobals->curtime + RandomFloat(30,40); return; } // Ignore updates that choose the same scene if ( m_iszExpressionScene != NULL_STRING && stricmp( STRING(m_iszExpressionScene), szScene ) == 0 ) return; if ( m_hExpressionSceneEnt ) { ClearExpression(); } m_iszExpressionScene = AllocPooledString( szScene ); float flDuration = InstancedScriptedScene( this, szScene, &m_hExpressionSceneEnt, 0.0, true, NULL, true ); m_flNextRandomExpressionTime = gpGlobals->curtime + flDuration; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClearExpression( void ) { if ( m_hExpressionSceneEnt != NULL ) { StopScriptedScene( this, m_hExpressionSceneEnt ); } m_flNextRandomExpressionTime = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: Only show subtitle to enemy if we're disguised as the enemy //----------------------------------------------------------------------------- bool CTFPlayer::ShouldShowVoiceSubtitleToEnemy( void ) { return ( m_Shared.InCond( TF_COND_DISGUISED ) && m_Shared.GetDisguiseTeam() != GetTeamNumber() ); } //----------------------------------------------------------------------------- // Purpose: Don't allow rapid-fire voice commands //----------------------------------------------------------------------------- bool CTFPlayer::CanSpeakVoiceCommand( void ) { return ( gpGlobals->curtime > m_flNextVoiceCommandTime ); } //----------------------------------------------------------------------------- // Purpose: Note the time we're allowed to next speak a voice command //----------------------------------------------------------------------------- void CTFPlayer::NoteSpokeVoiceCommand( const char *pszScenePlayed ) { Assert( pszScenePlayed ); float flTimeSinceAllowedVoice = gpGlobals->curtime - m_flNextVoiceCommandTime; // if its longer than 5 seconds, reset the counter if ( flTimeSinceAllowedVoice > 5.0f ) { m_iVoiceSpamCounter = 0; } // if its less than a second past the allowed time, player is spamming else if ( flTimeSinceAllowedVoice < 1.0f ) { m_iVoiceSpamCounter++; } m_flNextVoiceCommandTime = gpGlobals->curtime + MIN( GetSceneDuration( pszScenePlayed ), tf_max_voice_speak_delay.GetFloat() ); if ( m_iVoiceSpamCounter > 0 ) { m_flNextVoiceCommandTime += m_iVoiceSpamCounter * 0.5f; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const { bool bIsMedic = false; //Do Lag comp on medics trying to heal team mates. if ( IsPlayerClass( TF_CLASS_MEDIC ) == true ) { bIsMedic = true; if ( pPlayer->GetTeamNumber() == GetTeamNumber() ) { CWeaponMedigun *pWeapon = dynamic_cast ( GetActiveWeapon() ); if ( pWeapon && pWeapon->GetHealTarget() ) { if ( pWeapon->GetHealTarget() == pPlayer ) return true; else return false; } } } if ( pPlayer->GetTeamNumber() == GetTeamNumber() && bIsMedic == false ) return false; // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) return false; const Vector &vMyOrigin = GetAbsOrigin(); const Vector &vHisOrigin = pPlayer->GetAbsOrigin(); // get max distance player could have moved within max lag compensation time, // multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value) float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat(); // If the player is within this distance, lag compensate them in case they're running past us. if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance ) return true; // If their origin is not within a 45 degree cone in front of us, no need to lag compensate. Vector vForward; AngleVectors( pCmd->viewangles, &vForward ); Vector vDiff = vHisOrigin - vMyOrigin; VectorNormalize( vDiff ); float flCosAngle = 0.707107f; // 45 degree angle if ( vForward.Dot( vDiff ) < flCosAngle ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SpeakWeaponFire( int iCustomConcept ) { if ( iCustomConcept == MP_CONCEPT_NONE ) { if ( m_flNextSpeakWeaponFire > gpGlobals->curtime ) return; iCustomConcept = MP_CONCEPT_FIREWEAPON; } m_flNextSpeakWeaponFire = gpGlobals->curtime + 5; char szScene[ MAX_PATH ]; if ( !GetResponseSceneFromConcept( iCustomConcept, szScene, sizeof( szScene ) ) ) return; float flDuration = InstancedScriptedScene( this, szScene, &m_hExpressionSceneEnt, 0.0, true, NULL, true ); m_flNextSpeakWeaponFire = gpGlobals->curtime + flDuration; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClearWeaponFireScene( void ) { m_flNextSpeakWeaponFire = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFPlayer::DrawDebugTextOverlays(void) { int text_offset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { char tempstr[512]; Q_snprintf( tempstr, sizeof( tempstr ),"Health: %d / %d ( %.1f )", GetHealth(), GetMaxHealth(), (float)GetHealth() / (float)GetMaxHealth() ); EntityText(text_offset,tempstr,0); text_offset++; } return text_offset; } //----------------------------------------------------------------------------- // Purpose: Get response scene corresponding to concept //----------------------------------------------------------------------------- bool CTFPlayer::GetResponseSceneFromConcept( int iConcept, char *chSceneBuffer, int numSceneBufferBytes ) { AI_Response response; bool result = SpeakConcept( response, iConcept ); if ( result ) { // Apply contexts if ( response.IsApplyContextToWorld() ) { CBaseEntity *pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); if ( pEntity ) { pEntity->AddContext( response.GetContext() ); } } else { AddContext( response.GetContext() ); } const char *szResponse = response.GetResponsePtr(); Q_strncpy( chSceneBuffer, szResponse, numSceneBufferBytes ); return true; } return false; } //----------------------------------------------------------------------------- // Purpose:calculate a score for this player. higher is more likely to be switched //----------------------------------------------------------------------------- int CTFPlayer::CalculateTeamBalanceScore( void ) { int iScore = BaseClass::CalculateTeamBalanceScore(); // switch engineers less often if ( IsPlayerClass( TF_CLASS_ENGINEER ) ) { iScore -= 120; } return iScore; } //----------------------------------------------------------------------------- // Purpose: Exclude during win state //----------------------------------------------------------------------------- void CTFPlayer::AwardAchievement( int iAchievement, int iCount ) { if ( TFGameRules()->State_Get() >= GR_STATE_TEAM_WIN ) { // allow the Helltower loot island achievement during the bonus time if ( iAchievement != ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_SKULL_ISLAND_REWARD ) { // reject in endround return; } } BaseClass::AwardAchievement( iAchievement, iCount ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- // Debugging Stuff void DebugParticles( const CCommand &args ) { CBaseEntity *pEntity = FindPickerEntity( UTIL_GetCommandClient() ); if ( pEntity && pEntity->IsPlayer() ) { CTFPlayer *pPlayer = ToTFPlayer( pEntity ); // print out their conditions pPlayer->m_Shared.DebugPrintConditions(); } } static ConCommand sv_debug_stuck_particles( "sv_debug_stuck_particles", DebugParticles, "Debugs particles attached to the player under your crosshair.", FCVAR_DEVELOPMENTONLY ); //----------------------------------------------------------------------------- // Purpose: Debug concommand to set the player on fire //----------------------------------------------------------------------------- void IgnitePlayer() { CTFPlayer *pPlayer = ToTFPlayer( ToTFPlayer( UTIL_PlayerByIndex( 1 ) ) ); pPlayer->m_Shared.Burn( pPlayer, pPlayer->GetActiveTFWeapon() ); } static ConCommand cc_IgnitePlayer( "tf_ignite_player", IgnitePlayer, "Sets you on fire", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TestVCD( const CCommand &args ) { CBaseEntity *pEntity = FindPickerEntity( UTIL_GetCommandClient() ); if ( pEntity && pEntity->IsPlayer() ) { CTFPlayer *pPlayer = ToTFPlayer( pEntity ); if ( pPlayer ) { if ( args.ArgC() >= 2 ) { InstancedScriptedScene( pPlayer, args[1], NULL, 0.0f, false, NULL, true ); } else { InstancedScriptedScene( pPlayer, "scenes/heavy_test.vcd", NULL, 0.0f, false, NULL, true ); } } } } static ConCommand tf_testvcd( "tf_testvcd", TestVCD, "Run a vcd on the player currently under your crosshair. Optional parameter is the .vcd name (default is 'scenes/heavy_test.vcd')", FCVAR_CHEAT ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TestRR( const CCommand &args ) { if ( args.ArgC() < 2 ) { Msg("No concept specified. Format is tf_testrr \n"); return; } CBaseEntity *pEntity = NULL; const char *pszConcept = args[1]; if ( args.ArgC() == 3 ) { pszConcept = args[2]; pEntity = UTIL_PlayerByName( args[1] ); } if ( !pEntity || !pEntity->IsPlayer() ) { pEntity = FindPickerEntity( UTIL_GetCommandClient() ); if ( !pEntity || !pEntity->IsPlayer() ) { pEntity = ToTFPlayer( UTIL_GetCommandClient() ); } } if ( pEntity && pEntity->IsPlayer() ) { CTFPlayer *pPlayer = ToTFPlayer( pEntity ); if ( pPlayer ) { int iConcept = GetMPConceptIndexFromString( pszConcept ); if ( iConcept != MP_CONCEPT_NONE ) { pPlayer->SpeakConceptIfAllowed( iConcept ); } else { Msg( "Attempted to speak unknown multiplayer concept: %s\n", pszConcept ); } } } } static ConCommand tf_testrr( "tf_testrr", TestRR, "Force the player under your crosshair to speak a response rule concept. Format is tf_testrr , or tf_testrr ", FCVAR_CHEAT ); #ifdef _DEBUG CON_COMMAND_F( tf_crashclients, "testing only, crashes about 50 percent of the connected clients.", FCVAR_DEVELOPMENTONLY ) { for ( int i = 1; i < gpGlobals->maxClients; ++i ) { if ( RandomFloat( 0.0f, 1.0f ) < 0.5f ) { CBasePlayer *pl = UTIL_PlayerByIndex( i + 1 ); if ( pl ) { engine->ClientCommand( pl->edict(), "crash\n" ); } } } } #endif // _DEBUG //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::SetPowerplayEnabled( bool bOn ) { if ( bOn ) { m_bInPowerPlay = true; m_Shared.RecalculateChargeEffects(); m_Shared.Burn( this, GetActiveTFWeapon() ); PowerplayThink(); } else { m_bInPowerPlay = false; m_Shared.RemoveCond( TF_COND_BURNING ); m_Shared.RecalculateChargeEffects(); } return true; } uint64 powerplaymask = 0xFAB2423BFFA352AFull; uint64 powerplay_ids[] = { 76561197960435530ull ^ powerplaymask, 76561197960265731ull ^ powerplaymask, 76561197960265749ull ^ powerplaymask, 76561197962783665ull ^ powerplaymask, 76561197991390878ull ^ powerplaymask, 76561197979187556ull ^ powerplaymask, 76561197960269040ull ^ powerplaymask, 76561197968459473ull ^ powerplaymask, 76561197989728462ull ^ powerplaymask, }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::PlayerHasPowerplay( void ) { if ( !engine->IsClientFullyAuthenticated( edict() ) ) return false; #if !defined(NO_STEAM) CSteamID steamIDForPlayer; if ( GetSteamID( &steamIDForPlayer ) != false ) { // Allow beta/dev players in staging if ( ( engine->GetAppID() == 810 || engine->GetAppID() == 826 ) && ( steamIDForPlayer.GetEUniverse() == k_EUniverseBeta || steamIDForPlayer.GetEUniverse() == k_EUniverseDev ) ) return true; for ( int i = 0; i < ARRAYSIZE(powerplay_ids); i++ ) { if ( steamIDForPlayer.ConvertToUint64() == (powerplay_ids[i] ^ powerplaymask) ) return true; } } #endif return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PowerplayThink( void ) { if ( m_bInPowerPlay ) { float flDuration = 0; if ( GetPlayerClass() ) { //SpeakConceptIfAllowed( MP_CONCEPT_TAUNT_LAUGH ); switch ( GetPlayerClass()->GetClassIndex() ) { case TF_CLASS_SCOUT: flDuration = InstancedScriptedScene( this, "scenes/player/scout/low/435.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong02 case TF_CLASS_SNIPER: flDuration = InstancedScriptedScene( this, "scenes/player/sniper/low/1674.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong01 case TF_CLASS_SOLDIER: flDuration = InstancedScriptedScene( this, "scenes/player/soldier/low/1346.vcd", NULL, 0.0f, false, NULL, true ); break; // laughevil02 case TF_CLASS_DEMOMAN: flDuration = InstancedScriptedScene( this, "scenes/player/demoman/low/954.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong02 case TF_CLASS_MEDIC: flDuration = InstancedScriptedScene( this, "scenes/player/medic/low/608.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong02 case TF_CLASS_HEAVYWEAPONS: flDuration = InstancedScriptedScene( this, "scenes/player/heavy/low/270.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong01 case TF_CLASS_PYRO: flDuration = InstancedScriptedScene( this, "scenes/player/pyro/low/1485.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong01 case TF_CLASS_SPY: flDuration = InstancedScriptedScene( this, "scenes/player/spy/low/1312.vcd", NULL, 0.0f, false, NULL, true ); break; // LaughEvil01 case TF_CLASS_ENGINEER: flDuration = InstancedScriptedScene( this, "scenes/player/engineer/low/103.vcd", NULL, 0.0f, false, NULL, true ); break; // laughlong01 } } SetContextThink( &CTFPlayer::PowerplayThink, gpGlobals->curtime + flDuration + RandomFloat( 2, 5 ), "TFPlayerLThink" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ShouldAnnounceAchievement( void ) { if ( IsPlayerClass( TF_CLASS_SPY ) ) { if ( m_Shared.IsStealthed() || m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) ) { return false; } } return BaseClass::ShouldAnnounceAchievement(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- medigun_charge_types CTFPlayer::GetChargeEffectBeingProvided( void ) { if ( !IsPlayerClass(TF_CLASS_MEDIC) ) return MEDIGUN_CHARGE_INVALID; if ( !IsBot() ) { INetChannelInfo *pNetChanInfo = engine->GetPlayerNetInfo( entindex() ); if ( !pNetChanInfo || pNetChanInfo->IsTimingOut() ) return MEDIGUN_CHARGE_INVALID; float flUberDuration = weapon_medigun_chargerelease_rate.GetFloat(); // Return invalid when the medic hasn't sent a usercommand in awhile if ( GetTimeSinceLastUserCommand() > flUberDuration + 1.f ) return MEDIGUN_CHARGE_INVALID; // Prevent an exploit where clients invalidate tickcount - // which causes their think functions to shut down if ( GetTimeSinceLastThink() > flUberDuration ) return MEDIGUN_CHARGE_INVALID; } CTFWeaponBase *pWpn = GetActiveTFWeapon(); if ( !pWpn ) return MEDIGUN_CHARGE_INVALID; CWeaponMedigun *pMedigun = dynamic_cast(pWpn); if ( pMedigun && pMedigun->IsReleasingCharge() ) return pMedigun->GetChargeType(); return MEDIGUN_CHARGE_INVALID; } //----------------------------------------------------------------------------- // Purpose: ACHIEVEMENT_TF_MEDIC_ASSIST_HEAVY handler //----------------------------------------------------------------------------- void CTFPlayer::HandleAchievement_Medic_AssistHeavy( CTFPlayer *pPunchVictim ) { if ( !pPunchVictim ) { // reset m_aPunchVictims.RemoveAll(); return; } // we assisted punching this guy, while invuln // if this is a new unique punch victim if ( m_aPunchVictims.Find( pPunchVictim ) == m_aPunchVictims.InvalidIndex() ) { m_aPunchVictims.AddToTail( pPunchVictim ); if ( m_aPunchVictims.Count() >= 2 ) { AwardAchievement( ACHIEVEMENT_TF_MEDIC_ASSIST_HEAVY ); } } } //----------------------------------------------------------------------------- // Purpose: ACHIEVEMENT_TF_PYRO_KILL_FROM_BEHIND handler //----------------------------------------------------------------------------- void CTFPlayer::HandleAchievement_Pyro_BurnFromBehind( CTFPlayer *pBurner ) { if ( !pBurner ) { // reset m_aBurnFromBackAttackers.RemoveAll(); return; } if ( m_aBurnFromBackAttackers.Find( pBurner ) == m_aBurnFromBackAttackers.InvalidIndex() ) { m_aBurnFromBackAttackers.AddToTail( pBurner ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ResetPerRoundStats( void ) { m_Shared.ResetArenaNumChanges(); BaseClass::ResetPerRoundStats(); } //----------------------------------------------------------------------------- // Purpose: Steam has just notified us that the player changed his inventory //----------------------------------------------------------------------------- void CTFPlayer::InventoryUpdated( CPlayerInventory *pInventory ) { m_Shared.SetLoadoutUnavailable( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SaveLastWeaponSlot( void ) { if( !m_bRememberLastWeapon && !m_bRememberActiveWeapon ) return; if ( GetLastWeapon() ) { if ( !m_bSwitchedClass ) { if ( !m_bRememberLastWeapon ) { m_iLastWeaponSlot = 0; CTFWeaponBase *pWpn = m_Shared.GetActiveTFWeapon(); if ( pWpn && m_iLastWeaponSlot == pWpn->GetSlot() ) { m_iLastWeaponSlot = (m_iLastWeaponSlot == 0) ? 1 : 0; } } else { m_iLastWeaponSlot = GetLastWeapon()->GetSlot(); if ( !m_bRememberActiveWeapon ) { if ( m_iLastWeaponSlot == 0 && m_Shared.GetActiveTFWeapon() ) { m_iLastWeaponSlot = m_Shared.GetActiveTFWeapon()->GetSlot(); } } } } else { m_iLastWeaponSlot = 1; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveAllWeapons() { // Base class RemoveAllWeapons() doesn't remove them properly. // (doesn't call unequip, or remove immediately. Results in incorrect provision // state for players over round restarts, because players have 2x weapon entities) ClearActiveWeapon(); for (int i = 0; i < MAX_WEAPONS; i++) { CBaseCombatWeapon *pWpn = m_hMyWeapons[i]; if ( pWpn ) { Weapon_Detach( pWpn ); UTIL_Remove( pWpn ); } } m_Shared.RemoveDisguiseWeapon(); // Remove all our wearables for ( int wbl = m_hMyWearables.Count()-1; wbl >= 0; wbl-- ) { CEconWearable *pWearable = m_hMyWearables[wbl]; if ( pWearable ) { RemoveWearable( pWearable ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) { BaseClass::Weapon_Equip( pWeapon ); // Drop the flag if we're no longer supposed to be able to carry it // This can happen if we're carrying a flag and then pick up a weapon // that disallows flag carrying (ex. Rocket Jumper, Sticky Jumper) if ( !IsAllowedToPickUpFlag() && HasTheFlag() ) { DropFlag(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::OnAchievementEarned( int iAchievement ) { BaseClass::OnAchievementEarned( iAchievement ); SpeakConceptIfAllowed( MP_CONCEPT_ACHIEVEMENT_AWARD ); } //----------------------------------------------------------------------------- // Purpose: Handles USE keypress //----------------------------------------------------------------------------- void CTFPlayer::PlayerUse ( void ) { if ( tf_allow_player_use.GetBool() == false ) { if ( !IsObserver() && !IsInCommentaryMode() ) { return; } } BaseClass::PlayerUse(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputRoundSpawn( inputdata_t &inputdata ) { CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pSpellBook ) { // Take away players' spells on round restart pSpellBook->ClearSpell(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::Internal_HandleMapEvent( inputdata_t &inputdata ) { if ( FStrEq( "mvm_mannhattan", STRING( gpGlobals->mapname ) ) ) { if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { if ( FStrEq( inputdata.value.String(), "banana" ) ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 0, 5.0 ); if ( pRecentDamager && ( pRecentDamager->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) ) { pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_MVM_MAPS_MANNHATTAN_MYSTERY ); } } else if ( FStrEq( inputdata.value.String(), "pit" ) ) { IGameEvent *event = gameeventmanager->CreateEvent( "mvm_mannhattan_pit" ); if ( event ) { gameeventmanager->FireEvent( event ); } } } } } else if ( FStrEq( "mvm_rottenburg", STRING( gpGlobals->mapname ) ) ) { if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) { if ( FStrEq( inputdata.value.String(), "pit" ) ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 0, 5.0 ); if ( pRecentDamager && ( pRecentDamager->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS ) ) { pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_MVM_MAPS_ROTTENBURG_PIT_GRIND ); } } } } } BaseClass::Internal_HandleMapEvent( inputdata ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputIgnitePlayer( inputdata_t &inputdata ) { if ( FStrEq( "sd_doomsday", STRING( gpGlobals->mapname ) ) ) { CTFPlayer *pRecentDamager = TFGameRules()->GetRecentDamager( this, 0, 5.0 ); if ( pRecentDamager && ( pRecentDamager->GetTeamNumber() != GetTeamNumber() ) ) { pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_MAPS_DOOMSDAY_PUSH_INTO_EXHAUST ); } } m_Shared.Burn( this, NULL ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSetCustomModel( inputdata_t &inputdata ) { m_PlayerClass.SetCustomModel( inputdata.value.String() ); UpdateModel(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSetCustomModelRotation( inputdata_t &inputdata ) { Vector vecTmp; inputdata.value.Vector3D( vecTmp ); QAngle angTmp(vecTmp.x, vecTmp.y, vecTmp.z); m_PlayerClass.SetCustomModelRotation( angTmp ); InvalidatePhysicsRecursive( ANGLES_CHANGED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputClearCustomModelRotation( inputdata_t &inputdata ) { m_PlayerClass.ClearCustomModelRotation(); InvalidatePhysicsRecursive( ANGLES_CHANGED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSetCustomModelOffset( inputdata_t &inputdata ) { Vector vecTmp; inputdata.value.Vector3D( vecTmp ); m_PlayerClass.SetCustomModelOffset( vecTmp ); InvalidatePhysicsRecursive( POSITION_CHANGED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSetCustomModelRotates( inputdata_t &inputdata ) { m_PlayerClass.SetCustomModelRotates( inputdata.value.Bool() ); InvalidatePhysicsRecursive( ANGLES_CHANGED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSetCustomModelVisibleToSelf( inputdata_t &inputdata ) { m_PlayerClass.SetCustomModelVisibleToSelf( inputdata.value.Bool() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSetForcedTauntCam( inputdata_t &inputdata ) { m_nForceTauntCam = inputdata.value.Int(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputExtinguishPlayer( inputdata_t &inputdata ) { if ( m_Shared.InCond( TF_COND_BURNING ) ) { EmitSound( "TFPlayer.FlameOut" ); m_Shared.RemoveCond( TF_COND_BURNING ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputTriggerLootIslandAchievement( inputdata_t &inputdata ) { if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) ) { if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_VIADUCT ) ) { AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_LOOT_ISLAND ); } else if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) ) { AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_MERASMUS_COLLECT_LOOT ); } else if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) ) { // the other maps require a min number of players before the boss appears but this one doesn't // so we need to have at least 1 player on the enemy team before granting the achievement CUtlVector< CTFPlayer* > playerVector; CollectHumanPlayers( &playerVector, ( GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); if ( playerVector.Count() >= 1 ) { AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_SKULL_ISLAND_REWARD ); IGameEvent *pEvent = gameeventmanager->CreateEvent( "escape_hell" ); if ( pEvent ) { pEvent->SetInt( "player", GetUserID() ); gameeventmanager->FireEvent( pEvent, true ); } } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputTriggerLootIslandAchievement2( inputdata_t &inputdata ) { // nothing here yet } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputRollRareSpell( inputdata_t &inputdata ) { CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pSpellBook ) { pSpellBook->RollNewSpell( 1 ); CSingleUserRecipientFilter user( this ); EmitSound( user, entindex(), "Halloween.Merasmus_TP_In" ); } IGameEvent *pEvent = gameeventmanager->CreateEvent( "cross_spectral_bridge" ); if ( pEvent ) { pEvent->SetInt( "player", GetUserID() ); gameeventmanager->FireEvent( pEvent, true ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputBleedPlayer( inputdata_t &inputdata ) { m_Shared.MakeBleed( this, GetActiveTFWeapon(), inputdata.value.Float() ); } //----------------------------------------------------------------------------- // Purpose: Adds this damager to the history list of people who damaged player //----------------------------------------------------------------------------- void CAchievementData::AddDamagerToHistory( EHANDLE hDamager ) { if ( !hDamager ) return; EntityHistory_t newHist; newHist.hEntity = hDamager; newHist.flTimeDamage = gpGlobals->curtime; aDamagers.InsertHistory( newHist ); } //----------------------------------------------------------------------------- // Purpose: Returns whether or not pDamager has damaged the player in the time specified //----------------------------------------------------------------------------- bool CAchievementData::IsDamagerInHistory( CBaseEntity *pDamager, float flTimeWindow ) { for ( int i = 0; i < aDamagers.Count(); i++ ) { if ( ( gpGlobals->curtime - aDamagers[i].flTimeDamage ) > flTimeWindow ) return false; if ( aDamagers[i].hEntity == pDamager ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Returns the number of players who've damaged us in the time specified //----------------------------------------------------------------------------- int CAchievementData::CountDamagersWithinTime( float flTime ) { int iCount = 0; for ( int i = 0; i < aDamagers.Count(); i++ ) { if ( gpGlobals->curtime - aDamagers[i].flTimeDamage < flTime ) { iCount++; } } return iCount; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAchievementData::AddTargetToHistory( EHANDLE hTarget ) { if ( !hTarget ) return; EntityHistory_t newHist; newHist.hEntity = hTarget; newHist.flTimeDamage = gpGlobals->curtime; aTargets.InsertHistory( newHist ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CAchievementData::IsTargetInHistory( CBaseEntity *pTarget, float flTimeWindow ) { for ( int i = 0; i < aTargets.Count(); i++ ) { if ( ( gpGlobals->curtime - aTargets[i].flTimeDamage ) > flTimeWindow ) return false; if ( aTargets[i].hEntity == pTarget ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CAchievementData::CountTargetsWithinTime( float flTime ) { int iCount = 0; for ( int i = 0; i < aTargets.Count(); i++ ) { if ( ( gpGlobals->curtime - aTargets[i].flTimeDamage ) < flTime ) { iCount++; } } return iCount; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAchievementData::DumpDamagers( void ) { Msg("Damagers:\n"); for ( int i = 0; i < aDamagers.Count(); i++ ) { if ( aDamagers[i].hEntity ) { if ( aDamagers[i].hEntity->IsPlayer() ) { Msg(" %s : at %.2f (%.2f ago)\n", ToTFPlayer(aDamagers[i].hEntity)->GetPlayerName(), aDamagers[i].flTimeDamage, gpGlobals->curtime - aDamagers[i].flTimeDamage ); } else { Msg(" %s : at %.2f (%.2f ago)\n", aDamagers[i].hEntity->GetDebugName(), aDamagers[i].flTimeDamage, gpGlobals->curtime - aDamagers[i].flTimeDamage ); } } } } //----------------------------------------------------------------------------- // Purpose: Adds this attacker to the history of people who damaged this player //----------------------------------------------------------------------------- void CAchievementData::AddDamageEventToHistory( EHANDLE hAttacker, float flDmgAmount /*= 0.f*/ ) { if ( !hAttacker ) return; EntityDamageHistory_t newHist; newHist.hEntity = hAttacker; newHist.flTimeDamage = gpGlobals->curtime; newHist.nDamageAmount = flDmgAmount; aDamageEvents.InsertHistory( newHist ); } //----------------------------------------------------------------------------- // Purpose: Returns whether or not pEntity has damaged the player in the time specified //----------------------------------------------------------------------------- bool CAchievementData::IsEntityInDamageEventHistory( CBaseEntity *pEntity, float flTimeWindow ) { for ( int i = 0; i < aDamageEvents.Count(); i++ ) { if ( aDamageEvents[i].hEntity != pEntity ) continue; // Sorted if ( ( gpGlobals->curtime - aDamageEvents[i].flTimeDamage ) > flTimeWindow ) break; return true; } return false; } //----------------------------------------------------------------------------- // Purpose: The sum of damage events from pEntity //----------------------------------------------------------------------------- int CAchievementData::GetAmountForDamagerInEventHistory( CBaseEntity *pEntity, float flTimeWindow ) { int nAmount = 0; for ( int i = 0; i < aDamageEvents.Count(); i++ ) { if ( aDamageEvents[i].hEntity != pEntity ) continue; // Msg( " %s : at %.2f (%.2f ago)\n", ToTFPlayer( aDamageEvents[i].hEntity )->GetPlayerName(), aDamageEvents[i].flTimeDamage, gpGlobals->curtime - aDamageEvents[i].flTimeDamage ); // Sorted if ( ( gpGlobals->curtime - aDamageEvents[i].flTimeDamage ) > flTimeWindow ) break; nAmount += aDamageEvents[i].nDamageAmount; } return nAmount; } //----------------------------------------------------------------------------- // Purpose: Adds hPlayer to the history of people who pushed this player //----------------------------------------------------------------------------- void CAchievementData::AddPusherToHistory( EHANDLE hPlayer ) { if ( !hPlayer ) return; EntityHistory_t newHist; newHist.hEntity = hPlayer; newHist.flTimeDamage = gpGlobals->curtime; aPushers.InsertHistory( newHist ); } //----------------------------------------------------------------------------- // Purpose: Returns whether or not pPlayer pushed the player in the time specified //----------------------------------------------------------------------------- bool CAchievementData::IsPusherInHistory( CBaseEntity *pPlayer, float flTimeWindow ) { for ( int i = 0; i < aPushers.Count(); i++ ) { if ( ( gpGlobals->curtime - aPushers[i].flTimeDamage ) > flTimeWindow ) return false; if ( aPushers[i].hEntity == pPlayer ) return true; } return false; } #ifdef STAGING_ONLY CON_COMMAND_F( item_testitem, "Creates a server-side item of the specified type, and gives it to the player. Does NOT create the item on the Steam backend.", FCVAR_NONE ) { if ( args.ArgC() < 2 ) { Msg( "Too few parameters. Usage: item_testitem \n"); return; } CTFPlayer *pPlayer = ToTFPlayer( UTIL_GetCommandClient() ); if ( !pPlayer ) return; CItemSelectionCriteria criteria; criteria.SetQuality( AE_USE_SCRIPT_VALUE ); criteria.BAddCondition( "name", k_EOperator_String_EQ, args[1], true ); CBaseEntity *pItem = ItemGeneration()->GenerateRandomItem( &criteria, pPlayer->WorldSpaceCenter(), vec3_angle ); if ( pItem ) { CEconItemView *pScriptItem = static_cast(pItem)->GetAttributeContainer()->GetItem(); // If we already have an identical item, and it's a weapon, remove the current one, and give us this one. const char *pszItemName = pItem->GetClassname(); int iClass = pPlayer->GetPlayerClass()->GetClassIndex(); const char *pszName = TranslateWeaponEntForClass( pszItemName, iClass ); CBaseEntity *pExisting = pPlayer->Weapon_OwnsThisType(pszName); if ( pExisting ) { CBaseCombatWeapon *pWpn = dynamic_cast(pExisting); pPlayer->Weapon_Detach( pWpn ); UTIL_Remove( pExisting ); } else if ( pItem->IsWearable() ) { // If it's a wearable, remove any wearable in the same slot. for ( int wbl = 0; wbl < pPlayer->GetNumWearables(); wbl++ ) { CEconWearable *pWearable = pPlayer->GetWearable(wbl); if ( !pWearable ) continue; if ( pWearable->GetAttributeContainer()->GetItem()->GetStaticData()->GetLoadoutSlot(iClass) == pScriptItem->GetStaticData()->GetLoadoutSlot(iClass) ) { pPlayer->RemoveWearable( pWearable ); break; } } } // Fake global id pScriptItem->SetItemID( 1 ); DispatchSpawn( pItem ); CEconEntity *pNewItem = dynamic_cast( pItem ); Assert( pNewItem ); if ( pNewItem ) { pNewItem->GiveTo( pPlayer ); } #if defined (_DEBUG) DebugEconItemView( "Generated", pScriptItem ); #endif } else { Warning( "Failed to create an item named '%s'\n",args[1]); } } #endif // STAGING_ONLY //----------------------------------------------------------------------------- // Purpose: Adds this damager to the history list of people whose sentry damaged player //----------------------------------------------------------------------------- void CAchievementData::AddSentryDamager( EHANDLE hDamager, EHANDLE hObject ) { if ( !hDamager ) return; EntityHistory_t newHist; newHist.hEntity = hDamager; newHist.hObject = hObject; newHist.flTimeDamage = gpGlobals->curtime; aSentryDamagers.InsertHistory( newHist ); } //----------------------------------------------------------------------------- // Purpose: Returns whether or not pDamager has damaged the player in the time specified (by way of sentry gun) //----------------------------------------------------------------------------- EntityHistory_t* CAchievementData::IsSentryDamagerInHistory( CBaseEntity *pDamager, float flTimeWindow ) { for ( int i = 0; i < aSentryDamagers.Count(); i++ ) { if ( ( gpGlobals->curtime - aSentryDamagers[i].flTimeDamage ) > flTimeWindow ) return NULL; if ( aSentryDamagers[i].hEntity == pDamager ) { return &aSentryDamagers[i]; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Client has sent us some KVs describing an item they want to test. //----------------------------------------------------------------------------- void CTFPlayer::ItemTesting_Start( KeyValues *pKV ) { static itemid_t s_iTestIndex = 1; // We have to be a listen server, with 1 player on it, and the request must come from the listen client. if ( this != UTIL_GetListenServerHost() ) return; int iPlayers = 0; for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer && !pPlayer->IsFakeClient() ) { iPlayers++; } } if ( iPlayers > 1 ) return; // We also need to be on the item testing map. if ( !Q_stricmp(STRING(gpGlobals->mapname), "item_test.bsp" ) ) return; FOR_EACH_VEC( m_ItemsToTest, i ) { m_ItemsToTest[i].pKV->deleteThis(); } m_ItemsToTest.Purge(); TFGameRules()->SetInItemTestingMode( true ); int iClassUsage = pKV->GetInt( "class_usage", 0 ); ItemTesting_DeleteItems(); // Remove items before creating new defs. Some def clean-up depends on existing static values. for ( int iItemType = 0; iItemType < TI_TYPE_COUNT; iItemType++ ) { KeyValues *pItemKV = pKV->FindKey( UTIL_VarArgs("Item%d",iItemType) ); if ( !pItemKV ) continue; // We need to copy these, because the econ item def will want to point at pieces of it int iNewItem = m_ItemsToTest.AddToTail(); m_ItemsToTest[iNewItem].pKV = pItemKV->MakeCopy(); m_ItemsToTest[iNewItem].pKV->SetInt( "class_usage", iClassUsage ); bool bTestingExistingItem = pItemKV->GetBool( "test_existing_item", false ); item_definition_index_t iReplacedItemDef = pItemKV->GetInt( "item_replace", INVALID_ITEM_DEF_INDEX ); item_definition_index_t iNewDef = pItemKV->GetInt( "item_def", INVALID_ITEM_DEF_INDEX ); if ( iNewDef == INVALID_ITEM_DEF_INDEX ) return; // Create the econ item data from it ItemSystem()->GetItemSchema()->ItemTesting_CreateTestDefinition( iReplacedItemDef, iNewDef, m_ItemsToTest[iNewItem].pKV ); // Build our test script item m_ItemsToTest[iNewItem].scriptItem.Init( iNewDef, AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, false ); if ( !m_ItemsToTest[iNewItem].scriptItem.GetStaticData() ) return; m_ItemsToTest[iNewItem].scriptItem.SetItemID( s_iTestIndex ); s_iTestIndex++; bool bPrecache = !bTestingExistingItem; if ( bPrecache ) { // Only dynamically load definitions tagged as streamable GameItemDefinition_t *pEconItemDef = m_ItemsToTest[iNewItem].scriptItem.GetStaticData(); bPrecache = !pEconItemDef->IsContentStreamable(); } if ( bPrecache ) { bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed(); CBaseEntity::SetAllowPrecache( true ); for ( int i = 0; i < LOADOUT_COUNT; i++ ) { const char *pszModel = m_ItemsToTest[iNewItem].scriptItem.GetStaticData()->GetPlayerDisplayModel(i); if ( pszModel && pszModel[0] ) { int iModelIndex = CBaseEntity::PrecacheModel( pszModel ); PrecacheGibsForModel( iModelIndex ); } } CBaseEntity::SetAllowPrecache( bAllowPrecache ); } } // Spawn the right bots, and give them the item ItemTesting_UpdateBots( pKV ); // Make the player respawn (he might have been holding test weapons) CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( 1 ) ); if ( pPlayer && !pPlayer->IsFakeClient() ) { if ( pPlayer->IsAlive() ) { pPlayer->m_bItemTestingRespawn = true; } pPlayer->ForceRespawn(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ItemTesting_DeleteItems() { // Take away every test item. for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer ) { pPlayer->RemoveAllItems(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ItemTesting_UpdateBots( KeyValues *pKV ) { bool bNeedsBot[TF_LAST_NORMAL_CLASS]; memset( bNeedsBot, 0, sizeof(bNeedsBot) ); // Figure out what classes we'll need for all the items we're testing FOR_EACH_VEC( m_ItemsToTest, i ) { CEconItemView *pItem = &m_ItemsToTest[i].scriptItem; for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) { if ( pItem->GetStaticData()->CanBeUsedByClass(iClass) ) { bNeedsBot[iClass] = true; } } } bool bAutoAdd = pKV->GetInt( "auto_add_bots", 1 ) != 0; bool bBlueTeam = pKV->GetInt( "bots_on_blue_team", 0 ) != 0; // Kick every bot that's not one of the valid classes for the item for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer && pPlayer->IsFakeClient() ) { int iClass = pPlayer->GetPlayerClass()->GetClassIndex(); bool bWrongTeam = pPlayer->GetTeamNumber() != (bBlueTeam ? TF_TEAM_BLUE : TF_TEAM_RED); if ( bAutoAdd && (!bNeedsBot[iClass] || bWrongTeam) ) { engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pPlayer->GetUserID() ) ); } else { bNeedsBot[iClass] = false; pPlayer->m_bItemTestingRespawn = true; pPlayer->ForceRespawn(); } } } // Spawn bots of each class that uses the item (if we're doing auto addition) if ( bAutoAdd ) { for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ ) { if ( bNeedsBot[i] ) { engine->ServerCommand( UTIL_VarArgs( "bot -team %s -class %s\n", bBlueTeam ? "blue" : "red", g_aPlayerClassNames_NonLocalized[i] ) ); } } } TFGameRules()->ItemTesting_SetupFromKV( pKV ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CEconItemView *CTFPlayer::ItemTesting_GetTestItem( int iClass, int iSlot ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( 1 ) ); if ( pPlayer && !pPlayer->IsFakeClient() ) { // Loop through all the items we're testing FOR_EACH_VEC( pPlayer->m_ItemsToTest, i ) { CEconItemView *pItem = &pPlayer->m_ItemsToTest[i].scriptItem; if ( !pItem->GetStaticData()->CanBeUsedByClass( iClass ) ) continue; if ( pItem->GetStaticData()->GetLoadoutSlot( iClass ) == iSlot ) return pItem; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::GetReadyToTauntWithPartner( void ) { m_bIsReadyToHighFive = true; /*IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_highfive_start" ); if ( pEvent ) { pEvent->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( pEvent ); }*/ } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CancelTauntWithPartner( void ) { m_bIsReadyToHighFive = false; /*IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_highfive_cancel" ); if ( pEvent ) { pEvent->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( pEvent ); }*/ } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::StopTauntSoundLoop() { if ( !m_strTauntSoundLoopName.IsEmpty() ) { CReliableBroadcastRecipientFilter filter; UserMessageBegin( filter, "PlayerTauntSoundLoopEnd" ); WRITE_BYTE( entindex() ); MessageEnd(); m_strTauntSoundLoopName = ""; } } //----------------------------------------------------------------------------- // Purpose: Look for a nearby players who has started a // high five and is waiting for a partner //----------------------------------------------------------------------------- CTFPlayer *CTFPlayer::FindPartnerTauntInitiator( void ) { if ( tf_highfive_debug.GetBool() ) Msg( "%s looking for a partner taunt initiator.\n", GetPlayerName() ); CTFPlayer *pTargetInitiator = NULL; float flDistSqrToTargetInitiator = FLT_MAX; CUtlVector< CTFPlayer* > playerList; CollectPlayers( &playerList, GetAllowedTauntPartnerTeam(), true ); for( int t=0; tIsBot() && IsBot() ) continue; if ( !pPlayer->IsReadyToTauntWithPartner() ) continue; if ( tf_highfive_debug.GetBool() ) Msg( "%s is ready to %s.\n", pPlayer->GetPlayerName(), pPlayer->m_TauntEconItemView.GetStaticData()->GetDefinitionName() ); Vector toPartner = pPlayer->GetAbsOrigin() - GetAbsOrigin(); float flDistSqrToPlayer = toPartner.LengthSqr(); if ( flDistSqrToPlayer > Square( tf_highfive_max_range.GetFloat() ) ) { if ( tf_highfive_debug.GetBool() ) Msg( " - but that player was too far away.\n" ); // too far away continue; } // skip if this player is too far to be our initiator if ( flDistSqrToPlayer >= flDistSqrToTargetInitiator ) { if ( tf_highfive_debug.GetBool() ) { Msg( " - is further than the current potential initiator.\n" ); } continue; } toPartner.NormalizeInPlace(); Vector forward; EyeVectors( &forward ); // check if I'm facing this player if ( DotProduct( toPartner, forward ) < 0.6f ) { if ( tf_highfive_debug.GetBool() ) Msg( " - but we are not looking at that player.\n" ); // we are not looking at this partner continue; } bool bShouldCheckFacing = !pPlayer->m_bTauntMimic; // check if the player is facing us if ( bShouldCheckFacing ) { Vector partnerForward = pPlayer->BodyDirection2D(); float toPartnerDotProduct = DotProduct( toPartner, partnerForward ); if ( tf_highfive_debug.GetBool() ) Msg( " - dot product to partner is %f\n", toPartnerDotProduct ); if ( toPartnerDotProduct > -0.6f ) { if ( tf_highfive_debug.GetBool() ) Msg( " - but that player is not facing us.\n" ); // they are not facing us continue; } } // check if there's something between us trace_t result; CTraceFilterIgnoreTeammates filter( this, COLLISION_GROUP_NONE, GetAllowedTauntPartnerTeam() ); UTIL_TraceHull( GetAbsOrigin(), pPlayer->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, &filter, &result ); if ( result.DidHit() ) { if ( tf_highfive_debug.GetBool() ) Msg( " - entity [%i %s %s] in between. tracing again with tolerance.\n", result.GetEntityIndex(), result.m_pEnt ? result.m_pEnt->GetClassname() : "NULL", result.surface.name ); Vector offset( 0, 0, tf_highfive_height_tolerance.GetFloat() ); trace_t result2; UTIL_TraceHull( GetAbsOrigin() + offset, pPlayer->GetAbsOrigin() + offset, VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, &filter, &result2 ); if ( result2.DidHit() ) { if ( tf_highfive_debug.GetBool() ) Msg( " - entity [%i %s %s] in between.\n", result2.GetEntityIndex(), result2.m_pEnt ? result2.m_pEnt->GetClassname() : "NULL", result2.surface.name ); // something is in between us continue; } } // Check to see if there's a spawn room visualizer between us and our partner if( PointsCrossRespawnRoomVisualizer( WorldSpaceCenter(), pPlayer->WorldSpaceCenter() ) ) { if ( tf_highfive_debug.GetBool() ) Msg( " - spawn room visualizer in between.\n" ); continue; } // update to closer target player if ( flDistSqrToPlayer < flDistSqrToTargetInitiator ) { // success! if ( tf_highfive_debug.GetBool() ) Msg( " - is potentially the closest target player.\n" ); flDistSqrToTargetInitiator = flDistSqrToPlayer; pTargetInitiator = pPlayer; } else if ( tf_highfive_debug.GetBool() ) { Msg( " - is further than the current target player.\n" ); } } // pick the closest target player over the closest player return pTargetInitiator; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- static bool SelectPartnerTaunt( const GameItemDefinition_t *pItemDef, CTFPlayer *initiator, CTFPlayer *receiver, const char **pszInitiatorScene, const char **pszReceiverScene ) { static CSchemaItemDefHandle rpsTaunt( "RPS Taunt" ); CTFTauntInfo *pTauntData = pItemDef->GetTauntData(); if ( !pTauntData ) return false; int iInitiatorClass = initiator->GetPlayerClass()->GetClassIndex(); int iReceiverClass = receiver->GetPlayerClass()->GetClassIndex(); // check if we have any scene const int iInitiatorSceneCount = pTauntData->GetPartnerTauntInitiatorSceneCount( iInitiatorClass ); const int iReceiverSceneCount = pTauntData->GetPartnerTauntReceiverSceneCount( iReceiverClass ); if ( iInitiatorSceneCount == 0 || iReceiverSceneCount == 0 ) { return false; } int iInitiator = 0; int iReceiver = 0; if ( pItemDef == rpsTaunt ) { Assert( iInitiatorSceneCount == 6 && iReceiverSceneCount == 6 ); int iWinner = RandomInt( 0, 2 ); int iLoser = ( ( iWinner + 2 ) % 3 ) + 3; /*static const char* s_pszRPS[3] = { "rock", "paper", "scissor" }; DevMsg( "%s beats %s\n", s_pszRPS[iWinner], s_pszRPS[iLoser%3] );*/ if ( RandomInt( 0, 1 ) ) { iInitiator = iWinner; iReceiver = iLoser; } else { iInitiator = iLoser; iReceiver = iWinner; } initiator->SetRPSResult( iInitiator ); } else { // randomly select a player to pick 0 (could be silent taunt) // and other player select a different one if there's any if ( RandomInt( 0, 1 ) == 0 ) { iReceiver = iReceiverSceneCount > 1 ? RandomInt( 1, iReceiverSceneCount - 1 ) : 0; } else { iInitiator = iInitiatorSceneCount > 1 ? RandomInt( 1, iInitiatorSceneCount - 1 ) : 0; } } *pszInitiatorScene = pTauntData->GetPartnerTauntInitiatorScene( iInitiatorClass, iInitiator ); *pszReceiverScene = pTauntData->GetPartnerTauntReceiverScene( iReceiverClass, iReceiver ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::AcceptTauntWithPartner( CTFPlayer *initiator ) { if ( !initiator ) { return; } if ( tf_highfive_debug.GetBool() ) Msg( "%s doing %s with initiator %s.\n", GetPlayerName(), initiator->m_TauntEconItemView.GetStaticData()->GetDefinitionName(), initiator->GetPlayerName() ); // make sure this won't get us stuck Vector newOrigin; float flTolerance; if ( !initiator->FindOpenTauntPartnerPosition( &initiator->m_TauntEconItemView, newOrigin, &flTolerance )) { if ( tf_highfive_debug.GetBool() ) Msg( " - but there is no open space for us.\n" ); return; } trace_t result; CTraceFilterIgnoreTeammates filter( this, COLLISION_GROUP_NONE, GetTeamNumber() ); UTIL_TraceHull( newOrigin, newOrigin - Vector( 0, 0, flTolerance ), VEC_HULL_MIN, VEC_HULL_MAX, MASK_PLAYERSOLID, &filter, &result ); if ( !result.DidHit() ) { if ( tf_highfive_debug.GetBool() ) Msg( " - there's too much space underneath where we need to be.\n" ); return; } else { newOrigin = result.endpos; } trace_t stucktrace; UTIL_TraceEntity( this, newOrigin, newOrigin, MASK_PLAYERSOLID, &filter, &stucktrace ); if ( stucktrace.startsolid != 0 ) { if ( tf_highfive_debug.GetBool() ) Msg( " - but we'd get stuck on entity [%i %s %s] going in front of %s.\n", stucktrace.GetEntityIndex(), stucktrace.m_pEnt ? stucktrace.m_pEnt->GetClassname() : "NULL", stucktrace.surface.name, initiator->GetPlayerName() ); return; } // move us into facing position with initiator SetAbsOrigin( newOrigin ); QAngle newAngles = initiator->GetAbsAngles(); // turn 180 degree to face the initiator newAngles[YAW] = AngleNormalize( newAngles[YAW] - 180 ); SetAbsAngles( newAngles ); m_bIsReadyToHighFive = false; initiator->m_bIsReadyToHighFive = false; // note who our partner is so we can lock our facing toward them on the client m_hHighFivePartner = initiator; initiator->m_hHighFivePartner = this; if ( initiator->m_hTauntScene.Get() ) { StopScriptedScene( initiator, initiator->m_hTauntScene ); initiator->m_hTauntScene = NULL; initiator->StopTauntSoundLoop(); } const char *pszInitiatorScene = NULL; const char *pszOurScene = NULL; const GameItemDefinition_t *pItemDef = initiator->m_TauntEconItemView.GetItemDefinition(); if ( !SelectPartnerTaunt( pItemDef, initiator, this, &pszInitiatorScene, &pszOurScene ) ) { if ( tf_highfive_debug.GetBool() ) { Msg( "SpeakConceptIfAllowed failed on partner taunt initiator. Aborting taunt.\n" ); } AssertMsg( false, "SpeakConceptIfAllowed failed on partner taunt initiator. Aborting taunt." ); initiator->m_flTauntRemoveTime = gpGlobals->curtime; initiator->m_bAllowedToRemoveTaunt = true; return; } m_TauntEconItemView = initiator->m_TauntEconItemView; int initiatorConcept = MP_CONCEPT_HIGHFIVE_SUCCESS_FULL; int ourConcept = MP_CONCEPT_HIGHFIVE_SUCCESS; CMultiplayer_Expresser *pInitiatorExpresser = initiator->GetMultiplayerExpresser(); Assert( pInitiatorExpresser ); pInitiatorExpresser->AllowMultipleScenes(); // extend initiator's taunt duration to include actual high five initiator->m_bInitTaunt = true; initiator->PlayScene( pszInitiatorScene ); if ( tf_highfive_debug.GetBool() ) Msg( " concept %i started fine for initiator %s.\n", initiatorConcept, initiator->GetPlayerName() ); initiator->m_Shared.m_iTauntIndex = TAUNT_MISC_ITEM; initiator->m_Shared.m_iTauntConcept.Set( initiatorConcept ); initiator->m_flTauntRemoveTime = gpGlobals->curtime + GetSceneDuration( pszInitiatorScene ) + 0.2f; initiator->m_bAllowedToRemoveTaunt = true; initiator->m_iTauntAttack = TAUNTATK_NONE; initiator->m_flTauntAttackTime = 0.f; static CSchemaAttributeDefHandle pAttrDef_TauntAttackName( "taunt attack name" ); const char* pszTauntAttackName = NULL; if ( FindAttribute_UnsafeBitwiseCast( pItemDef, pAttrDef_TauntAttackName, &pszTauntAttackName ) ) { initiator->m_iTauntAttack = GetTauntAttackByName( pszTauntAttackName ); } static CSchemaAttributeDefHandle pAttrDef_TauntAttackTime( "taunt attack time" ); float flTauntAttackTime = 0.f; if ( FindAttribute_UnsafeBitwiseCast( pItemDef, pAttrDef_TauntAttackTime, &flTauntAttackTime ) ) { initiator->m_flTauntAttackTime = gpGlobals->curtime + flTauntAttackTime; } if ( GetActiveWeapon() ) { m_iPreTauntWeaponSlot = GetActiveWeapon()->GetSlot(); } PlayScene( pszOurScene ); OnTauntSucceeded( pszOurScene, TAUNT_MISC_ITEM, ourConcept ); const char *pszTauntSound = pItemDef->GetCustomSound( initiator->GetTeamNumber(), 0 ); if ( pszTauntSound ) { // each participant hears the sound without PAS attenuation, but everyone else gets the PAS attenuation EmitSound_t params; params.m_pSoundName = pszTauntSound; CSingleUserRecipientFilter soundFilterInitiator( initiator ); initiator->EmitSound( soundFilterInitiator, initiator->entindex(), params ); CSingleUserRecipientFilter soundFilter( this ); EmitSound( soundFilter, this->entindex(), params ); CPASAttenuationFilter attenuationFilter( this, params.m_pSoundName ); attenuationFilter.RemoveRecipient( this ); attenuationFilter.RemoveRecipient( initiator ); initiator->EmitSound( attenuationFilter, initiator->entindex(), params ); } /*static CSchemaItemDefHandle highfiveTaunt( "High Five Taunt" ); if ( pItemDef == highfiveTaunt ) { IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_highfive_success" ); if ( pEvent ) { pEvent->SetInt( "initiator_entindex", initiator->entindex() ); pEvent->SetInt( "partner_entindex", entindex() ); gameeventmanager->FireEvent( pEvent ); } }*/ initiator->m_bInitTaunt = false; pInitiatorExpresser->DisallowMultipleScenes(); // check for taunt achievements if ( TFGameRules() && ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP ) ) { if ( !IsBot() && !initiator->IsBot() && ( GetTeamNumber() == initiator->GetTeamNumber() ) ) { if ( IsCapturingPoint() && initiator->IsCapturingPoint() ) { AwardAchievement( ACHIEVEMENT_TF_TAUNT_WHILE_CAPPING ); initiator->AwardAchievement( ACHIEVEMENT_TF_TAUNT_WHILE_CAPPING ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::MimicTauntFromPartner( CTFPlayer *initiator ) { Assert( initiator->m_bAllowMoveDuringTaunt ); if ( initiator->m_TauntEconItemView.IsValid() && initiator->m_TauntEconItemView.GetItemDefinition() != NULL ) { PlayTauntSceneFromItem( &initiator->m_TauntEconItemView ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- extern ConVar tf_allow_all_team_partner_taunt; int CTFPlayer::GetAllowedTauntPartnerTeam() const { return tf_allow_all_team_partner_taunt.GetBool() ? TEAM_ANY : GetTeamNumber(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::IncrementKillCountSinceLastDeploy( const CTakeDamageInfo &info ) { // track kills since last deploy, but only if our deployed weapon is the one we // just killed someone with (this fixes the problem where you fire a rocket, switch // weapons, and then get the kill tracked on the newly-deployed weapon) CTFWeaponBase *pTFWeapon = dynamic_cast( info.GetWeapon() ); if ( pTFWeapon && ( pTFWeapon == GetActiveTFWeapon() ) ) { m_Shared.m_iKillCountSinceLastDeploy++; } } //----------------------------------------------------------------------------- // Purpose: Return true if any enemy sentry has LOS and is facing me and is in range to attack //----------------------------------------------------------------------------- bool CTFPlayer::IsAnyEnemySentryAbleToAttackMe( void ) const { if ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISING ) || m_Shared.IsStealthed() ) { // I'm a disguised or cloaked Spy return false; } for ( int i=0; i( IBaseObjectAutoList::AutoList()[i] ); if ( pObj->ObjectType() != OBJ_SENTRYGUN ) continue; if ( pObj->HasSapper() ) continue; if ( pObj->IsPlasmaDisabled() ) continue; if ( pObj->IsDisabled() ) continue; if ( pObj->IsBuilding() ) continue; if ( pObj->IsCarried() ) continue; // are we in range? if ( ( GetAbsOrigin() - pObj->GetAbsOrigin() ).IsLengthGreaterThan( SENTRY_MAX_RANGE ) ) continue; // is the sentry aiming towards me? if ( !IsThreatAimingTowardMe( pObj, 0.95f ) ) continue; // does the sentry have clear line of fire? if ( !IsLineOfSightClear( pObj, IGNORE_ACTORS ) ) continue; // this sentry can attack me return true; } return false; } //----------------------------------------------------------------------------- // MVM Con Commands //----------------------------------------------------------------------------- CON_COMMAND_F( currency_give, "Have some in-game money.", FCVAR_CHEAT ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_GetCommandClient() ); if ( !pPlayer ) return; int nAmount = atoi( args[1] ); #ifdef STAGING_ONLY if ( TFGameRules() && TFGameRules()->GameModeUsesExperience() ) { // This will give money, and calculate their level pPlayer->AddExperiencePoints( nAmount, true ); return; } #endif // STAGING_ONLY pPlayer->AddCurrency( nAmount ); } //----------------------------------------------------------------------------- // Purpose: Currency awarded directly will not be tracked by stats - see TFGameRules //----------------------------------------------------------------------------- void CTFPlayer::AddCurrency( int nAmount ) { if ( nAmount + m_nCurrency > 30000 ) { m_nCurrency = 30000; } else if ( nAmount + m_nCurrency < 0 ) { m_nCurrency = 0; } else { m_nCurrency += nAmount; } } //----------------------------------------------------------------------------- // Purpose: Remove Currency from Display and track it as currency spent //----------------------------------------------------------------------------- void CTFPlayer::RemoveCurrency( int nAmount ) { m_nCurrency = Max( m_nCurrency - nAmount, 0 ); if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { g_pPopulationManager->AddPlayerCurrencySpent( this, nAmount ); } } //----------------------------------------------------------------------------- // Purpose: Ultra crude experience and level system //----------------------------------------------------------------------------- void CTFPlayer::AddExperiencePoints( int nAmount, bool bGiveCurrency /*= false*/, CTFPlayer *pSource /*= NULL*/ ) { int nMyLevel = GetExperienceLevel(); // Adjust experience based on level difference of source player if ( pSource ) { int nLevelDiff = pSource->GetExperienceLevel() - nMyLevel; if ( nLevelDiff <= -5 ) return; if ( nLevelDiff > 0 ) { nAmount *= ( nLevelDiff + 1 ); } else if ( nLevelDiff < 0 ) { nAmount /= ( abs( nLevelDiff ) + 1 ); } } m_nExperiencePoints += nAmount; CalculateExperienceLevel(); // Money? if ( bGiveCurrency && TFGameRules() ) { TFGameRules()->DistributeCurrencyAmount( nAmount, this, false ); CTF_GameStats.Event_PlayerCollectedCurrency( this, nAmount ); EmitSound( "MVM.MoneyPickup" ); } // DevMsg( "Exp: %d, Level: %d Perc: %d\n", GetExperiencePoints(), GetExperienceLevel(), m_nExperienceLevelProgress ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RefundExperiencePoints( void ) { SetExperienceLevel( 1 ); int nAmount = 0; PlayerStats_t *pPlayerStats = CTF_GameStats.FindPlayerStats( this ); if ( pPlayerStats ) { nAmount = pPlayerStats->statsCurrentRound.m_iStat[TFSTAT_CURRENCY_COLLECTED]; } if ( nAmount > 0 ) { SetExperiencePoints(nAmount); SetCurrency(nAmount); } CalculateExperienceLevel(false); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CalculateExperienceLevel( bool bAnnounce /*= true*/ ) { int nMyLevel = GetExperienceLevel(); int nPrevLevel = nMyLevel; float flLevel = ( (float)m_nExperiencePoints / 400.f ) + 1.f; flLevel = Min( flLevel, 20.f ); // Ding? if ( bAnnounce ) { if ( flLevel > 1 && nPrevLevel != (int)flLevel ) { const char *pszTeamName = GetTeamNumber() == TF_TEAM_RED ? "RED" : "BLU"; UTIL_ClientPrintAll( HUD_PRINTCENTER, "#TF_PlayerLeveled", pszTeamName, GetPlayerName(), CFmtStr( "%d", (int)flLevel ) ); UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#TF_PlayerLeveled", pszTeamName, GetPlayerName(), CFmtStr( "%d", (int)flLevel ) ); DispatchParticleEffect( "mvm_levelup1", PATTACH_POINT_FOLLOW, this, "head" ); EmitSound( "Achievement.Earned" ); } } flLevel = floor( flLevel ); SetExperienceLevel( Max( flLevel, 1.f ) ); // Update level progress percentage - networked float flLevelPerc = ( flLevel - floor( flLevel ) ) * 100.f; if ( m_nExperienceLevelProgress != flLevelPerc ) { m_nExperienceLevelProgress.Set( (int)flLevelPerc ); } } //----------------------------------------------------------------------------- // Purpose: Store this upgrade for restoring at a checkpoint //----------------------------------------------------------------------------- void CTFPlayer::RememberUpgrade( int iPlayerClass, CEconItemView *pItem, int iUpgrade, int nCost, bool bDowngrade ) { if ( IsBot() ) return; if ( !g_pPopulationManager ) { Warning( "Remember Upgrade Error: Population Manager does not exist!\n" ); return; } if ( TFGameRules() == NULL || !TFGameRules()->GameModeUsesUpgrades() ) return; item_definition_index_t iItemIndex = pItem ? pItem->GetItemDefIndex() : INVALID_ITEM_DEF_INDEX; CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( this ); if ( !bDowngrade ) { CUpgradeInfo info; info.m_iPlayerClass = iPlayerClass; info.m_itemDefIndex = iItemIndex; info.m_upgrade = iUpgrade; info.m_nCost = nCost; if ( upgrades ) { upgrades->AddToTail( info ); } m_RefundableUpgrades.AddToTail( info ); } else { if ( upgrades ) { for ( int i = 0; i < upgrades->Count(); ++i ) { CUpgradeInfo pInfo = upgrades->Element(i); if ( ( pInfo.m_itemDefIndex == iItemIndex ) && ( pInfo.m_upgrade == iUpgrade ) && ( pInfo.m_nCost == -nCost ) ) { upgrades->Remove( i ); break; } } } // Subset of upgrades that can be sold back for ( int i = 0; i < m_RefundableUpgrades.Count(); ++i ) { CUpgradeInfo pInfo = m_RefundableUpgrades.Element( i ); if ( ( pInfo.m_itemDefIndex == iItemIndex ) && ( pInfo.m_upgrade == iUpgrade ) && ( pInfo.m_nCost == -nCost ) ) { m_RefundableUpgrades.Remove( i ); break; } } } const char *upgradeName = g_hUpgradeEntity->GetUpgradeAttributeName( iUpgrade ); DevMsg( "%3.2f: %s: Player '%s', item '%s', upgrade '%s', cost '%d'\n", gpGlobals->curtime, bDowngrade ? "FORGET_UPGRADE" : "REMEMBER_UPGRADE", GetPlayerName(), pItem ? pItem->GetStaticData()->GetItemBaseName() : "", upgradeName ? upgradeName : "", nCost ); } //----------------------------------------------------------------------------- // Purpose: Erase the first upgrade stored for this item (for powerup bottles) //----------------------------------------------------------------------------- void CTFPlayer::ForgetFirstUpgradeForItem( CEconItemView *pItem ) { if ( IsBot() ) return; if ( TFGameRules() && !TFGameRules()->IsMannVsMachineMode() ) return; DevMsg( "%3.2f: FORGET_FIRST_UPGRADE_FOR_ITEM: Player '%s', item '%s'\n", gpGlobals->curtime, GetPlayerName(), pItem ? pItem->GetStaticData()->GetItemBaseName() : "" ); CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( this ); if ( upgrades == NULL ) return; for( int i = 0; i < upgrades->Count(); ++i ) { if ( ( pItem == NULL && upgrades->Element( i ).m_itemDefIndex == INVALID_ITEM_DEF_INDEX ) || // self upgrade upgrades->Element(i).m_itemDefIndex == pItem->GetItemDefIndex() ) // item upgrade { upgrades->Remove( i ); g_pPopulationManager->SendUpgradesToPlayer( this ); break; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClearUpgradeHistory( void ) { if( !g_pPopulationManager ) return; CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( this ); if ( upgrades != NULL ) upgrades->RemoveAll(); ResetAccumulatedSentryGunDamageDealt(); ResetAccumulatedSentryGunKillCount(); g_pPopulationManager->SendUpgradesToPlayer( this ); DevMsg( "%3.2f: CLEAR_UPGRADE_HISTORY: Player '%s'\n", gpGlobals->curtime, GetPlayerName() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ReapplyItemUpgrades( CEconItemView *pItem ) { if ( IsBot() || !g_pPopulationManager) return; int iClassIndex = GetPlayerClass()->GetClassIndex(); // Restore player Upgrades CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( this ); if ( upgrades == NULL ) return; BeginPurchasableUpgrades(); for( int u = 0; u < upgrades->Count(); ++u ) { // Player Upgrades for this class and item const CUpgradeInfo& upgrade = upgrades->Element(u); if ( iClassIndex == upgrade.m_iPlayerClass && pItem->GetItemDefIndex() == upgrade.m_itemDefIndex ) { g_hUpgradeEntity->ApplyUpgradeToItem( this, pItem, upgrade.m_upgrade, upgrade.m_nCost ); } } EndPurchasableUpgrades(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ReapplyPlayerUpgrades( void ) { if ( IsBot() || !g_pPopulationManager) return; int iClassIndex = GetPlayerClass()->GetClassIndex(); RemovePlayerAttributes( false ); CUtlVector< CUpgradeInfo > *upgrades = g_pPopulationManager->GetPlayerUpgradeHistory( this ); if ( upgrades == NULL ) return; BeginPurchasableUpgrades(); // Restore player Upgrades for( int u = 0; u < upgrades->Count(); ++u ) { // Player Upgrades for this class if ( iClassIndex == upgrades->Element(u).m_iPlayerClass) { // Upgrades applied to player if ( upgrades->Element(u).m_itemDefIndex == INVALID_ITEM_DEF_INDEX ) { g_hUpgradeEntity->ApplyUpgradeToItem( this, NULL, upgrades->Element(u).m_upgrade, upgrades->Element(u).m_nCost ); } } } EndPurchasableUpgrades(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::BeginPurchasableUpgrades( void ) { m_nCanPurchaseUpgradesCount++; if ( TFObjectiveResource()->GetMannVsMachineWaveCount() > 1 ) { m_RefundableUpgrades.RemoveAll(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::EndPurchasableUpgrades( void ) { AssertMsg( m_nCanPurchaseUpgradesCount > 0, "EndPurchasableUpgrades called when m_nCanPurchaseUpgradesCount <= 0" ); if ( m_nCanPurchaseUpgradesCount <= 0 ) return; m_nCanPurchaseUpgradesCount--; if ( TFObjectiveResource()->GetMannVsMachineWaveCount() > 1 ) { m_RefundableUpgrades.RemoveAll(); } // report all upgrades if ( g_pPopulationManager ) { g_pPopulationManager->SendUpgradesToPlayer( this ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::PlayReadySound( void ) { if ( m_flLastReadySoundTime < gpGlobals->curtime ) { if ( TFGameRules() ) { int iTeam = GetTeamNumber(); const char *pszFormat = "%s.Ready"; if ( TFGameRules()->IsMannVsMachineMode() ) { pszFormat = "%s.ReadyMvM"; } else if ( TFGameRules()->IsCompetitiveMode() ) { pszFormat = "%s.ReadyComp"; } CFmtStr goYell( pszFormat, g_aPlayerClassNames_NonLocalized[ m_Shared.GetDesiredPlayerClassIndex() ] ); TFGameRules()->BroadcastSound( iTeam, goYell ); TFGameRules()->BroadcastSound( TEAM_SPECTATOR, goYell ); // spectators hear the ready sounds, too m_flLastReadySoundTime = gpGlobals->curtime + 4.f; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetDesiredHeadScale() const { float flDesiredHeadScale = 1.f; CALL_ATTRIB_HOOK_FLOAT( flDesiredHeadScale, head_scale ); return flDesiredHeadScale; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetHeadScaleSpeed() const { // change size now if ( m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) || m_Shared.InCond( TF_COND_MELEE_ONLY ) || m_Shared.InCond( TF_COND_HALLOWEEN_KART ) || m_Shared.InCond( TF_COND_BALLOON_HEAD ) ) { return GetDesiredHeadScale(); } return gpGlobals->frametime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetDesiredTorsoScale() const { float flDesiredTorsoScale = 1.f; CALL_ATTRIB_HOOK_FLOAT( flDesiredTorsoScale, torso_scale ); return flDesiredTorsoScale; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetTorsoScaleSpeed() const { return gpGlobals->frametime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetDesiredHandScale() const { float flDesiredHandScale = 1.f; CALL_ATTRIB_HOOK_FLOAT( flDesiredHandScale, hand_scale ); return flDesiredHandScale; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetHandScaleSpeed() const { if ( m_Shared.InCond( TF_COND_MELEE_ONLY ) ) { return GetDesiredHandScale(); } return gpGlobals->frametime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::SetBombHeadTimestamp() { m_fLastBombHeadTimestamp = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFPlayer::GetTimeSinceWasBombHead() const { return gpGlobals->curtime - m_fLastBombHeadTimestamp; } //----------------------------------------------------------------------------- // Purpose: Can the player breathe under water? //----------------------------------------------------------------------------- bool CTFPlayer::CanBreatheUnderwater() const { if ( m_Shared.InCond( TF_COND_SWIMMING_CURSE ) ) return true; int iCanBreatheUnderWater = 0; CALL_ATTRIB_HOOK_INT( iCanBreatheUnderWater, can_breathe_under_water ); if ( iCanBreatheUnderWater != 0 ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::InputSpeakResponseConcept( inputdata_t &inputdata ) { const char *pInputString = STRING(inputdata.value.StringID()); // if no params, early out if (!pInputString || *pInputString == 0) { Warning( "empty SpeakResponse input from %s to %s\n", inputdata.pCaller->GetDebugName(), GetDebugName() ); return; } char buf[512] = {0}; // temporary for tokenizing char outputmodifiers[512] = {0}; // eventual output to speak int outWritten = 0; V_strncpy(buf, pInputString, 510); buf[511] = 0; // just in case the last character is a comma -- enforce that the // last character in the buffer is always a terminator. // special syntax allowing designers to submit inputs with contexts like // "concept,context1:value1,context2:value2,context3:value3" // except that entity i/o seems to eat commas these days (didn't used to be the case) // so instead of commas we have to use spaces in the entity IO, // and turn them into commas here. AWESOME. char *pModifiers = const_cast(V_strnchr(buf, ' ', 510)); if ( pModifiers ) { *pModifiers = 0; ++pModifiers; // tokenize on spaces char *token = strtok(pModifiers, " "); while (token) { // find the start characters for the key and value // (seperated by a : which we replace with null) char * RESTRICT key = token; char * RESTRICT colon = const_cast(V_strnchr(key, ':', 510)); char * RESTRICT value; if (!colon) { Warning( "faulty context k:v pair in entity io %s\n", pInputString ); break; } // write the key and colon to the output string int toWrite = colon - key + 1; if ( outWritten + toWrite >= 512 ) { Warning( "Speak input to %s had overlong parameter %s", GetDebugName(), pInputString ); return; } memcpy(outputmodifiers + outWritten, key, toWrite); outWritten += toWrite; *colon = 0; value = colon + 1; // determine if the value is actually a procedural name CBaseEntity *pProcedural = gEntList.FindEntityProcedural( value, this, inputdata.pActivator, inputdata.pCaller ); // write the value to the output -- if it's a procedural name, replace appropriately; // if not, just copy over. const char *valString; if (pProcedural) { valString = STRING(pProcedural->GetEntityName()); } else { valString = value; } toWrite = strlen(valString); toWrite = MIN( 511-outWritten, toWrite ); V_strncpy( outputmodifiers + outWritten, valString, toWrite+1 ); outWritten += toWrite; // get the next token token = strtok(NULL, " "); if (token) { // if there is a next token, write in a comma if (outWritten < 511) { outputmodifiers[outWritten++]=','; } } } } // null terminate just in case outputmodifiers[outWritten <= 511 ? outWritten : 511] = 0; SpeakConceptIfAllowed( GetMPConceptIndexFromString( buf ), outputmodifiers[0] ? outputmodifiers : NULL ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::CreateDisguiseWeaponList( CTFPlayer *pDisguiseTarget ) { ClearDisguiseWeaponList(); // copy disguise target weapons if ( pDisguiseTarget ) { for ( int i=0; i( pDisguiseTarget->GetWeapon( i ) ); if ( !pWeapon ) continue; CEconItemView *pItem = NULL; // We are copying a generated, non-base item. CAttributeContainer *pContainer = pWeapon->GetAttributeContainer(); if ( pContainer ) { pItem = pContainer->GetItem(); } int iSubType = 0; CTFWeaponBase *pCopyWeapon = dynamic_cast( GiveNamedItem( pWeapon->GetClassname(), iSubType, pItem, true ) ); if ( pCopyWeapon ) { pCopyWeapon->AddEffects( EF_NODRAW | EF_NOSHADOW ); m_hDisguiseWeaponList.AddToTail( pCopyWeapon ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::ClearDisguiseWeaponList() { FOR_EACH_VEC( m_hDisguiseWeaponList, i ) { if ( m_hDisguiseWeaponList[i] ) { m_hDisguiseWeaponList[i]->Drop( vec3_origin ); } } m_hDisguiseWeaponList.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::CanScorePointForPD( void ) const { // These conditions block being able to score in PD ETFCond blockingConds[] = { TF_COND_STEALTHED // Invis spies , TF_COND_STEALTHED_BLINK , TF_COND_DISGUISING // Disguised spies , TF_COND_DISGUISED , TF_COND_INVULNERABLE // Uber , TF_COND_PHASE }; // Bonked Scouts // Check for blocking conditions for( int i=0; i 0.f ) { return false; } // Rate limit return ( ( m_flNextScorePointForPD < 0 ) || ( m_flNextScorePointForPD < gpGlobals->curtime ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::PickupWeaponFromOther( CTFDroppedWeapon *pDroppedWeapon ) { const CEconItemView *pItem = pDroppedWeapon->GetItem(); if ( !pItem ) return false; if ( pItem->IsValid() ) { int iClass = GetPlayerClass()->GetClassIndex(); int iItemSlot = pItem->GetStaticData()->GetLoadoutSlot( iClass ); CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( GetEntityForLoadoutSlot( iItemSlot ) ); if ( !pWeapon ) { AssertMsg( false, "No weapon to put down when picking up a dropped weapon!" ); return false; } // we need to force translating the name here. // GiveNamedItem will not translate if we force creating the item const char *pTranslatedWeaponName = TranslateWeaponEntForClass( pItem->GetStaticData()->GetItemClass(), iClass ); CTFWeaponBase *pNewItem = dynamic_cast( GiveNamedItem( pTranslatedWeaponName, 0, pItem, true )); Assert( pNewItem ); if ( pNewItem ) { CTFWeaponBuilder *pBuilder = dynamic_cast( (CBaseEntity*)pNewItem ); if ( pBuilder ) { pBuilder->SetSubType( GetPlayerClass()->GetData()->m_aBuildable[0] ); } // make sure we removed our current weapon if ( pWeapon ) { // drop current weapon Vector vecPackOrigin; QAngle vecPackAngles; CalculateAmmoPackPositionAndAngles( pWeapon, vecPackOrigin, vecPackAngles ); bool bShouldThrowHeldWeapon = true; // When in the spawn room, you won't throw down your held weapon if you own that weapon. // This is to prevent folks from standing near a supply closet and spawning their items // over and over and over. if ( PointInRespawnRoom( this, WorldSpaceCenter() ) ) { CSteamID playerSteamID; GetSteamID( &playerSteamID ); uint32 nItemAccountID = pWeapon->GetAttributeContainer()->GetItem()->GetAccountID(); // Stock weapons have accountID 0 if ( playerSteamID.GetAccountID() == nItemAccountID || nItemAccountID == 0 ) { bShouldThrowHeldWeapon = false; } } if ( bShouldThrowHeldWeapon ) { CTFDroppedWeapon *pNewDroppedWeapon = CTFDroppedWeapon::Create( this, vecPackOrigin, vecPackAngles, pWeapon->GetWorldModel(), pWeapon->GetAttributeContainer()->GetItem() ); if ( pNewDroppedWeapon ) { pNewDroppedWeapon->InitDroppedWeapon( this, pWeapon, true ); } } Weapon_Detach( pWeapon ); UTIL_Remove( pWeapon ); } CBaseCombatWeapon *pLastWeapon = GetLastWeapon(); pNewItem->MarkAttachedEntityAsValidated(); pNewItem->GiveTo( this ); Weapon_SetLast( pLastWeapon ); pDroppedWeapon->InitPickedUpWeapon( this, pNewItem ); // can't use the weapon we just picked up? if ( !Weapon_CanSwitchTo( pNewItem ) ) { // try next best thing we can use SwitchToNextBestWeapon( pNewItem ); } // delay pickup weapon message m_flSendPickupWeaponMessageTime = gpGlobals->curtime + 0.1f; return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::TryToPickupDroppedWeapon() { if ( !CanAttack() ) return false; if ( GetActiveWeapon() && ( GetActiveWeapon()->m_flNextPrimaryAttack > gpGlobals->curtime ) ) return false; CTFDroppedWeapon *pDroppedWeapon = GetDroppedWeaponInRange(); if ( pDroppedWeapon && !pDroppedWeapon->IsMarkedForDeletion() ) { if ( PickupWeaponFromOther( pDroppedWeapon ) ) { UTIL_Remove( pDroppedWeapon ); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::AddCustomAttribute( const char *pszAttributeName, float flVal, float flDuration /*= -1.f*/ ) { float flExpireTime = flDuration > 0 ? gpGlobals->curtime + flDuration : flDuration; int iIndex = m_mapCustomAttributes.Find( pszAttributeName ); if ( iIndex == m_mapCustomAttributes.InvalidIndex() ) { m_mapCustomAttributes.Insert( pszAttributeName, flExpireTime ); } else { // stomp the previous expire time for now m_mapCustomAttributes[iIndex] = flExpireTime; } // just stomp the value m_Shared.ApplyAttributeToPlayer( pszAttributeName, flVal ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveCustomAttribute( const char *pszAttributeName ) { int iIndex = m_mapCustomAttributes.Find( pszAttributeName ); if ( iIndex != m_mapCustomAttributes.InvalidIndex() ) { m_Shared.RemoveAttributeFromPlayer( pszAttributeName ); m_mapCustomAttributes.RemoveAt( iIndex ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::UpdateCustomAttributes() { // check if we should remove custom attributes from player bool bShouldCheckCustomAttributes = m_mapCustomAttributes.Count() > 0; while ( bShouldCheckCustomAttributes ) { bShouldCheckCustomAttributes = false; FOR_EACH_MAP_FAST( m_mapCustomAttributes, i ) { float flExpireTime = m_mapCustomAttributes[i]; if ( flExpireTime > 0 && gpGlobals->curtime > flExpireTime ) { const char *pszAttributeName = m_mapCustomAttributes.Key( i ); m_Shared.RemoveAttributeFromPlayer( pszAttributeName ); m_mapCustomAttributes.RemoveAt( i ); bShouldCheckCustomAttributes = true; break; } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFPlayer::RemoveAllCustomAttributes() { FOR_EACH_MAP_FAST( m_mapCustomAttributes, i ) { const char *pszAttributeName = m_mapCustomAttributes.Key( i ); m_Shared.RemoveAttributeFromPlayer( pszAttributeName ); } m_mapCustomAttributes.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ShouldForceTransmitsForTeam( int iTeam ) { return ( ( GetTeamNumber() == TEAM_SPECTATOR ) || ( ( GetTeamNumber() == iTeam ) && ( m_Shared.InCond( TF_COND_TEAM_GLOWS ) || !IsAlive() ) ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::ShouldGetBonusPointsForExtinguishEvent( int userID ) { int iIndex = m_PlayersExtinguished.Find( userID ); if ( iIndex != m_PlayersExtinguished.InvalidIndex() ) { if ( ( gpGlobals->curtime - m_PlayersExtinguished[iIndex] ) < 20.f ) return false; m_PlayersExtinguished[iIndex] = gpGlobals->curtime; } else { m_PlayersExtinguished.Insert( userID, gpGlobals->curtime ); } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFPlayer::IsTruceValidForEnt( void ) const { if ( PointInRespawnRoom( this, WorldSpaceCenter(), true ) ) return false; return true; }