Team Fortress 2 Source Code as on 22/4/2020
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.

1224 lines
42 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. #include "hud.h"
  9. #include "hudelement.h"
  10. #include "c_tf_player.h"
  11. #include "iclientmode.h"
  12. #include "ienginevgui.h"
  13. #include <vgui/ILocalize.h>
  14. #include <vgui/ISurface.h>
  15. #include <vgui/IVGui.h>
  16. #include <vgui_controls/EditablePanel.h>
  17. #include <vgui_controls/ProgressBar.h>
  18. #include "view_scene.h"
  19. #include "view.h"
  20. #include "tf_gamerules.h"
  21. #include "tf_logic_halloween_2014.h"
  22. #include "tf_weapon_invis.h"
  23. #include <vgui_controls/AnimationController.h>
  24. #include "c_tf_objective_resource.h"
  25. // memdbgon must be the last include file in a .cpp file!!!
  26. #include "tier0/memdbgon.h"
  27. using namespace vgui;
  28. extern ISoundEmitterSystemBase *soundemitterbase;
  29. // Floating delta text items, float off the top of the frame to
  30. // show changes to the metal account value
  31. typedef struct
  32. {
  33. enum eAccountDeltaType_t
  34. {
  35. ACCOUNT_DELTA_INVALID,
  36. ACCOUNT_DELTA_HEALING,
  37. ACCOUNT_DELTA_DAMAGE,
  38. ACCOUNT_DELTA_BONUS_POINTS,
  39. ACCOUNT_DELTA_ROBOT_DESTRUCTION_POINT_RED,
  40. ACCOUNT_DELTA_ROBOT_DESTRUCTION_POINT_BLUE,
  41. };
  42. // amount of delta
  43. int m_iAmount;
  44. bool m_bLargeFont; // display larger font
  45. eAccountDeltaType_t m_eDataType;
  46. // die time
  47. float m_flDieTime;
  48. // position
  49. int m_nX; // X Pos in screen space & world space
  50. int m_nXEnd; // Ending X Pos in screen space and world space
  51. int m_nHStart; // Starting Y Pos in screen space, Z pos in world space
  52. int m_nHEnd; // Ending Y Pos in screen space, Z pos in world space
  53. int m_nY; // Y Coord in world space, not used in screen space
  54. bool m_bWorldSpace;
  55. float m_flBatchWindow;
  56. int m_nSourceID; // Can be entindex, etc
  57. Color m_color;
  58. bool m_bShadows;
  59. // append a bit of extra text to the end
  60. wchar_t m_wzText[8];
  61. } account_delta_t;
  62. #define NUM_ACCOUNT_DELTA_ITEMS 10
  63. ConVar hud_combattext( "hud_combattext", "1", FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
  64. ConVar hud_combattext_healing( "hud_combattext_healing", "1", FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Shows health restored per-second over heal targets." );
  65. ConVar hud_combattext_batching( "hud_combattext_batching", "0", FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "If set to 1, numbers that are too close together are merged." );
  66. ConVar hud_combattext_batching_window( "hud_combattext_batching_window", "0.2", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Maximum delay between damage events in order to batch numbers.", true, 0.1, true, 2.0 );
  67. ConVar hud_combattext_doesnt_block_overhead_text( "hud_combattext_doesnt_block_overhead_text", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "If set to 1, allow text like \"CRIT\" to still show over a victim's head." );
  68. ConVar tf_dingalingaling( "tf_dingalingaling", "0", FCVAR_ARCHIVE, "If set to 1, play a sound everytime you injure an enemy. The sound can be customized by replacing the 'tf/sound/ui/hitsound.wav' file." );
  69. ConVar tf_dingaling_volume( "tf_dingaling_volume", "0.75", FCVAR_ARCHIVE, "Desired volume of the hit sound.", true, 0.0, true, 1.0 );
  70. ConVar tf_dingaling_pitchmindmg( "tf_dingaling_pitchmindmg", "100", FCVAR_ARCHIVE, "Desired pitch of the hit sound when a minimal damage hit (<= 10 health) is done.", true, 1, true, 255 );
  71. ConVar tf_dingaling_pitchmaxdmg( "tf_dingaling_pitchmaxdmg", "100", FCVAR_ARCHIVE, "Desired pitch of the hit sound when a maximum damage hit (>= 150 health) is done.", true, 1, true, 255 );
  72. ConVar tf_dingaling_pitch_override( "tf_dingaling_pitch_override", "-1", FCVAR_NONE, "If set, pitch for all hit sounds." );
  73. ConVar tf_dingalingaling_lasthit( "tf_dingalingaling_lasthit", "0", FCVAR_ARCHIVE, "If set to 1, play a sound whenever one of your attacks kills an enemy. The sound can be customized by replacing the 'tf/sound/ui/killsound.wav' file." );
  74. ConVar tf_dingaling_lasthit_volume( "tf_dingaling_lasthit_volume", "0.75", FCVAR_ARCHIVE, "Desired volume of the last hit sound.", true, 0.0, true, 1.0 );
  75. ConVar tf_dingaling_lasthit_pitchmindmg( "tf_dingaling_lasthit_pitchmindmg", "100", FCVAR_ARCHIVE, "Desired pitch of the last hit sound when a minimal damage hit (<= 10 health) is done.", true, 1, true, 255 );
  76. ConVar tf_dingaling_lasthit_pitchmaxdmg( "tf_dingaling_lasthit_pitchmaxdmg", "100", FCVAR_ARCHIVE, "Desired pitch of the last hit sound when a maximum damage hit (>= 150 health) is done.", true, 1, true, 255 );
  77. ConVar tf_dingaling_lasthit_pitch_override( "tf_dingaling_lasthit_pitch_override", "-1", FCVAR_NONE, "If set, pitch for last hit sounds." );
  78. ConVar tf_dingalingaling_repeat_delay( "tf_dingalingaling_repeat_delay", "0.0", FCVAR_ARCHIVE, "Desired repeat delay of the hit sound. Set to 0 to play a sound for every instance of damage dealt.", true, 0.f, false, 0.f );
  79. ConVar hud_damagemeter( "hud_damagemeter", "0", FCVAR_CHEAT, "Display damage-per-second information in the lower right corner of the screen." );
  80. ConVar hud_damagemeter_period( "hud_damagemeter_period", "0", FCVAR_NONE, "When set to zero, average damage-per-second across all recent damage events, otherwise average damage across defined period (number of seconds)." );
  81. ConVar hud_damagemeter_ooctimer( "hud_damagemeter_ooctimer", "1", FCVAR_NONE, "How many seconds after the last damage event before we consider the player out of combat." );
  82. struct hitsound_params_t
  83. {
  84. hitsound_params_t( const char * pszName, int minpitch, int maxpitch )
  85. {
  86. m_pszName = pszName;
  87. m_iMinPitch = minpitch;
  88. m_iMaxPitch = maxpitch;
  89. }
  90. float GetPitchMin( bool bLastHit ) const
  91. {
  92. return bLastHit ? tf_dingaling_lasthit_pitchmindmg.GetInt() : tf_dingaling_pitchmindmg.GetInt();
  93. //return RemapValClamped( tf_dingaling_pitchmindmg.GetInt(), 0, 100, m_iMinPitch, m_iMaxPitch );
  94. }
  95. float GetPitchMax( bool bLastHit ) const
  96. {
  97. return bLastHit ? tf_dingaling_lasthit_pitchmaxdmg.GetInt() : tf_dingaling_pitchmaxdmg.GetInt();
  98. //return RemapValClamped( tf_dingaling_pitchmaxdmg.GetInt(), 0, 100, m_iMinPitch, m_iMaxPitch );
  99. }
  100. float GetPitchFromDamage( int damage, bool bLastHit ) const
  101. {
  102. if ( bLastHit && tf_dingaling_lasthit_pitch_override.GetInt() > 0 )
  103. {
  104. return tf_dingaling_lasthit_pitch_override.GetFloat();
  105. }
  106. else if ( tf_dingaling_pitch_override.GetInt() > 0 )
  107. {
  108. return tf_dingaling_pitch_override.GetFloat();
  109. }
  110. return RemapValClamped( damage, 10, 150, GetPitchMin( bLastHit ), GetPitchMax( bLastHit ) );
  111. }
  112. const char *m_pszName;
  113. int m_iMinPitch;
  114. int m_iMaxPitch;
  115. };
  116. static const hitsound_params_t g_HitSounds[] =
  117. {
  118. hitsound_params_t( "Player.HitSoundDefaultDing", 1, 255 ),
  119. hitsound_params_t( "Player.HitSoundElectro", 1, 255 ),
  120. hitsound_params_t( "Player.HitSoundNotes", 1, 255 ),
  121. hitsound_params_t( "Player.HitSoundPercussion", 1, 255 ),
  122. hitsound_params_t( "Player.HitSoundRetro", 1, 255 ),
  123. hitsound_params_t( "Player.HitSoundSpace", 1, 255 ),
  124. hitsound_params_t( "Player.HitSoundBeepo", 1, 255 ),
  125. hitsound_params_t( "Player.HitSoundVortex", 1, 255 ),
  126. hitsound_params_t( "Player.HitSoundSquasher", 1, 255 ),
  127. };
  128. static const hitsound_params_t g_LastHitSounds[] =
  129. {
  130. hitsound_params_t( "Player.KillSoundDefaultDing", 1, 255 ),
  131. hitsound_params_t( "Player.KillSoundElectro", 1, 255 ),
  132. hitsound_params_t( "Player.KillSoundNotes", 1, 255 ),
  133. hitsound_params_t( "Player.KillSoundPercussion", 1, 255 ),
  134. hitsound_params_t( "Player.KillSoundRetro", 1, 255 ),
  135. hitsound_params_t( "Player.KillSoundSpace", 1, 255 ),
  136. hitsound_params_t( "Player.KillSoundBeepo", 1, 255 ),
  137. hitsound_params_t( "Player.KillSoundVortex", 1, 255 ),
  138. hitsound_params_t( "Player.KillSoundSquasher", 1, 255 ),
  139. };
  140. ConVar tf_dingalingaling_effect( "tf_dingalingaling_effect", "0", FCVAR_ARCHIVE, "Which Dingalingaling sound is used", true, 0, true, ARRAYSIZE( g_HitSounds )-1 );
  141. ConVar tf_dingalingaling_last_effect( "tf_dingalingaling_last_effect", "0", FCVAR_ARCHIVE, "Which final hit sound to play when the target expires.", true, 0, true, ARRAYSIZE( g_LastHitSounds )-1 );
  142. //-----------------------------------------------------------------------------
  143. // Purpose:
  144. //-----------------------------------------------------------------------------
  145. class CAccountPanel : public EditablePanel
  146. {
  147. DECLARE_CLASS_SIMPLE( CAccountPanel, EditablePanel );
  148. public:
  149. CAccountPanel( Panel *parent, const char *name )
  150. : EditablePanel( parent, name )
  151. {
  152. m_nBGTexture = -1;
  153. m_bNegativeFlipDir = false;
  154. SetDialogVariable( "metal", 0 );
  155. }
  156. virtual void ApplySchemeSettings( IScheme *scheme ) OVERRIDE;
  157. virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
  158. virtual void Paint( void ) OVERRIDE;
  159. virtual account_delta_t *OnAccountValueChanged( int iOldValue, int iNewValue, account_delta_t::eAccountDeltaType_t type );
  160. virtual const char *GetResFileName( void ) { return "resource/UI/HudAccountPanel.res"; }
  161. protected:
  162. virtual Color GetColor( const account_delta_t::eAccountDeltaType_t& type );
  163. CUtlVector <account_delta_t> m_AccountDeltaItems;
  164. int m_nBGTexture;
  165. bool m_bNegativeFlipDir;
  166. CPanelAnimationVarAliasType( float, m_flDeltaItemStartPos, "delta_item_start_y", "100", "proportional_float" );
  167. CPanelAnimationVarAliasType( float, m_flDeltaItemEndPos, "delta_item_end_y", "0", "proportional_float" );
  168. CPanelAnimationVarAliasType( float, m_flDeltaItemX, "delta_item_x", "0", "proportional_float" );
  169. CPanelAnimationVarAliasType( float, m_flDeltaItemXEndPos, "delta_item_end_x", "0", "proportional_float" );
  170. CPanelAnimationVarAliasType( float, m_flBGImageX, "bg_image_x", "0", "proportional_float" );
  171. CPanelAnimationVarAliasType( float, m_flBGImageY, "bg_image_y", "0", "proportional_float" );
  172. CPanelAnimationVarAliasType( float, m_flBGImageWide, "bg_image_wide", "0", "proportional_float" );
  173. CPanelAnimationVarAliasType( float, m_flBGImageTall, "bg_image_tall", "0", "proportional_float" );
  174. CPanelAnimationVar( Color, m_DeltaPositiveColor, "PositiveColor", "0 255 0 255" );
  175. CPanelAnimationVar( Color, m_DeltaNegativeColor, "NegativeColor", "255 0 0 255" );
  176. CPanelAnimationVar( Color, m_DeltaEventColor, "EventColor", "255 0 255 255" );
  177. CPanelAnimationVar( Color, m_DeltaRedRobotScoreColor, "RedRobotScoreColor", "255 0 0 255" );
  178. CPanelAnimationVar( Color, m_DeltaBlueRobotScoreColor, "BlueRobotScoreColor", "0 166 255 255" );
  179. CPanelAnimationVar( float, m_flDeltaLifetime, "delta_lifetime", "2.0" );
  180. CPanelAnimationVar( vgui::HFont, m_hDeltaItemFont, "delta_item_font", "Default" );
  181. CPanelAnimationVar( vgui::HFont, m_hDeltaItemFontBig, "delta_item_font_big", "Default" );
  182. };
  183. //-----------------------------------------------------------------------------
  184. // Purpose:
  185. //-----------------------------------------------------------------------------
  186. class CHudAccountPanel : public CHudElement, public CAccountPanel
  187. {
  188. DECLARE_CLASS_SIMPLE( CHudAccountPanel, CAccountPanel );
  189. public:
  190. CHudAccountPanel( const char *pElementName )
  191. : CHudElement( pElementName )
  192. , CAccountPanel( NULL, pElementName )
  193. {
  194. Panel *pParent = g_pClientMode->GetViewport();
  195. SetParent( pParent );
  196. SetHiddenBits( HIDEHUD_MISCSTATUS );
  197. ListenForGameEvent( "player_account_changed" );
  198. }
  199. virtual void LevelInit( void ) OVERRIDE
  200. {
  201. CHudElement::LevelInit();
  202. }
  203. virtual void FireGameEvent( IGameEvent *event ) OVERRIDE
  204. {
  205. const char * type = event->GetName();
  206. if ( Q_strcmp(type, "player_account_changed") == 0 )
  207. {
  208. int iOldValue = event->GetInt( "old_account" );
  209. int iNewValue = event->GetInt( "new_account" );
  210. account_delta_t::eAccountDeltaType_t deltaType = ( iNewValue - iOldValue >= 0 ) ? account_delta_t::ACCOUNT_DELTA_HEALING
  211. : account_delta_t::ACCOUNT_DELTA_DAMAGE;
  212. OnAccountValueChanged( iOldValue, iNewValue, deltaType );
  213. }
  214. else
  215. {
  216. CHudElement::FireGameEvent( event );
  217. }
  218. }
  219. virtual bool ShouldDraw( void ) OVERRIDE
  220. {
  221. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  222. if ( !pPlayer || !pPlayer->IsAlive() || !pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) )
  223. {
  224. m_AccountDeltaItems.RemoveAll();
  225. return false;
  226. }
  227. CTFPlayer *pTFPlayer = CTFPlayer::GetLocalTFPlayer();
  228. if ( pTFPlayer && pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
  229. return false;
  230. if ( CTFMinigameLogic::GetMinigameLogic() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame() )
  231. return false;
  232. if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
  233. return false;
  234. return CHudElement::ShouldDraw();
  235. }
  236. };
  237. DECLARE_HUDELEMENT( CHudAccountPanel );
  238. // Derived account panels
  239. //-----------------------------------------------------------------------------
  240. // Purpose:
  241. //-----------------------------------------------------------------------------
  242. class CHealthAccountPanel : public CHudAccountPanel
  243. {
  244. DECLARE_CLASS_SIMPLE( CHealthAccountPanel, CHudAccountPanel );
  245. public:
  246. CHealthAccountPanel( const char *pElementName ) : CHudAccountPanel(pElementName)
  247. {
  248. ListenForGameEvent( "player_healonhit" );
  249. }
  250. virtual const char *GetResFileName( void ) { return "resource/UI/HudHealthAccount.res"; }
  251. void FireGameEvent( IGameEvent *event )
  252. {
  253. const char * type = event->GetName();
  254. if ( Q_strcmp(type, "player_healonhit") == 0 )
  255. {
  256. int iAmount = event->GetInt( "amount" );
  257. int iPlayer = event->GetInt( "entindex" );
  258. CTFPlayer *pEventPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
  259. if ( pEventPlayer && !pEventPlayer->IsDormant() )
  260. {
  261. if ( pEventPlayer == C_TFPlayer::GetLocalTFPlayer() )
  262. {
  263. OnAccountValueChanged( 0, iAmount, account_delta_t::ACCOUNT_DELTA_HEALING );
  264. }
  265. else
  266. {
  267. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  268. if ( pLocalPlayer )
  269. {
  270. bool bEnemySpy = !pLocalPlayer->InSameTeam( pEventPlayer ) && pEventPlayer->IsPlayerClass( TF_CLASS_SPY );
  271. bool bSneakyEnemySpy = bEnemySpy && ( pEventPlayer->m_Shared.IsStealthed() || pLocalPlayer->m_Shared.IsSpyDisguisedAsMyTeam( pEventPlayer ) );
  272. bool bShouldSpawnRedParticle = ( pEventPlayer->GetTeamNumber() == TF_TEAM_RED );
  273. if ( bSneakyEnemySpy )
  274. {
  275. bShouldSpawnRedParticle = ( GetLocalPlayerTeam() == TF_TEAM_RED );
  276. }
  277. const char *pEffectName;
  278. if ( iAmount < 0 )
  279. {
  280. pEffectName = bShouldSpawnRedParticle ? "healthlost_red" : "healthlost_blu";
  281. }
  282. else if ( iAmount >= 100 )
  283. {
  284. if ( pEventPlayer->IsMiniBoss() )
  285. {
  286. pEffectName = bShouldSpawnRedParticle ? "healthgained_red_giant" : "healthgained_blu_giant";
  287. }
  288. else
  289. {
  290. pEffectName = bShouldSpawnRedParticle ? "healthgained_red_large" : "healthgained_blu_large";
  291. }
  292. }
  293. else
  294. {
  295. pEffectName = bShouldSpawnRedParticle ? "healthgained_red" : "healthgained_blu";
  296. }
  297. pEventPlayer->ParticleProp()->Create( pEffectName, PATTACH_POINT, "head" );
  298. }
  299. }
  300. }
  301. }
  302. else
  303. {
  304. CHudElement::FireGameEvent( event );
  305. }
  306. }
  307. //-----------------------------------------------------------------------------
  308. // Purpose:
  309. //-----------------------------------------------------------------------------
  310. bool ShouldDraw( void )
  311. {
  312. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  313. if ( !pPlayer || !pPlayer->IsAlive() )
  314. {
  315. m_AccountDeltaItems.RemoveAll();
  316. }
  317. if ( !m_AccountDeltaItems.Count() )
  318. return false;
  319. if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
  320. return false;
  321. if ( CTFMinigameLogic::GetMinigameLogic() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame() )
  322. return false;
  323. if ( TFGameRules() && TFGameRules()->ShowMatchSummary() )
  324. return false;
  325. return CHudElement::ShouldDraw();
  326. }
  327. };
  328. DECLARE_HUDELEMENT( CHealthAccountPanel );
  329. class CScoreAccountPanel : public CAccountPanel, public CGameEventListener
  330. {
  331. DECLARE_CLASS_SIMPLE( CScoreAccountPanel, CAccountPanel );
  332. public:
  333. CScoreAccountPanel( Panel *parent, const char *name )
  334. : CAccountPanel( parent, name )
  335. , m_nTeam( TF_TEAM_COUNT )
  336. {}
  337. virtual const char *GetResFileName( void ) { return "resource/UI/HudScoreAccount.res"; }
  338. virtual void FireGameEvent( IGameEvent *event ) OVERRIDE
  339. {
  340. const char * pszEventName = event->GetName();
  341. if ( Q_strcmp(pszEventName, m_pszEventName) == 0 )
  342. {
  343. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  344. if ( !pLocalPlayer || !pLocalPlayer->IsAlive() )
  345. return;
  346. int nTeam = event->GetInt( "team" );
  347. if ( m_nTeam != nTeam )
  348. return;
  349. const int nPoints = event->GetInt( "points" );
  350. if ( !nPoints )
  351. return;
  352. account_delta_t* pNewAccount = OnAccountValueChanged( 0, nPoints, account_delta_t::ACCOUNT_DELTA_BONUS_POINTS );
  353. if ( pNewAccount )
  354. {
  355. pNewAccount->m_bShadows = true;
  356. pNewAccount->m_flBatchWindow = pNewAccount->m_flDieTime;
  357. pNewAccount->m_nSourceID = (( nPoints > 0 ) ? 0 : 1 ) + (nTeam * 2);
  358. if ( ( GetLocalPlayerTeam() == nTeam && nPoints > 0 ) || ( GetLocalPlayerTeam() != nTeam && nPoints < 0 ) )
  359. {
  360. pNewAccount->m_color = m_DeltaPositiveColor;
  361. }
  362. else
  363. {
  364. pNewAccount->m_color = m_DeltaNegativeColor;
  365. }
  366. }
  367. }
  368. }
  369. void ApplySettings( KeyValues *inResourceData )
  370. {
  371. BaseClass::ApplySettings( inResourceData );
  372. Q_strncpy( m_pszEventName, inResourceData->GetString( "event" ), sizeof( m_pszEventName ) );
  373. if ( m_pszEventName )
  374. {
  375. ListenForGameEvent( m_pszEventName );
  376. }
  377. const char *pszTeam = inResourceData->GetString( "team" );
  378. if ( Q_stricmp( pszTeam, "red" ) == 0 )
  379. {
  380. m_nTeam = TF_TEAM_RED;
  381. }
  382. else
  383. {
  384. m_nTeam = TF_TEAM_BLUE;
  385. }
  386. }
  387. private:
  388. char m_pszEventName[32]; // max length of event names
  389. int m_nTeam;
  390. };
  391. DECLARE_BUILD_FACTORY( CScoreAccountPanel );
  392. //-----------------------------------------------------------------------------
  393. // Purpose:
  394. //-----------------------------------------------------------------------------
  395. class CDamageAccountPanel : public CHudAccountPanel
  396. {
  397. DECLARE_CLASS_SIMPLE( CDamageAccountPanel, CHudAccountPanel );
  398. public:
  399. CDamageAccountPanel( const char *pElementName ) : CHudAccountPanel(pElementName)
  400. {
  401. ListenForGameEvent( "player_hurt" );
  402. ListenForGameEvent( "npc_hurt" );
  403. ListenForGameEvent( "player_healed" );
  404. ListenForGameEvent( "player_bonuspoints" );
  405. ListenForGameEvent( "building_healed" );
  406. ResetDamageVars();
  407. vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
  408. }
  409. virtual void OnTick( void );
  410. virtual const char *GetResFileName( void ) { return "resource/UI/HudDamageAccount.res"; }
  411. virtual void Paint( void );
  412. //-----------------------------------------------------------------------------
  413. // Purpose:
  414. //-----------------------------------------------------------------------------
  415. bool ShouldDrawDPSMeter( void )
  416. {
  417. return hud_damagemeter.GetBool();
  418. }
  419. //-----------------------------------------------------------------------------
  420. // Purpose:
  421. //-----------------------------------------------------------------------------
  422. void DisplayDamageFeedback( CTFPlayer *pAttacker, CBaseCombatCharacter *pVictim, int iDamage, int iHealth, bool bIsCrit )
  423. {
  424. if ( iDamage <= 0 ) // zero value (invuln?)
  425. return;
  426. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  427. if ( !pLocalPlayer || !pLocalPlayer->IsAlive() )
  428. return;
  429. if ( !pAttacker || !pVictim )
  430. return;
  431. // Show the attacker, or when healing the player that is
  432. if ( ( pAttacker == pLocalPlayer ) ||
  433. ( pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && ( pLocalPlayer->MedicGetHealTarget() == pAttacker ) ) )
  434. {
  435. bool bDeadRingerSpy = false;
  436. C_TFPlayer *pVictimPlayer = ToTFPlayer( pVictim );
  437. if ( pVictimPlayer )
  438. {
  439. // Player hurt self
  440. if ( pAttacker == pVictimPlayer )
  441. return;
  442. // Don't show damage on stealthed and/or disguised enemy spies
  443. if ( pVictimPlayer->IsPlayerClass( TF_CLASS_SPY ) && pVictimPlayer->GetTeamNumber() != pLocalPlayer->GetTeamNumber() )
  444. {
  445. CTFWeaponInvis *pWpn = (CTFWeaponInvis *)pVictimPlayer->Weapon_OwnsThisID( TF_WEAPON_INVIS );
  446. if ( pWpn && pWpn->HasFeignDeath() )
  447. {
  448. if ( pVictimPlayer->m_Shared.IsFeignDeathReady() )
  449. {
  450. bDeadRingerSpy = true;
  451. }
  452. }
  453. if ( !bDeadRingerSpy )
  454. {
  455. if ( pVictimPlayer->m_Shared.GetDisguiseTeam() == pLocalPlayer->GetTeamNumber() || pVictimPlayer->m_Shared.IsStealthed() )
  456. return;
  457. }
  458. }
  459. }
  460. if ( pAttacker == pLocalPlayer )
  461. {
  462. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "DamagedPlayer" );
  463. bool bHitEnabled = ( tf_dingalingaling.GetBool() );
  464. bool bLastHitEnabled = ( tf_dingalingaling_lasthit.GetBool() );
  465. bool bLastHit = ( iHealth <= 0 ) || bDeadRingerSpy;
  466. if ( bLastHitEnabled && bLastHit )
  467. {
  468. // Always allow the last hit sound
  469. m_flLastDingTime = 0.f;
  470. }
  471. // Play hitbeeps
  472. if ( ( bHitEnabled || bLastHitEnabled ) &&
  473. ( gpGlobals->curtime > ( m_flLastDingTime + tf_dingalingaling_repeat_delay.GetFloat() ) || tf_dingalingaling_repeat_delay.GetFloat() == 0.f ) )
  474. {
  475. m_flLastDingTime = gpGlobals->curtime;
  476. CSoundParameters params;
  477. CLocalPlayerFilter filter;
  478. const char *pszSound = NULL;
  479. const hitsound_params_t *pHitSound = NULL;
  480. if ( bLastHit && bLastHitEnabled )
  481. {
  482. pszSound = g_LastHitSounds[tf_dingalingaling_last_effect.GetInt()].m_pszName;
  483. pHitSound = &g_LastHitSounds[tf_dingalingaling_last_effect.GetInt()];
  484. if ( pszSound && pHitSound && CBaseEntity::GetParametersForSound( pszSound, params, NULL ) )
  485. {
  486. EmitSound_t es( params );
  487. es.m_nPitch = pHitSound->GetPitchFromDamage( iDamage, bLastHit );
  488. es.m_flVolume = tf_dingaling_lasthit_volume.GetFloat();
  489. pLocalPlayer->EmitSound( filter, pLocalPlayer->entindex(), es );
  490. }
  491. }
  492. else if ( bHitEnabled )
  493. {
  494. pszSound = g_HitSounds[tf_dingalingaling_effect.GetInt()].m_pszName;
  495. pHitSound = &g_HitSounds[tf_dingalingaling_effect.GetInt()];
  496. if ( pszSound && pHitSound && CBaseEntity::GetParametersForSound( pszSound, params, NULL ) )
  497. {
  498. EmitSound_t es( params );
  499. es.m_nPitch = pHitSound->GetPitchFromDamage( iDamage, false );
  500. es.m_flVolume = tf_dingaling_volume.GetFloat();
  501. pLocalPlayer->EmitSound( filter, pLocalPlayer->entindex(), es );
  502. }
  503. }
  504. }
  505. }
  506. if ( hud_combattext.GetBool() )
  507. {
  508. // Ignore damage events on targets that we can't see, so it's not a cheat
  509. trace_t tr;
  510. UTIL_TraceLine( pVictim->WorldSpaceCenter(), MainViewOrigin(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  511. if ( tr.fraction >= 1.f )
  512. {
  513. account_delta_t *pNewAccount = OnAccountValueChanged( 0, -iDamage, account_delta_t::ACCOUNT_DELTA_DAMAGE );
  514. if ( pNewAccount )
  515. {
  516. Vector vecPos = pVictim->GetAbsOrigin();
  517. Vector vecDistance = vecPos - pLocalPlayer->GetAbsOrigin();
  518. int nHeightoffset = RemapValClamped( vecDistance.LengthSqr(), 0.0f, (200.0f * 200.0f), 1, 16 );
  519. vecPos.z += (VEC_HULL_MAX_SCALED( pVictim ).z + nHeightoffset);
  520. pNewAccount->m_nX = vecPos.x;
  521. pNewAccount->m_nXEnd = pNewAccount->m_nX;
  522. pNewAccount->m_nY = vecPos.y;
  523. pNewAccount->m_nHStart = vecPos.z;
  524. pNewAccount->m_nHEnd = pNewAccount->m_nHStart + 32; // How many units to float up
  525. pNewAccount->m_bWorldSpace = true;
  526. pNewAccount->m_nSourceID = pVictim->entindex();
  527. pNewAccount->m_flBatchWindow = hud_combattext_batching.GetBool() ? hud_combattext_batching_window.GetFloat() : 0.f;
  528. pNewAccount->m_bLargeFont = bIsCrit;
  529. // V_swprintf_safe( pNewAccount->m_wzText, L" (%d)", m_nQueuedDamageEvents );
  530. }
  531. }
  532. }
  533. m_flLastDamageEventTime = gpGlobals->curtime;
  534. // Damage meter tracking
  535. if ( hud_damagemeter_period.GetFloat() > 0.f )
  536. {
  537. // Store events and average across a sliding window
  538. DamageHistory_t damage = { (float)iDamage, m_flLastDamageEventTime };
  539. m_DamageHistory.AddToTail( damage );
  540. }
  541. else
  542. {
  543. // Running tally until we hit the out-of-combat timer
  544. if ( m_flFirstDamageEventTime == 0.f )
  545. {
  546. m_flFirstDamageEventTime = m_flLastDamageEventTime;
  547. m_flDamagePerSecond = 0.f;
  548. m_flDamageMeterTotal = 0.f;
  549. }
  550. m_flDamageMeterTotal += iDamage;
  551. }
  552. }
  553. }
  554. //-----------------------------------------------------------------------------
  555. // Purpose:
  556. //-----------------------------------------------------------------------------
  557. void FireGameEvent( IGameEvent *event )
  558. {
  559. if ( FStrEq( event->GetName(), "player_hurt" ) )
  560. {
  561. const int iDamage = event->GetInt( "damageamount" );
  562. const int iHealth = event->GetInt( "health" );
  563. const int iAttacker = engine->GetPlayerForUserID( event->GetInt( "attacker" ) );
  564. C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( iAttacker ) );
  565. const int iVictim = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  566. C_TFPlayer *pVictim = ToTFPlayer( UTIL_PlayerByIndex( iVictim ) );
  567. DisplayDamageFeedback( pAttacker, pVictim, iDamage, iHealth, event->GetBool( "crit", 0 ) );
  568. }
  569. else if ( FStrEq( event->GetName(), "npc_hurt" ) )
  570. {
  571. const int iDamage = event->GetInt( "damageamount" );
  572. const int iHealth = event->GetInt( "health" );
  573. const int iAttacker = engine->GetPlayerForUserID( event->GetInt( "attacker_player" ) );
  574. C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( iAttacker ) );
  575. C_BaseCombatCharacter *pVictim = (C_BaseCombatCharacter *)ClientEntityList().GetClientEntity( event->GetInt( "entindex" ) );
  576. DisplayDamageFeedback( pAttacker, pVictim, iDamage, iHealth, event->GetBool( "crit", 0 ) );
  577. }
  578. else if ( FStrEq( event->GetName(), "player_healed" ) )
  579. {
  580. if ( hud_combattext.GetBool() && hud_combattext_healing.GetBool() )
  581. {
  582. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  583. if ( !pLocalPlayer || !pLocalPlayer->IsAlive() )
  584. return;
  585. const int iHealer = engine->GetPlayerForUserID( event->GetInt( "healer" ) );
  586. CBasePlayer *pHealer = UTIL_PlayerByIndex( iHealer );
  587. if ( pHealer && pHealer == pLocalPlayer )
  588. {
  589. const int iPatient = engine->GetPlayerForUserID( event->GetInt( "patient" ) );
  590. CBasePlayer *pPatient = UTIL_PlayerByIndex( iPatient );
  591. if ( pPatient )
  592. {
  593. const int iHealedAmt = event->GetInt( "amount" );
  594. account_delta_t *pNewAccount = OnAccountValueChanged( 0, iHealedAmt, account_delta_t::ACCOUNT_DELTA_HEALING );
  595. if ( pNewAccount )
  596. {
  597. Vector vecPos = pPatient->GetAbsOrigin();
  598. Vector vecDistance = vecPos - pLocalPlayer->GetAbsOrigin();
  599. int nHeightoffset = RemapValClamped( vecDistance.LengthSqr(), 0.0f, (200.0f * 200.0f), 1, 16 );
  600. vecPos.z += ( VEC_HULL_MAX_SCALED( pPatient ).z + nHeightoffset );
  601. pNewAccount->m_nX = vecPos.x;
  602. pNewAccount->m_nXEnd = pNewAccount->m_nX;
  603. pNewAccount->m_nY = vecPos.y;
  604. pNewAccount->m_nHStart = vecPos.z;
  605. pNewAccount->m_nHEnd = pNewAccount->m_nHStart + 32; // Float 32 units up in worldspace
  606. pNewAccount->m_bWorldSpace = true;
  607. }
  608. }
  609. }
  610. }
  611. }
  612. else if ( FStrEq( event->GetName(), "player_bonuspoints" ) )
  613. {
  614. if ( hud_combattext.GetBool() )
  615. {
  616. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  617. if ( !pLocalPlayer || !pLocalPlayer->IsAlive() )
  618. return;
  619. const int nPoints = ( event->GetInt( "points" ) / 10 );
  620. if ( !nPoints )
  621. return;
  622. const int iPlayer = event->GetInt( "player_entindex" );
  623. CBasePlayer *pPlayer = UTIL_PlayerByIndex( iPlayer );
  624. if ( pPlayer && pPlayer == pLocalPlayer )
  625. {
  626. const int iSource = event->GetInt( "source_entindex" );
  627. C_BaseEntity *pSource = ClientEntityList().GetBaseEntity( iSource );
  628. if ( !pSource )
  629. return;
  630. account_delta_t *pNewAccount = OnAccountValueChanged( 0, nPoints, account_delta_t::ACCOUNT_DELTA_BONUS_POINTS );
  631. if ( pNewAccount )
  632. {
  633. Vector vecPos = pSource->GetAbsOrigin();
  634. Vector vecDistance = vecPos - pLocalPlayer->GetAbsOrigin();
  635. int nHeightoffset = RemapValClamped( vecDistance.LengthSqr(), 0.0f, (200.0f * 200.0f), 1, 16 );
  636. vecPos.z += ( pSource->IsPlayer() ) ? (VEC_HULL_MAX_SCALED( pSource->GetBaseAnimating() ).z + nHeightoffset) : 0;
  637. pNewAccount->m_nX = vecPos.x;
  638. pNewAccount->m_nXEnd = pNewAccount->m_nX;
  639. pNewAccount->m_nY = vecPos.y;
  640. pNewAccount->m_nHStart = vecPos.z;
  641. pNewAccount->m_nHEnd = pNewAccount->m_nHStart + 16;
  642. pNewAccount->m_bWorldSpace = true;
  643. }
  644. }
  645. }
  646. }
  647. else if ( FStrEq( event->GetName(), "building_healed" ) )
  648. {
  649. if ( !hud_combattext.GetBool() || !hud_combattext_healing.GetBool() )
  650. return;
  651. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  652. if ( !pLocalPlayer || !pLocalPlayer->IsAlive() )
  653. return;
  654. CBaseEntity *pHealer = ClientEntityList().GetEnt( event->GetInt( "healer" ) );
  655. if ( pHealer && pHealer == pLocalPlayer )
  656. {
  657. CBaseEntity *pBuilding = ClientEntityList().GetEnt( event->GetInt( "building" ) );
  658. if ( !pBuilding )
  659. return;
  660. const int iHealedAmt = event->GetInt( "amount" );
  661. account_delta_t *pNewAccount = OnAccountValueChanged( 0, iHealedAmt, account_delta_t::ACCOUNT_DELTA_HEALING );
  662. if ( pNewAccount )
  663. {
  664. Vector vecPos = pBuilding->GetAbsOrigin();
  665. Vector vecDistance = vecPos - pLocalPlayer->GetAbsOrigin();
  666. int nHeightoffset = RemapValClamped( vecDistance.LengthSqr(), 0.0f, (200.0f * 200.0f), 1, 16 );
  667. vecPos.z += ( 64 + nHeightoffset );
  668. pNewAccount->m_nX = vecPos.x;
  669. pNewAccount->m_nXEnd = pNewAccount->m_nX;
  670. pNewAccount->m_nY = vecPos.y;
  671. pNewAccount->m_nHStart = vecPos.z;
  672. pNewAccount->m_nHEnd = pNewAccount->m_nHStart + 32; // Float 32 units up in worldspace
  673. pNewAccount->m_bWorldSpace = true;
  674. }
  675. }
  676. }
  677. }
  678. //-----------------------------------------------------------------------------
  679. // Purpose:
  680. //-----------------------------------------------------------------------------
  681. bool ShouldDraw( void )
  682. {
  683. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  684. if ( !pPlayer || !pPlayer->IsAlive() )
  685. {
  686. m_AccountDeltaItems.RemoveAll();
  687. }
  688. if ( ShouldDrawDPSMeter() )
  689. return true;
  690. if ( !m_AccountDeltaItems.Count() )
  691. return false;
  692. return CHudElement::ShouldDraw();
  693. }
  694. //-----------------------------------------------------------------------------
  695. // Purpose: called whenever a new level is starting
  696. //-----------------------------------------------------------------------------
  697. virtual void LevelInit( void ) OVERRIDE
  698. {
  699. ResetDamageVars();
  700. BaseClass::LevelInit();
  701. }
  702. private:
  703. //-----------------------------------------------------------------------------
  704. // Purpose:
  705. //-----------------------------------------------------------------------------
  706. void ResetDamageVars( void )
  707. {
  708. m_flFirstDamageEventTime = 0.f;
  709. m_flLastDamageEventTime = 0.f;
  710. m_flDamagePerSecond = 0.f;
  711. m_flDamageMeterTotal = 0.f;
  712. m_flLastDingTime = 0.f;
  713. }
  714. private:
  715. // DamageMeter
  716. float m_flFirstDamageEventTime;
  717. float m_flLastDamageEventTime;
  718. float m_flDamagePerSecond;
  719. float m_flDamageMeterTotal;
  720. struct DamageHistory_t
  721. {
  722. float flDamage;
  723. float flDamageTime;
  724. };
  725. CUtlVector< DamageHistory_t > m_DamageHistory;
  726. // Dings
  727. float m_flLastDingTime;
  728. };
  729. //-----------------------------------------------------------------------------
  730. // Purpose:
  731. //-----------------------------------------------------------------------------
  732. void CDamageAccountPanel::OnTick( void )
  733. {
  734. if ( ShouldDrawDPSMeter() )
  735. {
  736. // We're out of combat - nuke everything
  737. if ( m_flLastDamageEventTime < gpGlobals->curtime - hud_damagemeter_ooctimer.GetFloat() )
  738. {
  739. m_DamageHistory.RemoveAll();
  740. m_flFirstDamageEventTime = 0.f;
  741. }
  742. else
  743. {
  744. float flPeriod = hud_damagemeter_period.GetFloat();
  745. // Period-based calculation (averaged across a defined range)
  746. if ( flPeriod > 0.f )
  747. {
  748. m_flDamageMeterTotal = 0.f;
  749. FOR_EACH_VEC_BACK( m_DamageHistory, i )
  750. {
  751. if ( flPeriod > 0.f )
  752. {
  753. // This method averages across a fixed period, so nuke entires outside the period
  754. if ( gpGlobals->curtime - flPeriod > m_DamageHistory[i].flDamageTime )
  755. {
  756. m_DamageHistory.Remove( i );
  757. continue;
  758. }
  759. }
  760. // What's left is within the period (sliding window)
  761. m_flDamageMeterTotal += m_DamageHistory[i].flDamage;
  762. }
  763. m_flDamagePerSecond = m_flDamageMeterTotal / flPeriod;
  764. }
  765. // Event-based calculation (absolute dps)
  766. else if ( m_flFirstDamageEventTime > 0.f )
  767. {
  768. flPeriod = Max( m_flLastDamageEventTime - m_flFirstDamageEventTime, 1.f );
  769. m_flDamagePerSecond = m_flDamageMeterTotal / flPeriod;
  770. }
  771. }
  772. }
  773. }
  774. //-----------------------------------------------------------------------------
  775. // Purpose:
  776. //-----------------------------------------------------------------------------
  777. void CDamageAccountPanel::Paint( void )
  778. {
  779. BaseClass::Paint();
  780. if ( ShouldDrawDPSMeter() )
  781. {
  782. int iScreenWide, iScreenTall;
  783. GetHudSize( iScreenWide, iScreenTall );
  784. int nX = iScreenWide / 1.15;
  785. int nY = iScreenTall / 1.20;
  786. int r = 255, g = 255, b = 255;
  787. if ( m_flLastDamageEventTime < gpGlobals->curtime - hud_damagemeter_ooctimer.GetFloat() )
  788. {
  789. r = 255, g = 0, b = 0;
  790. }
  791. Color cDPS( r, g, b, 255 );
  792. vgui::surface()->DrawSetTextFont( m_hDeltaItemFontBig );
  793. vgui::surface()->DrawSetTextColor( cDPS );
  794. vgui::surface()->DrawSetTextPos( nX, nY );
  795. wchar_t wDPSBuf[20];
  796. V_swprintf_safe( wDPSBuf, L"%d DPS", (int)m_flDamagePerSecond );
  797. vgui::surface()->DrawPrintText( wDPSBuf, wcslen( wDPSBuf ), FONT_DRAW_NONADDITIVE );
  798. }
  799. }
  800. DECLARE_HUDELEMENT( CDamageAccountPanel );
  801. //-----------------------------------------------------------------------------
  802. // Purpose:
  803. //-----------------------------------------------------------------------------
  804. void CAccountPanel::ApplySchemeSettings( IScheme *pScheme )
  805. {
  806. // load control settings...
  807. LoadControlSettings( GetResFileName() );
  808. BaseClass::ApplySchemeSettings( pScheme );
  809. }
  810. void CAccountPanel::ApplySettings( KeyValues *inResourceData )
  811. {
  812. BaseClass::ApplySettings( inResourceData );
  813. // Backwards compatibility. If we DONT find "delta_item_end_x" specified in the keyvalues,
  814. // then just take the starting x-pos as the ending x-pos.
  815. if ( inResourceData->FindKey( "delta_item_end_x" ) == NULL )
  816. {
  817. m_flDeltaItemXEndPos = m_flDeltaItemX;
  818. }
  819. m_bNegativeFlipDir = inResourceData->FindKey( "negative_flip_dir", false );
  820. const char *pszBGTextureName = inResourceData->GetString( "bg_texture", NULL );
  821. if ( m_nBGTexture == -1 && pszBGTextureName && pszBGTextureName[0] )
  822. {
  823. m_nBGTexture = vgui::surface()->CreateNewTextureID();
  824. vgui::surface()->DrawSetTextureFile( m_nBGTexture , pszBGTextureName, true, false);
  825. }
  826. }
  827. //-----------------------------------------------------------------------------
  828. // Purpose:
  829. //-----------------------------------------------------------------------------
  830. account_delta_t *CAccountPanel::OnAccountValueChanged( int iOldValue, int iNewValue, account_delta_t::eAccountDeltaType_t type )
  831. {
  832. // update the account value
  833. SetDialogVariable( "metal", iNewValue );
  834. int iDelta = iNewValue - iOldValue;
  835. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  836. if ( iDelta != 0 && pPlayer && pPlayer->IsAlive() )
  837. {
  838. int index = m_AccountDeltaItems.AddToTail();
  839. account_delta_t *pNewDeltaItem = &m_AccountDeltaItems[index];
  840. pNewDeltaItem->m_flDieTime = gpGlobals->curtime + m_flDeltaLifetime;
  841. pNewDeltaItem->m_iAmount = iDelta;
  842. pNewDeltaItem->m_nX = m_flDeltaItemX;
  843. pNewDeltaItem->m_nXEnd = m_flDeltaItemXEndPos;
  844. pNewDeltaItem->m_nHStart = m_flDeltaItemStartPos;
  845. pNewDeltaItem->m_nHEnd = m_flDeltaItemEndPos;
  846. pNewDeltaItem->m_bWorldSpace = false;
  847. pNewDeltaItem->m_nSourceID = -1;
  848. pNewDeltaItem->m_flBatchWindow = 0.f;
  849. pNewDeltaItem->m_bLargeFont = false;
  850. pNewDeltaItem->m_eDataType = type;
  851. pNewDeltaItem->m_wzText[0] = NULL;
  852. pNewDeltaItem->m_color = GetColor( type );
  853. pNewDeltaItem->m_bShadows = false;
  854. return &m_AccountDeltaItems[index];
  855. }
  856. return NULL;
  857. }
  858. Color CAccountPanel::GetColor( const account_delta_t::eAccountDeltaType_t& type )
  859. {
  860. if ( type == account_delta_t::ACCOUNT_DELTA_BONUS_POINTS )
  861. {
  862. return m_DeltaEventColor;
  863. }
  864. else if ( type == account_delta_t::ACCOUNT_DELTA_HEALING )
  865. {
  866. return m_DeltaPositiveColor;
  867. }
  868. else if ( type == account_delta_t::ACCOUNT_DELTA_DAMAGE )
  869. {
  870. return m_DeltaNegativeColor;
  871. }
  872. else if ( type == account_delta_t::ACCOUNT_DELTA_ROBOT_DESTRUCTION_POINT_BLUE )
  873. {
  874. return m_DeltaBlueRobotScoreColor;
  875. }
  876. else if ( type == account_delta_t::ACCOUNT_DELTA_ROBOT_DESTRUCTION_POINT_RED )
  877. {
  878. return m_DeltaRedRobotScoreColor;
  879. }
  880. return Color( 255, 255, 255, 255 );
  881. }
  882. //-----------------------------------------------------------------------------
  883. // Purpose: Paint the deltas
  884. //-----------------------------------------------------------------------------
  885. void CAccountPanel::Paint( void )
  886. {
  887. BaseClass::Paint();
  888. FOR_EACH_VEC_BACK( m_AccountDeltaItems, i )
  889. {
  890. // Reduce lifetime when count grows too high
  891. float flTimeMod = m_AccountDeltaItems.Count() > NUM_ACCOUNT_DELTA_ITEMS ? RemapValClamped( m_AccountDeltaItems.Count(), 10.f, 15.f, 0.5f, 1.5f ) : 0.f;
  892. // update all the valid delta items
  893. if ( ( m_AccountDeltaItems[i].m_flDieTime - flTimeMod ) > gpGlobals->curtime )
  894. {
  895. // position and alpha are determined from the lifetime
  896. Color c = m_AccountDeltaItems[i].m_color;
  897. float flLifetimePercent = ( m_flDeltaLifetime - ( m_AccountDeltaItems[i].m_flDieTime - gpGlobals->curtime ) ) / m_flDeltaLifetime;
  898. // fade out after half our lifetime
  899. int nAlpha = flLifetimePercent > 0.5 ? (int)( 255.0f * ( ( 0.5f - flLifetimePercent ) / 0.5f ) ) : 255;
  900. c[3] = nAlpha;
  901. // Some items want to be batched together as they're super frequent (i.e. damage events from a flamethrower, or minigun)
  902. if ( m_AccountDeltaItems[i].m_flBatchWindow > 0.f && m_AccountDeltaItems[i].m_nSourceID != -1 && m_AccountDeltaItems.IsValidIndex( i - 1 ) )
  903. {
  904. // If next item is from the same source and too close, merge
  905. float flDelay = m_AccountDeltaItems[i].m_flBatchWindow;
  906. if ( m_AccountDeltaItems[i].m_flDieTime - m_AccountDeltaItems[i-1].m_flDieTime <= flDelay &&
  907. m_AccountDeltaItems[i-1].m_nSourceID == m_AccountDeltaItems[i].m_nSourceID )
  908. {
  909. m_AccountDeltaItems[i].m_iAmount += m_AccountDeltaItems[i-1].m_iAmount;
  910. m_AccountDeltaItems.Remove( i - 1 );
  911. continue;
  912. }
  913. }
  914. float flHeight = m_AccountDeltaItems[i].m_nHEnd - m_AccountDeltaItems[i].m_nHStart;
  915. float flWidth = m_AccountDeltaItems[i].m_nXEnd - m_AccountDeltaItems[i].m_nX;
  916. // We can be told to go the opposite direction if we're negative
  917. if ( m_bNegativeFlipDir && m_AccountDeltaItems[i].m_iAmount < 0 )
  918. {
  919. flHeight = -flHeight;
  920. flWidth = -flWidth;
  921. }
  922. float flYPos = m_AccountDeltaItems[i].m_nHStart + ( flLifetimePercent * flHeight );
  923. float flXPos = m_AccountDeltaItems[i].m_nX + ( flLifetimePercent * flWidth );
  924. if ( m_AccountDeltaItems[i].m_bWorldSpace )
  925. {
  926. Vector vecWorld( m_AccountDeltaItems[i].m_nX, m_AccountDeltaItems[i].m_nY, flYPos );
  927. int iX,iY;
  928. if ( !GetVectorInHudSpace( vecWorld, iX, iY ) ) // Tested - NOT GetVectorInScreenSpace
  929. continue;
  930. flXPos = iX;
  931. flYPos = iY;
  932. }
  933. // If we have a background texture, then draw it!
  934. if ( m_nBGTexture != -1 )
  935. {
  936. vgui::surface()->DrawSetColor(255,255,255,nAlpha);
  937. vgui::surface()->DrawSetTexture(m_nBGTexture);
  938. vgui::surface()->DrawTexturedRect( flXPos + m_flBGImageX, flYPos + m_flBGImageY, flXPos + m_flBGImageX + m_flBGImageWide, flYPos + m_flBGImageY + m_flBGImageTall );
  939. }
  940. wchar_t wBuf[20];
  941. if ( m_AccountDeltaItems[i].m_iAmount > 0 )
  942. {
  943. V_swprintf_safe( wBuf, L"+%d", m_AccountDeltaItems[i].m_iAmount );
  944. }
  945. else
  946. {
  947. V_swprintf_safe( wBuf, L"%d", m_AccountDeltaItems[i].m_iAmount );
  948. }
  949. // Append?
  950. if ( m_AccountDeltaItems[i].m_wzText[0] )
  951. {
  952. wchar_t wAppend[8] = { 0 };
  953. V_swprintf_safe( wAppend, L"%ls", m_AccountDeltaItems[i].m_wzText );
  954. V_wcscat_safe( wBuf, wAppend );
  955. }
  956. if ( m_AccountDeltaItems[i].m_bLargeFont )
  957. {
  958. vgui::surface()->DrawSetTextFont( m_hDeltaItemFontBig );
  959. }
  960. else
  961. {
  962. vgui::surface()->DrawSetTextFont( m_hDeltaItemFont );
  963. }
  964. // If we're supposed to have shadows, then draw the text as black and offset a bit first.
  965. // Things get ugly as we approach 0 alpha, so stop drawing the shadow a bit early.
  966. if ( m_AccountDeltaItems[i].m_bShadows && c[3] > 10 )
  967. {
  968. vgui::surface()->DrawSetTextPos( (int)flXPos + XRES(1), (int)flYPos + YRES(1) );
  969. vgui::surface()->DrawSetTextColor( COLOR_BLACK );
  970. vgui::surface()->DrawPrintText( wBuf, wcslen(wBuf), FONT_DRAW_NONADDITIVE );
  971. }
  972. vgui::surface()->DrawSetTextPos( (int)flXPos, (int)flYPos );
  973. vgui::surface()->DrawSetTextColor( c );
  974. vgui::surface()->DrawPrintText( wBuf, wcslen(wBuf), FONT_DRAW_NONADDITIVE );
  975. }
  976. else
  977. {
  978. m_AccountDeltaItems.Remove( i );
  979. }
  980. }
  981. }
  982. #ifdef STAGING_ONLY
  983. //-----------------------------------------------------------------------------
  984. // Purpose:
  985. //-----------------------------------------------------------------------------
  986. class CBountyAccountPanel : public CHudAccountPanel
  987. {
  988. DECLARE_CLASS_SIMPLE( CBountyAccountPanel, CHudAccountPanel );
  989. public:
  990. CBountyAccountPanel( const char *pElementName ) : CHudAccountPanel( pElementName )
  991. {
  992. }
  993. virtual const char *GetResFileName( void ) { return "resource/UI/HudDamageAccount.res"; }
  994. //-----------------------------------------------------------------------------
  995. // Purpose:
  996. //-----------------------------------------------------------------------------
  997. void Paint( void )
  998. {
  999. if ( TFGameRules() && ( !TFGameRules()->IsBountyMode() || TFGameRules()->IsMannVsMachineMode() ) )
  1000. return;
  1001. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  1002. if ( !pPlayer )
  1003. return;
  1004. int iScreenWide, iScreenTall;
  1005. GetHudSize( iScreenWide, iScreenTall );
  1006. int nX = iScreenWide * 0.06f;
  1007. int nY = iScreenTall * 0.97f;
  1008. Color cDPS( 25, 255, 25, 255 );
  1009. vgui::surface()->DrawSetTextFont( m_hDeltaItemFontBig );
  1010. vgui::surface()->DrawSetTextColor( cDPS );
  1011. vgui::surface()->DrawSetTextPos( nX, nY );
  1012. m_nCurrency = pPlayer->GetCurrency();
  1013. wchar_t wCurrency[20];
  1014. V_swprintf_safe( wCurrency, L"$%d", m_nCurrency );
  1015. vgui::surface()->DrawPrintText( wCurrency, wcslen( wCurrency ), FONT_DRAW_NONADDITIVE );
  1016. if ( pPlayer->GetCurrency() != m_nCurrency )
  1017. {
  1018. pPlayer->EmitSound( "Credits.Updated" );
  1019. }
  1020. }
  1021. //-----------------------------------------------------------------------------
  1022. // Purpose:
  1023. //-----------------------------------------------------------------------------
  1024. bool ShouldDraw( void )
  1025. {
  1026. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  1027. if ( !pPlayer || !pPlayer->IsAlive() )
  1028. return false;
  1029. if ( TFGameRules() && ( !TFGameRules()->IsBountyMode() || TFGameRules()->IsMannVsMachineMode() ) )
  1030. return false;
  1031. return CHudElement::ShouldDraw();
  1032. }
  1033. private:
  1034. int m_nCurrency;
  1035. };
  1036. DECLARE_HUDELEMENT( CBountyAccountPanel );
  1037. #endif // STAGING_ONLY