//========= 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( 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 ); }