diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/hud_basedeathnotice.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/tf/hud_basedeathnotice.cpp')
| -rw-r--r-- | game/client/tf/hud_basedeathnotice.cpp | 920 |
1 files changed, 920 insertions, 0 deletions
diff --git a/game/client/tf/hud_basedeathnotice.cpp b/game/client/tf/hud_basedeathnotice.cpp new file mode 100644 index 0000000..a5f6a33 --- /dev/null +++ b/game/client/tf/hud_basedeathnotice.cpp @@ -0,0 +1,920 @@ +//========= 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; +} |