You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
473 lines
11 KiB
473 lines
11 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose: Basic BOT handling.
|
||
|
//
|
||
|
// $Workfile: $
|
||
|
// $Date: $
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// $Log: $
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "player.h"
|
||
|
#include "sdk_player.h"
|
||
|
#include "in_buttons.h"
|
||
|
#include "movehelper_server.h"
|
||
|
#include "gameinterface.h"
|
||
|
|
||
|
|
||
|
class CSDKBot;
|
||
|
void Bot_Think( CSDKBot *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;
|
||
|
|
||
|
|
||
|
// This is our bot class.
|
||
|
class CSDKBot : public CSDKPlayer
|
||
|
{
|
||
|
public:
|
||
|
bool m_bBackwards;
|
||
|
|
||
|
float m_flNextTurnTime;
|
||
|
bool m_bLastTurnToRight;
|
||
|
|
||
|
float m_flNextStrafeTime;
|
||
|
float m_flSideMove;
|
||
|
|
||
|
QAngle m_ForwardAngle;
|
||
|
QAngle m_LastAngles;
|
||
|
};
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( sdk_bot, CSDKBot );
|
||
|
|
||
|
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;
|
||
|
|
||
|
CSDKBot *pPlayer = static_cast<CSDKBot *>( CreateEntityByName( "sdk_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
|
||
|
CSDKBot *pPlayer = ((CSDKBot*)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 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// 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++ )
|
||
|
{
|
||
|
CSDKPlayer *pPlayer = ToSDKPlayer( UTIL_PlayerByIndex( i ) );
|
||
|
|
||
|
if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
|
||
|
{
|
||
|
CSDKBot *pBot = dynamic_cast< CSDKBot* >( pPlayer );
|
||
|
if ( pBot )
|
||
|
Bot_Think( pBot );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool Bot_RunMimicCommand( CUserCmd& cmd )
|
||
|
{
|
||
|
if ( bot_mimic.GetInt() <= 0 )
|
||
|
return false;
|
||
|
|
||
|
if ( bot_mimic.GetInt() > gpGlobals->maxClients )
|
||
|
return false;
|
||
|
|
||
|
|
||
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
|
||
|
if ( !pPlayer )
|
||
|
return false;
|
||
|
|
||
|
if ( !pPlayer->GetLastUserCommand() )
|
||
|
return false;
|
||
|
|
||
|
cmd = *pPlayer->GetLastUserCommand();
|
||
|
cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
|
||
|
|
||
|
if( bot_crouch.GetInt() )
|
||
|
cmd.buttons |= IN_DUCK;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Simulates a single frame of movement for a player
|
||
|
// Input : *fakeclient -
|
||
|
// *viewangles -
|
||
|
// forwardmove -
|
||
|
// m_flSideMove -
|
||
|
// upmove -
|
||
|
// buttons -
|
||
|
// impulse -
|
||
|
// msec -
|
||
|
// Output : virtual void
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static void RunPlayerMove( CSDKPlayer *fakeclient, CUserCmd &cmd, float frametime )
|
||
|
{
|
||
|
if ( !fakeclient )
|
||
|
return;
|
||
|
|
||
|
// Store off the globals.. they're gonna get whacked
|
||
|
float flOldFrametime = gpGlobals->frametime;
|
||
|
float flOldCurtime = gpGlobals->curtime;
|
||
|
|
||
|
float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
|
||
|
fakeclient->SetTimeBase( flTimeBase );
|
||
|
|
||
|
MoveHelperServer()->SetHost( fakeclient );
|
||
|
fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
|
||
|
|
||
|
// save off the last good usercmd
|
||
|
fakeclient->SetLastUserCommand( cmd );
|
||
|
|
||
|
// Clear out any fixangle that has been set
|
||
|
fakeclient->pl.fixangle = FIXANGLE_NONE;
|
||
|
|
||
|
// Restore the globals..
|
||
|
gpGlobals->frametime = flOldFrametime;
|
||
|
gpGlobals->curtime = flOldCurtime;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void Bot_UpdateStrafing( CSDKBot *pBot, CUserCmd &cmd )
|
||
|
{
|
||
|
if ( gpGlobals->curtime >= pBot->m_flNextStrafeTime )
|
||
|
{
|
||
|
pBot->m_flNextStrafeTime = gpGlobals->curtime + 1.0f;
|
||
|
|
||
|
if ( random->RandomInt( 0, 5 ) == 0 )
|
||
|
{
|
||
|
pBot->m_flSideMove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pBot->m_flSideMove = 0;
|
||
|
}
|
||
|
cmd.sidemove = pBot->m_flSideMove;
|
||
|
|
||
|
if ( random->RandomInt( 0, 20 ) == 0 )
|
||
|
{
|
||
|
pBot->m_bBackwards = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pBot->m_bBackwards = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void Bot_UpdateDirection( CSDKBot *pBot )
|
||
|
{
|
||
|
float angledelta = 15.0;
|
||
|
QAngle angle;
|
||
|
|
||
|
int maxtries = (int)360.0/angledelta;
|
||
|
|
||
|
if ( pBot->m_bLastTurnToRight )
|
||
|
{
|
||
|
angledelta = -angledelta;
|
||
|
}
|
||
|
|
||
|
angle = pBot->GetLocalAngles();
|
||
|
|
||
|
trace_t trace;
|
||
|
Vector vecSrc, vecEnd, forward;
|
||
|
while ( --maxtries >= 0 )
|
||
|
{
|
||
|
AngleVectors( angle, &forward );
|
||
|
|
||
|
vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
|
||
|
|
||
|
vecEnd = vecSrc + forward * 10;
|
||
|
|
||
|
UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
|
||
|
MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
|
||
|
|
||
|
if ( trace.fraction == 1.0 )
|
||
|
{
|
||
|
if ( gpGlobals->curtime < pBot->m_flNextTurnTime )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
angle.y += angledelta;
|
||
|
|
||
|
if ( angle.y > 180 )
|
||
|
angle.y -= 360;
|
||
|
else if ( angle.y < -180 )
|
||
|
angle.y += 360;
|
||
|
|
||
|
pBot->m_flNextTurnTime = gpGlobals->curtime + 2.0;
|
||
|
pBot->m_bLastTurnToRight = random->RandomInt( 0, 1 ) == 0 ? true : false;
|
||
|
|
||
|
pBot->m_ForwardAngle = angle;
|
||
|
pBot->m_LastAngles = angle;
|
||
|
}
|
||
|
|
||
|
pBot->SetLocalAngles( angle );
|
||
|
}
|
||
|
|
||
|
|
||
|
void Bot_FlipOut( CSDKBot *pBot, CUserCmd &cmd )
|
||
|
{
|
||
|
if ( bot_flipout.GetInt() > 0 && pBot->IsAlive() )
|
||
|
{
|
||
|
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
|
||
|
{
|
||
|
cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
|
||
|
}
|
||
|
|
||
|
if ( bot_flipout.GetInt() >= 2 )
|
||
|
{
|
||
|
QAngle angOffset = RandomAngle( -1, 1 );
|
||
|
|
||
|
pBot->m_LastAngles += angOffset;
|
||
|
|
||
|
for ( int i = 0 ; i < 2; i++ )
|
||
|
{
|
||
|
if ( fabs( pBot->m_LastAngles[ i ] - pBot->m_ForwardAngle[ i ] ) > 15.0f )
|
||
|
{
|
||
|
if ( pBot->m_LastAngles[ i ] > pBot->m_ForwardAngle[ i ] )
|
||
|
{
|
||
|
pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] + 15;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] - 15;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pBot->m_LastAngles[ 2 ] = 0;
|
||
|
|
||
|
pBot->SetLocalAngles( pBot->m_LastAngles );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void Bot_HandleSendCmd( CSDKBot *pBot )
|
||
|
{
|
||
|
if ( strlen( bot_sendcmd.GetString() ) > 0 )
|
||
|
{
|
||
|
//send the cmd from this bot
|
||
|
pBot->ClientCommand( bot_sendcmd.GetString() );
|
||
|
|
||
|
bot_sendcmd.SetValue("");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// If bots are being forced to fire a weapon, see if I have it
|
||
|
void Bot_ForceFireWeapon( CSDKBot *pBot, CUserCmd &cmd )
|
||
|
{
|
||
|
if ( bot_forcefireweapon.GetString() )
|
||
|
{
|
||
|
CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
// Switch to it if we don't have it out
|
||
|
CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
|
||
|
|
||
|
// Switch?
|
||
|
if ( pActiveWeapon != pWeapon )
|
||
|
{
|
||
|
pBot->Weapon_Switch( pWeapon );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Start firing
|
||
|
// Some weapons require releases, so randomise firing
|
||
|
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
|
||
|
{
|
||
|
cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void Bot_SetForwardMovement( CSDKBot *pBot, CUserCmd &cmd )
|
||
|
{
|
||
|
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
|
||
|
{
|
||
|
if ( pBot->m_iHealth == 100 )
|
||
|
{
|
||
|
cmd.forwardmove = 600 * ( pBot->m_bBackwards ? -1 : 1 );
|
||
|
if ( pBot->m_flSideMove != 0.0f )
|
||
|
{
|
||
|
cmd.forwardmove *= random->RandomFloat( 0.1, 1.0f );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Stop when shot
|
||
|
cmd.forwardmove = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void Bot_HandleRespawn( CSDKBot *pBot, CUserCmd &cmd )
|
||
|
{
|
||
|
// Wait for Reinforcement wave
|
||
|
if ( !pBot->IsAlive() )
|
||
|
{
|
||
|
// Try hitting my buttons occasionally
|
||
|
if ( random->RandomInt( 0, 100 ) > 80 )
|
||
|
{
|
||
|
// Respawn the bot
|
||
|
if ( random->RandomInt( 0, 1 ) == 0 )
|
||
|
{
|
||
|
cmd.buttons |= IN_JUMP;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cmd.buttons = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Run this Bot's AI for one frame.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void Bot_Think( CSDKBot *pBot )
|
||
|
{
|
||
|
// Make sure we stay being a bot
|
||
|
pBot->AddFlag( FL_FAKECLIENT );
|
||
|
|
||
|
|
||
|
CUserCmd cmd;
|
||
|
Q_memset( &cmd, 0, sizeof( cmd ) );
|
||
|
|
||
|
|
||
|
// Finally, override all this stuff if the bot is being forced to mimic a player.
|
||
|
if ( !Bot_RunMimicCommand( cmd ) )
|
||
|
{
|
||
|
cmd.sidemove = pBot->m_flSideMove;
|
||
|
|
||
|
if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
|
||
|
{
|
||
|
Bot_SetForwardMovement( pBot, cmd );
|
||
|
|
||
|
// Only turn if I haven't been hurt
|
||
|
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
|
||
|
{
|
||
|
Bot_UpdateDirection( pBot );
|
||
|
Bot_UpdateStrafing( pBot, cmd );
|
||
|
}
|
||
|
|
||
|
// Handle console settings.
|
||
|
Bot_ForceFireWeapon( pBot, cmd );
|
||
|
Bot_HandleSendCmd( pBot );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Bot_HandleRespawn( pBot, cmd );
|
||
|
}
|
||
|
|
||
|
Bot_FlipOut( pBot, cmd );
|
||
|
|
||
|
cmd.viewangles = pBot->GetLocalAngles();
|
||
|
cmd.upmove = 0;
|
||
|
cmd.impulse = 0;
|
||
|
}
|
||
|
|
||
|
float frametime = gpGlobals->frametime;
|
||
|
RunPlayerMove( pBot, cmd, frametime );
|
||
|
}
|
||
|
|
||
|
|