mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2025-09-19 20:16:10 +08:00
637 lines
18 KiB
C++
637 lines
18 KiB
C++
//===== Copyright <20> 1996-2006, Valve Corporation, All rights reserved. ======//
|
||
//
|
||
// Purpose:
|
||
//
|
||
// $Workfile: $
|
||
// $Date: $
|
||
// $NoKeywords: $
|
||
//===========================================================================//
|
||
|
||
#include "tier1/CommandBuffer.h"
|
||
#include "tier1/utlbuffer.h"
|
||
#include "tier1/strtools.h"
|
||
|
||
// memdbgon must be the last include file in a .cpp file!!!
|
||
#include "tier0/memdbgon.h"
|
||
|
||
#define MAX_ALIAS_NAME 32
|
||
#define MAX_COMMAND_LENGTH 1024
|
||
|
||
struct cmdalias_t
|
||
{
|
||
cmdalias_t *next;
|
||
char name[ MAX_ALIAS_NAME ];
|
||
char *value;
|
||
};
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Constructor, destructor
|
||
//-----------------------------------------------------------------------------
|
||
CCommandBuffer::CCommandBuffer( ) : m_Commands( 32, 32 )
|
||
{
|
||
m_hNextCommand = m_Commands.InvalidIndex();
|
||
m_nWaitDelayTicks = 1;
|
||
m_nCurrentTick = 0;
|
||
m_nLastTickToProcess = -1;
|
||
m_nArgSBufferSize = 0;
|
||
m_bIsProcessingCommands = false;
|
||
m_nMaxArgSBufferLength = ARGS_BUFFER_LENGTH;
|
||
}
|
||
|
||
CCommandBuffer::~CCommandBuffer()
|
||
{
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Indicates how long to delay when encoutering a 'wait' command
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::SetWaitDelayTime( int nTickDelay )
|
||
{
|
||
Assert( nTickDelay >= 0 );
|
||
m_nWaitDelayTicks = nTickDelay;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Specifies a max limit of the args buffer. For unittesting. Size == 0 means use default
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::LimitArgumentBufferSize( int nSize )
|
||
{
|
||
if ( nSize > ARGS_BUFFER_LENGTH )
|
||
{
|
||
nSize = ARGS_BUFFER_LENGTH;
|
||
}
|
||
|
||
m_nMaxArgSBufferLength = ( nSize == 0 ) ? ARGS_BUFFER_LENGTH : nSize;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Parses argv0 out of the buffer
|
||
//-----------------------------------------------------------------------------
|
||
bool CCommandBuffer::ParseArgV0( CUtlBuffer &buf, char *pArgV0, int nMaxLen, const char **pArgS )
|
||
{
|
||
pArgV0[0] = 0;
|
||
*pArgS = NULL;
|
||
|
||
if ( !buf.IsValid() )
|
||
return false;
|
||
|
||
int nSize = buf.ParseToken( CCommand::DefaultBreakSet(), pArgV0, nMaxLen );
|
||
if ( ( nSize <= 0 ) || ( nMaxLen == nSize ) )
|
||
return false;
|
||
|
||
int nArgSLen = buf.TellMaxPut() - buf.TellGet();
|
||
*pArgS = (nArgSLen > 0) ? (const char*)buf.PeekGet() : NULL;
|
||
return true;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Insert a command into the command queue
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::InsertCommandAtAppropriateTime( int hCommand )
|
||
{
|
||
int i;
|
||
Command_t &command = m_Commands[hCommand];
|
||
for ( i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
|
||
{
|
||
if ( m_Commands[i].m_nTick > command.m_nTick )
|
||
break;
|
||
}
|
||
m_Commands.LinkBefore( i, hCommand );
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Insert a command into the command queue at the appropriate time
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::InsertImmediateCommand( int hCommand )
|
||
{
|
||
m_Commands.LinkBefore( m_hNextCommand, hCommand );
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Insert a command into the command queue
|
||
//-----------------------------------------------------------------------------
|
||
bool CCommandBuffer::InsertCommand( const char *pArgS, int nCommandSize, int nTick )
|
||
{
|
||
if ( nCommandSize >= CCommand::MaxCommandLength() )
|
||
{
|
||
Warning( "WARNING: Command too long... ignoring!\n%s\n", pArgS );
|
||
return false;
|
||
}
|
||
|
||
// Add one for null termination
|
||
if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength )
|
||
{
|
||
Compact();
|
||
if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength )
|
||
return false;
|
||
}
|
||
|
||
memcpy( &m_pArgSBuffer[m_nArgSBufferSize], pArgS, nCommandSize );
|
||
m_pArgSBuffer[m_nArgSBufferSize + nCommandSize] = 0;
|
||
++nCommandSize;
|
||
|
||
int hCommand = m_Commands.Alloc();
|
||
Command_t &command = m_Commands[hCommand];
|
||
command.m_nTick = nTick;
|
||
command.m_nFirstArgS = m_nArgSBufferSize;
|
||
command.m_nBufferSize = nCommandSize;
|
||
|
||
m_nArgSBufferSize += nCommandSize;
|
||
|
||
if ( !m_bIsProcessingCommands || ( nTick > m_nCurrentTick ) )
|
||
{
|
||
InsertCommandAtAppropriateTime( hCommand );
|
||
}
|
||
else
|
||
{
|
||
InsertImmediateCommand( hCommand );
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Returns the length of the next command
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::GetNextCommandLength( const char *pText, int nMaxLen, int *pCommandLength, int *pNextCommandOffset )
|
||
{
|
||
int nCommandLength = 0;
|
||
int nNextCommandOffset;
|
||
bool bIsQuoted = false;
|
||
bool bIsCommented = false;
|
||
for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 )
|
||
{
|
||
char c = pText[nNextCommandOffset];
|
||
if ( !bIsCommented )
|
||
{
|
||
if ( c == '"' )
|
||
{
|
||
bIsQuoted = !bIsQuoted;
|
||
continue;
|
||
}
|
||
|
||
// don't break if inside a C++ style comment
|
||
if ( !bIsQuoted && c == '/' )
|
||
{
|
||
bIsCommented = ( nNextCommandOffset < nMaxLen-1 ) && pText[nNextCommandOffset+1] == '/';
|
||
if ( bIsCommented )
|
||
{
|
||
++nNextCommandOffset;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// don't break if inside a quoted string
|
||
if ( !bIsQuoted && c == ';' )
|
||
break;
|
||
}
|
||
|
||
// FIXME: This is legacy behavior; should we not break if a \n is inside a quoted string?
|
||
if ( c == '\n' )
|
||
break;
|
||
}
|
||
|
||
*pCommandLength = nCommandLength;
|
||
*pNextCommandOffset = nNextCommandOffset;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Add text to command buffer, return false if it couldn't owing to overflow
|
||
//-----------------------------------------------------------------------------
|
||
bool CCommandBuffer::AddText( const char *pText, int nTickDelay )
|
||
{
|
||
Assert( nTickDelay >= 0 );
|
||
|
||
int nLen = Q_strlen( pText );
|
||
int nTick = m_nCurrentTick + nTickDelay;
|
||
|
||
// Parse the text into distinct commands
|
||
const char *pCurrentCommand = pText;
|
||
int nOffsetToNextCommand;
|
||
for( ; nLen > 0; nLen -= nOffsetToNextCommand+1, pCurrentCommand += nOffsetToNextCommand+1 )
|
||
{
|
||
// find a \n or ; line break
|
||
int nCommandLength;
|
||
GetNextCommandLength( pCurrentCommand, nLen, &nCommandLength, &nOffsetToNextCommand );
|
||
if ( nCommandLength <= 0 )
|
||
continue;
|
||
|
||
const char *pArgS;
|
||
char *pArgV0 = (char*)_alloca( nCommandLength+1 );
|
||
CUtlBuffer bufParse( pCurrentCommand, nCommandLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
|
||
ParseArgV0( bufParse, pArgV0, nCommandLength+1, &pArgS );
|
||
if ( pArgV0[0] == 0 )
|
||
continue;
|
||
|
||
// Deal with the special 'wait' command
|
||
if ( !Q_stricmp( pArgV0, "wait" ) )
|
||
{
|
||
int nDelay = pArgS ? atoi( pArgS ) : m_nWaitDelayTicks;
|
||
nTick += nDelay;
|
||
continue;
|
||
}
|
||
|
||
if ( !InsertCommand( pCurrentCommand, nCommandLength, nTick ) )
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Are we in the middle of processing commands?
|
||
//-----------------------------------------------------------------------------
|
||
bool CCommandBuffer::IsProcessingCommands()
|
||
{
|
||
return m_bIsProcessingCommands;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Delays all queued commands to execute at a later time
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::DelayAllQueuedCommands( int nDelay )
|
||
{
|
||
if ( nDelay <= 0 )
|
||
return;
|
||
|
||
for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
|
||
{
|
||
m_Commands[i].m_nTick += nDelay;
|
||
}
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Call this to begin iterating over all commands up to flCurrentTime
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::BeginProcessingCommands( int nDeltaTicks )
|
||
{
|
||
if ( nDeltaTicks == 0 )
|
||
return;
|
||
|
||
Assert( !m_bIsProcessingCommands );
|
||
m_bIsProcessingCommands = true;
|
||
m_nLastTickToProcess = m_nCurrentTick + nDeltaTicks - 1;
|
||
|
||
// Necessary to insert commands while commands are being processed
|
||
m_hNextCommand = m_Commands.Head();
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Returns the next command
|
||
//-----------------------------------------------------------------------------
|
||
bool CCommandBuffer::DequeueNextCommand( )
|
||
{
|
||
m_CurrentCommand.Reset();
|
||
|
||
Assert( m_bIsProcessingCommands );
|
||
if ( m_Commands.Count() == 0 )
|
||
return false;
|
||
|
||
int nHead = m_Commands.Head();
|
||
Command_t &command = m_Commands[ nHead ];
|
||
if ( command.m_nTick > m_nLastTickToProcess )
|
||
return false;
|
||
|
||
m_nCurrentTick = command.m_nTick;
|
||
|
||
// Copy the current command into a temp buffer
|
||
// NOTE: This is here to avoid the pointers returned by DequeueNextCommand
|
||
// to become invalid by calling AddText. Is there a way we can avoid the memcpy?
|
||
if ( command.m_nBufferSize > 0 )
|
||
{
|
||
m_CurrentCommand.Tokenize( &m_pArgSBuffer[command.m_nFirstArgS] );
|
||
}
|
||
|
||
m_Commands.Remove( nHead );
|
||
|
||
// Necessary to insert commands while commands are being processed
|
||
m_hNextCommand = m_Commands.Head();
|
||
|
||
// Msg("Dequeue : ");
|
||
// for ( int i = 0; i < nArgc; ++i )
|
||
// {
|
||
// Msg("%s ", m_pCurrentArgv[i] );
|
||
// }
|
||
// Msg("\n");
|
||
return true;
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Returns the next command
|
||
//-----------------------------------------------------------------------------
|
||
int CCommandBuffer::DequeueNextCommand( const char **& ppArgv )
|
||
{
|
||
DequeueNextCommand();
|
||
ppArgv = ArgV();
|
||
return ArgC();
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Compacts the command buffer
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::Compact()
|
||
{
|
||
// Compress argvbuffer + argv
|
||
// NOTE: I'm using this choice instead of calling malloc + free
|
||
// per command to allocate arguments because I expect to post a
|
||
// bunch of commands but not have many delayed commands;
|
||
// avoiding the allocation cost seems more important that the memcpy
|
||
// cost here since I expect to not have much to copy.
|
||
m_nArgSBufferSize = 0;
|
||
|
||
char pTempBuffer[ ARGS_BUFFER_LENGTH ];
|
||
for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) )
|
||
{
|
||
Command_t &command = m_Commands[ i ];
|
||
|
||
memcpy( &pTempBuffer[m_nArgSBufferSize], &m_pArgSBuffer[command.m_nFirstArgS], command.m_nBufferSize );
|
||
command.m_nFirstArgS = m_nArgSBufferSize;
|
||
m_nArgSBufferSize += command.m_nBufferSize;
|
||
}
|
||
|
||
// NOTE: We could also store 2 buffers in the command buffer and switch
|
||
// between the two to avoid the 2nd memcpy; but again I'm guessing the memory
|
||
// tradeoff isn't worth it
|
||
memcpy( m_pArgSBuffer, pTempBuffer, m_nArgSBufferSize );
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Call this to finish iterating over all commands
|
||
//-----------------------------------------------------------------------------
|
||
void CCommandBuffer::EndProcessingCommands()
|
||
{
|
||
Assert( m_bIsProcessingCommands );
|
||
m_bIsProcessingCommands = false;
|
||
m_nCurrentTick = m_nLastTickToProcess + 1;
|
||
m_hNextCommand = m_Commands.InvalidIndex();
|
||
|
||
// Extract commands that are before the end time
|
||
// NOTE: This is a bug for this to
|
||
int i = m_Commands.Head();
|
||
if ( i == m_Commands.InvalidIndex() )
|
||
{
|
||
m_nArgSBufferSize = 0;
|
||
return;
|
||
}
|
||
|
||
while ( i != m_Commands.InvalidIndex() )
|
||
{
|
||
if ( m_Commands[i].m_nTick >= m_nCurrentTick )
|
||
break;
|
||
|
||
AssertMsgOnce( false, "CCommandBuffer::EndProcessingCommands() called before all appropriate commands were dequeued.\n" );
|
||
int nNext = i;
|
||
Msg( "Warning: Skipping command %s\n", m_pArgSBuffer[ m_Commands[i].m_nFirstArgS ] );
|
||
m_Commands.Remove( i );
|
||
i = nNext;
|
||
}
|
||
|
||
Compact();
|
||
}
|
||
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Returns a handle to the next command to process
|
||
//-----------------------------------------------------------------------------
|
||
CommandHandle_t CCommandBuffer::GetNextCommandHandle()
|
||
{
|
||
Assert( m_bIsProcessingCommands );
|
||
return m_Commands.Head();
|
||
}
|
||
|
||
|
||
#if 0
|
||
/*
|
||
===============
|
||
Cmd_Alias_f
|
||
|
||
Creates a new command that executes a command string (possibly ; seperated)
|
||
===============
|
||
*/
|
||
void Cmd_Alias_f (void)
|
||
{
|
||
cmdalias_t *a;
|
||
char cmd[MAX_COMMAND_LENGTH];
|
||
int i, c;
|
||
char *s;
|
||
|
||
if (Cmd_Argc() == 1)
|
||
{
|
||
Con_Printf ("Current alias commands:\n");
|
||
for (a = cmd_alias ; a ; a=a->next)
|
||
Con_Printf ("%s : %s\n", a->name, a->value);
|
||
return;
|
||
}
|
||
|
||
s = Cmd_Argv(1);
|
||
if (strlen(s) >= MAX_ALIAS_NAME)
|
||
{
|
||
Con_Printf ("Alias name is too long\n");
|
||
return;
|
||
}
|
||
|
||
// copy the rest of the command line
|
||
cmd[0] = 0; // start out with a null string
|
||
c = Cmd_Argc();
|
||
for (i=2 ; i< c ; i++)
|
||
{
|
||
Q_strncat(cmd, Cmd_Argv(i), sizeof( cmd ), COPY_ALL_CHARACTERS);
|
||
if (i != c)
|
||
{
|
||
Q_strncat (cmd, " ", sizeof( cmd ), COPY_ALL_CHARACTERS );
|
||
}
|
||
}
|
||
Q_strncat (cmd, "\n", sizeof( cmd ), COPY_ALL_CHARACTERS);
|
||
|
||
// if the alias already exists, reuse it
|
||
for (a = cmd_alias ; a ; a=a->next)
|
||
{
|
||
if (!strcmp(s, a->name))
|
||
{
|
||
if ( !strcmp( a->value, cmd ) ) // Re-alias the same thing
|
||
return;
|
||
|
||
delete[] a->value;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!a)
|
||
{
|
||
a = (cmdalias_t *)new cmdalias_t;
|
||
a->next = cmd_alias;
|
||
cmd_alias = a;
|
||
}
|
||
Q_strncpy (a->name, s, sizeof( a->name ) );
|
||
|
||
a->value = COM_StringCopy(cmd);
|
||
}
|
||
|
||
|
||
|
||
/*
|
||
=============================================================================
|
||
|
||
COMMAND EXECUTION
|
||
|
||
=============================================================================
|
||
*/
|
||
|
||
#define MAX_ARGS 80
|
||
|
||
static int cmd_argc;
|
||
static char *cmd_argv[MAX_ARGS];
|
||
static char *cmd_null_string = "";
|
||
static const char *cmd_args = NULL;
|
||
|
||
cmd_source_t cmd_source;
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Purpose:
|
||
// Output : void Cmd_Init
|
||
//-----------------------------------------------------------------------------
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// Purpose:
|
||
//-----------------------------------------------------------------------------
|
||
void Cmd_Shutdown( void )
|
||
{
|
||
// TODO, cleanup
|
||
while ( cmd_alias )
|
||
{
|
||
cmdalias_t *next = cmd_alias->next;
|
||
delete cmd_alias->value; // created by StringCopy()
|
||
delete cmd_alias;
|
||
cmd_alias = next;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/*
|
||
============
|
||
Cmd_ExecuteString
|
||
|
||
A complete command line has been parsed, so try to execute it
|
||
FIXME: lookupnoadd the token to speed search?
|
||
============
|
||
*/
|
||
const ConCommandBase *Cmd_ExecuteString (const char *text, cmd_source_t src)
|
||
{
|
||
cmdalias_t *a;
|
||
|
||
cmd_source = src;
|
||
Cmd_TokenizeString (text);
|
||
|
||
// execute the command line
|
||
if (!Cmd_Argc())
|
||
return NULL; // no tokens
|
||
|
||
// check alias
|
||
for (a=cmd_alias ; a ; a=a->next)
|
||
{
|
||
if (!Q_strcasecmp (cmd_argv[0], a->name))
|
||
{
|
||
Cbuf_InsertText (a->value);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
// check ConCommands
|
||
ConCommandBase const *pCommand = ConCommandBase::FindCommand( cmd_argv[ 0 ] );
|
||
if ( pCommand && pCommand->IsCommand() )
|
||
{
|
||
bool isServerCommand = ( pCommand->IsBitSet( FCVAR_GAMEDLL ) &&
|
||
// Typed at console
|
||
cmd_source == src_command &&
|
||
// Not HLDS
|
||
!sv.IsDedicated() );
|
||
|
||
// Hook to allow game .dll to figure out who type the message on a listen server
|
||
if ( serverGameClients )
|
||
{
|
||
// We're actually the server, so set it up locally
|
||
if ( sv.IsActive() )
|
||
{
|
||
g_pServerPluginHandler->SetCommandClient( -1 );
|
||
|
||
#ifndef SWDS
|
||
// Special processing for listen server player
|
||
if ( isServerCommand )
|
||
{
|
||
g_pServerPluginHandler->SetCommandClient( cl.m_nPlayerSlot );
|
||
}
|
||
#endif
|
||
}
|
||
// We're not the server, but we've been a listen server (game .dll loaded)
|
||
// forward this command tot he server instead of running it locally if we're still
|
||
// connected
|
||
// Otherwise, things like "say" won't work unless you quit and restart
|
||
else if ( isServerCommand )
|
||
{
|
||
if ( cl.IsConnected() )
|
||
{
|
||
Cmd_ForwardToServer();
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
// It's a server command, but we're not connected to a server. Don't try to execute it.
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on
|
||
#ifndef _DEBUG
|
||
if ( pCommand->IsBitSet( FCVAR_CHEAT ) )
|
||
{
|
||
if ( !Host_IsSinglePlayerGame() && sv_cheats.GetInt() == 0 )
|
||
{
|
||
Msg( "Can't use cheat command %s in multiplayer, unless the server has sv_cheats set to 1.\n", pCommand->GetName() );
|
||
return NULL;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
(( ConCommand * )pCommand )->Dispatch();
|
||
return pCommand;
|
||
}
|
||
|
||
// check cvars
|
||
if ( cv->IsCommand() )
|
||
{
|
||
return pCommand;
|
||
}
|
||
|
||
// forward the command line to the server, so the entity DLL can parse it
|
||
if ( cmd_source == src_command )
|
||
{
|
||
if ( cl.IsConnected() )
|
||
{
|
||
Cmd_ForwardToServer();
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
Msg("Unknown command \"%s\"\n", Cmd_Argv(0));
|
||
|
||
return NULL;
|
||
}
|
||
#endif
|