//========= 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 #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 ¶ms ); 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 > 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( 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( 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( 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( 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; im_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; iAddEffects( 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(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( pBaseEntity ); if ( pViewModelAttachment && pViewModelAttachment->GetOuter() ) { return pViewModelAttachment->GetOuter()->GetAttributeContainer()->GetItem(); } CTFViewModel *pViewModel = dynamic_cast( pBaseEntity ); if ( pViewModel && pViewModel->GetWeapon() ) { return pViewModel->GetWeapon()->GetAttributeContainer()->GetItem(); } CTFDroppedWeapon *pDroppedWeapon = dynamic_cast( 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( 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( pBaseEntity ); if ( !pWeapon ) { for ( int i = 0; i < 1; ++i ) { CEconWearable* pWearable = dynamic_cast( pBaseEntity ); if ( pWearable ) { pItem = pWearable->GetAttributeContainer()->GetItem(); pTFPlayer = ToTFPlayer( pWearable->GetOwnerEntity() ); break; } C_ViewmodelAttachmentModel *pModel = dynamic_cast( 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( 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( 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( pEntity->GetMoveParent() ); if ( pViewModel ) { //C_TFPlayer *pPlayer = ToTFPlayer( pViewModel->GetOwnerEntity() ); //if ( pPlayer ) CTFWeaponBase *pWeap = dynamic_cast( 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( 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; iAddPhonemeFile( "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_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( 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, ¤tdir, &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; iGetTauntInputRemapCount(); ++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( 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(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( 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( 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 headGib; int nClassIndex = GetPlayerClass()->GetClassIndex(); if ( bHasCustomModel ) { for ( int i=0; iVPhysicsGetObject(); 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 (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 ¶ms ) { // 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, ¶ms.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;iIsDormant() || 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; iParticleProp()->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; iParticleProp()->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(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(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; iLookupBone( 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; jLookupBone( 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; iLookupBone( 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; jLookupBone( 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 ( 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( 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 (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 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; im_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD ); } } #endif // STAGING_ONLY