You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
12135 lines
352 KiB
12135 lines
352 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
//=============================================================================
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "c_tf_player.h"
|
||
|
#include "c_user_message_register.h"
|
||
|
#include "view.h"
|
||
|
#include "iclientvehicle.h"
|
||
|
#include "ivieweffects.h"
|
||
|
#include "input.h"
|
||
|
#include "IEffects.h"
|
||
|
#include "fx.h"
|
||
|
#include "c_basetempentity.h"
|
||
|
#include "hud_macros.h"
|
||
|
#include "engine/ivdebugoverlay.h"
|
||
|
#include "smoke_fog_overlay.h"
|
||
|
#include "playerandobjectenumerator.h"
|
||
|
#include "bone_setup.h"
|
||
|
#include "in_buttons.h"
|
||
|
#include "r_efx.h"
|
||
|
#include "dlight.h"
|
||
|
#include "shake.h"
|
||
|
#include "cl_animevent.h"
|
||
|
#include "animation.h"
|
||
|
#include "choreoscene.h"
|
||
|
#include "tf_weaponbase.h"
|
||
|
#include "c_tf_playerresource.h"
|
||
|
#include "toolframework/itoolframework.h"
|
||
|
#include "tier1/KeyValues.h"
|
||
|
#include "tier0/vprof.h"
|
||
|
#include "prediction.h"
|
||
|
#include "effect_dispatch_data.h"
|
||
|
#include "c_te_effect_dispatch.h"
|
||
|
#include "tf_fx_muzzleflash.h"
|
||
|
#include "tf_gamerules.h"
|
||
|
#include "view_scene.h"
|
||
|
#include "c_baseobject.h"
|
||
|
#include "toolframework_client.h"
|
||
|
#include "materialsystem/imaterialvar.h"
|
||
|
#include "soundenvelope.h"
|
||
|
#include "voice_status.h"
|
||
|
#include "clienteffectprecachesystem.h"
|
||
|
#include "functionproxy.h"
|
||
|
#include "toolframework_client.h"
|
||
|
#include "choreoevent.h"
|
||
|
#include "vguicenterprint.h"
|
||
|
#include "eventlist.h"
|
||
|
#include "input.h"
|
||
|
#include "tf_weapon_medigun.h"
|
||
|
#include "tf_weapon_pipebomblauncher.h"
|
||
|
#include "tf_weapon_shovel.h"
|
||
|
#include "tf_hud_mediccallers.h"
|
||
|
#include "in_main.h"
|
||
|
#include "c_team.h"
|
||
|
#include "collisionutils.h"
|
||
|
// for spy material proxy
|
||
|
#include "tf_proxyentity.h"
|
||
|
#include "materialsystem/imaterial.h"
|
||
|
#include "materialsystem/imaterialvar.h"
|
||
|
#include "materialsystem/itexturecompositor.h"
|
||
|
#include "c_tf_team.h"
|
||
|
#include "tf_item_inventory.h"
|
||
|
#include "model_types.h"
|
||
|
#include "dt_utlvector_recv.h"
|
||
|
#include "tf_item_wearable.h"
|
||
|
#include "cam_thirdperson.h"
|
||
|
#include "c_tf_projectile_arrow.h"
|
||
|
#include "econ_entity.h"
|
||
|
#include "ihasowner.h"
|
||
|
#include "tf_hud_itemeffectmeter.h"
|
||
|
#include "replay/vgui/replayinputpanel.h"
|
||
|
#include "tf_replay.h"
|
||
|
#include "netadr.h"
|
||
|
#include "input.h"
|
||
|
|
||
|
#include "gcsdk/gcclientsdk.h"
|
||
|
#include "econ_gcmessages.h"
|
||
|
#include "rtime.h"
|
||
|
#include "networkstringtable_clientdll.h"
|
||
|
#include "replay/ireplaymanager.h"
|
||
|
#include "gc_clientsystem.h"
|
||
|
#include "c_entitydissolve.h"
|
||
|
#include "tf_viewmodel.h"
|
||
|
#include "player_vs_environment/c_tf_upgrades.h"
|
||
|
#include "sourcevr/isourcevirtualreality.h"
|
||
|
#include "tempent.h"
|
||
|
#include "confirm_dialog.h"
|
||
|
#include "c_tf_weapon_builder.h"
|
||
|
#include "tf_shared_content_manager.h"
|
||
|
#include "baseanimatedtextureproxy.h"
|
||
|
#include "econ_entity.h"
|
||
|
#include "halloween/tf_weapon_spellbook.h"
|
||
|
#include "tf_weapon_grapplinghook.h"
|
||
|
#include "tf_logic_robot_destruction.h"
|
||
|
#include "econ_notifications.h"
|
||
|
#include "tf_weapon_buff_item.h"
|
||
|
#include "tf_dropped_weapon.h"
|
||
|
#include "tf_hud_notification_panel.h"
|
||
|
#include "tf_dropped_weapon.h"
|
||
|
#include "tf_hud_passtime_reticle.h"
|
||
|
#include "passtime_convars.h"
|
||
|
#include "c_tf_passtime_logic.h"
|
||
|
#include "tf_weapon_passtime_gun.h"
|
||
|
#include "eiface.h"
|
||
|
#include "filesystem.h"
|
||
|
#include "debugoverlay_shared.h"
|
||
|
#include "tf_hud_chat.h"
|
||
|
#include "tf_item_powerup_bottle.h"
|
||
|
#include <vgui_controls/AnimationController.h>
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
#include "tf_matchmaking_shared.h"
|
||
|
#include "tier2/keyvaluesmacros.h"
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
#if defined( REPLAY_ENABLED )
|
||
|
#include "replay/ienginereplay.h"
|
||
|
#endif
|
||
|
|
||
|
#if defined( CTFPlayer )
|
||
|
#undef CTFPlayer
|
||
|
#endif
|
||
|
|
||
|
#include "materialsystem/imesh.h" //for materials->FindMaterial
|
||
|
#include "iviewrender.h" //for view->
|
||
|
|
||
|
// NVNT haptics system interface
|
||
|
#include "c_tf_haptics.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
|
||
|
using namespace GCSDK;
|
||
|
|
||
|
static_assert( TEAM_UNASSIGNED == 0, "If this assert fires, update the assert and the enum in ctexturecompositor.cpp which specifies team colors" );
|
||
|
static_assert( TF_TEAM_RED == 2, "If this assert fires, update the assert and the enum in ctexturecompositor.cpp which specifies team colors" );
|
||
|
static_assert( TF_TEAM_BLUE == 3, "If this assert fires, update the assert and the enum in ctexturecompositor.cpp which specifies team colors" );
|
||
|
|
||
|
// Forward decl
|
||
|
CEconItemView *GetEconItemViewFromProxyEntity( void *pEntity );
|
||
|
C_TFPlayer *GetOwnerFromProxyEntity( void *pEntity );
|
||
|
|
||
|
// --------------------------------------------------------------------------------
|
||
|
// Local Convar Helper Function
|
||
|
// --------------------------------------------------------------------------------
|
||
|
void VisionMode_ChangeCallback( IConVar *pConVar, char const *pOldString, float flOldValue )
|
||
|
{
|
||
|
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
pLocalPlayer->CalculateVisionUsingCurrentFlags();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
CON_COMMAND_F ( tf_test_bomb, "Test halloween bomb", 0 )
|
||
|
{
|
||
|
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
|
||
|
if ( !pLocalPlayer )
|
||
|
return;
|
||
|
|
||
|
C_TFPlayer *pPlayer = ToTFPlayer( pLocalPlayer );
|
||
|
if ( !pPlayer )
|
||
|
return;
|
||
|
|
||
|
pPlayer->CreateBombonomiconHint();
|
||
|
}
|
||
|
|
||
|
ConVar test_vision_off( "test_vision_off", "0", FCVAR_NONE, "Force vision modes off!", VisionMode_ChangeCallback );
|
||
|
ConVar test_pyrovision( "test_pyrovision", "0", FCVAR_NONE, "Force Pyrovision on!", VisionMode_ChangeCallback );
|
||
|
ConVar test_romevision( "test_romevision", "0", FCVAR_NONE, "Force Romevision on!", VisionMode_ChangeCallback );
|
||
|
ConVar test_halloweenvision( "test_halloweenvision", "0", FCVAR_NONE, "Force halloween vision on!", VisionMode_ChangeCallback );
|
||
|
#endif
|
||
|
|
||
|
// These are all permanently STAGING_ONLY
|
||
|
#ifdef STAGING_ONLY
|
||
|
ConVar tf_paint_kit_disable( "tf_paint_kit_disable", "0", FCVAR_NONE, "Set to disable using paintkits completely." );
|
||
|
ConVar tf_paint_kit_team_override( "tf_paint_kit_team_override", "-1", FCVAR_NONE, "Set to -1:disabled, 0:TF_TEAM_RED, 1:TF_TEAM_BLUE" );
|
||
|
ConVar tf_paint_kit_seed_override( "tf_paint_kit_seed_override", "0", FCVAR_NONE, "Set to none-0 will force composite weapon skin to use this as random seed number." );
|
||
|
ConVar tf_paint_kit_force_regen( "tf_paint_kit_force_regen", "0", FCVAR_NONE, "Set to 1 to force regen after every generation. (dev only)" );
|
||
|
ConVar tf_paint_kit_generating_icons( "tf_paint_kit_generating_icons", "0", FCVAR_NONE, "Only Set by the Icon Generating System." );
|
||
|
extern ConVar tf_paint_kit_force_wear;
|
||
|
|
||
|
KeyValues *s_kvOverridePaintkit = NULL;
|
||
|
bool s_bIsPaintkitOverrideSet = false;
|
||
|
|
||
|
void LoadPaintKit( const char* pszFile )
|
||
|
{
|
||
|
//scripts\items\unencrypted\paintkits
|
||
|
static char pszPaintkitFile[MAX_PATH] = { '\0' };
|
||
|
if ( pszFile )
|
||
|
{
|
||
|
V_sprintf_safe( pszPaintkitFile, "scripts/items/unencrypted/paintkits/%s.paintkit", pszFile );
|
||
|
}
|
||
|
|
||
|
if ( pszPaintkitFile[0] == '\0' )
|
||
|
return;
|
||
|
|
||
|
// Make sure we have a file system
|
||
|
if ( !g_pFullFileSystem || !g_pFullFileSystem->FileExists( pszPaintkitFile ) )
|
||
|
{
|
||
|
Msg( "Unable to load %s from File System \n", pszPaintkitFile );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( s_kvOverridePaintkit )
|
||
|
{
|
||
|
s_kvOverridePaintkit->deleteThis();
|
||
|
s_kvOverridePaintkit = NULL;
|
||
|
}
|
||
|
|
||
|
// Parse the KeyValues
|
||
|
s_kvOverridePaintkit = new KeyValues( "paintkit" );
|
||
|
if ( !s_kvOverridePaintkit->LoadFromFile( g_pFullFileSystem, pszPaintkitFile ) )
|
||
|
{
|
||
|
Msg( "Unable to read KeyValues from %s \n", pszPaintkitFile );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
HandleKeyValuesMacros( s_kvOverridePaintkit );
|
||
|
|
||
|
Msg( "Setting %s as Paintkit override.\nCall with no args to disable\ntf_paint_kit_force_regen set to 1\n", pszPaintkitFile );
|
||
|
s_bIsPaintkitOverrideSet = true;
|
||
|
}
|
||
|
|
||
|
CON_COMMAND_F( tf_paint_kit_override_file, "Set a override paintkit file", FCVAR_NONE )
|
||
|
{
|
||
|
// Enable force regen
|
||
|
tf_paint_kit_force_regen.SetValue( 1 );
|
||
|
s_bIsPaintkitOverrideSet = false;
|
||
|
|
||
|
// Take the con var
|
||
|
if ( args.ArgC() <= 1 )
|
||
|
{
|
||
|
Msg( "No paintkit file specified, turning override off.\nYou may want to set tf_paint_kit_force_regen to 0\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const char *pszFile = args.Arg( 1 );
|
||
|
LoadPaintKit( pszFile );
|
||
|
}
|
||
|
|
||
|
CON_COMMAND( tf_paint_kit_override_refresh, "Refresh the current override paint kit file" )
|
||
|
{
|
||
|
if ( !s_bIsPaintkitOverrideSet )
|
||
|
return;
|
||
|
|
||
|
LoadPaintKit( NULL );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
ConVar tf_playergib_forceup( "tf_playersgib_forceup", "1.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward added velocity for gibs." );
|
||
|
ConVar tf_playergib_force( "tf_playersgib_force", "500.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Gibs force." );
|
||
|
ConVar tf_playergib_maxspeed( "tf_playergib_maxspeed", "400", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Max gib speed." );
|
||
|
|
||
|
ConVar tf_always_deathanim( "tf_always_deathanim", "0", FCVAR_CHEAT, "Force death anims to always play." );
|
||
|
|
||
|
ConVar tf_clientsideeye_lookats( "tf_clientsideeye_lookats", "1", FCVAR_NONE, "When on, players will turn their pupils to look at nearby players." );
|
||
|
|
||
|
extern ConVar tf_halloween_kart_boost_recharge;
|
||
|
extern ConVar tf_halloween_kart_boost_duration;
|
||
|
|
||
|
extern ConVar cl_thirdperson;
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
ConVar test_local_player_skin_override( "test_local_player_skin_override", "-1", FCVAR_CHEAT, "If >= 0, force this skin index to be used by the local player model." );
|
||
|
ConVar tf_test_setkillcount( "tf_test_setkillcount", "0", FCVAR_NONE, "Force a kill streak count for eye glow testing" );
|
||
|
|
||
|
extern ConVar tf_random_item_min;
|
||
|
extern ConVar tf_random_item_max;
|
||
|
|
||
|
extern ConVar tf_killstreak_eyeglow;
|
||
|
extern ConVar tf_killstreak_color;
|
||
|
extern ConVar tf_eyeglow_wip;
|
||
|
|
||
|
ConVar tf_sheen_all( "tf_sheen_all", "0", FCVAR_NONE, "When on, enables sheens on all weapons" );
|
||
|
ConVar tf_sheen_fast( "tf_sheen_fast", "0", FCVAR_NONE, "When on, no delay's between sheens" );
|
||
|
ConVar tf_sheen_shader_override( "tf_sheen_shader_override", "0", FCVAR_NONE, "Set for shader effect override" );
|
||
|
|
||
|
ConVar tf_sheen_color_override_r( "tf_sheen_color_override_r", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
|
||
|
ConVar tf_sheen_color_override_g( "tf_sheen_color_override_g", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
|
||
|
ConVar tf_sheen_color_override_b( "tf_sheen_color_override_b", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
|
||
|
ConVar tf_sheen_color_override_a( "tf_sheen_color_override_a", "0", FCVAR_HIDDEN, "Set by ConCommand tf_sheen_color_override" );
|
||
|
|
||
|
CON_COMMAND_F ( tf_sheen_color_override, "Sheen Color Override RGBA 0-255", 0 )
|
||
|
{
|
||
|
if ( args.ArgC() < 5 )
|
||
|
{
|
||
|
Msg( "requires 4 inputs, r g b a (in 0 - 255 range) \n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
float r = atof( args.Arg(1) ) / 255.0f;
|
||
|
float g = atof( args.Arg(2) ) / 255.0f;
|
||
|
float b = atof( args.Arg(3) ) / 255.0f;
|
||
|
float a = atof( args.Arg(4) ) / 255.0f;
|
||
|
|
||
|
tf_sheen_color_override_r.SetValue( r );
|
||
|
tf_sheen_color_override_g.SetValue( g );
|
||
|
tf_sheen_color_override_b.SetValue( b );
|
||
|
tf_sheen_color_override_a.SetValue( a );
|
||
|
}
|
||
|
|
||
|
ConVar cl_taunt_max_turn_speed( "cl_taunt_max_turn_speed", "0", FCVAR_HIDDEN | FCVAR_CHEAT );
|
||
|
ConVar cl_halloween_kart_turn_speed( "cl_halloween_kart_turn_speed", "100" );
|
||
|
#endif // Staging only
|
||
|
|
||
|
ConVar tf_sheen_framerate( "tf_sheen_framerate", "25", FCVAR_NONE | FCVAR_HIDDEN, "Set Sheen Frame Rate" );
|
||
|
|
||
|
extern ConVar tf_killstreak_alwayson;
|
||
|
|
||
|
ConVar tf_sheen_alpha_firstperson( "tf_sheen_alpha_firstperson", "0.1", FCVAR_NONE, "Set the Alpha Value for first person sheens" );
|
||
|
ConVar tf_killstreakeyes_minkills( "tf_killstreakeyes_minkills", "5", FCVAR_DEVELOPMENTONLY, "min kills to get base eyeglow" );
|
||
|
ConVar tf_killstreakeyes_maxkills( "tf_killstreakeyes_maxkills", "10", FCVAR_DEVELOPMENTONLY, "kills to get the max eye glow effect" );
|
||
|
|
||
|
//ConVar spectate_random_server_basetime( "spectate_random_server_basetime", "240", FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
ConVar cl_autorezoom( "cl_autorezoom", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "When set to 1, sniper rifle will re-zoom after firing a zoomed shot." );
|
||
|
ConVar tf_remember_activeweapon( "tf_remember_activeweapon", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "Setting this to 1 will make the active weapon persist between lives." );
|
||
|
ConVar tf_remember_lastswitched( "tf_remember_lastswitched", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "Setting this to 1 will make the 'last weapon' persist between lives." );
|
||
|
ConVar cl_autoreload( "cl_autoreload", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "When set to 1, clip-using weapons will automatically be reloaded whenever they're not being fired." );
|
||
|
|
||
|
ConVar tf_respawn_on_loadoutchanges( "tf_respawn_on_loadoutchanges", "1", FCVAR_ARCHIVE, "When set to 1, you will automatically respawn whenever you change loadouts inside a respawn zone." );
|
||
|
|
||
|
ConVar sb_dontshow_maxplayer_warning( "sb_dontshow_maxplayer_warning", "0", FCVAR_ARCHIVE );
|
||
|
ConVar sb_close_browser_on_connect( "sb_close_browser_on_connect", "1", FCVAR_ARCHIVE );
|
||
|
|
||
|
ConVar tf_spectate_pyrovision( "tf_spectate_pyrovision", "0", FCVAR_ARCHIVE, "When on, spectator will see the world with Pyrovision active", VisionMode_ChangeCallback );
|
||
|
ConVar tf_replay_pyrovision( "tf_replay_pyrovision", "0", FCVAR_ARCHIVE, "When on, replays will be seen with Pyrovision active", VisionMode_ChangeCallback );
|
||
|
|
||
|
ConVar tf_taunt_first_person( "tf_taunt_first_person", "0", FCVAR_NONE, "1 = taunts remain first-person" );
|
||
|
|
||
|
ConVar tf_romevision_opt_in( "tf_romevision_opt_in", "0", FCVAR_ARCHIVE, "Enable Romevision in Mann vs. Machine mode when available." );
|
||
|
ConVar tf_romevision_skip_prompt( "tf_romevision_skip_prompt", "0", FCVAR_ARCHIVE, "If nonzero, skip the prompt about sharing Romevision." );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
extern ConVar tf_bountymode_showhealth;
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
#define BDAY_HAT_MODEL "models/effects/bday_hat.mdl"
|
||
|
#define BOMB_HAT_MODEL "models/props_lakeside_event/bomb_temp_hat.mdl"
|
||
|
#define BOMBONOMICON_MODEL "models/props_halloween/bombonomicon.mdl"
|
||
|
|
||
|
IMaterial *g_pHeadLabelMaterial[2] = { NULL, NULL };
|
||
|
void SetupHeadLabelMaterials( void );
|
||
|
|
||
|
extern CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
|
||
|
const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t ¶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<CHandle<CEconWearable > > m_hRagWearables; // These look like they are no longer used?
|
||
|
|
||
|
CUtlVector< CHandle< CEconWearable > > m_hClientWearables; // wearables on the ragdoll that are "following" it
|
||
|
|
||
|
bool m_bCreatedWhilePlaybackSkipping;
|
||
|
};
|
||
|
|
||
|
IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_TFRagdoll, DT_TFRagdoll, CTFRagdoll )
|
||
|
RecvPropVector( RECVINFO(m_vecRagdollOrigin) ),
|
||
|
RecvPropInt( RECVINFO( m_iPlayerIndex ) ),
|
||
|
RecvPropVector( RECVINFO(m_vecForce) ),
|
||
|
RecvPropVector( RECVINFO(m_vecRagdollVelocity) ),
|
||
|
RecvPropInt( RECVINFO( m_nForceBone ) ),
|
||
|
RecvPropBool( RECVINFO( m_bGib ) ),
|
||
|
RecvPropBool( RECVINFO( m_bBurning ) ),
|
||
|
RecvPropBool( RECVINFO( m_bElectrocuted ) ),
|
||
|
RecvPropBool( RECVINFO( m_bFeignDeath ) ),
|
||
|
RecvPropBool( RECVINFO( m_bWasDisguised ) ),
|
||
|
RecvPropBool( RECVINFO( m_bOnGround ) ),
|
||
|
RecvPropBool( RECVINFO( m_bCloaked ) ),
|
||
|
RecvPropBool( RECVINFO( m_bBecomeAsh ) ),
|
||
|
RecvPropInt( RECVINFO( m_iDamageCustom ) ),
|
||
|
RecvPropInt( RECVINFO( m_iTeam ) ),
|
||
|
RecvPropInt( RECVINFO( m_iClass ) ),
|
||
|
RecvPropUtlVector( RECVINFO_UTLVECTOR( m_hRagWearables ), 8, RecvPropEHandle(NULL, 0, 0) ),
|
||
|
RecvPropBool( RECVINFO( m_bGoldRagdoll ) ),
|
||
|
RecvPropBool( RECVINFO( m_bIceRagdoll ) ),
|
||
|
RecvPropBool( RECVINFO( m_bCritOnHardHit ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flHeadScale ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flTorsoScale ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flHandScale ) ),
|
||
|
END_RECV_TABLE()
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
C_TFRagdoll::C_TFRagdoll()
|
||
|
{
|
||
|
m_iPlayerIndex = TF_PLAYER_INDEX_NONE;
|
||
|
m_fDeathTime = -1;
|
||
|
m_bFadingOut = false;
|
||
|
m_bGib = false;
|
||
|
m_bBurning = false;
|
||
|
m_bElectrocuted = false;
|
||
|
m_bBatted = false;
|
||
|
m_bDissolving = false;
|
||
|
m_bFeignDeath = false;
|
||
|
m_bWasDisguised = false;
|
||
|
m_bCloaked = false;
|
||
|
m_bBecomeAsh = false;
|
||
|
m_flBurnEffectStartTime = 0.0f;
|
||
|
m_iDamageCustom = 0;
|
||
|
m_bGoldRagdoll = false;
|
||
|
m_bIceRagdoll = false;
|
||
|
m_freezeTimer.Invalidate();
|
||
|
m_frozenTimer.Invalidate();
|
||
|
m_iTeam = -1;
|
||
|
m_iClass = -1;
|
||
|
m_nForceBone = -1;
|
||
|
m_bRagdollOn = false;
|
||
|
m_bDeathAnim = false;
|
||
|
m_bOnGround = false;
|
||
|
m_bBaseTransform = false;
|
||
|
m_bFixedConstraints = false;
|
||
|
m_flTimeToDissolve = 0.3f;
|
||
|
m_bCritOnHardHit = false;
|
||
|
m_flHeadScale = 1.f;
|
||
|
m_flTorsoScale = 1.f;
|
||
|
m_flHandScale = 1.f;
|
||
|
|
||
|
UseClientSideAnimation();
|
||
|
|
||
|
m_bCreatedWhilePlaybackSkipping = engine->IsSkippingPlayback();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
C_TFRagdoll::~C_TFRagdoll()
|
||
|
{
|
||
|
PhysCleanupFrictionSounds( this );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : *pSourceEntity -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity )
|
||
|
{
|
||
|
if ( !pSourceEntity )
|
||
|
return;
|
||
|
|
||
|
VarMapping_t *pSrc = pSourceEntity->GetVarMapping();
|
||
|
VarMapping_t *pDest = GetVarMapping();
|
||
|
|
||
|
// Find all the VarMapEntry_t's that represent the same variable.
|
||
|
for ( int i = 0; i < pDest->m_Entries.Count(); i++ )
|
||
|
{
|
||
|
VarMapEntry_t *pDestEntry = &pDest->m_Entries[i];
|
||
|
for ( int j=0; j < pSrc->m_Entries.Count(); j++ )
|
||
|
{
|
||
|
VarMapEntry_t *pSrcEntry = &pSrc->m_Entries[j];
|
||
|
if ( !Q_strcmp( pSrcEntry->watcher->GetDebugName(), pDestEntry->watcher->GetDebugName() ) )
|
||
|
{
|
||
|
pDestEntry->watcher->Copy( pSrcEntry->watcher );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Setup vertex weights for drawing
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
|
||
|
{
|
||
|
// While we're dying, we want to mimic the facial animation of the player. Once they're dead, we just stay as we are.
|
||
|
EHANDLE hPlayer = GetPlayerHandle();
|
||
|
if ( ( hPlayer && hPlayer->IsAlive()) || !hPlayer )
|
||
|
{
|
||
|
BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
|
||
|
}
|
||
|
else if ( hPlayer )
|
||
|
{
|
||
|
hPlayer->SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : *pTrace -
|
||
|
// iDamageType -
|
||
|
// *pCustomImpactName -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
|
||
|
{
|
||
|
VPROF( "C_TFRagdoll::ImpactTrace" );
|
||
|
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
|
||
|
if( !pPhysicsObject )
|
||
|
return;
|
||
|
|
||
|
Vector vecDir;
|
||
|
VectorSubtract( pTrace->endpos, pTrace->startpos, vecDir );
|
||
|
|
||
|
if ( iDamageType == DMG_BLAST )
|
||
|
{
|
||
|
// Adjust the impact strength and apply the force at the center of mass.
|
||
|
vecDir *= 4000;
|
||
|
pPhysicsObject->ApplyForceCenter( vecDir );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Find the apporx. impact point.
|
||
|
Vector vecHitPos;
|
||
|
VectorMA( pTrace->startpos, pTrace->fraction, vecDir, vecHitPos );
|
||
|
VectorNormalize( vecDir );
|
||
|
|
||
|
// Adjust the impact strength and apply the force at the impact point..
|
||
|
vecDir *= 4000;
|
||
|
pPhysicsObject->ApplyForceOffset( vecDir, vecHitPos );
|
||
|
}
|
||
|
|
||
|
m_pRagdoll->ResetRagdollSleepAfterTime();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::CreateTFRagdoll()
|
||
|
{
|
||
|
// Get the player.
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
EHANDLE hPlayer = GetPlayerHandle();
|
||
|
if ( hPlayer )
|
||
|
{
|
||
|
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
|
||
|
}
|
||
|
|
||
|
int nModelIndex = -1;
|
||
|
|
||
|
if ( pPlayer && pPlayer->GetPlayerClass() && !pPlayer->ShouldDrawSpyAsDisguised() )
|
||
|
{
|
||
|
nModelIndex = modelinfo->GetModelIndex( pPlayer->GetPlayerClass()->GetModelName() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TFPlayerClassData_t *pData = GetPlayerClassData( m_iClass );
|
||
|
if ( pData )
|
||
|
{
|
||
|
nModelIndex = modelinfo->GetModelIndex( pData->GetModelName() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
m_flHeadScale = pPlayer->GetHeadScale();
|
||
|
m_flTorsoScale = pPlayer->GetTorsoScale();
|
||
|
m_flHandScale = pPlayer->GetHandScale();
|
||
|
}
|
||
|
|
||
|
if ( nModelIndex != -1 )
|
||
|
{
|
||
|
SetModelIndex( nModelIndex );
|
||
|
|
||
|
if ( m_iTeam == TF_TEAM_RED )
|
||
|
{
|
||
|
m_nSkin = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_nSkin = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check for any special player skin override behaviour.
|
||
|
if ( pPlayer && pPlayer->BRenderAsZombie() )
|
||
|
{
|
||
|
C_TFPlayer::AdjustSkinIndexForZombie( m_iClass, m_nSkin );
|
||
|
}
|
||
|
|
||
|
// We check against new-style (special flag to indicate goldification) and old style (custom damage type)
|
||
|
// to maintain old demos involving the golden wrench.
|
||
|
if ( m_bGoldRagdoll || m_iDamageCustom == TF_DMG_CUSTOM_GOLD_WRENCH )
|
||
|
{
|
||
|
EmitSound( "Saxxy.TurnGold" );
|
||
|
m_bFixedConstraints = true;
|
||
|
}
|
||
|
|
||
|
if ( m_bIceRagdoll )
|
||
|
{
|
||
|
EmitSound( "Icicle.TurnToIce" );
|
||
|
ParticleProp()->Create( "xms_icicle_impact_dryice", PATTACH_ABSORIGIN_FOLLOW );
|
||
|
m_freezeTimer.Start( RandomFloat( 0.1f, 0.75f ) );
|
||
|
m_frozenTimer.Start( RandomFloat( 9.0f, 11.0f ) );
|
||
|
}
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
DevMsg( 2, "CreateTFRagdoll %d %d\n", gpGlobals->framecount, pPlayer ? pPlayer->entindex() : 0 );
|
||
|
#endif
|
||
|
|
||
|
if ( pPlayer && !pPlayer->IsDormant() )
|
||
|
{
|
||
|
// Move my current model instance to the ragdoll's so decals are preserved.
|
||
|
pPlayer->SnatchModelInstance( this );
|
||
|
|
||
|
VarMapping_t *varMap = GetVarMapping();
|
||
|
|
||
|
// Copy all the interpolated vars from the player entity.
|
||
|
// The entity uses the interpolated history to get bone velocity.
|
||
|
if ( !pPlayer->IsLocalPlayer() && pPlayer->IsInterpolationEnabled() )
|
||
|
{
|
||
|
Interp_Copy( pPlayer );
|
||
|
|
||
|
SetAbsAngles( pPlayer->GetRenderAngles() );
|
||
|
GetRotationInterpolator().Reset();
|
||
|
|
||
|
m_flAnimTime = pPlayer->m_flAnimTime;
|
||
|
SetSequence( pPlayer->GetSequence() );
|
||
|
m_flPlaybackRate = pPlayer->GetPlaybackRate();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// This is the local player, so set them in a default
|
||
|
// pose and slam their velocity, angles and origin
|
||
|
SetAbsOrigin( /* m_vecRagdollOrigin : */ pPlayer->GetRenderOrigin() );
|
||
|
SetAbsAngles( pPlayer->GetRenderAngles() );
|
||
|
SetAbsVelocity( m_vecRagdollVelocity );
|
||
|
|
||
|
// Hack! Find a neutral standing pose or use the idle.
|
||
|
int iSeq = LookupSequence( "RagdollSpawn" );
|
||
|
if ( iSeq == -1 )
|
||
|
{
|
||
|
Assert( false );
|
||
|
iSeq = 0;
|
||
|
}
|
||
|
SetSequence( iSeq );
|
||
|
SetCycle( 0.0 );
|
||
|
|
||
|
Interp_Reset( varMap );
|
||
|
}
|
||
|
|
||
|
if ( !m_bFeignDeath || m_bWasDisguised )
|
||
|
{
|
||
|
pPlayer->RecalcBodygroupsIfDirty();
|
||
|
m_nBody = pPlayer->GetBody();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Overwrite network origin so later interpolation will use this position.
|
||
|
SetNetworkOrigin( m_vecRagdollOrigin );
|
||
|
SetAbsOrigin( m_vecRagdollOrigin );
|
||
|
SetAbsVelocity( m_vecRagdollVelocity );
|
||
|
|
||
|
Interp_Reset( GetVarMapping() );
|
||
|
}
|
||
|
|
||
|
if ( IsCloaked() )
|
||
|
{
|
||
|
AddEffects( EF_NOSHADOW );
|
||
|
}
|
||
|
|
||
|
// Play a death anim depending on the custom damage type.
|
||
|
bool bPlayDeathInAir = false;
|
||
|
int iDeathSeq = -1;
|
||
|
if ( pPlayer && !m_bGoldRagdoll )
|
||
|
{
|
||
|
iDeathSeq = pPlayer->m_Shared.GetSequenceForDeath( this, m_bBurning, m_iDamageCustom );
|
||
|
|
||
|
if ( m_bDissolving && !m_bGib )
|
||
|
{
|
||
|
bPlayDeathInAir = true;
|
||
|
iDeathSeq = LookupSequence( "dieviolent" );
|
||
|
}
|
||
|
|
||
|
// did we find a death sequence?
|
||
|
if ( iDeathSeq > -1 && (m_iDamageCustom != TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING) &&
|
||
|
(m_iDamageCustom != TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH) && (m_iDamageCustom != TF_DMG_CUSTOM_TAUNTATK_ALLCLASS_GUITAR_RIFF) )
|
||
|
{
|
||
|
// we only want to show the death anims 25% of the time, unless this is a demoman kill taunt
|
||
|
// always play backstab animations for the ice ragdoll
|
||
|
if ( !m_bIceRagdoll && !tf_always_deathanim.GetBool() && (RandomFloat( 0, 1 ) > 0.25f) )
|
||
|
{
|
||
|
iDeathSeq = -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool bPlayDeathAnim = cl_ragdoll_physics_enable.GetBool() && (iDeathSeq > -1) && pPlayer;
|
||
|
|
||
|
if ( !m_bOnGround && bPlayDeathAnim && !bPlayDeathInAir )
|
||
|
bPlayDeathAnim = false; // Don't play most death anims in the air (headshot, etc).
|
||
|
|
||
|
if ( bPlayDeathAnim )
|
||
|
{
|
||
|
// Set our position for a death anim.
|
||
|
SetAbsOrigin( pPlayer->GetRenderOrigin() );
|
||
|
SetAbsAngles( pPlayer->GetRenderAngles() );
|
||
|
SetAbsVelocity( Vector(0,0,0) );
|
||
|
m_vecForce = Vector(0,0,0);
|
||
|
|
||
|
// Play the death anim.
|
||
|
ResetSequence( iDeathSeq );
|
||
|
m_bDeathAnim = true;
|
||
|
}
|
||
|
else if ( m_bIceRagdoll )
|
||
|
{
|
||
|
// couldn't play death anim because we were in midair - go ridig immediately
|
||
|
m_freezeTimer.Invalidate();
|
||
|
m_frozenTimer.Invalidate();
|
||
|
m_bFixedConstraints = true;
|
||
|
}
|
||
|
|
||
|
// Fade out the ragdoll in a while
|
||
|
StartFadeOut( cl_ragdoll_fade_time.GetFloat() );
|
||
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
||
|
|
||
|
// Copy over impact attachments.
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
pPlayer->CreateBoneAttachmentsFromWearables( this, m_bWasDisguised );
|
||
|
pPlayer->MoveBoneAttachments( this );
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( m_iDamageCustom == TF_DMG_CUSTOM_KART )
|
||
|
{
|
||
|
m_vecForce *= 100.0f;
|
||
|
SetAbsVelocity( GetAbsVelocity() + m_vecForce );
|
||
|
ApplyAbsVelocityImpulse( m_vecForce );
|
||
|
}
|
||
|
|
||
|
// Save ragdoll information.
|
||
|
if ( cl_ragdoll_physics_enable.GetBool() && !m_bDeathAnim )
|
||
|
{
|
||
|
// Make us a ragdoll..
|
||
|
m_nRenderFX = kRenderFxRagdoll;
|
||
|
|
||
|
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
|
||
|
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
|
||
|
matrix3x4_t currentBones[MAXSTUDIOBONES];
|
||
|
const float boneDt = 0.05f;
|
||
|
|
||
|
// We have to make sure that we're initting this client ragdoll off of the same model.
|
||
|
// GetRagdollInitBoneArrays uses the *player* Hdr, which may be a different model than
|
||
|
// the ragdoll Hdr, if we try to create a ragdoll in the same frame that the player
|
||
|
// changes their player model.
|
||
|
CStudioHdr *pRagdollHdr = GetModelPtr();
|
||
|
CStudioHdr *pPlayerHdr = pPlayer ? pPlayer->GetModelPtr() : NULL;
|
||
|
|
||
|
bool bChangedModel = false;
|
||
|
|
||
|
if ( pRagdollHdr && pPlayerHdr )
|
||
|
{
|
||
|
bChangedModel = pRagdollHdr->GetVirtualModel() != pPlayerHdr->GetVirtualModel();
|
||
|
|
||
|
// Assert( !bChangedModel && "C_TFRagdoll::CreateTFRagdoll: Trying to create ragdoll with a different model than the player it's based on" );
|
||
|
}
|
||
|
|
||
|
bool bBoneArraysInited;
|
||
|
if ( pPlayer && !pPlayer->IsDormant() && !bChangedModel )
|
||
|
{
|
||
|
bBoneArraysInited = pPlayer->GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bBoneArraysInited = GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
|
||
|
}
|
||
|
|
||
|
if ( bBoneArraysInited )
|
||
|
{
|
||
|
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt, m_bFixedConstraints );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), RENDER_GROUP_TRANSLUCENT_ENTITY );
|
||
|
}
|
||
|
|
||
|
if ( m_bBurning )
|
||
|
{
|
||
|
m_flBurnEffectStartTime = gpGlobals->curtime;
|
||
|
ParticleProp()->Create( "burningplayer_corpse", PATTACH_ABSORIGIN_FOLLOW );
|
||
|
}
|
||
|
|
||
|
if ( m_bElectrocuted )
|
||
|
{
|
||
|
const char *pEffectName = ( m_iTeam == TF_TEAM_RED ) ? "electrocuted_red" : "electrocuted_blue";
|
||
|
ParticleProp()->Create( pEffectName, PATTACH_ABSORIGIN_FOLLOW );
|
||
|
C_BaseEntity::EmitSound( "TFPlayer.MedicChargedDeath" );
|
||
|
}
|
||
|
|
||
|
if ( m_bBecomeAsh && !m_bDissolving && !m_bGib )
|
||
|
{
|
||
|
ParticleProp()->Create( "drg_fiery_death", PATTACH_ABSORIGIN_FOLLOW );
|
||
|
m_flTimeToDissolve = 0.5f;
|
||
|
}
|
||
|
|
||
|
if ( pPlayer->HasBombinomiconEffectOnDeath() && !m_bGib && !m_bDissolving )
|
||
|
{
|
||
|
m_flTimeToDissolve = 1.2f;
|
||
|
}
|
||
|
|
||
|
// Birthday mode.
|
||
|
if ( pPlayer && TFGameRules() && TFGameRules()->IsBirthday() )
|
||
|
{
|
||
|
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
|
||
|
breakablepropparams_t breakParams( m_vecRagdollOrigin, GetRenderAngles(), m_vecRagdollVelocity, angularImpulse );
|
||
|
breakParams.impactEnergyScale = 1.0f;
|
||
|
pPlayer->DropPartyHat( breakParams, m_vecRagdollVelocity.GetForModify() );
|
||
|
}
|
||
|
|
||
|
const char *materialOverrideFilename = NULL;
|
||
|
|
||
|
if ( m_bFixedConstraints )
|
||
|
{
|
||
|
if ( m_bGoldRagdoll )
|
||
|
{
|
||
|
// Gold texture...we've been turned into a golden corpse!
|
||
|
materialOverrideFilename = "models/player/shared/gold_player.vmt";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_bIceRagdoll )
|
||
|
{
|
||
|
// Ice texture...we've been turned into an ice statue!
|
||
|
materialOverrideFilename = "models/player/shared/ice_player.vmt";
|
||
|
}
|
||
|
|
||
|
if ( materialOverrideFilename )
|
||
|
{
|
||
|
// Ice texture...we've been turned into an ice statue!
|
||
|
m_MaterialOverride.Init( materialOverrideFilename, TEXTURE_GROUP_CLIENT_EFFECTS );
|
||
|
|
||
|
// override all of our wearables, too
|
||
|
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
|
||
|
{
|
||
|
if ( pEntity->GetFollowedEntity() == this )
|
||
|
{
|
||
|
CEconEntity *pItem = dynamic_cast< CEconEntity * >( pEntity );
|
||
|
if ( pItem )
|
||
|
{
|
||
|
pItem->SetMaterialOverride( m_iTeam, materialOverrideFilename );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float C_TFRagdoll::FrameAdvance( float flInterval )
|
||
|
{
|
||
|
// if we're in the process of becoming an ice statue, freeze
|
||
|
if ( m_freezeTimer.HasStarted() && !m_freezeTimer.IsElapsed() )
|
||
|
{
|
||
|
// play the backstab anim until the timer is up
|
||
|
return BaseClass::FrameAdvance( flInterval );
|
||
|
}
|
||
|
|
||
|
if ( m_frozenTimer.HasStarted() )
|
||
|
{
|
||
|
if ( m_frozenTimer.IsElapsed() )
|
||
|
{
|
||
|
// holding frozen time is up - turn to a stiff ragdoll and fall over
|
||
|
m_frozenTimer.Invalidate();
|
||
|
|
||
|
m_nRenderFX = kRenderFxRagdoll;
|
||
|
|
||
|
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
|
||
|
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
|
||
|
matrix3x4_t currentBones[MAXSTUDIOBONES];
|
||
|
const float boneDt = 0.1f;
|
||
|
GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
|
||
|
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt, true );
|
||
|
|
||
|
SetAbsVelocity( Vector( 0,0,0 ) );
|
||
|
m_bRagdollOn = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// don't move at all
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float fRes = BaseClass::FrameAdvance( flInterval );
|
||
|
|
||
|
if ( !m_bRagdollOn && IsSequenceFinished() && m_bDeathAnim )
|
||
|
{
|
||
|
m_nRenderFX = kRenderFxRagdoll;
|
||
|
|
||
|
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
|
||
|
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
|
||
|
matrix3x4_t currentBones[MAXSTUDIOBONES];
|
||
|
const float boneDt = 0.1f;
|
||
|
if ( !GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ) )
|
||
|
{
|
||
|
Warning( "C_TFRagdoll::FrameAdvance GetRagdollInitBoneArrays failed.\n" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt );
|
||
|
}
|
||
|
|
||
|
SetAbsVelocity( Vector( 0,0,0 ) );
|
||
|
m_bRagdollOn = true;
|
||
|
|
||
|
// Make it fade out.
|
||
|
StartFadeOut( cl_ragdoll_fade_time.GetFloat() );
|
||
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
||
|
}
|
||
|
|
||
|
return fRes;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::CreateTFHeadGib( void )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
EHANDLE hPlayer = GetPlayerHandle();
|
||
|
if ( hPlayer )
|
||
|
{
|
||
|
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
|
||
|
}
|
||
|
if ( pPlayer && ((pPlayer->m_hFirstGib == NULL) || m_bFeignDeath) )
|
||
|
{
|
||
|
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
|
||
|
VectorNormalize( vecVelocity );
|
||
|
|
||
|
pPlayer->CreatePlayerGibs( m_vecRagdollOrigin, vecVelocity, m_vecForce.Length(), m_bBurning, false, true );
|
||
|
// Decap Death Camera is disorienting on range Decaps (aka bullets)
|
||
|
// Use normal Deathcam
|
||
|
if ( m_iDamageCustom == TF_DMG_CUSTOM_HEADSHOT_DECAPITATION )
|
||
|
{
|
||
|
pPlayer->m_hHeadGib = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::CreateTFGibs( bool bDestroyRagdoll, bool bCurrentPosition )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
EHANDLE hPlayer = GetPlayerHandle();
|
||
|
if ( hPlayer )
|
||
|
{
|
||
|
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
|
||
|
}
|
||
|
|
||
|
if ( pPlayer && pPlayer->HasBombinomiconEffectOnDeath() )
|
||
|
{
|
||
|
m_vecForce *= 2.0f;
|
||
|
m_vecForce.z *= 3.0f;
|
||
|
|
||
|
DispatchParticleEffect( TFGameRules()->IsHolidayActive( kHoliday_Halloween ) ? "bombinomicon_burningdebris_halloween" : "bombinomicon_burningdebris",
|
||
|
bCurrentPosition ? GetAbsOrigin() : m_vecRagdollOrigin, GetAbsAngles() );
|
||
|
EmitSound( "Bombinomicon.Explode" );
|
||
|
}
|
||
|
|
||
|
if ( pPlayer && ((pPlayer->m_hFirstGib == NULL) || m_bFeignDeath) )
|
||
|
{
|
||
|
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
|
||
|
VectorNormalize( vecVelocity );
|
||
|
pPlayer->CreatePlayerGibs( bCurrentPosition ? pPlayer->GetRenderOrigin() : m_vecRagdollOrigin, vecVelocity, m_vecForce.Length(), m_bBurning );
|
||
|
}
|
||
|
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->IsBirthdayOrPyroVision() )
|
||
|
{
|
||
|
DispatchParticleEffect( "bday_confetti", pPlayer->GetAbsOrigin() + Vector(0,0,32), vec3_angle );
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->IsBirthday() )
|
||
|
{
|
||
|
C_BaseEntity::EmitSound( "Game.HappyBirthday" );
|
||
|
}
|
||
|
}
|
||
|
else if ( m_bCritOnHardHit && !UTIL_IsLowViolence() )
|
||
|
{
|
||
|
DispatchParticleEffect( "tfc_sniper_mist", pPlayer->WorldSpaceCenter(), vec3_angle );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bDestroyRagdoll )
|
||
|
{
|
||
|
EndFadeOut();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetRenderMode( kRenderNone );
|
||
|
UpdateVisibility();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Separate from CreateTFGibs so we can easily remove it if we don't like it...
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::CreateWearableGibs( bool bDisguiseWearables )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
EHANDLE hPlayer = GetPlayerHandle();
|
||
|
if ( hPlayer )
|
||
|
{
|
||
|
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
|
||
|
}
|
||
|
if ( !pPlayer )
|
||
|
return;
|
||
|
|
||
|
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
|
||
|
VectorNormalize( vecVelocity );
|
||
|
pPlayer->CreatePlayerGibs( m_vecRagdollOrigin, vecVelocity, m_vecForce.Length(), m_bBurning, true, false, bDisguiseWearables );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : type -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::OnDataChanged( DataUpdateType_t type )
|
||
|
{
|
||
|
BaseClass::OnDataChanged( type );
|
||
|
|
||
|
if ( type == DATA_UPDATE_CREATED )
|
||
|
{
|
||
|
bool bCreateRagdoll = true;
|
||
|
|
||
|
// Get the player.
|
||
|
EHANDLE hPlayer = GetPlayerHandle();
|
||
|
if ( hPlayer )
|
||
|
{
|
||
|
// If we're getting the initial update for this player (e.g., after resetting entities after
|
||
|
// lots of packet loss, then don't create gibs, ragdolls if the player and it's gib/ragdoll
|
||
|
// both show up on same frame.
|
||
|
if ( abs( hPlayer->GetCreationTick() - gpGlobals->tickcount ) < TIME_TO_TICKS( 1.0f ) )
|
||
|
{
|
||
|
bCreateRagdoll = false;
|
||
|
}
|
||
|
}
|
||
|
else if ( C_BasePlayer::GetLocalPlayer() )
|
||
|
{
|
||
|
// Ditto for recreation of the local player
|
||
|
if ( abs( C_BasePlayer::GetLocalPlayer()->GetCreationTick() - gpGlobals->tickcount ) < TIME_TO_TICKS( 1.0f ) )
|
||
|
{
|
||
|
bCreateRagdoll = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Prevent replays from creating ragdolls on the first frame of playback after skipping through playback.
|
||
|
// If a player died (leaving a ragdoll) previous to the first frame of replay playback,
|
||
|
// their ragdoll wasn't yet initialized because OnDataChanged events are queued but not processed
|
||
|
// until the first render.
|
||
|
if ( engine->IsPlayingDemo() )
|
||
|
{
|
||
|
bCreateRagdoll = !m_bCreatedWhilePlaybackSkipping;
|
||
|
}
|
||
|
|
||
|
if ( GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_GRAND_SLAM )
|
||
|
{
|
||
|
m_bBatted = true;
|
||
|
}
|
||
|
|
||
|
C_TFPlayer *pPlayer = ToTFPlayer( hPlayer.Get() );
|
||
|
bool bMiniBoss = ( pPlayer && pPlayer->IsMiniBoss() ) ? true : false;
|
||
|
|
||
|
if ( GetDamageCustom() == TF_DMG_CUSTOM_PLASMA )
|
||
|
{
|
||
|
if ( !m_bBecomeAsh && !bMiniBoss )
|
||
|
{
|
||
|
m_bDissolving = true;
|
||
|
}
|
||
|
|
||
|
m_bGib = false;
|
||
|
}
|
||
|
|
||
|
if ( GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED )
|
||
|
{
|
||
|
if ( !m_bBecomeAsh && !bMiniBoss )
|
||
|
{
|
||
|
m_bDissolving = true;
|
||
|
}
|
||
|
|
||
|
m_bGib = true;
|
||
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
||
|
}
|
||
|
|
||
|
// Don't gib zombies, always just ragdoll
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
if ( pPlayer->BRenderAsZombie() )
|
||
|
{
|
||
|
m_bGib = false;
|
||
|
}
|
||
|
pPlayer->UpdateMVMEyeGlowEffect( false );
|
||
|
}
|
||
|
|
||
|
if ( bCreateRagdoll )
|
||
|
{
|
||
|
if ( m_bGib )
|
||
|
{
|
||
|
CreateTFGibs( !m_bDissolving );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CreateTFRagdoll();
|
||
|
if ( IsDecapitation() )
|
||
|
{
|
||
|
CreateTFHeadGib();
|
||
|
EmitSound( "TFPlayer.Decapitated" );
|
||
|
|
||
|
bool bBlood = true;
|
||
|
if ( TFGameRules() && ( TFGameRules()->UseSillyGibs() ||
|
||
|
( TFGameRules()->IsMannVsMachineMode() && hPlayer && hPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) )
|
||
|
{
|
||
|
bBlood = false;
|
||
|
}
|
||
|
|
||
|
if ( bBlood )
|
||
|
{
|
||
|
ParticleProp()->Create( "blood_decap", PATTACH_POINT_FOLLOW, "head" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_bNoModelParticles = true;
|
||
|
|
||
|
// Drop wearables (hats, etc)
|
||
|
CreateWearableGibs( m_bWasDisguised );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( !cl_ragdoll_physics_enable.GetBool() )
|
||
|
{
|
||
|
// Don't let it set us back to a ragdoll with data from the server.
|
||
|
m_nRenderFX = kRenderFxNone;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFRagdoll::InternalDrawModel( int flags )
|
||
|
{
|
||
|
if ( m_MaterialOverride.IsValid() )
|
||
|
{
|
||
|
modelrender->ForcedMaterialOverride( m_MaterialOverride );
|
||
|
}
|
||
|
|
||
|
int ret = BaseClass::InternalDrawModel( flags );
|
||
|
|
||
|
if ( m_MaterialOverride.IsValid() )
|
||
|
{
|
||
|
modelrender->ForcedMaterialOverride( NULL );
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFRagdoll::IsDecapitation()
|
||
|
{
|
||
|
return (cl_ragdoll_fade_time.GetFloat() > 5.f) &&
|
||
|
((m_iDamageCustom == TF_DMG_CUSTOM_DECAPITATION)
|
||
|
|| (m_iDamageCustom == TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING)
|
||
|
|| (m_iDamageCustom == TF_DMG_CUSTOM_DECAPITATION_BOSS)
|
||
|
|| (m_iDamageCustom == TF_DMG_CUSTOM_HEADSHOT_DECAPITATION)
|
||
|
|| (m_iDamageCustom == TF_DMG_CUSTOM_MERASMUS_DECAPITATION) );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFRagdoll::IsHeadSmash()
|
||
|
{
|
||
|
return ((cl_ragdoll_fade_time.GetFloat() > 5.f) && (m_iDamageCustom == TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH));
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFRagdoll::GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld )
|
||
|
{
|
||
|
int iHeadAttachment = LookupAttachment( "head" );
|
||
|
if ( IsDecapitation() && (iAttachment == iHeadAttachment) )
|
||
|
{
|
||
|
MatrixCopy( m_mHeadAttachment, attachmentToWorld );
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return BaseClass::GetAttachment( iAttachment, attachmentToWorld );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : -
|
||
|
// Output : IRagdoll*
|
||
|
//-----------------------------------------------------------------------------
|
||
|
IRagdoll* C_TFRagdoll::GetIRagdoll() const
|
||
|
{
|
||
|
return m_pRagdoll;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : -
|
||
|
// Output : Returns true on success, false on failure.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFRagdoll::IsRagdollVisible()
|
||
|
{
|
||
|
Vector vMins = Vector(-1,-1,-1); //WorldAlignMins();
|
||
|
Vector vMaxs = Vector(1,1,1); //WorldAlignMaxs();
|
||
|
|
||
|
Vector origin = GetAbsOrigin();
|
||
|
|
||
|
if( !engine->IsBoxInViewCluster( vMins + origin, vMaxs + origin) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
else if( engine->CullBox( vMins + origin, vMaxs + origin ) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
#define DISSOLVE_FADE_IN_START_TIME 0.0f
|
||
|
#define DISSOLVE_FADE_IN_END_TIME 1.0f
|
||
|
#define DISSOLVE_FADE_OUT_MODEL_START_TIME 1.9f
|
||
|
#define DISSOLVE_FADE_OUT_MODEL_END_TIME 2.0f
|
||
|
#define DISSOLVE_FADE_OUT_START_TIME 2.0f
|
||
|
#define DISSOLVE_FADE_OUT_END_TIME 2.0f
|
||
|
|
||
|
void C_TFRagdoll::ClientThink( void )
|
||
|
{
|
||
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
||
|
|
||
|
// Store off the un-shrunken head location for blood spurts.
|
||
|
if ( IsDecapitation() )
|
||
|
{
|
||
|
int iAttach = LookupAttachment( "head" );
|
||
|
|
||
|
m_bBaseTransform = true;
|
||
|
BaseClass::GetAttachment( iAttach, m_mHeadAttachment );
|
||
|
m_bBaseTransform = false;
|
||
|
|
||
|
m_BoneAccessor.SetReadableBones( 0 );
|
||
|
SetupBones( NULL, -1, BONE_USED_BY_ATTACHMENT, gpGlobals->curtime );
|
||
|
}
|
||
|
|
||
|
if ( m_bCloaked && m_flPercentInvisible < 1.f )
|
||
|
{
|
||
|
m_flPercentInvisible += gpGlobals->frametime;
|
||
|
if ( m_flPercentInvisible > 1.f )
|
||
|
{
|
||
|
m_flPercentInvisible = 1.f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
C_TFPlayer *pPlayer = ToTFPlayer( GetPlayerHandle() );
|
||
|
bool bBombinomicon = ( pPlayer && pPlayer->HasBombinomiconEffectOnDeath() );
|
||
|
|
||
|
if ( !m_bGib )
|
||
|
{
|
||
|
if ( m_bDissolving )
|
||
|
{
|
||
|
m_bDissolving = false;
|
||
|
m_flTimeToDissolve = 1.2f;
|
||
|
|
||
|
DissolveEntity( this );
|
||
|
EmitSound( "TFPlayer.Dissolve" );
|
||
|
|
||
|
// Dissolve all cosmetics as well
|
||
|
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
|
||
|
{
|
||
|
if ( pEntity->GetFollowedEntity() == this )
|
||
|
{
|
||
|
CEconEntity *pItem = dynamic_cast< CEconEntity * >( pEntity );
|
||
|
if ( pItem )
|
||
|
{
|
||
|
DissolveEntity( pItem );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( bBombinomicon && ( GetFlags() & FL_DISSOLVING ) )
|
||
|
{
|
||
|
m_flTimeToDissolve -= gpGlobals->frametime;
|
||
|
if ( m_flTimeToDissolve <= 0 )
|
||
|
{
|
||
|
CreateTFGibs( true, true );
|
||
|
}
|
||
|
}
|
||
|
else if ( m_bBecomeAsh )
|
||
|
{
|
||
|
m_flTimeToDissolve -= gpGlobals->frametime;
|
||
|
if ( m_flTimeToDissolve <= 0 )
|
||
|
{
|
||
|
if ( bBombinomicon )
|
||
|
{
|
||
|
CreateTFGibs( true, true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Hide the ragdoll and stop everything but the ash particle effect
|
||
|
AddEffects( EF_NODRAW );
|
||
|
ParticleProp()->StopParticlesNamed( "drg_fiery_death", true, true );
|
||
|
|
||
|
// Hide all cosmetics
|
||
|
for ( C_BaseEntity *pEntity = ClientEntityList().FirstBaseEntity(); pEntity; pEntity = ClientEntityList().NextBaseEntity(pEntity) )
|
||
|
{
|
||
|
if ( pEntity->GetFollowedEntity() == this )
|
||
|
{
|
||
|
CEconEntity *pItem = dynamic_cast< CEconEntity * >( pEntity );
|
||
|
if ( pItem )
|
||
|
{
|
||
|
pItem->AddEffects( EF_NODRAW );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else if ( bBombinomicon )
|
||
|
{
|
||
|
m_flTimeToDissolve -= gpGlobals->frametime;
|
||
|
if ( m_flTimeToDissolve <= 0 )
|
||
|
{
|
||
|
CreateTFGibs( true, true );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Gibbing
|
||
|
else
|
||
|
{
|
||
|
if ( m_bDissolving )
|
||
|
{
|
||
|
m_flTimeToDissolve -= gpGlobals->frametime;
|
||
|
if ( m_flTimeToDissolve <= 0 )
|
||
|
{
|
||
|
m_bDissolving = false;
|
||
|
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
if ( bBombinomicon )
|
||
|
{
|
||
|
CreateTFGibs( true, true );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for ( int i=0; i<pPlayer->m_hSpawnedGibs.Count(); i++ )
|
||
|
{
|
||
|
C_BaseEntity* pGib = pPlayer->m_hSpawnedGibs[i].Get();
|
||
|
if ( pGib )
|
||
|
{
|
||
|
pGib->SetAbsVelocity( vec3_origin );
|
||
|
DissolveEntity( pGib );
|
||
|
pGib->ParticleProp()->StopParticlesInvolving( pGib );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
EndFadeOut();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fade us away...
|
||
|
if ( m_bFadingOut == true )
|
||
|
{
|
||
|
int iAlpha = GetRenderColor().a;
|
||
|
int iFadeSpeed = 600.0f;
|
||
|
|
||
|
iAlpha = MAX( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 );
|
||
|
|
||
|
SetRenderMode( kRenderTransAlpha );
|
||
|
SetRenderColorA( iAlpha );
|
||
|
|
||
|
if ( iAlpha == 0 )
|
||
|
{
|
||
|
// Remove clientside ragdoll.
|
||
|
EndFadeOut();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If the player is looking at us, delay the fade.
|
||
|
if ( IsRagdollVisible() )
|
||
|
{
|
||
|
if ( cl_ragdoll_forcefade.GetBool() )
|
||
|
{
|
||
|
m_bFadingOut = true;
|
||
|
float flDelay = cl_ragdoll_fade_time.GetFloat() * 0.33f;
|
||
|
m_fDeathTime = gpGlobals->curtime + flDelay;
|
||
|
|
||
|
RemoveAllDecals();
|
||
|
}
|
||
|
|
||
|
// Fade out after the specified delay.
|
||
|
StartFadeOut( cl_ragdoll_fade_time.GetFloat() * 0.33f );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Remove us if our death time has passed.
|
||
|
if ( m_fDeathTime < gpGlobals->curtime )
|
||
|
{
|
||
|
EndFadeOut();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Fire an event if we were batted by the scout's taunt kill and we have come to rest.
|
||
|
if ( m_bBatted )
|
||
|
{
|
||
|
Vector vVelocity;
|
||
|
EstimateAbsVelocity( vVelocity );
|
||
|
if ( vVelocity.LengthSqr() == 0.f )
|
||
|
{
|
||
|
m_bBatted = false;
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "scout_slamdoll_landed" );
|
||
|
if ( event )
|
||
|
{
|
||
|
Vector absOrigin = GetAbsOrigin();
|
||
|
event->SetInt( "target_index", m_iPlayerIndex );
|
||
|
event->SetFloat( "x", absOrigin.x );
|
||
|
event->SetFloat( "y", absOrigin.y );
|
||
|
event->SetFloat( "z", absOrigin.z );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Deal with recording
|
||
|
void C_TFRagdoll::GetToolRecordingState( KeyValues *msg )
|
||
|
{
|
||
|
#ifndef _XBOX
|
||
|
BaseClass::GetToolRecordingState( msg );
|
||
|
|
||
|
if ( m_MaterialOverride.IsValid() )
|
||
|
{
|
||
|
msg->SetString( "materialOverride", m_MaterialOverride->GetName() );
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void C_TFRagdoll::DissolveEntity( CBaseEntity* pEnt )
|
||
|
{
|
||
|
C_EntityDissolve *pDissolve = DissolveEffect( pEnt, gpGlobals->curtime );
|
||
|
if ( pDissolve )
|
||
|
{
|
||
|
pDissolve->SetRenderMode( kRenderTransColor );
|
||
|
pDissolve->m_nRenderFX = kRenderFxNone;
|
||
|
pDissolve->SetRenderColor( 255, 255, 255, 255 );
|
||
|
|
||
|
Vector vColor;
|
||
|
if ( m_iTeam == TF_TEAM_BLUE )
|
||
|
{
|
||
|
vColor = TF_PARTICLE_WEAPON_RED_1 * 255;
|
||
|
pDissolve->SetEffectColor( vColor );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vColor = TF_PARTICLE_WEAPON_BLUE_1 * 255;
|
||
|
pDissolve->SetEffectColor( vColor );
|
||
|
}
|
||
|
|
||
|
pDissolve->m_vDissolverOrigin = GetAbsOrigin();
|
||
|
|
||
|
pDissolve->m_flFadeInStart = DISSOLVE_FADE_IN_START_TIME;
|
||
|
pDissolve->m_flFadeInLength = DISSOLVE_FADE_IN_END_TIME - DISSOLVE_FADE_IN_START_TIME;
|
||
|
|
||
|
pDissolve->m_flFadeOutModelStart = DISSOLVE_FADE_OUT_MODEL_START_TIME;
|
||
|
pDissolve->m_flFadeOutModelLength = DISSOLVE_FADE_OUT_MODEL_END_TIME - DISSOLVE_FADE_OUT_MODEL_START_TIME;
|
||
|
|
||
|
pDissolve->m_flFadeOutStart = DISSOLVE_FADE_OUT_START_TIME;
|
||
|
pDissolve->m_flFadeOutLength = DISSOLVE_FADE_OUT_END_TIME - DISSOLVE_FADE_OUT_START_TIME;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void C_TFRagdoll::StartFadeOut( float fDelay )
|
||
|
{
|
||
|
if ( !cl_ragdoll_forcefade.GetBool() )
|
||
|
{
|
||
|
m_fDeathTime = gpGlobals->curtime + fDelay;
|
||
|
}
|
||
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
||
|
}
|
||
|
|
||
|
|
||
|
void C_TFRagdoll::EndFadeOut()
|
||
|
{
|
||
|
SetNextClientThink( CLIENT_THINK_NEVER );
|
||
|
ClearRagdoll();
|
||
|
SetRenderMode( kRenderNone );
|
||
|
UpdateVisibility();
|
||
|
DestroyBoneAttachments();
|
||
|
|
||
|
// Remove attached effect entity
|
||
|
C_BaseEntity *pEffect = GetEffectEntity();
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
pEffect->SUB_Remove();
|
||
|
}
|
||
|
|
||
|
ParticleProp()->StopEmission();
|
||
|
|
||
|
// Hide attached wearables.
|
||
|
// These are server objects so they'll go away when the actual server ragdoll dies.
|
||
|
for ( int i=0; i<m_hRagWearables.Count(); ++i )
|
||
|
{
|
||
|
if ( m_hRagWearables[i] )
|
||
|
{
|
||
|
m_hRagWearables[i]->AddEffects( EF_NODRAW );
|
||
|
m_hRagWearables[i]->SetMoveType( MOVETYPE_NONE );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for spy invisiblity material
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CSpyInvisProxy : public CBaseInvisMaterialProxy
|
||
|
{
|
||
|
public:
|
||
|
CSpyInvisProxy( void );
|
||
|
virtual bool Init( IMaterial *pMaterial, KeyValues* pKeyValues ) OVERRIDE;
|
||
|
virtual void OnBind( C_BaseEntity *pBaseEntity ) OVERRIDE;
|
||
|
virtual void OnBindNotEntity( void *pRenderable ) OVERRIDE;
|
||
|
|
||
|
private:
|
||
|
IMaterialVar *m_pCloakColorTint;
|
||
|
};
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CSpyInvisProxy::CSpyInvisProxy( void )
|
||
|
{
|
||
|
m_pCloakColorTint = NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Get pointer to the color value
|
||
|
// Input : *pMaterial -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CSpyInvisProxy::Init( IMaterial *pMaterial, KeyValues* pKeyValues )
|
||
|
{
|
||
|
// Need to get the material var
|
||
|
bool bInvis = CBaseInvisMaterialProxy::Init( pMaterial, pKeyValues );
|
||
|
|
||
|
bool bTint;
|
||
|
m_pCloakColorTint = pMaterial->FindVar( "$cloakColorTint", &bTint );
|
||
|
|
||
|
return ( bInvis && bTint );
|
||
|
}
|
||
|
|
||
|
ConVar tf_teammate_max_invis( "tf_teammate_max_invis", "0.95", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input :
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CSpyInvisProxy::OnBind( C_BaseEntity *pBaseEntity )
|
||
|
{
|
||
|
if( !m_pPercentInvisible || !m_pCloakColorTint )
|
||
|
return;
|
||
|
|
||
|
float fInvis = 0.0f;
|
||
|
|
||
|
C_TFPlayer *pPlayer = ToTFPlayer( pBaseEntity );
|
||
|
|
||
|
if ( !pPlayer )
|
||
|
{
|
||
|
C_TFPlayer *pOwningPlayer = ToTFPlayer( pBaseEntity->GetOwnerEntity() );
|
||
|
|
||
|
C_TFRagdoll *pRagdoll = dynamic_cast< C_TFRagdoll* >( pBaseEntity );
|
||
|
if ( pRagdoll && pRagdoll->IsCloaked() )
|
||
|
{
|
||
|
fInvis = pRagdoll->GetPercentInvisible();
|
||
|
}
|
||
|
else if ( pOwningPlayer )
|
||
|
{
|
||
|
// mimic the owner's invisibility
|
||
|
fInvis = pOwningPlayer->GetEffectiveInvisibilityLevel();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float r = 1.0f, g = 1.0f, b = 1.0f;
|
||
|
fInvis = pPlayer->GetEffectiveInvisibilityLevel();
|
||
|
|
||
|
switch( pPlayer->GetTeamNumber() )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
r = 1.0; g = 0.5; b = 0.4;
|
||
|
break;
|
||
|
|
||
|
case TF_TEAM_BLUE:
|
||
|
default:
|
||
|
r = 0.4; g = 0.5; b = 1.0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
m_pCloakColorTint->SetVecValue( r, g, b );
|
||
|
}
|
||
|
|
||
|
m_pPercentInvisible->SetFloatValue( fInvis );
|
||
|
}
|
||
|
|
||
|
void CSpyInvisProxy::OnBindNotEntity( void *pRenderable )
|
||
|
{
|
||
|
CBaseInvisMaterialProxy::OnBindNotEntity( pRenderable );
|
||
|
|
||
|
if ( m_pCloakColorTint )
|
||
|
{
|
||
|
m_pCloakColorTint->SetVecValue( 1.f, 1.f, 1.f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EXPOSE_INTERFACE( CSpyInvisProxy, IMaterialProxy, "spy_invis" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for invulnerability material
|
||
|
// Returns 1 if the player is invulnerable, and 0 if the player is losing / doesn't have invuln.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyInvulnLevel : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
float flResult = 1.0;
|
||
|
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( !pEntity )
|
||
|
{
|
||
|
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
|
||
|
{
|
||
|
flResult = 1.0f;
|
||
|
}
|
||
|
m_pResult->SetFloatValue( flResult );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( pEntity->IsPlayer() )
|
||
|
{
|
||
|
pPlayer = dynamic_cast< C_TFPlayer* >( pEntity );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
IHasOwner *pOwnerInterface = dynamic_cast< IHasOwner* >( pEntity );
|
||
|
if ( pOwnerInterface )
|
||
|
{
|
||
|
pPlayer = ToTFPlayer( pOwnerInterface->GetOwnerViaInterface() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pPlayer && pPlayer->m_Shared.IsInvulnerable() && pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
|
||
|
{
|
||
|
flResult = 0.0;
|
||
|
}
|
||
|
|
||
|
m_pResult->SetFloatValue( flResult );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyInvulnLevel, IMaterialProxy, "InvulnLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for burning material on player models
|
||
|
// Returns 0.0->1.0 for level of burn to show on player skin
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyBurnLevel : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
float flResult = 0.0;
|
||
|
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( !pEntity )
|
||
|
{
|
||
|
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_BURN )
|
||
|
{
|
||
|
flResult = 1.0f;
|
||
|
}
|
||
|
m_pResult->SetFloatValue( flResult );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// default to zero
|
||
|
float flBurnStartTime = 0;
|
||
|
|
||
|
if ( pEntity->IsPlayer() )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = assert_cast< C_TFPlayer* >( pEntity );
|
||
|
// is the player burning?
|
||
|
if ( pPlayer->m_Shared.InCond( TF_COND_BURNING ) )
|
||
|
{
|
||
|
flBurnStartTime = pPlayer->m_flBurnEffectStartTime;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// is the ragdoll burning?
|
||
|
C_TFRagdoll *pRagDoll = dynamic_cast< C_TFRagdoll* >( pEntity );
|
||
|
if ( pRagDoll )
|
||
|
{
|
||
|
flBurnStartTime = pRagDoll->GetBurnStartTime();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if player/ragdoll is burning, set the burn level on the skin
|
||
|
if ( flBurnStartTime > 0 )
|
||
|
{
|
||
|
float flBurnPeakTime = flBurnStartTime + 0.3;
|
||
|
float flTempResult;
|
||
|
if ( gpGlobals->curtime < flBurnPeakTime )
|
||
|
{
|
||
|
// fade in from 0->1 in 0.3 seconds
|
||
|
flTempResult = RemapValClamped( gpGlobals->curtime, flBurnStartTime, flBurnPeakTime, 0.0, 1.0 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// fade out from 1->0 in the remaining time until flame extinguished
|
||
|
flTempResult = RemapValClamped( gpGlobals->curtime, flBurnPeakTime, flBurnStartTime + TF_BURNING_FLAME_LIFE, 1.0, 0.0 );
|
||
|
}
|
||
|
|
||
|
// We have to do some more calc here instead of in materialvars.
|
||
|
flResult = 1.0 - abs( flTempResult - 1.0 );
|
||
|
}
|
||
|
|
||
|
m_pResult->SetFloatValue( flResult );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyBurnLevel, IMaterialProxy, "BurnLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for turning player models yellow (jarate)
|
||
|
// Returns 0.0->1.0 for level of yellow to show on player skin
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyUrineLevel : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
// default to zero
|
||
|
Vector vResult = Vector( 1, 1, 1 );
|
||
|
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( !pEntity )
|
||
|
{
|
||
|
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_URINE )
|
||
|
{
|
||
|
if ( g_PlayerPreviewEffect.GetTeam() == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector ( 6, 9, 2 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector ( 7, 5, 1 );
|
||
|
}
|
||
|
}
|
||
|
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
if ( pEntity->IsPlayer() )
|
||
|
{
|
||
|
pPlayer = assert_cast< C_TFPlayer* >( pEntity );
|
||
|
}
|
||
|
else if ( pEntity->GetOwnerEntity() && pEntity->GetOwnerEntity()->IsPlayer() )
|
||
|
{
|
||
|
pPlayer = assert_cast< C_TFPlayer* >( pEntity->GetOwnerEntity() );
|
||
|
}
|
||
|
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
// is the player peed on?
|
||
|
if ( pPlayer->m_Shared.InCond( TF_COND_URINE ) )
|
||
|
{
|
||
|
int iVisibleTeam = pPlayer->GetTeamNumber();
|
||
|
if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
if ( !pPlayer->IsLocalPlayer() && iVisibleTeam != GetLocalPlayerTeam() )
|
||
|
{
|
||
|
iVisibleTeam = pPlayer->m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
}
|
||
|
if ( iVisibleTeam == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector ( 6, 9, 2 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector ( 7, 5, 1 );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector( 1, 1, 1 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyUrineLevel, IMaterialProxy, "YellowLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: CritBoosted FX
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyModelGlowColor : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( !pEntity )
|
||
|
{
|
||
|
Vector vResult = Vector( 1, 1, 1 );
|
||
|
#if 0 // It looks like this code path is never used
|
||
|
if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_CRIT )
|
||
|
{
|
||
|
if ( g_PlayerPreviewEffect.GetTeam() == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector ( 80, 8, 5 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector ( 5, 20, 80 );
|
||
|
}
|
||
|
}
|
||
|
#endif // 0
|
||
|
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// default to [1 1 1]
|
||
|
Vector vResult = Vector( 1, 1, 1 );
|
||
|
int iVisibleTeam = 0;
|
||
|
|
||
|
IHasOwner *pOwnerInterface = dynamic_cast< IHasOwner* >( pEntity );
|
||
|
if ( pOwnerInterface )
|
||
|
{
|
||
|
pPlayer = ToTFPlayer( pOwnerInterface->GetOwnerViaInterface() );
|
||
|
}
|
||
|
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
iVisibleTeam = pPlayer->GetTeamNumber();
|
||
|
if ( pPlayer->m_Shared.IsCritBoosted() )
|
||
|
{
|
||
|
// never show critboosted effect on a disguised spy (unless it's me)
|
||
|
if ( !pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) || pPlayer->IsLocalPlayer() )
|
||
|
{
|
||
|
if ( iVisibleTeam == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector ( 80, 8, 5 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector ( 5, 20, 80 );
|
||
|
}
|
||
|
}
|
||
|
pPlayer->m_Shared.m_bChargeGlowing = false;
|
||
|
}
|
||
|
else if ( pPlayer->m_Shared.IsHypeBuffed() )
|
||
|
{
|
||
|
vResult = Vector( 50, 2, 48 );
|
||
|
pPlayer->m_Shared.m_bChargeGlowing = false;
|
||
|
}
|
||
|
else if ( pPlayer->m_Shared.InCond( TF_COND_OFFENSEBUFF ) || pPlayer->m_Shared.InCond( TF_COND_ENERGY_BUFF ) )
|
||
|
{
|
||
|
// Temporarily hijacking this proxy for buff FX.
|
||
|
if ( iVisibleTeam == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector ( 226, 150, 62 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector( 29, 202, 135 );
|
||
|
}
|
||
|
pPlayer->m_Shared.m_bChargeGlowing = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( pPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) || (pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT) )
|
||
|
{
|
||
|
float flGlow;
|
||
|
if ( pPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
|
||
|
{
|
||
|
// Ramp up the charge glow while charging.
|
||
|
flGlow = (100.f - pPlayer->m_Shared.GetDemomanChargeMeter()) / 100.f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Cool down the charge glow after charging.
|
||
|
flGlow = 1.f - MIN((gpGlobals->curtime - pPlayer->m_Shared.m_flLastNoChargeTime - 1.5f) / 0.3f,1);
|
||
|
}
|
||
|
if ( iVisibleTeam == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector( MAX(80*flGlow,1), MAX(8*flGlow,1), MAX(5*flGlow,1) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector( MAX(5*flGlow,1), MAX(20*flGlow,1), MAX(80*flGlow,1) );
|
||
|
}
|
||
|
pPlayer->m_Shared.m_bChargeGlowing = true;
|
||
|
}
|
||
|
else if ( pPlayer->m_Shared.m_bChargeGlowing )
|
||
|
{
|
||
|
// Cool down the charge glow after charging.
|
||
|
float flGlow = 1.f - MIN( (gpGlobals->curtime - pPlayer->m_Shared.m_flLastNoChargeTime) / 0.3f, 1.f );
|
||
|
|
||
|
if ( flGlow <= 0 )
|
||
|
{
|
||
|
pPlayer->m_Shared.m_bChargeGlowing = false;
|
||
|
}
|
||
|
|
||
|
if ( iVisibleTeam == TF_TEAM_RED )
|
||
|
{
|
||
|
vResult = Vector( MAX(80*flGlow,1), MAX(8*flGlow,1), MAX(5*flGlow,1) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector( MAX(5*flGlow,1), MAX(20*flGlow,1), MAX(80*flGlow,1) );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vResult = Vector( 1, 1, 1 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyModelGlowColor, IMaterialProxy, "ModelGlowColor" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Proxy used to tell the material it's on a community weapon
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyCommunityWeapon : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
if ( pC_BaseEntity )
|
||
|
{
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( pEntity )
|
||
|
{
|
||
|
CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
|
||
|
if ( pItem )
|
||
|
{
|
||
|
CEconItemView *pScriptItem = pItem->GetAttributeContainer()->GetItem();
|
||
|
if ( pScriptItem && pScriptItem->GetStaticData() )
|
||
|
{
|
||
|
if ( pScriptItem->GetItemQuality() == AE_COMMUNITY )
|
||
|
{
|
||
|
m_pResult->SetIntValue( 1 );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_pResult->SetIntValue( 0 );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyCommunityWeapon, IMaterialProxy, "CommunityWeapon" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for scaling the beating heart texture to make it pulse
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyHeartbeatScale : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
const float pi = 3.141592f;
|
||
|
const float twoPI = 2.0f * pi;
|
||
|
|
||
|
float s1 = sin( gpGlobals->curtime * twoPI );
|
||
|
s1 = clamp( s1, 0.0f, 1.0f );
|
||
|
s1 *= s1;
|
||
|
s1 *= s1;
|
||
|
s1 = clamp( s1, 0.5f, 1.0f );
|
||
|
s1 -= 0.5f;
|
||
|
s1 *= 2.0f;
|
||
|
|
||
|
float s2 = sin( ( gpGlobals->curtime + 0.25f ) * twoPI );
|
||
|
s2 = clamp( s2, 0.0f, 1.0f );
|
||
|
s2 *= s2;
|
||
|
s2 *= s2;
|
||
|
s2 = clamp( s2, 0.5f, 1.0f );
|
||
|
s2 -= 0.5f;
|
||
|
s2 *= 2.0f;
|
||
|
|
||
|
float beat = MAX( s1, s2 );
|
||
|
|
||
|
const float scale = 0.6f;
|
||
|
const float loBeat = 1.0f * scale;
|
||
|
const float hiBeat = 0.8f * scale;
|
||
|
|
||
|
float scaledBeat = loBeat + ( hiBeat - loBeat ) * beat;
|
||
|
|
||
|
m_pResult->SetFloatValue( scaledBeat );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyHeartbeatScale, IMaterialProxy, "HeartbeatScale" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
ConVar tf_benefactor_gift_count( "tf_benefactor_gift_count", "-1", FCVAR_CHEAT, "For testing" );
|
||
|
#endif
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for scaling the beating heart based on the number of gifts player has given
|
||
|
// Returns a texture scale factor where 1 is max size (big benefactor) and factor gets larger the fewer gifts you've given
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyBenefactorLevel : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
CEconItemView *pScriptItem = NULL;
|
||
|
|
||
|
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
|
||
|
C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
|
||
|
if ( pEntity )
|
||
|
{
|
||
|
CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
|
||
|
if ( pItem )
|
||
|
{
|
||
|
pScriptItem = pItem->GetAttributeContainer()->GetItem();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pScriptItem )
|
||
|
{
|
||
|
static CSchemaAttributeDefHandle pAttrDef_KillEater( "kill eater" );
|
||
|
|
||
|
// Use the kill-eater prefix if the weapon has one.
|
||
|
uint32 unKillCount;
|
||
|
if ( !pScriptItem->FindAttribute( pAttrDef_KillEater, &unKillCount ) )
|
||
|
return;
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
int testCount = tf_benefactor_gift_count.GetInt();
|
||
|
if ( testCount >= 0 )
|
||
|
{
|
||
|
unKillCount = (uint32)testCount;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( unKillCount == 0 )
|
||
|
{
|
||
|
// heartless
|
||
|
m_pResult->SetFloatValue( 1000.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const int maxBenefatorLevel = 250;
|
||
|
|
||
|
float value = (float)unKillCount / (float)maxBenefatorLevel;
|
||
|
|
||
|
value = clamp( value, 0.0f, 1.0f );
|
||
|
|
||
|
// a linear scale doesn't show the size change until the very end - nonlinearize it
|
||
|
value = sin( value * 1.57f );
|
||
|
value = sin( value * 1.57f );
|
||
|
|
||
|
// this seems backwards because this is a texture scaling factor
|
||
|
// the bigger the number, the smaller the textured image is (tiles more in same space)
|
||
|
const float minValue = 5.0f;
|
||
|
const float maxValue = 1.0f;
|
||
|
|
||
|
m_pResult->SetFloatValue( minValue + ( maxValue - minValue ) * value );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyBenefactorLevel, IMaterialProxy, "BenefactorLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for scaling the oscilloscope on the Building Rescue Gun
|
||
|
// Flattens the Wave when the player has no energy
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyBuildingRescueLevel : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pPlayer )
|
||
|
return;
|
||
|
|
||
|
int iIncreasedRangeCost = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iIncreasedRangeCost, building_teleporting_pickup );
|
||
|
if ( iIncreasedRangeCost == 0 )
|
||
|
return;
|
||
|
|
||
|
CTFWeaponBase * pWeapon = pPlayer->GetActiveTFWeapon();
|
||
|
if ( !pWeapon )
|
||
|
return;
|
||
|
|
||
|
int iAmmo = pPlayer->GetAmmoCount( TF_AMMO_METAL );
|
||
|
|
||
|
float scale = 1.0f;
|
||
|
if ( iAmmo < iIncreasedRangeCost )
|
||
|
{
|
||
|
scale = 10.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
scale = ( 3.0f - ((float)(iAmmo - iIncreasedRangeCost) / (float)(pPlayer->GetMaxAmmo( TF_AMMO_METAL ) - iIncreasedRangeCost) * 3.0f ) + 1.0f );
|
||
|
}
|
||
|
|
||
|
VMatrix mat, temp;
|
||
|
|
||
|
Vector2D center( 0.5, 0.5 );
|
||
|
MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
|
||
|
|
||
|
// scale
|
||
|
{
|
||
|
MatrixBuildScale( temp, 1.0f, scale, 1.0f );
|
||
|
MatrixMultiply( temp, mat, mat );
|
||
|
}
|
||
|
|
||
|
MatrixBuildTranslation( temp, center.x, center.y, 0.0f );
|
||
|
MatrixMultiply( temp, mat, mat );
|
||
|
|
||
|
m_pResult->SetMatrixValue( mat );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyBuildingRescueLevel, IMaterialProxy, "BuildingRescueLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Used to pulse the Vaccinator's uber shield
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyResistShield : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
|
||
|
C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
|
||
|
if ( pEntity )
|
||
|
{
|
||
|
C_LocalTempEntity* pTempEnt = dynamic_cast<C_LocalTempEntity*>(pEntity);
|
||
|
if ( pTempEnt )
|
||
|
{
|
||
|
C_BaseEntity *pBaseEnt = cl_entitylist->GetEnt( pTempEnt->clientIndex );
|
||
|
// This should be the owning player
|
||
|
C_TFPlayer* pTFPlayer = ToTFPlayer( pBaseEnt );
|
||
|
if( pTFPlayer )
|
||
|
{
|
||
|
float flTimeSince = gpGlobals->curtime - pTFPlayer->GetLastResistTime();
|
||
|
float flOut = RemapValClamped( flTimeSince, 0, 0.4f, 7.f, -4.f );
|
||
|
m_pResult->SetVecValue( flOut, flOut, flOut, 1.f );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
m_pResult->SetVecValue( 1.0, 1.0, 1.0, 1.0 );
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyResistShield, IMaterialProxy, "ShieldFalloff" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for pulsing the Wheatly Sappers eye glow when he talks
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyWheatlyEyeGlow : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
Assert( m_pResult );
|
||
|
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pPlayer )
|
||
|
return;
|
||
|
|
||
|
const float flBase = 0.4f;
|
||
|
const float flMinTalking = 0.6f;
|
||
|
const float flMaxTalking = 1.3f;
|
||
|
const float flAdjustUp = 0.01f;
|
||
|
const float flAdjustDown = -0.006f;
|
||
|
|
||
|
static float s_flCurr = 0.2f;
|
||
|
static float s_flDir = 0.2f;
|
||
|
|
||
|
const float flEyeHold = 1.9f;
|
||
|
const float flMinEyes = 0.1f;
|
||
|
const float flMaxEyes = 0.35f;
|
||
|
const float flEyeAdjust = 0.005f;
|
||
|
|
||
|
static float s_flEyePose = 0.0f;
|
||
|
static float s_flNextEyeChange = 0;
|
||
|
static float s_flEyeDir = 0.005f;
|
||
|
|
||
|
C_TFWeaponSapper *pWeapon = dynamic_cast< C_TFWeaponSapper* >( pPlayer->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING ) );
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
if ( pWeapon->IsWheatleyTalking() )
|
||
|
{
|
||
|
float flNoise = RandomGaussianFloat( 0.0f, 0.01f );
|
||
|
s_flCurr += s_flDir + flNoise;
|
||
|
if ( s_flCurr > flMaxTalking && s_flDir > 0 )
|
||
|
{
|
||
|
s_flDir = flAdjustDown;
|
||
|
}
|
||
|
else if ( s_flCurr < flMinTalking && s_flDir < 0 )
|
||
|
{
|
||
|
s_flDir = flAdjustUp;
|
||
|
}
|
||
|
|
||
|
// Animate to eye's and hold for a few seconds
|
||
|
float currTime = gpGlobals->curtime;
|
||
|
if ( currTime > s_flNextEyeChange)
|
||
|
{
|
||
|
s_flEyePose += s_flEyeDir;
|
||
|
|
||
|
if ( s_flEyePose > flMaxEyes && s_flEyeDir > 0 )
|
||
|
{
|
||
|
s_flNextEyeChange = currTime + flEyeHold;
|
||
|
s_flEyeDir = -flEyeAdjust;
|
||
|
}
|
||
|
if ( s_flEyePose < flMinEyes && s_flEyeDir < 0 )
|
||
|
{
|
||
|
s_flNextEyeChange = currTime + flEyeHold;
|
||
|
s_flEyeDir = flEyeAdjust;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// adjust towards base
|
||
|
s_flCurr += flAdjustDown;
|
||
|
if ( s_flCurr < flBase )
|
||
|
{
|
||
|
s_flCurr = flBase;
|
||
|
s_flDir = flAdjustUp;
|
||
|
}
|
||
|
|
||
|
if ( s_flEyePose > 0 )
|
||
|
{
|
||
|
s_flEyePose -= flEyeAdjust;
|
||
|
}
|
||
|
s_flEyeDir = flEyeAdjust;
|
||
|
}
|
||
|
|
||
|
CBaseViewModel *pViewModel = pPlayer->GetViewModel(0);
|
||
|
if ( pViewModel )
|
||
|
{
|
||
|
pViewModel->SetPoseParameter( "eyelids", s_flEyePose );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_pResult->SetFloatValue( s_flCurr );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyWheatlyEyeGlow, IMaterialProxy, "WheatlyEyeGlow" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CEconItemView *GetEconItemViewFromProxyEntity( void *pEntity )
|
||
|
{
|
||
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||
|
|
||
|
IClientRenderable *pRend = (IClientRenderable *)pEntity;
|
||
|
CBaseEntity *pBaseEntity = pRend ? pRend->GetIClientUnknown()->GetBaseEntity() : NULL;
|
||
|
|
||
|
// If an entity, find out what types it is and get the econ item view
|
||
|
if ( pBaseEntity )
|
||
|
{
|
||
|
// Generic Entity that has an Item ( Weapons / Hats )
|
||
|
IHasAttributes *pAttribInterface = GetAttribInterface( pBaseEntity );
|
||
|
if ( pAttribInterface )
|
||
|
return pAttribInterface->GetAttributeContainer()->GetItem();
|
||
|
|
||
|
// ViewModel Attachment (aka view model Weapon)
|
||
|
C_ViewmodelAttachmentModel *pViewModelAttachment = dynamic_cast<C_ViewmodelAttachmentModel*>( pBaseEntity );
|
||
|
if ( pViewModelAttachment && pViewModelAttachment->GetOuter() )
|
||
|
{
|
||
|
return pViewModelAttachment->GetOuter()->GetAttributeContainer()->GetItem();
|
||
|
}
|
||
|
|
||
|
CTFViewModel *pViewModel = dynamic_cast<CTFViewModel*>( pBaseEntity );
|
||
|
if ( pViewModel && pViewModel->GetWeapon() )
|
||
|
{
|
||
|
return pViewModel->GetWeapon()->GetAttributeContainer()->GetItem();
|
||
|
}
|
||
|
|
||
|
CTFDroppedWeapon *pDroppedWeapon = dynamic_cast<CTFDroppedWeapon*>( pBaseEntity );
|
||
|
if ( pDroppedWeapon && pDroppedWeapon->GetItem() && pDroppedWeapon->GetItem()->GetItemDefIndex() != INVALID_ITEM_DEF_INDEX )
|
||
|
{
|
||
|
return pDroppedWeapon->GetItem();
|
||
|
}
|
||
|
}
|
||
|
// No direct entity, might be a EconItem (PlayerModelPanels)
|
||
|
else
|
||
|
{
|
||
|
CEconItemView *pItem = dynamic_cast< CEconItemView* >( pRend );
|
||
|
if ( pItem )
|
||
|
{
|
||
|
return pItem;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
C_TFPlayer *GetOwnerFromProxyEntity( void *pEntity )
|
||
|
{
|
||
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||
|
|
||
|
IClientRenderable *pRend = ( IClientRenderable * ) pEntity;
|
||
|
CBaseEntity *pBaseEntity = pRend ? pRend->GetIClientUnknown()->GetBaseEntity() : NULL;
|
||
|
|
||
|
// If an entity, find out what types it is and get the econ item view
|
||
|
if ( pBaseEntity )
|
||
|
{
|
||
|
CBaseEntity* pOwner = pBaseEntity->GetOwnerEntity();
|
||
|
if ( pOwner )
|
||
|
return dynamic_cast<C_TFPlayer*>( pOwner->GetOwnerEntity() );
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used to animate the weapon sheen effect for kill streak attr items
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CProxyAnimatedWeaponSheen : public CBaseAnimatedTextureProxy
|
||
|
{
|
||
|
public:
|
||
|
CProxyAnimatedWeaponSheen() {}
|
||
|
virtual ~CProxyAnimatedWeaponSheen() {}
|
||
|
|
||
|
bool Init( IMaterial *pMaterial, KeyValues *pKeyValues )
|
||
|
{
|
||
|
bool foundVar = false;
|
||
|
m_flNextStartTime = 0;
|
||
|
m_flScaleX = -1;
|
||
|
m_flScaleY = -1;
|
||
|
m_flSheenOffsetX = 0;
|
||
|
m_flSheenOffsetY = 0;
|
||
|
m_iSheenDir = 0;
|
||
|
|
||
|
m_pScaleXVar = NULL;
|
||
|
m_pScaleYVar = NULL;
|
||
|
m_pOffsetXVar = NULL;
|
||
|
m_pOffsetYVar = NULL;
|
||
|
m_pDirectionVar = NULL;
|
||
|
m_pSheenIndexVar = NULL;
|
||
|
|
||
|
// Get Tint
|
||
|
m_pTintVar = pMaterial->FindVar( "$sheenmaptint", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pSheenIndexVar = pMaterial->FindVar( "$sheenindex", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
// Material vars for scale and offset
|
||
|
// Need to get the material var
|
||
|
m_pScaleXVar = pMaterial->FindVar( "$sheenmapmaskscaleX", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pScaleYVar = pMaterial->FindVar( "$sheenmapmaskscaleY", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pOffsetXVar = pMaterial->FindVar( "$sheenmapmaskoffsetX", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pOffsetYVar = pMaterial->FindVar( "$sheenmapmaskoffsetY", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pDirectionVar = pMaterial->FindVar( "$sheenmapmaskdirection", &foundVar );
|
||
|
if( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pSheenVar = pMaterial->FindVar( "$sheenmap", &foundVar );
|
||
|
if ( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
m_pSheenMaskVar = pMaterial->FindVar( "$sheenmapmask", &foundVar );
|
||
|
if ( !foundVar )
|
||
|
return false;
|
||
|
|
||
|
return CBaseAnimatedTextureProxy::Init( pMaterial, pKeyValues );
|
||
|
}
|
||
|
|
||
|
void OnBind( void *pEntity )
|
||
|
{
|
||
|
Assert( m_AnimatedTextureVar );
|
||
|
if ( m_AnimatedTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
|
||
|
return;
|
||
|
|
||
|
// if no entity, just check the owner
|
||
|
if ( !pEntity )
|
||
|
{
|
||
|
// Might be gunslinger, hard to get this item so
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static CSchemaAttributeDefHandle pAttr_killstreak( "killstreak idleeffect" );
|
||
|
if ( !pAttr_killstreak )
|
||
|
return;
|
||
|
|
||
|
IClientRenderable *pRend = (IClientRenderable *)pEntity;
|
||
|
// Find the weapon and player and see if it has the attribute
|
||
|
if ( !pRend )
|
||
|
return;
|
||
|
|
||
|
ITexture *pTexture;
|
||
|
pTexture = m_AnimatedTextureVar->GetTextureValue();
|
||
|
int numFrames = pTexture->GetNumAnimationFrames();
|
||
|
|
||
|
if ( numFrames <= 0 )
|
||
|
{
|
||
|
Assert( !"0 frames in material calling animated texture proxy" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
C_BaseEntity *pBaseEntity = pRend->GetIClientUnknown()->GetBaseEntity();
|
||
|
const CEconItemView *pItem = dynamic_cast< CEconItemView* >( pRend );
|
||
|
|
||
|
uint32 unAttrValue = 0;
|
||
|
uint32 unEffectValue = 0;
|
||
|
|
||
|
// !TEST!
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_killstreak_alwayson.GetBool() )
|
||
|
{
|
||
|
unEffectValue = 1;
|
||
|
}
|
||
|
if ( tf_sheen_all.GetFloat() != 0 )
|
||
|
{
|
||
|
unEffectValue = tf_sheen_all.GetInt();
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
// Find the tf player owner
|
||
|
bool bIsFirstPerson = false;
|
||
|
C_TFPlayer* pTFPlayer = NULL;
|
||
|
if ( pItem ) // ItemModelPanels
|
||
|
{
|
||
|
if ( !pBaseEntity )
|
||
|
{
|
||
|
pItem->FindAttribute( pAttr_killstreak, &unAttrValue );
|
||
|
unEffectValue = (int)((float&)unAttrValue);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBaseEntity, unAttrValue, killstreak_idleeffect );
|
||
|
}
|
||
|
|
||
|
if ( ( unEffectValue ) && pBaseEntity && pBaseEntity->GetOwnerEntity() )
|
||
|
{
|
||
|
pTFPlayer = ToTFPlayer( pBaseEntity->GetOwnerEntity() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CTFWeaponBase* pWeapon = dynamic_cast<CTFWeaponBase*>( pBaseEntity );
|
||
|
if ( !pWeapon )
|
||
|
{
|
||
|
for ( int i = 0; i < 1; ++i )
|
||
|
{
|
||
|
CEconWearable* pWearable = dynamic_cast<CEconWearable*>( pBaseEntity );
|
||
|
if ( pWearable )
|
||
|
{
|
||
|
pItem = pWearable->GetAttributeContainer()->GetItem();
|
||
|
pTFPlayer = ToTFPlayer( pWearable->GetOwnerEntity() );
|
||
|
break;
|
||
|
}
|
||
|
C_ViewmodelAttachmentModel *pModel = dynamic_cast<C_ViewmodelAttachmentModel*>( pBaseEntity );
|
||
|
if ( pModel )
|
||
|
{
|
||
|
if ( pModel->GetOuter() )
|
||
|
{
|
||
|
pItem = pModel->GetOuter()->GetAttributeContainer()->GetItem();
|
||
|
pBaseEntity = pBaseEntity->GetOwnerEntity();
|
||
|
if ( pItem )
|
||
|
{
|
||
|
pTFPlayer = ToTFPlayer( pModel->GetOuter()->GetOwnerEntity() );
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// not a weapon, is a viewmodel
|
||
|
IHasOwner *pHasOwner = dynamic_cast<IHasOwner*>( pBaseEntity );
|
||
|
if ( pHasOwner )
|
||
|
{
|
||
|
// View model owner is player, so get the players active weapon
|
||
|
CBaseEntity *pOwner = pHasOwner->GetOwnerViaInterface();
|
||
|
pTFPlayer = ToTFPlayer( pOwner );
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
pWeapon = pTFPlayer->GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pItem = pWeapon->GetAttributeContainer()->GetItem();
|
||
|
pBaseEntity = pWeapon;
|
||
|
}
|
||
|
bIsFirstPerson = true;
|
||
|
}
|
||
|
}
|
||
|
else if ( pBaseEntity && pBaseEntity->IsPlayer( ) )
|
||
|
{
|
||
|
pTFPlayer = ToTFPlayer( pBaseEntity );
|
||
|
pWeapon = pTFPlayer->GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pItem = pWeapon->GetAttributeContainer()->GetItem();
|
||
|
pBaseEntity = pWeapon;
|
||
|
}
|
||
|
}
|
||
|
} // for
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pItem = pWeapon->GetAttributeContainer()->GetItem();
|
||
|
pBaseEntity = pWeapon;
|
||
|
pTFPlayer = ToTFPlayer( pWeapon->GetOwner() );
|
||
|
}
|
||
|
|
||
|
// I have an econ item, does it have the attr
|
||
|
if ( ( pBaseEntity && pItem ) || unEffectValue )
|
||
|
{
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pBaseEntity, unAttrValue, killstreak_idleeffect );
|
||
|
|
||
|
if ( !unEffectValue )
|
||
|
{
|
||
|
unEffectValue = unAttrValue;
|
||
|
}
|
||
|
|
||
|
// Use the Spies target if disguised and we're on different teams
|
||
|
if ( pTFPlayer && pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->GetTeamNumber() != GetLocalPlayerTeam() )
|
||
|
{
|
||
|
pTFPlayer = ToTFPlayer( pTFPlayer->m_Shared.GetDisguiseTarget() );
|
||
|
if ( pTFPlayer && pTFPlayer->m_Shared.GetDisguiseWeapon() )
|
||
|
{
|
||
|
pItem = pTFPlayer->m_Shared.GetDisguiseWeapon()->GetAttributeContainer()->GetItem();
|
||
|
pBaseEntity = pTFPlayer->m_Shared.GetDisguiseWeapon();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float flNextStartTime = 0;
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
// if player is taunting, make sure they can start animating
|
||
|
if ( pTFPlayer->IsTaunting() && flNextStartTime > gpGlobals->curtime )
|
||
|
{
|
||
|
pTFPlayer->m_flNextSheenStartTime = gpGlobals->curtime;
|
||
|
}
|
||
|
flNextStartTime = pTFPlayer->m_flNextSheenStartTime;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
flNextStartTime = m_flNextStartTime;
|
||
|
}
|
||
|
|
||
|
// !TEST!
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_killstreak_alwayson.GetBool() )
|
||
|
{
|
||
|
unEffectValue = 1;
|
||
|
}
|
||
|
|
||
|
if ( tf_sheen_all.GetFloat() != 0 )
|
||
|
{
|
||
|
unEffectValue = tf_sheen_all.GetInt();
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
// Not ready, so just exit
|
||
|
if ( !pItem || !unEffectValue || flNextStartTime > gpGlobals->curtime || unEffectValue > ARRAYSIZE( g_KillStreakEffectsBase ) - 1 )
|
||
|
{
|
||
|
RunNoProxy();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Negative Value implies it has not been set, set it now
|
||
|
if ( m_flScaleX < 0 )
|
||
|
{
|
||
|
if ( !InitParams( pRend, pBaseEntity ) )
|
||
|
{
|
||
|
RunNoProxy();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NOTE: Must not use relative time based methods here
|
||
|
// because the bind proxy can be called many times per frame.
|
||
|
// Prevent multiple Wrap callbacks to be sent for no wrap mode
|
||
|
float startTime = pTFPlayer ? pTFPlayer->m_flNextSheenStartTime : 0;
|
||
|
float deltaTime = gpGlobals->curtime - startTime;
|
||
|
float prevTime = deltaTime - gpGlobals->frametime;
|
||
|
|
||
|
// Clamp..
|
||
|
if (deltaTime < 0.0f)
|
||
|
deltaTime = 0.0f;
|
||
|
if (prevTime < 0.0f)
|
||
|
prevTime = 0.0f;
|
||
|
|
||
|
// Code Frame rate to be 25
|
||
|
float frame = tf_sheen_framerate.GetInt() * deltaTime;
|
||
|
float prevFrame = tf_sheen_framerate.GetInt() * prevTime;
|
||
|
|
||
|
int intFrame = ((int)frame) % numFrames;
|
||
|
int intPrevFrame = ((int)prevFrame) % numFrames;
|
||
|
|
||
|
// Report wrap situation...
|
||
|
if ( intPrevFrame > intFrame )
|
||
|
{
|
||
|
// Set frame to zero and set the time for the next
|
||
|
intFrame = 0;
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
pTFPlayer->m_flNextSheenStartTime = gpGlobals->curtime + GetTimeBetweenAnims( pTFPlayer );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_flNextStartTime = gpGlobals->curtime + GetTimeBetweenAnims( NULL );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||
|
killstreak_params_t sheenParams = GetSheenParams( unEffectValue, pTFPlayer ? pTFPlayer->GetTeamNumber() == TF_TEAM_BLUE : false );
|
||
|
color[ 0 ] = sheenParams.m_sheen_r;
|
||
|
color[ 1 ] = sheenParams.m_sheen_g;
|
||
|
color[ 2 ] = sheenParams.m_sheen_b;
|
||
|
color[ 3 ] = sheenParams.m_sheen_a;
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_sheen_color_override_a.GetFloat() > 0 )
|
||
|
{
|
||
|
color[0] = tf_sheen_color_override_r.GetFloat();
|
||
|
color[1] = tf_sheen_color_override_g.GetFloat();
|
||
|
color[2] = tf_sheen_color_override_b.GetFloat();
|
||
|
color[3] = tf_sheen_color_override_a.GetFloat();
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
if ( bIsFirstPerson )
|
||
|
{
|
||
|
color[ 3 ] = tf_sheen_alpha_firstperson.GetFloat();
|
||
|
m_pTintVar->SetVecValue( color, 4 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pTintVar->SetVecValue( color, 4 );
|
||
|
}
|
||
|
|
||
|
// Set vars
|
||
|
m_AnimatedTextureFrameNumVar->SetIntValue( intFrame );
|
||
|
m_pScaleXVar->SetFloatValue( m_flScaleX ); // Only need to set once?
|
||
|
m_pScaleYVar->SetFloatValue( m_flScaleY );
|
||
|
m_pOffsetXVar->SetFloatValue( m_flSheenOffsetX );
|
||
|
m_pOffsetYVar->SetFloatValue( m_flSheenOffsetY );
|
||
|
m_pDirectionVar->SetIntValue( m_iSheenDir );
|
||
|
|
||
|
int iShaderIndex = sheenParams.m_iShaderIndex;
|
||
|
|
||
|
// Australium weapons always use iShaderIndex 1
|
||
|
const CEconStyleInfo *pStyle = pItem->GetStaticData()->GetStyleInfo( pItem->GetItemStyle() );
|
||
|
if ( pStyle && !pStyle->IsSelectable() )
|
||
|
{
|
||
|
iShaderIndex = 1;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_sheen_shader_override.GetInt() > 0 )
|
||
|
{
|
||
|
iShaderIndex = tf_sheen_shader_override.GetInt();
|
||
|
}
|
||
|
#endif // staging_only
|
||
|
|
||
|
m_pSheenIndexVar->SetIntValue( iShaderIndex );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// the last time the animation was run (or will allowed to be run)
|
||
|
float GetAnimationStartTime( void* pBaseEntity )
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
float GetTimeBetweenAnims ( C_TFPlayer* pTFPlayer )
|
||
|
{
|
||
|
const float MAX_SHEEN_WAIT = 5.0f;
|
||
|
const float MAX_KILLS = 5.0f;
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_sheen_fast.GetBool() )
|
||
|
return 0;
|
||
|
#endif
|
||
|
|
||
|
if ( !pTFPlayer )
|
||
|
return MAX_SHEEN_WAIT;
|
||
|
|
||
|
if ( pTFPlayer->IsTaunting() )
|
||
|
return 0;
|
||
|
|
||
|
// Set the time between sheens based on kill streak
|
||
|
int iStreak = pTFPlayer->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills );
|
||
|
|
||
|
if ( iStreak >= MAX_KILLS )
|
||
|
return 0;
|
||
|
|
||
|
if ( iStreak == 0 )
|
||
|
return MAX_SHEEN_WAIT;
|
||
|
|
||
|
// as player gets more kills, time decreases
|
||
|
return ( 1.0f - ( iStreak / MAX_KILLS ) ) * MAX_SHEEN_WAIT;
|
||
|
}
|
||
|
|
||
|
killstreak_params_t GetSheenParams( uint32 unEffectValue, bool bIsTeamBlue )
|
||
|
{
|
||
|
Assert( unEffectValue > 0 && unEffectValue < ARRAYSIZE( g_KillStreakEffectsBase ) );
|
||
|
killstreak_params_t params = g_KillStreakEffectsBase[ unEffectValue ];
|
||
|
|
||
|
if ( bIsTeamBlue && params.m_bHasTeamColor )
|
||
|
{
|
||
|
Assert( unEffectValue > 0 && unEffectValue < ARRAYSIZE( g_KillStreakEffectsBlue ) );
|
||
|
return g_KillStreakEffectsBlue[ unEffectValue ];
|
||
|
}
|
||
|
return params;
|
||
|
}
|
||
|
|
||
|
bool InitParams( IClientRenderable *pRend, C_BaseEntity *pBaseEntity )
|
||
|
{
|
||
|
// Negative Value implies it has not been set, set it now
|
||
|
if ( m_flScaleX < 0 )
|
||
|
{
|
||
|
Vector vMin, vMax;
|
||
|
|
||
|
// Check if the baseEntity is ready
|
||
|
if ( pBaseEntity )
|
||
|
{
|
||
|
CBaseAnimating *pAnimating = dynamic_cast< CBaseAnimating * > ( pBaseEntity );
|
||
|
if ( !pAnimating || !pAnimating->GetModelPtr() || pAnimating->GetModelPtr()->GetNumSeq() < pAnimating->GetSequence() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pRend->GetRenderBounds( vMin, vMax );
|
||
|
m_flScaleX = vMax.x - vMin.x;
|
||
|
m_flSheenOffsetX = vMin.x;
|
||
|
|
||
|
m_flScaleY = vMax.z - vMin.z;
|
||
|
m_flSheenOffsetY = vMin.z;
|
||
|
m_iSheenDir = 0;
|
||
|
|
||
|
if ( vMax.y - vMin.y > m_flScaleX )
|
||
|
{
|
||
|
m_flScaleX = vMax.y - vMin.y;
|
||
|
m_flSheenOffsetX = vMin.y;
|
||
|
|
||
|
m_flScaleY = vMax.x - vMin.x;
|
||
|
m_flSheenOffsetY = vMin.x;
|
||
|
|
||
|
m_iSheenDir = 1;
|
||
|
}
|
||
|
|
||
|
if ( vMax.z - vMin.z > m_flScaleX )
|
||
|
{
|
||
|
m_flScaleX = vMax.z - vMin.z;
|
||
|
m_flSheenOffsetX = vMin.z;
|
||
|
|
||
|
m_flScaleY = vMax.y - vMin.y;
|
||
|
m_flSheenOffsetY = vMin.y;
|
||
|
|
||
|
m_iSheenDir = 2;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void Cleanup()
|
||
|
{
|
||
|
m_pTintVar = NULL;
|
||
|
CBaseAnimatedTextureProxy::Cleanup();
|
||
|
}
|
||
|
|
||
|
void RunNoProxy ()
|
||
|
{
|
||
|
m_pTintVar->SetVecValue( 0, 0, 0 );
|
||
|
m_AnimatedTextureFrameNumVar->SetIntValue( 0 );
|
||
|
m_pSheenIndexVar->SetIntValue( 0 );
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
|
||
|
IMaterialVar *m_pSheenIndexVar;
|
||
|
IMaterialVar *m_pTintVar;
|
||
|
|
||
|
IMaterialVar *m_pSheenVar; // Overloaded for Weapon Pattern
|
||
|
IMaterialVar *m_pSheenMaskVar; // Weapon Pattern Mask
|
||
|
|
||
|
IMaterialVar *m_pScaleXVar;
|
||
|
IMaterialVar *m_pScaleYVar;
|
||
|
IMaterialVar *m_pOffsetXVar;
|
||
|
IMaterialVar *m_pOffsetYVar;
|
||
|
IMaterialVar *m_pDirectionVar;
|
||
|
|
||
|
float m_flNextStartTime; // Used in the rare case of playermodelpanels with no local player
|
||
|
|
||
|
float m_flScaleX;
|
||
|
float m_flScaleY;
|
||
|
float m_flSheenOffsetX;
|
||
|
float m_flSheenOffsetY;
|
||
|
int m_iSheenDir;
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CProxyAnimatedWeaponSheen, IMaterialProxy, "AnimatedWeaponSheen" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
// StatTrack Proxy
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// StatTrakIllum proxy
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CStatTrakIllumProxy : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
|
||
|
virtual void OnBind( void *pC_BaseEntity );
|
||
|
|
||
|
private:
|
||
|
CFloatInput m_flMinVal;
|
||
|
CFloatInput m_flMaxVal;
|
||
|
};
|
||
|
|
||
|
|
||
|
bool CStatTrakIllumProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
|
||
|
{
|
||
|
if ( !CResultProxy::Init( pMaterial, pKeyValues ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !m_flMinVal.Init( pMaterial, pKeyValues, "minVal", 0.5 ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !m_flMaxVal.Init( pMaterial, pKeyValues, "maxVal", 1 ) )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CStatTrakIllumProxy::OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
|
||
|
if ( !pC_BaseEntity )
|
||
|
return;
|
||
|
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( pEntity )
|
||
|
{
|
||
|
// StatTrak modules are children of their accompanying viewmodels
|
||
|
C_BaseViewModel *pViewModel = dynamic_cast<C_BaseViewModel*>( pEntity->GetMoveParent() );
|
||
|
if ( pViewModel )
|
||
|
{
|
||
|
//SetFloatResult( Lerp( pViewModel->GetStatTrakGlowMultiplier(), m_flMinVal.GetFloat(), m_flMaxVal.GetFloat() ) );
|
||
|
SetFloatResult( 0.75f );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
EXPOSE_INTERFACE( CStatTrakIllumProxy, IMaterialProxy, "StatTrakIllum" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// StatTrak 'kill odometer' support: given a numerical value expressed as a string, pick a texture frame to represent a given digit
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CStatTrakDigitProxy : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
|
||
|
virtual void OnBind( void *pC_BaseEntity );
|
||
|
|
||
|
virtual bool HelperOnBindGetStatTrakScore( void *pC_BaseEntity, int *piScore );
|
||
|
|
||
|
private:
|
||
|
CFloatInput m_flDisplayDigit; // the particular digit we want to display
|
||
|
CFloatInput m_flTrimZeros;
|
||
|
};
|
||
|
|
||
|
|
||
|
bool CStatTrakDigitProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
|
||
|
{
|
||
|
if ( !CResultProxy::Init( pMaterial, pKeyValues ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !m_flDisplayDigit.Init( pMaterial, pKeyValues, "displayDigit", 0 ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !m_flTrimZeros.Init( pMaterial, pKeyValues, "trimZeros", 0 ) )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool CStatTrakDigitProxy::HelperOnBindGetStatTrakScore( void *pC_BaseEntity, int *piScore )
|
||
|
{
|
||
|
if ( !pC_BaseEntity )
|
||
|
return false;
|
||
|
|
||
|
if ( !piScore )
|
||
|
return false;
|
||
|
|
||
|
bool bReturnValue = false;
|
||
|
uint32 unScore = 0;
|
||
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
if ( pEntity )
|
||
|
{
|
||
|
// StatTrak modules are children of their accompanying viewmodels
|
||
|
C_ViewmodelAttachmentModel *pViewModel = dynamic_cast<C_ViewmodelAttachmentModel*>( pEntity->GetMoveParent() );
|
||
|
if ( pViewModel )
|
||
|
{
|
||
|
//C_TFPlayer *pPlayer = ToTFPlayer( pViewModel->GetOwnerEntity() );
|
||
|
//if ( pPlayer )
|
||
|
CTFWeaponBase *pWeap = dynamic_cast<CTFWeaponBase*>( pViewModel->GetOwnerEntity() );
|
||
|
if ( pWeap )
|
||
|
{
|
||
|
|
||
|
// Use the strange prefix if the weapon has one.
|
||
|
if ( pWeap->GetAttributeContainer()->GetItem()->FindAttribute( GetKillEaterAttr_Score( 0 ), &unScore ) )
|
||
|
{
|
||
|
*piScore = unScore;
|
||
|
bReturnValue = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No Base entity, may be a straight econ item view (item model panel)
|
||
|
IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
|
||
|
if ( pRend )
|
||
|
{
|
||
|
const CEconItemView *pItem = dynamic_cast< CEconItemView* >( pRend );
|
||
|
if ( pItem && pItem->FindAttribute( GetKillEaterAttr_Score( 0 ), &unScore ) )
|
||
|
{
|
||
|
*piScore = unScore;
|
||
|
bReturnValue = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bReturnValue;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
ConVar tf_stattrak_test_score( "tf_stattrak_test_score", "-1", FCVAR_CHEAT, "" );
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
void CStatTrakDigitProxy::OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
int nKillEaterAltScore = 0;
|
||
|
|
||
|
bool bHasScoreToDisplay = HelperOnBindGetStatTrakScore( pC_BaseEntity, &nKillEaterAltScore );
|
||
|
if ( !bHasScoreToDisplay )
|
||
|
{ // Error?
|
||
|
//SetFloatResult( (int)fmod( gpGlobals->curtime, 10.0f ) );
|
||
|
SetFloatResult( 0 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_stattrak_test_score.GetInt() > -1 )
|
||
|
{
|
||
|
nKillEaterAltScore = tf_stattrak_test_score.GetInt();
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
int iDesiredDigit = (int)m_flDisplayDigit.GetFloat();
|
||
|
|
||
|
// trim preceding zeros
|
||
|
if ( m_flTrimZeros.GetFloat() > 0 )
|
||
|
{
|
||
|
if ( pow( 10.0f, iDesiredDigit ) > nKillEaterAltScore )
|
||
|
{
|
||
|
SetFloatResult( 10.0f ); //assumed blank frame
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get the [0-9] value of the digit we want
|
||
|
int iDigitCount = MIN( iDesiredDigit, 10 );
|
||
|
for ( int i = 0; i < iDigitCount; i++ )
|
||
|
{
|
||
|
nKillEaterAltScore /= 10;
|
||
|
}
|
||
|
nKillEaterAltScore %= 10;
|
||
|
|
||
|
SetFloatResult( nKillEaterAltScore );
|
||
|
}
|
||
|
|
||
|
EXPOSE_INTERFACE( CStatTrakDigitProxy, IMaterialProxy, "StatTrakDigit" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Stattrak Icon proxy
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CStatTrakIconProxy : public CResultProxy
|
||
|
{
|
||
|
public:
|
||
|
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
|
||
|
virtual void OnBind( void *pC_BaseEntity );
|
||
|
|
||
|
private:
|
||
|
//CFloatInput m_flMinVal;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
bool CStatTrakIconProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
|
||
|
{
|
||
|
/*if ( !CResultProxy::Init( pMaterial, pKeyValues ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !m_flMinVal.Init( pMaterial, pKeyValues, "minVal", 0.5 ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !m_flMaxVal.Init( pMaterial, pKeyValues, "maxVal", 1 ) )
|
||
|
return false;*/
|
||
|
|
||
|
return CResultProxy::Init( pMaterial, pKeyValues );
|
||
|
}
|
||
|
|
||
|
ConVar tf_stattrak_icon_offset_x( "tf_stattrak_icon_offset_x", "0", FCVAR_DEVELOPMENTONLY );
|
||
|
ConVar tf_stattrak_icon_offset_y( "tf_stattrak_icon_offset_y", "0", FCVAR_DEVELOPMENTONLY );
|
||
|
ConVar tf_stattrak_icon_scale( "tf_stattrak_icon_scale", "1.0", FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
void CStatTrakIconProxy::OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
// Find the StatTracker Type and Lookup the offset, for now hacks!
|
||
|
//if ( !pC_BaseEntity )
|
||
|
// return;
|
||
|
|
||
|
//C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
|
||
|
//if ( pEntity )
|
||
|
//{
|
||
|
// // StatTrak modules are children of their accompanying viewmodels
|
||
|
// C_BaseViewModel *pViewModel = dynamic_cast<C_BaseViewModel*>( pEntity->GetMoveParent() );
|
||
|
// if ( pViewModel )
|
||
|
// {
|
||
|
// //SetFloatResult( Lerp( pViewModel->GetStatTrakGlowMultiplier(), m_flMinVal.GetFloat(), m_flMaxVal.GetFloat() ) );
|
||
|
// SetFloatResult( 0.75f );
|
||
|
// return;
|
||
|
// }
|
||
|
//}
|
||
|
|
||
|
|
||
|
Vector2D center( 0.5, 0.5 );
|
||
|
Vector2D translation( 0, 0 );
|
||
|
|
||
|
VMatrix mat, temp;
|
||
|
mat.Identity();
|
||
|
//if ( m_pCenterVar )
|
||
|
//{
|
||
|
// m_pCenterVar->GetVecValue( center.Base(), 2 );
|
||
|
//}
|
||
|
//MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
|
||
|
|
||
|
//if ( m_pScaleVar )
|
||
|
//{
|
||
|
// Vector2D scale;
|
||
|
// m_pScaleVar->GetVecValue( scale.Base(), 2 );
|
||
|
// MatrixBuildScale( temp, scale.x, scale.y, 1.0f );
|
||
|
// MatrixMultiply( temp, mat, mat );
|
||
|
//}
|
||
|
|
||
|
//if ( m_pRotateVar )
|
||
|
//{
|
||
|
// float angle = m_pRotateVar->GetFloatValue();
|
||
|
// MatrixBuildRotateZ( temp, angle );
|
||
|
// MatrixMultiply( temp, mat, mat );
|
||
|
//}
|
||
|
//MatrixBuildTranslation( temp, center.x, center.y, 0.0f );
|
||
|
//MatrixMultiply( temp, mat, mat );
|
||
|
|
||
|
//if ( m_pTranslateVar )
|
||
|
{
|
||
|
//m_pTranslateVar->GetVecValue( translation.Base(), 2 );
|
||
|
MatrixBuildTranslation( temp, tf_stattrak_icon_offset_x.GetFloat(), tf_stattrak_icon_offset_y.GetFloat(), 0.0f );
|
||
|
MatrixMultiply( temp, mat, mat );
|
||
|
}
|
||
|
|
||
|
m_pResult->SetMatrixValue( mat );
|
||
|
|
||
|
if ( ToolsEnabled() )
|
||
|
{
|
||
|
ToolFramework_RecordMaterialParams( GetMaterial() );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
EXPOSE_INTERFACE( CStatTrakIconProxy, IMaterialProxy, "StatTrakIcon" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
struct TextureVarSetter
|
||
|
{
|
||
|
TextureVarSetter( IMaterialVar* pDestVar, ITexture* pDefaultTexture )
|
||
|
: m_pDestVar( pDestVar )
|
||
|
, m_pTexture( pDefaultTexture )
|
||
|
{
|
||
|
Assert( pDestVar && pDefaultTexture );
|
||
|
}
|
||
|
|
||
|
void SetTexture( ITexture* pTexture ) { m_pTexture = pTexture; }
|
||
|
|
||
|
~TextureVarSetter()
|
||
|
{
|
||
|
m_pDestVar->SetTextureValue( m_pTexture );
|
||
|
}
|
||
|
|
||
|
IMaterialVar* m_pDestVar;
|
||
|
ITexture* m_pTexture;
|
||
|
};
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Used for weapon skins.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CWeaponSkinProxy : public IMaterialProxy
|
||
|
{
|
||
|
public:
|
||
|
CWeaponSkinProxy( void )
|
||
|
: m_pMaterial( NULL )
|
||
|
, m_pBaseTextureVar( NULL )
|
||
|
, m_pBaseTextureOrig( NULL )
|
||
|
, m_nGeneration( CRTime::RTime32TimeCur() )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
~CWeaponSkinProxy()
|
||
|
{
|
||
|
SafeRelease( &m_pBaseTextureOrig );
|
||
|
}
|
||
|
|
||
|
inline ITexture* GetWeaponSkinBaseLowRes( bool bPlayerIsLocalPlayer, itemid_t nID, int iTeam ) const
|
||
|
{
|
||
|
if ( !bPlayerIsLocalPlayer )
|
||
|
return NULL;
|
||
|
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( !pLocalInv )
|
||
|
return NULL;
|
||
|
|
||
|
return pLocalInv->GetWeaponSkinBaseLowRes( nID, iTeam );
|
||
|
}
|
||
|
|
||
|
inline bool TestAndSetBaseTexture()
|
||
|
{
|
||
|
if ( m_pBaseTextureOrig )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Assert( m_pBaseTextureVar != NULL );
|
||
|
|
||
|
// If the material is in the process of being async loaded, the var won't be a
|
||
|
// texture yet, it'll be a string.
|
||
|
if ( !m_pBaseTextureVar->IsTexture() )
|
||
|
return false;
|
||
|
|
||
|
ITexture* baseTexture = m_pBaseTextureVar->GetTextureValue();
|
||
|
Assert( baseTexture != NULL );
|
||
|
SafeAssign( &m_pBaseTextureOrig, baseTexture );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual bool Init( IMaterial *pMaterial, KeyValues* pKeyValues )
|
||
|
{
|
||
|
// We don't support DX8
|
||
|
ConVarRef mat_dxlevel( "mat_dxlevel" );
|
||
|
if ( mat_dxlevel.GetInt() < 90 )
|
||
|
return false;
|
||
|
|
||
|
Assert( pMaterial );
|
||
|
m_pMaterial = pMaterial;
|
||
|
|
||
|
bool bFound = false;
|
||
|
m_pBaseTextureVar = m_pMaterial->FindVar( "$basetexture", &bFound );
|
||
|
if ( !bFound )
|
||
|
return false;
|
||
|
|
||
|
// If we are doing load on demand, this might not be ready yet.
|
||
|
// If not, then don't set it so the OnBind code knows not to rely on it.
|
||
|
// We don't actually care if the code succeeds here.
|
||
|
TestAndSetBaseTexture();
|
||
|
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
virtual void OnBind( void *pC_BaseEntity )
|
||
|
{
|
||
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
||
|
|
||
|
// If the base texture isn't ready yet, we cannot composite. So just bail out. Not even sure what
|
||
|
// we could feasibly do in this case to workaround this, we don't have a texture to use yet.
|
||
|
if ( !TestAndSetBaseTexture() )
|
||
|
return;
|
||
|
|
||
|
Assert( m_pBaseTextureVar );
|
||
|
|
||
|
// This will set the texture when it goes out of scope. We can override with other textures along the way.
|
||
|
// This handles the return cases gracefully.
|
||
|
TextureVarSetter setter( m_pBaseTextureVar, m_pBaseTextureOrig );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
// If we're not doing paintkits, exit out now (to set the base texture correctly).
|
||
|
if ( tf_paint_kit_disable.GetBool() )
|
||
|
return;
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
CEconItemView *pItem = GetEconItemViewFromProxyEntity( pC_BaseEntity );
|
||
|
if ( !pItem )
|
||
|
return;
|
||
|
|
||
|
C_TFPlayer *pOwner = GetOwnerFromProxyEntity( pC_BaseEntity );
|
||
|
int desiredW = m_pBaseTextureOrig->GetActualWidth();
|
||
|
int desiredH = m_pBaseTextureOrig->GetActualHeight();
|
||
|
const bool cbPlayerIsLocalPlayer = C_TFPlayer::GetLocalTFPlayer() && pOwner == C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
// Doing material overrides from the econ definitions can cause the same
|
||
|
// item to be referred to from multiple materials. The code treats the
|
||
|
// override material as the controller material.
|
||
|
const IMaterial* pMaterialOverride = pItem->GetMaterialOverride( pItem->GetTeamNumber() );
|
||
|
const bool cbIsControllingMaterial = pMaterialOverride == NULL || pMaterialOverride == m_pMaterial;
|
||
|
|
||
|
// if we're not using high res, check if we should down res
|
||
|
// We may force low res for some composites.
|
||
|
if ( pItem->ShouldWeaponSkinUseLowRes() || ( !pItem->ShouldWeaponSkinUseHighRes() && !cbPlayerIsLocalPlayer ) )
|
||
|
{
|
||
|
const int cDropMips = 2;
|
||
|
desiredW = Max( 1, desiredW >> cDropMips );
|
||
|
desiredH = Max( 1, desiredH >> cDropMips );
|
||
|
}
|
||
|
|
||
|
// If the object's generation isn't equal to when we told it, we need to regenerate it.
|
||
|
if ( cbIsControllingMaterial && ( pItem->GetWeaponSkinGeneration() != m_nGeneration || pItem->GetWeaponSkinGenerationTeam() != pItem->GetTeamNumber() ) )
|
||
|
{
|
||
|
// Skip this so we dont see a pop in staging
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( !tf_paint_kit_force_regen.GetBool() )
|
||
|
#endif
|
||
|
{
|
||
|
pItem->SetWeaponSkinBase( NULL );
|
||
|
pItem->SetWeaponSkinBaseCompositor( NULL );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ITexture* pWeaponSkinBase = pItem->GetWeaponSkinBase();
|
||
|
|
||
|
// If we have already completed the composite and stored it (or if there was an error)
|
||
|
// indicate that here.
|
||
|
if ( pWeaponSkinBase )
|
||
|
{
|
||
|
{
|
||
|
setter.SetTexture( pWeaponSkinBase );
|
||
|
// If the texture is the correct res already, we're done!
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( !tf_paint_kit_force_regen.GetBool() && desiredW == pWeaponSkinBase->GetActualWidth() && desiredH == pWeaponSkinBase->GetActualHeight() )
|
||
|
#else
|
||
|
if ( desiredW == pWeaponSkinBase->GetActualWidth() && desiredH == pWeaponSkinBase->GetActualHeight() )
|
||
|
#endif
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we're doing material overrides, we may get in twice--but the non-controlling material should
|
||
|
// bail out now.
|
||
|
if ( !cbIsControllingMaterial )
|
||
|
return;
|
||
|
|
||
|
ITextureCompositor* pWeaponSkinBaseCompositor = pItem->GetWeaponSkinBaseCompositor();
|
||
|
|
||
|
bool bUseLowRes = false;
|
||
|
|
||
|
if ( pWeaponSkinBaseCompositor )
|
||
|
{
|
||
|
ECompositeResolveStatus status = pWeaponSkinBaseCompositor->GetResolveStatus();
|
||
|
|
||
|
bool cleanupCompositor = false;
|
||
|
|
||
|
switch ( status )
|
||
|
{
|
||
|
case ECRS_Idle:
|
||
|
Assert( !"Unexpected state, shouldn't be idle here." );
|
||
|
break;
|
||
|
|
||
|
case ECRS_Scheduled:
|
||
|
// This is fine, this happens when multiple views ask for the same composite on the
|
||
|
// same frame. For example, a Model Panel and the world view.
|
||
|
bUseLowRes = true;
|
||
|
break;
|
||
|
|
||
|
case ECRS_PendingTextureLoads:
|
||
|
// Totally fine, try again later.
|
||
|
bUseLowRes = true;
|
||
|
break;
|
||
|
|
||
|
case ECRS_PendingComposites:
|
||
|
// Totally fine, try again later.
|
||
|
bUseLowRes = true;
|
||
|
break;
|
||
|
|
||
|
case ECRS_Error:
|
||
|
// Had an error, just show the current base texture forever.
|
||
|
// Is this a reasonable error handler? Seems like it is, though maybe
|
||
|
// we want to show the error texture for at least dev mode.
|
||
|
Assert( !"Error while compositing, should figure out wtf.");
|
||
|
pItem->SetWeaponSkinBase( m_pBaseTextureOrig );
|
||
|
cleanupCompositor = true;
|
||
|
break;
|
||
|
|
||
|
case ECRS_Complete:
|
||
|
// Success! Use the new texture for all time. Or whatever.
|
||
|
pWeaponSkinBase = pWeaponSkinBaseCompositor->GetResultTexture();
|
||
|
pItem->SetWeaponSkinBase( pWeaponSkinBase );
|
||
|
setter.SetTexture( pWeaponSkinBase );
|
||
|
cleanupCompositor = true;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Assert( !"Unexpected return value from ITextureCompositor::GetResolveStatus" );
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
if ( cleanupCompositor )
|
||
|
{
|
||
|
pItem->SetWeaponSkinBaseCompositor( NULL );
|
||
|
pWeaponSkinBaseCompositor = NULL;
|
||
|
}
|
||
|
|
||
|
if ( bUseLowRes )
|
||
|
{
|
||
|
ITexture* pTex = GetWeaponSkinBaseLowRes( cbPlayerIsLocalPlayer, pItem->GetItemID(), pItem->GetTeamNumber() );
|
||
|
if ( pTex )
|
||
|
setter.SetTexture( pTex );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Start the composite.
|
||
|
KeyValues* rootKV = NULL;
|
||
|
float flWear = 0;
|
||
|
if ( !pItem->GetCustomPaintKitWear( flWear ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
int nWear = EconWear_ToIntCategory( flWear );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_paint_kit_force_wear.GetInt() > 0 )
|
||
|
{
|
||
|
nWear = Min( tf_paint_kit_force_wear.GetInt(), 5 );
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
const char* pItemName = "unknown";
|
||
|
const GameItemDefinition_t* tfItemDef = pItem->GetItemDefinition();
|
||
|
if ( tfItemDef )
|
||
|
{
|
||
|
const char* pMaybeName = tfItemDef->GetPaintKitName();
|
||
|
if ( pMaybeName )
|
||
|
pItemName = pMaybeName;
|
||
|
rootKV = tfItemDef->GetPaintKitWearDefinition( nWear );
|
||
|
}
|
||
|
|
||
|
uint32 nCompositeFlags = 0;
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( s_bIsPaintkitOverrideSet == true )
|
||
|
{
|
||
|
// Fetch the wear level KV
|
||
|
const char *vArgs = VarArgs( "wear_level_%d", nWear );
|
||
|
FOR_EACH_SUBKEY( s_kvOverridePaintkit, wearKv )
|
||
|
{
|
||
|
if ( !V_strcmp( vArgs, wearKv->GetName() ) )
|
||
|
{
|
||
|
rootKV = wearKv;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
nCompositeFlags = TEX_COMPOSITE_CREATE_FLAGS_FORCE;
|
||
|
}
|
||
|
|
||
|
if ( tf_paint_kit_generating_icons.GetBool() )
|
||
|
{
|
||
|
nCompositeFlags = TEX_COMPOSITE_CREATE_FLAGS_FORCE;
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
if ( rootKV )
|
||
|
{
|
||
|
uint64 seed = pItem->GetOriginalID();
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_paint_kit_seed_override.GetInt() != 0 )
|
||
|
{
|
||
|
seed = tf_paint_kit_seed_override.GetInt();
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
Assert( pItem->GetTeamNumber() != TEAM_UNASSIGNED );
|
||
|
int teamNum = pItem->GetTeamNumber() != TEAM_UNASSIGNED ? pItem->GetTeamNumber() : TF_TEAM_RED;
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_paint_kit_team_override.GetInt() >= 0 )
|
||
|
{
|
||
|
teamNum = tf_paint_kit_team_override.GetInt() == 0 ? TF_TEAM_RED : TF_TEAM_BLUE;
|
||
|
pItem->SetTeamNumber( teamNum );
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
char finalItemName[_MAX_PATH];
|
||
|
V_sprintf_safe( finalItemName, "%s_wear_%02d", pItemName, nWear );
|
||
|
|
||
|
SafeAssign( &pWeaponSkinBaseCompositor, materials->NewTextureCompositor( desiredW, desiredH, finalItemName, teamNum, seed, rootKV, nCompositeFlags ) );
|
||
|
if ( pWeaponSkinBaseCompositor )
|
||
|
{
|
||
|
pWeaponSkinBaseCompositor->ScheduleResolve();
|
||
|
pItem->SetWeaponSkinGeneration( m_nGeneration );
|
||
|
pItem->SetWeaponSkinGenerationTeam( teamNum );
|
||
|
|
||
|
if ( pWeaponSkinBaseCompositor->GetResolveStatus() != ECRS_Complete )
|
||
|
{
|
||
|
// Normal case
|
||
|
pItem->SetWeaponSkinBaseCompositor( pWeaponSkinBaseCompositor );
|
||
|
|
||
|
// Try to sub out the low res, if it's ready.
|
||
|
ITexture* pTex = GetWeaponSkinBaseLowRes( cbPlayerIsLocalPlayer, pItem->GetItemID(), pItem->GetTeamNumber() );
|
||
|
if ( pTex )
|
||
|
setter.SetTexture( pTex );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Had a cache hit, so add the texture here.
|
||
|
pWeaponSkinBase = pWeaponSkinBaseCompositor->GetResultTexture();
|
||
|
pItem->SetWeaponSkinBase( pWeaponSkinBase );
|
||
|
setter.SetTexture( pWeaponSkinBase );
|
||
|
}
|
||
|
|
||
|
SafeRelease( pWeaponSkinBaseCompositor );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
virtual void Release() { delete this; }
|
||
|
virtual IMaterial * GetMaterial() { return m_pMaterial; }
|
||
|
|
||
|
private:
|
||
|
IMaterial *m_pMaterial;
|
||
|
|
||
|
IMaterialVar *m_pBaseTextureVar;
|
||
|
ITexture *m_pBaseTextureOrig;
|
||
|
|
||
|
RTime32 m_nGeneration;
|
||
|
|
||
|
bool m_bForceUpdate;
|
||
|
};
|
||
|
|
||
|
EXPOSE_INTERFACE( CWeaponSkinProxy, IMaterialProxy, "WeaponSkin" IMATERIAL_PROXY_INTERFACE_VERSION );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: RecvProxy that converts the Player's object UtlVector to entindexes
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void RecvProxy_PlayerObjectList( const CRecvProxyData *pData, void *pStruct, void *pOut )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = (C_TFPlayer*)pStruct;
|
||
|
CBaseHandle *pHandle = (CBaseHandle*)(&(pPlayer->m_aObjects[pData->m_iElement]));
|
||
|
RecvProxy_IntToEHandle( pData, pStruct, pHandle );
|
||
|
}
|
||
|
|
||
|
void RecvProxyArrayLength_PlayerObjects( void *pStruct, int objectID, int currentArrayLength )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = (C_TFPlayer*)pStruct;
|
||
|
|
||
|
if ( pPlayer->m_aObjects.Count() != currentArrayLength )
|
||
|
{
|
||
|
pPlayer->m_aObjects.SetSize( currentArrayLength );
|
||
|
}
|
||
|
|
||
|
pPlayer->ForceUpdateObjectHudState();
|
||
|
}
|
||
|
|
||
|
EXTERN_RECV_TABLE( DT_ScriptCreatedItem );
|
||
|
|
||
|
// specific to the local player
|
||
|
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFLocalPlayerExclusive )
|
||
|
RecvPropVectorXY( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
|
||
|
RecvPropFloat( RECVINFO_NAME( m_vecNetworkOrigin[2], m_vecOrigin[2] ) ),
|
||
|
RecvPropArray2(
|
||
|
RecvProxyArrayLength_PlayerObjects,
|
||
|
RecvPropInt( "player_object_array_element", 0, SIZEOF_IGNORE, 0, RecvProxy_PlayerObjectList ),
|
||
|
MAX_OBJECTS_PER_PLAYER,
|
||
|
0,
|
||
|
"player_object_array" ),
|
||
|
|
||
|
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ), // No longer used by the local player, could be omitted. Preserved for backwards-compat for now.
|
||
|
// RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bIsCoaching ) ),
|
||
|
RecvPropEHandle( RECVINFO( m_hCoach ) ),
|
||
|
RecvPropEHandle( RECVINFO( m_hStudent ) ),
|
||
|
|
||
|
RecvPropInt( RECVINFO( m_nCurrency ) ),
|
||
|
RecvPropInt( RECVINFO( m_nExperienceLevel ) ),
|
||
|
RecvPropInt( RECVINFO( m_nExperienceLevelProgress ) ),
|
||
|
RecvPropInt( RECVINFO( m_bMatchSafeToLeave ) ),
|
||
|
|
||
|
END_RECV_TABLE()
|
||
|
|
||
|
// all players except the local player
|
||
|
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFNonLocalPlayerExclusive )
|
||
|
RecvPropVectorXY( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
|
||
|
RecvPropFloat( RECVINFO_NAME( m_vecNetworkOrigin[2], m_vecOrigin[2] ) ),
|
||
|
|
||
|
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ),
|
||
|
RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
|
||
|
|
||
|
END_RECV_TABLE()
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Data that gets sent to attached medics
|
||
|
//-----------------------------------------------------------------------------
|
||
|
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFSendHealersDataTable )
|
||
|
RecvPropInt( RECVINFO( m_nActiveWpnClip ) ),
|
||
|
END_RECV_TABLE()
|
||
|
|
||
|
IMPLEMENT_CLIENTCLASS_DT( C_TFPlayer, DT_TFPlayer, CTFPlayer )
|
||
|
|
||
|
RecvPropBool(RECVINFO(m_bSaveMeParity)),
|
||
|
RecvPropBool(RECVINFO(m_bIsMiniBoss)),
|
||
|
RecvPropBool(RECVINFO(m_bIsABot)),
|
||
|
RecvPropInt(RECVINFO(m_nBotSkill)),
|
||
|
|
||
|
// This will create a race condition will the local player, but the data will be the same so.....
|
||
|
RecvPropInt( RECVINFO( m_nWaterLevel ) ),
|
||
|
RecvPropEHandle( RECVINFO( m_hRagdoll ) ),
|
||
|
RecvPropDataTable( RECVINFO_DT( m_PlayerClass ), 0, &REFERENCE_RECV_TABLE( DT_TFPlayerClassShared ) ),
|
||
|
RecvPropDataTable( RECVINFO_DT( m_Shared ), 0, &REFERENCE_RECV_TABLE( DT_TFPlayerShared ) ),
|
||
|
RecvPropEHandle( RECVINFO(m_hItem ) ),
|
||
|
|
||
|
RecvPropDataTable( "tflocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFLocalPlayerExclusive) ),
|
||
|
RecvPropDataTable( "tfnonlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFNonLocalPlayerExclusive) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bAllowMoveDuringTaunt ) ),
|
||
|
RecvPropBool( RECVINFO( m_bIsReadyToHighFive ) ),
|
||
|
RecvPropEHandle( RECVINFO( m_hHighFivePartner ) ),
|
||
|
RecvPropInt( RECVINFO( m_nForceTauntCam ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flTauntYaw ) ),
|
||
|
RecvPropInt( RECVINFO( m_nActiveTauntSlot ) ),
|
||
|
RecvPropInt( RECVINFO( m_iTauntItemDefIndex ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flCurrentTauntMoveSpeed ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flVehicleReverseTime ) ),
|
||
|
|
||
|
RecvPropFloat( RECVINFO( m_flLastDamageTime ) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bInPowerPlay ) ),
|
||
|
|
||
|
RecvPropInt( RECVINFO( m_iSpawnCounter ) ),
|
||
|
RecvPropBool( RECVINFO( m_bArenaSpectator ) ),
|
||
|
|
||
|
RecvPropDataTable( RECVINFO_DT( m_AttributeManager ), 0, &REFERENCE_RECV_TABLE(DT_AttributeManager) ),
|
||
|
RecvPropFloat( RECVINFO( m_flHeadScale ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flTorsoScale ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flHandScale ) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bUseBossHealthBar ) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bUsingVRHeadset ) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bForcedSkin ) ),
|
||
|
RecvPropInt( RECVINFO( m_nForcedSkin ) ),
|
||
|
|
||
|
RecvPropBool( RECVINFO( m_bGlowEnabled ) ),
|
||
|
|
||
|
RecvPropDataTable("TFSendHealersDataTable", 0, 0, &REFERENCE_RECV_TABLE( DT_TFSendHealersDataTable ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flKartNextAvailableBoost ) ),
|
||
|
RecvPropInt( RECVINFO( m_iKartHealth ) ),
|
||
|
RecvPropInt( RECVINFO( m_iKartState ) ),
|
||
|
RecvPropEHandle( RECVINFO( m_hGrapplingHookTarget ) ),
|
||
|
RecvPropEHandle( RECVINFO( m_hSecondaryLastWeapon ) ),
|
||
|
RecvPropBool( RECVINFO( m_bUsingActionSlot ) ),
|
||
|
RecvPropFloat( RECVINFO( m_flInspectTime ) ),
|
||
|
RecvPropInt( RECVINFO( m_iCampaignMedals ) ),
|
||
|
RecvPropInt( RECVINFO( m_iPlayerSkinOverride ) ),
|
||
|
END_RECV_TABLE()
|
||
|
|
||
|
|
||
|
BEGIN_PREDICTION_DATA( C_TFPlayer )
|
||
|
DEFINE_PRED_TYPEDESCRIPTION( m_Shared, CTFPlayerShared ),
|
||
|
DEFINE_PRED_FIELD( m_nSkin, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
|
||
|
DEFINE_PRED_FIELD( m_nBody, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
|
||
|
DEFINE_PRED_FIELD( m_nSequence, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
|
||
|
DEFINE_PRED_FIELD( m_flPlaybackRate, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
|
||
|
DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
|
||
|
DEFINE_PRED_ARRAY_TOL( m_flEncodedController, FIELD_FLOAT, MAXSTUDIOBONECTRLS, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE, 0.02f ),
|
||
|
DEFINE_PRED_FIELD( m_nNewSequenceParity, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
|
||
|
DEFINE_PRED_FIELD( m_nResetEventsParity, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
|
||
|
DEFINE_PRED_FIELD( m_nMuzzleFlashParity, FIELD_CHARACTER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
|
||
|
DEFINE_PRED_FIELD( m_hOffHandWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
|
||
|
DEFINE_PRED_FIELD( m_flTauntYaw, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
||
|
DEFINE_PRED_FIELD( m_flCurrentTauntMoveSpeed, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
||
|
DEFINE_PRED_FIELD( m_flVehicleReverseTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
||
|
DEFINE_PRED_FIELD( m_flInspectTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
||
|
END_PREDICTION_DATA()
|
||
|
|
||
|
// ------------------------------------------------------------------------------------------ //
|
||
|
// C_TFPlayer implementation.
|
||
|
// ------------------------------------------------------------------------------------------ //
|
||
|
|
||
|
C_TFPlayer::C_TFPlayer() :
|
||
|
m_iv_angEyeAngles( "C_TFPlayer::m_iv_angEyeAngles" ),
|
||
|
m_mapOverheadEffects( DefLessFunc( const char * ) )
|
||
|
{
|
||
|
m_pAttributes = this;
|
||
|
|
||
|
m_PlayerAnimState = CreateTFPlayerAnimState( this );
|
||
|
m_Shared.Init( this );
|
||
|
|
||
|
m_iIDEntIndex = 0;
|
||
|
|
||
|
AddVar( &m_angEyeAngles, &m_iv_angEyeAngles, LATCH_SIMULATION_VAR );
|
||
|
|
||
|
memset( m_pKartParticles, NULL, sizeof( m_pKartParticles ) );
|
||
|
memset( m_pKartSounds, NULL, sizeof( m_pKartSounds ) );
|
||
|
|
||
|
m_pMVMEyeGlowEffect[ 0 ] = NULL;
|
||
|
m_pMVMEyeGlowEffect[ 1 ] = NULL;
|
||
|
|
||
|
m_pEyeGlowEffect[ 0 ] = NULL;
|
||
|
m_pEyeGlowEffect[ 1 ] = NULL;
|
||
|
m_pszEyeGlowEffectName[0] = '\0';
|
||
|
m_vEyeGlowColor1.Zero();
|
||
|
m_vEyeGlowColor2.Zero();
|
||
|
m_flNextSheenStartTime = 0;
|
||
|
|
||
|
m_pTeleporterEffect = NULL;
|
||
|
m_pBurningSound = NULL;
|
||
|
m_pBurningEffect = NULL;
|
||
|
m_pUrineEffect = NULL;
|
||
|
m_pMilkEffect = NULL;
|
||
|
m_pSoldierOffensiveBuffEffect = NULL;
|
||
|
m_pSoldierDefensiveBuffEffect = NULL;
|
||
|
m_pSoldierOffensiveHealthRegenBuffEffect = NULL;
|
||
|
m_pSoldierNoHealingDamageBuffEffect = NULL;
|
||
|
m_pCritBoostEffect = NULL;
|
||
|
m_flBurnEffectStartTime = 0;
|
||
|
m_pDisguisingEffect = NULL;
|
||
|
m_pSaveMeEffect = NULL;
|
||
|
m_pTauntWithMeEffect = NULL;
|
||
|
m_hOldObserverTarget = NULL;
|
||
|
m_iOldObserverMode = OBS_MODE_NONE;
|
||
|
m_pStunnedEffect = NULL;
|
||
|
m_pPhaseStandingEffect = NULL;
|
||
|
m_pRadiusHealEffect = NULL;
|
||
|
m_pKingRuneRadiusEffect = NULL;
|
||
|
m_pKingBuffRadiusEffect = NULL;
|
||
|
m_pRunePlagueEffect = NULL;
|
||
|
m_pMegaHealEffect = NULL;
|
||
|
m_pTempShield = NULL;
|
||
|
|
||
|
m_pMVMBotRadiowave = NULL;
|
||
|
|
||
|
m_pRuneChargeReadyEffect = NULL;
|
||
|
|
||
|
m_aGibs.Purge();
|
||
|
m_aNormalGibs.PurgeAndDeleteElements();
|
||
|
m_aSillyGibs.Purge();
|
||
|
|
||
|
m_bCigaretteSmokeActive = false;
|
||
|
|
||
|
m_hRagdoll.Set( NULL );
|
||
|
|
||
|
m_iPreviousMetal = 0;
|
||
|
m_bIsDisplayingNemesisIcon = false;
|
||
|
m_bIsDisplayingDuelingIcon = false;
|
||
|
m_bIsDisplayingIconForIT = false;
|
||
|
m_bShouldShowBirthdayEffect = false;
|
||
|
|
||
|
m_bWasTaunting = false;
|
||
|
m_angTauntPredViewAngles.Init();
|
||
|
m_angTauntEngViewAngles.Init();
|
||
|
m_pTauntSoundLoop = NULL;
|
||
|
|
||
|
m_flWaterImpactTime = 0.0f;
|
||
|
m_rtSpottedInPVSTime = 0;
|
||
|
m_rtJoinedSpectatorTeam = 0;
|
||
|
m_rtJoinedNormalTeam = 0;
|
||
|
|
||
|
m_flWaterEntryTime = 0;
|
||
|
m_nOldWaterLevel = WL_NotInWater;
|
||
|
m_bWaterExitEffectActive = false;
|
||
|
|
||
|
m_bUpdateObjectHudState = false;
|
||
|
|
||
|
m_flSaveMeExpireTime = 0;
|
||
|
|
||
|
m_bWasHealedByLocalPlayer = false;
|
||
|
|
||
|
m_bDuckJumpInterp = false;
|
||
|
m_flFirstDuckJumpInterp = 0.0f;
|
||
|
m_flLastDuckJumpInterp = 0.0f;
|
||
|
m_flDuckJumpInterp = 0.0f;
|
||
|
|
||
|
m_bIsCoaching = false;
|
||
|
m_pStudentGlowEffect = NULL;
|
||
|
m_pPowerupGlowEffect = NULL;
|
||
|
|
||
|
m_nBotSkill = -1;
|
||
|
m_nOldBotSkill = -1;
|
||
|
m_nOldMaxHealth = -1;
|
||
|
|
||
|
m_bIsCalculatingMaximumSpeed = false;
|
||
|
|
||
|
m_bBodygroupsDirty = false;
|
||
|
|
||
|
m_pBlastJumpLoop = NULL;
|
||
|
m_flBlastJumpLaunchTime = 0.f;
|
||
|
|
||
|
m_nExperienceLevel = 0;
|
||
|
m_nExperienceLevelProgress = 0;
|
||
|
m_nPrevExperienceLevel = 0;
|
||
|
|
||
|
m_bMatchSafeToLeave = true;
|
||
|
|
||
|
for( int i=0; i<kBonusEffect_Count; ++i )
|
||
|
{
|
||
|
m_flNextMiniCritEffectTime[i] = 0;
|
||
|
}
|
||
|
|
||
|
m_flHeadScale = 1.f;
|
||
|
m_flTorsoScale = 1.f;
|
||
|
m_flHandScale = 1.f;
|
||
|
|
||
|
m_bIsMiniBoss = false;
|
||
|
m_bUseBossHealthBar = false;
|
||
|
m_bUsingVRHeadset = false;
|
||
|
|
||
|
m_bForcedSkin = false;
|
||
|
m_nForcedSkin = 0;
|
||
|
|
||
|
m_flChangeClassTime = 0.f;
|
||
|
|
||
|
m_hRevivePrompt = NULL;
|
||
|
|
||
|
m_bIsDisplayingTranqMark = false;
|
||
|
m_eDisplayingRuneIcon = RUNE_NONE;
|
||
|
|
||
|
m_pKart = NULL;
|
||
|
m_iOldKartHealth = 0;
|
||
|
|
||
|
m_bUsingActionSlot = false;
|
||
|
m_iCampaignMedals = 0;
|
||
|
|
||
|
m_flInspectTime = 0.f;
|
||
|
|
||
|
m_bNotifiedWeaponInspectThisLife = false;
|
||
|
|
||
|
m_pPasstimePlayerReticle = NULL;
|
||
|
m_pPasstimeAskForBallReticle = NULL;
|
||
|
|
||
|
m_iPlayerSkinOverride = 0;
|
||
|
|
||
|
ListenForGameEvent( "player_hurt" );
|
||
|
ListenForGameEvent( "hltv_changed_mode" );
|
||
|
ListenForGameEvent( "hltv_changed_target" );
|
||
|
ListenForGameEvent( "post_inventory_application" );
|
||
|
ListenForGameEvent( "rocket_jump" );
|
||
|
ListenForGameEvent( "rocket_jump_landed" );
|
||
|
ListenForGameEvent( "sticky_jump" );
|
||
|
ListenForGameEvent( "sticky_jump_landed" );
|
||
|
ListenForGameEvent( "player_spawn" );
|
||
|
ListenForGameEvent( "damage_resisted" );
|
||
|
ListenForGameEvent( "revive_player_notify" );
|
||
|
ListenForGameEvent( "revive_player_stopped" );
|
||
|
ListenForGameEvent( "player_changeclass" );
|
||
|
ListenForGameEvent( "player_abandoned_match" );
|
||
|
#ifdef STAGING_ONLY
|
||
|
ListenForGameEvent( "player_death" );
|
||
|
#endif
|
||
|
|
||
|
//AddPhonemeFile
|
||
|
engine->AddPhonemeFile( "scripts/game_sounds_vo_phonemes.txt" );
|
||
|
engine->AddPhonemeFile( "scripts/game_sounds_vo_phonemes_local.txt" ); // Stomp over english for phoneme data
|
||
|
engine->AddPhonemeFile( NULL ); // Null indicates to engine that we are done loading phonemes if there are any present
|
||
|
}
|
||
|
|
||
|
C_TFPlayer::~C_TFPlayer()
|
||
|
{
|
||
|
ShowNemesisIcon( false );
|
||
|
ShowDuelingIcon( false );
|
||
|
m_PlayerAnimState->Release();
|
||
|
|
||
|
if ( m_pBlastJumpLoop )
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
controller.SoundDestroy( m_pBlastJumpLoop );
|
||
|
m_pBlastJumpLoop = NULL;
|
||
|
}
|
||
|
|
||
|
StopTauntSoundLoop();
|
||
|
|
||
|
delete m_pPasstimePlayerReticle;
|
||
|
delete m_pPasstimeAskForBallReticle;
|
||
|
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
g_ItemEffectMeterManager.ClearExistingMeters();
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
|
||
|
{
|
||
|
g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "CompetitiveGame_RestoreChatWindow", false );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NOTE: This is NOT called every time the player respawns!!
|
||
|
// only the first time we spawn a player into the world
|
||
|
void C_TFPlayer::Spawn( void )
|
||
|
{
|
||
|
m_AttributeManager.SetPlayer( this );
|
||
|
m_AttributeList.SetManager( &m_AttributeManager );
|
||
|
|
||
|
BaseClass::Spawn();
|
||
|
|
||
|
/*
|
||
|
// some extra stuff here because s_pLocalPlayer is not yet initialized
|
||
|
int iLocalPlayerIndex = engine->GetLocalPlayer();
|
||
|
|
||
|
if ( entindex() == iLocalPlayerIndex && !m_LeaveServerTimer.HasStarted() )
|
||
|
{
|
||
|
ConVarRef random_spec_server_mode( "random_spec_server_mode" );
|
||
|
if ( random_spec_server_mode.IsValid() && random_spec_server_mode.GetBool() )
|
||
|
{
|
||
|
m_LeaveServerTimer.Start( spectate_random_server_basetime.GetFloat() );
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
UpdateInventory( true );
|
||
|
|
||
|
UpdateMVMEyeGlowEffect( true );
|
||
|
|
||
|
SetShowHudMenuTauntSelection( false );
|
||
|
|
||
|
CleanUpAnimationOnSpawn();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::InventoryUpdated( CPlayerInventory *pInventory )
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
|
||
|
{
|
||
|
CHudUpgradePanel *pUpgradePanel = GET_HUDELEMENT( CHudUpgradePanel );
|
||
|
if ( pUpgradePanel && pUpgradePanel->IsVisible() )
|
||
|
{
|
||
|
pUpgradePanel->PlayerInventoryChanged( this );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Request this player's inventories from the steam backend
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateInventory( bool bInit )
|
||
|
{
|
||
|
#if !defined(NO_STEAM)
|
||
|
if ( bInit )
|
||
|
{
|
||
|
CSteamID steamIDForPlayer;
|
||
|
if ( GetSteamID( &steamIDForPlayer ) )
|
||
|
{
|
||
|
TFInventoryManager()->SteamRequestInventory( &m_Inventory, steamIDForPlayer, this );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we have an SOCache, we've got a connection to the GC
|
||
|
bool bInvalid = true;
|
||
|
if ( m_Inventory.GetSOC() )
|
||
|
{
|
||
|
bInvalid = (m_Inventory.GetSOC()->BIsInitialized() == false);
|
||
|
}
|
||
|
m_bInventoryReceived = !bInvalid;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
C_TFPlayer* C_TFPlayer::GetLocalTFPlayer()
|
||
|
{
|
||
|
return ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
|
||
|
}
|
||
|
|
||
|
const QAngle& C_TFPlayer::GetRenderAngles()
|
||
|
{
|
||
|
if ( IsRagdoll() )
|
||
|
{
|
||
|
return vec3_angle;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return m_PlayerAnimState->GetRenderAngles();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::CanDisplayAllSeeEffect( EAttackBonusEffects_t effect ) const
|
||
|
{
|
||
|
if( effect >= EAttackBonusEffects_t(0) && effect < kBonusEffect_Count )
|
||
|
{
|
||
|
return gpGlobals->curtime > m_flNextMiniCritEffectTime[ effect ];
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::SetNextAllSeeEffectTime( EAttackBonusEffects_t effect, float flTime )
|
||
|
{
|
||
|
if( effect >= EAttackBonusEffects_t(0) && effect < kBonusEffect_Count )
|
||
|
{
|
||
|
if ( gpGlobals->curtime > m_flNextMiniCritEffectTime[ effect ] )
|
||
|
{
|
||
|
m_flNextMiniCritEffectTime[ effect ] = flTime;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateOnRemove( void )
|
||
|
{
|
||
|
// Stop the taunt.
|
||
|
if ( m_bWasTaunting )
|
||
|
{
|
||
|
// Need to go ahead and call both. Otherwise, if we changelevel while we're taunting or
|
||
|
// otherwise in "game wants us in third person mode", we will stay in third person mode
|
||
|
// in the new map.
|
||
|
TurnOffTauntCam();
|
||
|
TurnOffTauntCam_Finish();
|
||
|
}
|
||
|
|
||
|
// HACK!!! ChrisG needs to fix this in the particle system.
|
||
|
ParticleProp()->OwnerSetDormantTo( true );
|
||
|
ParticleProp()->StopParticlesInvolving( this );
|
||
|
|
||
|
m_Shared.RemoveAllCond();
|
||
|
|
||
|
m_Inventory.RemoveListener( this );
|
||
|
|
||
|
BaseClass::UpdateOnRemove();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: returns max health for this player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetMaxHealth( void ) const
|
||
|
{
|
||
|
return ( g_TF_PR ) ? g_TF_PR->GetMaxHealth( entindex() ) : 1;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: returns max buffed health for this player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetMaxHealthForBuffing( void ) const
|
||
|
{
|
||
|
return ( g_TF_PR ) ? g_TF_PR->GetMaxHealthForBuffing( entindex() ) : 1;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Deal with recording
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::GetToolRecordingState( KeyValues *msg )
|
||
|
{
|
||
|
#ifndef _XBOX
|
||
|
BaseClass::GetToolRecordingState( msg );
|
||
|
BaseEntityRecordingState_t *pBaseEntityState = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" );
|
||
|
|
||
|
bool bDormant = IsDormant();
|
||
|
bool bDead = !IsAlive();
|
||
|
bool bSpectator = ( GetTeamNumber() == TEAM_SPECTATOR );
|
||
|
bool bNoRender = ( GetRenderMode() == kRenderNone );
|
||
|
bool bDeathCam = (GetObserverMode() == OBS_MODE_DEATHCAM);
|
||
|
bool bNoDraw = IsEffectActive(EF_NODRAW);
|
||
|
|
||
|
bool bVisible =
|
||
|
!bDormant &&
|
||
|
!bDead &&
|
||
|
!bSpectator &&
|
||
|
!bNoRender &&
|
||
|
!bDeathCam &&
|
||
|
!bNoDraw;
|
||
|
|
||
|
bool changed = m_bToolRecordingVisibility != bVisible;
|
||
|
// Remember state
|
||
|
m_bToolRecordingVisibility = bVisible;
|
||
|
|
||
|
pBaseEntityState->m_bVisible = bVisible;
|
||
|
if ( changed && !bVisible )
|
||
|
{
|
||
|
// If the entity becomes invisible this frame, we still want to record a final animation sample so that we have data to interpolate
|
||
|
// toward just before the logs return "false" for visiblity. Otherwise the animation will freeze on the last frame while the model
|
||
|
// is still able to render for just a bit.
|
||
|
pBaseEntityState->m_bRecordFinalVisibleSample = true;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
void C_TFPlayer::UpdateClientSideAnimation()
|
||
|
{
|
||
|
// Update the animation data. It does the local check here so this works when using
|
||
|
// a third-person camera (and we don't have valid player angles).
|
||
|
if ( this == C_TFPlayer::GetLocalTFPlayer() )
|
||
|
{
|
||
|
// m_angEyeAngles comes from the server, and updates are infrequent, so use the local values instead.
|
||
|
QAngle LocalEyeAngles = EyeAngles();
|
||
|
m_PlayerAnimState->Update( LocalEyeAngles[YAW], LocalEyeAngles[PITCH] );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
|
||
|
}
|
||
|
|
||
|
// StatTrak Module Test
|
||
|
// Update ViewModels
|
||
|
// We only update the view model for the local player.
|
||
|
//if ( IsLocalPlayer() )
|
||
|
{
|
||
|
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateAllViewmodelAddons();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BaseClass::UpdateClientSideAnimation();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::SetDormant( bool bDormant )
|
||
|
{
|
||
|
// If I'm burning, stop the burning sounds
|
||
|
if ( !IsDormant() && bDormant )
|
||
|
{
|
||
|
if ( m_pBurningSound)
|
||
|
{
|
||
|
StopBurningSound();
|
||
|
}
|
||
|
if ( m_bIsDisplayingNemesisIcon )
|
||
|
{
|
||
|
ShowNemesisIcon( false );
|
||
|
}
|
||
|
if ( m_bIsDisplayingDuelingIcon )
|
||
|
{
|
||
|
ShowDuelingIcon( false );
|
||
|
}
|
||
|
if ( m_bIsDisplayingIconForIT )
|
||
|
{
|
||
|
ShowIconForIT( false );
|
||
|
}
|
||
|
UpdatedMarkedForDeathEffect( true );
|
||
|
UpdateRuneIcon( true );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( m_bIsDisplayingTranqMark )
|
||
|
{
|
||
|
UpdateTranqMark( false, true );
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
if ( m_bShouldShowBirthdayEffect )
|
||
|
{
|
||
|
ShowBirthdayEffect( false );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( IsDormant() && !bDormant )
|
||
|
{
|
||
|
SetBodygroupsDirty();
|
||
|
|
||
|
if ( IsTaunting() )
|
||
|
{
|
||
|
float flCycle = 0.f;
|
||
|
if ( m_flTauntDuration > 0.f )
|
||
|
{
|
||
|
float dt = gpGlobals->curtime - m_flTauntStartTime;
|
||
|
while ( dt >= m_flTauntDuration )
|
||
|
{
|
||
|
dt -= m_flTauntDuration;
|
||
|
}
|
||
|
flCycle = dt / m_flTauntDuration;
|
||
|
flCycle = clamp( flCycle, 0.f, 1.0f );
|
||
|
}
|
||
|
|
||
|
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
|
||
|
m_PlayerAnimState->AddVCDSequenceToGestureSlot( GESTURE_SLOT_VCD, m_nTauntSequence, flCycle, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bDormant == false )
|
||
|
{
|
||
|
m_rtSpottedInPVSTime = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetServerRealTime() : CRTime::RTime32TimeCur();
|
||
|
}
|
||
|
|
||
|
// Deliberately skip base combat weapon
|
||
|
C_BaseEntity::SetDormant( bDormant );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::OnPreDataChanged( DataUpdateType_t updateType )
|
||
|
{
|
||
|
BaseClass::OnPreDataChanged( updateType );
|
||
|
|
||
|
m_iOldHealth = m_iHealth;
|
||
|
m_iOldPlayerClass = m_PlayerClass.GetClassIndex();
|
||
|
m_iOldSpawnCounter = m_iSpawnCounter;
|
||
|
m_bOldSaveMeParity = m_bSaveMeParity;
|
||
|
m_nOldWaterLevel = GetWaterLevel();
|
||
|
|
||
|
m_iOldTeam = GetTeamNumber();
|
||
|
C_TFPlayerClass *pClass = GetPlayerClass();
|
||
|
m_iOldClass = pClass ? pClass->GetClassIndex() : TF_CLASS_UNDEFINED;
|
||
|
m_bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
|
||
|
m_iOldDisguiseTeam = m_Shared.GetDisguiseTeam();
|
||
|
m_iOldDisguiseClass = m_Shared.GetDisguiseClass();
|
||
|
|
||
|
m_flPrevTauntYaw = m_flTauntYaw;
|
||
|
|
||
|
m_nPrevTauntSlot = m_nActiveTauntSlot;
|
||
|
m_iPrevTauntItemDefIndex = m_iTauntItemDefIndex;
|
||
|
|
||
|
if ( !IsReplay() )
|
||
|
{
|
||
|
m_iOldObserverMode = GetObserverMode();
|
||
|
m_hOldObserverTarget = GetObserverTarget();
|
||
|
}
|
||
|
|
||
|
m_nOldCurrency = m_nCurrency;
|
||
|
|
||
|
m_Shared.OnPreDataChanged();
|
||
|
|
||
|
m_bOldCustomModelVisible = m_PlayerClass.CustomModelIsVisibleToSelf();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::OnDataChanged( DataUpdateType_t updateType )
|
||
|
{
|
||
|
// C_BaseEntity assumes we're networking the entity's angles, so pretend that it
|
||
|
// networked the same value we already have.
|
||
|
SetNetworkAngles( GetLocalAngles() );
|
||
|
|
||
|
BaseClass::OnDataChanged( updateType );
|
||
|
|
||
|
if ( updateType == DATA_UPDATE_CREATED )
|
||
|
{
|
||
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
||
|
|
||
|
InitInvulnerableMaterial();
|
||
|
|
||
|
// There used to be code here to switch to first person. This breaks thirdperson mode
|
||
|
// and karts when we have to get a full client update--don't do it here anymore.
|
||
|
// We may need to do something more clever if this breaks something.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( m_iOldTeam != GetTeamNumber() || m_iOldDisguiseTeam != m_Shared.GetDisguiseTeam() )
|
||
|
{
|
||
|
InitInvulnerableMaterial();
|
||
|
}
|
||
|
|
||
|
if ( m_iOldDisguiseClass != m_Shared.GetDisguiseClass() )
|
||
|
{
|
||
|
RemoveAllDecals();
|
||
|
}
|
||
|
|
||
|
if ( m_nOldBotSkill != m_nBotSkill )
|
||
|
{
|
||
|
UpdateMVMEyeGlowEffect( true );
|
||
|
|
||
|
m_nOldBotSkill = m_nBotSkill;
|
||
|
}
|
||
|
|
||
|
if ( m_nOldMaxHealth != GetMaxHealth() )
|
||
|
{
|
||
|
UpdateMVMEyeGlowEffect( true );
|
||
|
|
||
|
m_nOldMaxHealth = GetMaxHealth();
|
||
|
}
|
||
|
|
||
|
if ( m_nPrevTauntSlot != m_nActiveTauntSlot || m_iPrevTauntItemDefIndex != m_iTauntItemDefIndex )
|
||
|
{
|
||
|
UpdateTauntItem();
|
||
|
}
|
||
|
|
||
|
if ( m_iOldKartHealth != m_iKartHealth )
|
||
|
{
|
||
|
UpdateKartEffects();
|
||
|
}
|
||
|
|
||
|
if ( m_iOldKartState != m_iKartState )
|
||
|
{
|
||
|
UpdateKartState();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GetAttributeManager()->OnDataChanged( updateType );
|
||
|
|
||
|
// Check for full health and remove decals.
|
||
|
if ( ( m_iHealth > m_iOldHealth && m_iHealth >= GetMaxHealth() ) || m_Shared.IsInvulnerable() )
|
||
|
{
|
||
|
// If we were just fully healed, remove all decals
|
||
|
RemoveAllDecals();
|
||
|
}
|
||
|
|
||
|
if ( ( m_iOldHealth != m_iHealth ) || ( m_iOldTeam != GetTeamNumber() ) )
|
||
|
{
|
||
|
UpdateGlowColor();
|
||
|
}
|
||
|
bool bNeedsStudentGlow = m_hCoach && m_hCoach->IsLocalPlayer() && m_hCoach->m_bIsCoaching;
|
||
|
bool bHasStudentGlow = m_pStudentGlowEffect != NULL;
|
||
|
if ( bNeedsStudentGlow != bHasStudentGlow )
|
||
|
{
|
||
|
UpdateGlowEffect();
|
||
|
}
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_KING_BUFFED ) )
|
||
|
{
|
||
|
const char *m_szRadiusEffect;
|
||
|
int nTeamNumber = GetTeamNumber();
|
||
|
if ( IsPlayerClass( TF_CLASS_SPY ) && m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
if ( !IsLocalPlayer() && GetTeamNumber() == GetLocalPlayerTeam() ) // Always display own team colors even when disguised, unless it's you (same rules as uber skin)
|
||
|
{
|
||
|
nTeamNumber = GetLocalPlayerTeam();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nTeamNumber = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( nTeamNumber == TF_TEAM_RED )
|
||
|
{
|
||
|
m_szRadiusEffect = "powerup_king_red";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_szRadiusEffect = "powerup_king_blue";
|
||
|
}
|
||
|
if ( !m_pKingBuffRadiusEffect )
|
||
|
{
|
||
|
m_pKingBuffRadiusEffect = ParticleProp()->Create( m_szRadiusEffect, PATTACH_ABSORIGIN_FOLLOW );
|
||
|
}
|
||
|
}
|
||
|
else if ( m_pKingBuffRadiusEffect )
|
||
|
{
|
||
|
m_Shared.EndKingBuffRadiusEffect();
|
||
|
}
|
||
|
|
||
|
bool bNeedsPowerupGlow = ShouldShowPowerupGlowEffect();
|
||
|
bool bHasPowerupGlow = m_pPowerupGlowEffect != NULL;
|
||
|
if ( bNeedsPowerupGlow != bHasPowerupGlow )
|
||
|
{
|
||
|
UpdateGlowEffect();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Detect class changes
|
||
|
if ( m_iOldPlayerClass != m_PlayerClass.GetClassIndex() )
|
||
|
{
|
||
|
OnPlayerClassChange();
|
||
|
}
|
||
|
|
||
|
bool bJustSpawned = false;
|
||
|
|
||
|
if ( m_iOldSpawnCounter != m_iSpawnCounter )
|
||
|
{
|
||
|
ClientPlayerRespawn();
|
||
|
|
||
|
bJustSpawned = true;
|
||
|
}
|
||
|
|
||
|
if ( m_bSaveMeParity != m_bOldSaveMeParity )
|
||
|
{
|
||
|
// Player has triggered a save me command
|
||
|
CreateSaveMeEffect();
|
||
|
}
|
||
|
|
||
|
// To better support old demos, which have some screwed up flags, we just ignore various things if we're a SourceTV client.
|
||
|
if ( !IsHLTV() )
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_BURNING ) && !m_pBurningSound )
|
||
|
{
|
||
|
StartBurningSound();
|
||
|
}
|
||
|
|
||
|
bool bShouldShowIconForIT = TFGameRules() && TFGameRules()->IsIT( this ) && !IsLocalPlayer();
|
||
|
if ( bShouldShowIconForIT != m_bIsDisplayingIconForIT )
|
||
|
{
|
||
|
ShowIconForIT( bShouldShowIconForIT );
|
||
|
}
|
||
|
|
||
|
bool bShouldShowBirthdayEffect = false;//TFGameRules() && ( TFGameRules()->GetBirthdayPlayer() == this ) && !IsLocalPlayer();
|
||
|
if ( bShouldShowBirthdayEffect != m_bShouldShowBirthdayEffect )
|
||
|
{
|
||
|
ShowBirthdayEffect( bShouldShowBirthdayEffect );
|
||
|
}
|
||
|
|
||
|
bool bShouldShowDuelingIcon = ShouldShowDuelingIcon();
|
||
|
if ( bShouldShowDuelingIcon != m_bIsDisplayingDuelingIcon )
|
||
|
{
|
||
|
ShowDuelingIcon( bShouldShowDuelingIcon );
|
||
|
}
|
||
|
|
||
|
// See if we should show or hide nemesis icon for this player
|
||
|
bool bShouldDisplayNemesisIcon = ( !bShouldShowDuelingIcon && !m_bIsDisplayingIconForIT && ShouldShowNemesisIcon() );
|
||
|
if ( bShouldDisplayNemesisIcon != m_bIsDisplayingNemesisIcon )
|
||
|
{
|
||
|
ShowNemesisIcon( bShouldDisplayNemesisIcon );
|
||
|
}
|
||
|
|
||
|
m_Shared.OnDataChanged();
|
||
|
|
||
|
if ( m_bDisguised != m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
m_flDisguiseEndEffectStartTime = MAX( m_flDisguiseEndEffectStartTime, gpGlobals->curtime );
|
||
|
|
||
|
// Update visibility of any worn items.
|
||
|
UpdateWearables();
|
||
|
SetBodygroupsDirty();
|
||
|
|
||
|
// Remove decals.
|
||
|
RemoveAllDecals();
|
||
|
|
||
|
if ( GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY )
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
UpdateMVMEyeGlowEffect( false );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UpdateMVMEyeGlowEffect( true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int nNewWaterLevel = GetWaterLevel();
|
||
|
|
||
|
if ( nNewWaterLevel != m_nOldWaterLevel )
|
||
|
{
|
||
|
if ( ( m_nOldWaterLevel == WL_NotInWater ) && ( nNewWaterLevel > WL_NotInWater ) )
|
||
|
{
|
||
|
// Set when we do a transition to/from partially in water to completely out
|
||
|
m_flWaterEntryTime = gpGlobals->curtime;
|
||
|
}
|
||
|
|
||
|
// If player is now up to his eyes in water and has entered the water very recently (not just bobbing eyes in and out), play a bubble effect.
|
||
|
if ( ( nNewWaterLevel == WL_Eyes ) && ( gpGlobals->curtime - m_flWaterEntryTime ) < 0.5f )
|
||
|
{
|
||
|
CNewParticleEffect *pEffect = ParticleProp()->Create( "water_playerdive", PATTACH_ABSORIGIN_FOLLOW );
|
||
|
ParticleProp()->AddControlPoint( pEffect, 1, NULL, PATTACH_WORLDORIGIN, NULL, WorldSpaceCenter() );
|
||
|
}
|
||
|
// If player was up to his eyes in water and is now out to waist level or less, play a water drip effect
|
||
|
else if ( m_nOldWaterLevel == WL_Eyes && ( nNewWaterLevel < WL_Eyes ) && !bJustSpawned )
|
||
|
{
|
||
|
CNewParticleEffect *pWaterExitEffect = ParticleProp()->Create( "water_playeremerge", PATTACH_ABSORIGIN_FOLLOW );
|
||
|
ParticleProp()->AddControlPoint( pWaterExitEffect, 1, this, PATTACH_ABSORIGIN_FOLLOW );
|
||
|
m_bWaterExitEffectActive = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
if ( updateType == DATA_UPDATE_CREATED )
|
||
|
{
|
||
|
SetupHeadLabelMaterials();
|
||
|
GetClientVoiceMgr()->SetHeadLabelOffset( 50 );
|
||
|
}
|
||
|
|
||
|
if ( m_iOldTeam != GetTeamNumber() )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changeteam" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
if ( IsX360() )
|
||
|
{
|
||
|
const char *pTeam = NULL;
|
||
|
switch( GetTeamNumber() )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
pTeam = "red";
|
||
|
break;
|
||
|
|
||
|
case TF_TEAM_BLUE:
|
||
|
pTeam = "blue";
|
||
|
break;
|
||
|
|
||
|
case TEAM_SPECTATOR:
|
||
|
pTeam = "spectate";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( pTeam )
|
||
|
{
|
||
|
engine->ChangeTeam( pTeam );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// let the server know that we're using a VR headset
|
||
|
if ( UseVR() )
|
||
|
{
|
||
|
KeyValues *kv = new KeyValues( "UsingVRHeadset" );
|
||
|
engine->ServerCmdKeyValues( kv );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !IsPlayerClass(m_iOldClass) )
|
||
|
{
|
||
|
m_flChangeClassTime = gpGlobals->curtime;
|
||
|
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changeclass" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "updateType", updateType );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
// Spy Tranq Mark
|
||
|
// Update tranq mark if you were or are a spy
|
||
|
if ( m_iOldClass == TF_CLASS_SPY || IsPlayerClass( TF_CLASS_SPY ) )
|
||
|
{
|
||
|
bool bShowTranqMark = IsPlayerClass( TF_CLASS_SPY );
|
||
|
for ( int iPlayerIndex = 1; iPlayerIndex <= MAX_PLAYERS; ++iPlayerIndex )
|
||
|
{
|
||
|
C_TFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
pTFPlayer->UpdateTranqMark( bShowTranqMark );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
}
|
||
|
|
||
|
bool bUpdateAttachedWeapons = (GetObserverTarget() != m_hOldObserverTarget);
|
||
|
if ( m_iOldObserverMode != GetObserverMode() )
|
||
|
{
|
||
|
if ( m_iOldObserverMode == OBS_MODE_NONE )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_becameobserver" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
// NVNT send spectator nav
|
||
|
if ( haptics )
|
||
|
haptics->SetNavigationClass("spectate");
|
||
|
}
|
||
|
else if ( m_iOldObserverMode < OBS_MODE_FIXED && GetObserverMode() >= OBS_MODE_FIXED )
|
||
|
{
|
||
|
#if defined( REPLAY_ENABLED )
|
||
|
// If the player is entering a title for a replay, defer displaying the items screen until afterwards
|
||
|
if ( !IsReplayInputPanelVisible() )
|
||
|
#endif
|
||
|
{
|
||
|
// Show items we've picked up when we exit freezecam, or after deathcam on suicide
|
||
|
TFInventoryManager()->ShowItemsPickedUp();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_iOldObserverMode == OBS_MODE_IN_EYE || GetObserverMode() == OBS_MODE_IN_EYE )
|
||
|
{
|
||
|
bUpdateAttachedWeapons = true;
|
||
|
}
|
||
|
|
||
|
if ( m_iOldObserverMode == OBS_MODE_IN_EYE )
|
||
|
{
|
||
|
CBaseEntity* pObserveTarget = GetObserverTarget();
|
||
|
if( pObserveTarget )
|
||
|
{
|
||
|
pObserveTarget->UpdateVisibility();
|
||
|
}
|
||
|
}
|
||
|
// NVNT send onfoot nav if observer mode is none.
|
||
|
if(GetObserverMode()==OBS_MODE_NONE &&haptics)
|
||
|
{
|
||
|
haptics->SetNavigationClass("on_foot");
|
||
|
}
|
||
|
|
||
|
if ( IsReplay() )
|
||
|
{
|
||
|
m_iOldObserverMode = GetObserverMode();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bUpdateAttachedWeapons )
|
||
|
{
|
||
|
C_TFPlayer *pTFOldObserverTarget = ToTFPlayer( m_hOldObserverTarget.Get() );
|
||
|
if ( m_hOldObserverTarget != GetObserverTarget() && pTFOldObserverTarget )
|
||
|
{
|
||
|
C_TFWeaponBase *pWeapon = pTFOldObserverTarget->m_Shared.GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateAttachmentModels();
|
||
|
}
|
||
|
|
||
|
// Update visibility of any worn items.
|
||
|
pTFOldObserverTarget->UpdateWearables();
|
||
|
pTFOldObserverTarget->SetBodygroupsDirty();
|
||
|
|
||
|
if ( IsReplay() )
|
||
|
{
|
||
|
m_hOldObserverTarget = GetObserverTarget();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
C_TFPlayer *pTFObserverTarget = ToTFPlayer( GetObserverTarget() );
|
||
|
if ( pTFObserverTarget )
|
||
|
{
|
||
|
C_TFWeaponBase *pWeapon = pTFObserverTarget->m_Shared.GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateAttachmentModels();
|
||
|
}
|
||
|
|
||
|
// Update visibility of any worn items.
|
||
|
pTFObserverTarget->UpdateWearables();
|
||
|
pTFObserverTarget->SetBodygroupsDirty();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_iOldClass == TF_CLASS_SPY &&
|
||
|
( m_bDisguised != m_Shared.InCond( TF_COND_DISGUISED ) || m_iOldDisguiseClass != m_Shared.GetDisguiseClass() ) )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changedisguise" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetBool( "disguised", m_Shared.InCond( TF_COND_DISGUISED ) );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If our metal amount changed, send a game event
|
||
|
int iCurrentMetal = GetAmmoCount( TF_AMMO_METAL );
|
||
|
|
||
|
if ( iCurrentMetal != m_iPreviousMetal )
|
||
|
{
|
||
|
//msg
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_account_changed" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "old_account", m_iPreviousMetal );
|
||
|
event->SetInt( "new_account", iCurrentMetal );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
|
||
|
m_iPreviousMetal = iCurrentMetal;
|
||
|
}
|
||
|
|
||
|
// did the local player get any health?
|
||
|
if ( m_iHealth > m_iOldHealth )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_healed" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "amount", m_iHealth - m_iOldHealth );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_nOldCurrency != m_nCurrency )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_currency_changed" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "currency", m_nCurrency );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_bOldCustomModelVisible != m_PlayerClass.CustomModelIsVisibleToSelf() )
|
||
|
{
|
||
|
UpdateVisibility();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
C_TFWeaponBase *pOldActiveWeapon = assert_cast< CTFWeaponBase* >( m_hOldActiveWeapon.Get() );
|
||
|
C_TFWeaponBase *pActiveWeapon = GetActiveTFWeapon();
|
||
|
|
||
|
if ( pOldActiveWeapon != pActiveWeapon )
|
||
|
{
|
||
|
// make sure weapons data are up to date before doing anything here
|
||
|
UpdateClientData();
|
||
|
if ( pOldActiveWeapon )
|
||
|
{
|
||
|
pOldActiveWeapon->UpdateVisibility();
|
||
|
}
|
||
|
|
||
|
if ( pActiveWeapon )
|
||
|
{
|
||
|
pActiveWeapon->UpdateVisibility();
|
||
|
|
||
|
if ( GetLocalTFPlayer() == this && pActiveWeapon->CanInspect() )
|
||
|
{
|
||
|
HandleInspectHint();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_hOldActiveWeapon = pActiveWeapon;
|
||
|
|
||
|
// Update HandPoses if needed
|
||
|
CBaseAnimating *pWeaponModel = GetRenderedWeaponModel();
|
||
|
if ( !pWeaponModel )
|
||
|
return;
|
||
|
|
||
|
int iPose = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iPose, righthand_pose_parameter );
|
||
|
|
||
|
float flMin, flMax = 0;
|
||
|
int iPoseParam = LookupPoseParameter( "r_hand_grip" );
|
||
|
if ( iPoseParam > 0 )
|
||
|
{
|
||
|
// SetPoseParameter() normalize's values. r_hand_grip is special and goes beyond 0..1 so we have to counter act the normalization
|
||
|
GetPoseParameterRange( iPoseParam, flMin, flMax );
|
||
|
SetPoseParameter( iPoseParam, iPose * flMax );
|
||
|
}
|
||
|
pWeaponModel->SetPoseParameter( "r_hand_grip", iPose );
|
||
|
}
|
||
|
|
||
|
// Some time in this network transmit we changed the size of the object array.
|
||
|
// recalc the whole thing and update the hud
|
||
|
if ( m_bUpdateObjectHudState )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "building_info_changed" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "building_type", -1 );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
|
||
|
m_bUpdateObjectHudState = false;
|
||
|
}
|
||
|
|
||
|
if ( m_iOldTeam != GetTeamNumber() )
|
||
|
{
|
||
|
if ( GetTeamNumber() == TEAM_SPECTATOR )
|
||
|
{
|
||
|
m_rtJoinedSpectatorTeam = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetServerRealTime() : CRTime::RTime32TimeCur();
|
||
|
}
|
||
|
else if ( m_iOldTeam != TF_TEAM_RED && m_iOldTeam != TF_TEAM_BLUE )
|
||
|
{
|
||
|
m_rtJoinedNormalTeam = steamapicontext && steamapicontext->SteamUtils() ? steamapicontext->SteamUtils()->GetServerRealTime() : CRTime::RTime32TimeCur();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateTauntItem()
|
||
|
{
|
||
|
if ( m_nActiveTauntSlot == LOADOUT_POSITION_INVALID )
|
||
|
{
|
||
|
if ( m_iTauntItemDefIndex != INVALID_ITEM_DEF_INDEX )
|
||
|
{
|
||
|
m_TauntEconItemView.Init( m_iTauntItemDefIndex, AE_UNIQUE, 1 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_TauntEconItemView.Invalidate();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int iClass = GetPlayerClass()->GetClassIndex();
|
||
|
|
||
|
CEconItemView *pMiscItemView = Inventory() ? Inventory()->GetItemInLoadout( iClass, m_nActiveTauntSlot ) : NULL;
|
||
|
if ( pMiscItemView )
|
||
|
{
|
||
|
m_TauntEconItemView = *pMiscItemView;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_TauntEconItemView.IsValid() )
|
||
|
{
|
||
|
ParseSharedTauntDataFromEconItemView( &m_TauntEconItemView );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ConVar tf_halloween_kart_sound_slow_pitch( "tf_halloween_kart_sound_slow_pitch", "30.f", FCVAR_CHEAT | FCVAR_REPLICATED );
|
||
|
ConVar tf_halloween_kart_sound_fast_pitch( "tf_halloween_kart_sound_fast_pitch", "80.f", FCVAR_CHEAT | FCVAR_REPLICATED );
|
||
|
extern ConVar tf_halloween_kart_dash_speed;
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateKartEffects()
|
||
|
{
|
||
|
if ( m_hKartDamageEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_hKartDamageEffect );
|
||
|
m_hKartDamageEffect = NULL;
|
||
|
}
|
||
|
|
||
|
if ( !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
|
||
|
return;
|
||
|
|
||
|
m_iOldKartHealth = m_iKartHealth;
|
||
|
|
||
|
const char *pszEffect = "";
|
||
|
if ( m_iKartHealth > 150 )
|
||
|
{
|
||
|
pszEffect = "kartdamage_4";
|
||
|
m_hKartDamageEffect = ParticleProp()->Create( pszEffect, PATTACH_ABSORIGIN_FOLLOW, INVALID_PARTICLE_ATTACHMENT, Vector(0,0,40) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateKartState()
|
||
|
{
|
||
|
if ( !m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
|
||
|
return;
|
||
|
|
||
|
m_iOldKartState = m_iKartState;
|
||
|
|
||
|
// turn brake on and off
|
||
|
|
||
|
if ( m_iKartState & CTFPlayerShared::kKartState_Braking )
|
||
|
{
|
||
|
StartKartBrakeEffect();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StopKartBrakeEffect();
|
||
|
}
|
||
|
}
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateKartSounds()
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
// Create our engine sound if we dont have one and need one
|
||
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
|
||
|
{
|
||
|
if ( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] == NULL )
|
||
|
{
|
||
|
CBroadcastRecipientFilter filter;
|
||
|
m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] = controller.SoundCreate( filter, entindex(), "BumperCar.GoLoop" );
|
||
|
controller.Play( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ], 1.f, 1.f );
|
||
|
}
|
||
|
}
|
||
|
else if ( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] )
|
||
|
{
|
||
|
controller.SoundDestroy( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] );
|
||
|
m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] = NULL;
|
||
|
}
|
||
|
|
||
|
if ( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ] )
|
||
|
{
|
||
|
float flTargetPitch = RemapValClamped( GetCurrentTauntMoveSpeed(), 0.f, tf_halloween_kart_dash_speed.GetFloat(), tf_halloween_kart_sound_slow_pitch.GetFloat(), tf_halloween_kart_sound_fast_pitch.GetFloat() );
|
||
|
controller.SoundChangePitch( m_pKartSounds[ KART_SOUND_ENGINE_LOOP ], flTargetPitch, 0.1f );
|
||
|
}
|
||
|
|
||
|
// Uncomment this (if) when we have a tire screech sound
|
||
|
//if ( ( m_iKartState & CTFPlayerShared::kKartState_Driving ) && GetCurrentTauntMoveSpeed() < 300.f )
|
||
|
//{
|
||
|
// if ( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] == NULL )
|
||
|
// {
|
||
|
// CBroadcastRecipientFilter filter;
|
||
|
// m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] = controller.SoundCreate( filter, entindex(), "BumperCar.GoLoop" );
|
||
|
// controller.Play( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ], 1.f, 80.f );
|
||
|
// }
|
||
|
//}
|
||
|
//else if ( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] )
|
||
|
//{
|
||
|
// controller.SoundDestroy( m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] );
|
||
|
// m_pKartSounds[ KART_SOUND_BURNOUT_LOOP ] = NULL;
|
||
|
//}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::InitInvulnerableMaterial( void )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pLocalPlayer )
|
||
|
return;
|
||
|
|
||
|
const char *pszMaterial = NULL;
|
||
|
|
||
|
int iVisibleTeam = GetTeamNumber();
|
||
|
// if this player is disguised and on the other team, use disguise team
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) && !InSameTeam( pLocalPlayer ) )
|
||
|
{
|
||
|
iVisibleTeam = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
|
||
|
switch ( iVisibleTeam )
|
||
|
{
|
||
|
case TF_TEAM_BLUE:
|
||
|
pszMaterial = "models/effects/invulnfx_blue.vmt";
|
||
|
break;
|
||
|
case TF_TEAM_RED:
|
||
|
pszMaterial = "models/effects/invulnfx_red.vmt";
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( pszMaterial )
|
||
|
{
|
||
|
m_InvulnerableMaterial.Init( pszMaterial, TEXTURE_GROUP_CLIENT_EFFECTS );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_InvulnerableMaterial.Shutdown();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StartBurningSound( void )
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
|
||
|
if ( !m_pBurningSound )
|
||
|
{
|
||
|
CLocalPlayerFilter filter;
|
||
|
m_pBurningSound = controller.SoundCreate( filter, entindex(), "Player.OnFire" );
|
||
|
}
|
||
|
|
||
|
controller.Play( m_pBurningSound, 0.0, 100 );
|
||
|
controller.SoundChangeVolume( m_pBurningSound, 1.0, 0.1 );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopBurningSound( void )
|
||
|
{
|
||
|
if ( m_pBurningSound )
|
||
|
{
|
||
|
CSoundEnvelopeController::GetController().SoundDestroy( m_pBurningSound );
|
||
|
m_pBurningSound = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopBlastJumpLoopSound( int iUserID )
|
||
|
{
|
||
|
if ( m_pBlastJumpLoop )
|
||
|
{
|
||
|
if ( GetUserID() == iUserID )
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
controller.SoundDestroy( m_pBlastJumpLoop );
|
||
|
m_pBlastJumpLoop = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateRecentlyTeleportedEffect( void )
|
||
|
{
|
||
|
bool bShow = m_Shared.ShouldShowRecentlyTeleported();
|
||
|
|
||
|
if ( bShow )
|
||
|
{
|
||
|
// NVNT if this is the local player notify haptics system of this teleporter
|
||
|
// teleporting.
|
||
|
if(IsLocalPlayer()&&!tfHaptics.wasBeingTeleported) {
|
||
|
if ( haptics )
|
||
|
haptics->ProcessHapticEvent(2,"Game","player_teleport");
|
||
|
tfHaptics.wasBeingHealed = true;
|
||
|
}
|
||
|
if ( !m_pTeleporterEffect )
|
||
|
{
|
||
|
const char *pszEffectName = NULL;
|
||
|
|
||
|
int iTeam = GetTeamNumber();
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
iTeam = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
|
||
|
switch( iTeam )
|
||
|
{
|
||
|
case TF_TEAM_BLUE:
|
||
|
pszEffectName = "player_recent_teleport_blue";
|
||
|
break;
|
||
|
case TF_TEAM_RED:
|
||
|
pszEffectName = "player_recent_teleport_red";
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( TFGameRules()->IsMannVsMachineMode() && IsABot() )
|
||
|
{
|
||
|
#if 0 // Nice idea, but it's chewing into our particle budget, and because bots currently spawn in ubered it's nearly invisible.
|
||
|
pszEffectName = "bot_recent_teleport_blue";
|
||
|
#else
|
||
|
pszEffectName = NULL;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if ( pszEffectName )
|
||
|
{
|
||
|
m_pTeleporterEffect = ParticleProp()->Create( pszEffectName, PATTACH_ABSORIGIN_FOLLOW );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// NVNT if this is the local player and we were being teleported
|
||
|
// flag that we are no longer teleporting.
|
||
|
if(IsLocalPlayer()&&tfHaptics.wasBeingTeleported) {
|
||
|
tfHaptics.wasBeingHealed = false;
|
||
|
}
|
||
|
|
||
|
if ( m_pTeleporterEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pTeleporterEffect );
|
||
|
m_pTeleporterEffect = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdatedMarkedForDeathEffect( bool bForceStop )
|
||
|
{
|
||
|
|
||
|
// Dont show the particle over the local player's head. They have the icon that shows
|
||
|
// up over their health in the HUD which serves this purpose.
|
||
|
if ( IsLocalPlayer() )
|
||
|
return;
|
||
|
|
||
|
bool bShow = m_Shared.InCond( TF_COND_MARKEDFORDEATH ) || m_Shared.InCond( TF_COND_MARKEDFORDEATH_SILENT ) || m_Shared.InCond( TF_COND_PASSTIME_PENALTY_DEBUFF );
|
||
|
|
||
|
// force stop
|
||
|
if ( bForceStop || m_Shared.IsStealthed() || m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
bShow = false;
|
||
|
}
|
||
|
|
||
|
if ( !bShow )
|
||
|
{
|
||
|
// Stop and then go
|
||
|
RemoveOverheadEffect( "mark_for_death", true );
|
||
|
}
|
||
|
|
||
|
if ( bShow )
|
||
|
{
|
||
|
AddOverheadEffect( "mark_for_death" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Show an icon above the player's head to let other players know which Powerup Rune they are carrying
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateRuneIcon( bool bForceStop /*= false */ )
|
||
|
{
|
||
|
// Don't show the particle over the local player's head. They have the icon that shows
|
||
|
// up over their health in the HUD which serves this purpose.
|
||
|
if ( IsLocalPlayer() )
|
||
|
return;
|
||
|
|
||
|
const RuneTypes_t carryingRuneType = m_Shared.GetCarryingRuneType();
|
||
|
const bool bAllowedToShow = ( m_Shared.IsCarryingRune() && !m_Shared.IsStealthed() );
|
||
|
int iTeam = IsEnemyPlayer() && m_Shared.InCond( TF_COND_DISGUISED ) ? m_Shared.GetDisguiseTeam() : GetTeamNumber();
|
||
|
|
||
|
if ( !bAllowedToShow || bForceStop || ( carryingRuneType != m_eDisplayingRuneIcon ) )
|
||
|
{
|
||
|
// remove all particle for both team just in case
|
||
|
for ( int i=0; i<RUNE_TYPES_MAX; ++i )
|
||
|
{
|
||
|
RuneTypes_t type = RuneTypes_t(i);
|
||
|
RemoveOverheadEffect( GetPowerupIconName( type, TF_TEAM_RED ), true );
|
||
|
RemoveOverheadEffect( GetPowerupIconName( type, TF_TEAM_BLUE ), true );
|
||
|
}
|
||
|
m_eDisplayingRuneIcon = RUNE_NONE;
|
||
|
}
|
||
|
|
||
|
if ( bAllowedToShow && ( carryingRuneType != m_eDisplayingRuneIcon ) )
|
||
|
{
|
||
|
const char* pszEffect = NULL;
|
||
|
if ( carryingRuneType > RUNE_NONE && carryingRuneType < RUNE_TYPES_MAX )
|
||
|
{
|
||
|
pszEffect = GetPowerupIconName( carryingRuneType, iTeam );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#ifdef STAGING_ONLY
|
||
|
Assert( !"unexepected value in GetCarryingRuneType, probably a bug" );
|
||
|
pszEffect = "(null)"; // so we don't crash in the messages below.
|
||
|
Msg( "GetCarryingRuneType had surprising value: %d for player %s\n", carryingRuneType, GetPlayerName() );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if ( AddOverheadEffect( pszEffect ) )
|
||
|
{
|
||
|
m_eDisplayingRuneIcon = carryingRuneType;
|
||
|
}
|
||
|
#ifdef STAGING_ONLY
|
||
|
else
|
||
|
{
|
||
|
Msg( "ParticleProp()->Create failed for some reason for powerup %s, for player %s.\n", pszEffect, GetPlayerName() );
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::OnPlayerClassChange( void )
|
||
|
{
|
||
|
// Init the anim movement vars
|
||
|
m_PlayerAnimState->SetRunSpeed( GetPlayerClass()->GetMaxSpeed() );
|
||
|
m_PlayerAnimState->SetWalkSpeed( GetPlayerClass()->GetMaxSpeed() * 0.5 );
|
||
|
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
g_ItemEffectMeterManager.SetPlayer( this );
|
||
|
}
|
||
|
ShowNemesisIcon( false );
|
||
|
ShowDuelingIcon( false );
|
||
|
|
||
|
SetAppropriateCamera( this );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::InitPhonemeMappings()
|
||
|
{
|
||
|
CStudioHdr *pStudio = GetModelPtr();
|
||
|
if ( pStudio )
|
||
|
{
|
||
|
char szBasename[MAX_PATH];
|
||
|
Q_StripExtension( pStudio->pszName(), szBasename, sizeof( szBasename ) );
|
||
|
char szExpressionName[MAX_PATH];
|
||
|
Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename );
|
||
|
if ( FindSceneFile( szExpressionName ) )
|
||
|
{
|
||
|
SetupMappings( szExpressionName );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
BaseClass::InitPhonemeMappings();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ResetFlexWeights( CStudioHdr *pStudioHdr )
|
||
|
{
|
||
|
if ( !pStudioHdr || pStudioHdr->numflexdesc() == 0 )
|
||
|
return;
|
||
|
|
||
|
// Reset the flex weights to their starting position.
|
||
|
LocalFlexController_t iController;
|
||
|
for ( iController = LocalFlexController_t(0); iController < pStudioHdr->numflexcontrollers(); ++iController )
|
||
|
{
|
||
|
SetFlexWeight( iController, 0.0f );
|
||
|
}
|
||
|
|
||
|
// Reset the prediction interpolation values.
|
||
|
m_iv_flexWeight.Reset();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov )
|
||
|
{
|
||
|
QAngle myEyeAngles;
|
||
|
VectorCopy( EyeAngles(), myEyeAngles );
|
||
|
BaseClass::CalcInEyeCamView( eyeOrigin, eyeAngles, fov );
|
||
|
|
||
|
/*
|
||
|
// if we are coaching, we override the eye angles with our original ones
|
||
|
// @note Tom Bui: we don't try to capture the "up" button event, because that doesn't seem so reliable
|
||
|
if ( m_bIsCoaching )
|
||
|
{
|
||
|
const float kLerpTime = 1.0f;
|
||
|
if ( ( m_nButtons & IN_JUMP ) != 0 )
|
||
|
{
|
||
|
VectorCopy( myEyeAngles, eyeAngles );
|
||
|
engine->SetViewAngles( eyeAngles );
|
||
|
m_flCoachLookAroundLerpTime = kLerpTime;
|
||
|
m_angCoachLookAroundEyeAngles = myEyeAngles;
|
||
|
}
|
||
|
else if ( m_flCoachLookAroundLerpTime > 0 )
|
||
|
{
|
||
|
m_flCoachLookAroundLerpTime -= gpGlobals->frametime;
|
||
|
float flPercent = ( kLerpTime - m_flCoachLookAroundLerpTime / kLerpTime );
|
||
|
eyeAngles = Lerp( flPercent, m_angCoachLookAroundEyeAngles, eyeAngles );
|
||
|
engine->SetViewAngles( eyeAngles );
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CStudioHdr *C_TFPlayer::OnNewModel( void )
|
||
|
{
|
||
|
CStudioHdr *hdr = BaseClass::OnNewModel();
|
||
|
|
||
|
// Initialize the gibs.
|
||
|
InitPlayerGibs();
|
||
|
|
||
|
InitializePoseParams();
|
||
|
|
||
|
// Init flexes, cancel any scenes we're playing
|
||
|
ClearSceneEvents( NULL, false );
|
||
|
|
||
|
// Reset the flex weights.
|
||
|
ResetFlexWeights( hdr );
|
||
|
|
||
|
// Reset the players animation states, gestures
|
||
|
if ( m_PlayerAnimState )
|
||
|
{
|
||
|
m_PlayerAnimState->OnNewModel();
|
||
|
}
|
||
|
|
||
|
if ( hdr )
|
||
|
{
|
||
|
InitPhonemeMappings();
|
||
|
}
|
||
|
|
||
|
if ( IsPlayerClass( TF_CLASS_SPY ) )
|
||
|
{
|
||
|
m_iSpyMaskBodygroup = FindBodygroupByName( "spyMask" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_iSpyMaskBodygroup = -1;
|
||
|
}
|
||
|
|
||
|
return hdr;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Is this player an enemy to the local player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsEnemyPlayer( void )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
if ( !pLocalPlayer )
|
||
|
return false;
|
||
|
|
||
|
int iTeam = pLocalPlayer->GetTeamNumber();
|
||
|
|
||
|
// if we are coaching, use the team of the student
|
||
|
if ( pLocalPlayer->m_hStudent && pLocalPlayer->m_bIsCoaching )
|
||
|
{
|
||
|
iTeam = pLocalPlayer->m_hStudent->GetTeamNumber();
|
||
|
}
|
||
|
|
||
|
switch( iTeam )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
return ( GetTeamNumber() == TF_TEAM_BLUE );
|
||
|
|
||
|
case TF_TEAM_BLUE:
|
||
|
return ( GetTeamNumber() == TF_TEAM_RED );
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Displays a nemesis icon on this player to the local player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ShowNemesisIcon( bool bShow )
|
||
|
{
|
||
|
if ( bShow )
|
||
|
{
|
||
|
const char *pszEffect = NULL;
|
||
|
switch ( GetTeamNumber() )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
pszEffect = "particle_nemesis_red";
|
||
|
break;
|
||
|
case TF_TEAM_BLUE:
|
||
|
pszEffect = "particle_nemesis_blue";
|
||
|
break;
|
||
|
default:
|
||
|
return; // shouldn't get called if we're not on a team; bail out if it does
|
||
|
}
|
||
|
AddOverheadEffect( pszEffect );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// stop effects for both team colors (to make sure we remove effects in event of team change)
|
||
|
RemoveOverheadEffect( "particle_nemesis_red", true );
|
||
|
RemoveOverheadEffect( "particle_nemesis_blue", true );
|
||
|
}
|
||
|
m_bIsDisplayingNemesisIcon = bShow;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Displays a dueling icon on this player to the local player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ShowDuelingIcon( bool bShow )
|
||
|
{
|
||
|
if ( bShow )
|
||
|
{
|
||
|
const char *pszEffect = NULL;
|
||
|
switch ( GetTeamNumber() )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
pszEffect = "duel_red";
|
||
|
break;
|
||
|
case TF_TEAM_BLUE:
|
||
|
pszEffect = "duel_blue";
|
||
|
break;
|
||
|
default:
|
||
|
return; // shouldn't get called if we're not on a team; bail out if it does
|
||
|
}
|
||
|
AddOverheadEffect( pszEffect );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// stop effects for both team colors (to make sure we remove effects in event of team change)
|
||
|
RemoveOverheadEffect( "duel_red", true );
|
||
|
RemoveOverheadEffect( "duel_blue", true );
|
||
|
}
|
||
|
m_bIsDisplayingDuelingIcon = bShow;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Displays an icon denoting this player as "IT" to the local player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ShowIconForIT( bool bShow )
|
||
|
{
|
||
|
if ( bShow )
|
||
|
{
|
||
|
AddOverheadEffect( "halloween_boss_victim" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
RemoveOverheadEffect( "halloween_boss_victim", true );
|
||
|
}
|
||
|
m_bIsDisplayingIconForIT = bShow;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Displays an icon denoting this player as the Birthday Player to the local player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ShowBirthdayEffect( bool bShow )
|
||
|
{
|
||
|
/*
|
||
|
if ( bShow )
|
||
|
{
|
||
|
ParticleProp()->Create( "birthday_player_circling", PATTACH_POINT_FOLLOW, "head" );
|
||
|
DispatchParticleEffect( "bday_confetti", GetAbsOrigin() + Vector(0,0,32), vec3_angle );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ParticleProp()->StopParticlesNamed( "birthday_player_circling", true );
|
||
|
}
|
||
|
*/
|
||
|
m_bShouldShowBirthdayEffect = bShow;
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::HasBombinomiconEffectOnDeath( void )
|
||
|
{
|
||
|
int iBombinomicomEffectOnDeath = 0;
|
||
|
CALL_ATTRIB_HOOK_INT( iBombinomicomEffectOnDeath, bombinomicon_effect_on_death );
|
||
|
|
||
|
return ( iBombinomicomEffectOnDeath != 0 );
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateTranqMark( bool bShow, bool bForceStop /*= false */ )
|
||
|
{
|
||
|
// Dont show the particle over the local player's head. They have the icon that shows
|
||
|
// up over their health in the HUD which serves this purpose.
|
||
|
// only show this mark to spies
|
||
|
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
|
||
|
if ( IsLocalPlayer() || !pLocalPlayer || !pLocalPlayer->IsPlayerClass( TF_CLASS_SPY ) )
|
||
|
{
|
||
|
bShow = false;
|
||
|
}
|
||
|
|
||
|
int iTranq = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iTranq, override_projectile_type );
|
||
|
if ( iTranq != TF_PROJECTILE_TRANQ )
|
||
|
{
|
||
|
bShow = false;
|
||
|
}
|
||
|
|
||
|
if ( bShow && !m_bIsDisplayingTranqMark && m_Shared.InCond( TF_COND_TRANQ_MARKED ) )
|
||
|
{
|
||
|
AddOverheadEffect( "marked_for_tranq" );
|
||
|
m_bIsDisplayingTranqMark = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( m_bIsDisplayingTranqMark || bForceStop || ( !m_Shared.InCond( TF_COND_TRANQ_MARKED ) ) )
|
||
|
{
|
||
|
RemoveOverheadEffect( "marked_for_tranq", true );
|
||
|
m_bIsDisplayingTranqMark = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateSpyClassStealParticle( bool bShow )
|
||
|
{
|
||
|
if ( bShow )
|
||
|
{
|
||
|
const char * pParticleName = NULL;
|
||
|
if ( GetTeamNumber() == TF_TEAM_RED )
|
||
|
{
|
||
|
pParticleName = "spy_stolen_smoke_red";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pParticleName = "spy_stolen_smoke_blue";
|
||
|
}
|
||
|
AddOverheadEffect( pParticleName );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Turn off both in the case of team change
|
||
|
RemoveOverheadEffect( "spy_stolen_smoke_red", true );
|
||
|
RemoveOverheadEffect( "spy_stolen_smoke_blue", true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
#define TF_TAUNT_PITCH 0
|
||
|
#define TF_TAUNT_YAW 1
|
||
|
#define TF_TAUNT_DIST 2
|
||
|
|
||
|
#define TF_TAUNT_MAXYAW 135
|
||
|
#define TF_TAUNT_MINYAW -135
|
||
|
#define TF_TAUNT_MAXPITCH 90
|
||
|
#define TF_TAUNT_MINPITCH 0
|
||
|
#define TF_TAUNT_IDEALLAG 4.0f
|
||
|
|
||
|
static Vector TF_TAUNTCAM_HULL_MIN( -9.0f, -9.0f, -9.0f );
|
||
|
static Vector TF_TAUNTCAM_HULL_MAX( 9.0f, 9.0f, 9.0f );
|
||
|
|
||
|
static ConVar tf_tauntcam_yaw( "tf_tauntcam_yaw", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
|
||
|
static ConVar tf_tauntcam_pitch( "tf_tauntcam_pitch", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
|
||
|
static ConVar tf_tauntcam_dist( "tf_tauntcam_dist", "150", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
|
||
|
static ConVar tf_tauntcam_speed( "tf_tauntcam_speed", "300", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
ConVar tf_halloween_kart_cam_dist( "tf_halloween_kart_cam_dist", "225", FCVAR_CHEAT );
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::TurnOnTauntCam( void )
|
||
|
{
|
||
|
if ( !IsLocalPlayer() )
|
||
|
return;
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
|
||
|
return;
|
||
|
|
||
|
m_flTauntCamTargetDist = ( m_flTauntCamTargetDist != 0.0f ) ? m_flTauntCamTargetDist : tf_tauntcam_dist.GetFloat();
|
||
|
m_flTauntCamTargetDistUp = ( m_flTauntCamTargetDistUp != 0.0f ) ? m_flTauntCamTargetDistUp : 0.f;
|
||
|
|
||
|
m_flTauntCamCurrentDist = 0.f;
|
||
|
m_flTauntCamCurrentDistUp = 0.f;
|
||
|
|
||
|
// Save the old view angles.
|
||
|
engine->GetViewAngles( m_angTauntEngViewAngles );
|
||
|
prediction->GetViewAngles( m_angTauntPredViewAngles );
|
||
|
|
||
|
m_TauntCameraData.m_flPitch = 0;
|
||
|
m_TauntCameraData.m_flYaw = 0;
|
||
|
m_TauntCameraData.m_flDist = m_flTauntCamTargetDist;
|
||
|
m_TauntCameraData.m_flLag = 1.f;
|
||
|
m_TauntCameraData.m_vecHullMin.Init( -9.0f, -9.0f, -9.0f );
|
||
|
m_TauntCameraData.m_vecHullMax.Init( 9.0f, 9.0f, 9.0f );
|
||
|
|
||
|
if ( tf_taunt_first_person.GetBool() )
|
||
|
{
|
||
|
// Remain in first-person.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_ThirdPersonManager.SetDesiredCameraOffset( Vector( 0, 0, 0 ) );
|
||
|
g_ThirdPersonManager.SetOverridingThirdPerson( true );
|
||
|
|
||
|
::input->CAM_ToThirdPerson();
|
||
|
ThirdPersonSwitch( true );
|
||
|
UpdateKillStreakEffects( m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
|
||
|
}
|
||
|
|
||
|
m_bTauntInterpolating = true;
|
||
|
|
||
|
if ( m_hItem )
|
||
|
{
|
||
|
m_hItem->UpdateVisibility();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::TurnOnTauntCam_Finish( void )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::TurnOffTauntCam( void )
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
|
||
|
return;
|
||
|
|
||
|
// We want to interpolate back into the guy's head.
|
||
|
if ( g_ThirdPersonManager.GetForcedThirdPerson() == false )
|
||
|
{
|
||
|
m_flTauntCamTargetDist = 0.f;
|
||
|
m_TauntCameraData.m_flDist = m_flTauntCamTargetDist;
|
||
|
}
|
||
|
|
||
|
g_ThirdPersonManager.SetOverridingThirdPerson( false );
|
||
|
|
||
|
if ( g_ThirdPersonManager.GetForcedThirdPerson() )
|
||
|
{
|
||
|
TurnOffTauntCam_Finish();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::TurnOffTauntCam_Finish( void )
|
||
|
{
|
||
|
if ( !IsLocalPlayer() )
|
||
|
return;
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
|
||
|
return;
|
||
|
|
||
|
const Vector& vecOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
|
||
|
tf_tauntcam_pitch.SetValue( vecOffset[PITCH] - m_angTauntPredViewAngles[PITCH] );
|
||
|
tf_tauntcam_yaw.SetValue( vecOffset[YAW] - m_angTauntPredViewAngles[YAW] );
|
||
|
|
||
|
QAngle angles;
|
||
|
|
||
|
angles[PITCH] = vecOffset[PITCH];
|
||
|
angles[YAW] = vecOffset[YAW];
|
||
|
angles[DIST] = vecOffset[DIST];
|
||
|
|
||
|
if( g_ThirdPersonManager.WantToUseGameThirdPerson() == false )
|
||
|
{
|
||
|
::input->CAM_ToFirstPerson();
|
||
|
ThirdPersonSwitch( false );
|
||
|
UpdateKillStreakEffects( m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
|
||
|
angles = vec3_angle;
|
||
|
}
|
||
|
|
||
|
::input->CAM_SetCameraThirdData( NULL, angles );
|
||
|
|
||
|
// Reset the old view angles.
|
||
|
// engine->SetViewAngles( m_angTauntEngViewAngles );
|
||
|
// prediction->SetViewAngles( m_angTauntPredViewAngles );
|
||
|
|
||
|
// Force the feet to line up with the view direction post taunt.
|
||
|
// If you are forcing aim yaw, your code is almost definitely broken if you don't include a delay between
|
||
|
// teleporting and forcing yaw. This is due to an unfortunate interaction between the command lookback window,
|
||
|
// and the fact that m_flEyeYaw is never propogated from the server to the client.
|
||
|
// TODO: Fix this after Halloween 2014.
|
||
|
m_PlayerAnimState->m_bForceAimYaw = true;
|
||
|
|
||
|
m_bTauntInterpolating = false;
|
||
|
|
||
|
if ( GetViewModel() )
|
||
|
{
|
||
|
GetViewModel()->UpdateVisibility();
|
||
|
}
|
||
|
|
||
|
if ( m_hItem )
|
||
|
{
|
||
|
m_hItem->UpdateVisibility();
|
||
|
}
|
||
|
|
||
|
SetAppropriateCamera( this );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::HandleTaunting( void )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
// This code is only for the local player.
|
||
|
Assert( pLocalPlayer == NULL || pLocalPlayer == this );
|
||
|
|
||
|
// Clear the taunt slot.
|
||
|
if ( !m_bWasTaunting &&
|
||
|
(
|
||
|
m_Shared.InCond( TF_COND_TAUNTING ) ||
|
||
|
m_Shared.IsControlStunned() ||
|
||
|
m_Shared.IsLoser() ||
|
||
|
m_bIsReadyToHighFive ||
|
||
|
m_nForceTauntCam ||
|
||
|
m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ||
|
||
|
m_Shared.InCond( TF_COND_HALLOWEEN_GIANT ) ||
|
||
|
m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) ||
|
||
|
m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ||
|
||
|
m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ||
|
||
|
m_Shared.InCond( TF_COND_MELEE_ONLY ) ||
|
||
|
m_Shared.InCond( TF_COND_SWIMMING_CURSE )
|
||
|
)
|
||
|
)
|
||
|
{
|
||
|
m_bWasTaunting = true;
|
||
|
|
||
|
// Handle the camera for the local player.
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
|
||
|
TurnOnTauntCam();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( ( !IsAlive() && m_nForceTauntCam < 2 ) ||
|
||
|
(
|
||
|
m_bWasTaunting && !m_Shared.InCond( TF_COND_TAUNTING ) && !m_Shared.IsControlStunned() &&
|
||
|
!m_Shared.InCond( TF_COND_PHASE ) && !m_Shared.IsLoser() && !m_bIsReadyToHighFive &&
|
||
|
!m_nForceTauntCam && !m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) &&
|
||
|
!m_Shared.InCond( TF_COND_HALLOWEEN_THRILLER ) &&
|
||
|
!m_Shared.InCond( TF_COND_HALLOWEEN_GIANT ) &&
|
||
|
!m_Shared.InCond( TF_COND_HALLOWEEN_TINY ) &&
|
||
|
!m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) &&
|
||
|
!m_Shared.InCond( TF_COND_HALLOWEEN_KART ) &&
|
||
|
!m_Shared.InCond( TF_COND_MELEE_ONLY ) &&
|
||
|
!m_Shared.InCond( TF_COND_SWIMMING_CURSE )
|
||
|
)
|
||
|
)
|
||
|
{
|
||
|
m_bWasTaunting = false;
|
||
|
|
||
|
// Clear the vcd slot.
|
||
|
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
|
||
|
|
||
|
// Handle the camera for the local player.
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
TurnOffTauntCam();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TauntCamInterpolation();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Handles third person camera interpolation directly
|
||
|
// so we can manage enter & exit behavior without hacking the camera.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::TauntCamInterpolation()
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
if ( pLocalPlayer && m_bTauntInterpolating )
|
||
|
{
|
||
|
if ( m_flTauntCamCurrentDist != m_flTauntCamTargetDist )
|
||
|
{
|
||
|
m_flTauntCamCurrentDist += Sign( m_flTauntCamTargetDist - m_flTauntCamCurrentDist ) * gpGlobals->frametime * tf_tauntcam_speed.GetFloat();
|
||
|
m_flTauntCamCurrentDist = clamp( m_flTauntCamCurrentDist, m_flTauntCamCurrentDist, m_flTauntCamTargetDist );
|
||
|
}
|
||
|
|
||
|
if ( m_flTauntCamCurrentDistUp != m_flTauntCamTargetDistUp )
|
||
|
{
|
||
|
m_flTauntCamCurrentDistUp += Sign( m_flTauntCamTargetDistUp - m_flTauntCamCurrentDistUp ) * gpGlobals->frametime * tf_tauntcam_speed.GetFloat();
|
||
|
m_flTauntCamCurrentDistUp = clamp( m_flTauntCamCurrentDistUp, m_flTauntCamCurrentDistUp, m_flTauntCamTargetDistUp );
|
||
|
}
|
||
|
|
||
|
const Vector& vecCamOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
|
||
|
|
||
|
Vector vecOrigin = pLocalPlayer->GetLocalOrigin();
|
||
|
vecOrigin += pLocalPlayer->GetViewOffset();
|
||
|
|
||
|
Vector vecForward, vecUp;
|
||
|
AngleVectors( QAngle( vecCamOffset[PITCH], vecCamOffset[YAW], 0 ), &vecForward, NULL, &vecUp );
|
||
|
|
||
|
trace_t trace;
|
||
|
UTIL_TraceHull( vecOrigin, vecOrigin - ( vecForward * m_flTauntCamCurrentDist ) + ( vecUp * m_flTauntCamCurrentDistUp ), Vector( -9.f, -9.f, -9.f ),
|
||
|
Vector( 9.f, 9.f, 9.f ), MASK_SOLID_BRUSHONLY, pLocalPlayer, COLLISION_GROUP_DEBRIS, &trace );
|
||
|
|
||
|
if ( trace.fraction < 1.0 )
|
||
|
m_flTauntCamCurrentDist *= trace.fraction;
|
||
|
|
||
|
QAngle angCameraOffset = QAngle( vecCamOffset[PITCH], vecCamOffset[YAW], m_flTauntCamCurrentDist );
|
||
|
::input->CAM_SetCameraThirdData( &m_TauntCameraData, angCameraOffset ); // Override camera distance interpolation.
|
||
|
|
||
|
g_ThirdPersonManager.SetDesiredCameraOffset( Vector( m_flTauntCamCurrentDist, 0, m_flTauntCamCurrentDistUp ) );
|
||
|
|
||
|
if ( m_flTauntCamCurrentDist == m_flTauntCamTargetDist && m_flTauntCamCurrentDistUp == m_flTauntCamTargetDistUp )
|
||
|
{
|
||
|
if ( m_flTauntCamTargetDist == 0.f )
|
||
|
TurnOffTauntCam_Finish();
|
||
|
else
|
||
|
TurnOnTauntCam_Finish();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::PlayTauntSoundLoop( const char *pszSoundLoopName )
|
||
|
{
|
||
|
if ( pszSoundLoopName && *pszSoundLoopName )
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
CPASAttenuationFilter filter( this );
|
||
|
m_pTauntSoundLoop = controller.SoundCreate( filter, entindex(), pszSoundLoopName );
|
||
|
controller.Play( m_pTauntSoundLoop, 1.0, 100 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopTauntSoundLoop()
|
||
|
{
|
||
|
if ( m_pTauntSoundLoop )
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
controller.SoundDestroy( m_pTauntSoundLoop );
|
||
|
m_pTauntSoundLoop = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Indicates whether the spy's cigarette should be burning or not.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::CanLightCigarette( void )
|
||
|
{
|
||
|
// Used to be a massive if-conditional.
|
||
|
// Expanded for readability.
|
||
|
if ( !IsPlayerClass( TF_CLASS_SPY ) )
|
||
|
return false;
|
||
|
|
||
|
if ( !IsAlive() )
|
||
|
return false;
|
||
|
|
||
|
// Don't light if we are disguised and an enemy (not the spy model).
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
|
||
|
{
|
||
|
if ( m_Shared.GetDisguiseClass() != TF_CLASS_SPY )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// don't light for MvM Spy robots
|
||
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
|
||
|
return false;
|
||
|
|
||
|
// Don't light if we are invis.
|
||
|
if ( GetPercentInvisible() > 0 )
|
||
|
return false;
|
||
|
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
// Don't light for the local player.
|
||
|
if ( ( pLocalPlayer == this ) || !pLocalPlayer )
|
||
|
return false;
|
||
|
|
||
|
// Don't light if we're spectating in first person mode.
|
||
|
if ( (pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE) && (pLocalPlayer->GetObserverTarget() == this) )
|
||
|
return false;
|
||
|
|
||
|
// Don't light if we're covered in urine.
|
||
|
if ( m_Shared.InCond( TF_COND_URINE ) )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Flash the bomb hat at ever increasing frequency
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::HalloweenBombHeadUpdate( void )
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
|
||
|
{
|
||
|
if ( !m_hHalloweenBombHat && gpGlobals->curtime > m_flBombDelay )
|
||
|
{
|
||
|
m_hHalloweenBombHat = C_PlayerAttachedModel::Create( BOMB_HAT_MODEL, this, LookupAttachment("head"), vec3_origin, PAM_PERMANENT, 0 );
|
||
|
m_hHalloweenBombHat->FollowEntity( this, true );
|
||
|
}
|
||
|
|
||
|
if ( m_hHalloweenBombHat )
|
||
|
{
|
||
|
m_hHalloweenBombHat->m_nSkin = m_Shared.m_nHalloweenBombHeadStage;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( m_hHalloweenBombHat )
|
||
|
{
|
||
|
m_hHalloweenBombHat->StopFollowingEntity();
|
||
|
m_hHalloweenBombHat->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldPlayerDrawParticles( void )
|
||
|
{
|
||
|
#ifdef STAGING_ONLY
|
||
|
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) )
|
||
|
return false;
|
||
|
#endif // TF_CLIENT_DLL
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Check if passed in player is on the local player's friends list
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsPlayerOnSteamFriendsList( C_BasePlayer *pPlayer )
|
||
|
{
|
||
|
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
|
||
|
if ( !pLocalPlayer )
|
||
|
return false;
|
||
|
|
||
|
if ( !pPlayer )
|
||
|
return false;
|
||
|
|
||
|
if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() )
|
||
|
return false;
|
||
|
|
||
|
player_info_t pi;
|
||
|
if ( engine->GetPlayerInfo( pPlayer->entindex(), &pi ) && pi.friendsID )
|
||
|
{
|
||
|
CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
|
||
|
if ( steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate ) )
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
ConVar tf_debug_moving_taunt( "tf_debug_moving_taunt", "1" );
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ClientThink()
|
||
|
{
|
||
|
// Pass on through to the base class.
|
||
|
BaseClass::ClientThink();
|
||
|
|
||
|
UpdateIDTarget();
|
||
|
|
||
|
UpdateLookAt();
|
||
|
|
||
|
UpdateOverheadEffects();
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_debug_moving_taunt.GetBool() )
|
||
|
#endif // STAGING_ONLY
|
||
|
{
|
||
|
if ( !IsTaunting() && m_PlayerAnimState->IsGestureSlotActive( GESTURE_SLOT_VCD ) )
|
||
|
{
|
||
|
#ifdef STAGING_ONLY
|
||
|
CAnimationLayer *pLayer = m_PlayerAnimState->GetGestureSlotLayer( GESTURE_SLOT_VCD );
|
||
|
if ( pLayer )
|
||
|
{
|
||
|
const char *pszSequenceName = GetSequenceName( pLayer->m_nSequence );
|
||
|
Warning( "'%s' is playing '%s' sequence when not taunting.\n", GetPlayerName(), pszSequenceName );
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NVNT update state based effects (prior to healer clear)
|
||
|
if ( haptics &&haptics->HasDevice() && IsLocalPlayer())
|
||
|
{
|
||
|
tfHaptics.HapticsThink(this);
|
||
|
}
|
||
|
|
||
|
// Clear our healer, it'll be reset by the medigun client think if we're being healed
|
||
|
m_hHealer = NULL;
|
||
|
|
||
|
// Start smoke if we're not invisible or disguised.
|
||
|
if ( CanLightCigarette() )
|
||
|
{
|
||
|
if ( !m_bCigaretteSmokeActive )
|
||
|
{
|
||
|
int iSmokeAttachment = LookupAttachment( "cig_smoke" );
|
||
|
ParticleProp()->Create( "cig_smoke", PATTACH_POINT_FOLLOW, iSmokeAttachment );
|
||
|
m_bCigaretteSmokeActive = true;
|
||
|
}
|
||
|
}
|
||
|
else // stop the smoke otherwise if its active
|
||
|
{
|
||
|
if ( m_bCigaretteSmokeActive )
|
||
|
{
|
||
|
ParticleProp()->StopParticlesNamed( "cig_smoke", false );
|
||
|
m_bCigaretteSmokeActive = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_bWaterExitEffectActive && !IsAlive() )
|
||
|
{
|
||
|
ParticleProp()->StopParticlesNamed( "water_playeremerge", false );
|
||
|
m_bWaterExitEffectActive = false;
|
||
|
}
|
||
|
|
||
|
// Kill the effect if either
|
||
|
// a) the player is dead
|
||
|
// b) the enemy disguised spy is now invisible
|
||
|
|
||
|
if ( !IsAlive() ||
|
||
|
( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() && ( GetPercentInvisible() > 0 ) ) )
|
||
|
{
|
||
|
StopSaveMeEffect( true );
|
||
|
}
|
||
|
|
||
|
if ( ShouldTauntHintIconBeVisible() )
|
||
|
{
|
||
|
CreateTauntWithMeEffect();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StopTauntWithMeEffect();
|
||
|
}
|
||
|
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
g_ItemEffectMeterManager.Update( this );
|
||
|
}
|
||
|
|
||
|
if ( m_Shared.InCond( TF_COND_DEMO_BUFF ) )
|
||
|
{
|
||
|
m_Shared.ClientDemoBuffThink();
|
||
|
}
|
||
|
|
||
|
if ( m_pBlastJumpLoop )
|
||
|
{
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
if ( !IsAlive() )
|
||
|
{
|
||
|
controller.SoundDestroy( m_pBlastJumpLoop );
|
||
|
m_pBlastJumpLoop = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float flTimeAloft = gpGlobals->curtime - m_flBlastJumpLaunchTime;
|
||
|
float flPitch = RemapValClamped( flTimeAloft, 0.1f, 3.f, 200.f, 100.f );
|
||
|
float flVolume = RemapValClamped( flTimeAloft, 0.1f, 2.f, 0.25f, 0.95f );
|
||
|
controller.SoundChangePitch( m_pBlastJumpLoop, flPitch, 0.1f );
|
||
|
controller.SoundChangeVolume( m_pBlastJumpLoop, flVolume, 0.1f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( HasTheFlag() && GetGlowObject() )
|
||
|
{
|
||
|
C_TFItem *pFlag = GetItem();
|
||
|
if ( pFlag->ShouldHideGlowEffect() )
|
||
|
{
|
||
|
GetGlowObject()->SetEntity( NULL );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetGlowObject()->SetEntity( this );
|
||
|
}
|
||
|
}
|
||
|
#ifdef STAGING_ONLY
|
||
|
/*
|
||
|
// this code was added for a new sniper rifle that was being looked at, but it breaks glows for existing players by always
|
||
|
// turning off the glow if the weapon doesn't think it needs to be on. this needs to be fixed before we can use it.
|
||
|
// some mods use the glows and the SourceTV clients see player glows when they're watching a match.
|
||
|
else
|
||
|
{
|
||
|
bool bGlow = false;
|
||
|
|
||
|
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_ZOOMED ) && !( pLocalTFPlayer->InSameTeam( this ) || m_Shared.GetDisguiseTeam() == pLocalTFPlayer->GetTeamNumber() ) )
|
||
|
{
|
||
|
int iGlowEnemiesInScope = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalTFPlayer, iGlowEnemiesInScope, add_sniper_glow_enemies_in_scope );
|
||
|
if ( iGlowEnemiesInScope != 0 )
|
||
|
{
|
||
|
trace_t tr;
|
||
|
UTIL_TraceLine( pLocalTFPlayer->EyePosition(), EyePosition(), MASK_BLOCKLOS, pLocalTFPlayer, COLLISION_GROUP_NONE, &tr );
|
||
|
if ( !tr.DidHit() )
|
||
|
{
|
||
|
bGlow = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bGlow )
|
||
|
{
|
||
|
float r, g, b;
|
||
|
GetGlowEffectColor( &r, &g, &b );
|
||
|
EnableGlowEffect( r, g, b );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DestroyGlowEffect();
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
m_Shared.ClientKillStreakBuffThink();
|
||
|
|
||
|
/*
|
||
|
if ( m_LeaveServerTimer.HasStarted() && m_LeaveServerTimer.IsElapsed() )
|
||
|
{
|
||
|
engine->ExecuteClientCmd( "disconnect" );
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
if ( m_Shared.IsEnteringOrExitingFullyInvisible() )
|
||
|
{
|
||
|
UpdateSpyStateChange();
|
||
|
}
|
||
|
|
||
|
// Predict Halloween Kart
|
||
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) && !m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
|
||
|
{
|
||
|
if ( IsLocalPlayer() && cl_predict->GetBool() )
|
||
|
{
|
||
|
cl_predict->SetValue( false );
|
||
|
}
|
||
|
|
||
|
//if ( m_nButtons & IN_ATTACK )
|
||
|
//{
|
||
|
// // Check if this is the spellbook so we can save off info to preserve weapon switching
|
||
|
// CTFSpellBook *pSpellBook = dynamic_cast<CTFSpellBook*>( GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
|
||
|
// if ( pSpellBook )
|
||
|
// {
|
||
|
// if ( pSpellBook )
|
||
|
// {
|
||
|
// pSpellBook->PrimaryAttack();
|
||
|
// }
|
||
|
// }
|
||
|
//}
|
||
|
|
||
|
//// Speed Boost
|
||
|
//if ( m_nButtons & IN_ATTACK2 )
|
||
|
//{
|
||
|
// if ( GetKartSpeedBoost() >= 1.0f )
|
||
|
// {
|
||
|
// m_flKartNextAvailableBoost = gpGlobals->curtime + tf_halloween_kart_boost_recharge.GetFloat();
|
||
|
// m_Shared.AddCond( TF_COND_HALLOWEEN_KART_DASH, tf_halloween_kart_boost_duration.GetFloat() );
|
||
|
// }
|
||
|
//}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Make Sure its on otherwise
|
||
|
if ( IsLocalPlayer() && !cl_predict->GetBool() )
|
||
|
{
|
||
|
cl_predict->SetValue( true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// update rune charge particle
|
||
|
if ( m_Shared.IsRuneCharged() && !m_pRuneChargeReadyEffect && !m_Shared.IsStealthed() )
|
||
|
{
|
||
|
m_pRuneChargeReadyEffect = ParticleProp()->Create( "powerup_supernova_ready", PATTACH_ABSORIGIN_FOLLOW );
|
||
|
}
|
||
|
else if ( m_pRuneChargeReadyEffect && ( m_Shared.IsStealthed() || !m_Shared.IsRuneCharged() ) )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pRuneChargeReadyEffect );
|
||
|
m_pRuneChargeReadyEffect = NULL;
|
||
|
}
|
||
|
|
||
|
UpdateRuneIcon();
|
||
|
|
||
|
UpdatedMarkedForDeathEffect();
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->IsPasstimeMode() )
|
||
|
{
|
||
|
//
|
||
|
// Passtime player reticle
|
||
|
//
|
||
|
if ( !IsLocalPlayer() && !m_pPasstimePlayerReticle )
|
||
|
{
|
||
|
m_pPasstimePlayerReticle = new C_PasstimePlayerReticle( this );
|
||
|
}
|
||
|
if ( m_pPasstimePlayerReticle )
|
||
|
{
|
||
|
m_pPasstimePlayerReticle->OnClientThink();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Passtime ask for ball reticle
|
||
|
//
|
||
|
if ( !IsLocalPlayer() && !m_pPasstimeAskForBallReticle )
|
||
|
{
|
||
|
m_pPasstimeAskForBallReticle = new C_PasstimeAskForBallReticle( this );
|
||
|
}
|
||
|
if ( m_pPasstimeAskForBallReticle )
|
||
|
{
|
||
|
m_pPasstimeAskForBallReticle->OnClientThink();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Passtime ask for ball button
|
||
|
//
|
||
|
if ( m_nButtons & IN_ATTACK3 )
|
||
|
{
|
||
|
engine->ClientCmd("voicemenu 1 8");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::Touch( CBaseEntity *pOther )
|
||
|
{
|
||
|
BaseClass::Touch( pOther );
|
||
|
|
||
|
C_TFPlayer *pVictim = ToTFPlayer( pOther );
|
||
|
|
||
|
if ( pVictim )
|
||
|
{
|
||
|
// ****************************************************************************************************************
|
||
|
// Halloween Karts
|
||
|
// Predict Dash Crash
|
||
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) )
|
||
|
{
|
||
|
Vector vAim = GetLocalVelocity();
|
||
|
vAim.NormalizeInPlace();
|
||
|
vAim.z += 0.50f;
|
||
|
vAim.NormalizeInPlace();
|
||
|
Vector vOrigin = GetAbsOrigin();
|
||
|
|
||
|
// Force direction is velocity of the player in the case that this is a head on collison.
|
||
|
// Trace
|
||
|
trace_t pTrace;
|
||
|
Ray_t ray;
|
||
|
CTraceFilterOnlyNPCsAndPlayer pFilter( this, COLLISION_GROUP_NONE );
|
||
|
|
||
|
ray.Init( vOrigin, vOrigin + vAim * 16, GetPlayerMins(), GetPlayerMaxs() );
|
||
|
enginetrace->TraceRay( ray, MASK_SOLID, &pFilter, &pTrace );
|
||
|
|
||
|
Vector vecForceDirection;
|
||
|
vecForceDirection = vAim;
|
||
|
if ( pTrace.m_pEnt == pVictim )
|
||
|
{
|
||
|
// Stop moving
|
||
|
SetAbsVelocity( vec3_origin );
|
||
|
SetCurrentTauntMoveSpeed( 0 );
|
||
|
m_Shared.RemoveCond( TF_COND_HALLOWEEN_KART_DASH );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::GetPredictable( void ) const
|
||
|
{
|
||
|
// Halloween Kart Hackery to Get prediction system to behave the way we want we though prediction is actually off
|
||
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return BaseClass::GetPredictable();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateLookAt( void )
|
||
|
{
|
||
|
bool bFoundViewTarget = false;
|
||
|
|
||
|
Vector vForward;
|
||
|
AngleVectors( GetLocalAngles(), &vForward );
|
||
|
|
||
|
Vector vMyOrigin = GetAbsOrigin();
|
||
|
|
||
|
Vector vecLookAtTarget = vec3_origin;
|
||
|
|
||
|
if ( tf_clientsideeye_lookats.GetBool() )
|
||
|
{
|
||
|
for( int iClient = 1; iClient <= gpGlobals->maxClients; ++iClient )
|
||
|
{
|
||
|
CBaseEntity *pEnt = UTIL_PlayerByIndex( iClient );
|
||
|
if ( !pEnt || !pEnt->IsPlayer() )
|
||
|
continue;
|
||
|
|
||
|
if ( !pEnt->IsAlive() )
|
||
|
continue;
|
||
|
|
||
|
if ( pEnt == this )
|
||
|
continue;
|
||
|
|
||
|
Vector vDir = pEnt->GetAbsOrigin() - vMyOrigin;
|
||
|
|
||
|
if ( vDir.Length() > 300 )
|
||
|
continue;
|
||
|
|
||
|
VectorNormalize( vDir );
|
||
|
|
||
|
if ( DotProduct( vForward, vDir ) < 0.0f )
|
||
|
continue;
|
||
|
|
||
|
vecLookAtTarget = pEnt->EyePosition();
|
||
|
bFoundViewTarget = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bFoundViewTarget == false )
|
||
|
{
|
||
|
// no target, look forward
|
||
|
vecLookAtTarget = GetAbsOrigin() + vForward * 512;
|
||
|
}
|
||
|
|
||
|
// orient eyes
|
||
|
m_viewtarget = vecLookAtTarget;
|
||
|
|
||
|
/*
|
||
|
// blinking
|
||
|
if (m_blinkTimer.IsElapsed())
|
||
|
{
|
||
|
m_blinktoggle = !m_blinktoggle;
|
||
|
m_blinkTimer.Start( RandomFloat( 1.5f, 4.0f ) );
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
// Figure out where we want to look in world space.
|
||
|
QAngle desiredAngles;
|
||
|
Vector to = vecLookAtTarget - EyePosition();
|
||
|
VectorAngles( to, desiredAngles );
|
||
|
|
||
|
// Figure out where our body is facing in world space.
|
||
|
QAngle bodyAngles( 0, 0, 0 );
|
||
|
bodyAngles[YAW] = GetLocalAngles()[YAW];
|
||
|
|
||
|
float flBodyYawDiff = bodyAngles[YAW] - m_flLastBodyYaw;
|
||
|
m_flLastBodyYaw = bodyAngles[YAW];
|
||
|
|
||
|
// Set the head's yaw.
|
||
|
float desired = AngleNormalize( desiredAngles[YAW] - bodyAngles[YAW] );
|
||
|
desired = clamp( -desired, m_headYawMin, m_headYawMax );
|
||
|
m_flCurrentHeadYaw = ApproachAngle( desired, m_flCurrentHeadYaw, 130 * gpGlobals->frametime );
|
||
|
|
||
|
// Counterrotate the head from the body rotation so it doesn't rotate past its target.
|
||
|
m_flCurrentHeadYaw = AngleNormalize( m_flCurrentHeadYaw - flBodyYawDiff );
|
||
|
|
||
|
SetPoseParameter( m_headYawPoseParam, m_flCurrentHeadYaw );
|
||
|
|
||
|
// Set the head's yaw.
|
||
|
desired = AngleNormalize( desiredAngles[PITCH] );
|
||
|
desired = clamp( desired, m_headPitchMin, m_headPitchMax );
|
||
|
|
||
|
m_flCurrentHeadPitch = ApproachAngle( -desired, m_flCurrentHeadPitch, 130 * gpGlobals->frametime );
|
||
|
m_flCurrentHeadPitch = AngleNormalize( m_flCurrentHeadPitch );
|
||
|
SetPoseParameter( m_headPitchPoseParam, m_flCurrentHeadPitch );
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Try to steer away from any players and objects we might interpenetrate
|
||
|
//-----------------------------------------------------------------------------
|
||
|
#define TF_AVOID_MAX_RADIUS_SQR 5184.0f // Based on player extents and max buildable extents.
|
||
|
#define TF_OO_AVOID_MAX_RADIUS_SQR 0.00019f
|
||
|
|
||
|
ConVar tf_max_separation_force ( "tf_max_separation_force", "256", FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
extern ConVar cl_forwardspeed;
|
||
|
extern ConVar cl_backspeed;
|
||
|
extern ConVar cl_sidespeed;
|
||
|
|
||
|
void C_TFPlayer::AvoidPlayers( CUserCmd *pCmd )
|
||
|
{
|
||
|
// Turn off the avoid player code.
|
||
|
if ( !tf_avoidteammates.GetBool() || !tf_avoidteammates_pushaway.GetBool() )
|
||
|
return;
|
||
|
|
||
|
// Don't test if the player doesn't exist or is dead.
|
||
|
if ( IsAlive() == false )
|
||
|
return;
|
||
|
|
||
|
C_TFTeam *pTeam = (C_TFTeam*)GetTeam();
|
||
|
if ( !pTeam )
|
||
|
return;
|
||
|
|
||
|
CHudUpgradePanel *pHudVote = GET_HUDELEMENT( CHudUpgradePanel );
|
||
|
if ( pHudVote && pHudVote->IsActive() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Up vector.
|
||
|
static Vector vecUp( 0.0f, 0.0f, 1.0f );
|
||
|
|
||
|
Vector vecTFPlayerCenter = GetAbsOrigin();
|
||
|
Vector vecTFPlayerMin = GetPlayerMins();
|
||
|
Vector vecTFPlayerMax = GetPlayerMaxs();
|
||
|
float flZHeight = vecTFPlayerMax.z - vecTFPlayerMin.z;
|
||
|
vecTFPlayerCenter.z += 0.5f * flZHeight;
|
||
|
VectorAdd( vecTFPlayerMin, vecTFPlayerCenter, vecTFPlayerMin );
|
||
|
VectorAdd( vecTFPlayerMax, vecTFPlayerCenter, vecTFPlayerMax );
|
||
|
|
||
|
// Find an intersecting player or object.
|
||
|
int nAvoidPlayerCount = 0;
|
||
|
C_TFPlayer *pAvoidPlayerList[MAX_PLAYERS];
|
||
|
|
||
|
C_TFPlayer *pIntersectPlayer = NULL;
|
||
|
CBaseObject *pIntersectObject = NULL;
|
||
|
float flAvoidRadius = 0.0f;
|
||
|
|
||
|
Vector vecAvoidCenter, vecAvoidMin, vecAvoidMax;
|
||
|
for ( int i = 0; i < pTeam->GetNumPlayers(); ++i )
|
||
|
{
|
||
|
C_TFPlayer *pAvoidPlayer = static_cast< C_TFPlayer * >( pTeam->GetPlayer( i ) );
|
||
|
if ( pAvoidPlayer == NULL )
|
||
|
continue;
|
||
|
// Is the avoid player me?
|
||
|
if ( pAvoidPlayer == this )
|
||
|
continue;
|
||
|
|
||
|
// Save as list to check against for objects.
|
||
|
pAvoidPlayerList[nAvoidPlayerCount] = pAvoidPlayer;
|
||
|
++nAvoidPlayerCount;
|
||
|
|
||
|
// Check to see if the avoid player is dormant.
|
||
|
if ( pAvoidPlayer->IsDormant() )
|
||
|
continue;
|
||
|
|
||
|
// Is the avoid player solid?
|
||
|
if ( pAvoidPlayer->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
|
||
|
continue;
|
||
|
|
||
|
Vector t1, t2;
|
||
|
|
||
|
vecAvoidCenter = pAvoidPlayer->GetAbsOrigin();
|
||
|
vecAvoidMin = pAvoidPlayer->GetPlayerMins();
|
||
|
vecAvoidMax = pAvoidPlayer->GetPlayerMaxs();
|
||
|
flZHeight = vecAvoidMax.z - vecAvoidMin.z;
|
||
|
vecAvoidCenter.z += 0.5f * flZHeight;
|
||
|
VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
|
||
|
VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );
|
||
|
|
||
|
if ( IsBoxIntersectingBox( vecTFPlayerMin, vecTFPlayerMax, vecAvoidMin, vecAvoidMax ) )
|
||
|
{
|
||
|
// Need to avoid this player.
|
||
|
if ( !pIntersectPlayer )
|
||
|
{
|
||
|
pIntersectPlayer = pAvoidPlayer;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We didn't find a player - look for objects to avoid.
|
||
|
if ( !pIntersectPlayer )
|
||
|
{
|
||
|
for ( int iPlayer = 0; iPlayer < nAvoidPlayerCount; ++iPlayer )
|
||
|
{
|
||
|
// Stop when we found an intersecting object.
|
||
|
if ( pIntersectObject )
|
||
|
break;
|
||
|
|
||
|
for ( int iObject = 0; iObject < pTeam->GetNumObjects(); ++iObject )
|
||
|
{
|
||
|
CBaseObject *pAvoidObject = pTeam->GetObject( iObject );
|
||
|
if ( !pAvoidObject )
|
||
|
continue;
|
||
|
|
||
|
// Check to see if the object is dormant.
|
||
|
if ( pAvoidObject->IsDormant() )
|
||
|
continue;
|
||
|
|
||
|
// Is the object solid.
|
||
|
if ( pAvoidObject->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
|
||
|
continue;
|
||
|
|
||
|
// If we shouldn't avoid it, see if we intersect it.
|
||
|
if ( pAvoidObject->ShouldPlayersAvoid() )
|
||
|
{
|
||
|
vecAvoidCenter = pAvoidObject->WorldSpaceCenter();
|
||
|
vecAvoidMin = pAvoidObject->WorldAlignMins();
|
||
|
vecAvoidMax = pAvoidObject->WorldAlignMaxs();
|
||
|
VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
|
||
|
VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );
|
||
|
|
||
|
if ( IsBoxIntersectingBox( vecTFPlayerMin, vecTFPlayerMax, vecAvoidMin, vecAvoidMax ) )
|
||
|
{
|
||
|
// Need to avoid this object.
|
||
|
pIntersectObject = pAvoidObject;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Anything to avoid?
|
||
|
if ( !pIntersectPlayer && !pIntersectObject )
|
||
|
{
|
||
|
m_Shared.SetSeparation( false );
|
||
|
m_Shared.SetSeparationVelocity( vec3_origin );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Calculate the push strength and direction.
|
||
|
Vector vecDelta;
|
||
|
|
||
|
// Avoid a player - they have precedence.
|
||
|
if ( pIntersectPlayer )
|
||
|
{
|
||
|
VectorSubtract( pIntersectPlayer->WorldSpaceCenter(), vecTFPlayerCenter, vecDelta );
|
||
|
|
||
|
Vector vRad = pIntersectPlayer->WorldAlignMaxs() - pIntersectPlayer->WorldAlignMins();
|
||
|
vRad.z = 0;
|
||
|
|
||
|
flAvoidRadius = vRad.Length();
|
||
|
}
|
||
|
// Avoid a object.
|
||
|
else
|
||
|
{
|
||
|
VectorSubtract( pIntersectObject->WorldSpaceCenter(), vecTFPlayerCenter, vecDelta );
|
||
|
|
||
|
Vector vRad = pIntersectObject->WorldAlignMaxs() - pIntersectObject->WorldAlignMins();
|
||
|
vRad.z = 0;
|
||
|
|
||
|
flAvoidRadius = vRad.Length();
|
||
|
}
|
||
|
|
||
|
float flPushStrength = RemapValClamped( vecDelta.Length(), flAvoidRadius, 0, 0, tf_max_separation_force.GetInt() ); //flPushScale;
|
||
|
|
||
|
//Msg( "PushScale = %f\n", flPushStrength );
|
||
|
|
||
|
// Check to see if we have enough push strength to make a difference.
|
||
|
if ( flPushStrength < 0.01f )
|
||
|
return;
|
||
|
|
||
|
Vector vecPush;
|
||
|
if ( GetAbsVelocity().Length2DSqr() > 0.1f )
|
||
|
{
|
||
|
Vector vecVelocity = GetAbsVelocity();
|
||
|
vecVelocity.z = 0.0f;
|
||
|
CrossProduct( vecUp, vecVelocity, vecPush );
|
||
|
VectorNormalize( vecPush );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We are not moving, but we're still intersecting.
|
||
|
QAngle angView = pCmd->viewangles;
|
||
|
angView.x = 0.0f;
|
||
|
AngleVectors( angView, NULL, &vecPush, NULL );
|
||
|
}
|
||
|
|
||
|
// Move away from the other player/object.
|
||
|
Vector vecSeparationVelocity;
|
||
|
if ( vecDelta.Dot( vecPush ) < 0 )
|
||
|
{
|
||
|
vecSeparationVelocity = vecPush * flPushStrength;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vecSeparationVelocity = vecPush * -flPushStrength;
|
||
|
}
|
||
|
|
||
|
// Don't allow the max push speed to be greater than the max player speed.
|
||
|
float flMaxPlayerSpeed = MaxSpeed();
|
||
|
float flCropFraction = 1.33333333f;
|
||
|
|
||
|
if ( ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
|
||
|
{
|
||
|
flMaxPlayerSpeed *= flCropFraction;
|
||
|
}
|
||
|
|
||
|
float flMaxPlayerSpeedSqr = flMaxPlayerSpeed * flMaxPlayerSpeed;
|
||
|
|
||
|
if ( vecSeparationVelocity.LengthSqr() > flMaxPlayerSpeedSqr )
|
||
|
{
|
||
|
vecSeparationVelocity.NormalizeInPlace();
|
||
|
VectorScale( vecSeparationVelocity, flMaxPlayerSpeed, vecSeparationVelocity );
|
||
|
}
|
||
|
|
||
|
QAngle vAngles = pCmd->viewangles;
|
||
|
vAngles.x = 0;
|
||
|
Vector currentdir;
|
||
|
Vector rightdir;
|
||
|
|
||
|
AngleVectors( vAngles, ¤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; i<pTaunt->GetTauntInputRemapCount(); ++i )
|
||
|
{
|
||
|
const CTFTauntInfo::TauntInputRemap_t& tauntRemap = pTaunt->GetTauntInputRemapScene( i );
|
||
|
if ( nCurrentButtons & tauntRemap.m_iButton )
|
||
|
{
|
||
|
pCmd->buttons |= tauntRemap.m_iButton;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bNoTaunt = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
flTauntTurnSpeed = 0.f;
|
||
|
VectorCopy( pCmd->viewangles, angMoveAngle );
|
||
|
}
|
||
|
|
||
|
BaseClass::CreateMove( flInputSampleTime, pCmd );
|
||
|
|
||
|
// Don't avoid players if in the middle of a high five. This prevents high-fivers from becoming separated.
|
||
|
if ( !bInTaunt || ( !m_bIsReadyToHighFive && !CTFPlayerSharedUtils::ConceptIsPartnerTaunt( m_Shared.m_iTauntConcept ) ) )
|
||
|
{
|
||
|
AvoidPlayers( pCmd );
|
||
|
}
|
||
|
|
||
|
return bNoTaunt;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: This prevents some anims from being thrown out when the client is in prediction simulation.
|
||
|
// Stun anims, for example, (additive gestures) are synchronized by time and can keep playing on the client
|
||
|
// to prevent pauses during the stun loop.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::PlayAnimEventInPrediction( PlayerAnimEvent_t event )
|
||
|
{
|
||
|
if ( !cl_predict->GetBool() )
|
||
|
return true;
|
||
|
|
||
|
switch ( event )
|
||
|
{
|
||
|
case PLAYERANIMEVENT_STUN_BEGIN:
|
||
|
case PLAYERANIMEVENT_STUN_MIDDLE:
|
||
|
case PLAYERANIMEVENT_STUN_END:
|
||
|
case PLAYERANIMEVENT_PASSTIME_THROW_BEGIN:
|
||
|
case PLAYERANIMEVENT_PASSTIME_THROW_MIDDLE:
|
||
|
case PLAYERANIMEVENT_PASSTIME_THROW_END:
|
||
|
case PLAYERANIMEVENT_PASSTIME_THROW_CANCEL:
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
|
||
|
{
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
if ( !prediction->IsFirstTimePredicted() && !PlayAnimEventInPrediction( event ) )
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
MDLCACHE_CRITICAL_SECTION();
|
||
|
m_PlayerAnimState->DoAnimationEvent( event, nData );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreateBombonomiconHint()
|
||
|
{
|
||
|
if ( IsLocalPlayer() && IsAlive() )
|
||
|
{
|
||
|
m_hBombonomiconHint = C_MerasmusBombEffect::Create( BOMBONOMICON_MODEL, this, Vector(-40, 0, 120), QAngle(30, 0, 0), 100.0, 4.0, PRM_SPIN_Z );
|
||
|
m_flBombDelay = gpGlobals->curtime + 2.0f;
|
||
|
m_hBombonomiconHint->SetModelScale( 0.5f );
|
||
|
|
||
|
CSingleUserRecipientFilter filter(this);
|
||
|
CSoundParameters params;
|
||
|
if ( CBaseEntity::GetParametersForSound( "Halloween.BombinomiconSpin", params, NULL ) )
|
||
|
{
|
||
|
EmitSound_t es( params );
|
||
|
EmitSound( filter, m_hBombonomiconHint->entindex(), es );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::DestroyBombonomiconHint()
|
||
|
{
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
if ( m_hBombonomiconHint )
|
||
|
{
|
||
|
m_hBombonomiconHint->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Similar to OnNewModel. only reset animation related data
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CleanUpAnimationOnSpawn()
|
||
|
{
|
||
|
CStudioHdr *hdr = GetModelPtr();
|
||
|
if ( !hdr )
|
||
|
return;
|
||
|
|
||
|
InitializePoseParams();
|
||
|
|
||
|
// Init flexes, cancel any scenes we're playing
|
||
|
ClearSceneEvents( NULL, false );
|
||
|
|
||
|
// Reset the flex weights.
|
||
|
ResetFlexWeights( GetModelPtr() );
|
||
|
|
||
|
// Reset the players animation states, gestures
|
||
|
if ( m_PlayerAnimState )
|
||
|
{
|
||
|
m_PlayerAnimState->ClearAnimationState();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsABot( void )
|
||
|
{
|
||
|
if ( m_bIsABot )
|
||
|
return true;
|
||
|
|
||
|
if ( g_PR && g_PR->IsFakePlayer( entindex() ) )
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Vector C_TFPlayer::GetObserverCamOrigin( void )
|
||
|
{
|
||
|
if ( !IsAlive() )
|
||
|
{
|
||
|
if ( m_hFirstGib )
|
||
|
{
|
||
|
IPhysicsObject *pPhysicsObject = m_hFirstGib->VPhysicsGetObject();
|
||
|
if( pPhysicsObject )
|
||
|
{
|
||
|
Vector vecMassCenter = pPhysicsObject->GetMassCenterLocalSpace();
|
||
|
Vector vecWorld;
|
||
|
m_hFirstGib->CollisionProp()->CollisionToWorldSpace( vecMassCenter, &vecWorld );
|
||
|
return (vecWorld);
|
||
|
}
|
||
|
return m_hFirstGib->GetRenderOrigin();
|
||
|
}
|
||
|
|
||
|
return GetDeathViewPosition();
|
||
|
}
|
||
|
|
||
|
return BaseClass::GetObserverCamOrigin();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Consider the viewer and other factors when determining resulting
|
||
|
// invisibility
|
||
|
//-----------------------------------------------------------------------------
|
||
|
float C_TFPlayer::GetEffectiveInvisibilityLevel( void )
|
||
|
{
|
||
|
float flPercentInvisible = GetPercentInvisible();
|
||
|
|
||
|
// Crude way to limit Halloween spell
|
||
|
bool bHalloweenSpellStealth = TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) && m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF );
|
||
|
bool bLimitedInvis = !IsEnemyPlayer() || bHalloweenSpellStealth;
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalTFPlayer && pLocalTFPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalTFPlayer != this )
|
||
|
{
|
||
|
bLimitedInvis = false;
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
// If this is a teammate of the local player or viewer is observer,
|
||
|
// dont go above a certain max invis
|
||
|
if ( bLimitedInvis )
|
||
|
{
|
||
|
float flMax = tf_teammate_max_invis.GetFloat();
|
||
|
if ( flPercentInvisible > flMax )
|
||
|
{
|
||
|
flPercentInvisible = flMax;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If this player just killed me, show them slightly
|
||
|
// less than full invis in the deathcam and freezecam
|
||
|
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
int iObserverMode = pLocalPlayer->GetObserverMode();
|
||
|
|
||
|
if ( ( iObserverMode == OBS_MODE_FREEZECAM || iObserverMode == OBS_MODE_DEATHCAM ) &&
|
||
|
pLocalPlayer->GetObserverTarget() == this )
|
||
|
{
|
||
|
float flMax = tf_teammate_max_invis.GetFloat();
|
||
|
if ( flPercentInvisible > flMax )
|
||
|
{
|
||
|
flPercentInvisible = flMax;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return flPercentInvisible;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::SetBodygroupsDirty( void )
|
||
|
{
|
||
|
m_bBodygroupsDirty = true;
|
||
|
|
||
|
CTFViewModel *pVM = dynamic_cast<CTFViewModel *>( GetViewModel() );
|
||
|
if ( pVM )
|
||
|
{
|
||
|
pVM->m_bBodygroupsDirty = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::RecalcBodygroupsIfDirty( void )
|
||
|
{
|
||
|
if ( m_bBodygroupsDirty )
|
||
|
{
|
||
|
m_Shared.RecalculatePlayerBodygroups();
|
||
|
m_bBodygroupsDirty = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::DrawModel( int flags )
|
||
|
{
|
||
|
// If we're a dead player with a fresh ragdoll, don't draw
|
||
|
if ( m_nRenderFX == kRenderFxRagdoll )
|
||
|
return 0;
|
||
|
|
||
|
RecalcBodygroupsIfDirty();
|
||
|
|
||
|
// Don't draw the model at all if we're fully invisible
|
||
|
if ( GetEffectiveInvisibilityLevel() >= 1.0f )
|
||
|
{
|
||
|
if ( m_hHalloweenBombHat && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) && !m_hHalloweenBombHat->IsEffectActive( EF_NODRAW ) )
|
||
|
{
|
||
|
m_hHalloweenBombHat->SetEffects( EF_NODRAW );
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( m_hHalloweenBombHat && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) && m_hHalloweenBombHat->IsEffectActive( EF_NODRAW ) )
|
||
|
{
|
||
|
m_hHalloweenBombHat->RemoveEffects( EF_NODRAW );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CMatRenderContextPtr pRenderContext( materials );
|
||
|
bool bDoEffect = false;
|
||
|
|
||
|
float flAmountToChop = 0.0;
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISING ) )
|
||
|
{
|
||
|
flAmountToChop = ( gpGlobals->curtime - m_flDisguiseEffectStartTime ) *
|
||
|
( 1.0 / TF_TIME_TO_DISGUISE );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
float flETime = gpGlobals->curtime - m_flDisguiseEffectStartTime;
|
||
|
if ( ( flETime > 0.0 ) && ( flETime < TF_TIME_TO_SHOW_DISGUISED_FINISHED_EFFECT ) )
|
||
|
{
|
||
|
flAmountToChop = 1.0 - ( flETime * ( 1.0/TF_TIME_TO_SHOW_DISGUISED_FINISHED_EFFECT ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bDoEffect = ( flAmountToChop > 0.0 ) && ( ! IsLocalPlayer() );
|
||
|
#if ( SHOW_DISGUISE_EFFECT == 0 )
|
||
|
bDoEffect = false;
|
||
|
#endif
|
||
|
bDoEffect = false;
|
||
|
if ( bDoEffect )
|
||
|
{
|
||
|
Vector vMyOrigin = GetAbsOrigin();
|
||
|
BoxDeformation_t mybox;
|
||
|
mybox.m_ClampMins = vMyOrigin - Vector(100,100,100);
|
||
|
mybox.m_ClampMaxes = vMyOrigin + Vector(500,500,72 * ( 1 - flAmountToChop ) );
|
||
|
pRenderContext->PushDeformation( &mybox );
|
||
|
}
|
||
|
|
||
|
int ret = BaseClass::DrawModel( flags );
|
||
|
|
||
|
if ( bDoEffect )
|
||
|
pRenderContext->PopDeformation();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ProcessMuzzleFlashEvent()
|
||
|
{
|
||
|
CBasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
|
||
|
|
||
|
// Reenable when the weapons have muzzle flash attachments in the right spot.
|
||
|
bool bInToolRecordingMode = ToolsEnabled() && clienttools->IsInRecordingMode();
|
||
|
if ( this == pLocalPlayer && !bInToolRecordingMode )
|
||
|
return; // don't show own world muzzle flash for localplayer
|
||
|
|
||
|
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
|
||
|
{
|
||
|
// also don't show in 1st person spec mode
|
||
|
if ( pLocalPlayer->GetObserverTarget() == this )
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
|
||
|
if ( !pWeapon )
|
||
|
return;
|
||
|
|
||
|
pWeapon->ProcessMuzzleFlashEvent();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetIDTarget() const
|
||
|
{
|
||
|
return m_iIDEntIndex;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::SetForcedIDTarget( int iTarget )
|
||
|
{
|
||
|
m_iForcedIDTarget = iTarget;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Update this client's targetid entity
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateIDTarget()
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pLocalPlayer || !IsLocalPlayer() )
|
||
|
return;
|
||
|
|
||
|
// don't show IDs if mp_fadetoblack is on
|
||
|
if ( GetTeamNumber() > TEAM_SPECTATOR && mp_fadetoblack.GetBool() && !IsAlive() )
|
||
|
{
|
||
|
m_iIDEntIndex = 0;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_iForcedIDTarget )
|
||
|
{
|
||
|
m_iIDEntIndex = m_iForcedIDTarget;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If we're in deathcam, ID our killer
|
||
|
if ( (GetObserverMode() == OBS_MODE_DEATHCAM || GetObserverMode() == OBS_MODE_CHASE) && GetObserverTarget() && GetObserverTarget() != GetLocalTFPlayer() )
|
||
|
{
|
||
|
m_iIDEntIndex = GetObserverTarget()->entindex();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Clear old target and find a new one
|
||
|
m_iIDEntIndex = 0;
|
||
|
|
||
|
trace_t tr;
|
||
|
Vector vecStart, vecEnd;
|
||
|
VectorMA( MainViewOrigin(), MAX_TRACE_LENGTH, MainViewForward(), vecEnd );
|
||
|
VectorMA( MainViewOrigin(), 10, MainViewForward(), vecStart );
|
||
|
|
||
|
// If we're in observer mode, ignore our observer target. Otherwise, ignore ourselves.
|
||
|
if ( IsObserver() )
|
||
|
{
|
||
|
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, GetObserverTarget(), COLLISION_GROUP_NONE, &tr );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Add DEBRIS when a medic has revive (for tracing against revive markers)
|
||
|
int iReviveMedic = 0;
|
||
|
CALL_ATTRIB_HOOK_INT( iReviveMedic, revive );
|
||
|
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
|
||
|
{
|
||
|
iReviveMedic = 1;
|
||
|
}
|
||
|
|
||
|
int nMask = MASK_SOLID | CONTENTS_DEBRIS;
|
||
|
UTIL_TraceLine( vecStart, vecEnd, nMask, this, COLLISION_GROUP_NONE, &tr );
|
||
|
}
|
||
|
|
||
|
bool bIsEnemyPlayer = false;
|
||
|
|
||
|
if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() )
|
||
|
{
|
||
|
// It's okay to start solid against enemies because we sometimes press right against them
|
||
|
bIsEnemyPlayer = GetTeamNumber() != tr.m_pEnt->GetTeamNumber();
|
||
|
}
|
||
|
|
||
|
if ( ( !tr.startsolid || bIsEnemyPlayer ) && tr.DidHitNonWorldEntity() )
|
||
|
{
|
||
|
C_BaseEntity *pEntity = tr.m_pEnt;
|
||
|
|
||
|
if ( pEntity && ( pEntity != this ) )
|
||
|
{
|
||
|
m_iIDEntIndex = pEntity->entindex();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Display appropriate hints for the target we're looking at
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::DisplaysHintsForTarget( C_BaseEntity *pTarget )
|
||
|
{
|
||
|
// If the entity provides hints, ask them if they have one for this player
|
||
|
ITargetIDProvidesHint *pHintInterface = dynamic_cast<ITargetIDProvidesHint*>(pTarget);
|
||
|
if ( pHintInterface )
|
||
|
{
|
||
|
pHintInterface->DisplayHintTo( this );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetRenderTeamNumber( void )
|
||
|
{
|
||
|
return m_nSkin;
|
||
|
}
|
||
|
|
||
|
static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
|
||
|
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Vector C_TFPlayer::GetDeathViewPosition()
|
||
|
{
|
||
|
Vector origin = EyePosition();
|
||
|
|
||
|
C_TFRagdoll *pRagdoll = static_cast<C_TFRagdoll*>( m_hRagdoll.Get() );
|
||
|
if ( pRagdoll )
|
||
|
{
|
||
|
if ( pRagdoll->IsDeathAnim() )
|
||
|
{
|
||
|
origin.z += VEC_DEAD_VIEWHEIGHT_SCALED( this ).z*4;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
IRagdoll *pIRagdoll = GetRepresentativeRagdoll();
|
||
|
if ( pIRagdoll )
|
||
|
{
|
||
|
origin = pIRagdoll->GetRagdollOrigin();
|
||
|
origin.z += VEC_DEAD_VIEWHEIGHT_SCALED( this ).z; // look over ragdoll, not through
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return origin;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CalcDeathCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
|
||
|
{
|
||
|
CBaseEntity * killer = GetObserverTarget();
|
||
|
C_BaseAnimating *pKillerAnimating = killer ? killer->GetBaseAnimating() : NULL;
|
||
|
|
||
|
// Swing to face our killer within half the death anim time
|
||
|
float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / (TF_DEATH_ANIMATION_TIME * 0.5);
|
||
|
interpolation = clamp( interpolation, 0.0f, 1.0f );
|
||
|
interpolation = SimpleSpline( interpolation );
|
||
|
|
||
|
float flMinChaseDistance = CHASE_CAM_DISTANCE_MIN;
|
||
|
float flMaxChaseDistance = CHASE_CAM_DISTANCE_MAX;
|
||
|
|
||
|
if ( pKillerAnimating )
|
||
|
{
|
||
|
float flScaleSquared = pKillerAnimating->GetModelScale() * pKillerAnimating->GetModelScale();
|
||
|
flMinChaseDistance *= flScaleSquared;
|
||
|
flMaxChaseDistance *= flScaleSquared;
|
||
|
}
|
||
|
|
||
|
m_flObserverChaseDistance += gpGlobals->frametime * 48.0f;
|
||
|
m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, flMinChaseDistance, flMaxChaseDistance );
|
||
|
|
||
|
QAngle aForward = eyeAngles = EyeAngles();
|
||
|
Vector origin = GetDeathViewPosition();
|
||
|
|
||
|
if ( m_hHeadGib )
|
||
|
{
|
||
|
// View from our decapitated head.
|
||
|
IPhysicsObject *pPhysicsObject = m_hHeadGib->VPhysicsGetObject();
|
||
|
if( pPhysicsObject )
|
||
|
{
|
||
|
Vector vecMassCenter = pPhysicsObject->GetMassCenterLocalSpace();
|
||
|
Vector vecWorld;
|
||
|
m_hHeadGib->CollisionProp()->CollisionToWorldSpace( vecMassCenter, &vecWorld );
|
||
|
m_hHeadGib->AddEffects( EF_NODRAW );
|
||
|
|
||
|
eyeOrigin = vecWorld + Vector(0,0,6);
|
||
|
|
||
|
QAngle aHead = m_hHeadGib->GetAbsAngles();
|
||
|
Vector vBody;
|
||
|
if ( m_hRagdoll )
|
||
|
{
|
||
|
// Turn to face our ragdoll.
|
||
|
vBody = m_hRagdoll->GetAbsOrigin() - eyeOrigin;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vBody = m_hHeadGib->GetAbsOrigin();
|
||
|
}
|
||
|
QAngle aBody; VectorAngles( vBody, aBody );
|
||
|
InterpolateAngles( aHead, aBody, eyeAngles, interpolation );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( killer && (killer != this) )
|
||
|
{
|
||
|
Vector vKiller = killer->EyePosition() - origin;
|
||
|
QAngle aKiller;
|
||
|
VectorAngles( vKiller, aKiller );
|
||
|
InterpolateAngles( aForward, aKiller, eyeAngles, interpolation );
|
||
|
}
|
||
|
|
||
|
Vector vForward;
|
||
|
AngleVectors( eyeAngles, &vForward );
|
||
|
|
||
|
VectorNormalize( vForward );
|
||
|
|
||
|
VectorMA( origin, -m_flObserverChaseDistance, vForward, eyeOrigin );
|
||
|
|
||
|
trace_t trace; // clip against world
|
||
|
C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
|
||
|
UTIL_TraceHull( origin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
|
||
|
C_BaseEntity::PopEnableAbsRecomputations();
|
||
|
|
||
|
if (trace.fraction < 1.0)
|
||
|
{
|
||
|
eyeOrigin = trace.endpos;
|
||
|
m_flObserverChaseDistance = VectorLength(origin - eyeOrigin);
|
||
|
}
|
||
|
|
||
|
fov = GetFOV();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Do nothing multiplayer_animstate takes care of animation.
|
||
|
// Input : playerAnim -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::SetAnimation( PLAYER_ANIM playerAnim )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
float C_TFPlayer::GetMinFOV() const
|
||
|
{
|
||
|
// Min FOV for Sniper Rifle
|
||
|
return 20;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const QAngle& C_TFPlayer::EyeAngles()
|
||
|
{
|
||
|
if ( IsLocalPlayer() && g_nKillCamMode == OBS_MODE_NONE )
|
||
|
{
|
||
|
return BaseClass::EyeAngles();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return m_angEyeAngles;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : &color -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::GetTeamColor( Color &color )
|
||
|
{
|
||
|
color[3] = 255;
|
||
|
|
||
|
if ( GetTeamNumber() == TF_TEAM_RED )
|
||
|
{
|
||
|
color[0] = 159;
|
||
|
color[1] = 55;
|
||
|
color[2] = 34;
|
||
|
}
|
||
|
else if ( GetTeamNumber() == TF_TEAM_BLUE )
|
||
|
{
|
||
|
color[0] = 76;
|
||
|
color[1] = 109;
|
||
|
color[2] = 129;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
color[0] = 255;
|
||
|
color[1] = 255;
|
||
|
color[2] = 255;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : bCopyEntity -
|
||
|
// Output : C_BaseAnimating *
|
||
|
//-----------------------------------------------------------------------------
|
||
|
C_BaseAnimating *C_TFPlayer::BecomeRagdollOnClient()
|
||
|
{
|
||
|
// Let the C_TFRagdoll take care of this.
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : -
|
||
|
// Output : IRagdoll*
|
||
|
//-----------------------------------------------------------------------------
|
||
|
IRagdoll* C_TFPlayer::GetRepresentativeRagdoll() const
|
||
|
{
|
||
|
if ( m_hRagdoll.Get() )
|
||
|
{
|
||
|
C_TFRagdoll *pRagdoll = static_cast<C_TFRagdoll*>( m_hRagdoll.Get() );
|
||
|
if ( !pRagdoll )
|
||
|
return NULL;
|
||
|
|
||
|
return pRagdoll->GetIRagdoll();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::InitPlayerGibs( void )
|
||
|
{
|
||
|
// Clear out the gib list and create a new one.
|
||
|
m_aGibs.Purge();
|
||
|
m_aNormalGibs.PurgeAndDeleteElements();
|
||
|
m_aSillyGibs.Purge();
|
||
|
|
||
|
int nModelIndex = GetPlayerClass()->HasCustomModel() ? modelinfo->GetModelIndex( GetPlayerClass()->GetModelName() ) : GetModelIndex();
|
||
|
BuildGibList( m_aGibs, nModelIndex, 1.0f, COLLISION_GROUP_NONE );
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->IsBirthday() )
|
||
|
{
|
||
|
for ( int i = 0; i < m_aGibs.Count(); i++ )
|
||
|
{
|
||
|
if ( RandomFloat(0,1) < 0.75 )
|
||
|
{
|
||
|
V_strcpy_safe( m_aGibs[i].modelName, g_pszBDayGibs[ RandomInt(0,ARRAYSIZE(g_pszBDayGibs)-1) ] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Copy the normal gibs list to be saved for later when swapping with Pyro Vision
|
||
|
FOR_EACH_VEC ( m_aGibs, i )
|
||
|
{
|
||
|
char *cloneStr = new char [ 512 ];
|
||
|
Q_strncpy( cloneStr, m_aGibs[i].modelName, 512 );
|
||
|
m_aNormalGibs.AddToTail( cloneStr );
|
||
|
|
||
|
// Create a list of silly gibs
|
||
|
int iRandIndex = RandomInt(4,ARRAYSIZE(g_pszBDayGibs)-1);
|
||
|
m_aSillyGibs.AddToTail( iRandIndex );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose : Checks vision flags and ensures the proper gib models are loaded for vision mode
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CheckAndUpdateGibType( void )
|
||
|
{
|
||
|
// check the first gib, if it's different copy them all over
|
||
|
if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) || ( TFGameRules() && TFGameRules()->UseSillyGibs() ) )
|
||
|
{
|
||
|
if ( Q_strcmp( m_aGibs[0].modelName, g_pszBDayGibs[ m_aSillyGibs[0] ]) != 0 )
|
||
|
{
|
||
|
FOR_EACH_VEC( m_aGibs, i )
|
||
|
{
|
||
|
V_strcpy_safe( m_aGibs[i].modelName, g_pszBDayGibs[ m_aSillyGibs[i] ] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( Q_strcmp( m_aGibs[0].modelName, m_aNormalGibs[0]) != 0 )
|
||
|
{
|
||
|
FOR_EACH_VEC( m_aGibs, i )
|
||
|
{
|
||
|
V_strcpy_safe( m_aGibs[i].modelName, m_aNormalGibs[i] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : &vecOrigin -
|
||
|
// &vecVelocity -
|
||
|
// &vecImpactVelocity -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreatePlayerGibs( const Vector &vecOrigin, const Vector &vecVelocity, float flImpactScale, bool bBurning, bool bWearableGibs, bool bOnlyHead, bool bDisguiseGibs )
|
||
|
{
|
||
|
// Make sure we have Gibs to create.
|
||
|
if ( m_aGibs.Count() == 0 )
|
||
|
return;
|
||
|
|
||
|
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
|
||
|
|
||
|
Vector vecBreakVelocity = vecVelocity;
|
||
|
vecBreakVelocity.z += tf_playergib_forceup.GetFloat();
|
||
|
VectorNormalize( vecBreakVelocity );
|
||
|
vecBreakVelocity *= tf_playergib_force.GetFloat();
|
||
|
|
||
|
// Cap the impulse.
|
||
|
float flSpeed = vecBreakVelocity.Length();
|
||
|
if ( flSpeed > tf_playergib_maxspeed.GetFloat() )
|
||
|
{
|
||
|
VectorScale( vecBreakVelocity, tf_playergib_maxspeed.GetFloat() / flSpeed, vecBreakVelocity );
|
||
|
}
|
||
|
|
||
|
breakablepropparams_t breakParams( vecOrigin, GetRenderAngles(), vecBreakVelocity, angularImpulse );
|
||
|
breakParams.impactEnergyScale = 1.0f;//
|
||
|
|
||
|
// Break up the player.
|
||
|
if ( !bWearableGibs )
|
||
|
{
|
||
|
// Gib the player's body.
|
||
|
m_hHeadGib = NULL;
|
||
|
m_hSpawnedGibs.Purge();
|
||
|
|
||
|
bool bHasCustomModel = GetPlayerClass()->HasCustomModel();
|
||
|
int nModelIndex = bHasCustomModel ? modelinfo->GetModelIndex( GetPlayerClass()->GetModelName() ) : GetModelIndex();
|
||
|
|
||
|
if ( bOnlyHead )
|
||
|
{
|
||
|
if ( UTIL_IsLowViolence() )
|
||
|
{
|
||
|
// No bloody gibs with pyro-vision goggles
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Create only a head gib.
|
||
|
CUtlVector<breakmodel_t> headGib;
|
||
|
int nClassIndex = GetPlayerClass()->GetClassIndex();
|
||
|
if ( bHasCustomModel )
|
||
|
{
|
||
|
for ( int i=0; i<m_aGibs.Count(); ++i )
|
||
|
{
|
||
|
if ( Q_strcmp( m_aGibs[i].modelName, g_pszBotHeadGibs[nClassIndex] ) == 0 )
|
||
|
{
|
||
|
headGib.AddToHead( m_aGibs[i] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for ( int i=0; i<m_aGibs.Count(); ++i )
|
||
|
{
|
||
|
if ( Q_strcmp( m_aGibs[i].modelName, g_pszHeadGibs[nClassIndex] ) == 0 )
|
||
|
{
|
||
|
headGib.AddToHead( m_aGibs[i] );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_hFirstGib = CreateGibsFromList( headGib, nModelIndex, NULL, breakParams, this, -1 , false, true, &m_hSpawnedGibs, bBurning );
|
||
|
m_hHeadGib = m_hFirstGib;
|
||
|
if ( m_hFirstGib )
|
||
|
{
|
||
|
IPhysicsObject *pPhysicsObject = m_hFirstGib->VPhysicsGetObject();
|
||
|
if( pPhysicsObject )
|
||
|
{
|
||
|
// Give the head some rotational damping so it doesn't roll so much (for the player's view).
|
||
|
float damping, rotdamping;
|
||
|
pPhysicsObject->GetDamping( &damping, &rotdamping );
|
||
|
rotdamping *= 6.f;
|
||
|
pPhysicsObject->SetDamping( &damping, &rotdamping );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CheckAndUpdateGibType();
|
||
|
m_hFirstGib = CreateGibsFromList( m_aGibs, nModelIndex, NULL, breakParams, this, -1 , false, true, &m_hSpawnedGibs, bBurning );
|
||
|
}
|
||
|
DropPartyHat( breakParams, vecBreakVelocity );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Gib up the player's clothing.
|
||
|
for ( int i=0; i<GetNumWearables(); ++i )
|
||
|
{
|
||
|
C_TFWearable *pItem = dynamic_cast<C_TFWearable*> (GetWearable(i));
|
||
|
|
||
|
if ( !pItem )
|
||
|
continue;
|
||
|
|
||
|
// Don't try to drop items which haven't loaded yet
|
||
|
if ( !pItem->GetModel() || !pItem->GetModelPtr() )
|
||
|
continue;
|
||
|
|
||
|
// Only drop wearable gibs for wearables that are flagged as droppable.
|
||
|
if ( pItem->GetDropType() != ITEM_DROP_TYPE_DROP )
|
||
|
continue;
|
||
|
|
||
|
if ( pItem->IsDisguiseWearable() && !bDisguiseGibs )
|
||
|
continue;
|
||
|
|
||
|
if ( !pItem->IsDisguiseWearable() && bDisguiseGibs )
|
||
|
continue;
|
||
|
|
||
|
DropWearable( pItem, breakParams );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::DropWearable( C_TFWearable *pItem, const breakablepropparams_t ¶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;i<iCount;i++ )
|
||
|
{
|
||
|
C_BaseObject *pObj = m_aObjects[i].Get();
|
||
|
|
||
|
if ( !pObj )
|
||
|
continue;
|
||
|
|
||
|
if ( pObj->IsDormant() || pObj->IsMarkedForDeletion() )
|
||
|
continue;
|
||
|
|
||
|
if ( pObj->GetType() != iObjectType )
|
||
|
continue;
|
||
|
|
||
|
if ( pObj->GetObjectMode() != iObjectMode )
|
||
|
continue;
|
||
|
|
||
|
return pObj;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : collisionGroup -
|
||
|
// Output : Returns true on success, false on failure.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldCollide( int collisionGroup, int contentsMask ) const
|
||
|
{
|
||
|
if ( ( ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) && tf_avoidteammates.GetBool() ) ||
|
||
|
collisionGroup == TFCOLLISION_GROUP_ROCKETS )
|
||
|
{
|
||
|
switch( GetTeamNumber() )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
if ( !( contentsMask & CONTENTS_REDTEAM ) )
|
||
|
return false;
|
||
|
break;
|
||
|
|
||
|
case TF_TEAM_BLUE:
|
||
|
if ( !( contentsMask & CONTENTS_BLUETEAM ) )
|
||
|
return false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return BaseClass::ShouldCollide( collisionGroup, contentsMask );
|
||
|
}
|
||
|
|
||
|
float C_TFPlayer::GetPercentInvisible( void )
|
||
|
{
|
||
|
return m_Shared.GetPercentInvisible();
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::AdjustSkinIndexForZombie( int iClassIndex, int &iSkinIndex )
|
||
|
{
|
||
|
// We only know how to adjust the 4 standard base skins (red/blue * normal/invuln)
|
||
|
Assert( iSkinIndex >= 0 && iSkinIndex < 4 );
|
||
|
|
||
|
if ( iClassIndex == TF_CLASS_SPY )
|
||
|
{
|
||
|
// Spy has a bunch of extra skins used to adjust the mask
|
||
|
iSkinIndex += 22;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// 4: red zombie
|
||
|
// 5: blue zombie
|
||
|
// 6: red zombie invuln
|
||
|
// 7: blue zombie invuln
|
||
|
iSkinIndex += 4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::BRenderAsZombie( bool bWeaponsCheck /*= false */ )
|
||
|
{
|
||
|
// Only if the local player is optining in.
|
||
|
if ( !IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_HALLOWEEN, bWeaponsCheck ) )
|
||
|
return false;
|
||
|
|
||
|
// Should we render as somebody else?
|
||
|
bool bRenderDisguised = false;
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
|
||
|
// When disguised, our teammates will see us with a mask.
|
||
|
// Don't show us as a zombie in that state, because the zombie parts
|
||
|
// (like every other cosmetic) disappear.
|
||
|
if ( !IsEnemyPlayer() )
|
||
|
return false;
|
||
|
|
||
|
// Ditto when we are disguised as an enemy spy. We always use the mask
|
||
|
// in that case and hide cosmetics
|
||
|
if ( m_Shared.GetDisguiseClass() == TF_CLASS_SPY )
|
||
|
return false;
|
||
|
|
||
|
bRenderDisguised = true;
|
||
|
}
|
||
|
|
||
|
int iPlayerSkinOverride = bRenderDisguised ? m_Shared.GetDisguisedSkinOverride() : GetSkinOverride();
|
||
|
|
||
|
return iPlayerSkinOverride == 1;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetSkin()
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
|
||
|
|
||
|
if ( !pLocalPlayer )
|
||
|
return 0;
|
||
|
|
||
|
// Allow server plugins to override
|
||
|
if ( m_bForcedSkin )
|
||
|
return m_nForcedSkin;
|
||
|
|
||
|
int iVisibleTeam = GetTeamNumber();
|
||
|
|
||
|
// if this player is disguised and on the other team, use disguise team
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) && IsEnemyPlayer() && ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
|
||
|
{
|
||
|
iVisibleTeam = ( iVisibleTeam == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED );
|
||
|
}
|
||
|
else if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
|
||
|
{
|
||
|
iVisibleTeam = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
|
||
|
int nSkin;
|
||
|
|
||
|
switch( iVisibleTeam )
|
||
|
{
|
||
|
case TF_TEAM_RED:
|
||
|
nSkin = 0;
|
||
|
break;
|
||
|
|
||
|
case TF_TEAM_BLUE:
|
||
|
nSkin = 1;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
nSkin = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Assume we'll switch skins to show the spy mask
|
||
|
bool bCheckSpyMask = true;
|
||
|
|
||
|
// 3 and 4 are invulnerable
|
||
|
if ( m_Shared.IsInvulnerable() &&
|
||
|
( !m_Shared.InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) || gpGlobals->curtime < GetLastDamageTime() + 2.0f ) )
|
||
|
{
|
||
|
nSkin += 2;
|
||
|
bCheckSpyMask = false;
|
||
|
}
|
||
|
|
||
|
// Check for any special player skin override behaviour.
|
||
|
if ( BRenderAsZombie() )
|
||
|
{
|
||
|
int iClass = GetPlayerClass()->GetClassIndex();
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
Assert( IsEnemyPlayer() );
|
||
|
iClass = m_Shared.GetDisguiseClass();
|
||
|
}
|
||
|
|
||
|
AdjustSkinIndexForZombie( iClass, nSkin );
|
||
|
bCheckSpyMask = false; // no spy masks for zombies
|
||
|
}
|
||
|
|
||
|
if ( bCheckSpyMask && m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
if ( !IsEnemyPlayer() )
|
||
|
{
|
||
|
nSkin += 4 + ( ( m_Shared.GetDisguiseClass() - TF_FIRST_NORMAL_CLASS ) * 2 );
|
||
|
}
|
||
|
else if ( m_Shared.GetDisguiseClass() == TF_CLASS_SPY )
|
||
|
{
|
||
|
nSkin += 4 + ( ( m_Shared.GetDisguiseMask() - TF_FIRST_NORMAL_CLASS ) * 2 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check for testing override
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( ( this == pLocalPlayer ) && ( test_local_player_skin_override.GetInt() >= 0 ) )
|
||
|
nSkin = test_local_player_skin_override.GetInt();
|
||
|
#endif
|
||
|
|
||
|
return nSkin;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : iClass -
|
||
|
// Output : Returns true on success, false on failure.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsPlayerClass( int iClass ) const
|
||
|
{
|
||
|
const C_TFPlayerClass *pClass = GetPlayerClass();
|
||
|
if ( !pClass )
|
||
|
return false;
|
||
|
|
||
|
return ( pClass->GetClassIndex() == iClass );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Don't take damage decals while stealthed
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::AddDecal( const Vector& rayStart, const Vector& rayEnd,
|
||
|
const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal )
|
||
|
{
|
||
|
if ( m_Shared.IsStealthed() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_Shared.IsInvulnerable() )
|
||
|
{
|
||
|
Vector vecDir = rayEnd - rayStart;
|
||
|
VectorNormalize(vecDir);
|
||
|
g_pEffects->Ricochet( rayEnd - (vecDir * 8), -vecDir );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifdef TF_RAID_MODE
|
||
|
// no decals for the BLUE team in Raid mode
|
||
|
// (temp workaround for decals causing the glows to not draw correctly)
|
||
|
if ( TFGameRules() && TFGameRules()->IsRaidMode() )
|
||
|
{
|
||
|
if ( GetTeamNumber() == TF_TEAM_BLUE )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
#endif // TF_RAID_MODE
|
||
|
|
||
|
// don't decal from inside the player
|
||
|
if ( tr.startsolid )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
BaseClass::AddDecal( rayStart, rayEnd, decalCenter, hitbox, decalIndex, doTrace, tr, maxLODToDecal );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Called every time the player respawns
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ClientPlayerRespawn( void )
|
||
|
{
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
// MCJOHN - For testing, dump out all of the textures whenever we respawn
|
||
|
STAGING_ONLY_EXEC( engine->ClientCmd_Unrestricted( "mat_evict_all" ) );
|
||
|
|
||
|
// Dod called these, not sure why
|
||
|
//MoveToLastReceivedPosition( true );
|
||
|
//ResetLatched();
|
||
|
|
||
|
// Reset the camera.
|
||
|
HandleTaunting();
|
||
|
|
||
|
ResetToneMapping(1.0);
|
||
|
|
||
|
// Release the duck toggle key
|
||
|
KeyUp( &in_ducktoggle, NULL );
|
||
|
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_respawn" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
// NVNT revert tf haptics
|
||
|
tfHaptics.Revert();
|
||
|
|
||
|
#if defined( REPLAY_ENABLED )
|
||
|
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED )
|
||
|
{
|
||
|
// Notify replay history manager that the local player has spawned.
|
||
|
// NOTE: This won't do anything if replay isn't enabled, isn't recording, etc.
|
||
|
g_pClientReplayContext->OnPlayerSpawn();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
SetAppropriateCamera( this );
|
||
|
|
||
|
if ( m_hRevivePrompt )
|
||
|
{
|
||
|
m_hRevivePrompt->MarkForDeletion();
|
||
|
m_hRevivePrompt = NULL;
|
||
|
}
|
||
|
|
||
|
m_bNotifiedWeaponInspectThisLife = false;
|
||
|
}
|
||
|
|
||
|
UpdateVisibility();
|
||
|
|
||
|
DestroyBoneAttachments();
|
||
|
|
||
|
UpdateDemomanEyeEffect( 0 );
|
||
|
|
||
|
UpdateKillStreakEffects( 0 );
|
||
|
|
||
|
UpdateMVMEyeGlowEffect( true );
|
||
|
|
||
|
m_hHeadGib = NULL;
|
||
|
m_hFirstGib = NULL;
|
||
|
m_hSpawnedGibs.Purge();
|
||
|
|
||
|
m_fMetersRan = 0;
|
||
|
|
||
|
SetShowHudMenuTauntSelection( false );
|
||
|
|
||
|
CleanUpAnimationOnSpawn();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldDraw()
|
||
|
{
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
if ( m_PlayerClass.HasCustomModel() && !m_PlayerClass.CustomModelIsVisibleToSelf() )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( this == C_TFPlayer::GetLocalTFPlayer() )
|
||
|
{
|
||
|
if ( this->m_Shared.InCond( TF_COND_ZOOMED ) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return BaseClass::ShouldDraw();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetVisionFilterFlags( bool bWeaponsCheck /*= false */ )
|
||
|
{
|
||
|
#if defined( REPLAY_ENABLED )
|
||
|
extern IEngineClientReplay *g_pEngineClientReplay;
|
||
|
if ( g_pEngineClientReplay->IsPlayingReplayDemo() )
|
||
|
{
|
||
|
if ( tf_replay_pyrovision.GetBool() )
|
||
|
{
|
||
|
return TF_VISION_FILTER_PYRO;
|
||
|
}
|
||
|
return 0x00;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
int nVisionOptInFlags = 0;
|
||
|
CALL_ATTRIB_HOOK_INT( nVisionOptInFlags, vision_opt_in_flags );
|
||
|
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
// Force our vision filter to a specific setting
|
||
|
if ( bWeaponsCheck && m_nForceVisionFilterFlags != 0 )
|
||
|
{
|
||
|
return m_nForceVisionFilterFlags;
|
||
|
}
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
int nFlags = 0;
|
||
|
if ( test_pyrovision.GetBool() )
|
||
|
{
|
||
|
nFlags |= TF_VISION_FILTER_PYRO;
|
||
|
}
|
||
|
|
||
|
if ( test_halloweenvision.GetBool() )
|
||
|
{
|
||
|
nFlags |= TF_VISION_FILTER_HALLOWEEN;
|
||
|
}
|
||
|
|
||
|
if ( test_romevision.GetBool() )
|
||
|
{
|
||
|
nFlags |= TF_VISION_FILTER_ROME;
|
||
|
}
|
||
|
|
||
|
if ( nFlags != 0 )
|
||
|
{
|
||
|
return nFlags;
|
||
|
}
|
||
|
|
||
|
if ( test_vision_off.GetBool() )
|
||
|
{
|
||
|
return 0x00;
|
||
|
}
|
||
|
#endif
|
||
|
// Check the PyroVision Convar
|
||
|
if ( GetTeamNumber() == TEAM_SPECTATOR )
|
||
|
{
|
||
|
if ( tf_spectate_pyrovision.GetBool() )
|
||
|
{
|
||
|
nVisionOptInFlags |= TF_VISION_FILTER_PYRO;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check for holidays and add them in to the mix
|
||
|
// Halloween / Fullmoon vision
|
||
|
if ( TFGameRules()->IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
|
||
|
{
|
||
|
nVisionOptInFlags |= TF_VISION_FILTER_HALLOWEEN;
|
||
|
}
|
||
|
|
||
|
// opt-in for romevision?
|
||
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() &&
|
||
|
TFSharedContentManager() && TFSharedContentManager()->IsSharedVisionAvailable( TF_VISION_FILTER_ROME ) &&
|
||
|
tf_romevision_opt_in.GetBool() )
|
||
|
{
|
||
|
nVisionOptInFlags |= TF_VISION_FILTER_ROME;
|
||
|
}
|
||
|
|
||
|
return nVisionOptInFlags;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : updateType -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CalculateVisionUsingCurrentFlags( void )
|
||
|
{
|
||
|
if ( IsLocalPlayer() )
|
||
|
{
|
||
|
for( int iClient = 1; iClient <= gpGlobals->maxClients; ++iClient )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iClient ) );
|
||
|
if ( !pPlayer || !pPlayer->IsPlayer() )
|
||
|
continue;
|
||
|
|
||
|
if ( !pPlayer->IsAlive() )
|
||
|
continue;
|
||
|
|
||
|
pPlayer->UpdateWearables();
|
||
|
pPlayer->SetBodygroupsDirty();
|
||
|
if ( pPlayer->GetActiveWeapon() )
|
||
|
{
|
||
|
pPlayer->GetActiveWeapon()->RestartParticleEffect();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreateSaveMeEffect( MedicCallerType nType /*= CALLER_TYPE_NORMAL*/ )
|
||
|
{
|
||
|
// Don't create them for the local player in first-person view.
|
||
|
if ( IsLocalPlayer() && InFirstPersonView() )
|
||
|
return;
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
|
||
|
return;
|
||
|
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
// If I'm disguised as the enemy, play to all players
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) && m_Shared.GetDisguiseTeam() != GetTeamNumber() && !m_Shared.IsStealthed() )
|
||
|
{
|
||
|
// play to all players
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// only play to teammates
|
||
|
if ( pLocalPlayer && pLocalPlayer->GetTeamNumber() != GetTeamNumber() )
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
StopSaveMeEffect();
|
||
|
|
||
|
float flHealth = float( GetHealth() ) / float( GetMaxHealth() );
|
||
|
Vector vHealth;
|
||
|
vHealth.x = flHealth;
|
||
|
vHealth.y = flHealth;
|
||
|
vHealth.z = flHealth;
|
||
|
|
||
|
if ( nType == CALLER_TYPE_AUTO )
|
||
|
{
|
||
|
m_pSaveMeEffect = ParticleProp()->Create( "speech_mediccall_auto", PATTACH_POINT_FOLLOW, "head" );
|
||
|
EmitSound( "Medic.AutoCallerAnnounce" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pSaveMeEffect = ParticleProp()->Create( "speech_mediccall", PATTACH_POINT_FOLLOW, "head" );
|
||
|
}
|
||
|
|
||
|
if ( m_pSaveMeEffect )
|
||
|
{
|
||
|
m_pSaveMeEffect->SetControlPoint( 1, vHealth );
|
||
|
}
|
||
|
|
||
|
// If the local player is a medic, add this player to our list of medic callers
|
||
|
if ( pLocalPlayer && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && pLocalPlayer->IsAlive() == true )
|
||
|
{
|
||
|
Vector vecPos;
|
||
|
if ( GetAttachmentLocal( LookupAttachment( "head" ), vecPos ) )
|
||
|
{
|
||
|
vecPos += Vector(0,0,18); // Particle effect is 18 units above the attachment
|
||
|
CTFMedicCallerPanel::AddMedicCaller( this, 5.0, vecPos, nType );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_calledformedic" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "userid", GetUserID() );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
|
||
|
m_flSaveMeExpireTime = gpGlobals->curtime + 5.0f;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopSaveMeEffect( bool bForceRemoveInstantly /*= false*/ )
|
||
|
{
|
||
|
if ( m_pSaveMeEffect )
|
||
|
{
|
||
|
if ( bForceRemoveInstantly )
|
||
|
{
|
||
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_pSaveMeEffect );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pSaveMeEffect );
|
||
|
}
|
||
|
|
||
|
m_pSaveMeEffect = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreateTauntWithMeEffect()
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pLocalPlayer || this == pLocalPlayer )
|
||
|
return;
|
||
|
|
||
|
if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
|
||
|
return;
|
||
|
|
||
|
if ( !m_pTauntWithMeEffect )
|
||
|
{
|
||
|
const char *pszImageName;
|
||
|
const char *pszParticleName;
|
||
|
if ( GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
|
||
|
{
|
||
|
pszImageName = "../Effects/speech_taunt";
|
||
|
pszParticleName = "speech_taunt_all";
|
||
|
}
|
||
|
else if ( GetTeamNumber() == TF_TEAM_RED )
|
||
|
{
|
||
|
pszImageName = "../Effects/speech_taunt_red";
|
||
|
pszParticleName = "speech_taunt_red";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pszImageName = "../Effects/speech_taunt_blue";
|
||
|
pszParticleName = "speech_taunt_blue";
|
||
|
}
|
||
|
m_pTauntWithMeEffect = ParticleProp()->Create( pszParticleName, PATTACH_POINT_FOLLOW, "head" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopTauntWithMeEffect()
|
||
|
{
|
||
|
if ( m_pTauntWithMeEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_pTauntWithMeEffect );
|
||
|
m_pTauntWithMeEffect = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreateKart()
|
||
|
{
|
||
|
Assert( !m_pKart );
|
||
|
|
||
|
m_pKart = new C_BaseAnimating();
|
||
|
if ( m_pKart )
|
||
|
{
|
||
|
m_pKart->SetModel( "models/player/items/taunts/bumpercar/parts/bumpercar.mdl" );
|
||
|
m_pKart->m_nSkin = GetTeamNumber() == TF_TEAM_RED ? 0 : 1;
|
||
|
|
||
|
// Add to the client entity list. This has to be done before we attach to the parent or
|
||
|
// else we won't wind up on their "also shadow these children" list.
|
||
|
ClientEntityList().AddNonNetworkableEntity(m_pKart);
|
||
|
|
||
|
m_pKart->FollowEntity(this, true);
|
||
|
m_pKart->CreateShadow();
|
||
|
|
||
|
TrackAngRotation( false );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::RemoveKart()
|
||
|
{
|
||
|
StopKartEffect();
|
||
|
|
||
|
if ( m_pKart )
|
||
|
{
|
||
|
// Cleanup our shadows.
|
||
|
m_pKart->DestroyShadow();
|
||
|
|
||
|
// Remove from the client entity list.
|
||
|
ClientEntityList().RemoveEntity( m_pKart->GetClientHandle() );
|
||
|
m_pKart->Release();
|
||
|
m_pKart = NULL;
|
||
|
|
||
|
TrackAngRotation( true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreateKartEffect( const char *pszEffectName )
|
||
|
{
|
||
|
if ( !m_pKart )
|
||
|
return;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
for ( int i=0; i<ARRAYSIZE( m_pKartParticles ); ++i )
|
||
|
{
|
||
|
Assert( !m_pKartParticles[i] );
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
|
||
|
m_pKartParticles[ KART_PARTICLE_LEFT_LIGHT ] = m_pKart->ParticleProp()->Create( pszEffectName , PATTACH_POINT_FOLLOW, "bumpercar_wheel_left" );
|
||
|
m_pKartParticles[ KART_PARTICLE_RIGHT_LIGHT ] = m_pKart->ParticleProp()->Create( pszEffectName , PATTACH_POINT_FOLLOW, "bumpercar_wheel_right" );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopKartEffect()
|
||
|
{
|
||
|
if ( !m_pKart )
|
||
|
return;
|
||
|
|
||
|
for ( int i=0; i<ARRAYSIZE( m_pKartParticles ); ++i )
|
||
|
{
|
||
|
if ( m_pKartParticles[i] )
|
||
|
{
|
||
|
m_pKart->ParticleProp()->StopEmission( m_pKartParticles[i] );
|
||
|
m_pKartParticles[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StartKartBrakeEffect()
|
||
|
{
|
||
|
if ( !m_pKart )
|
||
|
return;
|
||
|
|
||
|
if ( !m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] )
|
||
|
{
|
||
|
m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] = m_pKart->ParticleProp()->Create( "kart_braking_sparks" , PATTACH_POINT_FOLLOW, "left_rear_wheel" );
|
||
|
}
|
||
|
|
||
|
if ( !m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] )
|
||
|
{
|
||
|
m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] = m_pKart->ParticleProp()->Create( "kart_braking_sparks", PATTACH_POINT_FOLLOW, "right_rear_wheel" );
|
||
|
}
|
||
|
}
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::StopKartBrakeEffect()
|
||
|
{
|
||
|
if ( !m_pKart )
|
||
|
return;
|
||
|
|
||
|
if ( m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] )
|
||
|
{
|
||
|
m_pKart->ParticleProp()->StopEmission( m_pKartParticles[KART_PARTICLE_LEFT_WHEEL] );
|
||
|
}
|
||
|
|
||
|
if ( m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] )
|
||
|
{
|
||
|
m_pKart->ParticleProp()->StopEmission( m_pKartParticles[KART_PARTICLE_RIGHT_WHEEL] );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsOverridingViewmodel( void )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = this;
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
|
||
|
pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() )
|
||
|
{
|
||
|
pPlayer = assert_cast<C_TFPlayer*>(pLocalPlayer->GetObserverTarget());
|
||
|
}
|
||
|
|
||
|
if ( pPlayer->m_Shared.IsInvulnerable() )
|
||
|
return true;
|
||
|
|
||
|
return BaseClass::IsOverridingViewmodel();
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::OverrideView( CViewSetup *pSetup )
|
||
|
{
|
||
|
BaseClass::OverrideView( pSetup );
|
||
|
|
||
|
TFPlayerClassData_t *pData = GetPlayerClass()->GetData();
|
||
|
|
||
|
if ( pData && g_ThirdPersonManager.WantToUseGameThirdPerson() )
|
||
|
{
|
||
|
Vector vecOffset = pData->m_vecThirdPersonOffset;
|
||
|
|
||
|
g_ThirdPersonManager.SetDesiredCameraOffset( vecOffset );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Draw my viewmodel in some special way
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags )
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
C_TFPlayer *pPlayer = this;
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
|
||
|
pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() )
|
||
|
{
|
||
|
pPlayer = assert_cast<C_TFPlayer*>(pLocalPlayer->GetObserverTarget());
|
||
|
}
|
||
|
|
||
|
if ( pPlayer->m_Shared.IsInvulnerable() )
|
||
|
{
|
||
|
if ( flags & STUDIO_RENDER )
|
||
|
{
|
||
|
// Force the invulnerable material
|
||
|
modelrender->ForcedMaterialOverride( *pPlayer->GetInvulnMaterialRef() );
|
||
|
}
|
||
|
|
||
|
// We allow our weapon to then override this if it wants to.
|
||
|
// This allows c_* weapons to draw themselves.
|
||
|
C_BaseCombatWeapon *pWeapon = pViewmodel->GetOwningWeapon();
|
||
|
if ( pWeapon && pWeapon->IsOverridingViewmodel() )
|
||
|
{
|
||
|
ret = pWeapon->DrawOverriddenViewmodel( pViewmodel, flags );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ret = pViewmodel->DrawOverriddenViewmodel( flags );
|
||
|
}
|
||
|
|
||
|
if ( flags & STUDIO_RENDER )
|
||
|
{
|
||
|
modelrender->ForcedMaterialOverride( NULL );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ApplyBoneMatrixTransform( matrix3x4_t& transform )
|
||
|
{
|
||
|
BaseClass::ApplyBoneMatrixTransform ( transform );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void BuildBigHeadTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale )
|
||
|
{
|
||
|
if ( !pObject || flScale == 1.f )
|
||
|
return;
|
||
|
|
||
|
// Scale the head.
|
||
|
int iHeadBone = pObject->LookupBone( "bip_head" );
|
||
|
if ( iHeadBone == -1 )
|
||
|
return;
|
||
|
|
||
|
matrix3x4_t &transform = pObject->GetBoneForWrite( iHeadBone );
|
||
|
Vector head_position;
|
||
|
MatrixGetTranslation( transform, head_position );
|
||
|
|
||
|
// Scale the head.
|
||
|
MatrixScaleBy ( flScale, transform );
|
||
|
|
||
|
const int cMaxNumHelms = 2;
|
||
|
int iHelmIndex[cMaxNumHelms];
|
||
|
iHelmIndex[0] = pObject->LookupBone( "prp_helmet" );
|
||
|
iHelmIndex[1] = pObject->LookupBone( "prp_hat" );
|
||
|
|
||
|
for ( int i = 0; i < cMaxNumHelms; i++ )
|
||
|
{
|
||
|
if ( iHelmIndex[i] != -1 )
|
||
|
{
|
||
|
matrix3x4_t &transformhelmet = pObject->GetBoneForWrite( iHelmIndex[i] );
|
||
|
MatrixScaleBy ( flScale, transformhelmet );
|
||
|
|
||
|
Vector helmet_position;
|
||
|
MatrixGetTranslation( transformhelmet, helmet_position );
|
||
|
Vector offset = helmet_position - head_position;
|
||
|
MatrixSetTranslation ( ( offset * flScale ) + head_position, transformhelmet );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void BuildDecapitatedTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
|
||
|
{
|
||
|
if ( !pObject )
|
||
|
return;
|
||
|
|
||
|
// Scale the head to nothing.
|
||
|
int iHeadBone = pObject->LookupBone( "bip_head" );
|
||
|
if ( iHeadBone != -1 )
|
||
|
{
|
||
|
matrix3x4_t &transform = pObject->GetBoneForWrite( iHeadBone );
|
||
|
MatrixScaleByZero ( transform );
|
||
|
}
|
||
|
|
||
|
int iHelm = pObject->LookupBone( "prp_helmet" );
|
||
|
if ( iHelm != -1 )
|
||
|
{
|
||
|
// Scale the helmet.
|
||
|
matrix3x4_t &transformhelmet = pObject->GetBoneForWrite( iHelm );
|
||
|
MatrixScaleByZero ( transformhelmet );
|
||
|
}
|
||
|
|
||
|
iHelm = pObject->LookupBone( "prp_hat" );
|
||
|
if ( iHelm != -1 )
|
||
|
{
|
||
|
matrix3x4_t &transformhelmet = pObject->GetBoneForWrite( iHelm );
|
||
|
MatrixScaleByZero ( transformhelmet );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void BuildNeckScaleTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale, int iClass )
|
||
|
{
|
||
|
if ( !pObject || flScale == 1.f )
|
||
|
return;
|
||
|
|
||
|
int iNeck = pObject->LookupBone( "bip_neck" );
|
||
|
if ( iNeck == -1 )
|
||
|
return;
|
||
|
|
||
|
matrix3x4_t &neck_transform = pObject->GetBoneForWrite( iNeck );
|
||
|
|
||
|
Vector spine_position, neck_position, head_position, position, offset(0, 0, 0);
|
||
|
if ( iClass != TF_CLASS_HEAVYWEAPONS )
|
||
|
{
|
||
|
// Compress the neck into the spine.
|
||
|
int iSpine = pObject->LookupBone( "bip_spine_3" );
|
||
|
if ( iSpine != -1 )
|
||
|
{
|
||
|
matrix3x4_t &spine_transform = pObject->GetBoneForWrite( iSpine );
|
||
|
MatrixPosition( spine_transform, spine_position );
|
||
|
MatrixPosition( neck_transform, neck_position );
|
||
|
position = flScale * ( neck_position - spine_position );
|
||
|
MatrixSetTranslation( spine_position + position, neck_transform );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( iClass == TF_CLASS_SPY )
|
||
|
{
|
||
|
int iCig = pObject->LookupBone( "prp_cig" );
|
||
|
if ( iCig != -1 )
|
||
|
{
|
||
|
matrix3x4_t &cig_transform = pObject->GetBoneForWrite( iCig );
|
||
|
MatrixScaleByZero ( cig_transform );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Compress the head into the neck.
|
||
|
int iHead = pObject->LookupBone( "bip_head" );
|
||
|
if ( iHead != -1 )
|
||
|
{
|
||
|
matrix3x4_t &head_transform = pObject->GetBoneForWrite( iHead );
|
||
|
MatrixPosition( head_transform, head_position );
|
||
|
MatrixPosition( neck_transform, neck_position );
|
||
|
offset = head_position - neck_position;
|
||
|
MatrixSetTranslation( neck_position, head_transform );
|
||
|
}
|
||
|
|
||
|
// Store helmet bone offset.
|
||
|
int iHelm = pObject->LookupBone( "prp_helmet" );
|
||
|
if ( iHelm != -1 )
|
||
|
{
|
||
|
matrix3x4_t &helmet_transform = pObject->GetBoneForWrite( iHelm );
|
||
|
MatrixPosition( helmet_transform, position );
|
||
|
MatrixSetTranslation( position - offset, helmet_transform );
|
||
|
}
|
||
|
|
||
|
// Store alternate helmet bone offset.
|
||
|
iHelm = pObject->LookupBone( "prp_hat" );
|
||
|
if ( iHelm != -1 )
|
||
|
{
|
||
|
matrix3x4_t &hat_transform = pObject->GetBoneForWrite( iHelm );
|
||
|
MatrixPosition( hat_transform, position );
|
||
|
MatrixSetTranslation( position - offset, hat_transform );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Get child bones from a specified bone index
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void AppendChildren_R( CUtlVector< const mstudiobone_t * > *pChildBones, const studiohdr_t *pHdr, int nBone )
|
||
|
{
|
||
|
if ( !pChildBones || !pHdr )
|
||
|
return;
|
||
|
|
||
|
// Child bones have to have a larger bone index than their parent, so start searching from nBone + 1
|
||
|
for ( int i = nBone + 1; i < pHdr->numbones; ++i )
|
||
|
{
|
||
|
const mstudiobone_t *pBone = pHdr->pBone( i );
|
||
|
if ( pBone->parent == nBone )
|
||
|
{
|
||
|
pChildBones->AddToTail( pBone );
|
||
|
// If you just want immediate children don't recurse, this will do a depth first traversal, could do
|
||
|
// breadth first by adding all children first and then looping through the added bones and recursing
|
||
|
AppendChildren_R( pChildBones, pHdr, i );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void BuildTorsoScaleTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale, int iClass )
|
||
|
{
|
||
|
if ( !pObject || flScale == 1.f )
|
||
|
return;
|
||
|
|
||
|
int iPelvis = pObject->LookupBone( "bip_pelvis" );
|
||
|
if ( iPelvis == -1 )
|
||
|
return;
|
||
|
|
||
|
const studiohdr_t *pHdr = modelinfo->GetStudiomodel( pObject->GetModel() );
|
||
|
|
||
|
int iTargetBone = iPelvis;
|
||
|
|
||
|
// must be in this order
|
||
|
static const char *s_torsoBoneNames[] =
|
||
|
{
|
||
|
"bip_spine_0",
|
||
|
"bip_spine_1",
|
||
|
"bip_spine_2",
|
||
|
"bip_spine_3",
|
||
|
"bip_neck"
|
||
|
};
|
||
|
|
||
|
// Compress torso bones toward pelvis in order.
|
||
|
for ( int i=0; i<ARRAYSIZE( s_torsoBoneNames ); ++i )
|
||
|
{
|
||
|
int iMoveBone = pObject->LookupBone( s_torsoBoneNames[i] );
|
||
|
if ( iMoveBone == -1 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const matrix3x4_t &targetBone_transform = pObject->GetBone( iTargetBone );
|
||
|
Vector vTargetBonePos;
|
||
|
MatrixPosition( targetBone_transform, vTargetBonePos );
|
||
|
|
||
|
matrix3x4_t &moveBone_transform = pObject->GetBoneForWrite( iMoveBone );
|
||
|
Vector vMoveBonePos;
|
||
|
MatrixPosition( moveBone_transform, vMoveBonePos );
|
||
|
Vector vNewMovePos = vTargetBonePos + flScale * ( vMoveBonePos - vTargetBonePos );
|
||
|
MatrixSetTranslation( vNewMovePos, moveBone_transform );
|
||
|
|
||
|
iTargetBone = iMoveBone;
|
||
|
|
||
|
Vector vOffset = vNewMovePos - vMoveBonePos;
|
||
|
|
||
|
// apply to all its child bones
|
||
|
CUtlVector< const mstudiobone_t * > vecChildBones;
|
||
|
AppendChildren_R( &vecChildBones, pHdr, iMoveBone );
|
||
|
for ( int j=0; j<vecChildBones.Count(); ++j )
|
||
|
{
|
||
|
int iChildBone = pObject->LookupBone( vecChildBones[j]->pszName() );
|
||
|
if ( iChildBone == -1 )
|
||
|
continue;
|
||
|
|
||
|
matrix3x4_t &childBone_transform = pObject->GetBoneForWrite( iChildBone );
|
||
|
Vector vChildPos;
|
||
|
MatrixPosition( childBone_transform, vChildPos );
|
||
|
MatrixSetTranslation( vChildPos + vOffset, childBone_transform );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void BuildHandScaleTransformations( CBaseAnimating *pObject, CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, float flScale )
|
||
|
{
|
||
|
if ( !pObject || flScale == 1.f )
|
||
|
return;
|
||
|
|
||
|
const studiohdr_t *pHdr = modelinfo->GetStudiomodel( pObject->GetModel() );
|
||
|
|
||
|
// must be in this order
|
||
|
static const char *s_handBoneNames[] =
|
||
|
{
|
||
|
"bip_hand_L",
|
||
|
"bip_hand_R"
|
||
|
};
|
||
|
|
||
|
// scale hand bones
|
||
|
for ( int i=0; i<ARRAYSIZE( s_handBoneNames ); ++i )
|
||
|
{
|
||
|
int iHand = pObject->LookupBone( s_handBoneNames[i] );
|
||
|
if ( iHand == -1 )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
matrix3x4_t& transform = pObject->GetBoneForWrite( iHand );
|
||
|
MatrixScaleBy( flScale, transform );
|
||
|
|
||
|
// apply to all its child bones
|
||
|
CUtlVector< const mstudiobone_t * > vecChildBones;
|
||
|
AppendChildren_R( &vecChildBones, pHdr, iHand );
|
||
|
for ( int j=0; j<vecChildBones.Count(); ++j )
|
||
|
{
|
||
|
int iChildBone = pObject->LookupBone( vecChildBones[j]->pszName() );
|
||
|
if ( iChildBone == -1 )
|
||
|
continue;
|
||
|
|
||
|
matrix3x4_t &childBone_transform = pObject->GetBoneForWrite( iChildBone );
|
||
|
MatrixScaleBy( flScale, childBone_transform );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
|
||
|
{
|
||
|
BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed );
|
||
|
|
||
|
if ( GetGroundEntity() == NULL )
|
||
|
{
|
||
|
Vector hullSizeNormal = VEC_HULL_MAX_SCALED( this ) - VEC_HULL_MIN_SCALED( this );
|
||
|
Vector hullSizeCrouch = VEC_DUCK_HULL_MAX_SCALED( this ) - VEC_DUCK_HULL_MIN_SCALED( this );
|
||
|
Vector duckOffset = ( hullSizeNormal - hullSizeCrouch );
|
||
|
|
||
|
// The player is in the air.
|
||
|
if ( GetFlags() & FL_DUCKING )
|
||
|
{
|
||
|
if ( !m_bDuckJumpInterp )
|
||
|
{
|
||
|
m_flFirstDuckJumpInterp = gpGlobals->curtime;
|
||
|
}
|
||
|
m_bDuckJumpInterp = true;
|
||
|
m_flLastDuckJumpInterp = gpGlobals->curtime;
|
||
|
|
||
|
float flRatio = MIN( 0.15f, gpGlobals->curtime - m_flFirstDuckJumpInterp ) / 0.15f;
|
||
|
m_flDuckJumpInterp = 1.f - flRatio;
|
||
|
}
|
||
|
else if ( m_bDuckJumpInterp )
|
||
|
{
|
||
|
float flRatio = MIN( 0.15f, gpGlobals->curtime - m_flLastDuckJumpInterp ) / 0.15f;
|
||
|
m_flDuckJumpInterp = -(1.f - flRatio);
|
||
|
if ( m_flDuckJumpInterp == 0.f )
|
||
|
{
|
||
|
// Turn off interpolation when we return to our normal, unducked location.
|
||
|
m_bDuckJumpInterp = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_bDuckJumpInterp && m_flDuckJumpInterp != 0.f )
|
||
|
{
|
||
|
duckOffset *= m_flDuckJumpInterp;
|
||
|
for (int i = 0; i < hdr->numbones(); i++)
|
||
|
{
|
||
|
if ( !( hdr->boneFlags( i ) & boneMask ) )
|
||
|
continue;
|
||
|
|
||
|
matrix3x4_t &transform = GetBoneForWrite( i );
|
||
|
Vector bone_pos;
|
||
|
MatrixGetTranslation( transform, bone_pos );
|
||
|
MatrixSetTranslation( bone_pos - duckOffset, transform );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( m_bDuckJumpInterp )
|
||
|
{
|
||
|
m_bDuckJumpInterp = false;
|
||
|
}
|
||
|
|
||
|
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
|
||
|
float flHeadScale = m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ? 1.5 : m_flHeadScale;
|
||
|
BuildBigHeadTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, flHeadScale );
|
||
|
BuildTorsoScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flTorsoScale, GetPlayerClass()->GetClassIndex() );
|
||
|
BuildHandScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flHandScale );
|
||
|
|
||
|
BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "bip_head" );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFRagdoll::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
|
||
|
{
|
||
|
BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed );
|
||
|
|
||
|
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
|
||
|
BuildBigHeadTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flHeadScale );
|
||
|
BuildTorsoScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flTorsoScale, GetClass() );
|
||
|
BuildHandScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, m_flHandScale );
|
||
|
|
||
|
if ( IsDecapitation() && !m_bBaseTransform )
|
||
|
{
|
||
|
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
|
||
|
BuildDecapitatedTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed );
|
||
|
}
|
||
|
|
||
|
if ( IsHeadSmash() && !m_bBaseTransform )
|
||
|
{
|
||
|
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
|
||
|
BuildNeckScaleTransformations( this, hdr, pos, q, cameraTransform, boneMask, boneComputed, 0.5f, GetClass() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::SetHealer( C_TFPlayer *pHealer, float flChargeLevel )
|
||
|
{
|
||
|
if ( pHealer && IsPlayerClass( TF_CLASS_SPY ) )
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_healedbymedic" );
|
||
|
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "medic", pHealer->entindex() );
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// We may be getting healed by multiple healers. Show the healer
|
||
|
// who's got the highest charge level.
|
||
|
if ( m_hHealer )
|
||
|
{
|
||
|
if ( m_flHealerChargeLevel > flChargeLevel )
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_hHealer = pHealer;
|
||
|
m_flHealerChargeLevel = flChargeLevel;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::MedicIsReleasingCharge( void )
|
||
|
{
|
||
|
if ( IsPlayerClass(TF_CLASS_MEDIC) )
|
||
|
{
|
||
|
CTFWeaponBase *pWpn = GetActiveTFWeapon();
|
||
|
|
||
|
if ( pWpn == NULL )
|
||
|
return false;
|
||
|
|
||
|
CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pWpn );
|
||
|
|
||
|
if ( pMedigun )
|
||
|
return pMedigun->IsReleasingCharge();
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::CanShowClassMenu( void )
|
||
|
{
|
||
|
if ( IsHLTV() )
|
||
|
return false;
|
||
|
|
||
|
if( TFGameRules() )
|
||
|
{
|
||
|
if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
|
||
|
{
|
||
|
return !m_bArenaSpectator;
|
||
|
}
|
||
|
|
||
|
// Dont allow the change class menu to come up when we're doing the doors and things. There's really weird
|
||
|
// sorting issues that go on even though the class menu is supposed to draw under the match status panel.
|
||
|
if ( TFGameRules()->IsCompetitiveMode() )
|
||
|
{
|
||
|
float flRestartTime = TFGameRules()->GetRoundRestartTime() - gpGlobals->curtime;
|
||
|
if ( flRestartTime > 0.f && flRestartTime < 10.f )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ( GetTeamNumber() > LAST_SHARED_TEAM );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::CanShowTeamMenu( void )
|
||
|
{
|
||
|
if ( IsHLTV() )
|
||
|
return false;
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() )
|
||
|
#else
|
||
|
if ( TFGameRules() && ( TFGameRules()->IsCompetitiveMode() || TFGameRules()->IsPowerupMode() ) )
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
return false;
|
||
|
|
||
|
return ( GetTeamNumber() != TEAM_UNASSIGNED );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::InitializePoseParams( void )
|
||
|
{
|
||
|
/*
|
||
|
m_headYawPoseParam = LookupPoseParameter( "head_yaw" );
|
||
|
GetPoseParameterRange( m_headYawPoseParam, m_headYawMin, m_headYawMax );
|
||
|
|
||
|
m_headPitchPoseParam = LookupPoseParameter( "head_pitch" );
|
||
|
GetPoseParameterRange( m_headPitchPoseParam, m_headPitchMin, m_headPitchMax );
|
||
|
*/
|
||
|
|
||
|
CStudioHdr *hdr = GetModelPtr();
|
||
|
Assert( hdr );
|
||
|
if ( !hdr )
|
||
|
return;
|
||
|
|
||
|
for ( int i = 0; i < hdr->GetNumPoseParameters() ; i++ )
|
||
|
{
|
||
|
SetPoseParameter( hdr, i, 0.0 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Vector C_TFPlayer::GetChaseCamViewOffset( CBaseEntity *target )
|
||
|
{
|
||
|
if ( target->IsBaseObject() )
|
||
|
{
|
||
|
CBaseObject* pObj = dynamic_cast<CBaseObject*>( target );
|
||
|
if ( pObj && pObj->IsMiniBuilding() )
|
||
|
return Vector(0,0,40);
|
||
|
else
|
||
|
return Vector(0,0,64);
|
||
|
}
|
||
|
|
||
|
return BaseClass::GetChaseCamViewOffset( target );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Called from PostDataUpdate to update the model index
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ValidateModelIndex( void )
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) && IsEnemyPlayer() && ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
|
||
|
{
|
||
|
m_nModelIndex = modelinfo->GetModelIndex( "models/buildables/dispenser_light.mdl" );
|
||
|
|
||
|
if ( C_BasePlayer::GetLocalPlayer() != this )
|
||
|
{
|
||
|
SetAbsAngles( vec3_angle );
|
||
|
}
|
||
|
}
|
||
|
else if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
|
||
|
{
|
||
|
TFPlayerClassData_t *pData = GetPlayerClassData( m_Shared.GetDisguiseClass() );
|
||
|
m_nModelIndex = modelinfo->GetModelIndex( pData->GetModelName() );
|
||
|
}
|
||
|
else if ( m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
|
||
|
{
|
||
|
if ( GetTeamNumber() == TF_TEAM_BLUE )
|
||
|
{
|
||
|
m_nModelIndex = modelinfo->GetModelIndex( "models/props_halloween/ghost_no_hat.mdl" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_nModelIndex = modelinfo->GetModelIndex( "models/props_halloween/ghost_no_hat_red.mdl" );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
C_TFPlayerClass *pClass = GetPlayerClass();
|
||
|
if ( pClass )
|
||
|
{
|
||
|
m_nModelIndex = modelinfo->GetModelIndex( pClass->GetModelName() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_iSpyMaskBodygroup > -1 && GetModelPtr() != NULL && IsPlayerClass( TF_CLASS_SPY ) )
|
||
|
{
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) || m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) )
|
||
|
{
|
||
|
if ( !IsEnemyPlayer() || (m_Shared.GetDisguiseClass() == TF_CLASS_SPY) )
|
||
|
{
|
||
|
SetBodygroup( m_iSpyMaskBodygroup, 1 );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetBodygroup( m_iSpyMaskBodygroup, 0 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BaseClass::ValidateModelIndex();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Simulate the player for this frame
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::Simulate( void )
|
||
|
{
|
||
|
//Frame updates
|
||
|
if ( this == C_BasePlayer::GetLocalPlayer() )
|
||
|
{
|
||
|
//Update the flashlight
|
||
|
Flashlight();
|
||
|
}
|
||
|
|
||
|
// TF doesn't do step sounds based on velocity, instead using anim events
|
||
|
// So we deliberately skip over the base player simulate, which calls them.
|
||
|
BaseClass::BaseClass::Simulate();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
#define PLAYER_HALFWIDTH 10
|
||
|
#define SURFACE_SNOW 91
|
||
|
void C_TFPlayer::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
|
||
|
{
|
||
|
if ( event == 7001 )
|
||
|
{
|
||
|
// Force a footstep sound
|
||
|
m_flStepSoundTime = 0;
|
||
|
Vector vel;
|
||
|
EstimateAbsVelocity( vel );
|
||
|
surfacedata_t *t_pSurface = GetGroundSurface();
|
||
|
UpdateStepSound( t_pSurface, GetAbsOrigin(), vel );
|
||
|
|
||
|
if ( t_pSurface && !this->m_Shared.IsStealthed() && !this->m_Shared.InCond( TF_COND_DISGUISED ) && ( ( vel.x < -150 || vel.x > 150 ) || ( vel.y < -150 || vel.y > 150 ) ) )
|
||
|
{
|
||
|
// check for snow underfoot and trigger particle and decal fx
|
||
|
if ( t_pSurface->game.material == SURFACE_SNOW )
|
||
|
{
|
||
|
ParticleProp()->Create("snow_steppuff01", PATTACH_ABSORIGIN, 0 );
|
||
|
Vector right;
|
||
|
AngleVectors( angles, 0, &right, 0 );
|
||
|
|
||
|
// Figure out where the top of the stepping leg is
|
||
|
trace_t tr;
|
||
|
Vector hipOrigin;
|
||
|
VectorMA( origin, m_IsFootprintOnLeft ? -PLAYER_HALFWIDTH : PLAYER_HALFWIDTH, right, hipOrigin );
|
||
|
|
||
|
// Find where that leg hits the ground
|
||
|
UTIL_TraceLine( hipOrigin, hipOrigin + Vector(0, 0, -COORD_EXTENT * 1.74),
|
||
|
MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr);
|
||
|
|
||
|
// Create the decal
|
||
|
CPVSFilter filter( tr.endpos );
|
||
|
UTIL_DecalTrace( &tr, m_IsFootprintOnLeft ? "footprintL_snow" : "footprintR_snow" );
|
||
|
|
||
|
m_IsFootprintOnLeft = !m_IsFootprintOnLeft;
|
||
|
}
|
||
|
|
||
|
// Halloween-specific bonus footsteps
|
||
|
int iHalloweenFootstepType = 0;
|
||
|
if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
|
||
|
{
|
||
|
CALL_ATTRIB_HOOK_INT( iHalloweenFootstepType, halloween_footstep_type );
|
||
|
}
|
||
|
|
||
|
if ( m_nFootStamps > 0 )
|
||
|
{
|
||
|
// White stamps!
|
||
|
iHalloweenFootstepType = 0xFFFFFFFF;
|
||
|
}
|
||
|
|
||
|
if ( iHalloweenFootstepType != 0 )
|
||
|
{
|
||
|
SpawnHalloweenSpellFootsteps( PATTACH_ABSORIGIN, iHalloweenFootstepType );
|
||
|
}
|
||
|
|
||
|
if ( m_nFootStamps > 0 )
|
||
|
{
|
||
|
m_nFootStamps--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( event == AE_WPN_HIDE )
|
||
|
{
|
||
|
if ( GetActiveWeapon() )
|
||
|
{
|
||
|
int iDisableWeaponHidingForAnimations = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetActiveWeapon(), iDisableWeaponHidingForAnimations, disable_weapon_hiding_for_animations );
|
||
|
if ( iDisableWeaponHidingForAnimations == 0 )
|
||
|
{
|
||
|
GetActiveWeapon()->SetWeaponVisible( false );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( event == AE_WPN_UNHIDE )
|
||
|
{
|
||
|
if ( m_Shared.IsControlStunned() )
|
||
|
return;
|
||
|
|
||
|
if ( GetActiveWeapon() )
|
||
|
{
|
||
|
GetActiveWeapon()->SetWeaponVisible( true );
|
||
|
}
|
||
|
}
|
||
|
else if ( event == AE_WPN_PLAYWPNSOUND )
|
||
|
{
|
||
|
if ( GetActiveWeapon() )
|
||
|
{
|
||
|
int iSnd = GetWeaponSoundFromString(options);
|
||
|
if ( iSnd != -1 )
|
||
|
{
|
||
|
GetActiveWeapon()->WeaponSound( (WeaponSound_t)iSnd );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( event == TF_AE_CIGARETTE_THROW )
|
||
|
{
|
||
|
CEffectData data;
|
||
|
int iAttach = LookupAttachment( options );
|
||
|
GetAttachment( iAttach, data.m_vOrigin, data.m_vAngles );
|
||
|
|
||
|
data.m_vAngles = GetRenderAngles();
|
||
|
|
||
|
data.m_hEntity = ClientEntityList().EntIndexToHandle( entindex() );
|
||
|
DispatchEffect( "TF_ThrowCigarette", data );
|
||
|
return;
|
||
|
}
|
||
|
else if ( event == AE_CL_BODYGROUP_SET_VALUE_CMODEL_WPN )
|
||
|
{
|
||
|
CTFWeaponBase *pWpn = GetActiveTFWeapon();
|
||
|
if ( pWpn )
|
||
|
{
|
||
|
pWpn->GetAppropriateWorldOrViewModel()->FireEvent( origin, angles, AE_CL_BODYGROUP_SET_VALUE, options );
|
||
|
}
|
||
|
}
|
||
|
else if ( event == AE_TAUNT_ENABLE_MOVE )
|
||
|
{
|
||
|
m_bAllowMoveDuringTaunt = true;
|
||
|
}
|
||
|
else if ( event == AE_TAUNT_DISABLE_MOVE )
|
||
|
{
|
||
|
m_bAllowMoveDuringTaunt = false;
|
||
|
}
|
||
|
else
|
||
|
BaseClass::FireEvent( origin, angles, event, options );
|
||
|
}
|
||
|
|
||
|
|
||
|
void C_TFPlayer::UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity )
|
||
|
{
|
||
|
// don't play footstep sound while in kart
|
||
|
if ( m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// don't play footstep sound while taunting
|
||
|
if ( IsTaunting() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
BaseClass::UpdateStepSound( psurface, vecOrigin, vecVelocity );
|
||
|
}
|
||
|
|
||
|
|
||
|
CNewParticleEffect *C_TFPlayer::SpawnHalloweenSpellFootsteps( ParticleAttachment_t eParticleAttachment, int iHalloweenFootstepType )
|
||
|
{
|
||
|
enum EPileOfHalloweenConstantHacks
|
||
|
{
|
||
|
kHalloweenSpell_RGBConstant_HHH = 2,
|
||
|
kHalloweenSpell_RGBConstant_TeamColor = 1,
|
||
|
kHalloweenSpell_RGB_Red = 12073019,
|
||
|
kHalloweenSpell_RGB_Blue = 5801378,
|
||
|
};
|
||
|
|
||
|
if ( iHalloweenFootstepType == kHalloweenSpell_RGBConstant_HHH )
|
||
|
return ParticleProp()->Create( "halloween_boss_foot_impact", eParticleAttachment, 0 );
|
||
|
|
||
|
CNewParticleEffect *pEffect = ParticleProp()->Create( m_nFootStamps ? "foot_stamp" : "halloween_boss_foot_impact_customcolor", eParticleAttachment, 0 );
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
const int iRGB = iHalloweenFootstepType != kHalloweenSpell_RGBConstant_TeamColor // special "use team-color" hack value
|
||
|
? iHalloweenFootstepType // use the attribute value as the RGB
|
||
|
: GetTeamNumber() == TF_TEAM_BLUE // which team are we on?
|
||
|
? kHalloweenSpell_RGB_Blue
|
||
|
: kHalloweenSpell_RGB_Red;
|
||
|
|
||
|
pEffect->SetControlPoint( 1, Vector( ((iRGB & 0xff0000) >> 16) / 255.0f, ((iRGB & 0xff00) >> 8) / 255.0f, (iRGB & 0xff) / 255.0f ) );
|
||
|
}
|
||
|
|
||
|
return pEffect;
|
||
|
}
|
||
|
|
||
|
// Shadows
|
||
|
|
||
|
ConVar cl_blobbyshadows( "cl_blobbyshadows", "0", FCVAR_CLIENTDLL );
|
||
|
ShadowType_t C_TFPlayer::ShadowCastType( void )
|
||
|
{
|
||
|
// Removed the GetPercentInvisible - should be taken care off in BindProxy now.
|
||
|
if ( !IsVisible() /*|| GetPercentInvisible() > 0.0f*/ )
|
||
|
return SHADOWS_NONE;
|
||
|
|
||
|
if ( IsEffectActive(EF_NODRAW | EF_NOSHADOW) )
|
||
|
return SHADOWS_NONE;
|
||
|
|
||
|
// If in ragdoll mode.
|
||
|
if ( m_nRenderFX == kRenderFxRagdoll )
|
||
|
return SHADOWS_NONE;
|
||
|
|
||
|
if ( !ShouldDrawThisPlayer() )
|
||
|
{
|
||
|
// First-person with viewmodels.
|
||
|
return SHADOWS_NONE;
|
||
|
}
|
||
|
|
||
|
if ( cl_blobbyshadows.GetBool() )
|
||
|
return SHADOWS_SIMPLE;
|
||
|
|
||
|
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
|
||
|
}
|
||
|
|
||
|
float g_flFattenAmt = 24.0; // Roughly how far out the Heavy's minigun pokes out.
|
||
|
void C_TFPlayer::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType )
|
||
|
{
|
||
|
if ( shadowType == SHADOWS_SIMPLE )
|
||
|
{
|
||
|
// Don't let the render bounds change when we're using blobby shadows, or else the shadow
|
||
|
// will pop and stretch.
|
||
|
mins = CollisionProp()->OBBMins();
|
||
|
maxs = CollisionProp()->OBBMaxs();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetRenderBounds( mins, maxs );
|
||
|
|
||
|
// We do this because the normal bbox calculations don't take pose params into account, and
|
||
|
// the rotation of the guy's upper torso can place his gun a ways out of his bbox, and
|
||
|
// the shadow will get cut off as he rotates.
|
||
|
//
|
||
|
// Thus, we give it some padding here.
|
||
|
g_flFattenAmt = 36.0f;
|
||
|
mins -= Vector( g_flFattenAmt, g_flFattenAmt, 0 );
|
||
|
maxs += Vector( g_flFattenAmt, g_flFattenAmt, 0 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void C_TFPlayer::GetRenderBounds( Vector& theMins, Vector& theMaxs )
|
||
|
{
|
||
|
// TODO POSTSHIP - this hack/fix goes hand-in-hand with a fix in CalcSequenceBoundingBoxes in utils/studiomdl/simplify.cpp.
|
||
|
// When we enable the fix in CalcSequenceBoundingBoxes, we can get rid of this.
|
||
|
//
|
||
|
// What we're doing right here is making sure it only uses the bbox for our lower-body sequences since,
|
||
|
// with the current animations and the bug in CalcSequenceBoundingBoxes, are WAY bigger than they need to be.
|
||
|
C_BaseAnimating::GetRenderBounds( theMins, theMaxs );
|
||
|
}
|
||
|
|
||
|
|
||
|
bool C_TFPlayer::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const
|
||
|
{
|
||
|
if ( shadowType == SHADOWS_SIMPLE )
|
||
|
{
|
||
|
// Blobby shadows should sit directly underneath us.
|
||
|
pDirection->Init( 0, 0, -1 );
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return BaseClass::GetShadowCastDirection( pDirection, shadowType );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Returns whether this player is the nemesis of the local player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsNemesisOfLocalPlayer()
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
// return whether this player is dominating the local player
|
||
|
return m_Shared.IsPlayerDominated( pLocalPlayer->entindex() );
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
extern ConVar tf_tournament_hide_domination_icons;
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Returns whether we should show the dueling icon for this player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldShowDuelingIcon()
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->IsInTournamentMode() && tf_tournament_hide_domination_icons.GetBool() )
|
||
|
return false;
|
||
|
|
||
|
if ( m_PlayerClass.HasCustomModel() )
|
||
|
return false;
|
||
|
|
||
|
extern bool DuelMiniGame_IsDuelingLocalPlayer( C_TFPlayer *pPlayer );
|
||
|
|
||
|
// we should show the dueling effect on this player if he is dueling the local player,
|
||
|
// and is not dead, cloaked or disguised
|
||
|
if ( DuelMiniGame_IsDuelingLocalPlayer( this ) && g_PR && g_PR->IsConnected( entindex() ) )
|
||
|
{
|
||
|
bool bStealthed = m_Shared.IsStealthed();
|
||
|
bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
|
||
|
if ( IsAlive() && !bStealthed && !bDisguised )
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Returns whether we should show the nemesis icon for this player
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldShowNemesisIcon()
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->IsInTournamentMode() && tf_tournament_hide_domination_icons.GetBool() )
|
||
|
return false;
|
||
|
|
||
|
if ( m_PlayerClass.HasCustomModel() )
|
||
|
return false;
|
||
|
|
||
|
// we should show the nemesis effect on this player if he is the nemesis of the local player,
|
||
|
// and is not dead, cloaked or disguised
|
||
|
if ( IsNemesisOfLocalPlayer() && g_PR && g_PR->IsConnected( entindex() ) )
|
||
|
{
|
||
|
bool bStealthed = m_Shared.IsStealthed();
|
||
|
bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
|
||
|
if ( IsAlive() && !bStealthed && !bDisguised )
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::IsWeaponLowered( void )
|
||
|
{
|
||
|
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
|
||
|
|
||
|
if ( !pWeapon )
|
||
|
return false;
|
||
|
|
||
|
CTFGameRules *pRules = TFGameRules();
|
||
|
|
||
|
// Lower losing team's weapons in bonus round
|
||
|
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
|
||
|
return true;
|
||
|
|
||
|
// Hide all view models after the game is over
|
||
|
if ( pRules->State_Get() == GR_STATE_GAME_OVER && ( !pRules->IsInTournamentMode() || pRules->IsMannVsMachineMode() ) )
|
||
|
return true;
|
||
|
|
||
|
if ( m_Shared.InCond( TF_COND_PHASE ) )
|
||
|
return true;
|
||
|
|
||
|
if ( m_Shared.InCond( TF_COND_COMPETITIVE_LOSER ) )
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
|
||
|
{
|
||
|
switch ( event->GetType() )
|
||
|
{
|
||
|
case CChoreoEvent::SEQUENCE:
|
||
|
case CChoreoEvent::GESTURE:
|
||
|
return StartGestureSceneEvent( info, scene, event, actor, pTarget );
|
||
|
default:
|
||
|
return BaseClass::StartSceneEvent( info, scene, event, actor, pTarget );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled )
|
||
|
{
|
||
|
switch ( info->m_pEvent->GetType() )
|
||
|
{
|
||
|
case CChoreoEvent::SEQUENCE:
|
||
|
case CChoreoEvent::GESTURE:
|
||
|
return StopGestureSceneEvent( info, fastKill, canceled );
|
||
|
default:
|
||
|
return BaseClass::ClearSceneEvent( info, fastKill, canceled );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::StartGestureSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
|
||
|
{
|
||
|
// Get the (gesture) sequence.
|
||
|
info->m_nSequence = LookupSequence( event->GetParameters() );
|
||
|
if ( info->m_nSequence < 0 )
|
||
|
return false;
|
||
|
|
||
|
// Player the (gesture) sequence.
|
||
|
float flCycle = 0.0f;
|
||
|
bool looping = ((GetSequenceFlags( GetModelPtr(), info->m_nSequence ) & STUDIO_LOOPING) != 0);
|
||
|
if (!looping)
|
||
|
{
|
||
|
float dt = scene->GetTime() - event->GetStartTime();
|
||
|
m_flTauntDuration = SequenceDuration( info->m_nSequence );
|
||
|
flCycle = dt / m_flTauntDuration;
|
||
|
flCycle = clamp( flCycle, 0.f, 1.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float flStart, flEnd;
|
||
|
scene->GetSceneTimes( flStart, flEnd );
|
||
|
m_flTauntDuration = flEnd - flStart;
|
||
|
}
|
||
|
|
||
|
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
|
||
|
m_PlayerAnimState->AddVCDSequenceToGestureSlot( GESTURE_SLOT_VCD, info->m_nSequence, flCycle, true );
|
||
|
|
||
|
m_nTauntSequence = info->m_nSequence;
|
||
|
m_flTauntStartTime = gpGlobals->curtime;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::StopGestureSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled )
|
||
|
{
|
||
|
// The ResetGestureSlot call will prevent people from doing running taunts (which they like to do),
|
||
|
// so let's only reset the gesture slot if the scene contains a loop (such as the high five pose).
|
||
|
bool bSceneContainsLoop = false;
|
||
|
for ( int i = 0; i < info->m_pScene->GetNumEvents(); i++ )
|
||
|
{
|
||
|
CChoreoEvent *pEvent = info->m_pScene->GetEvent( i );
|
||
|
if ( pEvent->GetType() == CChoreoEvent::LOOP )
|
||
|
{
|
||
|
bSceneContainsLoop = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( bSceneContainsLoop )
|
||
|
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool C_TFPlayer::IsAllowedToSwitchWeapons( void )
|
||
|
{
|
||
|
if ( IsWeaponLowered() == true )
|
||
|
return false;
|
||
|
|
||
|
if ( TFGameRules() )
|
||
|
{
|
||
|
if ( TFGameRules()->IsPasstimeMode() && m_Shared.HasPasstimeBall() )
|
||
|
return false;
|
||
|
|
||
|
if ( TFGameRules()->ShowMatchSummary() )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Can't weapon switch during a taunt.
|
||
|
if( m_Shared.InCond( TF_COND_TAUNTING ) && tf_allow_taunt_switch.GetInt() <= 1 )
|
||
|
return false;
|
||
|
|
||
|
return BaseClass::IsAllowedToSwitchWeapons();
|
||
|
}
|
||
|
|
||
|
IMaterial *C_TFPlayer::GetHeadLabelMaterial( void )
|
||
|
{
|
||
|
if ( g_pHeadLabelMaterial[0] == NULL )
|
||
|
SetupHeadLabelMaterials();
|
||
|
|
||
|
if ( GetTeamNumber() == TF_TEAM_RED )
|
||
|
{
|
||
|
return g_pHeadLabelMaterial[TF_PLAYER_HEAD_LABEL_RED];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return g_pHeadLabelMaterial[TF_PLAYER_HEAD_LABEL_BLUE];
|
||
|
}
|
||
|
|
||
|
return BaseClass::GetHeadLabelMaterial();
|
||
|
}
|
||
|
|
||
|
void SetupHeadLabelMaterials( void )
|
||
|
{
|
||
|
for ( int i = 0; i < 2; i++ )
|
||
|
{
|
||
|
if ( g_pHeadLabelMaterial[i] )
|
||
|
{
|
||
|
g_pHeadLabelMaterial[i]->DecrementReferenceCount();
|
||
|
g_pHeadLabelMaterial[i] = NULL;
|
||
|
}
|
||
|
|
||
|
g_pHeadLabelMaterial[i] = materials->FindMaterial( pszHeadLabelNames[i], TEXTURE_GROUP_VGUI );
|
||
|
if ( g_pHeadLabelMaterial[i] )
|
||
|
{
|
||
|
g_pHeadLabelMaterial[i]->IncrementReferenceCount();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::ComputeFxBlend( void )
|
||
|
{
|
||
|
BaseClass::ComputeFxBlend();
|
||
|
|
||
|
float flInvisible = GetPercentInvisible();
|
||
|
if ( flInvisible != 0.0f )
|
||
|
{
|
||
|
// Tell our shadow
|
||
|
ClientShadowHandle_t hShadow = GetShadowHandle();
|
||
|
if ( hShadow != CLIENTSHADOW_INVALID_HANDLE )
|
||
|
{
|
||
|
g_pClientShadowMgr->SetFalloffBias( hShadow, flInvisible * 255 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNear, float &zFar, float &fov )
|
||
|
{
|
||
|
HandleTaunting();
|
||
|
BaseClass::CalcView( eyeOrigin, eyeAngles, zNear, zFar, fov );
|
||
|
}
|
||
|
|
||
|
void SelectDisguise( int iClass, int iTeam );
|
||
|
|
||
|
static void cc_tf_player_lastdisguise( const CCommand &args )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
if ( pPlayer == NULL )
|
||
|
return;
|
||
|
|
||
|
// disguise as our last known disguise. desired disguise will be initted to something sensible
|
||
|
if ( pPlayer->CanDisguise() || pPlayer->CanDisguise_OnKill() )
|
||
|
{
|
||
|
// disguise as the previous class, if one exists
|
||
|
int nClass = pPlayer->m_Shared.GetDesiredDisguiseClass();
|
||
|
|
||
|
int iLocalTeam = pPlayer->GetTeamNumber();
|
||
|
int iEnemyTeam = ( iLocalTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
|
||
|
int nTeam = pPlayer->m_Shared.WasLastDisguiseAsOwnTeam() ? iLocalTeam : iEnemyTeam;
|
||
|
|
||
|
//If we pass in "random" or whatever then just make it pick a random class.
|
||
|
if ( args.ArgC() > 1 )
|
||
|
{
|
||
|
nClass = TF_CLASS_UNDEFINED;
|
||
|
}
|
||
|
|
||
|
if ( nClass == TF_CLASS_UNDEFINED )
|
||
|
{
|
||
|
// they haven't disguised yet, pick a nice one for them.
|
||
|
// exclude some undesirable classes
|
||
|
do
|
||
|
{
|
||
|
nClass = random->RandomInt( TF_FIRST_NORMAL_CLASS, TF_LAST_NORMAL_CLASS );
|
||
|
} while( nClass == TF_CLASS_SCOUT || nClass == TF_CLASS_SPY );
|
||
|
|
||
|
nTeam = iEnemyTeam;
|
||
|
}
|
||
|
|
||
|
SelectDisguise( nClass, nTeam );
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
static ConCommand lastdisguise( "lastdisguise", cc_tf_player_lastdisguise, "Disguises the spy as the last disguise." );
|
||
|
|
||
|
|
||
|
static void cc_tf_player_disguise( const CCommand &args )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
if ( pPlayer == NULL )
|
||
|
return;
|
||
|
|
||
|
if ( args.ArgC() >= 3 )
|
||
|
{
|
||
|
if ( pPlayer->CanDisguise() || pPlayer->CanDisguise_OnKill() )
|
||
|
{
|
||
|
int nClass = atoi( args[ 1 ] );
|
||
|
int nTeam = atoi( args[ 2 ] );
|
||
|
|
||
|
//Disguise as enemy team
|
||
|
if ( nTeam == -1 )
|
||
|
{
|
||
|
if ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
|
||
|
{
|
||
|
nTeam = TF_TEAM_RED;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nTeam = TF_TEAM_BLUE;
|
||
|
}
|
||
|
}
|
||
|
else if ( nTeam == -2 ) //disguise as my own team
|
||
|
{
|
||
|
nTeam = pPlayer->GetTeamNumber();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nTeam = ( nTeam == 1 ) ? TF_TEAM_BLUE : TF_TEAM_RED;
|
||
|
}
|
||
|
|
||
|
// intercepting the team value and reassigning what gets passed into Disguise()
|
||
|
// because the team numbers in the client menu don't match the #define values for the teams
|
||
|
SelectDisguise( nClass, nTeam );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static ConCommand disguise( "disguise", cc_tf_player_disguise, "Disguises the spy." );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void cc_tf_crashclient()
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = NULL;
|
||
|
pPlayer->ComputeFxBlend();
|
||
|
}
|
||
|
static ConCommand tf_crashclient( "tf_crashclient", cc_tf_crashclient, "Crashes this client for testing.", FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ForceUpdateObjectHudState( void )
|
||
|
{
|
||
|
m_bUpdateObjectHudState = true;
|
||
|
}
|
||
|
|
||
|
#include "c_obj_sentrygun.h"
|
||
|
|
||
|
|
||
|
static void cc_tf_debugsentrydmg()
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
pPlayer->UpdateIDTarget();
|
||
|
int iTarget = pPlayer->GetIDTarget();
|
||
|
if ( iTarget > 0 )
|
||
|
{
|
||
|
C_BaseEntity *pEnt = cl_entitylist->GetEnt( iTarget );
|
||
|
|
||
|
C_ObjectSentrygun *pSentry = dynamic_cast< C_ObjectSentrygun * >( pEnt );
|
||
|
|
||
|
if ( pSentry )
|
||
|
{
|
||
|
pSentry->DebugDamageParticles();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static ConCommand tf_debugsentrydamage( "tf_debugsentrydamage", cc_tf_debugsentrydmg, "", FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
/*
|
||
|
CON_COMMAND_F( spectate_random_server_extend_time, "extend the timer we're spectating this server before we disconnect", FCVAR_DEVELOPMENTONLY )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
if ( pPlayer->m_LeaveServerTimer.HasStarted() )
|
||
|
{
|
||
|
float flTime = spectate_random_server_basetime.GetFloat();
|
||
|
|
||
|
if ( args.ArgC() > 1 )
|
||
|
{
|
||
|
flTime = MAX( 0, Q_atof( args[ 1 ] ) );
|
||
|
}
|
||
|
|
||
|
pPlayer->m_LeaveServerTimer.Start( flTime );
|
||
|
}
|
||
|
}
|
||
|
}*/
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::GetTargetIDDataString( bool bIsDisguised, OUT_Z_BYTECAP(iMaxLenInBytes) wchar_t *sDataString, int iMaxLenInBytes, bool &bIsAmmoData, bool &bIsKillStreakData )
|
||
|
{
|
||
|
Assert( iMaxLenInBytes >= sizeof(sDataString[0]) );
|
||
|
// Make sure the output string is always initialized to a null-terminated string,
|
||
|
// since the conditions below are tricky.
|
||
|
sDataString[0] = 0;
|
||
|
|
||
|
if ( bIsDisguised )
|
||
|
{
|
||
|
if ( !IsEnemyPlayer() )
|
||
|
{
|
||
|
// The target is a disguised friendly spy. They appear to the player with no disguise. Add the disguise
|
||
|
// team & class to the target ID element.
|
||
|
bool bDisguisedAsEnemy = ( m_Shared.GetDisguiseTeam() != GetTeamNumber() );
|
||
|
const wchar_t *wszAlignment = g_pVGuiLocalize->Find( bDisguisedAsEnemy ? "#TF_enemy" : "#TF_friendly" );
|
||
|
|
||
|
int classindex = m_Shared.GetDisguiseClass();
|
||
|
const wchar_t *wszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[classindex] );
|
||
|
|
||
|
// build a string with disguise information
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_friendlyspy_disguise" ),
|
||
|
2, wszAlignment, wszClassName );
|
||
|
}
|
||
|
else if ( IsEnemyPlayer() && (m_Shared.GetDisguiseClass() == TF_CLASS_SPY) )
|
||
|
{
|
||
|
// The target is an enemy spy disguised as a friendly spy. Show a fake team & class ID element.
|
||
|
int classindex = m_Shared.GetDisguiseMask();
|
||
|
const wchar_t *wszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[classindex] );
|
||
|
const wchar_t *wszAlignment = g_pVGuiLocalize->Find( "#TF_enemy" );
|
||
|
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_friendlyspy_disguise" ),
|
||
|
2, wszAlignment, wszClassName );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( IsPlayerClass( TF_CLASS_MEDIC ) )
|
||
|
{
|
||
|
CTFWeaponBase *pMedigun = NULL;
|
||
|
|
||
|
// Medics put their ubercharge & medigun type into the data string
|
||
|
wchar_t wszChargeLevel[ 10 ];
|
||
|
_snwprintf( wszChargeLevel, ARRAYSIZE(wszChargeLevel) - 1, L"%.0f", MedicGetChargeLevel( &pMedigun ) * 100 );
|
||
|
wszChargeLevel[ ARRAYSIZE(wszChargeLevel)-1 ] = '\0';
|
||
|
|
||
|
if ( pMedigun && pMedigun->GetAttributeContainer()->GetItem() && pMedigun->GetAttributeContainer()->GetItem()->GetItemQuality() != AE_NORMAL )
|
||
|
{
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_mediccharge_wpn" ), 2, wszChargeLevel, pMedigun->GetAttributeContainer()->GetItem()->GetItemName() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_mediccharge" ), 1, wszChargeLevel );
|
||
|
}
|
||
|
}
|
||
|
else if ( bIsDisguised && (m_Shared.GetDisguiseClass() == TF_CLASS_MEDIC) && IsEnemyPlayer() )
|
||
|
{
|
||
|
// Show a fake charge level for a disguised enemy medic.
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_mediccharge" ), 1, L"0" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
|
||
|
{
|
||
|
CTFWeaponBase *pTFWeapon = GetActiveTFWeapon();
|
||
|
if ( pTFWeapon )
|
||
|
{
|
||
|
// Check for weapon_blocks_healing
|
||
|
int iBlockHealing = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iBlockHealing, weapon_blocks_healing );
|
||
|
if ( iBlockHealing )
|
||
|
{
|
||
|
if ( pTFWeapon->GetAttributeContainer() && pTFWeapon->GetAttributeContainer()->GetItem() )
|
||
|
{
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_noheal" ), 1, pTFWeapon->GetAttributeContainer()->GetItem()->GetItemName() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_noheal_unknown" ), 0 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Show target's clip state to attached medics
|
||
|
if ( !sDataString[0] && m_nActiveWpnClip >= 0 )
|
||
|
{
|
||
|
C_TFPlayer *pTFHealTarget = ToTFPlayer( pLocalPlayer->MedicGetHealTarget() );
|
||
|
if ( pTFHealTarget && pTFHealTarget == this )
|
||
|
{
|
||
|
wchar_t wszClip[10];
|
||
|
V_snwprintf( wszClip, ARRAYSIZE(wszClip) - 1, L"%d", m_nActiveWpnClip );
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_ammo" ), 1, wszClip );
|
||
|
bIsAmmoData = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !bIsAmmoData )
|
||
|
{
|
||
|
// Check for kill streak data
|
||
|
if ( m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) > 0 )
|
||
|
{
|
||
|
bIsKillStreakData = true;
|
||
|
wchar_t wszClip[10];
|
||
|
V_snwprintf( wszClip, ARRAYSIZE(wszClip) - 1, L"%d", m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
|
||
|
g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find( "#TF_playerid_ammo" ), 1, wszClip );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::ThirdPersonSwitch( bool bThirdperson )
|
||
|
{
|
||
|
BaseClass::ThirdPersonSwitch( bThirdperson );
|
||
|
|
||
|
// Update our viewmodel whenever we switch view modes
|
||
|
C_TFPlayer *pTFObserverTarget = ToTFPlayer( GetObserverTarget() );
|
||
|
if ( pTFObserverTarget )
|
||
|
{
|
||
|
C_TFWeaponBase *pWeapon = pTFObserverTarget->m_Shared.GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateAttachmentModels();
|
||
|
}
|
||
|
|
||
|
pTFObserverTarget->UpdateWearables();
|
||
|
pTFObserverTarget->SetBodygroupsDirty();
|
||
|
}
|
||
|
|
||
|
// Update our weapon's visibility when we switch
|
||
|
C_BaseCombatWeapon* pWeapon = GetActiveWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateVisibility();
|
||
|
}
|
||
|
|
||
|
// Update visibility of any worn items.
|
||
|
UpdateWearables();
|
||
|
SetBodygroupsDirty();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Update our active weapon's extra wearable's visibility and shadows
|
||
|
// as well as all our wearables.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateWearables()
|
||
|
{
|
||
|
BaseClass::UpdateWearables();
|
||
|
|
||
|
CTFWeaponBase* pWeapon = dynamic_cast< CTFWeaponBase* >( GetActiveWeapon() );
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateVisibility();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::OnAchievementAchieved( int iAchievement )
|
||
|
{
|
||
|
EmitSound( "Achievement.Earned" );
|
||
|
|
||
|
BaseClass::OnAchievementAchieved( iAchievement );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Feign Death
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::FeignDeath( CTakeDamageInfo& info )
|
||
|
{
|
||
|
// Can't feign death if we're actually dead or if we're not a spy.
|
||
|
if ( !IsAlive() || !IsPlayerClass( TF_CLASS_SPY) )
|
||
|
return;
|
||
|
|
||
|
// Can't feign death if we're already stealthed.
|
||
|
if ( m_Shared.IsStealthed() )
|
||
|
return;
|
||
|
|
||
|
// Can't feign death if we aren't at full cloak energy.
|
||
|
if ( !CanGoInvisible() || ( m_Shared.GetSpyCloakMeter() < 100.0f ) )
|
||
|
return;
|
||
|
|
||
|
// Predict feign death condition effects.
|
||
|
m_Shared.AddCond( TF_COND_FEIGN_DEATH );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateSpyStateChange( void )
|
||
|
{
|
||
|
UpdateOverhealEffect();
|
||
|
UpdateRecentlyTeleportedEffect();
|
||
|
UpdatedMarkedForDeathEffect();
|
||
|
|
||
|
// Update our resist shield color
|
||
|
if ( m_pTempShield && m_Shared.GetCarryingRuneType() == RUNE_RESIST )
|
||
|
{
|
||
|
m_pTempShield->m_nSkin = ( m_Shared.GetDisplayedTeam() == TF_TEAM_RED ) ? 0 : 1;
|
||
|
}
|
||
|
|
||
|
UpdateRuneIcon( true );
|
||
|
|
||
|
// Remove Speed lines if Stealthed
|
||
|
if ( m_Shared.IsStealthed() )
|
||
|
{
|
||
|
if ( m_pSpeedBoostEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pSpeedBoostEffect );
|
||
|
m_pSpeedBoostEffect = NULL;
|
||
|
}
|
||
|
|
||
|
m_Shared.EndRadiusHealEffect();
|
||
|
}
|
||
|
|
||
|
// Force Weapon updates
|
||
|
if ( GetActiveWeapon() )
|
||
|
{
|
||
|
GetActiveWeapon()->PreDataUpdate( DATA_UPDATE_DATATABLE_CHANGED );
|
||
|
}
|
||
|
|
||
|
// TranqMark
|
||
|
// test : Let Marked spies be seen via the debuff
|
||
|
//bool bShow = ( m_Shared.InCond( TF_COND_TRANQ_MARKED )
|
||
|
// && !m_Shared.IsStealthed();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateOverhealEffect( void )
|
||
|
{
|
||
|
bool bShow = m_Shared.InCond( TF_COND_HEALTH_OVERHEALED );
|
||
|
int iTeam = GetTeamNumber();
|
||
|
|
||
|
if ( IsLocalPlayer() || ( m_Shared.IsStealthed() && !InSameTeam( GetLocalTFPlayer() ) ) )
|
||
|
{
|
||
|
bShow = false;
|
||
|
}
|
||
|
else if ( IsPlayerClass( TF_CLASS_SPY ) && !InSameTeam( GetLocalTFPlayer() ) && m_Shared.InCond( TF_COND_DISGUISED ))
|
||
|
{
|
||
|
iTeam = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
|
||
|
if ( bShow )
|
||
|
{
|
||
|
if ( !m_pOverHealedEffect )
|
||
|
{
|
||
|
CreateOverhealEffect( iTeam );
|
||
|
}
|
||
|
else if ( m_pOverHealedEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pOverHealedEffect );
|
||
|
m_pOverHealedEffect = NULL;
|
||
|
CreateOverhealEffect( iTeam );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( m_pOverHealedEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pOverHealedEffect );
|
||
|
m_pOverHealedEffect = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::CreateOverhealEffect( int iTeam )
|
||
|
{
|
||
|
const char *pEffect = NULL;
|
||
|
switch( iTeam )
|
||
|
{
|
||
|
case TF_TEAM_BLUE:
|
||
|
pEffect = "overhealedplayer_blue_pluses";
|
||
|
break;
|
||
|
case TF_TEAM_RED:
|
||
|
pEffect = "overhealedplayer_red_pluses";
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
m_pOverHealedEffect = ParticleProp()->Create( pEffect, PATTACH_ABSORIGIN_FOLLOW );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::SetMetersRan( float fMeters, int iFrame )
|
||
|
{
|
||
|
if ( iFrame != m_iLastRanFrame )
|
||
|
{
|
||
|
m_iLastRanFrame = iFrame;
|
||
|
m_fMetersRan = fMeters;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::InSameDisguisedTeam( CBaseEntity *pEnt )
|
||
|
{
|
||
|
if ( pEnt == NULL )
|
||
|
return false;
|
||
|
|
||
|
int iMyApparentTeam = GetTeamNumber();
|
||
|
|
||
|
if ( m_bIsCoaching && m_hStudent )
|
||
|
{
|
||
|
iMyApparentTeam = m_hStudent->GetTeamNumber();
|
||
|
}
|
||
|
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
iMyApparentTeam = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
|
||
|
C_TFPlayer *pPlayerEnt = ToTFPlayer( pEnt );
|
||
|
int iTheirApparentTeam = pEnt->GetTeamNumber();
|
||
|
if ( pPlayerEnt && pPlayerEnt->m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
iTheirApparentTeam = pPlayerEnt->m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
|
||
|
return ( iMyApparentTeam == iTheirApparentTeam || GetTeamNumber() == pEnt->GetTeamNumber() || iTheirApparentTeam == GetTeamNumber() );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: When a player's ragdoll is created we want the ragdoll to
|
||
|
// appear to have any attached wearable items the player had.
|
||
|
// This uses client side entities instead of the actual wearable items.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static bool IsDecapitationCustomDamageType( int iCustomDamageType )
|
||
|
{
|
||
|
return iCustomDamageType == TF_DMG_CUSTOM_DECAPITATION
|
||
|
|| iCustomDamageType == TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING
|
||
|
|| iCustomDamageType == TF_DMG_CUSTOM_DECAPITATION_BOSS
|
||
|
|| iCustomDamageType == TF_DMG_CUSTOM_MERASMUS_DECAPITATION;
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::CreateBoneAttachmentsFromWearables( C_TFRagdoll *pRagdoll, bool bDisguised )
|
||
|
{
|
||
|
if ( bDisguised && !ShouldDrawSpyAsDisguised() )
|
||
|
{
|
||
|
// the team of disguised spy don't see any wearable
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for ( int wbl = GetNumWearables()-1; wbl >= 0; wbl-- )
|
||
|
{
|
||
|
C_TFWearable *pItem = dynamic_cast<C_TFWearable*> (GetWearable(wbl));
|
||
|
if ( !pItem )
|
||
|
continue;
|
||
|
|
||
|
if ( pItem->IsViewModelWearable() )
|
||
|
continue;
|
||
|
|
||
|
if ( pItem->IsDisguiseWearable() && !bDisguised )
|
||
|
continue;
|
||
|
|
||
|
if ( !pItem->IsDisguiseWearable() && bDisguised )
|
||
|
continue;
|
||
|
|
||
|
pItem->OnWearerDeath();
|
||
|
|
||
|
if ( pItem->GetDropType() >= ITEM_DROP_TYPE_DROP )
|
||
|
continue;
|
||
|
|
||
|
CAttributeContainer *pCont = pItem->GetAttributeContainer();
|
||
|
CEconItemView *pEconItemView = pCont ? pCont->GetItem() : NULL;
|
||
|
|
||
|
// If this is a decapitated ragdoll, don't attach anything in our head/misc item slots.
|
||
|
if ( IsDecapitationCustomDamageType( pRagdoll->GetDamageCustom() ) )
|
||
|
{
|
||
|
int iLoadoutSlot = pEconItemView
|
||
|
? pEconItemView->GetStaticData()->GetDefaultLoadoutSlot()
|
||
|
: LOADOUT_POSITION_INVALID;
|
||
|
|
||
|
if ( iLoadoutSlot == LOADOUT_POSITION_HEAD || iLoadoutSlot == LOADOUT_POSITION_MISC )
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
C_EconWearableGib *pAttachment = new C_EconWearableGib;
|
||
|
if ( !pAttachment )
|
||
|
return;
|
||
|
|
||
|
const char *pszModelName = modelinfo->GetModelName( pItem->GetModel() );
|
||
|
if ( !pszModelName || pszModelName[ 0 ] == '\0' || pszModelName[ 0 ] == '?' )
|
||
|
continue;
|
||
|
|
||
|
// We need to set the item now, so that it can pull data out during Initialize();
|
||
|
if ( pEconItemView )
|
||
|
{
|
||
|
Assert( pAttachment->GetAttributeContainer() );
|
||
|
pAttachment->GetAttributeContainer()->SetItem( pEconItemView );
|
||
|
}
|
||
|
|
||
|
pAttachment->SetModelName( AllocPooledString( pszModelName ) );
|
||
|
if ( !pAttachment->Initialize( true ) )
|
||
|
{
|
||
|
pAttachment->Release();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
pAttachment->m_iTeamNum = pRagdoll->GetRagdollTeam();
|
||
|
pAttachment->m_nSkin = pItem->GetSkin();
|
||
|
pAttachment->AttachEntityToBone( this, -1, Vector(0,0,0), QAngle(0,0,0) );
|
||
|
|
||
|
if ( pEconItemView )
|
||
|
{
|
||
|
if ( pEconItemView->GetStaticData()->UsesPerClassBodygroups( GetTeamNumber() ) )
|
||
|
{
|
||
|
// Classes start at 1, bodygroups at 0, so we shift them all back 1.
|
||
|
pAttachment->SetBodygroup( 1, (pRagdoll->GetClass()-1) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Loop through all non-standard items carried by this player, and pick the next one.
|
||
|
// pLastItem - pointer to the int that stores the last item found, for iteration purposes.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CEconItemView *C_TFPlayer::GetInspectItem( int *pLastItem )
|
||
|
{
|
||
|
int iItemsFound = 0;
|
||
|
CEconItemView *pFirstItem = NULL;
|
||
|
int nCount = WeaponCount();
|
||
|
for ( int i = 0; i < nCount; ++i )
|
||
|
{
|
||
|
C_BaseCombatWeapon *pWeapon = GetWeapon(i);
|
||
|
if ( !pWeapon )
|
||
|
continue;
|
||
|
|
||
|
CEconItemView *pTmp = pWeapon->GetAttributeContainer()->GetItem();
|
||
|
if ( !pTmp->IsValid() )
|
||
|
continue;
|
||
|
|
||
|
// don't show hidden items in the inspect panel
|
||
|
if ( pTmp->GetItemDefinition() && pTmp->GetItemDefinition()->IsHidden() )
|
||
|
continue;
|
||
|
|
||
|
if ( !pFirstItem )
|
||
|
{
|
||
|
pFirstItem = pTmp;
|
||
|
}
|
||
|
|
||
|
iItemsFound++;
|
||
|
if ( iItemsFound <= *pLastItem )
|
||
|
continue;
|
||
|
|
||
|
// Found the next item, we're done.
|
||
|
*pLastItem = iItemsFound;
|
||
|
return pTmp;
|
||
|
}
|
||
|
|
||
|
// Check wearables too
|
||
|
nCount = GetNumWearables();
|
||
|
for ( int i = 0; i < nCount; ++i )
|
||
|
{
|
||
|
C_EconWearable *pWearable = GetWearable(i);
|
||
|
if ( pWearable )
|
||
|
{
|
||
|
CEconItemView *pTmp = pWearable->GetAttributeContainer()->GetItem();
|
||
|
if ( !pTmp->IsValid() )
|
||
|
continue;
|
||
|
|
||
|
if ( !pFirstItem )
|
||
|
{
|
||
|
pFirstItem = pTmp;
|
||
|
}
|
||
|
|
||
|
iItemsFound++;
|
||
|
if ( iItemsFound <= *pLastItem )
|
||
|
continue;
|
||
|
|
||
|
// Found the next item, we're done.
|
||
|
*pLastItem = iItemsFound;
|
||
|
return pTmp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we didn't find an item, go back to the first one
|
||
|
if ( pFirstItem )
|
||
|
{
|
||
|
*pLastItem = 1;
|
||
|
return pFirstItem;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::CanUseFirstPersonCommand( void )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
if ( pLocalPlayer->m_Shared.InCond( TF_COND_PHASE ) ||
|
||
|
pLocalPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ||
|
||
|
pLocalPlayer->m_Shared.IsControlStunned() ||
|
||
|
pLocalPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return BaseClass::CanUseFirstPersonCommand();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateDemomanEyeEffect( int iDecapitations )
|
||
|
{
|
||
|
if ( m_pEyeEffect )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pEyeEffect );
|
||
|
m_pEyeEffect = NULL;
|
||
|
}
|
||
|
|
||
|
if ( iDecapitations == 0 )
|
||
|
return;
|
||
|
|
||
|
iDecapitations = MIN( iDecapitations, MAX_DECAPITATIONS );
|
||
|
const char* pszEffect = GetDemomanEyeEffectName( iDecapitations );
|
||
|
|
||
|
if ( pszEffect )
|
||
|
{
|
||
|
m_pEyeEffect = ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, "eyeglow_L" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const char *C_TFPlayer::GetDemomanEyeEffectName( int iDecapitations )
|
||
|
{
|
||
|
if ( iDecapitations < 1 )
|
||
|
return NULL;
|
||
|
|
||
|
switch ( iDecapitations )
|
||
|
{
|
||
|
case 1:
|
||
|
return "eye_powerup_green_lvl_1";
|
||
|
case 2:
|
||
|
return "eye_powerup_green_lvl_2";
|
||
|
case 3:
|
||
|
return "eye_powerup_green_lvl_3";
|
||
|
default:
|
||
|
return "eye_powerup_green_lvl_4";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void GetVectorColorForParticleSystem ( int iSystem, bool bIsBlueTeam, bool bUseColor2, Vector &vecColor )
|
||
|
{
|
||
|
if ( iSystem < 0 || iSystem >= ARRAYSIZE( g_KillStreakEffectsBase ) )
|
||
|
return;
|
||
|
|
||
|
killstreak_params_t params = g_KillStreakEffectsBase[iSystem];
|
||
|
|
||
|
if ( bIsBlueTeam && g_KillStreakEffectsBase[iSystem].m_bHasTeamColor )
|
||
|
{
|
||
|
Assert( iSystem > 0 && iSystem < ARRAYSIZE( g_KillStreakEffectsBlue ) );
|
||
|
params = g_KillStreakEffectsBlue[iSystem];
|
||
|
}
|
||
|
|
||
|
if ( bUseColor2 )
|
||
|
{
|
||
|
vecColor = Vector( params.m_color2_r, params.m_color2_g, params.m_color2_b );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vecColor = Vector( params.m_color1_r, params.m_color1_g, params.m_color1_b );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateKillStreakEffects( int iCount, bool bKillScored /* = false */ )
|
||
|
{
|
||
|
#ifdef STAGING_ONLY
|
||
|
const int LOW_GLOW = 2001;
|
||
|
#endif
|
||
|
const int HIGH_GLOW = 20000;
|
||
|
|
||
|
// Staging only hack to test eye glows on players
|
||
|
if ( m_pEyeGlowEffect[0] )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pEyeGlowEffect[ 0 ] );
|
||
|
m_pEyeGlowEffect[ 0 ] = NULL;
|
||
|
}
|
||
|
if ( m_pEyeGlowEffect[1] )
|
||
|
{
|
||
|
ParticleProp()->StopEmission( m_pEyeGlowEffect[ 1 ] );
|
||
|
m_pEyeGlowEffect[ 1 ] = NULL;
|
||
|
}
|
||
|
|
||
|
CTFWeaponBase *pActiveWeapon = GetActiveTFWeapon();
|
||
|
if ( !pActiveWeapon )
|
||
|
return;
|
||
|
|
||
|
// Disguised Spies, Use targets kill streak. Otherwise nothing
|
||
|
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
|
||
|
{
|
||
|
C_TFPlayer *pDisguiseTarget = ToTFPlayer( m_Shared.GetDisguiseTarget() );
|
||
|
if ( pDisguiseTarget )
|
||
|
iCount = pDisguiseTarget->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills );
|
||
|
else
|
||
|
iCount = 0;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
// !TEST!
|
||
|
if ( tf_test_setkillcount.GetInt() > 0 )
|
||
|
{
|
||
|
iCount = tf_test_setkillcount.GetInt();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Check if they have the appropriate attribute.
|
||
|
int iKillStreakEffectIndex = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iKillStreakEffectIndex, killstreak_effect );
|
||
|
|
||
|
int iKillStreakColorIndex = 0;
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pActiveWeapon, iKillStreakColorIndex, killstreak_idleeffect );
|
||
|
|
||
|
// !TEST!
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_killstreak_eyeglow.GetInt() != 0 )
|
||
|
{
|
||
|
iKillStreakEffectIndex = tf_killstreak_eyeglow.GetInt();
|
||
|
}
|
||
|
if ( tf_killstreak_color.GetInt() != 0 )
|
||
|
{
|
||
|
iKillStreakColorIndex = tf_killstreak_color.GetInt();
|
||
|
}
|
||
|
#endif // staging_only
|
||
|
|
||
|
// Need a color and eyeeffect to continue, otherwise you only have a sheen at best
|
||
|
if ( iKillStreakColorIndex == 0 || iKillStreakEffectIndex == 0 )
|
||
|
{
|
||
|
// Just in case search weapon wearables for killstreak effects
|
||
|
// This only applies for soldier (mantreads) and the demoshields
|
||
|
if ( IsPlayerClass( TF_CLASS_SOLDIER ) || IsPlayerClass( TF_CLASS_DEMOMAN ) )
|
||
|
{
|
||
|
// Iterate over all of our wearables
|
||
|
for ( int i = 0; i < GetNumWearables(); ++i )
|
||
|
{
|
||
|
CEconWearable *pWearable = GetWearable( i );
|
||
|
if ( pWearable && pWearable->GetAttributeContainer( )->GetItem( )->GetEquippedPositionForClass( GetPlayerClass( )->GetClassIndex( ) ) == LOADOUT_POSITION_SECONDARY )
|
||
|
{
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearable, iKillStreakEffectIndex, killstreak_effect );
|
||
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearable, iKillStreakColorIndex, killstreak_idleeffect );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( iKillStreakColorIndex == 0 || iKillStreakEffectIndex == 0 )
|
||
|
{
|
||
|
m_pszEyeGlowEffectName[0] = '\0';
|
||
|
m_vEyeGlowColor1.Zero();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Play the pop effect on all kills
|
||
|
GetVectorColorForParticleSystem( iKillStreakColorIndex, GetTeamNumber() == TF_TEAM_BLUE, false, m_vEyeGlowColor1 );
|
||
|
GetVectorColorForParticleSystem( iKillStreakColorIndex, GetTeamNumber() == TF_TEAM_BLUE, true, m_vEyeGlowColor2 );
|
||
|
|
||
|
// Do not render in first person
|
||
|
if ( !pActiveWeapon->IsFirstPersonView() && bKillScored )
|
||
|
{
|
||
|
int iAttachment = 0;
|
||
|
iAttachment = LookupAttachment( "eyeglow_R" );
|
||
|
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
|
||
|
{
|
||
|
CNewParticleEffect* pEffect = ParticleProp()->Create( "killstreak_t0_lvl1_flash", PATTACH_POINT_FOLLOW, iAttachment );
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_DEMOMAN )
|
||
|
{
|
||
|
iAttachment = LookupAttachment( "eyeglow_L" );
|
||
|
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
|
||
|
{
|
||
|
CNewParticleEffect* pEffect = ParticleProp()->Create( "killstreak_t0_lvl1_flash", PATTACH_POINT_FOLLOW, iAttachment );
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// only render eye glows if they have enough kills
|
||
|
if ( iCount < tf_killstreakeyes_minkills.GetInt() || m_Shared.InCond( TF_COND_STEALTHED ) )
|
||
|
{
|
||
|
m_pszEyeGlowEffectName[0] = '\0';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool bForceEyeGlow = false;
|
||
|
#ifdef STAGING_ONLY
|
||
|
bForceEyeGlow = tf_eyeglow_wip.GetBool();
|
||
|
#endif
|
||
|
|
||
|
int iEyeGlowEffectIndex = iKillStreakEffectIndex;
|
||
|
if ( ( iEyeGlowEffectIndex > 0 && iCount > 0 ) || bForceEyeGlow )
|
||
|
{
|
||
|
// 2,4,6,8 for effects
|
||
|
const char* pszEffect = NULL;
|
||
|
|
||
|
// Verify the system is in the desired range
|
||
|
#ifdef STAGING_ONLY
|
||
|
// Legacy support for when staging had test eye glow indexes
|
||
|
if ( iEyeGlowEffectIndex < LOW_GLOW )
|
||
|
{
|
||
|
iEyeGlowEffectIndex = LOW_GLOW;
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
// This is the wrong type, value is too large should not exist so just bail
|
||
|
if ( iEyeGlowEffectIndex > HIGH_GLOW )
|
||
|
return;
|
||
|
|
||
|
// Hack do not have eyeglows for tier0
|
||
|
if ( iEyeGlowEffectIndex == 2001 )
|
||
|
return;
|
||
|
|
||
|
// Should we be using a High_glow
|
||
|
if ( iCount >= tf_killstreakeyes_maxkills.GetInt() )
|
||
|
{
|
||
|
iEyeGlowEffectIndex += HIGH_GLOW;
|
||
|
}
|
||
|
|
||
|
attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iEyeGlowEffectIndex );
|
||
|
|
||
|
if ( pSystem )
|
||
|
{
|
||
|
// Check for TeamColor EyeGlows
|
||
|
if ( GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pSystem->pszSystemName, "_teamcolor_red" ))
|
||
|
{
|
||
|
static char pBlue[256];
|
||
|
V_StrSubst( pSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
|
||
|
pSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
|
||
|
}
|
||
|
else if ( GetTeamNumber() == TF_TEAM_RED && V_stristr( pSystem->pszSystemName, "_teamcolor_blue" ))
|
||
|
{
|
||
|
// Guard against accidentally giving out the blue team color (support tool)
|
||
|
static char pRed[256];
|
||
|
V_StrSubst( pSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
|
||
|
pSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pRed );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pSystem )
|
||
|
{
|
||
|
pszEffect = pSystem->pszSystemName;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_eyeglow_wip.GetBool() )
|
||
|
{
|
||
|
pszEffect = "killstreak_wip";
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
if ( pszEffect )
|
||
|
{
|
||
|
// Do not render in first person
|
||
|
if ( !pActiveWeapon->IsFirstPersonView() )
|
||
|
{
|
||
|
int iAttachment = 0;
|
||
|
iAttachment = LookupAttachment( "eyeglow_R" );
|
||
|
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
|
||
|
{
|
||
|
CNewParticleEffect* pEffect = ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, iAttachment );
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
|
||
|
}
|
||
|
m_pEyeGlowEffect[0] = pEffect;
|
||
|
}
|
||
|
|
||
|
// do not put glow on left eye, that is reserved for eyelander
|
||
|
if ( GetPlayerClass()->GetClassIndex() != TF_CLASS_DEMOMAN )
|
||
|
{
|
||
|
iAttachment = LookupAttachment( "eyeglow_L" );
|
||
|
if ( iAttachment != INVALID_PARTICLE_ATTACHMENT )
|
||
|
{
|
||
|
CNewParticleEffect* pEffect = ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, iAttachment );
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, m_vEyeGlowColor1 );
|
||
|
}
|
||
|
m_pEyeGlowEffect[1] = pEffect;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
V_strcpy_safe( m_pszEyeGlowEffectName, pszEffect );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_pszEyeGlowEffectName[0] = '\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::UpdateMVMEyeGlowEffect( bool bVisible )
|
||
|
{
|
||
|
if ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() || GetTeamNumber() != TF_TEAM_PVE_INVADERS )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Remove the eye glows
|
||
|
ParticleProp()->StopParticlesNamed( "bot_eye_glow", true );
|
||
|
m_pMVMEyeGlowEffect[ 0 ] = NULL;
|
||
|
m_pMVMEyeGlowEffect[ 1 ] = NULL;
|
||
|
|
||
|
if ( bVisible )
|
||
|
{
|
||
|
// Set color based on skill
|
||
|
Vector vColor = m_nBotSkill >= 2 ? Vector( 255, 180, 36 ) : Vector( 0, 240, 255 );
|
||
|
|
||
|
// Create the effects
|
||
|
int nAttachement = LookupAttachment( IsMiniBoss() ? "eye_boss_1" : "eye_1" );
|
||
|
if ( nAttachement > 0 )
|
||
|
{
|
||
|
m_pMVMEyeGlowEffect[ 0 ] = ParticleProp()->Create( "bot_eye_glow", PATTACH_POINT_FOLLOW, nAttachement );
|
||
|
if ( m_pMVMEyeGlowEffect[ 0 ] )
|
||
|
{
|
||
|
m_pMVMEyeGlowEffect[ 0 ]->SetControlPoint( 1, vColor );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
nAttachement = LookupAttachment( IsMiniBoss() ? "eye_boss_2" : "eye_2" );
|
||
|
if ( nAttachement > 0 )
|
||
|
{
|
||
|
m_pMVMEyeGlowEffect[ 1 ] = ParticleProp()->Create( "bot_eye_glow", PATTACH_POINT_FOLLOW, nAttachement );
|
||
|
if ( m_pMVMEyeGlowEffect[ 1 ] )
|
||
|
{
|
||
|
m_pMVMEyeGlowEffect[ 1 ]->SetControlPoint( 1, vColor );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Check if local player should see spy as disguised body
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldDrawSpyAsDisguised()
|
||
|
{
|
||
|
if ( C_BasePlayer::GetLocalPlayer() && m_Shared.InCond( TF_COND_DISGUISED ) &&
|
||
|
( GetEnemyTeam( GetTeamNumber() ) == C_BasePlayer::GetLocalPlayer()->GetTeamNumber() ) )
|
||
|
{
|
||
|
if ( m_Shared.GetDisguiseClass() == TF_CLASS_SPY &&
|
||
|
m_Shared.GetDisguiseTeam() == C_BasePlayer::GetLocalPlayer()->GetTeamNumber() )
|
||
|
{
|
||
|
// This enemy is disguised as a friendly spy.
|
||
|
// Show the spy's original bodygroups.
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// This enemy is disguised. Show the disguise body.
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int C_TFPlayer::GetBody( void )
|
||
|
{
|
||
|
if ( ShouldDrawSpyAsDisguised() )
|
||
|
{
|
||
|
// This enemy is disguised. Show the disguise body.
|
||
|
return m_Shared.GetDisguiseBody();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return BaseClass::GetBody();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const Vector& C_TFPlayer::GetRenderOrigin( void )
|
||
|
{
|
||
|
if ( GetPlayerClass()->HasCustomModel() )
|
||
|
{
|
||
|
m_vecCustomModelOrigin = BaseClass::GetRenderOrigin() + GetPlayerClass()->GetCustomModelOffset();
|
||
|
return m_vecCustomModelOrigin;
|
||
|
}
|
||
|
|
||
|
return BaseClass::GetRenderOrigin();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
ConVar tf_taunt_hint_max_distance( "tf_taunt_hint_max_distance", "256", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT );
|
||
|
bool C_TFPlayer::ShouldTauntHintIconBeVisible() const
|
||
|
{
|
||
|
C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pLocalTFPlayer || pLocalTFPlayer == this || pLocalTFPlayer->IsTaunting() )
|
||
|
return false;
|
||
|
|
||
|
if ( !IsTaunting() || !IsReadyToTauntWithPartner() )
|
||
|
return false;
|
||
|
|
||
|
if ( m_Shared.InCond( TF_COND_TAUNTING ) && m_Shared.GetTauntIndex() == TAUNT_LONG )
|
||
|
{
|
||
|
return GetAbsOrigin().DistToSqr( pLocalTFPlayer->GetAbsOrigin() ) < Square( tf_taunt_hint_max_distance.GetFloat() );
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::IsHealthBarVisible( void ) const
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS || m_Shared.InCond( TF_COND_REPROGRAMMED ) )
|
||
|
{
|
||
|
float flRegenAmount = 0;
|
||
|
CALL_ATTRIB_HOOK_FLOAT( flRegenAmount, add_health_regen );
|
||
|
if ( (int)flRegenAmount != 0 )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( TFGameRules() && TFGameRules()->IsBountyMode() && tf_bountymode_showhealth.GetInt() )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
return IsMiniBoss();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const char* C_TFPlayer::GetBossProgressImageName() const
|
||
|
{
|
||
|
if ( m_bUseBossHealthBar )
|
||
|
{
|
||
|
return GetPlayerClass()->GetClassIconName();
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
float C_TFPlayer::GetBossStatusProgress() const
|
||
|
{
|
||
|
float flProgress = float( GetHealth() ) / float( GetMaxHealth() );
|
||
|
return flProgress;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Handle karts.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::NotifyShouldTransmit( ShouldTransmitState_t state )
|
||
|
{
|
||
|
BaseClass::NotifyShouldTransmit( state );
|
||
|
|
||
|
if ( state == SHOULDTRANSMIT_START )
|
||
|
{
|
||
|
if ( m_Shared.WasInCond( TF_COND_HALLOWEEN_KART ) && m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
|
||
|
{
|
||
|
// Readd the condition for halloween karts.
|
||
|
// This deals with the situation documented here:
|
||
|
// 1) Out of PVS
|
||
|
// 2) In PVS, add cart
|
||
|
// 3) Out of PVS, remove cart
|
||
|
// 4) Returned to PVS, add cart
|
||
|
//
|
||
|
// This situation occurs in the halloween 2014 event map, where you are teleported to and from hell
|
||
|
// Need to research a better fix, but this one works.
|
||
|
m_Shared.ForceRecondNextSync( TF_COND_HALLOWEEN_KART );
|
||
|
|
||
|
// Other PVS bugs can probably be fixed here.
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::IsEffectRateLimited( EBonusEffectFilter_t effect, const C_TFPlayer* pAttacker ) const
|
||
|
{
|
||
|
// Check if we're rate limited
|
||
|
switch( effect )
|
||
|
{
|
||
|
case kEffectFilter_AttackerOnly:
|
||
|
case kEffectFilter_VictimOnly:
|
||
|
case kEffectFilter_AttackerAndVictimOnly:
|
||
|
return false;
|
||
|
case kEffectFilter_AttackerTeam:
|
||
|
case kEffectFilter_VictimTeam:
|
||
|
case kEffectFilter_BothTeams:
|
||
|
// Dont rate limit ourselves
|
||
|
if( pAttacker == this )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
default:
|
||
|
AssertMsg1( 0, "EBonusEffectFilter_t type not handled in %s", __FUNCTION__ );
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool C_TFPlayer::ShouldPlayEffect( EBonusEffectFilter_t filter, const C_TFPlayer* pAttacker, const C_TFPlayer* pVictim ) const
|
||
|
{
|
||
|
Assert( pAttacker );
|
||
|
Assert( pVictim );
|
||
|
if( !pAttacker || !pVictim )
|
||
|
return false;
|
||
|
|
||
|
// Check if the right player relationship
|
||
|
switch( filter )
|
||
|
{
|
||
|
case kEffectFilter_AttackerOnly:
|
||
|
return ( pAttacker == this );
|
||
|
case kEffectFilter_AttackerTeam:
|
||
|
return ( pAttacker->GetTeamNumber() == this->GetTeamNumber() );
|
||
|
case kEffectFilter_VictimOnly:
|
||
|
return ( pVictim == this );
|
||
|
case kEffectFilter_VictimTeam:
|
||
|
return ( pVictim->GetTeamNumber() == this->GetTeamNumber() );
|
||
|
case kEffectFilter_AttackerAndVictimOnly:
|
||
|
return ( pAttacker == this || pVictim == this );
|
||
|
case kEffectFilter_BothTeams:
|
||
|
return ( pAttacker->GetTeamNumber() == this->GetTeamNumber() || pVictim->GetTeamNumber() == this->GetTeamNumber() );
|
||
|
|
||
|
default:
|
||
|
AssertMsg1( 0, "EBonusEffectFilter_t type not handled in %s", __FUNCTION__ );
|
||
|
return false;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::FireGameEvent( IGameEvent *event )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
|
||
|
if ( FStrEq( event->GetName(), "player_hurt" ) )
|
||
|
{
|
||
|
static BonusEffect_t bonusEffects[] =
|
||
|
{
|
||
|
// Sound name Particle name Particle filter Sound filter Sound plays in attacker's ears for them, the world for everyone else
|
||
|
BonusEffect_t( "TFPlayer.CritHit", "crit_text", kEffectFilter_AttackerOnly, kEffectFilter_AttackerOnly, true ),
|
||
|
BonusEffect_t( "TFPlayer.CritHitMini", "minicrit_text", kEffectFilter_AttackerOnly, kEffectFilter_AttackerOnly, true ),
|
||
|
BonusEffect_t( "TFPlayer.DoubleDonk", "doubledonk_text", kEffectFilter_BothTeams, kEffectFilter_BothTeams, true ),
|
||
|
BonusEffect_t( NULL, "sploosh_text", kEffectFilter_BothTeams, kEffectFilter_BothTeams, true )
|
||
|
};
|
||
|
COMPILE_TIME_ASSERT( ARRAYSIZE( bonusEffects ) == kBonusEffect_Count );
|
||
|
|
||
|
// These only should affect the local player
|
||
|
if ( !pLocalPlayer || pLocalPlayer != this )
|
||
|
return;
|
||
|
|
||
|
// By default we get kBonusEffect_None. We want to use whatever value we get here if it's not kBonusEffect_None.
|
||
|
// If it's not, then check for crit or minicrit
|
||
|
EAttackBonusEffects_t eBonusEffect = (EAttackBonusEffects_t)event->GetInt( "bonuseffect", (int)kBonusEffect_None );
|
||
|
if( eBonusEffect == kBonusEffect_None )
|
||
|
{
|
||
|
// Keep reading for these fields to keep replays happy
|
||
|
eBonusEffect = event->GetBool( "minicrit", false ) ? kBonusEffect_MiniCrit : eBonusEffect;
|
||
|
eBonusEffect = event->GetBool( "crit", false ) ? kBonusEffect_Crit : eBonusEffect;
|
||
|
}
|
||
|
|
||
|
// No effect to show? Bail
|
||
|
if( eBonusEffect == kBonusEffect_None || eBonusEffect >= kBonusEffect_Count )
|
||
|
return;
|
||
|
|
||
|
const int iAttacker = engine->GetPlayerForUserID( event->GetInt( "attacker" ) );
|
||
|
C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( iAttacker ) );
|
||
|
|
||
|
const int iVictim = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
|
||
|
C_TFPlayer *pVictim = ToTFPlayer( UTIL_PlayerByIndex( iVictim ) );
|
||
|
|
||
|
// No pointers to players? Bail
|
||
|
if( !pAttacker || !pVictim )
|
||
|
return;
|
||
|
|
||
|
bool bShowDisguisedCrit = event->GetBool( "showdisguisedcrit", 0 );
|
||
|
|
||
|
// Victim is disguised and we're not showing disguised effects? Bail
|
||
|
if ( pVictim->m_Shared.InCond( TF_COND_DISGUISED ) && !bShowDisguisedCrit )
|
||
|
return;
|
||
|
|
||
|
// Victim is carrying Resist Powerup, which is immune to crit damage
|
||
|
if ( pVictim && pVictim->m_Shared.GetCarryingRuneType() == RUNE_RESIST &&
|
||
|
( eBonusEffect == kBonusEffect_Crit || eBonusEffect == kBonusEffect_MiniCrit ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Support old system. If "allseecrit" is set that means we want this to show for our whole team.
|
||
|
EBonusEffectFilter_t eParticleFilter = bonusEffects[ eBonusEffect ].m_eParticleFilter;
|
||
|
EBonusEffectFilter_t eSoundFilter = bonusEffects[ eBonusEffect ].m_eSoundFilter;
|
||
|
if( event->GetBool( "allseecrit", false ) )
|
||
|
{
|
||
|
eParticleFilter = kEffectFilter_AttackerTeam;
|
||
|
eSoundFilter = kEffectFilter_AttackerTeam;
|
||
|
}
|
||
|
|
||
|
// Check if either of our effects are rate limited
|
||
|
if( IsEffectRateLimited( eParticleFilter, pAttacker ) || IsEffectRateLimited( eSoundFilter, pAttacker ) )
|
||
|
{
|
||
|
// Check if we're cooling down
|
||
|
if( !pVictim->CanDisplayAllSeeEffect( eBonusEffect ) )
|
||
|
{
|
||
|
// Too often! Return
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Set cooldown period
|
||
|
pVictim->SetNextAllSeeEffectTime( eBonusEffect, gpGlobals->curtime + 0.5f );
|
||
|
}
|
||
|
|
||
|
ConVarRef hud_combattext( "hud_combattext", false );
|
||
|
ConVarRef hud_combattext_doesnt_block_overhead_text( "hud_combattext_doesnt_block_overhead_text", false );
|
||
|
bool bCombatTextBlocks = hud_combattext.GetBool() && !hud_combattext_doesnt_block_overhead_text.GetBool();
|
||
|
|
||
|
// Show the effect, unless combat text blocks
|
||
|
if( ShouldPlayEffect( eParticleFilter, pAttacker, pVictim ) && !bCombatTextBlocks )
|
||
|
{
|
||
|
pVictim->ParticleProp()->Create( bonusEffects[ eBonusEffect ].m_pszParticleName, PATTACH_POINT_FOLLOW, "head" );
|
||
|
}
|
||
|
// Play the sound!
|
||
|
if( ShouldPlayEffect( eSoundFilter, pAttacker, pVictim ) && bonusEffects[ eBonusEffect ].m_pszSoundName != NULL )
|
||
|
{
|
||
|
const bool& bPlayInAttackersEars = bonusEffects[ eBonusEffect ].m_bPlaySoundInAttackersEars;
|
||
|
|
||
|
// sound effects
|
||
|
EmitSound_t params;
|
||
|
params.m_flSoundTime = 0;
|
||
|
params.m_pflSoundDuration = 0;
|
||
|
params.m_pSoundName = bonusEffects[ eBonusEffect ].m_pszSoundName;
|
||
|
|
||
|
CPASFilter filter( pVictim->GetAbsOrigin() );
|
||
|
if( bPlayInAttackersEars && pAttacker == this )
|
||
|
{
|
||
|
// Don't let the attacker hear this version if its to be played in their ears
|
||
|
filter.RemoveRecipient( pAttacker );
|
||
|
|
||
|
// Play a sound in the ears of the attacker
|
||
|
CSingleUserRecipientFilter attackerFilter( pAttacker );
|
||
|
EmitSound( attackerFilter, pAttacker->entindex(), params );
|
||
|
}
|
||
|
|
||
|
EmitSound( filter, pVictim->entindex(), params );
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "hltv_changed_mode" ) )
|
||
|
{
|
||
|
int iTarget = event->GetInt( "obs_target" );
|
||
|
if ( iTarget == entindex() )
|
||
|
{
|
||
|
int iOld = event->GetInt( "oldmode" );
|
||
|
int iNew = event->GetInt( "newmode" );
|
||
|
|
||
|
if ( iOld == OBS_MODE_IN_EYE || iNew == OBS_MODE_IN_EYE )
|
||
|
{
|
||
|
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateAttachmentModels();
|
||
|
}
|
||
|
|
||
|
// Update visibility of any worn items.
|
||
|
UpdateWearables();
|
||
|
SetBodygroupsDirty();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "hltv_changed_target" ) )
|
||
|
{
|
||
|
int iOldTarget = event->GetInt( "old_target" );
|
||
|
int iTarget = event->GetInt( "obs_target" );
|
||
|
if ( iTarget == entindex() || iOldTarget == entindex() )
|
||
|
{
|
||
|
int iMode = event->GetInt( "mode" );
|
||
|
if ( iMode == OBS_MODE_IN_EYE )
|
||
|
{
|
||
|
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
pWeapon->UpdateAttachmentModels();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update visibility of any worn items.
|
||
|
UpdateWearables();
|
||
|
SetBodygroupsDirty();
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "post_inventory_application" ) )
|
||
|
{
|
||
|
const int iPlayer = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
|
||
|
C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
|
||
|
if ( pPlayer )
|
||
|
{
|
||
|
pPlayer->SetBodygroupsDirty();
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "rocket_jump" ) || FStrEq( event->GetName(), "sticky_jump" ) )
|
||
|
{
|
||
|
// Play a special sound when blast jumping with weapons that don't hurt the player
|
||
|
const int iUserID = event->GetInt( "userid" );
|
||
|
bool bWhistle = event->GetBool( "playsound" );
|
||
|
if ( bWhistle && GetUserID() == iUserID )
|
||
|
{
|
||
|
if ( !m_pBlastJumpLoop )
|
||
|
{
|
||
|
CBroadcastRecipientFilter filter;
|
||
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
||
|
m_pBlastJumpLoop = controller.SoundCreate( filter, entindex(), "BlastJump.Whistle" );
|
||
|
controller.Play( m_pBlastJumpLoop, 0.25, 200 );
|
||
|
m_flBlastJumpLaunchTime = gpGlobals->curtime;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "player_spawn" ) )
|
||
|
{
|
||
|
StopBlastJumpLoopSound( event->GetInt( "userid" ) );
|
||
|
|
||
|
const int iUserID = event->GetInt( "userid" );
|
||
|
if ( pLocalPlayer && GetUserID() == pLocalPlayer->GetUserID() && iUserID == pLocalPlayer->GetUserID() )
|
||
|
{
|
||
|
|
||
|
// ADD EconNotification to equip spellbook here
|
||
|
if ( TFGameRules() && TFGameRules()->IsUsingSpells() )
|
||
|
{
|
||
|
int iCount = NotificationQueue_Count( &CEquipSpellbookNotification::IsNotificationType );
|
||
|
CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( event->GetInt( "class"), LOADOUT_POSITION_ACTION );
|
||
|
// no spell book
|
||
|
if ( !pItem || !pItem->GetStaticData()->GetItemClass() || !FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_spellbook" ) )
|
||
|
{
|
||
|
if ( iCount == 0 )
|
||
|
{
|
||
|
CEquipSpellbookNotification *pNotification = new CEquipSpellbookNotification();
|
||
|
pNotification->SetText( "#TF_SpellBook_EquipAction" );
|
||
|
pNotification->SetLifetime( 10.0f );
|
||
|
NotificationQueue_Add( pNotification );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NotificationQueue_Remove( &CEquipSpellbookNotification::IsNotificationType );
|
||
|
}
|
||
|
}
|
||
|
// ADD EconNotification to equip grapplinghook here
|
||
|
else if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() )
|
||
|
{
|
||
|
int iCount = NotificationQueue_Count( &CEquipGrapplingHookNotification::IsNotificationType );
|
||
|
CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( event->GetInt( "class"), LOADOUT_POSITION_ACTION );
|
||
|
// no spell book
|
||
|
if ( !pItem || !pItem->GetStaticData()->GetItemClass() || !FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_grapplinghook" ) )
|
||
|
{
|
||
|
if ( iCount == 0 )
|
||
|
{
|
||
|
CEquipGrapplingHookNotification *pNotification = new CEquipGrapplingHookNotification();
|
||
|
pNotification->SetText( "#TF_GrapplingHook_EquipAction" );
|
||
|
pNotification->SetLifetime( 10.0f );
|
||
|
NotificationQueue_Add( pNotification );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NotificationQueue_Remove( &CEquipGrapplingHookNotification::IsNotificationType );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// Add EconNotification to equip Canteen here
|
||
|
else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
int iCount = NotificationQueue_Count( &CEquipMvMCanteenNotification::IsNotificationType );
|
||
|
CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( event->GetInt( "class" ), LOADOUT_POSITION_ACTION );
|
||
|
// no spell book
|
||
|
if ( !pItem || !pItem->GetStaticData()->GetItemClass() || !FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_powerup_bottle" ) )
|
||
|
{
|
||
|
if ( iCount == 0 )
|
||
|
{
|
||
|
CEquipMvMCanteenNotification *pNotification = new CEquipMvMCanteenNotification();
|
||
|
pNotification->SetText( "#TF_Canteen_EquipAction" );
|
||
|
pNotification->SetLifetime( 10.0f );
|
||
|
NotificationQueue_Add( pNotification );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NotificationQueue_Remove( &CEquipMvMCanteenNotification::IsNotificationType );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "rocket_jump_landed" ) || FStrEq( event->GetName(), "sticky_jump_landed" ) )
|
||
|
{
|
||
|
StopBlastJumpLoopSound( event->GetInt( "userid" ) );
|
||
|
}
|
||
|
else if( FStrEq( event->GetName(), "damage_resisted" ) )
|
||
|
{
|
||
|
const int index_ = event->GetInt( "entindex" );
|
||
|
if ( index_ == entindex() )
|
||
|
{
|
||
|
m_flLastResistTime = gpGlobals->curtime;
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "revive_player_notify" ) )
|
||
|
{
|
||
|
if ( !pLocalPlayer )
|
||
|
return;
|
||
|
|
||
|
const int index_ = event->GetInt( "entindex" );
|
||
|
if ( pLocalPlayer == this && entindex() == index_ && !m_hRevivePrompt )
|
||
|
{
|
||
|
const int nMarkerIndex = event->GetInt( "marker_entindex" );
|
||
|
CBaseEntity *pMarker = ClientEntityList().GetEnt( nMarkerIndex );
|
||
|
if ( pMarker )
|
||
|
{
|
||
|
m_hRevivePrompt = ShowRevivePrompt( pMarker, "#TF_Prompt_Revive_Title", "#TF_Prompt_Revive_Message", "#TF_Prompt_Revive_Cancel", &PromptAcceptReviveCallback, NULL, NULL );
|
||
|
if ( m_hRevivePrompt )
|
||
|
{
|
||
|
m_hRevivePrompt->SetKeyBoardInputEnabled( false );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "revive_player_stopped" ) )
|
||
|
{
|
||
|
if ( !pLocalPlayer )
|
||
|
return;
|
||
|
|
||
|
if ( m_hRevivePrompt )
|
||
|
{
|
||
|
m_hRevivePrompt->MarkForDeletion();
|
||
|
m_hRevivePrompt = NULL;
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "player_changeclass" ) )
|
||
|
{
|
||
|
if ( TFGameRules() && TFGameRules()->IsMatchTypeCompetitive() )
|
||
|
{
|
||
|
if ( g_PR &&
|
||
|
pLocalPlayer &&
|
||
|
pLocalPlayer == this &&
|
||
|
TFGameRules() &&
|
||
|
TFGameRules()->IsCompetitiveMode() &&
|
||
|
TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
|
||
|
{
|
||
|
CBaseHudChat *pHudChat = (CBaseHudChat*)GET_HUDELEMENT( CHudChat );
|
||
|
if ( pHudChat )
|
||
|
{
|
||
|
C_BasePlayer *pEventPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
|
||
|
if ( pEventPlayer && pLocalPlayer->GetTeamNumber() == g_PR->GetTeam( pEventPlayer->entindex() ) )
|
||
|
{
|
||
|
int nClassID = event->GetInt( "class" );
|
||
|
if ( nClassID >= 0 && nClassID < ARRAYSIZE( g_aPlayerClassNames ) )
|
||
|
{
|
||
|
wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
|
||
|
g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( pEventPlayer->entindex() ), wszPlayerName, sizeof( wszPlayerName ) );
|
||
|
|
||
|
wchar_t wszLocalized[100];
|
||
|
g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Class_Change" ), 2, wszPlayerName, g_pVGuiLocalize->Find( g_aPlayerClassNames[nClassID] ) );
|
||
|
|
||
|
char szLocalized[100];
|
||
|
g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) );
|
||
|
|
||
|
pHudChat->ChatPrintf( pLocalPlayer->entindex(), CHAT_FILTER_NAMECHANGE, "%s", szLocalized );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( FStrEq( event->GetName(), "player_abandoned_match" ) )
|
||
|
{
|
||
|
if ( pLocalPlayer && pLocalPlayer == this )
|
||
|
{
|
||
|
wchar_t wzNotification[1024] = L"";
|
||
|
const wchar_t *pwzTitle = g_pVGuiLocalize->Find( "#TF_Competitive_Abandoned" );
|
||
|
g_pVGuiLocalize->ConstructString_safe( wzNotification, pwzTitle, 0 );
|
||
|
|
||
|
if ( event->GetBool( "game_over" ) )
|
||
|
{
|
||
|
ShowMessageBox( "#TF_Competitive_AbandonedTitle", wzNotification, "#GameUI_OK" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CBaseHudChat *pHudChat = (CBaseHudChat*)GET_HUDELEMENT( CHudChat );
|
||
|
if ( pHudChat )
|
||
|
{
|
||
|
char szLocalized[1024];
|
||
|
g_pVGuiLocalize->ConvertUnicodeToANSI( wzNotification, szLocalized, sizeof( szLocalized ) );
|
||
|
pHudChat->ChatPrintf( pLocalPlayer->entindex(), CHAT_FILTER_SERVERMSG, "%s", szLocalized );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#ifdef STAGING_ONLY
|
||
|
else if ( FStrEq( event->GetName(), "player_death" ) && tf_random_item_min.GetInt() > 0 && tf_random_item_max.GetInt() > tf_random_item_min.GetInt() )
|
||
|
{
|
||
|
const int iUserID = event->GetInt( "userid" );
|
||
|
if ( pLocalPlayer && GetUserID() == pLocalPlayer->GetUserID() && iUserID == pLocalPlayer->GetUserID() )
|
||
|
{
|
||
|
// Give random items and tell the user there items have been changed
|
||
|
// Get a list of all cosmetics for this class in this item range
|
||
|
CUtlVector<CEconItemView*> vecItemViews;
|
||
|
//GetItemDef
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( !pLocalInv )
|
||
|
return;
|
||
|
|
||
|
int iClass = GetPlayerClass()->GetClassIndex();
|
||
|
for ( int i = 0; i < pLocalInv->GetItemCount(); ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pLocalInv->GetItem( i );
|
||
|
const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition();
|
||
|
if ( IsMiscSlot( pItemDef->GetLoadoutSlot( iClass ) ) && pItemDef->GetDefinitionIndex() >= tf_random_item_min.GetInt() && pItemDef->GetDefinitionIndex() <= tf_random_item_max.GetInt() )
|
||
|
{
|
||
|
vecItemViews.AddToTail( pItem );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
equip_region_mask_t unCumulativeRegionMask = 0;
|
||
|
|
||
|
// Unequip everything
|
||
|
InventoryManager()->UpdateInventoryEquippedState( pLocalInv, INVALID_ITEM_ID, iClass, LOADOUT_POSITION_HEAD );
|
||
|
InventoryManager()->UpdateInventoryEquippedState( pLocalInv, INVALID_ITEM_ID, iClass, LOADOUT_POSITION_MISC );
|
||
|
InventoryManager()->UpdateInventoryEquippedState( pLocalInv, INVALID_ITEM_ID, iClass, LOADOUT_POSITION_MISC2 );
|
||
|
|
||
|
// pick a random item slot2
|
||
|
for ( int iSlot = 0; iSlot < 3; ++iSlot )
|
||
|
{
|
||
|
int iLoadoutPos = LOADOUT_POSITION_HEAD;
|
||
|
switch ( iSlot )
|
||
|
{
|
||
|
case 0: iLoadoutPos = LOADOUT_POSITION_HEAD; break;
|
||
|
case 1: iLoadoutPos = LOADOUT_POSITION_MISC; break;
|
||
|
case 2: iLoadoutPos = LOADOUT_POSITION_MISC2; break;
|
||
|
}
|
||
|
|
||
|
int iRandomItem = RandomInt( 0, vecItemViews.Count() - 1 );
|
||
|
for ( int i = 0; i < vecItemViews.Count(); ++i )
|
||
|
{
|
||
|
CEconItemView *pItemView = vecItemViews[( iRandomItem + i ) % vecItemViews.Count()];
|
||
|
equip_region_mask_t unItemEquipMask = pItemView->GetItemDefinition()->GetEquipRegionMask();
|
||
|
if ( !( unItemEquipMask & unCumulativeRegionMask ) )
|
||
|
{
|
||
|
TFInventoryManager()->EquipItemInLoadout( pLocalPlayer->GetPlayerClass()->GetClassIndex(), iLoadoutPos, pItemView->GetID() );
|
||
|
vecItemViews.Remove( ( iRandomItem + i ) % vecItemViews.Count() );
|
||
|
unCumulativeRegionMask |= unItemEquipMask;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Notify player there items have changed
|
||
|
CEconNotification *pNotification = new CEconNotification();
|
||
|
pNotification->SetText( "#TF_Test_ItemsChanged" );
|
||
|
pNotification->SetLifetime( 6.0f );
|
||
|
NotificationQueue_Add( pNotification );
|
||
|
}
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
BaseClass::FireGameEvent( event );
|
||
|
}
|
||
|
|
||
|
const char* C_TFPlayer::ModifyEventParticles( const char* token )
|
||
|
{
|
||
|
if ( GetPlayerClass()->IsClass( TF_CLASS_SCOUT ) )
|
||
|
{
|
||
|
if ( !Q_strcmp( token, "doublejump_puff" ) )
|
||
|
{
|
||
|
if ( m_Shared.GetAirDash() > 1 )
|
||
|
{
|
||
|
return "doublejump_puff_alt";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return BaseClass::ModifyEventParticles( token );
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::SetTauntCameraTargets( float back, float up )
|
||
|
{
|
||
|
m_flTauntCamTargetDist = back;
|
||
|
m_flTauntCamTargetDistUp = up;
|
||
|
// Force this on
|
||
|
m_bTauntInterpolating = true;
|
||
|
}
|
||
|
|
||
|
CampaignMedalDisplayType_t C_TFPlayer::GetCampaignMedalType( void )
|
||
|
{
|
||
|
// static CSchemaItemDefHandle pItemDef_Summer2015Operation( "Activated Summer 2015 Operation Pass" );
|
||
|
// static CSchemaItemDefHandle pItemDef_InvasionPass( "Activated Invasion Pass" );
|
||
|
// static CSchemaItemDefHandle pItemDef_HalloweenPass( "Activated Halloween Pass" );
|
||
|
// static CSchemaItemDefHandle pItemDef_Winter2016Pass( "Activated Operation Tough Break Pass" );
|
||
|
CampaignMedalDisplayType_t retVal = CAMPAIGN_MEDAL_DISPLAY_TYPE_NONE;
|
||
|
/*
|
||
|
if ( HasCampaignMedal( CAMPAIGN_MEDAL_WINTER2016 ) )
|
||
|
{
|
||
|
CTFPlayerInventory *pInv = Inventory();
|
||
|
if ( pInv )
|
||
|
{
|
||
|
for ( int i = 0; i < pInv->GetItemCount(); ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pInv->GetItem( i );
|
||
|
if ( pItem && ( pItem->GetItemDefinition() == pItemDef_Winter2016Pass ) )
|
||
|
{
|
||
|
style_index_t iStyle = pItem->GetItemStyle();
|
||
|
if ( iStyle != INVALID_STYLE_INDEX )
|
||
|
{
|
||
|
iStyle += ( ( entindex()%2 < 1 ) ? CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL1 : CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL2 ); // styles start at 0 and Winter2016 images start at CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL1
|
||
|
if ( ( iStyle >= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GRAVEL1 ) && ( iStyle <= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_WINTER2016_GOLD2 ) )
|
||
|
{
|
||
|
retVal = (CampaignMedalDisplayType_t)iStyle;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( IsPlayingInvasionMap() && HasCampaignMedal( CAMPAIGN_MEDAL_INVASION ) )
|
||
|
{
|
||
|
retVal = CAMPAIGN_MEDAL_DISPLAY_TYPE_INVASION;
|
||
|
}
|
||
|
else if ( HasCampaignMedal( CAMPAIGN_MEDAL_HALLOWEEN ) )
|
||
|
{
|
||
|
CTFPlayerInventory *pInv = Inventory();
|
||
|
if ( pInv )
|
||
|
{
|
||
|
for ( int i = 0; i < pInv->GetItemCount(); ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pInv->GetItem( i );
|
||
|
if ( pItem && ( pItem->GetItemDefinition() == pItemDef_HalloweenPass ) )
|
||
|
{
|
||
|
style_index_t iStyle = pItem->GetItemStyle();
|
||
|
if ( iStyle != INVALID_STYLE_INDEX )
|
||
|
{
|
||
|
iStyle += CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GRAVEL; // styles start at 0 and Halloween images start at CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GRAVEL
|
||
|
if ( ( iStyle >= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GRAVEL ) && ( iStyle <= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_HALLOWEEN_GOLD ) )
|
||
|
{
|
||
|
retVal = (CampaignMedalDisplayType_t)iStyle;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( HasCampaignMedal( CAMPAIGN_MEDAL_SUMMER2015 ) )
|
||
|
{
|
||
|
CTFPlayerInventory *pInv = Inventory();
|
||
|
if ( pInv )
|
||
|
{
|
||
|
for ( int i = 0; i < pInv->GetItemCount(); ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pInv->GetItem( i );
|
||
|
if ( pItem && ( pItem->GetItemDefinition() == pItemDef_Summer2015Operation ) )
|
||
|
{
|
||
|
style_index_t iStyle = pItem->GetItemStyle();
|
||
|
if ( iStyle != INVALID_STYLE_INDEX )
|
||
|
{
|
||
|
iStyle += 1; // styles start at 0 and images start at 1
|
||
|
if ( ( iStyle >= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_SUMMER2015_GRAVEL ) && ( iStyle <= (style_index_t)CAMPAIGN_MEDAL_DISPLAY_TYPE_SUMMER2015_GOLD ) )
|
||
|
{
|
||
|
retVal = (CampaignMedalDisplayType_t)iStyle;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}*/
|
||
|
|
||
|
return retVal;
|
||
|
}
|
||
|
|
||
|
const char *C_TFPlayer::GetCampaignMedalImage( void )
|
||
|
{
|
||
|
return g_pszCampaignMedalIcons[GetCampaignMedalType()];
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::UpdateGlowEffect( void )
|
||
|
{
|
||
|
DestroyGlowEffect();
|
||
|
|
||
|
BaseClass::UpdateGlowEffect();
|
||
|
|
||
|
// create a new effect if we have a coach
|
||
|
if ( m_hCoach && m_hCoach->IsLocalPlayer() && m_hCoach->m_bIsCoaching )
|
||
|
{
|
||
|
float r, g, b;
|
||
|
GetGlowEffectColor( &r, &g, &b );
|
||
|
|
||
|
m_pStudentGlowEffect = new CGlowObject( this, Vector( r, g, b ), 1.0, true );
|
||
|
}
|
||
|
|
||
|
// create a power up effect if needed
|
||
|
if ( ShouldShowPowerupGlowEffect() )
|
||
|
{
|
||
|
float r, g, b;
|
||
|
GetPowerupGlowEffectColor( &r, &g, &b );
|
||
|
|
||
|
m_pPowerupGlowEffect = new CGlowObject( this, Vector( r, g, b ), 1.0, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void C_TFPlayer::DestroyGlowEffect( void )
|
||
|
{
|
||
|
BaseClass::DestroyGlowEffect();
|
||
|
|
||
|
if ( m_pStudentGlowEffect )
|
||
|
{
|
||
|
delete m_pStudentGlowEffect;
|
||
|
m_pStudentGlowEffect = NULL;
|
||
|
}
|
||
|
|
||
|
if ( m_pPowerupGlowEffect )
|
||
|
{
|
||
|
delete m_pPowerupGlowEffect;
|
||
|
m_pPowerupGlowEffect = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::UpdateGlowColor( void )
|
||
|
{
|
||
|
CGlowObject* pGlowObject = GetGlowObject();
|
||
|
if ( pGlowObject )
|
||
|
{
|
||
|
float r, g, b;
|
||
|
GetGlowEffectColor( &r, &g, &b );
|
||
|
|
||
|
pGlowObject->SetColor( Vector( r, g, b ) );
|
||
|
}
|
||
|
|
||
|
if ( m_pPowerupGlowEffect )
|
||
|
{
|
||
|
float r, g, b;
|
||
|
GetPowerupGlowEffectColor( &r, &g, &b );
|
||
|
|
||
|
m_pPowerupGlowEffect->SetColor( Vector( r, g, b ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::GetGlowEffectColor( float *r, float *g, float *b )
|
||
|
{
|
||
|
#ifdef TF_CREEP_MODE
|
||
|
if ( TFGameRules() && TFGameRules()->IsCreepWaveMode() )
|
||
|
{
|
||
|
if ( GetTeamNumber() == TF_TEAM_RED )
|
||
|
{
|
||
|
*r = 255;
|
||
|
*g = 0;
|
||
|
*b = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*r = 0;
|
||
|
*g = 0;
|
||
|
*b = 255;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
#endif // TF_CREEP_MODE
|
||
|
|
||
|
int nTeam = GetTeamNumber();
|
||
|
|
||
|
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
|
||
|
// In CTF, show health color glow for alive player
|
||
|
if ( pLocalPlayer && pLocalPlayer->IsAlive() && TFGameRules() && ( TFGameRules()->GetGameType() == TF_GAMETYPE_CTF ) && HasTheFlag() )
|
||
|
{
|
||
|
float flHealth = (float)GetHealth() / (float)GetMaxHealth();
|
||
|
|
||
|
if ( flHealth > 0.6 )
|
||
|
{
|
||
|
*r = 0.33f;
|
||
|
*g = 0.75f;
|
||
|
*b = 0.23f;
|
||
|
}
|
||
|
else if( flHealth > 0.3 )
|
||
|
{
|
||
|
*r = 0.75f;
|
||
|
*g = 0.72f;
|
||
|
*b = 0.23f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*r = 0.75f;
|
||
|
*g = 0.23f;
|
||
|
*b = 0.23f;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !engine->IsHLTV() && ( GetLocalPlayerTeam() >= FIRST_GAME_TEAM ) )
|
||
|
{
|
||
|
if ( IsPlayerClass( TF_CLASS_SPY ) && m_Shared.InCond( TF_COND_DISGUISED ) && ( GetTeamNumber() != GetLocalPlayerTeam() ) )
|
||
|
{
|
||
|
nTeam = m_Shared.GetDisguiseTeam();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TFGameRules()->GetTeamGlowColor( nTeam, *r, *g, *b );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::ShouldShowPowerupGlowEffect()
|
||
|
{
|
||
|
// should local player see enemy glow with powerup related
|
||
|
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer->IsAlive() && this != pLocalPlayer && GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
|
||
|
{
|
||
|
// give advantage to local player who doesn't have rune to fight against enemy with rune by glowing their health
|
||
|
if ( m_Shared.IsCarryingRune() && !pLocalPlayer->m_Shared.IsCarryingRune() )
|
||
|
{
|
||
|
// only show glow when the enemy is lower than 30% HP
|
||
|
float flHealth = ( float )GetHealth() / ( float )GetMaxHealth();
|
||
|
return flHealth <= 0.3 && pLocalPlayer->IsLineOfSightClear( this, IGNORE_ACTORS );
|
||
|
}
|
||
|
// local player with supernova can see enemy glow within supernova range
|
||
|
else if ( pLocalPlayer->m_Shared.GetCarryingRuneType() == RUNE_SUPERNOVA && pLocalPlayer->m_Shared.IsRuneCharged() && !m_Shared.IsStealthed() )
|
||
|
{
|
||
|
const float flEffectRadiusSqr = Sqr( 1500.f );
|
||
|
Vector toPlayer = WorldSpaceCenter() - pLocalPlayer->WorldSpaceCenter();
|
||
|
return toPlayer.LengthSqr() <= flEffectRadiusSqr && pLocalPlayer->IsLineOfSightClear( this, IGNORE_ACTORS );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::GetPowerupGlowEffectColor( float *r, float *g, float *b )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
|
||
|
// no need to add extra logics here. we already know that other players are glowing from SUPERNOVA
|
||
|
if ( pLocalPlayer->m_Shared.GetCarryingRuneType() == RUNE_SUPERNOVA )
|
||
|
{
|
||
|
*r = 255;
|
||
|
*g = 255;
|
||
|
*b = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetGlowEffectColor( r, g, b );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static ConVar tf_inspect_hint_count( "tf_inspect_hint_count", "0", FCVAR_ARCHIVE );
|
||
|
void C_TFPlayer::HandleInspectHint()
|
||
|
{
|
||
|
int nNotifyCount = tf_inspect_hint_count.GetInt();
|
||
|
if ( nNotifyCount > 10 )
|
||
|
return;
|
||
|
|
||
|
if ( m_bNotifiedWeaponInspectThisLife )
|
||
|
return;
|
||
|
|
||
|
CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
|
||
|
if ( pNotifyPanel )
|
||
|
{
|
||
|
wchar_t szNotification[1024]=L"";
|
||
|
wchar_t wKeyBind[80] = L"";
|
||
|
const wchar_t *wpszFormat = g_pVGuiLocalize->Find( "#Hint_inspect_weapon" );
|
||
|
if ( wpszFormat )
|
||
|
{
|
||
|
const char *key = engine->Key_LookupBinding( "+inspect" );
|
||
|
if ( !key || FStrEq( key, "(null)" ) )
|
||
|
{
|
||
|
key = "< not bound >";
|
||
|
}
|
||
|
|
||
|
g_pVGuiLocalize->ConvertANSIToUnicode( key, wKeyBind, sizeof( wKeyBind ) );
|
||
|
g_pVGuiLocalize->ConstructString_safe( szNotification, wpszFormat, 1, wKeyBind );
|
||
|
pNotifyPanel->SetupNotifyCustom( szNotification, "", GetTeamNumber() );
|
||
|
|
||
|
tf_inspect_hint_count.SetValue( nNotifyCount + 1 );
|
||
|
}
|
||
|
|
||
|
m_bNotifiedWeaponInspectThisLife = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool C_TFPlayer::AddOverheadEffect( const char *pszEffectName )
|
||
|
{
|
||
|
int index_ = m_mapOverheadEffects.Find( pszEffectName );
|
||
|
|
||
|
// particle is added already
|
||
|
if ( index_ != m_mapOverheadEffects.InvalidIndex() )
|
||
|
return false;
|
||
|
|
||
|
CNewParticleEffect *pEffect = ParticleProp()->Create( pszEffectName, PATTACH_ABSORIGIN_FOLLOW, 0, GetOverheadEffectPosition() );
|
||
|
if ( pEffect )
|
||
|
{
|
||
|
if ( m_mapOverheadEffects.Count() == 0 )
|
||
|
{
|
||
|
m_flOverheadEffectStartTime = gpGlobals->curtime;
|
||
|
}
|
||
|
|
||
|
m_mapOverheadEffects.Insert( pszEffectName, pEffect );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void C_TFPlayer::RemoveOverheadEffect( const char *pszEffectName, bool bRemoveInstantly )
|
||
|
{
|
||
|
int index_ = m_mapOverheadEffects.Find( pszEffectName );
|
||
|
|
||
|
// particle is added already
|
||
|
if ( index_ != m_mapOverheadEffects.InvalidIndex() )
|
||
|
{
|
||
|
if ( bRemoveInstantly )
|
||
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_mapOverheadEffects[index_] );
|
||
|
ParticleProp()->StopParticlesNamed( pszEffectName, bRemoveInstantly );
|
||
|
m_mapOverheadEffects.RemoveAt( index_ );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void C_TFPlayer::UpdateOverheadEffects()
|
||
|
{
|
||
|
if ( IsLocalPlayer() )
|
||
|
return;
|
||
|
|
||
|
const int nOverheadEffectCount = m_mapOverheadEffects.Count();
|
||
|
if ( nOverheadEffectCount == 0 )
|
||
|
return;
|
||
|
|
||
|
Vector vecOverheadEffectPosition = GetOverheadEffectPosition();
|
||
|
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
|
||
|
if ( !pLocalPlayer )
|
||
|
return;
|
||
|
|
||
|
Vector vecHeadToHead = EyePosition() - pLocalPlayer->EyePosition();
|
||
|
|
||
|
const float flEffectGap = 24.f;
|
||
|
Vector vecRightOffset = CrossProduct( vecHeadToHead, Vector( 0, 0, 1 ) ).Normalized();
|
||
|
float flFirstEffectOffset = -flEffectGap * 0.5f * ( nOverheadEffectCount - 1 );
|
||
|
int iValidParticleIndex = 0;
|
||
|
FOR_EACH_MAP_FAST( m_mapOverheadEffects, i )
|
||
|
{
|
||
|
HPARTICLEFFECT hEffect = m_mapOverheadEffects[i];
|
||
|
if ( hEffect )
|
||
|
{
|
||
|
float flCurrentOffset = flFirstEffectOffset + flEffectGap * iValidParticleIndex;
|
||
|
Vector vecOffset = vecOverheadEffectPosition + flCurrentOffset * vecRightOffset;
|
||
|
ParticleProp()->AddControlPoint( hEffect, 0, this, PATTACH_ABSORIGIN_FOLLOW, 0, vecOffset );
|
||
|
iValidParticleIndex++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Vector C_TFPlayer::GetOverheadEffectPosition()
|
||
|
{
|
||
|
return GetClassEyeHeight() + Vector( 0, 0, 20 );
|
||
|
}
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// The serverbrowser has just added a server to the favorite list.
|
||
|
// Send this information to the GC.
|
||
|
static void cc_tf_register_favorite_with_gc( const CCommand &args )
|
||
|
{
|
||
|
netadr_t netaddr( args.ArgS() );
|
||
|
|
||
|
CGCMsg< MsgGCServerBrowser_Server_t > msg( k_EMsgGCServerBrowser_FavoriteServer );
|
||
|
msg.Body().m_unIP = netaddr.GetIPNetworkByteOrder(); // <<<< Note: this is wrong. But it was wrong before. And we can fix the data if it is *consistently* wrong.
|
||
|
msg.Body().m_usPort = netaddr.GetPort();
|
||
|
msg.Body().m_ubSource = k_EGCMsgServerBrowser_FromServerBrowser;
|
||
|
GCClientSystem()->BSendMessage( msg );
|
||
|
}
|
||
|
static ConCommand tf_register_favorite_with_gc( "rfgc", cc_tf_register_favorite_with_gc, "", FCVAR_HIDDEN );
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// The serverbrowser has just added a server to the blacklist.
|
||
|
// Send this information to the GC.
|
||
|
static void cc_tf_register_blacklist_with_gc( const CCommand &args )
|
||
|
{
|
||
|
netadr_t netaddr( args.ArgS() );
|
||
|
|
||
|
CGCMsg< MsgGCServerBrowser_Server_t > msg( k_EMsgGCServerBrowser_BlacklistServer );
|
||
|
msg.Body().m_unIP = netaddr.GetIPNetworkByteOrder(); // <<<< Note: this is wrong. But it was wrong before. And we can fix the data if it is *consistently* wrong.
|
||
|
msg.Body().m_usPort = netaddr.GetPort();
|
||
|
msg.Body().m_ubSource = k_EGCMsgServerBrowser_FromServerBrowser;
|
||
|
GCClientSystem()->BSendMessage( msg );
|
||
|
}
|
||
|
static ConCommand tf_register_blacklist_with_gc( "rbgc", cc_tf_register_blacklist_with_gc, "", FCVAR_HIDDEN );
|
||
|
|
||
|
static void cc_taunt_by_name( const CCommand &args )
|
||
|
{
|
||
|
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( !pPlayer || !pPlayer->IsAlive() )
|
||
|
return;
|
||
|
|
||
|
const char *pszTauntItemName = args.ArgS();
|
||
|
|
||
|
int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
|
||
|
|
||
|
CTFPlayerInventory *pInv = pPlayer->Inventory();
|
||
|
if ( !pInv )
|
||
|
return;
|
||
|
|
||
|
CUtlStringList strTauntList;
|
||
|
for ( int iSlot = LOADOUT_POSITION_TAUNT; iSlot<= LOADOUT_POSITION_TAUNT8; ++iSlot )
|
||
|
{
|
||
|
CEconItemView *pItem = pInv->GetItemInLoadout( iClass, iSlot );
|
||
|
if ( !pItem || !pItem->IsValid() )
|
||
|
continue;
|
||
|
|
||
|
static char pszItemName[512];
|
||
|
g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find ( pItem->GetStaticData()->GetItemBaseName() ) , pszItemName, sizeof(pszItemName) );
|
||
|
strTauntList.CopyAndAddToTail( pszItemName );
|
||
|
|
||
|
if ( V_stricmp( pszTauntItemName, pszItemName ) == 0 )
|
||
|
{
|
||
|
int iTauntSlot = iSlot - LOADOUT_POSITION_TAUNT + 1;
|
||
|
engine->ClientCmd( CFmtStr( "taunt %d", iTauntSlot ) );
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Msg( "taunt_by_name failed. Taunt [%s] is not equipped in the loadout.\n", pszTauntItemName );
|
||
|
Msg( "[Taunt(s) in loadout]\n");
|
||
|
for ( int i=0; i<strTauntList.Count(); ++i )
|
||
|
{
|
||
|
Msg( "%s\n", strTauntList[i] );
|
||
|
}
|
||
|
}
|
||
|
static ConCommand taunt_by_name( "taunt_by_name", cc_taunt_by_name, "Use equipped taunt by name." );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
CON_COMMAND( force_reset_gesture_slot_vcd, "" )
|
||
|
{
|
||
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
if ( pLocalPlayer )
|
||
|
{
|
||
|
pLocalPlayer->m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
|
||
|
}
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|