//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// nav_entities.cpp
// AI Navigation entities
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003

#include "cbase.h"

#include "nav_mesh.h"
#include "nav_node.h"
#include "nav_pathfind.h"
#include "nav_colors.h"
#include "fmtstr.h"
#include "props_shared.h"
#include "func_breakablesurf.h"

#ifdef TERROR
#include "func_elevator.h"
#include "AmbientLight.h"
#endif

#ifdef TF_DLL
#include "tf_player.h"
#include "bot/tf_bot.h"
#endif

#include "Color.h"
#include "collisionutils.h"
#include "functorutils.h"
#include "team.h"
#include "nav_entities.h"

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


//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
BEGIN_DATADESC( CFuncNavCost )

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
	DEFINE_KEYFIELD( m_iszTags, FIELD_STRING, "tags" ),
	DEFINE_KEYFIELD( m_team, FIELD_INTEGER, "team" ),
	DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "start_disabled" ),

	DEFINE_THINKFUNC( CostThink ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( func_nav_avoid, CFuncNavAvoid );
LINK_ENTITY_TO_CLASS( func_nav_prefer, CFuncNavPrefer );

CUtlVector< CHandle< CFuncNavCost > > CFuncNavCost::gm_masterCostVector;
CountdownTimer CFuncNavCost::gm_dirtyTimer;

#define UPDATE_DIRTY_TIME 0.2f


//--------------------------------------------------------------------------------------------------------
void CFuncNavCost::Spawn( void )
{
	BaseClass::Spawn();

	gm_masterCostVector.AddToTail( this );
	gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );

	SetSolid( SOLID_BSP );	
	AddSolidFlags( FSOLID_NOT_SOLID );

	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );
	AddEffects( EF_NODRAW );
	SetCollisionGroup( COLLISION_GROUP_NONE );

	VPhysicsInitShadow( false, false );

	SetThink( &CFuncNavCost::CostThink );
	SetNextThink( gpGlobals->curtime + UPDATE_DIRTY_TIME );

	m_tags.RemoveAll();

	const char *tags = STRING( m_iszTags );

	// chop space-delimited string into individual tokens
	if ( tags )
	{
		char *buffer = new char [ strlen( tags ) + 1 ];
		Q_strcpy( buffer, tags );

		for( char *token = strtok( buffer, " " ); token; token = strtok( NULL, " " ) )
		{
			m_tags.AddToTail( CFmtStr( "%s", token ) );
		}

		delete [] buffer;
	}
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavCost::UpdateOnRemove( void )
{
	gm_masterCostVector.FindAndFastRemove( this );
	BaseClass::UpdateOnRemove();

	gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavCost::InputEnable( inputdata_t &inputdata )
{
	m_isDisabled = false;
	gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavCost::InputDisable( inputdata_t &inputdata )
{
	m_isDisabled = true;
	gm_dirtyTimer.Start( UPDATE_DIRTY_TIME );
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavCost::CostThink( void )
{
	SetNextThink( gpGlobals->curtime + UPDATE_DIRTY_TIME );

	if ( gm_dirtyTimer.HasStarted() && gm_dirtyTimer.IsElapsed() )
	{
		// one or more avoid entities have changed - update nav decoration
		gm_dirtyTimer.Invalidate();

		UpdateAllNavCostDecoration();
	}
}


//--------------------------------------------------------------------------------------------------------
bool CFuncNavCost::HasTag( const char *groupname ) const
{
	for( int i=0; i<m_tags.Count(); ++i )
	{
		if ( FStrEq( m_tags[i], groupname ) )
		{
			return true;
		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------
// Return true if this cost applies to the given actor
bool CFuncNavCost::IsApplicableTo( CBaseCombatCharacter *who ) const
{
	if ( !who )
	{
		return false;
	}

	if ( m_team > 0 )
	{
		if ( who->GetTeamNumber() != m_team )
		{
			return false;
		}
	}

#ifdef TF_DLL
	// TODO: Make group comparison efficient and move to base combat character
	CTFBot *bot = ToTFBot( who );
	if ( bot )
	{
		if ( bot->HasTheFlag() )
		{
			if ( HasTag( "bomb_carrier" ) )
			{
				return true;
			}

			// check custom bomb_carrier tags for this bot
			for( int i=0; i<m_tags.Count(); ++i )
			{
				const char* pszTag = m_tags[i];
				if ( V_stristr( pszTag, "bomb_carrier" ) )
				{
					if ( bot->HasTag( pszTag ) )
					{
						return true;
					}
				}
			}

			// the bomb carrier only pays attention to bomb_carrier costs
			return false;
		}

		if ( bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) )
		{
			if ( HasTag( "mission_sentry_buster" ) )
			{
				return true;
			}
		}
		
		if ( bot->HasMission( CTFBot::MISSION_SNIPER ) )
		{
			if ( HasTag( "mission_sniper" ) )
			{
				return true;
			}
		}
		
		if ( bot->HasMission( CTFBot::MISSION_SPY ) )
		{
			if ( HasTag( "mission_spy" ) )
			{
				return true;
			}
		}

		if ( bot->HasMission( CTFBot::MISSION_REPROGRAMMED ) )
		{
			return false;
		}

		if ( !bot->IsOnAnyMission() )
		{
			if ( HasTag( "common" ) )
			{
				return true;
			}
		}

		if ( HasTag( bot->GetPlayerClass()->GetName() ) )
		{
			return true;
		}

		// check custom tags for this bot
		for( int i=0; i<m_tags.Count(); ++i )
		{
			if ( bot->HasTag( m_tags[i] ) )
			{
				return true;
			}
		}

		// this cost doesn't apply to me
		return false;
	}
#endif

	return false;
}


//--------------------------------------------------------------------------------------------------------
// Reevaluate all func_nav_cost entities and update the nav decoration accordingly.
// This is required to handle overlapping func_nav_cost entities.
void CFuncNavCost::UpdateAllNavCostDecoration( void )
{
	int i, j;

	// first, clear all avoid decoration from the mesh
	for( i=0; i<TheNavAreas.Count(); ++i )
	{
		TheNavAreas[i]->ClearAllNavCostEntities();
	}

	// now, mark all areas with active cost entities overlapping them
	for( i=0; i<gm_masterCostVector.Count(); ++i )
	{
		CFuncNavCost *cost = gm_masterCostVector[i];

		if ( !cost || !cost->IsEnabled() )
		{
			continue;
		}

		Extent extent;
		extent.Init( cost );

		CUtlVector< CNavArea * > overlapVector;
		TheNavMesh->CollectAreasOverlappingExtent( extent, &overlapVector );

		Ray_t ray;
		trace_t tr;
		ICollideable *pCollide = cost->CollisionProp();

		for( j=0; j<overlapVector.Count(); ++j )
		{
			ray.Init( overlapVector[j]->GetCenter(), overlapVector[j]->GetCenter() );

			enginetrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &tr );
			
			if ( tr.startsolid )
			{
				overlapVector[j]->AddFuncNavCostEntity( cost );
			}
		}
	}
}


//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
// Return pathfind cost multiplier for the given actor
float CFuncNavAvoid::GetCostMultiplier( CBaseCombatCharacter *who ) const
{
	if ( IsApplicableTo( who ) )
	{
		return 25.0f;
	}

	return 1.0f;
}


//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
// Return pathfind cost multiplier for the given actor
float CFuncNavPrefer::GetCostMultiplier( CBaseCombatCharacter *who ) const
{
	if ( IsApplicableTo( who ) )
	{
		return 0.04f;	// 1/25th
	}

	return 1.0f;
}



//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
BEGIN_DATADESC( CFuncNavBlocker )

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "BlockNav", InputBlockNav ),
	DEFINE_INPUTFUNC( FIELD_VOID, "UnblockNav", InputUnblockNav ),
	DEFINE_KEYFIELD( m_blockedTeamNumber, FIELD_INTEGER, "teamToBlock" ),
	DEFINE_KEYFIELD( m_bDisabled,	FIELD_BOOLEAN,	"StartDisabled" ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( func_nav_blocker, CFuncNavBlocker );


CUtlLinkedList<CFuncNavBlocker *> CFuncNavBlocker::gm_NavBlockers;

//-----------------------------------------------------------------------------------------------------
int CFuncNavBlocker::DrawDebugTextOverlays( void )
{
	int offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		CFmtStr str;

		// FIRST_GAME_TEAM skips TEAM_SPECTATOR and TEAM_UNASSIGNED, so we can print
		// useful team names in a non-game-specific fashion.
		for ( int i=FIRST_GAME_TEAM; i<FIRST_GAME_TEAM + MAX_NAV_TEAMS; ++i )
		{
			if ( IsBlockingNav( i ) )
			{
				CTeam *team = GetGlobalTeam( i );
				if ( team )
				{
					EntityText( offset++, str.sprintf( "blocking team %s", team->GetName() ), 0 );
				}
				else
				{
					EntityText( offset++, str.sprintf( "blocking team %d", i ), 0 );
				}
			}
		}

		NavAreaCollector collector( true );
		Extent extent;
		extent.Init( this );
		TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );

		for ( int i=0; i<collector.m_area.Count(); ++i )
		{
			CNavArea *area = collector.m_area[i];
			Extent areaExtent;
			area->GetExtent( &areaExtent );
			debugoverlay->AddBoxOverlay( vec3_origin, areaExtent.lo, areaExtent.hi, vec3_angle, 0, 255, 0, 10, NDEBUG_PERSIST_TILL_NEXT_SERVER );
		}
	}

	return offset;
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavBlocker::UpdateBlocked()
{
	NavAreaCollector collector( true );
	Extent extent;
	extent.Init( this );
	TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );

	for ( int i=0; i<collector.m_area.Count(); ++i )
	{
		CNavArea *area = collector.m_area[i];
		area->UpdateBlocked( true );
	}

}


//--------------------------------------------------------------------------------------------------------
// Forces nav areas to unblock when the nav blocker is deleted (round restart) so flow can compute properly
void CFuncNavBlocker::UpdateOnRemove( void )
{
	UnblockNav();

	gm_NavBlockers.FindAndRemove( this );

	BaseClass::UpdateOnRemove();
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavBlocker::Spawn( void )
{
	gm_NavBlockers.AddToTail( this );

	if ( !m_blockedTeamNumber )
		m_blockedTeamNumber = TEAM_ANY;

	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );
	AddEffects( EF_NODRAW );
	SetCollisionGroup( COLLISION_GROUP_NONE );
	SetSolid( SOLID_NONE );
	AddSolidFlags( FSOLID_NOT_SOLID );
	CollisionProp()->WorldSpaceAABB( &m_CachedMins, &m_CachedMaxs );

	if ( m_bDisabled )
	{
		UnblockNav();
	}
	else
	{
		BlockNav();
	}
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavBlocker::InputBlockNav( inputdata_t &inputdata )
{
	BlockNav();
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavBlocker::InputUnblockNav( inputdata_t &inputdata )
{
	UnblockNav();
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavBlocker::BlockNav( void )
{
	if ( m_blockedTeamNumber == TEAM_ANY )
	{
		for ( int i=0; i<MAX_NAV_TEAMS; ++i )
		{
			m_isBlockingNav[ i ] = true;
		}
	}
	else
	{
		int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
		m_isBlockingNav[ teamNumber ] = true;
	}

	Extent extent;
	extent.Init( this );
	TheNavMesh->ForAllAreasOverlappingExtent( *this, extent );
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavBlocker::UnblockNav( void )
{
	if ( m_blockedTeamNumber == TEAM_ANY )
	{
		for ( int i=0; i<MAX_NAV_TEAMS; ++i )
		{
			m_isBlockingNav[ i ] = false;
		}
	}
	else
	{
		int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
		m_isBlockingNav[ teamNumber ] = false;
	}

	UpdateBlocked();
}


//--------------------------------------------------------------------------------------------------------
// functor that blocks areas in our extent
bool CFuncNavBlocker::operator()( CNavArea *area )
{
	area->MarkAsBlocked( m_blockedTeamNumber, this );
	return true;
}


//--------------------------------------------------------------------------------------------------------
bool CFuncNavBlocker::CalculateBlocked( bool *pResultByTeam, const Vector &vecMins, const Vector &vecMaxs )
{
	int nTeamsBlocked = 0;
	int i;
	bool bBlocked = false;
	for ( i=0; i<MAX_NAV_TEAMS; ++i )
	{
		pResultByTeam[i] = false;
	}

	FOR_EACH_LL( gm_NavBlockers, iBlocker )
	{
		CFuncNavBlocker *pBlocker = gm_NavBlockers[iBlocker];
		bool bIsIntersecting = false;

		for ( i=0; i<MAX_NAV_TEAMS; ++i )
		{
			if ( pBlocker->m_isBlockingNav[i] )
			{
				if ( !pResultByTeam[i] )
				{
					if ( bIsIntersecting || ( bIsIntersecting = IsBoxIntersectingBox( pBlocker->m_CachedMins, pBlocker->m_CachedMaxs, vecMins, vecMaxs ) ) != false )
					{
						bBlocked = true;
						pResultByTeam[i] = true;
						nTeamsBlocked++;
					}
					else
					{
						continue;
					}
				}
			}
		}

		if ( nTeamsBlocked == MAX_NAV_TEAMS )
		{
			break;
		}
 	}
	return bBlocked;
}


//-----------------------------------------------------------------------------------------------------
/**
  * An entity that can obstruct nav areas.  This is meant for semi-transient areas that obstruct
  * pathfinding but can be ignored for longer-term queries like computing L4D flow distances and
  * escape routes.
  */
class CFuncNavObstruction : public CBaseEntity, public INavAvoidanceObstacle
{
	DECLARE_DATADESC();
	DECLARE_CLASS( CFuncNavObstruction, CBaseEntity );

public:
	void Spawn();
	virtual void UpdateOnRemove( void );

	void InputEnable( inputdata_t &inputdata );
	void InputDisable( inputdata_t &inputdata );

	virtual bool IsPotentiallyAbleToObstructNavAreas( void ) const { return true; }		// could we at some future time obstruct nav?
	virtual float GetNavObstructionHeight( void ) const { return JumpCrouchHeight; }	// height at which to obstruct nav areas
	virtual bool CanObstructNavAreas( void ) const { return !m_bDisabled; }				// can we obstruct nav right this instant?
	virtual CBaseEntity *GetObstructingEntity( void ) { return this; }
	virtual void OnNavMeshLoaded( void )
	{
		if ( !m_bDisabled )
		{
			ObstructNavAreas();
		}
	}

	int DrawDebugTextOverlays( void );

	bool operator()( CNavArea *area );	// functor that obstructs areas in our extent

private:

	void ObstructNavAreas( void );
	bool m_bDisabled;
};



//--------------------------------------------------------------------------------------------------------
BEGIN_DATADESC( CFuncNavObstruction )
	DEFINE_KEYFIELD( m_bDisabled,	FIELD_BOOLEAN,	"StartDisabled" ),
END_DATADESC()


LINK_ENTITY_TO_CLASS( func_nav_avoidance_obstacle, CFuncNavObstruction );


//-----------------------------------------------------------------------------------------------------
int CFuncNavObstruction::DrawDebugTextOverlays( void )
{
	int offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		if ( CanObstructNavAreas() )
		{
			EntityText( offset++, "Obstructing nav", NDEBUG_PERSIST_TILL_NEXT_SERVER );
		}
		else
		{
			EntityText( offset++, "Not obstructing nav", NDEBUG_PERSIST_TILL_NEXT_SERVER );
		}
	}

	return offset;
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavObstruction::UpdateOnRemove( void )
{
	TheNavMesh->UnregisterAvoidanceObstacle( this );

	BaseClass::UpdateOnRemove();
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavObstruction::Spawn( void )
{
	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );
	AddEffects( EF_NODRAW );
	SetCollisionGroup( COLLISION_GROUP_NONE );
	SetSolid( SOLID_NONE );
	AddSolidFlags( FSOLID_NOT_SOLID );

	if ( !m_bDisabled )
	{
		ObstructNavAreas();
		TheNavMesh->RegisterAvoidanceObstacle( this );
	}
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavObstruction::InputEnable( inputdata_t &inputdata )
{
	m_bDisabled = false;
	ObstructNavAreas();
	TheNavMesh->RegisterAvoidanceObstacle( this );
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavObstruction::InputDisable( inputdata_t &inputdata )
{
	m_bDisabled = true;
	TheNavMesh->UnregisterAvoidanceObstacle( this );
}


//--------------------------------------------------------------------------------------------------------
void CFuncNavObstruction::ObstructNavAreas( void )
{
	Extent extent;
	extent.Init( this );
	TheNavMesh->ForAllAreasOverlappingExtent( *this, extent );
}


//--------------------------------------------------------------------------------------------------------
// functor that blocks areas in our extent
bool CFuncNavObstruction::operator()( CNavArea *area )
{
	area->MarkObstacleToAvoid( GetNavObstructionHeight() );
	return true;
}


//--------------------------------------------------------------------------------------------------------------
