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.

485 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Draws CSPort's death notices
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "hudelement.h"
  9. #include "hud_macros.h"
  10. #include "c_playerresource.h"
  11. #include "iclientmode.h"
  12. #include <vgui_controls/Controls.h>
  13. #include <vgui_controls/Panel.h>
  14. #include <vgui/ISurface.h>
  15. #include <vgui/ILocalize.h>
  16. #include <KeyValues.h>
  17. #include "c_baseplayer.h"
  18. #include "c_team.h"
  19. #include "cs_shareddefs.h"
  20. #include "clientmode_csnormal.h"
  21. #include "c_cs_player.h"
  22. #include "c_cs_playerresource.h"
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. const int DOMINATION_DRAW_HEIGHT = 20;
  26. const int DOMINATION_DRAW_WIDTH = 20;
  27. static ConVar hud_deathnotice_time( "hud_deathnotice_time", "6", 0 );
  28. // Player entries in a death notice
  29. struct DeathNoticePlayer
  30. {
  31. char szName[MAX_PLAYER_NAME_LENGTH];
  32. char szClan[MAX_CLAN_TAG_LENGTH];
  33. int iEntIndex;
  34. Color color;
  35. };
  36. // Contents of each entry in our list of death notices
  37. struct DeathNoticeItem
  38. {
  39. DeathNoticePlayer Killer;
  40. DeathNoticePlayer Victim;
  41. CHudTexture *iconDeath;
  42. int iSuicide;
  43. float flDisplayTime;
  44. bool bHeadshot;
  45. int iDominationImageId;
  46. };
  47. //-----------------------------------------------------------------------------
  48. // Purpose:
  49. //-----------------------------------------------------------------------------
  50. class CHudDeathNotice : public CHudElement, public vgui::Panel
  51. {
  52. DECLARE_CLASS_SIMPLE( CHudDeathNotice, vgui::Panel );
  53. public:
  54. CHudDeathNotice( const char *pElementName );
  55. void Init( void );
  56. void VidInit( void );
  57. virtual bool ShouldDraw( void );
  58. virtual void Paint( void );
  59. virtual void ApplySchemeSettings( vgui::IScheme *scheme );
  60. void RetireExpiredDeathNotices( void );
  61. void FireGameEvent( IGameEvent *event );
  62. protected:
  63. int SetupHudImageId( const char* fname );
  64. private:
  65. CPanelAnimationVarAliasType( float, m_flLineHeight, "LineHeight", "15", "proportional_float" );
  66. CPanelAnimationVar( float, m_flMaxDeathNotices, "MaxDeathNotices", "4" );
  67. CPanelAnimationVar( bool, m_bRightJustify, "RightJustify", "1" );
  68. CPanelAnimationVar( vgui::HFont, m_hTextFont, "TextFont", "HudNumbersTimer" );
  69. CPanelAnimationVar( Color, m_clrCTText, "CTTextColor", "CTTextColor" );
  70. CPanelAnimationVar( Color, m_clrTerroristText, "TerroristTextColor", "TerroristTextColor" );
  71. // Texture for skull symbol
  72. CHudTexture *m_iconD_skull;
  73. CHudTexture *m_iconD_headshot;
  74. int m_iNemesisImageId;
  75. int m_iDominatedImageId;
  76. int m_iRevengeImageId;
  77. Color m_teamColors[TEAM_MAXCOUNT];
  78. CUtlVector<DeathNoticeItem> m_DeathNotices;
  79. };
  80. using namespace vgui;
  81. DECLARE_HUDELEMENT( CHudDeathNotice );
  82. //-----------------------------------------------------------------------------
  83. // Purpose:
  84. //-----------------------------------------------------------------------------
  85. CHudDeathNotice::CHudDeathNotice( const char *pElementName ) :
  86. CHudElement( pElementName ), BaseClass( NULL, "HudDeathNotice" )
  87. {
  88. vgui::Panel *pParent = g_pClientMode->GetViewport();
  89. SetParent( pParent );
  90. m_iconD_headshot = NULL;
  91. m_iconD_skull = NULL;
  92. SetHiddenBits( HIDEHUD_MISCSTATUS );
  93. m_iNemesisImageId = SetupHudImageId("hud/freeze_nemesis");
  94. m_iDominatedImageId = SetupHudImageId("hud/freeze_dominated");
  95. m_iRevengeImageId = SetupHudImageId("hud/freeze_revenge");
  96. }
  97. /**
  98. * Helper function to get an image id and set
  99. */
  100. int CHudDeathNotice::SetupHudImageId( const char* fname )
  101. {
  102. int imageId = surface()->CreateNewTextureID();
  103. surface()->DrawSetTextureFile( imageId, fname, true, false );
  104. return imageId;
  105. }
  106. //-----------------------------------------------------------------------------
  107. // Purpose:
  108. //-----------------------------------------------------------------------------
  109. void CHudDeathNotice::ApplySchemeSettings( IScheme *scheme )
  110. {
  111. BaseClass::ApplySchemeSettings( scheme );
  112. SetPaintBackgroundEnabled( false );
  113. // make team color lookups easier
  114. memset(m_teamColors, 0, sizeof(m_teamColors));
  115. m_teamColors[TEAM_CT] = m_clrCTText;
  116. m_teamColors[TEAM_TERRORIST] = m_clrTerroristText;
  117. }
  118. //-----------------------------------------------------------------------------
  119. // Purpose:
  120. //-----------------------------------------------------------------------------
  121. void CHudDeathNotice::Init( void )
  122. {
  123. ListenForGameEvent( "player_death" );
  124. }
  125. //-----------------------------------------------------------------------------
  126. // Purpose:
  127. //-----------------------------------------------------------------------------
  128. void CHudDeathNotice::VidInit( void )
  129. {
  130. m_iconD_skull = gHUD.GetIcon( "d_skull_cs" );
  131. m_iconD_headshot = gHUD.GetIcon( "d_headshot" );
  132. m_DeathNotices.Purge();
  133. }
  134. //-----------------------------------------------------------------------------
  135. // Purpose: Draw if we've got at least one death notice in the queue
  136. //-----------------------------------------------------------------------------
  137. bool CHudDeathNotice::ShouldDraw( void )
  138. {
  139. C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
  140. if ( !pPlayer )
  141. return false;
  142. // don't show death notices when flashed
  143. if ( pPlayer->IsAlive() && pPlayer->m_flFlashBangTime >= gpGlobals->curtime )
  144. {
  145. float flAlpha = pPlayer->m_flFlashMaxAlpha * (pPlayer->m_flFlashBangTime - gpGlobals->curtime) / pPlayer->m_flFlashDuration;
  146. if ( flAlpha > 75.0f ) // 0..255
  147. {
  148. return false;
  149. }
  150. }
  151. return ( CHudElement::ShouldDraw() && ( m_DeathNotices.Count() ) );
  152. }
  153. //-----------------------------------------------------------------------------
  154. // Purpose:
  155. //-----------------------------------------------------------------------------
  156. void CHudDeathNotice::Paint()
  157. {
  158. if ( !m_iconD_headshot || !m_iconD_skull )
  159. return;
  160. int yStart = GetClientModeCSNormal()->GetDeathMessageStartHeight();
  161. surface()->DrawSetTextFont( m_hTextFont );
  162. surface()->DrawSetTextColor( m_clrCTText );
  163. int dominationDrawWidth = scheme()->GetProportionalScaledValueEx( GetScheme(), DOMINATION_DRAW_WIDTH );
  164. int dominationDrawHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DOMINATION_DRAW_HEIGHT );
  165. int iconHeadshotWide;
  166. int iconHeadshotTall;
  167. if( m_iconD_headshot->bRenderUsingFont )
  168. {
  169. iconHeadshotWide = surface()->GetCharacterWidth( m_iconD_headshot->hFont, m_iconD_headshot->cCharacterInFont );
  170. iconHeadshotTall = surface()->GetFontTall( m_iconD_headshot->hFont );
  171. }
  172. else
  173. {
  174. float scale = ( (float)ScreenHeight() / 480.0f ); //scale based on 640x480
  175. iconHeadshotWide = (int)( scale * (float)m_iconD_headshot->Width() );
  176. iconHeadshotTall = (int)( scale * (float)m_iconD_headshot->Height() );
  177. }
  178. int iCount = m_DeathNotices.Count();
  179. for ( int i = 0; i < iCount; i++ )
  180. {
  181. CHudTexture *icon = m_DeathNotices[i].iconDeath;
  182. if ( !icon )
  183. continue;
  184. wchar_t victim[ 256 ];
  185. wchar_t killer[ 256 ];
  186. wchar_t victimclan[ 256 ];
  187. wchar_t killerclan[ 256 ];
  188. g_pVGuiLocalize->ConvertANSIToUnicode( m_DeathNotices[i].Victim.szName, victim, sizeof( victim ) );
  189. g_pVGuiLocalize->ConvertANSIToUnicode( m_DeathNotices[i].Killer.szName, killer, sizeof( killer ) );
  190. g_pVGuiLocalize->ConvertANSIToUnicode( m_DeathNotices[i].Victim.szClan, victimclan, sizeof( victimclan ) );
  191. g_pVGuiLocalize->ConvertANSIToUnicode( m_DeathNotices[i].Killer.szClan, killerclan, sizeof( killerclan ) );
  192. // Get the local position for this notice
  193. int victimNameLen = UTIL_ComputeStringWidth( m_hTextFont, victim );
  194. int victimClanLen = UTIL_ComputeStringWidth( m_hTextFont, victimclan );
  195. int y = yStart + (m_flLineHeight * i);
  196. int iconWide;
  197. int iconTall;
  198. if( icon->bRenderUsingFont )
  199. {
  200. iconWide = surface()->GetCharacterWidth( icon->hFont, icon->cCharacterInFont );
  201. iconTall = surface()->GetFontTall( icon->hFont );
  202. }
  203. else
  204. {
  205. float scale = ( (float)ScreenHeight() / 480.0f ); //scale based on 640x480
  206. iconWide = (int)( scale * (float)icon->Width() );
  207. iconTall = (int)( scale * (float)icon->Height() );
  208. }
  209. int x = 0;
  210. if ( m_bRightJustify )
  211. {
  212. x = GetWide();
  213. x -= victimNameLen;
  214. x -= victimClanLen;
  215. x -= iconWide;
  216. if ( m_DeathNotices[i].bHeadshot )
  217. x -= iconHeadshotWide;
  218. if ( !m_DeathNotices[i].iSuicide )
  219. {
  220. x -= UTIL_ComputeStringWidth( m_hTextFont, killer );
  221. x -= UTIL_ComputeStringWidth( m_hTextFont, killerclan );
  222. }
  223. if (m_DeathNotices[i].iDominationImageId >= 0)
  224. {
  225. x -= dominationDrawWidth;
  226. }
  227. }
  228. if (m_DeathNotices[i].iDominationImageId >= 0)
  229. {
  230. surface()->DrawSetTexture(m_DeathNotices[i].iDominationImageId);
  231. surface()->DrawSetColor(m_DeathNotices[i].Killer.color);
  232. surface()->DrawTexturedRect( x, y, x + dominationDrawWidth, y + dominationDrawHeight );
  233. x += dominationDrawWidth;
  234. }
  235. // Only draw killers name if it wasn't a suicide
  236. if ( !m_DeathNotices[i].iSuicide )
  237. {
  238. // Draw killer's clan
  239. surface()->DrawSetTextColor( m_DeathNotices[i].Killer.color );
  240. surface()->DrawSetTextPos( x, y );
  241. surface()->DrawSetTextFont( m_hTextFont );
  242. surface()->DrawUnicodeString( killerclan );
  243. surface()->DrawGetTextPos( x, y );
  244. // Draw killer's name
  245. surface()->DrawSetTextColor( m_DeathNotices[i].Killer.color );
  246. surface()->DrawSetTextPos( x, y );
  247. surface()->DrawSetTextFont( m_hTextFont );
  248. surface()->DrawUnicodeString( killer );
  249. surface()->DrawGetTextPos( x, y );
  250. }
  251. // Draw death weapon
  252. //If we're using a font char, this will ignore iconTall and iconWide
  253. Color iconColor( 255, 80, 0, 255 );
  254. icon->DrawSelf( x, y, iconWide, iconTall, iconColor );
  255. x += iconWide;
  256. if( m_DeathNotices[i].bHeadshot )
  257. {
  258. m_iconD_headshot->DrawSelf( x, y, iconHeadshotWide, iconHeadshotTall, iconColor );
  259. x += iconHeadshotWide;
  260. }
  261. // Draw victims clan
  262. surface()->DrawSetTextColor( m_DeathNotices[i].Victim.color );
  263. surface()->DrawSetTextPos( x, y );
  264. surface()->DrawSetTextFont( m_hTextFont ); //reset the font, draw icon can change it
  265. surface()->DrawUnicodeString( victimclan );
  266. surface()->DrawGetTextPos( x, y );
  267. // Draw victims name
  268. surface()->DrawSetTextColor( m_DeathNotices[i].Victim.color );
  269. surface()->DrawSetTextPos( x, y );
  270. surface()->DrawSetTextFont( m_hTextFont ); //reset the font, draw icon can change it
  271. surface()->DrawUnicodeString( victim );
  272. }
  273. // Now retire any death notices that have expired
  274. RetireExpiredDeathNotices();
  275. }
  276. //-----------------------------------------------------------------------------
  277. // Purpose: This message handler may be better off elsewhere
  278. //-----------------------------------------------------------------------------
  279. void CHudDeathNotice::RetireExpiredDeathNotices( void )
  280. {
  281. // Loop backwards because we might remove one
  282. int iSize = m_DeathNotices.Size();
  283. for ( int i = iSize-1; i >= 0; i-- )
  284. {
  285. if ( m_DeathNotices[i].flDisplayTime < gpGlobals->curtime )
  286. {
  287. m_DeathNotices.Remove(i);
  288. }
  289. }
  290. }
  291. //-----------------------------------------------------------------------------
  292. // Purpose: Server's told us that someone's died
  293. //-----------------------------------------------------------------------------
  294. void CHudDeathNotice::FireGameEvent( IGameEvent *event )
  295. {
  296. if (!g_PR)
  297. return;
  298. C_CS_PlayerResource *cs_PR = dynamic_cast<C_CS_PlayerResource *>( g_PR );
  299. if ( !cs_PR )
  300. return;
  301. if ( hud_deathnotice_time.GetFloat() == 0 )
  302. return;
  303. // the event should be "player_death"
  304. int iKiller = engine->GetPlayerForUserID( event->GetInt("attacker") );
  305. int iVictim = engine->GetPlayerForUserID( event->GetInt("userid") );
  306. const char *killedwith = event->GetString( "weapon" );
  307. bool headshot = event->GetInt( "headshot" ) > 0;
  308. char fullkilledwith[128];
  309. if ( killedwith && *killedwith )
  310. {
  311. Q_snprintf( fullkilledwith, sizeof(fullkilledwith), "d_%s", killedwith );
  312. }
  313. else
  314. {
  315. fullkilledwith[0] = 0;
  316. }
  317. // Do we have too many death messages in the queue?
  318. if ( m_DeathNotices.Count() > 0 &&
  319. m_DeathNotices.Count() >= (int)m_flMaxDeathNotices )
  320. {
  321. // Remove the oldest one in the queue, which will always be the first
  322. m_DeathNotices.Remove(0);
  323. }
  324. // Get the names of the players
  325. const char *killer_name = iKiller > 0 ? g_PR->GetPlayerName( iKiller ) : NULL;
  326. const char *victim_name = iVictim > 0 ? g_PR->GetPlayerName( iVictim ) : NULL;
  327. if ( !killer_name )
  328. killer_name = "";
  329. if ( !victim_name )
  330. victim_name = "";
  331. // Get the clan tags of the players
  332. const char *killer_clan = iKiller > 0 ? cs_PR->GetClanTag( iKiller ) : NULL;
  333. const char *victim_clan = iVictim > 0 ? cs_PR->GetClanTag( iVictim ) : NULL;
  334. if ( !killer_clan )
  335. killer_clan = "";
  336. if ( !victim_clan )
  337. victim_clan = "";
  338. // Make a new death notice
  339. DeathNoticeItem deathMsg;
  340. deathMsg.Killer.iEntIndex = iKiller;
  341. deathMsg.Victim.iEntIndex = iVictim;
  342. deathMsg.Killer.color = iKiller > 0 ? m_teamColors[g_PR->GetTeam(iKiller)] : COLOR_WHITE;
  343. deathMsg.Victim.color = iVictim > 0 ? m_teamColors[g_PR->GetTeam(iVictim)] : COLOR_WHITE;
  344. Q_snprintf( deathMsg.Killer.szClan, sizeof( deathMsg.Killer.szClan ), "%s ", killer_clan );
  345. Q_snprintf( deathMsg.Victim.szClan, sizeof( deathMsg.Victim.szClan ), "%s ", victim_clan );
  346. Q_strncpy( deathMsg.Killer.szName, killer_name, MAX_PLAYER_NAME_LENGTH );
  347. Q_strncpy( deathMsg.Victim.szName, victim_name, MAX_PLAYER_NAME_LENGTH );
  348. deathMsg.flDisplayTime = gpGlobals->curtime + hud_deathnotice_time.GetFloat();
  349. deathMsg.iSuicide = ( !iKiller || iKiller == iVictim );
  350. deathMsg.bHeadshot = headshot;
  351. deathMsg.iDominationImageId = -1;
  352. CCSPlayer* pKiller = ToCSPlayer(ClientEntityList().GetBaseEntity(iKiller));
  353. // the local player is dead, see if this is a new nemesis or a revenge
  354. if ( event->GetInt( "dominated" ) > 0 || (pKiller != NULL && pKiller->IsPlayerDominated(iVictim)) )
  355. {
  356. deathMsg.iDominationImageId = m_iDominatedImageId;
  357. }
  358. else if ( event->GetInt( "revenge" ) > 0 )
  359. {
  360. deathMsg.iDominationImageId = m_iRevengeImageId;
  361. }
  362. // Try and find the death identifier in the icon list
  363. deathMsg.iconDeath = gHUD.GetIcon( fullkilledwith );
  364. if ( !deathMsg.iconDeath )
  365. {
  366. // Can't find it, so use the default skull & crossbones icon
  367. deathMsg.iconDeath = m_iconD_skull;
  368. }
  369. // Add it to our list of death notices
  370. m_DeathNotices.AddToTail( deathMsg );
  371. char sDeathMsg[512];
  372. // Record the death notice in the console
  373. if ( deathMsg.iSuicide )
  374. {
  375. if ( !strcmp( fullkilledwith, "d_planted_c4" ) )
  376. {
  377. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s was a bit too close to the c4.\n", deathMsg.Victim.szName );
  378. }
  379. else if ( !strcmp( fullkilledwith, "d_worldspawn" ) )
  380. {
  381. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s died.\n", deathMsg.Victim.szName );
  382. }
  383. else //d_world
  384. {
  385. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s suicided.\n", deathMsg.Victim.szName );
  386. }
  387. }
  388. else
  389. {
  390. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s killed %s", deathMsg.Killer.szName, deathMsg.Victim.szName );
  391. if ( fullkilledwith && *fullkilledwith && (*fullkilledwith > 13 ) )
  392. {
  393. Q_strncat( sDeathMsg, VarArgs( " with %s.\n", fullkilledwith+2 ), sizeof( sDeathMsg ), COPY_ALL_CHARACTERS );
  394. }
  395. }
  396. Msg( "%s", sDeathMsg );
  397. }