1 // command.cpp: implements the parsing and execution of a tiny script language which
2 // is largely backwards compatible with the quake console language.
3 
4 #include "cube.h"
5 
6 bool allowidentaccess(ident *id);
exchangestr(char * o,const char * n)7 char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); }
8 void scripterr();
9 
10 vector<int> contextstack;
11 bool contextsealed = false;
12 bool contextisolated[IEXC_NUM] = { false };
13 int execcontext;
14 
15 bool loop_break = false, loop_skip = false;             // break or continue (skip) current loop
16 int loop_level = 0;                                      // avoid bad calls of break & continue
17 
18 hashtable<const char *, ident> *idents = NULL;          // contains ALL vars/commands/aliases
19 
20 VAR(persistidents, 0, 1, 1);
21 
22 bool per_idents = true, neverpersist = false;
23 COMMANDF(per_idents, "i", (int *on) {
24     per_idents = neverpersist ? false : (*on != 0);
25 });
26 
clearstack(ident & id)27 void clearstack(ident &id)
28 {
29     identstack *stack = id.stack;
30     while(stack)
31     {
32         delete[] stack->action;
33         identstack *tmp = stack;
34         stack = stack->next;
35         delete tmp;
36     }
37     id.stack = NULL;
38 }
39 
pushident(ident & id,char * val,int context=execcontext)40 void pushident(ident &id, char *val, int context = execcontext)
41 {
42     if(id.type != ID_ALIAS) return;
43     identstack *stack = new identstack;
44     stack->action = id.executing==id.action ? newstring(id.action) : id.action;
45     stack->context = id.context;
46     stack->next = id.stack;
47     id.stack = stack;
48     id.action = val;
49     id.context = context;
50 }
51 
popident(ident & id)52 void popident(ident &id)
53 {
54     if(id.type != ID_ALIAS || !id.stack) return;
55     if(id.action != id.executing) delete[] id.action;
56     identstack *stack = id.stack;
57     id.action = stack->action;
58     id.stack = stack->next;
59     id.context = stack->context;
60     delete stack;
61 }
62 
newident(const char * name,int context=execcontext)63 ident *newident(const char *name, int context = execcontext)
64 {
65     ident *id = idents->access(name);
66     if(!id)
67     {
68         ident init(ID_ALIAS, newstring(name), newstring(""), per_idents, context);
69         id = &idents->access(init.name, init);
70     }
71     return id;
72 }
73 
pusha(const char * name,char * action)74 void pusha(const char *name, char *action)
75 {
76     ident *id = newident(name, execcontext);
77     if(contextisolated[execcontext] && execcontext > id->context)
78     {
79         conoutf("cannot redefine alias %s in this execution context", id->name);
80         scripterr();
81         return;
82     }
83     pushident(*id, action);
84 }
85 
push(const char * name,const char * action)86 void push(const char *name, const char *action)
87 {
88     pusha(name, newstring(action));
89 }
90 
pop(const char * name)91 void pop(const char *name)
92 {
93     ident *id = idents->access(name);
94     if(!id) return;
95     if(contextisolated[execcontext] && execcontext > id->context)
96     {
97         conoutf("cannot redefine alias %s in this execution context", id->name);
98         scripterr();
99         return;
100     }
101     popident(*id);
102 }
103 
104 COMMAND(push, "ss");
105 COMMAND(pop, "s");
delalias(const char * name)106 void delalias(const char *name)
107 {
108     ident *id = idents->access(name);
109     if(!id || id->type != ID_ALIAS) return;
110     if(contextisolated[execcontext] && execcontext > id->context)
111     {
112         conoutf("cannot remove alias %s in this execution context", id->name);
113         scripterr();
114         return;
115     }
116     idents->remove(name);
117 }
118 COMMAND(delalias, "s");
119 
alias(const char * name,const char * action,bool constant)120 void alias(const char *name, const char *action, bool constant)
121 {
122     ident *b = idents->access(name);
123     if(!b)
124     {
125         ident b(ID_ALIAS, newstring(name), newstring(action), persistidents && !constant, execcontext);
126         b.isconst = constant;
127         idents->access(b.name, b);
128         return;
129     }
130     else if(b->type==ID_ALIAS)
131     {
132         if(contextisolated[execcontext] && execcontext > b->context)
133         {
134             conoutf("cannot redefine alias %s in this execution context", b->name);
135             scripterr();
136             return;
137         }
138 
139         if(b->isconst)
140         {
141             conoutf("alias %s is a constant and cannot be redefined", b->name);
142             scripterr();
143             return;
144         }
145 
146         b->isconst = constant;
147         if(!constant || (action && action[0]))
148         {
149             if(b->action!=b->executing) delete[] b->action;
150             b->action = newstring(action);
151             b->persist = persistidents != 0;
152         }
153     }
154     else
155     {
156         conoutf("cannot redefine builtin %s with an alias", name);
157         scripterr();
158     }
159 }
160 
161 COMMANDF(alias, "ss", (const char *name, const char *action) { alias(name, action, false); });
162 COMMANDF(const, "ss", (const char *name, const char *action) { alias(name, action, true); });
163 
164 COMMANDF(checkalias, "s", (const char *name) { intret(getalias(name) ? 1 : 0); });
165 COMMANDF(isconst, "s", (const char *name) { ident *id = idents->access(name); intret(id && id->isconst ? 1 : 0); });
166 
167 // variable's and commands are registered through globals, see cube.h
168 
variable(const char * name,int minval,int cur,int maxval,int * storage,void (* fun)(),bool persist)169 int variable(const char *name, int minval, int cur, int maxval, int *storage, void (*fun)(), bool persist)
170 {
171     if(!idents) idents = new hashtable<const char *, ident>;
172     ident v(ID_VAR, name, minval, maxval, storage, fun, persist, IEXC_CORE);
173     idents->access(name, v);
174     return cur;
175 }
176 
fvariable(const char * name,float minval,float cur,float maxval,float * storage,void (* fun)(),bool persist)177 float fvariable(const char *name, float minval, float cur, float maxval, float *storage, void (*fun)(), bool persist)
178 {
179     if(!idents) idents = new hashtable<const char *, ident>;
180     ident v(ID_FVAR, name, minval, maxval, storage, fun, persist, IEXC_CORE);
181     idents->access(name, v);
182     return cur;
183 }
184 
svariable(const char * name,const char * cur,char ** storage,void (* fun)(),bool persist)185 char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), bool persist)
186 {
187     if(!idents) idents = new hashtable<const char *, ident>;
188     ident v(ID_SVAR, name, storage, fun, persist, IEXC_CORE);
189     idents->access(name, v);
190     return newstring(cur);
191 }
192 
193 #define _GETVAR(id, vartype, name, retval) \
194     ident *id = idents->access(name); \
195     if(!id || id->type!=vartype) return retval;
196 #define GETVAR(id, name, retval) _GETVAR(id, ID_VAR, name, retval)
setvar(const char * name,int i,bool dofunc)197 void setvar(const char *name, int i, bool dofunc)
198 {
199     GETVAR(id, name, );
200     *id->storage.i = clamp(i, id->minval, id->maxval);
201     if(dofunc && id->fun) ((void (__cdecl *)())id->fun)();            // call trigger function if available
202 }
setfvar(const char * name,float f,bool dofunc)203 void setfvar(const char *name, float f, bool dofunc)
204 {
205     _GETVAR(id, ID_FVAR, name, );
206     *id->storage.f = clamp(f, id->minvalf, id->maxvalf);
207     if(dofunc && id->fun) ((void (__cdecl *)())id->fun)();            // call trigger function if available
208 }
setsvar(const char * name,const char * str,bool dofunc)209 void setsvar(const char *name, const char *str, bool dofunc)
210 {
211     _GETVAR(id, ID_SVAR, name, );
212     *id->storage.s = exchangestr(*id->storage.s, str);
213     if(dofunc && id->fun) ((void (__cdecl *)())id->fun)();            // call trigger function if available
214 }
215 
modifyvar(const char * name,int arg,char op)216 void modifyvar(const char *name, int arg, char op)
217 {
218     ident *id = idents->access(name);
219     if(!id) return;
220     if(!allowidentaccess(id))
221     {
222         conoutf("not allowed in this execution context: %s", id->name);
223         scripterr();
224         return;
225     }
226     int val = 0;
227     switch(id->type)
228     {
229         case ID_VAR: val = *id->storage.i; break;
230         case ID_FVAR: val = int(*id->storage.f); break;
231         case ID_SVAR: val = ATOI(*id->storage.s); break;
232         case ID_ALIAS: val = ATOI(id->action); break;
233     }
234     switch(op)
235     {
236         case '+': val += arg; break;
237         case '-': val -= arg; break;
238         case '*': val *= arg; break;
239         case '/': val = arg ? val/arg : 0; break;
240     }
241     switch(id->type)
242     {
243         case ID_VAR: *id->storage.i = clamp(val, id->minval, id->maxval); break;
244         case ID_FVAR: *id->storage.f = clamp((float)val, id->minvalf, id->maxvalf); break;
245         case ID_SVAR: { string str; itoa(str, val); *id->storage.s = exchangestr(*id->storage.s, str); break; }
246         case ID_ALIAS: { string str; itoa(str, val); alias(name, str); return; }
247         default: return;
248     }
249     if(id->fun) ((void (__cdecl *)())id->fun)();
250 }
251 
modifyfvar(const char * name,float arg,char op)252 void modifyfvar(const char *name, float arg, char op)
253 {
254     ident *id = idents->access(name);
255     if(!id) return;
256     if(!allowidentaccess(id))
257     {
258         conoutf("not allowed in this execution context: %s", id->name);
259         scripterr();
260         return;
261     }
262     float val = 0;
263     switch(id->type)
264     {
265         case ID_VAR: val = *id->storage.i; break;
266         case ID_FVAR: val = *id->storage.f; break;
267         case ID_SVAR: val = atof(*id->storage.s); break;
268         case ID_ALIAS: val = atof(id->action); break;
269     }
270     switch(op)
271     {
272         case '+': val += arg; break;
273         case '-': val -= arg; break;
274         case '*': val *= arg; break;
275         case '/': val = (arg == 0.0f) ? 0 : val/arg; break;
276     }
277     switch(id->type)
278     {
279         case ID_VAR: *id->storage.i = clamp((int)val, id->minval, id->maxval); break;
280         case ID_FVAR: *id->storage.f = clamp(val, id->minvalf, id->maxvalf); break;
281         case ID_SVAR: *id->storage.s = exchangestr(*id->storage.s, floatstr(val)); break;
282         case ID_ALIAS: alias(name, floatstr(val)); return;
283         default: return;
284     }
285     if(id->fun) ((void (__cdecl *)())id->fun)();
286 }
287 
addeq(char * name,int * arg)288 void addeq(char *name, int *arg) { modifyvar(name, *arg, '+'); }
subeq(char * name,int * arg)289 void subeq(char *name, int *arg) { modifyvar(name, *arg, '-'); }
muleq(char * name,int * arg)290 void muleq(char *name, int *arg) { modifyvar(name, *arg, '*'); }
diveq(char * name,int * arg)291 void diveq(char *name, int *arg) { modifyvar(name, *arg, '/'); }
addeqf(char * name,float * arg)292 void addeqf(char *name, float *arg) { modifyfvar(name, *arg, '+'); }
subeqf(char * name,float * arg)293 void subeqf(char *name, float *arg) { modifyfvar(name, *arg, '-'); }
muleqf(char * name,float * arg)294 void muleqf(char *name, float *arg) { modifyfvar(name, *arg, '*'); }
diveqf(char * name,float * arg)295 void diveqf(char *name, float *arg) { modifyfvar(name, *arg, '/'); }
296 
297 COMMANDN(+=, addeq, "si");
298 COMMANDN(-=, subeq, "si");
299 COMMANDN(*=, muleq, "si");
300 COMMANDN(div=, diveq, "si");
301 COMMANDN(+=f, addeqf, "sf");
302 COMMANDN(-=f, subeqf, "sf");
303 COMMANDN(*=f, muleqf, "sf");
304 COMMANDN(div=f, diveqf, "sf");
305 
getvar(const char * name)306 int getvar(const char *name)
307 {
308     GETVAR(id, name, 0);
309     return *id->storage.i;
310 }
311 
identexists(const char * name)312 bool identexists(const char *name) { return idents->access(name)!=NULL; }
313 
getalias(const char * name)314 const char *getalias(const char *name)
315 {
316     ident *i = idents->access(name);
317     return i && i->type==ID_ALIAS ? i->action : NULL;
318 }
_getalias(char * name)319 void _getalias(char *name)
320 {
321     string o;
322     ident *id = idents->access(name);
323     const char *action = getalias(name);
324     if(id)
325     {
326         switch(id->type)
327         {
328             case ID_VAR:
329                 formatstring(o)("%d", *id->storage.i);
330                 result(o);
331                 break;
332             case ID_FVAR:
333                 formatstring(o)("%.3f", *id->storage.f);
334                 result(o);
335                 break;
336             case ID_SVAR:
337                 formatstring(o)("%s", *id->storage.s);
338                 result(o);
339                 break;
340             case ID_ALIAS:
341                 result(action ? action : "");
342                 break;
343             default: break;
344         }
345     }
346 }
347 COMMANDN(getalias, _getalias, "s");
348 
349 COMMANDF(isIdent, "s", (char *name) { intret(identexists(name) ? 1 : 0); });
350 
addcommand(const char * name,void (* fun)(),const char * sig)351 bool addcommand(const char *name, void (*fun)(), const char *sig)
352 {
353     if(!idents) idents = new hashtable<const char *, ident>;
354     ident c(ID_COMMAND, name, fun, sig, IEXC_CORE);
355     idents->access(name, c);
356     return false;
357 }
358 
parseexp(const char * & p,int right)359 char *parseexp(const char *&p, int right)             // parse any nested set of () or []
360 {
361     int left = *p++;
362     const char *word = p;
363     bool quot = false;
364     for(int brak = 1; brak; )
365     {
366         int c = *p++;
367         if(c==left && !quot) brak++;
368         else if(c=='"') quot = !quot;
369         else if(c==right && !quot) brak--;
370         else if(!c)
371         {
372             p--;
373             conoutf("missing \"%c\"", right);
374             scripterr();
375             return NULL;
376         }
377     }
378     char *s = newstring(word, p-word-1);
379     if(left=='(')
380     {
381         char *ret = executeret(s); // evaluate () exps directly, and substitute result
382         delete[] s;
383         s = ret ? ret : newstring("");
384     }
385     return s;
386 }
387 
lookup(char * n)388 char *lookup(char *n)                           // find value of ident referenced with $ in exp
389 {
390     ident *id = idents->access(n+1);
391     if(id) switch(id->type)
392     {
393         case ID_VAR: { string t; itoa(t, *id->storage.i); return exchangestr(n, t); }
394         case ID_FVAR: return exchangestr(n, floatstr(*id->storage.f));
395         case ID_SVAR: return exchangestr(n, *id->storage.s);
396         case ID_ALIAS: return exchangestr(n, id->action);
397     }
398     conoutf("unknown alias lookup: %s", n+1);
399     scripterr();
400     return n;
401 }
402 
parseword(const char * & p,int arg,int & infix)403 char *parseword(const char *&p, int arg, int &infix)                       // parse single argument, including expressions
404 {
405     p += strspn(p, " \t\r");
406     if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\0");
407     if(*p=='\"')
408     {
409         p++;
410         const char *word = p;
411         p += strcspn(p, "\"\r\n\0");
412         char *s = newstring(word, p-word);
413         if(*p=='\"') p++;
414         return s;
415     }
416     if(*p=='(') return parseexp(p, ')');
417     if(*p=='[') return parseexp(p, ']');
418     const char *word = p;
419     p += strcspn(p, "; \t\r\n\0");
420     if(p-word==0) return NULL;
421     if(arg==1 && p-word==1) switch(*word)
422     {
423         case '=': infix = *word; break;
424     }
425     char *s = newstring(word, p-word);
426     if(*s=='$') return lookup(s);
427     return s;
428 }
429 
conc(char ** w,int n,bool space)430 char *conc(char **w, int n, bool space)
431 {
432     int len = space ? max(n-1, 0) : 0;
433     loopj(n) len += (int)strlen(w[j]);
434     char *r = newstring("", len);
435     loopi(n)
436     {
437         strcat(r, w[i]);  // make string-list out of all arguments
438         if(i==n-1) break;
439         bool col = w[i][0] == '\f' && w[i][2] == '\0';
440         if(space && !col) strcat(r, " ");
441     }
442     return r;
443 }
444 
445 VARN(numargs, _numargs, 25, 0, 0);
446 
447 char *commandret = NULL;
448 
intret(int v)449 void intret(int v)
450 {
451     string t;
452     itoa(t, v);
453     commandret = newstring(t);
454 }
455 
floatstr(float v)456 const char *floatstr(float v)
457 {
458     static string l;
459     ftoa(l, 0.5);
460     static int n = 0;
461     static string t[3];
462     n = (n + 1)%3;
463     ftoa(t[n], v);
464     return t[n];
465 }
466 
467 
floatret(float v)468 void floatret(float v)
469 {
470     commandret = newstring(floatstr(v));
471 }
472 
result(const char * s)473 void result(const char *s) { commandret = newstring(s); }
474 
475 #if 0
476 // seer : script evaluation excessive recursion
477 static int seer_count = 0; // count calls to executeret, check time every n1 (100) calls
478 static int seer_index = -1; // position in timestamp vector
479 vector<long long> seer_t1; // timestamp of last n2 (10) level-1 calls
480 vector<long long> seer_t2; // timestamp of last n3 (10) level-2 calls
481 #endif
executeret(const char * p)482 char *executeret(const char *p)                            // all evaluation happens here, recursively
483 {
484     if(!p || !p[0]) return NULL;
485     bool noproblem = true;
486 #if 0
487     if(execcontext>IEXC_CFG) // only PROMPT and MAP-CFG are checked for this, fooling with core/cfg at your own risk!
488     {
489         seer_count++;
490         if(seer_count>=100)
491         {
492             seer_index = (seer_index+1)%10;
493             long long cts = (long long) time(NULL);
494             if(seer_t1.length()>=10) seer_t1[seer_index] = cts;
495             seer_t1.add(cts);
496             int lc = (seer_index+11)%10;
497             if(lc<=seer_t1.length())
498             {
499                 int dt = seer_t1[seer_index] - seer_t1[lc];
500                 if(iabs(dt)<2)
501                 {
502                     conoutf("SCRIPT EXECUTION warning [%d:%s]", &p, p);
503                     seer_t2.add(seer_t1[seer_index]);
504                     if(seer_t2.length() >= 10)
505                     {
506                         if(seer_t2[0] == seer_t2.last())
507                         {
508                             conoutf("SCRIPT EXECUTION in danger of crashing the client - dropping script [%s].", p);
509                             noproblem = false;
510                             seer_t2.shrink(0);
511                             seer_t1.shrink(0);
512                             seer_index = 0;
513                         }
514                     }
515                 }
516             }
517             seer_count = 0;
518         }
519     }
520 #endif
521     const int MAXWORDS = 25;                    // limit, remove
522     char *w[MAXWORDS];
523     char *retval = NULL;
524     #define setretval(v) { char *rv = v; if(rv) retval = rv; }
525     if(noproblem) // if the "seer"-algorithm doesn't object
526     {
527         for(bool cont = true; cont;)                // for each ; seperated statement
528         {
529             if(loop_level && loop_skip) break;
530             int numargs = MAXWORDS, infix = 0;
531             loopi(MAXWORDS)                         // collect all argument values
532             {
533                 w[i] = (char *)"";
534                 if(i>numargs) continue;
535                 char *s = parseword(p, i, infix);   // parse and evaluate exps
536                 if(s) w[i] = s;
537                 else numargs = i;
538             }
539 
540             p += strcspn(p, ";\n\0");
541             cont = *p++!=0;                         // more statements if this isn't the end of the string
542             const char *c = w[0];
543             if(!*c) continue;                       // empty statement
544 
545             DELETEA(retval);
546 
547             if(infix)
548             {
549                 switch(infix)
550                 {
551                     case '=':
552                         DELETEA(w[1]);
553                         swap(w[0], w[1]);
554                         c = "alias";
555                         break;
556                 }
557             }
558 
559             ident *id = idents->access(c);
560             if(!id)
561             {
562                 if(!isdigit(*c) && ((*c!='+' && *c!='-' && *c!='.') || !isdigit(c[1])))
563                 {
564                     conoutf("unknown command: %s", c);
565                     scripterr();
566                 }
567                 setretval(newstring(c));
568             }
569             else
570             {
571                 if(!allowidentaccess(id))
572                 {
573                     conoutf("not allowed in this execution context: %s", id->name);
574                     scripterr();
575                     continue;
576                 }
577 
578                 switch(id->type)
579                 {
580                     case ID_COMMAND:                    // game defined commands
581                     {
582 
583                         if(strstr(id->sig, "v")) ((void (__cdecl *)(char **, int))id->fun)(&w[1], numargs-1);
584                         else if(strstr(id->sig, "c") || strstr(id->sig, "w"))
585                         {
586                             char *r = conc(w+1, numargs-1, strstr(id->sig, "c") != NULL);
587                             ((void (__cdecl *)(char *))id->fun)(r);
588                             delete[] r;
589                         }
590                         else if(strstr(id->sig, "d"))
591                         {
592 #ifndef STANDALONE
593                             ((void (__cdecl *)(bool))id->fun)(addreleaseaction(id->name)!=NULL);
594 #endif
595                         }
596                         else
597                         {
598                             int ib1, ib2, ib3, ib4, ib5, ib6, ib7, ib8;
599                             float fb1, fb2, fb3, fb4, fb5, fb6, fb7, fb8;
600                             #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]))
601 
602                             switch(strlen(id->sig))                // use very ad-hoc function signature, and just call it
603                             {
604                                 case 0: ((void (__cdecl *)())id->fun)(); break;
605                                 case 1: ((void (__cdecl *)(void*))id->fun)(ARG(1)); break;
606                                 case 2: ((void (__cdecl *)(void*, void*))id->fun)(ARG(1), ARG(2)); break;
607                                 case 3: ((void (__cdecl *)(void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3)); break;
608                                 case 4: ((void (__cdecl *)(void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4)); break;
609                                 case 5: ((void (__cdecl *)(void*, void*, void*, void*, void*))id->fun)(ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); break;
610                                 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;
611                                 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;
612                                 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;
613                                 default: fatal("command %s has too many arguments (signature: %s)", id->name, id->sig); break;
614                             }
615                             #undef ARG
616                         }
617 
618                         setretval(commandret);
619                         commandret = NULL;
620                         break;
621                     }
622 
623                     case ID_VAR:                        // game defined variables
624                         if(!w[1][0]) conoutf("%s = %d", c, *id->storage.i);      // var with no value just prints its current value
625                         else if(id->minval>id->maxval) conoutf("variable %s is read-only", id->name);
626                         else
627                         {
628                             int i1 = ATOI(w[1]);
629                             if(i1<id->minval || i1>id->maxval)
630                             {
631                                 i1 = i1<id->minval ? id->minval : id->maxval;       // clamp to valid range
632                                 conoutf("valid range for %s is %d..%d", id->name, id->minval, id->maxval);
633                             }
634                             *id->storage.i = i1;
635                             if(id->fun) ((void (__cdecl *)())id->fun)();            // call trigger function if available
636                         }
637                         break;
638 
639                     case ID_FVAR:                        // game defined variables
640                         if(!w[1][0]) conoutf("%s = %s", c, floatstr(*id->storage.f));      // var with no value just prints its current value
641                         else if(id->minvalf>id->maxvalf) conoutf("variable %s is read-only", id->name);
642                         else
643                         {
644                             float f1 = atof(w[1]);
645                             if(f1<id->minvalf || f1>id->maxvalf)
646                             {
647                                 f1 = f1<id->minvalf ? id->minvalf : id->maxvalf;       // clamp to valid range
648                                 conoutf("valid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf));
649                                 //scripterr(); // Why throw this error here when it's not done for ID_VAR above? Only difference is datatype, both are "valid range errors". // Bukz 2011june04
650                             }
651                             *id->storage.f = f1;
652                             if(id->fun) ((void (__cdecl *)())id->fun)();            // call trigger function if available
653                         }
654                         break;
655 
656                     case ID_SVAR:                        // game defined variables
657                         if(!w[1][0]) conoutf(strchr(*id->storage.s, '"') ? "%s = [%s]" : "%s = \"%s\"", c, *id->storage.s); // var with no value just prints its current value
658                         else
659                         {
660                             *id->storage.s = exchangestr(*id->storage.s, newstring(w[1]));
661                             if(id->fun) ((void (__cdecl *)())id->fun)();            // call trigger function if available
662                         }
663                         break;
664 
665                     case ID_ALIAS:                              // alias, also used as functions and (global) variables
666                         delete[] w[0];
667                         static vector<ident *> argids;
668                         for(int i = 1; i<numargs; i++)
669                         {
670                             if(i > argids.length())
671                             {
672                                 defformatstring(argname)("arg%d", i);
673                                 argids.add(newident(argname, IEXC_CORE));
674                             }
675                             pushident(*argids[i-1], w[i]); // set any arguments as (global) arg values so functions can access them
676                         }
677                         _numargs = numargs-1;
678                         char *wasexecuting = id->executing;
679                         id->executing = id->action;
680                         setretval(executeret(id->action));
681                         if(id->executing!=id->action && id->executing!=wasexecuting) delete[] id->executing;
682                         id->executing = wasexecuting;
683                         for(int i = 1; i<numargs; i++) popident(*argids[i-1]);
684                         continue;
685                 }
686             }
687             loopj(numargs) if(w[j]) delete[] w[j];
688         }
689     }
690     return retval;
691 }
692 
execute(const char * p)693 int execute(const char *p)
694 {
695     char *ret = executeret(p);
696     int i = 0;
697     if(ret) { i = ATOI(ret); delete[] ret; }
698     return i;
699 }
700 
701 #ifndef STANDALONE
702 // tab-completion of all idents
703 
704 static int completesize = -1, completeidx = 0;
705 static playerent *completeplayer = NULL;
706 
resetcomplete()707 void resetcomplete()
708 {
709     completesize = -1;
710     completeplayer = NULL;
711 }
712 
nickcomplete(char * s)713 bool nickcomplete(char *s)
714 {
715     if(!players.length()) return false;
716 
717     char *cp = s;
718     for(int i = (int)strlen(s) - 1; i > 0; i--)
719         if(s[i] == ' ') { cp = s + i + 1; break; }
720     if(completesize < 0) { completesize = (int)strlen(cp); completeidx = 0; }
721 
722     int idx = 0;
723     if(completeplayer!=NULL)
724     {
725         idx = players.find(completeplayer)+1;
726         if(!players.inrange(idx)) idx = 0;
727     }
728 
729     for(int i=idx; i<idx+players.length(); i++)
730     {
731         playerent *p = players[i % players.length()];
732         if(p && !strncasecmp(p->name, cp, completesize))
733         {
734             *cp = '\0';
735             concatstring(s, p->name);
736             completeplayer = p;
737             return true;
738         }
739     }
740     return false;
741 }
742 
743 enum { COMPLETE_FILE = 0, COMPLETE_LIST, COMPLETE_NICK };
744 
745 struct completekey
746 {
747     int type;
748     const char *dir, *ext;
749 
completekeycompletekey750     completekey() {}
completekeycompletekey751     completekey(int type, const char *dir, const char *ext) : type(type), dir(dir), ext(ext) {}
752 };
753 
754 struct completeval
755 {
756     int type;
757     char *dir, *ext;
758     vector<char *> dirlist;
759     vector<char *> list;
760 
completevalcompleteval761     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) {}
~completevalcompleteval762     ~completeval() { DELETEA(dir); DELETEA(ext); dirlist.deletearrays(); list.deletearrays(); }
763 };
764 
htcmp(const completekey & x,const completekey & y)765 static inline bool htcmp(const completekey &x, const completekey &y)
766 {
767     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)));
768 }
769 
hthash(const completekey & k)770 static inline uint hthash(const completekey &k)
771 {
772     return k.dir ? hthash(k.dir) + k.type : k.type;
773 }
774 
775 static hashtable<completekey, completeval *> completedata;
776 static hashtable<char *, completeval *> completions;
777 
addcomplete(char * command,int type,char * dir,char * ext)778 void addcomplete(char *command, int type, char *dir, char *ext)
779 {
780     if(type==COMPLETE_FILE)
781     {
782         int dirlen = (int)strlen(dir);
783         while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\'))
784             dir[--dirlen] = '\0';
785         if(ext)
786         {
787             if(strchr(ext, '*')) ext[0] = '\0';
788             if(!ext[0]) ext = NULL;
789         }
790     }
791     completekey key(type, dir, ext);
792     completeval **val = completedata.access(key);
793     if(!val)
794     {
795         completeval *f = new completeval(type, dir, ext);
796         if(type==COMPLETE_LIST) explodelist(dir, f->list);
797         if(type==COMPLETE_FILE)
798         {
799             explodelist(dir, f->dirlist);
800             loopv(f->dirlist)
801             {
802                 char *dir = f->dirlist[i];
803                 int dirlen = (int)strlen(dir);
804                 while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\'))
805                     dir[--dirlen] = '\0';
806             }
807         }
808         val = &completedata[completekey(type, f->dir, f->ext)];
809         *val = f;
810     }
811     completeval **hascomplete = completions.access(command);
812     if(hascomplete) *hascomplete = *val;
813     else completions[newstring(command)] = *val;
814 }
815 
addfilecomplete(char * command,char * dir,char * ext)816 void addfilecomplete(char *command, char *dir, char *ext)
817 {
818     addcomplete(command, COMPLETE_FILE, dir, ext);
819 }
820 
addlistcomplete(char * command,char * list)821 void addlistcomplete(char *command, char *list)
822 {
823     addcomplete(command, COMPLETE_LIST, list, NULL);
824 }
825 
addnickcomplete(char * command)826 void addnickcomplete(char *command)
827 {
828     addcomplete(command, COMPLETE_NICK, NULL, NULL);
829 }
830 
831 COMMANDN(complete, addfilecomplete, "sss");
832 COMMANDN(listcomplete, addlistcomplete, "ss");
833 COMMANDN(nickcomplete, addnickcomplete, "s");
834 
commandcomplete(char * s)835 void commandcomplete(char *s)
836 {
837     if(*s!='/')
838     {
839         string t;
840         copystring(t, s);
841         copystring(s, "/");
842         concatstring(s, t);
843     }
844     if(!s[1]) return;
845 
846     int o = 0; //offset
847 
848     while(*s) {s++; o++;} //seek to end
849     s--; //last character
850 
851     for (int i = o; i > 1; i--) //seek backwards
852     {
853         s--;
854         o--;
855         if (*s == ';') //until ';' is found
856         {
857             s++; break; //string after ';'
858         }
859     }
860 
861     char *openblock = strrchr(s+1, '('); //find last open parenthesis
862     char *closeblock = strrchr(s+1, ')'); //find last closed parenthesis
863 
864     if (openblock)
865     {
866         if (!closeblock || closeblock < openblock) //open block
867         s = openblock;
868     }
869 
870     char *cp = s; //part to complete
871 
872     for(int i = (int)strlen(s) - 1; i > 0; i--)
873         if(s[i] == ' ') { cp = s + i; break; } //testing for command/argument needs completion
874 
875     bool init = false;
876     if(completesize < 0)
877     {
878         completesize = (int)strlen(cp)-1;
879         completeidx = 0;
880         if(*cp == ' ') init = true;
881     }
882 
883     completeval *cdata = NULL;
884 
885     char *end = strchr(s+1, ' '); //find end of command name
886 
887     if(end && end <= cp) //full command is present
888     {
889         string command;
890         copystring(command, s+1, min(size_t(end-s), sizeof(command)));
891         completeval **hascomplete = completions.access(command);
892          if(hascomplete) cdata = *hascomplete;
893     }
894     if(init && cdata && cdata->type==COMPLETE_FILE)
895     {
896        cdata->list.deletearrays();
897        loopv(cdata->dirlist) listfiles(cdata->dirlist[i], cdata->ext, cdata->list);
898     }
899 
900     if(*cp == '/' || *cp == ';'
901     || (cp == s && (*cp == ' ' || *cp == '(')))
902     { // commandname completion
903         int idx = 0;
904         enumerate(*idents, ident, id,
905             if(!strncasecmp(id.name, cp+1, completesize) && idx++==completeidx)
906             {
907                 cp[1] = '\0';
908                 strcpy(s+1, id.name); //concatstring/copystring will crash because of overflow
909             }
910         );
911         completeidx++;
912         if(completeidx>=idx) completeidx = 0;
913     }
914     else if(!cdata) return;
915     else if(cdata->type==COMPLETE_NICK) nickcomplete(s);
916     else
917     { // argument completion
918         loopv(cdata->list)
919         {
920             int j = (i + completeidx) % cdata->list.length();
921             if(!strncasecmp(cdata->list[j], cp + 1, completesize))
922             {
923                 cp[1] = '\0';
924                 strcpy(cp+1, cdata->list[j]); //concatstring/copystring will crash because of overflow
925                 completeidx = j;
926                 break;
927             }
928         }
929         completeidx++;
930         if(completeidx >= cdata->list.length()) completeidx = 1;
931     }
932 }
933 
complete(char * s)934 void complete(char *s)
935 {
936     if(*s!='/')
937     {
938         if(nickcomplete(s)) return;
939     }
940     commandcomplete(s);
941 }
942 #endif
943 
944 const char *curcontext = NULL, *curinfo = NULL;
945 
scripterr()946 void scripterr()
947 {
948     if(curcontext) conoutf("(%s: %s)", curcontext, curinfo);
949     else conoutf("(from console or builtin)");
950 }
951 
setcontext(const char * context,const char * info)952 void setcontext(const char *context, const char *info)
953 {
954     curcontext = context;
955     curinfo = info;
956 }
957 
resetcontext()958 void resetcontext()
959 {
960     curcontext = curinfo = NULL;
961 }
962 
execfile(const char * cfgfile)963 bool execfile(const char *cfgfile)
964 {
965     string s;
966     copystring(s, cfgfile);
967     setcontext("file", cfgfile);
968     char *buf = loadfile(path(s), NULL);
969     if(!buf)
970     {
971         resetcontext();
972         return false;
973     }
974     execute(buf);
975     delete[] buf;
976     resetcontext();
977     return true;
978 }
979 
exec(const char * cfgfile)980 void exec(const char *cfgfile)
981 {
982     if(!execfile(cfgfile)) conoutf("could not read \"%s\"", cfgfile);
983 }
984 
execdir(const char * dir)985 void execdir(const char *dir)
986 {
987         if(dir[0])
988         {
989             vector<char *> files;
990             listfiles(dir, "cfg", files);
991             loopv(files)
992             {
993                 defformatstring(d)("%s/%s.cfg",dir,files[i]);
994                 exec(d);
995             }
996         }
997 }
998 COMMAND(execdir, "s");
999 
1000 // below the commands that implement a small imperative language. thanks to the semantics of
1001 // () and [] expressions, any control construct can be defined trivially.
1002 
ifthen(char * cond,char * thenp,char * elsep)1003 void ifthen(char *cond, char *thenp, char *elsep) { commandret = executeret(cond[0]!='0' ? thenp : elsep); }
loopa(char * var,int * times,char * body)1004 void loopa(char *var, int *times, char *body)
1005 {
1006     int t = *times;
1007     if(t<=0) return;
1008     ident *id = newident(var, execcontext);
1009     if(id->type!=ID_ALIAS) return;
1010     char *buf = newstring("0", 16);
1011     pushident(*id, buf);
1012     loop_level++;
1013     execute(body);
1014     if(loop_skip) loop_skip = false;
1015     if(loop_break) loop_break = false;
1016     else
1017     {
1018         loopi(t-1)
1019         {
1020             if(buf != id->action)
1021             {
1022                 if(id->action != id->executing) delete[] id->action;
1023                 id->action = buf = newstring(16);
1024             }
1025             itoa(id->action, i+1);
1026             execute(body);
1027             if(loop_skip) loop_skip = false;
1028             if(loop_break)
1029             {
1030                 loop_break = false;
1031                 break;
1032             }
1033         }
1034     }
1035     popident(*id);
1036     loop_level--;
1037 }
whilea(char * cond,char * body)1038 void whilea(char *cond, char *body)
1039 {
1040     loop_level++;
1041     while(execute(cond))
1042     {
1043         execute(body);
1044         if(loop_skip) loop_skip = false;
1045         if(loop_break)
1046         {
1047             loop_break = false;
1048             break;
1049         }
1050     }
1051     loop_level--;
1052 }
1053 
breaka()1054 void breaka() { if(loop_level) loop_skip = loop_break = true; }
continuea()1055 void continuea() { if(loop_level) loop_skip = true; }
1056 
concat(char * s)1057 void concat(char *s) { result(s); }
concatword(char * s)1058 void concatword(char *s) { result(s); }
1059 
format(char ** args,int numargs)1060 void format(char **args, int numargs)
1061 {
1062     if(numargs < 1)
1063     {
1064         result("");
1065         return;
1066     }
1067 
1068     vector<char> s;
1069     char *f = args[0];
1070     while(*f)
1071     {
1072         int c = *f++;
1073         if(c == '%')
1074         {
1075             int i = *f++;
1076             if(i >= '1' && i <= '9')
1077             {
1078                 i -= '0';
1079                 const char *sub = i < numargs ? args[i] : "";
1080                 while(*sub) s.add(*sub++);
1081             }
1082             else s.add(i);
1083         }
1084         else s.add(c);
1085     }
1086     s.add('\0');
1087     result(s.getbuf());
1088 }
1089 
1090 #define whitespaceskip s += strspn(s, "\n\t \r")
1091 #define elementskip *s=='"' ? (++s, s += strcspn(s, "\"\n\0"), s += *s=='"') : s += strcspn(s, "\n\t \0")
1092 
explodelist(const char * s,vector<char * > & elems)1093 void explodelist(const char *s, vector<char *> &elems)
1094 {
1095     whitespaceskip;
1096     while(*s)
1097     {
1098         const char *elem = s;
1099         elementskip;
1100         elems.add(*elem=='"' ? newstring(elem+1, s-elem-(s[-1]=='"' ? 2 : 1)) : newstring(elem, s-elem));
1101         whitespaceskip;
1102     }
1103 }
1104 
looplist(char * list,char * var,char * body)1105 void looplist(char *list, char *var, char *body)
1106 {
1107     ident *id = newident(var, execcontext);
1108     if(id->type!=ID_ALIAS) return;
1109     char *buf = newstring(MAXSTRLEN);
1110 
1111     vector<char *> elems;
1112     explodelist(list, elems);
1113 
1114     loop_level++;
1115     loopv(elems)
1116     {
1117         const char *elem = elems[i];
1118         if(buf != id->action)
1119         {
1120             if(id->action != id->executing) delete[] id->action;
1121             id->action = buf = newstring(MAXSTRLEN);
1122         }
1123         copystring(id->action, elem);
1124         execute(body);
1125         if(loop_skip) loop_skip = false;
1126         if(loop_break)
1127         {
1128             loop_break = false;
1129             break;
1130         }
1131     }
1132     popident(*id);
1133     loop_level--;
1134 }
1135 
indexlist(const char * s,int pos)1136 char *indexlist(const char *s, int pos)
1137 {
1138     whitespaceskip;
1139     loopi(pos)
1140     {
1141         elementskip;
1142         whitespaceskip;
1143         if(!*s) break;
1144     }
1145     const char *e = s;
1146     elementskip;
1147     if(*e=='"')
1148     {
1149         e++;
1150         if(s[-1]=='"') --s;
1151     }
1152     return newstring(e, s-e);
1153 }
1154 
listlen(char * s)1155 int listlen(char *s)
1156 {
1157     int n = 0;
1158     whitespaceskip;
1159     for(; *s; n++) elementskip, whitespaceskip;
1160     return n;
1161 }
1162 
at(char * s,int * pos)1163 void at(char *s, int *pos)
1164 {
1165     commandret = indexlist(s, *pos);
1166 }
1167 
find(const char * s,const char * key)1168 int find(const char *s, const char *key)
1169 {
1170     whitespaceskip;
1171     int len = strlen(key);
1172     for(int i = 0; *s; i++)
1173     {
1174         const char *a = s, *e = s;
1175         elementskip;
1176         if(*e=='"')
1177         {
1178             e++;
1179             if(s[-1]=='"') --s;
1180         }
1181         if(s-e==len && !strncmp(e, key, s-e)) return i;
1182         else s = a;
1183         elementskip, whitespaceskip;
1184     }
1185     return -1;
1186 }
1187 
findlist(char * s,char * key)1188 void findlist(char *s, char *key)
1189 {
1190     intret(find(s, key));
1191 }
colora(char * s)1192 void colora(char *s)
1193 {
1194     if(s[0] && s[1]=='\0')
1195     {
1196         defformatstring(x)("\f%c",s[0]);
1197         commandret = newstring(x);
1198     }
1199 }
1200 
1201 // Easily inject a string into various CubeScript punctuations
addpunct(char * s,int * type)1202 void addpunct(char *s, int *type)
1203 {
1204     switch(*type)
1205     {
1206         case 1:  defformatstring(o1)("[%s]", s);   result(o1); break;
1207         case 2:  defformatstring(o2)("(%s)", s);   result(o2); break;
1208         case 3:  defformatstring(o3)("$%s", s);    result(o3); break;
1209         case 4:  result("\""); break;
1210         case 5:  result("%");  break;
1211         default: defformatstring(o4)("\"%s\"", s); result(o4); break;
1212     }
1213 }
1214 
toLower(char * s)1215 void toLower(char *s) { result(strcaps(s, false)); }
toUpper(char * s)1216 void toUpper(char *s) { result(strcaps(s, true)); }
1217 
testchar(char * s,int * type)1218 void testchar(char *s, int *type)
1219 {
1220     bool istrue = false;
1221     switch(*type) {
1222         case 1:
1223             if(isalpha(s[0]) != 0) { istrue = true; }
1224             break;
1225         case 2:
1226             if(isalnum(s[0]) != 0) { istrue = true; }
1227             break;
1228         case 3:
1229             if(islower(s[0]) != 0) { istrue = true; }
1230             break;
1231         case 4:
1232             if(isupper(s[0]) != 0) { istrue = true; }
1233             break;
1234         case 5:
1235             if(isprint(s[0]) != 0) { istrue = true; }
1236             break;
1237         case 6:
1238             if(ispunct(s[0]) != 0) { istrue = true; }
1239             break;
1240         case 7:
1241             if(isspace(s[0]) != 0) { istrue = true; }
1242             break;
1243         case 8: // Without this it is impossible to determine if a character === " in cubescript
1244             if(!strcmp(s, "\"")) { istrue = true; }
1245             break;
1246         default:
1247             if(isdigit(s[0]) != 0) { istrue = true; }
1248             break;
1249     }
1250     if(istrue)
1251         intret(1);
1252     else
1253         intret(0);
1254 }
1255 
strreplace(char * dest,const char * source,const char * search,const char * replace)1256 char *strreplace(char *dest, const char *source, const char *search, const char *replace)
1257 {
1258     vector<char> buf;
1259 
1260     int searchlen = strlen(search);
1261     if(!searchlen) { copystring(dest, source); return dest; }
1262     for(;;)
1263     {
1264         const char *found = strstr(source, search);
1265         if(found)
1266         {
1267             while(source < found) buf.add(*source++);
1268             for(const char *n = replace; *n; n++) buf.add(*n);
1269             source = found + searchlen;
1270         }
1271         else
1272         {
1273             while(*source) buf.add(*source++);
1274             buf.add('\0');
1275             return copystring(dest, buf.getbuf());
1276         }
1277     }
1278 }
1279 
stringsort(const char ** a,const char ** b)1280 int stringsort(const char **a, const char **b) { return strcmp(*a, *b); }
1281 
sortlist(char * list)1282 void sortlist(char *list)
1283  {
1284     char* buf;
1285     buf = new char [strlen(list)]; strcpy(buf, ""); //output
1286 
1287     if(strcmp(list, "") == 0)
1288     {
1289         //no input
1290         result(buf);
1291         delete [] buf;
1292         return;
1293     }
1294 
1295     vector<char *> elems;
1296     explodelist(list, elems);
1297     elems.sort(stringsort);
1298 
1299     strcpy(buf, elems[0]);
1300     for(int i = 1; i < elems.length(); i++)
1301     {
1302         strcat(buf, " ");
1303         strcat(buf, elems[i]);
1304     }
1305 
1306     result(buf); //result
1307     delete [] buf;
1308  }
1309 
swapelements(char * list,char * v)1310  void swapelements(char *list, char *v)
1311  {
1312     char* buf;
1313     buf = new char [strlen(list)]; strcpy(buf, ""); //output
1314 
1315     if(strcmp(list, "") == 0)
1316     {
1317         // no input
1318          result(buf);
1319          delete [] buf;
1320          return;
1321     }
1322 
1323     vector<char *> elems;
1324     explodelist(list, elems);
1325 
1326     vector<char *> swap;
1327     explodelist(v, swap);
1328 
1329     if (strcmp(v, "") == 0 || //no input
1330     swap.length()%2 != 0) //incorrect input
1331     {
1332         result(buf);
1333         delete [] buf;
1334         return;
1335     }
1336 
1337     char tmp[255]; strcpy (tmp, "");
1338 
1339     for(int i = 0; i < swap.length(); i+=2)
1340     {
1341         if (elems.inrange(atoi(swap[i])) && elems.inrange(atoi(swap[i + 1])))
1342         {
1343             strcpy(tmp, elems[atoi(swap[i])]);
1344             strcpy(elems[atoi(swap[i])], elems[atoi(swap[i+1])]);
1345             strcpy(elems[atoi(swap[i+1])], tmp);
1346         }
1347     }
1348 
1349     strcpy(buf, elems[0]);
1350     for(int i = 1; i < elems.length(); i++)
1351     {
1352         strcat(buf, " ");
1353         strcat(buf, elems[i]);
1354     }
1355 
1356     result(buf); //result
1357     delete [] buf;
1358  }
1359 
1360 COMMANDN(c, colora, "s");
1361 COMMANDN(loop, loopa, "sis");
1362 COMMAND(looplist, "sss");
1363 COMMANDN(while, whilea, "ss");
1364 COMMANDN(break, breaka, "");
1365 COMMANDN(continue, continuea, "");
1366 COMMANDN(if, ifthen, "sss");
1367 COMMAND(exec, "s");
1368 COMMAND(concat, "c");
1369 COMMAND(concatword, "w");
1370 COMMAND(format, "v");
1371 COMMAND(result, "s");
1372 COMMAND(execute, "s");
1373 COMMAND(at, "si");
1374 COMMANDF(listlen, "s", (char *l) { intret(listlen(l)); });
1375 COMMAND(findlist, "ss");
1376 COMMAND(addpunct, "si");
1377 COMMANDN(tolower, toLower, "s");
1378 COMMANDN(toupper, toUpper, "s");
1379 COMMAND(testchar, "si");
1380 COMMAND(sortlist, "c");
1381 COMMANDF(strreplace, "sss", (const char *source, const char *search, const char *replace) { string d; result(strreplace(d, source, search, replace)); });
1382 COMMAND(swapelements, "ss");
1383 
add(int * a,int * b)1384 void add(int *a, int *b)   { intret(*a + *b); }            COMMANDN(+, add, "ii");
mul(int * a,int * b)1385 void mul(int *a, int *b)   { intret(*a * *b); }            COMMANDN(*, mul, "ii");
sub(int * a,int * b)1386 void sub(int *a, int *b)   { intret(*a - *b); }            COMMANDN(-, sub, "ii");
div_(int * a,int * b)1387 void div_(int *a, int *b)  { intret(*b ? (*a)/(*b) : 0); }    COMMANDN(div, div_, "ii");
mod_(int * a,int * b)1388 void mod_(int *a, int *b)   { intret(*b ? (*a)%(*b) : 0); }    COMMANDN(mod, mod_, "ii");
addf(float * a,float * b)1389 void addf(float *a, float *b)   { floatret(*a + *b); }            COMMANDN(+f, addf, "ff");
mulf(float * a,float * b)1390 void mulf(float *a, float *b)   { floatret(*a * *b); }            COMMANDN(*f, mulf, "ff");
subf(float * a,float * b)1391 void subf(float *a, float *b)   { floatret(*a - *b); }            COMMANDN(-f, subf, "ff");
divf_(float * a,float * b)1392 void divf_(float *a, float *b)  { floatret(*b ? (*a)/(*b) : 0); }    COMMANDN(divf, divf_, "ff");
modf_(float * a,float * b)1393 void modf_(float *a, float *b)   { floatret(*b ? fmod(*a, *b) : 0); }    COMMANDN(modf, modf_, "ff");
powf_(float * a,float * b)1394 void powf_(float *a, float *b)   { floatret(powf(*a, *b)); }    COMMANDN(powf, powf_, "ff");
not_(int * a)1395 void not_(int *a) { intret((int)(!(*a))); }              COMMANDN(!, not_, "i");
equal(int * a,int * b)1396 void equal(int *a, int *b) { intret((int)(*a == *b)); }    COMMANDN(=, equal, "ii");
notequal(int * a,int * b)1397 void notequal(int *a, int *b) { intret((int)(*a != *b)); } COMMANDN(!=, notequal, "ii");
lt(int * a,int * b)1398 void lt(int *a, int *b)    { intret((int)(*a < *b)); }     COMMANDN(<, lt, "ii");
gt(int * a,int * b)1399 void gt(int *a, int *b)    { intret((int)(*a > *b)); }     COMMANDN(>, gt, "ii");
lte(int * a,int * b)1400 void lte(int *a, int *b)    { intret((int)(*a <= *b)); }   COMMANDN(<=, lte, "ii");
gte(int * a,int * b)1401 void gte(int *a, int *b)    { intret((int)(*a >= *b)); }   COMMANDN(>=, gte, "ii");
1402 
1403 COMMANDF(round, "f", (float *a) { intret((int)round(*a)); });
1404 COMMANDF(ceil,  "f", (float *a) { intret((int)ceil(*a)); });
1405 COMMANDF(floor, "f", (float *a) { intret((int)floor(*a)); });
1406 
1407 #define COMPAREF(opname, func, op) \
1408     void func(float *a, float *b) { intret((int)((*a) op (*b))); } \
1409     COMMANDN(opname, func, "ff")
1410 COMPAREF(=f, equalf, ==);
1411 COMPAREF(!=f, notequalf, !=);
1412 COMPAREF(<f, ltf, <);
1413 COMPAREF(>f, gtf, >);
1414 COMPAREF(<=f, ltef, <=);
1415 COMPAREF(>=f, gtef, >=);
1416 
anda(char * a,char * b)1417 void anda (char *a, char *b) { intret(execute(a)!=0 && execute(b)!=0); }
ora(char * a,char * b)1418 void ora  (char *a, char *b) { intret(execute(a)!=0 || execute(b)!=0); }
1419 
1420 COMMANDN(&&, anda, "ss");
1421 COMMANDN(||, ora, "ss");
1422 
1423 COMMANDF(strcmp, "ss", (char *a, char *b) { intret((strcmp(a, b) == 0) ? 1 : 0); });
1424 
1425 COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); });
1426 
1427 #ifndef STANDALONE
writecfg()1428 void writecfg()
1429 {
1430     stream *f = openfile(path("config/saved.cfg", true), "w");
1431     if(!f) return;
1432     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");
1433     f->printf("// basic settings\n\n");
1434     f->printf("name \"%s\"\n", player1->name);
1435     extern const char *crosshairnames[CROSSHAIR_NUM];
1436     extern Texture *crosshairs[CROSSHAIR_NUM];
1437     loopi(CROSSHAIR_NUM) if(crosshairs[i] && crosshairs[i]!=notexture)
1438     {
1439         const char *fname = crosshairs[i]->name+strlen("packages/crosshairs/");
1440         if(i==CROSSHAIR_DEFAULT) f->printf("loadcrosshair %s\n", fname);
1441         else f->printf("loadcrosshair %s %s\n", fname, crosshairnames[i]);
1442     }
1443     extern int lowfps, highfps;
1444     f->printf("fpsrange %d %d\n", lowfps, highfps);
1445     extern string myfont;
1446     f->printf("setfont %s\n", myfont);
1447     f->printf("\n");
1448     audiomgr.writesoundconfig(f);
1449     f->printf("\n");
1450     f->printf("// kill messages for each weapon\n");
1451     loopi(NUMGUNS)
1452     {
1453         const char *fragmsg = killmessage(i, false);
1454         const char *gibmsg = killmessage(i, true);
1455         f->printf("\nfragmessage %d [%s]", i, fragmsg);
1456         f->printf("\ngibmessage %d [%s]", i, gibmsg);
1457     }
1458     f->printf("\n\n// client variables\n\n");
1459     enumerate(*idents, ident, id,
1460         if(!id.persist) continue;
1461         switch(id.type)
1462         {
1463             case ID_VAR: f->printf("%s %d\n", id.name, *id.storage.i); break;
1464             case ID_FVAR: f->printf("%s %s\n", id.name, floatstr(*id.storage.f)); break;
1465             case ID_SVAR: f->printf("%s [%s]\n", id.name, *id.storage.s); break;
1466         }
1467     );
1468     f->printf("\n// weapon settings\n\n");
1469     loopi(NUMGUNS) if(guns[i].isauto)
1470     {
1471         f->printf("burstshots %d %d\n", i, burstshotssettings[i]);
1472     }
1473     f->printf("\n// key binds\n\n");
1474     writebinds(f);
1475     f->printf("\n// aliases\n\n");
1476     enumerate(*idents, ident, id,
1477         if(id.type==ID_ALIAS && id.persist && id.action[0])
1478         {
1479             f->printf("%s = [%s]\n", id.name, id.action);
1480         }
1481     );
1482     f->printf("\n");
1483     delete f;
1484 }
1485 
1486 COMMAND(writecfg, "");
1487 
deletecfg()1488 void deletecfg()
1489 {
1490     string configs[] = { "config/saved.cfg", "config/init.cfg" };
1491     loopj(2) // delete files in homedir and basedir if possible
1492     {
1493         loopi(sizeof(configs)/sizeof(configs[0]))
1494         {
1495             const char *file = findfile(path(configs[i], true), "r");
1496             if(!file) continue;
1497             delfile(file);
1498         }
1499     }
1500 }
1501 #endif
1502 
identnames(vector<const char * > & names,bool builtinonly)1503 void identnames(vector<const char *> &names, bool builtinonly)
1504 {
1505     enumeratekt(*idents, const char *, name, ident, id,
1506     {
1507         if(!builtinonly || id.type != ID_ALIAS) names.add(name);
1508     });
1509 }
1510 
pushscontext(int newcontext)1511 void pushscontext(int newcontext)
1512 {
1513     contextstack.add(execcontext);
1514     execcontext = newcontext;
1515 }
1516 
popscontext()1517 int popscontext()
1518 {
1519     ASSERT(contextstack.length() > 0);
1520     int old = execcontext;
1521     execcontext = contextstack.pop();
1522 
1523     if(execcontext < old && old >= IEXC_MAPCFG) // clean up aliases created in the old (map cfg) context
1524     {
1525         int limitcontext = max(execcontext + 1, (int) IEXC_MAPCFG);  // don't clean up below IEXC_MAPCFG
1526         enumeratekt(*idents, const char *, name, ident, id,
1527         {
1528             if(id.type == ID_ALIAS && id.context >= limitcontext)
1529             {
1530                 while(id.stack && id.stack->context >= limitcontext)
1531                     popident(id);
1532                 if(id.context >= limitcontext)
1533                 {
1534                     if(id.action != id.executing) delete[] id.action;
1535                     idents->remove(name);
1536                 }
1537             }
1538         });
1539     }
1540     return execcontext;
1541 }
1542 
scriptcontext(int * context,char * idname)1543 void scriptcontext(int *context, char *idname)
1544 {
1545     if(contextsealed) return;
1546     ident *id = idents->access(idname);
1547     if(!id) return;
1548     int c = *context;
1549     if(c >= 0 && c < IEXC_NUM) id->context = c;
1550 }
1551 
isolatecontext(int * context)1552 void isolatecontext(int *context)
1553 {
1554     if(*context >= 0 && *context < IEXC_NUM && !contextsealed) contextisolated[*context] = true;
1555 }
1556 
sealcontexts()1557 void sealcontexts() { contextsealed = true; }
1558 
allowidentaccess(ident * id)1559 bool allowidentaccess(ident *id) // check if ident is allowed in current context
1560 {
1561     ASSERT(execcontext >= 0 && execcontext < IEXC_NUM);
1562     if(!id) return false;
1563     if(!contextisolated[execcontext]) return true; // only check if context is isolated
1564     return execcontext <= id->context;
1565 }
1566 
1567 COMMAND(scriptcontext, "is");
1568 COMMAND(isolatecontext, "i");
1569 COMMAND(sealcontexts, "");
1570 
1571 #ifndef STANDALONE
1572 COMMANDF(watchingdemo, "", () { intret(watchingdemo); });
1573 
systime()1574 void systime()
1575 {
1576     result(numtime());
1577 }
1578 
timestamp_()1579 void timestamp_()
1580 {
1581     result(timestring(true, "%Y %m %d %H %M %S"));
1582 }
1583 
datestring()1584 void datestring()
1585 {
1586     result(timestring(true, "%c"));
1587 }
1588 
timestring_()1589 void timestring_()
1590 {
1591     const char *res = timestring(true, "%H:%M:%S");
1592     result(res[0] == '0' ? res + 1 : res);
1593 }
1594 
millis_()1595 extern int millis_() { extern int totalmillis; return totalmillis; }
strlen_(char * s)1596 void strlen_(char *s) { intret(strlen(s)); }
1597 
substr_(char * fs,int * pa,int * len)1598 void substr_(char *fs, int *pa, int *len)
1599 {
1600     int ia = *pa;
1601     int ilen = *len;
1602     int fslen = (int)strlen(fs);
1603     if(ia<0) ia += fslen;
1604     if(ia>fslen || ia < 0 || ilen < 0) return;
1605 
1606     if(!ilen) ilen = fslen-ia;
1607     (fs+ia)[ilen] = '\0';
1608     result(fs+ia);
1609 }
1610 
strpos_(char * haystack,char * needle,int * occurence)1611 void strpos_(char *haystack, char *needle, int *occurence)
1612 {
1613     int position = -1;
1614     char *ptr = haystack;
1615 
1616     if(haystack && needle)
1617     for(int iocc = *occurence; iocc >= 0; iocc--)
1618     {
1619         ptr = strstr(ptr, needle);
1620         if (ptr)
1621         {
1622             position = ptr-haystack;
1623             ptr += strlen(needle);
1624         }
1625         else
1626         {
1627             position = -1;
1628             break;
1629         }
1630     }
1631     intret(position);
1632 }
1633 
l0(int * p,int * v)1634 void l0(int *p, int *v) { string f; string r; formatstring(f)("%%0%dd", *p); formatstring(r)(f, *v); result(r); }
1635 
getscrext()1636 void getscrext()
1637 {
1638     switch(screenshottype)
1639     {
1640         case 2: result(".png"); break;
1641         case 1: result(".jpg"); break;
1642         case 0:
1643         default: result(".bmp"); break;
1644     }
1645 }
1646 
1647 COMMANDF(millis, "", () { intret(millis_()); });
1648 COMMANDN(strlen, strlen_, "s");
1649 COMMANDN(substr, substr_, "sii");
1650 COMMANDN(strpos, strpos_, "ssi");
1651 COMMAND(l0, "ii");
1652 COMMAND(systime, "");
1653 COMMANDN(timestamp, timestamp_, "");
1654 COMMAND(datestring, "");
1655 COMMANDN(timestring, timestring_, "");
1656 COMMANDF(getmode, "i", (int *acr) { result(modestr(gamemode, *acr != 0)); });
1657 COMMAND(getscrext, "");
1658 
currentserver(int i)1659 const char *currentserver(int i) // [client version]
1660 {
1661     static string curSRVinfo;
1662     // using the curpeer directly we can get the info of our currently connected server
1663     string r;
1664     r[0] = '\0';
1665     extern ENetPeer *curpeer;
1666     if(curpeer)
1667     {
1668         switch(i)
1669         {
1670             case 1: // IP
1671             {
1672                 uchar *ip = (uchar *)&curpeer->address.host;
1673                 formatstring(r)("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
1674                 break;
1675             }
1676             case 2: // HOST
1677             {
1678                 char hn[1024];
1679                 formatstring(r)("%s", (enet_address_get_host(&curpeer->address, hn, sizeof(hn))==0) ? hn : "unknown");
1680                 break;
1681             }
1682             case 3: // PORT
1683             {
1684                 formatstring(r)("%d", curpeer->address.port);
1685                 break;
1686             }
1687             case 4: // STATE
1688             {
1689                 const char *statenames[] =
1690                 {
1691                     "disconnected",
1692                     "connecting",
1693                     "acknowledging connect",
1694                     "connection pending",
1695                     "connection succeeded",
1696                     "connected",
1697                     "disconnect later",
1698                     "disconnecting",
1699                     "acknowledging disconnect",
1700                     "zombie"
1701                 };
1702                 if(curpeer->state>=0 && curpeer->state<int(sizeof(statenames)/sizeof(statenames[0])))
1703                     copystring(r, statenames[curpeer->state]);
1704                 break; // 5 == Connected (compare ../enet/include/enet/enet.h +165)
1705             }
1706             // CAUTION: the following are only filled if the serverbrowser was used or the scoreboard shown
1707             // SERVERNAME
1708             case 5: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->name); break; }
1709             // DESCRIPTION (3)
1710             case 6: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->sdesc); break; }
1711             case 7: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->description); break; }
1712             // CAUTION: the following is only the last full-description _seen_ in the serverbrowser!
1713             case 8: { serverinfo *si = getconnectedserverinfo(); if(si) copystring(r, si->full); break; }
1714             // just IP & PORT as default response - always available, no lookup-delay either
1715              default:
1716             {
1717                 uchar *ip = (uchar *)&curpeer->address.host;
1718                 formatstring(r)("%d.%d.%d.%d %d", ip[0], ip[1], ip[2], ip[3], curpeer->address.port);
1719                 break;
1720             }
1721         }
1722     }
1723     copystring(curSRVinfo, r);
1724     return curSRVinfo;
1725 }
1726 
1727 COMMANDF(curserver, "i", (int *i) { result(currentserver(*i)); });
1728 #endif
1729 
debugargs(char ** args,int numargs)1730 void debugargs(char **args, int numargs)
1731 {
1732     printf("debugargs: ");
1733     loopi(numargs)
1734     {
1735         if(i) printf(", ");
1736         printf("\"%s\"", args[i]);
1737     }
1738     printf("\n");
1739 }
1740 
1741 COMMAND(debugargs, "v");
1742