//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003

#include "cbase.h"
#include "cs_gamerules.h"
#include "KeyValues.h"

#include "cs_bot.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//--------------------------------------------------------------------------------------------------------------
/**
 * Checks if the bot can hear the event
 */
void CCSBot::OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep, const Vector *actualOrigin )
{
	/// @todo Listen to non-player sounds
	if (player == NULL)
		return;

	// don't pay attention to noise that friends make
	if (!IsEnemy( player ))
		return;

	Vector playerOrigin = GetCentroid( player );
	Vector myOrigin = GetCentroid( this );

	// If the event occurs far from the triggering player, it may override the origin
	if ( actualOrigin )
	{
		playerOrigin = *actualOrigin;
	}

	// check if noise is close enough for us to hear
	const Vector *newNoisePosition = &playerOrigin;
	float newNoiseDist = (myOrigin - *newNoisePosition).Length();
	if (newNoiseDist < range)
	{
		// we heard the sound
		if ((IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() == 3) || cv_bot_debug.GetInt() == 4)
		{
			PrintIfWatched( "Heard noise (%s from %s, pri %s, time %3.1f)\n", 
											(FStrEq( "weapon_fire", event->GetName() )) ? "Weapon fire " : "",
											(player) ? player->GetPlayerName() : "NULL",
											(priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
											gpGlobals->curtime );
		}

		// should we pay attention to it
		// if noise timestamp is zero, there is no prior noise
		if (m_noiseTimestamp > 0.0f)
		{
			// only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
			const float shortTermMemoryTime = 3.0f;
			if (gpGlobals->curtime - m_noiseTimestamp < shortTermMemoryTime)
			{
				// prior noise is more important - ignore new one
				if (priority < m_noisePriority)
					return;

				float oldNoiseDist = (myOrigin - m_noisePosition).Length();
				if (newNoiseDist >= oldNoiseDist)
					return;
			}
		}

		// find the area in which the noise occured
		/// @todo Better handle when noise occurs off the nav mesh
		/// @todo Make sure noise area is not through a wall or ceiling from source of noise
		/// @todo Change GetNavTravelTime to better deal with NULL destination areas
		CNavArea *noiseArea = TheNavMesh->GetNearestNavArea( *newNoisePosition );
		if (noiseArea == NULL)
		{
			PrintIfWatched( "  *** Noise occurred off the nav mesh - ignoring!\n" );
			return;
		}

		m_noiseArea = noiseArea;

		// remember noise priority
		m_noisePriority = priority;

		// randomize noise position in the area a bit - hearing isn't very accurate
		// the closer the noise is, the more accurate our placement
		/// @todo Make sure not to pick a position on the opposite side of ourselves.
		const float maxErrorRadius = 400.0f;
		const float maxHearingRange = 2000.0f;
		float errorRadius = maxErrorRadius * newNoiseDist/maxHearingRange;

		m_noisePosition.x = newNoisePosition->x + RandomFloat( -errorRadius, errorRadius );
		m_noisePosition.y = newNoisePosition->y + RandomFloat( -errorRadius, errorRadius );

		// note the *travel distance* to the noise
		m_noiseTravelDistance = GetTravelDistanceToPlayer( (CCSPlayer *)player );

		// make sure noise position remains in the same area
		m_noiseArea->GetClosestPointOnArea( m_noisePosition, &m_noisePosition );

		// note when we heard the noise
		m_noiseTimestamp = gpGlobals->curtime;

		// if we hear a nearby enemy, become alert
		const float nearbyNoiseRange = 1000.0f;
		if (m_noiseTravelDistance < nearbyNoiseRange && m_noiseTravelDistance > 0.0f)
		{
			BecomeAlert();
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHEGrenadeDetonate( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // hegrenade_detonate
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnFlashbangDetonate( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // flashbang_detonate
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnSmokeGrenadeDetonate( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // smokegrenade_detonate
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnGrenadeBounce( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 500.0f, PRIORITY_LOW, true ); // grenade_bounce
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBulletImpact( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	// Construct an origin for the sound, since it can be far from the originating player
	Vector actualOrigin;
	actualOrigin.x = event->GetFloat( "x", 0.0f );
	actualOrigin.y = event->GetFloat( "y", 0.0f );
	actualOrigin.z = event->GetFloat( "z", 0.0f );

	/// @todo Ignoring bullet impact events for now - we dont want bots to look directly at them!
	//OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true, false, &actualOrigin ); // bullet_impact
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakProp( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_prop
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakBreakable( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_glass
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDoorMoving( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, false ); // door_moving
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageFollows( IGameEvent *event )
{
	if ( !IsAlive() )
		return;

	// don't react to our own events
	CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
	if ( player == this )
		return;

	// player_follows needs a player
	if (player == NULL)
		return;

	// don't pay attention to noise that friends make
	if (!IsEnemy( player ))
		return;

	Vector playerOrigin = GetCentroid( player );
	Vector myOrigin = GetCentroid( this );
	const float range = 1200.0f;

	// this is here so T's not only act on the noise, but look at it, too
	if (GetTeamNumber() == TEAM_TERRORIST)
	{
		// make sure we can hear the noise
		if ((playerOrigin - myOrigin).IsLengthGreaterThan( range ))
			return;

		// tell our teammates that the hostages are being taken
		GetChatter()->HostagesBeingTaken();

		// only move if we hear them being rescued and can't see any hostages
		if (GetGameState()->GetNearestVisibleFreeHostage() == NULL)
		{			
			// since we are guarding the hostages, presumably we know where they are
			// if we're close enough to "hear" this event, either go to where the event occured,
			// or head for an escape zone to head them off
			if (GetTask() != CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
			{
				//const float headOffChance = 33.3f;
				if (true) // || RandomFloat( 0, 100 ) < headOffChance)
				{
					// head them off at a rescue zone
					if (GuardRandomZone())
					{
						SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
						SetDisposition( CCSBot::OPPORTUNITY_FIRE );
						PrintIfWatched( "Trying to beat them to an escape zone!\n" );
					}
				}
				else
				{
					SetTask( SEEK_AND_DESTROY );
					StandUp();
					Run();
					MoveTo( playerOrigin, FASTEST_ROUTE );
				}
			}
		}
	}
	else
	{
		// CT's don't care about this noise
		return;
	}

	OnAudibleEvent( event, player, range, PRIORITY_MEDIUM, false ); // hostage_follows
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundEnd( IGameEvent *event )
{
	// Morale adjustments happen even for dead players
	int winner = event->GetInt( "winner" );
	switch ( winner )
	{
	case WINNER_TER:
		if (GetTeamNumber() == TEAM_CT)
		{
			DecreaseMorale();
		}
		else
		{
			IncreaseMorale();
		}
		break;

	case WINNER_CT:
		if (GetTeamNumber() == TEAM_CT)
		{
			IncreaseMorale();
		}
		else
		{
			DecreaseMorale();
		}
		break;

	default:
		break;
	}

	m_gameState.OnRoundEnd( event );

	if ( !IsAlive() )
		return;

	if ( event->GetInt( "winner" ) == WINNER_TER )
	{
		if (GetTeamNumber() == TEAM_TERRORIST)
			GetChatter()->CelebrateWin();
	}
	else if ( event->GetInt( "winner" ) == WINNER_CT )
	{
		if (GetTeamNumber() == TEAM_CT)
			GetChatter()->CelebrateWin();
	}
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundStart( IGameEvent *event )
{
	m_gameState.OnRoundStart( event );
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageRescuedAll( IGameEvent *event )
{
	m_gameState.OnHostageRescuedAll( event );
}


//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnNavBlocked( IGameEvent *event )
{
	if ( event->GetBool( "blocked" ) )
	{
		unsigned int areaID = event->GetInt( "area" );
		if ( areaID )
		{
			// An area was blocked off.  Reset our path if it has this area on it.
			for( int i=0; i<m_pathLength; ++i )
			{
				const ConnectInfo *info = &m_path[ i ];
				if ( info->area && info->area->GetID() == areaID )
				{
					DestroyPath();
					return;
				}
			}
		}
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Invoked when bot enters a nav area
 */
void CCSBot::OnEnteredNavArea( CNavArea *newArea )
{
	// assume that we "clear" an area of enemies when we enter it
	newArea->SetClearedTimestamp( GetTeamNumber()-1 );

	// if we just entered a 'stop' area, set the flag
	if ( newArea->GetAttributes() & NAV_MESH_STOP )
	{
		m_isStopping = true;
	}

	/// @todo Flag these areas as spawn areas during load
	if (IsAtEnemySpawn())
	{
		m_hasVisitedEnemySpawn = true;
	}
}