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
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; |
|
}
|
|
|