//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose:		
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "mp_shareddefs.h"
#include "basemultiplayerplayer.h"
#include "cdll_int.h"
#include "gameinterface.h"
#include "usermessages.h"

ConVar sv_allchat("sv_allchat", "1", FCVAR_NOTIFY, "Players can receive all other players' text chat, team restrictions apply");

// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"

CBaseMultiplayerPlayer::CBaseMultiplayerPlayer()
{
	m_iCurrentConcept = MP_CONCEPT_NONE;
	m_flLastForcedChangeTeamTime = -1;
	m_iBalanceScore = 0;
	m_flConnectionTime = gpGlobals->curtime;
	m_pExpresser = NULL;

	// per life achievement counters
	m_pAchievementKV = new KeyValues( "achievement_counts" );

	m_flAreaCaptureScoreAccumulator = 0.0f;
}

CBaseMultiplayerPlayer::~CBaseMultiplayerPlayer()
{
	m_pAchievementKV->deleteThis();
	delete m_pExpresser;
}

BEGIN_ENT_SCRIPTDESC( CBaseMultiplayerPlayer, CBasePlayer, "Player" )
END_SCRIPTDESC();


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CAI_Expresser *CBaseMultiplayerPlayer::CreateExpresser( void )
{
	m_pExpresser = new CMultiplayer_Expresser(this);
	if ( !m_pExpresser)
		return NULL;

	m_pExpresser->Connect(this);
	return m_pExpresser;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseMultiplayerPlayer::PostConstructor( const char *szClassname )
{
	BaseClass::PostConstructor( szClassname );
	CreateExpresser();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseMultiplayerPlayer::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
{
	BaseClass::ModifyOrAppendCriteria( criteriaSet );

	ModifyOrAppendPlayerCriteria( criteriaSet );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseMultiplayerPlayer::SpeakIfAllowed( AIConcept_t concept, SpeechPriorityType priority, const char *modifiers, char *pszOutResponseChosen, size_t bufsize, IRecipientFilter *filter ) 
{ 
	if ( !IsAlive() )
		return false;

	//if ( IsAllowedToSpeak( concept, bRespondingToPlayer ) )
	return Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter );
}

//-----------------------------------------------------------------------------
// Purpose: Fill out given response with the appropriate one for this concept
//-----------------------------------------------------------------------------
void CBaseMultiplayerPlayer::SpeakConcept( AI_Response &outResponse, int iConcept )
{
	m_iCurrentConcept = iConcept;
	AIConcept_t concept( g_pszMPConcepts[iConcept] );
	FindResponse( outResponse, concept );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseMultiplayerPlayer::SpeakConceptIfAllowed( int iConcept, const char *modifiers, char *pszOutResponseChosen, size_t bufsize, IRecipientFilter *filter )
{
	// Save the current concept.
	m_iCurrentConcept = iConcept;
	return SpeakIfAllowed( g_pszMPConcepts[iConcept], SPEECH_PRIORITY_NORMAL, modifiers, pszOutResponseChosen, bufsize, filter );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseMultiplayerPlayer::CanHearAndReadChatFrom( CBasePlayer *pPlayer )
{
	// can always hear the console unless we're ignoring every chat
	if ( !pPlayer )
		return m_iIgnoreGlobalChat != CHAT_IGNORE_EVERYTHING;

	// check if we're ignoring all chat
	if ( m_iIgnoreGlobalChat == CHAT_IGNORE_BROADCAST_AND_TEAM )
		return false;

	// check if we're ignoring all but teammates
	if ( m_iIgnoreGlobalChat == CHAT_IGNORE_BROADCAST && g_pGameRules->PlayerRelationship( this, pPlayer ) != GR_TEAMMATE )
		return false;

	// can't hear dead players if we're alive
	if ( pPlayer->m_lifeState != LIFE_ALIVE && m_lifeState == LIFE_ALIVE )
		return false;

	return true;
}

bool CBaseMultiplayerPlayer::ClientCommand( const CCommand &args )
{
	const char *pcmd = args[0];

	if ( FStrEq( pcmd, "ignoremsg" ) )
	{
		m_iIgnoreGlobalChat = (m_iIgnoreGlobalChat + 1) % 4;
		switch( m_iIgnoreGlobalChat )
		{
		case CHAT_IGNORE_NONE:
			ClientPrint( this, HUD_PRINTTALK, "#Accept_All_Messages" );
			break;
		case CHAT_IGNORE_BROADCAST:
			ClientPrint( this, HUD_PRINTTALK, "#Ignore_Broadcast_Messages" );
			break;
		case CHAT_IGNORE_BROADCAST_AND_TEAM:
			ClientPrint( this, HUD_PRINTTALK, "#Ignore_Broadcast_Team_Messages" );
			break;
		case CHAT_IGNORE_EVERYTHING:
			ClientPrint( this, HUD_PRINTTALK, "#Ignore_All_Messages" );
			break;
		default:
			break;
		}
		return true;
	}

	return BaseClass::ClientCommand( args );
}

bool CBaseMultiplayerPlayer::ShouldShowVoiceSubtitleToEnemy( void )
{
	return false;
}

//-----------------------------------------------------------------------------
// calculate a score for this player. higher is more likely to be switched
//-----------------------------------------------------------------------------
int	CBaseMultiplayerPlayer::CalculateTeamBalanceScore( void )
{
	// base score is 0 - ( seconds on server )
	float flTimeConnected = gpGlobals->curtime - m_flConnectionTime;
	int iScore = 0 - (int)flTimeConnected;

	// if we were switched recently, score us way down
	float flLastSwitchedTime = GetLastForcedChangeTeamTime();
	if ( flLastSwitchedTime > 0 && ( gpGlobals->curtime - flLastSwitchedTime ) < 300 )
	{
		iScore -= 10000;
	}
	return iScore;
}

void CBaseMultiplayerPlayer::Spawn( void )
{
	ResetPerLifeCounters();

	BaseClass::Spawn();
}

void CBaseMultiplayerPlayer::AwardAchievement( int iAchievement, int iCount )
{
	Assert( iAchievement >= 0 && iAchievement < 0xFFFF );		// must fit in short

	CSingleUserRecipientFilter filter( this );

	int userID = GetPlayerInfo()->GetUserID();

	CCSUsrMsg_AchievementEvent msg;
	msg.set_achievement( iAchievement );
	msg.set_count( iCount );
	msg.set_user_id( userID );
	SendUserMessage( filter, CS_UM_AchievementEvent, msg );
}

#ifdef _DEBUG

	#include "utlbuffer.h"

	void DumpAchievementCounters( const CCommand &args )
	{
		int iPlayerIndex = 1;

		if ( args.ArgC() >= 2 )
		{
			iPlayerIndex = atoi( args[1] );
		}

		CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
		if ( pPlayer && pPlayer->GetPerLifeCounterKeys() )
		{
			CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
			pPlayer->GetPerLifeCounterKeys()->RecursiveSaveToFile( buf, 0 );

			char szBuf[1024];

			// probably not the best way to print out a CUtlBuffer
			int pos = 0;
			while ( buf.PeekStringLength() )
			{
				szBuf[pos] = buf.GetChar();
				pos++;
			}
			szBuf[pos] = '\0';

			Msg( "%s\n", szBuf );
		}
	}
	ConCommand dump_achievement_counters( "dump_achievement_counters", DumpAchievementCounters, "Spew the per-life achievement counters for multiplayer players", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );

#endif // _DEBUG

int	CBaseMultiplayerPlayer::GetPerLifeCounterKV( const char *name )
{
	return m_pAchievementKV->GetInt( name, 0 );
}

void CBaseMultiplayerPlayer::SetPerLifeCounterKV( const char *name, int value )
{
	m_pAchievementKV->SetInt( name, value );
}

void CBaseMultiplayerPlayer::ResetPerLifeCounters( void )
{
	m_pAchievementKV->Clear();
}


#ifndef CSTRIKE_DLL
ConVar tf_escort_score_rate( "tf_escort_score_rate", "1", FCVAR_CHEAT, "Score for escorting the train, in points per second" );
#endif

#define ESCORT_SCORE_CONTEXT		"AreaScoreContext"
#define ESCORT_SCORE_INTERVAL		0.1

//-----------------------------------------------------------------------------
// Purpose: think to accumulate and award points for escorting the train
//-----------------------------------------------------------------------------
void CBaseMultiplayerPlayer::EscortScoringThink( void )
{
	m_flAreaCaptureScoreAccumulator += ESCORT_SCORE_INTERVAL;

	if ( m_flCapPointScoreRate > 0 )
	{
		float flTimeForOnePoint = 1.0f / m_flCapPointScoreRate; 

		int iPoints = 0;

		while ( m_flAreaCaptureScoreAccumulator >= flTimeForOnePoint )
		{
			m_flAreaCaptureScoreAccumulator -= flTimeForOnePoint;
			iPoints++;
		}

		if ( iPoints > 0 )
		{
			IGameEvent *event = gameeventmanager->CreateEvent( "player_escort_score" );
			if ( event )
			{
				event->SetInt( "player", entindex() );
				event->SetInt( "points", iPoints );
				gameeventmanager->FireEvent( event, true /* only to server */ );
			}
		}
	}

	SetContextThink( &CBaseMultiplayerPlayer::EscortScoringThink, gpGlobals->curtime + ESCORT_SCORE_INTERVAL, ESCORT_SCORE_CONTEXT );	
}

//-----------------------------------------------------------------------------
// Purpose: We're escorting the train, start giving points
//-----------------------------------------------------------------------------
void CBaseMultiplayerPlayer::StartScoringEscortPoints( float flRate )
{
	Assert( flRate > 0.0f );
	m_flCapPointScoreRate = flRate;
	SetContextThink( &CBaseMultiplayerPlayer::EscortScoringThink, gpGlobals->curtime + ESCORT_SCORE_INTERVAL, ESCORT_SCORE_CONTEXT );
}

//-----------------------------------------------------------------------------
// Purpose: Stopped escorting the train
//-----------------------------------------------------------------------------
void CBaseMultiplayerPlayer::StopScoringEscortPoints( void )
{
	SetContextThink( NULL, 0, ESCORT_SCORE_CONTEXT );
}

#if !defined(NO_STEAM)
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseMultiplayerPlayer::GetSteamID( CSteamID *pID ) const
{
	const CSteamID *pClientID = engine->GetClientSteamID( edict() );
	if ( pClientID )
	{
		*pID = *pClientID;
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
uint64 CBaseMultiplayerPlayer::GetSteamIDAsUInt64( void ) const
{
	CSteamID steamIDForPlayer;
	if ( GetSteamID( &steamIDForPlayer ) )
		return steamIDForPlayer.ConvertToUint64();
	return 0;
}
#endif // NO_STEAM