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.

920 lines
31 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 <game_controls/baseviewport.h>
  18. #include "clientmode_shared.h"
  19. #include "c_baseplayer.h"
  20. #include "c_team.h"
  21. #include "tf_shareddefs.h"
  22. #include "tf_shareddefs.h"
  23. #include "tf_gamerules.h"
  24. #include "tf_logic_player_destruction.h"
  25. #include "hud_basedeathnotice.h"
  26. // memdbgon must be the last include file in a .cpp file!!!
  27. #include "tier0/memdbgon.h"
  28. static ConVar hud_deathnotice_time( "hud_deathnotice_time", "6", 0 );
  29. using namespace vgui;
  30. //-----------------------------------------------------------------------------
  31. // Purpose:
  32. //-----------------------------------------------------------------------------
  33. CHudBaseDeathNotice::CHudBaseDeathNotice( const char *pElementName ) :
  34. CHudElement( pElementName ), BaseClass( NULL, "HudDeathNotice" )
  35. {
  36. vgui::Panel *pParent = g_pClientMode->GetViewport();
  37. SetParent( pParent );
  38. }
  39. //-----------------------------------------------------------------------------
  40. // Purpose:
  41. //-----------------------------------------------------------------------------
  42. void CHudBaseDeathNotice::ApplySchemeSettings( IScheme *scheme )
  43. {
  44. BaseClass::ApplySchemeSettings( scheme );
  45. SetPaintBackgroundEnabled( false );
  46. CalcRoundedCorners();
  47. }
  48. //-----------------------------------------------------------------------------
  49. // Purpose:
  50. //-----------------------------------------------------------------------------
  51. void CHudBaseDeathNotice::Init( void )
  52. {
  53. ListenForGameEvent( "player_death" );
  54. ListenForGameEvent( "object_destroyed" );
  55. ListenForGameEvent( "teamplay_point_captured" );
  56. ListenForGameEvent( "teamplay_capture_blocked" );
  57. ListenForGameEvent( "teamplay_flag_event" );
  58. ListenForGameEvent( "rd_robot_killed" );
  59. ListenForGameEvent( "special_score" );
  60. ListenForGameEvent( "team_leader_killed" );
  61. }
  62. //-----------------------------------------------------------------------------
  63. // Purpose:
  64. //-----------------------------------------------------------------------------
  65. void CHudBaseDeathNotice::VidInit( void )
  66. {
  67. m_DeathNotices.RemoveAll();
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Purpose: Draw if we've got at least one death notice in the queue
  71. //-----------------------------------------------------------------------------
  72. bool CHudBaseDeathNotice::ShouldDraw( void )
  73. {
  74. return ( CHudElement::ShouldDraw() && ( m_DeathNotices.Count() ) );
  75. }
  76. //-----------------------------------------------------------------------------
  77. // Purpose:
  78. //-----------------------------------------------------------------------------
  79. Color CHudBaseDeathNotice::GetTeamColor( int iTeamNumber, bool bLocalPlayerInvolved /* = false */ )
  80. {
  81. // By default, return the standard team color. Subclasses may override this.
  82. return g_PR->GetTeamColor( iTeamNumber );
  83. }
  84. //-----------------------------------------------------------------------------
  85. // Purpose:
  86. //-----------------------------------------------------------------------------
  87. int CHudBaseDeathNotice::UseExistingNotice( IGameEvent *event )
  88. {
  89. if ( FStrEq( event->GetName(), "special_score" ) )
  90. {
  91. int iIndex = event->GetInt( "player" );
  92. // Look for a matching pre-existing notice.
  93. for ( int i = 0; i < m_DeathNotices.Count(); ++i )
  94. {
  95. DeathNoticeItem &msg = m_DeathNotices[i];
  96. if ( !msg.bSpecialScore )
  97. continue;
  98. if ( msg.iKillerID != iIndex )
  99. continue;
  100. return i;
  101. }
  102. }
  103. return -1;
  104. }
  105. //-----------------------------------------------------------------------------
  106. // Purpose:
  107. //-----------------------------------------------------------------------------
  108. void CHudBaseDeathNotice::Paint()
  109. {
  110. // Retire any death notices that have expired
  111. RetireExpiredDeathNotices();
  112. CBaseViewport *pViewport = dynamic_cast<CBaseViewport *>( GetClientModeNormal()->GetViewport() );
  113. int yStart = pViewport->GetDeathMessageStartHeight();
  114. surface()->DrawSetTextFont( m_hTextFont );
  115. int xMargin = XRES( 10 );
  116. int xSpacing = UTIL_ComputeStringWidth( m_hTextFont, L" " );
  117. int iCount = m_DeathNotices.Count();
  118. for ( int i = 0; i < iCount; i++ )
  119. {
  120. DeathNoticeItem &msg = m_DeathNotices[i];
  121. CHudTexture *icon = msg.iconDeath;
  122. CHudTexture *iconPostKillerName = msg.iconPostKillerName;
  123. CHudTexture *iconPreKillerName = msg.iconPreKillerName;
  124. CHudTexture *iconPostVictimName = msg.iconPostVictimName;
  125. wchar_t victim[256]=L"";
  126. wchar_t killer[256]=L"";
  127. // TEMP - print the death icon name if we don't have a material for it
  128. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Victim.szName, victim, sizeof( victim ) );
  129. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Killer.szName, killer, sizeof( killer ) );
  130. int iVictimTextWide = UTIL_ComputeStringWidth( m_hTextFont, victim ) + xSpacing;
  131. int iDeathInfoTextWide= msg.wzInfoText[0] ? UTIL_ComputeStringWidth( m_hTextFont, msg.wzInfoText ) + xSpacing : 0;
  132. int iDeathInfoEndTextWide= msg.wzInfoTextEnd[0] ? UTIL_ComputeStringWidth( m_hTextFont, msg.wzInfoTextEnd ) + xSpacing : 0;
  133. int iKillerTextWide = killer[0] ? UTIL_ComputeStringWidth( m_hTextFont, killer ) + xSpacing : 0;
  134. int iLineTall = m_flLineHeight;
  135. int iTextTall = surface()->GetFontTall( m_hTextFont );
  136. int iconWide = 0, iconTall = 0, iDeathInfoOffset = 0, iVictimTextOffset = 0, iconActualWide = 0;
  137. int iPreKillerTextWide = msg.wzPreKillerText[0] ? UTIL_ComputeStringWidth( m_hTextFont, msg.wzPreKillerText ) - xSpacing : 0;
  138. int iconPrekillerWide = 0, iconPrekillerActualWide = 0, iconPrekillerTall = 0;
  139. int iconPostkillerWide = 0, iconPostkillerActualWide = 0, iconPostkillerTall = 0;
  140. int iconPostVictimWide = 0, iconPostVictimActualWide = 0, iconPostVictimTall = 0;
  141. // Get the local position for this notice
  142. if ( icon )
  143. {
  144. iconActualWide = icon->EffectiveWidth( 1.0f );
  145. iconWide = iconActualWide + xSpacing;
  146. iconTall = icon->EffectiveHeight( 1.0f );
  147. int iconTallDesired = iLineTall-YRES(2);
  148. Assert( 0 != iconTallDesired );
  149. float flScale = (float) iconTallDesired / (float) iconTall;
  150. iconActualWide *= flScale;
  151. iconTall *= flScale;
  152. iconWide *= flScale;
  153. }
  154. if ( iconPreKillerName )
  155. {
  156. iconPrekillerActualWide = iconPreKillerName->EffectiveWidth( 1.0f );
  157. iconPrekillerWide = iconPrekillerActualWide;
  158. iconPrekillerTall = iconPreKillerName->EffectiveHeight( 1.0f );
  159. int iconTallDesired = iLineTall - YRES( 2 );
  160. Assert( 0 != iconTallDesired );
  161. float flScale = (float)iconTallDesired / (float)iconPrekillerTall;
  162. iconPrekillerActualWide *= flScale;
  163. iconPrekillerTall *= flScale;
  164. iconPrekillerWide *= flScale;
  165. }
  166. if ( iconPostKillerName )
  167. {
  168. iconPostkillerActualWide = iconPostKillerName->EffectiveWidth( 1.0f );
  169. iconPostkillerWide = iconPostkillerActualWide;
  170. iconPostkillerTall = iconPostKillerName->EffectiveHeight( 1.0f );
  171. int iconTallDesired = iLineTall-YRES(2);
  172. Assert( 0 != iconTallDesired );
  173. float flScale = (float) iconTallDesired / (float) iconPostkillerTall;
  174. iconPostkillerActualWide *= flScale;
  175. iconPostkillerTall *= flScale;
  176. iconPostkillerWide *= flScale;
  177. }
  178. if ( iconPostVictimName )
  179. {
  180. iconPostVictimActualWide = iconPostVictimName->EffectiveWidth( 1.0f );
  181. iconPostVictimWide = iconPostVictimActualWide;
  182. iconPostVictimTall = iconPostVictimName->EffectiveHeight( 1.0f );
  183. int iconTallDesired = iLineTall - YRES( 2 );
  184. Assert( 0 != iconTallDesired );
  185. float flScale = (float)iconTallDesired / (float)iconPostVictimTall;
  186. iconPostVictimActualWide *= flScale;
  187. iconPostVictimTall *= flScale;
  188. iconPostVictimWide *= flScale;
  189. }
  190. int iTotalWide = iKillerTextWide + iconWide + iVictimTextWide + iDeathInfoTextWide + iDeathInfoEndTextWide + ( xMargin * 2 );
  191. iTotalWide += iconPrekillerWide + iconPostkillerWide + iPreKillerTextWide + iconPostVictimWide;
  192. int y = yStart + ( ( iLineTall + m_flLineSpacing ) * i );
  193. int yText = y + ( ( iLineTall - iTextTall ) / 2 );
  194. int yIcon = y + ( ( iLineTall - iconTall ) / 2 );
  195. int x=0;
  196. if ( m_bRightJustify )
  197. {
  198. x = GetWide() - iTotalWide;
  199. }
  200. // draw a background panel for the message
  201. Vertex_t vert[NUM_BACKGROUND_COORD];
  202. GetBackgroundPolygonVerts( x, y+1, x+iTotalWide, y+iLineTall-1, ARRAYSIZE( vert ), vert );
  203. surface()->DrawSetTexture( -1 );
  204. surface()->DrawSetColor( GetBackgroundColor ( i ) );
  205. surface()->DrawTexturedPolygon( ARRAYSIZE( vert ), vert );
  206. x += xMargin;
  207. // prekiller icon
  208. if ( iconPreKillerName )
  209. {
  210. int yPreIconTall = y + ( ( iLineTall - iconPrekillerTall ) / 2 );
  211. iconPreKillerName->DrawSelf( x, yPreIconTall, iconPrekillerActualWide, iconPrekillerTall, m_clrIcon);
  212. x += iconPrekillerWide + xSpacing;
  213. }
  214. if ( killer[0] )
  215. {
  216. // Draw killer's name
  217. DrawText( x, yText, m_hTextFont, GetTeamColor( msg.Killer.iTeam, msg.bLocalPlayerInvolved ), killer );
  218. x += iKillerTextWide;
  219. }
  220. // prekiller text
  221. if ( msg.wzPreKillerText[0] )
  222. {
  223. x += xSpacing;
  224. DrawText( x + iDeathInfoOffset, yText, m_hTextFont, GetInfoTextColor( i ), msg.wzPreKillerText );
  225. x += iPreKillerTextWide;
  226. }
  227. // postkiller icon
  228. if ( iconPostKillerName )
  229. {
  230. int yPreIconTall = y + ( ( iLineTall - iconPostkillerTall ) / 2 );
  231. iconPostKillerName->DrawSelf( x, yPreIconTall, iconPostkillerActualWide, iconPostkillerTall, m_clrIcon );
  232. x += iconPostkillerWide + xSpacing;
  233. }
  234. // Draw glow behind weapon icon to show it was a crit death
  235. if ( msg.bCrit && msg.iconCritDeath )
  236. {
  237. msg.iconCritDeath->DrawSelf( x, yIcon, iconActualWide, iconTall, m_clrIcon );
  238. }
  239. // Draw death icon
  240. if ( icon )
  241. {
  242. icon->DrawSelf( x, yIcon, iconActualWide, iconTall, m_clrIcon );
  243. x += iconWide;
  244. }
  245. // Draw additional info text next to death icon
  246. if ( msg.wzInfoText[0] )
  247. {
  248. if ( msg.bSelfInflicted )
  249. {
  250. iDeathInfoOffset += iVictimTextWide;
  251. iVictimTextOffset -= iDeathInfoTextWide;
  252. }
  253. DrawText( x + iDeathInfoOffset, yText, m_hTextFont, GetInfoTextColor( i ), msg.wzInfoText );
  254. x += iDeathInfoTextWide;
  255. }
  256. // Draw victims name
  257. DrawText( x + iVictimTextOffset, yText, m_hTextFont, GetTeamColor( msg.Victim.iTeam, msg.bLocalPlayerInvolved ), victim );
  258. x += iVictimTextWide;
  259. // postkiller icon
  260. if ( iconPostVictimName )
  261. {
  262. int yPreIconTall = y + ( ( iLineTall - iconPostVictimTall ) / 2 );
  263. iconPostVictimName->DrawSelf( x, yPreIconTall, iconPostVictimActualWide, iconPostVictimTall, m_clrIcon );
  264. x += iconPostkillerWide + xSpacing;
  265. }
  266. // Draw Additional Text on the end of the victims name
  267. if ( msg.wzInfoTextEnd[0] )
  268. {
  269. DrawText( x , yText, m_hTextFont, GetInfoTextColor( i ), msg.wzInfoTextEnd );
  270. }
  271. }
  272. }
  273. //-----------------------------------------------------------------------------
  274. // Purpose: This message handler may be better off elsewhere
  275. //-----------------------------------------------------------------------------
  276. void CHudBaseDeathNotice::RetireExpiredDeathNotices()
  277. {
  278. // Remove any expired death notices. Loop backwards because we might remove one
  279. int iCount = m_DeathNotices.Count();
  280. for ( int i = iCount-1; i >= 0; i-- )
  281. {
  282. if ( gpGlobals->curtime > m_DeathNotices[i].GetExpiryTime() )
  283. {
  284. m_DeathNotices.Remove(i);
  285. }
  286. }
  287. // Do we have too many death messages in the queue?
  288. if ( m_DeathNotices.Count() > 0 &&
  289. m_DeathNotices.Count() > (int)m_flMaxDeathNotices )
  290. {
  291. // First, remove any notices not involving the local player, since they are lower priority.
  292. iCount = m_DeathNotices.Count();
  293. int iNeedToRemove = iCount - (int)m_flMaxDeathNotices;
  294. // loop condition is iCount-1 because we won't remove the most recent death notice, otherwise
  295. // new non-local-player-involved messages would not appear if the queue was full of messages involving the local player
  296. for ( int i = 0; i < iCount-1 && iNeedToRemove > 0 ; i++ )
  297. {
  298. if ( !m_DeathNotices[i].bLocalPlayerInvolved )
  299. {
  300. m_DeathNotices.Remove( i );
  301. iCount--;
  302. iNeedToRemove--;
  303. }
  304. }
  305. // Now that we've culled any non-local-player-involved messages up to the amount we needed to remove, see
  306. // if we've removed enough
  307. iCount = m_DeathNotices.Count();
  308. iNeedToRemove = iCount - (int)m_flMaxDeathNotices;
  309. if ( iNeedToRemove > 0 )
  310. {
  311. // if we still have too many messages, then just remove however many we need, oldest first
  312. for ( int i = 0; i < iNeedToRemove; i++ )
  313. {
  314. m_DeathNotices.Remove( 0 );
  315. }
  316. }
  317. }
  318. }
  319. //-----------------------------------------------------------------------------
  320. // Purpose:
  321. //-----------------------------------------------------------------------------
  322. bool CHudBaseDeathNotice::EventIsPlayerDeath( const char* eventName )
  323. {
  324. if ( FStrEq( eventName, "player_death" ) )
  325. return true;
  326. else
  327. return false;
  328. }
  329. //-----------------------------------------------------------------------------
  330. // Purpose: Server's told us that someone's died
  331. //-----------------------------------------------------------------------------
  332. void CHudBaseDeathNotice::FireGameEvent( IGameEvent *event )
  333. {
  334. if ( !g_PR )
  335. {
  336. return;
  337. }
  338. if ( hud_deathnotice_time.GetFloat() == 0 )
  339. {
  340. return;
  341. }
  342. int iLocalPlayerIndex = GetLocalPlayerIndex();
  343. const char *pszEventName = event->GetName();
  344. bool bPlayerDeath = EventIsPlayerDeath( pszEventName );
  345. bool bObjectDeath = FStrEq( pszEventName, "object_destroyed" );
  346. bool bSpecialScore = FStrEq( pszEventName, "special_score" );
  347. bool bTeamLeaderKilled = false;
  348. bool bIsFeignDeath = event->GetInt( "death_flags" ) & TF_DEATH_FEIGN_DEATH;
  349. if ( bPlayerDeath )
  350. {
  351. if ( !ShouldShowDeathNotice( event ) )
  352. return;
  353. if ( bIsFeignDeath )
  354. {
  355. // Only display fake death messages to the enemy team.
  356. int victimid = event->GetInt( "userid" );
  357. int victim = engine->GetPlayerForUserID( victimid );
  358. CBasePlayer *pVictim = UTIL_PlayerByIndex( victim );
  359. CBasePlayer *pLocalPlayer = CBasePlayer::GetLocalPlayer();
  360. if ( pVictim && pLocalPlayer &&
  361. !BAreTeamsEnemies( pLocalPlayer->GetTeamNumber(), pVictim->GetTeamNumber() ) )
  362. {
  363. return;
  364. }
  365. if ( iLocalPlayerIndex == victim )
  366. {
  367. return;
  368. }
  369. }
  370. }
  371. // Add a new death message. Note we always look it up by index rather than create a reference or pointer to it;
  372. // additional messages may get added during this function that cause the underlying array to get realloced, so don't
  373. // ever keep a pointer to memory here.
  374. int iMsg = -1;
  375. if ( bPlayerDeath || bSpecialScore )
  376. {
  377. iMsg = UseExistingNotice( event );
  378. }
  379. if ( iMsg == -1 )
  380. {
  381. iMsg = AddDeathNoticeItem();
  382. }
  383. if ( bPlayerDeath || bObjectDeath )
  384. {
  385. int victim = engine->GetPlayerForUserID( event->GetInt( "userid" ) );
  386. int killer = engine->GetPlayerForUserID( event->GetInt( "attacker" ) );
  387. const char *killedwith = event->GetString( "weapon" );
  388. const char *killedwithweaponlog = event->GetString( "weapon_logclassname" );
  389. if ( bObjectDeath && victim == 0 )
  390. {
  391. // for now, no death notices of map placed objects
  392. m_DeathNotices.Remove( iMsg );
  393. return;
  394. }
  395. // Get the names of the players
  396. const char *killer_name = ( killer > 0 ) ? g_PR->GetPlayerName( killer ) : "";
  397. const char *victim_name = g_PR->GetPlayerName( victim );
  398. if ( !killer_name )
  399. {
  400. killer_name = "";
  401. }
  402. if ( !victim_name )
  403. {
  404. victim_name = "";
  405. }
  406. // Make a new death notice
  407. bool bLocalPlayerInvolved = false;
  408. if ( iLocalPlayerIndex == killer || iLocalPlayerIndex == victim )
  409. {
  410. bLocalPlayerInvolved = true;
  411. }
  412. if ( event->GetInt( "death_flags" ) & TF_DEATH_AUSTRALIUM )
  413. {
  414. m_DeathNotices[iMsg].bCrit= true;
  415. m_DeathNotices[iMsg].iconCritDeath = GetIcon( "d_australium", bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard );
  416. }
  417. else if ( event->GetInt( "damagebits" ) & DMG_CRITICAL )
  418. {
  419. m_DeathNotices[iMsg].bCrit= true;
  420. m_DeathNotices[iMsg].iconCritDeath = GetIcon( "d_crit", bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard );
  421. }
  422. else
  423. {
  424. m_DeathNotices[iMsg].bCrit= false;
  425. m_DeathNotices[iMsg].iconCritDeath = NULL;
  426. }
  427. m_DeathNotices[iMsg].bLocalPlayerInvolved = bLocalPlayerInvolved;
  428. m_DeathNotices[iMsg].Killer.iTeam = ( killer > 0 ) ? g_PR->GetTeam( killer ) : 0;
  429. m_DeathNotices[iMsg].Victim.iTeam = g_PR->GetTeam( victim );
  430. Q_strncpy( m_DeathNotices[iMsg].Killer.szName, killer_name, ARRAYSIZE( m_DeathNotices[iMsg].Killer.szName ) );
  431. Q_strncpy( m_DeathNotices[iMsg].Victim.szName, victim_name, ARRAYSIZE( m_DeathNotices[iMsg].Victim.szName ) );
  432. if ( killedwith && *killedwith )
  433. {
  434. Q_snprintf( m_DeathNotices[iMsg].szIcon, sizeof(m_DeathNotices[iMsg].szIcon), "d_%s", killedwith );
  435. }
  436. if ( !killer || killer == victim )
  437. {
  438. m_DeathNotices[iMsg].bSelfInflicted = true;
  439. m_DeathNotices[iMsg].Killer.szName[0] = 0;
  440. if ( event->GetInt( "death_flags" ) & TF_DEATH_PURGATORY )
  441. {
  442. // special case icon for dying in purgatory
  443. Q_strncpy( m_DeathNotices[iMsg].szIcon, "d_purgatory", ARRAYSIZE( m_DeathNotices[iMsg].szIcon ) );
  444. }
  445. else if ( event->GetInt( "damagebits" ) & DMG_FALL )
  446. {
  447. // special case text for falling death
  448. V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, g_pVGuiLocalize->Find( "#DeathMsg_Fall" ), sizeof( m_DeathNotices[iMsg].wzInfoText ) );
  449. }
  450. else if ( ( event->GetInt( "damagebits" ) & DMG_VEHICLE ) || ( 0 == Q_stricmp( m_DeathNotices[iMsg].szIcon, "d_tracktrain" ) ) )
  451. {
  452. // special case icon for hit-by-vehicle death
  453. Q_strncpy( m_DeathNotices[iMsg].szIcon, "d_vehicle", ARRAYSIZE( m_DeathNotices[iMsg].szIcon ) );
  454. }
  455. }
  456. m_DeathNotices[iMsg].iWeaponID = event->GetInt( "weaponid" );
  457. m_DeathNotices[iMsg].iKillerID = event->GetInt( "attacker" );
  458. m_DeathNotices[iMsg].iVictimID = event->GetInt( "userid" );
  459. char sDeathMsg[512];
  460. // Record the death notice in the console
  461. if ( m_DeathNotices[iMsg].bSelfInflicted )
  462. {
  463. if ( !strcmp( m_DeathNotices[iMsg].szIcon, "d_worldspawn" ) )
  464. {
  465. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s died.", m_DeathNotices[iMsg].Victim.szName );
  466. }
  467. else // d_world
  468. {
  469. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s suicided.", m_DeathNotices[iMsg].Victim.szName );
  470. }
  471. }
  472. else
  473. {
  474. Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s killed %s", m_DeathNotices[iMsg].Killer.szName, m_DeathNotices[iMsg].Victim.szName );
  475. if ( killedwithweaponlog && killedwithweaponlog[0] && ( killedwithweaponlog[0] > 13 ) )
  476. {
  477. Q_strncat( sDeathMsg, VarArgs( " with %s.", killedwithweaponlog ), sizeof( sDeathMsg ), COPY_ALL_CHARACTERS );
  478. }
  479. else if ( m_DeathNotices[iMsg].szIcon[0] && ( m_DeathNotices[iMsg].szIcon[0] > 13 ) )
  480. {
  481. Q_strncat( sDeathMsg, VarArgs( " with %s.", &m_DeathNotices[iMsg].szIcon[2] ), sizeof( sDeathMsg ), COPY_ALL_CHARACTERS );
  482. }
  483. }
  484. if ( FStrEq( pszEventName, "player_death" ) )
  485. {
  486. if ( m_DeathNotices[iMsg].bCrit )
  487. {
  488. Msg( "%s (crit)\n", sDeathMsg );
  489. }
  490. else
  491. {
  492. Msg( "%s\n", sDeathMsg );
  493. }
  494. }
  495. }
  496. else if ( FStrEq( "teamplay_point_captured", pszEventName ) )
  497. {
  498. GetLocalizedControlPointName( event, m_DeathNotices[iMsg].Victim.szName, ARRAYSIZE( m_DeathNotices[iMsg].Victim.szName ) );
  499. // Array of capper indices
  500. const char *cappers = event->GetString("cappers");
  501. char szCappers[256];
  502. szCappers[0] = '\0';
  503. int len = Q_strlen(cappers);
  504. for( int i=0;i<len;i++ )
  505. {
  506. int iPlayerIndex = (int)cappers[i];
  507. Assert( iPlayerIndex > 0 && iPlayerIndex <= gpGlobals->maxClients );
  508. const char *pPlayerName = g_PR->GetPlayerName( iPlayerIndex );
  509. if ( i == 0 )
  510. {
  511. // use first player as the team
  512. m_DeathNotices[iMsg].Killer.iTeam = g_PR->GetTeam( iPlayerIndex );
  513. m_DeathNotices[iMsg].Victim.iTeam = TEAM_UNASSIGNED;
  514. }
  515. else
  516. {
  517. Q_strncat( szCappers, ", ", sizeof(szCappers), 2 );
  518. }
  519. Q_strncat( szCappers, pPlayerName, sizeof(szCappers), COPY_ALL_CHARACTERS );
  520. if ( iLocalPlayerIndex == iPlayerIndex )
  521. m_DeathNotices[iMsg].bLocalPlayerInvolved = true;
  522. }
  523. Q_strncpy( m_DeathNotices[iMsg].Killer.szName, szCappers, sizeof(m_DeathNotices[iMsg].Killer.szName) );
  524. V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, g_pVGuiLocalize->Find( len > 1 ? "#Msg_Captured_Multiple" : "#Msg_Captured" ), sizeof( m_DeathNotices[iMsg].wzInfoText ) );
  525. // print a log message
  526. Msg( "%s captured %s for team #%d\n", m_DeathNotices[iMsg].Killer.szName, m_DeathNotices[iMsg].Victim.szName, m_DeathNotices[iMsg].Killer.iTeam );
  527. }
  528. else if ( FStrEq( "teamplay_capture_blocked", pszEventName ) )
  529. {
  530. GetLocalizedControlPointName( event, m_DeathNotices[iMsg].Victim.szName, ARRAYSIZE( m_DeathNotices[iMsg].Victim.szName ) );
  531. V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, g_pVGuiLocalize->Find( "#Msg_Defended" ), sizeof( m_DeathNotices[iMsg].wzInfoText ) );
  532. int iPlayerIndex = event->GetInt( "blocker" );
  533. const char *blocker_name = g_PR->GetPlayerName( iPlayerIndex );
  534. Q_strncpy( m_DeathNotices[iMsg].Killer.szName, blocker_name, ARRAYSIZE( m_DeathNotices[iMsg].Killer.szName ) );
  535. m_DeathNotices[iMsg].Killer.iTeam = g_PR->GetTeam( iPlayerIndex );
  536. if ( iLocalPlayerIndex == iPlayerIndex )
  537. m_DeathNotices[iMsg].bLocalPlayerInvolved = true;
  538. // print a log message
  539. Msg( "%s defended %s for team #%d\n", m_DeathNotices[iMsg].Killer.szName, m_DeathNotices[iMsg].Victim.szName, m_DeathNotices[iMsg].Killer.iTeam );
  540. }
  541. else if ( FStrEq( "teamplay_flag_event", pszEventName ) )
  542. {
  543. // don't handle any flag events for death notices while in player destruction mode
  544. if ( CTFPlayerDestructionLogic::GetRobotDestructionLogic() && CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() == CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION )
  545. {
  546. // don't put anything up
  547. m_DeathNotices.Remove( iMsg );
  548. return;
  549. }
  550. const char *pszMsgKey = NULL;
  551. int iEventType = event->GetInt( "eventtype" );
  552. bool bIsMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode();
  553. if ( bIsMvM )
  554. {
  555. // MvM only cares about Defend notifications
  556. if ( iEventType != TF_FLAGEVENT_DEFEND )
  557. {
  558. // unsupported, don't put anything up
  559. m_DeathNotices.Remove( iMsg );
  560. return;
  561. }
  562. }
  563. bool bIsHalloween2014 = TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY );
  564. switch ( iEventType )
  565. {
  566. case TF_FLAGEVENT_PICKUP:
  567. pszMsgKey = bIsHalloween2014 ? "#Msg_PickedUpFlagHalloween2014" : "#Msg_PickedUpFlag";
  568. break;
  569. case TF_FLAGEVENT_CAPTURE:
  570. pszMsgKey = bIsHalloween2014 ? "#Msg_CapturedFlagHalloween2014" : "#Msg_CapturedFlag";
  571. break;
  572. case TF_FLAGEVENT_DEFEND:
  573. if ( bIsMvM )
  574. {
  575. pszMsgKey = "#Msg_DefendedBomb";
  576. }
  577. else
  578. {
  579. pszMsgKey = bIsHalloween2014 ? "#Msg_DefendedFlagHalloween2014" : "#Msg_DefendedFlag";
  580. }
  581. break;
  582. // Add this when we can get localization for it
  583. //case TF_FLAGEVENT_DROPPED:
  584. // pszMsgKey = "#Msg_DroppedFlag";
  585. // break;
  586. default:
  587. // unsupported, don't put anything up
  588. m_DeathNotices.Remove( iMsg );
  589. return;
  590. }
  591. wchar_t *pwzEventText = g_pVGuiLocalize->Find( pszMsgKey );
  592. Assert( pwzEventText );
  593. if ( pwzEventText )
  594. {
  595. V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, pwzEventText, sizeof( m_DeathNotices[iMsg].wzInfoText ) );
  596. }
  597. else
  598. {
  599. V_memset( m_DeathNotices[iMsg].wzInfoText, 0, sizeof( m_DeathNotices[iMsg].wzInfoText ) );
  600. }
  601. int iPlayerIndex = event->GetInt( "player" );
  602. const char *szPlayerName = g_PR->GetPlayerName( iPlayerIndex );
  603. Q_strncpy( m_DeathNotices[iMsg].Killer.szName, szPlayerName, ARRAYSIZE( m_DeathNotices[iMsg].Killer.szName ) );
  604. m_DeathNotices[iMsg].Killer.iTeam = g_PR->GetTeam( iPlayerIndex );
  605. if ( iLocalPlayerIndex == iPlayerIndex )
  606. m_DeathNotices[iMsg].bLocalPlayerInvolved = true;
  607. }
  608. else if ( bSpecialScore )
  609. {
  610. DeathNoticeItem &msg = m_DeathNotices[iMsg];
  611. int iScorer = event->GetInt( "player" );
  612. const char *pszScorer = ( iScorer > 0 ) ? g_PR->GetPlayerName( iScorer ) : "";
  613. if ( !pszScorer )
  614. {
  615. pszScorer = "";
  616. }
  617. Q_strncpy( msg.Killer.szName, pszScorer, ARRAYSIZE( msg.Killer.szName ) );
  618. m_DeathNotices[iMsg].Killer.iTeam = ( iScorer > 0 ) ? g_PR->GetTeam( iScorer ) : 0;
  619. msg.bLocalPlayerInvolved = ( iScorer == GetLocalPlayerIndex() );
  620. msg.iKillerID = iScorer;
  621. msg.bCrit = false;
  622. msg.iconCritDeath = NULL;
  623. msg.bSpecialScore = true;
  624. wchar_t wzCount[10];
  625. _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", ++msg.iCount );
  626. g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, g_pVGuiLocalize->Find( "#SpecialScore_Count" ), 1, wzCount );
  627. }
  628. else if ( FStrEq( "team_leader_killed", pszEventName ) )
  629. {
  630. DeathNoticeItem &msg = m_DeathNotices[iMsg];
  631. int iKiller = event->GetInt( "killer" );
  632. const char *pszKiller = ( iKiller > 0 ) ? g_PR->GetPlayerName( iKiller ) : "";
  633. if ( !pszKiller )
  634. {
  635. pszKiller = "";
  636. }
  637. Q_strncpy( msg.Killer.szName, pszKiller, ARRAYSIZE( msg.Killer.szName ) );
  638. m_DeathNotices[iMsg].Killer.iTeam = ( iKiller > 0 ) ? g_PR->GetTeam( iKiller ) : 0;
  639. int iVictim = event->GetInt( "victim" );
  640. const char *pszVictim = ( iVictim > 0 ) ? g_PR->GetPlayerName( iVictim ) : "";
  641. if ( !pszVictim )
  642. {
  643. pszVictim = "";
  644. }
  645. Q_strncpy( msg.Victim.szName, pszVictim, ARRAYSIZE( msg.Victim.szName ) );
  646. m_DeathNotices[iMsg].Victim.iTeam = ( iVictim > 0 ) ? g_PR->GetTeam( iVictim ) : 0;
  647. msg.bLocalPlayerInvolved = ( ( iKiller == GetLocalPlayerIndex() ) || ( iVictim == GetLocalPlayerIndex() ) );
  648. msg.iKillerID = iKiller;
  649. msg.iVictimID = iVictim;
  650. msg.bCrit = false;
  651. msg.iconCritDeath = NULL;
  652. wchar_t *pwzEventText = g_pVGuiLocalize->Find( "#TeamLeader_Kill" );
  653. Assert( pwzEventText );
  654. if ( pwzEventText )
  655. {
  656. V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, pwzEventText, sizeof( m_DeathNotices[iMsg].wzInfoText ) );
  657. }
  658. bTeamLeaderKilled = true;
  659. }
  660. OnGameEvent( event, iMsg );
  661. if ( !bSpecialScore && !bTeamLeaderKilled )
  662. {
  663. if ( !m_DeathNotices[iMsg].iconDeath && m_DeathNotices[iMsg].szIcon )
  664. {
  665. // Try and find the death identifier in the icon list
  666. // On consoles, we flip usage of the inverted icon to make it more visible
  667. bool bInverted = m_DeathNotices[iMsg].bLocalPlayerInvolved;
  668. if ( IsConsole() )
  669. {
  670. bInverted = !bInverted;
  671. }
  672. m_DeathNotices[iMsg].iconDeath = GetIcon( m_DeathNotices[iMsg].szIcon, bInverted ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard );
  673. if ( !m_DeathNotices[iMsg].iconDeath )
  674. {
  675. // Can't find it, so use the default skull & crossbones icon
  676. m_DeathNotices[iMsg].iconDeath = GetIcon( "d_skull_tf", m_DeathNotices[iMsg].bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard );
  677. }
  678. }
  679. }
  680. }
  681. //-----------------------------------------------------------------------------
  682. // Purpose: Gets the localized name of the control point sent in the event
  683. //-----------------------------------------------------------------------------
  684. void CHudBaseDeathNotice::GetLocalizedControlPointName( IGameEvent *event, char *namebuf, int namelen )
  685. {
  686. // Cap point name ( MATTTODO: can't we find this from the point index ? )
  687. const char *pName = event->GetString( "cpname", "Unnamed Control Point" );
  688. const wchar_t *pLocalizedName = g_pVGuiLocalize->Find( pName );
  689. if ( pLocalizedName )
  690. {
  691. g_pVGuiLocalize->ConvertUnicodeToANSI( pLocalizedName, namebuf, namelen );
  692. }
  693. else
  694. {
  695. Q_strncpy( namebuf, pName, namelen );
  696. }
  697. }
  698. //-----------------------------------------------------------------------------
  699. // Purpose: Adds a new death notice to the queue
  700. //-----------------------------------------------------------------------------
  701. int CHudBaseDeathNotice::AddDeathNoticeItem()
  702. {
  703. int iMsg = m_DeathNotices.AddToTail();
  704. DeathNoticeItem &msg = m_DeathNotices[iMsg];
  705. msg.flCreationTime = gpGlobals->curtime;
  706. return iMsg;
  707. }
  708. //-----------------------------------------------------------------------------
  709. // Purpose: draw text helper
  710. //-----------------------------------------------------------------------------
  711. void CHudBaseDeathNotice::DrawText( int x, int y, HFont hFont, Color clr, const wchar_t *szText )
  712. {
  713. surface()->DrawSetTextPos( x, y );
  714. surface()->DrawSetTextColor( clr );
  715. surface()->DrawSetTextFont( hFont ); //reset the font, draw icon can change it
  716. surface()->DrawUnicodeString( szText, vgui::FONT_DRAW_NONADDITIVE );
  717. }
  718. //-----------------------------------------------------------------------------
  719. // Purpose: Creates a rounded-corner polygon that fits in the specified bounds
  720. //-----------------------------------------------------------------------------
  721. void CHudBaseDeathNotice::GetBackgroundPolygonVerts( int x0, int y0, int x1, int y1, int iVerts, vgui::Vertex_t vert[] )
  722. {
  723. Assert( iVerts == NUM_BACKGROUND_COORD );
  724. // use the offsets we generated for one corner and apply those to the passed-in dimensions to create verts for the poly
  725. for ( int i = 0; i < NUM_CORNER_COORD; i++ )
  726. {
  727. int j = ( NUM_CORNER_COORD-1 ) - i;
  728. // upper left corner
  729. vert[i].Init( Vector2D( x0 + m_CornerCoord[i].x, y0 + m_CornerCoord[i].y ) );
  730. // upper right corner
  731. vert[i+NUM_CORNER_COORD].Init( Vector2D( x1 - m_CornerCoord[j].x, y0 + m_CornerCoord[j].y ) );
  732. // lower right corner
  733. vert[i+(NUM_CORNER_COORD*2)].Init( Vector2D( x1 - m_CornerCoord[i].x, y1 - m_CornerCoord[i].y ) );
  734. // lower left corner
  735. vert[i+(NUM_CORNER_COORD*3)].Init( Vector2D( x0 + m_CornerCoord[j].x, y1 - m_CornerCoord[j].y) );
  736. }
  737. }
  738. //-----------------------------------------------------------------------------
  739. // Purpose: Creates the offsets for rounded corners based on current screen res
  740. //-----------------------------------------------------------------------------
  741. void CHudBaseDeathNotice::CalcRoundedCorners()
  742. {
  743. // generate the offset geometry for upper left corner
  744. int iMax = ARRAYSIZE( m_CornerCoord );
  745. for ( int i = 0; i < iMax; i++ )
  746. {
  747. m_CornerCoord[i].x = m_flCornerRadius * ( 1 - cos( ( (float) i / (float) (iMax - 1 ) ) * ( M_PI / 2 ) ) );
  748. m_CornerCoord[i].y = m_flCornerRadius * ( 1 - sin( ( (float) i / (float) (iMax - 1 ) ) * ( M_PI / 2 ) ) );
  749. }
  750. }
  751. //-----------------------------------------------------------------------------
  752. // Purpose: Gets specified icon
  753. //-----------------------------------------------------------------------------
  754. CHudTexture *CHudBaseDeathNotice::GetIcon( const char *szIcon, EDeathNoticeIconFormat eIconFormat )
  755. {
  756. // adjust the style (prefix) of the icon if requested
  757. if ( eIconFormat != kDeathNoticeIcon_Standard && V_strncmp( "d_", szIcon, 2 ) == 0 )
  758. {
  759. Assert( eIconFormat == kDeathNoticeIcon_Inverted );
  760. const char *cszNewPrefix = "dneg_";
  761. unsigned int iNewPrefixLen = V_strlen( cszNewPrefix );
  762. // generate new string with correct prefix
  763. enum { kIconTempStringLen = 256 };
  764. char szIconTmp[kIconTempStringLen];
  765. V_strncpy( szIconTmp, cszNewPrefix, kIconTempStringLen );
  766. V_strncat( szIconTmp, szIcon + 2, kIconTempStringLen - iNewPrefixLen );
  767. CHudTexture *pIcon = gHUD.GetIcon( szIconTmp );
  768. // return inverted version if found
  769. if ( pIcon )
  770. return pIcon;
  771. }
  772. // we either requested the default style or we requested an alternate style but
  773. // didn't have the art for it; either way, we can't, so fall back to our default
  774. return gHUD.GetIcon( szIcon );
  775. }
  776. //-----------------------------------------------------------------------------
  777. // Purpose: Gets the expiry time for this death notice item
  778. //-----------------------------------------------------------------------------
  779. float DeathNoticeItem::GetExpiryTime()
  780. {
  781. float flDuration = hud_deathnotice_time.GetFloat();
  782. if ( bLocalPlayerInvolved )
  783. {
  784. // if the local player is involved, make the message last longer
  785. flDuration *= 2;
  786. }
  787. return flCreationTime + flDuration;
  788. }