Team Fortress 2 Source Code as on 22/4/2020
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.

472 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Basic BOT handling.
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. //
  8. //-----------------------------------------------------------------------------
  9. // $Log: $
  10. //
  11. // $NoKeywords: $
  12. //=============================================================================//
  13. #include "cbase.h"
  14. #include "player.h"
  15. #include "sdk_player.h"
  16. #include "in_buttons.h"
  17. #include "movehelper_server.h"
  18. #include "gameinterface.h"
  19. class CSDKBot;
  20. void Bot_Think( CSDKBot *pBot );
  21. ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
  22. ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
  23. ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
  24. ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
  25. ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." );
  26. static ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
  27. static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
  28. ConVar bot_sendcmd( "bot_sendcmd", "", 0, "Forces bots to send the specified command." );
  29. ConVar bot_crouch( "bot_crouch", "0", 0, "Bot crouches" );
  30. static int g_CurBotNumber = 1;
  31. // This is our bot class.
  32. class CSDKBot : public CSDKPlayer
  33. {
  34. public:
  35. bool m_bBackwards;
  36. float m_flNextTurnTime;
  37. bool m_bLastTurnToRight;
  38. float m_flNextStrafeTime;
  39. float m_flSideMove;
  40. QAngle m_ForwardAngle;
  41. QAngle m_LastAngles;
  42. };
  43. LINK_ENTITY_TO_CLASS( sdk_bot, CSDKBot );
  44. class CBotManager
  45. {
  46. public:
  47. static CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername )
  48. {
  49. // This tells it which edict to use rather than creating a new one.
  50. CBasePlayer::s_PlayerEdict = pEdict;
  51. CSDKBot *pPlayer = static_cast<CSDKBot *>( CreateEntityByName( "sdk_bot" ) );
  52. if ( pPlayer )
  53. {
  54. pPlayer->SetPlayerName( playername );
  55. }
  56. return pPlayer;
  57. }
  58. };
  59. //-----------------------------------------------------------------------------
  60. // Purpose: Create a new Bot and put it in the game.
  61. // Output : Pointer to the new Bot, or NULL if there's no free clients.
  62. //-----------------------------------------------------------------------------
  63. CBasePlayer *BotPutInServer( bool bFrozen )
  64. {
  65. char botname[ 64 ];
  66. Q_snprintf( botname, sizeof( botname ), "Bot%02i", g_CurBotNumber );
  67. // This trick lets us create a CSDKBot for this client instead of the CSDKPlayer
  68. // that we would normally get when ClientPutInServer is called.
  69. ClientPutInServerOverride( &CBotManager::ClientPutInServerOverride_Bot );
  70. edict_t *pEdict = engine->CreateFakeClient( botname );
  71. ClientPutInServerOverride( NULL );
  72. if (!pEdict)
  73. {
  74. Msg( "Failed to create Bot.\n");
  75. return NULL;
  76. }
  77. // Allocate a player entity for the bot, and call spawn
  78. CSDKBot *pPlayer = ((CSDKBot*)CBaseEntity::Instance( pEdict ));
  79. pPlayer->ClearFlags();
  80. pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
  81. if ( bFrozen )
  82. pPlayer->AddEFlags( EFL_BOT_FROZEN );
  83. pPlayer->ChangeTeam( TEAM_UNASSIGNED );
  84. pPlayer->RemoveAllItems( true );
  85. pPlayer->Spawn();
  86. g_CurBotNumber++;
  87. return pPlayer;
  88. }
  89. // Handler for the "bot" command.
  90. CON_COMMAND_F( "bot_add", "Add a bot.", FCVAR_CHEAT )
  91. {
  92. // Look at -count.
  93. int count = args.FindArgInt( "-count", 1 );
  94. count = clamp( count, 1, 16 );
  95. // Look at -frozen.
  96. bool bFrozen = !!args.FindArg( "-frozen" );
  97. // Ok, spawn all the bots.
  98. while ( --count >= 0 )
  99. {
  100. BotPutInServer( bFrozen );
  101. }
  102. }
  103. //-----------------------------------------------------------------------------
  104. // Purpose: Run through all the Bots in the game and let them think.
  105. //-----------------------------------------------------------------------------
  106. void Bot_RunAll( void )
  107. {
  108. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  109. {
  110. CSDKPlayer *pPlayer = ToSDKPlayer( UTIL_PlayerByIndex( i ) );
  111. if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
  112. {
  113. CSDKBot *pBot = dynamic_cast< CSDKBot* >( pPlayer );
  114. if ( pBot )
  115. Bot_Think( pBot );
  116. }
  117. }
  118. }
  119. bool Bot_RunMimicCommand( CUserCmd& cmd )
  120. {
  121. if ( bot_mimic.GetInt() <= 0 )
  122. return false;
  123. if ( bot_mimic.GetInt() > gpGlobals->maxClients )
  124. return false;
  125. CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
  126. if ( !pPlayer )
  127. return false;
  128. if ( !pPlayer->GetLastUserCommand() )
  129. return false;
  130. cmd = *pPlayer->GetLastUserCommand();
  131. cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
  132. if( bot_crouch.GetInt() )
  133. cmd.buttons |= IN_DUCK;
  134. return true;
  135. }
  136. //-----------------------------------------------------------------------------
  137. // Purpose: Simulates a single frame of movement for a player
  138. // Input : *fakeclient -
  139. // *viewangles -
  140. // forwardmove -
  141. // m_flSideMove -
  142. // upmove -
  143. // buttons -
  144. // impulse -
  145. // msec -
  146. // Output : virtual void
  147. //-----------------------------------------------------------------------------
  148. static void RunPlayerMove( CSDKPlayer *fakeclient, CUserCmd &cmd, float frametime )
  149. {
  150. if ( !fakeclient )
  151. return;
  152. // Store off the globals.. they're gonna get whacked
  153. float flOldFrametime = gpGlobals->frametime;
  154. float flOldCurtime = gpGlobals->curtime;
  155. float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
  156. fakeclient->SetTimeBase( flTimeBase );
  157. MoveHelperServer()->SetHost( fakeclient );
  158. fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
  159. // save off the last good usercmd
  160. fakeclient->SetLastUserCommand( cmd );
  161. // Clear out any fixangle that has been set
  162. fakeclient->pl.fixangle = FIXANGLE_NONE;
  163. // Restore the globals..
  164. gpGlobals->frametime = flOldFrametime;
  165. gpGlobals->curtime = flOldCurtime;
  166. }
  167. void Bot_UpdateStrafing( CSDKBot *pBot, CUserCmd &cmd )
  168. {
  169. if ( gpGlobals->curtime >= pBot->m_flNextStrafeTime )
  170. {
  171. pBot->m_flNextStrafeTime = gpGlobals->curtime + 1.0f;
  172. if ( random->RandomInt( 0, 5 ) == 0 )
  173. {
  174. pBot->m_flSideMove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
  175. }
  176. else
  177. {
  178. pBot->m_flSideMove = 0;
  179. }
  180. cmd.sidemove = pBot->m_flSideMove;
  181. if ( random->RandomInt( 0, 20 ) == 0 )
  182. {
  183. pBot->m_bBackwards = true;
  184. }
  185. else
  186. {
  187. pBot->m_bBackwards = false;
  188. }
  189. }
  190. }
  191. void Bot_UpdateDirection( CSDKBot *pBot )
  192. {
  193. float angledelta = 15.0;
  194. QAngle angle;
  195. int maxtries = (int)360.0/angledelta;
  196. if ( pBot->m_bLastTurnToRight )
  197. {
  198. angledelta = -angledelta;
  199. }
  200. angle = pBot->GetLocalAngles();
  201. trace_t trace;
  202. Vector vecSrc, vecEnd, forward;
  203. while ( --maxtries >= 0 )
  204. {
  205. AngleVectors( angle, &forward );
  206. vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
  207. vecEnd = vecSrc + forward * 10;
  208. UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
  209. MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
  210. if ( trace.fraction == 1.0 )
  211. {
  212. if ( gpGlobals->curtime < pBot->m_flNextTurnTime )
  213. {
  214. break;
  215. }
  216. }
  217. angle.y += angledelta;
  218. if ( angle.y > 180 )
  219. angle.y -= 360;
  220. else if ( angle.y < -180 )
  221. angle.y += 360;
  222. pBot->m_flNextTurnTime = gpGlobals->curtime + 2.0;
  223. pBot->m_bLastTurnToRight = random->RandomInt( 0, 1 ) == 0 ? true : false;
  224. pBot->m_ForwardAngle = angle;
  225. pBot->m_LastAngles = angle;
  226. }
  227. pBot->SetLocalAngles( angle );
  228. }
  229. void Bot_FlipOut( CSDKBot *pBot, CUserCmd &cmd )
  230. {
  231. if ( bot_flipout.GetInt() > 0 && pBot->IsAlive() )
  232. {
  233. if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
  234. {
  235. cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
  236. }
  237. if ( bot_flipout.GetInt() >= 2 )
  238. {
  239. QAngle angOffset = RandomAngle( -1, 1 );
  240. pBot->m_LastAngles += angOffset;
  241. for ( int i = 0 ; i < 2; i++ )
  242. {
  243. if ( fabs( pBot->m_LastAngles[ i ] - pBot->m_ForwardAngle[ i ] ) > 15.0f )
  244. {
  245. if ( pBot->m_LastAngles[ i ] > pBot->m_ForwardAngle[ i ] )
  246. {
  247. pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] + 15;
  248. }
  249. else
  250. {
  251. pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] - 15;
  252. }
  253. }
  254. }
  255. pBot->m_LastAngles[ 2 ] = 0;
  256. pBot->SetLocalAngles( pBot->m_LastAngles );
  257. }
  258. }
  259. }
  260. void Bot_HandleSendCmd( CSDKBot *pBot )
  261. {
  262. if ( strlen( bot_sendcmd.GetString() ) > 0 )
  263. {
  264. //send the cmd from this bot
  265. pBot->ClientCommand( bot_sendcmd.GetString() );
  266. bot_sendcmd.SetValue("");
  267. }
  268. }
  269. // If bots are being forced to fire a weapon, see if I have it
  270. void Bot_ForceFireWeapon( CSDKBot *pBot, CUserCmd &cmd )
  271. {
  272. if ( bot_forcefireweapon.GetString() )
  273. {
  274. CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
  275. if ( pWeapon )
  276. {
  277. // Switch to it if we don't have it out
  278. CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
  279. // Switch?
  280. if ( pActiveWeapon != pWeapon )
  281. {
  282. pBot->Weapon_Switch( pWeapon );
  283. }
  284. else
  285. {
  286. // Start firing
  287. // Some weapons require releases, so randomise firing
  288. if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
  289. {
  290. cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
  291. }
  292. }
  293. }
  294. }
  295. }
  296. void Bot_SetForwardMovement( CSDKBot *pBot, CUserCmd &cmd )
  297. {
  298. if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
  299. {
  300. if ( pBot->m_iHealth == 100 )
  301. {
  302. cmd.forwardmove = 600 * ( pBot->m_bBackwards ? -1 : 1 );
  303. if ( pBot->m_flSideMove != 0.0f )
  304. {
  305. cmd.forwardmove *= random->RandomFloat( 0.1, 1.0f );
  306. }
  307. }
  308. else
  309. {
  310. // Stop when shot
  311. cmd.forwardmove = 0;
  312. }
  313. }
  314. }
  315. void Bot_HandleRespawn( CSDKBot *pBot, CUserCmd &cmd )
  316. {
  317. // Wait for Reinforcement wave
  318. if ( !pBot->IsAlive() )
  319. {
  320. // Try hitting my buttons occasionally
  321. if ( random->RandomInt( 0, 100 ) > 80 )
  322. {
  323. // Respawn the bot
  324. if ( random->RandomInt( 0, 1 ) == 0 )
  325. {
  326. cmd.buttons |= IN_JUMP;
  327. }
  328. else
  329. {
  330. cmd.buttons = 0;
  331. }
  332. }
  333. }
  334. }
  335. //-----------------------------------------------------------------------------
  336. // Run this Bot's AI for one frame.
  337. //-----------------------------------------------------------------------------
  338. void Bot_Think( CSDKBot *pBot )
  339. {
  340. // Make sure we stay being a bot
  341. pBot->AddFlag( FL_FAKECLIENT );
  342. CUserCmd cmd;
  343. Q_memset( &cmd, 0, sizeof( cmd ) );
  344. // Finally, override all this stuff if the bot is being forced to mimic a player.
  345. if ( !Bot_RunMimicCommand( cmd ) )
  346. {
  347. cmd.sidemove = pBot->m_flSideMove;
  348. if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
  349. {
  350. Bot_SetForwardMovement( pBot, cmd );
  351. // Only turn if I haven't been hurt
  352. if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
  353. {
  354. Bot_UpdateDirection( pBot );
  355. Bot_UpdateStrafing( pBot, cmd );
  356. }
  357. // Handle console settings.
  358. Bot_ForceFireWeapon( pBot, cmd );
  359. Bot_HandleSendCmd( pBot );
  360. }
  361. else
  362. {
  363. Bot_HandleRespawn( pBot, cmd );
  364. }
  365. Bot_FlipOut( pBot, cmd );
  366. cmd.viewangles = pBot->GetLocalAngles();
  367. cmd.upmove = 0;
  368. cmd.impulse = 0;
  369. }
  370. float frametime = gpGlobals->frametime;
  371. RunPlayerMove( pBot, cmd, frametime );
  372. }