spingle/source/cmd.c

865 lines
16 KiB
C

/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2010-2014 QuakeSpasm developers
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// cmd.c -- Quake script command processing module
#include "q_defs.h"
void Cmd_ForwardToServer(void);
#define MAX_ALIAS_NAME 32
#define CMDLINE_LENGTH 256 //johnfitz -- mirrored in common.c
typedef struct cmdalias_s
{
struct cmdalias_s *next;
char name[MAX_ALIAS_NAME];
char *value;
} cmdalias_t;
cmdalias_t *cmd_alias;
bool cmd_wait;
//=============================================================================
/*
============
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;
/*
============
Cbuf_Init
============
*/
void Cbuf_Init(void)
{
SZ_Alloc(&cmd_text, 1 << 18); // space for commands and script files. spike -- was 8192, but modern configs can be *HUGE*, at least if they contain lots of comments/docs for things.
}
/*
============
Cbuf_AddText
Adds command text at the end of the buffer
============
*/
void Cbuf_AddText(const char *text)
{
int32_t l;
l = strlen(text);
if(cmd_text.cursize + l >= cmd_text.maxsize)
{
Con_Printf("Cbuf_AddText: overflow\n");
return;
}
SZ_Write(&cmd_text, text, 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(const char *text)
{
char *temp;
int32_t templen;
// copy off any commands still remaining in the exec buffer
templen = cmd_text.cursize;
if(templen)
{
temp = (char *) Z_Malloc(templen);
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);
}
}
/*
============
Cbuf_Execute
============
*/
void Cbuf_Execute(void)
{
int32_t i;
char *text;
char line[1024];
int32_t quotes;
while(cmd_text.cursize)
{
// 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')
break;
}
if(i > (int32_t)strsizeof(line))
{
memcpy(line, text, strsizeof(line));
line[strsizeof(line)] = 0;
}
else
{
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;
memmove(text, text + i, cmd_text.cursize);
}
// execute the command 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;
}
}
}
/*
==============================================================================
SCRIPT COMMANDS
==============================================================================
*/
/*
===============
Cmd_StuffCmds_f -- johnfitz -- rewritten to read the "cmdline" cvar, for use with dynamic mod loading
Adds command line parameters as script statements
Commands lead with a +, and continue until a - or another +
<program name> +prog jctest.qp +cmd amlev1
<program name> -nosound +cmd amlev1
===============
*/
void Cmd_StuffCmds_f(void)
{
extern cvar_t cmdline;
char cmds[CMDLINE_LENGTH];
int32_t i, j, plus;
plus = false; // On Unix, argv[0] is command name
for(i = 0, j = 0; cmdline.string[i]; i++)
{
if(cmdline.string[i] == '+')
{
plus = true;
if(j > 0)
{
cmds[j - 1] = ';';
cmds[j++] = ' ';
}
}
else if(cmdline.string[i] == '-' &&
(i == 0 || cmdline.string[i - 1] == ' ')) //johnfitz -- allow hypenated map names with +map
plus = false;
else if(plus)
cmds[j++] = cmdline.string[i];
}
cmds[j] = 0;
Cbuf_InsertText(cmds);
}
/*
===============
Cmd_Exec_f
===============
*/
void Cmd_Exec_f(void)
{
char *f;
int32_t mark;
if(Cmd_Argc() != 2)
{
Con_Printf("exec <filename> : execute a script file\n");
return;
}
mark = Hunk_LowMark();
f = (char *)COM_LoadHunkFile(Cmd_Argv(1), NULL);
if(!f)
{
Con_Printf("couldn't exec %s\n", Cmd_Argv(1));
return;
}
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)
{
int32_t i;
for(i = 1 ; i < Cmd_Argc() ; i++)
Con_Printf("%s ", Cmd_Argv(i));
Con_Printf("\n");
}
/*
===============
Cmd_Alias_f -- johnfitz -- rewritten
Creates a new command that executes a command string (possibly ; seperated)
===============
*/
void Cmd_Alias_f(void)
{
cmdalias_t *a;
char cmd[1024];
int32_t i, c;
const char *s;
switch(Cmd_Argc())
{
case 1: //list all aliases
for(a = cmd_alias, i = 0; a; a = a->next, i++)
Con_SafePrintf(" %s: %s", a->name, a->value);
if(i)
Con_SafePrintf("%" PRIi32 " alias command(s)\n", i);
else
Con_SafePrintf("no alias commands found\n");
break;
case 2: //output current alias string
for(a = cmd_alias ; a ; a = a->next)
if(!strcmp(Cmd_Argv(1), a->name))
Con_Printf(" %s: %s", a->name, a->value);
break;
default: //set alias string
s = Cmd_Argv(1);
if(strlen(s) >= MAX_ALIAS_NAME)
{
Con_Printf("Alias name is too long\n");
return;
}
// if the alias allready exists, reuse it
for(a = cmd_alias ; a ; a = a->next)
{
if(!strcmp(s, a->name))
{
Z_Free(a->value);
break;
}
}
if(!a)
{
a = (cmdalias_t *) 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++)
{
q_strlcat(cmd, Cmd_Argv(i), sizeof(cmd));
if(i != c - 1)
q_strlcat(cmd, " ", sizeof(cmd));
}
if(q_strlcat(cmd, "\n", sizeof(cmd)) >= sizeof(cmd))
{
Con_Printf("alias value too long!\n");
cmd[0] = '\n'; // nullify the string
cmd[1] = 0;
}
a->value = Z_Strdup(cmd);
break;
}
}
/*
===============
Cmd_Unalias_f -- johnfitz
===============
*/
void Cmd_Unalias_f(void)
{
cmdalias_t *a, *prev;
switch(Cmd_Argc())
{
default:
case 1:
Con_Printf("unalias <name> : delete alias\n");
break;
case 2:
prev = NULL;
for(a = cmd_alias; a; a = a->next)
{
if(!strcmp(Cmd_Argv(1), a->name))
{
if(prev)
prev->next = a->next;
else
cmd_alias = a->next;
Z_Free(a->value);
Z_Free(a);
return;
}
prev = a;
}
Con_Printf("No alias named %s\n", Cmd_Argv(1));
break;
}
}
/*
===============
Cmd_Unaliasall_f -- johnfitz
===============
*/
void Cmd_Unaliasall_f(void)
{
cmdalias_t *blah;
while(cmd_alias)
{
blah = cmd_alias->next;
Z_Free(cmd_alias->value);
Z_Free(cmd_alias);
cmd_alias = blah;
}
}
/*
=============================================================================
COMMAND EXECUTION
=============================================================================
*/
typedef struct cmd_function_s
{
struct cmd_function_s *next;
const char *name;
xcommand_t function;
} cmd_function_t;
#define MAX_ARGS 80
static int32_t cmd_argc;
static char *cmd_argv[MAX_ARGS];
static char cmd_null_string[] = "";
static const char *cmd_args = NULL;
cmd_source_t cmd_source;
//johnfitz -- better tab completion
//static cmd_function_t *cmd_functions; // possible commands to execute
cmd_function_t *cmd_functions; // possible commands to execute
//johnfitz
/*
============
Cmd_List_f -- johnfitz
============
*/
void Cmd_List_f(void)
{
cmd_function_t *cmd;
const char *partial;
size_t len, count;
if(Cmd_Argc() > 1)
{
partial = Cmd_Argv(1);
len = strlen(partial);
}
else
{
partial = NULL;
len = 0;
}
count = 0;
for(cmd = cmd_functions ; cmd ; cmd = cmd->next)
{
if(partial && strncmp(partial, cmd->name, len))
{
continue;
}
Con_SafePrintf(" %s\n", cmd->name);
count++;
}
Con_SafePrintf("%zu commands", count);
if(partial)
{
Con_SafePrintf(" beginning with \"%s\"", partial);
}
Con_SafePrintf("\n");
}
static char *Cmd_TintSubstring(const char *in, const char *substr, char *out, size_t outsize)
{
size_t l;
char *m;
q_strlcpy(out, in, outsize);
while((m = q_strcasestr(out, substr)))
{
l = strlen(substr);
while(l-- > 0)
if(*m >= ' ' && *m < 127)
*m++ |= 0x80;
}
return out;
}
/*
============
Cmd_Apropos_f
scans through each command and cvar names+descriptions for the given substring
we don't support descriptions, so this isn't really all that useful, but even without the sake of consistency it still combines cvars+commands under a single command.
============
*/
void Cmd_Apropos_f(void)
{
char tmpbuf[256];
int32_t hits = 0;
cmd_function_t *cmd;
cvar_t *var;
const char *substr = Cmd_Argv(1);
if(!*substr)
{
Con_SafePrintf("%s <substring> : search through commands and cvars for the given substring\n", Cmd_Argv(0));
return;
}
for(cmd = cmd_functions ; cmd ; cmd = cmd->next)
{
if(q_strcasestr(cmd->name, substr))
{
hits++;
Con_SafePrintf("%s\n", Cmd_TintSubstring(cmd->name, substr, tmpbuf, sizeof(tmpbuf)));
}
}
for(var = Cvar_FindVarAfter("", 0) ; var ; var = var->next)
{
if(q_strcasestr(var->name, substr))
{
hits++;
Con_SafePrintf("%s (current value: \"%s\")\n", Cmd_TintSubstring(var->name, substr, tmpbuf, sizeof(tmpbuf)), var->string);
}
}
if(!hits)
Con_SafePrintf("no cvars nor commands contain that substring\n");
}
/*
============
Cmd_Init
============
*/
void Cmd_Init(void)
{
Cmd_AddCommand("cmdlist", Cmd_List_f); //johnfitz
Cmd_AddCommand("unalias", Cmd_Unalias_f); //johnfitz
Cmd_AddCommand("unaliasall", Cmd_Unaliasall_f); //johnfitz
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("cmd", Cmd_ForwardToServer);
Cmd_AddCommand("wait", Cmd_Wait_f);
Cmd_AddCommand("apropos", Cmd_Apropos_f);
Cmd_AddCommand("find", Cmd_Apropos_f);
}
/*
============
Cmd_Argc
============
*/
int32_t Cmd_Argc(void)
{
return cmd_argc;
}
/*
============
Cmd_Argv
============
*/
const char *Cmd_Argv(int32_t arg)
{
if(arg < 0 || arg >= cmd_argc)
return cmd_null_string;
return cmd_argv[arg];
}
/*
============
Cmd_Args
============
*/
const char *Cmd_Args(void)
{
return cmd_args;
}
/*
============
Cmd_TokenizeString
Parses the given string into command line tokens.
============
*/
void Cmd_TokenizeString(const char *text)
{
int32_t 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_Strdup(com_token);
cmd_argc++;
}
}
}
/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommand(const char *cmd_name, xcommand_t function)
{
cmd_function_t *cmd;
cmd_function_t *cursor, *prev; //johnfitz -- sorted list insert
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(!strcmp(cmd_name, cmd->name))
{
Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name);
return;
}
}
cmd = Hunk_AllocName(sizeof(cmd_function_t), "cmd_functions");
cmd->name = cmd_name;
cmd->function = function;
//johnfitz -- insert each entry in alphabetical order
if(cmd_functions == NULL || strcmp(cmd->name, cmd_functions->name) < 0) //insert at front
{
cmd->next = cmd_functions;
cmd_functions = cmd;
}
else //insert later
{
prev = cmd_functions;
cursor = cmd_functions->next;
while((cursor != NULL) && (strcmp(cmd->name, cursor->name) > 0))
{
prev = cursor;
cursor = cursor->next;
}
cmd->next = prev->next;
prev->next = cmd;
}
//johnfitz
}
/*
============
Cmd_Exists
============
*/
bool Cmd_Exists(const char *cmd_name)
{
cmd_function_t *cmd;
for(cmd = cmd_functions ; cmd ; cmd = cmd->next)
{
if(!strcmp(cmd_name, cmd->name))
return true;
}
return false;
}
/*
============
Cmd_CompleteCommand
============
*/
const char *Cmd_CompleteCommand(const char *partial)
{
cmd_function_t *cmd;
size_t len;
len = strlen(partial);
if(!len)
return NULL;
// check functions
for(cmd = cmd_functions ; cmd ; cmd = cmd->next)
if(!strncmp(partial, cmd->name, len))
return cmd->name;
return NULL;
}
/*
============
Cmd_ExecuteString
A complete command line has been parsed, so try to execute it
FIXME: lookupnoadd the token to speed search?
============
*/
void Cmd_ExecuteString(const 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))
{
cmd->function();
return;
}
}
// check alias
for(a = cmd_alias ; a ; a = a->next)
{
if(!q_strcasecmp(cmd_argv[0], a->name))
{
Cbuf_InsertText(a->value);
return;
}
}
// check cvars
if(!Cvar_Command())
Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0));
}
/*
===================
Cmd_ForwardToServer
Sends the entire command line over to the server
===================
*/
void Cmd_ForwardToServer(void)
{
if(cls.state != ca_connected)
{
Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
return;
}
if(cls.demoplayback)
return; // not really connected
MSG_WriteByte(&cls.message, clc_stringcmd);
if(q_strcasecmp(Cmd_Argv(0), "cmd") != 0)
{
SZ_Print(&cls.message, Cmd_Argv(0));
SZ_Print(&cls.message, " ");
}
if(Cmd_Argc() > 1)
SZ_Print(&cls.message, Cmd_Args());
else
SZ_Print(&cls.message, "\n");
}
/*
================
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
================
*/
int32_t Cmd_CheckParm(const char *parm)
{
int32_t 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;
}