//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Basic BOT handling.
//
// $Workfile:     $
// $Date:         $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "bot/bot.h"
#include "in_buttons.h"
#include "movehelper_server.h"
#include "gameinterface.h"
#include "bot/bot_navigator.h"
#include "bot/bot_pathfinder.h"

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


class CBot;
void Bot_Think( CBot *pBot );


ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." );
static ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );

ConVar bot_sendcmd( "bot_sendcmd", "", 0, "Forces bots to send the specified command." );

ConVar bot_crouch( "bot_crouch", "0", 0, "Bot crouches" );

static int g_CurBotNumber = 1;


CBot::CBot()
{
	m_pNavigator = new CBotNavigator(this);
	m_pPathFinder = new CBotPathfinder(this);
}

void CBot::Precache()
{
	BaseClass::Precache();

	PrecacheModel( "models/Combine_Soldier.mdl" );
}

void CBot::Spawn()
{
	BaseClass::Spawn();

	Precache();

	SetModel( "models/Combine_Soldier.mdl" );

	SetRTSMode(false);

	GetNavigator()->SetGoal( GetAbsOrigin(), 32.0f);
}

void CBot::SetGoal( Vector goal, float tolerance )
{
	GetNavigator()->SetGoal( goal, tolerance);
}

Vector CBot::GetGoal()
{
	return GetNavigator()->GetGoal();
}

void CBot::Update()
{
	GetNavigator()->Move();

	HandleSendCmd();
}

void CBot::HandleSendCmd()
{
	if ( strlen( bot_sendcmd.GetString() ) > 0 )
	{
		//send the cmd from this bot
		const char *command = bot_sendcmd.GetString();
		CCommand args(0, &command );
		ClientCommand( args );

		bot_sendcmd.SetValue("");
	}
}

//-----------------------------------------------------------------------------
// Purpose: Simulates a single frame of movement for a player
// Input  : *fakeclient - 
//			*viewangles - 
//			forwardmove - 
//			m_flSideMove - 
//			upmove - 
//			buttons - 
//			impulse - 
//			msec - 
// Output : 	virtual void
//-----------------------------------------------------------------------------
void CBot::RunPlayerMove( CUserCmd &cmd, float frametime )
{
	// add everything to the cmd
	cmd.forwardmove = m_flForwardMove;
	cmd.sidemove = m_flSideMove;
	cmd.viewangles = m_vViewAngles;
	cmd.buttons = m_iButtons;
	cmd.upmove = 0;
	cmd.impulse = 0;

	// Store off the globals.. they're gonna get whacked
	float flOldFrametime = gpGlobals->frametime;
	float flOldCurtime = gpGlobals->curtime;

	float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
	SetTimeBase( flTimeBase );

	MoveHelperServer()->SetHost( this );
	PlayerRunCommand( &cmd, MoveHelperServer() );

	// save off the last good usercmd
	SetLastUserCommand( cmd );

	// Clear out any fixangle that has been set
	pl.fixangle = FIXANGLE_NONE;

	// Restore the globals..
	gpGlobals->frametime = flOldFrametime;
	gpGlobals->curtime = flOldCurtime;
}

Vector CBot::GetLookAtTarget()
{
	return m_vLookAtTarget;
}

void CBot::SetLookAtTarget( Vector target )
{
	m_vLookAtTarget = target;
}

LINK_ENTITY_TO_CLASS( bot, CBot );

class CBotManager
{
public:
	static CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername )
	{
		// This tells it which edict to use rather than creating a new one.
		CBasePlayer::s_PlayerEdict = pEdict;

		CBot *pPlayer = static_cast<CBot *>( CreateEntityByName( "bot" ) );
		if ( pPlayer )
		{
			pPlayer->SetPlayerName( playername );
		}

		return pPlayer;
	}
};


//-----------------------------------------------------------------------------
// Purpose: Create a new Bot and put it in the game.
// Output : Pointer to the new Bot, or NULL if there's no free clients.
//-----------------------------------------------------------------------------
CBasePlayer *BotPutInServer( bool bFrozen )
{
	char botname[ 64 ];
	Q_snprintf( botname, sizeof( botname ), "Bot%02i", g_CurBotNumber );


	// This trick lets us create a CSDKBot for this client instead of the CSDKPlayer
	// that we would normally get when ClientPutInServer is called.
	ClientPutInServerOverride( &CBotManager::ClientPutInServerOverride_Bot );
	edict_t *pEdict = engine->CreateFakeClient( botname );
	ClientPutInServerOverride( NULL );

	if (!pEdict)
	{
		Msg( "Failed to create Bot.\n");
		return NULL;
	}

	// Allocate a player entity for the bot, and call spawn
	CBot *pPlayer = ((CBot*)CBaseEntity::Instance( pEdict ));

	pPlayer->ClearFlags();
	pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );

	if ( bFrozen )
		pPlayer->AddEFlags( EFL_BOT_FROZEN );

	pPlayer->ChangeTeam( TEAM_UNASSIGNED );
	pPlayer->RemoveAllItems( true );

	pPlayer->Spawn();

	g_CurBotNumber++;

	return pPlayer;
}

// Handler for the "bot" command.
CON_COMMAND_F( bot_add, "Add a bot.", FCVAR_CHEAT )
{
	// Look at -count.
	int count = args.FindArgInt( "-count", 1 );
	count = clamp( count, 1, 16 );

	// Look at -frozen.
	bool bFrozen = !!args.FindArg( "-frozen" );

	// Ok, spawn all the bots.
	while ( --count >= 0 )
	{
		BotPutInServer( bFrozen );
	}
}

// Set a goal for all bots
CON_COMMAND_F( bot_setgoal, "Set a goal for all bots.", FCVAR_CHEAT )
{
	CHL2Wars_Player* pPlayer = ToHL2WarsPlayer( UTIL_GetCommandClient() );
	if( !pPlayer )
		return;

	trace_t tr;
	Vector aim;
	aim = pPlayer->GetMouseAim();

	AI_TraceLine(pPlayer->Weapon_ShootPosition(),
		pPlayer->Weapon_ShootPosition() + aim * MAX_TRACE_LENGTH,MASK_NPCSOLID, 
		pPlayer, COLLISION_GROUP_NONE, &tr );

	Vector goal = tr.endpos;

	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );

		if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
		{
			CBot *pBot = dynamic_cast< CBot* >( pPlayer );
			if ( pBot )
				pBot->SetGoal( goal, 32.0f );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Run through all the Bots in the game and let them think.
//-----------------------------------------------------------------------------
void Bot_RunAll( void )
{
	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );

		if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
		{
			CBot *pBot = dynamic_cast< CBot* >( pPlayer );
			if ( pBot )
				Bot_Think( pBot );
		}
	}
}


//-----------------------------------------------------------------------------
// Run this Bot's AI for one frame.
//-----------------------------------------------------------------------------
void Bot_Think( CBot *pBot )
{
	// Make sure we stay being a bot
	pBot->AddFlag( FL_FAKECLIENT );

	CUserCmd cmd;
	Q_memset( &cmd, 0, sizeof( cmd ) );

	// let the bot do it's work
	pBot->Update();

	// Fix up the m_fEffects flags
	pBot->PostClientMessagesSent();

	float frametime = gpGlobals->frametime;
	pBot->RunPlayerMove( cmd, frametime );
}
