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.

470 lines
12 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 "tfc_player.h"
  16. #include "in_buttons.h"
  17. #include "movehelper_server.h"
  18. void ClientPutInServer( edict_t *pEdict, const char *playername );
  19. void Bot_Think( CTFCPlayer *pBot );
  20. ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
  21. ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
  22. ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
  23. ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
  24. ConVar bot_defend( "bot_defend", "0", 0, "Set to a team number, and that team will all keep their combat shields raised." );
  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", "180", 0, "Offsets the bot yaw." );
  28. static int BotNumber = 1;
  29. static int g_iNextBotTeam = -1;
  30. static int g_iNextBotClass = -1;
  31. typedef struct
  32. {
  33. bool backwards;
  34. float nextturntime;
  35. bool lastturntoright;
  36. float nextstrafetime;
  37. float sidemove;
  38. QAngle forwardAngle;
  39. QAngle lastAngles;
  40. float m_flJoinTeamTime;
  41. int m_WantedTeam;
  42. int m_WantedClass;
  43. bool m_bWasDead;
  44. float m_flDeadTime;
  45. } botdata_t;
  46. static botdata_t g_BotData[ MAX_PLAYERS ];
  47. //-----------------------------------------------------------------------------
  48. // Purpose: Create a new Bot and put it in the game.
  49. // Output : Pointer to the new Bot, or NULL if there's no free clients.
  50. //-----------------------------------------------------------------------------
  51. CBasePlayer *BotPutInServer( bool bFrozen, int iTeam, int iClass )
  52. {
  53. g_iNextBotTeam = iTeam;
  54. g_iNextBotClass = iClass;
  55. char botname[ 64 ];
  56. Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
  57. edict_t *pEdict = engine->CreateFakeClient( botname );
  58. if (!pEdict)
  59. {
  60. Msg( "Failed to create Bot.\n");
  61. return NULL;
  62. }
  63. // Allocate a CBasePlayer for the bot, and call spawn
  64. //ClientPutInServer( pEdict, botname );
  65. CTFCPlayer *pPlayer = ((CTFCPlayer *)CBaseEntity::Instance( pEdict ));
  66. pPlayer->ClearFlags();
  67. pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
  68. if ( bFrozen )
  69. pPlayer->AddEFlags( EFL_BOT_FROZEN );
  70. BotNumber++;
  71. botdata_t *pBot = &g_BotData[ pPlayer->entindex() - 1 ];
  72. pBot->m_bWasDead = false;
  73. pBot->m_WantedTeam = iTeam;
  74. pBot->m_WantedClass = iClass;
  75. pBot->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
  76. return pPlayer;
  77. }
  78. // Handler for the "bot" command.
  79. CON_COMMAND_F( "bot", "Add a bot.", FCVAR_CHEAT )
  80. {
  81. //CDODPlayer *pPlayer = CDODPlayer::Instance( UTIL_GetCommandClientIndex() );
  82. // The bot command uses switches like command-line switches.
  83. // -count <count> tells how many bots to spawn.
  84. // -team <index> selects the bot's team. Default is -1 which chooses randomly.
  85. // Note: if you do -team !, then it
  86. // -class <index> selects the bot's class. Default is -1 which chooses randomly.
  87. // -frozen prevents the bots from running around when they spawn in.
  88. // Look at -count.
  89. int count = args.FindArgInt( "-count", 1 );
  90. count = clamp( count, 1, 16 );
  91. int iTeam = 0;
  92. const char *pVal = args.FindArg( "-team" );
  93. if ( pVal )
  94. {
  95. if ( stricmp( pVal, "red" ) == 0 )
  96. iTeam = TEAM_RED;
  97. else
  98. iTeam = TEAM_BLUE;
  99. }
  100. // Look at -frozen.
  101. bool bFrozen = !!args.FindArg( "-frozen" );
  102. // Ok, spawn all the bots.
  103. while ( --count >= 0 )
  104. {
  105. // What class do they want?
  106. int iClass = RandomInt( 0, PC_LASTCLASS-1 );
  107. pVal = args.FindArg( "-class" );
  108. if ( pVal )
  109. {
  110. for ( int i=0; i < PC_LASTCLASS; i++ )
  111. {
  112. if ( stricmp( GetTFCClassInfo( i )->m_pClassName, pVal ) == 0 )
  113. {
  114. iClass = i;
  115. break;
  116. }
  117. }
  118. }
  119. BotPutInServer( bFrozen, iTeam, iClass );
  120. }
  121. }
  122. //-----------------------------------------------------------------------------
  123. // Purpose: Run through all the Bots in the game and let them think.
  124. //-----------------------------------------------------------------------------
  125. void Bot_RunAll( void )
  126. {
  127. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  128. {
  129. CTFCPlayer *pPlayer = ToTFCPlayer( UTIL_PlayerByIndex( i ) );
  130. if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
  131. {
  132. Bot_Think( pPlayer );
  133. }
  134. }
  135. }
  136. bool RunMimicCommand( CUserCmd& cmd )
  137. {
  138. if ( bot_mimic.GetInt() <= 0 )
  139. return false;
  140. if ( bot_mimic.GetInt() > gpGlobals->maxClients )
  141. return false;
  142. CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
  143. if ( !pPlayer )
  144. return false;
  145. if ( !pPlayer->GetLastUserCommand() )
  146. return false;
  147. cmd = *pPlayer->GetLastUserCommand();
  148. cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
  149. return true;
  150. }
  151. //-----------------------------------------------------------------------------
  152. // Purpose: Simulates a single frame of movement for a player
  153. // Input : *fakeclient -
  154. // *viewangles -
  155. // forwardmove -
  156. // sidemove -
  157. // upmove -
  158. // buttons -
  159. // impulse -
  160. // msec -
  161. // Output : virtual void
  162. //-----------------------------------------------------------------------------
  163. static void RunPlayerMove( CTFCPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
  164. {
  165. if ( !fakeclient )
  166. return;
  167. CUserCmd cmd;
  168. // Store off the globals.. they're gonna get whacked
  169. float flOldFrametime = gpGlobals->frametime;
  170. float flOldCurtime = gpGlobals->curtime;
  171. float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
  172. fakeclient->SetTimeBase( flTimeBase );
  173. Q_memset( &cmd, 0, sizeof( cmd ) );
  174. if ( !RunMimicCommand( cmd ) )
  175. {
  176. VectorCopy( viewangles, cmd.viewangles );
  177. cmd.forwardmove = forwardmove;
  178. cmd.sidemove = sidemove;
  179. cmd.upmove = upmove;
  180. cmd.buttons = buttons;
  181. cmd.impulse = impulse;
  182. cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
  183. }
  184. MoveHelperServer()->SetHost( fakeclient );
  185. fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
  186. // save off the last good usercmd
  187. fakeclient->SetLastUserCommand( cmd );
  188. // Clear out any fixangle that has been set
  189. fakeclient->pl.fixangle = FIXANGLE_NONE;
  190. // Restore the globals..
  191. gpGlobals->frametime = flOldFrametime;
  192. gpGlobals->curtime = flOldCurtime;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: Run this Bot's AI for one frame.
  196. //-----------------------------------------------------------------------------
  197. void Bot_Think( CTFCPlayer *pBot )
  198. {
  199. // Make sure we stay being a bot
  200. pBot->AddFlag( FL_FAKECLIENT );
  201. botdata_t *botdata = &g_BotData[ ENTINDEX( pBot->edict() ) - 1 ];
  202. QAngle vecViewAngles;
  203. float forwardmove = 0.0;
  204. float sidemove = botdata->sidemove;
  205. float upmove = 0.0;
  206. unsigned short buttons = 0;
  207. byte impulse = 0;
  208. float frametime = gpGlobals->frametime;
  209. vecViewAngles = pBot->GetLocalAngles();
  210. // Create some random values
  211. if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
  212. {
  213. pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam == TEAM_RED ? "red" : "blue" );
  214. }
  215. else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->m_Shared.GetPlayerClass() == PC_UNDEFINED )
  216. {
  217. // If they're on a team but haven't picked a class, choose a random class..
  218. pBot->HandleCommand_JoinClass( GetTFCClassInfo( botdata->m_WantedClass )->m_pClassName );
  219. }
  220. else if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
  221. {
  222. trace_t trace;
  223. botdata->m_bWasDead = false;
  224. // Stop when shot
  225. if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
  226. {
  227. if ( pBot->m_iHealth == 100 )
  228. {
  229. forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
  230. if ( botdata->sidemove != 0.0f )
  231. {
  232. forwardmove *= random->RandomFloat( 0.1, 1.0f );
  233. }
  234. }
  235. else
  236. {
  237. forwardmove = 0;
  238. }
  239. }
  240. // Only turn if I haven't been hurt
  241. if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
  242. {
  243. Vector vecEnd;
  244. Vector forward;
  245. QAngle angle;
  246. float angledelta = 15.0;
  247. int maxtries = (int)360.0/angledelta;
  248. if ( botdata->lastturntoright )
  249. {
  250. angledelta = -angledelta;
  251. }
  252. angle = pBot->GetLocalAngles();
  253. Vector vecSrc;
  254. while ( --maxtries >= 0 )
  255. {
  256. AngleVectors( angle, &forward );
  257. vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
  258. vecEnd = vecSrc + forward * 10;
  259. UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
  260. MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
  261. if ( trace.fraction == 1.0 )
  262. {
  263. if ( gpGlobals->curtime < botdata->nextturntime )
  264. {
  265. break;
  266. }
  267. }
  268. angle.y += angledelta;
  269. if ( angle.y > 180 )
  270. angle.y -= 360;
  271. else if ( angle.y < -180 )
  272. angle.y += 360;
  273. botdata->nextturntime = gpGlobals->curtime + 2.0;
  274. botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
  275. botdata->forwardAngle = angle;
  276. botdata->lastAngles = angle;
  277. }
  278. if ( gpGlobals->curtime >= botdata->nextstrafetime )
  279. {
  280. botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
  281. if ( random->RandomInt( 0, 5 ) == 0 )
  282. {
  283. botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
  284. }
  285. else
  286. {
  287. botdata->sidemove = 0;
  288. }
  289. sidemove = botdata->sidemove;
  290. if ( random->RandomInt( 0, 20 ) == 0 )
  291. {
  292. botdata->backwards = true;
  293. }
  294. else
  295. {
  296. botdata->backwards = false;
  297. }
  298. }
  299. pBot->SetLocalAngles( angle );
  300. vecViewAngles = angle;
  301. }
  302. // Is my team being forced to defend?
  303. if ( bot_defend.GetInt() == pBot->GetTeamNumber() )
  304. {
  305. buttons |= IN_ATTACK2;
  306. }
  307. // If bots are being forced to fire a weapon, see if I have it
  308. else if ( bot_forcefireweapon.GetString() )
  309. {
  310. CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
  311. if ( pWeapon )
  312. {
  313. // Switch to it if we don't have it out
  314. CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
  315. // Switch?
  316. if ( pActiveWeapon != pWeapon )
  317. {
  318. pBot->Weapon_Switch( pWeapon );
  319. }
  320. else
  321. {
  322. // Start firing
  323. // Some weapons require releases, so randomise firing
  324. if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
  325. {
  326. buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
  327. }
  328. }
  329. }
  330. }
  331. if ( bot_flipout.GetInt() )
  332. {
  333. if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
  334. {
  335. buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
  336. }
  337. }
  338. }
  339. else
  340. {
  341. // Wait for Reinforcement wave
  342. if ( !pBot->IsAlive() )
  343. {
  344. if ( botdata->m_bWasDead )
  345. {
  346. // Wait for a few seconds before respawning.
  347. if ( gpGlobals->curtime - botdata->m_flDeadTime > 3 )
  348. {
  349. // Respawn the bot
  350. buttons |= IN_JUMP;
  351. }
  352. }
  353. else
  354. {
  355. // Start a timer to respawn them in a few seconds.
  356. botdata->m_bWasDead = true;
  357. botdata->m_flDeadTime = gpGlobals->curtime;
  358. }
  359. }
  360. }
  361. if ( bot_flipout.GetInt() >= 2 )
  362. {
  363. QAngle angOffset = RandomAngle( -1, 1 );
  364. botdata->lastAngles += angOffset;
  365. for ( int i = 0 ; i < 2; i++ )
  366. {
  367. if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
  368. {
  369. if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
  370. {
  371. botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
  372. }
  373. else
  374. {
  375. botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
  376. }
  377. }
  378. }
  379. botdata->lastAngles[ 2 ] = 0;
  380. pBot->SetLocalAngles( botdata->lastAngles );
  381. }
  382. RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
  383. }