diff options
Diffstat (limited to 'game/client/tf/tf_hud_deathnotice.cpp')
| -rw-r--r-- | game/client/tf/tf_hud_deathnotice.cpp | 1635 |
1 files changed, 1635 insertions, 0 deletions
diff --git a/game/client/tf/tf_hud_deathnotice.cpp b/game/client/tf/tf_hud_deathnotice.cpp new file mode 100644 index 0000000..44485c6 --- /dev/null +++ b/game/client/tf/tf_hud_deathnotice.cpp @@ -0,0 +1,1635 @@ +//========= 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 "vgui_controls/TextImage.h" +#include <KeyValues.h> +#include "c_baseplayer.h" +#include "c_team.h" +#include "gcsdk/gcclientsdk.h" +#include "tf_gcmessages.h" +#include "tf_item_inventory.h" + +#include "hud_basedeathnotice.h" + +#include "tf_shareddefs.h" +#include "clientmode_tf.h" +#include "c_tf_player.h" +#include "c_tf_playerresource.h" +#include "tf_hud_freezepanel.h" +#include "engine/IEngineSound.h" +#include "tf_controls.h" +#include "tf_gamerules.h" +#include "econ_notifications.h" +//#include "econ/econ_controls.h" +#include "passtime_game_events.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +using namespace vgui; + +// Must match resource/tf_objects.txt!!! +const char *szLocalizedObjectNames[OBJ_LAST] = +{ + "#TF_Object_Dispenser", + "#TF_Object_Tele", + "#TF_Object_Sentry", + "#TF_object_Sapper" +}; + +ConVar cl_hud_killstreak_display_time( "cl_hud_killstreak_display_time", "3", FCVAR_ARCHIVE, "How long a killstreak notice stays on the screen (in seconds). Range is from 0 to 100." ); +ConVar cl_hud_killstreak_display_fontsize( "cl_hud_killstreak_display_fontsize", "0", FCVAR_ARCHIVE, "Adjusts font size of killstreak notices. Range is from 0 to 2 (default is 1)." ); +ConVar cl_hud_killstreak_display_alpha( "cl_hud_killstreak_display_alpha", "120", FCVAR_ARCHIVE, "Adjusts font alpha value of killstreak notices. Range is from 0 to 255 (default is 200)." ); + +const int STREAK_MIN = 5; +const int STREAK_MIN_MVM = 20; +const int STREAK_MIN_DUCKS = 10; + +static int MinStreakForType( CTFPlayerShared::ETFStreak eStreakType ) +{ + bool bIsMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); + if ( eStreakType == CTFPlayerShared::kTFStreak_Ducks ) + { + return STREAK_MIN_DUCKS; + } + if ( eStreakType == CTFPlayerShared::kTFStreak_Duck_levelup ) + { + return 1; + } + if ( bIsMvM ) + { + return STREAK_MIN_MVM; + } + return STREAK_MIN; +} + +//========================================================= +// CTFStreakNotice +//========================================================= +class CTFStreakNotice : public CHudElement, public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CTFStreakNotice, vgui::EditablePanel ); +public: + CTFStreakNotice( const char *pName ); + + virtual bool ShouldDraw( void ) OVERRIDE; + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE; + virtual void Paint( void ) OVERRIDE; + + void StreakEnded( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iVictimID, int iStreak ); + void StreakUpdated( CTFPlayerShared::ETFStreak eStreakType, int iPlayerID, int iStreak, int iStreakIncrement ); + + bool IsCurrentStreakHigherPriority( CTFPlayerShared::ETFStreak eStreakType, int iStreak ); + HFont GetStreakFont( void ); + +private: + CExLabel *m_pLabel; + EditablePanel *m_pBackground; + + float m_flLastMessageTime; + int m_nCurrStreakCount; + CTFPlayerShared::ETFStreak m_nCurrStreakType; + + int m_nLabelXPos; + int m_nLabelYPos; + + CHudTexture *m_iconKillStreak; + CHudTexture *m_iconDuckStreak; +}; + +//----------------------------------------------------------------------------- +CTFStreakNotice::CTFStreakNotice( const char *pName ) : CHudElement( pName ), vgui::EditablePanel( NULL, pName ) +{ + SetParent( g_pClientMode->GetViewport() ); + + m_pBackground = new EditablePanel( this, "Background" ); + m_pLabel = new CExLabel( this, "SplashLabel", "" ); + + m_flLastMessageTime = -10.0f; + m_nCurrStreakCount = 0; + m_nCurrStreakType = (CTFPlayerShared::ETFStreak)0; + + m_iconKillStreak = gHUD.GetIcon( "leaderboard_streak" ); + m_iconDuckStreak = gHUD.GetIcon( "eotl_duck" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFStreakNotice::ShouldDraw( void ) +{ + if ( !CHudElement::ShouldDraw() ) + return false; + + C_TFPlayer *pPlayer = CTFPlayer::GetLocalTFPlayer(); + if ( !pPlayer ) + return false; + + if ( IsTakingAFreezecamScreenshot() ) + return false; + + return m_nCurrStreakCount > 0; +} + +//----------------------------------------------------------------------------- +void CTFStreakNotice::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + LoadControlSettings( "resource/UI/HudKillStreakNotice.res" ); + + m_pLabel->GetPos( m_nLabelXPos, m_nLabelYPos ); + + SetSize( XRES(640), YRES(480) ); +} + +//----------------------------------------------------------------------------- +void CTFStreakNotice::Paint( void ) +{ + int nDisplayTime = clamp( cl_hud_killstreak_display_time.GetInt(), 1, 100 ); + if ( m_flLastMessageTime + nDisplayTime < gpGlobals->realtime ) + { + SetVisible( false ); + m_nCurrStreakCount = 0; + return; + } + + float flFadeTime = 1.5f; + float flTimeRemaining = (float)nDisplayTime - ( gpGlobals->realtime - m_flLastMessageTime ); + + SetVisible( true ); + if ( flTimeRemaining > flFadeTime ) + { + SetAlpha( 255 ); + } + else + { + float flAlpha = RemapValClamped( flTimeRemaining, flFadeTime, 0.f, 255.f, 0.f ); + SetAlpha( flAlpha ); + } + + // Move labels down when in spectator + C_TFPlayer *pPlayer = CTFPlayer::GetLocalTFPlayer(); + CHudTexture *pIcon = ( m_nCurrStreakType == CTFPlayerShared::kTFStreak_Ducks || m_nCurrStreakType == CTFPlayerShared::kTFStreak_Duck_levelup ) ? m_iconDuckStreak : m_iconKillStreak; + if ( pPlayer && pIcon ) + { + int nYOffset = ( pPlayer->GetObserverMode() > OBS_MODE_FREEZECAM ? YRES(40) : 0 ); + + int iWide, iTall; + m_pLabel->GetContentSize( iWide, iTall ); + m_pLabel->SizeToContents(); + m_pLabel->SetPos( XRES(320) - iWide / 2, m_nLabelYPos + nYOffset ); + + m_pBackground->SetSize( iWide + iTall / 2, iTall ); // add in icon width + m_pBackground->SetPos( XRES(315) - iWide / 2, m_nLabelYPos + nYOffset); + + wchar_t szTitle[256]; + m_pLabel->GetText( szTitle, 256 ); + HFont hFont = GetStreakFont(); + int iTextWide= UTIL_ComputeStringWidth( hFont, szTitle ); + pIcon->DrawSelf( XRES(320) - (iWide / 2) + iTextWide, m_nLabelYPos + nYOffset, iTall, iTall, Color(235, 226, 202, GetAlpha() ) ); + } + + BaseClass::Paint(); +} + +//----------------------------------------------------------------------------- +void CTFStreakNotice::StreakEnded( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iVictimID, int iStreak ) +{ + if ( iStreak < 10 ) + return; + + if ( IsCurrentStreakHigherPriority( eStreakType, iStreak ) ) + return; + + // Temp override all messages + // Add New message + m_flLastMessageTime = gpGlobals->realtime; + + // Generate the String + const wchar_t *wzMsg = NULL; + bool bSelfKill = false; + if ( iKillerID == iVictimID ) + { + wzMsg = g_pVGuiLocalize->Find( ( eStreakType == CTFPlayerShared::kTFStreak_Ducks ) ? "#Msg_DuckStreakEndSelf" : "#Msg_KillStreakEndSelf" ); + bSelfKill = true; + } + else + { + wzMsg = g_pVGuiLocalize->Find( ( eStreakType == CTFPlayerShared::kTFStreak_Ducks ) ? "#Msg_DuckStreakEnd" : "#Msg_KillStreakEnd" ); + } + + if ( !wzMsg ) + return; + + // m_nCurrStreakCount = iStreak; + + // Killer Name + wchar_t wszKillerName[MAX_PLAYER_NAME_LENGTH / 2]; + g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( iKillerID ), wszKillerName, sizeof(wszKillerName) ); + + // Victim Name + wchar_t wszVictimName[MAX_PLAYER_NAME_LENGTH / 2]; + g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( iVictimID ), wszVictimName, sizeof(wszVictimName) ); + + // Count + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iStreak ); + + wchar_t wTemp[256]; + if ( bSelfKill ) + { + g_pVGuiLocalize->ConstructString_safe( wTemp, wzMsg, 2, wszKillerName, wzCount ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( wTemp, wzMsg, 3, wszKillerName, wszVictimName, wzCount ); + } + + HFont hFont = GetStreakFont(); + if ( m_pLabel->GetFont() != hFont ) + { + m_pLabel->SetFont( hFont ); + } + m_pLabel->SetText( wTemp ); + + // Get player Team for color + Color cKillerColor(235, 226, 202, 255); + if ( g_PR->GetTeam( iKillerID ) == TF_TEAM_RED ) + { + cKillerColor = COLOR_RED; + } + else if ( g_PR->GetTeam( iKillerID ) == TF_TEAM_BLUE ) + { + cKillerColor = COLOR_BLUE; + } + + Color cVictimColor(235, 226, 202, 255); + if ( g_PR->GetTeam( iVictimID ) == TF_TEAM_RED ) + { + cVictimColor = COLOR_RED; + } + else if ( g_PR->GetTeam( iVictimID ) == TF_TEAM_BLUE ) + { + cVictimColor = COLOR_BLUE; + } + + m_pLabel->GetTextImage()->ClearColorChangeStream(); + + // We change the title's text color to match the colors of the matching model panel backgrounds + wchar_t *txt = wTemp; + int iWChars = 0; + while ( txt && *txt ) + { + switch ( *txt ) + { + case 0x01: // Normal color + m_pLabel->GetTextImage()->AddColorChange( Color(235, 226, 202, cl_hud_killstreak_display_alpha.GetInt() ), iWChars ); + break; + case 0x02: // Team color + m_pLabel->GetTextImage()->AddColorChange( Color( cKillerColor.r(), cKillerColor.g(), cKillerColor.b(), cl_hud_killstreak_display_alpha.GetInt() ), iWChars ); + break; + case 0x03: // Item 2 color + m_pLabel->GetTextImage()->AddColorChange( Color( cVictimColor.r(), cVictimColor.g(), cVictimColor.b(), cl_hud_killstreak_display_alpha.GetInt() ), iWChars ); + break; + default: + break; + } + + txt++; + iWChars++; + } + + m_flLastMessageTime = gpGlobals->realtime; + SetVisible( true ); +} + +//----------------------------------------------------------------------------- +void CTFStreakNotice::StreakUpdated( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iStreak, int iStreakIncrement ) +{ + // Temp override all messages + // Add New message + + bool bIsMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode(); + int iStreakMin = MinStreakForType( eStreakType ); + + if ( IsCurrentStreakHigherPriority( eStreakType, iStreak ) ) + return; + + // Is this message worth responding to + int iStreakTier = 0; + if ( eStreakType == CTFPlayerShared::kTFStreak_Ducks) + { + // Notices at 15, 30, then increments of 50. We may increment by multiple ducks per kill, so check if we passed over a milestone. + if ( iStreak >= 15 && ( iStreak - iStreakIncrement < 15 ) ) + { + iStreakTier = 1; + iStreak = 15; + } + else if ( iStreak >= 30 && ( iStreak - iStreakIncrement <30 ) ) + { + iStreakTier = 2; + iStreak = 30; + } + else if ( iStreak > 50 && iStreak % 50 < iStreakIncrement ) + { + iStreakTier = Min( 2 + ( iStreak / 50 ), 5 ); + iStreak -= iStreak % 50; + } + else + { + return; + } + } + else if ( eStreakType == CTFPlayerShared::kTFStreak_Duck_levelup ) + { + iStreakTier = 5; + } + else if ( bIsMvM ) + { + if ( iStreak % iStreakMin != 0 ) + return; + + iStreakTier = iStreak / iStreakMin; + } + else + { + if ( iStreak == 5 ) + { + iStreakTier = 1; + } + else if ( iStreak == 10 ) + { + iStreakTier = 2; + } + else if ( iStreak == 15 ) + { + iStreakTier = 3; + } + else if ( iStreak == 20 ) + { + iStreakTier = 4; + } + else if ( iStreak % 10 == 0 || iStreak % 10 == 5 ) + { + iStreakTier = 5; + } + else + { + return; + } + } + + m_nCurrStreakCount = iStreak; + m_nCurrStreakType = eStreakType; + + const wchar_t *wzMsg = NULL; + const char *pszSoundName = "Game.KillStreak"; + Color cCustomColor(235, 226, 202, 255); + if ( eStreakType == CTFPlayerShared::kTFStreak_Ducks ) + { + // Duckstreak tiers + switch ( iStreakTier ) + { + case 1: + // TODO duckier colors? + cCustomColor = Color( 112, 176, 74, 255); // Green + wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckStreak1" ); + //pszSoundName = "Announcer.DuckStreak_Level1"; + break; + case 2: + cCustomColor = Color( 207, 106, 50, 255); // Orange + wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckStreak2" ); + //pszSoundName = "Announcer.DuckStreak_Level2"; + break; + case 3: + cCustomColor = Color( 134, 80, 172, 255); // Purple + wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckStreak3" ); + //pszSoundName = "Announcer.DuckStreak_Level3"; + break; + case 4: + cCustomColor = Color(255, 215, 0, 255); // Gold + wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckStreak4" ); + //pszSoundName = "Announcer.DuckStreak_Level4"; + break; + default: + cCustomColor = Color(255, 215, 0, 255); // Still Gold + wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckStreak5" ); + //pszSoundName = "Announcer.DuckStreak_Level4"; + break; + } + } + else if ( eStreakType == CTFPlayerShared::kTFStreak_Duck_levelup ) + { + cCustomColor = Color( 255, 215, 0, 255 ); // Gold + switch ( RandomInt( 1, 3 ) ) + { + case 1: wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckLevelup1" ); break; + case 2: wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckLevelup2" ); break; + default: wzMsg = g_pVGuiLocalize->Find( "#Msg_DuckLevelup3" ); break; + } + } + else + { + // Killstreak tiers + switch ( iStreakTier ) + { + case 1: + cCustomColor = Color( 112, 176, 74, 255); // Green + wzMsg = g_pVGuiLocalize->Find( "#Msg_KillStreak1" ); + //pszSoundName = "Announcer.KillStreak_Level1"; + break; + case 2: + cCustomColor = Color( 207, 106, 50, 255); // Orange + wzMsg = g_pVGuiLocalize->Find( "#Msg_KillStreak2" ); + //pszSoundName = "Announcer.KillStreak_Level2"; + break; + case 3: + cCustomColor = Color( 134, 80, 172, 255); // Purple + wzMsg = g_pVGuiLocalize->Find( "#Msg_KillStreak3" ); + //pszSoundName = "Announcer.KillStreak_Level3"; + break; + case 4: + cCustomColor = Color(255, 215, 0, 255); // Gold + wzMsg = g_pVGuiLocalize->Find( "#Msg_KillStreak4" ); + //pszSoundName = "Announcer.KillStreak_Level4"; + break; + default: + cCustomColor = Color(255, 215, 0, 255); // Still Gold + wzMsg = g_pVGuiLocalize->Find( "#Msg_KillStreak5" ); + //pszSoundName = "Announcer.KillStreak_Level4"; + break; + } + } + + if ( !wzMsg ) + return; + + // Get player Team for color + Color cTeamColor(235, 226, 202, 255); + if ( g_PR->GetTeam( iKillerID ) == TF_TEAM_RED ) + { + cTeamColor = COLOR_RED; + } + else if ( g_PR->GetTeam( iKillerID ) == TF_TEAM_BLUE ) + { + cTeamColor = COLOR_BLUE; + } + + // Generate the String + // Count + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iStreak ); + + // Name + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH / 2]; + g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( iKillerID ), wszPlayerName, sizeof(wszPlayerName) ); + + wchar_t wTemp[256]; + g_pVGuiLocalize->ConstructString_safe( wTemp, wzMsg, 2, wszPlayerName, wzCount ); + + HFont hFont = GetStreakFont(); + if ( m_pLabel->GetFont() != hFont ) + { + m_pLabel->SetFont( hFont ); + } + m_pLabel->SetText( wTemp ); + + // Now go through the string and find the escape characters telling us where the color changes are + m_pLabel->GetTextImage()->ClearColorChangeStream(); + + // We change the title's text color to match the colors of the matching model panel backgrounds + wchar_t *txt = wTemp; + int iWChars = 0; + while ( txt && *txt ) + { + switch ( *txt ) + { + case 0x01: // Normal color + m_pLabel->GetTextImage()->AddColorChange( Color(235,226,202,cl_hud_killstreak_display_alpha.GetInt() ), iWChars ); + break; + case 0x02: // Team color + m_pLabel->GetTextImage()->AddColorChange( Color( cTeamColor.r(), cTeamColor.g(), cTeamColor.b(), cl_hud_killstreak_display_alpha.GetInt() ), iWChars ); + break; + case 0x03: // Item 2 color + m_pLabel->GetTextImage()->AddColorChange( Color( cCustomColor.r(), cCustomColor.g(), cCustomColor.b(), cl_hud_killstreak_display_alpha.GetInt() ), iWChars ); + break; + default: + break; + } + + txt++; + iWChars++; + } + + // Play Local Sound + int iLocalPlayerIndex = GetLocalPlayerIndex(); + if ( iLocalPlayerIndex == iKillerID && pszSoundName ) + { + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, pszSoundName ); + } + + m_flLastMessageTime = gpGlobals->realtime + (float)iStreakTier / 2.0; + SetVisible( true ); +} + +//----------------------------------------------------------------------------- +bool CTFStreakNotice::IsCurrentStreakHigherPriority( CTFPlayerShared::ETFStreak eStreakType, int iStreak ) +{ + // duck level ups are highest priority + if ( eStreakType == CTFPlayerShared::kTFStreak_Duck_levelup ) + return false; + + if ( !m_nCurrStreakCount ) + return false; + + // Ducks never override kills + if ( m_nCurrStreakType == CTFPlayerShared::kTFStreak_Kills && eStreakType == CTFPlayerShared::kTFStreak_Ducks ) + return true; + + // But kills always override ducks + if ( m_nCurrStreakType == CTFPlayerShared::kTFStreak_Ducks && eStreakType == CTFPlayerShared::kTFStreak_Kills ) + return false; + + // Don't stomp a higher streak with a lower, unless it's been around long enough + float flElapsedTime = gpGlobals->realtime - m_flLastMessageTime; + float flDisplayMinTime = Max( ( cl_hud_killstreak_display_time.GetFloat() / 3.f ), 1.f ); + return ( iStreak < m_nCurrStreakCount && flElapsedTime < flDisplayMinTime ); +} + +//----------------------------------------------------------------------------- +HFont CTFStreakNotice::GetStreakFont( void ) +{ + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + + const char *pszFontName = "HudFontSmallestBold"; + int nFontSize = cl_hud_killstreak_display_fontsize.GetInt(); // Default is 1: HudFontSmallBold + if ( nFontSize == 1 ) + { + pszFontName = "HudFontSmallBold"; + } + else if ( nFontSize == 2 ) + { + pszFontName = "HudFontMediumSmallBold"; + } + + return pScheme->GetFont( pszFontName, true ); +} + +DECLARE_HUDELEMENT( CTFStreakNotice ); + + +//----------------------------------------------------------------------------- +// TFDeathNotice +//----------------------------------------------------------------------------- +class CTFHudDeathNotice : public CHudBaseDeathNotice +{ + DECLARE_CLASS_SIMPLE( CTFHudDeathNotice, CHudBaseDeathNotice ); +public: + CTFHudDeathNotice( const char *pElementName ) : CHudBaseDeathNotice( pElementName ) {}; + virtual void Init( void ); + virtual void ApplySchemeSettings( vgui::IScheme *scheme ); + virtual bool IsVisible( void ); + virtual bool ShouldDraw( void ); + + virtual void FireGameEvent( IGameEvent *event ); + void PlayRivalrySounds( int iKillerIndex, int iVictimIndex, int iType ); + virtual bool ShouldShowDeathNotice( IGameEvent *event ); + +protected: + virtual void OnGameEvent( IGameEvent *event, int iDeathNoticeMsg ); + virtual Color GetTeamColor( int iTeamNumber, bool bLocalPlayerInvolved = false ); + virtual Color GetInfoTextColor( int iDeathNoticeMsg ); + virtual Color GetBackgroundColor ( int iDeathNoticeMsg ); + virtual bool EventIsPlayerDeath( const char *eventName ); + + virtual int UseExistingNotice( IGameEvent *event ); + +private: + void AddAdditionalMsg( int iKillerID, int iVictimID, const char *pMsgKey ); + void AddStreakMsg( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iKillerStreak, int iStreakIncrement, int iVictimID, int iDeathNoticeMsg ); + void AddStreakEndedMsg( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iVictimID, int iVictimStreak, int iDeathNoticeMsg ); + + CHudTexture* GetMannPowerIcon( RuneTypes_t tRuneType, bool bRedTeam ); + + CHudTexture *m_iconDomination; + CHudTexture *m_iconKillStreak; + CHudTexture *m_iconDuckStreak; + CHudTexture *m_iconDuckStreakDNeg; + CHudTexture *m_iconKillStreakDNeg; + + CPanelAnimationVar( Color, m_clrBlueText, "TeamBlue", "153 204 255 255" ); + CPanelAnimationVar( Color, m_clrRedText, "TeamRed", "255 64 64 255" ); + CPanelAnimationVar( Color, m_clrPurpleText, "PurpleText", "134 80 172 255" ); + CPanelAnimationVar( Color, m_clrGreenText, "GreenText", "112 176 74 255" ); + CPanelAnimationVar( Color, m_clrLocalPlayerText, "LocalPlayerColor", "65 65 65 255" ); + + CTFStreakNotice *m_pStreakNotice; + + bool m_bShowItemOnKill; +}; + +DECLARE_HUDELEMENT( CTFHudDeathNotice ); + +void CTFHudDeathNotice::Init() +{ + BaseClass::Init(); + + ListenForGameEvent( "fish_notice" ); + ListenForGameEvent( "fish_notice__arm" ); + ListenForGameEvent( "duck_xp_level_up" ); + //ListenForGameEvent( "throwable_hit" ); + + m_bShowItemOnKill = true; + + // PASSTIME if this is called at level load or something we should check mode before this block + ListenForGameEvent( PasstimeGameEvents::BallGet::s_eventName ); + ListenForGameEvent( PasstimeGameEvents::BallStolen::s_eventName ); + ListenForGameEvent( PasstimeGameEvents::Score::s_eventName ); + ListenForGameEvent( PasstimeGameEvents::PassCaught::s_eventName ); + ListenForGameEvent( PasstimeGameEvents::BallBlocked::s_eventName ); +} + +void CTFHudDeathNotice::ApplySchemeSettings( vgui::IScheme *scheme ) +{ + BaseClass::ApplySchemeSettings( scheme ); + + m_iconDomination = gHUD.GetIcon( "leaderboard_dominated" ); + + m_iconKillStreak = gHUD.GetIcon( "leaderboard_streak" ); + m_iconKillStreakDNeg = gHUD.GetIcon( "leaderboard_streak_dneg" ); + m_iconDuckStreak = gHUD.GetIcon( "eotl_duck" ); + m_iconDuckStreakDNeg = gHUD.GetIcon( "eotl_duck_dneg" ); + m_pStreakNotice = new CTFStreakNotice( "KillStreakNotice" ); +} + +bool CTFHudDeathNotice::IsVisible( void ) +{ + if ( IsTakingAFreezecamScreenshot() ) + return false; + + return BaseClass::IsVisible(); +} + +bool CTFHudDeathNotice::ShouldDraw( void ) +{ + return true; +} + +bool CTFHudDeathNotice::ShouldShowDeathNotice( IGameEvent *event ) +{ + if ( event->GetBool( "silent_kill" ) ) + { + // Don't show a kill event for the team of the silent kill victim. + int iVictimID = engine->GetPlayerForUserID( event->GetInt( "userid" ) ); + C_TFPlayer* pVictim = ToTFPlayer( UTIL_PlayerByIndex( iVictimID ) ); + if ( pVictim && pVictim->GetTeamNumber() == GetLocalPlayerTeam() && iVictimID != GetLocalPlayerIndex() ) + { + return false; + } + } + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( event->GetInt( "death_flags" ) & TF_DEATH_MINIBOSS ) == 0 ) + { + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + if ( iLocalPlayerIndex != engine->GetPlayerForUserID( event->GetInt( "attacker" ) ) && + iLocalPlayerIndex != engine->GetPlayerForUserID( event->GetInt( "assister" ) ) ) + { + C_TFPlayer* pVictim = ToTFPlayer( UTIL_PlayerByIndex( engine->GetPlayerForUserID( event->GetInt( "userid" ) ) ) ); + if ( pVictim && pVictim->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + { + return false; + } + } + } + + return true; +} + +void CTFHudDeathNotice::PlayRivalrySounds( int iKillerIndex, int iVictimIndex, int iType ) +{ + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + //We're not involved in this kill + if ( iKillerIndex != iLocalPlayerIndex && iVictimIndex != iLocalPlayerIndex ) + return; + + const char *pszSoundName = NULL; + + if ( iType == TF_DEATH_DOMINATION ) + { + if ( iKillerIndex == iLocalPlayerIndex ) + { + pszSoundName = "Game.Domination"; + } + else if ( iVictimIndex == iLocalPlayerIndex ) + { + pszSoundName = "Game.Nemesis"; + } + } + else if ( iType == TF_DEATH_REVENGE ) + { + pszSoundName = "Game.Revenge"; + } + + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, pszSoundName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Server's told us that someone's died +//----------------------------------------------------------------------------- +void CTFHudDeathNotice::FireGameEvent( IGameEvent *event ) +{ + const char * pszEventName = event->GetName(); + if ( FStrEq( "duck_xp_level_up", pszEventName ) ) + { + int level = event->GetInt( "level" ); + AddStreakMsg( CTFPlayerShared::kTFStreak_Duck_levelup, GetLocalPlayerIndex(), level, 1, -1, 0 ); + return; + } + + BaseClass::FireGameEvent( event ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFHudDeathNotice::EventIsPlayerDeath( const char* eventName ) +{ + return FStrEq( eventName, "fish_notice" ) + || FStrEq( eventName, "fish_notice__arm" ) + //|| FStrEq( eventName, "throwable_hit" ) + || BaseClass::EventIsPlayerDeath( eventName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when a game event happens and a death notice is about to be +// displayed. This method can examine the event and death notice and +// make game-specific tweaks to it before it is displayed +//----------------------------------------------------------------------------- +void CTFHudDeathNotice::OnGameEvent( IGameEvent *event, int iDeathNoticeMsg ) +{ + const bool bIsSillyPyroVision = IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ); + + const char *pszEventName = event->GetName(); + + if ( FStrEq( pszEventName, "player_death" ) || FStrEq( pszEventName, "object_destroyed" ) ) + { + bool bIsObjectDestroyed = FStrEq( pszEventName, "object_destroyed" ); + int iCustomDamage = event->GetInt( "customkill" ); + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + const int iKillerID = engine->GetPlayerForUserID( event->GetInt( "attacker" ) ); + const int iVictimID = engine->GetPlayerForUserID( event->GetInt( "userid" ) ); + // if there was an assister, put both the killer's and assister's names in the death message + int iAssisterID = engine->GetPlayerForUserID( event->GetInt( "assister" ) ); + + EHorriblePyroVisionHack ePyroVisionHack = kHorriblePyroVisionHack_KillAssisterType_Default; + CUtlConstString sAssisterNameScratch; + const char *assister_name = ( iAssisterID > 0 ? g_PR->GetPlayerName( iAssisterID ) : NULL ); + + // If we don't have a real assister (would have been passed in to us as a player index) and + // we're in crazy pyrovision mode and we got a dummy assister, than fall back and display + // that just for giggles. We use this so the Balloonicorn and friends can get the assist + // credit they so rightly deserve. + if ( !assister_name && bIsSillyPyroVision ) + { + // Ignore this for self-kills. + if ( bIsObjectDestroyed || (iKillerID != iVictimID) ) + { + const char *pszMaybeFallbackAssisterName = event->GetString( "assister_fallback" ); + if ( pszMaybeFallbackAssisterName && pszMaybeFallbackAssisterName[0] ) + { + // We store the type of silly assist in the first byte of the string because we + // are terrible people. + ePyroVisionHack = (EHorriblePyroVisionHack)pszMaybeFallbackAssisterName[0]; + Assert( ePyroVisionHack != kHorriblePyroVisionHack_KillAssisterType_Default ); + pszMaybeFallbackAssisterName = &pszMaybeFallbackAssisterName[1]; + + // If we pass in a localization string, we need to convert it back to ANSI temporarily. + // This won't localize "The" Balloonicorn because we don't have a real item with a real + // quality, etc., just a single localization token. + switch ( ePyroVisionHack ) + { + case kHorriblePyroVisionHack_KillAssisterType_LocalizationString: + case kHorriblePyroVisionHack_KillAssisterType_LocalizationString_First: + { + wchar_t *wszLocalizedItemName = GLocalizationProvider()->Find( pszMaybeFallbackAssisterName ); + char szANSIConvertedItemName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedItemName, szANSIConvertedItemName, MAX_PLAYER_NAME_LENGTH ); + sAssisterNameScratch = szANSIConvertedItemName; + assister_name = sAssisterNameScratch.Get(); + break; + } + case kHorriblePyroVisionHack_KillAssisterType_CustomName: + case kHorriblePyroVisionHack_KillAssisterType_CustomName_First: + { + sAssisterNameScratch = pszMaybeFallbackAssisterName; + assister_name = sAssisterNameScratch.Get(); + break; + } + default: + assert( !"Unknown pyro item hack type! Something has gone horribly, horribly worse." ); + } + } + } + } + + bool bMultipleKillers = false; + + if ( assister_name ) + { + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + const char *pszKillerName = msg.Killer.szName; + const char *pszAssisterName = assister_name; + + // Check to see if we're swapping the killer and the assister. We use this so the brain slug can get the kill + // credit for the HUD death notices, with the player being the assister. + if ( pszAssisterName && (ePyroVisionHack == kHorriblePyroVisionHack_KillAssisterType_CustomName_First || + ePyroVisionHack == kHorriblePyroVisionHack_KillAssisterType_LocalizationString_First) ) + { + std::swap( pszKillerName, pszAssisterName ); + } + + char szKillerBuf[MAX_PLAYER_NAME_LENGTH*2]; + Q_snprintf( szKillerBuf, ARRAYSIZE(szKillerBuf), "%s + %s", pszKillerName, pszAssisterName ); + Q_strncpy( msg.Killer.szName, szKillerBuf, ARRAYSIZE( msg.Killer.szName ) ); + if ( iLocalPlayerIndex == iAssisterID ) + { + msg.bLocalPlayerInvolved = true; + } + + bMultipleKillers = true; + } + + // play an exciting sound if a sniper pulls off any sort of penetration kill + const int iPlayerPenetrationCount = !event->IsEmpty( "playerpenetratecount" ) ? event->GetInt( "playerpenetratecount" ) : 0; + + bool bPenetrateSound = iPlayerPenetrationCount > 0; + + // This happens too frequently in Coop/TD + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + bPenetrateSound = false; + } + + if ( bPenetrateSound ) + { + CLocalPlayerFilter filter; + C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, "Game.PenetrationKill" ); + } + + + int deathFlags = event->GetInt( "death_flags" ); + + if ( !bIsObjectDestroyed ) + { + // if this death involved a player dominating another player or getting revenge on another player, add an additional message + // mentioning that + + // WARNING: AddAdditionalMsg will grow and potentially realloc the m_DeathNotices array. So be careful + // using pointers to m_DeathNotices elements... + + if ( deathFlags & TF_DEATH_DOMINATION ) + { + AddAdditionalMsg( iKillerID, iVictimID, bIsSillyPyroVision ? "#Msg_Dominating_What" : "#Msg_Dominating" ); + PlayRivalrySounds( iKillerID, iVictimID, TF_DEATH_DOMINATION ); + } + if ( deathFlags & TF_DEATH_ASSISTER_DOMINATION && ( iAssisterID > 0 ) ) + { + AddAdditionalMsg( iAssisterID, iVictimID, bIsSillyPyroVision ? "#Msg_Dominating_What" : "#Msg_Dominating" ); + PlayRivalrySounds( iAssisterID, iVictimID, TF_DEATH_DOMINATION ); + } + if ( deathFlags & TF_DEATH_REVENGE ) + { + AddAdditionalMsg( iKillerID, iVictimID, bIsSillyPyroVision ? "#Msg_Revenge_What" : "#Msg_Revenge" ); + PlayRivalrySounds( iKillerID, iVictimID, TF_DEATH_REVENGE ); + } + if ( deathFlags & TF_DEATH_ASSISTER_REVENGE && ( iAssisterID > 0 ) ) + { + AddAdditionalMsg( iAssisterID, iVictimID, bIsSillyPyroVision ? "#Msg_Revenge_What" : "#Msg_Revenge" ); + PlayRivalrySounds( iAssisterID, iVictimID, TF_DEATH_REVENGE ); + } + } + else + { + // if this is an object destroyed message, set the victim name to "<object type> (<owner>)" + int iObjectType = event->GetInt( "objecttype" ); + if ( iObjectType >= 0 && iObjectType < OBJ_LAST ) + { + // get the localized name for the object + char szLocalizedObjectName[MAX_PLAYER_NAME_LENGTH]; + szLocalizedObjectName[ 0 ] = 0; + const wchar_t *wszLocalizedObjectName = g_pVGuiLocalize->Find( szLocalizedObjectNames[iObjectType] ); + if ( wszLocalizedObjectName ) + { + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedObjectName, szLocalizedObjectName, ARRAYSIZE( szLocalizedObjectName ) ); + } + else + { + Warning( "Couldn't find localized object name for '%s'\n", szLocalizedObjectNames[iObjectType] ); + Q_strncpy( szLocalizedObjectName, szLocalizedObjectNames[iObjectType], sizeof( szLocalizedObjectName ) ); + } + + // compose the string + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + if ( msg.Victim.szName[0] ) + { + char szVictimBuf[MAX_PLAYER_NAME_LENGTH*2]; + Q_snprintf( szVictimBuf, ARRAYSIZE(szVictimBuf), "%s (%s)", szLocalizedObjectName, msg.Victim.szName ); + Q_strncpy( msg.Victim.szName, szVictimBuf, ARRAYSIZE( msg.Victim.szName ) ); + } + else + { + Q_strncpy( msg.Victim.szName, szLocalizedObjectName, ARRAYSIZE( msg.Victim.szName ) ); + } + + } + else + { + Assert( false ); // invalid object type + } + } + + const wchar_t *pMsg = NULL; + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + switch ( iCustomDamage ) + { + case TF_DMG_CUSTOM_BACKSTAB: + if ( FStrEq( msg.szIcon, "d_sharp_dresser" ) ) + { + Q_strncpy( msg.szIcon, "d_sharp_dresser_backstab", ARRAYSIZE( msg.szIcon ) ); + } + else + { + Q_strncpy( msg.szIcon, "d_backstab", ARRAYSIZE( msg.szIcon ) ); + } + break; + case TF_DMG_CUSTOM_HEADSHOT_DECAPITATION: + case TF_DMG_CUSTOM_HEADSHOT: + { + if ( FStrEq( event->GetString( "weapon" ), "ambassador" ) ) + { + Q_strncpy( msg.szIcon, "d_ambassador_headshot", ARRAYSIZE( msg.szIcon ) ); + } + else if ( FStrEq( event->GetString( "weapon" ), "huntsman" ) ) + { + Q_strncpy( msg.szIcon, "d_huntsman_headshot", ARRAYSIZE( msg.szIcon ) ); + } + else + { + // Did this headshot penetrate something before the kill? If so, show a fancy icon + // so the player feels proud. + if ( iPlayerPenetrationCount > 0 ) + { + Q_strncpy( msg.szIcon, "d_headshot_player_penetration", ARRAYSIZE( msg.szIcon ) ); + } + else + { + Q_strncpy( msg.szIcon, "d_headshot", ARRAYSIZE( msg.szIcon ) ); + } + } + + break; + } + case TF_DMG_CUSTOM_BURNING: + if ( event->GetInt( "attacker" ) == event->GetInt( "userid" ) ) + { + // suicide by fire + Q_strncpy( msg.szIcon, "d_firedeath", ARRAYSIZE( msg.szIcon ) ); + msg.wzInfoText[0] = 0; + } + break; + + case TF_DMG_CUSTOM_BURNING_ARROW: + // special-case if the player is killed from a burning arrow after it has already landed + Q_strncpy( msg.szIcon, "d_huntsman_burning", ARRAYSIZE( msg.szIcon ) ); + msg.wzInfoText[0] = 0; + break; + + case TF_DMG_CUSTOM_FLYINGBURN: + // special-case if the player is killed from a burning arrow as the killing blow + Q_strncpy( msg.szIcon, "d_huntsman_flyingburn", ARRAYSIZE( msg.szIcon ) ); + msg.wzInfoText[0] = 0; + break; + + case TF_DMG_CUSTOM_PUMPKIN_BOMB: + // special-case if the player is killed by a pumpkin bomb + Q_strncpy( msg.szIcon, "d_pumpkindeath", ARRAYSIZE( msg.szIcon ) ); + msg.wzInfoText[0] = 0; + break; + + case TF_DMG_CUSTOM_SUICIDE: + { + // display a different message if this was suicide, or assisted suicide (suicide w/recent damage, kill awarded to damager) + bool bAssistedSuicide = event->GetInt( "userid" ) != event->GetInt( "attacker" ); + pMsg = g_pVGuiLocalize->Find( ( bAssistedSuicide ) ? ( bMultipleKillers ? "#DeathMsg_AssistedSuicide_Multiple" : "#DeathMsg_AssistedSuicide" ) : ( "#DeathMsg_Suicide" ) ); + if ( pMsg ) + { + V_wcsncpy( msg.wzInfoText, pMsg, sizeof( msg.wzInfoText ) ); + } + break; + } + case TF_DMG_CUSTOM_EYEBALL_ROCKET: + { + if ( msg.Killer.iTeam == TEAM_UNASSIGNED ) + { + char szLocalizedName[MAX_PLAYER_NAME_LENGTH]; + szLocalizedName[ 0 ] = 0; + const wchar_t *wszLocalizedName = g_pVGuiLocalize->Find( "#TF_HALLOWEEN_EYEBALL_BOSS_DEATHCAM_NAME" ); + if ( wszLocalizedName ) + { + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedName, szLocalizedName, ARRAYSIZE( szLocalizedName ) ); + Q_strncpy( msg.Killer.szName, szLocalizedName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = TF_TEAM_HALLOWEEN; // This will set the name to purple for MONOCULUS! + } + } + break; + } + case TF_DMG_CUSTOM_MERASMUS_ZAP: + case TF_DMG_CUSTOM_MERASMUS_GRENADE: + case TF_DMG_CUSTOM_MERASMUS_DECAPITATION: + { + if ( msg.Killer.iTeam == TEAM_UNASSIGNED ) + { + char szLocalizedName[MAX_PLAYER_NAME_LENGTH]; + szLocalizedName[ 0 ] = 0; + const wchar_t *wszLocalizedName = g_pVGuiLocalize->Find( "#TF_HALLOWEEN_MERASMUS_DEATHCAM_NAME" ); + if ( wszLocalizedName ) + { + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedName, szLocalizedName, ARRAYSIZE( szLocalizedName ) ); + Q_strncpy( msg.Killer.szName, szLocalizedName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = TF_TEAM_HALLOWEEN; // This will set the name to green for MERASMUS! + } + } + break; + } + case TF_DMG_CUSTOM_SPELL_SKELETON: + { + if ( msg.Killer.iTeam == TEAM_UNASSIGNED ) + { + char szLocalizedName[MAX_PLAYER_NAME_LENGTH]; + szLocalizedName[ 0 ] = 0; + const wchar_t *wszLocalizedName = g_pVGuiLocalize->Find( "#TF_HALLOWEEN_SKELETON_DEATHCAM_NAME" ); + if ( wszLocalizedName ) + { + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedName, szLocalizedName, ARRAYSIZE( szLocalizedName ) ); + Q_strncpy( msg.Killer.szName, szLocalizedName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = TF_TEAM_HALLOWEEN; // This will set the name to green for THE UNDEAD! + } + } + break; + } + + case TF_DMG_CUSTOM_KART: + // special-case if the player is pushed by kart + Q_strncpy( msg.szIcon, "d_bumper_kart", ARRAYSIZE( msg.szIcon ) ); + msg.wzInfoText[0] = 0; + break; + case TF_DMG_CUSTOM_GIANT_HAMMER: + // special-case Giant hammer + Q_strncpy( msg.szIcon, "d_necro_smasher", ARRAYSIZE( msg.szIcon ) ); + msg.wzInfoText[0] = 0; + break; + default: + break; + } + + if ( ( event->GetInt( "damagebits" ) & DMG_NERVEGAS ) ) + { + // special case icon for hit-by-vehicle death + Q_strncpy( msg.szIcon, "d_saw_kill", ARRAYSIZE( msg.szIcon ) ); + } + + int iKillStreakTotal = event->GetInt( "kill_streak_total" ); + int iKillStreakWep = event->GetInt( "kill_streak_wep" ); + int iDuckStreakTotal = event->GetInt( "duck_streak_total" ); + int iDucksThisKill = event->GetInt( "ducks_streaked" ); + + // if the active weapon is kill streak + C_TFPlayer* pKiller = ToTFPlayer( UTIL_PlayerByIndex( iKillerID ) ); + C_TFPlayer* pVictim = ToTFPlayer( UTIL_PlayerByIndex( iVictimID ) ); + C_TFPlayer* pAssister = ToTFPlayer( UTIL_PlayerByIndex( iAssisterID ) ); + + // Mannpower runes + if ( pKiller && pKiller->m_Shared.IsCarryingRune() ) + { + msg.iconPreKillerName = GetMannPowerIcon( pKiller->m_Shared.GetCarryingRuneType(), pKiller->GetTeamNumber() == TF_TEAM_RED ); + } + + if ( pVictim && pVictim->m_Shared.IsCarryingRune() ) + { + msg.iconPostVictimName = GetMannPowerIcon( pVictim->m_Shared.GetCarryingRuneType(), pVictim->GetTeamNumber() == TF_TEAM_RED ); + } + + if ( iKillStreakWep > 0 ) + { + // append kill streak count to this notification + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iKillStreakWep ); + g_pVGuiLocalize->ConstructString_safe( msg.wzPreKillerText, g_pVGuiLocalize->Find("#Kill_Streak"), 1, wzCount ); + if ( msg.bLocalPlayerInvolved ) + { + msg.iconPostKillerName = m_iconKillStreakDNeg; + } + else + { + msg.iconPostKillerName = m_iconKillStreak; + } + } + else if ( iDuckStreakTotal > 0 && iDucksThisKill ) + { + // Duckstreak icon (always lower priority) + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", iDuckStreakTotal ); + g_pVGuiLocalize->ConstructString_safe( msg.wzPreKillerText, g_pVGuiLocalize->Find("#Duck_Streak"), 1, wzCount ); + msg.iconPostKillerName = msg.bLocalPlayerInvolved ? m_iconDuckStreakDNeg : m_iconDuckStreak; + } + + // Check to see if we want a extra notification + // Attempt to display these in order of descending priority + + // Check Assister for Additional Messages + int iKillStreakAssist = event->GetInt( "kill_streak_assist" ); + int iKillStreakVictim = event->GetInt( "kill_streak_victim" ); + + // Kills + AddStreakMsg( CTFPlayerShared::kTFStreak_Kills, iKillerID, iKillStreakTotal, 1, iVictimID, iDeathNoticeMsg ); + if ( pAssister && iKillStreakAssist > 1 ) + { + AddStreakMsg( CTFPlayerShared::kTFStreak_Kills, iAssisterID, iKillStreakAssist, 1, iVictimID, iDeathNoticeMsg ); + } + + if ( pVictim && iKillStreakVictim > 2 ) + { + AddStreakEndedMsg( CTFPlayerShared::kTFStreak_Kills, iKillerID, iVictimID, iKillStreakVictim, iDeathNoticeMsg ); + } + + // Ducks + int iDuckStreakAssist = event->GetInt( "duck_streak_assist" ); + int iDuckStreakVictim = event->GetInt( "duck_streak_victim" ); + int iDuckStreakIncrement = event->GetInt( "ducks_streaked" ); + + AddStreakMsg( CTFPlayerShared::kTFStreak_Ducks, iKillerID, iDuckStreakTotal, iDuckStreakIncrement, iVictimID, iDeathNoticeMsg ); + if ( pAssister && iDuckStreakAssist > 0 && iDucksThisKill ) + { + AddStreakMsg( CTFPlayerShared::kTFStreak_Ducks, iAssisterID, iDuckStreakAssist, iDuckStreakIncrement, iVictimID, iDeathNoticeMsg ); + } + + if ( pVictim && iDuckStreakVictim > 2 ) + { + AddStreakEndedMsg( CTFPlayerShared::kTFStreak_Ducks, iKillerID, iVictimID, iDuckStreakVictim, iDeathNoticeMsg ); + } + + // STAGING ONLY test + // If Local Player killed someone and they have an item waiting, let them know +#ifdef STAGING_ONLY + //if ( iLocalPlayerIndex == iKillerID && m_bShowItemOnKill ) + //{ + // if ( CEconNotification_HasNewItemsOnKill::HasUnacknowledgedItems() ) + // { + // CEconNotification_HasNewItemsOnKill *pNotification = new CEconNotification_HasNewItemsOnKill( iVictimID ); + // NotificationQueue_Add( pNotification ); + // m_bShowItemOnKill = false; + // } + //} + //if ( iLocalPlayerIndex == iVictimID ) + //{ + // m_bShowItemOnKill = true; + //} +#endif + } + else if ( FStrEq( "teamplay_point_captured", pszEventName ) || + FStrEq( "teamplay_capture_blocked", pszEventName ) || + FStrEq( "teamplay_flag_event", pszEventName ) ) + { + bool bDefense = ( FStrEq( "teamplay_capture_blocked", pszEventName ) || ( FStrEq( "teamplay_flag_event", pszEventName ) && + TF_FLAGEVENT_DEFEND == event->GetInt( "eventtype" ) ) ); + + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + const char *szCaptureIcons[] = { "d_redcapture", "d_bluecapture" }; + const char *szDefenseIcons[] = { "d_reddefend", "d_bluedefend" }; + + int iTeam = msg.Killer.iTeam; + Assert( iTeam >= FIRST_GAME_TEAM ); + Assert( iTeam < FIRST_GAME_TEAM + TF_TEAM_COUNT ); + if ( iTeam < FIRST_GAME_TEAM || iTeam >= FIRST_GAME_TEAM + TF_TEAM_COUNT ) + return; + + int iIndex = msg.Killer.iTeam - FIRST_GAME_TEAM; + Assert( iIndex < ARRAYSIZE( szCaptureIcons ) ); + + Q_strncpy( msg.szIcon, bDefense ? szDefenseIcons[iIndex] : szCaptureIcons[iIndex], ARRAYSIZE( msg.szIcon ) ); + } + else if ( FStrEq( "fish_notice", pszEventName ) || FStrEq( "fish_notice__arm", pszEventName ) ) + { + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + int deathFlags = event->GetInt( "death_flags" ); + int iCustomDamage = event->GetInt( "customkill" ); + + if ( ( iCustomDamage == TF_DMG_CUSTOM_FISH_KILL ) || ( deathFlags & TF_DEATH_FEIGN_DEATH ) ) + { + g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, FStrEq( "fish_notice", pszEventName ) ? g_pVGuiLocalize->Find("#Humiliation_Kill") : g_pVGuiLocalize->Find("#Humiliation_Kill_Arm"), 0 ); + } + else + { + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", ++msg.iCount ); + g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, g_pVGuiLocalize->Find("#Humiliation_Count"), 1, wzCount ); + } + + // if there was an assister, put both the killer's and assister's names in the death message + int iAssisterID = engine->GetPlayerForUserID( event->GetInt( "assister" ) ); + const char *assister_name = ( iAssisterID > 0 ? g_PR->GetPlayerName( iAssisterID ) : NULL ); + if ( assister_name ) + { + char szKillerBuf[MAX_PLAYER_NAME_LENGTH*2]; + Q_snprintf( szKillerBuf, ARRAYSIZE(szKillerBuf), "%s + %s", msg.Killer.szName, assister_name ); + Q_strncpy( msg.Killer.szName, szKillerBuf, ARRAYSIZE( msg.Killer.szName ) ); + } + } + //else if ( FStrEq( "throwable_hit", pszEventName ) ) + //{ + // DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + // int deathFlags = event->GetInt( "death_flags" ); + // int iCustomDamage = event->GetInt( "customkill" ); + + // // Make sure the icon is up to date + // m_DeathNotices[iDeathNoticeMsg].iconDeath = GetIcon( m_DeathNotices[ iDeathNoticeMsg ].szIcon, m_DeathNotices[iDeathNoticeMsg].bLocalPlayerInvolved ? kDeathNoticeIcon_Inverted : kDeathNoticeIcon_Standard ); + + // if ( ( iCustomDamage == TF_DMG_CUSTOM_THROWABLE_KILL ) || ( deathFlags & TF_DEATH_FEIGN_DEATH ) ) + // { + // g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, g_pVGuiLocalize->Find("#Throwable_Kill"), 0 ); + // } + // else + // { + // wchar_t wzCount[10]; + // _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", event->GetInt( "totalhits" ) ); + // g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, g_pVGuiLocalize->Find("#Humiliation_Count"), 1, wzCount ); + // } + + // // if there was an assister, put both the killer's and assister's names in the death message + // int iAssisterID = engine->GetPlayerForUserID( event->GetInt( "assister" ) ); + // const char *assister_name = ( iAssisterID > 0 ? g_PR->GetPlayerName( iAssisterID ) : NULL ); + // if ( assister_name ) + // { + // char szKillerBuf[MAX_PLAYER_NAME_LENGTH*2]; + // Q_snprintf( szKillerBuf, ARRAYSIZE(szKillerBuf), "%s + %s", msg.Killer.szName, assister_name ); + // Q_strncpy( msg.Killer.szName, szKillerBuf, ARRAYSIZE( msg.Killer.szName ) ); + // } + //} + else if ( FStrEq( "rd_robot_killed", pszEventName ) ) + { + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + int killer = engine->GetPlayerForUserID( event->GetInt( "attacker" ) ); + const char *killedwith = event->GetString( "weapon" ); + + msg.Killer.iTeam = g_PR->GetTeam( killer ); + Q_strncpy( msg.Killer.szName, g_PR->GetPlayerName( killer ), ARRAYSIZE( msg.Killer.szName ) ); + + Q_strncpy( msg.Victim.szName, g_PR->GetTeam( killer ) == TF_TEAM_RED ? "BLUE ROBOT" : "RED ROBOT", ARRAYSIZE( msg.Victim.szName ) ); + msg.Victim.iTeam = g_PR->GetTeam( killer ) == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED; + + Q_snprintf( msg.szIcon, sizeof(msg.szIcon), "d_%s", killedwith ); + } + else if ( FStrEq( PasstimeGameEvents::BallGet::s_eventName, pszEventName ) ) // passtime ball get + { + PasstimeGameEvents::BallGet ev( event ); + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + // info + V_wcsncpy( msg.wzInfoText, g_pVGuiLocalize->Find("#Msg_PasstimeBallGet"), sizeof( msg.wzInfoText ) ); + + // killer + const char *szPlayerName = g_PR->GetPlayerName( ev.ownerIndex); + Q_strncpy( msg.Killer.szName, szPlayerName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = g_PR->GetTeam( ev.ownerIndex ); + + // flags + if ( GetLocalPlayerIndex() == ev.ownerIndex ) + msg.bLocalPlayerInvolved = true; + + // icon + const char *const icon = "d_passtime_pass"; + Q_strncpy( msg.szIcon, icon, ARRAYSIZE( msg.szIcon ) ); + } + else if ( FStrEq( PasstimeGameEvents::BallStolen::s_eventName, pszEventName ) ) // passtime ball stolen + { + PasstimeGameEvents::BallStolen ev( event ); + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + int attackerTeam = g_PR->GetTeam( ev.attackerIndex ); + int victimTeam = g_PR->GetTeam( ev.victimIndex ); + + // attacker + const char *szPlayerName = g_PR->GetPlayerName( ev.attackerIndex ); + Q_strncpy( msg.Killer.szName, szPlayerName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = attackerTeam; + + // victim + szPlayerName = g_PR->GetPlayerName( ev.victimIndex ); + Q_strncpy( msg.Victim.szName, szPlayerName, ARRAYSIZE( msg.Victim.szName ) ); + msg.Victim.iTeam = victimTeam; + + V_wcsncpy( msg.wzInfoText, g_pVGuiLocalize->Find("#Msg_PasstimeSteal"), sizeof( msg.wzInfoText ) ); + + // flags + int localPlayerIndex = GetLocalPlayerIndex(); + msg.bLocalPlayerInvolved = (localPlayerIndex == ev.attackerIndex) + || (localPlayerIndex == ev.victimIndex); + + // icon + Q_strncpy( msg.szIcon, "d_passtime_steal", ARRAYSIZE(msg.szIcon) ); + } + else if ( FStrEq( PasstimeGameEvents::Score::s_eventName, pszEventName ) ) // passtime score + { + PasstimeGameEvents::Score ev( event ); + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + // info + if ( ev.numPoints > 1 ) + { + wchar_t wzCount[10]; + _snwprintf( wzCount, ARRAYSIZE( wzCount ), L"%d", ev.numPoints ); + g_pVGuiLocalize->ConstructString_safe( msg.wzInfoText, g_pVGuiLocalize->Find("#Msg_PasstimeScoreCount"), 1, wzCount ); + } + else + { + V_wcsncpy( msg.wzInfoText, g_pVGuiLocalize->Find("#Msg_PasstimeScore"), sizeof( msg.wzInfoText ) ); + } + + // killer + const char *szPlayerName = g_PR->GetPlayerName( ev.scorerIndex ); + Q_strncpy( msg.Killer.szName, szPlayerName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = g_PR->GetTeam( ev.scorerIndex ); + + // flags + if ( GetLocalPlayerIndex() == ev.scorerIndex ) + msg.bLocalPlayerInvolved = true; + + // icon + const char *const icon = (msg.Killer.iTeam == TF_TEAM_RED) + ? "d_passtime_score_red" + : "d_passtime_score_blue"; + Q_strncpy( msg.szIcon, icon, ARRAYSIZE( msg.szIcon ) ); + } + else if ( FStrEq( PasstimeGameEvents::PassCaught::s_eventName, pszEventName ) ) // passtime pass + { + PasstimeGameEvents::PassCaught ev( event ); + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + int passerTeam = g_PR->GetTeam( ev.passerIndex ); + int catcherTeam = g_PR->GetTeam( ev.catcherIndex ); + + // + // Pass or interception? + // + int killerIndex, victimIndex, killerTeam, victimTeam; + const char *pszDesc; + if ( passerTeam == catcherTeam ) + { + // pass + killerIndex = ev.passerIndex; + killerTeam = passerTeam; + victimIndex = ev.catcherIndex; + victimTeam = catcherTeam; + pszDesc = "#Msg_PasstimePassComplete"; + Q_strncpy( msg.szIcon, "d_passtime_pass", ARRAYSIZE(msg.szIcon) ); + } + else + { + // interception + victimIndex = ev.passerIndex; + victimTeam = passerTeam; + killerIndex = ev.catcherIndex; + killerTeam = catcherTeam; + pszDesc = "#Msg_PasstimeInterception"; + Q_strncpy( msg.szIcon, "d_passtime_intercept", ARRAYSIZE(msg.szIcon) ); + } + + // killer + const char *szPlayerName = g_PR->GetPlayerName( killerIndex ); + Q_strncpy( msg.Killer.szName, szPlayerName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = killerTeam; + + // victim + szPlayerName = g_PR->GetPlayerName( victimIndex ); + Q_strncpy( msg.Victim.szName, szPlayerName, ARRAYSIZE( msg.Victim.szName ) ); + msg.Victim.iTeam = victimTeam; + + V_wcsncpy( msg.wzInfoText, g_pVGuiLocalize->Find( pszDesc ), sizeof( msg.wzInfoText ) ); + + // flags + int localPlayerIndex = GetLocalPlayerIndex(); + msg.bLocalPlayerInvolved = (localPlayerIndex == ev.catcherIndex) + || (localPlayerIndex == ev.passerIndex); + } + else if ( FStrEq( PasstimeGameEvents::BallBlocked::s_eventName, pszEventName ) ) // passtime ball stolen + { + PasstimeGameEvents::BallBlocked ev( event ); + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + // blocker + const char *szPlayerName = g_PR->GetPlayerName( ev.blockerIndex ); + Q_strncpy( msg.Killer.szName, szPlayerName, ARRAYSIZE( msg.Killer.szName ) ); + msg.Killer.iTeam = g_PR->GetTeam( ev.blockerIndex ); + + // owner + szPlayerName = g_PR->GetPlayerName( ev.ownerIndex ); + Q_strncpy( msg.Victim.szName, szPlayerName, ARRAYSIZE( msg.Victim.szName ) ); + msg.Victim.iTeam = g_PR->GetTeam( ev.ownerIndex ); + + V_wcsncpy( msg.wzInfoText, g_pVGuiLocalize->Find("#Msg_PasstimeBlock"), sizeof( msg.wzInfoText ) ); + + // flags + int localPlayerIndex = GetLocalPlayerIndex(); + msg.bLocalPlayerInvolved = (localPlayerIndex == ev.blockerIndex) + || (localPlayerIndex == ev.ownerIndex); + + // icon + Q_strncpy( msg.szIcon, "d_ball", ARRAYSIZE(msg.szIcon) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds an additional death message +//----------------------------------------------------------------------------- +void CTFHudDeathNotice::AddAdditionalMsg( int iKillerID, int iVictimID, const char *pMsgKey ) +{ + DeathNoticeItem &msg2 = m_DeathNotices[AddDeathNoticeItem()]; + Q_strncpy( msg2.Killer.szName, g_PR->GetPlayerName( iKillerID ), ARRAYSIZE( msg2.Killer.szName ) ); + msg2.Killer.iTeam = g_PR->GetTeam( iKillerID ); + Q_strncpy( msg2.Victim.szName, g_PR->GetPlayerName( iVictimID ), ARRAYSIZE( msg2.Victim.szName ) ); + msg2.Victim.iTeam = g_PR->GetTeam( iVictimID ); + const wchar_t *wzMsg = g_pVGuiLocalize->Find( pMsgKey ); + if ( wzMsg ) + { + V_wcsncpy( msg2.wzInfoText, wzMsg, sizeof( msg2.wzInfoText ) ); + } + msg2.iconDeath = m_iconDomination; + int iLocalPlayerIndex = GetLocalPlayerIndex(); + if ( iLocalPlayerIndex == iVictimID || iLocalPlayerIndex == iKillerID ) + { + msg2.bLocalPlayerInvolved = true; + } +} + +//----------------------------------------------------------------------------- +void CTFHudDeathNotice::AddStreakMsg( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iKillerStreak, int iStreakIncrement, int iVictimID, int iDeathNoticeMsg ) +{ + int nMinStreak = MinStreakForType( eStreakType ); + if ( iKillerStreak < nMinStreak ) + return; + + if ( !m_pStreakNotice ) + return; + + if ( cl_hud_killstreak_display_time.GetInt() <= 0 ) + return; + + m_pStreakNotice->StreakUpdated( eStreakType, iKillerID, iKillerStreak, iStreakIncrement ); +} + +//----------------------------------------------------------------------------- +void CTFHudDeathNotice::AddStreakEndedMsg( CTFPlayerShared::ETFStreak eStreakType, int iKillerID, int iVictimID, int iVictimStreak, int iDeathNoticeMsg ) +{ + int nMinStreak = MinStreakForType( eStreakType ); + if ( iVictimStreak < nMinStreak ) + return; + + if ( !m_pStreakNotice ) + return; + + if ( cl_hud_killstreak_display_time.GetInt() <= 0 ) + return; + + m_pStreakNotice->StreakEnded( eStreakType, iKillerID, iVictimID, iVictimStreak ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns the color to draw text in for this team. +//----------------------------------------------------------------------------- +Color CTFHudDeathNotice::GetTeamColor( int iTeamNumber, bool bLocalPlayerInvolved /* = false */ ) +{ + switch ( iTeamNumber ) + { + case TF_TEAM_BLUE: + return m_clrBlueText; + break; + case TF_TEAM_RED: + return m_clrRedText; + break; + case TEAM_UNASSIGNED: + if ( bLocalPlayerInvolved ) + return m_clrLocalPlayerText; + else + return Color( 255, 255, 255, 255 ); + break; + case TF_TEAM_HALLOWEEN: + if ( TFGameRules() && ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) || TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) ) ) + { + return m_clrGreenText; + } + else + { + return m_clrPurpleText; + } + break; + default: + AssertOnce( false ); // invalid team + return Color( 255, 255, 255, 255 ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Color CTFHudDeathNotice::GetInfoTextColor( int iDeathNoticeMsg ) +{ + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + if ( msg.bLocalPlayerInvolved ) + return m_clrLocalPlayerText; + + return Color( 255, 255, 255, 255 ); +} + +//----------------------------------------------------------------------------- +Color CTFHudDeathNotice::GetBackgroundColor ( int iDeathNoticeMsg ) +{ + DeathNoticeItem &msg = m_DeathNotices[ iDeathNoticeMsg ]; + + return msg.bLocalPlayerInvolved ? m_clrLocalBGColor : m_clrBaseBGColor; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFHudDeathNotice::UseExistingNotice( IGameEvent *event ) +{ + // Fish Notices and Throwables + // Add check for all throwables + int iTarget = event->GetInt( "weaponid" ); + if (iTarget == TF_WEAPON_BAT_FISH || iTarget == TF_WEAPON_THROWABLE || iTarget == TF_WEAPON_GRENADE_THROWABLE ) + { + // Look for a matching pre-existing notice. + for ( int i=0; i<m_DeathNotices.Count(); ++i ) + { + DeathNoticeItem &msg = m_DeathNotices[i]; + + if ( msg.iWeaponID != iTarget ) + continue; + + if ( msg.iKillerID != event->GetInt( "attacker" ) ) + continue; + + if ( msg.iVictimID != event->GetInt( "userid" ) ) + continue; + + return i; + } + } + + return BaseClass::UseExistingNotice( event ); +} +//----------------------------------------------------------------------------- +CHudTexture* CTFHudDeathNotice::GetMannPowerIcon( RuneTypes_t tRuneType, bool bIsRedTeam ) +{ + // Red team is normal file and blue is dNeg file + switch ( tRuneType ) + { + case RUNE_STRENGTH: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_strength" ) : gHUD.GetIcon( "dneg_mannpower_strength" ); + case RUNE_HASTE: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_haste" ) : gHUD.GetIcon( "dneg_mannpower_haste" ); + case RUNE_REGEN: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_regen" ) : gHUD.GetIcon( "dneg_mannpower_regen" ); + case RUNE_RESIST: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_resist" ) : gHUD.GetIcon( "dneg_mannpower_resist" ); + case RUNE_VAMPIRE: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_vamp" ) : gHUD.GetIcon( "dneg_mannpower_vamp" ); + case RUNE_REFLECT: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_reflect" ) : gHUD.GetIcon( "dneg_mannpower_reflect" ); + case RUNE_PRECISION: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_precision" ) : gHUD.GetIcon( "dneg_mannpower_precision" ); + case RUNE_AGILITY: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_agility" ) : gHUD.GetIcon( "dneg_mannpower_agility" ); + case RUNE_KNOCKOUT: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_fist" ) : gHUD.GetIcon( "dneg_mannpower_fist" ); + case RUNE_KING: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_king" ) : gHUD.GetIcon( "dneg_mannpower_king" ); + case RUNE_PLAGUE: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_plague" ) : gHUD.GetIcon( "dneg_mannpower_plague" ); + case RUNE_SUPERNOVA: return bIsRedTeam ? gHUD.GetIcon( "d_mannpower_supernova" ) : gHUD.GetIcon( "dneg_mannpower_supernova" ); + default: return NULL; + } + return NULL; +}
\ No newline at end of file |