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.

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