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