//========= 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;
}