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
920 lines
31 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Draws CSPort's death notices |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "hudelement.h" |
|
#include "hud_macros.h" |
|
#include "c_playerresource.h" |
|
#include "iclientmode.h" |
|
#include <vgui_controls/Controls.h> |
|
#include <vgui_controls/Panel.h> |
|
#include <vgui/ISurface.h> |
|
#include <vgui/ILocalize.h> |
|
#include <KeyValues.h> |
|
#include <game_controls/baseviewport.h> |
|
#include "clientmode_shared.h" |
|
#include "c_baseplayer.h" |
|
#include "c_team.h" |
|
#include "tf_shareddefs.h" |
|
#include "tf_shareddefs.h" |
|
#include "tf_gamerules.h" |
|
#include "tf_logic_player_destruction.h" |
|
|
|
#include "hud_basedeathnotice.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
static ConVar hud_deathnotice_time( "hud_deathnotice_time", "6", 0 ); |
|
|
|
|
|
using namespace vgui; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CHudBaseDeathNotice::CHudBaseDeathNotice( const char *pElementName ) : |
|
CHudElement( pElementName ), BaseClass( NULL, "HudDeathNotice" ) |
|
{ |
|
vgui::Panel *pParent = g_pClientMode->GetViewport(); |
|
SetParent( pParent ); |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::ApplySchemeSettings( IScheme *scheme ) |
|
{ |
|
BaseClass::ApplySchemeSettings( scheme ); |
|
SetPaintBackgroundEnabled( false ); |
|
|
|
CalcRoundedCorners(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::Init( void ) |
|
{ |
|
ListenForGameEvent( "player_death" ); |
|
ListenForGameEvent( "object_destroyed" ); |
|
ListenForGameEvent( "teamplay_point_captured" ); |
|
ListenForGameEvent( "teamplay_capture_blocked" ); |
|
ListenForGameEvent( "teamplay_flag_event" ); |
|
ListenForGameEvent( "rd_robot_killed" ); |
|
ListenForGameEvent( "special_score" ); |
|
ListenForGameEvent( "team_leader_killed" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::VidInit( void ) |
|
{ |
|
m_DeathNotices.RemoveAll(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw if we've got at least one death notice in the queue |
|
//----------------------------------------------------------------------------- |
|
bool CHudBaseDeathNotice::ShouldDraw( void ) |
|
{ |
|
return ( CHudElement::ShouldDraw() && ( m_DeathNotices.Count() ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
Color CHudBaseDeathNotice::GetTeamColor( int iTeamNumber, bool bLocalPlayerInvolved /* = false */ ) |
|
{ |
|
// By default, return the standard team color. Subclasses may override this. |
|
return g_PR->GetTeamColor( iTeamNumber ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CHudBaseDeathNotice::UseExistingNotice( IGameEvent *event ) |
|
{ |
|
if ( FStrEq( event->GetName(), "special_score" ) ) |
|
{ |
|
int iIndex = event->GetInt( "player" ); |
|
|
|
// Look for a matching pre-existing notice. |
|
for ( int i = 0; i < m_DeathNotices.Count(); ++i ) |
|
{ |
|
DeathNoticeItem &msg = m_DeathNotices[i]; |
|
|
|
if ( !msg.bSpecialScore ) |
|
continue; |
|
|
|
if ( msg.iKillerID != iIndex ) |
|
continue; |
|
|
|
return i; |
|
} |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::Paint() |
|
{ |
|
// Retire any death notices that have expired |
|
RetireExpiredDeathNotices(); |
|
|
|
CBaseViewport *pViewport = dynamic_cast<CBaseViewport *>( GetClientModeNormal()->GetViewport() ); |
|
int yStart = pViewport->GetDeathMessageStartHeight(); |
|
|
|
surface()->DrawSetTextFont( m_hTextFont ); |
|
|
|
int xMargin = XRES( 10 ); |
|
int xSpacing = UTIL_ComputeStringWidth( m_hTextFont, L" " ); |
|
|
|
int iCount = m_DeathNotices.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
DeathNoticeItem &msg = m_DeathNotices[i]; |
|
|
|
CHudTexture *icon = msg.iconDeath; |
|
CHudTexture *iconPostKillerName = msg.iconPostKillerName; |
|
CHudTexture *iconPreKillerName = msg.iconPreKillerName; |
|
CHudTexture *iconPostVictimName = msg.iconPostVictimName; |
|
|
|
wchar_t victim[256]=L""; |
|
wchar_t killer[256]=L""; |
|
|
|
// TEMP - print the death icon name if we don't have a material for it |
|
|
|
g_pVGuiLocalize->ConvertANSIToUnicode( msg.Victim.szName, victim, sizeof( victim ) ); |
|
g_pVGuiLocalize->ConvertANSIToUnicode( msg.Killer.szName, killer, sizeof( killer ) ); |
|
|
|
int iVictimTextWide = UTIL_ComputeStringWidth( m_hTextFont, victim ) + xSpacing; |
|
int iDeathInfoTextWide= msg.wzInfoText[0] ? UTIL_ComputeStringWidth( m_hTextFont, msg.wzInfoText ) + xSpacing : 0; |
|
int iDeathInfoEndTextWide= msg.wzInfoTextEnd[0] ? UTIL_ComputeStringWidth( m_hTextFont, msg.wzInfoTextEnd ) + xSpacing : 0; |
|
|
|
int iKillerTextWide = killer[0] ? UTIL_ComputeStringWidth( m_hTextFont, killer ) + xSpacing : 0; |
|
int iLineTall = m_flLineHeight; |
|
int iTextTall = surface()->GetFontTall( m_hTextFont ); |
|
int iconWide = 0, iconTall = 0, iDeathInfoOffset = 0, iVictimTextOffset = 0, iconActualWide = 0; |
|
|
|
int iPreKillerTextWide = msg.wzPreKillerText[0] ? UTIL_ComputeStringWidth( m_hTextFont, msg.wzPreKillerText ) - xSpacing : 0; |
|
|
|
int iconPrekillerWide = 0, iconPrekillerActualWide = 0, iconPrekillerTall = 0; |
|
int iconPostkillerWide = 0, iconPostkillerActualWide = 0, iconPostkillerTall = 0; |
|
|
|
int iconPostVictimWide = 0, iconPostVictimActualWide = 0, iconPostVictimTall = 0; |
|
|
|
// Get the local position for this notice |
|
if ( icon ) |
|
{ |
|
iconActualWide = icon->EffectiveWidth( 1.0f ); |
|
iconWide = iconActualWide + xSpacing; |
|
iconTall = icon->EffectiveHeight( 1.0f ); |
|
|
|
int iconTallDesired = iLineTall-YRES(2); |
|
Assert( 0 != iconTallDesired ); |
|
float flScale = (float) iconTallDesired / (float) iconTall; |
|
|
|
iconActualWide *= flScale; |
|
iconTall *= flScale; |
|
iconWide *= flScale; |
|
} |
|
|
|
if ( iconPreKillerName ) |
|
{ |
|
iconPrekillerActualWide = iconPreKillerName->EffectiveWidth( 1.0f ); |
|
iconPrekillerWide = iconPrekillerActualWide; |
|
iconPrekillerTall = iconPreKillerName->EffectiveHeight( 1.0f ); |
|
|
|
int iconTallDesired = iLineTall - YRES( 2 ); |
|
Assert( 0 != iconTallDesired ); |
|
float flScale = (float)iconTallDesired / (float)iconPrekillerTall; |
|
|
|
iconPrekillerActualWide *= flScale; |
|
iconPrekillerTall *= flScale; |
|
iconPrekillerWide *= flScale; |
|
} |
|
|
|
if ( iconPostKillerName ) |
|
{ |
|
iconPostkillerActualWide = iconPostKillerName->EffectiveWidth( 1.0f ); |
|
iconPostkillerWide = iconPostkillerActualWide; |
|
iconPostkillerTall = iconPostKillerName->EffectiveHeight( 1.0f ); |
|
|
|
int iconTallDesired = iLineTall-YRES(2); |
|
Assert( 0 != iconTallDesired ); |
|
float flScale = (float) iconTallDesired / (float) iconPostkillerTall; |
|
|
|
iconPostkillerActualWide *= flScale; |
|
iconPostkillerTall *= flScale; |
|
iconPostkillerWide *= flScale; |
|
} |
|
|
|
if ( iconPostVictimName ) |
|
{ |
|
iconPostVictimActualWide = iconPostVictimName->EffectiveWidth( 1.0f ); |
|
iconPostVictimWide = iconPostVictimActualWide; |
|
iconPostVictimTall = iconPostVictimName->EffectiveHeight( 1.0f ); |
|
|
|
int iconTallDesired = iLineTall - YRES( 2 ); |
|
Assert( 0 != iconTallDesired ); |
|
float flScale = (float)iconTallDesired / (float)iconPostVictimTall; |
|
|
|
iconPostVictimActualWide *= flScale; |
|
iconPostVictimTall *= flScale; |
|
iconPostVictimWide *= flScale; |
|
} |
|
|
|
int iTotalWide = iKillerTextWide + iconWide + iVictimTextWide + iDeathInfoTextWide + iDeathInfoEndTextWide + ( xMargin * 2 ); |
|
iTotalWide += iconPrekillerWide + iconPostkillerWide + iPreKillerTextWide + iconPostVictimWide; |
|
|
|
int y = yStart + ( ( iLineTall + m_flLineSpacing ) * i ); |
|
int yText = y + ( ( iLineTall - iTextTall ) / 2 ); |
|
int yIcon = y + ( ( iLineTall - iconTall ) / 2 ); |
|
|
|
int x=0; |
|
if ( m_bRightJustify ) |
|
{ |
|
x = GetWide() - iTotalWide; |
|
} |
|
|
|
// draw a background panel for the message |
|
Vertex_t vert[NUM_BACKGROUND_COORD]; |
|
GetBackgroundPolygonVerts( x, y+1, x+iTotalWide, y+iLineTall-1, ARRAYSIZE( vert ), vert ); |
|
surface()->DrawSetTexture( -1 ); |
|
surface()->DrawSetColor( GetBackgroundColor ( i ) ); |
|
surface()->DrawTexturedPolygon( ARRAYSIZE( vert ), vert ); |
|
|
|
x += xMargin; |
|
|
|
// prekiller icon |
|
if ( iconPreKillerName ) |
|
{ |
|
int yPreIconTall = y + ( ( iLineTall - iconPrekillerTall ) / 2 ); |
|
iconPreKillerName->DrawSelf( x, yPreIconTall, iconPrekillerActualWide, iconPrekillerTall, m_clrIcon); |
|
x += iconPrekillerWide + xSpacing; |
|
} |
|
|
|
if ( killer[0] ) |
|
{ |
|
// Draw killer's name |
|
DrawText( x, yText, m_hTextFont, GetTeamColor( msg.Killer.iTeam, msg.bLocalPlayerInvolved ), killer ); |
|
x += iKillerTextWide; |
|
} |
|
|
|
// prekiller text |
|
if ( msg.wzPreKillerText[0] ) |
|
{ |
|
x += xSpacing; |
|
DrawText( x + iDeathInfoOffset, yText, m_hTextFont, GetInfoTextColor( i ), msg.wzPreKillerText ); |
|
x += iPreKillerTextWide; |
|
} |
|
|
|
// postkiller icon |
|
if ( iconPostKillerName ) |
|
{ |
|
int yPreIconTall = y + ( ( iLineTall - iconPostkillerTall ) / 2 ); |
|
iconPostKillerName->DrawSelf( x, yPreIconTall, iconPostkillerActualWide, iconPostkillerTall, m_clrIcon ); |
|
x += iconPostkillerWide + xSpacing; |
|
} |
|
|
|
// Draw glow behind weapon icon to show it was a crit death |
|
if ( msg.bCrit && msg.iconCritDeath ) |
|
{ |
|
msg.iconCritDeath->DrawSelf( x, yIcon, iconActualWide, iconTall, m_clrIcon ); |
|
} |
|
|
|
// Draw death icon |
|
if ( icon ) |
|
{ |
|
icon->DrawSelf( x, yIcon, iconActualWide, iconTall, m_clrIcon ); |
|
x += iconWide; |
|
} |
|
|
|
// Draw additional info text next to death icon |
|
if ( msg.wzInfoText[0] ) |
|
{ |
|
if ( msg.bSelfInflicted ) |
|
{ |
|
iDeathInfoOffset += iVictimTextWide; |
|
iVictimTextOffset -= iDeathInfoTextWide; |
|
} |
|
|
|
DrawText( x + iDeathInfoOffset, yText, m_hTextFont, GetInfoTextColor( i ), msg.wzInfoText ); |
|
x += iDeathInfoTextWide; |
|
} |
|
|
|
// Draw victims name |
|
DrawText( x + iVictimTextOffset, yText, m_hTextFont, GetTeamColor( msg.Victim.iTeam, msg.bLocalPlayerInvolved ), victim ); |
|
x += iVictimTextWide; |
|
|
|
// postkiller icon |
|
if ( iconPostVictimName ) |
|
{ |
|
int yPreIconTall = y + ( ( iLineTall - iconPostVictimTall ) / 2 ); |
|
iconPostVictimName->DrawSelf( x, yPreIconTall, iconPostVictimActualWide, iconPostVictimTall, m_clrIcon ); |
|
x += iconPostkillerWide + xSpacing; |
|
} |
|
|
|
// Draw Additional Text on the end of the victims name |
|
if ( msg.wzInfoTextEnd[0] ) |
|
{ |
|
DrawText( x , yText, m_hTextFont, GetInfoTextColor( i ), msg.wzInfoTextEnd ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This message handler may be better off elsewhere |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::RetireExpiredDeathNotices() |
|
{ |
|
// Remove any expired death notices. Loop backwards because we might remove one |
|
int iCount = m_DeathNotices.Count(); |
|
for ( int i = iCount-1; i >= 0; i-- ) |
|
{ |
|
if ( gpGlobals->curtime > m_DeathNotices[i].GetExpiryTime() ) |
|
{ |
|
m_DeathNotices.Remove(i); |
|
} |
|
} |
|
|
|
// Do we have too many death messages in the queue? |
|
if ( m_DeathNotices.Count() > 0 && |
|
m_DeathNotices.Count() > (int)m_flMaxDeathNotices ) |
|
{ |
|
// First, remove any notices not involving the local player, since they are lower priority. |
|
iCount = m_DeathNotices.Count(); |
|
int iNeedToRemove = iCount - (int)m_flMaxDeathNotices; |
|
// loop condition is iCount-1 because we won't remove the most recent death notice, otherwise |
|
// new non-local-player-involved messages would not appear if the queue was full of messages involving the local player |
|
for ( int i = 0; i < iCount-1 && iNeedToRemove > 0 ; i++ ) |
|
{ |
|
if ( !m_DeathNotices[i].bLocalPlayerInvolved ) |
|
{ |
|
m_DeathNotices.Remove( i ); |
|
iCount--; |
|
iNeedToRemove--; |
|
} |
|
} |
|
|
|
// Now that we've culled any non-local-player-involved messages up to the amount we needed to remove, see |
|
// if we've removed enough |
|
iCount = m_DeathNotices.Count(); |
|
iNeedToRemove = iCount - (int)m_flMaxDeathNotices; |
|
if ( iNeedToRemove > 0 ) |
|
{ |
|
// if we still have too many messages, then just remove however many we need, oldest first |
|
for ( int i = 0; i < iNeedToRemove; i++ ) |
|
{ |
|
m_DeathNotices.Remove( 0 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CHudBaseDeathNotice::EventIsPlayerDeath( const char* eventName ) |
|
{ |
|
if ( FStrEq( eventName, "player_death" ) ) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Server's told us that someone's died |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::FireGameEvent( IGameEvent *event ) |
|
{ |
|
if ( !g_PR ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( hud_deathnotice_time.GetFloat() == 0 ) |
|
{ |
|
return; |
|
} |
|
|
|
int iLocalPlayerIndex = GetLocalPlayerIndex(); |
|
const char *pszEventName = event->GetName(); |
|
|
|
bool bPlayerDeath = EventIsPlayerDeath( pszEventName ); |
|
bool bObjectDeath = FStrEq( pszEventName, "object_destroyed" ); |
|
bool bSpecialScore = FStrEq( pszEventName, "special_score" ); |
|
bool bTeamLeaderKilled = false; |
|
|
|
bool bIsFeignDeath = event->GetInt( "death_flags" ) & TF_DEATH_FEIGN_DEATH; |
|
if ( bPlayerDeath ) |
|
{ |
|
if ( !ShouldShowDeathNotice( event ) ) |
|
return; |
|
|
|
if ( bIsFeignDeath ) |
|
{ |
|
// Only display fake death messages to the enemy team. |
|
int victimid = event->GetInt( "userid" ); |
|
int victim = engine->GetPlayerForUserID( victimid ); |
|
CBasePlayer *pVictim = UTIL_PlayerByIndex( victim ); |
|
CBasePlayer *pLocalPlayer = CBasePlayer::GetLocalPlayer(); |
|
if ( pVictim && pLocalPlayer && |
|
!BAreTeamsEnemies( pLocalPlayer->GetTeamNumber(), pVictim->GetTeamNumber() ) ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( iLocalPlayerIndex == victim ) |
|
{ |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// Add a new death message. Note we always look it up by index rather than create a reference or pointer to it; |
|
// additional messages may get added during this function that cause the underlying array to get realloced, so don't |
|
// ever keep a pointer to memory here. |
|
int iMsg = -1; |
|
if ( bPlayerDeath || bSpecialScore ) |
|
{ |
|
iMsg = UseExistingNotice( event ); |
|
} |
|
if ( iMsg == -1 ) |
|
{ |
|
iMsg = AddDeathNoticeItem(); |
|
} |
|
|
|
if ( bPlayerDeath || bObjectDeath ) |
|
{ |
|
int victim = engine->GetPlayerForUserID( event->GetInt( "userid" ) ); |
|
int killer = engine->GetPlayerForUserID( event->GetInt( "attacker" ) ); |
|
const char *killedwith = event->GetString( "weapon" ); |
|
const char *killedwithweaponlog = event->GetString( "weapon_logclassname" ); |
|
|
|
if ( bObjectDeath && victim == 0 ) |
|
{ |
|
// for now, no death notices of map placed objects |
|
m_DeathNotices.Remove( iMsg ); |
|
return; |
|
} |
|
|
|
// Get the names of the players |
|
const char *killer_name = ( killer > 0 ) ? g_PR->GetPlayerName( killer ) : ""; |
|
const char *victim_name = g_PR->GetPlayerName( victim ); |
|
if ( !killer_name ) |
|
{ |
|
killer_name = ""; |
|
} |
|
|
|
if ( !victim_name ) |
|
{ |
|
victim_name = ""; |
|
} |
|
|
|
// Make a new death notice |
|
bool bLocalPlayerInvolved = false; |
|
if ( iLocalPlayerIndex == killer || iLocalPlayerIndex == victim ) |
|
{ |
|
bLocalPlayerInvolved = true; |
|
} |
|
|
|
if ( event->GetInt( "death_flags" ) & TF_DEATH_AUSTRALIUM ) |
|
{ |
|
m_DeathNotices[iMsg].bCrit= true; |
|
m_DeathNotices[iMsg].iconCritDeath = GetIcon( "d_australium", bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard ); |
|
} |
|
else if ( event->GetInt( "damagebits" ) & DMG_CRITICAL ) |
|
{ |
|
m_DeathNotices[iMsg].bCrit= true; |
|
m_DeathNotices[iMsg].iconCritDeath = GetIcon( "d_crit", bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard ); |
|
} |
|
else |
|
{ |
|
m_DeathNotices[iMsg].bCrit= false; |
|
m_DeathNotices[iMsg].iconCritDeath = NULL; |
|
} |
|
|
|
m_DeathNotices[iMsg].bLocalPlayerInvolved = bLocalPlayerInvolved; |
|
m_DeathNotices[iMsg].Killer.iTeam = ( killer > 0 ) ? g_PR->GetTeam( killer ) : 0; |
|
m_DeathNotices[iMsg].Victim.iTeam = g_PR->GetTeam( victim ); |
|
Q_strncpy( m_DeathNotices[iMsg].Killer.szName, killer_name, ARRAYSIZE( m_DeathNotices[iMsg].Killer.szName ) ); |
|
Q_strncpy( m_DeathNotices[iMsg].Victim.szName, victim_name, ARRAYSIZE( m_DeathNotices[iMsg].Victim.szName ) ); |
|
if ( killedwith && *killedwith ) |
|
{ |
|
Q_snprintf( m_DeathNotices[iMsg].szIcon, sizeof(m_DeathNotices[iMsg].szIcon), "d_%s", killedwith ); |
|
} |
|
if ( !killer || killer == victim ) |
|
{ |
|
m_DeathNotices[iMsg].bSelfInflicted = true; |
|
m_DeathNotices[iMsg].Killer.szName[0] = 0; |
|
|
|
if ( event->GetInt( "death_flags" ) & TF_DEATH_PURGATORY ) |
|
{ |
|
// special case icon for dying in purgatory |
|
Q_strncpy( m_DeathNotices[iMsg].szIcon, "d_purgatory", ARRAYSIZE( m_DeathNotices[iMsg].szIcon ) ); |
|
} |
|
else if ( event->GetInt( "damagebits" ) & DMG_FALL ) |
|
{ |
|
// special case text for falling death |
|
V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, g_pVGuiLocalize->Find( "#DeathMsg_Fall" ), sizeof( m_DeathNotices[iMsg].wzInfoText ) ); |
|
} |
|
else if ( ( event->GetInt( "damagebits" ) & DMG_VEHICLE ) || ( 0 == Q_stricmp( m_DeathNotices[iMsg].szIcon, "d_tracktrain" ) ) ) |
|
{ |
|
// special case icon for hit-by-vehicle death |
|
Q_strncpy( m_DeathNotices[iMsg].szIcon, "d_vehicle", ARRAYSIZE( m_DeathNotices[iMsg].szIcon ) ); |
|
} |
|
} |
|
|
|
m_DeathNotices[iMsg].iWeaponID = event->GetInt( "weaponid" ); |
|
m_DeathNotices[iMsg].iKillerID = event->GetInt( "attacker" ); |
|
m_DeathNotices[iMsg].iVictimID = event->GetInt( "userid" ); |
|
|
|
char sDeathMsg[512]; |
|
|
|
// Record the death notice in the console |
|
if ( m_DeathNotices[iMsg].bSelfInflicted ) |
|
{ |
|
if ( !strcmp( m_DeathNotices[iMsg].szIcon, "d_worldspawn" ) ) |
|
{ |
|
Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s died.", m_DeathNotices[iMsg].Victim.szName ); |
|
} |
|
else // d_world |
|
{ |
|
Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s suicided.", m_DeathNotices[iMsg].Victim.szName ); |
|
} |
|
} |
|
else |
|
{ |
|
Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s killed %s", m_DeathNotices[iMsg].Killer.szName, m_DeathNotices[iMsg].Victim.szName ); |
|
|
|
if ( killedwithweaponlog && killedwithweaponlog[0] && ( killedwithweaponlog[0] > 13 ) ) |
|
{ |
|
Q_strncat( sDeathMsg, VarArgs( " with %s.", killedwithweaponlog ), sizeof( sDeathMsg ), COPY_ALL_CHARACTERS ); |
|
} |
|
else if ( m_DeathNotices[iMsg].szIcon[0] && ( m_DeathNotices[iMsg].szIcon[0] > 13 ) ) |
|
{ |
|
Q_strncat( sDeathMsg, VarArgs( " with %s.", &m_DeathNotices[iMsg].szIcon[2] ), sizeof( sDeathMsg ), COPY_ALL_CHARACTERS ); |
|
} |
|
} |
|
|
|
if ( FStrEq( pszEventName, "player_death" ) ) |
|
{ |
|
if ( m_DeathNotices[iMsg].bCrit ) |
|
{ |
|
Msg( "%s (crit)\n", sDeathMsg ); |
|
} |
|
else |
|
{ |
|
Msg( "%s\n", sDeathMsg ); |
|
} |
|
} |
|
} |
|
else if ( FStrEq( "teamplay_point_captured", pszEventName ) ) |
|
{ |
|
GetLocalizedControlPointName( event, m_DeathNotices[iMsg].Victim.szName, ARRAYSIZE( m_DeathNotices[iMsg].Victim.szName ) ); |
|
|
|
// Array of capper indices |
|
const char *cappers = event->GetString("cappers"); |
|
|
|
char szCappers[256]; |
|
szCappers[0] = '\0'; |
|
|
|
int len = Q_strlen(cappers); |
|
for( int i=0;i<len;i++ ) |
|
{ |
|
int iPlayerIndex = (int)cappers[i]; |
|
|
|
Assert( iPlayerIndex > 0 && iPlayerIndex <= gpGlobals->maxClients ); |
|
|
|
const char *pPlayerName = g_PR->GetPlayerName( iPlayerIndex ); |
|
|
|
if ( i == 0 ) |
|
{ |
|
// use first player as the team |
|
m_DeathNotices[iMsg].Killer.iTeam = g_PR->GetTeam( iPlayerIndex ); |
|
m_DeathNotices[iMsg].Victim.iTeam = TEAM_UNASSIGNED; |
|
} |
|
else |
|
{ |
|
Q_strncat( szCappers, ", ", sizeof(szCappers), 2 ); |
|
} |
|
|
|
Q_strncat( szCappers, pPlayerName, sizeof(szCappers), COPY_ALL_CHARACTERS ); |
|
if ( iLocalPlayerIndex == iPlayerIndex ) |
|
m_DeathNotices[iMsg].bLocalPlayerInvolved = true; |
|
} |
|
|
|
Q_strncpy( m_DeathNotices[iMsg].Killer.szName, szCappers, sizeof(m_DeathNotices[iMsg].Killer.szName) ); |
|
V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, g_pVGuiLocalize->Find( len > 1 ? "#Msg_Captured_Multiple" : "#Msg_Captured" ), sizeof( m_DeathNotices[iMsg].wzInfoText ) ); |
|
|
|
// print a log message |
|
Msg( "%s captured %s for team #%d\n", m_DeathNotices[iMsg].Killer.szName, m_DeathNotices[iMsg].Victim.szName, m_DeathNotices[iMsg].Killer.iTeam ); |
|
} |
|
else if ( FStrEq( "teamplay_capture_blocked", pszEventName ) ) |
|
{ |
|
GetLocalizedControlPointName( event, m_DeathNotices[iMsg].Victim.szName, ARRAYSIZE( m_DeathNotices[iMsg].Victim.szName ) ); |
|
V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, g_pVGuiLocalize->Find( "#Msg_Defended" ), sizeof( m_DeathNotices[iMsg].wzInfoText ) ); |
|
|
|
int iPlayerIndex = event->GetInt( "blocker" ); |
|
const char *blocker_name = g_PR->GetPlayerName( iPlayerIndex ); |
|
Q_strncpy( m_DeathNotices[iMsg].Killer.szName, blocker_name, ARRAYSIZE( m_DeathNotices[iMsg].Killer.szName ) ); |
|
m_DeathNotices[iMsg].Killer.iTeam = g_PR->GetTeam( iPlayerIndex ); |
|
if ( iLocalPlayerIndex == iPlayerIndex ) |
|
m_DeathNotices[iMsg].bLocalPlayerInvolved = true; |
|
|
|
// print a log message |
|
Msg( "%s defended %s for team #%d\n", m_DeathNotices[iMsg].Killer.szName, m_DeathNotices[iMsg].Victim.szName, m_DeathNotices[iMsg].Killer.iTeam ); |
|
} |
|
else if ( FStrEq( "teamplay_flag_event", pszEventName ) ) |
|
{ |
|
// don't handle any flag events for death notices while in player destruction mode |
|
if ( CTFPlayerDestructionLogic::GetRobotDestructionLogic() && CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() == CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION ) |
|
{ |
|
// don't put anything up |
|
m_DeathNotices.Remove( iMsg ); |
|
return; |
|
} |
|
|
|
const char *pszMsgKey = NULL; |
|
int iEventType = event->GetInt( "eventtype" ); |
|
|
|
bool bIsMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); |
|
if ( bIsMvM ) |
|
{ |
|
// MvM only cares about Defend notifications |
|
if ( iEventType != TF_FLAGEVENT_DEFEND ) |
|
{ |
|
// unsupported, don't put anything up |
|
m_DeathNotices.Remove( iMsg ); |
|
return; |
|
} |
|
} |
|
|
|
bool bIsHalloween2014 = TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ); |
|
|
|
switch ( iEventType ) |
|
{ |
|
case TF_FLAGEVENT_PICKUP: |
|
pszMsgKey = bIsHalloween2014 ? "#Msg_PickedUpFlagHalloween2014" : "#Msg_PickedUpFlag"; |
|
break; |
|
case TF_FLAGEVENT_CAPTURE: |
|
pszMsgKey = bIsHalloween2014 ? "#Msg_CapturedFlagHalloween2014" : "#Msg_CapturedFlag"; |
|
break; |
|
case TF_FLAGEVENT_DEFEND: |
|
if ( bIsMvM ) |
|
{ |
|
pszMsgKey = "#Msg_DefendedBomb"; |
|
} |
|
else |
|
{ |
|
pszMsgKey = bIsHalloween2014 ? "#Msg_DefendedFlagHalloween2014" : "#Msg_DefendedFlag"; |
|
} |
|
|
|
|
|
break; |
|
|
|
// Add this when we can get localization for it |
|
//case TF_FLAGEVENT_DROPPED: |
|
// pszMsgKey = "#Msg_DroppedFlag"; |
|
// break; |
|
|
|
default: |
|
// unsupported, don't put anything up |
|
m_DeathNotices.Remove( iMsg ); |
|
return; |
|
} |
|
|
|
wchar_t *pwzEventText = g_pVGuiLocalize->Find( pszMsgKey ); |
|
Assert( pwzEventText ); |
|
if ( pwzEventText ) |
|
{ |
|
V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, pwzEventText, sizeof( m_DeathNotices[iMsg].wzInfoText ) ); |
|
} |
|
else |
|
{ |
|
V_memset( m_DeathNotices[iMsg].wzInfoText, 0, sizeof( m_DeathNotices[iMsg].wzInfoText ) ); |
|
} |
|
|
|
int iPlayerIndex = event->GetInt( "player" ); |
|
const char *szPlayerName = g_PR->GetPlayerName( iPlayerIndex ); |
|
Q_strncpy( m_DeathNotices[iMsg].Killer.szName, szPlayerName, ARRAYSIZE( m_DeathNotices[iMsg].Killer.szName ) ); |
|
m_DeathNotices[iMsg].Killer.iTeam = g_PR->GetTeam( iPlayerIndex ); |
|
if ( iLocalPlayerIndex == iPlayerIndex ) |
|
m_DeathNotices[iMsg].bLocalPlayerInvolved = true; |
|
} |
|
else if ( bSpecialScore ) |
|
{ |
|
DeathNoticeItem &msg = m_DeathNotices[iMsg]; |
|
|
|
int iScorer = event->GetInt( "player" ); |
|
const char *pszScorer = ( iScorer > 0 ) ? g_PR->GetPlayerName( iScorer ) : ""; |
|
if ( !pszScorer ) |
|
{ |
|
pszScorer = ""; |
|
} |
|
Q_strncpy( msg.Killer.szName, pszScorer, ARRAYSIZE( msg.Killer.szName ) ); |
|
|
|
m_DeathNotices[iMsg].Killer.iTeam = ( iScorer > 0 ) ? g_PR->GetTeam( iScorer ) : 0; |
|
msg.bLocalPlayerInvolved = ( iScorer == GetLocalPlayerIndex() ); |
|
msg.iKillerID = iScorer; |
|
msg.bCrit = false; |
|
msg.iconCritDeath = NULL; |
|
msg.bSpecialScore = true; |
|
|
|
wchar_t wzCount[10]; |
|
_snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", ++msg.iCount ); |
|
g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, g_pVGuiLocalize->Find( "#SpecialScore_Count" ), 1, wzCount ); |
|
} |
|
else if ( FStrEq( "team_leader_killed", pszEventName ) ) |
|
{ |
|
DeathNoticeItem &msg = m_DeathNotices[iMsg]; |
|
|
|
int iKiller = event->GetInt( "killer" ); |
|
const char *pszKiller = ( iKiller > 0 ) ? g_PR->GetPlayerName( iKiller ) : ""; |
|
if ( !pszKiller ) |
|
{ |
|
pszKiller = ""; |
|
} |
|
Q_strncpy( msg.Killer.szName, pszKiller, ARRAYSIZE( msg.Killer.szName ) ); |
|
m_DeathNotices[iMsg].Killer.iTeam = ( iKiller > 0 ) ? g_PR->GetTeam( iKiller ) : 0; |
|
|
|
int iVictim = event->GetInt( "victim" ); |
|
const char *pszVictim = ( iVictim > 0 ) ? g_PR->GetPlayerName( iVictim ) : ""; |
|
if ( !pszVictim ) |
|
{ |
|
pszVictim = ""; |
|
} |
|
Q_strncpy( msg.Victim.szName, pszVictim, ARRAYSIZE( msg.Victim.szName ) ); |
|
m_DeathNotices[iMsg].Victim.iTeam = ( iVictim > 0 ) ? g_PR->GetTeam( iVictim ) : 0; |
|
|
|
msg.bLocalPlayerInvolved = ( ( iKiller == GetLocalPlayerIndex() ) || ( iVictim == GetLocalPlayerIndex() ) ); |
|
msg.iKillerID = iKiller; |
|
msg.iVictimID = iVictim; |
|
msg.bCrit = false; |
|
msg.iconCritDeath = NULL; |
|
|
|
wchar_t *pwzEventText = g_pVGuiLocalize->Find( "#TeamLeader_Kill" ); |
|
Assert( pwzEventText ); |
|
if ( pwzEventText ) |
|
{ |
|
V_wcsncpy( m_DeathNotices[iMsg].wzInfoText, pwzEventText, sizeof( m_DeathNotices[iMsg].wzInfoText ) ); |
|
} |
|
|
|
bTeamLeaderKilled = true; |
|
} |
|
|
|
OnGameEvent( event, iMsg ); |
|
|
|
if ( !bSpecialScore && !bTeamLeaderKilled ) |
|
{ |
|
if ( !m_DeathNotices[iMsg].iconDeath && m_DeathNotices[iMsg].szIcon ) |
|
{ |
|
// Try and find the death identifier in the icon list |
|
// On consoles, we flip usage of the inverted icon to make it more visible |
|
bool bInverted = m_DeathNotices[iMsg].bLocalPlayerInvolved; |
|
if ( IsConsole() ) |
|
{ |
|
bInverted = !bInverted; |
|
} |
|
m_DeathNotices[iMsg].iconDeath = GetIcon( m_DeathNotices[iMsg].szIcon, bInverted ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard ); |
|
if ( !m_DeathNotices[iMsg].iconDeath ) |
|
{ |
|
// Can't find it, so use the default skull & crossbones icon |
|
m_DeathNotices[iMsg].iconDeath = GetIcon( "d_skull_tf", m_DeathNotices[iMsg].bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets the localized name of the control point sent in the event |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::GetLocalizedControlPointName( IGameEvent *event, char *namebuf, int namelen ) |
|
{ |
|
// Cap point name ( MATTTODO: can't we find this from the point index ? ) |
|
const char *pName = event->GetString( "cpname", "Unnamed Control Point" ); |
|
const wchar_t *pLocalizedName = g_pVGuiLocalize->Find( pName ); |
|
|
|
if ( pLocalizedName ) |
|
{ |
|
g_pVGuiLocalize->ConvertUnicodeToANSI( pLocalizedName, namebuf, namelen ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( namebuf, pName, namelen ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds a new death notice to the queue |
|
//----------------------------------------------------------------------------- |
|
int CHudBaseDeathNotice::AddDeathNoticeItem() |
|
{ |
|
int iMsg = m_DeathNotices.AddToTail(); |
|
DeathNoticeItem &msg = m_DeathNotices[iMsg]; |
|
msg.flCreationTime = gpGlobals->curtime; |
|
return iMsg; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: draw text helper |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::DrawText( int x, int y, HFont hFont, Color clr, const wchar_t *szText ) |
|
{ |
|
surface()->DrawSetTextPos( x, y ); |
|
surface()->DrawSetTextColor( clr ); |
|
surface()->DrawSetTextFont( hFont ); //reset the font, draw icon can change it |
|
surface()->DrawUnicodeString( szText, vgui::FONT_DRAW_NONADDITIVE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates a rounded-corner polygon that fits in the specified bounds |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::GetBackgroundPolygonVerts( int x0, int y0, int x1, int y1, int iVerts, vgui::Vertex_t vert[] ) |
|
{ |
|
Assert( iVerts == NUM_BACKGROUND_COORD ); |
|
// use the offsets we generated for one corner and apply those to the passed-in dimensions to create verts for the poly |
|
for ( int i = 0; i < NUM_CORNER_COORD; i++ ) |
|
{ |
|
int j = ( NUM_CORNER_COORD-1 ) - i; |
|
// upper left corner |
|
vert[i].Init( Vector2D( x0 + m_CornerCoord[i].x, y0 + m_CornerCoord[i].y ) ); |
|
// upper right corner |
|
vert[i+NUM_CORNER_COORD].Init( Vector2D( x1 - m_CornerCoord[j].x, y0 + m_CornerCoord[j].y ) ); |
|
// lower right corner |
|
vert[i+(NUM_CORNER_COORD*2)].Init( Vector2D( x1 - m_CornerCoord[i].x, y1 - m_CornerCoord[i].y ) ); |
|
// lower left corner |
|
vert[i+(NUM_CORNER_COORD*3)].Init( Vector2D( x0 + m_CornerCoord[j].x, y1 - m_CornerCoord[j].y) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates the offsets for rounded corners based on current screen res |
|
//----------------------------------------------------------------------------- |
|
void CHudBaseDeathNotice::CalcRoundedCorners() |
|
{ |
|
// generate the offset geometry for upper left corner |
|
int iMax = ARRAYSIZE( m_CornerCoord ); |
|
for ( int i = 0; i < iMax; i++ ) |
|
{ |
|
m_CornerCoord[i].x = m_flCornerRadius * ( 1 - cos( ( (float) i / (float) (iMax - 1 ) ) * ( M_PI / 2 ) ) ); |
|
m_CornerCoord[i].y = m_flCornerRadius * ( 1 - sin( ( (float) i / (float) (iMax - 1 ) ) * ( M_PI / 2 ) ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets specified icon |
|
//----------------------------------------------------------------------------- |
|
CHudTexture *CHudBaseDeathNotice::GetIcon( const char *szIcon, EDeathNoticeIconFormat eIconFormat ) |
|
{ |
|
// adjust the style (prefix) of the icon if requested |
|
if ( eIconFormat != kDeathNoticeIcon_Standard && V_strncmp( "d_", szIcon, 2 ) == 0 ) |
|
{ |
|
Assert( eIconFormat == kDeathNoticeIcon_Inverted ); |
|
|
|
const char *cszNewPrefix = "dneg_"; |
|
unsigned int iNewPrefixLen = V_strlen( cszNewPrefix ); |
|
|
|
// generate new string with correct prefix |
|
enum { kIconTempStringLen = 256 }; |
|
|
|
char szIconTmp[kIconTempStringLen]; |
|
V_strncpy( szIconTmp, cszNewPrefix, kIconTempStringLen ); |
|
V_strncat( szIconTmp, szIcon + 2, kIconTempStringLen - iNewPrefixLen ); |
|
|
|
CHudTexture *pIcon = gHUD.GetIcon( szIconTmp ); |
|
|
|
// return inverted version if found |
|
if ( pIcon ) |
|
return pIcon; |
|
} |
|
|
|
// we either requested the default style or we requested an alternate style but |
|
// didn't have the art for it; either way, we can't, so fall back to our default |
|
return gHUD.GetIcon( szIcon ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets the expiry time for this death notice item |
|
//----------------------------------------------------------------------------- |
|
float DeathNoticeItem::GetExpiryTime() |
|
{ |
|
float flDuration = hud_deathnotice_time.GetFloat(); |
|
if ( bLocalPlayerInvolved ) |
|
{ |
|
// if the local player is involved, make the message last longer |
|
flDuration *= 2; |
|
} |
|
return flCreationTime + flDuration; |
|
}
|
|
|