1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20// cmd.c -- Quake script command processing module
21
22#include "quakedef.h"
23
24void Cmd_ForwardToServer (void);
25
26#define    MAX_ALIAS_NAME    32
27
28typedef struct cmdalias_s
29{
30    struct cmdalias_s    *next;
31    char    name[MAX_ALIAS_NAME];
32    char    *value;
33} cmdalias_t;
34
35cmdalias_t    *cmd_alias;
36
37int trashtest;
38int *trashspot;
39
40qboolean    cmd_wait;
41
42//=============================================================================
43
44/*
45============
46Cmd_Wait_f
47
48Causes execution of the remainder of the command buffer to be delayed until
49next frame.  This allows commands like:
50bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
51============
52*/
53void Cmd_Wait_f (void)
54{
55    cmd_wait = true;
56}
57
58/*
59=============================================================================
60
61                        COMMAND BUFFER
62
63=============================================================================
64*/
65
66sizebuf_t    cmd_text;
67
68/*
69============
70Cbuf_Init
71============
72*/
73void Cbuf_Init (void)
74{
75    SZ_Alloc (&cmd_text, 8192);        // space for commands and script files
76}
77
78
79/*
80============
81Cbuf_AddText
82
83Adds command text at the end of the buffer
84============
85*/
86void Cbuf_AddText (const char *text)
87{
88    int        l;
89
90    l = Q_strlen (text);
91
92    if (cmd_text.cursize + l >= cmd_text.maxsize)
93    {
94        Con_Printf ("Cbuf_AddText: overflow\n");
95        return;
96    }
97
98    SZ_Write (&cmd_text, text, Q_strlen (text));
99}
100
101
102/*
103============
104Cbuf_InsertText
105
106Adds command text immediately after the current command
107Adds a \n to the text
108FIXME: actually change the command buffer to do less copying
109============
110*/
111void Cbuf_InsertText (const char *text)
112{
113    char    *temp;
114    int        templen;
115
116// copy off any commands still remaining in the exec buffer
117    templen = cmd_text.cursize;
118    if (templen)
119    {
120        temp = (char*) Z_Malloc (templen);
121        Q_memcpy (temp, cmd_text.data, templen);
122        SZ_Clear (&cmd_text);
123    }
124    else
125        temp = NULL;    // shut up compiler
126
127// add the entire text of the file
128    Cbuf_AddText (text);
129
130// add the copied off data
131    if (templen)
132    {
133        SZ_Write (&cmd_text, temp, templen);
134        Z_Free (temp);
135    }
136}
137
138/*
139============
140Cbuf_Execute
141============
142*/
143void Cbuf_Execute (void)
144{
145    int        i;
146    char    *text;
147    char    line[1024];
148    int        quotes;
149
150    while (cmd_text.cursize)
151    {
152// find a \n or ; line break
153        text = (char *)cmd_text.data;
154
155        quotes = 0;
156        for (i=0 ; i< cmd_text.cursize ; i++)
157        {
158            if (text[i] == '"')
159                quotes++;
160            if ( !(quotes&1) &&  text[i] == ';')
161                break;    // don't break if inside a quoted string
162            if (text[i] == '\n' || text[i] == '\r')
163                break;
164        }
165
166
167        memcpy (line, text, i);
168        line[i] = 0;
169
170// delete the text from the command buffer and move remaining commands down
171// this is necessary because commands (exec, alias) can insert data at the
172// beginning of the text buffer
173
174        if (i == cmd_text.cursize)
175            cmd_text.cursize = 0;
176        else
177        {
178            i++;
179            cmd_text.cursize -= i;
180            Q_memcpy (text, text+i, cmd_text.cursize);
181        }
182
183// execute the command line
184        Cmd_ExecuteString (line, src_command);
185
186        if (cmd_wait)
187        {    // skip out while text still remains in buffer, leaving it
188            // for next frame
189            cmd_wait = false;
190            break;
191        }
192    }
193}
194
195/*
196==============================================================================
197
198                        SCRIPT COMMANDS
199
200==============================================================================
201*/
202
203/*
204===============
205Cmd_StuffCmds_f
206
207Adds command line parameters as script statements
208Commands lead with a +, and continue until a - or another +
209quake +prog jctest.qp +cmd amlev1
210quake -nosound +cmd amlev1
211===============
212*/
213void Cmd_StuffCmds_f (void)
214{
215    int        i, j;
216    int        s;
217    char    *text, *build, c;
218
219    if (Cmd_Argc () != 1)
220    {
221        Con_Printf ("stuffcmds : execute command line parameters\n");
222        return;
223    }
224
225// build the combined string to parse from
226    s = 0;
227    for (i=1 ; i<com_argc ; i++)
228    {
229        if (!com_argv[i])
230            continue;        // NEXTSTEP nulls out -NXHost
231        s += Q_strlen (com_argv[i]) + 1;
232    }
233    if (!s)
234        return;
235
236    text = (char*) Z_Malloc (s+1);
237    text[0] = 0;
238    for (i=1 ; i<com_argc ; i++)
239    {
240        if (!com_argv[i])
241            continue;        // NEXTSTEP nulls out -NXHost
242        Q_strcat (text,com_argv[i]);
243        if (i != com_argc-1)
244            Q_strcat (text, " ");
245    }
246
247// pull out the commands
248    build = (char*) Z_Malloc (s+1);
249    build[0] = 0;
250
251    for (i=0 ; i<s-1 ; i++)
252    {
253        if (text[i] == '+')
254        {
255            i++;
256
257            for (j=i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
258                ;
259
260            c = text[j];
261            text[j] = 0;
262
263            Q_strcat (build, text+i);
264            Q_strcat (build, "\n");
265            text[j] = c;
266            i = j-1;
267        }
268    }
269
270    if (build[0])
271        Cbuf_InsertText (build);
272
273    Z_Free (text);
274    Z_Free (build);
275}
276
277
278/*
279===============
280Cmd_Exec_f
281===============
282*/
283void Cmd_Exec_f (void)
284{
285    char    *f;
286    int        mark;
287
288    if (Cmd_Argc () != 2)
289    {
290        Con_Printf ("exec <filename> : execute a script file\n");
291        return;
292    }
293
294    mark = Hunk_LowMark ();
295    f = (char *)COM_LoadHunkFile (Cmd_Argv(1));
296    if (!f)
297    {
298        Con_Printf ("couldn't exec %s\n",Cmd_Argv(1));
299        return;
300    }
301    Con_Printf ("execing %s\n",Cmd_Argv(1));
302
303    Cbuf_InsertText (f);
304    Hunk_FreeToLowMark (mark);
305}
306
307
308/*
309===============
310Cmd_Echo_f
311
312Just prints the rest of the line to the console
313===============
314*/
315void Cmd_Echo_f (void)
316{
317    int        i;
318
319    for (i=1 ; i<Cmd_Argc() ; i++)
320        Con_Printf ("%s ",Cmd_Argv(i));
321    Con_Printf ("\n");
322}
323
324/*
325===============
326Cmd_Alias_f
327
328Creates a new command that executes a command string (possibly ; seperated)
329===============
330*/
331
332char *CopyString (const char *in)
333{
334    char    *out;
335
336    out = (char*) Z_Malloc (strlen(in)+1);
337    strcpy (out, in);
338    return out;
339}
340
341void Cmd_Alias_f (void)
342{
343    cmdalias_t    *a;
344    char        cmd[1024];
345    int            i, c;
346    const char        *s;
347
348    if (Cmd_Argc() == 1)
349    {
350        Con_Printf ("Current alias commands:\n");
351        for (a = cmd_alias ; a ; a=a->next)
352            Con_Printf ("%s : %s\n", a->name, a->value);
353        return;
354    }
355
356    s = Cmd_Argv(1);
357    if (strlen(s) >= MAX_ALIAS_NAME)
358    {
359        Con_Printf ("Alias name is too long\n");
360        return;
361    }
362
363    // if the alias allready exists, reuse it
364    for (a = cmd_alias ; a ; a=a->next)
365    {
366        if (!strcmp(s, a->name))
367        {
368            Z_Free (a->value);
369            break;
370        }
371    }
372
373    if (!a)
374    {
375        a = (cmdalias_t*) Z_Malloc (sizeof(cmdalias_t));
376        a->next = cmd_alias;
377        cmd_alias = a;
378    }
379    strcpy (a->name, s);
380
381// copy the rest of the command line
382    cmd[0] = 0;        // start out with a null string
383    c = Cmd_Argc();
384    for (i=2 ; i< c ; i++)
385    {
386        strcat (cmd, Cmd_Argv(i));
387        if (i != c)
388            strcat (cmd, " ");
389    }
390    strcat (cmd, "\n");
391
392    a->value = CopyString (cmd);
393}
394
395/*
396=============================================================================
397
398                    COMMAND EXECUTION
399
400=============================================================================
401*/
402
403typedef struct cmd_function_s
404{
405    struct cmd_function_s    *next;
406    char                    *name;
407    xcommand_t                function;
408} cmd_function_t;
409
410
411#define    MAX_ARGS        80
412
413static    int            cmd_argc;
414static    char        *cmd_argv[MAX_ARGS];
415static    char        *cmd_null_string = (char*) "";
416static    char        *cmd_args = NULL;
417
418cmd_source_t    cmd_source;
419
420
421static    cmd_function_t    *cmd_functions;        // possible commands to execute
422
423/*
424============
425Cmd_Init
426============
427*/
428void Cmd_Init (void)
429{
430//
431// register our commands
432//
433    Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f);
434    Cmd_AddCommand ("exec",Cmd_Exec_f);
435    Cmd_AddCommand ("echo",Cmd_Echo_f);
436    Cmd_AddCommand ("alias",Cmd_Alias_f);
437    Cmd_AddCommand ("cmd", Cmd_ForwardToServer);
438    Cmd_AddCommand ("wait", Cmd_Wait_f);
439}
440
441/*
442============
443Cmd_Argc
444============
445*/
446int        Cmd_Argc (void)
447{
448    return cmd_argc;
449}
450
451/*
452============
453Cmd_Argv
454============
455*/
456char    *Cmd_Argv (int arg)
457{
458    if ( arg >= cmd_argc )
459        return cmd_null_string;
460    return cmd_argv[arg];
461}
462
463/*
464============
465Cmd_Args
466============
467*/
468char        *Cmd_Args (void)
469{
470    return cmd_args;
471}
472
473
474/*
475============
476Cmd_TokenizeString
477
478Parses the given string into command line tokens.
479============
480*/
481void Cmd_TokenizeString (char *text)
482{
483    int        i;
484
485// clear the args from the last string
486    for (i=0 ; i<cmd_argc ; i++)
487        Z_Free (cmd_argv[i]);
488
489    cmd_argc = 0;
490    cmd_args = NULL;
491
492    while (1)
493    {
494// skip whitespace up to a /n
495        while (*text && *text <= ' ' && *text != '\n')
496        {
497            text++;
498        }
499
500        if (*text == '\n')
501        {    // a newline seperates commands in the buffer
502            text++;
503            break;
504        }
505
506        if (!*text)
507            return;
508
509        if (cmd_argc == 1)
510             cmd_args = text;
511
512        text = COM_Parse (text);
513        if (!text)
514            return;
515
516        if (cmd_argc < MAX_ARGS)
517        {
518            cmd_argv[cmd_argc] = (char*) Z_Malloc (Q_strlen(com_token)+1);
519            Q_strcpy (cmd_argv[cmd_argc], com_token);
520            cmd_argc++;
521        }
522    }
523
524}
525
526
527/*
528============
529Cmd_AddCommand
530============
531*/
532void    Cmd_AddCommand (const char *cmd_name, xcommand_t function)
533{
534    cmd_function_t    *cmd;
535
536    if (host_initialized)    // because hunk allocation would get stomped
537        Sys_Error ("Cmd_AddCommand after host_initialized");
538
539// fail if the command is a variable name
540    if (Cvar_VariableString(cmd_name)[0])
541    {
542        Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
543        return;
544    }
545
546// fail if the command already exists
547    for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
548    {
549        if (!Q_strcmp (cmd_name, cmd->name))
550        {
551            Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
552            return;
553        }
554    }
555
556    cmd = (cmd_function_t*) Hunk_Alloc (sizeof(cmd_function_t));
557    cmd->name = (char*) cmd_name;
558    cmd->function = function;
559    cmd->next = cmd_functions;
560    cmd_functions = cmd;
561}
562
563/*
564============
565Cmd_Exists
566============
567*/
568qboolean    Cmd_Exists (const char *cmd_name)
569{
570    cmd_function_t    *cmd;
571
572    for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
573    {
574        if (!Q_strcmp (cmd_name,cmd->name))
575            return true;
576    }
577
578    return false;
579}
580
581
582
583/*
584============
585Cmd_CompleteCommand
586============
587*/
588const char *Cmd_CompleteCommand (const char *partial)
589{
590    cmd_function_t    *cmd;
591    int                len;
592
593    len = Q_strlen(partial);
594
595    if (!len)
596        return NULL;
597
598// check functions
599    for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
600        if (!Q_strncmp (partial,cmd->name, len))
601            return cmd->name;
602
603    return NULL;
604}
605
606/*
607============
608Cmd_ExecuteString
609
610A complete command line has been parsed, so try to execute it
611FIXME: lookupnoadd the token to speed search?
612============
613*/
614void    Cmd_ExecuteString (char *text, cmd_source_t src)
615{
616    cmd_function_t    *cmd;
617    cmdalias_t        *a;
618
619    cmd_source = src;
620    Cmd_TokenizeString (text);
621
622// execute the command line
623    if (!Cmd_Argc())
624        return;        // no tokens
625
626// check functions
627    for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
628    {
629        if (!Q_strcasecmp (cmd_argv[0],cmd->name))
630        {
631            cmd->function ();
632            return;
633        }
634    }
635
636// check alias
637    for (a=cmd_alias ; a ; a=a->next)
638    {
639        if (!Q_strcasecmp (cmd_argv[0], a->name))
640        {
641            Cbuf_InsertText (a->value);
642            return;
643        }
644    }
645
646// check cvars
647    if (!Cvar_Command ())
648        Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0));
649
650}
651
652void    Cmd_ExecuteString2 (const char *text, cmd_source_t src)
653{
654    char buf[100];
655    Q_strncpy(buf, text, sizeof(buf));
656    buf[sizeof(buf)-1] = 0;
657    Cmd_ExecuteString(buf, src);
658}
659
660/*
661===================
662Cmd_ForwardToServer
663
664Sends the entire command line over to the server
665===================
666*/
667void Cmd_ForwardToServer (void)
668{
669    if (cls.state != ca_connected)
670    {
671        Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
672        return;
673    }
674
675    if (cls.demoplayback)
676        return;        // not really connected
677
678    MSG_WriteByte (&cls.message, clc_stringcmd);
679    if (Q_strcasecmp(Cmd_Argv(0), "cmd") != 0)
680    {
681        SZ_Print (&cls.message, Cmd_Argv(0));
682        SZ_Print (&cls.message, " ");
683    }
684    if (Cmd_Argc() > 1)
685        SZ_Print (&cls.message, Cmd_Args());
686    else
687        SZ_Print (&cls.message, "\n");
688}
689
690
691/*
692================
693Cmd_CheckParm
694
695Returns the position (1 to argc-1) in the command's argument list
696where the given parameter apears, or 0 if not present
697================
698*/
699
700int Cmd_CheckParm (const char *parm)
701{
702    int i;
703
704    if (!parm)
705        Sys_Error ("Cmd_CheckParm: NULL");
706
707    for (i = 1; i < Cmd_Argc (); i++)
708        if (! Q_strcasecmp (parm, Cmd_Argv (i)))
709            return i;
710
711    return 0;
712}
713