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.

636 lines
17 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //===========================================================================//
  9. #include "tier1/CommandBuffer.h"
  10. #include "tier1/utlbuffer.h"
  11. #include "tier1/strtools.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. #define MAX_ALIAS_NAME 32
  15. #define MAX_COMMAND_LENGTH 1024
  16. struct cmdalias_t
  17. {
  18. cmdalias_t *next;
  19. char name[ MAX_ALIAS_NAME ];
  20. char *value;
  21. };
  22. //-----------------------------------------------------------------------------
  23. // Constructor, destructor
  24. //-----------------------------------------------------------------------------
  25. CCommandBuffer::CCommandBuffer( ) : m_Commands( 32, 32 )
  26. {
  27. m_hNextCommand = m_Commands.InvalidIndex();
  28. m_nWaitDelayTicks = 1;
  29. m_nCurrentTick = 0;
  30. m_nLastTickToProcess = -1;
  31. m_nArgSBufferSize = 0;
  32. m_bIsProcessingCommands = false;
  33. m_nMaxArgSBufferLength = ARGS_BUFFER_LENGTH;
  34. }
  35. CCommandBuffer::~CCommandBuffer()
  36. {
  37. }
  38. //-----------------------------------------------------------------------------
  39. // Indicates how long to delay when encoutering a 'wait' command
  40. //-----------------------------------------------------------------------------
  41. void CCommandBuffer::SetWaitDelayTime( int nTickDelay )
  42. {
  43. Assert( nTickDelay >= 0 );
  44. m_nWaitDelayTicks = nTickDelay;
  45. }
  46. //-----------------------------------------------------------------------------
  47. // Specifies a max limit of the args buffer. For unittesting. Size == 0 means use default
  48. //-----------------------------------------------------------------------------
  49. void CCommandBuffer::LimitArgumentBufferSize( int nSize )
  50. {
  51. if ( nSize > ARGS_BUFFER_LENGTH )
  52. {
  53. nSize = ARGS_BUFFER_LENGTH;
  54. }
  55. m_nMaxArgSBufferLength = ( nSize == 0 ) ? ARGS_BUFFER_LENGTH : nSize;
  56. }
  57. //-----------------------------------------------------------------------------
  58. // Parses argv0 out of the buffer
  59. //-----------------------------------------------------------------------------
  60. bool CCommandBuffer::ParseArgV0( CUtlBuffer &buf, char *pArgV0, int nMaxLen, const char **pArgS )
  61. {
  62. pArgV0[0] = 0;
  63. *pArgS = NULL;
  64. if ( !buf.IsValid() )
  65. return false;
  66. int nSize = buf.ParseToken( CCommand::DefaultBreakSet(), pArgV0, nMaxLen );
  67. if ( ( nSize <= 0 ) || ( nMaxLen == nSize ) )
  68. return false;
  69. int nArgSLen = buf.TellMaxPut() - buf.TellGet();
  70. *pArgS = (nArgSLen > 0) ? (const char*)buf.PeekGet() : NULL;
  71. return true;
  72. }
  73. //-----------------------------------------------------------------------------
  74. // Insert a command into the command queue
  75. //-----------------------------------------------------------------------------
  76. void CCommandBuffer::InsertCommandAtAppropriateTime( int hCommand )
  77. {
  78. int i;
  79. Command_t &command = m_Commands[hCommand];
  80. for ( i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
  81. {
  82. if ( m_Commands[i].m_nTick > command.m_nTick )
  83. break;
  84. }
  85. m_Commands.LinkBefore( i, hCommand );
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Insert a command into the command queue at the appropriate time
  89. //-----------------------------------------------------------------------------
  90. void CCommandBuffer::InsertImmediateCommand( int hCommand )
  91. {
  92. m_Commands.LinkBefore( m_hNextCommand, hCommand );
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Insert a command into the command queue
  96. //-----------------------------------------------------------------------------
  97. bool CCommandBuffer::InsertCommand( const char *pArgS, int nCommandSize, int nTick )
  98. {
  99. if ( nCommandSize >= CCommand::MaxCommandLength() )
  100. {
  101. Warning( "WARNING: Command too long... ignoring!\n%s\n", pArgS );
  102. return false;
  103. }
  104. // Add one for null termination
  105. if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength )
  106. {
  107. Compact();
  108. if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength )
  109. return false;
  110. }
  111. memcpy( &m_pArgSBuffer[m_nArgSBufferSize], pArgS, nCommandSize );
  112. m_pArgSBuffer[m_nArgSBufferSize + nCommandSize] = 0;
  113. ++nCommandSize;
  114. int hCommand = m_Commands.Alloc();
  115. Command_t &command = m_Commands[hCommand];
  116. command.m_nTick = nTick;
  117. command.m_nFirstArgS = m_nArgSBufferSize;
  118. command.m_nBufferSize = nCommandSize;
  119. m_nArgSBufferSize += nCommandSize;
  120. if ( !m_bIsProcessingCommands || ( nTick > m_nCurrentTick ) )
  121. {
  122. InsertCommandAtAppropriateTime( hCommand );
  123. }
  124. else
  125. {
  126. InsertImmediateCommand( hCommand );
  127. }
  128. return true;
  129. }
  130. //-----------------------------------------------------------------------------
  131. // Returns the length of the next command
  132. //-----------------------------------------------------------------------------
  133. void CCommandBuffer::GetNextCommandLength( const char *pText, int nMaxLen, int *pCommandLength, int *pNextCommandOffset )
  134. {
  135. int nCommandLength = 0;
  136. int nNextCommandOffset;
  137. bool bIsQuoted = false;
  138. bool bIsCommented = false;
  139. for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 )
  140. {
  141. char c = pText[nNextCommandOffset];
  142. if ( !bIsCommented )
  143. {
  144. if ( c == '"' )
  145. {
  146. bIsQuoted = !bIsQuoted;
  147. continue;
  148. }
  149. // don't break if inside a C++ style comment
  150. if ( !bIsQuoted && c == '/' )
  151. {
  152. bIsCommented = ( nNextCommandOffset < nMaxLen-1 ) && pText[nNextCommandOffset+1] == '/';
  153. if ( bIsCommented )
  154. {
  155. ++nNextCommandOffset;
  156. continue;
  157. }
  158. }
  159. // don't break if inside a quoted string
  160. if ( !bIsQuoted && c == ';' )
  161. break;
  162. }
  163. // FIXME: This is legacy behavior; should we not break if a \n is inside a quoted string?
  164. if ( c == '\n' )
  165. break;
  166. }
  167. *pCommandLength = nCommandLength;
  168. *pNextCommandOffset = nNextCommandOffset;
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Add text to command buffer, return false if it couldn't owing to overflow
  172. //-----------------------------------------------------------------------------
  173. bool CCommandBuffer::AddText( const char *pText, int nTickDelay )
  174. {
  175. Assert( nTickDelay >= 0 );
  176. int nLen = Q_strlen( pText );
  177. int nTick = m_nCurrentTick + nTickDelay;
  178. // Parse the text into distinct commands
  179. const char *pCurrentCommand = pText;
  180. int nOffsetToNextCommand;
  181. for( ; nLen > 0; nLen -= nOffsetToNextCommand+1, pCurrentCommand += nOffsetToNextCommand+1 )
  182. {
  183. // find a \n or ; line break
  184. int nCommandLength;
  185. GetNextCommandLength( pCurrentCommand, nLen, &nCommandLength, &nOffsetToNextCommand );
  186. if ( nCommandLength <= 0 )
  187. continue;
  188. const char *pArgS;
  189. char *pArgV0 = (char*)_alloca( nCommandLength+1 );
  190. CUtlBuffer bufParse( pCurrentCommand, nCommandLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
  191. ParseArgV0( bufParse, pArgV0, nCommandLength+1, &pArgS );
  192. if ( pArgV0[0] == 0 )
  193. continue;
  194. // Deal with the special 'wait' command
  195. if ( !Q_stricmp( pArgV0, "wait" ) && IsWaitEnabled() )
  196. {
  197. int nDelay = pArgS ? atoi( pArgS ) : m_nWaitDelayTicks;
  198. nTick += nDelay;
  199. continue;
  200. }
  201. if ( !InsertCommand( pCurrentCommand, nCommandLength, nTick ) )
  202. return false;
  203. }
  204. return true;
  205. }
  206. //-----------------------------------------------------------------------------
  207. // Are we in the middle of processing commands?
  208. //-----------------------------------------------------------------------------
  209. bool CCommandBuffer::IsProcessingCommands()
  210. {
  211. return m_bIsProcessingCommands;
  212. }
  213. //-----------------------------------------------------------------------------
  214. // Delays all queued commands to execute at a later time
  215. //-----------------------------------------------------------------------------
  216. void CCommandBuffer::DelayAllQueuedCommands( int nDelay )
  217. {
  218. if ( nDelay <= 0 )
  219. return;
  220. for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
  221. {
  222. m_Commands[i].m_nTick += nDelay;
  223. }
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Call this to begin iterating over all commands up to flCurrentTime
  227. //-----------------------------------------------------------------------------
  228. void CCommandBuffer::BeginProcessingCommands( int nDeltaTicks )
  229. {
  230. if ( nDeltaTicks == 0 )
  231. return;
  232. Assert( !m_bIsProcessingCommands );
  233. m_bIsProcessingCommands = true;
  234. m_nLastTickToProcess = m_nCurrentTick + nDeltaTicks - 1;
  235. // Necessary to insert commands while commands are being processed
  236. m_hNextCommand = m_Commands.Head();
  237. }
  238. //-----------------------------------------------------------------------------
  239. // Returns the next command
  240. //-----------------------------------------------------------------------------
  241. bool CCommandBuffer::DequeueNextCommand( )
  242. {
  243. m_CurrentCommand.Reset();
  244. Assert( m_bIsProcessingCommands );
  245. if ( m_Commands.Count() == 0 )
  246. return false;
  247. int nHead = m_Commands.Head();
  248. Command_t &command = m_Commands[ nHead ];
  249. if ( command.m_nTick > m_nLastTickToProcess )
  250. return false;
  251. m_nCurrentTick = command.m_nTick;
  252. // Copy the current command into a temp buffer
  253. // NOTE: This is here to avoid the pointers returned by DequeueNextCommand
  254. // to become invalid by calling AddText. Is there a way we can avoid the memcpy?
  255. if ( command.m_nBufferSize > 0 )
  256. {
  257. m_CurrentCommand.Tokenize( &m_pArgSBuffer[command.m_nFirstArgS] );
  258. }
  259. m_Commands.Remove( nHead );
  260. // Necessary to insert commands while commands are being processed
  261. m_hNextCommand = m_Commands.Head();
  262. // Msg("Dequeue : ");
  263. // for ( int i = 0; i < nArgc; ++i )
  264. // {
  265. // Msg("%s ", m_pCurrentArgv[i] );
  266. // }
  267. // Msg("\n");
  268. return true;
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Returns the next command
  272. //-----------------------------------------------------------------------------
  273. int CCommandBuffer::DequeueNextCommand( const char **& ppArgv )
  274. {
  275. DequeueNextCommand();
  276. ppArgv = ArgV();
  277. return ArgC();
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Compacts the command buffer
  281. //-----------------------------------------------------------------------------
  282. void CCommandBuffer::Compact()
  283. {
  284. // Compress argvbuffer + argv
  285. // NOTE: I'm using this choice instead of calling malloc + free
  286. // per command to allocate arguments because I expect to post a
  287. // bunch of commands but not have many delayed commands;
  288. // avoiding the allocation cost seems more important that the memcpy
  289. // cost here since I expect to not have much to copy.
  290. m_nArgSBufferSize = 0;
  291. char pTempBuffer[ ARGS_BUFFER_LENGTH ];
  292. for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
  293. {
  294. Command_t &command = m_Commands[ i ];
  295. memcpy( &pTempBuffer[m_nArgSBufferSize], &m_pArgSBuffer[command.m_nFirstArgS], command.m_nBufferSize );
  296. command.m_nFirstArgS = m_nArgSBufferSize;
  297. m_nArgSBufferSize += command.m_nBufferSize;
  298. }
  299. // NOTE: We could also store 2 buffers in the command buffer and switch
  300. // between the two to avoid the 2nd memcpy; but again I'm guessing the memory
  301. // tradeoff isn't worth it
  302. memcpy( m_pArgSBuffer, pTempBuffer, m_nArgSBufferSize );
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Call this to finish iterating over all commands
  306. //-----------------------------------------------------------------------------
  307. void CCommandBuffer::EndProcessingCommands()
  308. {
  309. Assert( m_bIsProcessingCommands );
  310. m_bIsProcessingCommands = false;
  311. m_nCurrentTick = m_nLastTickToProcess + 1;
  312. m_hNextCommand = m_Commands.InvalidIndex();
  313. // Extract commands that are before the end time
  314. // NOTE: This is a bug for this to
  315. int i = m_Commands.Head();
  316. if ( i == m_Commands.InvalidIndex() )
  317. {
  318. m_nArgSBufferSize = 0;
  319. return;
  320. }
  321. while ( i != m_Commands.InvalidIndex() )
  322. {
  323. if ( m_Commands[i].m_nTick >= m_nCurrentTick )
  324. break;
  325. AssertMsgOnce( false, "CCommandBuffer::EndProcessingCommands() called before all appropriate commands were dequeued.\n" );
  326. int nNext = i;
  327. Msg( "Warning: Skipping command %s\n", &m_pArgSBuffer[ m_Commands[i].m_nFirstArgS ] );
  328. m_Commands.Remove( i );
  329. i = nNext;
  330. }
  331. Compact();
  332. }
  333. //-----------------------------------------------------------------------------
  334. // Returns a handle to the next command to process
  335. //-----------------------------------------------------------------------------
  336. CommandHandle_t CCommandBuffer::GetNextCommandHandle()
  337. {
  338. Assert( m_bIsProcessingCommands );
  339. return m_Commands.Head();
  340. }
  341. #if 0
  342. /*
  343. ===============
  344. Cmd_Alias_f
  345. Creates a new command that executes a command string (possibly ; seperated)
  346. ===============
  347. */
  348. void Cmd_Alias_f (void)
  349. {
  350. cmdalias_t *a;
  351. char cmd[MAX_COMMAND_LENGTH];
  352. int i, c;
  353. char *s;
  354. if (Cmd_Argc() == 1)
  355. {
  356. Con_Printf ("Current alias commands:\n");
  357. for (a = cmd_alias ; a ; a=a->next)
  358. Con_Printf ("%s : %s\n", a->name, a->value);
  359. return;
  360. }
  361. s = Cmd_Argv(1);
  362. if (strlen(s) >= MAX_ALIAS_NAME)
  363. {
  364. Con_Printf ("Alias name is too long\n");
  365. return;
  366. }
  367. // copy the rest of the command line
  368. cmd[0] = 0; // start out with a null string
  369. c = Cmd_Argc();
  370. for (i=2 ; i< c ; i++)
  371. {
  372. Q_strncat(cmd, Cmd_Argv(i), sizeof( cmd ), COPY_ALL_CHARACTERS);
  373. if (i != c)
  374. {
  375. Q_strncat (cmd, " ", sizeof( cmd ), COPY_ALL_CHARACTERS );
  376. }
  377. }
  378. Q_strncat (cmd, "\n", sizeof( cmd ), COPY_ALL_CHARACTERS);
  379. // if the alias already exists, reuse it
  380. for (a = cmd_alias ; a ; a=a->next)
  381. {
  382. if (!strcmp(s, a->name))
  383. {
  384. if ( !strcmp( a->value, cmd ) ) // Re-alias the same thing
  385. return;
  386. delete[] a->value;
  387. break;
  388. }
  389. }
  390. if (!a)
  391. {
  392. a = (cmdalias_t *)new cmdalias_t;
  393. a->next = cmd_alias;
  394. cmd_alias = a;
  395. }
  396. Q_strncpy (a->name, s, sizeof( a->name ) );
  397. a->value = COM_StringCopy(cmd);
  398. }
  399. /*
  400. =============================================================================
  401. COMMAND EXECUTION
  402. =============================================================================
  403. */
  404. #define MAX_ARGS 80
  405. static int cmd_argc;
  406. static char *cmd_argv[MAX_ARGS];
  407. static char *cmd_null_string = "";
  408. static const char *cmd_args = NULL;
  409. cmd_source_t cmd_source;
  410. //-----------------------------------------------------------------------------
  411. // Purpose:
  412. // Output : void Cmd_Init
  413. //-----------------------------------------------------------------------------
  414. //-----------------------------------------------------------------------------
  415. // Purpose:
  416. //-----------------------------------------------------------------------------
  417. void Cmd_Shutdown( void )
  418. {
  419. // TODO, cleanup
  420. while ( cmd_alias )
  421. {
  422. cmdalias_t *next = cmd_alias->next;
  423. delete cmd_alias->value; // created by StringCopy()
  424. delete cmd_alias;
  425. cmd_alias = next;
  426. }
  427. }
  428. /*
  429. ============
  430. Cmd_ExecuteString
  431. A complete command line has been parsed, so try to execute it
  432. FIXME: lookupnoadd the token to speed search?
  433. ============
  434. */
  435. const ConCommandBase *Cmd_ExecuteString (const char *text, cmd_source_t src)
  436. {
  437. cmdalias_t *a;
  438. cmd_source = src;
  439. Cmd_TokenizeString (text);
  440. // execute the command line
  441. if (!Cmd_Argc())
  442. return NULL; // no tokens
  443. // check alias
  444. for (a=cmd_alias ; a ; a=a->next)
  445. {
  446. if (!Q_strcasecmp (cmd_argv[0], a->name))
  447. {
  448. Cbuf_InsertText (a->value);
  449. return NULL;
  450. }
  451. }
  452. // check ConCommands
  453. ConCommandBase const *pCommand = ConCommandBase::FindCommand( cmd_argv[ 0 ] );
  454. if ( pCommand && pCommand->IsCommand() )
  455. {
  456. bool isServerCommand = ( pCommand->IsBitSet( FCVAR_GAMEDLL ) &&
  457. // Typed at console
  458. cmd_source == src_command &&
  459. // Not HLDS
  460. !sv.IsDedicated() );
  461. // Hook to allow game .dll to figure out who type the message on a listen server
  462. if ( serverGameClients )
  463. {
  464. // We're actually the server, so set it up locally
  465. if ( sv.IsActive() )
  466. {
  467. g_pServerPluginHandler->SetCommandClient( -1 );
  468. #ifndef SWDS
  469. // Special processing for listen server player
  470. if ( isServerCommand )
  471. {
  472. g_pServerPluginHandler->SetCommandClient( cl.m_nPlayerSlot );
  473. }
  474. #endif
  475. }
  476. // We're not the server, but we've been a listen server (game .dll loaded)
  477. // forward this command tot he server instead of running it locally if we're still
  478. // connected
  479. // Otherwise, things like "say" won't work unless you quit and restart
  480. else if ( isServerCommand )
  481. {
  482. if ( cl.IsConnected() )
  483. {
  484. Cmd_ForwardToServer();
  485. return NULL;
  486. }
  487. else
  488. {
  489. // It's a server command, but we're not connected to a server. Don't try to execute it.
  490. return NULL;
  491. }
  492. }
  493. }
  494. // Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on
  495. #ifndef _DEBUG
  496. if ( pCommand->IsBitSet( FCVAR_CHEAT ) )
  497. {
  498. if ( !Host_IsSinglePlayerGame() && sv_cheats.GetInt() == 0 )
  499. {
  500. Msg( "Can't use cheat command %s in multiplayer, unless the server has sv_cheats set to 1.\n", pCommand->GetName() );
  501. return NULL;
  502. }
  503. }
  504. #endif
  505. (( ConCommand * )pCommand )->Dispatch();
  506. return pCommand;
  507. }
  508. // check cvars
  509. if ( cv->IsCommand() )
  510. {
  511. return pCommand;
  512. }
  513. // forward the command line to the server, so the entity DLL can parse it
  514. if ( cmd_source == src_command )
  515. {
  516. if ( cl.IsConnected() )
  517. {
  518. Cmd_ForwardToServer();
  519. return NULL;
  520. }
  521. }
  522. Msg("Unknown command \"%s\"\n", Cmd_Argv(0));
  523. return NULL;
  524. }
  525. #endif