quakeforge-old/common/cmd.c
2000-08-20 13:33:48 +00:00

920 lines
16 KiB
C

/*
cmd.c
Quake script command processing module
Copyright (C) 1996-1997 Id Software, Inc.
Copyright (C) 1999,2000 contributors of the QuakeForge project
Please see the file "AUTHORS" for a list of contributors
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#include <common.h>
#include <qargs.h>
#include <cmd.h>
#include <console.h>
#include <cvar.h>
#include <sys.h>
#include <client.h>
#include <lib_replace.h>
#include <zone.h>
#include <string.h>
#include <ctype.h>
#include <net.h>
#include <common_quakedef.h>
#include <cvars.h>
void Cmd_ForwardToServer (void);
#define MAX_ALIAS_NAME 32
typedef struct cmdalias_s
{
struct cmdalias_s *next;
char name[MAX_ALIAS_NAME];
char *value;
} cmdalias_t;
cmdalias_t *cmd_alias;
qboolean cmd_wait;
cvar_t *cl_warncmd;
//=============================================================================
/*
============
Cmd_Wait_f
Causes execution of the remainder of the command buffer to be delayed until
next frame. This allows commands like:
bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
============
*/
void
Cmd_Wait_f ( void )
{
cmd_wait = true;
}
/*
=============================================================================
COMMAND BUFFER
=============================================================================
*/
sizebuf_t cmd_text;
byte cmd_text_buf[8192];
/*
============
Cbuf_Init
============
*/
void
Cbuf_Init ( void )
{
cmd_text.data = cmd_text_buf;
cmd_text.maxsize = sizeof(cmd_text_buf);
}
/*
============
Cbuf_AddText
Adds command text at the end of the buffer
============
*/
void
Cbuf_AddText ( char *text )
{
int l;
l = Q_strlen (text);
if (cmd_text.cursize + l >= cmd_text.maxsize)
{
Con_Printf ("Cbuf_AddText: overflow\n");
return;
}
SZ_Write (&cmd_text, text, Q_strlen (text));
}
/*
============
Cbuf_InsertText
Adds command text immediately after the current command
Adds a \n to the text
FIXME: actually change the command buffer to do less copying
============
*/
void
Cbuf_InsertText ( char *text )
{
char *temp;
int templen;
// copy off any commands still remaining in the exec buffer
templen = cmd_text.cursize;
if (templen)
{
temp = Z_Malloc (templen);
Q_memcpy (temp, cmd_text.data, templen);
SZ_Clear (&cmd_text);
}
else
temp = NULL; // shut up compiler
// add the entire text of the file
Cbuf_AddText (text);
SZ_Write (&cmd_text, "\n", 1);
// add the copied off data
if (templen)
{
SZ_Write (&cmd_text, temp, templen);
Z_Free (temp);
}
}
static void
extract_line ( char *line )
{
int i;
char *text;
int quotes;
// find a \n or ; line break
text = (char *)cmd_text.data;
quotes = 0;
for (i=0 ; i< cmd_text.cursize ; i++) {
if (text[i] == '"')
quotes++;
if ( !(quotes&1) && text[i] == ';')
break; // don't break if inside a quoted string
if (text[i] == '\n' || text[i] == '\r')
break;
}
memcpy (line, text, i);
line[i] = '\0';
// delete the text from the command buffer and move remaining commands down
// this is necessary because commands (exec, alias) can insert data at the
// beginning of the text buffer
if (i == cmd_text.cursize)
cmd_text.cursize = 0;
else {
i++;
cmd_text.cursize -= i;
Q_memcpy (text, text+i, cmd_text.cursize);
}
}
/*
Cbuf_Execute
*/
void
Cbuf_Execute ( void )
{
char line[1024] = {0};
while (cmd_text.cursize) {
extract_line (line);
// execute the command line
//Con_DPrintf("+%s\n",line),
Cmd_ExecuteString (line, src_command);
if (cmd_wait)
{ // skip out while text still remains in buffer, leaving it
// for next frame
cmd_wait = false;
break;
}
}
}
/*
Cbuf_Execute
*/
void
Cbuf_Execute_Sets ( void )
{
char line[1024] = {0};
while (cmd_text.cursize) {
extract_line (line);
// execute the command line
if (strncmp(line,"set",3)==0
&& isspace((int) line[3]))
//Con_DPrintf("+%s\n",line),
Cmd_ExecuteString (line, src_command);
}
}
/*
==============================================================================
SCRIPT COMMANDS
==============================================================================
*/
/*
===============
Cmd_StuffCmds_f
Adds command line parameters as script statements
Commands lead with a +, and continue until a - or another +
quake +prog jctest.qp +cmd amlev1
quake -nosound +cmd amlev1
===============
*/
void
Cmd_StuffCmds_f ( void )
{
int i, j;
int s;
char *build, c;
s = strlen (com_cmdline);
if (!s)
return;
// pull out the commands
build = Z_Malloc (s+1);
build[0] = 0;
for (i=0 ; i<s-1 ; i++)
{
if (com_cmdline[i] == '+')
{
i++;
for (j=i ; ((com_cmdline[j] != '+')
&& (com_cmdline[j] != '-')
&& (com_cmdline[j] != 0)) ; j++)
;
c = com_cmdline[j];
com_cmdline[j] = 0;
Q_strcat (build, com_cmdline+i);
Q_strcat (build, "\n");
com_cmdline[j] = c;
i = j-1;
}
}
// Con_Printf("[\n%s]\n",build);
if (build[0])
Cbuf_InsertText (build);
Z_Free (build);
}
/*
Cmd_Exec_File
*/
void
Cmd_Exec_File ( char *path )
{
char *f;
int mark;
int len;
char base[32];
QFile *file;
if ((file = Qopen (path, "r")) != NULL) {
// extract the filename base name for hunk tag
COM_FileBase (path, base);
len = COM_filelength (file);
mark = Hunk_LowMark ();
f = (char *)Hunk_AllocName (len+1, base);
if (f) {
f[len] = 0;
Qread (file, f, len);
Qclose (file);
Cbuf_InsertText (f);
}
Hunk_FreeToLowMark (mark);
}
}
/*
===============
Cmd_Exec_f
===============
*/
void
Cmd_Exec_f ( void )
{
char *f;
int mark;
if (Cmd_Argc () != 2)
{
Con_Printf ("exec <filename> : execute a script file\n");
return;
}
// FIXME: is this safe freeing the hunk here???
mark = Hunk_LowMark ();
f = (char *)COM_LoadHunkFile (Cmd_Argv(1));
if (!f)
{
Con_Printf ("couldn't exec %s\n",Cmd_Argv(1));
return;
}
if (!Cvar_Command () && (cl_warncmd->value || developer->value))
Con_Printf ("execing %s\n",Cmd_Argv(1));
Cbuf_InsertText (f);
Hunk_FreeToLowMark (mark);
}
/*
===============
Cmd_Echo_f
Just prints the rest of the line to the console
===============
*/
void
Cmd_Echo_f ( void )
{
int i;
for (i=1 ; i<Cmd_Argc() ; i++)
Con_Printf ("%s ",Cmd_Argv(i));
Con_Printf ("\n");
}
/*
===============
Cmd_Alias_f
Creates a new command that executes a command string (possibly ; seperated)
===============
*/
char *
CopyString ( char *in )
{
char *out;
out = Z_Malloc (strlen(in)+1);
strcpy (out, in);
return out;
}
void
Cmd_Alias_f ( void )
{
cmdalias_t *a;
char cmd[1024];
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;
}
// if the alias already exists, reuse it
for (a = cmd_alias ; a ; a=a->next)
{
if (!strcmp(s, a->name))
{
Z_Free (a->value);
break;
}
}
if (!a)
{
a = Z_Malloc (sizeof(cmdalias_t));
a->next = cmd_alias;
cmd_alias = a;
}
strcpy (a->name, s);
// 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++)
{
strcat (cmd, Cmd_Argv(i));
if (i != c)
strcat (cmd, " ");
}
strcat (cmd, "\n");
a->value = CopyString (cmd);
}
void
Cmd_UnAlias_f ( void )
{
cmdalias_t *a, *prev;
char *s;
if (Cmd_Argc() != 2)
{
Con_Printf ("unalias <alias>: erase an existing alias\n");
return;
}
s = Cmd_Argv(1);
if (strlen(s) >= MAX_ALIAS_NAME)
{
Con_Printf ("Alias name is too long\n");
return;
}
prev = cmd_alias;
for (a = cmd_alias ; a ; a = a->next)
{
if (!strcmp(s, a->name))
{
Z_Free (a->value);
prev->next = a->next;
if (a == cmd_alias)
cmd_alias = a->next;
Z_Free (a);
return;
}
prev = a;
}
Con_Printf ("Unknown alias \"%s\"\n", s);
}
/*
=============================================================================
COMMAND EXECUTION
=============================================================================
*/
typedef struct cmd_function_s
{
struct cmd_function_s *next;
char *name;
xcommand_t function;
} cmd_function_t;
#define MAX_ARGS 80
static int cmd_argc;
static char *cmd_argv[MAX_ARGS];
static char *cmd_null_string = "";
static char *cmd_args = NULL;
cmd_source_t cmd_source;
static cmd_function_t *cmd_functions; // possible commands to execute
/*
============
Cmd_Argc
============
*/
int
Cmd_Argc ( void )
{
return cmd_argc;
}
/*
============
Cmd_Argv
============
*/
char *
Cmd_Argv ( int arg )
{
if ( arg >= cmd_argc )
return cmd_null_string;
return cmd_argv[arg];
}
/*
============
Cmd_Args
Returns a single string containing argv(1) to argv(argc()-1)
============
*/
char *
Cmd_Args ( void )
{
if (!cmd_args)
return "";
return cmd_args;
}
/*
============
Cmd_TokenizeString
Parses the given string into command line tokens.
============
*/
void
Cmd_TokenizeString ( char *text )
{
int i;
// clear the args from the last string
for (i=0 ; i<cmd_argc ; i++)
Z_Free (cmd_argv[i]);
cmd_argc = 0;
cmd_args = NULL;
while (1)
{
// skip whitespace up to a /n
while (*text && *text <= ' ' && *text != '\n')
{
text++;
}
if (*text == '\n')
{ // a newline seperates commands in the buffer
text++;
break;
}
if (!*text)
return;
if (cmd_argc == 1)
cmd_args = text;
text = COM_Parse (text);
if (!text)
return;
if (cmd_argc < MAX_ARGS)
{
cmd_argv[cmd_argc] = Z_Malloc (Q_strlen(com_token)+1);
Q_strcpy (cmd_argv[cmd_argc], com_token);
cmd_argc++;
}
}
}
/*
============
Cmd_AddCommand
============
*/
void
Cmd_AddCommand ( char *cmd_name, xcommand_t function )
{
cmd_function_t *cmd;
if (host_initialized) // because hunk allocation would get stomped
Sys_Error ("Cmd_AddCommand after host_initialized");
// fail if the command is a variable name
if (Cvar_VariableString(cmd_name)[0])
{
Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
return;
}
// fail if the command already exists
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
{
if (!Q_strcmp (cmd_name, cmd->name))
{
Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
return;
}
}
cmd = Hunk_Alloc (sizeof(cmd_function_t));
cmd->name = cmd_name;
cmd->function = function;
cmd->next = cmd_functions;
cmd_functions = cmd;
}
/*
============
Cmd_Exists
============
*/
qboolean
Cmd_Exists ( char *cmd_name )
{
cmd_function_t *cmd;
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
{
if (!Q_strcmp (cmd_name,cmd->name))
return true;
}
return false;
}
/*
============
Cmd_CompleteCommand
============
*/
char *
Cmd_CompleteCommand ( char *partial )
{
cmd_function_t *cmd;
int len;
cmdalias_t *a;
len = Q_strlen(partial);
if (!len)
return NULL;
// check for exact match
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
if (!strcmp (partial,cmd->name))
return cmd->name;
for (a=cmd_alias ; a ; a=a->next)
if (!strcmp (partial, a->name))
return a->name;
// check for partial match
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
if (!strncmp (partial,cmd->name, len))
return cmd->name;
for (a=cmd_alias ; a ; a=a->next)
if (!strncmp (partial, a->name, len))
return a->name;
return NULL;
}
#ifndef SERVERONLY // FIXME
/*
===================
Cmd_ForwardToServer
adds the current command line as a clc_stringcmd to the client message.
things like godmode, noclip, etc, are commands directed to the server,
so when they are typed in at the console, they will need to be forwarded.
===================
*/
void
Cmd_ForwardToServer ( void )
{
if (cls.state == ca_disconnected)
{
Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
return;
}
if (cls.demoplayback)
return; // not really connected
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
SZ_Print (&cls.netchan.message, Cmd_Argv(0));
if (Cmd_Argc() > 1) {
SZ_Print (&cls.netchan.message, " ");
SZ_Print (&cls.netchan.message, Cmd_Args());
}
#if 0
MSG_WriteByte (&cls.message, clc_stringcmd);
SZ_Print (&cls.message, Cmd_Argv(0));
if (Cmd_Argc() > 1) {
SZ_Print (&cls.message, " ");
SZ_Print (&cls.message, Cmd_Args());
}
#endif
}
// don't forward the first argument
void
Cmd_ForwardToServer_f ( void )
{
if (cls.state == ca_disconnected)
{
Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
return;
}
if (Q_strcasecmp(Cmd_Argv(1), "snap") == 0) {
Cbuf_InsertText ("snap\n");
return;
}
if (cls.demoplayback)
return; // not really connected
if (Cmd_Argc() > 1) {
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
SZ_Print (&cls.netchan.message, Cmd_Args());
#if 0
MSG_WriteByte (&cls.message, clc_stringcmd);
SZ_Print (&cls.message, Cmd_Args());
#endif
}
}
#else
void
Cmd_ForwardToServer ( void )
{
}
#endif
/*
============
Cmd_ExecuteString
A complete command line has been parsed, so try to execute it
FIXME: lookupnoadd the token to speed search?
============
*/
void
Cmd_ExecuteString ( char *text, cmd_source_t src )
{
cmd_function_t *cmd;
cmdalias_t *a;
cmd_source = src;
Cmd_TokenizeString (text);
// execute the command line
if (!Cmd_Argc())
return; // no tokens
// check functions
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
{
if (!Q_strcasecmp (cmd_argv[0],cmd->name))
{
if (!cmd->function)
Cmd_ForwardToServer ();
else
cmd->function ();
return;
}
}
// Tonik: check cvars
if (Cvar_Command())
return;
// check alias
for (a=cmd_alias ; a ; a=a->next)
{
if (!Q_strcasecmp (cmd_argv[0], a->name))
{
Cbuf_InsertText (a->value);
return;
}
}
if (cl_warncmd->value || developer->value)
Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0));
}
/*
================
Cmd_CheckParm
Returns the position (1 to argc-1) in the command's argument list
where the given parameter apears, or 0 if not present
================
*/
int
Cmd_CheckParm ( char *parm )
{
int i;
if (!parm)
Sys_Error ("Cmd_CheckParm: NULL");
for (i = 1; i < Cmd_Argc (); i++)
if (! Q_strcasecmp (parm, Cmd_Argv (i)))
return i;
return 0;
}
void
Cmd_CmdList_f ( void )
{
cmd_function_t *cmd;
int i;
// 2000-07-10 Partial selection for CmdList command by Maddes start
char *partial;
int len;
int count;
if (Cmd_Argc() > 1)
{
partial = Cmd_Argv (1);
len = Q_strlen(partial);
}
else
{
partial = NULL;
len = 0;
}
count=0;
// 2000-07-10 Partial selection for CmdList command by Maddes end
for (cmd=cmd_functions, i=0 ; cmd ; cmd=cmd->next, i++)
{
// 2000-07-10 Partial selection for CmdList command by Maddes start
if (partial && Q_strncmp (partial, cmd->name, len))
{
continue;
}
count++;
// 2000-07-10 Partial selection for CmdList command by Maddes end
Con_Printf("%s\n", cmd->name);
}
// 2000-07-10 Partial selection for CmdList command by Maddes start
Con_Printf ("------------\n");
if (partial)
{
Con_Printf ("%i beginning with \"%s\" out of ", count, partial);
}
Con_Printf ("%i commands\n", i);
// 2000-07-10 Partial selection for CmdList command by Maddes end
}
/*
============
Cmd_Init
============
*/
void
Cmd_Init ( void )
{
//
// register our commands
//
Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f);
Cmd_AddCommand ("exec",Cmd_Exec_f);
Cmd_AddCommand ("echo",Cmd_Echo_f);
Cmd_AddCommand ("alias",Cmd_Alias_f);
Cmd_AddCommand ("unalias",Cmd_UnAlias_f);
Cmd_AddCommand ("wait", Cmd_Wait_f);
Cmd_AddCommand ("cmdlist", Cmd_CmdList_f);
#ifndef SERVERONLY
Cmd_AddCommand ("cmd", Cmd_ForwardToServer_f);
#endif
}