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.
12134 lines
352 KiB
12134 lines
352 KiB
//========= 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
|
|
|