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

22005 lines
668 KiB

//========= 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 <ent index or -1 for all>. 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<<ANIMATION_SEQUENCE_BITS) );
Assert( (1<<ANIMATION_SEQUENCE_BITS) >= 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; i<m_hRagWearables.Count(); ++i )
{
if ( m_hRagWearables[i] )
{
m_hRagWearables[i]->Remove();
}
}
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<CHandle<CEconWearable > > 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; i<playerVector.Count(); ++i )
{
if ( playerVector[i] == this )
continue;
// touching ghost player?
// we just check for radius of 100 and assume that we touch to avoid custom collision for ghost
if ( playerVector[i]->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
if ( ( playerVector[i]->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < Square( 100 ) )
{
ghostVector.AddToTail( playerVector[i] );
}
}
}
for ( int i=0; i<ghostVector.Count(); ++i )
{
CTFPlayer *pGhost = ghostVector[i];
// revive ghost on the same team
if ( pGhost->GetTeamNumber() == 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<CTFSpellBook*>( 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; i<ARRAYSIZE( s_pszTauntRPSParticleNames ); ++i )
{
PrecacheParticleSystem( s_pszTauntRPSParticleNames[i] );
}
PrecacheParticleSystem( "blood_decap" );
PrecacheParticleSystem( "xms_icicle_idle" );
PrecacheParticleSystem( "xms_icicle_impact" );
PrecacheParticleSystem( "xms_icicle_impact_dryice" );
PrecacheParticleSystem( "xms_icicle_melt" );
PrecacheParticleSystem( "xms_ornament_glitter" );
PrecacheParticleSystem( "xms_ornament_smash_blue" );
PrecacheParticleSystem( "xms_ornament_smash_red" );
PrecacheParticleSystem( "drg_pomson_muzzleflash" );
PrecacheParticleSystem( "drg_pomson_impact" );
PrecacheParticleSystem( "drg_pomson_impact_drain" );
PrecacheParticleSystem( "dxhr_arm_muzzleflash" );
PrecacheModel( "effects/beam001_red.vmt" );
PrecacheModel( "effects/beam001_blu.vmt" );
PrecacheModel( "effects/beam001_white.vmt" );
PrecacheScriptSound( "Weapon_Mantreads.Impact" );
// Precache footstep override sounds.
PrecacheScriptSound( "cleats_conc.StepLeft" );
PrecacheScriptSound( "cleats_conc.StepRight" );
PrecacheScriptSound( "cleats_dirt.StepLeft" );
PrecacheScriptSound( "cleats_dirt.StepRight" );
PrecacheScriptSound( "xmas.jingle" );
PrecacheScriptSound( "xmas.jingle_higher" );
PrecacheScriptSound( "PegLeg.StepRight" );
// Halloween
// Bombinomicon deaths
PrecacheParticleSystem( "bombinomicon_burningdebris" );
PrecacheParticleSystem( "bombinomicon_burningdebris_halloween" );
PrecacheParticleSystem( "halloween_player_death_blue" );
PrecacheParticleSystem( "halloween_player_death" );
PrecacheScriptSound( "Bombinomicon.Explode" );
PrecacheScriptSound( "Weapon_DRG_Wrench.Teleport" );
PrecacheScriptSound( "Weapon_Pomson.Single" );
PrecacheScriptSound( "Weapon_Pomson.SingleCrit" );
PrecacheScriptSound( "Weapon_Pomson.Reload" );
PrecacheScriptSound( "Weapon_Pomson.DrainedVictim" );
PrecacheScriptSound( "BlastJump.Whistle" );
PrecacheScriptSound( "Spy.TeaseVictim" );
PrecacheScriptSound( "Demoman.CritDeath" );
PrecacheScriptSound( "Heavy.Battlecry03" );
PrecacheModel( "models/effects/resist_shield/resist_shield.mdl" );
PrecacheModel( "models/props_mvm/mvm_revive_tombstone.mdl" );
PrecacheScriptSound( "General.banana_slip" ); // Used for SodaPopper Hype Jumps
#ifdef STAGING_ONLY
PrecacheScriptSound( "RD.SpaceGravityTransition" );
#endif // STAGING_ONLY
PrecacheScriptSound( "Parachute_open" );
PrecacheScriptSound( "Parachute_close" );
#ifdef STAGING_ONLY
PrecacheScriptSound( "WeaponDNAGun.Transform" );
PrecacheParticleSystem( "spy_stolen_smoke_blue" );
PrecacheParticleSystem( "spy_stolen_smoke_red" );
#endif // STAGING_ONLY
// precache the EOTL bomb cart replacements
PrecacheModel( "models/props_trainyard/bomb_eotl_blue.mdl" );
PrecacheModel( "models/props_trainyard/bomb_eotl_red.mdl" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::Precache()
{
VPROF_BUDGET( "CTFPlayer::Precache", VPROF_BUDGETGROUP_PLAYER );
/*
Note: All TFPlayer specific must go inside PrecacheTFPlayer()
This assumes that we're loading all resources for any TF player class
The reason is to safe performance because tons of string compares is very EXPENSIVE!!!
The most offending function is PrecacheGibsForModel which re-parsing through KeyValues every time it's called
If you have any question, come talk to me (Bank)
*/
PrecacheTFPlayer();
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose: Allow pre-frame adjustments on the player
//-----------------------------------------------------------------------------
ConVar sv_runcmds( "sv_runcmds", "1" );
void CTFPlayer::PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper )
{
static bool bSeenSyncError = false;
VPROF( "CTFPlayer::PlayerRunCommand" );
if ( !sv_runcmds.GetInt() )
return;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
m_Shared.CreateVehicleMove( gpGlobals->frametime, 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<MsgGCVerifyCacheSubscription_t> 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<CTFPlayerResource *>( 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<const char *> 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<CEconEntity*>(GiveNamedItem( pItem->GetStaticData()->GetItemClass(), 0, pItem ));
Assert( pNewItem );
if ( pNewItem )
{
pNewItem->GetAttributeContainer()->GetItem()->SetOverrideAccountID( ownerSteamID.GetAccountID() );
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( (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<CTFWeaponBase*>( 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<CTFWeaponBase*>( GetEntityForLoadoutSlot( LOADOUT_POSITION_PRIMARY ) );
if ( !pWeapon || !pWeapon->CanBeSelected() || !Weapon_Switch( pWeapon ) )
{
pWeapon = dynamic_cast<CTFWeaponBase*>( GetEntityForLoadoutSlot( LOADOUT_POSITION_SECONDARY ) );
if ( !pWeapon || pWeapon->CanBeSelected() || !Weapon_Switch( pWeapon ) )
{
pWeapon = dynamic_cast<CTFWeaponBase*>( 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<CTFWearable *>( 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<CTFWeaponInfo*>( 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<CTFWeaponBase*>(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<CEconEntity*>(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<CBaseCombatWeapon*>( (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<const CEconItemSetDefinition *> 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; i<raidingTeam->GetNumPlayers(); ++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<CTFTeamSpawn*>( 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<CTFTeamSpawn*>( 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<CTFBot*>( 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<CTFBot*>( 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; i<raidingTeam->GetNumPlayers(); ++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<MsgGCRespawnPostLoadoutChange_t> 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<CTFWeaponPDA *>( 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 <building> <mode>\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 <building> <mode>\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 <url>\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*>( 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<CCaptureFlag*>( 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<int> 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<CTFBuffItem*>( 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<CTFFlameThrower*>( 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;i<iNumHealers;i++ )
{
CTFPlayer *pMedic = ToTFPlayer( m_Shared.GetHealerByIndex(i) );
// if its a medic healing us
if ( pMedic && pMedic->IsPlayerClass( 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<CTriggerHurt *>( 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<CTFProjectile_SentryRocket *>( 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<CTFPlayer*> 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<DPS_Period; ++i )
{
if ( m_damageRateArray[i] > m_peakDamagePerSecond )
{
m_peakDamagePerSecond = m_damageRateArray[i];
}
}
}
else
{
m_damageRateArray[ i ] += info.GetDamage();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPlayer::AddConnectedPlayers( CUtlVector<CTFPlayer*> &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<CBaseEntity*> 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<CEconEntity *>( pEntity ), this, pTFAttacker, kKillEaterEvent_BackstabAbsorbed );
// Unequip.
CTFWearable *pItem = dynamic_cast<CTFWearable *>( 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<CObjectSentrygun*>( 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<CTFWeaponBase*>(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<CTFPlayer *>( 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<CTFSniperRifle*>( 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 <CTFLunchBox *> ( 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<CBaseObject*>( 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<CTFKnife *>( GetActiveTFWeapon() );
if ( pKnife && pKnife->GetKnifeType() == KNIFE_DISGUISE_ONKILL )
{
bPlayspeech = false;
}
}
}
if ( bPlayspeech )
{
SpeakConceptIfAllowed( MP_CONCEPT_KILLED_PLAYER, modifiers );
}
CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(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().Count(); ++i )
{
pFlag = static_cast< CCaptureFlag* >( 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<CTFWeaponBase *>(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<m_Shared.m_nNumHealers; i++ )
{
m_Shared.m_aHealers[i].iKillsWhileBeingHealed++;
if ( IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
{
if ( m_Shared.m_aHealers[i].iKillsWhileBeingHealed >= 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<GetNumWearables(); ++i )
{
CTFWearableLevelableItem *pItem = dynamic_cast< CTFWearableLevelableItem* >( 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<CBaseObject *>( 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<CTFWeaponBase *>( 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<GetNumWearables(); ++i )
{
CTFWearableLevelableItem *pItem = dynamic_cast< CTFWearableLevelableItem* >( 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<CTFWeaponBase *>(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<CWeaponMedigun*>( 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<CEconEntity *>( 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<CEconEntity *>( 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<CBaseObject *>( 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<CCaptureFlag *>( 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<CEconEntity *>( pKillerWeapon ), pPlayerAttacker, this, kKillEaterEvent_DefenderKill );
}
}
}
CTFWeaponBase* pActiveWeapon = GetActiveTFWeapon();
if( pActiveWeapon )
{
CEconEntity *pVictimEconWeapon = dynamic_cast<CEconEntity *>( 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<CCaptureFlag*>( 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<CTFPlayerResource *>( 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<CTFKnife*>( 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;i<pPlayerAttacker->WeaponCount();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<CTFWeaponBase *>(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<CTFWearable*>( 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<CTFAmmoPack *>( 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<CHealthKitSmall*>( 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<CCurrencyPackSmall*>( CBaseEntity::Create( "item_currencypack_small", vecSrc, vec3_angle, this ) );
break;
case TF_CURRENCY_PACK_MEDIUM:
pCurrencyPack = assert_cast<CCurrencyPackMedium*>( CBaseEntity::Create( "item_currencypack_medium", vecSrc, vec3_angle, this ) );
break;
case TF_CURRENCY_PACK_LARGE:
pCurrencyPack = assert_cast<CCurrencyPack*>( 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<CCurrencyPack*>( 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<CTFWeaponBaseGrenadeProj*>( pProjectile );
if ( pGrenade )
{
bOwner = ( pGrenade->GetThrower() == this );
}
}
else if ( pProjectile->GetProjectileType() == TF_PROJECTILE_SENTRY_ROCKET )
{
CTFProjectile_SentryRocket *pRocket = assert_cast<CTFProjectile_SentryRocket*>( 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<CTFPlayer*>(this)->GetPlayerClass()->GetClassIndex() == TF_CLASS_DEMOMAN )
{
CTFSword *pSword = dynamic_cast<CTFSword*>(const_cast<CTFPlayer*>(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<CTFWearable*>( 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; i<iNumObjects; i++ )
{
CBaseObject *pObj = GetObject(i);
if ( !pObj )
continue;
if ( pObj->GetType() != 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<CBaseObject> 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;i<iNumHealers;i++ )
{
// Send a message to all medics invulning the Pyro at this time
CTFPlayer *pMedic = ToTFPlayer( m_Shared.GetHealerByIndex( i ) );
if ( pMedic && pMedic->GetChargeEffectBeingProvided() == 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<CTFTeam *>( 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<CTFRagdoll*>( 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<CTFRagdoll*>( m_hRagdoll.Get() );
if( pRagdoll )
{
UTIL_Remove( pRagdoll );
pRagdoll = NULL;
}
Assert( pRagdoll == NULL );
// Create a ragdoll.
pRagdoll = dynamic_cast<CTFRagdoll*>( 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<CTFRagdoll*>( m_hRagdoll.Get() );
if( pRagdoll )
{
UTIL_Remove( pRagdoll );
}
// Remove the feign death ragdoll at the same time.
pRagdoll = dynamic_cast<CTFRagdoll*>( 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<CEconEntity *>( 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<CTFRagdoll*>( m_hFeignRagdoll.Get() );
if( pRagdoll )
{
UTIL_Remove( pRagdoll );
pRagdoll = NULL;
}
Assert( pRagdoll == NULL );
// Create a ragdoll.
pRagdoll = dynamic_cast<CTFRagdoll*>( 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.Count(); ++i )
{
static_cast< CTFNavArea * >( 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<CTeamControlPoint*>(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<CTeamTrainWatcher*>( 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<CTeamTrainWatcher*>( 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<CObserverPoint *>(target);
if ( pObsPoint && !pObsPoint->CanUseObserverPoint( this ) )
return false;
CFuncTrackTrain *pTrain = dynamic_cast<CFuncTrackTrain *>(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<CObserverPoint *>(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;i<iNumObjects;i++ )
{
CBaseObject *pObj = GetObject(i);
if ( pObj && pObj->GetType() == 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<CObserverPoint *>(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; i<wasVisible.m_area.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)wasVisible.m_area[i];
area->RemovePotentiallyVisibleActor( 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; i<isVisible.m_area.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)isVisible.m_area[i];
area->AddPotentiallyVisibleActor( 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; i<MAX_VISUALS_CUSTOM_SOUNDS; ++i )
{
if ( vis->pszCustomSounds[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<attrib_value_t>( 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; i<pItemDef->GetNumAnimations( 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<CAttribute_String>( pItemDef, pAttrDef_TauntAttackName, &pszTauntAttackName ) )
{
m_iTauntAttack = GetTauntAttackByName( pszTauntAttackName );
}
static CSchemaAttributeDefHandle pAttrDef_TauntAttackTime( "taunt attack time" );
float flTauntAttackTime = 0.f;
if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( 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<CAttribute_String>( 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; iButtonIndex<pTaunt->GetTauntInputRemapCount(); ++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<CTFFists*>(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; i<count; i++ )
{
// Must be facing whoever we knock back.
Vector vecToTarget;
vecToTarget = pObjects[i]->WorldSpaceCenter() - 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<MEDIC_RELEASE_DOVE_COUNT; ++i )
{
Vector vecPos = launchSpot + Vector( 0, 0, RandomFloat( -10.0f, 20.0f ) );
SpawnClientsideFlyingBird( vecPos );
}
}
else if ( iTauntAttack == TAUNTATK_PYRO_ARMAGEDDON )
{
Vector origin( GetAbsOrigin() );
CPVSFilter filter( origin );
TE_TFExplosion( filter, 0.0f, origin, Vector( 0.0f, 0.0f, 1.0f ), TF_WEAPON_GRENADELAUNCHER, entindex() );
int nRandomPick[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
CUtlVector< CTFPlayer* > 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; i<itemCount; ++i )
{
CEconItemView *econItemView = Inventory()->GetItem( 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<CTFSniperRifle*>(pActiveWeapon);
if ( pRifle && pRifle->IsZoomed() )
{
criteriaSet.AppendCriteria( "sniperzoomed", "1" );
}
}
else if ( pActiveWeapon->GetWeaponID() == TF_WEAPON_MINIGUN )
{
CTFMinigun *pMinigun = dynamic_cast<CTFMinigun*>(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<CAttribute_String>( 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<CTriggerAreaCapture*>(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; i<pointVector->Count(); ++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<MAX_EDICTS> *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 <CWeaponMedigun*>( 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 <concept>\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 <concept>, or tf_testrr <player name> <concept>", 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<CWeaponMedigun*>(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 <item definition>\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<CBaseCombatWeapon*>(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<CBaseCombatWeapon *>(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<CEconEntity*>( 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; t<playerList.Count(); ++t )
{
CTFPlayer *pPlayer = playerList[t];
if ( pPlayer == this )
continue;
// don't allow bot to taunt with each other
if ( pPlayer->IsBot() && 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<CAttribute_String>( pItemDef, pAttrDef_TauntAttackName, &pszTauntAttackName ) )
{
initiator->m_iTauntAttack = GetTauntAttackByName( pszTauntAttackName );
}
static CSchemaAttributeDefHandle pAttrDef_TauntAttackTime( "taunt attack time" );
float flTauntAttackTime = 0.f;
if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( 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<CTFWeaponBase *>( 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().Count(); ++i )
{
CBaseObject* pObj = static_cast< CBaseObject* >( 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() : "<self>",
upgradeName ? upgradeName : "<NULL>",
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() : "<self>" );
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<char *>(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<char *>(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<TF_PLAYER_WEAPON_COUNT; ++i )
{
CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( 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<CTFWeaponBase*>( 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<ARRAYSIZE(blockingConds); ++i )
{
if ( m_Shared.InCond( blockingConds[i] ) )
{
return false;
}
}
// More aggressively deny invis than the code above
if ( m_Shared.GetPercentInvisible() > 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<CTFWeaponBase*>( GiveNamedItem( pTranslatedWeaponName, 0, pItem, true ));
Assert( pNewItem );
if ( pNewItem )
{
CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( (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;
}