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
1902 lines
62 KiB
C++
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
|