hl2_src-leak-2017/src/game/client/tf/c_tf_player.cpp

12135 lines
352 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "c_tf_player.h"
#include "c_user_message_register.h"
#include "view.h"
#include "iclientvehicle.h"
#include "ivieweffects.h"
#include "input.h"
#include "IEffects.h"
#include "fx.h"
#include "c_basetempentity.h"
#include "hud_macros.h"
#include "engine/ivdebugoverlay.h"
#include "smoke_fog_overlay.h"
#include "playerandobjectenumerator.h"
#include "bone_setup.h"
#include "in_buttons.h"
#include "r_efx.h"
#include "dlight.h"
#include "shake.h"
#include "cl_animevent.h"
#include "animation.h"
#include "choreoscene.h"
#include "tf_weaponbase.h"
#include "c_tf_playerresource.h"
#include "toolframework/itoolframework.h"
#include "tier1/KeyValues.h"
#include "tier0/vprof.h"
#include "prediction.h"
#include "effect_dispatch_data.h"
#include "c_te_effect_dispatch.h"
#include "tf_fx_muzzleflash.h"
#include "tf_gamerules.h"
#include "view_scene.h"
#include "c_baseobject.h"
#include "toolframework_client.h"
#include "materialsystem/imaterialvar.h"
#include "soundenvelope.h"
#include "voice_status.h"
#include "clienteffectprecachesystem.h"
#include "functionproxy.h"
#include "toolframework_client.h"
#include "choreoevent.h"
#include "vguicenterprint.h"
#include "eventlist.h"
#include "input.h"
#include "tf_weapon_medigun.h"
#include "tf_weapon_pipebomblauncher.h"
#include "tf_weapon_shovel.h"
#include "tf_hud_mediccallers.h"
#include "in_main.h"
#include "c_team.h"
#include "collisionutils.h"
// for spy material proxy
#include "tf_proxyentity.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/itexturecompositor.h"
#include "c_tf_team.h"
#include "tf_item_inventory.h"
#include "model_types.h"
#include "dt_utlvector_recv.h"
#include "tf_item_wearable.h"
#include "cam_thirdperson.h"
#include "c_tf_projectile_arrow.h"
#include "econ_entity.h"
#include "ihasowner.h"
#include "tf_hud_itemeffectmeter.h"
#include "replay/vgui/replayinputpanel.h"
#include "tf_replay.h"
#include "netadr.h"
#include "input.h"
#include "gcsdk/gcclientsdk.h"
#include "econ_gcmessages.h"
#include "rtime.h"
#include "networkstringtable_clientdll.h"
#include "replay/ireplaymanager.h"
#include "gc_clientsystem.h"
#include "c_entitydissolve.h"
#include "tf_viewmodel.h"
#include "player_vs_environment/c_tf_upgrades.h"
#include "sourcevr/isourcevirtualreality.h"
#include "tempent.h"
#include "confirm_dialog.h"
#include "c_tf_weapon_builder.h"
#include "tf_shared_content_manager.h"
#include "baseanimatedtextureproxy.h"
#include "econ_entity.h"
#include "halloween/tf_weapon_spellbook.h"
#include "tf_weapon_grapplinghook.h"
#include "tf_logic_robot_destruction.h"
#include "econ_notifications.h"
#include "tf_weapon_buff_item.h"
#include "tf_dropped_weapon.h"
#include "tf_hud_notification_panel.h"
#include "tf_dropped_weapon.h"
#include "tf_hud_passtime_reticle.h"
#include "passtime_convars.h"
#include "c_tf_passtime_logic.h"
#include "tf_weapon_passtime_gun.h"
#include "eiface.h"
#include "filesystem.h"
#include "debugoverlay_shared.h"
#include "tf_hud_chat.h"
#include "tf_item_powerup_bottle.h"
#include <vgui_controls/AnimationController.h>
#ifdef STAGING_ONLY
#include "tf_matchmaking_shared.h"
#include "tier2/keyvaluesmacros.h"
#endif // STAGING_ONLY
#if defined( REPLAY_ENABLED )
#include "replay/ienginereplay.h"
#endif
#if defined( CTFPlayer )
#undef CTFPlayer
#endif
#include "materialsystem/imesh.h" //for materials->FindMaterial
#include "iviewrender.h" //for view->
// NVNT haptics system interface
#include "c_tf_haptics.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace GCSDK;
static_assert( TEAM_UNASSIGNED == 0, "If this assert fires, update the assert and the enum in ctexturecompositor.cpp which specifies team colors" );
static_assert( TF_TEAM_RED == 2, "If this assert fires, update the assert and the enum in ctexturecompositor.cpp which specifies team colors" );
static_assert( TF_TEAM_BLUE == 3, "If this assert fires, update the assert and the enum in ctexturecompositor.cpp which specifies team colors" );
// Forward decl
CEconItemView *GetEconItemViewFromProxyEntity( void *pEntity );
C_TFPlayer *GetOwnerFromProxyEntity( void *pEntity );
// --------------------------------------------------------------------------------
// Local Convar Helper Function
// --------------------------------------------------------------------------------
void VisionMode_ChangeCallback( IConVar *pConVar, char const *pOldString, float flOldValue )
{
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if ( pLocalPlayer )
{
pLocalPlayer->CalculateVisionUsingCurrentFlags();
}
}
#ifdef _DEBUG
CON_COMMAND_F ( tf_test_bomb, "Test halloween bomb", 0 )
{
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if ( !pLocalPlayer )
return;
C_TFPlayer *pPlayer = ToTFPlayer( pLocalPlayer );
if ( !pPlayer )
return;
pPlayer->CreateBombonomiconHint();
}
ConVar test_vision_off( "test_vision_off", "0", FCVAR_NONE, "Force vision modes off!", VisionMode_ChangeCallback );
ConVar test_pyrovision( "test_pyrovision", "0", FCVAR_NONE, "Force Pyrovision on!", VisionMode_ChangeCallback );
ConVar test_romevision( "test_romevision", "0", FCVAR_NONE, "Force Romevision on!", VisionMode_ChangeCallback );
ConVar test_halloweenvision( "test_halloweenvision", "0", FCVAR_NONE, "Force halloween vision on!", VisionMode_ChangeCallback );
#endif
// These are all permanently STAGING_ONLY
#ifdef STAGING_ONLY
ConVar tf_paint_kit_disable( "tf_paint_kit_disable", "0", FCVAR_NONE, "Set to disable using paintkits completely." );
ConVar tf_paint_kit_team_override( "tf_paint_kit_team_override", "-1", FCVAR_NONE, "Set to -1:disabled, 0:TF_TEAM_RED, 1:TF_TEAM_BLUE" );
ConVar tf_paint_kit_seed_override( "tf_paint_kit_seed_override", "0", FCVAR_NONE, "Set to none-0 will force composite weapon skin to use this as random seed number." );
ConVar tf_paint_kit_force_regen( "tf_paint_kit_force_regen", "0", FCVAR_NONE, "Set to 1 to force regen after every generation. (dev only)" );
ConVar tf_paint_kit_generating_icons( "tf_paint_kit_generating_icons", "0", FCVAR_NONE, "Only Set by the Icon Generating System." );
extern ConVar tf_paint_kit_force_wear;
KeyValues *s_kvOverridePaintkit = NULL;
bool s_bIsPaintkitOverrideSet = false;
void LoadPaintKit( const char* pszFile )
{
//scripts\items\unencrypted\paintkits
static char pszPaintkitFile[MAX_PATH] = { '\0' };
if ( pszFile )
{
V_sprintf_safe( pszPaintkitFile, "scripts/items/unencrypted/paintkits/%s.paintkit", pszFile );
}
if ( pszPaintkitFile[0] == '\0' )
return;
// Make sure we have a file system
if ( !g_pFullFileSystem || !g_pFullFileSystem->FileExists( pszPaintkitFile ) )
{
Msg( "Unable to load %s from File System \n", pszPaintkitFile );
return;
}
if ( s_kvOverridePaintkit )
{
s_kvOverridePaintkit->deleteThis();
s_kvOverridePaintkit = NULL;
}
// Parse the KeyValues
s_kvOverridePaintkit = new KeyValues( "paintkit" );
if ( !s_kvOverridePaintkit->LoadFromFile( g_pFullFileSystem, pszPaintkitFile ) )
{
Msg( "Unable to read KeyValues from %s \n", pszPaintkitFile );
return;
}
HandleKeyValuesMacros( s_kvOverridePaintkit );
Msg( "Setting %s as Paintkit override.\nCall with no args to disable\ntf_paint_kit_force_regen set to 1\n", pszPaintkitFile );
s_bIsPaintkitOverrideSet = true;
}
CON_COMMAND_F( tf_paint_kit_override_file, "Set a override paintkit file", FCVAR_NONE )
{
// Enable force regen
tf_paint_kit_force_regen.SetValue( 1 );
s_bIsPaintkitOverrideSet = false;
// Take the con var
if ( args.ArgC() <= 1 )
{
Msg( "No paintkit file specified, turning override off.\nYou may want to set tf_paint_kit_force_regen to 0\n" );
return;
}
const char *pszFile = args.Arg( 1 );
LoadPaintKit( pszFile );
}
CON_COMMAND( tf_paint_kit_override_refresh, "Refresh the current override paint kit file" )
{
if ( !s_bIsPaintkitOverrideSet )
return;
LoadPaintKit( NULL );
}
#endif
ConVar tf_playergib_forceup( "tf_playersgib_forceup", "1.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward added velocity for gibs." );
ConVar tf_playergib_force( "tf_playersgib_force", "500.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Gibs force." );
ConVar tf_playergib_maxspeed( "tf_playergib_maxspeed", "400", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Max gib speed." );
ConVar tf_always_deathanim( "tf_always_deathanim", "0", FCVAR_CHEAT, "Force death anims to always play." );
ConVar tf_clientsideeye_lookats( "tf_clientsideeye_lookats", "1", FCVAR_NONE, "When on, players will turn their pupils to look at nearby players." );
extern ConVar tf_halloween_kart_boost_recharge;
extern ConVar tf_halloween_kart_boost_duration;
extern ConVar cl_thirdperson;
#ifdef STAGING_ONLY
ConVar test_local_player_skin_override( "test_local_player_skin_override", "-1", FCVAR_CHEAT, "If >= 0, force this skin index to be used by the local player model." );
ConVar tf_test_setkillcount( "tf_test_setkillcount", "0", FCVAR_NONE, "Force a kill streak count for eye glow testing" );
extern ConVar tf_random_item_min;
extern ConVar tf_random_item_max;
extern ConVar tf_killstreak_eyeglow;
extern ConVar tf_killstreak_color;
extern ConVar tf_eyeglow_wip;
ConVar tf_sheen_all( "tf_sheen_all", "0", FCVAR_NONE, "When on, enables sheens on all weapons" );
ConVar tf_sheen_fast( "tf_sheen_fast", "0", FCVAR_NONE, "When on, no delay's between sheens" );
ConVar tf_sheen_shader_override( "tf_sheen_shader_override", "0", FCVAR_NONE, "Set for shader effect override" );
ConVar tf_sheen_color_override_r( "tf_sheen_color_override_r", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
ConVar tf_sheen_color_override_g( "tf_sheen_color_override_g", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
ConVar tf_sheen_color_override_b( "tf_sheen_color_override_b", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
ConVar tf_sheen_color_override_a( "tf_sheen_color_override_a", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
CON_COMMAND_F ( tf_sheen_color_override, "Sheen Color Override RGBA 0-255", 0 )
{
if ( args.ArgC() < 5 )
{
Msg( "requires 4 inputs, r g b a (in 0 - 255 range) \n" );
return;
}
float r = atof( args.Arg(1) ) / 255.0f;
float g = atof( args.Arg(2) ) / 255.0f;
float b = atof( args.Arg(3) ) / 255.0f;
float a = atof( args.Arg(4) ) / 255.0f;
tf_sheen_color_override_r.SetValue( r );
tf_sheen_color_override_g.SetValue( g );
tf_sheen_color_override_b.SetValue( b );
tf_sheen_color_override_a.SetValue( a );
}
ConVar cl_taunt_max_turn_speed( "cl_taunt_max_turn_speed", "0", FCVAR_HIDDEN | FCVAR_CHEAT );
ConVar cl_halloween_kart_turn_speed( "cl_halloween_kart_turn_speed", "100" );
#endif // Staging only
ConVar tf_sheen_framerate( "tf_sheen_framerate", "25", FCVAR_NONE | FCVAR_HIDDEN, "Set Sheen Frame Rate" );
extern ConVar tf_killstreak_alwayson;
ConVar tf_sheen_alpha_firstperson( "tf_sheen_alpha_firstperson", "0.1", FCVAR_NONE, "Set the Alpha Value for first person sheens" );
ConVar tf_killstreakeyes_minkills( "tf_killstreakeyes_minkills", "5", FCVAR_DEVELOPMENTONLY, "min kills to get base eyeglow" );
ConVar tf_killstreakeyes_maxkills( "tf_killstreakeyes_maxkills", "10", FCVAR_DEVELOPMENTONLY, "kills to get the max eye glow effect" );
//ConVar spectate_random_server_basetime( "spectate_random_server_basetime", "240", FCVAR_DEVELOPMENTONLY );
ConVar cl_autorezoom( "cl_autorezoom", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "When set to 1, sniper rifle will re-zoom after firing a zoomed shot." );
ConVar tf_remember_activeweapon( "tf_remember_activeweapon", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "Setting this to 1 will make the active weapon persist between lives." );
ConVar tf_remember_lastswitched( "tf_remember_lastswitched", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "Setting this to 1 will make the 'last weapon' persist between lives." );
ConVar cl_autoreload( "cl_autoreload", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "When set to 1, clip-using weapons will automatically be reloaded whenever they're not being fired." );
ConVar tf_respawn_on_loadoutchanges( "tf_respawn_on_loadoutchanges", "1", FCVAR_ARCHIVE, "When set to 1, you will automatically respawn whenever you change loadouts inside a respawn zone." );
ConVar sb_dontshow_maxplayer_warning( "sb_dontshow_maxplayer_warning", "0", FCVAR_ARCHIVE );
ConVar sb_close_browser_on_connect( "sb_close_browser_on_connect", "1", FCVAR_ARCHIVE );
ConVar tf_spectate_pyrovision( "tf_spectate_pyrovision", "0", FCVAR_ARCHIVE, "When on, spectator will see the world with Pyrovision active", VisionMode_ChangeCallback );
ConVar tf_replay_pyrovision( "tf_replay_pyrovision", "0", FCVAR_ARCHIVE, "When on, replays will be seen with Pyrovision active", VisionMode_ChangeCallback );
ConVar tf_taunt_first_person( "tf_taunt_first_person", "0", FCVAR_NONE, "1 = taunts remain first-person" );
ConVar tf_romevision_opt_in( "tf_romevision_opt_in", "0", FCVAR_ARCHIVE, "Enable Romevision in Mann vs. Machine mode when available." );
ConVar tf_romevision_skip_prompt( "tf_romevision_skip_prompt", "0", FCVAR_ARCHIVE, "If nonzero, skip the prompt about sharing Romevision." );
#ifdef STAGING_ONLY
extern ConVar tf_bountymode_showhealth;
#endif // STAGING_ONLY
#define BDAY_HAT_MODEL "models/effects/bday_hat.mdl"
#define BOMB_HAT_MODEL "models/props_lakeside_event/bomb_temp_hat.mdl"
#define BOMBONOMICON_MODEL "models/props_halloween/bombonomicon.mdl"
IMaterial *g_pHeadLabelMaterial[2] = { NULL, NULL };
void SetupHeadLabelMaterials( void );
extern CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params );
extern int EconWear_ToIntCategory( float flWear );
const char *g_pszHeadGibs[] =
{
"",
"models/player\\gibs\\scoutgib007.mdl",
"models/player\\gibs\\snipergib005.mdl",
"models/player\\gibs\\soldiergib007.mdl",
"models/player\\gibs\\demogib006.mdl",
"models/player\\gibs\\medicgib007.mdl",
"models/player\\gibs\\heavygib007.mdl",
"models/player\\gibs\\pyrogib008.mdl",
"models/player\\gibs\\spygib007.mdl",
"models/player\\gibs\\engineergib006.mdl",
};
const char *g_pszBotHeadGibs[] =
{
"",
"models/bots\\gibs\\scoutbot_gib_head.mdl",
"models/bots\\gibs\\sniperbot_gib_head.mdl",
"models/bots\\gibs\\soldierbot_gib_head.mdl",
"models/bots\\gibs\\demobot_gib_head.mdl",
"models/bots\\gibs\\medicbot_gib_head.mdl",
"models/bots\\gibs\\heavybot_gib_head.mdl",
"models/bots\\gibs\\pyrobot_gib_head.mdl",
"models/bots\\gibs\\spybot_gib_head.mdl",
"models/bots\\gibs\\engineerbot_gib_head.mdl",
};
const char *pszHeadLabelNames[] =
{
"effects/speech_voice_red",
"effects/speech_voice_blue"
};
extern SkyBoxMaterials_t s_PyroSkyboxMaterials;
#define TF_PLAYER_HEAD_LABEL_RED 0
#define TF_PLAYER_HEAD_LABEL_BLUE 1
CLIENTEFFECT_REGISTER_BEGIN( PrecacheInvuln )
CLIENTEFFECT_MATERIAL( "models/effects/invulnfx_blue.vmt" )
CLIENTEFFECT_MATERIAL( "models/effects/invulnfx_red.vmt" )
CLIENTEFFECT_REGISTER_END()
// *********************************************************************************************************
// KillStreak Effect Data
// *********************************************************************************************************
struct killstreak_params_t
{
killstreak_params_t ( int iShaderIndex, const char *pTexture, bool bHasTeamColor,
int sheen_r, int sheen_g, int sheen_b, int sheen_a,
int color1_r, int color1_g, int color1_b, int color1_a,
int color2_r, int color2_g, int color2_b, int color2_a
) {
m_iShaderIndex = iShaderIndex;
m_pTexture = pTexture;
m_bHasTeamColor = bHasTeamColor;
m_sheen_r = (float)sheen_r / 255.0f;
m_sheen_g = (float)sheen_g / 255.0f;
m_sheen_b = (float)sheen_b / 255.0f;
m_sheen_a = (float)sheen_a / 255.0f;
m_color1_r = (float)color1_r / 255.0f;
m_color1_g = (float)color1_g / 255.0f;
m_color1_b = (float)color1_b / 255.0f;
m_color1_a = (float)color1_a / 255.0f;
m_color2_r = (float)color2_r / 255.0f;
m_color2_g = (float)color2_g / 255.0f;
m_color2_b = (float)color2_b / 255.0f;
m_color2_a = (float)color2_a / 255.0f;
}
int m_iShaderIndex;
bool m_bHasTeamColor;
const char *m_pTexture;
float m_sheen_r, m_sheen_g, m_sheen_b, m_sheen_a;
float m_color1_r, m_color1_g, m_color1_b, m_color1_a;
float m_color2_r, m_color2_g, m_color2_b, m_color2_a;
};
// *********************************************************************************************************
// Base Sheen Colors (Team Red)
static const killstreak_params_t g_KillStreakEffectsBase[] =
{
killstreak_params_t( 0, NULL, false, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0 ), // Empty (Index 0)
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", true, 200, 20, 15, 255, 255, 118, 118, 255, 255, 35, 28, 255 ),
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", false, 242, 172, 10, 255, 255, 237, 138, 255, 255, 213, 65, 255 ),
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", false, 255, 75, 5, 255, 255, 111, 5, 255, 255, 137, 31, 255 ),
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", false, 100, 255, 10, 255, 230, 255, 60, 255, 193, 255, 61, 255 ),
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", false, 40, 255, 70, 255, 103, 255, 121, 255, 165, 255, 193, 255 ),
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", false, 105, 20, 255, 255, 105, 20, 255, 255, 185, 145, 255, 255 ),
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", false, 255, 30, 255, 255, 255, 120, 255, 255, 255, 176, 217, 255 ),
};
// *********************************************************************************************************
// Optional Team Color
static const killstreak_params_t g_KillStreakEffectsBlue[] =
{
killstreak_params_t( 0, NULL, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), // Empty (Index 0)
killstreak_params_t( 0, "Effects/AnimatedSheen/animatedsheen0", true, 40, 98, 200, 255, 0, 92, 255, 255, 134, 203, 243, 255 ),
};
// thirdperson medieval
static ConVar tf_medieval_thirdperson( "tf_medieval_thirdperson", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE , "Turns on third-person camera in medieval mode." );
static ConVar tf_medieval_cam_idealdist( "tf_medieval_cam_idealdist", "125", FCVAR_CLIENTDLL | FCVAR_CHEAT ); // thirdperson distance
static ConVar tf_medieval_cam_idealdistright( "tf_medieval_cam_idealdistright", "25", FCVAR_CLIENTDLL | FCVAR_CHEAT ); // thirdperson distance
static ConVar tf_medieval_cam_idealdistup( "tf_medieval_cam_idealdistup", "-10", FCVAR_CLIENTDLL | FCVAR_CHEAT ); // thirdperson distance
static ConVar tf_medieval_cam_idealpitch( "tf_medieval_cam_idealpitch", "0", FCVAR_CLIENTDLL | FCVAR_CHEAT ); // thirdperson pitch
extern ConVar cam_idealpitch;
extern ConVar tf_allow_taunt_switch;
static void PromptAcceptReviveCallback( bool bCancel, void *pContext )
{
if ( bCancel )
{
KeyValues *kv = new KeyValues( "MVM_Revive_Response" );
kv->SetBool( "accepted", false );
engine->ServerCmdKeyValues( kv );
}
}
C_TFPlayerPreviewEffect g_PlayerPreviewEffect;
C_TFPlayerPreviewEffect::C_TFPlayerPreviewEffect() :
m_nPreviewEffect(PREVIEW_EFFECT_NONE)
, m_nTeam(-1)
{
}
void C_TFPlayerPreviewEffect::SetTeam(int nTeam)
{
if ( nTeam == m_nTeam )
return;
m_nTeam = nTeam;
const char *pszMaterial = NULL;
switch ( m_nTeam )
{
case TF_TEAM_BLUE:
pszMaterial = "models/effects/invulnfx_blue.vmt";
break;
case TF_TEAM_RED:
pszMaterial = "models/effects/invulnfx_red.vmt";
break;
default:
break;
}
if ( pszMaterial )
{
m_InvulnerableMaterial.Init( pszMaterial, TEXTURE_GROUP_CLIENT_EFFECTS );
}
else
{
m_InvulnerableMaterial.Shutdown();
}
}
void C_TFPlayerPreviewEffect::Reset()
{
SetEffect(PREVIEW_EFFECT_NONE);
SetTeam(-1);
}
C_EntityDissolve *DissolveEffect( C_BaseEntity *pTarget, float flTime );
void SetAppropriateCamera( C_TFPlayer *pPlayer )
{
if ( pPlayer->IsLocalPlayer() == false )
return;
if ( TFGameRules() &&
( ( TFGameRules()->IsInMedievalMode() && tf_medieval_thirdperson.GetBool() )
|| pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) )
{
g_ThirdPersonManager.SetForcedThirdPerson( true );
Vector offset( tf_medieval_cam_idealdist.GetFloat(), tf_medieval_cam_idealdistright.GetFloat(), tf_medieval_cam_idealdistup.GetFloat() );
g_ThirdPersonManager.SetDesiredCameraOffset( offset );
cam_idealpitch.SetValue( tf_medieval_cam_idealpitch.GetFloat() );
::input->CAM_ToThirdPerson();
pPlayer->ThirdPersonSwitch( true );
}
else
{
g_ThirdPersonManager.SetForcedThirdPerson( false );
}
}
// -------------------------------------------------------------------------------- //
// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
// -------------------------------------------------------------------------------- //
class C_TEPlayerAnimEvent : public C_BaseTempEntity
{
public:
DECLARE_CLASS( C_TEPlayerAnimEvent, C_BaseTempEntity );
DECLARE_CLIENTCLASS();
virtual void PostDataUpdate( DataUpdateType_t updateType )
{
VPROF( "C_TEPlayerAnimEvent::PostDataUpdate" );
// Create the effect.
if ( m_iPlayerIndex == TF_PLAYER_INDEX_NONE )
return;
EHANDLE hPlayer = cl_entitylist->GetNetworkableHandle( m_iPlayerIndex );
if ( !hPlayer )
return;
C_TFPlayer *pPlayer = dynamic_cast< C_TFPlayer* >( hPlayer.Get() );
if ( pPlayer && !pPlayer->IsDormant() )
{
// Ignore anim events that are also played on the client.
PlayerAnimEvent_t animEvent = (PlayerAnimEvent_t) m_iEvent.Get();
switch ( animEvent )
{
case PLAYERANIMEVENT_STUN_BEGIN:
case PLAYERANIMEVENT_STUN_MIDDLE:
case PLAYERANIMEVENT_STUN_END:
case PLAYERANIMEVENT_PASSTIME_THROW_BEGIN:
case PLAYERANIMEVENT_PASSTIME_THROW_MIDDLE:
case PLAYERANIMEVENT_PASSTIME_THROW_END:
case PLAYERANIMEVENT_PASSTIME_THROW_CANCEL:
break; // ignore these
default:
pPlayer->DoAnimationEvent( animEvent, m_nData );
break;
};
}
}
public:
CNetworkVar( int, m_iPlayerIndex );
CNetworkVar( int, m_iEvent );
CNetworkVar( int, m_nData );
};
IMPLEMENT_CLIENTCLASS_EVENT( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent, CTEPlayerAnimEvent );
//-----------------------------------------------------------------------------
// Data tables and prediction tables.
//-----------------------------------------------------------------------------
BEGIN_RECV_TABLE_NOBASE( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent )
RecvPropInt( RECVINFO( m_iPlayerIndex ) ),
RecvPropInt( RECVINFO( m_iEvent ) ),
RecvPropInt( RECVINFO( m_nData ) )
END_RECV_TABLE()
//=============================================================================
//
// Ragdoll
//
// ----------------------------------------------------------------------------- //
// Client ragdoll entity.
// ----------------------------------------------------------------------------- //
ConVar cl_ragdoll_physics_enable( "cl_ragdoll_physics_enable", "1", 0, "Enable/disable ragdoll physics." );
ConVar cl_ragdoll_fade_time( "cl_ragdoll_fade_time", "15", FCVAR_CLIENTDLL );
ConVar cl_ragdoll_forcefade( "cl_ragdoll_forcefade", "0", FCVAR_CLIENTDLL );
ConVar cl_ragdoll_pronecheck_distance( "cl_ragdoll_pronecheck_distance", "64", FCVAR_GAMEDLL );
class C_TFRagdoll : public C_BaseFlex
{
public:
DECLARE_CLASS( C_TFRagdoll, C_BaseFlex );
DECLARE_CLIENTCLASS();
C_TFRagdoll();
~C_TFRagdoll();
virtual void OnDataChanged( DataUpdateType_t type );
IRagdoll* GetIRagdoll() const;
void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName );
void ClientThink( void );
// Deal with recording
virtual void GetToolRecordingState( KeyValues *msg );
void StartFadeOut( float fDelay );
void EndFadeOut();
void DissolveEntity( CBaseEntity* pEnt );
EHANDLE GetPlayerHandle( void )
{
if ( m_iPlayerIndex == TF_PLAYER_INDEX_NONE )
return NULL;
return cl_entitylist->GetNetworkableHandle( m_iPlayerIndex );
}
bool IsRagdollVisible();
float GetBurnStartTime() { return m_flBurnEffectStartTime; }
virtual void BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed );
virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights );
bool IsDeathAnim() { return m_bDeathAnim; }
int GetDamageCustom() { return m_iDamageCustom; }
virtual bool GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld );
int GetClass() { return m_iClass; }
float GetPercentInvisible( void ) { return m_flPercentInvisible; }
bool IsCloaked( void ) { return m_bCloaked; }
int GetRagdollTeam( void ){ return m_iTeam; }
float GetHeadScale() const { return m_flHeadScale; }
float GetTorsoScale() const { return m_flTorsoScale; }
float GetHandScale() const { return m_flHandScale; }
private:
C_TFRagdoll( const C_TFRagdoll & ) {}
void Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity );
void CreateTFRagdoll();
void CreateTFGibs( bool bDestroyRagdoll = true, bool bCurrentPosition = false );
void CreateWearableGibs( bool bDisguiseWearables );
void CreateTFHeadGib();
virtual float FrameAdvance( float flInterval );
bool IsDecapitation();
bool IsHeadSmash();
virtual int InternalDrawModel( int flags );
private:
CNetworkVector( m_vecRagdollVelocity );
CNetworkVector( m_vecRagdollOrigin );
int m_iPlayerIndex;
float m_fDeathTime;
bool m_bFadingOut;
bool m_bGib;
bool m_bBurning;
bool m_bElectrocuted;
bool m_bBatted;
bool m_bDissolving;
bool m_bFeignDeath;
bool m_bWasDisguised;
bool m_bCloaked;
bool m_bBecomeAsh;
int m_iDamageCustom;
bool m_bGoldRagdoll;
bool m_bIceRagdoll;
CountdownTimer m_freezeTimer;
CountdownTimer m_frozenTimer;
int m_iTeam;
int m_iClass;
float m_flBurnEffectStartTime; // start time of burning, or 0 if not burning
bool m_bRagdollOn;
bool m_bDeathAnim;
bool m_bOnGround;
bool m_bFixedConstraints;
matrix3x4_t m_mHeadAttachment;
bool m_bBaseTransform;
float m_flPercentInvisible;
float m_flTimeToDissolve;
bool m_bCritOnHardHit; // plays the red mist particle effect
float m_flHeadScale;
float m_flTorsoScale;
float m_flHandScale;
CMaterialReference m_MaterialOverride;
CUtlVector<CHandle<CEconWearable > > m_hRagWearables; // These look like they are no longer used?
CUtlVector< CHandle< CEconWearable > > m_hClientWearables; // wearables on the ragdoll that are "following" it
bool m_bCreatedWhilePlaybackSkipping;
};
IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_TFRagdoll, DT_TFRagdoll, CTFRagdoll )
RecvPropVector( RECVINFO(m_vecRagdollOrigin) ),
RecvPropInt( RECVINFO( m_iPlayerIndex ) ),
RecvPropVector( RECVINFO(m_vecForce) ),
RecvPropVector( RECVINFO(m_vecRagdollVelocity) ),
RecvPropInt( RECVINFO( m_nForceBone ) ),
RecvPropBool( RECVINFO( m_bGib ) ),
RecvPropBool( RECVINFO( m_bBurning ) ),
RecvPropBool( RECVINFO( m_bElectrocuted ) ),
RecvPropBool( RECVINFO( m_bFeignDeath ) ),
RecvPropBool( RECVINFO( m_bWasDisguised ) ),
RecvPropBool( RECVINFO( m_bOnGround ) ),
RecvPropBool( RECVINFO( m_bCloaked ) ),
RecvPropBool( RECVINFO( m_bBecomeAsh ) ),
RecvPropInt( RECVINFO( m_iDamageCustom ) ),
RecvPropInt( RECVINFO( m_iTeam ) ),
RecvPropInt( RECVINFO( m_iClass ) ),
RecvPropUtlVector( RECVINFO_UTLVECTOR( m_hRagWearables ), 8, RecvPropEHandle(NULL, 0, 0) ),
RecvPropBool( RECVINFO( m_bGoldRagdoll ) ),
RecvPropBool( RECVINFO( m_bIceRagdoll ) ),
RecvPropBool( RECVINFO( m_bCritOnHardHit ) ),
RecvPropFloat( RECVINFO( m_flHeadScale ) ),
RecvPropFloat( RECVINFO( m_flTorsoScale ) ),
RecvPropFloat( RECVINFO( m_flHandScale ) ),
END_RECV_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
C_TFRagdoll::C_TFRagdoll()
{
m_iPlayerIndex = TF_PLAYER_INDEX_NONE;
m_fDeathTime = -1;
m_bFadingOut = false;
m_bGib = false;
m_bBurning = false;
m_bElectrocuted = false;
m_bBatted = false;
m_bDissolving = false;
m_bFeignDeath = false;
m_bWasDisguised = false;
m_bCloaked = false;
m_bBecomeAsh = false;
m_flBurnEffectStartTime = 0.0f;
m_iDamageCustom = 0;
m_bGoldRagdoll = false;
m_bIceRagdoll = false;
m_freezeTimer.Invalidate();
m_frozenTimer.Invalidate();
m_iTeam = -1;
m_iClass = -1;
m_nForceBone = -1;
m_bRagdollOn = false;
m_bDeathAnim = false;
m_bOnGround = false;
m_bBaseTransform = false;
m_bFixedConstraints = false;
m_flTimeToDissolve = 0.3f;
m_bCritOnHardHit = false;
m_flHeadScale = 1.f;
m_flTorsoScale = 1.f;
m_flHandScale = 1.f;
UseClientSideAnimation();
m_bCreatedWhilePlaybackSkipping = engine->IsSkippingPlayback();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
C_TFRagdoll::~C_TFRagdoll()
{
PhysCleanupFrictionSounds( this );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSourceEntity -
//-----------------------------------------------------------------------------
void C_TFRagdoll::Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity )
{
if ( !pSourceEntity )
return;
VarMapping_t *pSrc = pSourceEntity->GetVarMapping();
VarMapping_t *pDest = GetVarMapping();
// Find all the VarMapEntry_t's that represent the same variable.
for ( int i = 0; i < pDest->m_Entries.Count(); i++ )
{
VarMapEntry_t *pDestEntry = &pDest->m_Entries[i];
for ( int j=0; j < pSrc->m_Entries.Count(); j++ )
{
VarMapEntry_t *pSrcEntry = &pSrc->m_Entries[j];
if ( !Q_strcmp( pSrcEntry->watcher->GetDebugName(), pDestEntry->watcher->GetDebugName() ) )
{
pDestEntry->watcher->Copy( pSrcEntry->watcher );
break;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Setup vertex weights for drawing
//-----------------------------------------------------------------------------
void C_TFRagdoll::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
{
// While we're dying, we want to mimic the facial animation of the player. Once they're dead, we just stay as we are.
EHANDLE hPlayer = GetPlayerHandle();
if ( ( hPlayer && hPlayer->IsAlive()) || !hPlayer )
{
BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
}
else if ( hPlayer )
{
hPlayer->SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTrace -
// iDamageType -
// *pCustomImpactName -
//-----------------------------------------------------------------------------
void C_TFRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
{
VPROF( "C_TFRagdoll::ImpactTrace" );
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if( !pPhysicsObject )
return;
Vector vecDir;
VectorSubtract( pTrace->endpos, pTrace->startpos, vecDir );
if ( iDamageType == DMG_BLAST )
{
// Adjust the impact strength and apply the force at the center of mass.
vecDir *= 4000;
pPhysicsObject->ApplyForceCenter( vecDir );
}
else
{
// Find the apporx. impact point.
Vector vecHitPos;
VectorMA( pTrace->startpos, pTrace->fraction, vecDir, vecHitPos );
VectorNormalize( vecDir );
// Adjust the impact strength and apply the force at the impact point..
vecDir *= 4000;
pPhysicsObject->ApplyForceOffset( vecDir, vecHitPos );
}
m_pRagdoll->ResetRagdollSleepAfterTime();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void C_TFRagdoll::CreateTFRagdoll()
{
// Get the player.
C_TFPlayer *pPlayer = NULL;
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
}
int nModelIndex = -1;
if ( pPlayer && pPlayer->GetPlayerClass() && !pPlayer->ShouldDrawSpyAsDisguised() )
{
nModelIndex = modelinfo->GetModelIndex( pPlayer->GetPlayerClass()->GetModelName() );
}
else
{
TFPlayerClassData_t *pData = GetPlayerClassData( m_iClass );
if ( pData )
{
nModelIndex = modelinfo->GetModelIndex( pData->GetModelName() );
}
}
if ( pPlayer )
{
m_flHeadScale = pPlayer->GetHeadScale();
m_flTorsoScale = pPlayer->GetTorsoScale();
m_flHandScale = pPlayer->GetHandScale();
}
if ( nModelIndex != -1 )
{
SetModelIndex( nModelIndex );
if ( m_iTeam == TF_TEAM_RED )
{
m_nSkin = 0;
}
else
{
m_nSkin = 1;
}
}
// Check for any special player skin override behaviour.
if ( pPlayer && pPlayer->BRenderAsZombie() )
{
C_TFPlayer::AdjustSkinIndexForZombie( m_iClass, m_nSkin );
}
// We check against new-style (special flag to indicate goldification) and old style (custom damage type)
// to maintain old demos involving the golden wrench.
if ( m_bGoldRagdoll || m_iDamageCustom == TF_DMG_CUSTOM_GOLD_WRENCH )
{
EmitSound( "Saxxy.TurnGold" );
m_bFixedConstraints = true;
}
if ( m_bIceRagdoll )
{
EmitSound( "Icicle.TurnToIce" );
ParticleProp()->Create( "xms_icicle_impact_dryice", PATTACH_ABSORIGIN_FOLLOW );
m_freezeTimer.Start( RandomFloat( 0.1f, 0.75f ) );
m_frozenTimer.Start( RandomFloat( 9.0f, 11.0f ) );
}
#ifdef _DEBUG
DevMsg( 2, "CreateTFRagdoll %d %d\n", gpGlobals->framecount, pPlayer ? pPlayer->entindex() : 0 );
#endif
if ( pPlayer && !pPlayer->IsDormant() )
{
// Move my current model instance to the ragdoll's so decals are preserved.
pPlayer->SnatchModelInstance( this );
VarMapping_t *varMap = GetVarMapping();
// Copy all the interpolated vars from the player entity.
// The entity uses the interpolated history to get bone velocity.
if ( !pPlayer->IsLocalPlayer() && pPlayer->IsInterpolationEnabled() )
{
Interp_Copy( pPlayer );
SetAbsAngles( pPlayer->GetRenderAngles() );
GetRotationInterpolator().Reset();
m_flAnimTime = pPlayer->m_flAnimTime;
SetSequence( pPlayer->GetSequence() );
m_flPlaybackRate = pPlayer->GetPlaybackRate();
}
else
{
// This is the local player, so set them in a default
// pose and slam their velocity, angles and origin
SetAbsOrigin( /* m_vecRagdollOrigin : */ pPlayer->GetRenderOrigin() );
SetAbsAngles( pPlayer->GetRenderAngles() );
SetAbsVelocity( m_vecRagdollVelocity );
// Hack! Find a neutral standing pose or use the idle.
int iSeq = LookupSequence( "RagdollSpawn" );
if ( iSeq == -1 )
{
Assert( false );
iSeq = 0;
}
SetSequence( iSeq );
SetCycle( 0.0 );
Interp_Reset( varMap );
}
if ( !m_bFeignDeath || m_bWasDisguised )
{
pPlayer->RecalcBodygroupsIfDirty();
m_nBody = pPlayer->GetBody();
}
}
else
{
// Overwrite network origin so later interpolation will use this position.
SetNetworkOrigin( m_vecRagdollOrigin );
SetAbsOrigin( m_vecRagdollOrigin );
SetAbsVelocity( m_vecRagdollVelocity );
Interp_Reset( GetVarMapping() );
}
if ( IsCloaked() )
{
AddEffects( EF_NOSHADOW );
}
// Play a death anim depending on the custom damage type.
bool bPlayDeathInAir = false;
int iDeathSeq = -1;
if ( pPlayer && !m_bGoldRagdoll )
{
iDeathSeq = pPlayer->m_Shared.GetSequenceForDeath( this, m_bBurning, m_iDamageCustom );
if ( m_bDissolving && !m_bGib )
{
bPlayDeathInAir = true;
iDeathSeq = LookupSequence( "dieviolent" );
}
// did we find a death sequence?
if ( iDeathSeq > -1 && (m_iDamageCustom != TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING) &&
(m_iDamageCustom != TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH) && (m_iDamageCustom != TF_DMG_CUSTOM_TAUNTATK_ALLCLASS_GUITAR_RIFF) )
{
// we only want to show the death anims 25% of the time, unless this is a demoman kill taunt
// always play backstab animations for the ice ragdoll
if ( !m_bIceRagdoll && !tf_always_deathanim.GetBool() && (RandomFloat( 0, 1 ) > 0.25f) )
{
iDeathSeq = -1;
}
}
}
bool bPlayDeathAnim = cl_ragdoll_physics_enable.GetBool() && (iDeathSeq > -1) && pPlayer;
if ( !m_bOnGround && bPlayDeathAnim && !bPlayDeathInAir )
bPlayDeathAnim = false; // Don't play most death anims in the air (headshot, etc).
if ( bPlayDeathAnim )
{
// Set our position for a death anim.
SetAbsOrigin( pPlayer->GetRenderOrigin() );
SetAbsAngles( pPlayer->GetRenderAngles() );
SetAbsVelocity( Vector(0,0,0) );
m_vecForce = Vector(0,0,0);
// Play the death anim.
ResetSequence( iDeathSeq );
m_bDeathAnim = true;
}
else if ( m_bIceRagdoll )
{
// couldn't play death anim because we were in midair - go ridig immediately
m_freezeTimer.Invalidate();
m_frozenTimer.Invalidate();
m_bFixedConstraints = true;
}
// Fade out the ragdoll in a while
StartFadeOut( cl_ragdoll_fade_time.GetFloat() );
SetNextClientThink( CLIENT_THINK_ALWAYS );
// Copy over impact attachments.
if ( pPlayer )
{
pPlayer->CreateBoneAttachmentsFromWearables( this, m_bWasDisguised );
pPlayer->MoveBoneAttachments( this );
}
if ( m_iDamageCustom == TF_DMG_CUSTOM_KART )
{
m_vecForce *= 100.0f;
SetAbsVelocity( GetAbsVelocity() + m_vecForce );
ApplyAbsVelocityImpulse( m_vecForce );
}
// Save ragdoll information.
if ( cl_ragdoll_physics_enable.GetBool() && !m_bDeathAnim )
{
// Make us a ragdoll..
m_nRenderFX = kRenderFxRagdoll;
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
matrix3x4_t currentBones[MAXSTUDIOBONES];
const float boneDt = 0.05f;
// We have to make sure that we're initting this client ragdoll off of the same model.
// GetRagdollInitBoneArrays uses the *player* Hdr, which may be a different model than
// the ragdoll Hdr, if we try to create a ragdoll in the same frame that the player
// changes their player model.
CStudioHdr *pRagdollHdr = GetModelPtr();
CStudioHdr *pPlayerHdr = pPlayer ? pPlayer->GetModelPtr() : NULL;
bool bChangedModel = false;
if ( pRagdollHdr && pPlayerHdr )
{
bChangedModel = pRagdollHdr->GetVirtualModel() != pPlayerHdr->GetVirtualModel();
// Assert( !bChangedModel && "C_TFRagdoll::CreateTFRagdoll: Trying to create ragdoll with a different model than the player it's based on" );
}
bool bBoneArraysInited;
if ( pPlayer && !pPlayer->IsDormant() && !bChangedModel )
{
bBoneArraysInited = pPlayer->GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
}
else
{
bBoneArraysInited = GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
}
if ( bBoneArraysInited )
{
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt, m_bFixedConstraints );
}
}
else
{
ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), RENDER_GROUP_TRANSLUCENT_ENTITY );
}
if ( m_bBurning )
{
m_flBurnEffectStartTime = gpGlobals->curtime;
ParticleProp()->Create( "burningplayer_corpse", PATTACH_ABSORIGIN_FOLLOW );
}
if ( m_bElectrocuted )
{
const char *pEffectName = ( m_iTeam == TF_TEAM_RED ) ? "electrocuted_red" : "electrocuted_blue";
ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
C_BaseEntity::EmitSound( "TFPlayer.MedicChargedDeath" );
}
if ( m_bBecomeAsh && !m_bDissolving && !m_bGib )
{
ParticleProp()->Create( "drg_fiery_death", PATTACH_ABSORIGIN_FOLLOW );
m_flTimeToDissolve = 0.5f;
}
if ( pPlayer->HasBombinomiconEffectOnDeath() && !m_bGib && !m_bDissolving )
{
m_flTimeToDissolve = 1.2f;
}
// Birthday mode.
if ( pPlayer && TFGameRules() && TFGameRules()->IsBirthday() )
{
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
breakablepropparams_t breakParams( m_vecRagdollOrigin, GetRenderAngles(), m_vecRagdollVelocity, angularImpulse );
breakParams.impactEnergyScale = 1.0f;
pPlayer->DropPartyHat( breakParams, m_vecRagdollVelocity.GetForModify() );
}
const char *materialOverrideFilename = NULL;
if ( m_bFixedConstraints )
{
if ( m_bGoldRagdoll )
{
// Gold texture...we've been turned into a golden corpse!
materialOverrideFilename = "models/player/shared/gold_player.vmt";
}
}
if ( m_bIceRagdoll )
{
// Ice texture...we've been turned into an ice statue!
materialOverrideFilename = "models/player/shared/ice_player.vmt";
}
if ( materialOverrideFilename )
{
// Ice texture...we've been turned into an ice statue!
m_MaterialOverride.Init( materialOverrideFilename, TEXTURE_GROUP_CLIENT_EFFECTS );
// override all of our wearables, too
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
{
if ( pEntity->GetFollowedEntity() == this )
{
CEconEntity *pItem = dynamic_cast< CEconEntity * >( pEntity );
if ( pItem )
{
pItem->SetMaterialOverride( m_iTeam, materialOverrideFilename );
}
}
}
}
}
float C_TFRagdoll::FrameAdvance( float flInterval )
{
// if we're in the process of becoming an ice statue, freeze
if ( m_freezeTimer.HasStarted() && !m_freezeTimer.IsElapsed() )
{
// play the backstab anim until the timer is up
return BaseClass::FrameAdvance( flInterval );
}
if ( m_frozenTimer.HasStarted() )
{
if ( m_frozenTimer.IsElapsed() )
{
// holding frozen time is up - turn to a stiff ragdoll and fall over
m_frozenTimer.Invalidate();
m_nRenderFX = kRenderFxRagdoll;
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
matrix3x4_t currentBones[MAXSTUDIOBONES];
const float boneDt = 0.1f;
GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt, true );
SetAbsVelocity( Vector( 0,0,0 ) );
m_bRagdollOn = true;
}
else
{
// don't move at all
return 0.0f;
}
}
float fRes = BaseClass::FrameAdvance( flInterval );
if ( !m_bRagdollOn && IsSequenceFinished() && m_bDeathAnim )
{
m_nRenderFX = kRenderFxRagdoll;
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
matrix3x4_t currentBones[MAXSTUDIOBONES];
const float boneDt = 0.1f;
if ( !GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ) )
{
Warning( "C_TFRagdoll::FrameAdvance GetRagdollInitBoneArrays failed.\n" );
}
else
{
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt );
}
SetAbsVelocity( Vector( 0,0,0 ) );
m_bRagdollOn = true;
// Make it fade out.
StartFadeOut( cl_ragdoll_fade_time.GetFloat() );
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
return fRes;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFRagdoll::CreateTFHeadGib( void )
{
C_TFPlayer *pPlayer = NULL;
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
}
if ( pPlayer && ((pPlayer->m_hFirstGib == NULL) || m_bFeignDeath) )
{
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
VectorNormalize( vecVelocity );
pPlayer->CreatePlayerGibs( m_vecRagdollOrigin, vecVelocity, m_vecForce.Length(), m_bBurning, false, true );
// Decap Death Camera is disorienting on range Decaps (aka bullets)
// Use normal Deathcam
if ( m_iDamageCustom == TF_DMG_CUSTOM_HEADSHOT_DECAPITATION )
{
pPlayer->m_hHeadGib = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFRagdoll::CreateTFGibs( bool bDestroyRagdoll, bool bCurrentPosition )
{
C_TFPlayer *pPlayer = NULL;
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
}
if ( pPlayer && pPlayer->HasBombinomiconEffectOnDeath() )
{
m_vecForce *= 2.0f;
m_vecForce.z *= 3.0f;
DispatchParticleEffect( TFGameRules()->IsHolidayActive( kHoliday_Halloween ) ? "bombinomicon_burningdebris_halloween" : "bombinomicon_burningdebris",
bCurrentPosition ? GetAbsOrigin() : m_vecRagdollOrigin, GetAbsAngles() );
EmitSound( "Bombinomicon.Explode" );
}
if ( pPlayer && ((pPlayer->m_hFirstGib == NULL) || m_bFeignDeath) )
{
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
VectorNormalize( vecVelocity );
pPlayer->CreatePlayerGibs( bCurrentPosition ? pPlayer->GetRenderOrigin() : m_vecRagdollOrigin, vecVelocity, m_vecForce.Length(), m_bBurning );
}
if ( pPlayer )
{
if ( TFGameRules() && TFGameRules()->IsBirthdayOrPyroVision() )
{
DispatchParticleEffect( "bday_confetti", pPlayer->GetAbsOrigin() + Vector(0,0,32), vec3_angle );
if ( TFGameRules() && TFGameRules()->IsBirthday() )
{
C_BaseEntity::EmitSound( "Game.HappyBirthday" );
}
}
else if ( m_bCritOnHardHit && !UTIL_IsLowViolence() )
{
DispatchParticleEffect( "tfc_sniper_mist", pPlayer->WorldSpaceCenter(), vec3_angle );
}
}
if ( bDestroyRagdoll )
{
EndFadeOut();
}
else
{
SetRenderMode( kRenderNone );
UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose: Separate from CreateTFGibs so we can easily remove it if we don't like it...
//-----------------------------------------------------------------------------
void C_TFRagdoll::CreateWearableGibs( bool bDisguiseWearables )
{
C_TFPlayer *pPlayer = NULL;
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
}
if ( !pPlayer )
return;
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
VectorNormalize( vecVelocity );
pPlayer->CreatePlayerGibs( m_vecRagdollOrigin, vecVelocity, m_vecForce.Length(), m_bBurning, true, false, bDisguiseWearables );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
//-----------------------------------------------------------------------------
void C_TFRagdoll::OnDataChanged( DataUpdateType_t type )
{
BaseClass::OnDataChanged( type );
if ( type == DATA_UPDATE_CREATED )
{
bool bCreateRagdoll = true;
// Get the player.
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
// If we're getting the initial update for this player (e.g., after resetting entities after
// lots of packet loss, then don't create gibs, ragdolls if the player and it's gib/ragdoll
// both show up on same frame.
if ( abs( hPlayer->GetCreationTick() - gpGlobals->tickcount ) < TIME_TO_TICKS( 1.0f ) )
{
bCreateRagdoll = false;
}
}
else if ( C_BasePlayer::GetLocalPlayer() )
{
// Ditto for recreation of the local player
if ( abs( C_BasePlayer::GetLocalPlayer()->GetCreationTick() - gpGlobals->tickcount ) < TIME_TO_TICKS( 1.0f ) )
{
bCreateRagdoll = false;
}
}
// Prevent replays from creating ragdolls on the first frame of playback after skipping through playback.
// If a player died (leaving a ragdoll) previous to the first frame of replay playback,
// their ragdoll wasn't yet initialized because OnDataChanged events are queued but not processed
// until the first render.
if ( engine->IsPlayingDemo() )
{
bCreateRagdoll = !m_bCreatedWhilePlaybackSkipping;
}
if ( GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_GRAND_SLAM )
{
m_bBatted = true;
}
C_TFPlayer *pPlayer = ToTFPlayer( hPlayer.Get() );
bool bMiniBoss = ( pPlayer && pPlayer->IsMiniBoss() ) ? true : false;
if ( GetDamageCustom() == TF_DMG_CUSTOM_PLASMA )
{
if ( !m_bBecomeAsh && !bMiniBoss )
{
m_bDissolving = true;
}
m_bGib = false;
}
if ( GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED )
{
if ( !m_bBecomeAsh && !bMiniBoss )
{
m_bDissolving = true;
}
m_bGib = true;
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
// Don't gib zombies, always just ragdoll
if ( pPlayer )
{
if ( pPlayer->BRenderAsZombie() )
{
m_bGib = false;
}
pPlayer->UpdateMVMEyeGlowEffect( false );
}
if ( bCreateRagdoll )
{
if ( m_bGib )
{
CreateTFGibs( !m_bDissolving );
}
else
{
CreateTFRagdoll();
if ( IsDecapitation() )
{
CreateTFHeadGib();
EmitSound( "TFPlayer.Decapitated" );
bool bBlood = true;
if ( TFGameRules() && ( TFGameRules()->UseSillyGibs() ||
( TFGameRules()->IsMannVsMachineMode() && hPlayer && hPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) )
{
bBlood = false;
}
if ( bBlood )
{
ParticleProp()->Create( "blood_decap", PATTACH_POINT_FOLLOW, "head" );
}
}
}
m_bNoModelParticles = true;
// Drop wearables (hats, etc)
CreateWearableGibs( m_bWasDisguised );
}
}
else
{
if ( !cl_ragdoll_physics_enable.GetBool() )
{
// Don't let it set us back to a ragdoll with data from the server.
m_nRenderFX = kRenderFxNone;
}
}
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int C_TFRagdoll::InternalDrawModel( int flags )
{
if ( m_MaterialOverride.IsValid() )
{
modelrender->ForcedMaterialOverride( m_MaterialOverride );
}
int ret = BaseClass::InternalDrawModel( flags );
if ( m_MaterialOverride.IsValid() )
{
modelrender->ForcedMaterialOverride( NULL );
}
return ret;
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool C_TFRagdoll::IsDecapitation()
{
return (cl_ragdoll_fade_time.GetFloat() > 5.f) &&
((m_iDamageCustom == TF_DMG_CUSTOM_DECAPITATION)
|| (m_iDamageCustom == TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING)
|| (m_iDamageCustom == TF_DMG_CUSTOM_DECAPITATION_BOSS)
|| (m_iDamageCustom == TF_DMG_CUSTOM_HEADSHOT_DECAPITATION)
|| (m_iDamageCustom == TF_DMG_CUSTOM_MERASMUS_DECAPITATION) );
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool C_TFRagdoll::IsHeadSmash()
{
return ((cl_ragdoll_fade_time.GetFloat() > 5.f) && (m_iDamageCustom == TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH));
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool C_TFRagdoll::GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld )
{
int iHeadAttachment = LookupAttachment( "head" );
if ( IsDecapitation() && (iAttachment == iHeadAttachment) )
{
MatrixCopy( m_mHeadAttachment, attachmentToWorld );
return true;
}
else
{
return BaseClass::GetAttachment( iAttachment, attachmentToWorld );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : IRagdoll*
//-----------------------------------------------------------------------------
IRagdoll* C_TFRagdoll::GetIRagdoll() const
{
return m_pRagdoll;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_TFRagdoll::IsRagdollVisible()
{
Vector vMins = Vector(-1,-1,-1); //WorldAlignMins();
Vector vMaxs = Vector(1,1,1); //WorldAlignMaxs();
Vector origin = GetAbsOrigin();
if( !engine->IsBoxInViewCluster( vMins + origin, vMaxs + origin) )
{
return false;
}
else if( engine->CullBox( vMins + origin, vMaxs + origin ) )
{
return false;
}
return true;
}
#define DISSOLVE_FADE_IN_START_TIME 0.0f
#define DISSOLVE_FADE_IN_END_TIME 1.0f
#define DISSOLVE_FADE_OUT_MODEL_START_TIME 1.9f
#define DISSOLVE_FADE_OUT_MODEL_END_TIME 2.0f
#define DISSOLVE_FADE_OUT_START_TIME 2.0f
#define DISSOLVE_FADE_OUT_END_TIME 2.0f
void C_TFRagdoll::ClientThink( void )
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
// Store off the un-shrunken head location for blood spurts.
if ( IsDecapitation() )
{
int iAttach = LookupAttachment( "head" );
m_bBaseTransform = true;
BaseClass::GetAttachment( iAttach, m_mHeadAttachment );
m_bBaseTransform = false;
m_BoneAccessor.SetReadableBones( 0 );
SetupBones( NULL, -1, BONE_USED_BY_ATTACHMENT, gpGlobals->curtime );
}
if ( m_bCloaked && m_flPercentInvisible < 1.f )
{
m_flPercentInvisible += gpGlobals->frametime;
if ( m_flPercentInvisible > 1.f )
{
m_flPercentInvisible = 1.f;
}
}
C_TFPlayer *pPlayer = ToTFPlayer( GetPlayerHandle() );
bool bBombinomicon = ( pPlayer && pPlayer->HasBombinomiconEffectOnDeath() );
if ( !m_bGib )
{
if ( m_bDissolving )
{
m_bDissolving = false;
m_flTimeToDissolve = 1.2f;
DissolveEntity( this );
EmitSound( "TFPlayer.Dissolve" );
// Dissolve all cosmetics as well
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
{
if ( pEntity->GetFollowedEntity() == this )
{
CEconEntity *pItem = dynamic_cast< CEconEntity * >( pEntity );
if ( pItem )
{
DissolveEntity( pItem );
}
}
}
}
else if ( bBombinomicon && ( GetFlags() & FL_DISSOLVING ) )
{
m_flTimeToDissolve -= gpGlobals->frametime;
if ( m_flTimeToDissolve <= 0 )
{
CreateTFGibs( true, true );
}
}
else if ( m_bBecomeAsh )
{
m_flTimeToDissolve -= gpGlobals->frametime;
if ( m_flTimeToDissolve <= 0 )
{
if ( bBombinomicon )
{
CreateTFGibs( true, true );
}
else
{
// Hide the ragdoll and stop everything but the ash particle effect
AddEffects( EF_NODRAW );
ParticleProp()->StopParticlesNamed( "drg_fiery_death", true, true );
// Hide all cosmetics
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
{
if ( pEntity->GetFollowedEntity() == this )
{
CEconEntity *pItem = dynamic_cast< CEconEntity * >( pEntity );
if ( pItem )
{
pItem->AddEffects( EF_NODRAW );
}
}
}
}
return;
}
}
else if ( bBombinomicon )
{
m_flTimeToDissolve -= gpGlobals->frametime;
if ( m_flTimeToDissolve <= 0 )
{
CreateTFGibs( true, true );
return;
}
}
}
// Gibbing
else
{
if ( m_bDissolving )
{
m_flTimeToDissolve -= gpGlobals->frametime;
if ( m_flTimeToDissolve <= 0 )
{
m_bDissolving = false;
if ( pPlayer )
{
if ( bBombinomicon )
{
CreateTFGibs( true, true );
}
else
{
for ( int i=0; i<pPlayer->m_hSpawnedGibs.Count(); i++ )
{
C_BaseEntity* pGib = pPlayer->m_hSpawnedGibs[i].Get();
if ( pGib )
{
pGib->SetAbsVelocity( vec3_origin );
DissolveEntity( pGib );
pGib->ParticleProp()->StopParticlesInvolving( pGib );
}
}
}
}
EndFadeOut();
}
return;
}
}
// Fade us away...
if ( m_bFadingOut == true )
{
int iAlpha = GetRenderColor().a;
int iFadeSpeed = 600.0f;
iAlpha = MAX( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 );
SetRenderMode( kRenderTransAlpha );
SetRenderColorA( iAlpha );
if ( iAlpha == 0 )
{
// Remove clientside ragdoll.
EndFadeOut();
}
return;
}
// If the player is looking at us, delay the fade.
if ( IsRagdollVisible() )
{
if ( cl_ragdoll_forcefade.GetBool() )
{
m_bFadingOut = true;
float flDelay = cl_ragdoll_fade_time.GetFloat() * 0.33f;
m_fDeathTime = gpGlobals->curtime + flDelay;
RemoveAllDecals();
}
// Fade out after the specified delay.
StartFadeOut( cl_ragdoll_fade_time.GetFloat() * 0.33f );
return;
}
// Remove us if our death time has passed.
if ( m_fDeathTime < gpGlobals->curtime )
{
EndFadeOut();
return;
}
// Fire an event if we were batted by the scout's taunt kill and we have come to rest.
if ( m_bBatted )
{
Vector vVelocity;
EstimateAbsVelocity( vVelocity );
if ( vVelocity.LengthSqr() == 0.f )
{
m_bBatted = false;
IGameEvent *event = gameeventmanager->CreateEvent( "scout_slamdoll_landed" );
if ( event )
{
Vector absOrigin = GetAbsOrigin();
event->SetInt( "target_index", m_iPlayerIndex );
event->SetFloat( "x", absOrigin.x );
event->SetFloat( "y", absOrigin.y );
event->SetFloat( "z", absOrigin.z );
gameeventmanager->FireEventClientSide( event );
}
}
}
}
// Deal with recording
void C_TFRagdoll::GetToolRecordingState( KeyValues *msg )
{
#ifndef _XBOX
BaseClass::GetToolRecordingState( msg );
if ( m_MaterialOverride.IsValid() )
{
msg->SetString( "materialOverride", m_MaterialOverride->GetName() );
}
#endif
}
void C_TFRagdoll::DissolveEntity( CBaseEntity* pEnt )
{
C_EntityDissolve *pDissolve = DissolveEffect( pEnt, gpGlobals->curtime );
if ( pDissolve )
{
pDissolve->SetRenderMode( kRenderTransColor );
pDissolve->m_nRenderFX = kRenderFxNone;
pDissolve->SetRenderColor( 255, 255, 255, 255 );
Vector vColor;
if ( m_iTeam == TF_TEAM_BLUE )
{
vColor = TF_PARTICLE_WEAPON_RED_1 * 255;
pDissolve->SetEffectColor( vColor );
}
else
{
vColor = TF_PARTICLE_WEAPON_BLUE_1 * 255;
pDissolve->SetEffectColor( vColor );
}
pDissolve->m_vDissolverOrigin = GetAbsOrigin();
pDissolve->m_flFadeInStart = DISSOLVE_FADE_IN_START_TIME;
pDissolve->m_flFadeInLength = DISSOLVE_FADE_IN_END_TIME - DISSOLVE_FADE_IN_START_TIME;
pDissolve->m_flFadeOutModelStart = DISSOLVE_FADE_OUT_MODEL_START_TIME;
pDissolve->m_flFadeOutModelLength = DISSOLVE_FADE_OUT_MODEL_END_TIME - DISSOLVE_FADE_OUT_MODEL_START_TIME;
pDissolve->m_flFadeOutStart = DISSOLVE_FADE_OUT_START_TIME;
pDissolve->m_flFadeOutLength = DISSOLVE_FADE_OUT_END_TIME - DISSOLVE_FADE_OUT_START_TIME;
}
}
void C_TFRagdoll::StartFadeOut( float fDelay )
{
if ( !cl_ragdoll_forcefade.GetBool() )
{
m_fDeathTime = gpGlobals->curtime + fDelay;
}
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
void C_TFRagdoll::EndFadeOut()
{
SetNextClientThink( CLIENT_THINK_NEVER );
ClearRagdoll();
SetRenderMode( kRenderNone );
UpdateVisibility();
DestroyBoneAttachments();
// Remove attached effect entity
C_BaseEntity *pEffect = GetEffectEntity();
if ( pEffect )
{
pEffect->SUB_Remove();
}
ParticleProp()->StopEmission();
// Hide attached wearables.
// These are server objects so they'll go away when the actual server ragdoll dies.
for ( int i=0; i<m_hRagWearables.Count(); ++i )
{
if ( m_hRagWearables[i] )
{
m_hRagWearables[i]->AddEffects( EF_NODRAW );
m_hRagWearables[i]->SetMoveType( MOVETYPE_NONE );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Used for spy invisiblity material
//-----------------------------------------------------------------------------
class CSpyInvisProxy : public CBaseInvisMaterialProxy
{
public:
CSpyInvisProxy( void );
virtual bool Init( IMaterial *pMaterial, KeyValues* pKeyValues ) OVERRIDE;
virtual void OnBind( C_BaseEntity *pBaseEntity ) OVERRIDE;
virtual void OnBindNotEntity( void *pRenderable ) OVERRIDE;
private:
IMaterialVar *m_pCloakColorTint;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CSpyInvisProxy::CSpyInvisProxy( void )
{
m_pCloakColorTint = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Get pointer to the color value
// Input : *pMaterial -
//-----------------------------------------------------------------------------
bool CSpyInvisProxy::Init( IMaterial *pMaterial, KeyValues* pKeyValues )
{
// Need to get the material var
bool bInvis = CBaseInvisMaterialProxy::Init( pMaterial, pKeyValues );
bool bTint;
m_pCloakColorTint = pMaterial->FindVar( "$cloakColorTint", &bTint );
return ( bInvis && bTint );
}
ConVar tf_teammate_max_invis( "tf_teammate_max_invis", "0.95", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose:
// Input :
//-----------------------------------------------------------------------------
void CSpyInvisProxy::OnBind( C_BaseEntity *pBaseEntity )
{
if( !m_pPercentInvisible || !m_pCloakColorTint )
return;
float fInvis = 0.0f;
C_TFPlayer *pPlayer = ToTFPlayer( pBaseEntity );
if ( !pPlayer )
{
C_TFPlayer *pOwningPlayer = ToTFPlayer( pBaseEntity->GetOwnerEntity() );
C_TFRagdoll *pRagdoll = dynamic_cast< C_TFRagdoll* >( pBaseEntity );
if ( pRagdoll && pRagdoll->IsCloaked() )
{
fInvis = pRagdoll->GetPercentInvisible();
}
else if ( pOwningPlayer )
{
// mimic the owner's invisibility
fInvis = pOwningPlayer->GetEffectiveInvisibilityLevel();
}
}
else
{
float r = 1.0f, g = 1.0f, b = 1.0f;
fInvis = pPlayer->GetEffectiveInvisibilityLevel();
switch( pPlayer->GetTeamNumber() )
{
case TF_TEAM_RED:
r = 1.0; g = 0.5; b = 0.4;
break;
case TF_TEAM_BLUE:
default:
r = 0.4; g = 0.5; b = 1.0;
break;
}
m_pCloakColorTint->SetVecValue( r, g, b );
}
m_pPercentInvisible->SetFloatValue( fInvis );
}
void CSpyInvisProxy::OnBindNotEntity( void *pRenderable )
{
CBaseInvisMaterialProxy::OnBindNotEntity( pRenderable );
if ( m_pCloakColorTint )
{
m_pCloakColorTint->SetVecValue( 1.f, 1.f, 1.f );
}
}
EXPOSE_INTERFACE( CSpyInvisProxy, IMaterialProxy, "spy_invis" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for invulnerability material
// Returns 1 if the player is invulnerable, and 0 if the player is losing / doesn't have invuln.
//-----------------------------------------------------------------------------
class CProxyInvulnLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
float flResult = 1.0;
C_TFPlayer *pPlayer = NULL;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( !pEntity )
{
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
{
flResult = 1.0f;
}
m_pResult->SetFloatValue( flResult );
return;
}
if ( pEntity->IsPlayer() )
{
pPlayer = dynamic_cast< C_TFPlayer* >( pEntity );
}
else
{
IHasOwner *pOwnerInterface = dynamic_cast< IHasOwner* >( pEntity );
if ( pOwnerInterface )
{
pPlayer = ToTFPlayer( pOwnerInterface->GetOwnerViaInterface() );
}
}
if ( pPlayer && pPlayer->m_Shared.IsInvulnerable() && pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
{
flResult = 0.0;
}
m_pResult->SetFloatValue( flResult );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyInvulnLevel, IMaterialProxy, "InvulnLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for burning material on player models
// Returns 0.0->1.0 for level of burn to show on player skin
//-----------------------------------------------------------------------------
class CProxyBurnLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
float flResult = 0.0;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( !pEntity )
{
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_BURN )
{
flResult = 1.0f;
}
m_pResult->SetFloatValue( flResult );
return;
}
// default to zero
float flBurnStartTime = 0;
if ( pEntity->IsPlayer() )
{
C_TFPlayer *pPlayer = assert_cast< C_TFPlayer* >( pEntity );
// is the player burning?
if ( pPlayer->m_Shared.InCond( TF_COND_BURNING ) )
{
flBurnStartTime = pPlayer->m_flBurnEffectStartTime;
}
}
else
{
// is the ragdoll burning?
C_TFRagdoll *pRagDoll = dynamic_cast< C_TFRagdoll* >( pEntity );
if ( pRagDoll )
{
flBurnStartTime = pRagDoll->GetBurnStartTime();
}
}
// if player/ragdoll is burning, set the burn level on the skin
if ( flBurnStartTime > 0 )
{
float flBurnPeakTime = flBurnStartTime + 0.3;
float flTempResult;
if ( gpGlobals->curtime < flBurnPeakTime )
{
// fade in from 0->1 in 0.3 seconds
flTempResult = RemapValClamped( gpGlobals->curtime, flBurnStartTime, flBurnPeakTime, 0.0, 1.0 );
}
else
{
// fade out from 1->0 in the remaining time until flame extinguished
flTempResult = RemapValClamped( gpGlobals->curtime, flBurnPeakTime, flBurnStartTime + TF_BURNING_FLAME_LIFE, 1.0, 0.0 );
}
// We have to do some more calc here instead of in materialvars.
flResult = 1.0 - abs( flTempResult - 1.0 );
}
m_pResult->SetFloatValue( flResult );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyBurnLevel, IMaterialProxy, "BurnLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for turning player models yellow (jarate)
// Returns 0.0->1.0 for level of yellow to show on player skin
//-----------------------------------------------------------------------------
class CProxyUrineLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
// default to zero
Vector vResult = Vector( 1, 1, 1 );
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( !pEntity )
{
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_URINE )
{
if ( g_PlayerPreviewEffect.GetTeam() == TF_TEAM_RED )
{
vResult = Vector ( 6, 9, 2 );
}
else
{
vResult = Vector ( 7, 5, 1 );
}
}
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
return;
}
C_TFPlayer *pPlayer = NULL;
if ( pEntity->IsPlayer() )
{
pPlayer = assert_cast< C_TFPlayer* >( pEntity );
}
else if ( pEntity->GetOwnerEntity() && pEntity->GetOwnerEntity()->IsPlayer() )
{
pPlayer = assert_cast< C_TFPlayer* >( pEntity->GetOwnerEntity() );
}
if ( pPlayer )
{
// is the player peed on?
if ( pPlayer->m_Shared.InCond( TF_COND_URINE ) )
{
int iVisibleTeam = pPlayer->GetTeamNumber();
if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
{
if ( !pPlayer->IsLocalPlayer() && iVisibleTeam != GetLocalPlayerTeam() )
{
iVisibleTeam = pPlayer->m_Shared.GetDisguiseTeam();
}
}
if ( iVisibleTeam == TF_TEAM_RED )
{
vResult = Vector ( 6, 9, 2 );
}
else
{
vResult = Vector ( 7, 5, 1 );
}
}
else
{
vResult = Vector( 1, 1, 1 );
}
}
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyUrineLevel, IMaterialProxy, "YellowLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: CritBoosted FX
//
//-----------------------------------------------------------------------------
class CProxyModelGlowColor : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
C_TFPlayer *pPlayer = NULL;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( !pEntity )
{
Vector vResult = Vector( 1, 1, 1 );
#if 0 // It looks like this code path is never used
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_CRIT )
{
if ( g_PlayerPreviewEffect.GetTeam() == TF_TEAM_RED )
{
vResult = Vector ( 80, 8, 5 );
}
else
{
vResult = Vector ( 5, 20, 80 );
}
}
#endif // 0
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
return;
}
// default to [1 1 1]
Vector vResult = Vector( 1, 1, 1 );
int iVisibleTeam = 0;
IHasOwner *pOwnerInterface = dynamic_cast< IHasOwner* >( pEntity );
if ( pOwnerInterface )
{
pPlayer = ToTFPlayer( pOwnerInterface->GetOwnerViaInterface() );
}
if ( pPlayer )
{
iVisibleTeam = pPlayer->GetTeamNumber();
if ( pPlayer->m_Shared.IsCritBoosted() )
{
// never show critboosted effect on a disguised spy (unless it's me)
if ( !pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) || pPlayer->IsLocalPlayer() )
{
if ( iVisibleTeam == TF_TEAM_RED )
{
vResult = Vector ( 80, 8, 5 );
}
else
{
vResult = Vector ( 5, 20, 80 );
}
}
pPlayer->m_Shared.m_bChargeGlowing = false;
}
else if ( pPlayer->m_Shared.IsHypeBuffed() )
{
vResult = Vector( 50, 2, 48 );
pPlayer->m_Shared.m_bChargeGlowing = false;
}
else if ( pPlayer->m_Shared.InCond( TF_COND_OFFENSEBUFF ) || pPlayer->m_Shared.InCond( TF_COND_ENERGY_BUFF ) )
{
// Temporarily hijacking this proxy for buff FX.
if ( iVisibleTeam == TF_TEAM_RED )
{
vResult = Vector ( 226, 150, 62 );
}
else
{
vResult = Vector( 29, 202, 135 );
}
pPlayer->m_Shared.m_bChargeGlowing = false;
}
else
{
if ( pPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) || (pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT) )
{
float flGlow;
if ( pPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
{
// Ramp up the charge glow while charging.
flGlow = (100.f - pPlayer->m_Shared.GetDemomanChargeMeter()) / 100.f;
}
else
{
// Cool down the charge glow after charging.
flGlow = 1.f - MIN((gpGlobals->curtime - pPlayer->m_Shared.m_flLastNoChargeTime - 1.5f) / 0.3f,1);
}
if ( iVisibleTeam == TF_TEAM_RED )
{
vResult = Vector( MAX(80*flGlow,1), MAX(8*flGlow,1), MAX(5*flGlow,1) );
}
else
{
vResult = Vector( MAX(5*flGlow,1), MAX(20*flGlow,1), MAX(80*flGlow,1) );
}
pPlayer->m_Shared.m_bChargeGlowing = true;
}
else if ( pPlayer->m_Shared.m_bChargeGlowing )
{
// Cool down the charge glow after charging.
float flGlow = 1.f - MIN( (gpGlobals->curtime - pPlayer->m_Shared.m_flLastNoChargeTime) / 0.3f, 1.f );
if ( flGlow <= 0 )
{
pPlayer->m_Shared.m_bChargeGlowing = false;
}
if ( iVisibleTeam == TF_TEAM_RED )
{
vResult = Vector( MAX(80*flGlow,1), MAX(8*flGlow,1), MAX(5*flGlow,1) );
}
else
{
vResult = Vector( MAX(5*flGlow,1), MAX(20*flGlow,1), MAX(80*flGlow,1) );
}
}
else
{
vResult = Vector( 1, 1, 1 );
}
}
}
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyModelGlowColor, IMaterialProxy, "ModelGlowColor" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Proxy used to tell the material it's on a community weapon
//-----------------------------------------------------------------------------
class CProxyCommunityWeapon : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
if ( pC_BaseEntity )
{
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( pEntity )
{
CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
if ( pItem )
{
CEconItemView *pScriptItem = pItem->GetAttributeContainer()->GetItem();
if ( pScriptItem && pScriptItem->GetStaticData() )
{
if ( pScriptItem->GetItemQuality() == AE_COMMUNITY )
{
m_pResult->SetIntValue( 1 );
return;
}
}
}
}
}
m_pResult->SetIntValue( 0 );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyCommunityWeapon, IMaterialProxy, "CommunityWeapon" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for scaling the beating heart texture to make it pulse
//-----------------------------------------------------------------------------
class CProxyHeartbeatScale : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
const float pi = 3.141592f;
const float twoPI = 2.0f * pi;
float s1 = sin( gpGlobals->curtime * twoPI );
s1 = clamp( s1, 0.0f, 1.0f );
s1 *= s1;
s1 *= s1;
s1 = clamp( s1, 0.5f, 1.0f );
s1 -= 0.5f;
s1 *= 2.0f;
float s2 = sin( ( gpGlobals->curtime + 0.25f ) * twoPI );
s2 = clamp( s2, 0.0f, 1.0f );
s2 *= s2;
s2 *= s2;
s2 = clamp( s2, 0.5f, 1.0f );
s2 -= 0.5f;
s2 *= 2.0f;
float beat = MAX( s1, s2 );
const float scale = 0.6f;
const float loBeat = 1.0f * scale;
const float hiBeat = 0.8f * scale;
float scaledBeat = loBeat + ( hiBeat - loBeat ) * beat;
m_pResult->SetFloatValue( scaledBeat );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyHeartbeatScale, IMaterialProxy, "HeartbeatScale" IMATERIAL_PROXY_INTERFACE_VERSION );
#ifdef _DEBUG
ConVar tf_benefactor_gift_count( "tf_benefactor_gift_count", "-1", FCVAR_CHEAT, "For testing" );
#endif
//-----------------------------------------------------------------------------
// Purpose: Used for scaling the beating heart based on the number of gifts player has given
// Returns a texture scale factor where 1 is max size (big benefactor) and factor gets larger the fewer gifts you've given
//-----------------------------------------------------------------------------
class CProxyBenefactorLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
CEconItemView *pScriptItem = NULL;
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
if ( pEntity )
{
CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
if ( pItem )
{
pScriptItem = pItem->GetAttributeContainer()->GetItem();
}
}
if ( pScriptItem )
{
static CSchemaAttributeDefHandle pAttrDef_KillEater( "kill eater" );
// Use the kill-eater prefix if the weapon has one.
uint32 unKillCount;
if ( !pScriptItem->FindAttribute( pAttrDef_KillEater, &unKillCount ) )
return;
#ifdef _DEBUG
int testCount = tf_benefactor_gift_count.GetInt();
if ( testCount >= 0 )
{
unKillCount = (uint32)testCount;
}
#endif
if ( unKillCount == 0 )
{
// heartless
m_pResult->SetFloatValue( 1000.0f );
}
else
{
const int maxBenefatorLevel = 250;
float value = (float)unKillCount / (float)maxBenefatorLevel;
value = clamp( value, 0.0f, 1.0f );
// a linear scale doesn't show the size change until the very end - nonlinearize it
value = sin( value * 1.57f );
value = sin( value * 1.57f );
// this seems backwards because this is a texture scaling factor
// the bigger the number, the smaller the textured image is (tiles more in same space)
const float minValue = 5.0f;
const float maxValue = 1.0f;
m_pResult->SetFloatValue( minValue + ( maxValue - minValue ) * value );
}
}
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyBenefactorLevel, IMaterialProxy, "BenefactorLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for scaling the oscilloscope on the Building Rescue Gun
// Flattens the Wave when the player has no energy
//-----------------------------------------------------------------------------
class CProxyBuildingRescueLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pPlayer )
return;
int iIncreasedRangeCost = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iIncreasedRangeCost, building_teleporting_pickup );
if ( iIncreasedRangeCost == 0 )
return;
CTFWeaponBase * pWeapon = pPlayer->GetActiveTFWeapon();
if ( !pWeapon )
return;
int iAmmo = pPlayer->GetAmmoCount( TF_AMMO_METAL );
float scale = 1.0f;
if ( iAmmo < iIncreasedRangeCost )
{
scale = 10.0f;
}
else
{
scale = ( 3.0f - ((float)(iAmmo - iIncreasedRangeCost) / (float)(pPlayer->GetMaxAmmo( TF_AMMO_METAL ) - iIncreasedRangeCost) * 3.0f ) + 1.0f );
}
VMatrix mat, temp;
Vector2D center( 0.5, 0.5 );
MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
// scale
{
MatrixBuildScale( temp, 1.0f, scale, 1.0f );
MatrixMultiply( temp, mat, mat );
}
MatrixBuildTranslation( temp, center.x, center.y, 0.0f );
MatrixMultiply( temp, mat, mat );
m_pResult->SetMatrixValue( mat );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyBuildingRescueLevel, IMaterialProxy, "BuildingRescueLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Used to pulse the Vaccinator's uber shield
//-----------------------------------------------------------------------------
class CProxyResistShield : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
if ( pEntity )
{
C_LocalTempEntity* pTempEnt = dynamic_cast<C_LocalTempEntity*>(pEntity);
if ( pTempEnt )
{
C_BaseEntity *pBaseEnt = cl_entitylist->GetEnt( pTempEnt->clientIndex );
// This should be the owning player
C_TFPlayer* pTFPlayer = ToTFPlayer( pBaseEnt );
if( pTFPlayer )
{
float flTimeSince = gpGlobals->curtime - pTFPlayer->GetLastResistTime();
float flOut = RemapValClamped( flTimeSince, 0, 0.4f, 7.f, -4.f );
m_pResult->SetVecValue( flOut, flOut, flOut, 1.f );
return;
}
}
}
m_pResult->SetVecValue( 1.0, 1.0, 1.0, 1.0 );
}
};
EXPOSE_INTERFACE( CProxyResistShield, IMaterialProxy, "ShieldFalloff" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for pulsing the Wheatly Sappers eye glow when he talks
//-----------------------------------------------------------------------------
class CProxyWheatlyEyeGlow : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pPlayer )
return;
const float flBase = 0.4f;
const float flMinTalking = 0.6f;
const float flMaxTalking = 1.3f;
const float flAdjustUp = 0.01f;
const float flAdjustDown = -0.006f;
static float s_flCurr = 0.2f;
static float s_flDir = 0.2f;
const float flEyeHold = 1.9f;
const float flMinEyes = 0.1f;
const float flMaxEyes = 0.35f;
const float flEyeAdjust = 0.005f;
static float s_flEyePose = 0.0f;
static float s_flNextEyeChange = 0;
static float s_flEyeDir = 0.005f;
C_TFWeaponSapper *pWeapon = dynamic_cast< C_TFWeaponSapper* >( pPlayer->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING ) );
if ( pWeapon )
{
if ( pWeapon->IsWheatleyTalking() )
{
float flNoise = RandomGaussianFloat( 0.0f, 0.01f );
s_flCurr += s_flDir + flNoise;
if ( s_flCurr > flMaxTalking && s_flDir > 0 )
{
s_flDir = flAdjustDown;
}
else if ( s_flCurr < flMinTalking && s_flDir < 0 )
{
s_flDir = flAdjustUp;
}
// Animate to eye's and hold for a few seconds
float currTime = gpGlobals->curtime;
if ( currTime > s_flNextEyeChange)
{
s_flEyePose += s_flEyeDir;
if ( s_flEyePose > flMaxEyes && s_flEyeDir > 0 )
{
s_flNextEyeChange = currTime + flEyeHold;
s_flEyeDir = -flEyeAdjust;
}
if ( s_flEyePose < flMinEyes && s_flEyeDir < 0 )
{
s_flNextEyeChange = currTime + flEyeHold;
s_flEyeDir = flEyeAdjust;
}
}
}
else
{
// adjust towards base
s_flCurr += flAdjustDown;
if ( s_flCurr < flBase )
{
s_flCurr = flBase;
s_flDir = flAdjustUp;
}
if ( s_flEyePose > 0 )
{
s_flEyePose -= flEyeAdjust;
}
s_flEyeDir = flEyeAdjust;
}
CBaseViewModel *pViewModel = pPlayer->GetViewModel(0);
if ( pViewModel )
{
pViewModel->SetPoseParameter( "eyelids", s_flEyePose );
}
}
m_pResult->SetFloatValue( s_flCurr );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyWheatlyEyeGlow, IMaterialProxy, "WheatlyEyeGlow" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
CEconItemView *GetEconItemViewFromProxyEntity( void *pEntity )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
IClientRenderable *pRend = (IClientRenderable *)pEntity;
CBaseEntity *pBaseEntity = pRend ? pRend->GetIClientUnknown()->GetBaseEntity() : NULL;
// If an entity, find out what types it is and get the econ item view
if ( pBaseEntity )
{
// Generic Entity that has an Item ( Weapons / Hats )
IHasAttributes *pAttribInterface = GetAttribInterface( pBaseEntity );
if ( pAttribInterface )
return pAttribInterface->GetAttributeContainer()->GetItem();
// ViewModel Attachment (aka view model Weapon)
C_ViewmodelAttachmentModel *pViewModelAttachment = dynamic_cast<C_ViewmodelAttachmentModel*>( pBaseEntity );
if ( pViewModelAttachment && pViewModelAttachment->GetOuter() )
{
return pViewModelAttachment->GetOuter()->GetAttributeContainer()->GetItem();
}
CTFViewModel *pViewModel = dynamic_cast<CTFViewModel*>( pBaseEntity );
if ( pViewModel && pViewModel->GetWeapon() )
{
return pViewModel->GetWeapon()->GetAttributeContainer()->GetItem();
}
CTFDroppedWeapon *pDroppedWeapon = dynamic_cast<CTFDroppedWeapon*>( pBaseEntity );
if ( pDroppedWeapon && pDroppedWeapon->GetItem() && pDroppedWeapon->GetItem()->GetItemDefIndex() != INVALID_ITEM_DEF_INDEX )
{
return pDroppedWeapon->GetItem();
}
}
// No direct entity, might be a EconItem (PlayerModelPanels)
else
{
CEconItemView *pItem = dynamic_cast< CEconItemView* >( pRend );
if ( pItem )
{
return pItem;
}
}
return NULL;
}
//-----------------------------------------------------------------------------
C_TFPlayer *GetOwnerFromProxyEntity( void *pEntity )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
IClientRenderable *pRend = ( IClientRenderable * ) pEntity;
CBaseEntity *pBaseEntity = pRend ? pRend->GetIClientUnknown()->GetBaseEntity() : NULL;
// If an entity, find out what types it is and get the econ item view
if ( pBaseEntity )
{
CBaseEntity* pOwner = pBaseEntity->GetOwnerEntity();
if ( pOwner )
return dynamic_cast<C_TFPlayer*>( pOwner->GetOwnerEntity() );
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Used to animate the weapon sheen effect for kill streak attr items
//-----------------------------------------------------------------------------
class CProxyAnimatedWeaponSheen : public CBaseAnimatedTextureProxy
{
public:
CProxyAnimatedWeaponSheen() {}
virtual ~CProxyAnimatedWeaponSheen() {}
bool Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool foundVar = false;
m_flNextStartTime = 0;
m_flScaleX = -1;
m_flScaleY = -1;
m_flSheenOffsetX = 0;
m_flSheenOffsetY = 0;
m_iSheenDir = 0;
m_pScaleXVar = NULL;
m_pScaleYVar = NULL;
m_pOffsetXVar = NULL;
m_pOffsetYVar = NULL;
m_pDirectionVar = NULL;
m_pSheenIndexVar = NULL;
// Get Tint
m_pTintVar = pMaterial->FindVar( "$sheenmaptint", &foundVar );
if( !foundVar )
return false;
m_pSheenIndexVar = pMaterial->FindVar( "$sheenindex", &foundVar );
if( !foundVar )
return false;
// Material vars for scale and offset
// Need to get the material var
m_pScaleXVar = pMaterial->FindVar( "$sheenmapmaskscaleX", &foundVar );
if( !foundVar )
return false;
m_pScaleYVar = pMaterial->FindVar( "$sheenmapmaskscaleY", &foundVar );
if( !foundVar )
return false;
m_pOffsetXVar = pMaterial->FindVar( "$sheenmapmaskoffsetX", &foundVar );
if( !foundVar )
return false;
m_pOffsetYVar = pMaterial->FindVar( "$sheenmapmaskoffsetY", &foundVar );
if( !foundVar )
return false;
m_pDirectionVar = pMaterial->FindVar( "$sheenmapmaskdirection", &foundVar );
if( !foundVar )
return false;
m_pSheenVar = pMaterial->FindVar( "$sheenmap", &foundVar );
if ( !foundVar )
return false;
m_pSheenMaskVar = pMaterial->FindVar( "$sheenmapmask", &foundVar );
if ( !foundVar )
return false;
return CBaseAnimatedTextureProxy::Init( pMaterial, pKeyValues );
}
void OnBind( void *pEntity )
{
Assert( m_AnimatedTextureVar );
if ( m_AnimatedTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
return;
// if no entity, just check the owner
if ( !pEntity )
{
// Might be gunslinger, hard to get this item so
return;
}
static CSchemaAttributeDefHandle pAttr_killstreak( "killstreak idleeffect" );
if ( !pAttr_killstreak )
return;
IClientRenderable *pRend = (IClientRenderable *)pEntity;
// Find the weapon and player and see if it has the attribute
if ( !pRend )
return;
ITexture *pTexture;
pTexture = m_AnimatedTextureVar->GetTextureValue();
int numFrames = pTexture->GetNumAnimationFrames();
if ( numFrames <= 0 )
{
Assert( !"0 frames in material calling animated texture proxy" );
return;
}
C_BaseEntity *pBaseEntity = pRend->GetIClientUnknown()->GetBaseEntity();
const CEconItemView *pItem = dynamic_cast< CEconItemView* >( pRend );
uint32 unAttrValue = 0;
uint32 unEffectValue = 0;
// !TEST!
#ifdef STAGING_ONLY
if ( tf_killstreak_alwayson.GetBool() )
{
unEffectValue = 1;
}
if ( tf_sheen_all.GetFloat() != 0 )
{
unEffectValue = tf_sheen_all.GetInt();
}
#endif // STAGING_ONLY
// Find the tf player owner
bool bIsFirstPerson = false;
C_TFPlayer* pTFPlayer = NULL;
if ( pItem ) // ItemModelPanels
{
if ( !pBaseEntity )
{
pItem->FindAttribute( pAttr_killstreak, &unAttrValue );
unEffectValue = (int)((float&)unAttrValue);
}
else
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBaseEntity, unAttrValue, killstreak_idleeffect );
}
if ( ( unEffectValue ) && pBaseEntity && pBaseEntity->GetOwnerEntity() )
{
pTFPlayer = ToTFPlayer( pBaseEntity->GetOwnerEntity() );
}
else
{
pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
}
}
else
{
CTFWeaponBase* pWeapon = dynamic_cast<CTFWeaponBase*>( pBaseEntity );
if ( !pWeapon )
{
for ( int i = 0; i < 1; ++i )
{
CEconWearable* pWearable = dynamic_cast<CEconWearable*>( pBaseEntity );
if ( pWearable )
{
pItem = pWearable->GetAttributeContainer()->GetItem();
pTFPlayer = ToTFPlayer( pWearable->GetOwnerEntity() );
break;
}
C_ViewmodelAttachmentModel *pModel = dynamic_cast<C_ViewmodelAttachmentModel*>( pBaseEntity );
if ( pModel )
{
if ( pModel->GetOuter() )
{
pItem = pModel->GetOuter()->GetAttributeContainer()->GetItem();
pBaseEntity = pBaseEntity->GetOwnerEntity();
if ( pItem )
{
pTFPlayer = ToTFPlayer( pModel->GetOuter()->GetOwnerEntity() );
}
}
break;
}
// not a weapon, is a viewmodel
IHasOwner *pHasOwner = dynamic_cast<IHasOwner*>( pBaseEntity );
if ( pHasOwner )
{
// View model owner is player, so get the players active weapon
CBaseEntity *pOwner = pHasOwner->GetOwnerViaInterface();
pTFPlayer = ToTFPlayer( pOwner );
if ( pTFPlayer )
{
pWeapon = pTFPlayer->GetActiveTFWeapon();
if ( pWeapon )
{
pItem = pWeapon->GetAttributeContainer()->GetItem();
pBaseEntity = pWeapon;
}
bIsFirstPerson = true;
}
}
else if ( pBaseEntity && pBaseEntity->IsPlayer( ) )
{
pTFPlayer = ToTFPlayer( pBaseEntity );
pWeapon = pTFPlayer->GetActiveTFWeapon();
if ( pWeapon )
{
pItem = pWeapon->GetAttributeContainer()->GetItem();
pBaseEntity = pWeapon;
}
}
} // for
}
else
{
pItem = pWeapon->GetAttributeContainer()->GetItem();
pBaseEntity = pWeapon;
pTFPlayer = ToTFPlayer( pWeapon->GetOwner() );
}
// I have an econ item, does it have the attr
if ( ( pBaseEntity && pItem ) || unEffectValue )
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBaseEntity, unAttrValue, killstreak_idleeffect );
if ( !unEffectValue )
{
unEffectValue = unAttrValue;
}
// Use the Spies target if disguised and we're on different teams
if ( pTFPlayer && pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->GetTeamNumber() != GetLocalPlayerTeam() )
{
pTFPlayer = ToTFPlayer( pTFPlayer->m_Shared.GetDisguiseTarget() );
if ( pTFPlayer && pTFPlayer->m_Shared.GetDisguiseWeapon() )
{
pItem = pTFPlayer->m_Shared.GetDisguiseWeapon()->GetAttributeContainer()->GetItem();
pBaseEntity = pTFPlayer->m_Shared.GetDisguiseWeapon();
}
}
}
}
float flNextStartTime = 0;
if ( pTFPlayer )
{
// if player is taunting, make sure they can start animating
if ( pTFPlayer->IsTaunting() && flNextStartTime > gpGlobals->curtime )
{
pTFPlayer->m_flNextSheenStartTime = gpGlobals->curtime;
}
flNextStartTime = pTFPlayer->m_flNextSheenStartTime;
}
else
{
flNextStartTime = m_flNextStartTime;
}
// !TEST!
#ifdef STAGING_ONLY
if ( tf_killstreak_alwayson.GetBool() )
{
unEffectValue = 1;
}
if ( tf_sheen_all.GetFloat() != 0 )
{
unEffectValue = tf_sheen_all.GetInt();
}
#endif // STAGING_ONLY
// Not ready, so just exit
if ( !pItem || !unEffectValue || flNextStartTime > gpGlobals->curtime || unEffectValue > ARRAYSIZE( g_KillStreakEffectsBase ) - 1 )
{
RunNoProxy();
return;
}
// Negative Value implies it has not been set, set it now
if ( m_flScaleX < 0 )
{
if ( !InitParams( pRend, pBaseEntity ) )
{
RunNoProxy();
return;
}
}
// NOTE: Must not use relative time based methods here
// because the bind proxy can be called many times per frame.
// Prevent multiple Wrap callbacks to be sent for no wrap mode
float startTime = pTFPlayer ? pTFPlayer->m_flNextSheenStartTime : 0;
float deltaTime = gpGlobals->curtime - startTime;
float prevTime = deltaTime - gpGlobals->frametime;
// Clamp..
if (deltaTime < 0.0f)
deltaTime = 0.0f;
if (prevTime < 0.0f)
prevTime = 0.0f;
// Code Frame rate to be 25
float frame = tf_sheen_framerate.GetInt() * deltaTime;
float prevFrame = tf_sheen_framerate.GetInt() * prevTime;
int intFrame = ((int)frame) % numFrames;
int intPrevFrame = ((int)prevFrame) % numFrames;
// Report wrap situation...
if ( intPrevFrame > intFrame )
{
// Set frame to zero and set the time for the next
intFrame = 0;
if ( pTFPlayer )
{
pTFPlayer->m_flNextSheenStartTime = gpGlobals->curtime + GetTimeBetweenAnims( pTFPlayer );
}
else
{
m_flNextStartTime = gpGlobals->curtime + GetTimeBetweenAnims( NULL );
}
}
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
killstreak_params_t sheenParams = GetSheenParams( unEffectValue, pTFPlayer ? pTFPlayer->GetTeamNumber() == TF_TEAM_BLUE : false );
color[ 0 ] = sheenParams.m_sheen_r;
color[ 1 ] = sheenParams.m_sheen_g;
color[ 2 ] = sheenParams.m_sheen_b;
color[ 3 ] = sheenParams.m_sheen_a;
#ifdef STAGING_ONLY
if ( tf_sheen_color_override_a.GetFloat() > 0 )
{
color[0] = tf_sheen_color_override_r.GetFloat();
color[1] = tf_sheen_color_override_g.GetFloat();
color[2] = tf_sheen_color_override_b.GetFloat();
color[3] = tf_sheen_color_override_a.GetFloat();
}
#endif // STAGING_ONLY
if ( bIsFirstPerson )
{
color[ 3 ] = tf_sheen_alpha_firstperson.GetFloat();
m_pTintVar->SetVecValue( color, 4 );
}
else
{
m_pTintVar->SetVecValue( color, 4 );
}
// Set vars
m_AnimatedTextureFrameNumVar->SetIntValue( intFrame );
m_pScaleXVar->SetFloatValue( m_flScaleX ); // Only need to set once?
m_pScaleYVar->SetFloatValue( m_flScaleY );
m_pOffsetXVar->SetFloatValue( m_flSheenOffsetX );
m_pOffsetYVar->SetFloatValue( m_flSheenOffsetY );
m_pDirectionVar->SetIntValue( m_iSheenDir );
int iShaderIndex = sheenParams.m_iShaderIndex;
// Australium weapons always use iShaderIndex 1
const CEconStyleInfo *pStyle = pItem->GetStaticData()->GetStyleInfo( pItem->GetItemStyle() );
if ( pStyle && !pStyle->IsSelectable() )
{
iShaderIndex = 1;
}
#ifdef STAGING_ONLY
if ( tf_sheen_shader_override.GetInt() > 0 )
{
iShaderIndex = tf_sheen_shader_override.GetInt();
}
#endif // staging_only
m_pSheenIndexVar->SetIntValue( iShaderIndex );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
// the last time the animation was run (or will allowed to be run)
float GetAnimationStartTime( void* pBaseEntity )
{
return 0;
}
float GetTimeBetweenAnims ( C_TFPlayer* pTFPlayer )
{
const float MAX_SHEEN_WAIT = 5.0f;
const float MAX_KILLS = 5.0f;
#ifdef STAGING_ONLY
if ( tf_sheen_fast.GetBool() )
return 0;
#endif
if ( !pTFPlayer )
return MAX_SHEEN_WAIT;
if ( pTFPlayer->IsTaunting() )
return 0;
// Set the time between sheens based on kill streak
int iStreak = pTFPlayer->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills );
if ( iStreak >= MAX_KILLS )
return 0;
if ( iStreak == 0 )
return MAX_SHEEN_WAIT;
// as player gets more kills, time decreases
return ( 1.0f - ( iStreak / MAX_KILLS ) ) * MAX_SHEEN_WAIT;
}
killstreak_params_t GetSheenParams( uint32 unEffectValue, bool bIsTeamBlue )
{
Assert( unEffectValue > 0 && unEffectValue < ARRAYSIZE( g_KillStreakEffectsBase ) );
killstreak_params_t params = g_KillStreakEffectsBase[ unEffectValue ];
if ( bIsTeamBlue && params.m_bHasTeamColor )
{
Assert( unEffectValue > 0 && unEffectValue < ARRAYSIZE( g_KillStreakEffectsBlue ) );
return g_KillStreakEffectsBlue[ unEffectValue ];
}
return params;
}
bool InitParams( IClientRenderable *pRend, C_BaseEntity *pBaseEntity )
{
// Negative Value implies it has not been set, set it now
if ( m_flScaleX < 0 )
{
Vector vMin, vMax;
// Check if the baseEntity is ready
if ( pBaseEntity )
{
CBaseAnimating *pAnimating = dynamic_cast< CBaseAnimating * > ( pBaseEntity );
if ( !pAnimating || !pAnimating->GetModelPtr() || pAnimating->GetModelPtr()->GetNumSeq() < pAnimating->GetSequence() )
{
return false;
}
}
pRend->GetRenderBounds( vMin, vMax );
m_flScaleX = vMax.x - vMin.x;
m_flSheenOffsetX = vMin.x;
m_flScaleY = vMax.z - vMin.z;
m_flSheenOffsetY = vMin.z;
m_iSheenDir = 0;
if ( vMax.y - vMin.y > m_flScaleX )
{
m_flScaleX = vMax.y - vMin.y;
m_flSheenOffsetX = vMin.y;
m_flScaleY = vMax.x - vMin.x;
m_flSheenOffsetY = vMin.x;
m_iSheenDir = 1;
}
if ( vMax.z - vMin.z > m_flScaleX )
{
m_flScaleX = vMax.z - vMin.z;
m_flSheenOffsetX = vMin.z;
m_flScaleY = vMax.y - vMin.y;
m_flSheenOffsetY = vMin.y;
m_iSheenDir = 2;
}
}
return true;
}
void Cleanup()
{
m_pTintVar = NULL;
CBaseAnimatedTextureProxy::Cleanup();
}
void RunNoProxy ()
{
m_pTintVar->SetVecValue( 0, 0, 0 );
m_AnimatedTextureFrameNumVar->SetIntValue( 0 );
m_pSheenIndexVar->SetIntValue( 0 );
}
private:
IMaterialVar *m_pSheenIndexVar;
IMaterialVar *m_pTintVar;
IMaterialVar *m_pSheenVar; // Overloaded for Weapon Pattern
IMaterialVar *m_pSheenMaskVar; // Weapon Pattern Mask
IMaterialVar *m_pScaleXVar;
IMaterialVar *m_pScaleYVar;
IMaterialVar *m_pOffsetXVar;
IMaterialVar *m_pOffsetYVar;
IMaterialVar *m_pDirectionVar;
float m_flNextStartTime; // Used in the rare case of playermodelpanels with no local player
float m_flScaleX;
float m_flScaleY;
float m_flSheenOffsetX;
float m_flSheenOffsetY;
int m_iSheenDir;
};
EXPOSE_INTERFACE( CProxyAnimatedWeaponSheen, IMaterialProxy, "AnimatedWeaponSheen" IMATERIAL_PROXY_INTERFACE_VERSION );
// StatTrack Proxy
//-----------------------------------------------------------------------------
// StatTrakIllum proxy
//-----------------------------------------------------------------------------
class CStatTrakIllumProxy : public CResultProxy
{
public:
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( void *pC_BaseEntity );
private:
CFloatInput m_flMinVal;
CFloatInput m_flMaxVal;
};
bool CStatTrakIllumProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
if ( !CResultProxy::Init( pMaterial, pKeyValues ) )
return false;
if ( !m_flMinVal.Init( pMaterial, pKeyValues, "minVal", 0.5 ) )
return false;
if ( !m_flMaxVal.Init( pMaterial, pKeyValues, "maxVal", 1 ) )
return false;
return true;
}
void CStatTrakIllumProxy::OnBind( void *pC_BaseEntity )
{
if ( !pC_BaseEntity )
return;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( pEntity )
{
// StatTrak modules are children of their accompanying viewmodels
C_BaseViewModel *pViewModel = dynamic_cast<C_BaseViewModel*>( pEntity->GetMoveParent() );
if ( pViewModel )
{
//SetFloatResult( Lerp( pViewModel->GetStatTrakGlowMultiplier(), m_flMinVal.GetFloat(), m_flMaxVal.GetFloat() ) );
SetFloatResult( 0.75f );
return;
}
}
}
EXPOSE_INTERFACE( CStatTrakIllumProxy, IMaterialProxy, "StatTrakIllum" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// StatTrak 'kill odometer' support: given a numerical value expressed as a string, pick a texture frame to represent a given digit
//-----------------------------------------------------------------------------
class CStatTrakDigitProxy : public CResultProxy
{
public:
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( void *pC_BaseEntity );
virtual bool HelperOnBindGetStatTrakScore( void *pC_BaseEntity, int *piScore );
private:
CFloatInput m_flDisplayDigit; // the particular digit we want to display
CFloatInput m_flTrimZeros;
};
bool CStatTrakDigitProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
if ( !CResultProxy::Init( pMaterial, pKeyValues ) )
return false;
if ( !m_flDisplayDigit.Init( pMaterial, pKeyValues, "displayDigit", 0 ) )
return false;
if ( !m_flTrimZeros.Init( pMaterial, pKeyValues, "trimZeros", 0 ) )
return false;
return true;
}
bool CStatTrakDigitProxy::HelperOnBindGetStatTrakScore( void *pC_BaseEntity, int *piScore )
{
if ( !pC_BaseEntity )
return false;
if ( !piScore )
return false;
bool bReturnValue = false;
uint32 unScore = 0;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( pEntity )
{
// StatTrak modules are children of their accompanying viewmodels
C_ViewmodelAttachmentModel *pViewModel = dynamic_cast<C_ViewmodelAttachmentModel*>( pEntity->GetMoveParent() );
if ( pViewModel )
{
//C_TFPlayer *pPlayer = ToTFPlayer( pViewModel->GetOwnerEntity() );
//if ( pPlayer )
CTFWeaponBase *pWeap = dynamic_cast<CTFWeaponBase*>( pViewModel->GetOwnerEntity() );
if ( pWeap )
{
// Use the strange prefix if the weapon has one.
if ( pWeap->GetAttributeContainer()->GetItem()->FindAttribute( GetKillEaterAttr_Score( 0 ), &unScore ) )
{
*piScore = unScore;
bReturnValue = true;
}
}
}
}
else
{
// No Base entity, may be a straight econ item view (item model panel)
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
if ( pRend )
{
const CEconItemView *pItem = dynamic_cast< CEconItemView* >( pRend );
if ( pItem && pItem->FindAttribute( GetKillEaterAttr_Score( 0 ), &unScore ) )
{
*piScore = unScore;
bReturnValue = true;
}
}
}
return bReturnValue;
}
#ifdef STAGING_ONLY
ConVar tf_stattrak_test_score( "tf_stattrak_test_score", "-1", FCVAR_CHEAT, "" );
#endif // STAGING_ONLY
void CStatTrakDigitProxy::OnBind( void *pC_BaseEntity )
{
int nKillEaterAltScore = 0;
bool bHasScoreToDisplay = HelperOnBindGetStatTrakScore( pC_BaseEntity, &nKillEaterAltScore );
if ( !bHasScoreToDisplay )
{ // Error?
//SetFloatResult( (int)fmod( gpGlobals->curtime, 10.0f ) );
SetFloatResult( 0 );
return;
}
#ifdef STAGING_ONLY
if ( tf_stattrak_test_score.GetInt() > -1 )
{
nKillEaterAltScore = tf_stattrak_test_score.GetInt();
}
#endif // STAGING_ONLY
int iDesiredDigit = (int)m_flDisplayDigit.GetFloat();
// trim preceding zeros
if ( m_flTrimZeros.GetFloat() > 0 )
{
if ( pow( 10.0f, iDesiredDigit ) > nKillEaterAltScore )
{
SetFloatResult( 10.0f ); //assumed blank frame
return;
}
}
// get the [0-9] value of the digit we want
int iDigitCount = MIN( iDesiredDigit, 10 );
for ( int i = 0; i < iDigitCount; i++ )
{
nKillEaterAltScore /= 10;
}
nKillEaterAltScore %= 10;
SetFloatResult( nKillEaterAltScore );
}
EXPOSE_INTERFACE( CStatTrakDigitProxy, IMaterialProxy, "StatTrakDigit" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Stattrak Icon proxy
//-----------------------------------------------------------------------------
class CStatTrakIconProxy : public CResultProxy
{
public:
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( void *pC_BaseEntity );
private:
//CFloatInput m_flMinVal;
};
bool CStatTrakIconProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
/*if ( !CResultProxy::Init( pMaterial, pKeyValues ) )
return false;
if ( !m_flMinVal.Init( pMaterial, pKeyValues, "minVal", 0.5 ) )
return false;
if ( !m_flMaxVal.Init( pMaterial, pKeyValues, "maxVal", 1 ) )
return false;*/
return CResultProxy::Init( pMaterial, pKeyValues );
}
ConVar tf_stattrak_icon_offset_x( "tf_stattrak_icon_offset_x", "0", FCVAR_DEVELOPMENTONLY );
ConVar tf_stattrak_icon_offset_y( "tf_stattrak_icon_offset_y", "0", FCVAR_DEVELOPMENTONLY );
ConVar tf_stattrak_icon_scale( "tf_stattrak_icon_scale", "1.0", FCVAR_DEVELOPMENTONLY );
void CStatTrakIconProxy::OnBind( void *pC_BaseEntity )
{
// Find the StatTracker Type and Lookup the offset, for now hacks!
//if ( !pC_BaseEntity )
// return;
//C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
//if ( pEntity )
//{
// // StatTrak modules are children of their accompanying viewmodels
// C_BaseViewModel *pViewModel = dynamic_cast<C_BaseViewModel*>( pEntity->GetMoveParent() );
// if ( pViewModel )
// {
// //SetFloatResult( Lerp( pViewModel->GetStatTrakGlowMultiplier(), m_flMinVal.GetFloat(), m_flMaxVal.GetFloat() ) );
// SetFloatResult( 0.75f );
// return;
// }
//}
Vector2D center( 0.5, 0.5 );
Vector2D translation( 0, 0 );
VMatrix mat, temp;
mat.Identity();
//if ( m_pCenterVar )
//{
// m_pCenterVar->GetVecValue( center.Base(), 2 );
//}
//MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
//if ( m_pScaleVar )
//{
// Vector2D scale;
// m_pScaleVar->GetVecValue( scale.Base(), 2 );
// MatrixBuildScale( temp, scale.x, scale.y, 1.0f );
// MatrixMultiply( temp, mat, mat );
//}
//if ( m_pRotateVar )
//{
// float angle = m_pRotateVar->GetFloatValue();
// MatrixBuildRotateZ( temp, angle );
// MatrixMultiply( temp, mat, mat );
//}
//MatrixBuildTranslation( temp, center.x, center.y, 0.0f );
//MatrixMultiply( temp, mat, mat );
//if ( m_pTranslateVar )
{
//m_pTranslateVar->GetVecValue( translation.Base(), 2 );
MatrixBuildTranslation( temp, tf_stattrak_icon_offset_x.GetFloat(), tf_stattrak_icon_offset_y.GetFloat(), 0.0f );
MatrixMultiply( temp, mat, mat );
}
m_pResult->SetMatrixValue( mat );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
EXPOSE_INTERFACE( CStatTrakIconProxy, IMaterialProxy, "StatTrakIcon" IMATERIAL_PROXY_INTERFACE_VERSION );
struct TextureVarSetter
{
TextureVarSetter( IMaterialVar* pDestVar, ITexture* pDefaultTexture )
: m_pDestVar( pDestVar )
, m_pTexture( pDefaultTexture )
{
Assert( pDestVar && pDefaultTexture );
}
void SetTexture( ITexture* pTexture ) { m_pTexture = pTexture; }
~TextureVarSetter()
{
m_pDestVar->SetTextureValue( m_pTexture );
}
IMaterialVar* m_pDestVar;
ITexture* m_pTexture;
};
//-----------------------------------------------------------------------------
// Purpose: Used for weapon skins.
//-----------------------------------------------------------------------------
class CWeaponSkinProxy : public IMaterialProxy
{
public:
CWeaponSkinProxy( void )
: m_pMaterial( NULL )
, m_pBaseTextureVar( NULL )
, m_pBaseTextureOrig( NULL )
, m_nGeneration( CRTime::RTime32TimeCur() )
{
}
~CWeaponSkinProxy()
{
SafeRelease( &m_pBaseTextureOrig );
}
inline ITexture* GetWeaponSkinBaseLowRes( bool bPlayerIsLocalPlayer, itemid_t nID, int iTeam ) const
{
if ( !bPlayerIsLocalPlayer )
return NULL;
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
if ( !pLocalInv )
return NULL;
return pLocalInv->GetWeaponSkinBaseLowRes( nID, iTeam );
}
inline bool TestAndSetBaseTexture()
{
if ( m_pBaseTextureOrig )
{
return true;
}
Assert( m_pBaseTextureVar != NULL );
// If the material is in the process of being async loaded, the var won't be a
// texture yet, it'll be a string.
if ( !m_pBaseTextureVar->IsTexture() )
return false;
ITexture* baseTexture = m_pBaseTextureVar->GetTextureValue();
Assert( baseTexture != NULL );
SafeAssign( &m_pBaseTextureOrig, baseTexture );
return true;
}
virtual bool Init( IMaterial *pMaterial, KeyValues* pKeyValues )
{
// We don't support DX8
ConVarRef mat_dxlevel( "mat_dxlevel" );
if ( mat_dxlevel.GetInt() < 90 )
return false;
Assert( pMaterial );
m_pMaterial = pMaterial;
bool bFound = false;
m_pBaseTextureVar = m_pMaterial->FindVar( "$basetexture", &bFound );
if ( !bFound )
return false;
// If we are doing load on demand, this might not be ready yet.
// If not, then don't set it so the OnBind code knows not to rely on it.
// We don't actually care if the code succeeds here.
TestAndSetBaseTexture();
return true;
}
virtual void OnBind( void *pC_BaseEntity )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
// If the base texture isn't ready yet, we cannot composite. So just bail out. Not even sure what
// we could feasibly do in this case to workaround this, we don't have a texture to use yet.
if ( !TestAndSetBaseTexture() )
return;
Assert( m_pBaseTextureVar );
// This will set the texture when it goes out of scope. We can override with other textures along the way.
// This handles the return cases gracefully.
TextureVarSetter setter( m_pBaseTextureVar, m_pBaseTextureOrig );
#ifdef STAGING_ONLY
// If we're not doing paintkits, exit out now (to set the base texture correctly).
if ( tf_paint_kit_disable.GetBool() )
return;
#endif // STAGING_ONLY
CEconItemView *pItem = GetEconItemViewFromProxyEntity( pC_BaseEntity );
if ( !pItem )
return;
C_TFPlayer *pOwner = GetOwnerFromProxyEntity( pC_BaseEntity );
int desiredW = m_pBaseTextureOrig->GetActualWidth();
int desiredH = m_pBaseTextureOrig->GetActualHeight();
const bool cbPlayerIsLocalPlayer = C_TFPlayer::GetLocalTFPlayer() && pOwner == C_TFPlayer::GetLocalTFPlayer();
// Doing material overrides from the econ definitions can cause the same
// item to be referred to from multiple materials. The code treats the
// override material as the controller material.
const IMaterial* pMaterialOverride = pItem->GetMaterialOverride( pItem->GetTeamNumber() );
const bool cbIsControllingMaterial = pMaterialOverride == NULL || pMaterialOverride == m_pMaterial;
// if we're not using high res, check if we should down res
// We may force low res for some composites.
if ( pItem->ShouldWeaponSkinUseLowRes() || ( !pItem->ShouldWeaponSkinUseHighRes() && !cbPlayerIsLocalPlayer ) )
{
const int cDropMips = 2;
desiredW = Max( 1, desiredW >> cDropMips );
desiredH = Max( 1, desiredH >> cDropMips );
}
// If the object's generation isn't equal to when we told it, we need to regenerate it.
if ( cbIsControllingMaterial && ( pItem->GetWeaponSkinGeneration() != m_nGeneration || pItem->GetWeaponSkinGenerationTeam() != pItem->GetTeamNumber() ) )
{
// Skip this so we dont see a pop in staging
#ifdef STAGING_ONLY
if ( !tf_paint_kit_force_regen.GetBool() )
#endif
{
pItem->SetWeaponSkinBase( NULL );
pItem->SetWeaponSkinBaseCompositor( NULL );
}
}
ITexture* pWeaponSkinBase = pItem->GetWeaponSkinBase();
// If we have already completed the composite and stored it (or if there was an error)
// indicate that here.
if ( pWeaponSkinBase )
{
{
setter.SetTexture( pWeaponSkinBase );
// If the texture is the correct res already, we're done!
#ifdef STAGING_ONLY
if ( !tf_paint_kit_force_regen.GetBool() && desiredW == pWeaponSkinBase->GetActualWidth() && desiredH == pWeaponSkinBase->GetActualHeight() )
#else
if ( desiredW == pWeaponSkinBase->GetActualWidth() && desiredH == pWeaponSkinBase->GetActualHeight() )
#endif
return;
}
}
// If we're doing material overrides, we may get in twice--but the non-controlling material should
// bail out now.
if ( !cbIsControllingMaterial )
return;
ITextureCompositor* pWeaponSkinBaseCompositor = pItem->GetWeaponSkinBaseCompositor();
bool bUseLowRes = false;
if ( pWeaponSkinBaseCompositor )
{
ECompositeResolveStatus status = pWeaponSkinBaseCompositor->GetResolveStatus();
bool cleanupCompositor = false;
switch ( status )
{
case ECRS_Idle:
Assert( !"Unexpected state, shouldn't be idle here." );
break;
case ECRS_Scheduled:
// This is fine, this happens when multiple views ask for the same composite on the
// same frame. For example, a Model Panel and the world view.
bUseLowRes = true;
break;
case ECRS_PendingTextureLoads:
// Totally fine, try again later.
bUseLowRes = true;
break;
case ECRS_PendingComposites:
// Totally fine, try again later.
bUseLowRes = true;
break;
case ECRS_Error:
// Had an error, just show the current base texture forever.
// Is this a reasonable error handler? Seems like it is, though maybe
// we want to show the error texture for at least dev mode.
Assert( !"Error while compositing, should figure out wtf.");
pItem->SetWeaponSkinBase( m_pBaseTextureOrig );
cleanupCompositor = true;
break;
case ECRS_Complete:
// Success! Use the new texture for all time. Or whatever.
pWeaponSkinBase = pWeaponSkinBaseCompositor->GetResultTexture();
pItem->SetWeaponSkinBase( pWeaponSkinBase );
setter.SetTexture( pWeaponSkinBase );
cleanupCompositor = true;
break;
default:
Assert( !"Unexpected return value from ITextureCompositor::GetResolveStatus" );
break;
};
if ( cleanupCompositor )
{
pItem->SetWeaponSkinBaseCompositor( NULL );
pWeaponSkinBaseCompositor = NULL;
}
if ( bUseLowRes )
{
ITexture* pTex = GetWeaponSkinBaseLowRes( cbPlayerIsLocalPlayer, pItem->GetItemID(), pItem->GetTeamNumber() );
if ( pTex )
setter.SetTexture( pTex );
}
return;
}
// Start the composite.
KeyValues* rootKV = NULL;
float flWear = 0;
if ( !pItem->GetCustomPaintKitWear( flWear ) )
{
return;
}
int nWear = EconWear_ToIntCategory( flWear );
#ifdef STAGING_ONLY
if ( tf_paint_kit_force_wear.GetInt() > 0 )
{
nWear = Min( tf_paint_kit_force_wear.GetInt(), 5 );
}
#endif // STAGING_ONLY
const char* pItemName = "unknown";
const GameItemDefinition_t* tfItemDef = pItem->GetItemDefinition();
if ( tfItemDef )
{
const char* pMaybeName = tfItemDef->GetPaintKitName();
if ( pMaybeName )
pItemName = pMaybeName;
rootKV = tfItemDef->GetPaintKitWearDefinition( nWear );
}
uint32 nCompositeFlags = 0;
#ifdef STAGING_ONLY
if ( s_bIsPaintkitOverrideSet == true )
{
// Fetch the wear level KV
const char *vArgs = VarArgs( "wear_level_%d", nWear );
FOR_EACH_SUBKEY( s_kvOverridePaintkit, wearKv )
{
if ( !V_strcmp( vArgs, wearKv->GetName() ) )
{
rootKV = wearKv;
break;
}
}
nCompositeFlags = TEX_COMPOSITE_CREATE_FLAGS_FORCE;
}
if ( tf_paint_kit_generating_icons.GetBool() )
{
nCompositeFlags = TEX_COMPOSITE_CREATE_FLAGS_FORCE;
}
#endif // STAGING_ONLY
if ( rootKV )
{
uint64 seed = pItem->GetOriginalID();
#ifdef STAGING_ONLY
if ( tf_paint_kit_seed_override.GetInt() != 0 )
{
seed = tf_paint_kit_seed_override.GetInt();
}
#endif // STAGING_ONLY
Assert( pItem->GetTeamNumber() != TEAM_UNASSIGNED );
int teamNum = pItem->GetTeamNumber() != TEAM_UNASSIGNED ? pItem->GetTeamNumber() : TF_TEAM_RED;
#ifdef STAGING_ONLY
if ( tf_paint_kit_team_override.GetInt() >= 0 )
{
teamNum = tf_paint_kit_team_override.GetInt() == 0 ? TF_TEAM_RED : TF_TEAM_BLUE;
pItem->SetTeamNumber( teamNum );
}
#endif // STAGING_ONLY
char finalItemName[_MAX_PATH];
V_sprintf_safe( finalItemName, "%s_wear_%02d", pItemName, nWear );
SafeAssign( &pWeaponSkinBaseCompositor, materials->NewTextureCompositor( desiredW, desiredH, finalItemName, teamNum, seed, rootKV, nCompositeFlags ) );
if ( pWeaponSkinBaseCompositor )
{
pWeaponSkinBaseCompositor->ScheduleResolve();
pItem->SetWeaponSkinGeneration( m_nGeneration );
pItem->SetWeaponSkinGenerationTeam( teamNum );
if ( pWeaponSkinBaseCompositor->GetResolveStatus() != ECRS_Complete )
{
// Normal case
pItem->SetWeaponSkinBaseCompositor( pWeaponSkinBaseCompositor );
// Try to sub out the low res, if it's ready.
ITexture* pTex = GetWeaponSkinBaseLowRes( cbPlayerIsLocalPlayer, pItem->GetItemID(), pItem->GetTeamNumber() );
if ( pTex )
setter.SetTexture( pTex );
}
else
{
// Had a cache hit, so add the texture here.
pWeaponSkinBase = pWeaponSkinBaseCompositor->GetResultTexture();
pItem->SetWeaponSkinBase( pWeaponSkinBase );
setter.SetTexture( pWeaponSkinBase );
}
SafeRelease( pWeaponSkinBaseCompositor );
return;
}
}
}
virtual void Release() { delete this; }
virtual IMaterial * GetMaterial() { return m_pMaterial; }
private:
IMaterial *m_pMaterial;
IMaterialVar *m_pBaseTextureVar;
ITexture *m_pBaseTextureOrig;
RTime32 m_nGeneration;
bool m_bForceUpdate;
};
EXPOSE_INTERFACE( CWeaponSkinProxy, IMaterialProxy, "WeaponSkin" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: RecvProxy that converts the Player's object UtlVector to entindexes
//-----------------------------------------------------------------------------
void RecvProxy_PlayerObjectList( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_TFPlayer *pPlayer = (C_TFPlayer*)pStruct;
CBaseHandle *pHandle = (CBaseHandle*)(&(pPlayer->m_aObjects[pData->m_iElement]));
RecvProxy_IntToEHandle( pData, pStruct, pHandle );
}
void RecvProxyArrayLength_PlayerObjects( void *pStruct, int objectID, int currentArrayLength )
{
C_TFPlayer *pPlayer = (C_TFPlayer*)pStruct;
if ( pPlayer->m_aObjects.Count() != currentArrayLength )
{
pPlayer->m_aObjects.SetSize( currentArrayLength );
}
pPlayer->ForceUpdateObjectHudState();
}
EXTERN_RECV_TABLE( DT_ScriptCreatedItem );
// specific to the local player
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFLocalPlayerExclusive )
RecvPropVectorXY( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
RecvPropFloat( RECVINFO_NAME( m_vecNetworkOrigin[2], m_vecOrigin[2] ) ),
RecvPropArray2(
RecvProxyArrayLength_PlayerObjects,
RecvPropInt( "player_object_array_element", 0, SIZEOF_IGNORE, 0, RecvProxy_PlayerObjectList ),
MAX_OBJECTS_PER_PLAYER,
0,
"player_object_array" ),
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ), // No longer used by the local player, could be omitted. Preserved for backwards-compat for now.
// RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
RecvPropBool( RECVINFO( m_bIsCoaching ) ),
RecvPropEHandle( RECVINFO( m_hCoach ) ),
RecvPropEHandle( RECVINFO( m_hStudent ) ),
RecvPropInt( RECVINFO( m_nCurrency ) ),
RecvPropInt( RECVINFO( m_nExperienceLevel ) ),
RecvPropInt( RECVINFO( m_nExperienceLevelProgress ) ),
RecvPropInt( RECVINFO( m_bMatchSafeToLeave ) ),
END_RECV_TABLE()
// all players except the local player
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFNonLocalPlayerExclusive )
RecvPropVectorXY( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
RecvPropFloat( RECVINFO_NAME( m_vecNetworkOrigin[2], m_vecOrigin[2] ) ),
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ),
RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
END_RECV_TABLE()
//-----------------------------------------------------------------------------
// Purpose: Data that gets sent to attached medics
//-----------------------------------------------------------------------------
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFSendHealersDataTable )
RecvPropInt( RECVINFO( m_nActiveWpnClip ) ),
END_RECV_TABLE()
IMPLEMENT_CLIENTCLASS_DT( C_TFPlayer, DT_TFPlayer, CTFPlayer )
RecvPropBool(RECVINFO(m_bSaveMeParity)),
RecvPropBool(RECVINFO(m_bIsMiniBoss)),
RecvPropBool(RECVINFO(m_bIsABot)),
RecvPropInt(RECVINFO(m_nBotSkill)),
// This will create a race condition will the local player, but the data will be the same so.....
RecvPropInt( RECVINFO( m_nWaterLevel ) ),
RecvPropEHandle( RECVINFO( m_hRagdoll ) ),
RecvPropDataTable( RECVINFO_DT( m_PlayerClass ), 0, &REFERENCE_RECV_TABLE( DT_TFPlayerClassShared ) ),
RecvPropDataTable( RECVINFO_DT( m_Shared ), 0, &REFERENCE_RECV_TABLE( DT_TFPlayerShared ) ),
RecvPropEHandle( RECVINFO(m_hItem ) ),
RecvPropDataTable( "tflocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFLocalPlayerExclusive) ),
RecvPropDataTable( "tfnonlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFNonLocalPlayerExclusive) ),
RecvPropBool( RECVINFO( m_bAllowMoveDuringTaunt ) ),
RecvPropBool( RECVINFO( m_bIsReadyToHighFive ) ),
RecvPropEHandle( RECVINFO( m_hHighFivePartner ) ),
RecvPropInt( RECVINFO( m_nForceTauntCam ) ),
RecvPropFloat( RECVINFO( m_flTauntYaw ) ),
RecvPropInt( RECVINFO( m_nActiveTauntSlot ) ),
RecvPropInt( RECVINFO( m_iTauntItemDefIndex ) ),
RecvPropFloat( RECVINFO( m_flCurrentTauntMoveSpeed ) ),
RecvPropFloat( RECVINFO( m_flVehicleReverseTime ) ),
RecvPropFloat( RECVINFO( m_flLastDamageTime ) ),
RecvPropBool( RECVINFO( m_bInPowerPlay ) ),
RecvPropInt( RECVINFO( m_iSpawnCounter ) ),
RecvPropBool( RECVINFO( m_bArenaSpectator ) ),
RecvPropDataTable( RECVINFO_DT( m_AttributeManager ), 0, &REFERENCE_RECV_TABLE(DT_AttributeManager) ),
RecvPropFloat( RECVINFO( m_flHeadScale ) ),
RecvPropFloat( RECVINFO( m_flTorsoScale ) ),
RecvPropFloat( RECVINFO( m_flHandScale ) ),
RecvPropBool( RECVINFO( m_bUseBossHealthBar ) ),
RecvPropBool( RECVINFO( m_bUsingVRHeadset ) ),
RecvPropBool( RECVINFO( m_bForcedSkin ) ),
RecvPropInt( RECVINFO( m_nForcedSkin ) ),
RecvPropBool( RECVINFO( m_bGlowEnabled ) ),
RecvPropDataTable("TFSendHealersDataTable", 0, 0, &REFERENCE_RECV_TABLE( DT_TFSendHealersDataTable ) ),
RecvPropFloat( RECVINFO( m_flKartNextAvailableBoost ) ),
RecvPropInt( RECVINFO( m_iKartHealth ) ),
RecvPropInt( RECVINFO( m_iKartState ) ),
RecvPropEHandle( RECVINFO( m_hGrapplingHookTarget ) ),
RecvPropEHandle( RECVINFO( m_hSecondaryLastWeapon ) ),
RecvPropBool( RECVINFO( m_bUsingActionSlot ) ),
RecvPropFloat( RECVINFO( m_flInspectTime ) ),
RecvPropInt( RECVINFO( m_iCampaignMedals ) ),
RecvPropInt( RECVINFO( m_iPlayerSkinOverride ) ),
END_RECV_TABLE()
BEGIN_PREDICTION_DATA( C_TFPlayer )
DEFINE_PRED_TYPEDESCRIPTION( m_Shared, CTFPlayerShared ),
DEFINE_PRED_FIELD( m_nSkin, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
DEFINE_PRED_FIELD( m_nBody, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
DEFINE_PRED_FIELD( m_nSequence, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_flPlaybackRate, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_ARRAY_TOL( m_flEncodedController, FIELD_FLOAT, MAXSTUDIOBONECTRLS, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE, 0.02f ),
DEFINE_PRED_FIELD( m_nNewSequenceParity, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_nResetEventsParity, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_nMuzzleFlashParity, FIELD_CHARACTER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
DEFINE_PRED_FIELD( m_hOffHandWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flTauntYaw, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flCurrentTauntMoveSpeed, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flVehicleReverseTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
DEFINE_PRED_FIELD( m_flInspectTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
// ------------------------------------------------------------------------------------------ //
// C_TFPlayer implementation.
// ------------------------------------------------------------------------------------------ //
C_TFPlayer::C_TFPlayer() :
m_iv_angEyeAngles( "C_TFPlayer::m_iv_angEyeAngles" ),
m_mapOverheadEffects( DefLessFunc( const char * ) )
{
m_pAttributes = this;
m_PlayerAnimState = CreateTFPlayerAnimState( this );
m_Shared.Init( this );
m_iIDEntIndex = 0;
AddVar( &m_angEyeAngles, &m_iv_angEyeAngles, LATCH_SIMULATION_VAR );
memset( m_pKartParticles, NULL, sizeof( m_pKartParticles ) );
memset( m_pKartSounds, NULL, sizeof( m_pKartSounds ) );
m_pMVMEyeGlowEffect[ 0 ] = NULL;
m_pMVMEyeGlowEffect[ 1 ] = NULL;
m_pEyeGlowEffect[ 0 ] = NULL;
m_pEyeGlowEffect[ 1 ] = NULL;
m_pszEyeGlowEffectName[0] = '\0';
m_vEyeGlowColor1.Zero();
m_vEyeGlowColor2.Zero();
m_flNextSheenStartTime = 0;
m_pTeleporterEffect = NULL;
m_pBurningSound = NULL;
m_pBurningEffect = NULL;
m_pUrineEffect = NULL;
m_pMilkEffect = NULL;
m_pSoldierOffensiveBuffEffect = NULL;
m_pSoldierDefensiveBuffEffect = NULL;
m_pSoldierOffensiveHealthRegenBuffEffect = NULL;
m_pSoldierNoHealingDamageBuffEffect = NULL;
m_pCritBoostEffect = NULL;
m_flBurnEffectStartTime = 0;
m_pDisguisingEffect = NULL;
m_pSaveMeEffect = NULL;
m_pTauntWithMeEffect = NULL;
m_hOldObserverTarget = NULL;
m_iOldObserverMode = OBS_MODE_NONE;
m_pStunnedEffect = NULL;
m_pPhaseStandingEffect = NULL;
m_pRadiusHealEffect = NULL;
m_pKingRuneRadiusEffect = NULL;
m_pKingBuffRadiusEffect = NULL;
m_pRunePlagueEffect = NULL;
m_pMegaHealEffect = NULL;
m_pTempShield = NULL;
m_pMVMBotRadiowave = NULL;
m_pRuneChargeReadyEffect = NULL;
m_aGibs.Purge();
m_aNormalGibs.PurgeAndDeleteElements();
m_aSillyGibs.Purge();
m_bCigaretteSmokeActive = false;
m_hRagdoll.Set( NULL );
m_iPreviousMetal = 0;
m_bIsDisplayingNemesisIcon = false;
m_bIsDisplayingDuelingIcon = false;
m_bIsDisplayingIconForIT = false;
m_bShouldShowBirthdayEffect = false;
m_bWasTaunting = false;
m_angTauntPredViewAngles.Init();
m_angTauntEngViewAngles.Init();
m_pTauntSoundLoop = NULL;
m_flWaterImpactTime = 0.0f;
m_rtSpottedInPVSTime = 0;
m_rtJoinedSpectatorTeam = 0;
m_rtJoinedNormalTeam = 0;
m_flWaterEntryTime = 0;
m_nOldWaterLevel = WL_NotInWater;
m_bWaterExitEffectActive = false;
m_bUpdateObjectHudState = false;
m_flSaveMeExpireTime = 0;
m_bWasHealedByLocalPlayer = false;
m_bDuckJumpInterp = false;
m_flFirstDuckJumpInterp = 0.0f;
m_flLastDuckJumpInterp = 0.0f;
m_flDuckJumpInterp = 0.0f;
m_bIsCoaching = false;
m_pStudentGlowEffect = NULL;
m_pPowerupGlowEffect = NULL;
m_nBotSkill = -1;
m_nOldBotSkill = -1;
m_nOldMaxHealth = -1;
m_bIsCalculatingMaximumSpeed = false;
m_bBodygroupsDirty = false;
m_pBlastJumpLoop = NULL;
m_flBlastJumpLaunchTime = 0.f;
m_nExperienceLevel = 0;
m_nExperienceLevelProgress = 0;
m_nPrevExperienceLevel = 0;
m_bMatchSafeToLeave = true;
for( int i=0; i<kBonusEffect_Count; ++i )
{
m_flNextMiniCritEffectTime[i] = 0;
}
m_flHeadScale = 1.f;
m_flTorsoScale = 1.f;
m_flHandScale = 1.f;
m_bIsMiniBoss = false;
m_bUseBossHealthBar = false;
m_bUsingVRHeadset = false;
m_bForcedSkin = false;
m_nForcedSkin = 0;
m_flChangeClassTime = 0.f;
m_hRevivePrompt = NULL;
m_bIsDisplayingTranqMark = false;
m_eDisplayingRuneIcon = RUNE_NONE;
m_pKart = NULL;
m_iOldKartHealth = 0;
m_bUsingActionSlot = false;
m_iCampaignMedals = 0;
m_flInspectTime = 0.f;
m_bNotifiedWeaponInspectThisLife = false;
m_pPasstimePlayerReticle = NULL;
m_pPasstimeAskForBallReticle = NULL;
m_iPlayerSkinOverride = 0;
ListenForGameEvent( "player_hurt" );
ListenForGameEvent( "hltv_changed_mode" );
ListenForGameEvent( "hltv_changed_target" );
ListenForGameEvent( "post_inventory_application" );
ListenForGameEvent( "rocket_jump" );
ListenForGameEvent( "rocket_jump_landed" );
ListenForGameEvent( "sticky_jump" );
ListenForGameEvent( "sticky_jump_landed" );
ListenForGameEvent( "player_spawn" );
ListenForGameEvent( "damage_resisted" );
ListenForGameEvent( "revive_player_notify" );
ListenForGameEvent( "revive_player_stopped" );
ListenForGameEvent( "player_changeclass" );
ListenForGameEvent( "player_abandoned_match" );
#ifdef STAGING_ONLY
ListenForGameEvent( "player_death" );
#endif
//AddPhonemeFile
engine->AddPhonemeFile( "scripts/game_sounds_vo_phonemes.txt" );
engine->AddPhonemeFile( "scripts/game_sounds_vo_phonemes_local.txt" ); // Stomp over english for phoneme data
engine->AddPhonemeFile( NULL ); // Null indicates to engine that we are done loading phonemes if there are any present
}
C_TFPlayer::~C_TFPlayer()
{
ShowNemesisIcon( false );
ShowDuelingIcon( false );
m_PlayerAnimState->Release();
if ( m_pBlastJumpLoop )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pBlastJumpLoop );
m_pBlastJumpLoop = NULL;
}
StopTauntSoundLoop();
delete m_pPasstimePlayerReticle;
delete m_pPasstimeAskForBallReticle;
if ( IsLocalPlayer() )
{
g_ItemEffectMeterManager.ClearExistingMeters();
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
{
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "CompetitiveGame_RestoreChatWindow", false );
}
}
}
// NOTE: This is NOT called every time the player respawns!!
// only the first time we spawn a player into the world
void C_TFPlayer::Spawn( void )
{
m_AttributeManager.SetPlayer( this );
m_AttributeList.SetManager( &m_AttributeManager );
BaseClass::Spawn();
/*
// some extra stuff here because s_pLocalPlayer is not yet initialized
int iLocalPlayerIndex = engine->GetLocalPlayer();
if ( entindex() == iLocalPlayerIndex && !m_LeaveServerTimer.HasStarted() )
{
ConVarRef random_spec_server_mode( "random_spec_server_mode" );
if ( random_spec_server_mode.IsValid() && random_spec_server_mode.GetBool() )
{
m_LeaveServerTimer.Start( spectate_random_server_basetime.GetFloat() );
}
}
*/
UpdateInventory( true );
UpdateMVMEyeGlowEffect( true );
SetShowHudMenuTauntSelection( false );
CleanUpAnimationOnSpawn();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InventoryUpdated( CPlayerInventory *pInventory )
{
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
{
CHudUpgradePanel *pUpgradePanel = GET_HUDELEMENT( CHudUpgradePanel );
if ( pUpgradePanel && pUpgradePanel->IsVisible() )
{
pUpgradePanel->PlayerInventoryChanged( this );
}
}
return;
}
//-----------------------------------------------------------------------------
// Purpose: Request this player's inventories from the steam backend
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateInventory( bool bInit )
{
#if !defined(NO_STEAM)
if ( bInit )
{
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_bInventoryReceived = !bInvalid;
#endif
}
C_TFPlayer* C_TFPlayer::GetLocalTFPlayer()
{
return ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
}
const QAngle& C_TFPlayer::GetRenderAngles()
{
if ( IsRagdoll() )
{
return vec3_angle;
}
else
{
return m_PlayerAnimState->GetRenderAngles();
}
}
bool C_TFPlayer::CanDisplayAllSeeEffect( EAttackBonusEffects_t effect ) const
{
if( effect >= EAttackBonusEffects_t(0) && effect < kBonusEffect_Count )
{
return gpGlobals->curtime > m_flNextMiniCritEffectTime[ effect ];
}
return true;
}
void C_TFPlayer::SetNextAllSeeEffectTime( EAttackBonusEffects_t effect, float flTime )
{
if( effect >= EAttackBonusEffects_t(0) && effect < kBonusEffect_Count )
{
if ( gpGlobals->curtime > m_flNextMiniCritEffectTime[ effect ] )
{
m_flNextMiniCritEffectTime[ effect ] = flTime;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateOnRemove( void )
{
// Stop the taunt.
if ( m_bWasTaunting )
{
// Need to go ahead and call both. Otherwise, if we changelevel while we're taunting or
// otherwise in "game wants us in third person mode", we will stay in third person mode
// in the new map.
TurnOffTauntCam();
TurnOffTauntCam_Finish();
}
// HACK!!! ChrisG needs to fix this in the particle system.
ParticleProp()->OwnerSetDormantTo( true );
ParticleProp()->StopParticlesInvolving( this );
m_Shared.RemoveAllCond();
m_Inventory.RemoveListener( this );
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Purpose: returns max health for this player
//-----------------------------------------------------------------------------
int C_TFPlayer::GetMaxHealth( void ) const
{
return ( g_TF_PR ) ? g_TF_PR->GetMaxHealth( entindex() ) : 1;
}
//-----------------------------------------------------------------------------
// Purpose: returns max buffed health for this player
//-----------------------------------------------------------------------------
int C_TFPlayer::GetMaxHealthForBuffing( void ) const
{
return ( g_TF_PR ) ? g_TF_PR->GetMaxHealthForBuffing( entindex() ) : 1;
}
//-----------------------------------------------------------------------------
// Deal with recording
//-----------------------------------------------------------------------------
void C_TFPlayer::GetToolRecordingState( KeyValues *msg )
{
#ifndef _XBOX
BaseClass::GetToolRecordingState( msg );
BaseEntityRecordingState_t *pBaseEntityState = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" );
bool bDormant = IsDormant();
bool bDead = !IsAlive();
bool bSpectator = ( GetTeamNumber() == TEAM_SPECTATOR );
bool bNoRender = ( GetRenderMode() == kRenderNone );
bool bDeathCam = (GetObserverMode() == OBS_MODE_DEATHCAM);
bool bNoDraw = IsEffectActive(EF_NODRAW);
bool bVisible =
!bDormant &&
!bDead &&
!bSpectator &&
!bNoRender &&
!bDeathCam &&
!bNoDraw;
bool changed = m_bToolRecordingVisibility != bVisible;
// Remember state
m_bToolRecordingVisibility = bVisible;
pBaseEntityState->m_bVisible = bVisible;
if ( changed && !bVisible )
{
// If the entity becomes invisible this frame, we still want to record a final animation sample so that we have data to interpolate
// toward just before the logs return "false" for visiblity. Otherwise the animation will freeze on the last frame while the model
// is still able to render for just a bit.
pBaseEntityState->m_bRecordFinalVisibleSample = true;
}
#endif
}
void C_TFPlayer::UpdateClientSideAnimation()
{
// Update the animation data. It does the local check here so this works when using
// a third-person camera (and we don't have valid player angles).
if ( this == C_TFPlayer::GetLocalTFPlayer() )
{
// m_angEyeAngles comes from the server, and updates are infrequent, so use the local values instead.
QAngle LocalEyeAngles = EyeAngles();
m_PlayerAnimState->Update( LocalEyeAngles[YAW], LocalEyeAngles[PITCH] );
}
else
{
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
}
// StatTrak Module Test
// Update ViewModels
// We only update the view model for the local player.
//if ( IsLocalPlayer() )
{
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->UpdateAllViewmodelAddons();
}
}
BaseClass::UpdateClientSideAnimation();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetDormant( bool bDormant )
{
// If I'm burning, stop the burning sounds
if ( !IsDormant() && bDormant )
{
if ( m_pBurningSound)
{
StopBurningSound();
}
if ( m_bIsDisplayingNemesisIcon )
{
ShowNemesisIcon( false );
}
if ( m_bIsDisplayingDuelingIcon )
{
ShowDuelingIcon( false );
}
if ( m_bIsDisplayingIconForIT )
{
ShowIconForIT( false );
}
UpdatedMarkedForDeathEffect( true );
UpdateRuneIcon( true );
#ifdef STAGING_ONLY
if ( m_bIsDisplayingTranqMark )
{
UpdateTranqMark( false, true );
}
#endif // STAGING_ONLY
if ( m_bShouldShowBirthdayEffect )
{
ShowBirthdayEffect( false );
}
}
if ( IsDormant() && !bDormant )
{
SetBodygroupsDirty();
if ( IsTaunting() )
{
float flCycle = 0.f;
if ( m_flTauntDuration > 0.f )
{
float dt = gpGlobals->curtime - m_flTauntStartTime;
while ( dt >= m_flTauntDuration )
{
dt -= m_flTauntDuration;
}
flCycle = dt / m_flTauntDuration;
flCycle = clamp( flCycle, 0.f, 1.0f );
}
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
m_PlayerAnimState->AddVCDSequenceToGestureSlot( GESTURE_SLOT_VCD, m_nTauntSequence, flCycle, true );
}
}
if ( bDormant == false )
{
m_rtSpottedInPVSTime = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetServerRealTime() : CRTime::RTime32TimeCur();
}
// Deliberately skip base combat weapon
C_BaseEntity::SetDormant( bDormant );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnPreDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnPreDataChanged( updateType );
m_iOldHealth = m_iHealth;
m_iOldPlayerClass = m_PlayerClass.GetClassIndex();
m_iOldSpawnCounter = m_iSpawnCounter;
m_bOldSaveMeParity = m_bSaveMeParity;
m_nOldWaterLevel = GetWaterLevel();
m_iOldTeam = GetTeamNumber();
C_TFPlayerClass *pClass = GetPlayerClass();
m_iOldClass = pClass ? pClass->GetClassIndex() : TF_CLASS_UNDEFINED;
m_bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
m_iOldDisguiseTeam = m_Shared.GetDisguiseTeam();
m_iOldDisguiseClass = m_Shared.GetDisguiseClass();
m_flPrevTauntYaw = m_flTauntYaw;
m_nPrevTauntSlot = m_nActiveTauntSlot;
m_iPrevTauntItemDefIndex = m_iTauntItemDefIndex;
if ( !IsReplay() )
{
m_iOldObserverMode = GetObserverMode();
m_hOldObserverTarget = GetObserverTarget();
}
m_nOldCurrency = m_nCurrency;
m_Shared.OnPreDataChanged();
m_bOldCustomModelVisible = m_PlayerClass.CustomModelIsVisibleToSelf();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnDataChanged( DataUpdateType_t updateType )
{
// C_BaseEntity assumes we're networking the entity's angles, so pretend that it
// networked the same value we already have.
SetNetworkAngles( GetLocalAngles() );
BaseClass::OnDataChanged( updateType );
if ( updateType == DATA_UPDATE_CREATED )
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
InitInvulnerableMaterial();
// There used to be code here to switch to first person. This breaks thirdperson mode
// and karts when we have to get a full client update--don't do it here anymore.
// We may need to do something more clever if this breaks something.
}
else
{
if ( m_iOldTeam != GetTeamNumber() || m_iOldDisguiseTeam != m_Shared.GetDisguiseTeam() )
{
InitInvulnerableMaterial();
}
if ( m_iOldDisguiseClass != m_Shared.GetDisguiseClass() )
{
RemoveAllDecals();
}
if ( m_nOldBotSkill != m_nBotSkill )
{
UpdateMVMEyeGlowEffect( true );
m_nOldBotSkill = m_nBotSkill;
}
if ( m_nOldMaxHealth != GetMaxHealth() )
{
UpdateMVMEyeGlowEffect( true );
m_nOldMaxHealth = GetMaxHealth();
}
if ( m_nPrevTauntSlot != m_nActiveTauntSlot || m_iPrevTauntItemDefIndex != m_iTauntItemDefIndex )
{
UpdateTauntItem();
}
if ( m_iOldKartHealth != m_iKartHealth )
{
UpdateKartEffects();
}
if ( m_iOldKartState != m_iKartState )
{
UpdateKartState();
}
}
GetAttributeManager()->OnDataChanged( updateType );
// Check for full health and remove decals.
if ( ( m_iHealth > m_iOldHealth && m_iHealth >= GetMaxHealth() ) || m_Shared.IsInvulnerable() )
{
// If we were just fully healed, remove all decals
RemoveAllDecals();
}
if ( ( m_iOldHealth != m_iHealth ) || ( m_iOldTeam != GetTeamNumber() ) )
{
UpdateGlowColor();
}
bool bNeedsStudentGlow = m_hCoach && m_hCoach->IsLocalPlayer() && m_hCoach->m_bIsCoaching;
bool bHasStudentGlow = m_pStudentGlowEffect != NULL;
if ( bNeedsStudentGlow != bHasStudentGlow )
{
UpdateGlowEffect();
}
if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
{
if ( m_Shared.InCond( TF_COND_KING_BUFFED ) )
{
const char *m_szRadiusEffect;
int nTeamNumber = GetTeamNumber();
if ( IsPlayerClass( TF_CLASS_SPY ) && m_Shared.InCond( TF_COND_DISGUISED ) )
{
if ( !IsLocalPlayer() && GetTeamNumber() == GetLocalPlayerTeam() ) // Always display own team colors even when disguised, unless it's you (same rules as uber skin)
{
nTeamNumber = GetLocalPlayerTeam();
}
else
{
nTeamNumber = m_Shared.GetDisguiseTeam();
}
}
if ( nTeamNumber == TF_TEAM_RED )
{
m_szRadiusEffect = "powerup_king_red";
}
else
{
m_szRadiusEffect = "powerup_king_blue";
}
if ( !m_pKingBuffRadiusEffect )
{
m_pKingBuffRadiusEffect = ParticleProp()->Create( m_szRadiusEffect, PATTACH_ABSORIGIN_FOLLOW );
}
}
else if ( m_pKingBuffRadiusEffect )
{
m_Shared.EndKingBuffRadiusEffect();
}
bool bNeedsPowerupGlow = ShouldShowPowerupGlowEffect();
bool bHasPowerupGlow = m_pPowerupGlowEffect != NULL;
if ( bNeedsPowerupGlow != bHasPowerupGlow )
{
UpdateGlowEffect();
}
}
// Detect class changes
if ( m_iOldPlayerClass != m_PlayerClass.GetClassIndex() )
{
OnPlayerClassChange();
}
bool bJustSpawned = false;
if ( m_iOldSpawnCounter != m_iSpawnCounter )
{
ClientPlayerRespawn();
bJustSpawned = true;
}
if ( m_bSaveMeParity != m_bOldSaveMeParity )
{
// Player has triggered a save me command
CreateSaveMeEffect();
}
// To better support old demos, which have some screwed up flags, we just ignore various things if we're a SourceTV client.
if ( !IsHLTV() )
{
if ( m_Shared.InCond( TF_COND_BURNING ) && !m_pBurningSound )
{
StartBurningSound();
}
bool bShouldShowIconForIT = TFGameRules() && TFGameRules()->IsIT( this ) && !IsLocalPlayer();
if ( bShouldShowIconForIT != m_bIsDisplayingIconForIT )
{
ShowIconForIT( bShouldShowIconForIT );
}
bool bShouldShowBirthdayEffect = false;//TFGameRules() && ( TFGameRules()->GetBirthdayPlayer() == this ) && !IsLocalPlayer();
if ( bShouldShowBirthdayEffect != m_bShouldShowBirthdayEffect )
{
ShowBirthdayEffect( bShouldShowBirthdayEffect );
}
bool bShouldShowDuelingIcon = ShouldShowDuelingIcon();
if ( bShouldShowDuelingIcon != m_bIsDisplayingDuelingIcon )
{
ShowDuelingIcon( bShouldShowDuelingIcon );
}
// See if we should show or hide nemesis icon for this player
bool bShouldDisplayNemesisIcon = ( !bShouldShowDuelingIcon && !m_bIsDisplayingIconForIT && ShouldShowNemesisIcon() );
if ( bShouldDisplayNemesisIcon != m_bIsDisplayingNemesisIcon )
{
ShowNemesisIcon( bShouldDisplayNemesisIcon );
}
m_Shared.OnDataChanged();
if ( m_bDisguised != m_Shared.InCond( TF_COND_DISGUISED ) )
{
m_flDisguiseEndEffectStartTime = MAX( m_flDisguiseEndEffectStartTime, gpGlobals->curtime );
// Update visibility of any worn items.
UpdateWearables();
SetBodygroupsDirty();
// Remove decals.
RemoveAllDecals();
if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY )
{
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
UpdateMVMEyeGlowEffect( false );
}
else
{
UpdateMVMEyeGlowEffect( true );
}
}
}
}
int nNewWaterLevel = GetWaterLevel();
if ( nNewWaterLevel != m_nOldWaterLevel )
{
if ( ( m_nOldWaterLevel == WL_NotInWater ) && ( nNewWaterLevel > WL_NotInWater ) )
{
// Set when we do a transition to/from partially in water to completely out
m_flWaterEntryTime = gpGlobals->curtime;
}
// If player is now up to his eyes in water and has entered the water very recently (not just bobbing eyes in and out), play a bubble effect.
if ( ( nNewWaterLevel == WL_Eyes ) && ( gpGlobals->curtime - m_flWaterEntryTime ) < 0.5f )
{
CNewParticleEffect *pEffect = ParticleProp()->Create( "water_playerdive", PATTACH_ABSORIGIN_FOLLOW );
ParticleProp()->AddControlPoint( pEffect, 1, NULL, PATTACH_WORLDORIGIN, NULL, WorldSpaceCenter() );
}
// If player was up to his eyes in water and is now out to waist level or less, play a water drip effect
else if ( m_nOldWaterLevel == WL_Eyes && ( nNewWaterLevel < WL_Eyes ) && !bJustSpawned )
{
CNewParticleEffect *pWaterExitEffect = ParticleProp()->Create( "water_playeremerge", PATTACH_ABSORIGIN_FOLLOW );
ParticleProp()->AddControlPoint( pWaterExitEffect, 1, this, PATTACH_ABSORIGIN_FOLLOW );
m_bWaterExitEffectActive = true;
}
}
if ( IsLocalPlayer() )
{
if ( updateType == DATA_UPDATE_CREATED )
{
SetupHeadLabelMaterials();
GetClientVoiceMgr()->SetHeadLabelOffset( 50 );
}
if ( m_iOldTeam != GetTeamNumber() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changeteam" );
if ( event )
{
gameeventmanager->FireEventClientSide( event );
}
if ( IsX360() )
{
const char *pTeam = NULL;
switch( GetTeamNumber() )
{
case TF_TEAM_RED:
pTeam = "red";
break;
case TF_TEAM_BLUE:
pTeam = "blue";
break;
case TEAM_SPECTATOR:
pTeam = "spectate";
break;
}
if ( pTeam )
{
engine->ChangeTeam( pTeam );
}
}
// let the server know that we're using a VR headset
if ( UseVR() )
{
KeyValues *kv = new KeyValues( "UsingVRHeadset" );
engine->ServerCmdKeyValues( kv );
}
}
if ( !IsPlayerClass(m_iOldClass) )
{
m_flChangeClassTime = gpGlobals->curtime;
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changeclass" );
if ( event )
{
event->SetInt( "updateType", updateType );
gameeventmanager->FireEventClientSide( event );
}
#ifdef STAGING_ONLY
// Spy Tranq Mark
// Update tranq mark if you were or are a spy
if ( m_iOldClass == TF_CLASS_SPY || IsPlayerClass( TF_CLASS_SPY ) )
{
bool bShowTranqMark = IsPlayerClass( TF_CLASS_SPY );
for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex )
{
C_TFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
if ( pTFPlayer )
{
pTFPlayer->UpdateTranqMark( bShowTranqMark );
}
}
}
#endif // STAGING_ONLY
}
bool bUpdateAttachedWeapons = (GetObserverTarget() != m_hOldObserverTarget);
if ( m_iOldObserverMode != GetObserverMode() )
{
if ( m_iOldObserverMode == OBS_MODE_NONE )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_becameobserver" );
if ( event )
{
gameeventmanager->FireEventClientSide( event );
}
// NVNT send spectator nav
if ( haptics )
haptics->SetNavigationClass("spectate");
}
else if ( m_iOldObserverMode < OBS_MODE_FIXED && GetObserverMode() >= OBS_MODE_FIXED )
{
#if defined( REPLAY_ENABLED )
// If the player is entering a title for a replay, defer displaying the items screen until afterwards
if ( !IsReplayInputPanelVisible() )
#endif
{
// Show items we've picked up when we exit freezecam, or after deathcam on suicide
TFInventoryManager()->ShowItemsPickedUp();
}
}
if ( m_iOldObserverMode == OBS_MODE_IN_EYE || GetObserverMode() == OBS_MODE_IN_EYE )
{
bUpdateAttachedWeapons = true;
}
if ( m_iOldObserverMode == OBS_MODE_IN_EYE )
{
CBaseEntity* pObserveTarget = GetObserverTarget();
if( pObserveTarget )
{
pObserveTarget->UpdateVisibility();
}
}
// NVNT send onfoot nav if observer mode is none.
if(GetObserverMode()==OBS_MODE_NONE &&haptics)
{
haptics->SetNavigationClass("on_foot");
}
if ( IsReplay() )
{
m_iOldObserverMode = GetObserverMode();
}
}
if ( bUpdateAttachedWeapons )
{
C_TFPlayer *pTFOldObserverTarget = ToTFPlayer( m_hOldObserverTarget.Get() );
if ( m_hOldObserverTarget != GetObserverTarget() && pTFOldObserverTarget )
{
C_TFWeaponBase *pWeapon = pTFOldObserverTarget->m_Shared.GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->UpdateAttachmentModels();
}
// Update visibility of any worn items.
pTFOldObserverTarget->UpdateWearables();
pTFOldObserverTarget->SetBodygroupsDirty();
if ( IsReplay() )
{
m_hOldObserverTarget = GetObserverTarget();
}
}
C_TFPlayer *pTFObserverTarget = ToTFPlayer( GetObserverTarget() );
if ( pTFObserverTarget )
{
C_TFWeaponBase *pWeapon = pTFObserverTarget->m_Shared.GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->UpdateAttachmentModels();
}
// Update visibility of any worn items.
pTFObserverTarget->UpdateWearables();
pTFObserverTarget->SetBodygroupsDirty();
}
}
if ( m_iOldClass == TF_CLASS_SPY &&
( m_bDisguised != m_Shared.InCond( TF_COND_DISGUISED ) || m_iOldDisguiseClass != m_Shared.GetDisguiseClass() ) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changedisguise" );
if ( event )
{
event->SetBool( "disguised", m_Shared.InCond( TF_COND_DISGUISED ) );
gameeventmanager->FireEventClientSide( event );
}
}
// If our metal amount changed, send a game event
int iCurrentMetal = GetAmmoCount( TF_AMMO_METAL );
if ( iCurrentMetal != m_iPreviousMetal )
{
//msg
IGameEvent *event = gameeventmanager->CreateEvent( "player_account_changed" );
if ( event )
{
event->SetInt( "old_account", m_iPreviousMetal );
event->SetInt( "new_account", iCurrentMetal );
gameeventmanager->FireEventClientSide( event );
}
m_iPreviousMetal = iCurrentMetal;
}
// did the local player get any health?
if ( m_iHealth > m_iOldHealth )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_healed" );
if ( event )
{
event->SetInt( "amount", m_iHealth - m_iOldHealth );
gameeventmanager->FireEventClientSide( event );
}
}
if ( m_nOldCurrency != m_nCurrency )
{
IGameEvent *event = gameeventmanager->CreateEvent( "player_currency_changed" );
if ( event )
{
event->SetInt( "currency", m_nCurrency );
gameeventmanager->FireEventClientSide( event );
}
}
if ( m_bOldCustomModelVisible != m_PlayerClass.CustomModelIsVisibleToSelf() )
{
UpdateVisibility();
}
}
C_TFWeaponBase *pOldActiveWeapon = assert_cast< CTFWeaponBase* >( m_hOldActiveWeapon.Get() );
C_TFWeaponBase *pActiveWeapon = GetActiveTFWeapon();
if ( pOldActiveWeapon != pActiveWeapon )
{
// make sure weapons data are up to date before doing anything here
UpdateClientData();
if ( pOldActiveWeapon )
{
pOldActiveWeapon->UpdateVisibility();
}
if ( pActiveWeapon )
{
pActiveWeapon->UpdateVisibility();
if ( GetLocalTFPlayer() == this && pActiveWeapon->CanInspect() )
{
HandleInspectHint();
}
}
m_hOldActiveWeapon = pActiveWeapon;
// Update HandPoses if needed
CBaseAnimating *pWeaponModel = GetRenderedWeaponModel();
if ( !pWeaponModel )
return;
int iPose = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iPose, righthand_pose_parameter );
float flMin, flMax = 0;
int iPoseParam = LookupPoseParameter( "r_hand_grip" );
if ( iPoseParam > 0 )
{
// SetPoseParameter() normalize's values. r_hand_grip is special and goes beyond 0..1 so we have to counter act the normalization
GetPoseParameterRange( iPoseParam, flMin, flMax );
SetPoseParameter( iPoseParam, iPose * flMax );
}
pWeaponModel->SetPoseParameter( "r_hand_grip", iPose );
}
// Some time in this network transmit we changed the size of the object array.
// recalc the whole thing and update the hud
if ( m_bUpdateObjectHudState )
{
IGameEvent *event = gameeventmanager->CreateEvent( "building_info_changed" );
if ( event )
{
event->SetInt( "building_type", -1 );
gameeventmanager->FireEventClientSide( event );
}
m_bUpdateObjectHudState = false;
}
if ( m_iOldTeam != GetTeamNumber() )
{
if ( GetTeamNumber() == TEAM_SPECTATOR )
{
m_rtJoinedSpectatorTeam = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetServerRealTime() : CRTime::RTime32TimeCur();
}
else if ( m_iOldTeam != TF_TEAM_RED && m_iOldTeam != TF_TEAM_BLUE )
{
m_rtJoinedNormalTeam = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetServerRealTime() : CRTime::RTime32TimeCur();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateTauntItem()
{
if ( m_nActiveTauntSlot == LOADOUT_POSITION_INVALID )
{
if ( m_iTauntItemDefIndex != INVALID_ITEM_DEF_INDEX )
{
m_TauntEconItemView.Init( m_iTauntItemDefIndex, AE_UNIQUE, 1 );
}
else
{
m_TauntEconItemView.Invalidate();
}
}
else
{
int iClass = GetPlayerClass()->GetClassIndex();
CEconItemView *pMiscItemView = Inventory() ? Inventory()->GetItemInLoadout( iClass, m_nActiveTauntSlot ) : NULL;
if ( pMiscItemView )
{
m_TauntEconItemView = *pMiscItemView;
}
}
if ( m_TauntEconItemView.IsValid() )
{
ParseSharedTauntDataFromEconItemView( &m_TauntEconItemView );
}
}
ConVar tf_halloween_kart_sound_slow_pitch( "tf_halloween_kart_sound_slow_pitch", "30.f", FCVAR_CHEAT | FCVAR_REPLICATED );
ConVar tf_halloween_kart_sound_fast_pitch( "tf_halloween_kart_sound_fast_pitch", "80.f", FCVAR_CHEAT | FCVAR_REPLICATED );
extern ConVar tf_halloween_kart_dash_speed;
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateKartEffects()
{
if ( m_hKartDamageEffect )
{
ParticleProp()->StopEmission( m_hKartDamageEffect );
m_hKartDamageEffect = NULL;
}
if ( !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return;
m_iOldKartHealth = m_iKartHealth;
const char *pszEffect = "";
if ( m_iKartHealth > 150 )
{
pszEffect = "kartdamage_4";
m_hKartDamageEffect = ParticleProp()->Create( pszEffect, PATTACH_ABSORIGIN_FOLLOW, INVALID_PARTICLE_ATTACHMENT, Vector(0,0,40) );
}
}
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateKartState()
{
if ( !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
return;
m_iOldKartState = m_iKartState;
// turn brake on and off
if ( m_iKartState & CTFPlayerShared::kKartState_Braking )
{
StartKartBrakeEffect();
}
else
{
StopKartBrakeEffect();
}
}
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateKartSounds()
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
// Create our engine sound if we dont have one and need one
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
if ( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] == NULL )
{
CBroadcastRecipientFilter filter;
m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] = controller.SoundCreate( filter, entindex(), "BumperCar.GoLoop" );
controller.Play( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ], 1.f, 1.f );
}
}
else if ( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] )
{
controller.SoundDestroy( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] );
m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] = NULL;
}
if ( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] )
{
float flTargetPitch = RemapValClamped( GetCurrentTauntMoveSpeed(), 0.f, tf_halloween_kart_dash_speed.GetFloat(), tf_halloween_kart_sound_slow_pitch.GetFloat(), tf_halloween_kart_sound_fast_pitch.GetFloat() );
controller.SoundChangePitch( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ], flTargetPitch, 0.1f );
}
// Uncomment this (if) when we have a tire screech sound
//if ( ( m_iKartState & CTFPlayerShared::kKartState_Driving ) && GetCurrentTauntMoveSpeed() < 300.f )
//{
// if ( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] == NULL )
// {
// CBroadcastRecipientFilter filter;
// m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] = controller.SoundCreate( filter, entindex(), "BumperCar.GoLoop" );
// controller.Play( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ], 1.f, 80.f );
// }
//}
//else if ( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] )
//{
// controller.SoundDestroy( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] );
// m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] = NULL;
//}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitInvulnerableMaterial( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer )
return;
const char *pszMaterial = NULL;
int iVisibleTeam = GetTeamNumber();
// if this player is disguised and on the other team, use disguise team
if ( m_Shared.InCond( TF_COND_DISGUISED ) && !InSameTeam( pLocalPlayer ) )
{
iVisibleTeam = m_Shared.GetDisguiseTeam();
}
switch ( iVisibleTeam )
{
case TF_TEAM_BLUE:
pszMaterial = "models/effects/invulnfx_blue.vmt";
break;
case TF_TEAM_RED:
pszMaterial = "models/effects/invulnfx_red.vmt";
break;
default:
break;
}
if ( pszMaterial )
{
m_InvulnerableMaterial.Init( pszMaterial, TEXTURE_GROUP_CLIENT_EFFECTS );
}
else
{
m_InvulnerableMaterial.Shutdown();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StartBurningSound( void )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( !m_pBurningSound )
{
CLocalPlayerFilter filter;
m_pBurningSound = controller.SoundCreate( filter, entindex(), "Player.OnFire" );
}
controller.Play( m_pBurningSound, 0.0, 100 );
controller.SoundChangeVolume( m_pBurningSound, 1.0, 0.1 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopBurningSound( void )
{
if ( m_pBurningSound )
{
CSoundEnvelopeController::GetController().SoundDestroy( m_pBurningSound );
m_pBurningSound = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopBlastJumpLoopSound( int iUserID )
{
if ( m_pBlastJumpLoop )
{
if ( GetUserID() == iUserID )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pBlastJumpLoop );
m_pBlastJumpLoop = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateRecentlyTeleportedEffect( void )
{
bool bShow = m_Shared.ShouldShowRecentlyTeleported();
if ( bShow )
{
// NVNT if this is the local player notify haptics system of this teleporter
// teleporting.
if(IsLocalPlayer()&&!tfHaptics.wasBeingTeleported) {
if ( haptics )
haptics->ProcessHapticEvent(2,"Game","player_teleport");
tfHaptics.wasBeingHealed = true;
}
if ( !m_pTeleporterEffect )
{
const char *pszEffectName = NULL;
int iTeam = GetTeamNumber();
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
iTeam = m_Shared.GetDisguiseTeam();
}
switch( iTeam )
{
case TF_TEAM_BLUE:
pszEffectName = "player_recent_teleport_blue";
break;
case TF_TEAM_RED:
pszEffectName = "player_recent_teleport_red";
break;
default:
break;
}
if ( TFGameRules()->IsMannVsMachineMode() && IsABot() )
{
#if 0 // Nice idea, but it's chewing into our particle budget, and because bots currently spawn in ubered it's nearly invisible.
pszEffectName = "bot_recent_teleport_blue";
#else
pszEffectName = NULL;
#endif
}
if ( pszEffectName )
{
m_pTeleporterEffect = ParticleProp()->Create( pszEffectName, PATTACH_ABSORIGIN_FOLLOW );
}
}
}
else
{
// NVNT if this is the local player and we were being teleported
// flag that we are no longer teleporting.
if(IsLocalPlayer()&&tfHaptics.wasBeingTeleported) {
tfHaptics.wasBeingHealed = false;
}
if ( m_pTeleporterEffect )
{
ParticleProp()->StopEmission( m_pTeleporterEffect );
m_pTeleporterEffect = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdatedMarkedForDeathEffect( bool bForceStop )
{
// Dont show the particle over the local player's head. They have the icon that shows
// up over their health in the HUD which serves this purpose.
if ( IsLocalPlayer() )
return;
bool bShow = m_Shared.InCond( TF_COND_MARKEDFORDEATH ) || m_Shared.InCond( TF_COND_MARKEDFORDEATH_SILENT ) || m_Shared.InCond( TF_COND_PASSTIME_PENALTY_DEBUFF );
// force stop
if ( bForceStop || m_Shared.IsStealthed() || m_Shared.InCond( TF_COND_DISGUISED ) )
{
bShow = false;
}
if ( !bShow )
{
// Stop and then go
RemoveOverheadEffect( "mark_for_death", true );
}
if ( bShow )
{
AddOverheadEffect( "mark_for_death" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Show an icon above the player's head to let other players know which Powerup Rune they are carrying
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateRuneIcon( bool bForceStop /*= false */ )
{
// Don't show the particle over the local player's head. They have the icon that shows
// up over their health in the HUD which serves this purpose.
if ( IsLocalPlayer() )
return;
const RuneTypes_t carryingRuneType = m_Shared.GetCarryingRuneType();
const bool bAllowedToShow = ( m_Shared.IsCarryingRune() && !m_Shared.IsStealthed() );
int iTeam = IsEnemyPlayer() && m_Shared.InCond( TF_COND_DISGUISED ) ? m_Shared.GetDisguiseTeam() : GetTeamNumber();
if ( !bAllowedToShow || bForceStop || ( carryingRuneType != m_eDisplayingRuneIcon ) )
{
// remove all particle for both team just in case
for ( int i=0; i<RUNE_TYPES_MAX; ++i )
{
RuneTypes_t type = RuneTypes_t(i);
RemoveOverheadEffect( GetPowerupIconName( type, TF_TEAM_RED ), true );
RemoveOverheadEffect( GetPowerupIconName( type, TF_TEAM_BLUE ), true );
}
m_eDisplayingRuneIcon = RUNE_NONE;
}
if ( bAllowedToShow && ( carryingRuneType != m_eDisplayingRuneIcon ) )
{
const char* pszEffect = NULL;
if ( carryingRuneType > RUNE_NONE && carryingRuneType < RUNE_TYPES_MAX )
{
pszEffect = GetPowerupIconName( carryingRuneType, iTeam );
}
else
{
#ifdef STAGING_ONLY
Assert( !"unexepected value in GetCarryingRuneType, probably a bug" );
pszEffect = "(null)"; // so we don't crash in the messages below.
Msg( "GetCarryingRuneType had surprising value: %d for player %s\n", carryingRuneType, GetPlayerName() );
#endif
}
if ( AddOverheadEffect( pszEffect ) )
{
m_eDisplayingRuneIcon = carryingRuneType;
}
#ifdef STAGING_ONLY
else
{
Msg( "ParticleProp()->Create failed for some reason for powerup %s, for player %s.\n", pszEffect, GetPlayerName() );
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnPlayerClassChange( void )
{
// Init the anim movement vars
m_PlayerAnimState->SetRunSpeed( GetPlayerClass()->GetMaxSpeed() );
m_PlayerAnimState->SetWalkSpeed( GetPlayerClass()->GetMaxSpeed() * 0.5 );
if ( IsLocalPlayer() )
{
g_ItemEffectMeterManager.SetPlayer( this );
}
ShowNemesisIcon( false );
ShowDuelingIcon( false );
SetAppropriateCamera( this );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitPhonemeMappings()
{
CStudioHdr *pStudio = GetModelPtr();
if ( pStudio )
{
char szBasename[MAX_PATH];
Q_StripExtension( pStudio->pszName(), szBasename, sizeof( szBasename ) );
char szExpressionName[MAX_PATH];
Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename );
if ( FindSceneFile( szExpressionName ) )
{
SetupMappings( szExpressionName );
}
else
{
BaseClass::InitPhonemeMappings();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ResetFlexWeights( CStudioHdr *pStudioHdr )
{
if ( !pStudioHdr || pStudioHdr->numflexdesc() == 0 )
return;
// Reset the flex weights to their starting position.
LocalFlexController_t iController;
for ( iController = LocalFlexController_t(0); iController < pStudioHdr->numflexcontrollers(); ++iController )
{
SetFlexWeight( iController, 0.0f );
}
// Reset the prediction interpolation values.
m_iv_flexWeight.Reset();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov )
{
QAngle myEyeAngles;
VectorCopy( EyeAngles(), myEyeAngles );
BaseClass::CalcInEyeCamView( eyeOrigin, eyeAngles, fov );
/*
// if we are coaching, we override the eye angles with our original ones
// @note Tom Bui: we don't try to capture the "up" button event, because that doesn't seem so reliable
if ( m_bIsCoaching )
{
const float kLerpTime = 1.0f;
if ( ( m_nButtons & IN_JUMP ) != 0 )
{
VectorCopy( myEyeAngles, eyeAngles );
engine->SetViewAngles( eyeAngles );
m_flCoachLookAroundLerpTime = kLerpTime;
m_angCoachLookAroundEyeAngles = myEyeAngles;
}
else if ( m_flCoachLookAroundLerpTime > 0 )
{
m_flCoachLookAroundLerpTime -= gpGlobals->frametime;
float flPercent = ( kLerpTime - m_flCoachLookAroundLerpTime / kLerpTime );
eyeAngles = Lerp( flPercent, m_angCoachLookAroundEyeAngles, eyeAngles );
engine->SetViewAngles( eyeAngles );
}
}
*/
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CStudioHdr *C_TFPlayer::OnNewModel( void )
{
CStudioHdr *hdr = BaseClass::OnNewModel();
// Initialize the gibs.
InitPlayerGibs();
InitializePoseParams();
// Init flexes, cancel any scenes we're playing
ClearSceneEvents( NULL, false );
// Reset the flex weights.
ResetFlexWeights( hdr );
// Reset the players animation states, gestures
if ( m_PlayerAnimState )
{
m_PlayerAnimState->OnNewModel();
}
if ( hdr )
{
InitPhonemeMappings();
}
if ( IsPlayerClass( TF_CLASS_SPY ) )
{
m_iSpyMaskBodygroup = FindBodygroupByName( "spyMask" );
}
else
{
m_iSpyMaskBodygroup = -1;
}
return hdr;
}
//-----------------------------------------------------------------------------
// Purpose: Is this player an enemy to the local player
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsEnemyPlayer( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer )
return false;
int iTeam = pLocalPlayer->GetTeamNumber();
// if we are coaching, use the team of the student
if ( pLocalPlayer->m_hStudent && pLocalPlayer->m_bIsCoaching )
{
iTeam = pLocalPlayer->m_hStudent->GetTeamNumber();
}
switch( iTeam )
{
case TF_TEAM_RED:
return ( GetTeamNumber() == TF_TEAM_BLUE );
case TF_TEAM_BLUE:
return ( GetTeamNumber() == TF_TEAM_RED );
default:
break;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Displays a nemesis icon on this player to the local player
//-----------------------------------------------------------------------------
void C_TFPlayer::ShowNemesisIcon( bool bShow )
{
if ( bShow )
{
const char *pszEffect = NULL;
switch ( GetTeamNumber() )
{
case TF_TEAM_RED:
pszEffect = "particle_nemesis_red";
break;
case TF_TEAM_BLUE:
pszEffect = "particle_nemesis_blue";
break;
default:
return; // shouldn't get called if we're not on a team; bail out if it does
}
AddOverheadEffect( pszEffect );
}
else
{
// stop effects for both team colors (to make sure we remove effects in event of team change)
RemoveOverheadEffect( "particle_nemesis_red", true );
RemoveOverheadEffect( "particle_nemesis_blue", true );
}
m_bIsDisplayingNemesisIcon = bShow;
}
//-----------------------------------------------------------------------------
// Purpose: Displays a dueling icon on this player to the local player
//-----------------------------------------------------------------------------
void C_TFPlayer::ShowDuelingIcon( bool bShow )
{
if ( bShow )
{
const char *pszEffect = NULL;
switch ( GetTeamNumber() )
{
case TF_TEAM_RED:
pszEffect = "duel_red";
break;
case TF_TEAM_BLUE:
pszEffect = "duel_blue";
break;
default:
return; // shouldn't get called if we're not on a team; bail out if it does
}
AddOverheadEffect( pszEffect );
}
else
{
// stop effects for both team colors (to make sure we remove effects in event of team change)
RemoveOverheadEffect( "duel_red", true );
RemoveOverheadEffect( "duel_blue", true );
}
m_bIsDisplayingDuelingIcon = bShow;
}
//-----------------------------------------------------------------------------
// Purpose: Displays an icon denoting this player as "IT" to the local player
//-----------------------------------------------------------------------------
void C_TFPlayer::ShowIconForIT( bool bShow )
{
if ( bShow )
{
AddOverheadEffect( "halloween_boss_victim" );
}
else
{
RemoveOverheadEffect( "halloween_boss_victim", true );
}
m_bIsDisplayingIconForIT = bShow;
}
//-----------------------------------------------------------------------------
// Purpose: Displays an icon denoting this player as the Birthday Player to the local player
//-----------------------------------------------------------------------------
void C_TFPlayer::ShowBirthdayEffect( bool bShow )
{
/*
if ( bShow )
{
ParticleProp()->Create( "birthday_player_circling", PATTACH_POINT_FOLLOW, "head" );
DispatchParticleEffect( "bday_confetti", GetAbsOrigin() + Vector(0,0,32), vec3_angle );
}
else
{
ParticleProp()->StopParticlesNamed( "birthday_player_circling", true );
}
*/
m_bShouldShowBirthdayEffect = bShow;
}
bool C_TFPlayer::HasBombinomiconEffectOnDeath( void )
{
int iBombinomicomEffectOnDeath = 0;
CALL_ATTRIB_HOOK_INT( iBombinomicomEffectOnDeath, bombinomicon_effect_on_death );
return ( iBombinomicomEffectOnDeath != 0 );
}
#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateTranqMark( bool bShow, bool bForceStop /*= false */ )
{
// Dont show the particle over the local player's head. They have the icon that shows
// up over their health in the HUD which serves this purpose.
// only show this mark to spies
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
if ( IsLocalPlayer() || !pLocalPlayer || !pLocalPlayer->IsPlayerClass( TF_CLASS_SPY ) )
{
bShow = false;
}
int iTranq = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iTranq, override_projectile_type );
if ( iTranq != TF_PROJECTILE_TRANQ )
{
bShow = false;
}
if ( bShow && !m_bIsDisplayingTranqMark && m_Shared.InCond( TF_COND_TRANQ_MARKED ) )
{
AddOverheadEffect( "marked_for_tranq" );
m_bIsDisplayingTranqMark = true;
}
else
{
if ( m_bIsDisplayingTranqMark || bForceStop || ( !m_Shared.InCond( TF_COND_TRANQ_MARKED ) ) )
{
RemoveOverheadEffect( "marked_for_tranq", true );
m_bIsDisplayingTranqMark = false;
}
}
}
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateSpyClassStealParticle( bool bShow )
{
if ( bShow )
{
const char * pParticleName = NULL;
if ( GetTeamNumber() == TF_TEAM_RED )
{
pParticleName = "spy_stolen_smoke_red";
}
else
{
pParticleName = "spy_stolen_smoke_blue";
}
AddOverheadEffect( pParticleName );
}
else
{
// Turn off both in the case of team change
RemoveOverheadEffect( "spy_stolen_smoke_red", true );
RemoveOverheadEffect( "spy_stolen_smoke_blue", true );
}
}
#endif // STAGING_ONLY
#define TF_TAUNT_PITCH 0
#define TF_TAUNT_YAW 1
#define TF_TAUNT_DIST 2
#define TF_TAUNT_MAXYAW 135
#define TF_TAUNT_MINYAW -135
#define TF_TAUNT_MAXPITCH 90
#define TF_TAUNT_MINPITCH 0
#define TF_TAUNT_IDEALLAG 4.0f
static Vector TF_TAUNTCAM_HULL_MIN( -9.0f, -9.0f, -9.0f );
static Vector TF_TAUNTCAM_HULL_MAX( 9.0f, 9.0f, 9.0f );
static ConVar tf_tauntcam_yaw( "tf_tauntcam_yaw", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
static ConVar tf_tauntcam_pitch( "tf_tauntcam_pitch", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
static ConVar tf_tauntcam_dist( "tf_tauntcam_dist", "150", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
static ConVar tf_tauntcam_speed( "tf_tauntcam_speed", "300", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_halloween_kart_cam_dist( "tf_halloween_kart_cam_dist", "225", FCVAR_CHEAT );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::TurnOnTauntCam( void )
{
if ( !IsLocalPlayer() )
return;
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
return;
m_flTauntCamTargetDist = ( m_flTauntCamTargetDist != 0.0f ) ? m_flTauntCamTargetDist : tf_tauntcam_dist.GetFloat();
m_flTauntCamTargetDistUp = ( m_flTauntCamTargetDistUp != 0.0f ) ? m_flTauntCamTargetDistUp : 0.f;
m_flTauntCamCurrentDist = 0.f;
m_flTauntCamCurrentDistUp = 0.f;
// Save the old view angles.
engine->GetViewAngles( m_angTauntEngViewAngles );
prediction->GetViewAngles( m_angTauntPredViewAngles );
m_TauntCameraData.m_flPitch = 0;
m_TauntCameraData.m_flYaw = 0;
m_TauntCameraData.m_flDist = m_flTauntCamTargetDist;
m_TauntCameraData.m_flLag = 1.f;
m_TauntCameraData.m_vecHullMin.Init( -9.0f, -9.0f, -9.0f );
m_TauntCameraData.m_vecHullMax.Init( 9.0f, 9.0f, 9.0f );
if ( tf_taunt_first_person.GetBool() )
{
// Remain in first-person.
}
else
{
g_ThirdPersonManager.SetDesiredCameraOffset( Vector( 0, 0, 0 ) );
g_ThirdPersonManager.SetOverridingThirdPerson( true );
::input->CAM_ToThirdPerson();
ThirdPersonSwitch( true );
UpdateKillStreakEffects( m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
}
m_bTauntInterpolating = true;
if ( m_hItem )
{
m_hItem->UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::TurnOnTauntCam_Finish( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::TurnOffTauntCam( void )
{
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
return;
// We want to interpolate back into the guy's head.
if ( g_ThirdPersonManager.GetForcedThirdPerson() == false )
{
m_flTauntCamTargetDist = 0.f;
m_TauntCameraData.m_flDist = m_flTauntCamTargetDist;
}
g_ThirdPersonManager.SetOverridingThirdPerson( false );
if ( g_ThirdPersonManager.GetForcedThirdPerson() )
{
TurnOffTauntCam_Finish();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::TurnOffTauntCam_Finish( void )
{
if ( !IsLocalPlayer() )
return;
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
return;
const Vector& vecOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
tf_tauntcam_pitch.SetValue( vecOffset[PITCH] - m_angTauntPredViewAngles[PITCH] );
tf_tauntcam_yaw.SetValue( vecOffset[YAW] - m_angTauntPredViewAngles[YAW] );
QAngle angles;
angles[PITCH] = vecOffset[PITCH];
angles[YAW] = vecOffset[YAW];
angles[DIST] = vecOffset[DIST];
if( g_ThirdPersonManager.WantToUseGameThirdPerson() == false )
{
::input->CAM_ToFirstPerson();
ThirdPersonSwitch( false );
UpdateKillStreakEffects( m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
angles = vec3_angle;
}
::input->CAM_SetCameraThirdData( NULL, angles );
// Reset the old view angles.
// engine->SetViewAngles( m_angTauntEngViewAngles );
// prediction->SetViewAngles( m_angTauntPredViewAngles );
// Force the feet to line up with the view direction post taunt.
// If you are forcing aim yaw, your code is almost definitely broken if you don't include a delay between
// teleporting and forcing yaw. This is due to an unfortunate interaction between the command lookback window,
// and the fact that m_flEyeYaw is never propogated from the server to the client.
// TODO: Fix this after Halloween 2014.
m_PlayerAnimState->m_bForceAimYaw = true;
m_bTauntInterpolating = false;
if ( GetViewModel() )
{
GetViewModel()->UpdateVisibility();
}
if ( m_hItem )
{
m_hItem->UpdateVisibility();
}
SetAppropriateCamera( this );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::HandleTaunting( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// This code is only for the local player.
Assert( pLocalPlayer == NULL || pLocalPlayer == this );
// Clear the taunt slot.
if ( !m_bWasTaunting &&
(
m_Shared.InCond( TF_COND_TAUNTING ) ||
m_Shared.IsControlStunned() ||
m_Shared.IsLoser() ||
m_bIsReadyToHighFive ||
m_nForceTauntCam ||
m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ||
m_Shared.InCond( TF_COND_HALLOWEEN_GIANT ) ||
m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) ||
m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ||
m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ||
m_Shared.InCond( TF_COND_MELEE_ONLY ) ||
m_Shared.InCond( TF_COND_SWIMMING_CURSE )
)
)
{
m_bWasTaunting = true;
// Handle the camera for the local player.
if ( pLocalPlayer )
{
TurnOnTauntCam();
}
}
if ( ( !IsAlive() && m_nForceTauntCam < 2 ) ||
(
m_bWasTaunting && !m_Shared.InCond( TF_COND_TAUNTING ) && !m_Shared.IsControlStunned() &&
!m_Shared.InCond( TF_COND_PHASE ) && !m_Shared.IsLoser() && !m_bIsReadyToHighFive &&
!m_nForceTauntCam && !m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) &&
!m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) &&
!m_Shared.InCond( TF_COND_HALLOWEEN_GIANT ) &&
!m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) &&
!m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) &&
!m_Shared.InCond( TF_COND_HALLOWEEN_KART ) &&
!m_Shared.InCond( TF_COND_MELEE_ONLY ) &&
!m_Shared.InCond( TF_COND_SWIMMING_CURSE )
)
)
{
m_bWasTaunting = false;
// Clear the vcd slot.
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
// Handle the camera for the local player.
if ( pLocalPlayer )
{
TurnOffTauntCam();
}
}
TauntCamInterpolation();
}
//-----------------------------------------------------------------------------
// Purpose: Handles third person camera interpolation directly
// so we can manage enter & exit behavior without hacking the camera.
//-----------------------------------------------------------------------------
void C_TFPlayer::TauntCamInterpolation()
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && m_bTauntInterpolating )
{
if ( m_flTauntCamCurrentDist != m_flTauntCamTargetDist )
{
m_flTauntCamCurrentDist += Sign( m_flTauntCamTargetDist - m_flTauntCamCurrentDist ) * gpGlobals->frametime * tf_tauntcam_speed.GetFloat();
m_flTauntCamCurrentDist = clamp( m_flTauntCamCurrentDist, m_flTauntCamCurrentDist, m_flTauntCamTargetDist );
}
if ( m_flTauntCamCurrentDistUp != m_flTauntCamTargetDistUp )
{
m_flTauntCamCurrentDistUp += Sign( m_flTauntCamTargetDistUp - m_flTauntCamCurrentDistUp ) * gpGlobals->frametime * tf_tauntcam_speed.GetFloat();
m_flTauntCamCurrentDistUp = clamp( m_flTauntCamCurrentDistUp, m_flTauntCamCurrentDistUp, m_flTauntCamTargetDistUp );
}
const Vector& vecCamOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
Vector vecOrigin = pLocalPlayer->GetLocalOrigin();
vecOrigin += pLocalPlayer->GetViewOffset();
Vector vecForward, vecUp;
AngleVectors( QAngle( vecCamOffset[PITCH], vecCamOffset[YAW], 0 ), &vecForward, NULL, &vecUp );
trace_t trace;
UTIL_TraceHull( vecOrigin, vecOrigin - ( vecForward * m_flTauntCamCurrentDist ) + ( vecUp * m_flTauntCamCurrentDistUp ), Vector( -9.f, -9.f, -9.f ),
Vector( 9.f, 9.f, 9.f ), MASK_SOLID_BRUSHONLY, pLocalPlayer, COLLISION_GROUP_DEBRIS, &trace );
if ( trace.fraction < 1.0 )
m_flTauntCamCurrentDist *= trace.fraction;
QAngle angCameraOffset = QAngle( vecCamOffset[PITCH], vecCamOffset[YAW], m_flTauntCamCurrentDist );
::input->CAM_SetCameraThirdData( &m_TauntCameraData, angCameraOffset ); // Override camera distance interpolation.
g_ThirdPersonManager.SetDesiredCameraOffset( Vector( m_flTauntCamCurrentDist, 0, m_flTauntCamCurrentDistUp ) );
if ( m_flTauntCamCurrentDist == m_flTauntCamTargetDist && m_flTauntCamCurrentDistUp == m_flTauntCamTargetDistUp )
{
if ( m_flTauntCamTargetDist == 0.f )
TurnOffTauntCam_Finish();
else
TurnOnTauntCam_Finish();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::PlayTauntSoundLoop( const char *pszSoundLoopName )
{
if ( pszSoundLoopName && *pszSoundLoopName )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CPASAttenuationFilter filter( this );
m_pTauntSoundLoop = controller.SoundCreate( filter, entindex(), pszSoundLoopName );
controller.Play( m_pTauntSoundLoop, 1.0, 100 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopTauntSoundLoop()
{
if ( m_pTauntSoundLoop )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pTauntSoundLoop );
m_pTauntSoundLoop = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose: Indicates whether the spy's cigarette should be burning or not.
//-----------------------------------------------------------------------------
bool C_TFPlayer::CanLightCigarette( void )
{
// Used to be a massive if-conditional.
// Expanded for readability.
if ( !IsPlayerClass( TF_CLASS_SPY ) )
return false;
if ( !IsAlive() )
return false;
// Don't light if we are disguised and an enemy (not the spy model).
if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
{
if ( m_Shared.GetDisguiseClass() != TF_CLASS_SPY )
{
return false;
}
}
// don't light for MvM Spy robots
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
return false;
// Don't light if we are invis.
if ( GetPercentInvisible() > 0 )
return false;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// Don't light for the local player.
if ( ( pLocalPlayer == this ) || !pLocalPlayer )
return false;
// Don't light if we're spectating in first person mode.
if ( (pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE) && (pLocalPlayer->GetObserverTarget() == this) )
return false;
// Don't light if we're covered in urine.
if ( m_Shared.InCond( TF_COND_URINE ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Flash the bomb hat at ever increasing frequency
//-----------------------------------------------------------------------------
void C_TFPlayer::HalloweenBombHeadUpdate( void )
{
if ( m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
{
if ( !m_hHalloweenBombHat && gpGlobals->curtime > m_flBombDelay )
{
m_hHalloweenBombHat = C_PlayerAttachedModel::Create( BOMB_HAT_MODEL, this, LookupAttachment("head"), vec3_origin, PAM_PERMANENT, 0 );
m_hHalloweenBombHat->FollowEntity( this, true );
}
if ( m_hHalloweenBombHat )
{
m_hHalloweenBombHat->m_nSkin = m_Shared.m_nHalloweenBombHeadStage;
}
}
else
{
if ( m_hHalloweenBombHat )
{
m_hHalloweenBombHat->StopFollowingEntity();
m_hHalloweenBombHat->Release();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldPlayerDrawParticles( void )
{
#ifdef STAGING_ONLY
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) )
return false;
#endif // TF_CLIENT_DLL
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Check if passed in player is on the local player's friends list
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsPlayerOnSteamFriendsList( C_BasePlayer *pPlayer )
{
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if ( !pLocalPlayer )
return false;
if ( !pPlayer )
return false;
if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() )
return false;
player_info_t pi;
if ( engine->GetPlayerInfo( pPlayer->entindex(), &pi ) && pi.friendsID )
{
CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
if ( steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) )
return true;
}
return false;
}
#ifdef STAGING_ONLY
ConVar tf_debug_moving_taunt( "tf_debug_moving_taunt", "1" );
#endif // STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ClientThink()
{
// Pass on through to the base class.
BaseClass::ClientThink();
UpdateIDTarget();
UpdateLookAt();
UpdateOverheadEffects();
#ifdef STAGING_ONLY
if ( tf_debug_moving_taunt.GetBool() )
#endif // STAGING_ONLY
{
if ( !IsTaunting() && m_PlayerAnimState->IsGestureSlotActive( GESTURE_SLOT_VCD ) )
{
#ifdef STAGING_ONLY
CAnimationLayer *pLayer = m_PlayerAnimState->GetGestureSlotLayer( GESTURE_SLOT_VCD );
if ( pLayer )
{
const char *pszSequenceName = GetSequenceName( pLayer->m_nSequence );
Warning( "'%s' is playing '%s' sequence when not taunting.\n", GetPlayerName(), pszSequenceName );
}
#endif // STAGING_ONLY
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
}
}
// NVNT update state based effects (prior to healer clear)
if ( haptics &&haptics->HasDevice() && IsLocalPlayer())
{
tfHaptics.HapticsThink(this);
}
// Clear our healer, it'll be reset by the medigun client think if we're being healed
m_hHealer = NULL;
// Start smoke if we're not invisible or disguised.
if ( CanLightCigarette() )
{
if ( !m_bCigaretteSmokeActive )
{
int iSmokeAttachment = LookupAttachment( "cig_smoke" );
ParticleProp()->Create( "cig_smoke", PATTACH_POINT_FOLLOW, iSmokeAttachment );
m_bCigaretteSmokeActive = true;
}
}
else // stop the smoke otherwise if its active
{
if ( m_bCigaretteSmokeActive )
{
ParticleProp()->StopParticlesNamed( "cig_smoke", false );
m_bCigaretteSmokeActive = false;
}
}
if ( m_bWaterExitEffectActive && !IsAlive() )
{
ParticleProp()->StopParticlesNamed( "water_playeremerge", false );
m_bWaterExitEffectActive = false;
}
// Kill the effect if either
// a) the player is dead
// b) the enemy disguised spy is now invisible
if ( !IsAlive() ||
( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() && ( GetPercentInvisible() > 0 ) ) )
{
StopSaveMeEffect( true );
}
if ( ShouldTauntHintIconBeVisible() )
{
CreateTauntWithMeEffect();
}
else
{
StopTauntWithMeEffect();
}
if ( IsLocalPlayer() )
{
g_ItemEffectMeterManager.Update( this );
}
if ( m_Shared.InCond( TF_COND_DEMO_BUFF ) )
{
m_Shared.ClientDemoBuffThink();
}
if ( m_pBlastJumpLoop )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( !IsAlive() )
{
controller.SoundDestroy( m_pBlastJumpLoop );
m_pBlastJumpLoop = NULL;
}
else
{
float flTimeAloft = gpGlobals->curtime - m_flBlastJumpLaunchTime;
float flPitch = RemapValClamped( flTimeAloft, 0.1f, 3.f, 200.f, 100.f );
float flVolume = RemapValClamped( flTimeAloft, 0.1f, 2.f, 0.25f, 0.95f );
controller.SoundChangePitch( m_pBlastJumpLoop, flPitch, 0.1f );
controller.SoundChangeVolume( m_pBlastJumpLoop, flVolume, 0.1f );
}
}
if ( HasTheFlag() && GetGlowObject() )
{
C_TFItem *pFlag = GetItem();
if ( pFlag->ShouldHideGlowEffect() )
{
GetGlowObject()->SetEntity( NULL );
}
else
{
GetGlowObject()->SetEntity( this );
}
}
#ifdef STAGING_ONLY
/*
// this code was added for a new sniper rifle that was being looked at, but it breaks glows for existing players by always
// turning off the glow if the weapon doesn't think it needs to be on. this needs to be fixed before we can use it.
// some mods use the glows and the SourceTV clients see player glows when they're watching a match.
else
{
bool bGlow = false;
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_ZOOMED ) && !( pLocalTFPlayer->InSameTeam( this ) || m_Shared.GetDisguiseTeam() == pLocalTFPlayer->GetTeamNumber() ) )
{
int iGlowEnemiesInScope = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalTFPlayer, iGlowEnemiesInScope, add_sniper_glow_enemies_in_scope );
if ( iGlowEnemiesInScope != 0 )
{
trace_t tr;
UTIL_TraceLine( pLocalTFPlayer->EyePosition(), EyePosition(), MASK_BLOCKLOS, pLocalTFPlayer, COLLISION_GROUP_NONE, &tr );
if ( !tr.DidHit() )
{
bGlow = true;
}
}
}
if ( bGlow )
{
float r, g, b;
GetGlowEffectColor( &r, &g, &b );
EnableGlowEffect( r, g, b );
}
else
{
DestroyGlowEffect();
}
}
*/
#endif // STAGING_ONLY
m_Shared.ClientKillStreakBuffThink();
/*
if ( m_LeaveServerTimer.HasStarted() && m_LeaveServerTimer.IsElapsed() )
{
engine->ExecuteClientCmd( "disconnect" );
}
*/
if ( m_Shared.IsEnteringOrExitingFullyInvisible() )
{
UpdateSpyStateChange();
}
// Predict Halloween Kart
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) && !m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
if ( IsLocalPlayer() && cl_predict->GetBool() )
{
cl_predict->SetValue( false );
}
//if ( m_nButtons & IN_ATTACK )
//{
// // 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 )
// {
// 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() );
// }
//}
}
else
{
// Make Sure its on otherwise
if ( IsLocalPlayer() && !cl_predict->GetBool() )
{
cl_predict->SetValue( true );
}
}
// update rune charge particle
if ( m_Shared.IsRuneCharged() && !m_pRuneChargeReadyEffect && !m_Shared.IsStealthed() )
{
m_pRuneChargeReadyEffect = ParticleProp()->Create( "powerup_supernova_ready", PATTACH_ABSORIGIN_FOLLOW );
}
else if ( m_pRuneChargeReadyEffect && ( m_Shared.IsStealthed() || !m_Shared.IsRuneCharged() ) )
{
ParticleProp()->StopEmission( m_pRuneChargeReadyEffect );
m_pRuneChargeReadyEffect = NULL;
}
UpdateRuneIcon();
UpdatedMarkedForDeathEffect();
if ( TFGameRules() && TFGameRules()->IsPasstimeMode() )
{
//
// Passtime player reticle
//
if ( !IsLocalPlayer() && !m_pPasstimePlayerReticle )
{
m_pPasstimePlayerReticle = new C_PasstimePlayerReticle( this );
}
if ( m_pPasstimePlayerReticle )
{
m_pPasstimePlayerReticle->OnClientThink();
}
//
// Passtime ask for ball reticle
//
if ( !IsLocalPlayer() && !m_pPasstimeAskForBallReticle )
{
m_pPasstimeAskForBallReticle = new C_PasstimeAskForBallReticle( this );
}
if ( m_pPasstimeAskForBallReticle )
{
m_pPasstimeAskForBallReticle->OnClientThink();
}
//
// Passtime ask for ball button
//
if ( m_nButtons & IN_ATTACK3 )
{
engine->ClientCmd("voicemenu 1 8");
}
}
}
void C_TFPlayer::Touch( CBaseEntity *pOther )
{
BaseClass::Touch( pOther );
C_TFPlayer *pVictim = ToTFPlayer( pOther );
if ( pVictim )
{
// ****************************************************************************************************************
// Halloween Karts
// Predict Dash Crash
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) )
{
Vector vAim = GetLocalVelocity();
vAim.NormalizeInPlace();
vAim.z += 0.50f;
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 );
ray.Init( vOrigin, vOrigin + vAim * 16, GetPlayerMins(), GetPlayerMaxs() );
enginetrace->TraceRay( ray, MASK_SOLID, &pFilter, &pTrace );
Vector vecForceDirection;
vecForceDirection = vAim;
if ( pTrace.m_pEnt == pVictim )
{
// Stop moving
SetAbsVelocity( vec3_origin );
SetCurrentTauntMoveSpeed( 0 );
m_Shared.RemoveCond( TF_COND_HALLOWEEN_KART_DASH );
}
}
}
}
//
//-----------------------------------------------------------------------------
bool C_TFPlayer::GetPredictable( void ) const
{
// Halloween Kart Hackery to Get prediction system to behave the way we want we though prediction is actually off
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
return true;
}
return BaseClass::GetPredictable();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateLookAt( void )
{
bool bFoundViewTarget = false;
Vector vForward;
AngleVectors( GetLocalAngles(), &vForward );
Vector vMyOrigin = GetAbsOrigin();
Vector vecLookAtTarget = vec3_origin;
if ( tf_clientsideeye_lookats.GetBool() )
{
for( int iClient = 1; iClient <= gpGlobals->maxClients; ++iClient )
{
CBaseEntity *pEnt = UTIL_PlayerByIndex( iClient );
if ( !pEnt || !pEnt->IsPlayer() )
continue;
if ( !pEnt->IsAlive() )
continue;
if ( pEnt == this )
continue;
Vector vDir = pEnt->GetAbsOrigin() - vMyOrigin;
if ( vDir.Length() > 300 )
continue;
VectorNormalize( vDir );
if ( DotProduct( vForward, vDir ) < 0.0f )
continue;
vecLookAtTarget = pEnt->EyePosition();
bFoundViewTarget = true;
break;
}
}
if ( bFoundViewTarget == false )
{
// no target, look forward
vecLookAtTarget = GetAbsOrigin() + vForward * 512;
}
// orient eyes
m_viewtarget = vecLookAtTarget;
/*
// blinking
if (m_blinkTimer.IsElapsed())
{
m_blinktoggle = !m_blinktoggle;
m_blinkTimer.Start( RandomFloat( 1.5f, 4.0f ) );
}
*/
/*
// Figure out where we want to look in world space.
QAngle desiredAngles;
Vector to = vecLookAtTarget - EyePosition();
VectorAngles( to, desiredAngles );
// Figure out where our body is facing in world space.
QAngle bodyAngles( 0, 0, 0 );
bodyAngles[YAW] = GetLocalAngles()[YAW];
float flBodyYawDiff = bodyAngles[YAW] - m_flLastBodyYaw;
m_flLastBodyYaw = bodyAngles[YAW];
// Set the head's yaw.
float desired = AngleNormalize( desiredAngles[YAW] - bodyAngles[YAW] );
desired = clamp( -desired, m_headYawMin, m_headYawMax );
m_flCurrentHeadYaw = ApproachAngle( desired, m_flCurrentHeadYaw, 130 * gpGlobals->frametime );
// Counterrotate the head from the body rotation so it doesn't rotate past its target.
m_flCurrentHeadYaw = AngleNormalize( m_flCurrentHeadYaw - flBodyYawDiff );
SetPoseParameter( m_headYawPoseParam, m_flCurrentHeadYaw );
// Set the head's yaw.
desired = AngleNormalize( desiredAngles[PITCH] );
desired = clamp( desired, m_headPitchMin, m_headPitchMax );
m_flCurrentHeadPitch = ApproachAngle( -desired, m_flCurrentHeadPitch, 130 * gpGlobals->frametime );
m_flCurrentHeadPitch = AngleNormalize( m_flCurrentHeadPitch );
SetPoseParameter( m_headPitchPoseParam, m_flCurrentHeadPitch );
*/
}
//-----------------------------------------------------------------------------
// Purpose: Try to steer away from any players and objects we might interpenetrate
//-----------------------------------------------------------------------------
#define TF_AVOID_MAX_RADIUS_SQR 5184.0f // Based on player extents and max buildable extents.
#define TF_OO_AVOID_MAX_RADIUS_SQR 0.00019f
ConVar tf_max_separation_force ( "tf_max_separation_force", "256", FCVAR_DEVELOPMENTONLY );
extern ConVar cl_forwardspeed;
extern ConVar cl_backspeed;
extern ConVar cl_sidespeed;
void C_TFPlayer::AvoidPlayers( CUserCmd *pCmd )
{
// Turn off the avoid player code.
if ( !tf_avoidteammates.GetBool() || !tf_avoidteammates_pushaway.GetBool() )
return;
// Don't test if the player doesn't exist or is dead.
if ( IsAlive() == false )
return;
C_TFTeam *pTeam = (C_TFTeam*)GetTeam();
if ( !pTeam )
return;
CHudUpgradePanel *pHudVote = GET_HUDELEMENT( CHudUpgradePanel );
if ( pHudVote && pHudVote->IsActive() )
{
return;
}
// Up vector.
static Vector vecUp( 0.0f, 0.0f, 1.0f );
Vector vecTFPlayerCenter = GetAbsOrigin();
Vector vecTFPlayerMin = GetPlayerMins();
Vector vecTFPlayerMax = GetPlayerMaxs();
float flZHeight = vecTFPlayerMax.z - vecTFPlayerMin.z;
vecTFPlayerCenter.z += 0.5f * flZHeight;
VectorAdd( vecTFPlayerMin, vecTFPlayerCenter, vecTFPlayerMin );
VectorAdd( vecTFPlayerMax, vecTFPlayerCenter, vecTFPlayerMax );
// Find an intersecting player or object.
int nAvoidPlayerCount = 0;
C_TFPlayer *pAvoidPlayerList[MAX_PLAYERS];
C_TFPlayer *pIntersectPlayer = NULL;
CBaseObject *pIntersectObject = NULL;
float flAvoidRadius = 0.0f;
Vector vecAvoidCenter, vecAvoidMin, vecAvoidMax;
for ( int i = 0; i < pTeam->GetNumPlayers(); ++i )
{
C_TFPlayer *pAvoidPlayer = static_cast< C_TFPlayer * >( pTeam->GetPlayer( i ) );
if ( pAvoidPlayer == NULL )
continue;
// Is the avoid player me?
if ( pAvoidPlayer == this )
continue;
// Save as list to check against for objects.
pAvoidPlayerList[nAvoidPlayerCount] = pAvoidPlayer;
++nAvoidPlayerCount;
// Check to see if the avoid player is dormant.
if ( pAvoidPlayer->IsDormant() )
continue;
// Is the avoid player solid?
if ( pAvoidPlayer->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
continue;
Vector t1, t2;
vecAvoidCenter = pAvoidPlayer->GetAbsOrigin();
vecAvoidMin = pAvoidPlayer->GetPlayerMins();
vecAvoidMax = pAvoidPlayer->GetPlayerMaxs();
flZHeight = vecAvoidMax.z - vecAvoidMin.z;
vecAvoidCenter.z += 0.5f * flZHeight;
VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );
if ( IsBoxIntersectingBox( vecTFPlayerMin, vecTFPlayerMax, vecAvoidMin, vecAvoidMax ) )
{
// Need to avoid this player.
if ( !pIntersectPlayer )
{
pIntersectPlayer = pAvoidPlayer;
break;
}
}
}
// We didn't find a player - look for objects to avoid.
if ( !pIntersectPlayer )
{
for ( int iPlayer = 0; iPlayer < nAvoidPlayerCount; ++iPlayer )
{
// Stop when we found an intersecting object.
if ( pIntersectObject )
break;
for ( int iObject = 0; iObject < pTeam->GetNumObjects(); ++iObject )
{
CBaseObject *pAvoidObject = pTeam->GetObject( iObject );
if ( !pAvoidObject )
continue;
// Check to see if the object is dormant.
if ( pAvoidObject->IsDormant() )
continue;
// Is the object solid.
if ( pAvoidObject->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
continue;
// If we shouldn't avoid it, see if we intersect it.
if ( pAvoidObject->ShouldPlayersAvoid() )
{
vecAvoidCenter = pAvoidObject->WorldSpaceCenter();
vecAvoidMin = pAvoidObject->WorldAlignMins();
vecAvoidMax = pAvoidObject->WorldAlignMaxs();
VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );
if ( IsBoxIntersectingBox( vecTFPlayerMin, vecTFPlayerMax, vecAvoidMin, vecAvoidMax ) )
{
// Need to avoid this object.
pIntersectObject = pAvoidObject;
break;
}
}
}
}
}
// Anything to avoid?
if ( !pIntersectPlayer && !pIntersectObject )
{
m_Shared.SetSeparation( false );
m_Shared.SetSeparationVelocity( vec3_origin );
return;
}
// Calculate the push strength and direction.
Vector vecDelta;
// Avoid a player - they have precedence.
if ( pIntersectPlayer )
{
VectorSubtract( pIntersectPlayer->WorldSpaceCenter(), vecTFPlayerCenter, vecDelta );
Vector vRad = pIntersectPlayer->WorldAlignMaxs() - pIntersectPlayer->WorldAlignMins();
vRad.z = 0;
flAvoidRadius = vRad.Length();
}
// Avoid a object.
else
{
VectorSubtract( pIntersectObject->WorldSpaceCenter(), vecTFPlayerCenter, vecDelta );
Vector vRad = pIntersectObject->WorldAlignMaxs() - pIntersectObject->WorldAlignMins();
vRad.z = 0;
flAvoidRadius = vRad.Length();
}
float flPushStrength = RemapValClamped( vecDelta.Length(), flAvoidRadius, 0, 0, tf_max_separation_force.GetInt() ); //flPushScale;
//Msg( "PushScale = %f\n", flPushStrength );
// Check to see if we have enough push strength to make a difference.
if ( flPushStrength < 0.01f )
return;
Vector vecPush;
if ( GetAbsVelocity().Length2DSqr() > 0.1f )
{
Vector vecVelocity = GetAbsVelocity();
vecVelocity.z = 0.0f;
CrossProduct( vecUp, vecVelocity, vecPush );
VectorNormalize( vecPush );
}
else
{
// We are not moving, but we're still intersecting.
QAngle angView = pCmd->viewangles;
angView.x = 0.0f;
AngleVectors( angView, NULL, &vecPush, NULL );
}
// Move away from the other player/object.
Vector vecSeparationVelocity;
if ( vecDelta.Dot( vecPush ) < 0 )
{
vecSeparationVelocity = vecPush * flPushStrength;
}
else
{
vecSeparationVelocity = vecPush * -flPushStrength;
}
// Don't allow the max push speed to be greater than the max player speed.
float flMaxPlayerSpeed = MaxSpeed();
float flCropFraction = 1.33333333f;
if ( ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
{
flMaxPlayerSpeed *= flCropFraction;
}
float flMaxPlayerSpeedSqr = flMaxPlayerSpeed * flMaxPlayerSpeed;
if ( vecSeparationVelocity.LengthSqr() > flMaxPlayerSpeedSqr )
{
vecSeparationVelocity.NormalizeInPlace();
VectorScale( vecSeparationVelocity, flMaxPlayerSpeed, vecSeparationVelocity );
}
QAngle vAngles = pCmd->viewangles;
vAngles.x = 0;
Vector currentdir;
Vector rightdir;
AngleVectors( vAngles, &currentdir, &rightdir, NULL );
Vector vDirection = vecSeparationVelocity;
VectorNormalize( vDirection );
float fwd = currentdir.Dot( vDirection );
float rt = rightdir.Dot( vDirection );
float forward = fwd * flPushStrength;
float side = rt * flPushStrength;
//Msg( "fwd: %f - rt: %f - forward: %f - side: %f\n", fwd, rt, forward, side );
m_Shared.SetSeparation( true );
m_Shared.SetSeparationVelocity( vecSeparationVelocity );
pCmd->forwardmove += forward;
pCmd->sidemove += side;
// Clamp the move to within legal limits, preserving direction. This is a little
// complicated because we have different limits for forward, back, and side
//Msg( "PRECLAMP: forwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
float flForwardScale = 1.0f;
if ( pCmd->forwardmove > fabs( cl_forwardspeed.GetFloat() ) )
{
flForwardScale = fabs( cl_forwardspeed.GetFloat() ) / pCmd->forwardmove;
}
else if ( pCmd->forwardmove < -fabs( cl_backspeed.GetFloat() ) )
{
flForwardScale = fabs( cl_backspeed.GetFloat() ) / fabs( pCmd->forwardmove );
}
float flSideScale = 1.0f;
if ( fabs( pCmd->sidemove ) > fabs( cl_sidespeed.GetFloat() ) )
{
flSideScale = fabs( cl_sidespeed.GetFloat() ) / fabs( pCmd->sidemove );
}
float flScale = MIN( flForwardScale, flSideScale );
pCmd->forwardmove *= flScale;
pCmd->sidemove *= flScale;
//Msg( "Pforwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : flInputSampleTime -
// *pCmd -
//-----------------------------------------------------------------------------
bool C_TFPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd )
{
static QAngle angMoveAngle( 0.0f, 0.0f, 0.0f );
static float flTauntTurnSpeed = 0.f;
bool bNoTaunt = true;
bool bInTaunt = m_Shared.InCond( TF_COND_TAUNTING ) || m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER );
if ( m_Shared.InCond( TF_COND_FREEZE_INPUT ) )
{
pCmd->viewangles = angMoveAngle; // use the last save angles
pCmd->forwardmove = 0.0f;
pCmd->sidemove = 0.0f;
pCmd->upmove = 0.0f;
pCmd->buttons = 0;
pCmd->weaponselect = 0;
pCmd->weaponsubtype = 0;
pCmd->mousedx = 0;
pCmd->mousedy = 0;
}
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
m_Shared.CreateVehicleMove( flInputSampleTime, pCmd );
}
else if ( bInTaunt )
{
if ( tf_allow_taunt_switch.GetInt() <= 1 )
{
pCmd->weaponselect = 0;
}
int nCurrentButtons = pCmd->buttons;
pCmd->buttons = 0;
if ( !CanMoveDuringTaunt() )
{
pCmd->forwardmove = 0.0f;
pCmd->sidemove = 0.0f;
pCmd->upmove = 0.0f;
VectorCopy( angMoveAngle, pCmd->viewangles );
}
else
{
float flSign = pCmd->sidemove != 0.f ? 1.f : -1.f;
float flMaxTurnSpeed = m_flTauntTurnSpeed;
#ifdef STAGING_ONLY
flMaxTurnSpeed = cl_taunt_max_turn_speed.GetFloat() > 0.f ? cl_taunt_max_turn_speed.GetFloat() : flMaxTurnSpeed;
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
flMaxTurnSpeed = cl_halloween_kart_turn_speed.GetFloat();
}
#endif // STAGING_ONLY
if ( m_flTauntTurnAccelerationTime > 0.f )
{
flTauntTurnSpeed = clamp( flTauntTurnSpeed + flSign * ( flInputSampleTime / m_flTauntTurnAccelerationTime ) * flMaxTurnSpeed, 0.f, flMaxTurnSpeed );
}
else
{
flTauntTurnSpeed = flMaxTurnSpeed;
}
float flSmoothTurnSpeed = 0.f;
if ( flMaxTurnSpeed > 0.f )
{
flSmoothTurnSpeed = SimpleSpline( flTauntTurnSpeed / flMaxTurnSpeed ) * flMaxTurnSpeed;
}
// only let these button through
if ( pCmd->sidemove < 0 )
{
angMoveAngle += QAngle( 0.f, flSmoothTurnSpeed * flInputSampleTime, 0.f );
}
else if( pCmd->sidemove > 0 )
{
angMoveAngle += QAngle( 0.f, -flSmoothTurnSpeed * flInputSampleTime, 0.f );
}
pCmd->buttons = nCurrentButtons & ( IN_MOVELEFT | IN_MOVERIGHT | IN_FORWARD | IN_BACK );
pCmd->sidemove = 0.0f;
VectorCopy( angMoveAngle, pCmd->viewangles );
}
// allow remap taunt keys to go through
CTFTauntInfo *pTaunt = m_TauntEconItemView.GetStaticData()->GetTauntData();
if ( pTaunt )
{
for ( int i=0; i<pTaunt->GetTauntInputRemapCount(); ++i )
{
const CTFTauntInfo::TauntInputRemap_t& tauntRemap = pTaunt->GetTauntInputRemapScene( i );
if ( nCurrentButtons & tauntRemap.m_iButton )
{
pCmd->buttons |= tauntRemap.m_iButton;
}
}
}
bNoTaunt = false;
}
else
{
flTauntTurnSpeed = 0.f;
VectorCopy( pCmd->viewangles, angMoveAngle );
}
BaseClass::CreateMove( flInputSampleTime, pCmd );
// Don't avoid players if in the middle of a high five. This prevents high-fivers from becoming separated.
if ( !bInTaunt || ( !m_bIsReadyToHighFive && !CTFPlayerSharedUtils::ConceptIsPartnerTaunt( m_Shared.m_iTauntConcept ) ) )
{
AvoidPlayers( pCmd );
}
return bNoTaunt;
}
//-----------------------------------------------------------------------------
// Purpose: This prevents some anims from being thrown out when the client is in prediction simulation.
// Stun anims, for example, (additive gestures) are synchronized by time and can keep playing on the client
// to prevent pauses during the stun loop.
//-----------------------------------------------------------------------------
bool C_TFPlayer::PlayAnimEventInPrediction( PlayerAnimEvent_t event )
{
if ( !cl_predict->GetBool() )
return true;
switch ( event )
{
case PLAYERANIMEVENT_STUN_BEGIN:
case PLAYERANIMEVENT_STUN_MIDDLE:
case PLAYERANIMEVENT_STUN_END:
case PLAYERANIMEVENT_PASSTIME_THROW_BEGIN:
case PLAYERANIMEVENT_PASSTIME_THROW_MIDDLE:
case PLAYERANIMEVENT_PASSTIME_THROW_END:
case PLAYERANIMEVENT_PASSTIME_THROW_CANCEL:
return true;
default:
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
{
if ( IsLocalPlayer() )
{
if ( !prediction->IsFirstTimePredicted() && !PlayAnimEventInPrediction( event ) )
return;
}
MDLCACHE_CRITICAL_SECTION();
m_PlayerAnimState->DoAnimationEvent( event, nData );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateBombonomiconHint()
{
if ( IsLocalPlayer() && IsAlive() )
{
m_hBombonomiconHint = C_MerasmusBombEffect::Create( BOMBONOMICON_MODEL, this, Vector(-40, 0, 120), QAngle(30, 0, 0), 100.0, 4.0, PRM_SPIN_Z );
m_flBombDelay = gpGlobals->curtime + 2.0f;
m_hBombonomiconHint->SetModelScale( 0.5f );
CSingleUserRecipientFilter filter(this);
CSoundParameters params;
if ( CBaseEntity::GetParametersForSound( "Halloween.BombinomiconSpin", params, NULL ) )
{
EmitSound_t es( params );
EmitSound( filter, m_hBombonomiconHint->entindex(), es );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::DestroyBombonomiconHint()
{
if ( IsLocalPlayer() )
{
if ( m_hBombonomiconHint )
{
m_hBombonomiconHint->Release();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Similar to OnNewModel. only reset animation related data
//-----------------------------------------------------------------------------
void C_TFPlayer::CleanUpAnimationOnSpawn()
{
CStudioHdr *hdr = GetModelPtr();
if ( !hdr )
return;
InitializePoseParams();
// Init flexes, cancel any scenes we're playing
ClearSceneEvents( NULL, false );
// Reset the flex weights.
ResetFlexWeights( GetModelPtr() );
// Reset the players animation states, gestures
if ( m_PlayerAnimState )
{
m_PlayerAnimState->ClearAnimationState();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsABot( void )
{
if ( m_bIsABot )
return true;
if ( g_PR && g_PR->IsFakePlayer( entindex() ) )
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Vector C_TFPlayer::GetObserverCamOrigin( void )
{
if ( !IsAlive() )
{
if ( m_hFirstGib )
{
IPhysicsObject *pPhysicsObject = m_hFirstGib->VPhysicsGetObject();
if( pPhysicsObject )
{
Vector vecMassCenter = pPhysicsObject->GetMassCenterLocalSpace();
Vector vecWorld;
m_hFirstGib->CollisionProp()->CollisionToWorldSpace( vecMassCenter, &vecWorld );
return (vecWorld);
}
return m_hFirstGib->GetRenderOrigin();
}
return GetDeathViewPosition();
}
return BaseClass::GetObserverCamOrigin();
}
//-----------------------------------------------------------------------------
// Purpose: Consider the viewer and other factors when determining resulting
// invisibility
//-----------------------------------------------------------------------------
float C_TFPlayer::GetEffectiveInvisibilityLevel( void )
{
float flPercentInvisible = GetPercentInvisible();
// Crude way to limit Halloween spell
bool bHalloweenSpellStealth = TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) && m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF );
bool bLimitedInvis = !IsEnemyPlayer() || bHalloweenSpellStealth;
#ifdef STAGING_ONLY
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalTFPlayer != this )
{
bLimitedInvis = false;
}
#endif // STAGING_ONLY
// If this is a teammate of the local player or viewer is observer,
// dont go above a certain max invis
if ( bLimitedInvis )
{
float flMax = tf_teammate_max_invis.GetFloat();
if ( flPercentInvisible > flMax )
{
flPercentInvisible = flMax;
}
}
else
{
// If this player just killed me, show them slightly
// less than full invis in the deathcam and freezecam
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
int iObserverMode = pLocalPlayer->GetObserverMode();
if ( ( iObserverMode == OBS_MODE_FREEZECAM || iObserverMode == OBS_MODE_DEATHCAM ) &&
pLocalPlayer->GetObserverTarget() == this )
{
float flMax = tf_teammate_max_invis.GetFloat();
if ( flPercentInvisible > flMax )
{
flPercentInvisible = flMax;
}
}
}
}
return flPercentInvisible;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetBodygroupsDirty( void )
{
m_bBodygroupsDirty = true;
CTFViewModel *pVM = dynamic_cast<CTFViewModel *>( GetViewModel() );
if ( pVM )
{
pVM->m_bBodygroupsDirty = true;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::RecalcBodygroupsIfDirty( void )
{
if ( m_bBodygroupsDirty )
{
m_Shared.RecalculatePlayerBodygroups();
m_bBodygroupsDirty = false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::DrawModel( int flags )
{
// If we're a dead player with a fresh ragdoll, don't draw
if ( m_nRenderFX == kRenderFxRagdoll )
return 0;
RecalcBodygroupsIfDirty();
// Don't draw the model at all if we're fully invisible
if ( GetEffectiveInvisibilityLevel() >= 1.0f )
{
if ( m_hHalloweenBombHat && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) && !m_hHalloweenBombHat->IsEffectActive( EF_NODRAW ) )
{
m_hHalloweenBombHat->SetEffects( EF_NODRAW );
}
return 0;
}
else
{
if ( m_hHalloweenBombHat && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) && m_hHalloweenBombHat->IsEffectActive( EF_NODRAW ) )
{
m_hHalloweenBombHat->RemoveEffects( EF_NODRAW );
}
}
CMatRenderContextPtr pRenderContext( materials );
bool bDoEffect = false;
float flAmountToChop = 0.0;
if ( m_Shared.InCond( TF_COND_DISGUISING ) )
{
flAmountToChop = ( gpGlobals->curtime - m_flDisguiseEffectStartTime ) *
( 1.0 / TF_TIME_TO_DISGUISE );
}
else
{
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
float flETime = gpGlobals->curtime - m_flDisguiseEffectStartTime;
if ( ( flETime > 0.0 ) && ( flETime < TF_TIME_TO_SHOW_DISGUISED_FINISHED_EFFECT ) )
{
flAmountToChop = 1.0 - ( flETime * ( 1.0/TF_TIME_TO_SHOW_DISGUISED_FINISHED_EFFECT ) );
}
}
}
bDoEffect = ( flAmountToChop > 0.0 ) && ( ! IsLocalPlayer() );
#if ( SHOW_DISGUISE_EFFECT == 0 )
bDoEffect = false;
#endif
bDoEffect = false;
if ( bDoEffect )
{
Vector vMyOrigin = GetAbsOrigin();
BoxDeformation_t mybox;
mybox.m_ClampMins = vMyOrigin - Vector(100,100,100);
mybox.m_ClampMaxes = vMyOrigin + Vector(500,500,72 * ( 1 - flAmountToChop ) );
pRenderContext->PushDeformation( &mybox );
}
int ret = BaseClass::DrawModel( flags );
if ( bDoEffect )
pRenderContext->PopDeformation();
return ret;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ProcessMuzzleFlashEvent()
{
CBasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
// Reenable when the weapons have muzzle flash attachments in the right spot.
bool bInToolRecordingMode = ToolsEnabled() && clienttools->IsInRecordingMode();
if ( this == pLocalPlayer && !bInToolRecordingMode )
return; // don't show own world muzzle flash for localplayer
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
{
// also don't show in 1st person spec mode
if ( pLocalPlayer->GetObserverTarget() == this )
return;
}
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
if ( !pWeapon )
return;
pWeapon->ProcessMuzzleFlashEvent();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetIDTarget() const
{
return m_iIDEntIndex;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetForcedIDTarget( int iTarget )
{
m_iForcedIDTarget = iTarget;
}
//-----------------------------------------------------------------------------
// Purpose: Update this client's targetid entity
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateIDTarget()
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer || !IsLocalPlayer() )
return;
// don't show IDs if mp_fadetoblack is on
if ( GetTeamNumber() > TEAM_SPECTATOR && mp_fadetoblack.GetBool() && !IsAlive() )
{
m_iIDEntIndex = 0;
return;
}
if ( m_iForcedIDTarget )
{
m_iIDEntIndex = m_iForcedIDTarget;
return;
}
// If we're in deathcam, ID our killer
if ( (GetObserverMode() == OBS_MODE_DEATHCAM || GetObserverMode() == OBS_MODE_CHASE) && GetObserverTarget() && GetObserverTarget() != GetLocalTFPlayer() )
{
m_iIDEntIndex = GetObserverTarget()->entindex();
return;
}
// Clear old target and find a new one
m_iIDEntIndex = 0;
trace_t tr;
Vector vecStart, vecEnd;
VectorMA( MainViewOrigin(), MAX_TRACE_LENGTH, MainViewForward(), vecEnd );
VectorMA( MainViewOrigin(), 10, MainViewForward(), vecStart );
// If we're in observer mode, ignore our observer target. Otherwise, ignore ourselves.
if ( IsObserver() )
{
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, GetObserverTarget(), COLLISION_GROUP_NONE, &tr );
}
else
{
// Add DEBRIS when a medic has revive (for tracing against revive markers)
int iReviveMedic = 0;
CALL_ATTRIB_HOOK_INT( iReviveMedic, revive );
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
{
iReviveMedic = 1;
}
int nMask = MASK_SOLID | CONTENTS_DEBRIS;
UTIL_TraceLine( vecStart, vecEnd, nMask, this, COLLISION_GROUP_NONE, &tr );
}
bool bIsEnemyPlayer = false;
if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() )
{
// It's okay to start solid against enemies because we sometimes press right against them
bIsEnemyPlayer = GetTeamNumber() != tr.m_pEnt->GetTeamNumber();
}
if ( ( !tr.startsolid || bIsEnemyPlayer ) && tr.DidHitNonWorldEntity() )
{
C_BaseEntity *pEntity = tr.m_pEnt;
if ( pEntity && ( pEntity != this ) )
{
m_iIDEntIndex = pEntity->entindex();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Display appropriate hints for the target we're looking at
//-----------------------------------------------------------------------------
void C_TFPlayer::DisplaysHintsForTarget( C_BaseEntity *pTarget )
{
// If the entity provides hints, ask them if they have one for this player
ITargetIDProvidesHint *pHintInterface = dynamic_cast<ITargetIDProvidesHint*>(pTarget);
if ( pHintInterface )
{
pHintInterface->DisplayHintTo( this );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetRenderTeamNumber( void )
{
return m_nSkin;
}
static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Vector C_TFPlayer::GetDeathViewPosition()
{
Vector origin = EyePosition();
C_TFRagdoll *pRagdoll = static_cast<C_TFRagdoll*>( m_hRagdoll.Get() );
if ( pRagdoll )
{
if ( pRagdoll->IsDeathAnim() )
{
origin.z += VEC_DEAD_VIEWHEIGHT_SCALED( this ).z*4;
}
else
{
IRagdoll *pIRagdoll = GetRepresentativeRagdoll();
if ( pIRagdoll )
{
origin = pIRagdoll->GetRagdollOrigin();
origin.z += VEC_DEAD_VIEWHEIGHT_SCALED( this ).z; // look over ragdoll, not through
}
}
}
return origin;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CalcDeathCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
{
CBaseEntity * killer = GetObserverTarget();
C_BaseAnimating *pKillerAnimating = killer ? killer->GetBaseAnimating() : NULL;
// Swing to face our killer within half the death anim time
float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / (TF_DEATH_ANIMATION_TIME * 0.5);
interpolation = clamp( interpolation, 0.0f, 1.0f );
interpolation = SimpleSpline( interpolation );
float flMinChaseDistance = CHASE_CAM_DISTANCE_MIN;
float flMaxChaseDistance = CHASE_CAM_DISTANCE_MAX;
if ( pKillerAnimating )
{
float flScaleSquared = pKillerAnimating->GetModelScale() * pKillerAnimating->GetModelScale();
flMinChaseDistance *= flScaleSquared;
flMaxChaseDistance *= flScaleSquared;
}
m_flObserverChaseDistance += gpGlobals->frametime * 48.0f;
m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, flMinChaseDistance, flMaxChaseDistance );
QAngle aForward = eyeAngles = EyeAngles();
Vector origin = GetDeathViewPosition();
if ( m_hHeadGib )
{
// View from our decapitated head.
IPhysicsObject *pPhysicsObject = m_hHeadGib->VPhysicsGetObject();
if( pPhysicsObject )
{
Vector vecMassCenter = pPhysicsObject->GetMassCenterLocalSpace();
Vector vecWorld;
m_hHeadGib->CollisionProp()->CollisionToWorldSpace( vecMassCenter, &vecWorld );
m_hHeadGib->AddEffects( EF_NODRAW );
eyeOrigin = vecWorld + Vector(0,0,6);
QAngle aHead = m_hHeadGib->GetAbsAngles();
Vector vBody;
if ( m_hRagdoll )
{
// Turn to face our ragdoll.
vBody = m_hRagdoll->GetAbsOrigin() - eyeOrigin;
}
else
{
vBody = m_hHeadGib->GetAbsOrigin();
}
QAngle aBody; VectorAngles( vBody, aBody );
InterpolateAngles( aHead, aBody, eyeAngles, interpolation );
return;
}
}
if ( killer && (killer != this) )
{
Vector vKiller = killer->EyePosition() - origin;
QAngle aKiller;
VectorAngles( vKiller, aKiller );
InterpolateAngles( aForward, aKiller, eyeAngles, interpolation );
}
Vector vForward;
AngleVectors( eyeAngles, &vForward );
VectorNormalize( vForward );
VectorMA( origin, -m_flObserverChaseDistance, vForward, eyeOrigin );
trace_t trace; // clip against world
C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
UTIL_TraceHull( origin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
C_BaseEntity::PopEnableAbsRecomputations();
if (trace.fraction < 1.0)
{
eyeOrigin = trace.endpos;
m_flObserverChaseDistance = VectorLength(origin - eyeOrigin);
}
fov = GetFOV();
}
//-----------------------------------------------------------------------------
// Purpose: Do nothing multiplayer_animstate takes care of animation.
// Input : playerAnim -
//-----------------------------------------------------------------------------
void C_TFPlayer::SetAnimation( PLAYER_ANIM playerAnim )
{
return;
}
float C_TFPlayer::GetMinFOV() const
{
// Min FOV for Sniper Rifle
return 20;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const QAngle& C_TFPlayer::EyeAngles()
{
if ( IsLocalPlayer() && g_nKillCamMode == OBS_MODE_NONE )
{
return BaseClass::EyeAngles();
}
else
{
return m_angEyeAngles;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &color -
//-----------------------------------------------------------------------------
void C_TFPlayer::GetTeamColor( Color &color )
{
color[3] = 255;
if ( GetTeamNumber() == TF_TEAM_RED )
{
color[0] = 159;
color[1] = 55;
color[2] = 34;
}
else if ( GetTeamNumber() == TF_TEAM_BLUE )
{
color[0] = 76;
color[1] = 109;
color[2] = 129;
}
else
{
color[0] = 255;
color[1] = 255;
color[2] = 255;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bCopyEntity -
// Output : C_BaseAnimating *
//-----------------------------------------------------------------------------
C_BaseAnimating *C_TFPlayer::BecomeRagdollOnClient()
{
// Let the C_TFRagdoll take care of this.
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : IRagdoll*
//-----------------------------------------------------------------------------
IRagdoll* C_TFPlayer::GetRepresentativeRagdoll() const
{
if ( m_hRagdoll.Get() )
{
C_TFRagdoll *pRagdoll = static_cast<C_TFRagdoll*>( m_hRagdoll.Get() );
if ( !pRagdoll )
return NULL;
return pRagdoll->GetIRagdoll();
}
else
{
return NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitPlayerGibs( void )
{
// Clear out the gib list and create a new one.
m_aGibs.Purge();
m_aNormalGibs.PurgeAndDeleteElements();
m_aSillyGibs.Purge();
int nModelIndex = GetPlayerClass()->HasCustomModel() ? modelinfo->GetModelIndex( GetPlayerClass()->GetModelName() ) : GetModelIndex();
BuildGibList( m_aGibs, nModelIndex, 1.0f, COLLISION_GROUP_NONE );
if ( TFGameRules() && TFGameRules()->IsBirthday() )
{
for ( int i = 0; i < m_aGibs.Count(); i++ )
{
if ( RandomFloat(0,1) < 0.75 )
{
V_strcpy_safe( m_aGibs[i].modelName, g_pszBDayGibs[ RandomInt(0,ARRAYSIZE(g_pszBDayGibs)-1) ] );
}
}
}
// Copy the normal gibs list to be saved for later when swapping with Pyro Vision
FOR_EACH_VEC ( m_aGibs, i )
{
char *cloneStr = new char [ 512 ];
Q_strncpy( cloneStr, m_aGibs[i].modelName, 512 );
m_aNormalGibs.AddToTail( cloneStr );
// Create a list of silly gibs
int iRandIndex = RandomInt(4,ARRAYSIZE(g_pszBDayGibs)-1);
m_aSillyGibs.AddToTail( iRandIndex );
}
}
//-----------------------------------------------------------------------------
// Purpose : Checks vision flags and ensures the proper gib models are loaded for vision mode
//-----------------------------------------------------------------------------
void C_TFPlayer::CheckAndUpdateGibType( void )
{
// check the first gib, if it's different copy them all over
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) || ( TFGameRules() && TFGameRules()->UseSillyGibs() ) )
{
if ( Q_strcmp( m_aGibs[0].modelName, g_pszBDayGibs[ m_aSillyGibs[0] ]) != 0 )
{
FOR_EACH_VEC( m_aGibs, i )
{
V_strcpy_safe( m_aGibs[i].modelName, g_pszBDayGibs[ m_aSillyGibs[i] ] );
}
}
}
else
{
if ( Q_strcmp( m_aGibs[0].modelName, m_aNormalGibs[0]) != 0 )
{
FOR_EACH_VEC( m_aGibs, i )
{
V_strcpy_safe( m_aGibs[i].modelName, m_aNormalGibs[i] );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &vecOrigin -
// &vecVelocity -
// &vecImpactVelocity -
//-----------------------------------------------------------------------------
void C_TFPlayer::CreatePlayerGibs( const Vector &vecOrigin, const Vector &vecVelocity, float flImpactScale, bool bBurning, bool bWearableGibs, bool bOnlyHead, bool bDisguiseGibs )
{
// Make sure we have Gibs to create.
if ( m_aGibs.Count() == 0 )
return;
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
Vector vecBreakVelocity = vecVelocity;
vecBreakVelocity.z += tf_playergib_forceup.GetFloat();
VectorNormalize( vecBreakVelocity );
vecBreakVelocity *= tf_playergib_force.GetFloat();
// Cap the impulse.
float flSpeed = vecBreakVelocity.Length();
if ( flSpeed > tf_playergib_maxspeed.GetFloat() )
{
VectorScale( vecBreakVelocity, tf_playergib_maxspeed.GetFloat() / flSpeed, vecBreakVelocity );
}
breakablepropparams_t breakParams( vecOrigin, GetRenderAngles(), vecBreakVelocity, angularImpulse );
breakParams.impactEnergyScale = 1.0f;//
// Break up the player.
if ( !bWearableGibs )
{
// Gib the player's body.
m_hHeadGib = NULL;
m_hSpawnedGibs.Purge();
bool bHasCustomModel = GetPlayerClass()->HasCustomModel();
int nModelIndex = bHasCustomModel ? modelinfo->GetModelIndex( GetPlayerClass()->GetModelName() ) : GetModelIndex();
if ( bOnlyHead )
{
if ( UTIL_IsLowViolence() )
{
// No bloody gibs with pyro-vision goggles
return;
}
// Create only a head gib.
CUtlVector<breakmodel_t> headGib;
int nClassIndex = GetPlayerClass()->GetClassIndex();
if ( bHasCustomModel )
{
for ( int i=0; i<m_aGibs.Count(); ++i )
{
if ( Q_strcmp( m_aGibs[i].modelName, g_pszBotHeadGibs[nClassIndex] ) == 0 )
{
headGib.AddToHead( m_aGibs[i] );
}
}
}
else
{
for ( int i=0; i<m_aGibs.Count(); ++i )
{
if ( Q_strcmp( m_aGibs[i].modelName, g_pszHeadGibs[nClassIndex] ) == 0 )
{
headGib.AddToHead( m_aGibs[i] );
}
}
}
m_hFirstGib = CreateGibsFromList( headGib, nModelIndex, NULL, breakParams, this, -1 , false, true, &m_hSpawnedGibs, bBurning );
m_hHeadGib = m_hFirstGib;
if ( m_hFirstGib )
{
IPhysicsObject *pPhysicsObject = m_hFirstGib->VPhysicsGetObject();
if( pPhysicsObject )
{
// Give the head some rotational damping so it doesn't roll so much (for the player's view).
float damping, rotdamping;
pPhysicsObject->GetDamping( &damping, &rotdamping );
rotdamping *= 6.f;
pPhysicsObject->SetDamping( &damping, &rotdamping );
}
}
}
else
{
CheckAndUpdateGibType();
m_hFirstGib = CreateGibsFromList( m_aGibs, nModelIndex, NULL, breakParams, this, -1 , false, true, &m_hSpawnedGibs, bBurning );
}
DropPartyHat( breakParams, vecBreakVelocity );
}
else
{
// Gib up the player's clothing.
for ( int i=0; i<GetNumWearables(); ++i )
{
C_TFWearable *pItem = dynamic_cast<C_TFWearable*> (GetWearable(i));
if ( !pItem )
continue;
// Don't try to drop items which haven't loaded yet
if ( !pItem->GetModel() || !pItem->GetModelPtr() )
continue;
// Only drop wearable gibs for wearables that are flagged as droppable.
if ( pItem->GetDropType() != ITEM_DROP_TYPE_DROP )
continue;
if ( pItem->IsDisguiseWearable() && !bDisguiseGibs )
continue;
if ( !pItem->IsDisguiseWearable() && bDisguiseGibs )
continue;
DropWearable( pItem, breakParams );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::DropWearable( C_TFWearable *pItem, const breakablepropparams_t &params )
{
// Get the position from the rootbone of the wearable entity itself
Vector position;
matrix3x4_t rootBone;
if ( !pItem->IsDynamicModelLoading() && pItem->GetRootBone( rootBone ) )
{
MatrixPosition( rootBone, position );
}
else
{
position = pItem->GetAbsOrigin();
}
// Don't spawn wearables out of bounds
if ( !IsEntityPositionReasonable( position ) )
{
return;
}
const model_t *pModel = modelinfo->GetModel( pItem->GetModelIndex() );
// Check that the entity wouldn't be spawned in a wall
Vector mins, maxs;
modelinfo->GetModelRenderBounds( pModel, mins, maxs );
trace_t trace;
CTraceFilterNoNPCsOrPlayer filter( this, COLLISION_GROUP_NONE );
UTIL_TraceHull( position, position, mins, maxs, MASK_SOLID, &filter, &trace );
if ( trace.startsolid )
{
return;
}
// Velocity
Vector objectVelocity = params.velocity;
float flScale = VectorNormalize( objectVelocity );
objectVelocity.x += RandomFloat( -1.f, 1.0f );
objectVelocity.y += RandomFloat( -1.0f, 1.0f );
objectVelocity.z += RandomFloat( 0.0f, 1.0f );
VectorNormalize( objectVelocity );
objectVelocity *= flScale;
// Now create the TF2 wearable gib
C_EconWearableGib *pEntity = new C_EconWearableGib();
if ( !pEntity )
return;
const char *pszModelName = modelinfo->GetModelName( pModel );
pEntity->SetModelName( AllocPooledString( pszModelName ) );
pEntity->SetAbsOrigin( position );
pEntity->SetAbsAngles( pItem->GetAbsAngles() );
pEntity->SetOwnerEntity( this );
pEntity->ChangeTeam( GetTeamNumber() ); // our gibs will match our team; this will probably not be used for anything besides team coloring
// Copy the script created item data over
pEntity->GetAttributeContainer()->SetItem( pItem->GetAttributeContainer()->GetItem() );
if ( !pEntity->Initialize( false ) )
{
pEntity->Release();
return;
}
pEntity->m_nSkin = m_nSkin;
pEntity->StartFadeOut( 15.0f );
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
if ( !pPhysicsObject )
{
pEntity->Release();
return;
}
// randomize velocity by 5%
float rndf = RandomFloat( -0.025, 0.025 );
Vector rndVel = objectVelocity + rndf*objectVelocity;
pPhysicsObject->AddVelocity( &rndVel, &params.angularVelocity );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::DropPartyHat( breakablepropparams_t &breakParams, Vector &vecBreakVelocity )
{
// Turning off party hats because we've moving to real hats
return;
/*
if ( m_hPartyHat )
{
breakmodel_t breakModel;
Q_strncpy( breakModel.modelName, BDAY_HAT_MODEL, sizeof(breakModel.modelName) );
breakModel.health = 1;
breakModel.fadeTime = RandomFloat(5,10);
breakModel.fadeMinDist = 0.0f;
breakModel.fadeMaxDist = 0.0f;
breakModel.burstScale = breakParams.defBurstScale;
breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
breakModel.isRagdoll = false;
breakModel.isMotionDisabled = false;
breakModel.placementName[0] = 0;
breakModel.placementIsBone = false;
breakModel.offset = GetAbsOrigin() - m_hPartyHat->GetAbsOrigin();
BreakModelCreateSingle( this, &breakModel, m_hPartyHat->GetAbsOrigin(), m_hPartyHat->GetAbsAngles(), vecBreakVelocity, breakParams.angularVelocity, m_hPartyHat->m_nSkin, breakParams );
m_hPartyHat->Release();
}
*/
}
//-----------------------------------------------------------------------------
// Purpose: How many buildables does this player own
//-----------------------------------------------------------------------------
int C_TFPlayer::GetObjectCount( void )
{
return m_aObjects.Count();
}
//-----------------------------------------------------------------------------
// Purpose: Get a specific buildable that this player owns
//-----------------------------------------------------------------------------
C_BaseObject *C_TFPlayer::GetObject( int index_ )
{
return m_aObjects[index_].Get();
}
//-----------------------------------------------------------------------------
// Purpose: Get a specific buildable that this player owns
//-----------------------------------------------------------------------------
C_BaseObject *C_TFPlayer::GetObjectOfType( int iObjectType, int iObjectMode ) const
{
int iCount = m_aObjects.Count();
for ( int i=0;i<iCount;i++ )
{
C_BaseObject *pObj = m_aObjects[i].Get();
if ( !pObj )
continue;
if ( pObj->IsDormant() || pObj->IsMarkedForDeletion() )
continue;
if ( pObj->GetType() != iObjectType )
continue;
if ( pObj->GetObjectMode() != iObjectMode )
continue;
return pObj;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : collisionGroup -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldCollide( int collisionGroup, int contentsMask ) const
{
if ( ( ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) && tf_avoidteammates.GetBool() ) ||
collisionGroup == TFCOLLISION_GROUP_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 );
}
float C_TFPlayer::GetPercentInvisible( void )
{
return m_Shared.GetPercentInvisible();
}
void C_TFPlayer::AdjustSkinIndexForZombie( int iClassIndex, int &iSkinIndex )
{
// We only know how to adjust the 4 standard base skins (red/blue * normal/invuln)
Assert( iSkinIndex >= 0 && iSkinIndex < 4 );
if ( iClassIndex == TF_CLASS_SPY )
{
// Spy has a bunch of extra skins used to adjust the mask
iSkinIndex += 22;
}
else
{
// 4: red zombie
// 5: blue zombie
// 6: red zombie invuln
// 7: blue zombie invuln
iSkinIndex += 4;
}
}
bool C_TFPlayer::BRenderAsZombie( bool bWeaponsCheck /*= false */ )
{
// Only if the local player is optining in.
if ( !IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_HALLOWEEN, bWeaponsCheck ) )
return false;
// Should we render as somebody else?
bool bRenderDisguised = false;
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
// When disguised, our teammates will see us with a mask.
// Don't show us as a zombie in that state, because the zombie parts
// (like every other cosmetic) disappear.
if ( !IsEnemyPlayer() )
return false;
// Ditto when we are disguised as an enemy spy. We always use the mask
// in that case and hide cosmetics
if ( m_Shared.GetDisguiseClass() == TF_CLASS_SPY )
return false;
bRenderDisguised = true;
}
int iPlayerSkinOverride = bRenderDisguised ? m_Shared.GetDisguisedSkinOverride() : GetSkinOverride();
return iPlayerSkinOverride == 1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetSkin()
{
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
if ( !pLocalPlayer )
return 0;
// Allow server plugins to override
if ( m_bForcedSkin )
return m_nForcedSkin;
int iVisibleTeam = GetTeamNumber();
// if this player is disguised and on the other team, use disguise team
if ( m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) && IsEnemyPlayer() && ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
{
iVisibleTeam = ( iVisibleTeam == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED );
}
else if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
{
iVisibleTeam = m_Shared.GetDisguiseTeam();
}
int nSkin;
switch( iVisibleTeam )
{
case TF_TEAM_RED:
nSkin = 0;
break;
case TF_TEAM_BLUE:
nSkin = 1;
break;
default:
nSkin = 0;
break;
}
// Assume we'll switch skins to show the spy mask
bool bCheckSpyMask = true;
// 3 and 4 are invulnerable
if ( m_Shared.IsInvulnerable() &&
( !m_Shared.InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) || gpGlobals->curtime < GetLastDamageTime() + 2.0f ) )
{
nSkin += 2;
bCheckSpyMask = false;
}
// Check for any special player skin override behaviour.
if ( BRenderAsZombie() )
{
int iClass = GetPlayerClass()->GetClassIndex();
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
Assert( IsEnemyPlayer() );
iClass = m_Shared.GetDisguiseClass();
}
AdjustSkinIndexForZombie( iClass, nSkin );
bCheckSpyMask = false; // no spy masks for zombies
}
if ( bCheckSpyMask && m_Shared.InCond( TF_COND_DISGUISED ) )
{
if ( !IsEnemyPlayer() )
{
nSkin += 4 + ( ( m_Shared.GetDisguiseClass() - TF_FIRST_NORMAL_CLASS ) * 2 );
}
else if ( m_Shared.GetDisguiseClass() == TF_CLASS_SPY )
{
nSkin += 4 + ( ( m_Shared.GetDisguiseMask() - TF_FIRST_NORMAL_CLASS ) * 2 );
}
}
// Check for testing override
#ifdef STAGING_ONLY
if ( ( this == pLocalPlayer ) && ( test_local_player_skin_override.GetInt() >= 0 ) )
nSkin = test_local_player_skin_override.GetInt();
#endif
return nSkin;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iClass -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsPlayerClass( int iClass ) const
{
const C_TFPlayerClass *pClass = GetPlayerClass();
if ( !pClass )
return false;
return ( pClass->GetClassIndex() == iClass );
}
//-----------------------------------------------------------------------------
// Purpose: Don't take damage decals while stealthed
//-----------------------------------------------------------------------------
void C_TFPlayer::AddDecal( const Vector& rayStart, const Vector& rayEnd,
const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal )
{
if ( m_Shared.IsStealthed() )
{
return;
}
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
return;
}
if ( m_Shared.IsInvulnerable() )
{
Vector vecDir = rayEnd - rayStart;
VectorNormalize(vecDir);
g_pEffects->Ricochet( rayEnd - (vecDir * 8), -vecDir );
return;
}
#ifdef TF_RAID_MODE
// no decals for the BLUE team in Raid mode
// (temp workaround for decals causing the glows to not draw correctly)
if ( TFGameRules() && TFGameRules()->IsRaidMode() )
{
if ( GetTeamNumber() == TF_TEAM_BLUE )
{
return;
}
}
#endif // TF_RAID_MODE
// don't decal from inside the player
if ( tr.startsolid )
{
return;
}
BaseClass::AddDecal( rayStart, rayEnd, decalCenter, hitbox, decalIndex, doTrace, tr, maxLODToDecal );
}
//-----------------------------------------------------------------------------
// Called every time the player respawns
//-----------------------------------------------------------------------------
void C_TFPlayer::ClientPlayerRespawn( void )
{
if ( IsLocalPlayer() )
{
// MCJOHN - For testing, dump out all of the textures whenever we respawn
STAGING_ONLY_EXEC( engine->ClientCmd_Unrestricted( "mat_evict_all" ) );
// Dod called these, not sure why
//MoveToLastReceivedPosition( true );
//ResetLatched();
// Reset the camera.
HandleTaunting();
ResetToneMapping(1.0);
// Release the duck toggle key
KeyUp( &in_ducktoggle, NULL );
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_respawn" );
if ( event )
{
gameeventmanager->FireEventClientSide( event );
}
// NVNT revert tf haptics
tfHaptics.Revert();
#if defined( REPLAY_ENABLED )
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED )
{
// Notify replay history manager that the local player has spawned.
// NOTE: This won't do anything if replay isn't enabled, isn't recording, etc.
g_pClientReplayContext->OnPlayerSpawn();
}
#endif
SetAppropriateCamera( this );
if ( m_hRevivePrompt )
{
m_hRevivePrompt->MarkForDeletion();
m_hRevivePrompt = NULL;
}
m_bNotifiedWeaponInspectThisLife = false;
}
UpdateVisibility();
DestroyBoneAttachments();
UpdateDemomanEyeEffect( 0 );
UpdateKillStreakEffects( 0 );
UpdateMVMEyeGlowEffect( true );
m_hHeadGib = NULL;
m_hFirstGib = NULL;
m_hSpawnedGibs.Purge();
m_fMetersRan = 0;
SetShowHudMenuTauntSelection( false );
CleanUpAnimationOnSpawn();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldDraw()
{
if ( IsLocalPlayer() )
{
if ( m_PlayerClass.HasCustomModel() && !m_PlayerClass.CustomModelIsVisibleToSelf() )
return false;
}
if ( this == C_TFPlayer::GetLocalTFPlayer() )
{
if ( this->m_Shared.InCond( TF_COND_ZOOMED ) )
{
return false;
}
}
return BaseClass::ShouldDraw();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetVisionFilterFlags( bool bWeaponsCheck /*= false */ )
{
#if defined( REPLAY_ENABLED )
extern IEngineClientReplay *g_pEngineClientReplay;
if ( g_pEngineClientReplay->IsPlayingReplayDemo() )
{
if ( tf_replay_pyrovision.GetBool() )
{
return TF_VISION_FILTER_PYRO;
}
return 0x00;
}
#endif
int nVisionOptInFlags = 0;
CALL_ATTRIB_HOOK_INT( nVisionOptInFlags, vision_opt_in_flags );
if ( IsLocalPlayer() )
{
// Force our vision filter to a specific setting
if ( bWeaponsCheck && m_nForceVisionFilterFlags != 0 )
{
return m_nForceVisionFilterFlags;
}
#ifdef _DEBUG
int nFlags = 0;
if ( test_pyrovision.GetBool() )
{
nFlags |= TF_VISION_FILTER_PYRO;
}
if ( test_halloweenvision.GetBool() )
{
nFlags |= TF_VISION_FILTER_HALLOWEEN;
}
if ( test_romevision.GetBool() )
{
nFlags |= TF_VISION_FILTER_ROME;
}
if ( nFlags != 0 )
{
return nFlags;
}
if ( test_vision_off.GetBool() )
{
return 0x00;
}
#endif
// Check the PyroVision Convar
if ( GetTeamNumber() == TEAM_SPECTATOR )
{
if ( tf_spectate_pyrovision.GetBool() )
{
nVisionOptInFlags |= TF_VISION_FILTER_PYRO;
}
}
}
// check for holidays and add them in to the mix
// Halloween / Fullmoon vision
if ( TFGameRules()->IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
{
nVisionOptInFlags |= TF_VISION_FILTER_HALLOWEEN;
}
// opt-in for romevision?
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() &&
TFSharedContentManager() && TFSharedContentManager()->IsSharedVisionAvailable( TF_VISION_FILTER_ROME ) &&
tf_romevision_opt_in.GetBool() )
{
nVisionOptInFlags |= TF_VISION_FILTER_ROME;
}
return nVisionOptInFlags;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : updateType -
//-----------------------------------------------------------------------------
void C_TFPlayer::CalculateVisionUsingCurrentFlags( void )
{
if ( IsLocalPlayer() )
{
for( int iClient = 1; iClient <= gpGlobals->maxClients; ++iClient )
{
C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iClient ) );
if ( !pPlayer || !pPlayer->IsPlayer() )
continue;
if ( !pPlayer->IsAlive() )
continue;
pPlayer->UpdateWearables();
pPlayer->SetBodygroupsDirty();
if ( pPlayer->GetActiveWeapon() )
{
pPlayer->GetActiveWeapon()->RestartParticleEffect();
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateSaveMeEffect( MedicCallerType nType /*= CALLER_TYPE_NORMAL*/ )
{
// Don't create them for the local player in first-person view.
if ( IsLocalPlayer() && InFirstPersonView() )
return;
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
return;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// If I'm disguised as the enemy, play to all players
if ( m_Shared.InCond( TF_COND_DISGUISED ) && m_Shared.GetDisguiseTeam() != GetTeamNumber() && !m_Shared.IsStealthed() )
{
// play to all players
}
else
{
// only play to teammates
if ( pLocalPlayer && pLocalPlayer->GetTeamNumber() != GetTeamNumber() )
return;
}
StopSaveMeEffect();
float flHealth = float( GetHealth() ) / float( GetMaxHealth() );
Vector vHealth;
vHealth.x = flHealth;
vHealth.y = flHealth;
vHealth.z = flHealth;
if ( nType == CALLER_TYPE_AUTO )
{
m_pSaveMeEffect = ParticleProp()->Create( "speech_mediccall_auto", PATTACH_POINT_FOLLOW, "head" );
EmitSound( "Medic.AutoCallerAnnounce" );
}
else
{
m_pSaveMeEffect = ParticleProp()->Create( "speech_mediccall", PATTACH_POINT_FOLLOW, "head" );
}
if ( m_pSaveMeEffect )
{
m_pSaveMeEffect->SetControlPoint( 1, vHealth );
}
// If the local player is a medic, add this player to our list of medic callers
if ( pLocalPlayer && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && pLocalPlayer->IsAlive() == true )
{
Vector vecPos;
if ( GetAttachmentLocal( LookupAttachment( "head" ), vecPos ) )
{
vecPos += Vector(0,0,18); // Particle effect is 18 units above the attachment
CTFMedicCallerPanel::AddMedicCaller( this, 5.0, vecPos, nType );
}
}
IGameEvent *event = gameeventmanager->CreateEvent( "player_calledformedic" );
if ( event )
{
event->SetInt( "userid", GetUserID() );
gameeventmanager->FireEventClientSide( event );
}
m_flSaveMeExpireTime = gpGlobals->curtime + 5.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopSaveMeEffect( bool bForceRemoveInstantly /*= false*/ )
{
if ( m_pSaveMeEffect )
{
if ( bForceRemoveInstantly )
{
ParticleProp()->StopEmissionAndDestroyImmediately( m_pSaveMeEffect );
}
else
{
ParticleProp()->StopEmission( m_pSaveMeEffect );
}
m_pSaveMeEffect = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateTauntWithMeEffect()
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer || this == pLocalPlayer )
return;
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
return;
if ( !m_pTauntWithMeEffect )
{
const char *pszImageName;
const char *pszParticleName;
if ( GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
{
pszImageName = "../Effects/speech_taunt";
pszParticleName = "speech_taunt_all";
}
else if ( GetTeamNumber() == TF_TEAM_RED )
{
pszImageName = "../Effects/speech_taunt_red";
pszParticleName = "speech_taunt_red";
}
else
{
pszImageName = "../Effects/speech_taunt_blue";
pszParticleName = "speech_taunt_blue";
}
m_pTauntWithMeEffect = ParticleProp()->Create( pszParticleName, PATTACH_POINT_FOLLOW, "head" );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopTauntWithMeEffect()
{
if ( m_pTauntWithMeEffect )
{
ParticleProp()->StopEmissionAndDestroyImmediately( m_pTauntWithMeEffect );
m_pTauntWithMeEffect = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateKart()
{
Assert( !m_pKart );
m_pKart = new C_BaseAnimating();
if ( m_pKart )
{
m_pKart->SetModel( "models/player/items/taunts/bumpercar/parts/bumpercar.mdl" );
m_pKart->m_nSkin = GetTeamNumber() == TF_TEAM_RED ? 0 : 1;
// Add to the client entity list. This has to be done before we attach to the parent or
// else we won't wind up on their "also shadow these children" list.
ClientEntityList().AddNonNetworkableEntity(m_pKart);
m_pKart->FollowEntity(this, true);
m_pKart->CreateShadow();
TrackAngRotation( false );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::RemoveKart()
{
StopKartEffect();
if ( m_pKart )
{
// Cleanup our shadows.
m_pKart->DestroyShadow();
// Remove from the client entity list.
ClientEntityList().RemoveEntity( m_pKart->GetClientHandle() );
m_pKart->Release();
m_pKart = NULL;
TrackAngRotation( true );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateKartEffect( const char *pszEffectName )
{
if ( !m_pKart )
return;
#ifdef DEBUG
for ( int i=0; i<ARRAYSIZE( m_pKartParticles ); ++i )
{
Assert( !m_pKartParticles[i] );
}
#endif // DEBUG
m_pKartParticles[ KART_PARTICLE_LEFT_LIGHT ] = m_pKart->ParticleProp()->Create( pszEffectName , PATTACH_POINT_FOLLOW, "bumpercar_wheel_left" );
m_pKartParticles[ KART_PARTICLE_RIGHT_LIGHT ] = m_pKart->ParticleProp()->Create( pszEffectName , PATTACH_POINT_FOLLOW, "bumpercar_wheel_right" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopKartEffect()
{
if ( !m_pKart )
return;
for ( int i=0; i<ARRAYSIZE( m_pKartParticles ); ++i )
{
if ( m_pKartParticles[i] )
{
m_pKart->ParticleProp()->StopEmission( m_pKartParticles[i] );
m_pKartParticles[i] = NULL;
}
}
}
//-----------------------------------------------------------------------------
void C_TFPlayer::StartKartBrakeEffect()
{
if ( !m_pKart )
return;
if ( !m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] )
{
m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] = m_pKart->ParticleProp()->Create( "kart_braking_sparks" , PATTACH_POINT_FOLLOW, "left_rear_wheel" );
}
if ( !m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] )
{
m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] = m_pKart->ParticleProp()->Create( "kart_braking_sparks", PATTACH_POINT_FOLLOW, "right_rear_wheel" );
}
}
//-----------------------------------------------------------------------------
void C_TFPlayer::StopKartBrakeEffect()
{
if ( !m_pKart )
return;
if ( m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] )
{
m_pKart->ParticleProp()->StopEmission( m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] );
}
if ( m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] )
{
m_pKart->ParticleProp()->StopEmission( m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsOverridingViewmodel( void )
{
C_TFPlayer *pPlayer = this;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() )
{
pPlayer = assert_cast<C_TFPlayer*>(pLocalPlayer->GetObserverTarget());
}
if ( pPlayer->m_Shared.IsInvulnerable() )
return true;
return BaseClass::IsOverridingViewmodel();
}
void C_TFPlayer::OverrideView( CViewSetup *pSetup )
{
BaseClass::OverrideView( pSetup );
TFPlayerClassData_t *pData = GetPlayerClass()->GetData();
if ( pData && g_ThirdPersonManager.WantToUseGameThirdPerson() )
{
Vector vecOffset = pData->m_vecThirdPersonOffset;
g_ThirdPersonManager.SetDesiredCameraOffset( vecOffset );
}
}
//-----------------------------------------------------------------------------
// Purpose: Draw my viewmodel in some special way
//-----------------------------------------------------------------------------
int C_TFPlayer::DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags )
{
int ret = 0;
C_TFPlayer *pPlayer = this;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() )
{
pPlayer = assert_cast<C_TFPlayer*>(pLocalPlayer->GetObserverTarget());
}
if ( pPlayer->m_Shared.IsInvulnerable() )
{
if ( flags & STUDIO_RENDER )
{
// Force the invulnerable material
modelrender->ForcedMaterialOverride( *pPlayer->GetInvulnMaterialRef() );
}
// We allow our weapon to then override this if it wants to.
// This allows c_* weapons to draw themselves.
C_BaseCombatWeapon *pWeapon = pViewmodel->GetOwningWeapon();
if ( pWeapon && pWeapon->IsOverridingViewmodel() )
{
ret = pWeapon->DrawOverriddenViewmodel( pViewmodel, flags );
}
else
{
ret = pViewmodel->DrawOverriddenViewmodel( flags );
}
if ( flags & STUDIO_RENDER )
{
modelrender->ForcedMaterialOverride( NULL );
}
}
return ret;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ApplyBoneMatrixTransform( matrix3x4_t& transform )
{
BaseClass::ApplyBoneMatrixTransform ( transform );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void BuildBigHeadTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale )
{
if ( !pObject || flScale == 1.f )
return;
// Scale the head.
int iHeadBone = pObject->LookupBone( "bip_head" );
if ( iHeadBone == -1 )
return;
matrix3x4_t &transform = pObject->GetBoneForWrite( iHeadBone );
Vector head_position;
MatrixGetTranslation( transform, head_position );
// Scale the head.
MatrixScaleBy ( flScale, transform );
const int cMaxNumHelms = 2;
int iHelmIndex[cMaxNumHelms];
iHelmIndex[0] = pObject->LookupBone( "prp_helmet" );
iHelmIndex[1] = pObject->LookupBone( "prp_hat" );
for ( int i = 0; i < cMaxNumHelms; i++ )
{
if ( iHelmIndex[i] != -1 )
{
matrix3x4_t &transformhelmet = pObject->GetBoneForWrite( iHelmIndex[i] );
MatrixScaleBy ( flScale, transformhelmet );
Vector helmet_position;
MatrixGetTranslation( transformhelmet, helmet_position );
Vector offset = helmet_position - head_position;
MatrixSetTranslation ( ( offset * flScale ) + head_position, transformhelmet );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void BuildDecapitatedTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
{
if ( !pObject )
return;
// Scale the head to nothing.
int iHeadBone = pObject->LookupBone( "bip_head" );
if ( iHeadBone != -1 )
{
matrix3x4_t &transform = pObject->GetBoneForWrite( iHeadBone );
MatrixScaleByZero ( transform );
}
int iHelm = pObject->LookupBone( "prp_helmet" );
if ( iHelm != -1 )
{
// Scale the helmet.
matrix3x4_t &transformhelmet = pObject->GetBoneForWrite( iHelm );
MatrixScaleByZero ( transformhelmet );
}
iHelm = pObject->LookupBone( "prp_hat" );
if ( iHelm != -1 )
{
matrix3x4_t &transformhelmet = pObject->GetBoneForWrite( iHelm );
MatrixScaleByZero ( transformhelmet );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void BuildNeckScaleTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale, int iClass )
{
if ( !pObject || flScale == 1.f )
return;
int iNeck = pObject->LookupBone( "bip_neck" );
if ( iNeck == -1 )
return;
matrix3x4_t &neck_transform = pObject->GetBoneForWrite( iNeck );
Vector spine_position, neck_position, head_position, position, offset(0, 0, 0);
if ( iClass != TF_CLASS_HEAVYWEAPONS )
{
// Compress the neck into the spine.
int iSpine = pObject->LookupBone( "bip_spine_3" );
if ( iSpine != -1 )
{
matrix3x4_t &spine_transform = pObject->GetBoneForWrite( iSpine );
MatrixPosition( spine_transform, spine_position );
MatrixPosition( neck_transform, neck_position );
position = flScale * ( neck_position - spine_position );
MatrixSetTranslation( spine_position + position, neck_transform );
}
}
if ( iClass == TF_CLASS_SPY )
{
int iCig = pObject->LookupBone( "prp_cig" );
if ( iCig != -1 )
{
matrix3x4_t &cig_transform = pObject->GetBoneForWrite( iCig );
MatrixScaleByZero ( cig_transform );
}
}
// Compress the head into the neck.
int iHead = pObject->LookupBone( "bip_head" );
if ( iHead != -1 )
{
matrix3x4_t &head_transform = pObject->GetBoneForWrite( iHead );
MatrixPosition( head_transform, head_position );
MatrixPosition( neck_transform, neck_position );
offset = head_position - neck_position;
MatrixSetTranslation( neck_position, head_transform );
}
// Store helmet bone offset.
int iHelm = pObject->LookupBone( "prp_helmet" );
if ( iHelm != -1 )
{
matrix3x4_t &helmet_transform = pObject->GetBoneForWrite( iHelm );
MatrixPosition( helmet_transform, position );
MatrixSetTranslation( position - offset, helmet_transform );
}
// Store alternate helmet bone offset.
iHelm = pObject->LookupBone( "prp_hat" );
if ( iHelm != -1 )
{
matrix3x4_t &hat_transform = pObject->GetBoneForWrite( iHelm );
MatrixPosition( hat_transform, position );
MatrixSetTranslation( position - offset, hat_transform );
}
}
//-----------------------------------------------------------------------------
// Purpose: Get child bones from a specified bone index
//-----------------------------------------------------------------------------
void AppendChildren_R( CUtlVector< const mstudiobone_t * > *pChildBones, const studiohdr_t *pHdr, int nBone )
{
if ( !pChildBones || !pHdr )
return;
// Child bones have to have a larger bone index than their parent, so start searching from nBone + 1
for ( int i = nBone + 1; i < pHdr->numbones; ++i )
{
const mstudiobone_t *pBone = pHdr->pBone( i );
if ( pBone->parent == nBone )
{
pChildBones->AddToTail( pBone );
// If you just want immediate children don't recurse, this will do a depth first traversal, could do
// breadth first by adding all children first and then looping through the added bones and recursing
AppendChildren_R( pChildBones, pHdr, i );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void BuildTorsoScaleTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale, int iClass )
{
if ( !pObject || flScale == 1.f )
return;
int iPelvis = pObject->LookupBone( "bip_pelvis" );
if ( iPelvis == -1 )
return;
const studiohdr_t *pHdr = modelinfo->GetStudiomodel( pObject->GetModel() );
int iTargetBone = iPelvis;
// must be in this order
static const char *s_torsoBoneNames[] =
{
"bip_spine_0",
"bip_spine_1",
"bip_spine_2",
"bip_spine_3",
"bip_neck"
};
// Compress torso bones toward pelvis in order.
for ( int i=0; i<ARRAYSIZE( s_torsoBoneNames ); ++i )
{
int iMoveBone = pObject->LookupBone( s_torsoBoneNames[i] );
if ( iMoveBone == -1 )
{
return;
}
const matrix3x4_t &targetBone_transform = pObject->GetBone( iTargetBone );
Vector vTargetBonePos;
MatrixPosition( targetBone_transform, vTargetBonePos );
matrix3x4_t &moveBone_transform = pObject->GetBoneForWrite( iMoveBone );
Vector vMoveBonePos;
MatrixPosition( moveBone_transform, vMoveBonePos );
Vector vNewMovePos = vTargetBonePos + flScale * ( vMoveBonePos - vTargetBonePos );
MatrixSetTranslation( vNewMovePos, moveBone_transform );
iTargetBone = iMoveBone;
Vector vOffset = vNewMovePos - vMoveBonePos;
// apply to all its child bones
CUtlVector< const mstudiobone_t * > vecChildBones;
AppendChildren_R( &vecChildBones, pHdr, iMoveBone );
for ( int j=0; j<vecChildBones.Count(); ++j )
{
int iChildBone = pObject->LookupBone( vecChildBones[j]->pszName() );
if ( iChildBone == -1 )
continue;
matrix3x4_t &childBone_transform = pObject->GetBoneForWrite( iChildBone );
Vector vChildPos;
MatrixPosition( childBone_transform, vChildPos );
MatrixSetTranslation( vChildPos + vOffset, childBone_transform );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void BuildHandScaleTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale )
{
if ( !pObject || flScale == 1.f )
return;
const studiohdr_t *pHdr = modelinfo->GetStudiomodel( pObject->GetModel() );
// must be in this order
static const char *s_handBoneNames[] =
{
"bip_hand_L",
"bip_hand_R"
};
// scale hand bones
for ( int i=0; i<ARRAYSIZE( s_handBoneNames ); ++i )
{
int iHand = pObject->LookupBone( s_handBoneNames[i] );
if ( iHand == -1 )
{
continue;
}
matrix3x4_t& transform = pObject->GetBoneForWrite( iHand );
MatrixScaleBy( flScale, transform );
// apply to all its child bones
CUtlVector< const mstudiobone_t * > vecChildBones;
AppendChildren_R( &vecChildBones, pHdr, iHand );
for ( int j=0; j<vecChildBones.Count(); ++j )
{
int iChildBone = pObject->LookupBone( vecChildBones[j]->pszName() );
if ( iChildBone == -1 )
continue;
matrix3x4_t &childBone_transform = pObject->GetBoneForWrite( iChildBone );
MatrixScaleBy( flScale, childBone_transform );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
{
BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed );
if ( GetGroundEntity() == NULL )
{
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( this ) - VEC_HULL_MIN_SCALED( this );
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( this ) - VEC_DUCK_HULL_MIN_SCALED( this );
Vector duckOffset = ( hullSizeNormal - hullSizeCrouch );
// The player is in the air.
if ( GetFlags() & FL_DUCKING )
{
if ( !m_bDuckJumpInterp )
{
m_flFirstDuckJumpInterp = gpGlobals->curtime;
}
m_bDuckJumpInterp = true;
m_flLastDuckJumpInterp = gpGlobals->curtime;
float flRatio = MIN( 0.15f, gpGlobals->curtime - m_flFirstDuckJumpInterp ) / 0.15f;
m_flDuckJumpInterp = 1.f - flRatio;
}
else if ( m_bDuckJumpInterp )
{
float flRatio = MIN( 0.15f, gpGlobals->curtime - m_flLastDuckJumpInterp ) / 0.15f;
m_flDuckJumpInterp = -(1.f - flRatio);
if ( m_flDuckJumpInterp == 0.f )
{
// Turn off interpolation when we return to our normal, unducked location.
m_bDuckJumpInterp = false;
}
}
if ( m_bDuckJumpInterp && m_flDuckJumpInterp != 0.f )
{
duckOffset *= m_flDuckJumpInterp;
for (int i = 0; i < hdr->numbones(); i++)
{
if ( !( hdr->boneFlags( i ) & boneMask ) )
continue;
matrix3x4_t &transform = GetBoneForWrite( i );
Vector bone_pos;
MatrixGetTranslation( transform, bone_pos );
MatrixSetTranslation( bone_pos - duckOffset, transform );
}
}
}
else if ( m_bDuckJumpInterp )
{
m_bDuckJumpInterp = false;
}
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
float flHeadScale = m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ? 1.5 : m_flHeadScale;
BuildBigHeadTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, flHeadScale );
BuildTorsoScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flTorsoScale, GetPlayerClass()->GetClassIndex() );
BuildHandScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flHandScale );
BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "bip_head" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFRagdoll::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
{
BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed );
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
BuildBigHeadTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flHeadScale );
BuildTorsoScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flTorsoScale, GetClass() );
BuildHandScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flHandScale );
if ( IsDecapitation() && !m_bBaseTransform )
{
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
BuildDecapitatedTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed );
}
if ( IsHeadSmash() && !m_bBaseTransform )
{
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
BuildNeckScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, 0.5f, GetClass() );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetHealer( C_TFPlayer *pHealer, float flChargeLevel )
{
if ( pHealer && IsPlayerClass( TF_CLASS_SPY ) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "player_healedbymedic" );
if ( event )
{
event->SetInt( "medic", pHealer->entindex() );
gameeventmanager->FireEventClientSide( event );
}
}
// We may be getting healed by multiple healers. Show the healer
// who's got the highest charge level.
if ( m_hHealer )
{
if ( m_flHealerChargeLevel > flChargeLevel )
return;
}
m_hHealer = pHealer;
m_flHealerChargeLevel = flChargeLevel;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::MedicIsReleasingCharge( void )
{
if ( IsPlayerClass(TF_CLASS_MEDIC) )
{
CTFWeaponBase *pWpn = GetActiveTFWeapon();
if ( pWpn == NULL )
return false;
CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pWpn );
if ( pMedigun )
return pMedigun->IsReleasingCharge();
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::CanShowClassMenu( void )
{
if ( IsHLTV() )
return false;
if( TFGameRules() )
{
if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
{
return !m_bArenaSpectator;
}
// Dont allow the change class menu to come up when we're doing the doors and things. There's really weird
// sorting issues that go on even though the class menu is supposed to draw under the match status panel.
if ( TFGameRules()->IsCompetitiveMode() )
{
float flRestartTime = TFGameRules()->GetRoundRestartTime() - gpGlobals->curtime;
if ( flRestartTime > 0.f && flRestartTime < 10.f )
{
return false;
}
}
}
return ( GetTeamNumber() > LAST_SHARED_TEAM );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::CanShowTeamMenu( void )
{
if ( IsHLTV() )
return false;
#ifdef STAGING_ONLY
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
#else
if ( TFGameRules() && ( TFGameRules()->IsCompetitiveMode() || TFGameRules()->IsPowerupMode() ) )
#endif // STAGING_ONLY
return false;
return ( GetTeamNumber() != TEAM_UNASSIGNED );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitializePoseParams( void )
{
/*
m_headYawPoseParam = LookupPoseParameter( "head_yaw" );
GetPoseParameterRange( m_headYawPoseParam, m_headYawMin, m_headYawMax );
m_headPitchPoseParam = LookupPoseParameter( "head_pitch" );
GetPoseParameterRange( m_headPitchPoseParam, m_headPitchMin, m_headPitchMax );
*/
CStudioHdr *hdr = GetModelPtr();
Assert( hdr );
if ( !hdr )
return;
for ( int i = 0; i < hdr->GetNumPoseParameters() ; i++ )
{
SetPoseParameter( hdr, i, 0.0 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Vector C_TFPlayer::GetChaseCamViewOffset( CBaseEntity *target )
{
if ( target->IsBaseObject() )
{
CBaseObject* pObj = dynamic_cast<CBaseObject*>( target );
if ( pObj && pObj->IsMiniBuilding() )
return Vector(0,0,40);
else
return Vector(0,0,64);
}
return BaseClass::GetChaseCamViewOffset( target );
}
//-----------------------------------------------------------------------------
// Purpose: Called from PostDataUpdate to update the model index
//-----------------------------------------------------------------------------
void C_TFPlayer::ValidateModelIndex( void )
{
if ( m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) && IsEnemyPlayer() && ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
{
m_nModelIndex = modelinfo->GetModelIndex( "models/buildables/dispenser_light.mdl" );
if ( C_BasePlayer::GetLocalPlayer() != this )
{
SetAbsAngles( vec3_angle );
}
}
else if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
{
TFPlayerClassData_t *pData = GetPlayerClassData( m_Shared.GetDisguiseClass() );
m_nModelIndex = modelinfo->GetModelIndex( pData->GetModelName() );
}
else if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
if ( GetTeamNumber() == TF_TEAM_BLUE )
{
m_nModelIndex = modelinfo->GetModelIndex( "models/props_halloween/ghost_no_hat.mdl" );
}
else
{
m_nModelIndex = modelinfo->GetModelIndex( "models/props_halloween/ghost_no_hat_red.mdl" );
}
}
else
{
C_TFPlayerClass *pClass = GetPlayerClass();
if ( pClass )
{
m_nModelIndex = modelinfo->GetModelIndex( pClass->GetModelName() );
}
}
if ( m_iSpyMaskBodygroup > -1 && GetModelPtr() != NULL && IsPlayerClass( TF_CLASS_SPY ) )
{
if ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) )
{
if ( !IsEnemyPlayer() || (m_Shared.GetDisguiseClass() == TF_CLASS_SPY) )
{
SetBodygroup( m_iSpyMaskBodygroup, 1 );
}
}
else
{
SetBodygroup( m_iSpyMaskBodygroup, 0 );
}
}
BaseClass::ValidateModelIndex();
}
//-----------------------------------------------------------------------------
// Purpose: Simulate the player for this frame
//-----------------------------------------------------------------------------
void C_TFPlayer::Simulate( void )
{
//Frame updates
if ( this == C_BasePlayer::GetLocalPlayer() )
{
//Update the flashlight
Flashlight();
}
// TF doesn't do step sounds based on velocity, instead using anim events
// So we deliberately skip over the base player simulate, which calls them.
BaseClass::BaseClass::Simulate();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#define PLAYER_HALFWIDTH 10
#define SURFACE_SNOW 91
void C_TFPlayer::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
{
if ( event == 7001 )
{
// Force a footstep sound
m_flStepSoundTime = 0;
Vector vel;
EstimateAbsVelocity( vel );
surfacedata_t *t_pSurface = GetGroundSurface();
UpdateStepSound( t_pSurface, GetAbsOrigin(), vel );
if ( t_pSurface && !this->m_Shared.IsStealthed() && !this->m_Shared.InCond( TF_COND_DISGUISED ) && ( ( vel.x < -150 || vel.x > 150 ) || ( vel.y < -150 || vel.y > 150 ) ) )
{
// check for snow underfoot and trigger particle and decal fx
if ( t_pSurface->game.material == SURFACE_SNOW )
{
ParticleProp()->Create("snow_steppuff01", PATTACH_ABSORIGIN, 0 );
Vector right;
AngleVectors( angles, 0, &right, 0 );
// Figure out where the top of the stepping leg is
trace_t tr;
Vector hipOrigin;
VectorMA( origin, m_IsFootprintOnLeft ? -PLAYER_HALFWIDTH : PLAYER_HALFWIDTH, right, hipOrigin );
// Find where that leg hits the ground
UTIL_TraceLine( hipOrigin, hipOrigin + Vector(0, 0, -COORD_EXTENT * 1.74),
MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr);
// Create the decal
CPVSFilter filter( tr.endpos );
UTIL_DecalTrace( &tr, m_IsFootprintOnLeft ? "footprintL_snow" : "footprintR_snow" );
m_IsFootprintOnLeft = !m_IsFootprintOnLeft;
}
// Halloween-specific bonus footsteps
int iHalloweenFootstepType = 0;
if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
{
CALL_ATTRIB_HOOK_INT( iHalloweenFootstepType, halloween_footstep_type );
}
if ( m_nFootStamps > 0 )
{
// White stamps!
iHalloweenFootstepType = 0xFFFFFFFF;
}
if ( iHalloweenFootstepType != 0 )
{
SpawnHalloweenSpellFootsteps( PATTACH_ABSORIGIN, iHalloweenFootstepType );
}
if ( m_nFootStamps > 0 )
{
m_nFootStamps--;
}
}
}
else if ( event == AE_WPN_HIDE )
{
if ( GetActiveWeapon() )
{
int iDisableWeaponHidingForAnimations = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetActiveWeapon(), iDisableWeaponHidingForAnimations, disable_weapon_hiding_for_animations );
if ( iDisableWeaponHidingForAnimations == 0 )
{
GetActiveWeapon()->SetWeaponVisible( false );
}
}
}
else if ( event == AE_WPN_UNHIDE )
{
if ( m_Shared.IsControlStunned() )
return;
if ( GetActiveWeapon() )
{
GetActiveWeapon()->SetWeaponVisible( true );
}
}
else if ( event == AE_WPN_PLAYWPNSOUND )
{
if ( GetActiveWeapon() )
{
int iSnd = GetWeaponSoundFromString(options);
if ( iSnd != -1 )
{
GetActiveWeapon()->WeaponSound( (WeaponSound_t)iSnd );
}
}
}
else if ( event == TF_AE_CIGARETTE_THROW )
{
CEffectData data;
int iAttach = LookupAttachment( options );
GetAttachment( iAttach, data.m_vOrigin, data.m_vAngles );
data.m_vAngles = GetRenderAngles();
data.m_hEntity = ClientEntityList().EntIndexToHandle( entindex() );
DispatchEffect( "TF_ThrowCigarette", data );
return;
}
else if ( event == AE_CL_BODYGROUP_SET_VALUE_CMODEL_WPN )
{
CTFWeaponBase *pWpn = GetActiveTFWeapon();
if ( pWpn )
{
pWpn->GetAppropriateWorldOrViewModel()->FireEvent( origin, angles, AE_CL_BODYGROUP_SET_VALUE, options );
}
}
else if ( event == AE_TAUNT_ENABLE_MOVE )
{
m_bAllowMoveDuringTaunt = true;
}
else if ( event == AE_TAUNT_DISABLE_MOVE )
{
m_bAllowMoveDuringTaunt = false;
}
else
BaseClass::FireEvent( origin, angles, event, options );
}
void C_TFPlayer::UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity )
{
// don't play footstep sound while in kart
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
return;
}
// don't play footstep sound while taunting
if ( IsTaunting() )
{
return;
}
BaseClass::UpdateStepSound( psurface, vecOrigin, vecVelocity );
}
CNewParticleEffect *C_TFPlayer::SpawnHalloweenSpellFootsteps( ParticleAttachment_t eParticleAttachment, int iHalloweenFootstepType )
{
enum EPileOfHalloweenConstantHacks
{
kHalloweenSpell_RGBConstant_HHH = 2,
kHalloweenSpell_RGBConstant_TeamColor = 1,
kHalloweenSpell_RGB_Red = 12073019,
kHalloweenSpell_RGB_Blue = 5801378,
};
if ( iHalloweenFootstepType == kHalloweenSpell_RGBConstant_HHH )
return ParticleProp()->Create( "halloween_boss_foot_impact", eParticleAttachment, 0 );
CNewParticleEffect *pEffect = ParticleProp()->Create( m_nFootStamps ? "foot_stamp" : "halloween_boss_foot_impact_customcolor", eParticleAttachment, 0 );
if ( pEffect )
{
const int iRGB = iHalloweenFootstepType != kHalloweenSpell_RGBConstant_TeamColor // special "use team-color" hack value
? iHalloweenFootstepType // use the attribute value as the RGB
: GetTeamNumber() == TF_TEAM_BLUE // which team are we on?
? kHalloweenSpell_RGB_Blue
: kHalloweenSpell_RGB_Red;
pEffect->SetControlPoint( 1, Vector( ((iRGB & 0xff0000) >> 16) / 255.0f, ((iRGB & 0xff00) >> 8) / 255.0f, (iRGB & 0xff) / 255.0f ) );
}
return pEffect;
}
// Shadows
ConVar cl_blobbyshadows( "cl_blobbyshadows", "0", FCVAR_CLIENTDLL );
ShadowType_t C_TFPlayer::ShadowCastType( void )
{
// Removed the GetPercentInvisible - should be taken care off in BindProxy now.
if ( !IsVisible() /*|| GetPercentInvisible() > 0.0f*/ )
return SHADOWS_NONE;
if ( IsEffectActive(EF_NODRAW | EF_NOSHADOW) )
return SHADOWS_NONE;
// If in ragdoll mode.
if ( m_nRenderFX == kRenderFxRagdoll )
return SHADOWS_NONE;
if ( !ShouldDrawThisPlayer() )
{
// First-person with viewmodels.
return SHADOWS_NONE;
}
if ( cl_blobbyshadows.GetBool() )
return SHADOWS_SIMPLE;
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
}
float g_flFattenAmt = 24.0; // Roughly how far out the Heavy's minigun pokes out.
void C_TFPlayer::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType )
{
if ( shadowType == SHADOWS_SIMPLE )
{
// Don't let the render bounds change when we're using blobby shadows, or else the shadow
// will pop and stretch.
mins = CollisionProp()->OBBMins();
maxs = CollisionProp()->OBBMaxs();
}
else
{
GetRenderBounds( mins, maxs );
// We do this because the normal bbox calculations don't take pose params into account, and
// the rotation of the guy's upper torso can place his gun a ways out of his bbox, and
// the shadow will get cut off as he rotates.
//
// Thus, we give it some padding here.
g_flFattenAmt = 36.0f;
mins -= Vector( g_flFattenAmt, g_flFattenAmt, 0 );
maxs += Vector( g_flFattenAmt, g_flFattenAmt, 0 );
}
}
void C_TFPlayer::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
// TODO POSTSHIP - this hack/fix goes hand-in-hand with a fix in CalcSequenceBoundingBoxes in utils/studiomdl/simplify.cpp.
// When we enable the fix in CalcSequenceBoundingBoxes, we can get rid of this.
//
// What we're doing right here is making sure it only uses the bbox for our lower-body sequences since,
// with the current animations and the bug in CalcSequenceBoundingBoxes, are WAY bigger than they need to be.
C_BaseAnimating::GetRenderBounds( theMins, theMaxs );
}
bool C_TFPlayer::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const
{
if ( shadowType == SHADOWS_SIMPLE )
{
// Blobby shadows should sit directly underneath us.
pDirection->Init( 0, 0, -1 );
return true;
}
else
{
return BaseClass::GetShadowCastDirection( pDirection, shadowType );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether this player is the nemesis of the local player
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsNemesisOfLocalPlayer()
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
// return whether this player is dominating the local player
return m_Shared.IsPlayerDominated( pLocalPlayer->entindex() );
}
return false;
}
extern ConVar tf_tournament_hide_domination_icons;
//-----------------------------------------------------------------------------
// Purpose: Returns whether we should show the dueling icon for this player
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldShowDuelingIcon()
{
if ( TFGameRules() && TFGameRules()->IsInTournamentMode() && tf_tournament_hide_domination_icons.GetBool() )
return false;
if ( m_PlayerClass.HasCustomModel() )
return false;
extern bool DuelMiniGame_IsDuelingLocalPlayer( C_TFPlayer *pPlayer );
// we should show the dueling effect on this player if he is dueling the local player,
// and is not dead, cloaked or disguised
if ( DuelMiniGame_IsDuelingLocalPlayer( this ) && g_PR && g_PR->IsConnected( entindex() ) )
{
bool bStealthed = m_Shared.IsStealthed();
bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
if ( IsAlive() && !bStealthed && !bDisguised )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether we should show the nemesis icon for this player
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldShowNemesisIcon()
{
if ( TFGameRules() && TFGameRules()->IsInTournamentMode() && tf_tournament_hide_domination_icons.GetBool() )
return false;
if ( m_PlayerClass.HasCustomModel() )
return false;
// we should show the nemesis effect on this player if he is the nemesis of the local player,
// and is not dead, cloaked or disguised
if ( IsNemesisOfLocalPlayer() && g_PR && g_PR->IsConnected( entindex() ) )
{
bool bStealthed = m_Shared.IsStealthed();
bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
if ( IsAlive() && !bStealthed && !bDisguised )
return true;
}
return false;
}
bool C_TFPlayer::IsWeaponLowered( void )
{
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
if ( !pWeapon )
return false;
CTFGameRules *pRules = TFGameRules();
// Lower losing team's weapons in bonus round
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
return true;
// Hide all view models after the game is over
if ( pRules->State_Get() == GR_STATE_GAME_OVER && ( !pRules->IsInTournamentMode() || pRules->IsMannVsMachineMode() ) )
return true;
if ( m_Shared.InCond( TF_COND_PHASE ) )
return true;
if ( m_Shared.InCond( TF_COND_COMPETITIVE_LOSER ) )
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
{
switch ( event->GetType() )
{
case CChoreoEvent::SEQUENCE:
case CChoreoEvent::GESTURE:
return StartGestureSceneEvent( info, scene, event, actor, pTarget );
default:
return BaseClass::StartSceneEvent( info, scene, event, actor, pTarget );
}
}
bool C_TFPlayer::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled )
{
switch ( info->m_pEvent->GetType() )
{
case CChoreoEvent::SEQUENCE:
case CChoreoEvent::GESTURE:
return StopGestureSceneEvent( info, fastKill, canceled );
default:
return BaseClass::ClearSceneEvent( info, fastKill, canceled );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::StartGestureSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
{
// Get the (gesture) sequence.
info->m_nSequence = LookupSequence( event->GetParameters() );
if ( info->m_nSequence < 0 )
return false;
// Player the (gesture) sequence.
float flCycle = 0.0f;
bool looping = ((GetSequenceFlags( GetModelPtr(), info->m_nSequence ) & STUDIO_LOOPING) != 0);
if (!looping)
{
float dt = scene->GetTime() - event->GetStartTime();
m_flTauntDuration = SequenceDuration( info->m_nSequence );
flCycle = dt / m_flTauntDuration;
flCycle = clamp( flCycle, 0.f, 1.0f );
}
else
{
float flStart, flEnd;
scene->GetSceneTimes( flStart, flEnd );
m_flTauntDuration = flEnd - flStart;
}
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
m_PlayerAnimState->AddVCDSequenceToGestureSlot( GESTURE_SLOT_VCD, info->m_nSequence, flCycle, true );
m_nTauntSequence = info->m_nSequence;
m_flTauntStartTime = gpGlobals->curtime;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::StopGestureSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled )
{
// The ResetGestureSlot call will prevent people from doing running taunts (which they like to do),
// so let's only reset the gesture slot if the scene contains a loop (such as the high five pose).
bool bSceneContainsLoop = false;
for ( int i = 0; i < info->m_pScene->GetNumEvents(); i++ )
{
CChoreoEvent *pEvent = info->m_pScene->GetEvent( i );
if ( pEvent->GetType() == CChoreoEvent::LOOP )
{
bSceneContainsLoop = true;
break;
}
}
if( bSceneContainsLoop )
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
return true;
}
bool C_TFPlayer::IsAllowedToSwitchWeapons( void )
{
if ( IsWeaponLowered() == true )
return false;
if ( TFGameRules() )
{
if ( TFGameRules()->IsPasstimeMode() && m_Shared.HasPasstimeBall() )
return false;
if ( TFGameRules()->ShowMatchSummary() )
return false;
}
// Can't weapon switch during a taunt.
if( m_Shared.InCond( TF_COND_TAUNTING ) && tf_allow_taunt_switch.GetInt() <= 1 )
return false;
return BaseClass::IsAllowedToSwitchWeapons();
}
IMaterial *C_TFPlayer::GetHeadLabelMaterial( void )
{
if ( g_pHeadLabelMaterial[0] == NULL )
SetupHeadLabelMaterials();
if ( GetTeamNumber() == TF_TEAM_RED )
{
return g_pHeadLabelMaterial[TF_PLAYER_HEAD_LABEL_RED];
}
else
{
return g_pHeadLabelMaterial[TF_PLAYER_HEAD_LABEL_BLUE];
}
return BaseClass::GetHeadLabelMaterial();
}
void SetupHeadLabelMaterials( void )
{
for ( int i = 0; i < 2; i++ )
{
if ( g_pHeadLabelMaterial[i] )
{
g_pHeadLabelMaterial[i]->DecrementReferenceCount();
g_pHeadLabelMaterial[i] = NULL;
}
g_pHeadLabelMaterial[i] = materials->FindMaterial( pszHeadLabelNames[i], TEXTURE_GROUP_VGUI );
if ( g_pHeadLabelMaterial[i] )
{
g_pHeadLabelMaterial[i]->IncrementReferenceCount();
}
}
}
void C_TFPlayer::ComputeFxBlend( void )
{
BaseClass::ComputeFxBlend();
float flInvisible = GetPercentInvisible();
if ( flInvisible != 0.0f )
{
// Tell our shadow
ClientShadowHandle_t hShadow = GetShadowHandle();
if ( hShadow != CLIENTSHADOW_INVALID_HANDLE )
{
g_pClientShadowMgr->SetFalloffBias( hShadow, flInvisible * 255 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNear, float &zFar, float &fov )
{
HandleTaunting();
BaseClass::CalcView( eyeOrigin, eyeAngles, zNear, zFar, fov );
}
void SelectDisguise( int iClass, int iTeam );
static void cc_tf_player_lastdisguise( const CCommand &args )
{
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pPlayer == NULL )
return;
// disguise as our last known disguise. desired disguise will be initted to something sensible
if ( pPlayer->CanDisguise() || pPlayer->CanDisguise_OnKill() )
{
// disguise as the previous class, if one exists
int nClass = pPlayer->m_Shared.GetDesiredDisguiseClass();
int iLocalTeam = pPlayer->GetTeamNumber();
int iEnemyTeam = ( iLocalTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
int nTeam = pPlayer->m_Shared.WasLastDisguiseAsOwnTeam() ? iLocalTeam : iEnemyTeam;
//If we pass in "random" or whatever then just make it pick a random class.
if ( args.ArgC() > 1 )
{
nClass = TF_CLASS_UNDEFINED;
}
if ( nClass == TF_CLASS_UNDEFINED )
{
// they haven't disguised yet, pick a nice one for them.
// exclude some undesirable classes
do
{
nClass = random->RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS );
} while( nClass == TF_CLASS_SCOUT || nClass == TF_CLASS_SPY );
nTeam = iEnemyTeam;
}
SelectDisguise( nClass, nTeam );
}
}
static ConCommand lastdisguise( "lastdisguise", cc_tf_player_lastdisguise, "Disguises the spy as the last disguise." );
static void cc_tf_player_disguise( const CCommand &args )
{
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pPlayer == NULL )
return;
if ( args.ArgC() >= 3 )
{
if ( pPlayer->CanDisguise() || pPlayer->CanDisguise_OnKill() )
{
int nClass = atoi( args[ 1 ] );
int nTeam = atoi( args[ 2 ] );
//Disguise as enemy team
if ( nTeam == -1 )
{
if ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
{
nTeam = TF_TEAM_RED;
}
else
{
nTeam = TF_TEAM_BLUE;
}
}
else if ( nTeam == -2 ) //disguise as my own team
{
nTeam = pPlayer->GetTeamNumber();
}
else
{
nTeam = ( nTeam == 1 ) ? TF_TEAM_BLUE : TF_TEAM_RED;
}
// intercepting the team value and reassigning what gets passed into Disguise()
// because the team numbers in the client menu don't match the #define values for the teams
SelectDisguise( nClass, nTeam );
}
}
}
static ConCommand disguise( "disguise", cc_tf_player_disguise, "Disguises the spy." );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void cc_tf_crashclient()
{
C_TFPlayer *pPlayer = NULL;
pPlayer->ComputeFxBlend();
}
static ConCommand tf_crashclient( "tf_crashclient", cc_tf_crashclient, "Crashes this client for testing.", FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ForceUpdateObjectHudState( void )
{
m_bUpdateObjectHudState = true;
}
#include "c_obj_sentrygun.h"
static void cc_tf_debugsentrydmg()
{
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
pPlayer->UpdateIDTarget();
int iTarget = pPlayer->GetIDTarget();
if ( iTarget > 0 )
{
C_BaseEntity *pEnt = cl_entitylist->GetEnt( iTarget );
C_ObjectSentrygun *pSentry = dynamic_cast< C_ObjectSentrygun * >( pEnt );
if ( pSentry )
{
pSentry->DebugDamageParticles();
}
}
}
static ConCommand tf_debugsentrydamage( "tf_debugsentrydamage", cc_tf_debugsentrydmg, "", FCVAR_DEVELOPMENTONLY );
/*
CON_COMMAND_F( spectate_random_server_extend_time, "extend the timer we're spectating this server before we disconnect", FCVAR_DEVELOPMENTONLY )
{
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pPlayer )
{
if ( pPlayer->m_LeaveServerTimer.HasStarted() )
{
float flTime = spectate_random_server_basetime.GetFloat();
if ( args.ArgC() > 1 )
{
flTime = MAX( 0, Q_atof( args[ 1 ] ) );
}
pPlayer->m_LeaveServerTimer.Start( flTime );
}
}
}*/
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::GetTargetIDDataString( bool bIsDisguised, OUT_Z_BYTECAP(iMaxLenInBytes) wchar_t *sDataString, int iMaxLenInBytes, bool &bIsAmmoData, bool &bIsKillStreakData )
{
Assert( iMaxLenInBytes >= sizeof(sDataString[0]) );
// Make sure the output string is always initialized to a null-terminated string,
// since the conditions below are tricky.
sDataString[0] = 0;
if ( bIsDisguised )
{
if ( !IsEnemyPlayer() )
{
// The target is a disguised friendly spy. They appear to the player with no disguise. Add the disguise
// team & class to the target ID element.
bool bDisguisedAsEnemy = ( m_Shared.GetDisguiseTeam() != GetTeamNumber() );
const wchar_t *wszAlignment = g_pVGuiLocalize->Find( bDisguisedAsEnemy ? "#TF_enemy" : "#TF_friendly" );
int classindex = m_Shared.GetDisguiseClass();
const wchar_t *wszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[classindex] );
// build a string with disguise information
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_friendlyspy_disguise" ),
2, wszAlignment, wszClassName );
}
else if ( IsEnemyPlayer() && (m_Shared.GetDisguiseClass() == TF_CLASS_SPY) )
{
// The target is an enemy spy disguised as a friendly spy. Show a fake team & class ID element.
int classindex = m_Shared.GetDisguiseMask();
const wchar_t *wszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[classindex] );
const wchar_t *wszAlignment = g_pVGuiLocalize->Find( "#TF_enemy" );
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_friendlyspy_disguise" ),
2, wszAlignment, wszClassName );
}
}
if ( IsPlayerClass( TF_CLASS_MEDIC ) )
{
CTFWeaponBase *pMedigun = NULL;
// Medics put their ubercharge & medigun type into the data string
wchar_t wszChargeLevel[ 10 ];
_snwprintf( wszChargeLevel, ARRAYSIZE(wszChargeLevel) - 1, L"%.0f", MedicGetChargeLevel( &pMedigun ) * 100 );
wszChargeLevel[ ARRAYSIZE(wszChargeLevel)-1 ] = '\0';
if ( pMedigun && pMedigun->GetAttributeContainer()->GetItem() && pMedigun->GetAttributeContainer()->GetItem()->GetItemQuality() != AE_NORMAL )
{
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_mediccharge_wpn" ), 2, wszChargeLevel, pMedigun->GetAttributeContainer()->GetItem()->GetItemName() );
}
else
{
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_mediccharge" ), 1, wszChargeLevel );
}
}
else if ( bIsDisguised && (m_Shared.GetDisguiseClass() == TF_CLASS_MEDIC) && IsEnemyPlayer() )
{
// Show a fake charge level for a disguised enemy medic.
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_mediccharge" ), 1, L"0" );
}
else
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
{
CTFWeaponBase *pTFWeapon = GetActiveTFWeapon();
if ( pTFWeapon )
{
// Check for weapon_blocks_healing
int iBlockHealing = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iBlockHealing, weapon_blocks_healing );
if ( iBlockHealing )
{
if ( pTFWeapon->GetAttributeContainer() && pTFWeapon->GetAttributeContainer()->GetItem() )
{
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_noheal" ), 1, pTFWeapon->GetAttributeContainer()->GetItem()->GetItemName() );
}
else
{
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_noheal_unknown" ), 0 );
}
}
// Show target's clip state to attached medics
if ( !sDataString[0] && m_nActiveWpnClip >= 0 )
{
C_TFPlayer *pTFHealTarget = ToTFPlayer( pLocalPlayer->MedicGetHealTarget() );
if ( pTFHealTarget && pTFHealTarget == this )
{
wchar_t wszClip[10];
V_snwprintf( wszClip, ARRAYSIZE(wszClip) - 1, L"%d", m_nActiveWpnClip );
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_ammo" ), 1, wszClip );
bIsAmmoData = true;
}
}
}
}
if ( !bIsAmmoData )
{
// Check for kill streak data
if ( m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) > 0 )
{
bIsKillStreakData = true;
wchar_t wszClip[10];
V_snwprintf( wszClip, ARRAYSIZE(wszClip) - 1, L"%d", m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_ammo" ), 1, wszClip );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ThirdPersonSwitch( bool bThirdperson )
{
BaseClass::ThirdPersonSwitch( bThirdperson );
// Update our viewmodel whenever we switch view modes
C_TFPlayer *pTFObserverTarget = ToTFPlayer( GetObserverTarget() );
if ( pTFObserverTarget )
{
C_TFWeaponBase *pWeapon = pTFObserverTarget->m_Shared.GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->UpdateAttachmentModels();
}
pTFObserverTarget->UpdateWearables();
pTFObserverTarget->SetBodygroupsDirty();
}
// Update our weapon's visibility when we switch
C_BaseCombatWeapon* pWeapon = GetActiveWeapon();
if ( pWeapon )
{
pWeapon->UpdateVisibility();
}
// Update visibility of any worn items.
UpdateWearables();
SetBodygroupsDirty();
}
//-----------------------------------------------------------------------------
// Purpose: Update our active weapon's extra wearable's visibility and shadows
// as well as all our wearables.
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateWearables()
{
BaseClass::UpdateWearables();
CTFWeaponBase* pWeapon = dynamic_cast< CTFWeaponBase* >( GetActiveWeapon() );
if ( pWeapon )
{
pWeapon->UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnAchievementAchieved( int iAchievement )
{
EmitSound( "Achievement.Earned" );
BaseClass::OnAchievementAchieved( iAchievement );
}
//-----------------------------------------------------------------------------
// Purpose: Feign Death
//-----------------------------------------------------------------------------
void C_TFPlayer::FeignDeath( 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.IsStealthed() )
return;
// Can't feign death if we aren't at full cloak energy.
if ( !CanGoInvisible() || ( m_Shared.GetSpyCloakMeter() < 100.0f ) )
return;
// Predict feign death condition effects.
m_Shared.AddCond( TF_COND_FEIGN_DEATH );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateSpyStateChange( void )
{
UpdateOverhealEffect();
UpdateRecentlyTeleportedEffect();
UpdatedMarkedForDeathEffect();
// Update our resist shield color
if ( m_pTempShield && m_Shared.GetCarryingRuneType() == RUNE_RESIST )
{
m_pTempShield->m_nSkin = ( m_Shared.GetDisplayedTeam() == TF_TEAM_RED ) ? 0 : 1;
}
UpdateRuneIcon( true );
// Remove Speed lines if Stealthed
if ( m_Shared.IsStealthed() )
{
if ( m_pSpeedBoostEffect )
{
ParticleProp()->StopEmission( m_pSpeedBoostEffect );
m_pSpeedBoostEffect = NULL;
}
m_Shared.EndRadiusHealEffect();
}
// Force Weapon updates
if ( GetActiveWeapon() )
{
GetActiveWeapon()->PreDataUpdate( DATA_UPDATE_DATATABLE_CHANGED );
}
// TranqMark
// test : Let Marked spies be seen via the debuff
//bool bShow = ( m_Shared.InCond( TF_COND_TRANQ_MARKED )
// && !m_Shared.IsStealthed();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateOverhealEffect( void )
{
bool bShow = m_Shared.InCond( TF_COND_HEALTH_OVERHEALED );
int iTeam = GetTeamNumber();
if ( IsLocalPlayer() || ( m_Shared.IsStealthed() && !InSameTeam( GetLocalTFPlayer() ) ) )
{
bShow = false;
}
else if ( IsPlayerClass( TF_CLASS_SPY ) && !InSameTeam( GetLocalTFPlayer() ) && m_Shared.InCond( TF_COND_DISGUISED ))
{
iTeam = m_Shared.GetDisguiseTeam();
}
if ( bShow )
{
if ( !m_pOverHealedEffect )
{
CreateOverhealEffect( iTeam );
}
else if ( m_pOverHealedEffect )
{
ParticleProp()->StopEmission( m_pOverHealedEffect );
m_pOverHealedEffect = NULL;
CreateOverhealEffect( iTeam );
}
}
else
{
if ( m_pOverHealedEffect )
{
ParticleProp()->StopEmission( m_pOverHealedEffect );
m_pOverHealedEffect = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateOverhealEffect( int iTeam )
{
const char *pEffect = NULL;
switch( iTeam )
{
case TF_TEAM_BLUE:
pEffect = "overhealedplayer_blue_pluses";
break;
case TF_TEAM_RED:
pEffect = "overhealedplayer_red_pluses";
break;
default:
break;
}
if ( pEffect )
{
m_pOverHealedEffect = ParticleProp()->Create( pEffect, PATTACH_ABSORIGIN_FOLLOW );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetMetersRan( float fMeters, int iFrame )
{
if ( iFrame != m_iLastRanFrame )
{
m_iLastRanFrame = iFrame;
m_fMetersRan = fMeters;
}
}
bool C_TFPlayer::InSameDisguisedTeam( CBaseEntity *pEnt )
{
if ( pEnt == NULL )
return false;
int iMyApparentTeam = GetTeamNumber();
if ( m_bIsCoaching && m_hStudent )
{
iMyApparentTeam = m_hStudent->GetTeamNumber();
}
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
iMyApparentTeam = m_Shared.GetDisguiseTeam();
}
C_TFPlayer *pPlayerEnt = ToTFPlayer( pEnt );
int iTheirApparentTeam = pEnt->GetTeamNumber();
if ( pPlayerEnt && pPlayerEnt->m_Shared.InCond( TF_COND_DISGUISED ) )
{
iTheirApparentTeam = pPlayerEnt->m_Shared.GetDisguiseTeam();
}
return ( iMyApparentTeam == iTheirApparentTeam || GetTeamNumber() == pEnt->GetTeamNumber() || iTheirApparentTeam == GetTeamNumber() );
}
//-----------------------------------------------------------------------------
// Purpose: When a player's ragdoll is created we want the ragdoll to
// appear to have any attached wearable items the player had.
// This uses client side entities instead of the actual wearable items.
//-----------------------------------------------------------------------------
static bool IsDecapitationCustomDamageType( int iCustomDamageType )
{
return iCustomDamageType == TF_DMG_CUSTOM_DECAPITATION
|| iCustomDamageType == TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING
|| iCustomDamageType == TF_DMG_CUSTOM_DECAPITATION_BOSS
|| iCustomDamageType == TF_DMG_CUSTOM_MERASMUS_DECAPITATION;
}
void C_TFPlayer::CreateBoneAttachmentsFromWearables( C_TFRagdoll *pRagdoll, bool bDisguised )
{
if ( bDisguised && !ShouldDrawSpyAsDisguised() )
{
// the team of disguised spy don't see any wearable
return;
}
for ( int wbl = GetNumWearables()-1; wbl >= 0; wbl-- )
{
C_TFWearable *pItem = dynamic_cast<C_TFWearable*> (GetWearable(wbl));
if ( !pItem )
continue;
if ( pItem->IsViewModelWearable() )
continue;
if ( pItem->IsDisguiseWearable() && !bDisguised )
continue;
if ( !pItem->IsDisguiseWearable() && bDisguised )
continue;
pItem->OnWearerDeath();
if ( pItem->GetDropType() >= ITEM_DROP_TYPE_DROP )
continue;
CAttributeContainer *pCont = pItem->GetAttributeContainer();
CEconItemView *pEconItemView = pCont ? pCont->GetItem() : NULL;
// If this is a decapitated ragdoll, don't attach anything in our head/misc item slots.
if ( IsDecapitationCustomDamageType( pRagdoll->GetDamageCustom() ) )
{
int iLoadoutSlot = pEconItemView
? pEconItemView->GetStaticData()->GetDefaultLoadoutSlot()
: LOADOUT_POSITION_INVALID;
if ( iLoadoutSlot == LOADOUT_POSITION_HEAD || iLoadoutSlot == LOADOUT_POSITION_MISC )
continue;
}
C_EconWearableGib *pAttachment = new C_EconWearableGib;
if ( !pAttachment )
return;
const char *pszModelName = modelinfo->GetModelName( pItem->GetModel() );
if ( !pszModelName || pszModelName[ 0 ] == '\0' || pszModelName[ 0 ] == '?' )
continue;
// We need to set the item now, so that it can pull data out during Initialize();
if ( pEconItemView )
{
Assert( pAttachment->GetAttributeContainer() );
pAttachment->GetAttributeContainer()->SetItem( pEconItemView );
}
pAttachment->SetModelName( AllocPooledString( pszModelName ) );
if ( !pAttachment->Initialize( true ) )
{
pAttachment->Release();
continue;
}
pAttachment->m_iTeamNum = pRagdoll->GetRagdollTeam();
pAttachment->m_nSkin = pItem->GetSkin();
pAttachment->AttachEntityToBone( this, -1, Vector(0,0,0), QAngle(0,0,0) );
if ( pEconItemView )
{
if ( pEconItemView->GetStaticData()->UsesPerClassBodygroups( GetTeamNumber() ) )
{
// Classes start at 1, bodygroups at 0, so we shift them all back 1.
pAttachment->SetBodygroup( 1, (pRagdoll->GetClass()-1) );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Loop through all non-standard items carried by this player, and pick the next one.
// pLastItem - pointer to the int that stores the last item found, for iteration purposes.
//-----------------------------------------------------------------------------
CEconItemView *C_TFPlayer::GetInspectItem( int *pLastItem )
{
int iItemsFound = 0;
CEconItemView *pFirstItem = NULL;
int nCount = WeaponCount();
for ( int i = 0; i < nCount; ++i )
{
C_BaseCombatWeapon *pWeapon = GetWeapon(i);
if ( !pWeapon )
continue;
CEconItemView *pTmp = pWeapon->GetAttributeContainer()->GetItem();
if ( !pTmp->IsValid() )
continue;
// don't show hidden items in the inspect panel
if ( pTmp->GetItemDefinition() && pTmp->GetItemDefinition()->IsHidden() )
continue;
if ( !pFirstItem )
{
pFirstItem = pTmp;
}
iItemsFound++;
if ( iItemsFound <= *pLastItem )
continue;
// Found the next item, we're done.
*pLastItem = iItemsFound;
return pTmp;
}
// Check wearables too
nCount = GetNumWearables();
for ( int i = 0; i < nCount; ++i )
{
C_EconWearable *pWearable = GetWearable(i);
if ( pWearable )
{
CEconItemView *pTmp = pWearable->GetAttributeContainer()->GetItem();
if ( !pTmp->IsValid() )
continue;
if ( !pFirstItem )
{
pFirstItem = pTmp;
}
iItemsFound++;
if ( iItemsFound <= *pLastItem )
continue;
// Found the next item, we're done.
*pLastItem = iItemsFound;
return pTmp;
}
}
// If we didn't find an item, go back to the first one
if ( pFirstItem )
{
*pLastItem = 1;
return pFirstItem;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::CanUseFirstPersonCommand( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
if ( pLocalPlayer->m_Shared.InCond( TF_COND_PHASE ) ||
pLocalPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ||
pLocalPlayer->m_Shared.IsControlStunned() ||
pLocalPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
return false;
}
}
return BaseClass::CanUseFirstPersonCommand();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateDemomanEyeEffect( int iDecapitations )
{
if ( m_pEyeEffect )
{
ParticleProp()->StopEmission( m_pEyeEffect );
m_pEyeEffect = NULL;
}
if ( iDecapitations == 0 )
return;
iDecapitations = MIN( iDecapitations, MAX_DECAPITATIONS );
const char* pszEffect = GetDemomanEyeEffectName( iDecapitations );
if ( pszEffect )
{
m_pEyeEffect = ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, "eyeglow_L" );
}
}
//-----------------------------------------------------------------------------
const char *C_TFPlayer::GetDemomanEyeEffectName( int iDecapitations )
{
if ( iDecapitations < 1 )
return NULL;
switch ( iDecapitations )
{
case 1:
return "eye_powerup_green_lvl_1";
case 2:
return "eye_powerup_green_lvl_2";
case 3:
return "eye_powerup_green_lvl_3";
default:
return "eye_powerup_green_lvl_4";
}
}
//-----------------------------------------------------------------------------
void GetVectorColorForParticleSystem ( int iSystem, bool bIsBlueTeam, bool bUseColor2, Vector &vecColor )
{
if ( iSystem < 0 || iSystem >= ARRAYSIZE( g_KillStreakEffectsBase ) )
return;
killstreak_params_t params = g_KillStreakEffectsBase[iSystem];
if ( bIsBlueTeam && g_KillStreakEffectsBase[iSystem].m_bHasTeamColor )
{
Assert( iSystem > 0 && iSystem < ARRAYSIZE( g_KillStreakEffectsBlue ) );
params = g_KillStreakEffectsBlue[iSystem];
}
if ( bUseColor2 )
{
vecColor = Vector( params.m_color2_r, params.m_color2_g, params.m_color2_b );
}
else
{
vecColor = Vector( params.m_color1_r, params.m_color1_g, params.m_color1_b );
}
return;
}
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateKillStreakEffects( int iCount, bool bKillScored /* = false */ )
{
#ifdef STAGING_ONLY
const int LOW_GLOW = 2001;
#endif
const int HIGH_GLOW = 20000;
// Staging only hack to test eye glows on players
if ( m_pEyeGlowEffect[0] )
{
ParticleProp()->StopEmission( m_pEyeGlowEffect[ 0 ] );
m_pEyeGlowEffect[ 0 ] = NULL;
}
if ( m_pEyeGlowEffect[1] )
{
ParticleProp()->StopEmission( m_pEyeGlowEffect[ 1 ] );
m_pEyeGlowEffect[ 1 ] = NULL;
}
CTFWeaponBase *pActiveWeapon = GetActiveTFWeapon();
if ( !pActiveWeapon )
return;
// Disguised Spies, Use targets kill streak. Otherwise nothing
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
C_TFPlayer *pDisguiseTarget = ToTFPlayer( m_Shared.GetDisguiseTarget() );
if ( pDisguiseTarget )
iCount = pDisguiseTarget->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills );
else
iCount = 0;
}
#ifdef STAGING_ONLY
// !TEST!
if ( tf_test_setkillcount.GetInt() > 0 )
{
iCount = tf_test_setkillcount.GetInt();
}
#endif
// Check if they have the appropriate attribute.
int iKillStreakEffectIndex = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iKillStreakEffectIndex, killstreak_effect );
int iKillStreakColorIndex = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iKillStreakColorIndex, killstreak_idleeffect );
// !TEST!
#ifdef STAGING_ONLY
if ( tf_killstreak_eyeglow.GetInt() != 0 )
{
iKillStreakEffectIndex = tf_killstreak_eyeglow.GetInt();
}
if ( tf_killstreak_color.GetInt() != 0 )
{
iKillStreakColorIndex = tf_killstreak_color.GetInt();
}
#endif // staging_only
// Need a color and eyeeffect to continue, otherwise you only have a sheen at best
if ( iKillStreakColorIndex == 0 || iKillStreakEffectIndex == 0 )
{
// Just in case search weapon wearables for killstreak effects
// This only applies for soldier (mantreads) and the demoshields
if ( IsPlayerClass( TF_CLASS_SOLDIER ) || IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
// Iterate over all of our wearables
for ( int i = 0; i < GetNumWearables(); ++i )
{
CEconWearable *pWearable = GetWearable( i );
if ( pWearable && pWearable->GetAttributeContainer( )->GetItem( )->GetEquippedPositionForClass( GetPlayerClass( )->GetClassIndex( ) ) == LOADOUT_POSITION_SECONDARY )
{
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearable, iKillStreakEffectIndex, killstreak_effect );
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearable, iKillStreakColorIndex, killstreak_idleeffect );
break;
}
}
}
}
if ( iKillStreakColorIndex == 0 || iKillStreakEffectIndex == 0 )
{
m_pszEyeGlowEffectName[0] = '\0';
m_vEyeGlowColor1.Zero();
return;
}
// Play the pop effect on all kills
GetVectorColorForParticleSystem( iKillStreakColorIndex, GetTeamNumber() == TF_TEAM_BLUE, false, m_vEyeGlowColor1 );
GetVectorColorForParticleSystem( iKillStreakColorIndex, GetTeamNumber() == TF_TEAM_BLUE, true, m_vEyeGlowColor2 );
// Do not render in first person
if ( !pActiveWeapon->IsFirstPersonView() && bKillScored )
{
int iAttachment = 0;
iAttachment = LookupAttachment( "eyeglow_R" );
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
{
CNewParticleEffect* pEffect = ParticleProp()->Create( "killstreak_t0_lvl1_flash", PATTACH_POINT_FOLLOW, iAttachment );
if ( pEffect )
{
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
}
}
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_DEMOMAN )
{
iAttachment = LookupAttachment( "eyeglow_L" );
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
{
CNewParticleEffect* pEffect = ParticleProp()->Create( "killstreak_t0_lvl1_flash", PATTACH_POINT_FOLLOW, iAttachment );
if ( pEffect )
{
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
}
}
}
}
// only render eye glows if they have enough kills
if ( iCount < tf_killstreakeyes_minkills.GetInt() || m_Shared.InCond( TF_COND_STEALTHED ) )
{
m_pszEyeGlowEffectName[0] = '\0';
return;
}
bool bForceEyeGlow = false;
#ifdef STAGING_ONLY
bForceEyeGlow = tf_eyeglow_wip.GetBool();
#endif
int iEyeGlowEffectIndex = iKillStreakEffectIndex;
if ( ( iEyeGlowEffectIndex > 0 && iCount > 0 ) || bForceEyeGlow )
{
// 2,4,6,8 for effects
const char* pszEffect = NULL;
// Verify the system is in the desired range
#ifdef STAGING_ONLY
// Legacy support for when staging had test eye glow indexes
if ( iEyeGlowEffectIndex < LOW_GLOW )
{
iEyeGlowEffectIndex = LOW_GLOW;
}
#endif // STAGING_ONLY
// This is the wrong type, value is too large should not exist so just bail
if ( iEyeGlowEffectIndex > HIGH_GLOW )
return;
// Hack do not have eyeglows for tier0
if ( iEyeGlowEffectIndex == 2001 )
return;
// Should we be using a High_glow
if ( iCount >= tf_killstreakeyes_maxkills.GetInt() )
{
iEyeGlowEffectIndex += HIGH_GLOW;
}
attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iEyeGlowEffectIndex );
if ( pSystem )
{
// Check for TeamColor EyeGlows
if ( GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pSystem->pszSystemName, "_teamcolor_red" ))
{
static char pBlue[256];
V_StrSubst( pSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
pSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
}
else if ( GetTeamNumber() == TF_TEAM_RED && V_stristr( pSystem->pszSystemName, "_teamcolor_blue" ))
{
// Guard against accidentally giving out the blue team color (support tool)
static char pRed[256];
V_StrSubst( pSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
pSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pRed );
}
}
if ( pSystem )
{
pszEffect = pSystem->pszSystemName;
}
#ifdef STAGING_ONLY
if ( tf_eyeglow_wip.GetBool() )
{
pszEffect = "killstreak_wip";
}
#endif // STAGING_ONLY
if ( pszEffect )
{
// Do not render in first person
if ( !pActiveWeapon->IsFirstPersonView() )
{
int iAttachment = 0;
iAttachment = LookupAttachment( "eyeglow_R" );
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
{
CNewParticleEffect* pEffect = ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, iAttachment );
if ( pEffect )
{
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
}
m_pEyeGlowEffect[0] = pEffect;
}
// do not put glow on left eye, that is reserved for eyelander
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_DEMOMAN )
{
iAttachment = LookupAttachment( "eyeglow_L" );
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
{
CNewParticleEffect* pEffect = ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, iAttachment );
if ( pEffect )
{
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
}
m_pEyeGlowEffect[1] = pEffect;
}
}
}
V_strcpy_safe( m_pszEyeGlowEffectName, pszEffect );
}
}
else
{
m_pszEyeGlowEffectName[0] = '\0';
}
}
void C_TFPlayer::UpdateMVMEyeGlowEffect( bool bVisible )
{
if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() || GetTeamNumber() != TF_TEAM_PVE_INVADERS )
{
return;
}
// Remove the eye glows
ParticleProp()->StopParticlesNamed( "bot_eye_glow", true );
m_pMVMEyeGlowEffect[ 0 ] = NULL;
m_pMVMEyeGlowEffect[ 1 ] = NULL;
if ( bVisible )
{
// Set color based on skill
Vector vColor = m_nBotSkill >= 2 ? Vector( 255, 180, 36 ) : Vector( 0, 240, 255 );
// Create the effects
int nAttachement = LookupAttachment( IsMiniBoss() ? "eye_boss_1" : "eye_1" );
if ( nAttachement > 0 )
{
m_pMVMEyeGlowEffect[ 0 ] = ParticleProp()->Create( "bot_eye_glow", PATTACH_POINT_FOLLOW, nAttachement );
if ( m_pMVMEyeGlowEffect[ 0 ] )
{
m_pMVMEyeGlowEffect[ 0 ]->SetControlPoint( 1, vColor );
}
}
nAttachement = LookupAttachment( IsMiniBoss() ? "eye_boss_2" : "eye_2" );
if ( nAttachement > 0 )
{
m_pMVMEyeGlowEffect[ 1 ] = ParticleProp()->Create( "bot_eye_glow", PATTACH_POINT_FOLLOW, nAttachement );
if ( m_pMVMEyeGlowEffect[ 1 ] )
{
m_pMVMEyeGlowEffect[ 1 ]->SetControlPoint( 1, vColor );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Check if local player should see spy as disguised body
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldDrawSpyAsDisguised()
{
if ( C_BasePlayer::GetLocalPlayer() && m_Shared.InCond( TF_COND_DISGUISED ) &&
( GetEnemyTeam( GetTeamNumber() ) == C_BasePlayer::GetLocalPlayer()->GetTeamNumber() ) )
{
if ( m_Shared.GetDisguiseClass() == TF_CLASS_SPY &&
m_Shared.GetDisguiseTeam() == C_BasePlayer::GetLocalPlayer()->GetTeamNumber() )
{
// This enemy is disguised as a friendly spy.
// Show the spy's original bodygroups.
return false;
}
else
{
// This enemy is disguised. Show the disguise body.
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetBody( void )
{
if ( ShouldDrawSpyAsDisguised() )
{
// This enemy is disguised. Show the disguise body.
return m_Shared.GetDisguiseBody();
}
else
{
return BaseClass::GetBody();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const Vector& C_TFPlayer::GetRenderOrigin( void )
{
if ( GetPlayerClass()->HasCustomModel() )
{
m_vecCustomModelOrigin = BaseClass::GetRenderOrigin() + GetPlayerClass()->GetCustomModelOffset();
return m_vecCustomModelOrigin;
}
return BaseClass::GetRenderOrigin();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ConVar tf_taunt_hint_max_distance( "tf_taunt_hint_max_distance", "256", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT );
bool C_TFPlayer::ShouldTauntHintIconBeVisible() const
{
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalTFPlayer || pLocalTFPlayer == this || pLocalTFPlayer->IsTaunting() )
return false;
if ( !IsTaunting() || !IsReadyToTauntWithPartner() )
return false;
if ( m_Shared.InCond( TF_COND_TAUNTING ) && m_Shared.GetTauntIndex() == TAUNT_LONG )
{
return GetAbsOrigin().DistToSqr( pLocalTFPlayer->GetAbsOrigin() ) < Square( tf_taunt_hint_max_distance.GetFloat() );
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsHealthBarVisible( void ) const
{
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS || m_Shared.InCond( TF_COND_REPROGRAMMED ) )
{
float flRegenAmount = 0;
CALL_ATTRIB_HOOK_FLOAT( flRegenAmount, add_health_regen );
if ( (int)flRegenAmount != 0 )
{
return true;
}
}
}
#ifdef STAGING_ONLY
if ( TFGameRules() && TFGameRules()->IsBountyMode() && tf_bountymode_showhealth.GetInt() )
{
return true;
}
#endif // STAGING_ONLY
return IsMiniBoss();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char* C_TFPlayer::GetBossProgressImageName() const
{
if ( m_bUseBossHealthBar )
{
return GetPlayerClass()->GetClassIconName();
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float C_TFPlayer::GetBossStatusProgress() const
{
float flProgress = float( GetHealth() ) / float( GetMaxHealth() );
return flProgress;
}
//-----------------------------------------------------------------------------
// Purpose: Handle karts.
//-----------------------------------------------------------------------------
void C_TFPlayer::NotifyShouldTransmit( ShouldTransmitState_t state )
{
BaseClass::NotifyShouldTransmit( state );
if ( state == SHOULDTRANSMIT_START )
{
if ( m_Shared.WasInCond( TF_COND_HALLOWEEN_KART ) && m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
// Readd the condition for halloween karts.
// This deals with the situation documented here:
// 1) Out of PVS
// 2) In PVS, add cart
// 3) Out of PVS, remove cart
// 4) Returned to PVS, add cart
//
// This situation occurs in the halloween 2014 event map, where you are teleported to and from hell
// Need to research a better fix, but this one works.
m_Shared.ForceRecondNextSync( TF_COND_HALLOWEEN_KART );
// Other PVS bugs can probably be fixed here.
}
}
}
bool C_TFPlayer::IsEffectRateLimited( EBonusEffectFilter_t effect, const C_TFPlayer* pAttacker ) const
{
// Check if we're rate limited
switch( effect )
{
case kEffectFilter_AttackerOnly:
case kEffectFilter_VictimOnly:
case kEffectFilter_AttackerAndVictimOnly:
return false;
case kEffectFilter_AttackerTeam:
case kEffectFilter_VictimTeam:
case kEffectFilter_BothTeams:
// Dont rate limit ourselves
if( pAttacker == this )
return false;
return true;
default:
AssertMsg1( 0, "EBonusEffectFilter_t type not handled in %s", __FUNCTION__ );
return false;
}
}
bool C_TFPlayer::ShouldPlayEffect( EBonusEffectFilter_t filter, const C_TFPlayer* pAttacker, const C_TFPlayer* pVictim ) const
{
Assert( pAttacker );
Assert( pVictim );
if( !pAttacker || !pVictim )
return false;
// Check if the right player relationship
switch( filter )
{
case kEffectFilter_AttackerOnly:
return ( pAttacker == this );
case kEffectFilter_AttackerTeam:
return ( pAttacker->GetTeamNumber() == this->GetTeamNumber() );
case kEffectFilter_VictimOnly:
return ( pVictim == this );
case kEffectFilter_VictimTeam:
return ( pVictim->GetTeamNumber() == this->GetTeamNumber() );
case kEffectFilter_AttackerAndVictimOnly:
return ( pAttacker == this || pVictim == this );
case kEffectFilter_BothTeams:
return ( pAttacker->GetTeamNumber() == this->GetTeamNumber() || pVictim->GetTeamNumber() == this->GetTeamNumber() );
default:
AssertMsg1( 0, "EBonusEffectFilter_t type not handled in %s", __FUNCTION__ );
return false;
};
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::FireGameEvent( IGameEvent *event )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( FStrEq( event->GetName(), "player_hurt" ) )
{
static BonusEffect_t bonusEffects[] =
{
// Sound name Particle name Particle filter Sound filter Sound plays in attacker's ears for them, the world for everyone else
BonusEffect_t( "TFPlayer.CritHit", "crit_text", kEffectFilter_AttackerOnly, kEffectFilter_AttackerOnly, true ),
BonusEffect_t( "TFPlayer.CritHitMini", "minicrit_text", kEffectFilter_AttackerOnly, kEffectFilter_AttackerOnly, true ),
BonusEffect_t( "TFPlayer.DoubleDonk", "doubledonk_text", kEffectFilter_BothTeams, kEffectFilter_BothTeams, true ),
BonusEffect_t( NULL, "sploosh_text", kEffectFilter_BothTeams, kEffectFilter_BothTeams, true )
};
COMPILE_TIME_ASSERT( ARRAYSIZE( bonusEffects ) == kBonusEffect_Count );
// These only should affect the local player
if ( !pLocalPlayer || pLocalPlayer != this )
return;
// By default we get kBonusEffect_None. We want to use whatever value we get here if it's not kBonusEffect_None.
// If it's not, then check for crit or minicrit
EAttackBonusEffects_t eBonusEffect = (EAttackBonusEffects_t)event->GetInt( "bonuseffect", (int)kBonusEffect_None );
if( eBonusEffect == kBonusEffect_None )
{
// Keep reading for these fields to keep replays happy
eBonusEffect = event->GetBool( "minicrit", false ) ? kBonusEffect_MiniCrit : eBonusEffect;
eBonusEffect = event->GetBool( "crit", false ) ? kBonusEffect_Crit : eBonusEffect;
}
// No effect to show? Bail
if( eBonusEffect == kBonusEffect_None || eBonusEffect >= kBonusEffect_Count )
return;
const int iAttacker = engine->GetPlayerForUserID( event->GetInt( "attacker" ) );
C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( iAttacker ) );
const int iVictim = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
C_TFPlayer *pVictim = ToTFPlayer( UTIL_PlayerByIndex( iVictim ) );
// No pointers to players? Bail
if( !pAttacker || !pVictim )
return;
bool bShowDisguisedCrit = event->GetBool( "showdisguisedcrit", 0 );
// Victim is disguised and we're not showing disguised effects? Bail
if ( pVictim->m_Shared.InCond( TF_COND_DISGUISED ) && !bShowDisguisedCrit )
return;
// Victim is carrying Resist Powerup, which is immune to crit damage
if ( pVictim && pVictim->m_Shared.GetCarryingRuneType() == RUNE_RESIST &&
( eBonusEffect == kBonusEffect_Crit || eBonusEffect == kBonusEffect_MiniCrit ) )
{
return;
}
// Support old system. If "allseecrit" is set that means we want this to show for our whole team.
EBonusEffectFilter_t eParticleFilter = bonusEffects[ eBonusEffect ].m_eParticleFilter;
EBonusEffectFilter_t eSoundFilter = bonusEffects[ eBonusEffect ].m_eSoundFilter;
if( event->GetBool( "allseecrit", false ) )
{
eParticleFilter = kEffectFilter_AttackerTeam;
eSoundFilter = kEffectFilter_AttackerTeam;
}
// Check if either of our effects are rate limited
if( IsEffectRateLimited( eParticleFilter, pAttacker ) || IsEffectRateLimited( eSoundFilter, pAttacker ) )
{
// Check if we're cooling down
if( !pVictim->CanDisplayAllSeeEffect( eBonusEffect ) )
{
// Too often! Return
return;
}
// Set cooldown period
pVictim->SetNextAllSeeEffectTime( eBonusEffect, gpGlobals->curtime + 0.5f );
}
ConVarRef hud_combattext( "hud_combattext", false );
ConVarRef hud_combattext_doesnt_block_overhead_text( "hud_combattext_doesnt_block_overhead_text", false );
bool bCombatTextBlocks = hud_combattext.GetBool() && !hud_combattext_doesnt_block_overhead_text.GetBool();
// Show the effect, unless combat text blocks
if( ShouldPlayEffect( eParticleFilter, pAttacker, pVictim ) && !bCombatTextBlocks )
{
pVictim->ParticleProp()->Create( bonusEffects[ eBonusEffect ].m_pszParticleName, PATTACH_POINT_FOLLOW, "head" );
}
// Play the sound!
if( ShouldPlayEffect( eSoundFilter, pAttacker, pVictim ) && bonusEffects[ eBonusEffect ].m_pszSoundName != NULL )
{
const bool& bPlayInAttackersEars = bonusEffects[ eBonusEffect ].m_bPlaySoundInAttackersEars;
// sound effects
EmitSound_t params;
params.m_flSoundTime = 0;
params.m_pflSoundDuration = 0;
params.m_pSoundName = bonusEffects[ eBonusEffect ].m_pszSoundName;
CPASFilter filter( pVictim->GetAbsOrigin() );
if( bPlayInAttackersEars && pAttacker == this )
{
// Don't let the attacker hear this version if its to be played in their ears
filter.RemoveRecipient( pAttacker );
// Play a sound in the ears of the attacker
CSingleUserRecipientFilter attackerFilter( pAttacker );
EmitSound( attackerFilter, pAttacker->entindex(), params );
}
EmitSound( filter, pVictim->entindex(), params );
}
}
else if ( FStrEq( event->GetName(), "hltv_changed_mode" ) )
{
int iTarget = event->GetInt( "obs_target" );
if ( iTarget == entindex() )
{
int iOld = event->GetInt( "oldmode" );
int iNew = event->GetInt( "newmode" );
if ( iOld == OBS_MODE_IN_EYE || iNew == OBS_MODE_IN_EYE )
{
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->UpdateAttachmentModels();
}
// Update visibility of any worn items.
UpdateWearables();
SetBodygroupsDirty();
}
}
}
else if ( FStrEq( event->GetName(), "hltv_changed_target" ) )
{
int iOldTarget = event->GetInt( "old_target" );
int iTarget = event->GetInt( "obs_target" );
if ( iTarget == entindex() || iOldTarget == entindex() )
{
int iMode = event->GetInt( "mode" );
if ( iMode == OBS_MODE_IN_EYE )
{
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
if ( pWeapon )
{
pWeapon->UpdateAttachmentModels();
}
}
// Update visibility of any worn items.
UpdateWearables();
SetBodygroupsDirty();
}
}
else if ( FStrEq( event->GetName(), "post_inventory_application" ) )
{
const int iPlayer = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
if ( pPlayer )
{
pPlayer->SetBodygroupsDirty();
}
}
else if ( FStrEq( event->GetName(), "rocket_jump" ) || FStrEq( event->GetName(), "sticky_jump" ) )
{
// Play a special sound when blast jumping with weapons that don't hurt the player
const int iUserID = event->GetInt( "userid" );
bool bWhistle = event->GetBool( "playsound" );
if ( bWhistle && GetUserID() == iUserID )
{
if ( !m_pBlastJumpLoop )
{
CBroadcastRecipientFilter filter;
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
m_pBlastJumpLoop = controller.SoundCreate( filter, entindex(), "BlastJump.Whistle" );
controller.Play( m_pBlastJumpLoop, 0.25, 200 );
m_flBlastJumpLaunchTime = gpGlobals->curtime;
}
}
}
else if ( FStrEq( event->GetName(), "player_spawn" ) )
{
StopBlastJumpLoopSound( event->GetInt( "userid" ) );
const int iUserID = event->GetInt( "userid" );
if ( pLocalPlayer && GetUserID() == pLocalPlayer->GetUserID() && iUserID == pLocalPlayer->GetUserID() )
{
// ADD EconNotification to equip spellbook here
if ( TFGameRules() && TFGameRules()->IsUsingSpells() )
{
int iCount = NotificationQueue_Count( &CEquipSpellbookNotification::IsNotificationType );
CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( event->GetInt( "class"), LOADOUT_POSITION_ACTION );
// no spell book
if ( !pItem || !pItem->GetStaticData()->GetItemClass() || !FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_spellbook" ) )
{
if ( iCount == 0 )
{
CEquipSpellbookNotification *pNotification = new CEquipSpellbookNotification();
pNotification->SetText( "#TF_SpellBook_EquipAction" );
pNotification->SetLifetime( 10.0f );
NotificationQueue_Add( pNotification );
}
}
else
{
NotificationQueue_Remove( &CEquipSpellbookNotification::IsNotificationType );
}
}
// ADD EconNotification to equip grapplinghook here
else if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() )
{
int iCount = NotificationQueue_Count( &CEquipGrapplingHookNotification::IsNotificationType );
CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( event->GetInt( "class"), LOADOUT_POSITION_ACTION );
// no spell book
if ( !pItem || !pItem->GetStaticData()->GetItemClass() || !FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_grapplinghook" ) )
{
if ( iCount == 0 )
{
CEquipGrapplingHookNotification *pNotification = new CEquipGrapplingHookNotification();
pNotification->SetText( "#TF_GrapplingHook_EquipAction" );
pNotification->SetLifetime( 10.0f );
NotificationQueue_Add( pNotification );
}
}
else
{
NotificationQueue_Remove( &CEquipGrapplingHookNotification::IsNotificationType );
}
}
// Add EconNotification to equip Canteen here
else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
int iCount = NotificationQueue_Count( &CEquipMvMCanteenNotification::IsNotificationType );
CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( event->GetInt( "class" ), LOADOUT_POSITION_ACTION );
// no spell book
if ( !pItem || !pItem->GetStaticData()->GetItemClass() || !FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_powerup_bottle" ) )
{
if ( iCount == 0 )
{
CEquipMvMCanteenNotification *pNotification = new CEquipMvMCanteenNotification();
pNotification->SetText( "#TF_Canteen_EquipAction" );
pNotification->SetLifetime( 10.0f );
NotificationQueue_Add( pNotification );
}
}
else
{
NotificationQueue_Remove( &CEquipMvMCanteenNotification::IsNotificationType );
}
}
}
}
else if ( FStrEq( event->GetName(), "rocket_jump_landed" ) || FStrEq( event->GetName(), "sticky_jump_landed" ) )
{
StopBlastJumpLoopSound( event->GetInt( "userid" ) );
}
else if( FStrEq( event->GetName(), "damage_resisted" ) )
{
const int index_ = event->GetInt( "entindex" );
if ( index_ == entindex() )
{
m_flLastResistTime = gpGlobals->curtime;
}
}
else if ( FStrEq( event->GetName(), "revive_player_notify" ) )
{
if ( !pLocalPlayer )
return;
const int index_ = event->GetInt( "entindex" );
if ( pLocalPlayer == this && entindex() == index_ && !m_hRevivePrompt )
{
const int nMarkerIndex = event->GetInt( "marker_entindex" );
CBaseEntity *pMarker = ClientEntityList().GetEnt( nMarkerIndex );
if ( pMarker )
{
m_hRevivePrompt = ShowRevivePrompt( pMarker, "#TF_Prompt_Revive_Title", "#TF_Prompt_Revive_Message", "#TF_Prompt_Revive_Cancel", &PromptAcceptReviveCallback, NULL, NULL );
if ( m_hRevivePrompt )
{
m_hRevivePrompt->SetKeyBoardInputEnabled( false );
}
}
}
}
else if ( FStrEq( event->GetName(), "revive_player_stopped" ) )
{
if ( !pLocalPlayer )
return;
if ( m_hRevivePrompt )
{
m_hRevivePrompt->MarkForDeletion();
m_hRevivePrompt = NULL;
}
}
else if ( FStrEq( event->GetName(), "player_changeclass" ) )
{
if ( TFGameRules() && TFGameRules()->IsMatchTypeCompetitive() )
{
if ( g_PR &&
pLocalPlayer &&
pLocalPlayer == this &&
TFGameRules() &&
TFGameRules()->IsCompetitiveMode() &&
TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
{
CBaseHudChat *pHudChat = (CBaseHudChat*)GET_HUDELEMENT( CHudChat );
if ( pHudChat )
{
C_BasePlayer *pEventPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( pEventPlayer && pLocalPlayer->GetTeamNumber() == g_PR->GetTeam( pEventPlayer->entindex() ) )
{
int nClassID = event->GetInt( "class" );
if ( nClassID >= 0 && nClassID < ARRAYSIZE( g_aPlayerClassNames ) )
{
wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( pEventPlayer->entindex() ), wszPlayerName, sizeof( wszPlayerName ) );
wchar_t wszLocalized[100];
g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Class_Change" ), 2, wszPlayerName, g_pVGuiLocalize->Find( g_aPlayerClassNames[nClassID] ) );
char szLocalized[100];
g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) );
pHudChat->ChatPrintf( pLocalPlayer->entindex(), CHAT_FILTER_NAMECHANGE, "%s", szLocalized );
}
}
}
}
}
}
else if ( FStrEq( event->GetName(), "player_abandoned_match" ) )
{
if ( pLocalPlayer && pLocalPlayer == this )
{
wchar_t wzNotification[1024] = L"";
const wchar_t *pwzTitle = g_pVGuiLocalize->Find( "#TF_Competitive_Abandoned" );
g_pVGuiLocalize->ConstructString_safe( wzNotification, pwzTitle, 0 );
if ( event->GetBool( "game_over" ) )
{
ShowMessageBox( "#TF_Competitive_AbandonedTitle", wzNotification, "#GameUI_OK" );
}
else
{
CBaseHudChat *pHudChat = (CBaseHudChat*)GET_HUDELEMENT( CHudChat );
if ( pHudChat )
{
char szLocalized[1024];
g_pVGuiLocalize->ConvertUnicodeToANSI( wzNotification, szLocalized, sizeof( szLocalized ) );
pHudChat->ChatPrintf( pLocalPlayer->entindex(), CHAT_FILTER_SERVERMSG, "%s", szLocalized );
}
}
}
}
#ifdef STAGING_ONLY
else if ( FStrEq( event->GetName(), "player_death" ) && tf_random_item_min.GetInt() > 0 && tf_random_item_max.GetInt() > tf_random_item_min.GetInt() )
{
const int iUserID = event->GetInt( "userid" );
if ( pLocalPlayer && GetUserID() == pLocalPlayer->GetUserID() && iUserID == pLocalPlayer->GetUserID() )
{
// Give random items and tell the user there items have been changed
// Get a list of all cosmetics for this class in this item range
CUtlVector<CEconItemView*> vecItemViews;
//GetItemDef
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
if ( !pLocalInv )
return;
int iClass = GetPlayerClass()->GetClassIndex();
for ( int i = 0; i < pLocalInv->GetItemCount(); ++i )
{
CEconItemView *pItem = pLocalInv->GetItem( i );
const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition();
if ( IsMiscSlot( pItemDef->GetLoadoutSlot( iClass ) ) && pItemDef->GetDefinitionIndex() >= tf_random_item_min.GetInt() && pItemDef->GetDefinitionIndex() <= tf_random_item_max.GetInt() )
{
vecItemViews.AddToTail( pItem );
}
}
equip_region_mask_t unCumulativeRegionMask = 0;
// Unequip everything
InventoryManager()->UpdateInventoryEquippedState( pLocalInv, INVALID_ITEM_ID, iClass, LOADOUT_POSITION_HEAD );
InventoryManager()->UpdateInventoryEquippedState( pLocalInv, INVALID_ITEM_ID, iClass, LOADOUT_POSITION_MISC );
InventoryManager()->UpdateInventoryEquippedState( pLocalInv, INVALID_ITEM_ID, iClass, LOADOUT_POSITION_MISC2 );
// pick a random item slot2
for ( int iSlot = 0; iSlot < 3; ++iSlot )
{
int iLoadoutPos = LOADOUT_POSITION_HEAD;
switch ( iSlot )
{
case 0: iLoadoutPos = LOADOUT_POSITION_HEAD; break;
case 1: iLoadoutPos = LOADOUT_POSITION_MISC; break;
case 2: iLoadoutPos = LOADOUT_POSITION_MISC2; break;
}
int iRandomItem = RandomInt( 0, vecItemViews.Count() - 1 );
for ( int i = 0; i < vecItemViews.Count(); ++i )
{
CEconItemView *pItemView = vecItemViews[( iRandomItem + i ) % vecItemViews.Count()];
equip_region_mask_t unItemEquipMask = pItemView->GetItemDefinition()->GetEquipRegionMask();
if ( !( unItemEquipMask & unCumulativeRegionMask ) )
{
TFInventoryManager()->EquipItemInLoadout( pLocalPlayer->GetPlayerClass()->GetClassIndex(), iLoadoutPos, pItemView->GetID() );
vecItemViews.Remove( ( iRandomItem + i ) % vecItemViews.Count() );
unCumulativeRegionMask |= unItemEquipMask;
break;
}
}
}
// Notify player there items have changed
CEconNotification *pNotification = new CEconNotification();
pNotification->SetText( "#TF_Test_ItemsChanged" );
pNotification->SetLifetime( 6.0f );
NotificationQueue_Add( pNotification );
}
}
#endif // STAGING_ONLY
BaseClass::FireGameEvent( event );
}
const char* C_TFPlayer::ModifyEventParticles( const char* token )
{
if ( GetPlayerClass()->IsClass( TF_CLASS_SCOUT ) )
{
if ( !Q_strcmp( token, "doublejump_puff" ) )
{
if ( m_Shared.GetAirDash() > 1 )
{
return "doublejump_puff_alt";
}
}
}
return BaseClass::ModifyEventParticles( token );
}
void C_TFPlayer::SetTauntCameraTargets( float back, float up )
{
m_flTauntCamTargetDist = back;
m_flTauntCamTargetDistUp = up;
// Force this on
m_bTauntInterpolating = true;
}
CampaignMedalDisplayType_t C_TFPlayer::GetCampaignMedalType( void )
{
// static CSchemaItemDefHandle pItemDef_Summer2015Operation( "Activated Summer 2015 Operation Pass" );
// static CSchemaItemDefHandle pItemDef_InvasionPass( "Activated Invasion Pass" );
// static CSchemaItemDefHandle pItemDef_HalloweenPass( "Activated Halloween Pass" );
// static CSchemaItemDefHandle pItemDef_Winter2016Pass( "Activated Operation Tough Break Pass" );
CampaignMedalDisplayType_t retVal = CAMPAIGN_MEDAL_DISPLAY_TYPE_NONE;
/*
if ( HasCampaignMedal( CAMPAIGN_MEDAL_WINTER2016 ) )
{
CTFPlayerInventory *pInv = Inventory();
if ( pInv )
{
for ( int i = 0; i < pInv->GetItemCount(); ++i )
{
CEconItemView *pItem = pInv->GetItem( i );
if ( pItem && ( pItem->GetItemDefinition() == pItemDef_Winter2016Pass ) )
{
style_index_t iStyle = pItem->GetItemStyle();
if ( iStyle != INVALID_STYLE_INDEX )
{
iStyle += ( ( entindex()%2 < 1 ) ? CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL1 : CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL2 ); // styles start at 0 and Winter2016 images start at CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL1
if ( ( iStyle >= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL1 ) && ( iStyle <= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GOLD2 ) )
{
retVal = (CampaignMedalDisplayType_t)iStyle;
break;
}
}
}
}
}
}
if ( IsPlayingInvasionMap() && HasCampaignMedal( CAMPAIGN_MEDAL_INVASION ) )
{
retVal = CAMPAIGN_MEDAL_DISPLAY_TYPE_INVASION;
}
else if ( HasCampaignMedal( CAMPAIGN_MEDAL_HALLOWEEN ) )
{
CTFPlayerInventory *pInv = Inventory();
if ( pInv )
{
for ( int i = 0; i < pInv->GetItemCount(); ++i )
{
CEconItemView *pItem = pInv->GetItem( i );
if ( pItem && ( pItem->GetItemDefinition() == pItemDef_HalloweenPass ) )
{
style_index_t iStyle = pItem->GetItemStyle();
if ( iStyle != INVALID_STYLE_INDEX )
{
iStyle += CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GRAVEL; // styles start at 0 and Halloween images start at CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GRAVEL
if ( ( iStyle >= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GRAVEL ) && ( iStyle <= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GOLD ) )
{
retVal = (CampaignMedalDisplayType_t)iStyle;
break;
}
}
}
}
}
}
else if ( HasCampaignMedal( CAMPAIGN_MEDAL_SUMMER2015 ) )
{
CTFPlayerInventory *pInv = Inventory();
if ( pInv )
{
for ( int i = 0; i < pInv->GetItemCount(); ++i )
{
CEconItemView *pItem = pInv->GetItem( i );
if ( pItem && ( pItem->GetItemDefinition() == pItemDef_Summer2015Operation ) )
{
style_index_t iStyle = pItem->GetItemStyle();
if ( iStyle != INVALID_STYLE_INDEX )
{
iStyle += 1; // styles start at 0 and images start at 1
if ( ( iStyle >= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_SUMMER2015_GRAVEL ) && ( iStyle <= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_SUMMER2015_GOLD ) )
{
retVal = (CampaignMedalDisplayType_t)iStyle;
break;
}
}
}
}
}
}*/
return retVal;
}
const char *C_TFPlayer::GetCampaignMedalImage( void )
{
return g_pszCampaignMedalIcons[GetCampaignMedalType()];
}
void C_TFPlayer::UpdateGlowEffect( void )
{
DestroyGlowEffect();
BaseClass::UpdateGlowEffect();
// create a new effect if we have a coach
if ( m_hCoach && m_hCoach->IsLocalPlayer() && m_hCoach->m_bIsCoaching )
{
float r, g, b;
GetGlowEffectColor( &r, &g, &b );
m_pStudentGlowEffect = new CGlowObject( this, Vector( r, g, b ), 1.0, true );
}
// create a power up effect if needed
if ( ShouldShowPowerupGlowEffect() )
{
float r, g, b;
GetPowerupGlowEffectColor( &r, &g, &b );
m_pPowerupGlowEffect = new CGlowObject( this, Vector( r, g, b ), 1.0, true );
}
}
void C_TFPlayer::DestroyGlowEffect( void )
{
BaseClass::DestroyGlowEffect();
if ( m_pStudentGlowEffect )
{
delete m_pStudentGlowEffect;
m_pStudentGlowEffect = NULL;
}
if ( m_pPowerupGlowEffect )
{
delete m_pPowerupGlowEffect;
m_pPowerupGlowEffect = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateGlowColor( void )
{
CGlowObject* pGlowObject = GetGlowObject();
if ( pGlowObject )
{
float r, g, b;
GetGlowEffectColor( &r, &g, &b );
pGlowObject->SetColor( Vector( r, g, b ) );
}
if ( m_pPowerupGlowEffect )
{
float r, g, b;
GetPowerupGlowEffectColor( &r, &g, &b );
m_pPowerupGlowEffect->SetColor( Vector( r, g, b ) );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::GetGlowEffectColor( float *r, float *g, float *b )
{
#ifdef TF_CREEP_MODE
if ( TFGameRules() && TFGameRules()->IsCreepWaveMode() )
{
if ( GetTeamNumber() == TF_TEAM_RED )
{
*r = 255;
*g = 0;
*b = 0;
}
else
{
*r = 0;
*g = 0;
*b = 255;
}
return;
}
#endif // TF_CREEP_MODE
int nTeam = GetTeamNumber();
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
// In CTF, show health color glow for alive player
if ( pLocalPlayer && pLocalPlayer->IsAlive() && TFGameRules() && ( TFGameRules()->GetGameType() == TF_GAMETYPE_CTF ) && HasTheFlag() )
{
float flHealth = (float)GetHealth() / (float)GetMaxHealth();
if ( flHealth > 0.6 )
{
*r = 0.33f;
*g = 0.75f;
*b = 0.23f;
}
else if( flHealth > 0.3 )
{
*r = 0.75f;
*g = 0.72f;
*b = 0.23f;
}
else
{
*r = 0.75f;
*g = 0.23f;
*b = 0.23f;
}
return;
}
if ( !engine->IsHLTV() && ( GetLocalPlayerTeam() >= FIRST_GAME_TEAM ) )
{
if ( IsPlayerClass( TF_CLASS_SPY ) && m_Shared.InCond( TF_COND_DISGUISED ) && ( GetTeamNumber() != GetLocalPlayerTeam() ) )
{
nTeam = m_Shared.GetDisguiseTeam();
}
}
TFGameRules()->GetTeamGlowColor( nTeam, *r, *g, *b );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldShowPowerupGlowEffect()
{
// should local player see enemy glow with powerup related
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
if ( pLocalPlayer->IsAlive() && this != pLocalPlayer && GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
{
// give advantage to local player who doesn't have rune to fight against enemy with rune by glowing their health
if ( m_Shared.IsCarryingRune() && !pLocalPlayer->m_Shared.IsCarryingRune() )
{
// only show glow when the enemy is lower than 30% HP
float flHealth = ( float )GetHealth() / ( float )GetMaxHealth();
return flHealth <= 0.3 && pLocalPlayer->IsLineOfSightClear( this, IGNORE_ACTORS );
}
// local player with supernova can see enemy glow within supernova range
else if ( pLocalPlayer->m_Shared.GetCarryingRuneType() == RUNE_SUPERNOVA && pLocalPlayer->m_Shared.IsRuneCharged() && !m_Shared.IsStealthed() )
{
const float flEffectRadiusSqr = Sqr( 1500.f );
Vector toPlayer = WorldSpaceCenter() - pLocalPlayer->WorldSpaceCenter();
return toPlayer.LengthSqr() <= flEffectRadiusSqr && pLocalPlayer->IsLineOfSightClear( this, IGNORE_ACTORS );
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::GetPowerupGlowEffectColor( float *r, float *g, float *b )
{
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
// no need to add extra logics here. we already know that other players are glowing from SUPERNOVA
if ( pLocalPlayer->m_Shared.GetCarryingRuneType() == RUNE_SUPERNOVA )
{
*r = 255;
*g = 255;
*b = 0;
}
else
{
GetGlowEffectColor( r, g, b );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static ConVar tf_inspect_hint_count( "tf_inspect_hint_count", "0", FCVAR_ARCHIVE );
void C_TFPlayer::HandleInspectHint()
{
int nNotifyCount = tf_inspect_hint_count.GetInt();
if ( nNotifyCount > 10 )
return;
if ( m_bNotifiedWeaponInspectThisLife )
return;
CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
if ( pNotifyPanel )
{
wchar_t szNotification[1024]=L"";
wchar_t wKeyBind[80] = L"";
const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#Hint_inspect_weapon" );
if ( wpszFormat )
{
const char *key = engine->Key_LookupBinding( "+inspect" );
if ( !key || FStrEq( key, "(null)" ) )
{
key = "< not bound >";
}
g_pVGuiLocalize->ConvertANSIToUnicode( key, wKeyBind, sizeof( wKeyBind ) );
g_pVGuiLocalize->ConstructString_safe( szNotification, wpszFormat, 1, wKeyBind );
pNotifyPanel->SetupNotifyCustom( szNotification, "", GetTeamNumber() );
tf_inspect_hint_count.SetValue( nNotifyCount + 1 );
}
m_bNotifiedWeaponInspectThisLife = true;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::AddOverheadEffect( const char *pszEffectName )
{
int index_ = m_mapOverheadEffects.Find( pszEffectName );
// particle is added already
if ( index_ != m_mapOverheadEffects.InvalidIndex() )
return false;
CNewParticleEffect *pEffect = ParticleProp()->Create( pszEffectName, PATTACH_ABSORIGIN_FOLLOW, 0, GetOverheadEffectPosition() );
if ( pEffect )
{
if ( m_mapOverheadEffects.Count() == 0 )
{
m_flOverheadEffectStartTime = gpGlobals->curtime;
}
m_mapOverheadEffects.Insert( pszEffectName, pEffect );
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::RemoveOverheadEffect( const char *pszEffectName, bool bRemoveInstantly )
{
int index_ = m_mapOverheadEffects.Find( pszEffectName );
// particle is added already
if ( index_ != m_mapOverheadEffects.InvalidIndex() )
{
if ( bRemoveInstantly )
ParticleProp()->StopEmissionAndDestroyImmediately( m_mapOverheadEffects[index_] );
ParticleProp()->StopParticlesNamed( pszEffectName, bRemoveInstantly );
m_mapOverheadEffects.RemoveAt( index_ );
}
}
void C_TFPlayer::UpdateOverheadEffects()
{
if ( IsLocalPlayer() )
return;
const int nOverheadEffectCount = m_mapOverheadEffects.Count();
if ( nOverheadEffectCount == 0 )
return;
Vector vecOverheadEffectPosition = GetOverheadEffectPosition();
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
if ( !pLocalPlayer )
return;
Vector vecHeadToHead = EyePosition() - pLocalPlayer->EyePosition();
const float flEffectGap = 24.f;
Vector vecRightOffset = CrossProduct( vecHeadToHead, Vector( 0, 0, 1 ) ).Normalized();
float flFirstEffectOffset = -flEffectGap * 0.5f * ( nOverheadEffectCount - 1 );
int iValidParticleIndex = 0;
FOR_EACH_MAP_FAST( m_mapOverheadEffects, i )
{
HPARTICLEFFECT hEffect = m_mapOverheadEffects[i];
if ( hEffect )
{
float flCurrentOffset = flFirstEffectOffset + flEffectGap * iValidParticleIndex;
Vector vecOffset = vecOverheadEffectPosition + flCurrentOffset * vecRightOffset;
ParticleProp()->AddControlPoint( hEffect, 0, this, PATTACH_ABSORIGIN_FOLLOW, 0, vecOffset );
iValidParticleIndex++;
}
}
}
Vector C_TFPlayer::GetOverheadEffectPosition()
{
return GetClassEyeHeight() + Vector( 0, 0, 20 );
}
//------------------------------------------------------------------------------
// The serverbrowser has just added a server to the favorite list.
// Send this information to the GC.
static void cc_tf_register_favorite_with_gc( const CCommand &args )
{
netadr_t netaddr( args.ArgS() );
CGCMsg< MsgGCServerBrowser_Server_t > msg( k_EMsgGCServerBrowser_FavoriteServer );
msg.Body().m_unIP = netaddr.GetIPNetworkByteOrder(); // <<<< Note: this is wrong. But it was wrong before. And we can fix the data if it is *consistently* wrong.
msg.Body().m_usPort = netaddr.GetPort();
msg.Body().m_ubSource = k_EGCMsgServerBrowser_FromServerBrowser;
GCClientSystem()->BSendMessage( msg );
}
static ConCommand tf_register_favorite_with_gc( "rfgc", cc_tf_register_favorite_with_gc, "", FCVAR_HIDDEN );
//------------------------------------------------------------------------------
// The serverbrowser has just added a server to the blacklist.
// Send this information to the GC.
static void cc_tf_register_blacklist_with_gc( const CCommand &args )
{
netadr_t netaddr( args.ArgS() );
CGCMsg< MsgGCServerBrowser_Server_t > msg( k_EMsgGCServerBrowser_BlacklistServer );
msg.Body().m_unIP = netaddr.GetIPNetworkByteOrder(); // <<<< Note: this is wrong. But it was wrong before. And we can fix the data if it is *consistently* wrong.
msg.Body().m_usPort = netaddr.GetPort();
msg.Body().m_ubSource = k_EGCMsgServerBrowser_FromServerBrowser;
GCClientSystem()->BSendMessage( msg );
}
static ConCommand tf_register_blacklist_with_gc( "rbgc", cc_tf_register_blacklist_with_gc, "", FCVAR_HIDDEN );
static void cc_taunt_by_name( const CCommand &args )
{
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pPlayer || !pPlayer->IsAlive() )
return;
const char *pszTauntItemName = args.ArgS();
int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
CTFPlayerInventory *pInv = pPlayer->Inventory();
if ( !pInv )
return;
CUtlStringList strTauntList;
for ( int iSlot = LOADOUT_POSITION_TAUNT; iSlot<= LOADOUT_POSITION_TAUNT8; ++iSlot )
{
CEconItemView *pItem = pInv->GetItemInLoadout( iClass, iSlot );
if ( !pItem || !pItem->IsValid() )
continue;
static char pszItemName[512];
g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find ( pItem->GetStaticData()->GetItemBaseName() ) , pszItemName, sizeof(pszItemName) );
strTauntList.CopyAndAddToTail( pszItemName );
if ( V_stricmp( pszTauntItemName, pszItemName ) == 0 )
{
int iTauntSlot = iSlot - LOADOUT_POSITION_TAUNT + 1;
engine->ClientCmd( CFmtStr( "taunt %d", iTauntSlot ) );
return;
}
}
Msg( "taunt_by_name failed. Taunt [%s] is not equipped in the loadout.\n", pszTauntItemName );
Msg( "[Taunt(s) in loadout]\n");
for ( int i=0; i<strTauntList.Count(); ++i )
{
Msg( "%s\n", strTauntList[i] );
}
}
static ConCommand taunt_by_name( "taunt_by_name", cc_taunt_by_name, "Use equipped taunt by name." );
#ifdef STAGING_ONLY
CON_COMMAND( force_reset_gesture_slot_vcd, "" )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
pLocalPlayer->m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
}
}
#endif // STAGING_ONLY