Files
assaultcube-/source/src/command.cpp
T
ac-stef 5e20a2343b add animated inlined images for console text
Images are stored in packages/misc/igraph/*.png and loaded during game
start. Filenames consist of the image shorthand (mnemonic) and optional
frame times, separated by "_". Keep in mind, media file names may only
contain letters, digits and "_-.()".

All texts displayed in the game console are scanned for mnemonics prefixed
with ":". All found matches are replaced by the image.

The images are displayed as squares with width and height the same as the
regular font height. To create animations, put several square images side
by side into the file. For example, a 128x32 image is interpreted as four
frames. Frame duration is encoded in the filename, so, for example the file
"nop_1000_200_100.png" shows the first frame for 1000 milliseconds, the
second frame for 200 msec and the third (as the rest) for 100 msec.

Igraphs "1" to "9" are hardcoded and can be manually encoded in menu texts.
Use the new escape code "\i" plus the number of the image: "\i\1".."\i\9".
Other images can be used in menus as well, but the codes have to be fetched
by getigraph.

new commands:

  updateigraphs

    Checks for new files in packages/misc/igraph. Only necessary, if new
    files are added during the game - for example, by mod package download.

  getigraph <mnemonic>

    Returns an encoded string to display an image outside the console, or
    an empty string, if the mnemonic was not found.

new parameters:

  igraphsize 80..300 (default 120)

    scales the images (in percent)

  igraphsizehardcoded 80..160 (default 106)

    scales the hardcoded images "1".."9" (in percent)

  igraphanimate 0..1 (default 1)

    if 0, only the first frame of every image is displayed

  igraphdefaultframetime 5..2000 (default 200)

    used for images with multiple frames but no frame duration specified
    in the filename

  hideigraphs 0..1 (default 0)

    if 1, no new images will be shown in the console

DOCREF updateigraphs getigraph igraphsizehardcoded
DOCREF igraphsize igraphanimate igraphdefaultframetime hideigraphs
2017-05-28 23:29:06 +02:00

1902 lines
62 KiB
C++

// command.cpp: implements the parsing and execution of a tiny script language which
// is largely backwards compatible with the quake console language.
#include "cube.h"
inline bool identaccessdenied(ident *id);
char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); }
vector<const char *> executionstack; // keep history of recursive command execution (to write to log in case of a crash)
char *commandret = NULL;
bool loop_break = false, loop_skip = false; // break or continue (skip) current loop
int loop_level = 0; // avoid bad calls of break & continue
hashtable<const char *, ident> *idents = NULL; // contains ALL vars/commands/aliases
bool persistidents = false;
COMMANDF(persistidents, "s", (char *on)
{
if(on && *on) persistidents = ATOI(on) != 0;
intret(persistidents ? 1 : 0);
});
void clearstack(ident &id)
{
identstack *stack = id.stack;
while(stack)
{
delete[] stack->action;
identstack *tmp = stack;
stack = stack->next;
delete tmp;
}
id.stack = NULL;
}
void pushident(ident &id, char *val, int context = execcontext)
{
if(id.type != ID_ALIAS) { delete[] val; return; }
identstack *stack = new identstack;
stack->action = id.executing==id.action ? newstring(id.action) : id.action;
stack->context = id.context;
stack->next = id.stack;
id.stack = stack;
id.action = val;
id.context = context;
}
void popident(ident &id)
{
if(id.type != ID_ALIAS || !id.stack) return;
if(id.action != id.executing) delete[] id.action;
identstack *stack = id.stack;
id.action = stack->action;
id.stack = stack->next;
id.context = stack->context;
delete stack;
}
ident *newident(const char *name, int context = execcontext)
{
ident *id = idents->access(name);
if(!id)
{
ident init(ID_ALIAS, newstring(name), newstring(""), persistidents, context);
id = &idents->access(init.name, init);
}
return id;
}
void pusha(const char *name, char *action)
{
ident *id = newident(name, execcontext);
if(identaccessdenied(id))
{
conoutf("cannot redefine alias %s in this execution context", id->name);
scripterr();
return;
}
pushident(*id, action);
}
void push(const char *name, const char *action)
{
pusha(name, newstring(action));
}
COMMAND(push, "ss");
void pop(const char *name)
{
ident *id = idents->access(name);
if(!id) return;
if(identaccessdenied(id))
{
conoutf("cannot redefine alias %s in this execution context", id->name);
scripterr();
return;
}
popident(*id);
}
COMMANDF(pop, "v", (char **args, int numargs)
{
if(numargs > 0)
{
const char *beforepopval = getalias(args[0]);
if(beforepopval) commandret = newstring(beforepopval);
}
loopi(numargs) pop(args[i]);
});
void delalias(const char *name)
{
ident *id = idents->access(name);
if(!id || id->type != ID_ALIAS) return;
if(identaccessdenied(id))
{
conoutf("cannot remove alias %s in this execution context", id->name);
scripterr();
return;
}
idents->remove(name);
}
COMMAND(delalias, "s");
void alias(const char *name, const char *action, bool temp, bool constant)
{
ident *b = idents->access(name);
if(!b)
{
ident b(ID_ALIAS, newstring(name), newstring(action), persistidents && !constant && !temp, execcontext);
b.isconst = constant;
b.istemp = temp;
idents->access(b.name, b);
return;
}
else if(b->type==ID_ALIAS)
{
if(identaccessdenied(b))
{
conoutf("cannot redefine alias %s in this execution context", b->name);
scripterr();
return;
}
if(b->isconst)
{
conoutf("alias %s is a constant and cannot be redefined", b->name);
scripterr();
return;
}
b->isconst = constant;
if(temp) b->istemp = true;
if(!constant || (action && action[0]))
{
if(b->action!=b->executing) delete[] b->action;
b->action = newstring(action);
b->persist = persistidents != 0;
}
if(b->istemp) b->persist = false;
}
else
{
conoutf("cannot redefine builtin %s with an alias", name);
scripterr();
}
}
COMMANDF(alias, "ss", (const char *name, const char *action) { alias(name, action, false, false); });
COMMANDF(tempalias, "ss", (const char *name, const char *action) { alias(name, action, true, false); });
COMMANDF(const, "ss", (const char *name, const char *action) { alias(name, action, false, true); });
COMMANDF(checkalias, "s", (const char *name) { intret(getalias(name) ? 1 : 0); });
COMMANDF(isconst, "s", (const char *name) { ident *id = idents->access(name); intret(id && id->isconst ? 1 : 0); });
// variable's and commands are registered through globals, see cube.h
int variable(const char *name, int minval, int cur, int maxval, int *storage, void (*fun)(), bool persist)
{
if(!idents) idents = new hashtable<const char *, ident>;
ident v(ID_VAR, name, minval, maxval, storage, cur, fun, persist, IEXC_CORE);
idents->access(name, v);
return cur;
}
float fvariable(const char *name, float minval, float cur, float maxval, float *storage, void (*fun)(), bool persist)
{
if(!idents) idents = new hashtable<const char *, ident>;
ident v(ID_FVAR, name, minval, maxval, storage, cur, fun, persist, IEXC_CORE);
idents->access(name, v);
return cur;
}
char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), void (*getfun)(), bool persist)
{
if(!idents) idents = new hashtable<const char *, ident>;
ident v(ID_SVAR, name, storage, fun, getfun, persist, IEXC_CORE);
idents->access(name, v);
return newstring(cur);
}
#define GETVAR(id, vartype, name) \
ident *id = idents->access(name); \
ASSERT(id && id->type == vartype); \
if(!id || id->type!=vartype) return;
void setvar(const char *name, int i, bool dofunc)
{
GETVAR(id, ID_VAR, name);
*id->storage.i = clamp(i, id->minval, id->maxval);
if(dofunc && id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
}
void setfvar(const char *name, float f, bool dofunc)
{
GETVAR(id, ID_FVAR, name);
*id->storage.f = clamp(f, id->minvalf, id->maxvalf);
if(dofunc && id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
}
void setsvar(const char *name, const char *str, bool dofunc)
{
GETVAR(id, ID_SVAR, name);
*id->storage.s = exchangestr(*id->storage.s, str);
if(dofunc && id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
}
void modifyvar(const char *name, int arg, char op)
{
ident *id = idents->access(name);
if(!id) return;
if(identaccessdenied(id))
{
conoutf("not allowed in this execution context: %s", id->name);
scripterr();
return;
}
if((id->type == ID_VAR && id->minval > id->maxval) || (id->type == ID_FVAR && id->minvalf > id->maxvalf)) { conoutf("variable %s is read-only", id->name); return; }
int val = 0;
switch(id->type)
{
case ID_VAR: val = *id->storage.i; break;
case ID_FVAR: val = int(*id->storage.f); break;
case ID_SVAR: { { if(id->getfun) ((void (__cdecl *)())id->getfun)(); } val = ATOI(*id->storage.s); break; }
case ID_ALIAS: val = ATOI(id->action); break;
}
switch(op)
{
case '+': val += arg; break;
case '-': val -= arg; break;
case '*': val *= arg; break;
case '/': val = arg ? val/arg : 0; break;
}
switch(id->type)
{
case ID_VAR: *id->storage.i = clamp(val, id->minval, id->maxval); break;
case ID_FVAR: *id->storage.f = clamp((float)val, id->minvalf, id->maxvalf); break;
case ID_SVAR: { string str; itoa(str, val); *id->storage.s = exchangestr(*id->storage.s, str); break; }
case ID_ALIAS: { string str; itoa(str, val); alias(name, str); return; }
default: return;
}
if(id->fun) ((void (__cdecl *)())id->fun)();
}
void modifyfvar(const char *name, float arg, char op)
{
ident *id = idents->access(name);
if(!id) return;
if(identaccessdenied(id))
{
conoutf("not allowed in this execution context: %s", id->name);
scripterr();
return;
}
if((id->type == ID_VAR && id->minval > id->maxval) || (id->type == ID_FVAR && id->minvalf > id->maxvalf)) { conoutf("variable %s is read-only", id->name); return; }
float val = 0;
switch(id->type)
{
case ID_VAR: val = *id->storage.i; break;
case ID_FVAR: val = *id->storage.f; break;
case ID_SVAR: { { if(id->getfun) ((void (__cdecl *)())id->getfun)(); } val = atof(*id->storage.s); break; }
case ID_ALIAS: val = atof(id->action); break;
}
switch(op)
{
case '+': val += arg; break;
case '-': val -= arg; break;
case '*': val *= arg; break;
case '/': val = (arg == 0.0f) ? 0 : val/arg; break;
}
switch(id->type)
{
case ID_VAR: *id->storage.i = clamp((int)val, id->minval, id->maxval); break;
case ID_FVAR: *id->storage.f = clamp(val, id->minvalf, id->maxvalf); break;
case ID_SVAR: *id->storage.s = exchangestr(*id->storage.s, floatstr(val)); break;
case ID_ALIAS: alias(name, floatstr(val)); return;
default: return;
}
if(id->fun) ((void (__cdecl *)())id->fun)();
}
void addeq(char *name, int *arg) { modifyvar(name, *arg, '+'); }
void subeq(char *name, int *arg) { modifyvar(name, *arg, '-'); }
void muleq(char *name, int *arg) { modifyvar(name, *arg, '*'); }
void diveq(char *name, int *arg) { modifyvar(name, *arg, '/'); }
void addeqf(char *name, float *arg) { modifyfvar(name, *arg, '+'); }
void subeqf(char *name, float *arg) { modifyfvar(name, *arg, '-'); }
void muleqf(char *name, float *arg) { modifyfvar(name, *arg, '*'); }
void diveqf(char *name, float *arg) { modifyfvar(name, *arg, '/'); }
COMMANDN(+=, addeq, "si");
COMMANDN(-=, subeq, "si");
COMMANDN(*=, muleq, "si");
COMMANDN(div=, diveq, "si");
COMMANDN(+=f, addeqf, "sf");
COMMANDN(-=f, subeqf, "sf");
COMMANDN(*=f, muleqf, "sf");
COMMANDN(div=f, diveqf, "sf");
bool identexists(const char *name) { return idents->access(name)!=NULL; }
const char *getalias(const char *name)
{
ident *i = idents->access(name);
return i && i->type==ID_ALIAS ? i->action : NULL;
}
void _getalias(char *name)
{
string o = "";
const char *res = o;
ident *id = idents->access(name);
if(id)
{
switch(id->type)
{
case ID_VAR:
formatstring(o)("%d", *id->storage.i);
break;
case ID_FVAR:
formatstring(o)("%.3f", *id->storage.f);
break;
case ID_SVAR:
if(id->getfun) ((void (__cdecl *)())id->getfun)();
res = *id->storage.s;
break;
case ID_ALIAS:
if(id->action) res = id->action;
break;
}
}
result(res);
}
COMMANDN(getalias, _getalias, "s");
#ifndef STANDALONE
void getvarrange(char *_what, char *name)
{
ident *id = idents->access(name);
const char *attrs[] = { "min", "max", "default", "" };
int what = getlistindex(_what, attrs, false, -1);
if(id)
{
int *i = NULL;
switch(what)
{
case 0: i = &(id->minval); break;
case 1: i = &(id->maxval); break;
case 2: i = &(id->defaultval); break;
}
if(i) switch(id->type)
{
case ID_VAR: intret(*i); return;
case ID_FVAR: floatret(*((float *) i), true); return;
}
}
result("");
}
COMMAND(getvarrange, "ss");
#endif
COMMANDF(isIdent, "s", (char *name) { intret(identexists(name) ? 1 : 0); });
bool addcommand(const char *name, void (*fun)(), const char *sig)
{
ASSERT(strlen(sig) < 9);
if(!idents) idents = new hashtable<const char *, ident>;
ident c(ID_COMMAND, name, fun, sig, IEXC_CORE);
idents->access(name, c);
return false;
}
char *parseexp(const char *&p, int right) // parse any nested set of () or []
{
int left = *p++;
const char *word = p;
bool quot = false;
for(int brak = 1; brak; )
{
int c = *p++;
if(c==left && !quot) brak++;
else if(c=='"') quot = !quot;
else if(c==right && !quot) brak--;
else if(!c)
{
p--;
conoutf("missing \"%c\"", right);
scripterr();
flagmapconfigerror(LWW_SCRIPTERR * 4);
return NULL;
}
}
char *s = newstring(word, p-word-1);
if(left=='(')
{
char *ret = executeret(s); // evaluate () exps directly, and substitute result
delete[] s;
s = ret ? ret : newstring("");
}
return s;
}
char *lookup(char *n) // find value of ident referenced with $ in exp
{
if(n[1] == '$') // nested ("$$var")
{
char *nn = lookup(newstring(n + 1));
delete[] n;
int nnl = strlen(nn);
n = newstring(nnl + 1);
n[0] = '$';
copystring(n + 1, nn, nnl + 1);
delete[] nn;
}
ident *id = idents->access(n+1);
if(id) switch(id->type)
{
case ID_VAR: { string t; itoa(t, *id->storage.i); return exchangestr(n, t); }
case ID_FVAR: return exchangestr(n, floatstr(*id->storage.f));
case ID_SVAR: { { if(id->getfun) ((void (__cdecl *)())id->getfun)(); } return exchangestr(n, *id->storage.s); }
case ID_ALIAS: return exchangestr(n, id->action);
}
conoutf("unknown alias lookup: %s", n+1);
scripterr();
flagmapconfigerror(LWW_SCRIPTERR * 4);
return n;
}
char *parseword(const char *&p, int arg, int &infix) // parse single argument, including expressions
{
p += strspn(p, " \t");
if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\r\0");
if(*p=='\"')
{
const char *word = p + 1;
do
{
p++;
p += strcspn(p, "\"\n\r");
}
while(*p == '\"' && p[-1] == '\\'); // skip escaped quotes
char *s = newstring(word, p - word);
if(*p=='\"') p++;
#ifndef STANDALONE
filterrichtext(s, s, strlen(s));
#endif
return s;
}
if(*p=='(') return parseexp(p, ')');
if(*p=='[') return parseexp(p, ']');
const char *word = p;
p += strcspn(p, "; \t\n\r\0");
if(p-word==0) return NULL;
if(arg==1 && p-word==1) switch(*word)
{
case '=': infix = *word; break;
}
char *s = newstring(word, p-word);
if(*s=='$') return lookup(s);
return s;
}
char *conc(const char **w, int n, bool space)
{
if(n < 0)
{ // auto-determine number of strings
n = 0;
while(w[n] && w[n][0]) n++;
}
static vector<int> wlen;
wlen.setsize(0);
int len = space ? max(n-1, 0) : 0;
loopj(n) len += wlen.add((int)strlen(w[j]));
char *r = newstring("", len), *res = r;
loopi(n)
{
strncpy(r, w[i], wlen[i]); // make string-list out of all arguments
r += wlen[i];
if(space) *r++ = ' ';
}
if(space && n) --r;
*r = '\0';
return res;
}
VARN(numargs, _numargs, 25, 0, 0);
void intret(int v)
{
string t;
itoa(t, v);
commandret = newstring(t);
}
const char *floatstr(float v, bool neat)
{
static char s[2 * MAXSTRLEN];
static int i = 0;
if(i > MAXSTRLEN - 10) i = 0;
char *t = s + i;
formatstring(t)(!neat && (v) == int(v) ? "%.1f" : "%.7g", v); // was ftoa()
i += strlen(t) + 1;
return t;
}
void floatret(float v, bool neat)
{
commandret = newstring(floatstr(v, neat));
}
void result(const char *s) { commandret = newstring(s); }
COMMAND(result, "s");
void resultcharvector(const vector<char> &res, int adj) // use char vector as result, optionally remove some bytes at the end
{
adj += res.length();
commandret = adj <= 0 ? newstring("", 0) : newstring(res.getbuf(), (size_t) adj);
}
char *executeret(const char *p) // all evaluation happens here, recursively
{
if(!p || !p[0]) return NULL;
executionstack.add(p);
const int MAXWORDS = 25; // limit, remove
char *w[MAXWORDS], emptychar = '\0';
char *retval = NULL;
#define setretval(v) { char *rv = v; if(rv) retval = rv; }
for(bool cont = true; cont;) // for each ; seperated statement
{
if(loop_level && loop_skip) break;
int numargs = MAXWORDS, infix = 0;
loopi(MAXWORDS) // collect all argument values
{
w[i] = &emptychar;
if(i>numargs) continue;
char *s = parseword(p, i, infix); // parse and evaluate exps
if(s) w[i] = s;
else numargs = i;
}
p += strcspn(p, ";\n\r\0");
cont = *p++!=0; // more statements if this isn't the end of the string
const char *c = w[0];
if(!*c) continue; // empty statement
DELETEA(retval);
if(infix)
{
switch(infix)
{
case '=':
DELETEA(w[1]);
swap(w[0], w[1]);
c = "alias";
break;
}
}
ident *id = idents->access(c);
if(!id)
{
if(!isdigit(*c) && ((*c!='+' && *c!='-' && *c!='.') || !isdigit(c[1])))
{
conoutf("unknown command: %s", c);
scripterr();
flagmapconfigerror(LWW_SCRIPTERR * 4);
}
setretval(newstring(c));
}
else if(identaccessdenied(id))
{
conoutf("not allowed in this execution context: %s", id->name);
scripterr();
flagmapconfigerror(LWW_SCRIPTERR * 4);
}
else
{
switch(id->type)
{
case ID_COMMAND: // game defined commands
{
if(strstr(id->sig, "v")) ((void (__cdecl *)(char **, int))id->fun)(&w[1], numargs-1);
else if(strstr(id->sig, "c") || strstr(id->sig, "w"))
{
char *r = conc((const char **)w+1, numargs-1, strstr(id->sig, "c") != NULL);
((void (__cdecl *)(char *))id->fun)(r);
delete[] r;
}
else if(strstr(id->sig, "d"))
{
#ifndef STANDALONE
((void (__cdecl *)(bool))id->fun)(addreleaseaction(id->name)!=NULL);
#endif
}
else
{
int ib1, ib2, ib3, ib4, ib5, ib6, ib7, ib8;
float fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8;
#define ARG(i) (id->sig[i-1] == 'i' ? ((void *)&(ib##i=strtol(w[i], NULL, 0))) : (id->sig[i-1] == 'f' ? ((void *)&(fb##i=atof(w[i]))) : (void *)w[i]))
switch(strlen(id->sig)) // use very ad-hoc function signature, and just call it
{
case 0: ((void (__cdecl *)())id->fun)(); break;
case 1: ((void (__cdecl *)(void*))id->fun)(ARG(1)); break;
case 2: ((void (__cdecl *)(void*, void*))id->fun)(ARG(1), ARG(2)); break;
case 3: ((void (__cdecl *)(void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3)); break;
case 4: ((void (__cdecl *)(void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4)); break;
case 5: ((void (__cdecl *)(void*, void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); break;
case 6: ((void (__cdecl *)(void*, void*, void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6)); break;
case 7: ((void (__cdecl *)(void*, void*, void*, void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7)); break;
case 8: ((void (__cdecl *)(void*, void*, void*, void*, void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8)); break;
default: fatal("command %s has too many arguments (signature: %s)", id->name, id->sig); break;
}
#undef ARG
}
setretval(commandret);
commandret = NULL;
break;
}
case ID_VAR: // game defined variables
if(!w[1][0]) conoutf("%s = %d", c, *id->storage.i); // var with no value just prints its current value
else if(id->minval>id->maxval) conoutf("variable %s is read-only", id->name);
else
{
int i1 = ATOI(w[1]);
if(i1<id->minval || i1>id->maxval)
{
i1 = i1<id->minval ? id->minval : id->maxval; // clamp to valid range
conoutf("valid range for %s is %d..%d", id->name, id->minval, id->maxval);
flagmapconfigerror(LWW_SCRIPTERR);
}
*id->storage.i = i1;
if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
}
break;
case ID_FVAR: // game defined variables
if(!w[1][0]) conoutf("%s = %s", c, floatstr(*id->storage.f)); // var with no value just prints its current value
else if(id->minvalf>id->maxvalf) conoutf("variable %s is read-only", id->name);
else
{
float f1 = atof(w[1]);
if(f1<id->minvalf || f1>id->maxvalf)
{
f1 = f1<id->minvalf ? id->minvalf : id->maxvalf; // clamp to valid range
conoutf("valid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf));
flagmapconfigerror(LWW_SCRIPTERR * 2);
}
*id->storage.f = f1;
if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
}
break;
case ID_SVAR: // game defined variables
if(!w[1][0])
{
if(id->getfun) ((void (__cdecl *)())id->getfun)();
conoutf(strchr(*id->storage.s, '"') ? "%s = [%s]" : "%s = \"%s\"", c, *id->storage.s); // var with no value just prints its current value
}
else
{
*id->storage.s = exchangestr(*id->storage.s, newstring(w[1]));
if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available
}
break;
case ID_ALIAS: // alias, also used as functions and (global) variables
delete[] w[0];
static vector<ident *> argids;
for(int i = 1; i<numargs; i++)
{
if(i > argids.length())
{
defformatstring(argname)("arg%d", i);
argids.add(newident(argname, IEXC_CORE));
}
pushident(*argids[i-1], w[i]); // set any arguments as (global) arg values so functions can access them
}
int old_numargs = _numargs;
_numargs = numargs-1;
char *wasexecuting = id->executing;
id->executing = id->action;
setretval(executeret(id->action));
if(id->executing!=id->action && id->executing!=wasexecuting) delete[] id->executing;
id->executing = wasexecuting;
_numargs = old_numargs;
for(int i = 1; i<numargs; i++) popident(*argids[i-1]);
continue;
}
}
loopj(numargs) if(w[j]) delete[] w[j];
}
executionstack.pop();
return retval;
}
int execute(const char *p)
{
char *ret = executeret(p);
int i = 0;
if(ret) { i = ATOI(ret); delete[] ret; }
return i;
}
#ifndef STANDALONE
bool exechook(int context, const char *ident, const char *body,...) // execute cubescript hook if available and allowed in current context/gamemode
{ // always use one of HOOK_SP_MP, HOOK_SP or HOOK_MP and then OR them (as needed) with HOOK_TEAM, HOOK_NOTEAM, HOOK_BOTMODE, HOOK_FLAGMODE, HOOK_ARENA
if(multiplayer(NULL) && (context & HOOK_FLAGMASK) != HOOK_MP && (context & HOOK_FLAGMASK) != HOOK_SP_MP) return false; // hook is singleplayer-only
if(((context & HOOK_TEAM) && !m_teammode) ||
((context & HOOK_NOTEAM) && m_teammode) ||
((context & HOOK_BOTMODE) && !m_botmode) ||
((context & HOOK_FLAGMODE) && m_flags) ||
((context & HOOK_ARENA) && m_arena)) return false; // wrong gamemode
if(identexists(ident))
{
defvformatstring(arglist, body, body);
defformatstring(execbody)("%s%c%s", ident, *arglist ? ' ' : '\0', arglist);
setcontext("hook", execbody);
execute(execbody);
resetcontext();
return true;
}
return false;
}
void identhash(uint64_t *d)
{
enumerate(*idents, ident, id, if(id.type == ID_COMMAND || id.type == ID_VAR) { *d ^= id.type == ID_VAR ? (size_t)"" - (size_t)id.name : (size_t)identhash - (size_t)id.fun; *d *= 16777619; });
}
// tab-completion of all idents
// always works at the end of the command line - the cursor position does not matter
static int completesize = -1, nickcompletesize = -1;
void resetcomplete()
{
nickcompletesize = completesize = -1;
}
bool nickcomplete(char *s, bool reversedirection)
{
static int nickcompleteidx;
char *cp = strrchr(s, ' '); // find last space
cp = cp ? cp + 1 : s;
if(nickcompletesize < 0)
{
nickcompletesize = (int)strlen(cp);
nickcompleteidx = reversedirection ? 0 : -1;
}
vector<int> matchingnames;
vector<const char *> matchingigs;
loopv(players) if(players[i] && !strncasecmp(players[i]->name, cp, nickcompletesize)) matchingnames.add(i); // find all matching player names first
if(nickcompletesize > 0 && *cp == ':') enumigraphs(matchingigs, cp + 1, nickcompletesize - 1); // find all matching igraphs
int totalmatches = matchingnames.length() + matchingigs.length();
if(totalmatches)
{
nickcompleteidx += reversedirection ? totalmatches - 1 : 1;
nickcompleteidx %= totalmatches;
bool isig = nickcompleteidx >= matchingnames.length();
const char *fillin = isig ? matchingigs[nickcompleteidx - matchingnames.length()] : players[matchingnames[nickcompleteidx]]->name;
if(*fillin == '/' && cp == s) *cp++ = ' ';
if(isig) cp++;
*cp = '\0';
concatstring(s, fillin);
return true;
}
return false;
}
enum { COMPLETE_FILE = 0, COMPLETE_LIST, COMPLETE_NICK };
struct completekey
{
int type;
const char *dir, *ext;
completekey() {}
completekey(int type, const char *dir, const char *ext) : type(type), dir(dir), ext(ext) {}
};
struct completeval
{
int type;
char *dir, *ext;
vector<char *> dirlist;
vector<char *> list;
completeval(int type, const char *dir, const char *ext) : type(type), dir(dir && dir[0] ? newstring(dir) : NULL), ext(ext && ext[0] ? newstring(ext) : NULL) {}
~completeval() { DELETEA(dir); DELETEA(ext); dirlist.deletearrays(); list.deletearrays(); }
};
static inline bool htcmp(const completekey &x, const completekey &y)
{
return x.type==y.type && (x.dir == y.dir || (x.dir && y.dir && !strcmp(x.dir, y.dir))) && (x.ext == y.ext || (x.ext && y.ext && !strcmp(x.ext, y.ext)));
}
static inline uint hthash(const completekey &k)
{
return k.dir ? hthash(k.dir) + k.type : k.type;
}
static hashtable<completekey, completeval *> completedata;
static hashtable<char *, completeval *> completions;
void addcomplete(char *command, int type, char *dir, char *ext)
{
if(type==COMPLETE_FILE)
{
int dirlen = (int)strlen(dir);
while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\'))
dir[--dirlen] = '\0';
if(ext)
{
if(strchr(ext, '*')) ext[0] = '\0';
if(!ext[0]) ext = NULL;
}
}
completekey key(type, dir, ext);
completeval **val = completedata.access(key);
if(!val)
{
completeval *f = new completeval(type, dir, ext);
if(type==COMPLETE_LIST) explodelist(dir, f->list);
if(type==COMPLETE_FILE)
{
explodelist(dir, f->dirlist);
loopv(f->dirlist)
{
char *dir = f->dirlist[i];
int dirlen = (int)strlen(dir);
while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\'))
dir[--dirlen] = '\0';
}
}
val = &completedata[completekey(type, f->dir, f->ext)];
*val = f;
}
completeval **hascomplete = completions.access(command);
if(hascomplete) *hascomplete = *val;
else completions[newstring(command)] = *val;
}
void addfilecomplete(char *command, char *dir, char *ext)
{
addcomplete(command, COMPLETE_FILE, dir, ext);
}
void addlistcomplete(char *command, char *list)
{
addcomplete(command, COMPLETE_LIST, list, NULL);
}
void addnickcomplete(char *command)
{
addcomplete(command, COMPLETE_NICK, NULL, NULL);
}
COMMANDN(complete, addfilecomplete, "sss");
COMMANDN(listcomplete, addlistcomplete, "ss");
COMMANDN(nickcomplete, addnickcomplete, "s");
void commandcomplete(char *s, bool reversedirection)
{ // s is required to be of size "string"!
static int completeidx;
if(*s != '/')
{
string t;
copystring(t, s);
copystring(s, "/");
concatstring(s, t);
}
if(!s[1]) return;
// find start position of last command
char *cmd = strrchr(s, ';'); // find last ';' (this will not always work properly, because it doesn't take quoted texts into account)
if(!cmd) cmd = s; // no ';' found: command starts after '/'
char *openblock = strrchr(cmd + 1, '('), *closeblock = strrchr(cmd + 1, ')'); // find last open and closed parenthesis
if(openblock && (!closeblock || closeblock < openblock)) cmd = openblock; // found opening parenthesis inside the command: assume, a new command starts here
cmd += strspn(cmd + 1, " ") + 1; // skip blanks and one of "/;( ", cmd now points to the first char of the command
// check, if the command is complete, and we want argument completion instead
char *arg = strrchr(cmd, ' '); // find last space in command -> if there is one, we use argument completion
completeval *cdata = NULL;
if(arg) // full command is present
{ // extract command name to find argument list
string command;
copystring(command, cmd);
command[strcspn(cmd, " ")] = '\0';
completeval **hascomplete = completions.access(command);
if(hascomplete) cdata = *hascomplete;
if(completesize < 0 && cdata && cdata->type == COMPLETE_FILE)
{ // get directory contents on first run
cdata->list.deletearrays();
vector<char *> files;
loopv(cdata->dirlist)
{
listfiles(cdata->dirlist[i], cdata->ext, files);
files.sort(stringsort);
loopv(files) cdata->list.add(files[i]);
files.setsize(0);
}
}
}
char *cp = arg ? arg + 1 : cmd; // part of string to complete
bool firstrun = false;
if(completesize < 0)
{ // first run since resetcomplete()
completesize = (int)strlen(cp);
completeidx = reversedirection ? 0 : -1;
firstrun = true;
}
if(!arg)
{ // commandname completion
vector<const char *> matchingidents;
enumerate(*idents, ident, id,
if(!strncasecmp(id.name, cp, completesize) && (id.type != ID_ALIAS || *id.action)) matchingidents.add(id.name); // find all matching possibilities to get the list length (and give an opportunity to sort the list first)
);
if(matchingidents.length())
{
completeidx += reversedirection ? matchingidents.length() - 1 : 1;
completeidx %= matchingidents.length();
matchingidents.sort(stringsortignorecase);
if(firstrun && !reversedirection && !strcmp(matchingidents[completeidx], cp)) completeidx = min(completeidx + 1, matchingidents.length() - 1);
*cp = '\0';
concatstring(s, matchingidents[completeidx]);
}
}
else if(!cdata) return;
else if(cdata->type == COMPLETE_NICK) nickcomplete(s, reversedirection);
else
{ // argument completion
vector<int> matchingargs;
loopv(cdata->list) if(!strncasecmp(cdata->list[i], cp, completesize)) matchingargs.add(i); // find all matching args first
if(matchingargs.length())
{
completeidx += reversedirection ? matchingargs.length() - 1 : 1;
completeidx %= matchingargs.length();
*cp = '\0';
concatstring(s, cdata->list[matchingargs[completeidx]]);
}
}
}
void complete(char *s, bool reversedirection)
{
if(*s == '/' || !nickcomplete(s, reversedirection))
{
commandcomplete(s, reversedirection);
}
}
#endif
bool execfile(const char *cfgfile)
{
string s;
copystring(s, cfgfile);
setcontext("file", cfgfile);
bool oldpersist = persistidents;
char *buf = loadfile(path(s), NULL);
if(!buf)
{
resetcontext();
return false;
}
execute(buf);
delete[] buf;
resetcontext();
persistidents = oldpersist;
return true;
}
void exec(const char *cfgfile)
{
if(!execfile(cfgfile)) conoutf("could not read \"%s\"", cfgfile);
}
COMMANDF(exec, "v", (char **args, int numargs)
{
defformatstring(buf)("%d", numargs - 1);
push("execnumargs", buf);
for(int i = 1; i < 24; i++) // MAXWORDS - 1
{
formatstring(buf)("execarg%d", i);
push(buf, i < numargs ? args[i] : "");
}
push("execresult", "");
exec(numargs > 0 ? args[0] : "");
const char *res = getalias("execresult");
if(res) result(res);
pop("execresult");
for(int i = 23; i > 0; i--)
{
formatstring(buf)("execarg%d", i);
pop(buf);
}
pop("execnumargs");
});
COMMANDF(execute, "s", (char *s) { intret(execute(s)); });
void execdir(const char *dir)
{
if(dir[0])
{
vector<char *> files;
listfiles(dir, "cfg", files);
loopv(files)
{
defformatstring(d)("%s/%s.cfg",dir,files[i]);
exec(d);
delstring(files[i]);
}
}
}
COMMAND(execdir, "s");
// below the commands that implement a small imperative language. thanks to the semantics of
// () and [] expressions, any control construct can be defined trivially.
void ifthen(char *cond, char *thenp, char *elsep) { commandret = executeret(cond[0]!='0' ? thenp : elsep); }
COMMANDN(if, ifthen, "sss");
void loopa(char *var, int *times, char *body)
{
int t = *times;
if(t<=0) return;
ident *id = newident(var, execcontext);
if(id->type!=ID_ALIAS) return;
char *buf = newstring("0", 16);
pushident(*id, buf);
loop_level++;
execute(body);
loop_skip = false;
if(loop_break) loop_break = false;
else
{
loopi(t-1)
{
if(buf != id->action)
{
if(id->action != id->executing) delete[] id->action;
id->action = buf = newstring(16);
}
itoa(id->action, i+1);
execute(body);
loop_skip = false;
if(loop_break)
{
loop_break = false;
break;
}
}
}
popident(*id);
loop_level--;
}
COMMANDN(loop, loopa, "sis");
void whilea(char *cond, char *body)
{
loop_level++;
while(execute(cond))
{
execute(body);
loop_skip = false;
if(loop_break)
{
loop_break = false;
break;
}
}
loop_level--;
}
COMMANDN(while, whilea, "ss");
COMMANDF(break, "", () { if(loop_level) loop_skip = loop_break = true; });
COMMANDF(continue, "", () { if(loop_level) loop_skip = true; });
COMMANDF(concat, "c", (const char *s) { result(s); });
COMMANDF(concatword, "w", (const char *s) { result(s); });
void format(char **args, int numargs)
{
vector<char> s;
const char *f = numargs > 0 ? args[0] : "";
while(*f)
{
int c = *f++;
if(c == '%')
{
int i = *f++;
if(i >= '1' && i <= '9')
{
i -= '0';
const char *sub = i < numargs ? args[i] : "";
while(*sub) s.add(*sub++);
}
else s.add(i);
}
else s.add(c);
}
resultcharvector(s, 0);
}
COMMAND(format, "v");
#define whitespaceskip do { s += strspn(s, "\n\r\t "); } while(s[0] == '/' && s[1] == '/' && (s += strcspn(s, "\n\r\0")))
#define elementskip { if(*s=='"') { do { ++s; s += strcspn(s, "\"\n\r"); } while(*s == '\"' && s[-1] == '\\'); s += *s=='"'; } else s += strcspn(s, "\n\r\t "); }
void explodelist(const char *s, vector<char *> &elems)
{
whitespaceskip;
while(*s)
{
const char *elem = s;
elementskip;
char *newelem = *elem == '"' ? newstring(elem + 1, s - elem - (s[-1]=='"' ? 2 : 1)) : newstring(elem, s-elem);
#ifndef STANDALONE
if(*elem == '\"') filterrichtext(newelem, newelem, strlen(newelem));
#endif
elems.add(newelem);
whitespaceskip;
}
}
void looplist(char *list, char *varlist, char *body, bool withi)
{
vector<char *> vars;
explodelist(varlist, vars);
if(vars.length() < 1) return;
int varslen = vars.length();
if(withi) vars.add(newstring("i"));
vector<ident *> ids;
bool ok = true;
loopv(vars) if(ids.add(newident(vars[i]))->type != ID_ALIAS) { conoutf("looplist error: \"%s\" is readonly", vars[i]); ok = false; }
if(ok)
{
int ii = 0;
vector<char *> elems;
explodelist(list, elems);
loopv(ids) pushident(*ids[i], newstring(""));
loop_level++;
for(int i = 0; i <= elems.length() - varslen; i += varslen)
{
loopj(vars.length())
{
if(ids[j]->action != ids[j]->executing) delete[] ids[j]->action;
if(j < varslen)
{
ids[j]->action = elems[i + j];
elems[i + j] = NULL;
}
else
{
defformatstring(sii)("%d", ii++);
ids[j]->action = newstring(sii);
}
}
execute(body);
loop_skip = false;
if(loop_break) break;
}
loopv(ids) popident(*ids[i]);
loopv(elems) if(elems[i]) delete[] elems[i];
loop_break = false;
loop_level--;
}
loopv(vars) delete[] vars[i];
}
COMMANDF(looplist, "sss", (char *list, char *varlist, char *body) { looplist(list, varlist, body, false); });
COMMANDF(looplisti, "sss", (char *list, char *varlist, char *body) { looplist(list, varlist, body, true); });
char *indexlist(const char *s, int pos)
{
whitespaceskip;
loopi(pos)
{
elementskip;
whitespaceskip;
if(!*s) break;
}
const char *e = s;
char *res;
elementskip;
if(*e=='"')
{
e++;
if(s[-1]=='"') --s;
res = newstring(e, s - e);
#ifndef STANDALONE
filterrichtext(res, res, s - e);
#endif
}
else res = newstring(e, s-e);
return res;
}
COMMANDF(at, "si", (char *s, int *pos) { commandret = indexlist(s, *pos); });
int listlen(const char *s)
{
int n = 0;
whitespaceskip;
for(; *s; n++) { elementskip; whitespaceskip; }
return n;
}
COMMANDF(listlen, "s", (char *l) { intret(listlen(l)); });
int find(char *s, const char *key)
{
whitespaceskip;
int len = strlen(key);
for(int i = 0; *s; i++)
{
char *e = s;
elementskip;
char *a = s;
if(*e == '"')
{
e++;
if(s[-1] == '"') --s;
if(s - e >= len)
{
*s = '\0';
#ifndef STANDALONE
filterrichtext(e, e, s - e);
#endif
if(int(strlen(e)) == len && !strncmp(e, key, len)) return i;
*s = ' ';
}
}
else if(s - e == len && !strncmp(e, key, s - e)) return i;
s = a;
whitespaceskip;
}
return -1;
}
COMMANDF(findlist, "ss", (char *s, char *key) { intret(find(s, key)); });
#ifndef STANDALONE
// Easily inject a string into various CubeScript punctuations
const char *punctnames[] = { "QUOTES", "BRACKETS", "PARENTHESIS", "_$_", "QUOTE", "PERCENT", "" };
void addpunct(char *s, char *type)
{
int t = getlistindex(type, punctnames, true, 0);
const char *puncts[] = { "\"%s\"", "[%s]", "(%s)", "$%s", "\"", "%" }, *punct = puncts[t];
if(strchr(punct, 's'))
{
defformatstring(res)(punct, s);
result(res);
}
else result(punct);
}
COMMAND(addpunct, "ss");
#endif
void toany(char *s, int (*c)(int)) { while(*s) { *s = (*c)(*s); s++; } }
COMMANDF(tolower, "s", (char *s) { toany(s, tolower); result(s); });
COMMANDF(toupper, "s", (char *s) { toany(s, toupper); result(s); });
void testchar(char *s, int *type)
{
bool istrue = false;
switch(*type) {
case 1:
if(isalpha(s[0]) != 0) { istrue = true; }
break;
case 2:
if(isalnum(s[0]) != 0) { istrue = true; }
break;
case 3:
if(islower(s[0]) != 0) { istrue = true; }
break;
case 4:
if(isupper(s[0]) != 0) { istrue = true; }
break;
case 5:
if(isprint(s[0]) != 0) { istrue = true; }
break;
case 6:
if(ispunct(s[0]) != 0) { istrue = true; }
break;
case 7:
if(isspace(s[0]) != 0) { istrue = true; }
break;
case 8: // Without this it is impossible to determine if a character === " in cubescript
if(!strcmp(s, "\"")) { istrue = true; }
break;
default:
if(isdigit(s[0]) != 0) { istrue = true; }
break;
}
intret(istrue ? 1 : 0);
}
COMMAND(testchar, "si");
char *strreplace(char *dest, const char *source, const char *search, const char *replace)
{
vector<char> buf;
int searchlen = strlen(search);
if(!searchlen) { copystring(dest, source); return dest; }
for(;;)
{
const char *found = strstr(source, search);
if(found)
{
while(source < found) buf.add(*source++);
for(const char *n = replace; *n; n++) buf.add(*n);
source = found + searchlen;
}
else
{
while(*source) buf.add(*source++);
buf.add('\0');
return copystring(dest, buf.getbuf());
}
}
}
COMMANDF(strreplace, "sss", (const char *source, const char *search, const char *replace) { string d; result(strreplace(d, source, search, replace)); });
void sortlist(char *list)
{
vector<char *> elems;
explodelist(list, elems);
elems.sort(stringsort);
commandret = conc((const char **)elems.getbuf(), elems.length(), true);
elems.deletearrays();
}
COMMAND(sortlist, "c");
void swapelements(char *list, char *v)
{
vector<char *> elems;
explodelist(list, elems);
vector<char *> swap;
explodelist(v, swap);
vector<int> swapi;
loopv(swap) swapi.add(atoi(swap[i]));
if(swapi.length() && !(swapi.length() & 1)) // swap needs to have an even number of elements
{
for(int i = 0; i < swapi.length(); i += 2)
{
if (elems.inrange(swapi[i]) && elems.inrange(swapi[i + 1]))
{
char *tmp = elems[swapi[i]];
elems[swapi[i]] = elems[swapi[i + 1]];
elems[swapi[i + 1]] = tmp;
}
}
}
commandret = conc((const char **)elems.getbuf(), elems.length(), true);
elems.deletearrays();
swap.deletearrays();
}
COMMAND(swapelements, "ss");
void add_(char **args, int numargs)
{
int sum = 0;
loopi(numargs) sum += ATOI(args[i]);
intret(sum);
}
COMMANDN(+, add_, "v");
void mul_(char **args, int numargs)
{
int prod = 1;
if(numargs < 2) numargs = 2; // emulate classic (* ) = 0, (* 2) = 0, (* 2 3) = 6
loopi(numargs) prod *= ATOI(args[i]);
intret(prod);
}
COMMANDN(*, mul_, "v");
void addf_(char **args, int numargs)
{
float sum = 0;
loopi(numargs) sum += atof(args[i]);
floatret(sum);
}
COMMANDN(+f, addf_, "v");
void mulf_(char **args, int numargs)
{
float prod = 1;
if(numargs < 2) numargs = 2; // emulate classic (* ) = 0, (* 2) = 0, (* 2 3) = 6
loopi(numargs) prod *= atof(args[i]);
floatret(prod);
}
COMMANDN(*f, mulf_, "v");
void sub_(int *a, int *b) { intret(*a - *b); } COMMANDN(-, sub_, "ii");
void div_(int *a, int *b) { intret(*b ? (*a)/(*b) : 0); } COMMANDN(div, div_, "ii");
void mod_(int *a, int *b) { intret(*b ? (*a)%(*b) : 0); } COMMANDN(mod, mod_, "ii");
void subf_(float *a, float *b) { floatret(*a - *b); } COMMANDN(-f, subf_, "ff");
void divf_(float *a, float *b) { floatret(*b ? (*a)/(*b) : 0); } COMMANDN(divf, divf_, "ff");
void modf_(float *a, float *b) { floatret(*b ? fmod(*a, *b) : 0); } COMMANDN(modf, modf_, "ff");
void powf_(float *a, float *b) { floatret(powf(*a, *b)); } COMMANDN(powf, powf_, "ff");
void not_(int *a) { intret((int)(!(*a))); } COMMANDN(!, not_, "i");
void equal_(int *a, int *b) { intret((int)(*a == *b)); } COMMANDN(=, equal_, "ii");
void notequal_(int *a, int *b) { intret((int)(*a != *b)); } COMMANDN(!=, notequal_, "ii");
void lt_(int *a, int *b) { intret((int)(*a < *b)); } COMMANDN(<, lt_, "ii");
void gt_(int *a, int *b) { intret((int)(*a > *b)); } COMMANDN(>, gt_, "ii");
void lte_(int *a, int *b) { intret((int)(*a <= *b)); } COMMANDN(<=, lte_, "ii");
void gte_(int *a, int *b) { intret((int)(*a >= *b)); } COMMANDN(>=, gte_, "ii");
void round_(float *a) { intret(int(*a + 0.5f)); } COMMANDN(round, round_, "f");
void ceil_(float *a) { intret((int)ceil(*a)); } COMMANDN(ceil, ceil_, "f");
void floor_(float *a) { intret((int)floor(*a)); } COMMANDN(floor, floor_, "f");
#define COMPAREF(opname, func, op) \
void func(float *a, float *b) { intret((int)((*a) op (*b))); } \
COMMANDN(opname, func, "ff")
COMPAREF(=f, equalf_, ==);
COMPAREF(!=f, notequalf_, !=);
COMPAREF(<f, ltf_, <);
COMPAREF(>f, gtf_, >);
COMPAREF(<=f, ltef_, <=);
COMPAREF(>=f, gtef_, >=);
void anda_(char *a, char *b) { intret(execute(a)!=0 && execute(b)!=0); } COMMANDN(&&, anda_, "ss");
void ora_(char *a, char *b) { intret(execute(a)!=0 || execute(b)!=0); } COMMANDN(||, ora_, "ss");
void band_(int *a, int *b) { intret((*a) & (*b)); } COMMANDN(&b, band_, "ii");
void bor_(int *a, int *b) { intret((*a) | (*b)); } COMMANDN(|b, bor_, "ii");
void bxor_(int *a, int *b) { intret((*a) ^ (*b)); } COMMANDN(^b, bxor_, "ii");
void bnot_(int *a) { intret(~(*a)); } COMMANDN(!b, bnot_, "i");
COMMANDF(strcmp, "ss", (char *a, char *b) { intret((strcmp(a, b) == 0) ? 1 : 0); });
COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); });
#ifndef STANDALONE
const char *escapestring(const char *s, bool force, bool noquotes)
{
static vector<char> strbuf[3];
static int stridx = 0;
if(noquotes) force = false;
if(!s) return force ? "\"\"" : "";
if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\n\r$"))) return s;
stridx = (stridx + 1) % 3;
vector<char> &buf = strbuf[stridx];
buf.setsize(0);
if(!noquotes) buf.add('"');
for(; *s; s++) switch(*s)
{
case '\n': buf.put("\\n", 2); break;
case '\r': buf.put("\\n", 2); break;
case '\t': buf.put("\\t", 2); break;
case '\a': buf.put("\\a", 2); break;
case '\f': buf.put("\\f", 2); break;
case '"': buf.put("\\\"", 2); break;
case '\\': buf.put("\\\\", 2); break;
default: buf.add(*s); break;
}
if(!noquotes) buf.add('"');
buf.add(0);
return buf.getbuf();
}
COMMANDF(escape, "s", (const char *s) { result(escapestring(s));});
int sortident(ident **a, ident **b) { return strcasecmp((*a)->name, (*b)->name); }
void enumalias(char *prefix)
{
vector<char> res;
vector<ident *> sids;
size_t np = strlen(prefix);
enumerate(*idents, ident, id, if(id.type == ID_ALIAS && id.persist && !id.isconst && !id.istemp && !strncmp(id.name, prefix, np)) sids.add(&id); );
sids.sort(sortident);
loopv(sids) cvecprintf(res, "%s %s\n", sids[i]->name, sids[i]->name + np);
resultcharvector(res, -1);
}
COMMAND(enumalias, "s");
VARP(omitunchangeddefaults, 0, 0, 1);
VAR(groupvariables, 0, 4, 10);
void writecfg()
{
filerotate("config/saved", "cfg", CONFIGROTATEMAX); // keep five old config sets
stream *f = openfile(path("config/saved.cfg", true), "w");
if(!f) return;
f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have defaults.cfg overwrite these settings\n// modify settings in game, or put settings in autoexec.cfg to override anything\n\n");
f->printf("// basic settings\n\n");
f->printf("name %s\n", escapestring(player1->name, false));
f->printf("skin_cla %d\nskin_rvsf %d\n", player1->skin(TEAM_CLA), player1->skin(TEAM_RVSF));
for(int i = CROSSHAIR_DEFAULT; i < CROSSHAIR_NUM; i++) if(crosshairs[i] && crosshairs[i] != notexture)
{
f->printf("loadcrosshair %s %s\n", crosshairnames[i], behindpath(crosshairs[i]->name));
}
extern int lowfps, highfps;
f->printf("fpsrange %d %d\n", lowfps, highfps);
if(curfont && curfont->name) f->printf("setfont %s\n", curfont->name);
f->printf("\n");
audiomgr.writesoundconfig(f);
f->printf("\n");
f->printf("// crosshairs for each weapon\n\nlooplist [\n");
loopi(NUMGUNS) f->printf(" %-7s %s\n", gunnames[i], crosshairs[i] && crosshairs[i] != notexture ? behindpath(crosshairs[i]->name) : "\"\"");
f->printf("] [ w cc ] [ loadcrosshair $w $cc ]\n");
f->printf("\n\n// client variables (unchanged default values %s)\n", omitunchangeddefaults ? "omitted" : "commented out");
vector<ident *> sids;
enumerate(*idents, ident, id,
if(id.persist) switch(id.type)
{
case ID_VAR:
case ID_FVAR:
case ID_SVAR:
sids.add(&id);
break;
}
);
sids.sort(sortident);
const char *rep = "";
int repn = 0;
bool lastdef = false, curdef;
loopv(sids)
{
ident &id = *sids[i];
curdef = (id.type == ID_VAR && *id.storage.i == id.defaultval) || (id.type == ID_FVAR && *id.storage.f == id.defaultvalf);
if(curdef && omitunchangeddefaults) continue;
f->printf("%s", !strncmp(rep, id.name, curdef ? 1 : 3) && ++repn < groupvariables && lastdef == curdef ? " ; " : (repn = 0, "\n"));
rep = id.name;
lastdef = curdef;
if(curdef && repn == 0) f->printf("// ");
switch(id.type)
{
case ID_VAR: f->printf("%s %d", id.name, *id.storage.i); break;
case ID_FVAR: f->printf("%s %s", id.name, floatstr(*id.storage.f)); break;
case ID_SVAR: f->printf("%s %s", id.name, escapestring(*id.storage.s, false)); break;
}
if(!groupvariables)
{
if(id.type == ID_VAR) f->printf(" // min: %d, max: %d, def: %d", id.minval, id.maxval, id.defaultval);
if(id.type == ID_FVAR) f->printf(" // min: %s, max: %s, def: %s", floatstr(id.minvalf), floatstr(id.maxvalf), floatstr(id.defaultvalf));
const char *doc = docgetdesc(id.name);
if(doc) f->printf(id.type == ID_SVAR ? " // %s" : ", %s", doc);
}
}
f->printf("\n\n// weapon settings\n\n");
loopi(NUMGUNS) if(guns[i].isauto)
{
f->printf("burstshots %s %d\n", gunnames[i], burstshotssettings[i]);
}
f->printf("\n// key binds\n\n");
writebinds(f);
f->printf("\n// aliases\n\n");
sids.setsize(0);
enumerate(*idents, ident, id, if(id.type == ID_ALIAS && id.persist) sids.add(&id); );
sids.sort(sortident);
loopv(sids)
{
ident &id = *sids[i];
if(strncmp(id.name, "demodesc_", 9))
{
const char *action = id.action;
for(identstack *s = id.stack; s; s = s->next) action = s->action;
if(action[0]) f->printf("alias %s %s\n", escapestring(id.name, false), escapestring(action, false));
sids.remove(i--);
}
}
if(sids.length())
{
f->printf("\n// demo descriptions\n\n");
loopv(sids)
{
ident &id = *sids[i];
const char *action = id.action;
for(identstack *s = id.stack; s; s = s->next) action = s->action;
if(action[0]) f->printf("alias %s %s\n", escapestring(id.name, false), escapestring(action, false));
}
}
f->printf("\n");
delete f;
}
COMMAND(writecfg, "");
void deletecfg()
{
string configs[] = { "config/saved.cfg", "config/init.cfg" };
loopj(2) // delete files in homedir and basedir if possible
{
loopi(sizeof(configs)/sizeof(configs[0]))
{
const char *file = findfile(path(configs[i], true), "r");
if(!file || findfilelocation == FFL_ZIP) continue;
delfile(file);
}
}
}
#endif
void identnames(vector<const char *> &names, bool builtinonly)
{
enumeratekt(*idents, const char *, name, ident, id,
{
if(!builtinonly || id.type != ID_ALIAS) names.add(name);
});
}
// script execution context management
const char *contextnames[IEXC_NUM + 1] = { "CORE", "CFG", "PROMPT", "MAPCFG", "MDLCFG", "" };
bool contextisolated[IEXC_NUM] = { false };
bool contextsealed = false;
vector<int> contextstack;
int execcontext;
void pushscontext(int newcontext)
{
contextstack.add(execcontext);
execcontext = newcontext;
}
int popscontext()
{
ASSERT(contextstack.length() > 0);
int old = execcontext;
execcontext = contextstack.pop();
if(execcontext < old && old >= IEXC_MAPCFG) // clean up aliases created in the old (map cfg) context
{
int limitcontext = max(execcontext + 1, (int) IEXC_MAPCFG); // don't clean up below IEXC_MAPCFG
enumeratekt(*idents, const char *, name, ident, id,
{
if(id.type == ID_ALIAS && id.context >= limitcontext)
{
while(id.stack && id.stack->context >= limitcontext)
popident(id);
if(id.context >= limitcontext)
{
if(id.action != id.executing) delete[] id.action;
idents->remove(name);
}
}
});
}
return execcontext;
}
void scriptcontext(char *context, char *idname)
{
if(contextsealed) return;
ident *id = idents->access(idname);
if(!id) return;
int c = getlistindex(context, contextnames, true, -1);
if(c >= 0 && c < IEXC_NUM) id->context = c;
}
COMMAND(scriptcontext, "ss");
void isolatecontext(char *context)
{
int c = getlistindex(context, contextnames, true, -1);
if(c >= 0 && c < IEXC_NUM && !contextsealed) contextisolated[c] = true;
}
COMMAND(isolatecontext, "s");
COMMANDF(sealcontexts, "",() { contextsealed = true; });
inline bool identaccessdenied(ident *id) // check if ident is allowed in current context
{
ASSERT(execcontext >= 0 && execcontext < IEXC_NUM && id);
return contextisolated[execcontext] && execcontext > id->context;
}
// script origin tracking
const char *curcontext = NULL, *curinfo = NULL;
void scripterr()
{
ASSERT(execcontext >= 0 && execcontext < IEXC_NUM);
if(curcontext) conoutf("(%s: %s [%s])", curcontext, curinfo, contextnames[execcontext]);
else conoutf("(from console or builtin [%s])", contextnames[execcontext]);
}
void setcontext(const char *context, const char *info)
{
curcontext = context;
curinfo = info;
}
void resetcontext()
{
curcontext = curinfo = NULL;
}
void dumpexecutionstack(stream *f)
{
if(f && executionstack.length())
{
f->printf("cubescript execution stack (%d levels)\n", executionstack.length());
if(curcontext) f->printf(" cubescript context: %s %s\n", curcontext, curinfo ? curinfo : "");
loopvrev(executionstack) f->printf("%4d: %s\n", i + 1, executionstack[i]);
f->fflush();
}
}
#ifndef STANDALONE
COMMANDF(watchingdemo, "", () { intret(watchingdemo); });
COMMANDF(systime, "", () { result(numtime()); });
COMMANDF(timestamp, "", () { result(timestring(true, "%Y %m %d %H %M %S")); });
COMMANDF(datestring, "", () { result(timestring(true, "%c")); });
COMMANDF(timestring, "", ()
{
const char *res = timestring(true, "%H:%M:%S");
result(res[0] == '0' ? res + 1 : res);
});
COMMANDF(millis, "", () { intret(totalmillis); });
COMMANDF(strlen, "s", (char *s) { intret(strlen(s)); });
void substr_(char *fs, int *pa, int *len)
{
int ia = *pa;
int ilen = *len;
int fslen = (int)strlen(fs);
if(ia<0) ia += fslen;
if(ia>fslen || ia < 0 || ilen < 0) return;
if(!ilen) ilen = fslen-ia;
if(ilen >= 0 && ilen < int(strlen(fs+ia))) (fs+ia)[ilen] = '\0';
result(fs+ia);
}
COMMANDN(substr, substr_, "sii");
void strpos_(char *haystack, char *needle, int *occurence)
{
int position = -1;
char *ptr = haystack;
if(haystack && needle)
for(int iocc = *occurence; iocc >= 0; iocc--)
{
ptr = strstr(ptr, needle);
if (ptr)
{
position = ptr-haystack;
ptr += strlen(needle);
}
else
{
position = -1;
break;
}
}
intret(position);
}
COMMANDN(strpos, strpos_, "ssi");
COMMANDF(l0, "ii", (int *p, int *v) { defformatstring(f)("%%0%dd", clamp(*p, 0, 200)); defformatstring(r)(f, *v); result(r); });
COMMANDF(h0, "ii", (int *p, int *v) { defformatstring(f)("%%0%dx", clamp(*p, 0, 200)); defformatstring(r)(f, *v); result(r); });
COMMANDF(getmode, "i", (int *acr) { result(modestr(gamemode, *acr != 0)); });
void listoptions(char *s)
{
extern const char *menufilesortorders[], *texturestacktypes[], *soundprioritynames[], *soundcategories[];
const char *optionnames[] = { "entities", "ents", "weapons", "teamnames", "teamnames-abbrv", "punctuations", "crosshairnames", "menufilesortorders", "texturestacktypes", "cubetypes", "soundpriorities", "soundcategories", "" };
const char **optionlists[] = { optionnames, entnames + 1, entnames + 1, gunnames, teamnames, teamnames_s, punctnames, crosshairnames, menufilesortorders, texturestacktypes, cubetypenames, soundprioritynames, soundcategories };
const char **listp = optionlists[getlistindex(s, optionnames, true, -1) + 1];
commandret = conc(listp, -1, true);
}
COMMAND(listoptions, "s");
const char *currentserver(int i) // [client version]
{
static string curSRVinfo;
// using the curpeer directly we can get the info of our currently connected server
string r;
r[0] = '\0';
extern ENetPeer *curpeer;
if(curpeer)
{
switch(i)
{
case 1: // IP
{
uchar *ip = (uchar *)&curpeer->address.host;
formatstring(r)("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
break;
}
case 2: // HOST
{
char hn[1024];
formatstring(r)("%s", (enet_address_get_host(&curpeer->address, hn, sizeof(hn))==0) ? hn : "unknown");
break;
}
case 3: // PORT
{
formatstring(r)("%d", curpeer->address.port);
break;
}
case 4: // STATE
{
const char *statenames[] =
{
"disconnected",
"connecting",
"acknowledging connect",
"connection pending",
"connection succeeded",
"connected",
"disconnect later",
"disconnecting",
"acknowledging disconnect",
"zombie"
};
if(curpeer->state>=0 && curpeer->state<int(sizeof(statenames)/sizeof(statenames[0])))
copystring(r, statenames[curpeer->state]);
break; // 5 == Connected (compare ../enet/include/enet/enet.h +165)
}
// CAUTION: the following are only filled if the serverbrowser was used or the scoreboard shown
// SERVERNAME
case 5: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->name); break; }
// DESCRIPTION (3)
case 6: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->sdesc); break; }
case 7: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->description); break; }
// CAUTION: the following is only the last full-description _seen_ in the serverbrowser!
case 8: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->full); break; }
// just IP & PORT as default response - always available, no lookup-delay either
default:
{
uchar *ip = (uchar *)&curpeer->address.host;
formatstring(r)("%d.%d.%d.%d %d", ip[0], ip[1], ip[2], ip[3], curpeer->address.port);
break;
}
}
}
copystring(curSRVinfo, r);
return curSRVinfo;
}
COMMANDF(curserver, "i", (int *i) { result(currentserver(*i)); });
#endif
void debugargs(char **args, int numargs)
{
printf("debugargs: ");
loopi(numargs)
{
if(i) printf(", ");
printf("\"%s\"", args[i]);
}
printf("\n");
}
COMMAND(debugargs, "v");
#ifndef STANDALONE
#ifdef _DEBUG
void debugline(char *fname, char *line) // print one line to a logfile
{
if(!multiplayer())
{
stream *f = openfile(behindpath(fname), "a");
if(f) f->printf("%s\n", line);
if(f) delete f;
}
}
COMMAND(debugline, "ss");
#endif
#endif