text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose:		
  //
  // $NoKeywords: $
  //=============================================================================
  
  #include "cbase.h"
  #include "mp_shareddefs.h"
  #include "basemultiplayerplayer.h"
  
  // Minimum interval between rate-limited commands that players can run.
  #define COMMAND_MAX_RATE  0.3
  
  CBaseMultiplayerPlayer::CBaseMultiplayerPlayer()
  {
  	m_iCurrentConcept = MP_CONCEPT_NONE;
  	m_flLastForcedChangeTeamTime = -1;
  	m_iBalanceScore = 0;
  	m_flConnectionTime = gpGlobals->curtime;
  
  	// per life achievement counters
  	m_pAchievementKV = new KeyValues( "achievement_counts" );
  
  	m_flAreaCaptureScoreAccumulator = 0.0f;
  }
  
  CBaseMultiplayerPlayer::~CBaseMultiplayerPlayer()
  {
  	m_pAchievementKV->deleteThis();
  }
  
  //-----------------------------------------------------------------------------
  // 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, 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: 
  //-----------------------------------------------------------------------------
  IResponseSystem *CBaseMultiplayerPlayer::GetResponseSystem()
  {
  	return BaseClass::GetResponseSystem();
  	// NOTE: This is where you would hook your custom responses.
  //	return <*>GameRules()->m_ResponseRules[iIndex].m_ResponseSystems[m_iCurrentConcept];
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Doesn't actually speak the concept. Just finds a response in the system. You then have to play it yourself.
  //-----------------------------------------------------------------------------
  bool CBaseMultiplayerPlayer::SpeakConcept( AI_Response &response, int iConcept )
  {
  	// Save the current concept.
  	m_iCurrentConcept = iConcept;
  	return SpeakFindResponse( response, g_pszMPConcepts[iConcept] );
  }
  
  //-----------------------------------------------------------------------------
  // 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], modifiers, pszOutResponseChosen, bufsize, filter );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CBaseMultiplayerPlayer::CanHearAndReadChatFrom( CBasePlayer *pPlayer )
  {
  	// can always hear the console unless we're ignoring all chat
  	if ( !pPlayer )
  		return m_iIgnoreGlobalChat != CHAT_IGNORE_ALL;
  
  	// check if we're ignoring all chat
  	if ( m_iIgnoreGlobalChat == CHAT_IGNORE_ALL )
  		return false;
  
  	// check if we're ignoring all but teammates
  	if ( m_iIgnoreGlobalChat == CHAT_IGNORE_TEAM && 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;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CBaseMultiplayerPlayer::ShouldRunRateLimitedCommand( const CCommand &args )
  {
  	return ShouldRunRateLimitedCommand( args[0] );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CBaseMultiplayerPlayer::ShouldRunRateLimitedCommand( const char *pszCommand )
  {
  	const char *pcmd = pszCommand;
  
  	int i = m_RateLimitLastCommandTimes.Find( pcmd );
  	if ( i == m_RateLimitLastCommandTimes.InvalidIndex() )
  	{
  		m_RateLimitLastCommandTimes.Insert( pcmd, gpGlobals->curtime );
  		return true;
  	}
  	else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < COMMAND_MAX_RATE )
  	{
  		// Too fast.
  		return false;
  	}
  	else
  	{
  		m_RateLimitLastCommandTimes[i] = gpGlobals->curtime;
  		return true;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CBaseMultiplayerPlayer::ClientCommand( const CCommand &args )
  {
  	const char *pcmd = args[0];
  
  	if ( FStrEq( pcmd, "ignoremsg" ) )
  	{
  		if ( ShouldRunRateLimitedCommand( args ) )
  		{
  			m_iIgnoreGlobalChat = (m_iIgnoreGlobalChat + 1) % 3;
  			switch( m_iIgnoreGlobalChat )
  			{
  			case CHAT_IGNORE_NONE:
  				ClientPrint( this, HUD_PRINTTALK, "#Accept_All_Messages" );
  				break;
  			case CHAT_IGNORE_ALL:
  				ClientPrint( this, HUD_PRINTTALK, "#Ignore_Broadcast_Messages" );
  				break;
  			case CHAT_IGNORE_TEAM:
  				ClientPrint( this, HUD_PRINTTALK, "#Ignore_Broadcast_Team_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();
  
  	StopScoringEscortPoints();
  
  	BaseClass::Spawn();
  }
  
  void CBaseMultiplayerPlayer::AwardAchievement( int iAchievement, int iCount )
  {
  	Assert( iAchievement >= 0 && iAchievement < 0xFFFF );		// must fit in short
  
  	CSingleUserRecipientFilter filter( this );
  
  	UserMessageBegin( filter, "AchievementEvent" );
  		WRITE_SHORT( iAchievement );
  		WRITE_SHORT( iCount );
  	MessageEnd();
  }
  
  #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();
  }
  
  
  ConVar tf_escort_score_rate( "tf_escort_score_rate", "1", FCVAR_CHEAT, "Score for escorting the train, in points per second" );
  
  #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 );
  }
  
  