// NextBotCombatCharacter.cpp
// Next generation bot system
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//

#include "cbase.h"

#include "team.h"
#include "CRagdollMagnet.h"

#include "NextBot.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"

#ifdef TERROR
#include "TerrorGamerules.h"
#endif

#include "vprof.h"
#include "datacache/imdlcache.h"
#include "EntityFlame.h"

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


ConVar NextBotStop( "nb_stop", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Stop all NextBots" );


//--------------------------------------------------------------------------------------------------------
class CSendBotCommand
{
public:
	CSendBotCommand( const char *command )
	{
		m_command = command;
	}

	bool operator() ( INextBot *bot )
	{
		bot->OnCommandString( m_command );
		return true;
	}

	const char *m_command;
};


CON_COMMAND_F( nb_command, "Sends a command string to all bots", FCVAR_CHEAT )
{
	if ( args.ArgC() <= 1 )
	{
		Msg( "Missing command string" );
		return;
	}

	CSendBotCommand sendCmd( args.ArgS() );
	TheNextBots().ForEachBot( sendCmd );
}



//-----------------------------------------------------------------------------------------------------
BEGIN_DATADESC( NextBotCombatCharacter )

	DEFINE_THINKFUNC( DoThink ),

END_DATADESC()


//-----------------------------------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( NextBotCombatCharacter, DT_NextBot )
END_SEND_TABLE()


//-----------------------------------------------------------------------------------------------------
NextBotDestroyer::NextBotDestroyer( int team )
{
	m_team = team;
}


//-----------------------------------------------------------------------------------------------------
bool NextBotDestroyer::operator() ( INextBot *bot  )
{
	if ( m_team == TEAM_ANY || bot->GetEntity()->GetTeamNumber() == m_team )
	{
		// players need to be kicked, not deleted
		if ( bot->GetEntity()->IsPlayer() )
		{
			CBasePlayer *player = dynamic_cast< CBasePlayer * >( bot->GetEntity() );
			engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
		}
		else
		{
			UTIL_Remove( bot->GetEntity() );
		}
	}
	return true;
}


//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( nb_delete_all, "Delete all non-player NextBot entities.", FCVAR_CHEAT )
{
	// Listenserver host or rcon access only!
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	CTeam *team = NULL;

	if ( args.ArgC() == 2 )
	{
		const char *teamName = args[1];

		for( int i=0; i < g_Teams.Count(); ++i )
		{
			if ( FStrEq( teamName, g_Teams[i]->GetName() ) )
			{
				// delete all bots on this team
				team = g_Teams[i];
				break;
			}
		}

		if ( team == NULL )
		{
			Msg( "Invalid team '%s'\n", teamName );
			return;
		}
	}

	// delete all bots on all teams
	NextBotDestroyer destroyer( team ? team->GetTeamNumber() : TEAM_ANY );
	TheNextBots().ForEachBot( destroyer );
}


//-----------------------------------------------------------------------------------------------------
class NextBotApproacher
{
public:
	NextBotApproacher( void )
	{
		CBasePlayer *player = UTIL_GetListenServerHost();
		if ( player )
		{
			Vector forward;
			player->EyeVectors( &forward );

			trace_t result;
			unsigned int mask = MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_GRATE | CONTENTS_WINDOW;
			UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, mask, player, COLLISION_GROUP_NONE, &result );
			if ( result.DidHit() )
			{
				NDebugOverlay::Cross3D( result.endpos, 5, 0, 255, 0, true, 10.0f );
				m_isGoalValid = true;
				m_goal = result.endpos;
			}
			else
			{
				m_isGoalValid = false;
			}
		}
	}

	bool operator() ( INextBot *bot )
	{
		if ( TheNextBots().IsDebugFilterMatch( bot ) )
		{
			bot->OnCommandApproach( m_goal );
		}
		return true;
	}

	bool m_isGoalValid;
	Vector m_goal;
};

CON_COMMAND_F( nb_move_to_cursor, "Tell all NextBots to move to the cursor position", FCVAR_CHEAT )
{
	// Listenserver host or rcon access only!
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	NextBotApproacher approach;
	TheNextBots().ForEachBot( approach );
}


//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
{
	CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
	return ( entity->MyCombatCharacterPointer() == NULL );	// includes all bots, npcs, players, and TF2 buildings
}


//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
{
	// Honor BlockLOS also to allow seeing through partially-broken doors
	CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
	return ( entity->MyCombatCharacterPointer() == NULL && entity->BlocksLOS() );
}


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

NextBotCombatCharacter::NextBotCombatCharacter( void )

{
	m_lastAttacker = NULL;
	m_didModelChange = false;
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Spawn( void )
{
	BaseClass::Spawn();
	
	// reset bot components
	Reset();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	
	SetMoveType( MOVETYPE_CUSTOM );	
	
	SetCollisionGroup( COLLISION_GROUP_PLAYER );

	m_iMaxHealth = m_iHealth;
	m_takedamage = DAMAGE_YES;

	MDLCACHE_CRITICAL_SECTION();
	InitBoneControllers( ); 

	// set up think callback
	SetThink( &NextBotCombatCharacter::DoThink );
	SetNextThink( gpGlobals->curtime );

	m_lastAttacker = NULL;
}


bool NextBotCombatCharacter::IsAreaTraversable( const CNavArea *area ) const
{
	if ( !area )
		return false;
	ILocomotion *mover = GetLocomotionInterface();
	if ( mover && !mover->IsAreaTraversable( area ) )
		return false;
	return BaseClass::IsAreaTraversable( area );
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::DoThink( void )
{
	VPROF_BUDGET( "NextBotCombatCharacter::DoThink", "NextBot" );

	SetNextThink( gpGlobals->curtime );

	if ( BeginUpdate() )
	{
		// emit model change event	
		if ( m_didModelChange )
		{
			m_didModelChange = false;

			OnModelChanged();

			// propagate model change into NextBot event responders
			for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
			{
				sub->OnModelChanged();
			}	
		}

		UpdateLastKnownArea();	

		// update bot components
		if ( !NextBotStop.GetBool() && (GetFlags() & FL_FROZEN) == 0 )
		{
			Update();
		}

		EndUpdate();
	}
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Touch( CBaseEntity *other )
{
	if ( ShouldTouch( other ) )
	{
		// propagate touch into NextBot event responders
		trace_t result;
		result = GetTouchTrace();

		// OnContact refers to *physical* contact, not triggers or other non-physical entities
		if ( result.DidHit() || other->MyCombatCharacterPointer() != NULL )
		{
			OnContact( other, &result );
		}
	}
	
	BaseClass::Touch( other );
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::SetModel( const char *szModelName )
{
	// actually change the model
	BaseClass::SetModel( szModelName );
	
	// need to do a lazy-check because precache system also invokes this
	m_didModelChange = true;
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
	BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
	
	// propagate event to components
	OnIgnite();
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Ignite( float flFlameLifetime, CBaseEntity *pAttacker )
{
	if ( IsOnFire() )
		return;

	// BaseClass::Ignite stuff, plus SetAttacker on the flame, so our attacker gets credit
	CEntityFlame *pFlame = CEntityFlame::Create( this );
	if ( pFlame )
	{
		pFlame->SetLifetime( flFlameLifetime );
		AddFlag( FL_ONFIRE );

		SetEffectEntity( pFlame );
	}
	m_OnIgnite.FireOutput( this, this );

	// propagate event to components
	OnIgnite();
}


//----------------------------------------------------------------------------------------------------------
int NextBotCombatCharacter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	// track our last attacker
	if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
	{
		m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
	}

	// propagate event to components
	OnInjured( info );
	
	return CBaseCombatCharacter::OnTakeDamage_Alive( info );
}


//----------------------------------------------------------------------------------------------------------
int NextBotCombatCharacter::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
	// track our last attacker	
	if ( info.GetAttacker()->MyCombatCharacterPointer() )
	{
		m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
	}

	// propagate event to components
	OnInjured( info );

	return CBaseCombatCharacter::OnTakeDamage_Dying( info );
}

//----------------------------------------------------------------------------------------------------------
/**
 * Can't use CBaseCombatCharacter's Event_Killed because it will immediately ragdoll us
 */
static int g_DeathStartEvent = 0;
void NextBotCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
{
	// track our last attacker
	if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
	{
		m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
	}

	// propagate event to my components
	OnKilled( info );
	
	// Advance life state to dying
	m_lifeState = LIFE_DYING;

#ifdef TERROR
	/*
	 * TODO: Make this game-generic
	 */
	// Create the death event just like players do.
	TerrorGameRules()->DeathNoticeForEntity( this, info );

	// Infected specific event
	TerrorGameRules()->DeathNoticeForInfected( this, info );
#endif

	if ( GetOwnerEntity() != NULL )
	{
		GetOwnerEntity()->DeathNotice( this );
	}

	// inform the other bots
	TheNextBots().OnKilled( this, info );
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
{
	ILocomotion *mover = GetLocomotionInterface();
	if ( mover )
	{
		// hack to keep ground entity from being NULL'd when Z velocity is positive
		SetGroundEntity( mover->GetGround() );
	}
}


//----------------------------------------------------------------------------------------------------------
bool NextBotCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
{
	// See if there's a ragdoll magnet that should influence our force.
	Vector adjustedForceVector = forceVector;
	CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( this );
	if ( magnet )
	{
		adjustedForceVector += magnet->GetForceVector( this );
	}

	// clear the deceased's sound channels.(may have been firing or reloading when killed)
	EmitSound( "BaseCombatCharacter.StopWeaponSounds" );

	return BaseClass::BecomeRagdoll( info, adjustedForceVector );
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::HandleAnimEvent( animevent_t *event )
{
	// propagate event to components
	OnAnimationEvent( event );
}


//----------------------------------------------------------------------------------------------------------
/**
 * Propagate event into NextBot event responders
 */
void NextBotCombatCharacter::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
{
	INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );

	BaseClass::OnNavAreaChanged( enteredArea, leftArea );
}


//----------------------------------------------------------------------------------------------------------
Vector NextBotCombatCharacter::EyePosition( void )
{
	if ( GetBodyInterface() )
	{
		return GetBodyInterface()->GetEyePosition();
	}

	return BaseClass::EyePosition();
}


//----------------------------------------------------------------------------------------------------------
/**
 * Return true if this object can be +used by the bot
 */
bool NextBotCombatCharacter::IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps )
{
	if ( entity )
	{
		int caps = entity->ObjectCaps();
		if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
		{
			if ( (caps & requiredCaps) == requiredCaps )
			{
				return true;
			}
		}
	}

	return false;
}


//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::UseEntity( CBaseEntity *entity, USE_TYPE useType )
{
	if ( IsUseableEntity( entity ) )
	{
		variant_t emptyVariant;
		entity->AcceptInput( "Use", this, this, emptyVariant, useType );
	}
}