//========= 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 #include #include #include #include "vgui_controls/TextImage.h" #include #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 " ()" 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; iGetInt( "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; }